这一篇并不是Dylan书中的内容,是我自己运用了解到的知识做出来的一个游戏——Pong。
对电子游戏历史有过些许了解的朋友应该都比较熟悉这个名字,这就是世界第一款家用游戏,在1972年由雅达利公司制作并发行,现在来看早已是老古董般的东西,不过因为代码实现简单,我们依然可以通过自制一个这样的游戏来练手。、
说干就干,首先第一步是经行构思,经典的Pong大家应该多少都玩过,游戏很简单:在屏幕的左右两边,由玩家控制的“球拍”可以上下移动,唯一的球在场景中运动,当碰到上下边界时会弹开,碰到玩家的“球拍”时也会弹开,如果玩家的“球拍”没有接到球,让球触碰到了左右边界,那么就是对家胜利——把这些东西转换成能够进行编程的逻辑就是下面几点:
1.球具有X轴和Y轴的速度:X轴速度DX一般是固定的,为了给游戏增加一些难度,我们可以让DX逐渐增加;
2.当球触碰到上下边界,既球的纵坐标Y=0或者Y=127时,球的Y轴速度DY变成原先的相反值,这样就可以实现球“弹开”的一个事件;
3.类似于2,当球触碰到玩家的球拍,既球的X坐标小于某个值且Y坐标在球拍的长度范围内时,X轴速度取反;
4.当球触碰到左右边界,既球的 X坐标<=0 且球的 Y坐标不在球拍的长度范围内时,游戏结束,对家胜利。
明白了这四点之后,我们就可以一步步来实现这个游戏了。
首先当然还是绘制出我们需要的sprit:这里的球拍其实可以直接画直线来表示,不过我这里还是用了两个sprit,三种球分别对应初始状态、被左边的玩家击中后的球以及被右边的玩家击中后的球,还有三种地砖:
然后可以来绘制一下地图:
尽管我们这里用油漆桶铺满了我们能看见的地图,但实际上依然存在空缺部分,所以这里可以缩小之后进行更大范围的填充:
然后是我用到的五种音效以及一个背景音乐:
然后就是我们枯燥乏味的代码部分了,首先让我们来对各个变量初始化,并且在画面上绘制出这些东西:
--0
function _init()
_player1()
_player2()
_ball()
music(0)
winner=0
end
function _update()
end
function _draw()
cls()
map()
_drawplayers()
_drawball()
end
--1
function _player1()
player1={}
player1.x=0
player1.y=0
player1.dy=0
end
function _player2()
player2={}
player2.x=120
player2.y=0
player2.dy=0
end
function _ball()
ball={}
ball.x=61
ball.y=64
ball.dx=0
ball.dy=0
ball.ball=18
end
--2
function _drawplayers()
for i=0,2 do
spr(1,player1.x,player1.y+8*i)
spr(2,player2.x,player2.y+8*i)
end
end
function _drawball()
spr(ball.ball,ball.x,ball.y)
end
2*8的球拍明显太短,所以这里我选择将其增加到原来的3倍,这样的话就是24像素格的长度,从而让接球不是那么困难的事情。
music()是开始播放音乐的代码,music和sfx都是独立于Game Loop的音轨,也就是说,music和sfx和Game Loop的循环没有关系,并不是说每个Game Loop播放多少个音节最后合成音乐。
Ctrl+R运行,画面上出现了我们想要的东西,并且欢快的小曲也随着一起播放:
接下来让我们给球拍添加运动能力:
--3
function _updateplayers()
if(btn(4)) player1.y-=3
if(btn(5)) player1.y+=3
if(btn(2)) player2.y-=3
if(btn(3)) player2.y+=3
end
--0
function _update()
if winner==0
then
updateplayers()
else
music(-1)
if btn(5) then
_init()
end
end
end
Ctrl+R运行,球拍可以上下移动,但是球拍会跑到屏幕外面去:
这显然是不合理的,所以这里需要改善一下代码解决这个问题,大家首先想到的解决方法或许就是在坐标值坐标值加减的语句外嵌套一个 if 判断,条件是player1.y>0 and player1.y<127-23,看上去确实没什么问题,但是我们运行之后就发现了这样做并不可以:球拍碰到上下边界的时候确实可以停下来,但是即使往反方向运动也做不到,球拍好像被卡住了一样,这个问题其实非常容易想出来原因:假设我们的球拍纵坐标是0,此时可以上下运动,我们让它往上运动,纵坐标值-3,此时player1.y=-3——这个时候 if 内的表达式返回的值是False,所以对player1.y的加减操作都是不可行的。
我这里想到的解决方法是将上下运动分开:
--3
function _updateplayers()
if player1.y>0 then
if(btn(4)) player1.y-=3
end
if player1.y<104 then
if(btn(5)) player1.y+=3
end
if player2.y>0 then
if(btn(2)) player2.y-=3
end
if player2.y<104 then
if(btn(3)) player2.y+=3
end
end
这样球拍就可以在屏幕范围内正常的移动,接下来就是球的运动:
--3
function _updateball()
ball.x+=ball.dx
ball.y+=ball.dy
end
球的运动本身很简单,重点在于之前提到的2、3、4三条小球速度的改变事件,在那之前我们先设置一个_start()函数给小球赋一个X轴速度的初始值:
--3
function _start()
if ball.dx==0 then
if(btn(0)) ball.dx=-3
if(btn(1)) ball.dx=3
end
end
我这里设计的方案是按下“←”小球往左运动,按下“右”小球向右运动,这里也可以选择给小球一个合适的随机数来作为速度,并不会比这个难多少。
现在有了X轴速度,并没有Y轴速度,我这里想到的是在接到球后将球拍的Y轴速度传递给球,这样我们就需要给球拍设定一个DY:
--3
function _updateplayers()
if player1.y>0 then
if(btn(4)) player1.y-=3
end
if player1.y<104 then
if(btn(5)) player1.y+=3
end
if player2.y>0 then
if(btn(2)) player2.y-=3
end
if player2.y<104 then
if(btn(3)) player2.y+=3
end
if btn(4)
then
if player1.y>0 then
player1.dy=-3
end
elseif btn(5)
then
if player1.y<104 then
player1.dy=3
end
else
player1.dy=0
end
if btn(2)
then
if player2.y>0 then
player2.dy=-3
end
elseif btn(3)
then
if player2.y<104 then
player2.dy=3
end
else
player2.dy=0
end
end
这一步是放在了_updateplayers()函数里面,接下来就是三个碰撞检测:
--3
function _hitbat()
if ball.x<=3
then
if ball.y+6>player1.y
and ball.y<player1.y+24
then
ball.dx=-ball.dx+0.1
ball.dy+=player1.dy
ball.ball=17
sfx(1)
end
end
if ball.x+6>=123
then
if ball.y+6>player2.y+0.1
and ball.y<player2.y+24
then
ball.dx=-ball.dx
ball.dy+=player2.dy
ball.ball=16
sfx(2)
end
end
end
在球拍与球“碰撞”后,将球的X轴速度取反,同时把球拍的Y轴速度赋给球。
--3
function _hitwall()
if ball.y<=0
or ball.y+7>=127
then
ball.dy=-ball.dy
end
end
与上下边界的碰撞检测。
--3
function _miss()
if ball.x<=0 then
if ball.y+6<player1.y
or ball.y>player1.y+24
then
winner=2
sfx(3)
end
end
if ball.x+7>=127 then
if ball.y+6<player2.y
or ball.y>player2.y+24
then
winner=1
sfx(4)
end
end
end
这里同样也嵌套了两层 if 语句,因为仅仅靠检测ball.x<=0是不够的,因为我们在球拍的碰撞检测同样用了X坐标的检测,在那个地方的条件是ball.x<=3,当球的速度比较小是是OK的,但当球的速度很大的时候就会出现问题:
假设球的速度到了10pixel/GL,既每个Game Loop中小球运动10个像素,在上一个GL中ball.x还是5,此时既不会碰到球拍,也不会miss,但下一个GL,ball.x直接变成了-5:此时ball.x<0,直接触发了miss,根本不给player1接球的机会,所以在判断X坐标的同时判断Y坐标是很有必要的。
最后再对三个流程函数进行修改,并且在游戏结束后打印一些字来对玩家进行引导并且重新拉起游戏:
function _init()
_player1()
_player2()
_ball()
music(0)
winner=0
end
function _update()
if winner==0
then
_start()
_updateplayers()
_updateball()
_hitbat()
_hitwall()
_miss()
else
music(-1)
if btn(5) then
_init()
end
end
end
function _draw()
cls()
map()
_drawplayers()
_drawball()
if winner==1 then
print("game over!",45,60,12)
print("winner is blue!",36,70,12)
end
if winner==2 then
print("game over!",45,60,8)
print("winner is red!",36,70,8)
end
if winner!=0 then
print("press x to restart",30,80,9)
end
end
大功告成,叫上你的舍友来一起战个痛快吧!