Introduction
The Ring Programming Language comes with a simple 2D Game engine (written in Ring) for prototyping simple 2D Games for Desktop and Mobile platforms, In this article we will create the Starts Fighter Game as a simple example about using the Game Engine to create games in little hours.
Background
To understand this article you need to know about programming basics in general and the simple concepts behind simple 2D Games development like (Images, Sprites and Collision detection). Also basic understanding of the Ring programming language is required (or at least experience from using a similar dynamic scripting language like Python and Ruby).
The game engine uses the Allegro game programming library for desktop development and the LibSDL library for Mobile Development. You will write the code once without direct interaction with these libraries where the engine provide a simple layer to concentrate on the game itself.
Check the next articles about Ring
(1) The Ring Programming Language
(2) Syntax Flexibility in the Ring Language
And the next Tutorials
(1) Getting Started
(2) Control Structures
(3) Functions
(4) Lists
(5) Classes
(6) Functional Programming
(7) Declarative Programming
Using the code
The game start by providing a screen that display the game title and version.
We will have music in the background and once the user press the space key, click using mouse or touch the mobile screen the game will start.
At first we will use a global variable (oGameState), We will store the game state in this object.
To start using the game engine we will using : Load "GameEngine.ring"
Then we will define the main function, create the game object and start the while loop
We will use the next classes
sprite | : Create new Sprite object and add it to the game objects. |
text | : Create new Text object and add it to the game objects. |
sound | : Create new Sound object and add it to the game objects |
| |
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
The text class requires the text positon (x,y) and the font file name (*.ttf) and the font size
The sound class requires the sound file name (*.wav)
The Sprite class provides more attributes. We have (x,y,width and height) for position.
We have other attributes like
image | :String determine the image file name. |
point | : Number determine the limit of automatic movement of the object. |
direction | : Number determine the direction of movement. |
nstep | : Number determine the increment/decrement during movement. |
type | : Number determine the object type in the game (Optional). |
transparent | : True/False value determine if the image is transparent. |
The sprite class get the next attributes by inheritance too
enabled | : True/False determine the state of the object (Active/Not Active) |
x | : Number determine the x position of the object. |
y | : Number determine the y position of the object. |
width | : Number determine the width of the object. |
height | : Number determine the height of the object. |
nIndex | : Number determine the index of the object in objects list. |
animate | : True/False to animate the object or not. |
move | : True/False to move the object using the keyboard or not. |
Scaled | : True/False to scale the object image or not. |
draw | : Function to be called when drawing the object. |
state | : Function to be called for object animation. |
keypress | : Function to be called when a key is pressed. |
mouse | : Function to be called when a mouse event happens. |
The user can play again
In the next code we have a while loop, the condition is true (will work foreever until we exit from it)
To exit from the loop we check the shutdown attribute in the Game Object (oGame)
When the user play again, we call the referesh() method
After each game we force calling the Garbage Collector using the callgc() function
In the end of the game we delete the sound object using the delete() method
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()
The next code for playing the game.
We have 30 levels to pass, each level provide number of enemies as the level number.
You can move using arrows (Up, Down, Left and Right) and you can shoot the enemy using Space
When playing using the mobile touch any location and the game will check the direction and move toward that direction (one step only). to shoot touch the player itself. You can test this on desktop and play using the mouse.
You have energy (100) for the complete game. Each enemy hit your player will decrease your energy by 10.
Each sprite have the state attribute, we assign an anonymous function to this attribute and this function will be called from the game engine loop before each frame update.
The state function will get two parameters, The Game Engine Object and the Sprite Object, Using the two parameters we can update the game state.
Using the Game Engine Object and during the runtime new objects are added and deleted from the game (Fire).
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
}
}
The next functions for the end of the game (Check if we have a winner or a Game Over).
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
The next function for fire
This function uses the animate class which contains the next attributes
frames | : Number determine the number of frames |
frame | : Number determine the active frame |
framewidth | : Number determine the frame width. |
animate | : True/False determine using animate or not. |
scaled | : True/False determine scaling image or not. |
The function uses the remove() method to remove the object from the game after displaying the fire images.
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
}
}
}
}
The next screen shot for the level (1) in the game.
The next class for the game state
We have the score attribute (increased after the player kill an enemy)
We have the level attribute (from 1 to 30)
We have the enemies count (start as the level count then decreased until the player pass the level)
We have the playerindex, The location of the player object in the game engine objects array/list.
We have the GameResult attribute, determine if we have a result (win/game over) or not (still playing)
We have the StartPlay attribute, Determine if the game started or not.
class gamestate
score = 0
level = 1
enemies = 1
value = 100
playerindex = 2
gameresult = false
startplay=false
Points of Interest
The game is developed in little hours, I designed, developed and released the game for my friends in the same day, Latter along the time I added very little improvements (No hard work, It's just a simple sample).
To build the Android Package (*.apk) for the game, Check this tutorial
History
This article is written in 2016.10.12 to provide a simple example about using the Ring programming language for simple 2D Games development using the Game Engine provided by Ring as a demo project.