Intro
Have you ever attempted to program that project of your dreams, and ended up with 10'000 lines of spagetti madness? Have you ever had a program that became more and more complex the more you added to it? With this guide I hope to introduce you to a very structured style of programming which guarantees success with larger projects.
We begin with the
5 HUGE RULES. These rules
can not, under any circumstance, be violated.
5 HUGE RULES
1) You must only have one main loop in your program. If you notice you have a loop for your menu, a loop for your game, a loop for your highscores etc. then you're doing it the wrong way.
2) Your main loop must only contain functions, and be command-free (except cls, sync, set cursor, and a few other very basic commands).
3) For every entity (be it a unit, effect, console, etc.) you must have a seperate source file containing functions. The structure of these functions are explained later in this guide.
4)
I DO NOT want to see any numbers for things you load. Use constants. This will be explained later on in this guide.
Wrong : load object "tank.x",1
Right : load object "tank.x",TankOBJ
5)
ALWAYS use User Defined Types (UDTs). This will be explained later on in this guide.
Code Template
Whenever you start a new project, and you want to use this method of programming, I advise you to just copy/paste the following template:
rem ----------------------------------------------
rem <project name>
rem ----------------------------------------------
rem Start Date :
rem End Date :
rem ----------------------------------------------
rem Author :
rem ----------------------------------------------
rem setup screen
sync on
sync rate 60
backdrop on
color backdrop 0
rem ----------------------------------------------
rem Constants
rem ----------------------------------------------
rem true/false
#constant false 0
#constant true 1
rem maximum values
rem IDs
rem ----------------------------------------------
rem User Defined Types
rem ----------------------------------------------
rem control types
type ctrlVT
endtype
rem ----------------------------------------------
rem Variables
rem ----------------------------------------------
rem control variable
global ctrl as ctrlVT
rem ----------------------------------------------
rem Arrays
rem ----------------------------------------------
rem ----------------------------------------------
rem Set Values
rem ----------------------------------------------
rem enable all controls
rem ----------------------------------------------
rem Load Images
rem ----------------------------------------------
rem ----------------------------------------------
rem Load Sounds
rem ----------------------------------------------
rem ----------------------------------------------
rem Load Music
rem ----------------------------------------------
rem ----------------------------------------------
rem main loop
rem ----------------------------------------------
rem start of main loop
repeat
rem reset cursor (so we can print debug info to the screen if needed)
set cursor 0,0
rem control game elements
Control_Game_Elements()
rem refresh screen
sync
rem end of main loop
until escapekey()=1
rem end the program
end
rem -----------------------------------------------
rem functions
rem -----------------------------------------------
rem control game elements
function Control_Game_Elements()
rem your control code goes here.
rem make sure you only put functions here.
endfunction
Constants, UDTs, Variables and Arrays
This section will wrap up rule #4 and rule #5 : Use constants where possible, and always use UDTs. First a few norms that you should use:
When making a constant or UDT, try to add some kind of "ID" to the name you give it. For example, all maximum values have a "Max" at the end, all IDs have an "ID" at the end, all images have an "IMG" at the end:
rem maximum values
#constant TankPlayerMax 10
#constant BulletMax 1000
#constant SpeedMax 15
rem IDs
#constant TankPlayerID 1
#constant BulletID 2
#constant EnemyID 3
rem images
#constant BulletIMG 1
#constant FireIMG 2
#constant ExplosionIMG 3
When making UDTs, you should have either a "VT", or an "AT" at the end of the name. "VT" stands for "variable type", and "AT" stands for "array type". Doing it this way tells us two things : We know that it's a UDT, and we know what it will be used for an array or a varaible. Examples:
rem vector 3
type vec3VT
x# as float
y# as float
z# as float
endtype
rem Bullet
type bulletAT
active as integer
pos as vec3VT : `notice, vec3VT is used for a variable, not an array.
life as integer
endtype
rem arrays -----------
dim Bullet(BulletMax) as BulletAT : `Again, notice that BulletAT was used for an array.
It is essential you follow those norms.
Let's say we want to add a player in form of a 3D Tank (because tank games are awesome). To do so, we need to define the following things:
-Maximum number of tanks
-Unique ID
-Control value
-UDT for tank
-array for the tank
Maximum values and the ID go at the top with the constants:
rem maximum values
#constant TankPlayerMax 10
rem IDs
#constant TankPlayerID 1
The control value goes in with the UDT
ctrlVT:
type ctrlVT
TankPlayer as boolean
endtype
Then we create a UDT for the tank. We need the position, angle, input buttons and the active value (explained later on in the section "Adding entities"). So:
rem vector 3 type
type vec3VT
x# as float
y# as float
z# as float
endtype
rem button input type
type btnVT
up as byte
down as byte
left as byte
right as byte
endtype
rem tank player type
type TankPlayerAT
active as integer
pos as vec3VT
btn as btnVT
angle# as float
endtype
And last but not least, the array for the tank:
dim TankPlayer(TankPlayerMax) as TankPlayerAT
The whole code will look like this in the end:
rem ----------------------------------------------
rem <project name>
rem ----------------------------------------------
rem Start Date :
rem End Date :
rem ----------------------------------------------
rem Author :
rem ----------------------------------------------
rem setup screen
sync on
sync rate 60
backdrop on
color backdrop 0
rem ----------------------------------------------
rem Constants
rem ----------------------------------------------
rem true/false
#constant false 0
#constant true 1
rem maximum values
#constant TankPlayerMax 10
rem IDs
#constant TankPlayerID 1
rem ----------------------------------------------
rem User Defined Types
rem ----------------------------------------------
rem control types
type ctrlVT
TankPlayer as boolean
endtype
rem vector 3 type
type vec3VT
x# as float
y# as float
z# as float
endtype
rem button input type
type btnVT
up as byte
down as byte
left as byte
right as byte
endtype
rem tank player type
type TankPlayerAT
active as integer
pos as vec3VT
btn as btnVT
angle# as float
endtype
rem ----------------------------------------------
rem Variables
rem ----------------------------------------------
rem control variable
global ctrl as ctrlVT
rem ----------------------------------------------
rem Arrays
rem ----------------------------------------------
dim TankPlayer(TankPlayerMax) as TankPlayerAT
rem ----------------------------------------------
rem Set Values
rem ----------------------------------------------
rem enable all controls
ctrl.TankPlayer=true
rem ----------------------------------------------
rem Load Images
rem ----------------------------------------------
rem ----------------------------------------------
rem Load Sounds
rem ----------------------------------------------
rem ----------------------------------------------
rem Load Music
rem ----------------------------------------------
rem ----------------------------------------------
rem main loop
rem ----------------------------------------------
rem start of main loop
repeat
rem reset cursor (so we can print debug info to the screen if needed)
set cursor 0,0
rem control game elements
Control_Game_Elements()
rem refresh screen
sync
rem end of main loop
until escapekey()=1
rem end the program
end
rem -----------------------------------------------
rem functions
rem -----------------------------------------------
rem control game elements
function Control_Game_Elements()
rem your control code goes here.
rem make sure you only put functions here.
endfunction
When loading media, you have to define it at the top among the constants:
rem image constants
#constant BulletIMG 1
#constant ShipIMG 2
#constant BackgroundIMG 3
`...
rem load images
load image "media\images\bullet.png",BulletIMG
load image "media\images\ship.png",ShipIMG
load image "media\images\stars.png",BackgroundIMG
`...
paste image BackgroundIMG,0,0
sprite 1,50,50,BulletIMG
sprite 2,100,100,ShipIMG
This makes it a lot easier to 1) keep track of what you've loaded and 2) remember the index value without having to look for the part where you loaded the image, remember the number, and jump back to where you were to write it into your code.
Adding entities
So let's start with adding things to your code. I'll do this with an example. Let's say we want to add a player in form of a 3D tank (because tank games are awesome).
Rule #3 requires that we make a seperate source file containing functions. The structure of these functions are as follows:
Create
Destroy
Control
Those are the fundamentals of creating an entity using this structure: You
create an entity with a function, you
destroy an entity with a function, and you
control an entity with a function. You can of course add more than just that later on, but for now we stick to the 3 basic functions. How does this look using our example?
CreateTankPlayer(x#,y#,z#,angle#,btn_up,btn_down,btn_left,btn_right)
DestroyTankPlayer(index)
ControlTankPlayer()
What I thought behind those functions is : Creating a player requires at least the position, the angle, and four buttons to move around. The buttons will just be scancode numbers. Destroying a player requires an index so the function knows what to destroy. Controlling the player must be called once every loop. So let's go ahead and examine the first function. Here the entire code:
function CreateTankPlayer(x#,y#,z#,angle#,btn_up,btn_down,btn_left,btn_right)
rem local variables
local n as integer
rem find free slot for player
for n=0 to MaxTankPlayer
if TankPlayer(n).active<2 then exit
next n
rem no free slot found
if n=MaxTankPlayer+1 then exitfunction -1
rem if active value is 0, player has never been created before
if TankPlayer(n).active=0
rem create player
TankPlayer(n).obj=find free object()
load object "media\models\tank.x",TankPlayer(n).obj
endif
rem set object properties
position object TankPlayer(n).obj,x#,y#,z#
yrotate object TankPlayer(n).obj,angle#
show object TankPlayer(n).obj
rem set parameters
TankPlayer(n).active=2
TankPlayer(n).pos.x#=x#
TankPlayer(n).pos.y#=y#
TankPlayer(n).pos.z#=z#
TankPlayer(n).btn.up=btn_up
TankPlayer(n).btn.down=btn_down
TankPlayer(n).btn.left=btn_left
TankPlayer(n).btn.right=btn_right
TankPlayer(n).angle#=angle#
endfunction n
rem find free slot for player
for n=0 to MaxTankPlayer
if TankPlayer(n).active<2 then exit
next n
rem no free slot found
if n=MaxTankPlayer+1 then exitfunction -1
This section will count through the array
Player(n) until it finds one that has an active value smaller than
2. If it doesn't find a free slot, it will exit the function immediately and return a value of
-1 (meaning it failed). Let's talk a bit about active values, and what they mean:
0-never used
1-inactive
2-active state 1
3-active state 2
4-active state 3
5-active state 4
...
If an entity has never been used before, it's active state will of course be 0. So the next thing to do is load it:
rem if active value is 0, player has never been created before
if TankPlayer(n).active=0
rem create player
TankPlayer(n).obj=find free object()
load object "media\models\tank.x",TankPlayer(n).obj
endif
After that we set some object properties, and save the values passed through the function into the array:
rem set object properties
position object TankPlayer(n).obj,x#,y#,z#
yrotate object TankPlayer(n).obj,angle#
show object TankPlayer(n).obj
rem set parameters
TankPlayer(n).active=2
TankPlayer(n).pos.x#=x#
TankPlayer(n).pos.y#=y#
TankPlayer(n).pos.z#=z#
TankPlayer(n).btn.up=btn_up
TankPlayer(n).btn.down=btn_down
TankPlayer(n).btn.left=btn_left
TankPlayer(n).btn.right=btn_right
TankPlayer(n).angle#=angle#
Note that here we set the active value to a value of
2, which means this entity is now active. This last section of code returns the index value of the entity:
An example on how to use this now would be something like this:
Player1 = CreateTankPlayer(0,0,0,0,201,203,205,208)
The index of the entity is stored in the variable
Player1. We can access the player later on in the game by using that value.
Next up is the Destroy function:
function DestroyTankPlayer(index)
rem if player is inactive, exit
if TankPlayer(index).active<2 then exitfunction -1
rem destroy player
hide object TankPlayer(index).obj
TankPlayer(index).active=1
endfunction n
If the passed index value of the player isn't active, the function exits and returns a value of
-1 indicating that it failed. If the player is active, it hides the object(s) affected, and sets the active value to
1, which means "inactive". You're probably asking why I don't just delete the object, and not have this complicated system where 0 means never used, 1 means used, and 2 means active. The reason is : speed. It could be that your tank is some really high-poly model that takes 5 seconds to load. Hiding and showing it is much easier than deleting and then reloading it again. An example on how to use this function would be something like this:
success = DestroyTankPlayer(Player1)
The last, and probably easiest part, is the
control section. This function is called once every loop, and controls how our tank behaves in the game. Here's the entire code:
function ControlTankPlayer()
rem local variables
local n as integer
rem loop through all active entities
for n=0 to MaxTankPlayer
select TankPlayer(n).active
rem tank is active
case 2
rem move tank around
if keystate(TankPlayer(n).btn.up) then move object TankPlayer(n).obj,2
if keystate(TankPlayer(n).btn.down) then move object TankPlayer(n).obj,-2
if keystate(TankPlayer(n).btn.left) the TankPlayer(n).angle#=wrapvalue(TankPlayer(n).angle#+3)
if keystate(TankPlayer(n).btn.right) then TankPlayer(n).angle#=wrapvalue(TankPlayer(n).angle#-3)
rem kill tank with spacekey (lol?)
if spacekey() then TankPlayer(n).active=3
rem get tank positions
TankPlayer(n).pos.x#=object position x(TankPlayer(n).obj)
TankPlayer(n).pos.y#=object position y(TankPlayer(n).obj)
TankPlayer(n).pos.z#=object position z(TankPlayer(n).obj)
rem update tank
yrotate object TankPlayer(n).obj,TankPlayer(n).angle#
endcase
rem tank is dying
case 3
rem continuously rotate tank to show it's dying
TankPlayer(n).angle#=wrapvalue(TankPlayer(n).angle#+10)
rem update tank
yrotate object TankPlayer(n).obj,TankPlayer(n).angle#
endcase
endselect
next n
endfunction
Here we can control all entities that are active. Notice how I use the
active value as a holder for what state the entity currently is in. This is known as
state-event based programming. If you don't know what this is, I urge you to google it, as it is vital to know. Here is a small example demonstrating what it is:
rem possible states
#constant idle 1
#constant walk 2
#constant run 3
#constant jump 4
rem state variable. Here we store the current state
global state
rem main loop
do
rem control player
select state
rem player is in idle
case idle
rem [play idle animation]
rem if user presses left or right to move, change state
if leftkey()=1 or rightkey()=1 then state=walk
rem jump
if spacekey()=1
player_landed=0
state=jump
endif
endcase
rem player is walking
case walk
rem [play walk animation]
rem move player
if leftkey()=1 then dec posx
if rightkey()=1 then inc posx
rem run
if controlkey()=1 then state=run
rem stop
if leftkey()=0 and rightkey()=0 then state=idle
rem jump
if spacekey()=1
player_landed=0
state=jump
endif
endcase
rem player is running
case run
rem [play run animation]
rem move player
if leftkey()=1 then dec posx,2
if rightkey()=1 then inc posx,2
rem walk again
if controlkey()=0 then state=walk
rem stop
if leftkey()=0 and rightkey()=0 then state=idle
rem jump
if spacekey()=1
player_landed=0
state=jump
endif
endcase
rem jump
case jump
rem [play jump animation]
rem if player lands, return to idle
if player_landed=1 then state=idle
endcase
endselect
loop
This is a powerful way of controlling things in your game if it has multiple states. You end up writing a little more than you'd normally write, but you eliminate the problem of the player getting confused and trying to jump and idle at the same time, because it can only have 1 state at any given time, and this structure makes it really simple to add and remove features.
It is essential you understand the concept of state-event based programming, and it is essential you understand how to correctly add entities using this method before we carry on.
The Main Loop
According to rule #1, we are only allowed one loop in our program. Behold, the main loop:
rem ----------------------------------------------
rem main loop
rem ----------------------------------------------
rem start of main loop
repeat
rem reset cursor (so we can print debug info to the screen if needed)
set cursor 0,0
rem control game elements
Control_Game_Elements()
rem refresh screen
sync
rem end of main loop
until escapekey()=1
rem end the program
end
rem -----------------------------------------------
rem functions
rem -----------------------------------------------
rem control game elements
function Control_Game_Elements()
rem your control code goes here.
rem make sure you only put functions here.
endfunction
This is the heart of any program, because it runs in a loop and stops the program from ever ending (or until the user presses the escapekey, but lets ignore that). So lets go ahead with our tank example and add it to the main loop:
rem -----------------------------------------------
rem functions
rem -----------------------------------------------
rem control game elements
function Control_Game_Elements()
rem control game elements
if ctrl.TankPlayer=true then ControlTankPlayer()
endfunction
I've added the line
if ctrl.TankPlayer=true then ControlTankPlayer()
This gives me the power to disable all control code to all tanks by just changing one variable. Just to show how easy this is, I'll make an example. Let's say you've been working on your tank game, and have something like this in your
Control_Game_Elements() function:
rem control game elements
function Control_Game_Elements()
rem control game elements
if ctrl.TankPlayer=true then ControlTankPlayer()
if ctrl.Enemy =true then controlEnemy()
if ctrl.Menu =true then ControlMenu()
if ctrl.highscore =true then ControlHighscore()
if ctrl.bullet =true then controlBullet()
endfunction
You can easily change game modes by writing functions such as:
function PauseGame()
ctrl.TankPlayer=false
ctrl.Enemy =false
ctrl.Menu =true
ctrl.highscore =false
ctrl.bullet =false
CreateMenu("Pause")
endfunction
function SetModeGame()
ctrl.TankPlayer=true
ctrl.Enemy =true
ctrl.Menu =false
ctrl.highscore =false
ctrl.bullet =true
endfunction
function setModeMenu()
ctrl.TankPlayer=false
ctrl.Enemy =false
ctrl.Menu =true
ctrl.highscore =false
ctrl.bullet =false
endfunction
function SetModeHighscore()
ctrl.TankPlayer=false
ctrl.Enemy =false
ctrl.Menu =true
ctrl.highscore =true
ctrl.bullet =false
endfunction
Outro
I hope this was helpful for you and I hope this small guide can improve your coding structure so you don't end up with that huge bowl of spagetti in the end
If you have any suggestions for improvement, feedback, things I should add/mention for this guide, please post
TheComet