0%

GD50 - Pong

两年前,在炎热夏日的宿舍房间里初学写C.
那时看到了这个游戏制作公开课,因为C都写得极其头大,打开三分钟决定搁置.
上个月一看:卧槽这公开课今年截止.
于是试图在死线前摸点笔记.

第一个project是经典小游戏Pong,附录Github代码.




0

这个课程使用的是lua语言,run游戏的是love 2D.

Download&Getting Started

摸了一下还是直接drag的方式最方便run.

Otherwise, the easiest way to run the game is to drag the folder onto either love.exe or a shortcut to love.exe. Remember to drag the folder containing main.lua, and not main.lua itself.

function

love.load()

  • Used for initializing our game state at the very beginning of program execution. Load function肯定自带有,这是overwrite初始function. Love2D默认read main.lua file.

love.update(dt) - dt:delta time

  • Called each frame by LÖVE; dt will be the elapsed time in seconds since the last frame, and we can use this to scale any changes in our game for even behavior across frame rates.

love.draw()

  • Called each frame by LÖVE after update for drawing things to the screen once they’ve changed.

LÖVE2D expects these functions to be implemented in main.lua and calls them internally; if we don’t define them, it will still function, but our game will be fundamentally incomplete, at least if update or draw are missing!

love.graphics.printf(text, x, y, [width], [align])

  • Versatile print function that can align text left, right, or center on the screen.

love.window.setMode(width, height, params)

  • Used to initialize the window’s dimensions and to set parameters like vsync (vertical sync), whether we’re fullscreen or not, and whether the window is resizable after startup. Won’t be using past this example in favor of the push virtual resolution library, which has its own method like this, but useful to know if encountered in other code.

source code

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
--[[
GD50 2018
Pong Remake

pong-0
"The Day-0 Update"

-- Main Program --
]]

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
love.window.setMode(WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen, updated or otherwise.
]]
function love.draw()
love.graphics.printf(
'Hello Pong!', -- text to render
0, -- starting X (0 since we're going to center it based on width)
WINDOW_HEIGHT / 2 - 6, -- starting Y (halfway down the screen)
WINDOW_WIDTH, -- number of pixels to center within (the entire screen here)
'center') -- alignment mode, can be 'center', 'left', or 'right'
end




1

下一步更改了resolution.

function

love.graphics.setDefaultFilter(min, mag)

  • Sets the texture scaling filter when minimizing and magnifying textures and fonts; default is bilinear, which causes blurriness, and for our use cases we will typically want nearest-neighbor filtering (‘nearest’), which results in perfect pixel upscaling and downscaling, simulating a retro feel.
    当love2D run text or image的时候,会apply一个default filter.

love.keypressed(key)

  • A LÖVE2D callback function that executes whenever we press a key, assuming we’ve implemented this in our main.lua, in the same vein as love.load(), love.update(dt), and love.draw().

love.event.quit()

  • Simple function that terminates the application.

source code

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
--[[
GD50 2018
Pong Remake

pong-1
"The Low-Res Update"

-- Main Program --
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
-- 导入library

push = require 'push'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- 这一步是原本432*243大小的图像之后render成 1280*720,看起来像老游戏

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
-- use nearest-neighbor filtering on upscaling and downscaling to prevent blurring of text
-- and graphics; try removing this function to see the difference!
love.graphics.setDefaultFilter('nearest', 'nearest')

-- initialize our virtual resolution, which will be rendered within our
-- actual window no matter its dimensions; replaces our love.window.setMode call
-- from the last example
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)
-- keys can be accessed by string name
if key == 'escape' then
-- function LÖVE gives us to terminate application
love.event.quit()
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()
-- begin rendering at virtual resolution
push:apply('start')

-- condensed onto one line from last example
-- note we are now using virtual width and height now for text placement
love.graphics.printf('Hello Pong!', 0, VIRTUAL_HEIGHT / 2 - 6, VIRTUAL_WIDTH, 'center')

-- end rendering at virtual resolution
push:apply('end')
end

这个folder下有两个file.
上文是main.lua ,下文是push.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
-- push.lua v0.4

-- Copyright (c) 2020 Ulysse Ramage
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

local love11 = love.getVersion() == 11
local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale
local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings)
local _, _, flags = love.window.getMode()
for k, v in pairs(settings) do flags[k] = v end
love.window.setMode(width, height, flags)
end

local push = {

defaults = {
fullscreen = false,
resizable = false,
pixelperfect = false,
highdpi = true,
canvas = true,
stencil = true
}

}
setmetatable(push, push)

function push:applySettings(settings)
for k, v in pairs(settings) do
self["_" .. k] = v
end
end

function push:resetSettings() return self:applySettings(self.defaults) end

function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings)

settings = settings or {}

self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT
self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT

self:applySettings(self.defaults) --set defaults first
self:applySettings(settings) --then fill with custom settings

windowUpdateMode(self._RWIDTH, self._RHEIGHT, {
fullscreen = self._fullscreen,
resizable = self._resizable,
highdpi = self._highdpi
})

self:initValues()

if self._canvas then
self:setupCanvas({ "default" }) --setup canvas
end

self._borderColor = {0, 0, 0}

self._drawFunctions = {
["start"] = self.start,
["end"] = self.finish
}

return self
end

function push:setupCanvas(canvases)
table.insert(canvases, { name = "_render", private = true }) --final render

self._canvas = true
self.canvases = {}

for i = 1, #canvases do
push:addCanvas(canvases[i])
end

return self
end
function push:addCanvas(params)
table.insert(self.canvases, {
name = params.name,
private = params.private,
shader = params.shader,
canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT),
stencil = params.stencil or self._stencil
})
end

function push:setCanvas(name)
if not self._canvas then return true end
local canvasTable = self:getCanvasTable(name)
return love.graphics.setCanvas({ canvasTable.canvas, stencil = canvasTable.stencil })
end
function push:getCanvasTable(name)
for i = 1, #self.canvases do
if self.canvases[i].name == name then
return self.canvases[i]
end
end
end
function push:setShader(name, shader)
if not shader then
self:getCanvasTable("_render").shader = name
else
self:getCanvasTable(name).shader = shader
end
end

function push:initValues()
self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1

self._SCALE = {
x = self._RWIDTH/self._WWIDTH * self._PSCALE,
y = self._RHEIGHT/self._WHEIGHT * self._PSCALE
}

if self._stretched then --if stretched, no need to apply offset
self._OFFSET = {x = 0, y = 0}
else
local scale = math.min(self._SCALE.x, self._SCALE.y)
if self._pixelperfect then scale = math.floor(scale) end

self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)}
self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y
end

self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2
self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2
end

function push:apply(operation, shader)
self._drawFunctions[operation](self, shader)
end

