我正在学习 CS50 游戏开发入门课程,该课程使用 Lua 和 LOVE2D 制作游戏。我在分配代码中发现了一个错误,但我不确定如何修复它。
作业是 Flappy Bird 的克隆。如果您不熟悉,游戏中有一只鸟飞过上下管组之间的缝隙。我将在本文末尾发布代码。学生将获得完整游戏的作业代码,然后必须进行修改。我会在最后贴出代码。
这里是相关部分。在 PipePair.lua 中,我们将常量 GAP_HEIGHT 设置为 90。然后我们使用 init() 函数实例化一对管道,如下所示:
self.pipes = {
['upper'] = Pipe('top', self.y),
['lower'] = Pipe('bottom', self.y + PIPE_HEIGHT + GAP_HEIGHT)
}
PIPE_HEIGHT 是 288,我们管道精灵的大小。
在 PlayState.lua 中。我们设置一个变量 self.y 如下:
self.lastY = -PIPE_HEIGHT + math.random(1,80) + 20
然后在更新方法中,我们将局部变量 y 设置为以下内容:
local y = math.max(-PIPE_HEIGHT + 10, math.min(self.lastY + math.random(-20, 20), VIRTUAL_HEIGHT - 90 - PIPE_HEIGHT))
self.lastY = y
table.insert(self.pipePairs, PipePair(y))
VIRTUAL_HEIGHT 为 288,与 PIPE_HEIGHT 相同。此方法的目的是将每个管道对的间隙位置更改为比最后一个间隙低 20 像素和高 20 像素之间的某处,以创建一种流。 math.max 的第一个值中的 (+10) 的目的是确保上部管道显示至少 10 个像素,以便您看到管道。 math.min 的第二个值中的硬编码值 90 应该是间隙高度。 math.min 中第二个值的目的是确保底部管道不会开始绘制在屏幕下方,因此不可见。
最后,Pipe.lua对每个管道的渲染函数如下:
love.graphics.draw(PIPE_IMAGE, self.x,
(self.orientation == 'top' and self.y + PIPE_HEIGHT or self.y),
0, 1, self.orientation == 'top' and -1 or 1)
这是我发现的错误。我在游戏中注意到有时底管不可见。经过测试和计算,我发现可以将底部管道的y设置为288,即虚拟屏幕高度。与顶部管道不同,底部管道未设置为在地面上方可见的最小高度。修复方法是将 PlayState 的本地 y 计算中的硬编码 90 更改为 110。但这仅在间隙高度为 90 时有效。如果间隙高度为 60,则该值需要为 80,如果间隙高度为 120,该值需要为 140.
所以问题来了。该任务要求我们随机化每个管道对中的间隙高度。我试图在 60 到 120 之间随机化。但为了避免错误,我需要为 PipePair 类和 PlayState 类提供相同的间隙高度值。随机数需要在每次更新时更改。但是我不知道在哪里放置随机数生成,该随机数生成将随每次更新而改变,并与 PipePair 和 PlayState 类共享该值。我该怎么做?
顺便说一句,我已经在多个地方发布了这个问题以进行 CS50 讨论,但没有得到任何回应,这就是我转向这里寻求帮助的原因。
以下是相关文件,减去注释:
PlayState.lua
PlayState = Class{__includes = BaseState}
PIPE_SPEED = 60
PIPE_WIDTH = 70
PIPE_HEIGHT = 288
BIRD_WIDTH = 38
BIRD_HEIGHT = 24
function PlayState:init()
self.bird = Bird()
self.pipePairs = {}
self.timer = 0
self.score = 0
self.lastY = -PIPE_HEIGHT + math.random(80) + 20
end
function PlayState:update(dt)
self.timer = self.timer + dt
if self.timer > 2 then
local y = math.max(-PIPE_HEIGHT + 10,
math.min(self.lastY + math.random(-20,20), VIRTUAL_HEIGHT - 90 - PIPE_HEIGHT))
self.lastY = y
table.insert(self.pipePairs, PipePair(y))
self.timer = 0
end
for k, pair in pairs(self.pipePairs) do
if not pair.scored then
if pair.x + PIPE_WIDTH < self.bird.x then
self.score = self.score + 1
pair.scored = true
sounds['score']:play()
end
end
pair:update(dt)
end
for k, pair in pairs(self.pipePairs) do
if pair.remove then
table.remove(self.pipePairs, k)
end
end
for k, pair in pairs(self.pipePairs) do
for l, pipe in pairs(pair.pipes) do
if self.bird:collides(pipe) then
sounds['explosion']:play()
sounds['hurt']:play()
gStateMachine:change('score', {
score = self.score
})
end
end
end
self.bird:update(dt)
if self.bird.y > VIRTUAL_HEIGHT - 15 then
sounds['explosion']:play()
sounds['hurt']:play()
gStateMachine:change('score', {
score = self.score
})
end
end
function PlayState:render()
for k, pair in pairs(self.pipePairs) do
pair:render()
end
love.graphics.setFont(flappyFont)
love.graphics.print('Score: ' .. tostring(self.score), 8, 8)
self.bird:render()
end
function PlayState:enter()
scrolling = true
end
function PlayState:exit()
scrolling = false
end
PipePair.lua
PipePair = Class{}
local GAP_HEIGHT = 90
function PipePair:init(y)
self.scored = false
self.x = VIRTUAL_WIDTH + 32
self.y = y
self.pipes = {
['upper'] = Pipe('top', self.y),
['lower'] = Pipe('bottom', self.y + PIPE_HEIGHT + GAP_HEIGHT)
}
self.remove = false
end
function PipePair:update(dt)
if self.x > -PIPE_WIDTH then
self.x = self.x - PIPE_SPEED * dt
self.pipes['lower'].x = self.x
self.pipes['upper'].x = self.x
else
self.remove = true
end
end
function PipePair:render()
for l, pipe in pairs(self.pipes) do
pipe:render()
end
end
管道.lua
Pipe = Class{}
local PIPE_IMAGE = love.graphics.newImage('pipe.png')
function Pipe:init(orientation, y)
self.x = VIRTUAL_WIDTH + 64
self.y = y
self.width = PIPE_WIDTH
self.height = PIPE_HEIGHT
self.orientation = orientation
end
function Pipe:update(dt)
end
function Pipe:render()
love.graphics.draw(PIPE_IMAGE, self.x,
(self.orientation == 'top' and self.y + PIPE_HEIGHT or self.y),
0, 1, self.orientation == 'top' and -1 or 1)
end
main.lua(不确定是否真的相关):
push = require 'push'
Class = require 'class'
require 'StateMachine'
require 'states/BaseState'
require 'states/CountdownState'
require 'states/PlayState'
require 'states/ScoreState'
require 'states/TitleScreenState'
require 'Bird'
require 'Pipe'
require 'PipePair'
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
VIRTUAL_WIDTH = 512
VIRTUAL_HEIGHT = 288
local background = love.graphics.newImage('background.png')
local backgroundScroll = 0
local ground = love.graphics.newImage('ground.png')
local groundScroll = 0
local BACKGROUND_SCROLL_SPEED = 30
local GROUND_SCROLL_SPEED = 60
local BACKGROUND_LOOPING_POINT = 413
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')
math.randomseed(os.time())
love.window.setTitle('Fifty Bird')
smallFont = love.graphics.newFont('font.ttf', 8)
mediumFont = love.graphics.newFont('flappy.ttf', 14)
flappyFont = love.graphics.newFont('flappy.ttf', 28)
hugeFont = love.graphics.newFont('flappy.ttf', 56)
love.graphics.setFont(flappyFont)
sounds = {
['jump'] = love.audio.newSource('jump.wav', 'static'),
['explosion'] = love.audio.newSource('explosion.wav', 'static'),
['hurt'] = love.audio.newSource('hurt.wav', 'static'),
['score'] = love.audio.newSource('score.wav', 'static'),
['music'] = love.audio.newSource('marios_way.mp3', 'static')
}
sounds['music']:setLooping(true)
sounds['music']:play()
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
vsync = true,
fullscreen = false,
resizable = true
})
gStateMachine = StateMachine {
['title'] = function() return TitleScreenState() end,
['countdown'] = function() return CountdownState() end,
['play'] = function() return PlayState() end,
['score'] = function() return ScoreState() end
}
gStateMachine:change('title')
love.keyboard.keysPressed = {}
love.mouse.buttonsPressed = {}
end
function love.resize(w, h)
push:resize(w, h)
end
function love.keypressed(key)
love.keyboard.keysPressed[key] = true
if key == 'escape' then
love.event.quit()
end
end
function love.mousepressed(x, y, button)
love.mouse.buttonsPressed[button] = true
end
function love.keyboard.wasPressed(key)
return love.keyboard.keysPressed[key]
end
function love.mouse.wasPressed(button)
return love.mouse.buttonsPressed[button]
end
function love.update(dt)
backgroundScroll = (backgroundScroll + BACKGROUND_SCROLL_SPEED * dt) % BACKGROUND_LOOPING_POINT
groundScroll = (groundScroll + GROUND_SCROLL_SPEED * dt) % VIRTUAL_WIDTH
gStateMachine:update(dt)
love.keyboard.keysPressed = {}
love.mouse.buttonsPressed = {}
end
function love.draw()
push:start()
love.graphics.draw(background, -backgroundScroll, 0)
gStateMachine:render()
love.graphics.draw(ground, -groundScroll, VIRTUAL_HEIGHT - 16)
push:finish()
end
'随机数需要在每次更新时改变。 但是我不知道在哪里放置随机数生成,该随机数生成将随每次更新而改变,并与 PipePair 和 PlayState 类共享该值。 我该怎么做?'
例子
-- rand.lua
-- Lua 5.1 needs math.randomseed() first
math.randomseed(math.random(math.floor(os.time() * os.clock())))
-- 6 out of 49
local lotto = {} -- Construct a Table
-- Fill it with Numbers you wish to use randomly
for i = 1, 49 do
table.insert(lotto, i)
end
-- Take six Numbers randomly
for i = 1, 6 do
local getone = table.remove(lotto, math.random(1, #lotto))
print(getone)
end
-- Above table.remove() makes sure number is Unique
-- Until table is empty