/zoeplat

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

« back to all changes in this revision

Viewing changes to zoetrope/core/group.lua

  • Committer: Josh C
  • Date: 2013-03-05 22:06:58 UTC
  • Revision ID: josh@9ix.org-20130305220658-92yptjso9z57vzly
use zoetrope 288:a82a08660477 2013-02-23

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
-- Extends:
6
6
--              <Class>
7
7
--
8
 
--
9
8
-- Event: onUpdate
10
9
-- Called once each frame, with the elapsed time since the last frame in seconds.
11
10
--
52
49
        -- sprites at their normal position, set both x and y to 1.
53
50
        translateScale = { x = 1, y = 1 },
54
51
 
55
 
        -- Property: gridSize
56
 
        -- The size, in pixels, of the grid used for collision detection.
57
 
        -- This partitions off space so that collision checks only need to do real
58
 
        -- checks against a few sprites at a time. If you notice collision detection
59
 
        -- taking a long time, changing this number may help.
60
 
        gridSize = 50,
61
 
 
62
52
        -- Method: add
63
53
        -- Adds a sprite to the group.
64
54
        --
106
96
                end
107
97
        end,
108
98
 
 
99
        -- Method: moveToFront
 
100
        -- Moves a sprite in the group so that it is drawn on top
 
101
        -- of all other sprites in the group.
 
102
        --
 
103
        -- Arguments:
 
104
        --              sprite - <Sprite> to move, should already be a member of the group
 
105
        --
 
106
        -- Returns:
 
107
        --              nothing
 
108
 
 
109
        moveToFront = function (self, sprite)
 
110
                for i, spr in ipairs(self.sprites) do
 
111
                        if spr == sprite then
 
112
                                table.remove(self.sprites, i)
 
113
                                table.insert(self.sprites, sprite)
 
114
                                return
 
115
                        end
 
116
                end
 
117
 
 
118
                if STRICT then
 
119
                        print('Warning: asked to move sprite to front of group, but is not a member: ' .. sprite)
 
120
                end
 
121
        end,
 
122
 
 
123
        -- Method: moveToBack
 
124
        -- Moves a sprite in the group so that it is drawn below
 
125
        -- all other sprites in the group.
 
126
        --
 
127
        -- Arguments:
 
128
        --              sprite - <Sprite> to move, should already be a member of the group
 
129
        --
 
130
        -- Returns:
 
131
        --              nothing
 
132
        
 
133
        moveToBack = function (self, sprite)
 
134
                for i, spr in ipairs(self.sprites) do
 
135
                        if spr == sprite then
 
136
                                table.remove(self.sprites, i)
 
137
                                table.insert(self.sprites, 1, sprite)
 
138
                                return
 
139
                        end
 
140
                end
 
141
 
 
142
                if STRICT then
 
143
                        print('Asked to move sprite to back of group, but is not a member: ' .. sprite)
 
144
                end
 
145
        end,
 
146
 
 
147
        -- Method: sort
 
148
        -- Sorts members into a new draw sequence.
 
149
        --
 
150
        -- Arguments:
 
151
        --              func - function to perform the sort. This will receive two <Sprite>s as arguments;
 
152
        --                         the function must return whether the first should be drawn below the second.
 
153
        --
 
154
        -- Returns:
 
155
        --              nothing
 
156
 
 
157
        sort = function (self, func)
 
158
                table.sort(self.sprites, func)
 
159
        end,
 
160
 
109
161
        -- Method: collide
110
162
        -- Collides all solid sprites in the group with another sprite or group.
111
163
        -- This calls the <Sprite.onCollide> event handlers on all sprites that
115
167
        -- This checks for collisions between the sprites that make up the group.
116
168
        --
117
169
        -- Arguments:
118
 
        --              other - <Sprite> or <Group> to collide with, default self
 
170
        --              ... - any number of <Sprite>s or <Group>s to collide with. If none
 
171
        --                        are specified, the group collides with itself.
119
172
        -- 
120
173
        -- Returns:
121
 
        --              boolean, whether any collision was detected
 
174
        --              nothing
122
175
        --
123
176
        -- See Also:
124
177
        --              <Sprite.collide>
125
178
 
126
 
        collide = function (self, other)
127
 
                other = other or self
128
 
 
129
 
                if STRICT then
130
 
                        assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to collide non-group/sprite ' ..
131
 
                                   type(other))
132
 
                end
133
 
 
134
 
                if not self.solid or not other.solid then return false end
135
 
                local hit = false
136
 
 
137
 
                if other.sprites then
138
 
                        local grid = self:grid()