function push:start()
if self._canvas then
love.graphics.push()
love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil })

else
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y)
love.graphics.push()
love.graphics.scale(self._SCALE.x, self._SCALE.y)
end
end

function push:applyShaders(canvas, shaders)
local _shader = love.graphics.getShader()
if #shaders <= 1 then
love.graphics.setShader(shaders[1])
love.graphics.draw(canvas)
else
local _canvas = love.graphics.getCanvas()

local _tmp = self:getCanvasTable("_tmp")
if not _tmp then --create temp canvas only if needed
self:addCanvas({ name = "_tmp", private = true, shader = nil })
_tmp = self:getCanvasTable("_tmp")
end

love.graphics.push()
love.graphics.origin()
local outputCanvas
for i = 1, #shaders do
local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas
outputCanvas = i % 2 == 0 and canvas or _tmp.canvas
love.graphics.setCanvas(outputCanvas)
love.graphics.clear()
love.graphics.setShader(shaders[i])
love.graphics.draw(inputCanvas)
love.graphics.setCanvas(inputCanvas)
end
love.graphics.pop()

love.graphics.setCanvas(_canvas)
love.graphics.draw(outputCanvas)
end
love.graphics.setShader(_shader)
end

function push:finish(shader)
love.graphics.setBackgroundColor(unpack(self._borderColor))
if self._canvas then
local _render = self:getCanvasTable("_render")

love.graphics.pop()

local white = love11 and 1 or 255
love.graphics.setColor(white, white, white)

--draw canvas
love.graphics.setCanvas(_render.canvas)
for i = 1, #self.canvases do --do not draw _render yet
local _table = self.canvases[i]
if not _table.private then
local _canvas = _table.canvas
local _shader = _table.shader
self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader })
end
end
love.graphics.setCanvas()

--draw render
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
local shader = shader or _render.shader
love.graphics.push()
love.graphics.scale(self._SCALE.x, self._SCALE.y)
self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader })
love.graphics.pop()

--clear canvas
for i = 1, #self.canvases do
love.graphics.setCanvas(self.canvases[i].canvas)
love.graphics.clear()
end

love.graphics.setCanvas()
love.graphics.setShader()
else
love.graphics.pop()
love.graphics.setScissor()
end
end

function push:setBorderColor(color, g, b)
self._borderColor = g and {color, g, b} or color
end

function push:toGame(x, y)
x, y = x - self._OFFSET.x, y - self._OFFSET.y
local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT

x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil
y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil

return x, y
end

--doesn't work - TODO
function push:toReal(x, y)
return x + self._OFFSET.x, y + self._OFFSET.y
end

function push:switchFullscreen(winw, winh)
self._fullscreen = not self._fullscreen
local windowWidth, windowHeight = love.window.getDesktopDimensions()

if self._fullscreen then --save windowed dimensions for later
self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT
elseif not self._WINWIDTH or not self._WINHEIGHT then
self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5
end

self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH
self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT

self:initValues()

love.window.setFullscreen(self._fullscreen, "desktop")
if not self._fullscreen and (winw or winh) then
windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions
end
end

function push:resize(w, h)
if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end
self._RWIDTH = w
self._RHEIGHT = h
self:initValues()
end

function push:getWidth() return self._WWIDTH end
function push:getHeight() return self._WHEIGHT end
function push:getDimensions() return self._WWIDTH, self._WHEIGHT end

return push




2

画出paddle和球.

function

love.graphics.newFont(path, size)

  • Loads a font file into memory at a specific path, setting it to a specific size, and storing it in an object we can use to globally change the currently active font that LÖVE2D is using to render text (functioning like a state machine). Deafult is Arial.

love.graphics.setFont(font)

  • Sets LÖVE2D’s currently active font (of which there can only be one at a time) to a passed-in font object that we can create using love.graphics.newFont.

love.graphics.clear(r, g, b, a)

  • Wipes the entire screen with a color defined by an RGBA set, each component of which being from 0-255.

love.graphics.rectangle(mode, x, y, width, height)

  • Draws a rectangle onto the screen using whichever our active color is (love.graphics.setColor, which we don’t need to use in this particular project since most everything is white, the default LÖVE2D color). mode can be set to ‘fill’ or ‘line’, which result in a filled or outlined rectangle, respectively, and the other four parameters are its position and size dimensions. This is the cornerstone drawing function of the entirety of our Pong implementation!

source code

这次文件夹下有main.lua, push.lua and font.ttf .

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
--[[
GD50 2018
Pong Remake

pong-2
"The Rectangle Update"
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
push = require 'push'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')

-- more "retro-looking" font object we can use for any text
smallFont = love.graphics.newFont('font.ttf', 8)

-- set LÖVE2D's active font to the smallFont obect
love.graphics.setFont(smallFont)

-- initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)
-- keys can be accessed by string name
if key == 'escape' then
-- function LÖVE gives us to terminate application
love.event.quit()
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()
-- begin rendering at virtual resolution
push:apply('start')

-- clear the screen with a specific color; in this case, a color similar
-- to some versions of the original Pong
love.graphics.clear(40/255, 45/255, 52/255, 255/255)

-- draw welcome text toward the top of the screen
love.graphics.printf('Hello Pong!', 0, 20, VIRTUAL_WIDTH, 'center')

--
-- paddles are simply rectangles we draw on the screen at certain points,
-- as is the ball
--

-- render first paddle (left side)
love.graphics.rectangle('fill', 10, 30, 5, 20)

-- render second paddle (right side)
love.graphics.rectangle('fill', VIRTUAL_WIDTH - 10, VIRTUAL_HEIGHT - 50, 5, 20)

-- render ball (center) 减2是因为ball本身有2 pixel
love.graphics.rectangle('fill', VIRTUAL_WIDTH / 2 - 2, VIRTUAL_HEIGHT / 2 - 2, 4, 4)

-- end rendering at virtual resolution
push:apply('end')
end




3

让paddle动起来!

function

love.keyboard.isDown(key)

  • Returns true or false depending on whether the specified key is currently held down; differs from love.keypressed(key) in that this can be called arbitrarily and will continuously return true if the key is pressed down, where love.keypressed(key) will only fire its code once every time the key is initially pressed down. However, since we want to be able to move our paddles up and down by holding down the appropriate keys, we need a function to test for longer periods of input, hence the use of love.keyboard.isDown(key)!
    This returns true or false.

source code

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
--[[
GD50 2018
Pong Remake

pong-3
"The Paddle Update"
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
push = require 'push'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- paddle移动速度; multiplied by dt in update
PADDLE_SPEED = 200

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')

-- more "retro-looking" font object we can use for any text
smallFont = love.graphics.newFont('font.ttf', 8)

-- larger font for drawing the score on the screen
scoreFont = love.graphics.newFont('font.ttf', 32)

-- set LÖVE2D's active font to the smallFont obect
love.graphics.setFont(smallFont)

-- initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})

-- initialize score variables, used for rendering on the screen and keeping
-- track of the winner
player1Score = 0
player2Score = 0

-- paddle positions on the Y axis (they can only move up or down)
player1Y = 30
player2Y = VIRTUAL_HEIGHT - 50
end

--[[
Runs every frame, with "dt" passed in, our delta in seconds
since the last frame, which LÖVE2D supplies us.
]]
function love.update(dt)
-- player 1 movement
if love.keyboard.isDown('w') then
-- add negative paddle speed to current Y scaled by deltaTime,这里的横线是负的意思
player1Y = player1Y + -PADDLE_SPEED * dt
elseif love.keyboard.isDown('s') then
-- add positive paddle speed to current Y scaled by deltaTime
player1Y = player1Y + PADDLE_SPEED * dt
end

-- player 2 movement
if love.keyboard.isDown('up') then
-- add negative paddle speed to current Y scaled by deltaTime
player2Y = player2Y + -PADDLE_SPEED * dt
elseif love.keyboard.isDown('down') then
-- add positive paddle speed to current Y scaled by deltaTime
player2Y = player2Y + PADDLE_SPEED * dt
end
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)
-- keys can be accessed by string name
if key == 'escape' then
-- function LÖVE gives us to terminate application
love.event.quit()
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()
-- begin rendering at virtual resolution
push:apply('start')

