/ld27

To get this branch, use:
bzr branch http://9ix.org/bzr/ld27

« back to all changes in this revision

Viewing changes to zoetrope/core/group.lua

  • Committer: Josh C
  • Date: 2013-08-25 21:05:20 UTC
  • Revision ID: josh@9ix.org-20130825210520-2of9iq2uemncp336
overhaul collision and physics to fix an annoying collision resolution 
bug

Show diffs side-by-side

added added

removed removed

Lines of Context:
8
8
-- Event: onUpdate
9
9
-- Called once each frame, with the elapsed time since the last frame in seconds.
10
10
--
 
11
-- Event: onBeginFrame
11
12
-- Called once each frame like onUpdate, but guaranteed to fire before any others' onUpdate handlers.
12
13
--
13
14
-- Event: onEndFrame
31
31
        solid = true,
32
32
 
33
33
        -- Property: sprites
34
 
        -- A table of member sprites, in drawing order. You can iterate over this
35
 
        -- table with <members()>.
 
34
        -- A table of member sprites, in drawing order.
36
35
        sprites = {},
37
36
 
38
37
        -- Property: timeScale
43
42
        -- This table's x and y properties shift member sprites' positions when drawn.
44
43
        -- To draw sprites at their normal position, set both x and y to 0.
45
44
        translate = { x = 0, y = 0 },
46
 
 
 
45
        
47
46
        -- Property: translateScale
48
47
        -- This table's x and y properties multiply member sprites'
49
48
        -- positions, which you can use to simulate parallax scrolling. To draw
50
49
        -- sprites at their normal position, set both x and y to 1.
51
50
        translateScale = { x = 1, y = 1 },
52
51
 
53
 
  -- an actual love.graphics.translate call
54
 
  realTranslate = { x = 0, y = 0 },
55
 
 
56
 
        -- Property: scale
57
 
        -- Zooms in or out all drawing operations in the group, centering them with
58
 
        -- respect to the <origin> property.
59
 
        scale = 1,
60
 
 
61
 
        -- Property: distort
62
 
        -- Distorts the group's scale, similar to <Sprite.distort>.
63
 
        distort = { x = 1, y = 1 },
64
 
 
65
 
        -- Property: origin
66
 
        -- Sets the center point for all scaling operations.
67
 
        origin = { x = 0, y = 0 },
68
 
 
69
52
        -- Method: add
70
53
        -- Adds a sprite to the group.
71
54
        --
73
56
        --              sprite - <Sprite> to add
74
57
        --
75
58
        -- Returns:
76
 
        --              the sprite added, so you can write things like
77
 
        --              self.player = self:add(Player:new())
 
59
        --              nothing
78
60
 
79
61
        add = function (self, sprite)
80
62
                assert(sprite, 'asked to add nil to a group')
81
63
                assert(sprite ~= self, "can't add a group to itself")
82
 
 
 
64
        
83
65
                if STRICT and self:contains(sprite) then
84
66
                        local info = debug.getinfo(2, 'Sl')
