-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpong.nelua
291 lines (238 loc) · 8.65 KB
/
pong.nelua
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
--[[
Copyright (c) 2021-present André Luiz Alvares
Nene is licensed under the Zlib license.
Please refer to the LICENSE file for details
SPDX-License-Identifier: Zlib
]]
-- basic pong example!
-- require basic libraries
local math = require 'math'
local string = require 'string'
-- require nene librarise and modules
local Nene = require 'nene.core'
local Color = require 'nene.color'
local Rect = require 'nene.math.rect'
local Rectf = require 'nene.math.rectf'
local Vec2 = require 'nene.math.vec2'
local Vec2i = require 'nene.math.vec2i'
local Intersections = require 'nene.intersections'
local Font = require 'nene.font'
local Texture = require 'nene.texture'
-- general game settings
local SETTINGS: record{
screen_size: Vec2i,
win_size: Vec2i,
racket: record{
size: Vec2i,
velocity: integer,
},
ball: record{
size: integer,
velocity: integer,
}
} <const> = {
screen_size = { 400, 240 },
win_size = { (400*2), (240*2) },
racket = {
size = { 20, 100 },
velocity = 120,
},
ball = {
size = 10,
velocity = 150,
}
}
-- pong code:
-- racket record
local Racket = @record{
pos_y: number,
points: integer,
}
function Racket.init(): Racket
return Racket{
pos_y = SETTINGS.racket.size.y / 2,
}
end
-- return the rect of the racket
function Racket:get_rectf(is_player_one: boolean): Rectf
local anchor_x = SETTINGS.screen_size.x / 2
local size = SETTINGS.racket.size
return Rectf{
pos = {
x = is_player_one and -anchor_x + 2 or anchor_x - size.x - 2,
y = self.pos_y,
},
size = Vec2.from_vec2i(size),
}
end
-- racket's gameplay code
function Racket:update(is_player_one: boolean <comptime>)
local nene = Nene.instance()
-- input
## if is_player_one.value then
local up_scancode, down_scancode = SDL_SCANCODE_W, SDL_SCANCODE_S
## else
local up_scancode, down_scancode = SDL_SCANCODE_UP, SDL_SCANCODE_DOWN
## end
-- movement
local limit_y <const> = SETTINGS.screen_size.y * 0.5
local velocity = SETTINGS.racket.velocity * nene.delta_time
local neg_limit_y = -limit_y + SETTINGS.racket.size.y + 2
local pos_limit_y = limit_y - 2
if Nene.is_scancode_held(up_scancode) then
self.pos_y = math.clamp(self.pos_y + velocity, neg_limit_y, pos_limit_y)
end
if Nene.is_scancode_held(down_scancode) then
self.pos_y = math.clamp(self.pos_y - velocity, neg_limit_y, pos_limit_y)
end
end
-- draw a racket
function Racket:draw(is_player_one: boolean)
local nene = Nene.instance()
Nene.render_draw_rect(self:get_rectf(is_player_one):to_rect(), false, Color.yellow, true)
end
-- ball record
local Ball = @record{
pos: Vec2,
direction: Vec2,
}
-- return the ball's rect
function Ball:get_rectf(): Rectf
return Rectf{
pos = self.pos,
size = Vec2.one * SETTINGS.ball.size,
}
end
-- ball's gameplay code
function Ball:update(racket_1: *Racket, racket_2: *Racket, delta_time: number)
self.pos = self.pos + self.direction * SETTINGS.ball.velocity * delta_time
-- does intersected limits?
local limit_x <const> = SETTINGS.screen_size.x * 0.5
local limit_y <const> = SETTINGS.screen_size.y * 0.5
local on_left_side <const> = self.pos.x < -limit_x
local on_right_side <const> = self.pos.x + SETTINGS.ball.size > limit_x
local on_up_side <const> = self.pos.y > limit_y
local on_down_side <const> = self.pos.y - SETTINGS.ball.size < -limit_y
-- invert horizontal direction when intersecting left or right side and add points to the winner player
-- also resets ball position to center
if on_left_side or on_right_side then
self.direction.x = -self.direction.x
local winner_racket = on_left_side and racket_2 or racket_1
winner_racket.points = winner_racket.points + 1
self.pos = {}
end
-- invert vertical direction when intersecting up or down side
if on_down_side and self.direction.y < 0 or on_up_side and self.direction.y > 0 then
self.direction.y = -self.direction.y
end
-- invert horizontal direction when intersecting rackets
local ball_rect = self:get_rectf()
local r1_rect, r2_rect = racket_1:get_rectf(true), racket_2:get_rectf(false)
local r1_intersected, r2_intersected = Intersections.is_intersecting_rectf_with_rectf(ball_rect, r1_rect),
Intersections.is_intersecting_rectf_with_rectf(ball_rect, r2_rect)
if r1_intersected or r2_intersected then
self.direction.x = -self.direction.x
self.pos.x = r1_intersected and r1_rect.pos.x + r1_rect.size.x or r2_rect.pos.x - SETTINGS.ball.size
end
end
function Ball:draw()
Nene.render_draw_rect(self:get_rectf():to_rect(), false, Color.white, true)
end
local function update_points(pts_text: *Texture, points: integer, font: Font)
local pts_str <close> = tostring(points)
font:update_text(pts_text, pts_str, Font.TextQuality.Solid, Color.white)
end
local function game()
-- nene setup
-- nene initialization
local ok = Nene.init('nene pong', SETTINGS.win_size.x, SETTINGS.win_size.y)
assert(ok, 'nene initialization failed')
-- game setup --
--[[
This creates a new "Nene Texture" which will be used as low resolution
screen through using render target (sometimes called as Render Texture on some engines),
thus it will use "target" texture access.
Note that the value comes from SDL directly, while Nene abstracts SDL many
times, it doesn't try to hide it, so you will interact with SDL sometimes,
especially to go beyond Nene capabilities if needed.
]]
local ok, screen = Texture.create_with_access(SETTINGS.screen_size.x, SETTINGS.screen_size.y, SDL_TEXTUREACCESS_TARGET)
assert(ok, 'could not create the screen texture target')
defer
screen:destroy()
end
-- creates players rackets and the ball
local pl_1: Racket = Racket.init()
local pl_2: Racket = Racket.init()
local ball: Ball = {
pos = {},
direction = { 1, 1 },
}
-- "monogram_extended" font is available with nene, but it's not loaded automatically.
local ok, font = Font.load('../../resources/monogram_extended.ttf', 72)
assert(ok, 'could not load monogram_extended font')
-- creates both players texts and then defer it's destroy calls
local ok, r1_text = font:render('0', Font.TextQuality.Solid, Color.white)
assert(ok, "could not render first text")
local ok, r2_text = font:render('0', Font.TextQuality.Solid, Color.white)
assert(ok, "could not render second text")
defer
font:destroy()
r1_text:destroy()
r2_text:destroy()
end
local nene = Nene.instance()
-- finally, the game loop
-- In Nene, you just `repeat` the loop until Nene gets a "quit" state
-- this happens when the game is just to be closed.
repeat
-- this updates the internal state, it also polls all the SDL events, you
-- must call update before using anything from Nene on a the game loop tick.
Nene.update()
-- update step
do
local prev_r1_points, prev_r2_points = pl_1.points, pl_2.points
pl_1:update(true)
pl_2:update(false)
ball:update(pl_1, pl_2, nene.delta_time)
if prev_r1_points ~= pl_1.points then
update_points(r1_text, pl_1.points, font)
end
if prev_r2_points ~= pl_2.points then
update_points(r2_text, pl_2.points, font)
end
end
-- draw step
do
-- set a low-resolution screen as the render target, this is very useful to make pixel-art games.
Nene.set_render_target(screen:get_raw())
-- clear screen, otherwise the game would draw frame on top of previous frames,
-- which gives weird results.
Nene.render_clear(Color.bg)
-- rendering on the screen
do
-- draw rackets and ball
pl_1:draw(true)
pl_2:draw(false)
ball:draw()
-- draw points texts
r1_text:draw_to_point({}, { x = 10 })
r2_text:draw_to_point({}, { x = SETTINGS.screen_size.x - r1_text.width - 10 })
end
-- reset render target to window screen, this is the default when no render target is used.
Nene.set_render_target(nilptr)
-- draw the whole screen on the whole window screen.
screen:draw_to_rect()
-- present the "drawing result".
-- In SDL (and consequently Nene) applies all draw operations on a backbuffer, and
-- render_present presents the composed backbuffer on the screen.
Nene.render_present()
end
until Nene.should_quit() -- this loop will be repeated until Nene assign `true` to `Nene.quit` variable.
-- all the memory will be released due the defer blocks above.
end
-- the code is inside a function, here we call it.
-- this is not mandatory in Nene, you can just write the code here too,
-- however, coding in a function is required to make address sanitizer
-- (a memory debug utility) work as expected.
game()