139
 
                        local gridSize = self.gridSize
140
 
 
141
 
                        for _, othSpr in pairs(other.sprites) do
142
 
                                local startX = math.floor(othSpr.x / gridSize)
143
 
                                local endX = math.floor((othSpr.x + othSpr.width) / gridSize)
144
 
                                local startY = math.floor(othSpr.y / gridSize)
145
 
                                local endY = math.floor((othSpr.y + othSpr.height) / gridSize)
146
 
 
147
 
                                for x = startX, endX do
148
 
                                        if grid[x] then
149
 
                                                for y = startY, endY do
150
 
                                                        if grid[x][y] then
151
 
                                                                for _, spr in pairs(grid[x][y]) do
152
 
                                                                        hit = spr:collide(othSpr) or hit
153
 
                                                                end
154
 
                                                        end
155
 
                                                end
156
 
                                        end
157
 
                                end
158
 
                        end
159
 
                else
160
 
                        for _, spr in pairs(self.sprites) do
161
 
                                hit = spr:collide(other) or hit
162
 
                        end
163
 
                end
164
 
 
165
 
                return hit
166
 
        end,
167
 
 
168
 
        -- Method: displace
169
 
        -- Displaces a sprite or group by all solid sprites in this group. This will *not* cause
170
 
        -- any onCollide event handlers to be called.
171
 
        --
172
 
        -- Arguments:
173
 
        --              other - <Sprite> or <Group> to collide with, default self
174
 
        --              xHint - force horizontal displacement in one direction, uses direction constants, optional
175
 
        --              yHint - force vertical displacement in one direction, uses direction constants, optional
176
 
        -- 
177
 
        -- Returns:
178
 
        --              nothing
179
 
        --
180
 
        -- See Also:
181
 
        --              <Sprite.displace>
182
 
 
183
 
        displace = function (self, other, xHint, yHint)
184
 
                other = other or self
185
 
 
186
 
                if STRICT then
187
 
                        assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to displace non-group/sprite ' ..
188
 
                                   type(other))
189
 
                end
190
 
 
191
 
                if not self.solid or not other.solid then return false end
192
 
 
193
 
                if other.sprites then
194
 
                        -- group displacing group
195
 
 
196
 
                        local grid = self:grid()
197
 
                        local gridSize = self.gridSize
198
 
 
199
 
                        for _, othSpr in pairs(other.sprites) do
200
 
                                local startX = math.floor(othSpr.x / gridSize)
201
 
                                local endX = math.floor((othSpr.x + othSpr.width) / gridSize)
202
 
                                local startY = math.floor(othSpr.y / gridSize)
203
 
                                local endY = math.floor((othSpr.y + othSpr.height) / gridSize)
204
 
                                local displacers = {}
205
 
 
206
 
                                for x = startX, endX do
207
 
                                        if grid[x] then
208
 
                                                for y = startY, endY do
209
 
                                                        if grid[x][y] then
210
 
                                                                for _, spr in pairs(grid[x][y]) do
211
 
                                                                        if spr ~= othSpr and spr.solid and spr:intersects(othSpr.x, othSpr.y, othSpr.width, othSpr.height) then
212
 
                                                                                table.insert(displacers, spr)
213
 
                                                                        end
214
 
                                                                end
215
 
                                                        end
216
 
                                                end
217
 
                                        end
218
 
                                end
219
 
 
220
 
                                -- see Map:subdisplace() for how this is done
221
 
 
222
 
                                if #displacers > 0 then
223
 
                                        local hit = true
224
 
                                        local loops = 0
225
 
 
226
 
                                        while hit and loops < 3 do
227
 
                                                hit = false
228
 
                                                loops = loops + 1
229
 
 
230
 
                                                local xVotes, yVotes = 0, 0
231
 
                                                local minChangeX, minChangeY, absMinChangeX, absMinChangeY
232
 
                                                local origX, origY = othSpr.x, othSpr.y
233
 
 
234
 
                                                for _, spr in pairs(displacers) do
235
 
                                                        spr:displace(othSpr)
236
 
                                                        local xChange = othSpr.x - origX
237
 
                                                        local yChange = othSpr.y - origY
238
 
 
239
 
                                                        if xChange ~= 0 then
240
 
                                                                xVotes = xVotes + math.abs(xChange)
241
 
                                                                hit = true
242
 
 
243
 
                                                                if not minChangeX or math.abs(xChange) < absMinChangeX then
244
 
                                                                        minChangeX = xChange
245
 
                                                                        absMinChangeX = math.abs(xChange)
246
 
                                                                end
