/zoeplat

To get this branch, use:
bzr branch http://9ix.org/bzr/zoeplat
2 by Josh C
basic tiles, map, player, movement
1
STRICT = true
2
DEBUG = true
3
4
require 'zoetrope'
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
5
__ = require 'underscore'
12 by Josh C
only jump when you're on the ground
6
--inspect = require 'inspect'
24 by Josh C
profiling and analysis
7
require 'pepperprof'
2 by Josh C
basic tiles, map, player, movement
8
26 by Josh C
don't go off the edge
9
util = {
10
   dim = function(dir)
11
      if dir == 'x' then
12
         return 'width'
13
      elseif dir == 'y' then
14
         return 'height'
15
      else
16
         print 'dir ??'
17
      end
18
   end
19
}
20
10 by Josh C
make player an animation
21
Player = Animation:extend {
2 by Josh C
basic tiles, map, player, movement
22
   image = 'data/player.png',
10 by Josh C
make player an animation
23
   height = 32,
24
   width = 32,
25
   sequences = {
26
      stand = { frames = { 1 }, fps = 1 },
27
      walk = { frames = { 2, 3 }, fps = 5 },
22 by Josh C
climbing animation
28
      jump = { frames = { 4 }, fps = 1 },
29
      climbLeft = { frames = { 5, 6 }, fps = 5 },
30
      climbRight = { frames = { 7, 8 }, fps = 5 }
10 by Josh C
make player an animation
31
   },
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
32
   collisions = {},
33
   onWall = false,
23 by Josh C
wall jump
34
   leftWallAt = 0,
3 by Josh C
jump
35
   onNew = function (self)
36
              self.velocity.y = 0
5 by Josh C
use built-in maxVelocity system
37
              self.maxVelocity.y = 400
3 by Josh C
jump
38
           end,
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
39
   doPhysics = function (self, dir, elapsed)
40
                  local vel = self.velocity
41
                  local acc = self.acceleration
42
                  local drag = self.drag
43
                  local minVel = self.minVelocity
44
                  local maxVel = self.maxVelocity
45
46
                  -- check existence of properties
47
48
                  if STRICT then
49
                     assert(vel, 'active sprite has no velocity property')
50
                     assert(acc, 'active sprite has no acceleration property')
51
                     assert(drag, 'active sprite has no drag property')
52
                     assert(minVel, 'active sprite has no minVelocity property')
53
                     assert(maxVel, 'active sprite has no maxVelocity property')
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
54
                     assert(__.include({'x','y','rotation'}, dir), 'direction should be x, y, or rotation')
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
55
                  end
56
57
                  vel.x = vel.x or 0
58
                  vel.y = vel.y or 0
59
                  vel.rotation = vel.rotation or 0
60
61
                  -- physics
62
63
                  if acc[dir] and acc[dir] ~= 0 then
64
                     vel[dir] = vel[dir] + acc[dir] * elapsed
65
                  else
66
                     if drag[dir] then
67
                        if vel[dir] > 0 then
68
                           vel[dir] = vel[dir] - drag[dir] * elapsed
69
                           if vel[dir] < 0 then vel[dir] = 0 end
70
                        elseif vel[dir] < 0 then
71
                           vel[dir] = vel[dir] + drag[dir] * elapsed
72
                           if vel[dir] > 0 then vel[dir] = 0 end
73
                        end
74
                     end
75
                  end
76
77
                  if minVel[dir] and vel[dir] < minVel[dir] then vel[dir] = minVel[dir] end
78
                  if maxVel[dir] and vel[dir] > maxVel[dir] then vel[dir] = maxVel[dir] end
79
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
80
                  -- ugly hack for falling through floor on really slow frames
81
                  if math.abs(vel[dir] * elapsed) > 32 then
82
                     print('skip')
83
                     return
84
                  end
85
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
86
                  if vel[dir] ~= 0 then self[dir] = self[dir] + vel[dir] * elapsed end
26 by Josh C
don't go off the edge
87
88
                  if self[dir] < 0 then self[dir] = 0 end
89
                  local edge = the.view.map[util.dim(dir)] -
90
                               the.player[util.dim(dir)]
91
                  -- TODO: take map position into account
92
                  if self[dir] > edge then self[dir] = edge end
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
93
               end,
4 by Josh C
fix jitter caused by focus shift happening in the wrong order. Looks
94
   onStartFrame = function (self)
95
                     -- this is all in startframe so it happens before
96
                     -- physics calc at beginning of update
3 by Josh C
jump
97
12 by Josh C
only jump when you're on the ground
98
                     -- jumping/falling updates could go in EndFrame...
99
                     self.falling = self.velocity.y > 0
100
                     if self.falling then self.jumping = false end
101
                     --print(self.jumping, self.falling)
102
19 by Josh C
climb walls
103
                     if (not self.onGround) and (not self.onWall) then
13 by Josh C
reapply jump animation after Y collision. (there's a frame of no
104
                        self:play('jump')
105
                     end
106
6 by Josh C
whitespace cleanup
107
                     self.acceleration.y = 800
108
19 by Josh C
climb walls
109
                     if self.onWall then
110
                        self.acceleration.y = 0
111
22 by Josh C
climbing animation
112
                        if self.onWall == 'right' then
113
                           self:play('climbRight')
114
                        elseif self.onWall == 'left' then
115
                           self:play('climbLeft')
116
                        end
117
19 by Josh C
climb walls
118
                        if the.keys:pressed('up') then
119
                           self.velocity.y = -200
120
                        elseif the.keys:pressed('down') then
121
                           self.velocity.y = 200
122
                        else
123
                           self.velocity.y = 0
22 by Josh C
climbing animation
124
                           self:freeze(self.sequences[self.currentName].frames[1])
19 by Josh C
climb walls
125
                        end
126
                     end
127
6 by Josh C
whitespace cleanup
128
                     if the.keys:pressed('left') then
129
                        self.velocity.x = -200
15 by Josh C
more reliable onGround calc
130
                        if self.onGround then self:play('walk') end
23 by Josh C
wall jump
131
                        if self.onWall == 'right' then
132
                           self.onWall = false
133
                           self.leftWallAt = love.timer.getTime()
134
                        end
6 by Josh C
whitespace cleanup
135
                     elseif the.keys:pressed('right') then
136
                        self.velocity.x = 200
15 by Josh C
more reliable onGround calc
137
                        if self.onGround then self:play('walk') end
23 by Josh C
wall jump
138
                        if self.onWall == 'left' then
139
                           self.onWall = false
140
                           self.leftWallAt = love.timer.getTime()
141
                        end
10 by Josh C
make player an animation
142
                     else
21 by Josh C
really easy version of knowing when we reached the top of a wall.
143
                        if not self.onWall then
22 by Josh C
climbing animation
144
                           if self.onGround then self:play('stand') end
21 by Josh C
really easy version of knowing when we reached the top of a wall.
145
                           self.velocity.x = 0
146
                        end
6 by Josh C
whitespace cleanup
147
                     end
148
23 by Josh C
wall jump
149
                     if the.keys:justPressed('up') and
25 by Josh C
build more level
150
                      (self.onGround or the.console.visible or
23 by Josh C
wall jump
151
                       (love.timer.getTime() - self.leftWallAt < .1) ) then
6 by Josh C
whitespace cleanup
152
                        self.velocity.y = -400
12 by Josh C
only jump when you're on the ground
153
                        self.jumping = true
6 by Josh C
whitespace cleanup
154
                     end
155
                  end,
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
156
   update = function (self, elapsed)
157
               -- NOTE: this is an override, not a callback
158
159
               self:doPhysics('x', elapsed)
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
160
               self:collide(the.view.map)
161
162
               -- handle X collisions
21 by Josh C
really easy version of knowing when we reached the top of a wall.
163
               self.onWall = false
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
164
               for _, col in ipairs(self.collisions) do
165
                  col.other:displaceDir(self, 'x')
166
                  if self.velocity.x > 0 then
167
                     self.onWall = 'right'
168
                  elseif self.velocity.x < 0 then
169
                     self.onWall = 'left'
170
                  else
171
                     print 'x ??'
172
                  end
173
               end
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
174
175
               self.onGround = false -- right before Y collision callbacks
176
               self:doPhysics('y', elapsed)
177
               self:collide(the.view.map)
18 by Josh C
call Animation.update so we actually get animations
178
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
179
               -- handle Y collisions
180
               for _, col in ipairs(self.collisions) do
181
                  if self.velocity.y > 0 then
182
                     self.onGround = true
183
                  end
184
185
                  col.other:displaceDir(self, 'y')
186
                  self.velocity.y = 0
187
                  self.jumping = false
188
               end
189
18 by Josh C
call Animation.update so we actually get animations
190
               Animation.update(self, elapsed)
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
191
            end,
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
192
   collide = function (self, ...)
193
                self.collisions = {}
194
                Animation.collide(self, ...)
195
                -- I could return a true/false value here if I wanted to...
196
             end,
8 by Josh C
some basic collision (and workarounds)
197
   onCollide = function (self, other, xOverlap, yOverlap)
198
                  if other == the.view.map then return end
199
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
200
                  table.insert(self.collisions, {other = other,
201
                                                 xOverlap = xOverlap,
202
                                                 yOverlap = yOverlap })
203
               end
2 by Josh C
basic tiles, map, player, movement
204
}
205
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
206
-- displace on a specific axis (monkey patch Sprite)
207
function Sprite:displaceDir(other, dir)
208
   if not self.solid or self == other or not other.solid then return end
209
   if STRICT then assert(other:instanceOf(Sprite), 'asked to displace a non-sprite') end
210
211
   if other.sprites then
212
      -- handle groups
213
214
      for _, spr in pairs(other.sprites) do
215
         self:displace(spr, dir)
216
      end
217
   else
218
      -- handle sprites
26 by Josh C
don't go off the edge
219
      local dim = util.dim(dir)
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
220
221
      local negMove = (other[dir] - self[dir]) + other[dim]
222
      local posMove = (self[dir] + self[dim]) - other[dir]
223
224
      -- TODO: re-add hinting?
225
      if negMove < posMove then
226
         chg = - negMove
227
      else
228
         chg = posMove
229
      end
230
   end
231
232
   other[dir] = other[dir] + chg
233
end
234
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
235
-- don't use zoetrope physics
236
function Sprite:update (elapsed)
237
   if self.onUpdate then self:onUpdate(elapsed) end
238
end
239
2 by Josh C
basic tiles, map, player, movement
240
GameView = View:extend {
241
   onNew = function (self)
242
              self:loadLayers('data/map.lua')
243
              self.focus = the.player
244
              self:clampTo(self.map)
8 by Josh C
some basic collision (and workarounds)
245
           end,
246
   onUpdate = function (self)
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
247
                 --print('drawTook: ', the.drawTook)
8 by Josh C
some basic collision (and workarounds)
248
                 --print('tick')
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
249
                 --the.player:collide(self.map)
8 by Josh C
some basic collision (and workarounds)
250
                 --self.map:collide(the.player)
251
              end
2 by Josh C
basic tiles, map, player, movement
252
}
253
254
the.app = App:new {
255
   onRun = function (self)
256
              self.view = GameView:new()
15 by Josh C
more reliable onGround calc
257
              self.console:watch('onGround', 'the.player.onGround')
16 by Josh C
try to track X collisions. break out Sprite's physics in prep for
258
              self.console:watch('onWall', 'the.player.onWall')
24 by Josh C
profiling and analysis
259
              self.console:watch('updateTook', 'the.updateTook')
260
              self.console:watch('drawTook', 'the.drawTook')
261
262
              --the.profiler = newProfiler('time', 2000)
263
              --the.profiler = newProfiler()
264
              --the.profiler:start()
2 by Josh C
basic tiles, map, player, movement
265
           end,
266
   onUpdate = function (self, dt)
24 by Josh C
profiling and analysis
267
                 if the.keys:justPressed('escape') then
268
                    if the.profiler then
269
                       the.profiler:stop()
270
                       local outfile = io.open( "profile.txt", "w+" )
271
                       the.profiler:report( outfile )
272
                       outfile:close()
273
                    end
274
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
275
                    self.quit()
12 by Josh C
only jump when you're on the ground
276
                 end
24 by Josh C
profiling and analysis
277
              end,
278
   update = function (self, dt)
279
               the.updateStart = love.timer.getMicroTime()
280
               App.update(self, dt)
281
               if the.updateStart then
282
                  the.updateTook = love.timer.getMicroTime() - the.updateStart
283
               end
284
            end
285
}