85
67
                        print('Warning: adding a sprite to a group it already belongs to (' ..
87
69
                end
88
70
 
89
71
                table.insert(self.sprites, sprite)
90
 
                return sprite
91
72
        end,
92
73
 
93
74
        -- Method: remove
94
75
        -- Removes a sprite from the group. If the sprite is
95
76
        -- not in the group, this does nothing.
96
 
        --
 
77
        -- 
97
78
        -- Arguments:
98
79
        --              sprite - <Sprite> to remove
99
 
        --
 
80
        -- 
100
81
        -- Returns:
101
82
        --              nothing
102
83
 
103
84
        remove = function (self, sprite)
104
 
                for i, spr in self:members() do
 
85
                for i, spr in ipairs(self.sprites) do
105
86
                        if spr == sprite then
106
87
                                table.remove(self.sprites, i)
107
88
                                return
108
89
                        end
109
90
                end
110
 
 
 
91
                
111
92
                if STRICT then
112
93
                        local info = debug.getinfo(2, 'Sl')
113
94
                        print('Warning: asked to remove a sprite from a group it was not a member of (' ..
126
107
        --              nothing
127
108
 
128
109
        moveToFront = function (self, sprite)
129
 
                for i, spr in self:members() do
 
110
                for i, spr in ipairs(self.sprites) do
130
111
                        if spr == sprite then
131
112
                                table.remove(self.sprites, i)
132
113
                                table.insert(self.sprites, sprite)
148
129
        --
149
130
        -- Returns:
150
131
        --              nothing
151
 
 
 
132
        
152
133
        moveToBack = function (self, sprite)
153
 
                for i, spr in self:members() do
 
134
                for i, spr in ipairs(self.sprites) do
154
135
                        if spr == sprite then
155
136
                                table.remove(self.sprites, i)
156
137
                                table.insert(self.sprites, 1, sprite)
177
158
                table.sort(self.sprites, func)
178
159
        end,
179
160
 
180
 
        -- Method: members
181
 
        -- A convenience method that iterates over all member sprites.
182
 
        --
183
 
        -- Arguments:
184
 
        --              none
185
 
        --
186
 
        -- Returns:
187
 
        --              order, sprite
188
 
 
189
 
        members = function (self)
190
 
                return ipairs(self.sprites)
191
 
        end,
192
 
 
193
161
        -- Method: collide
194
162
        -- Collides all solid sprites in the group with another sprite or group.
195
163
        -- This calls the <Sprite.onCollide> event handlers on all sprites that
201
169
        -- Arguments:
202
170
        --              ... - any number of <Sprite>s or <Group>s to collide with. If none
203
171
        --                        are specified, the group collides with itself.
204
 
        --
 
172
        -- 
205
173
        -- Returns:
206
174
        --              nothing
207
175
        --
247
215
        setEffect = function (self, filename, effectType)
248
216
                effectType = effectType or 'screen'
249
217
 
250
 
                if love.graphics.isSupported('shader') and
 
218
                if love.graphics.isSupported('pixeleffect') and
251
219
                   (effectType == 'sprite' or love.graphics.isSupported('canvas'))then
252
220
                        if filename then
253
 
                                self.effect = love.graphics.newShader(Cached:text(filename))
 
221
                                self.effect = love.graphics.newPixelEffect(Cached:text(filename))
254
222
                                self.effectType = effectType
255
223
                        else
256
224
                                self.effect = nil
264
232
 
265
233
        -- Method: count
266
234
        -- Counts how many sprites are in this group.
267
 
        --
 
235
        -- 
268
236
        -- Arguments:
269
237
        --              subgroups - include subgroups?
270
 
        --
 
238
        -- 
271
239
        -- Returns:
272
240
        --              integer count
273
241
 
275
243
                if subgroups then
276
244
                        local count = 0
277
245
 
278
 
                        for _, spr in self:members() do
 
246
                        for _, spr in pairs(self.sprites) do
279
247
                                if spr:instanceOf(Group) then
280
248
                                        count = count + spr:count(true)
281
249
                                else
334
302
        contains = function (self, sprite, recurse)
335
303
                if recurse ~= false then recurse = true end
336
304
 
337
 
                for _, spr in self:members() do
 
305
                for _, spr in pairs(self.sprites) do
338
306
                        if spr == sprite then return true end
339
307
 
340
308
                        if recurse and spr:instanceOf(Group) and spr:contains(sprite) then
363
331
        -- Returns:
364
332
        --              nothing
365
333
 
366
 
    loadLayers = function (self, file, tileClass)
367
 
        local ok, data = pcall(loadstring(Cached:text(file)))
368
 
        local _, _, directory = string.find(file, '^(.*[/\\])')
369
 
        directory = directory or ''
370
 
 
371
 
        if ok then
372
 
            -- store tile properties by gid
373
 
 
374
 
            local tileProtos = {}
375
 
 
376
 
            for _, tileset in pairs(data.tilesets) do
377
 
                for _, tile in pairs(tileset.tiles) do
378
 
                    local id = tileset.firstgid + tile.id
379
 
 
380
 
                    for key, value in pairs(tile.properties) do
381
 
                        tile.properties[key] = tovalue(value)
382
 
                    end
383
 
 
384
 
                    tileProtos[id] = tile
385
 
                    tileProtos[id].width = tileset.tilewidth
386
 
                    tileProtos[id].height = tileset.tileheight
387
 
                end
388
 
            end
389
 
 
390
 
            for _, layer in pairs(data.layers) do
391
 
                if self.prototype[layer.name] then
392
 
                    error('The class you are loading layers into reserves the ' .. layer.name .. ' property for its own use; you cannot load a layer with that name')
393
 
                end
394
 
 
395
 
                if STRICT and self[layer.name] then
396
 
                    local info = debug.getinfo(2, 'Sl')
397
 
                    print('Warning: a property named ' .. layer.name .. ' already exists in this group (' ..
398
 
                          info.short_src .. ', line ' .. info.currentline .. ')')
399
 
                end
400
 
 
401
 
                if layer.type == 'tilelayer' then
402
 
                    local map = Map:new{ spriteWidth = data.tilewidth, spriteHeight = data.tileheight }
403
 
                    map:empty(layer.width, layer.height)
404
 
 
405
 
                    -- load tiles
406
 
 
407
 
                    for _, tiles in pairs(data.tilesets) do
408
 
                        map:loadTiles(directory .. tiles.image, tileClass or Tile, tiles.firstgid)
409
 
 
410
 
                        -- and mix in properties where applicable
411
 
 
412
 
                        for id, tile in pairs(tileProtos) do
413
 
                            if map.sprites[id] then
414
 
                                map.sprites[id]:mixin(tile.properties)
415
 
                            end
416
 
                        end
417
 
                    end
418
 
 
419
 
                    -- load tile data
420
 
 
421
 
                    local x = 1
422
 
                    local y = 1
423
 
 
424
 
                    for _, val in ipairs(layer.data) do
425
 
                        map.map[x][y] = val
426
 
                        x = x + 1
427
 
 
428
 
                        if x > layer.width then
429
 
                            x = 1
430
 
                            y = y + 1
431
 
                        end
432
 
                    end
433
 
 
434
 
                    self[layer.name] = map
435
 
                    self:add(map)
436
 
                elseif layer.type == 'objectgroup' then
437
 
                    local group = Group:new()
438
 
 
439
 
                    for _, obj in pairs(layer.objects) do
440
 
                        -- roll in tile properties if based on a tile
441
 
 
442
 
                        if obj.gid and tileProtos[obj.gid] then
443
 
                            local tile = tileProtos[obj.gid]
444
 
 
445
 
                            obj.name = tile.properties.name
446
 
                            obj.width = tile.width
447
 
                            obj.height = tile.height
448
 
 
449
 
                            for key, value in pairs(tile.properties) do
450
 
                                obj.properties[key] = tovalue(value)
451
 
                            end
452
 
 
453
 
                            -- Tiled tile-based objects measure their y
454
 
                            -- position at their lower-left corner, instead
455
 
                            -- of their upper-left corner as usual
456
 
 
457
 
                            obj.y = obj.y - obj.height
458
 
                        end
459
 
 
460
 
                        -- create a new object if the class does exist
461
 
 
462
 
                        local spr
463
 
 
464
 
                        if _G[obj.name] then
465
 
                            obj.properties.x = obj.x
466
 
                            obj.properties.y = obj.y
467
 
                            obj.properties.width = obj.width
468
 
                            obj.properties.height = obj.height
469
 
 
470
 
                            spr = _G[obj.name]:new(obj.properties)
471
 
                        else
472
 
                            spr = Fill:new{ x = obj.x, y = obj.y, width = obj.width, height = obj.height, fill = { 128, 128, 128 } }
473
 
                        end
474
 
 
475
 
                        if obj.properties._the then
476
 
                            the[obj.properties._the] = spr
477
 
                        end
478
 
 
479
 
                        group:add(spr)
480
 
                    end
481
 
 
482
 
                    self[layer.name] = group
483
 
                    self:add(group)
484
 
                else
485
 
                    error("don't know how to create a " .. layer.type .. " layer from file data")
486
 
                end
487
 
            end
488
 
        else
489
 
            error('could not load layers from file: ' .. data)
490
 
        end
491
 
    end,
492
 
 
 
334
        loadLayers = function (self, file, tileClass)
 
335
                local ok, data = pcall(loadstring(Cached:text(file)))
 
336
                local _, _, directory = string.find(file, '^(.*[/\\])')
 
337
                directory = directory or ''
 
338
 
 
339
                if ok then
 
340
                        -- store tile properties by gid
 
341
                        
 
342
                        local tileProtos = {}
 
343
 
 
344
                        for _, tileset in pairs(data.tilesets) do
 
345
                                for _, tile in pairs(tileset.tiles) do
 
346
                                        local id = tileset.firstgid + tile.id
 
347
                                        
 
348
                                        for key, value in pairs(tile.properties) do
 
349
                                                tile.properties[key] = tovalue(value)
 
350
                                        end
 
351
 
 
352
                                        tileProtos[id] = tile
 
353
                                        tileProtos[id].width = tileset.tilewidth
 
354
                                        tileProtos[id].height = tileset.tileheight
 
355
                                end
 
356
                        end
 
357
 
 
358
                        for _, layer in pairs(data.layers) do
 
359
                                if self.prototype[layer.name] then
 
360
                                        error('The class you are loading layers into reserves the ' .. layer.name .. ' property for its own use; you cannot load a layer with that name')
 
361
                                end
 
362
 
 
363
                                if STRICT and self[layer.name] then
 
364
                                        local info = debug.getinfo(2, 'Sl')
 
365
                                        print('Warning: a property named ' .. layer.name .. ' already exists in this group (' ..
 
366
                                                  info.short_src .. ', line ' .. info.currentline .. ')')
 
367
                                end
 
368
 
 
369
                                if layer.type == 'tilelayer' then
 
370
                                        local map = Map:new{ spriteWidth = data.tilewidth, spriteHeight = data.tileheight }
 
371
                                        map:empty(layer.width, layer.height)
 
372
 
 
373
                                        -- load tiles
 
374
 
 
375
                                        for _, tiles in pairs(data.tilesets) do
 
376
                                                map:loadTiles(directory .. tiles.image, tileClass or Tile, tiles.firstgid)
 
377
 
 
378
                                                -- and mix in properties where applicable
 
379
 
 
380
                                                for id, tile in pairs(tileProtos) do
 
381
                                                        if map.sprites[id] then
 
382
                                                                map.sprites[id]:mixin(tile.properties)
 
383
                                                        end
 
384
                                                end
 
385
                                        end
 
386
 
 
387
                                        -- load tile data
 
388
 
 
389
                                        local x = 1
 
390
                                        local y = 1
 
391
 
 
392
                                        for _, val in ipairs(layer.data) do
 
393
                                                map.map[x][y] = val
 
394
                                                x = x + 1
 
395
 
 
396
                                                if x > layer.width then
 
397
                                                        x = 1
 
398
                                                        y = y + 1
 
399
                                                end
 
400
                                        end
 
401
 
 
402
                                        self[layer.name] = map
 
403
                                        self:add(map)
 
404
                                elseif layer.type == 'objectgroup' then
 
405
                                        local group = Group:new()
 
406
 
 
407
                                        for _, obj in pairs(layer.objects) do
 
408
                                                -- roll in tile properties if based on a tile
 
409
 
 
410
                                                if obj.gid and tileProtos[obj.gid] then
 
411
                                                        local tile = tileProtos[obj.gid]
 
412
 
 
413
                                                        obj.name = tile.properties.name
 
414
                                                        obj.width = tile.width
 
415
                                                        obj.height = tile.height
 
416
 
 
417
                                                        for key, value in pairs(tile.properties) do
 
418
                                                                obj.properties[key] = tovalue(value)
 
419
                                                        end
 
420
 
 
421
                                                        -- Tiled tile-based objects measure their y
 
422
                                                        -- position at their lower-left corner, instead
 
423
                                                        -- of their upper-left corner as usual
 
424
 
 
425
                                                        obj.y = obj.y - obj.height
 
426
                                                end
 
427
 
 
428
                                                -- create a new object if the class does exist
 
429
 
 
430
                                                local spr
 
431
 
 
432
                                                if _G[obj.name] then
 
433
                                                        obj.properties.x = obj.x
 
434
                                                        obj.properties.y = obj.y
 
435
                                                        obj.properties.width = obj.width
 
436
                                                        obj.properties.height = obj.height
 
437
 
 
438
                                                        spr = _G[obj.name]:new(obj.properties)
 
439
                                                else
 
440
                                                        spr = Fill:new{ x = obj.x, y = obj.y, width = obj.width, height = obj.height, fill = { 128, 128, 128 } }
 
441
                                                end
 
442
 
 
443
                                                if obj.properties._the then
 
444
                                                        the[obj.properties._the] = spr
 
445
                                                end
 
446
 
 
447
                                                group:add(spr)
 
448
                                        end
 
449
 
 
450
                                        self[layer.name] = group
 
451
                                        self:add(group)
 
452
                                else
 
453
                                        error("don't know how to create a " .. layer.type .. " layer from file data")
 
454
                                end
 
455
                        end
 
456
                else
 
457
                        error('could not load layers from file: ' .. data)
 
458
                end
 
459
        end,
493
460
 
494
461
        -- passes startFrame events to member sprites
495
462
 
496
463
        startFrame = function (self, elapsed)
497
464
                if not self.active then return end
498
465
                elapsed = elapsed * self.timeScale
499
 
 
500
 
                for _, spr in self:members() do
 
466
                
 
467
                for _, spr in pairs(self.sprites) do
501
468
                        if spr.active then spr:startFrame(elapsed) end
502
469
                end
503
470
 
510
477
                if not self.active then return end
511
478
                elapsed = elapsed * self.timeScale
512
479
 
513
 
                for _, spr in self:members() do
 
480
                for _, spr in pairs(self.sprites) do
514
481
                        if spr.active then spr:update(elapsed) end
515
482
                end
516
483
 
523
490
                if not self.active then return end
524
491
                elapsed = elapsed * self.timeScale
525
492
 
526
 
                for _, spr in self:members() do
 
493
                for _, spr in pairs(self.sprites) do
527
494
                        if spr.active then spr:endFrame(elapsed) end
528
495
                end
529
496
 
537
504
        --              x - x offset in pixels
538
505
        --              y - y offset in pixels
539
506
 
540
 
        draw = function (self, x, y, yield)
 
507
        draw = function (self, x, y)
541
508
                if not self.visible then return end
542
509
                x = x or self.translate.x
543
510
                y = y or self.translate.y
544
 
 
 
511
                
545
512
                local scrollX = x * self.translateScale.x
546
513
                local scrollY = y * self.translateScale.y
547
514
                local appWidth = the.app.width
548
515
                local appHeight = the.app.height
549
 
                local scaled = self.scale ~= 1 or self.distort.x ~= 1 or self.distort.y ~= 1 or self.realTranslate.x ~= 0 or self.realTranslate.y ~= 0
550
516
 
551
517
                if self.effect then
552
518
                        if self.effectType == 'screen' then
554
520
                                self._canvas:clear()
555
521
                                love.graphics.setCanvas(self._canvas)
556
522
                        elseif self.effectType == 'sprite' then
557
 
                                love.graphics.setShader(self.effect)
 
523
                                love.graphics.setPixelEffect(self.effect)
558
524
                        end
559
525
                end
560
 
 
561
 
                if scaled then
562
 
                        local scaleX = self.scale * self.distort.x
563
 
                        local scaleY = self.scale * self.distort.y
564
 
 
565
 
                        love.graphics.push()
566
 
 
567
 
      love.graphics.translate(self.realTranslate.x, self.realTranslate.y)
568
 
 
569
 
                        love.graphics.translate(self.origin.x, self.origin.y)
570
 
                        love.graphics.scale(scaleX, scaleY)
571
 
                        love.graphics.translate(- self.origin.x, - self.origin.y)
572
 
                end
573
 
 
574
 
                for _, spr in self:members() do
 
526
                
 
527
                for _, spr in pairs(self.sprites) do    
575
528
                        if spr.visible then
576
529
                                if spr.translate then
577
530
                                        spr:draw(spr.translate.x + scrollX, spr.translate.y + scrollY)
588
541
                                end
589
542
                        end
590
543
                end
591
 
 
592
 
    if yield then yield(self, x, y) end
593
 
 
594
 
                if scaled then
595
 
                        love.graphics.pop()
596
 
                end
597
 
 
 
544
                        
598
545
                if self.effect then
599
546
                        if self.effectType == 'screen' then
600
 
                                love.graphics.setShader(self.effect)
 
547
                                love.graphics.setPixelEffect(self.effect)
601
548
                                love.graphics.setCanvas()
602
549
                                love.graphics.draw(self._canvas)
603
550
                        end
604
551
 
605
 
                        love.graphics.setShader()
 
552
                        love.graphics.setPixelEffect()
606
553
                end
607
554
        end,
608
555