247
 
                                                        end
248
 
 
249
 
                                                        if yChange ~= 0 then
250
 
                                                                yVotes = yVotes + math.abs(yChange)
251
 
                                                                hit = true
252
 
 
253
 
                                                                if not minChangeY or math.abs(yChange) < absMinChangeY then
254
 
                                                                        minChangeY = yChange
255
 
                                                                        absMinChangeY = math.abs(yChange)
256
 
                                                                end
257
 
                                                        end
258
 
 
259
 
                                                        -- restore sprite to original position
260
 
 
261
 
                                                        othSpr.x = origX
262
 
                                                        othSpr.y = origY
263
 
                                                end
264
 
 
265
 
                                                if hit then
266
 
                                                        if xVotes > 0 and xVotes > yVotes then
267
 
                                                                othSpr.x = othSpr.x + minChangeX
268
 
                                                        elseif yVotes > 0 then
269
 
                                                                othSpr.y = othSpr.y + minChangeY
270
 
                                                        end
271
 
                                                end
272
 
                                        end
273
 
                                end
274
 
                        end
275
 
                else
276
 
                        -- group displacing sprite
277
 
 
278
 
                        local displacers = {}
279
 
 
280
 
                        for _, spr in pairs(self.sprites) do
281
 
                                if spr ~= other and spr:intersects(other.x, other.y, other.width, other.height) then
282
 
                                        table.insert(displacers, spr)
283
 
                                end
284
 
                        end
285
 
 
286
 
                        -- see Map:subdisplace() for how this is done
287
 
 
288
 
                        if #displacers > 0 then
289
 
                                local hit = true
290
 
                                local loops = 0
291
 
 
292
 
                                while hit and loops < 3 do
293
 
                                        hit = false
294
 
                                        loops = loops + 1
295
 
 
296
 
                                        local xVotes, yVotes = 0, 0
297
 
                                        local minChangeX, minChangeY, absMinChangeX, absMinChangeY
298
 
                                        local origX, origY = other.x, other.y
299
 
 
300
 
                                        for _, spr in pairs(displacers) do
301
 
                                                spr:displace(other)
302
 
                                                local xChange = other.x - origX
303
 
                                                local yChange = other.y - origY
304
 
 
305
 
                                                if xChange ~= 0 then
306
 
                                                        xVotes = xVotes + math.abs(xChange)
307
 
                                                        hit = true
308
 
 
309
 
                                                        if not minChangeX or math.abs(xChange) < absMinChangeX then
310
 
                                                                minChangeX = xChange
311
 
                                                                absMinChangeX = math.abs(xChange)
312
 
                                                        end
313
 
                                                end
314
 
 
315
 
                                                if yChange ~= 0 then
316
 
                                                        yVotes = yVotes + math.abs(yChange)
317
 
                                                        hit = true
318
 
 
319
 
                                                        if not minChangeY or math.abs(yChange) < absMinChangeY then
320
 
                                                                minChangeY = yChange
321
 
                                                                absMinChangeY = math.abs(yChange)
322
 
                                                        end
323
 
                                                end
324
 
 
325
 
                                                -- restore sprite to original position
326
 
 
327
 
                                                other.x = origX
328
 
                                                other.y = origY
329
 
                                        end
330
 
 
331
 
                                        if hit then
332
 
                                                if xVotes > 0 and xVotes > yVotes then
333
 
                                                        other.x = other.x + minChangeX
334
 
                                                elseif yVotes > 0 then
335
 
                                                        other.y = other.y + minChangeY
336
 
                                                end
337
 
                                        end
338
 
                                end
339
 
                        end
 
179
        collide = function (self, ...)
 
180
                local list = {...}
 
181
 
 
182
                if #list > 0 then
 
183
                        if STRICT then
 
184
                                for _, other in pairs(list) do
 
185
                                        assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to collide non-group/sprite ' ..
 
186
                                                   type(other))
 
187
                                end
 
188
                        end
 
189
 
 
190
                        Collision:check(self, ...)
 
191
                else
 
192
                        Collision:check(self, self)
340
193
                end
341
194
        end,
342
195
 
460
313
                return false
461
314
        end,
462
315
 
463
 
        -- Method: grid
464
 
        -- Creates a table indexed by x and y dimensions, with each
465
 
        -- cell a table of sprites that touch this grid element. For
466
 
        -- example, with a grid size of 50, a sprite at (10, 10) that 
467
 
        -- is 50 pixels square would be in the grid at [0][0], [1][0],
468
 
        -- [0][1], and [1][1].
469
 
        --