-- clear the screen with a specific color; in this case, a color similar
-- to some versions of the original Pong
love.graphics.clear(40/255, 45/255, 52/255, 255/255)

-- draw welcome text toward the top of the screen
love.graphics.setFont(smallFont)
love.graphics.printf('Hello Pong!', 0, 20, VIRTUAL_WIDTH, 'center')

-- draw score on the left and right center of the screen
-- need to switch font to draw before actually printing
love.graphics.setFont(scoreFont)
love.graphics.print(tostring(player1Score), VIRTUAL_WIDTH / 2 - 50,
VIRTUAL_HEIGHT / 3)
love.graphics.print(tostring(player2Score), VIRTUAL_WIDTH / 2 + 30,
VIRTUAL_HEIGHT / 3)

-- render first paddle (left side), now using the players' Y variable
love.graphics.rectangle('fill', 10, player1Y, 5, 20)

-- render second paddle (right side)
love.graphics.rectangle('fill', VIRTUAL_WIDTH - 10, player2Y, 5, 20)

-- render ball (center)
love.graphics.rectangle('fill', VIRTUAL_WIDTH / 2 - 2, VIRTUAL_HEIGHT / 2 - 2, 4, 4)

-- end rendering at virtual resolution
push:apply('end')
end



4

让球动起来!

function

math.randomseed(num)

  • ”Seeds” the random number generator used by Lua (math.random) with some value such that its randomness is dependent on that supplied value, allowing us to pass in different numbers each playthrough to guarantee non-consistency across different program executions (or uniformity if we want consistent behavior for testing).

os.time()

  • Lua function that returns, in seconds, the time since 00:00:00 UTC, January 1, 1970, also known as Unix epoch time
    (https://en.wikipedia.org/wiki/Unix_time).
    因为每次的时间不一样,所以可以拿来当作seed.

math.random(min, max)

  • Returns a random number, dependent on the seeded random number generator, between min and max,
    inclusive.

math.min(num1, num2)

  • Returns the lesser of the two numbers passed in.

math.max(num1, num2)

  • Returns the greater of the two numbers passed in.

source code

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
--[[
GD50 2018
Pong Remake

pong-4
"The Ball Update"

-- Main Program --
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
push = require 'push'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- speed at which we will move our paddle; multiplied by dt in update
PADDLE_SPEED = 200

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')

-- "seed" the RNG so that calls to random are always random
-- use the current time, since that will vary on startup every time
math.randomseed(os.time())

-- more "retro-looking" font object we can use for any text
smallFont = love.graphics.newFont('font.ttf', 8)

-- set LÖVE2D's active font to the smallFont obect
love.graphics.setFont(smallFont)

-- initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})

-- paddle positions on the Y axis (they can only move up or down)
player1Y = 30
player2Y = VIRTUAL_HEIGHT - 50

-- velocity and position variables for our ball when play starts
ballX = VIRTUAL_WIDTH / 2 - 2
ballY = VIRTUAL_HEIGHT / 2 - 2

-- math.random returns a random value between the left and right number
-- dx and dy are delta x and delta y, which represent velocity
ballDX = math.random(2) == 1 and 100 or -100
ballDY = math.random(-50, 50)
-- by adding them one frame by one frame in update, ball will move

-- game state variable used to transition between different parts of the game
-- (used for beginning, menus, main game, high score list, etc.)
-- we will use this to determine behavior during render and update
gameState = 'start'
end

--[[
Runs every frame, with "dt" passed in, our delta in seconds
since the last frame, which LÖVE2D supplies us.
]]
function love.update(dt)
-- player 1 movement
if love.keyboard.isDown('w') then
-- add negative paddle speed to current Y scaled by deltaTime
-- now, we clamp our position between the bounds of the screen
-- math.max returns the greater of two values; 0 and player Y
-- will ensure we don't go above it
player1Y = math.max(0, player1Y + -PADDLE_SPEED * dt)

-- 用max来锁住活动范围

elseif love.keyboard.isDown('s') then
-- add positive paddle speed to current Y scaled by deltaTime
-- math.min returns the lesser of two values; bottom of the egde minus paddle height
-- and player Y will ensure we don't go below it
player1Y = math.min(VIRTUAL_HEIGHT - 20, player1Y + PADDLE_SPEED * dt)
end

-- player 2 movement
if love.keyboard.isDown('up') then
-- add negative paddle speed to current Y scaled by deltaTime
player2Y = math.max(0, player2Y + -PADDLE_SPEED * dt)
elseif love.keyboard.isDown('down') then
-- add positive paddle speed to current Y scaled by deltaTime
player2Y = math.min(VIRTUAL_HEIGHT - 20, player2Y + PADDLE_SPEED * dt)
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ballX = ballX + ballDX * dt
ballY = ballY + ballDY * dt
end
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)
-- keys can be accessed by string name
if key == 'escape' then
-- function LÖVE gives us to terminate application
love.event.quit()
-- if we press enter during the start state of the game, we'll go into play mode
-- during play mode, the ball will move in a random direction
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'play'
else
gameState = 'start'

-- start ball's position in the middle of the screen
ballX = VIRTUAL_WIDTH / 2 - 2
ballY = VIRTUAL_HEIGHT / 2 - 2

