/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-02 20:40:57 UTC
  • Revision ID: josh@9ix.org-20130302204057-yrra0a51zgtpq2v2
zoetrope 1.3.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
-- Extends:
6
6
--              <Class>
7
7
--
 
8
-- Event: onDraw
 
9
-- Called after all member sprites are drawn onscreen.
 
10
--
8
11
-- Event: onUpdate
9
12
-- Called once each frame, with the elapsed time since the last frame in seconds.
10
13
--
49
52
        -- sprites at their normal position, set both x and y to 1.
50
53
        translateScale = { x = 1, y = 1 },
51
54
 
 
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
 
52
62
        -- Method: add
53
63
        -- Adds a sprite to the group.
54
64
        --
96
106
                end
97
107
        end,
98
108
 
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
 
 
161
109
        -- Method: collide
162
110
        -- Collides all solid sprites in the group with another sprite or group.
163
111
        -- This calls the <Sprite.onCollide> event handlers on all sprites that
167
115
        -- This checks for collisions between the sprites that make up the group.
168
116
        --
169
117
        -- Arguments:
170
 
        --              ... - any number of <Sprite>s or <Group>s to collide with. If none
171
 
        --                        are specified, the group collides with itself.
 
118
        --              other - <Sprite> or <Group> to collide with, default self
172
119
        -- 
173
120
        -- Returns:
174
 
        --              nothing
 
121
        --              boolean, whether any collision was detected
175
122
        --
176
123
        -- See Also:
177
124
        --              <Sprite.collide>
178
125
 
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)
 
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
193
340
                end
194
341
        end,
195
342
 
313
460
                return false
314
461
        end,
315
462
 
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.
 
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.
324
472
        --
325
473
        -- Arguments:
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)
 
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.
330
477
        --
331
478
        -- Returns:
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)
 
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
458
506
                end
 
507
 
 
508
                return result
459
509
        end,
460
510
 
461
511
        -- passes startFrame events to member sprites
463
513
        startFrame = function (self, elapsed)
464
514
                if not self.active then return end
465
515
                elapsed = elapsed * self.timeScale
 
516
                if self.onStartFrame then self:onStartFrame(elapsed) end
466
517
                
467
518
                for _, spr in pairs(self.sprites) do
468
519
                        if spr.active then spr:startFrame(elapsed) end
469
520
                end
470
 
 
471
 
                if self.onStartFrame then self:onStartFrame(elapsed) end
472
521
        end,
473
522
 
474
523
        -- passes update events to member sprites
476
525
        update = function (self, elapsed)
477
526
                if not self.active then return end
478
527
                elapsed = elapsed * self.timeScale
 
528
                if self.onUpdate then self:onUpdate(elapsed) end
479
529
 
480
530
                for _, spr in pairs(self.sprites) do
481
531
                        if spr.active then spr:update(elapsed) end
482
532
                end
483
 
 
484
 
                if self.onUpdate then self:onUpdate(elapsed) end
485
533
        end,
486
534
 
487
535
        -- passes endFrame events to member sprites
489
537
        endFrame = function (self, elapsed)
490
538
                if not self.active then return end
491
539
                elapsed = elapsed * self.timeScale
 
540
                if self.onEndFrame then self:onEndFrame(elapsed) end
492
541
 
493
542
                for _, spr in pairs(self.sprites) do
494
543
                        if spr.active then spr:endFrame(elapsed) end
495
544
                end
496
 
 
497
 
                if self.onEndFrame then self:onEndFrame(elapsed) end
498
545
        end,
499
546
 
500
547
        -- Method: draw
542
589
                        end
543
590
                end
544
591
                        
 
592
                if self.onDraw then self:onDraw() end
 
593
 
545
594
                if self.effect then
546
595
                        if self.effectType == 'screen' then
547
596
                                love.graphics.setPixelEffect(self.effect)