使用Ring编程语言为桌面和Android开发Stars Fighter游戏






2.71/5 (7投票s)
Stars Fighter 是一款使用 Ring 编程语言为桌面和 Android 开发的简单 2D 游戏。
引言
Ring 编程语言附带一个简单的 2D 游戏引擎(用 Ring 编写),用于为桌面和移动平台原型开发简单的 2D 游戏。在本文中,我们将创建 Starts Fighter 游戏,以此作为使用游戏引擎在短时间内创建游戏的简单示例。
背景
要理解本文,您需要了解通用编程基础以及简单的 2D 游戏开发背后的基本概念,例如(图像、精灵和碰撞检测)。此外,还需要对 Ring 编程语言有基本了解(或至少有使用类似动态脚本语言(如 Python 和 Ruby)的经验)。
游戏引擎使用 Allegro 游戏编程库进行桌面开发,并使用 LibSDL 库进行移动开发。您将编写一次代码,无需直接与这些库交互,因为引擎提供了一个简单的层,让您可以专注于游戏本身。
查看关于 Ring 的后续文章
(1) Ring 编程语言
(2) Ring 语言的语法灵活性
以及接下来的教程
(1) 入门
(2) 控制结构
(3) 函数
(4) 列表
(5) 类
(6) 函数式编程
(7) 声明式编程
使用代码
游戏开始时会显示一个屏幕,上面显示游戏标题和版本。
我们将有背景音乐,一旦用户按下空格键、用鼠标单击或触摸移动屏幕,游戏就会开始。
首先,我们将使用一个全局变量(oGameState)。我们将游戏状态存储在这个对象中。
要开始使用游戏引擎,我们将使用: Load "GameEngine.ring"
然后我们将定义主函数,创建游戏对象并启动 while 循环。
我们将使用以下类:
sprite | : 创建新的 Sprite 对象并将其添加到游戏对象中。 |
文本 | : 创建新的 Text 对象并将其添加到游戏对象中。 |
sound | : 创建新的 Sound 对象并将其添加到游戏对象中。 |
oGameState = NULL
Load "gameengine.ring"
func main
oGame = New Game
while true
oGameState = new GameState
oGame {
title = "Stars Fighter!"
sprite
{
file = "images/menu1.jpg"
x = 0 y=0 width=800 height = 600 scaled = true animate = false
keypress = func ogame,oself,nKey {
if nkey = key_esc or nKey = GE_AC_BACK
ogame.shutdown()
but nKey = key_space
oGameState.startplay=true
ogame.shutdown=true
ok
}
mouse = func ogame,oself,nType,aMouseList {
if nType = GE_MOUSE_UP
oGameState.startplay=true
ogame.shutdown=true
ok
}
}
text {
animate = false
size = 35
file = "fonts/pirulen.ttf"
text = "Stars Fighter"
x = 10 y=50
}
text {
animate = false
size = 25
file = "fonts/pirulen.ttf"
text = "Version 1.0"
x = 80 y=100
}
text {
animate = false
size = 16
file = "fonts/pirulen.ttf"
text = "(C) 2016, Mahmoud Fayed"
x = 45 y=140
}
text {
animate = false
size = 25
file = "fonts/pirulen.ttf"
text = "Press Space to start"
x = 190 y=470
}
text {
animate = false
size = 20
file = "fonts/pirulen.ttf"
text = "Press Esc to Exit"
x = 260 y=510
}
Sound {
file = "sound/music1.wav"
}
}
if oGameState.startplay
oGame.refresh()
playstart(oGame)
oGame.refresh()
ok
end
Text 类需要文本位置(x,y)、字体文件名(*.ttf)和字体大小。
Sound 类需要声音文件名(*.wav)。
Sprite 类提供了更多属性。我们有 (x,y,width 和 height) 用于定位。
我们还有其他属性,例如:
图像 | : String 确定图像文件名。 |
点 | : Number 确定对象的自动移动限制。 |
direction | : Number 确定移动方向。 |
nstep | : Number 确定移动过程中的增量/减量。 |
type | : Number 确定游戏中的对象类型(可选)。 |
transparent | : True/False 值决定图像是否透明。 |
Sprite 类也通过继承获得以下属性:
enabled | : True/False 决定对象的状态(激活/未激活)。 |
x | : Number 决定对象的 x 坐标。 |
是 | : Number 决定对象的 y 坐标。 |
width | : Number 决定对象的宽度。 |
height | : Number 决定对象的高度。 |
nIndex | : Number 确定对象在对象列表中的索引。 |
animate | : True/False 是否为对象设置动画。 |
move | : True/False 是否使用键盘移动对象。 |
Scaled | : True/False 是否缩放对象图像。 |
绘制。 | : 对象绘制时要调用的函数。 |
状态 | : 对象动画时要调用的函数。 |
keypress | : 按键时要调用的函数。 |
鼠标 | : 鼠标事件发生时要调用的函数。 |
用户可以再次玩游戏。
在接下来的代码中,我们有一个 while 循环,条件为真(将永远工作,直到我们退出)。
要退出循环,我们检查 Game 对象 (oGame) 中的 shutdown 属性。
当用户再次玩游戏时,我们调用 refresh() 方法。
在每场游戏之后,我们使用 callgc() 函数强制调用垃圾回收器。
在游戏结束时,我们使用 delete() 方法删除声音对象。
func playstart oGame
oSound = New Sound {
file = "sound/music2.wav"
}
while true
play(oGame)
if ogame.shutdown = true and oGameState.value = 0
exit
ok
ogame.refresh()
callgc()
end
oSound.Delete()
以下代码用于玩游戏。
我们有 30 个关卡要通过,每个关卡提供与关卡号相同的敌人数量。
您可以使用箭头键(上、下、左、右)移动,并用空格键射击敌人。
在移动设备上玩游戏时,触摸任何位置,游戏将检查方向并向该方向移动(一次一步)。要射击,请触摸玩家本身。您可以在桌面上测试此功能并使用鼠标玩。
您在整个游戏中拥有 100 点能量。每个击中您玩家的敌人将使您的能量减少 10 点。
每个精灵都有 state 属性,我们将一个匿名函数分配给这个属性,该函数将从游戏引擎循环中调用,在每次帧更新之前。
state 函数将接收两个参数:游戏引擎对象和精灵对象。使用这两个参数,我们可以更新游戏状态。
使用游戏引擎对象,可以在运行时向游戏中添加和删除新对象(开火)。
func play oGame
oGame
{
FPS = 60
FixedFPS = 120
title = "Stars Fighter!"
sprite
{
file = "images/stars.jpg"
x = 0
y = 0
point = -370
direction = ge_direction_dec
type = ge_type_background
state = func ogame,oself {
oself {
if x < -350
direction = ge_direction_inc
point = 370
but x = 0 and direction = ge_direction_inc
direction = ge_direction_dec
point = -370
ok
}
}
}
sprite
{
file = "images/player.png"
transparent = true
type = ge_type_player
x = 400 y =400 width=100 height=100
animate=false move=true Scaled=true
mouse = func ogame,oself,nType,aMouseList {
if not ( aMouseList[GE_MOUSE_X] >= oSelf.x and aMouseList[GE_MOUSE_X] <= oSelf.x+oSelf.width and
aMouseList[GE_MOUSE_Y] >= oself.y and aMouseList[GE_MOUSE_Y] <= oSelf.y+oSelf.height )
if nType = GE_MOUSE_DOWN
if aMouseList[1] < oSelf.X # left
oSelf.X -= 100
else
oSelf.X += 100
ok
if aMouseList[2] < oSelf.Y # up
oSelf.Y -= 100
else
oSelf.Y += 100
ok
ok
else
if nType = GE_MOUSE_UP
cFunc = oself.keypress
call cFunc(oGame,oSelf,Key_Space)
ok
ok
}
keypress = func oGame,oself,nkey {
if nkey = key_space
ogame {
sprite {
type = ge_type_fire
file = "images/rocket.png"
transparent = true
x = oself.x + 30
y = oself.y - 30
width = 30
height = 30
point = -30
nstep = 20
direction = ge_direction_decvertical
state = func oGame,oSelf {
for x in oGame.aObjects
if x.type = ge_type_enemy
if oself.x >= x.x and oself.y >= x.y and
oself.x <= x.x + x.width and
oself.y <= x.y + x.height
showfire(oGame,x.x+40,x.y+40)
ogame.remove(x.nindex)
oGameState.score+=10
oGameState.enemies--
checkwin(oGame)
exit
ok
ok
next
}
}
}
but nkey = key_esc or nKey = GE_AC_BACK ogame.shutdown()
ok
}
state = func oGame,oSelf {
oself {
if x < 0 x = 0 ok
if y < 0 y = 0 ok
if x > ogame.screen_w-width x= ogame.screen_w - width ok
if y > ogame.screen_h-height y=ogame.screen_h-height ok
}
}
}
for g = 1 to oGameState.enemies
sprite
{
type = ge_type_enemy
file = "images/enemy.png"
transparent = true
x = g*random(50) y =g width=100 height=100
animate=true Scaled=true
direction = ge_direction_random
state = func oGame,oSelf {
oself {
if x < 0 x = 0 ok
if y < 0 y = 0 ok
if x > ogame.screen_w-width x= ogame.screen_w - width ok
if y > ogame.screen_h-height y=ogame.screen_h-height ok
}
if random(100) = 1
ogame {
sprite {
type = ge_type_fire
file = "images/rocket2.png"
transparent = true
x = oself.x + 30
y = oself.y + oself.height+ 30
width = 30
height = 30
point = ogame.screen_h+30
nstep = 10
direction = ge_direction_incvertical
state = func oGame,oSelf {
x = oGame.aObjects[oGameState.playerindex]
if oself.x >= x.x and oself.y >= x.y and
oself.x <= x.x + x.width and
oself.y <= x.y + x.height
if oGameState.value > 0
oGameState.value-=10
ok
ogame.remove(oself.nindex)
checkgameover(oGame)
ok
}
}
}
ok
}
}
next
text {
size = 30
file = "fonts/pirulen.ttf"
text = "Destroy All Enemies!"
nstep = 3
color = GE_COLOR_GREEN
x = 100 y=50
direction = ge_direction_incvertical
point = 500
}
text {
animate = false
point = 400
size = 30
file = "fonts/pirulen.ttf"
text = "Score : " + oGameState.score
x = 500 y=10
state = func oGame,oSelf { oSelf { text = "Score : " + oGameState.score } }
}
text {
animate = false
point = 400
size = 30
file = "fonts/pirulen.ttf"
text = "Energy : " + oGameState.value
x = 500 y=50
state = func oGame,oSelf { oSelf { text = "Energy : " + oGameState.value } }
}
text {
animate = false
point = 400
size = 30
file = "fonts/pirulen.ttf"
text = "Level : " + oGameState.level
x = 500 y=90
}
}
以下函数用于游戏结束(检查我们是否有获胜者或游戏结束)。
func checkwin ogame
if oGameState.gameresult return ok
if oGameState.enemies = 0
oGameState.gameresult = true
oGame {
if oGameState.level < 30
text {
point = 400
size = 30
file = "fonts/pirulen.ttf"
text = "Level Completed!"
nStep = 3
x = 500 y=10
state = func ogame,oself {
if oself.y >= 400
ogame.shutdown = true
oGameState.level++
oGameState.enemies = oGameState.level
oGameState.gameresult = false
ok
}
}
else
text {
point = 400
size = 30
nStep = 3
file = "fonts/pirulen.ttf"
text = "You Win !!!"
x = 500 y=10
state = func ogame,oself {
if oself.y >= 400
ogame.shutdown = true
oGameState.value = 0
ok
}
}
ok
}
ok
func checkgameover ogame
if oGameState.gameresult return ok
if oGameState.value <= 0
oGameState.gameresult = true
oGame {
text {
point = 400
size = 30
nStep = 3
file = "fonts/pirulen.ttf"
text = "Game Over !!!"
x = 500 y=10
state = func ogame,oself {
if oself.y >= 400
ogame.shutdown = true
ok
}
}
}
showfire(oGame,oGame.aObjects[oGameState.PlayerIndex].x+40,oGame.aObjects[oGameState.PlayerIndex].y+40)
oGame.aObjects[oGameState.PlayerIndex].enabled = false
oGame.remove(oGameState.PlayerIndex)
ok
以下函数用于开火。
此函数使用 animate 类,该类包含以下属性:
frames | : Number 确定帧数。 |
frame | : Number 确定当前帧。 |
framewidth | : Number 确定帧的宽度。 |
animate | : True/False 决定是否使用动画。 |
scaled | : True/False 决定是否缩放图像。 |
该函数使用 remove() 方法在显示开火图像后从游戏中移除对象。
func showfire oGame,nX,nY
oGame {
animate {
file = "images/fire.png"
x = nX
y = nY
framewidth = 40
height = 42
nStep = 3
transparent = true
state = func oGame,oSelf {
oSelf {
nStep--
if nStep = 0
nStep = 3
if frame < 13
frame++
else
frame=1
oGame.remove(oself.nIndex)
ok
ok
}
}
}
}
以下是游戏中关卡(1)的屏幕截图。
以下是游戏状态的类。
我们有 score 属性(在玩家杀死敌人后增加)。
我们有 level 属性(从 1 到 30)。
我们有 enemies count(开始时等于关卡数,然后减少直到玩家通过关卡)。
我们有 playerindex,玩家对象在游戏引擎对象数组/列表中的位置。
我们有 GameResult 属性,决定我们是否有结果(赢/游戏结束)还是没有(仍在玩)。
我们有 StartPlay 属性,决定游戏是否已开始。
class gamestate
score = 0
level = 1
enemies = 1
value = 100
playerindex = 2
gameresult = false
startplay=false
关注点
游戏在很短的时间内开发完成,我在同一天为我的朋友设计、开发并发布了游戏。后来,我进行了一些微小的改进(没什么难的,这只是一个简单的示例)。
要为游戏构建 Android 包(*.apk),请查看此教程。
历史
本文写于 2016.10.12,旨在提供一个简单的示例,说明如何使用 Ring 编程语言通过 Ring 提供的游戏引擎进行简单的 2D 游戏开发,作为演示项目。