-- given ball's x and y velocity a random starting value
-- the and/or pattern here is Lua's way of accomplishing a ternary operation
-- in other programming languages like C
ballDX = math.random(2) == 1 and 100 or -100
-- 这跟在C里写 math.random(2) == 1 ? 100 : -100相同
ballDY = math.random(-50, 50) * 1.5
end
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()
-- begin rendering at virtual resolution
push:apply('start')

-- clear the screen with a specific color; in this case, a color similar
-- to some versions of the original Pong
love.graphics.clear(40/255, 45/255, 52/255, 255/255)

-- draw different things based on the state of the game
love.graphics.setFont(smallFont)

if gameState == 'start' then
love.graphics.printf('Hello Start State!', 0, 20, VIRTUAL_WIDTH, 'center')
else
love.graphics.printf('Hello Play State!', 0, 20, VIRTUAL_WIDTH, 'center')
end

-- render first paddle (left side), now using the players' Y variable
love.graphics.rectangle('fill', 10, player1Y, 5, 20)

-- render second paddle (right side)
love.graphics.rectangle('fill', VIRTUAL_WIDTH - 10, player2Y, 5, 20)

-- render ball (center)
love.graphics.rectangle('fill', ballX, ballY, 4, 4)

-- end rendering at virtual resolution
push:apply('end')
end




4

Class update,把paddle和球换成class.
(Yeah this makes more sense.)

function

What is a class?

  • Blueprints for creating bundles of data and code that are related.
  • A “Car” class can have attributes that describe its brand, model, color, miles, and anything else descriptive; these are also known as “fields”.
  • A “Car” class can also have “methods” that define its behavior, such as “accelerate”, “turn”, “honk”, and more, which take the form of functions.
  • Objects are instantiated from these class blueprints, and it’s these concrete objects that are the physical “cars” you see on the road, as opposed to the blueprints that may exist in the factory.
  • Our Paddles and Ball are perfect simple use cases for taking some of our code and bundling it together into classes and objects.

source code

main.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
--[[
GD50 2018
Pong Remake

pong-5
"The Class Update"

-- Main Program --
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
push = require 'push'

-- the "Class" library we're using will allow us to represent anything in
-- our game as code, rather than keeping track of many disparate variables and
-- methods
--
-- https://github.com/vrld/hump/blob/master/class.lua
Class = require 'class'

-- our Paddle class, which stores position and dimensions for each Paddle
-- and the logic for rendering them
require 'Paddle'

-- our Ball class, which isn't much different than a Paddle structure-wise
-- but which will mechanically function very differently
require 'Ball'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- speed at which we will move our paddle; multiplied by dt in update
PADDLE_SPEED = 200

--[[
Runs when the game first starts up, only once; used to initialize the game.
]]
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')

-- "seed" the RNG so that calls to random are always random
-- use the current time, since that will vary on startup every time
math.randomseed(os.time())

-- more "retro-looking" font object we can use for any text
smallFont = love.graphics.newFont('font.ttf', 8)

-- set LÖVE2D's active font to the smallFont object
love.graphics.setFont(smallFont)

-- initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = true,
vsync = true
})

-- initialize our player paddles; make them global so that they can be
-- detected by other functions and modules
player1 = Paddle(10, 30, 5, 20)
player2 = Paddle(VIRTUAL_WIDTH - 10, VIRTUAL_HEIGHT - 30, 5, 20)

-- place a ball in the middle of the screen
ball = Ball(VIRTUAL_WIDTH / 2 - 2, VIRTUAL_HEIGHT / 2 - 2, 4, 4)

-- game state variable used to transition between different parts of the game
-- (used for beginning, menus, main game, high score list, etc.)
-- we will use this to determine behavior during render and update
gameState = 'start'
end

--[[
Runs every frame, with "dt" passed in, our delta in seconds
since the last frame, which LÖVE2D supplies us.
]]
function love.update(dt)
-- player 1 movement
if love.keyboard.isDown('w') then
player1.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('s') then
player1.dy = PADDLE_SPEED
else
player1.dy = 0
end

-- player 2 movement
if love.keyboard.isDown('up') then
player2.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('down') then
player2.dy = PADDLE_SPEED
else
player2.dy = 0
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ball:update(dt)
end

player1:update(dt)
player2:update(dt)
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)
-- keys can be accessed by string name
if key == 'escape' then
-- function LÖVE gives us to terminate application
love.event.quit()
-- if we press enter during the start state of the game, we'll go into play mode
-- during play mode, the ball will move in a random direction
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'play'
else
gameState = 'start'

-- ball's new reset method
ball:reset()
end
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()
-- begin rendering at virtual resolution
push:apply('start')

-- clear the screen with a specific color; in this case, a color similar
-- to some versions of the original Pong
love.graphics.clear(40/255, 45/255, 52/255, 255/255)

-- draw different things based on the state of the game
love.graphics.setFont(smallFont)

if gameState == 'start' then
love.graphics.printf('Hello Start State!', 0, 20, VIRTUAL_WIDTH, 'center')
else
love.graphics.printf('Hello Play State!', 0, 20, VIRTUAL_WIDTH, 'center')
end

-- render paddles, now using their class's render method
player1:render()
player2:render()

-- render ball using its class's render method
ball:render()

-- end rendering at virtual resolution
push:apply('end')
end

Ball.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
--[[
GD50 2018
Pong Remake

-- Ball Class --

Author: Colton Ogden
cogden@cs50.harvard.edu

Represents a ball which will bounce back and forth between paddles
and walls until it passes a left or right boundary of the screen,
scoring a point for the opponent.
]]

Ball = Class{}

function Ball:init(x, y, width, height)
self.x = x
self.y = y
self.width = width
self.height = height

-- these variables are for keeping track of our velocity on both the
-- X and Y axis, since the ball can move in two dimensions
self.dy = math.random(2) == 1 and -100 or 100
self.dx = math.random(-50, 50)
end

--[[
Places the ball in the middle of the screen, with an initial random velocity
on both axes.
]]
function Ball:reset()
self.x = VIRTUAL_WIDTH / 2 - 2
self.y = VIRTUAL_HEIGHT / 2 - 2
self.dy = math.random(2) == 1 and -100 or 100
self.dx = math.random(-50, 50)
end

--[[
Simply applies velocity to position, scaled by deltaTime.
]]
function Ball:update(dt)
self.x = self.x + self.dx * dt
self.y = self.y + self.dy * dt
end

function Ball:render()
love.graphics.rectangle('fill', self.x, self.y, self.width, self.height)
end

