-
Notifications
You must be signed in to change notification settings - Fork 4
/
platformer.lua
353 lines (294 loc) · 9.17 KB
/
platformer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
pico-8 cartridge // http://www.pico-8.com
version 16
__lua__
-- linear interpolation function
function lerp(v0, v1, t)
return v0 + t * (v1 - v0)
end
function _init()
tilesize = 8
player = { x = 3, y = 13 }
player.speed = { x = 0, y = 0 }
player.collision = {
size = {
horizontal = {
width = 10 / tilesize,
height = 9 / tilesize
},
vertical = {
width = 6 / tilesize,
height = 13 / tilesize
}
}
}
player.anims = {
standing = { 12 },
jumping = { 14 },
walking = { 32, 34, 36, 38, 40, 42, 44, 46 }
}
updatecollisionbox(player)
screensize = {
width = 128,
height = 128
}
mapsize = {
width = 53,
height = 32
}
-- pixels of downward speed applied each frame
grav = 1.0 / tilesize
maxgrav = 7.5 / tilesize
-- horizontal movement speed
movspeed = 2.25 / tilesize
-- jump speed
jumpspeed = 10.5 / tilesize
-- jump buffering
jumpbuffer = 3 -- number of frames allowed to buffer jumps
jumpframes = jumpbuffer + 1
wasjumppressed = false
-- jump grace period
jumpgrace = 3 -- number of frames allowed after being on the ground to still jump
fallingframes = jumpgrace + 1
-- screen bounding box beyond which the camera will snap back to the player
camerasnap = { left = 40, top = 16, right = screensize.width - 40, bottom = screensize.height - 48 }
cam = { x = 0, y = 0 }
end
function _update()
player.speed.x = 0
jumpframes = min(jumpbuffer + 1, jumpframes + 1)
fallingframes = min(jumpgrace + 1, fallingframes + 1)
if btn(0) then
player.speed.x -= movspeed
end
if btn(1) then
player.speed.x += movspeed
end
if btn(4) and not wasjumppressed then
jumpframes = 0
end
if player.onground or fallingframes <= jumpgrace then
if jumpframes <= jumpbuffer then
jump(player)
end
else
if player.jumping and not btn(4) then
player.jumping = false
end
end
applyphysics(player)
animate(player)
wasjumppressed = btn(4)
-- update camera position
local screenx, screeny = player.x * tilesize - cam.x, player.y * tilesize - cam.y
if screenx < camerasnap.left then
cam.x += screenx - camerasnap.left
elseif screenx > camerasnap.right then
cam.x += screenx - camerasnap.right
else
local center = player.x * tilesize - screensize.width / 2
cam.x += (center - cam.x) / 6
end
if screeny < camerasnap.top then
cam.y += screeny - camerasnap.top
elseif screeny > camerasnap.bottom then
cam.y += screeny - camerasnap.bottom
elseif player.onground then
local center = player.y * tilesize - screensize.height / 2
cam.y += (center - cam.y) / 6
end
local maxcamx, maxcamy =
max(0, mapsize.width * tilesize - screensize.width),
max(0, mapsize.height * tilesize - screensize.height)
cam.x = mid(0, cam.x, maxcamx)
cam.y = mid(0, cam.y, maxcamy)
end
function animate(entity)
if not entity.onground then
setanim(entity, "jumping")
elseif entity.speed.x ~= 0 then
setanim(entity, "walking")
else
setanim(entity, "standing")
end
entity.animframes += 1
entity.frame = (flr(entity.animframes / 3) % #entity.anim) + 1
if entity.speed.x < 0 then
entity.mirror = true
elseif entity.speed.x > 0 then
entity.mirror = false
end
end
function setanim(entity, name)
if entity.anim ~= entity.anims[name] then
entity.anim = entity.anims[name]
entity.animframes = 0
end
end
function jump(entity)
entity.onground = false
entity.jumping = true
entity.curjumpspeed = jumpspeed
jumpframes = jumpbuffer + 1
end
function applyphysics(entity)
local speed = entity.speed
if entity.jumping or speed.y < 0 then
if entity.jumping then
entity.curjumpspeed -= jumpspeed / 10
else
entity.curjumpspeed = 0
end
speed.y = -entity.curjumpspeed
if entity.curjumpspeed <= 0 then
entity.jumping = false
end
else
speed.y = min(maxgrav, speed.y + grav)
end
local wasonground = entity.onground -- we need to know if the entity started on the ground for slopes
entity.onground = false
-- increase precision by applying physics in smaller steps
-- the more steps, the faster things can go without going through terrain
local steps = 1
local highestspeed = max(abs(speed.x), abs(speed.y))
if highestspeed >= 0.25 then
steps = ceil(highestspeed / 0.25)
end
for i = 1, steps do
entity.x += speed.x / steps
entity.y += speed.y / steps
updatecollisionbox(entity)
-- slope collisions
for tile in gettiles(entity, "floor") do
if tile.sprite > 0 then
local tiletop = tile.y
if tile.slope then
local slope = tile.slope
local xoffset = entity.x - tile.x
if xoffset < 0 or xoffset > 1 then
-- only do slopes if the entity's center x coordinate is inside the tile space
-- otherwise ignore this tile
tiletop = nil
else
local alpha
if slope.reversed then
alpha = 1 - xoffset
else
alpha = xoffset
end
local slopeheight = lerp(slope.offset, slope.offset + slope.height, alpha)
tiletop = tile.y + 1 - slopeheight
-- only snap the entity down to the slope's height if it wasn't jumping or on the ground
if entity.y < tiletop and not wasonground and not jumping then
tiletop = nil
end
end
else
tiletop = nil
end
if tiletop then
speed.y = 0
entity.y = tiletop
entity.onground = true
entity.jumping = false
fallingframes = 0
end
end
end
updatecollisionbox(entity)
-- wall collisions
for tile in gettiles(entity, "horizontal") do
if tile.sprite > 0 and not tile.slope then
if entity.x < tile.x + 0.5 then
-- push out to the left
entity.x = tile.x - entity.collision.size.horizontal.width / 2
else
-- push out to the right
entity.x = tile.x + 1 + entity.collision.size.horizontal.width / 2
end
end
end
updatecollisionbox(entity)
-- floor collisions
for tile in gettiles(entity, "floor") do
if tile.sprite > 0 and not tile.slope then
speed.y = 0
entity.y = tile.y
entity.onground = true
entity.jumping = false
fallingframes = 0
end
end
updatecollisionbox(entity)
-- ceiling collisions
for tile in gettiles(entity, "ceiling") do
if tile.sprite > 0 and not tile.slope then
speed.y = 0
entity.y = tile.y + 1 + entity.collision.size.vertical.height
entity.jumping = false
end
end
end
end
-- gets all tiles that might be intersecting entity's collision box
function gettiles(entity, boxtype)
local box = entity.collision.box[boxtype]
local left, top, right, bottom =
flr(box.left), flr(box.top), flr(box.right), flr(box.bottom)
local x, y = left, top
-- iterator function
return function()
if y > bottom then
return nil
end
local sprite = mget(x, y)
local ret = { sprite = sprite, x = x, y = y }
local flags = fget(sprite)
if band(flags, 128) == 128 then
-- this is a slope if flag 7 is set
ret.slope = {
reversed = band(flags, 64) == 64, -- reversed if flag 6 is set,
height = (band(flags, 7) + 1) / tilesize, -- the first 3 bits/flags set the slope height from 1-8
offset = band(lshr(flags, 3), 7) / tilesize -- bits/flags 4 through 6 set the offset from the bottom of the tile between 0 and 7
}
end
x += 1
if x > right then
x = left
y += 1
end
return ret
end
end
function updatecollisionbox(entity)
local size = entity.collision.size
entity.collision.box = {
horizontal = {
left = entity.x - size.horizontal.width / 2,
top = entity.y - size.vertical.height + (size.vertical.height - size.horizontal.height) / 2,
right = entity.x + size.horizontal.width / 2,
bottom = entity.y - (size.vertical.height - size.horizontal.height) / 2
},
floor = {
left = entity.x - size.vertical.width / 2,
top = entity.y - size.vertical.height / 2,
right = entity.x + size.vertical.width / 2,
bottom = entity.y
},
ceiling = {
left = entity.x - size.vertical.width / 2,
top = entity.y - size.vertical.height,
right = entity.x + size.vertical.width / 2,
bottom = entity.y - size.vertical.height / 2
}
}
end
function _draw()
camera(cam.x, cam.y)
pal()
palt(0, false)
palt(14, true)
cls(12)
map(0, 0, 0, 0)
spr(player.anim[player.frame], player.x * tilesize - 8, player.y * tilesize - 16, 2, 2, player.mirror)
end