470
 
        -- This can be used to speed up work that involves checking
471
 
        -- for sprites near each other, e.g. collision detection.
 
316
        -- Method: loadLayers
 
317
        -- Loads layers from a Lua source file (as generated by Tiled -- http://mapeditor.org).
 
318
        -- Each layer is created as a <Group> belonging to this one and added to preserve its
 
319
        -- ordering. Tile layers are created as <Map> instances; object layers will try to create
 
320
        -- instances of a class named by the object's name property. If no class exists by
 
321
        -- this name, or the object has no name property, a gray fill will be created instead,
 
322
        -- as a placeholder. If the object has a property named _the, then this will set
 
323
        -- the.[whatever] to it.
472
324
        --
473
325
        -- Arguments:
474
 
        --              existing - existing grid table to add sprites into,
475
 
        --                                 optional. Anything you pass must have
476
 
        --                                 used the same size as the current call.
 
326
        --              file - filename to load
 
327
        --              tileClass - class to create tiles in tile layers with; constructor
 
328
        --                                  will be called with properties: image, width,
 
329
        --                                  height, imageOffset (with x and y sub-properties)
477
330
        --
478
331
        -- Returns:
479
 
        --              table
480
 
 
481
 
        grid = function (self, existing)
482
 
                local result = existing or {}
483
 
                local size = self.gridSize
484
 
 
485
 
                for _, spr in pairs(self.sprites) do
486
 
                        if spr.sprites then
487
 
                                local oldSize = spr.gridSize
488
 
                                spr.gridSize = self.gridSize
489
 
                                result = spr:grid(result)
490
 
                                spr.gridSize = oldSize
491
 
                        else
492
 
                                local startX = math.floor(spr.x / size)
493
 
                                local endX = math.floor((spr.x + spr.width) / size)
494
 
                                local startY = math.floor(spr.y / size)
495
 
                                local endY = math.floor((spr.y + spr.height) / size)
496
 
 
497
 
                                for x = startX, endX do
498
 
                                        if not result[x] then result[x] = {} end
499
 
 
500
 
                                        for y = startY, endY do
501
 
                                                if not result[x][y] then result[x][y] = {} end
502
 
                                                table.insert(result[x][y], spr)
503
 
                                        end
504
 
                                end
505
 
                        end
 
332
        --              nothing
 
333
 
 
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)
506
458
                end
507
 
 
508
 
                return result
509
459
        end,
510
460
 
511
461
        -- passes startFrame events to member sprites
513
463
        startFrame = function (self, elapsed)
514
464
                if not self.active then return end
515
465
                elapsed = elapsed * self.timeScale
 
466
                
 
467
                for _, spr in pairs(self.sprites) do
 
468
                        if spr.active then spr:startFrame(elapsed) end
 
469
                end
 
470
 
516
471
                if self.onStartFrame then self:onStartFrame(elapsed) end
517
 
                
518
 
                for _, spr in pairs(self.sprites) do
519
 
                        if spr.active then spr:startFrame(elapsed) end
520
 
                end
521
472
        end,
522
473
 
523
474
        -- passes update events to member sprites
525
476
        update = function (self, elapsed)
526
477
                if not self.active then return end
527
478
                elapsed = elapsed * self.timeScale
 
479
 
 
480
                for _, spr in pairs(self.sprites) do
 
481
                        if spr.active then spr:update(elapsed) end
 
482
                end
 
483
 
528
484
                if self.onUpdate then self:onUpdate(elapsed) end
529
 
 
530
 
                for _, spr in pairs(self.sprites) do
531
 
                        if spr.active then spr:update(elapsed) end
532
 
                end
533
485
        end,
534
486
 
535
487
        -- passes endFrame events to member sprites
537
489
        endFrame = function (self, elapsed)
538
490
                if not self.active then return end
539
491
                elapsed = elapsed * self.timeScale
 
492
 
 
493
                for _, spr in pairs(self.sprites) do
 
494
                        if spr.active then spr:endFrame(elapsed) end
 
495
                end
 
496
 
540
497
                if self.onEndFrame then self:onEndFrame(elapsed) end
541
 
 
542
 
                for _, spr in pairs(self.sprites) do
543
 
                        if spr.active then spr:endFrame(elapsed) end
544
 
                end
545
498
        end,
546
499
 
547
500
        -- Method: draw
589
542
                        end
590
543
                end
591
544
                        
592
 
                if self.onDraw then self:onDraw() end
593
 
 
594
545
                if self.effect then
595
546
                        if self.effectType == 'screen' then
596
547
                                love.graphics.setPixelEffect(self.effect)