Paddle.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
--[[
GD50 2018
Pong Remake

-- Paddle Class --

Author: Colton Ogden
cogden@cs50.harvard.edu

Represents a paddle that can move up and down. Used in the main
program to deflect the ball back toward the opponent.
]]

Paddle = Class{}

--[[
The `init` function on our class is called just once, when the object
is first created. Used to set up all variables in the class and get it
ready for use.

Our Paddle should take an X and a Y, for positioning, as well as a width
and height for its dimensions.

Note that `self` is a reference to *this* object, whichever object is
instantiated at the time this function is called. Different objects can
have their own x, y, width, and height values, thus serving as containers
for data. In this sense, they're very similar to structs in C.
]]
function Paddle:init(x, y, width, height)
self.x = x
self.y = y
self.width = width
self.height = height
self.dy = 0
end

function Paddle:update(dt)
-- math.max here ensures that we're the greater of 0 or the player's
-- current calculated Y position when pressing up so that we don't
-- go into the negatives; the movement calculation is simply our
-- previously-defined paddle speed scaled by dt
if self.dy < 0 then
self.y = math.max(0, self.y + self.dy * dt)
-- similar to before, this time we use math.min to ensure we don't
-- go any farther than the bottom of the screen minus the paddle's
-- height (or else it will go partially below, since position is
-- based on its top left corner)
else
self.y = math.min(VIRTUAL_HEIGHT - self.height, self.y + self.dy * dt)
end
end

--[[
To be called by our main function in `love.draw`, ideally. Uses
LÖVE2D's `rectangle` function, which takes in a draw mode as the first
argument as well as the position and dimensions for the rectangle. To
change the color, one must call `love.graphics.setColor`. As of the
newest version of LÖVE2D, you can even draw rounded rectangles!
]]
function Paddle:render()
love.graphics.rectangle('fill', self.x, self.y, self.width, self.height)
end

class.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
--[[
Copyright (c) 2010-2013 Matthias Richter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--

local function include_helper(to, from, seen)
if from == nil then
return to
elseif type(from) ~= 'table' then
return from
elseif seen[from] then
return seen[from]
end

seen[from] = to
for k,v in pairs(from) do
k = include_helper({}, k, seen) -- keys might also be tables
if to[k] == nil then
to[k] = include_helper({}, v, seen)
end
end
return to
end

-- deeply copies 'other' into 'class'. keys in 'other' that are already
-- defined in 'class' are omitted
local function include(class, other)
return include_helper(class, other, {})
end

-- returns a deep copy of 'other'
local function clone(other)
return setmetatable(include({}, other), getmetatable(other))
end

local function new(class)
-- mixins
class = class or {} -- class can be nil
local inc = class.__includes or {}
if getmetatable(inc) then inc = {inc} end

for _, other in ipairs(inc) do
if type(other) == "string" then
other = _G[other]
end
include(class, other)
end

-- class implementation
class.__index = class
class.init = class.init or class[1] or function() end
class.include = class.include or include
class.clone = class.clone or clone

-- constructor call
return setmetatable(class, {__call = function(c, ...)
local o = setmetatable({}, c)
o:init(...)
return o
end})
end

-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
if class_commons ~= false and not common then
common = {}
function common.class(name, prototype, parent)
return new{__includes = {prototype, parent}}
end
function common.instance(class, ...)
return class(...)
end
end


-- the module
return setmetatable({new = new, include = include, clone = clone},
{__call = function(_,...) return new(...) end})




5

The FPS(frame per second) Update.

function

love.window.setTitle(title) - 改个标题

  • Simply sets the title of our application window, adding a slight level of polish.

love.timer.getFPS()

  • Returns the current FPS of our application, making it easy to monitor when printed.

source code

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

-- render paddles, now using their class's render method
player1:render()
player2:render()

-- render ball using its class's render method
ball:render()

-- new function just to demonstrate how to see FPS in LÖVE2D
displayFPS()

-- end rendering at virtual resolution
push:apply('end')
end

--[[
Renders the current FPS.
]]
function displayFPS()
-- simple FPS display across all states
love.graphics.setFont(smallFont)
love.graphics.setColor(0, 255/255, 0, 255/255)
love.graphics.print('FPS: ' .. tostring(love.timer.getFPS()), 10, 10) -- 这里的点点是合并string
end




7

终于要做撞击设定了.

function

AABB Collision Detection

Relies on all colliding entities to have “alix-aligned bounding boxes”, which simply means their collision boxes contain no rotation in our world space, which allows us to use a simple math formula to test for collision:

1
2
3
4
5
6
7
if rect1.x is not > rect2.x + rect2.width and
rect1.x + rect1.width is not < rect2.x and
rect1.y is not > rect2.y + rect2.height and
rect1.y + rect1.height is not < rect2.y:
collision is true
else
collision is false

逻辑是check四次:
A方块的左边在B方块的右边,那就不会collision.同理上下左右.

source code

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
function love.update(dt)
if gameState == 'play' then
-- detect ball collision with paddles, reversing dx if true and
-- slightly increasing it, then altering the dy based on the position of collision
if ball:collides(player1) then
ball.dx = -ball.dx * 1.03 -- this 1.03 is for speed up the game
ball.x = player1.x + 5 -- this is for not overlap paddle collision box and the ball

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end
if ball:collides(player2) then
ball.dx = -ball.dx * 1.03
ball.x = player2.x - 4 -- in this case 4 is for width of the ball

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end

-- detect upper and lower screen boundary collision and reverse if collided
if ball.y <= 0 then
ball.y = 0
ball.dy = -ball.dy
end

-- -4 to account for the ball's size
if ball.y >= VIRTUAL_HEIGHT - 4 then
ball.y = VIRTUAL_HEIGHT - 4
ball.dy = -ball.dy
end
end

-- player 1 movement
if love.keyboard.isDown('w') then
player1.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('s') then
player1.dy = PADDLE_SPEED
else
player1.dy = 0
end

-- player 2 movement
if love.keyboard.isDown('up') then
player2.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('down') then
player2.dy = PADDLE_SPEED
else
player2.dy = 0
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ball:update(dt)
end

player1:update(dt)
player2:update(dt)
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Ball:collides(paddle)
-- first, check to see if the left edge of either is farther to the right
-- than the right edge of the other
if self.x > paddle.x + paddle.width or paddle.x > self.x + self.width then
return false
end

-- then check to see if the bottom edge of either is higher than the top
-- edge of the other
if self.y > paddle.y + paddle.height or paddle.y > self.y + self.height then
return false
end

-- if the above aren't true, they're overlapping
return true
end



8

The Score Update.

source code

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
function love.update(dt)
if gameState == 'play' then
-- detect ball collision with paddles, reversing dx if true and
-- slightly increasing it, then altering the dy based on the position of collision
if ball:collides(player1) then
ball.dx = -ball.dx * 1.03
ball.x = player1.x + 5

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end
if ball:collides(player2) then
ball.dx = -ball.dx * 1.03
ball.x = player2.x - 4

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end

-- detect upper and lower screen boundary collision and reverse if collided
if ball.y <= 0 then
ball.y = 0
ball.dy = -ball.dy
end

-- -4 to account for the ball's size
if ball.y >= VIRTUAL_HEIGHT - 4 then
ball.y = VIRTUAL_HEIGHT - 4
ball.dy = -ball.dy
end
end

-- if we reach the left or right edge of the screen,
-- go back to start and update the score
if ball.x < 0 then
servingPlayer = 1
player2Score = player2Score + 1
ball:reset()
gameState = 'start'
end

if ball.x > VIRTUAL_WIDTH then
servingPlayer = 2
player1Score = player1Score + 1
ball:reset()
gameState = 'start'
end

-- player 1 movement
if love.keyboard.isDown('w') then
player1.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('s') then
player1.dy = PADDLE_SPEED
else
player1.dy = 0
end

-- player 2 movement
if love.keyboard.isDown('up') then
player2.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('down') then
player2.dy = PADDLE_SPEED
else
player2.dy = 0
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ball:update(dt)
end

player1:update(dt)
player2:update(dt)
end



9

增加一个叫做serve的stage.

游戏的设定是一开始在start stage,需要按enter才能正经进入游戏.
serve是分别给player1和player2一次试玩的机会.
在player1 的serve时,球会往player1那里跑.

这一步的目的是,把游戏分成不同的stage来考虑(make sense!).

function

source code

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

function love.load()
-- set love's default filter to "nearest-neighbor", which essentially
-- means there will be no filtering of pixels (blurriness), which is
-- important for a nice crisp, 2D look
love.graphics.setDefaultFilter('nearest', 'nearest')

-- set the title of our application window
love.window.setTitle('Pong')

-- "seed" the RNG so that calls to random are always random
-- use the current time, since that will vary on startup every time
math.randomseed(os.time())

-- more "retro-looking" font object we can use for any text
smallFont = love.graphics.newFont('font.ttf', 8)

-- larger font for drawing the score on the screen
scoreFont = love.graphics.newFont('font.ttf', 32)

-- set LÖVE2D's active font to the smallFont obect
love.graphics.setFont(smallFont)

-- initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = false,
vsync = true
})

-- initialize score variables, used for rendering on the screen and keeping
-- track of the winner
player1Score = 0
player2Score = 0

-- either going to be 1 or 2; whomever is scored on gets to serve the
-- following turn
servingPlayer = 1

-- initialize player paddles and ball
player1 = Paddle(10, 30, 5, 20)
player2 = Paddle(VIRTUAL_WIDTH - 10, VIRTUAL_HEIGHT - 30, 5, 20)
ball = Ball(VIRTUAL_WIDTH / 2 - 2, VIRTUAL_HEIGHT / 2 - 2, 4, 4)

gameState = 'start'
end

--[[
Runs every frame, with "dt" passed in, our delta in seconds
since the last frame, which LÖVE2D supplies us.
]]
function love.update(dt)
if gameState == 'serve' then -- serve state
-- before switching to play, initialize ball's velocity based
-- on player who last scored
ball.dy = math.random(-50, 50)
if servingPlayer == 1 then
ball.dx = math.random(140, 200)
else
ball.dx = -math.random(140, 200)
end
elseif gameState == 'play' then
-- detect ball collision with paddles, reversing dx if true and
-- slightly increasing it, then altering the dy based on the position of collision
if ball:collides(player1) then
ball.dx = -ball.dx * 1.03
ball.x = player1.x + 5

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end
if ball:collides(player2) then
ball.dx = -ball.dx * 1.03
ball.x = player2.x - 4

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end
end

-- detect upper and lower screen boundary collision and reverse if collided
if ball.y <= 0 then
ball.y = 0
ball.dy = -ball.dy
end

-- -4 to account for the ball's size
if ball.y >= VIRTUAL_HEIGHT - 4 then
ball.y = VIRTUAL_HEIGHT - 4
ball.dy = -ball.dy
end
end

-- if we reach the left or right edge of the screen,
-- go back to start and update the score
if ball.x < 0 then
servingPlayer = 1
player2Score = player2Score + 1
ball:reset()
gameState = 'serve'
end

if ball.x > VIRTUAL_WIDTH then
servingPlayer = 2
player1Score = player1Score + 1
ball:reset()
gameState = 'serve'
end

-- player 1 movement
if love.keyboard.isDown('w') then
player1.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('s') then
player1.dy = PADDLE_SPEED
else
player1.dy = 0
end

-- player 2 movement
if love.keyboard.isDown('up') then
player2.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('down') then
player2.dy = PADDLE_SPEED
else
player2.dy = 0
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ball:update(dt)
end

player1:update(dt)
player2:update(dt)
end

--[[
Keyboard handling, called by LÖVE2D each frame;
passes in the key we pressed so we can access.
]]
function love.keypressed(key)

if key == 'escape' then
love.event.quit()
-- if we press enter during either the start or serve phase, it should
-- transition to the next appropriate state
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'serve'
elseif gameState == 'serve' then
gameState = 'play'
end
end
end

--[[
Called after update by LÖVE2D, used to draw anything to the screen,
updated or otherwise.
]]
function love.draw()

push:apply('start')

-- clear the screen with a specific color; in this case, a color similar
-- to some versions of the original Pong
love.graphics.clear(40/255, 45/255, 52/255, 255/255)

love.graphics.setFont(smallFont)

displayScore()

if gameState == 'start' then
love.graphics.setFont(smallFont)
love.graphics.printf('Welcome to Pong!', 0, 10, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Press Enter to begin!', 0, 20, VIRTUAL_WIDTH, 'center')
elseif gameState == 'serve' then
love.graphics.setFont(smallFont)
love.graphics.printf('Player ' .. tostring(servingPlayer) .. "'s serve!",
0, 10, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Press Enter to serve!', 0, 20, VIRTUAL_WIDTH, 'center')
elseif gameState == 'play' then
-- no UI messages to display in play
end

player1:render()
player2:render()
ball:render()

displayFPS()

push:apply('end')
end





10

增加winning condition.

source code

当赢了的时候.

1
2
3
4
5
6
7
if player1Score == 10 then
winningPlayer = 1
gameState = 'done'
else
gameState = 'serve'
ball:reset()
end

当进入下一轮的时候.

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
function love.keypressed(key)

if key == 'escape' then
love.event.quit()
-- if we press enter during either the start or serve phase, it should
-- transition to the next appropriate state
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'serve'
elseif gameState == 'serve' then
gameState = 'play'
elseif gameState == 'done' then
-- game is simply in a restart phase here, but will set the serving
-- player to the opponent of whomever won for fairness!
gameState = 'serve'

ball:reset()

-- reset scores to 0
player1Score = 0
player2Score = 0

-- decide serving player as the opposite of who won
if winningPlayer == 1 then
servingPlayer = 2
else
servingPlayer = 1
end
end
end
end



11

加音乐!

function

love.audio.newSource(path, [type])

  • Creates a LÖVE2D Audio object that we can play back at any point in our program. Can also be given a “type” of “stream” or “static”; streamed assets will be streamed from disk as needed, whereas static assets will be preserved in memory. For larger sound effects and music tracks, streaming is more memory-effective; in our examples, audio assets are static, since they’re so small that they won’t take up much memory at all.

software

  • Simple sound-generating program, freely available on major OSes.
  • Used to generate all sound effects for our Pong example and most examples going forward.
  • https://www.bfxr.net/

source code

Import sound as a table.

1
2
3
4
5
6
7
8
9
-- set up our sound effects; later, we can just index this table and
-- call each entry's `play` method
sounds = {
['paddle_hit'] = love.audio.newSource('sounds/paddle_hit.wav', 'static'),
-- if static, it will load into and memory, if steam, they are loaded on the fly
-- steam is helpful for saving memory(for example large music files)
['score'] = love.audio.newSource('sounds/score.wav', 'static'),
['wall_hit'] = love.audio.newSource('sounds/wall_hit.wav', 'static')
}

需要的时候.

1
sounds['paddle_hit']:play()



12

The Resize Update.

function

love.resize(width, height)

  • Called by LÖVE every time we resize the application; logic should go in here if anything in the game (like a UI) is sized dynamically based on the window size. push:resize() needs to be called here for our use case so that it can dynamically rescale its internal canvas to fit our new window dimensions.

source code

这是最后的版本了,所以是全部的main.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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
--[[
GD50 2018
Pong Remake

-- Main Program --

Author: Colton Ogden
cogden@cs50.harvard.edu

Originally programmed by Atari in 1972. Features two
paddles, controlled by players, with the goal of getting
the ball past your opponent's edge. First to 10 points wins.

This version is built to more closely resemble the NES than
the original Pong machines or the Atari 2600 in terms of
resolution, though in widescreen (16:9) so it looks nicer on
modern systems.
]]

-- push is a library that will allow us to draw our game at a virtual
-- resolution, instead of however large our window is; used to provide
-- a more retro aesthetic
--
-- https://github.com/Ulydev/push
push = require 'push'

-- the "Class" library we're using will allow us to represent anything in
-- our game as code, rather than keeping track of many disparate variables and
-- methods
--
-- https://github.com/vrld/hump/blob/master/class.lua
Class = require 'class'

-- our Paddle class, which stores position and dimensions for each Paddle
-- and the logic for rendering them
require 'Paddle'

-- our Ball class, which isn't much different than a Paddle structure-wise
-- but which will mechanically function very differently
require 'Ball'

-- size of our actual window
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

-- size we're trying to emulate with push
VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243

-- paddle movement speed
PADDLE_SPEED = 200

--[[
Called just once at the beginning of the game; used to set up
game objects, variables, etc. and prepare the game world.
]]
function love.load()
-- set love's default filter to "nearest-neighbor", which essentially
-- means there will be no filtering of pixels (blurriness), which is
-- important for a nice crisp, 2D look
love.graphics.setDefaultFilter('nearest', 'nearest')

-- set the title of our application window
love.window.setTitle('Pong')

-- seed the RNG so that calls to random are always random
math.randomseed(os.time())

-- initialize our nice-looking retro text fonts
smallFont = love.graphics.newFont('font.ttf', 8)
largeFont = love.graphics.newFont('font.ttf', 16)
scoreFont = love.graphics.newFont('font.ttf', 32)
love.graphics.setFont(smallFont)

-- set up our sound effects; later, we can just index this table and
-- call each entry's `play` method
sounds = {
['paddle_hit'] = love.audio.newSource('sounds/paddle_hit.wav', 'static'),
['score'] = love.audio.newSource('sounds/score.wav', 'static'),
['wall_hit'] = love.audio.newSource('sounds/wall_hit.wav', 'static')
}

-- initialize our virtual resolution, which will be rendered within our
-- actual window no matter its dimensions
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = true,
vsync = true,
canvas = false
})

-- initialize our player paddles; make them global so that they can be
-- detected by other functions and modules
player1 = Paddle(10, 30, 5, 20)
player2 = Paddle(VIRTUAL_WIDTH - 10, VIRTUAL_HEIGHT - 30, 5, 20)

-- place a ball in the middle of the screen
ball = Ball(VIRTUAL_WIDTH / 2 - 2, VIRTUAL_HEIGHT / 2 - 2, 4, 4)

-- initialize score variables
player1Score = 0
player2Score = 0

-- either going to be 1 or 2; whomever is scored on gets to serve the
-- following turn
servingPlayer = 1

-- player who won the game; not set to a proper value until we reach
-- that state in the game
winningPlayer = 0

-- the state of our game; can be any of the following:
-- 1. 'start' (the beginning of the game, before first serve)
-- 2. 'serve' (waiting on a key press to serve the ball)
-- 3. 'play' (the ball is in play, bouncing between paddles)
-- 4. 'done' (the game is over, with a victor, ready for restart)
gameState = 'start'
end

--[[
Called whenever we change the dimensions of our window, as by dragging
out its bottom corner, for example. In this case, we only need to worry
about calling out to `push` to handle the resizing. Takes in a `w` and
`h` variable representing width and height, respectively.
]]
function love.resize(w, h)
push:resize(w, h)
end

--[[
Called every frame, passing in `dt` since the last frame. `dt`
is short for `deltaTime` and is measured in seconds. Multiplying
this by any changes we wish to make in our game will allow our
game to perform consistently across all hardware; otherwise, any
changes we make will be applied as fast as possible and will vary
across system hardware.
]]
function love.update(dt)
if gameState == 'serve' then
-- before switching to play, initialize ball's velocity based
-- on player who last scored
ball.dy = math.random(-50, 50)
if servingPlayer == 1 then
ball.dx = math.random(140, 200)
else
ball.dx = -math.random(140, 200)
end
elseif gameState == 'play' then
-- detect ball collision with paddles, reversing dx if true and
-- slightly increasing it, then altering the dy based on the position
-- at which it collided, then playing a sound effect
if ball:collides(player1) then
ball.dx = -ball.dx * 1.03
ball.x = player1.x + 5

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end

sounds['paddle_hit']:play()
end
if ball:collides(player2) then
ball.dx = -ball.dx * 1.03
ball.x = player2.x - 4

-- keep velocity going in the same direction, but randomize it
if ball.dy < 0 then
ball.dy = -math.random(10, 150)
else
ball.dy = math.random(10, 150)
end

sounds['paddle_hit']:play()
end

-- detect upper and lower screen boundary collision, playing a sound
-- effect and reversing dy if true
if ball.y <= 0 then
ball.y = 0
ball.dy = -ball.dy
sounds['wall_hit']:play()
end

-- -4 to account for the ball's size
if ball.y >= VIRTUAL_HEIGHT - 4 then
ball.y = VIRTUAL_HEIGHT - 4
ball.dy = -ball.dy
sounds['wall_hit']:play()
end

-- if we reach the left or right edge of the screen, go back to serve
-- and update the score and serving player
if ball.x < 0 then
servingPlayer = 1
player2Score = player2Score + 1
sounds['score']:play()

-- if we've reached a score of 10, the game is over; set the
-- state to done so we can show the victory message
if player2Score == 10 then
winningPlayer = 2
gameState = 'done'
else
gameState = 'serve'
-- places the ball in the middle of the screen, no velocity
ball:reset()
end
end

if ball.x > VIRTUAL_WIDTH then
servingPlayer = 2
player1Score = player1Score + 1
sounds['score']:play()

if player1Score == 10 then
winningPlayer = 1
gameState = 'done'
else
gameState = 'serve'
ball:reset()
end
end
end

--
-- paddles can move no matter what state we're in
--
-- player 1
if love.keyboard.isDown('w') then
player1.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('s') then
player1.dy = PADDLE_SPEED
else
player1.dy = 0
end

-- player 2
if love.keyboard.isDown('up') then
player2.dy = -PADDLE_SPEED
elseif love.keyboard.isDown('down') then
player2.dy = PADDLE_SPEED
else
player2.dy = 0
end

-- update our ball based on its DX and DY only if we're in play state;
-- scale the velocity by dt so movement is framerate-independent
if gameState == 'play' then
ball:update(dt)
end

player1:update(dt)
player2:update(dt)
end

--[[
A callback that processes key strokes as they happen, just the once.
Does not account for keys that are held down, which is handled by a
separate function (`love.keyboard.isDown`). Useful for when we want
things to happen right away, just once, like when we want to quit.
]]
function love.keypressed(key)
-- `key` will be whatever key this callback detected as pressed
if key == 'escape' then
-- the function LÖVE2D uses to quit the application
love.event.quit()
-- if we press enter during either the start or serve phase, it should
-- transition to the next appropriate state
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'serve'
elseif gameState == 'serve' then
gameState = 'play'
elseif gameState == 'done' then
-- game is simply in a restart phase here, but will set the serving
-- player to the opponent of whomever won for fairness!
gameState = 'serve'

ball:reset()

-- reset scores to 0
player1Score = 0
player2Score = 0

-- decide serving player as the opposite of who won
if winningPlayer == 1 then
servingPlayer = 2
else
servingPlayer = 1
end
end
end
end

--[[
Called each frame after update; is responsible simply for
drawing all of our game objects and more to the screen.
]]
function love.draw()
-- begin drawing with push, in our virtual resolution
push:start()

love.graphics.clear(40/255, 45/255, 52/255, 255/255)

-- render different things depending on which part of the game we're in
if gameState == 'start' then
-- UI messages
love.graphics.setFont(smallFont)
love.graphics.printf('Welcome to Pong!', 0, 10, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Press Enter to begin!', 0, 20, VIRTUAL_WIDTH, 'center')
elseif gameState == 'serve' then
-- UI messages
love.graphics.setFont(smallFont)
love.graphics.printf('Player ' .. tostring(servingPlayer) .. "'s serve!",
0, 10, VIRTUAL_WIDTH, 'center')
love.graphics.printf('Press Enter to serve!', 0, 20, VIRTUAL_WIDTH, 'center')
elseif gameState == 'play' then
-- no UI messages to display in play
elseif gameState == 'done' then
-- UI messages
love.graphics.setFont(largeFont)
love.graphics.printf('Player ' .. tostring(winningPlayer) .. ' wins!',
0, 10, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(smallFont)
love.graphics.printf('Press Enter to restart!', 0, 30, VIRTUAL_WIDTH, 'center')
end

-- show the score before ball is rendered so it can move over the text
displayScore()

player1:render()
player2:render()
ball:render()

-- display FPS for debugging; simply comment out to remove
displayFPS()

-- end our drawing to push
push:finish()
end

--[[
Simple function for rendering the scores.
]]
function displayScore()
-- score display
love.graphics.setFont(scoreFont)
love.graphics.print(tostring(player1Score), VIRTUAL_WIDTH / 2 - 50,
VIRTUAL_HEIGHT / 3)
love.graphics.print(tostring(player2Score), VIRTUAL_WIDTH / 2 + 30,
VIRTUAL_HEIGHT / 3)
end

--[[
Renders the current FPS.
]]
function displayFPS()
-- simple FPS display across all states
love.graphics.setFont(smallFont)
love.graphics.setColor(0, 255/255, 0, 255/255)
love.graphics.print('FPS: ' .. tostring(love.timer.getFPS()), 10, 10)
love.graphics.setColor(255, 255, 255, 255)
end




Encore

这个project的作业是给paddle1或2加入一个AI.
仔细一读,不需要从头开始code起,只要个feature就行,真是温柔呢.

Implement an AI-controlled paddle (either the left or the right will do) such that it will try to deflect the ball at all times. Since the paddle can move on only one axis (the Y axis), you will need to determine how to keep the paddle moving in relation to the ball. Currently, each paddle has its own chunk of code where input is detected by the keyboard; this feels like an excellent place to put the code we need! Once either the left or right paddle (or both, if desired) try to deflect the paddle on their own, you’ve done it!

At the time of the course’s lectures being filmed, we were using a now-outdated version of LÖVE (0.10.2); most of everything has remained the same into the newer version 11 series of the framework, but one core change is worth noting: love.graphics.clear and functions like it which take four arguments representing the red, green, blue, and alpha components of a color (or RGBA) formerly took integer values between 0 and 255:

love.graphics.clear(255, 0, 0, 255)
However, the API has now changed to where all functions that formerly took integer values for color components now take a floating-point value between 0 and 1; for example, the above API call would translate to the following:

love.graphics.clear(1, 0, 0, 1)
An easy way to normalize the old style to the new style is simply to divide components by 255. If, for example, we had a color we liked with the components 120, 30, 70, and 255, we could write it like the below:

love.graphics.clear(120/255, 30/255, 70/255, 255/255)