Click here to play X-NON  


    The making of X-NON

    

    Created by Teasy


Introduction What you will find here are ways to create a game, helpful methods and techniques to guide your way, and examples of organization and implementation all leading up to an end-result to your liking. Usually, before you create something you need to know at least a little bit what you're going to create. The more you know about the creation process, the less obstacles you will find on your way. Even if you are not (entirely) familiar with , or if you're just a little smart or a fairly quick learner, you won't have any difficulty following this tutorial I've organized this tutorial a bit like an adventure. There is an outline of what I want, and what I want to achieve is clear, but the paths I choose are completely unset and vary wildly to make the journey a lot more interesting
Knowledge reference Here's a list of things that might be useful before or after commencing the attack on this tutorial T-C's Blitz tutorials overview Language orientation, section guide & overview with links. http://www.bettiesart.com/tc/blitz/ BlitzBasic Full Foundation tutorial Includes everything you'd ever want to know about: - variables - conditions - loops - collections - routines - code constructs http://www.bettiesart.com/tc/blitz/blitz.html Relative positioning in 10 steps Geared towards increasing your awareness of relative coordinates. Using a unique what-you-see-is-what-it-is tutorial style. http://www.bettiesart.com/tc/blitz/relative.html Project Z: One A simple and short game tutorial especially written to give a good example of: - 2D-motion (vectors) - images created at run-time - variable game-flow and control - simple random handling - variable collection handling - simple artificial intelligence - score and difficulty handling - simple heads-up display - simple collision detection - keyboard and mouse control This game comes in various flavors, but each of them is simply a single source code file. http://www.bettiesart.com/tc/pz1/ Vectors and Sine waves This is an in-depth tutorial about motion in 2D space, and explains the ways of the sine wave as well. Also included are various demonstration programs such as "Asteroids"-style space physics. http://www.bettiesart.com/tc/blitz/vectors.html Value conversion tutorial In-depth explanation of value offsets and scales, which can be applied to countless scenarios. This also uses the what-you-see-is-what-it-is tutorial style. http://www.bettiesart.com/tc/blitz/conv.html Project Z: Two An advanced and in-depth game tutorial with many cool features, such as: - smooth space physics - scrolling multi-layered stars - simple game menus - dynamic heads-up display - variable levels and difficulties - special rotated rendered images - simple sound and channel handling - color fading using patterns - RGB color handling - customized text routines - frame limiting - intermediate collision detection - separated code construction - advanced game-flow - multi-level control states - sequence handling - intermediate A.I. - embedded media - variable game speed - advanced timing techniques - virtual screen-edge wrapping - screen-edge wrap collisions - intermediate random handling - and various other tricks Like Project Z: One, this is a single source code file with everything directly included. http://www.bettiesart.com/tc/pz2/ Tilemaps tutorial Includes all the basics, support for accurate object motion, and a fast, optimized pixel-perfect collision detection algorithm. This is yet another single source code file with all in-depth explanations embedded, so some revealing parts are sneakily hidden inside http://www.bettiesart.com/tc/blitz/tile.html You can also use all of the above simply as reference, so you can look it up if there's something further on in the tutorial that gives you the creeps There's a bunch of files included in the tutorial package, such as images, audio and source code files to help you try out every step of the way. You can also copy and paste the code ofcourse, if you want. You can download the tutorial files separately (while reading online) for which each linky is provided throughout this tutorial. Or you can download all files together with the tutorial itself as a single package (for offline reading): The making of X-NON.zip. If you want to take a peek at all the files, go to the Tutorial files index. Or if you want go straight to the game that is the final product of this tutorial, take a look at the X-NON files index. And to look for a specific chapter in this tutorial, please refer to the Index.
Concept What I'm creating is a remake of a retro Amiga game called Gravity Force which is a 2D split-screen shooter for 2 players. Actually I was creating this game just to be able to show it to people to find out what the original Amiga game was called. I started by writing down everything I knew about the game, as detailed as I could. Visualizing your ideas for example by writing them down or making sketches can be a great help! To keep at it, I kept trying to remember the feeling that lead me to making this game. So basically, make it interesting for yourself! But keep it simple at first, you can always add more. I found it harder to simplify something that is complicated than making something simple more advanced.
Construct Games usually have some very distinctive parts like various types of media (graphics, sound, music), code and other game data (tilemaps, text). You can find a lot of a game's elements just by looking at the game's description: X-NON is a 2-player split screen shooter with 2D "Asteroids" physics and gravity.
Specification While I was thinking about the game, here's what I wrote down for X-NON on paper: Split screen:
,---------------.---------------.
|               |               |
|               |               |
|               |               |
|               |               |
|    Player 1   |   Player 2    |
|               |               |
|               |               |
|               |               |
|               |               |
`---------------^---------------'
Ships: One ship image for each player (red, green) Bullet: One bullet image Tileset: Earth ground with variations Various earth edges with variations Wall to mark starting area Vegetation on edges (green) Panning: Player 1 sound effects left Player 2 sound effects right Sounds:
Spawn           (loud)
Thrust          (soft)
Fire            (loud)
Bullet collide  (soft)
Explode         (loud)
Music: Game (background) music: A unique track based on the original Gravity Force tune Control:
      ,-----,
      | ^   |\
      |     | |                       Fire
      |     | |
,-----+-----+-----,
| <   | v   | >   |\
|     |     |     | |  Rotate Left - Thrust - Rotate Right
|     |     |     | |
t-----t-----t-----< |
 \     \     \     \|
  `-----^-----^-----'
Input: Player 1: W, S, A, D Player 2: Up, Down, Left, Right Physics: 2D "Asteroids" physics Downward gravity Bullet: Bullets fly in a straight line And disappear after a certain amount of time Collisions: Legend:
/\   Ship
o    Bullet
[]   Tile

X    Destroy
Scheme:
/\ + /\ = X
o  + /\ = X
[] + /\ = /\ X
[] + o  = o  X
Ship collides: Spawn small amount of bullets in random directions (like an explosion) And ship disappears Bullet collides: Bullet simply disappears Game control: For each player 2 game states: In wait mode: If player presses Thrust Then play mode In play mode: If player dies Then wait mode Background music: Played through entire game (looping) Sound: Shoot bullet sound, when player ship fires bullet Thrust ship sound, when player ship thrusts Explode ship sound, when player ship collides with tilemap Bullet collide sound, when bullet collides with tilemap Spawn ship sound, when player ship respawns You can make fancy event tables as well (e.g. cause, condition, effect) like you also see in easy game creation packages such as Game Maker. But I figured that making it more simple would give me results even more quickly. Use whichever methods and tools available to make the creation process easier for yourself.
Direction If you look at the list above (specifications), you could make a list of elements you can build into your game, just by simplifying the list. Graphics: Ships Bullet Tileset Sound: Sound effects Music: Background music Code: Split screen Sound panning Control/input (ship behavior) Gravity (physics) Bullet (behavior) Collisions (ship, bullet, tilemap) Music Sound Game control This can also gives a better outline of the game creation process. Now, there are probably some things missing or incomplete, but you can always modify this to your own liking, like I have done, unnoticeably You can variate the creation process. Like creating some simple ships, then implementing them. Making some wacky sounds inbetween. Make it interesting for yourself. I can't stress this enough, since I'm used to taking this way too seriously. Actually you can do a lot simultaneously, but doing too much at once isn't always good. If you get some ideas on the way, instead of rushing right into creating them, write them down for later. And you'll have something cool to look forward to as well Like a list with "things to do when I'm bored" Or you can just let any new ideas go if you know how to "re-sync" with that same vibe later for more new ideas to appear in your mind automagically, so you can keep a steady focus on the task at hand. So let's start by implementing one element at a time. Before implementing something in code you can also test it in a separate program, to see how and if it actually works, safely, without disturbing the existing code. This can also be useful for other things, such as sound, graphics, tilemaps, etc. I will focus mostly on the code itself. The detailed art, sound and music creation process I'll leave up to you, or perhaps in another tutorial Besides, there are also other people on this planet who can help out in those areas
Structure There are many ways to organize the code. For example, you could organize the code by type of data: - Definition - Constants - Objects (types) - Variables - Execution (main) - Sub Routines (functions) - Main Instead, I've chosen to sort the game code by section. For example placing all player-related code in the Player section. It doesn't matter if you're not sure which sections there will be yet. It's more important that you know how each section is divided. For example: - Section - Constants - Objects (types) - Global variables - Sub Routines (functions) Some sections, like Collisions, depend on different sub-sections, like for the Tilemap. This also means that some definitions, which another section depends on, have to be defined before being used. Though, in Blitz, only arrays have this requirement. So you can really mess around if you want to To give an example of how I can structure the code in this way, here are a few sections: (Definition) - Player - Constants: ship speed - Objects: player object - Globals: player references - Functions: creating a player - Tilemap - Constants: tile size - Globals: tile map (array) - Functions: load a tile map (Initialization) - Main initialization (Execution) - Main loop This structure is actually a simplified version of the structure I used in Project Z: Two. I will further clarify and expand on all of these in the upcoming chapters.
Graphics resolution In any scenario, one needs to set a resolution for the game. You could use a 3D sprites world, where the actual resolution doesn't have to relate to the size of the game objects, pixel-wise. But I've chosen to use a 2D pixel world because this was quick and easy. As the screen is split, it's better if the screen is wider, so there's better overview horizontally and vertically. So you can actually see what's coming from all directions equally Gravity Force was actually vertically split, having an upper and lower half of the screen for each player. This seemed unlogical to me because in most screen resolutions the playing area is more square when horizontally split, right? Let's take a look at the resolutions one can choose from. Heck, you can even find out using a Blitz program. Copy the following program, run it (in debug mode), and scroll the debug log in Blitz' Debugger to see which resolutions are available on your videocard.
Modes = CountGfxModes ()
For Mode = 1 To Modes
    DebugLog GfxModeWidth ( Mode ) + "x" + GfxModeHeight ( Mode ) + "x" + GfxModeDepth ( Mode )
Next
WaitKey
End
You'll probably see a list of 16 bit and 32 bit resolutions. On modern video cards there are special widescreen resolutions, some even with a 2:1 ratio, giving a lot of horizontal space compared to vertical space. Unfortunately there will be videocards which do not support this resolution. The ultimate standard resolution is 640x480x16 which is always safe. Although Blitz has a technique you can use to detect if a resolution exists on a videocard, let's use 640x480 as a starting point. From this point I've started testing some tiles, ships and bullets to see which sizes look best for this resolution.
Orientation So, having found a resolution to work with, and using a ship placeholder, for example a simple filled triangle saved as a BMP file, I can now build a large part of the code relating to, for example, the player ship. Let's focus on the parts of the game related to the code. Split screen Sound panning Control/input (ship behavior) Gravity (physics) Bullet (behavior) Collisions (ship, bullet, tilemap) Music Sound Game control You can divide parts of the code in various ways. You can even put each part in separate files if you want. And you can separate each game element in various ways as well. A lot of ways are also explained in Part 7 - Game on of my BlitzBasic Full Foundation tutorial: http://www.bettiesart.com/tc/blitz/blitz.html (See sub-section "Concepts") I'm gonna take one of the easier (if not the easiest) approaches, using a single file for the entire code, divided into clear sections. Okay, you see control/input (ship behavior) and physics up there? As these are all directly related to the player (ship), these can all be put in a section solely for player routines; e.g. the Player section.
Ship gravity In X-NON gravity should be similar to Gravity Force, so a gravity pull going straight down. So any ship will be pulled towards the ground. This may not be as hard as it sounds. Or at least I won't bother you with difficult formulae Let's do this in a small testing program. I'm using a ship image called Ship Pre Final.PNG which you can also find in the downloadable package file containing this tutorial.
; Ship image handle
Local Image

Graphics 640 , 480

Local Image = LoadImage ( "Ship Pre Final.PNG" )

; Now drawing to the invisible screen
SetBuffer BackBuffer ()

Repeat

    ; Flip the invisible with the visible screen
    Flip
    ; Clear the invisible screen
    Cls

Until KeyHit ( 1 )  ; Escape

; Manual cleanup (not required though)
FreeImage Image

End
Ooh, look! A testing program template for our ship A nice black screen with 1 key to press (Esc) Time for some more functionality. Let's place the ship on a specific location using two variables, for the X and Y coordinates. Our gravity is straight down, so from the top of the screen, to the bottom. The Y coordinate at the top of the screen is 0, and increases when you go down. So that's exactly what we want! Let's start by putting our ship at the bottom of the screen, facing up. What should happen if the ship would thrust for a short moment (upwards)? The ship should 'jump' shortly and land back on the starting position, right? Is the gravity always in effect? Yes! And the gravity is downwards. So we can just add something to the current Y coordinate.
Y = Y + gravity
What happens if we thrust? We go up, right? So subtracting from the Y coordinate.
Y = Y - thrust
The gravity is pretty constant, usually, right? But what about thrust? We could use a variable thrust strength, e.g. the longer you hold thrust, the more thrust you build. But let's use a constant for thrust as well. Understandably, if our thrust strength equals gravity strength, we're not falling nor thrusting. So to be able to thrust out of the gravity pull, we have to use a thrust strength greater than the gravity strength.
Const Gravity = 1
Const Thrust  = 3
We can change the values of these later, if the strengths are too strong or too weak. We'll also need some thrust control using the keyboard.
If KeyDown ( 57 )  ; Space
    ; Thrusting!
End If
And finally making sure the ship can't leave the bottom of the screen.
If Y > 480 Then Y = 480
Can it be any simpler? Maybe But let's merge what we have for now into a single program and see what it does, shall we? For the assembled program, take a look at the file Ship Gravity 1.BB in the Tutorial files index.
; Strengths
Const Gravity = 1
Const Thrust  = 3

; Ship position
Local X
Local Y

; Ship image handle
Local Image

; Initial X coordinate (middle of screen width)
X = 320
; Initial Y coordinate (bottom of screen)
Y = 480

Graphics 640 , 480

Image = LoadImage ( "Ship Pre Final.PNG" )
MidHandle Image

; Now drawing to the invisible screen
SetBuffer BackBuffer ()

Repeat

    ; Space is being held down
    If KeyDown ( 57 )  ; Space
        ; Lift off!
        Y = Y - Thrust
    End If

    ; Gravity is always in effect
    Y = Y + Gravity

    ; Prevent ship from falling through the bottom
    If Y > 480 Then Y = 480

    ; Display the ship
    DrawImage Image , X , Y

    ; Flip the invisible with the visible screen
    Flip
    ; Clear the invisible screen
    Cls

Until KeyHit ( 1 )  ; Escape

; Manual cleanup (not required)
FreeImage Image

End
When you run the program, it doesn't look very realistic An easy solution is using a vertical speed variable we can then use for gravity and thrust.
; Thrust using space, upwards
If KeyDown ( 57 ) Then speedY = speedY - Thrust

; Apply gravity
speedY = speedY + Gravity
And then applying the vertical speed to the vertical position.
Y = Y + speedY
Oh, and adding another little ship stop thingy when the ship hits the bottom.
If Y > 480
    Y = 480

    ; Stop the ship
    speedY = 0
End If
Assembling the above would make the program Ship Gravity 2.BB in the Tutorial files index.
; Strengths
Const Gravity = 1
Const Thrust  = 3

; Ship position
Local X
Local Y

; Ship speed
Local speedY

; Ship image handle
Local Image

; Initial X coordinate (middle of screen width)
X = 320
; Initial Y coordinate (bottom of screen)
Y = 480

Graphics 640 , 480

Image = LoadImage ( "Ship Pre Final.PNG" )
MidHandle Image

; Now drawing to the invisible screen
SetBuffer BackBuffer ()

Repeat

    ; Thrust using space, upwards
    If KeyDown ( 57 ) Then speedY = speedY - Thrust

    ; Apply gravity
    speedY = speedY + Gravity

    ; Apply speed to position
    Y = Y + speedY

    ; Prevent ship from falling through the bottom
    If Y > 480
        Y = 480

        ; Stop the ship
        speedY = 0
    End If

    ; Display the ship
    DrawImage Image , X , Y

    ; Flip the invisible with the visible screen
    Flip
    ; Clear the invisible screen
    Cls

Until KeyHit ( 1 )  ; Escape

; Manual cleanup is not mandatory
FreeImage Image

End
If you run it, you'll notice that we now have a ship that acts more like a kangaroo! The strengths are a bit high, but the effect is what we want, right? We can implement this bit of code in the game later on, and tweak the strength to be just right. Now, for something completely different..
Sound panning I had the idea to make all sounds of the left player screen audible on the left speaker and the right player on the right speaker. Now, I'm just hoping that the person playing the game does not have a broken left speaker Okay, let's make a small program that does this. I'm using a sound called Test Sound.MP3 (from the package) to see how to pull this off easily.
; Load the sample and set it to loop
Local Sound = LoadSound ( "Test Sound.MP3" )
LoopSound Sound

; Play the sample and keep track of the channel
Local Channel = PlaySound ( Sound )

; Wait for key, then exit
WaitKey
End
From this point, to adjust the panning we can use the ChannelPan command.
; Panning
ChannelPan Channel , -1  ; Left
ChannelPan Channel , 0  ; Center
ChannelPan Channel , 1  ; Right
Now if we have two players, we will probably have to identify each. Like simply using a variable containing 1 for player 1, and 2 for player 2.
; Player identification
Local PlayerNumber
So if it's player 1, play the left channel, and if it's player 2, play the right channel.
; Left player
If PlayerNumber = 1

    ; Left panning
    ChannelPan Channel , -1

; Right player
ElseIf PlayerNumber = 2

    ; Right panning
    ChannelPan Channel , 1

EndIf
Did you know this can also be done using maths? The above code is probably faster in execution, though. Let's see if we can convert these numbers 1 and 2 to the new scale and offset. If you have seen my Conversions tutorial, this should be a snap. Starting with number 1; offset 1 from 0. With a range of 1; because 2 is 1 more than 1.
source offset = 1
source scale = 1
Converting that to the new offset -1; left channel. And with a range of 2: (1) - (-1) = 2, e.g. right channel - left channel.
target offset = -1
target scale = 2
So, first remove the source offset; 1.
target value = source value - 1
Remove the source scale; 1. But 1 is already the scale, so no biggy. Then convert to the target scale; 2.
target = ( source - 1 ) * 2
And finally, add the target offset; -1.
target = ( source - 1 ) * 2 + -1
Does this make any sense?
ChannelPan Channel , ( PlayerNumber - 1 ) * 2 - 1
It seems like the panning bit is ready for implementation If this bit is confusing, don't worry. Using a simple 'If Then' is easier to read anyway.
Ship "Asteroids" physics If you've already seen one or more of the following tutorials: - Vectors and Sine waves - Project Z: One - Project Z: Two Then you probably already understand how this works. To recap, it's basically about adding a thrust vector to the current velocity vector. Some vector examples: Imagine flying upwards, then pressing the right arrow key to rotate right, but still flying upwards. Now pressing the up arrow key will slowly shift the direction to the right. So the velocity vector (current X and Y ship speeds) is separate from the thrust vector. And the thrust vector is only significant when thrusting. Then, when calculating the new ship velocity vector, you simply add the thrust vector to the current ship velocity vector. First, setting up a screen resolution. A bunch of constants for convenience
; Blitz graphics window mode states
Const WindowMode_AutoDetect = 0
Const WindowMode_FullScreen = 1
Const WindowMode_Windowed   = 2
Const WindowMode_Scaled     = 3

; Desired screen settings
Const ScreenWidth  = 640
Const ScreenHeight = 480
Const ColorDepth   = 0
Const WindowMode   = WindowMode_AutoDetect

..

; Setup screen
Graphics ScreenWidth , ScreenHeight , ColorDepth , WindowMode
SetBuffer BackBuffer ()
I'm using the same spaceship image Ship Pre Final.PNG as before. Only this time, I'm gonna rotate it, in real-time, to make it easy.
; Original ship image (pointing upwards)
Local ShipImage
; Temporarily rotated ship (any direction)
Local RotatedShipImage

..

; Load and center ship image
ShipImage = LoadImage ( "Ship Pre Final.PNG" )
MidHandle ShipImage

; Disable rotation/scaling smoothing
TFormFilter False
Let's set the ship basics up, like its location.
; Ship position
Local PosX#
Local PosY#

..

; Start in center of screen
PosX = ScreenWidth  / 2
PosY = ScreenHeight / 2
You may wonder what's with the dots inbetween each piece of code. Well, I like separating variable definition from usage (execution). Like having a shopping list of items you want to buy, or in this case, use. In the code I'm using horizontal lines as separator. It's like what we talked about earlier in Structure, where the code becomes easier to read, understand and modify by separating the definition from the execution. The ship needs some sort of control, so I'm gonna add some keys.
; Ship motion control
Const KeyUp    = 200
Const KeyDown  = 208
Const KeyLeft  = 203
Const KeyRight = 205

; Game control
Const KeyEsc   =   1
Now, if you'd want to rotate the ship, we'd need to have an angle.
; Current ship angle
Local Angle#
So we can set the angle to any value and draw the ship using this angle.
; - Ship image
;   - Duplicate the original image
RotatedShipImage = CopyImage ( ShipImage )
;   - Rotate it to the current angle
RotateImage RotatedShipImage , Angle
;   - Then draw it on screen
DrawImage RotatedShipImage , PosX , PosY
;   - And clear the rotated image
FreeImage RotatedShipImage
This renders the rotated ship image each frame, in real-time, which is very slow. So I will pre-rotate it in the final version, storing the images in an array for easy access. Now we can rotate the ship by changing the angle.
; Ship rotation speed in degrees per frame
Const RotateSpeed = 4

..

; - Rotate left
If KeyDown ( KeyLeft )
    Angle = Angle - RotateSpeed
    If Angle < 0 Then Angle = Angle + 360
EndIf

; - Rotate right
If KeyDown ( KeyRight )
    Angle = Angle + RotateSpeed
    If Angle >= 360 Then Angle = Angle - 360
EndIf
Note the angle wrapper; when the angle becomes too high or too low, it is wrapped to the other side of the scale. I usually do this to keep it in range of, for example, the indices of an array, like:
Dim Images( 360 )
Which I do to prevent the angle from going out of range, which Blitz - as well as most other languages - does not like. Finally, the tricky bit; velocity vectors. I'm using my favorite vector functions to make it a little easier.
; Trigonometry: return horizontal vector
Function VectorX# ( Distance# , Angle# )
    Return Sin ( Angle ) * Distance
End Function

; Trigonometry: return vertical vector
Function VectorY# ( Distance# , Angle# )
    Return Sin ( Angle - 90 ) * Distance
End Function

; Trigonometry: return distance using vector
Function VectorDistance# ( X# , Y# )
    Return Sqr ( X * X + Y * Y )
End Function

; Trigonometry: return angle using vector
Function VectorAngle# ( X# , Y# )
    Return 180 - Atan2 ( X , Y )
End Function
I like putting functions like these in a section (or external library) for all math related routines. Now, the ship can have a speed (velocity), and a heading (direction), which, together, make up a velocity vector.
; Current ship heading (direction)
Local Heading#
; Current ship speed (velocity)
Local Speed#
These can also be written as an (X,Y) vector, like this:
; Ship speed vector
Local SpeedX#
Local SpeedY#
To be able to thrust, let's add a thrust vector too, as well as defining the thrust strength.
; Ship thrust speed in pixels per frame
Const ThrustSpeed# = 0.12

..

; Ship thrust vector
Local ThrustX#
Local ThrustY#
So when you press the Down key (as in Gravity Force), the thrust vector should be set to the thrust strength and the current ship angle.
; - Thrust
If KeyDown ( KeyDown )
    ; Combine thrust speed and ship angle to make the thrust vector
    ThrustX = VectorX ( ThrustSpeed , Angle )
    ThrustY = VectorY ( ThrustSpeed , Angle )
Else
    ; Reset thrust vector
    ThrustX = 0
    ThrustY = 0
EndIf
Then the thrust vector should be added to the current velocity vector. But first calculate the velocity vector using the current speed and heading.
; - Take the current velocity vector (speed and heading)
; - Then add the thrust vector to it (thrust and angle)
; - Which becomes the new velocity vector
SpeedX = VectorX ( Speed , Heading ) + ThrustX
SpeedY = VectorY ( Speed , Heading ) + ThrustY
But we don't know the actual speed and heading yet. So let's convert this velocity vector back to (X,Y).
; - Now update the current speed and heading with the new velocity vector
Speed = VectorDistance ( SpeedX , SpeedY )
Heading = VectorAngle ( SpeedX , SpeedY )
Now we can also check if the ship is going too fast. Because I've found that having a limited ship speed increases the fun to chase someone, it's easier to control the ship, and, for example, being able to stop faster when encountering an obstacle.
; Ship maximum velocity in pixels per frame
Const MaxSpeed = 5

..

; - Limit the speed to the maximum
If Speed > MaxSpeed
    Speed = MaxSpeed

    ; Apply the new speed to the velocity vector
    SpeedX = VectorX ( Speed , Heading )
    SpeedY = VectorY ( Speed , Heading )
EndIf
Using the most recent velocity vector, either the one calculated before (from the speed and heading), or the one that was recalculated when going too fast, we can change the ship's position.
; - Finally add the velocity vector to the ship position
PosX = PosX + SpeedX
PosY = PosY + SpeedY
Take a look at Ship Physics 1.BB in the Tutorial files index for the assembled version.
; Trigonometry: return horizontal vector
Function VectorX# ( Distance# , Angle# )
    Return Sin ( Angle ) * Distance
End Function

; Trigonometry: return vertical vector
Function VectorY# ( Distance# , Angle# )
    Return Sin ( Angle - 90 ) * Distance
End Function

; Trigonometry: return distance using vector
Function VectorDistance# ( X# , Y# )
    Return Sqr ( X * X + Y * Y )
End Function

; Trigonometry: return angle using vector
Function VectorAngle# ( X# , Y# )
    Return 180 - ATan2 ( X , Y )
End Function

;-------------------------------------------------------------------------------

; Blitz graphics window mode states
Const WindowMode_AutoDetect = 0
Const WindowMode_FullScreen = 1
Const WindowMode_Windowed   = 2
Const WindowMode_Scaled     = 3

; Desired screen settings
Const ScreenWidth  = 640
Const ScreenHeight = 480
Const ColorDepth   = 0
Const WindowMode   = WindowMode_AutoDetect

; Ship motion control
Const KeyUp    = 200
Const KeyDown  = 208
Const KeyLeft  = 203
Const KeyRight = 205
; Game control
Const KeyEsc   =   1

; Ship thrust speed in pixels per frame
Const ThrustSpeed# = 0.12
; Ship rotation speed in degrees per frame
Const RotateSpeed# = 4
; Ship maximum velocity in pixels per frame
Const MaxSpeed     = 5

;-------------------------------------------------------------------------------

; Ship position
Local PosX#
Local PosY#

; Current ship angle
Local Angle#
; Current ship heading (direction)
Local Heading#
; Current ship speed (velocity)
Local Speed#

; Ship speed vector
Local SpeedX#
Local SpeedY#

; Ship thrust vector
Local ThrustX#
Local ThrustY#

; Original ship image (pointing upwards)
Local ShipImage
; Temporarily rotated ship (any direction)
Local RotatedShipImage

;-------------------------------------------------------------------------------

; Setup screen
Graphics ScreenWidth , ScreenHeight , ColorDepth , WindowMode
SetBuffer BackBuffer ()

; Load and center ship image
ShipImage = LoadImage ( "Ship Pre Final.PNG" )
MidHandle ShipImage

; Disable rotation/scaling smoothing
TFormFilter False

; Start in center of screen
PosX = ScreenWidth  / 2
PosY = ScreenHeight / 2

Repeat

    ; Input

    ; - Rotate left
    If KeyDown ( KeyLeft )
        Angle = Angle - RotateSpeed
        If Angle < 0 Then Angle = Angle + 360
    EndIf

    ; - Rotate right
    If KeyDown ( KeyRight )
        Angle = Angle + RotateSpeed
        If Angle >= 360 Then Angle = Angle - 360
    EndIf

    ; - Thrust
    If KeyDown ( KeyDown )
        ; Combine thrust speed and ship angle to make the thrust vector
        ThrustX = VectorX ( ThrustSpeed , Angle )
        ThrustY = VectorY ( ThrustSpeed , Angle )
    Else
        ; Reset thrust vector
        ThrustX = 0
        ThrustY = 0
    EndIf

    ; Update

    ; - Take the current velocity vector (speed and heading)
    ; - Then add the thrust vector to it (thrust and angle)
    ; - Which becomes the new velocity vector
    SpeedX = VectorX ( Speed , Heading ) + ThrustX
    SpeedY = VectorY ( Speed , Heading ) + ThrustY

    ; - Now update the current speed and heading with the new velocity vector
    Speed = VectorDistance ( SpeedX , SpeedY )
    Heading = VectorAngle ( SpeedX , SpeedY )

    ; - Limit the speed to the maximum
    If Speed > MaxSpeed
        Speed = MaxSpeed

        ; Apply the new speed to the velocity vector
        SpeedX = VectorX ( Speed , Heading )
        SpeedY = VectorY ( Speed , Heading )
    EndIf

    ; - Finally add the velocity vector to the ship position
    PosX = PosX + SpeedX
    PosY = PosY + SpeedY

    ; - Screen edge wrap when crossing screen boundaries
    If PosX < 0
        PosX = PosX + ScreenWidth
    ElseIf PosX >= ScreenWidth
        PosX = PosX - ScreenWidth
    EndIf
    If PosY < 0
        PosY = PosY + ScreenHeight
    ElseIf PosY >= ScreenHeight
        PosY = PosY - ScreenHeight
    EndIf

    ; Render

    Cls

    ; - Ship image
    ;   - Duplicate the original image
    RotatedShipImage = CopyImage ( ShipImage )
    ;   - Rotate it to the current angle
    RotateImage RotatedShipImage , Angle
    ;   - Then draw it on screen
    DrawImage RotatedShipImage , PosX , PosY
    ;   - And clear the rotated image
    FreeImage RotatedShipImage

    Flip

Until KeyHit( KeyEsc )

; Blitz automagically cleans up
End
I've also added a screen position wrapper in the assembled program, so you don't disappear after crossing the screen boundaries. Now, having a functional space ship physics program, we can easily add other bits at any time, like gravity. Let's hop over to some more audio first.
Thrust sound To make the ship come alive, I've added a rather raw engine sound which makes it seem like some kind of magnetic drive. To test this in practice, I've created yet another small program called Thrust Sound 1.BB in the Tutorial files index. Starting with setting up the sound.
; Attempt to load the sound
Local Sound = LoadSound ( "Thrust.WAV" )
If Not Sound Then RuntimeError "Unable to load sound"

; Make it loop, start playing it, and pause it
LoopSound Sound
Local Channel = PlaySound ( Sound )
PauseChannel Channel
Now let's test this in real-time; we can use a Paused flag to set later on anyway.
; Test channel pause/resume
Repeat
    If KeyDown ( 57 )  ; Space
        ResumeChannel Channel
    Else
        PauseChannel Channel
    End If
Until KeyHit ( 1 )  ; Esc
That should do it
Tilemap The tilemap routines I've used are almost identical to the ones from my Tilemap tutorial. One main difference is that I'm using external files, rather than internal Data statements for the tile map data. I've created a spicy tileset that reminded me of Gravity Force, which I've put inside the file Tiles.PNG: Now for the code, let's create a new section for everything related to the Tilemap, which can be put in the Definition area as you can see in Structure. We can start by simply defining the tiles first, a bunch of constants at the top of our Tilemap section.
; Size of a single tile
Const TileWidth  = 32
Const TileHeight = 32

; Number of tiles in anim image
Const TileCount  = 42
Then we can setup the tilemap data, info and images, residing in the (Global) variables part of the Tilemap section.
; Tiles images
Global TileStrip

; ( 1-based tile position index )
Dim    Tilemap( 0 , 0 )
Global TilemapWidth
Global TilemapHeight

..

; Load tiles images
TileStrip = LoadAnimImage ( "Tiles.PNG" , TileWidth , TileHeight , 0 , TileCount )
The call to LoadAnimImage should actually be put in the Main initialization area (as outlined in Structure) because it happens after the Graphics screen is setup which I'll come to shortly in Video mode detection. And finally a function for loading a tilemap from file, which can be defined in the sub-routines (or functions) area at the bottom of the Tilemap section.
Function LoadTilemap ( FileName$ )
    Local FileHandle
    Local PosX
    Local PosY
    Local Line$
    Local TileNumber

    ; Attempt to open file (return False if unsuccesful)
    FileHandle = OpenFile ( FileName )
    If Not FileHandle Then Return False

    ; Grab tilemap dimensions
    TilemapWidth = ReadLine ( FileHandle )
    TilemapHeight = ReadLine ( FileHandle )

    ; Reset tilemap
    Dim Tilemap( TilemapWidth , TilemapHeight )

    For PosY = 1 To TilemapHeight

        Line = ReadLine ( FileHandle )

        For PosX = 1 To TilemapWidth

            ; Convert tilenumber in map to framenumber in anim image (chr 33 = tile 0)
            TileNumber = Asc ( Mid ( Line , PosX , 1 ) ) - 33

            ; Discard tiles which are out of range
            If TileNumber >= 0 And TileNumber <= TileCount-1
                Tilemap( PosX , PosY ) = TileNumber
            End If

        Next
    Next

    CloseFile FileHandle

    Return True
End Function
You can also see that I'm using a character offset of 33 for tile images. So Chr(33) or a "!" character in the file means the first tile image. This way, you can use any text editor to edit the tilemap data. Here's an example tilemap:
20
7
!!!!!!!!!!!!!!!!!!!!
!                  !
! ! # % ' ) + - /  !
!                  !
!  " $ & ( * , . 0 !
!                  !
!!!!!!!!!!!!!!!!!!!!
If you save this tilemap to file, like Test Tilemap 1.TXT in the package, you can now load it simply with:
LoadTilemap "Test Tilemap 1.TXT"
This call to the LoadTilemap function can be placed in the Main initialization section as defined in Structure. You probably noticed that the above tilemap is 20 tiles wide and 7 tiles high. Now, I've set up another rule where tiles outside of 0 and TileCount, which is the number of available tile images, will be discarded. So spaces, which is Chr(32), would make -1 in the LoadTilemap above, which is out of range, so these tiles will stay 0. Further on in the program, I will skip those 0 tiles from being drawn. Another thing is that I will need to know where players spawn. So I've decided to use a special tile that will be used as spawn spots. After having created the tile frames, this is the information I wrote down:
TileStrip: Frame 0 = spawn spot , Frame 1 = Wall , Frame 2 = Vegetation Lower , etc
TileMap: Tile 0 = dont display, Tile 1..42 = display frame from TileStrip
TileFile: [ ](#32) = Spacing, [!](#33) = Spawnspot, ["](#34) = Frame 1, etc

TileFile -> TileMap conversion:
#32   (spacing)     ->  -1  ->  out of range        ->  0   (dont display)
#33   (spawn spot)  ->   0  ->  create spawn spot   ->  0   (dont display)
#34   (frame 1)     ->   1  ->  on tilemap          ->  1   (display)
#35   (etc)

Spawn spots Maybe you have noticed that the first tile frame is a spawn spot. So when the LoadTilemap routine encounters a tile with frame 0, it should create a spawn spot, and hide the tile.
; Discard tiles which are out of range
If TileNumber >= 0 And TileNumber <= TileCount-1
    ; This is a spawn spot (tile 0)
    ; Add it to the list and dont put in tilemap
    If TileNumber = 0
        ; Tilemap scale
        AddSpawnSpot PosX , PosY
        SpawnSpots = SpawnSpots + 1
    ; Add all other tiles to tilemap
    Else
        Tilemap( PosX , PosY ) = TileNumber
    End If
End If
You can see the undefined AddSpawnSpot and SpawnSpots above. SpawnSpots will be a global variable holding the total amount of available spawn spots. And AddSpawnSpot a function to add a spawn spot to the list. I usually create the call to the function before creating the function itself, to clarify the working of it. For the list of spawn spots, I've chosen a list of objects, using Types.
Type SpawnSpot

    ; Position
    Field PosX
    Field PosY

End Type

..

; Total number of spawn spots in tilemap
Global SpawnSpots

..

Function AddSpawnSpot ( PosX , PosY )
    Local This.SpawnSpot

    This = New SpawnSpot
    This\PosX = PosX
    This\PosY = PosY

End Function
The variable SpawnSpots is not set to any value by default. But every time when loading a new tilemap, to start fresh, this should be set to 0 anyway, as well as deleting any existing spawnspots.
Function LoadTilemap ( FileName$ )

    ..

    ; Reset tilemap
    Dim Tilemap( TilemapWidth , TilemapHeight )

    ; And spawn spots
    Delete Each SpawnSpot
    SpawnSpots = 0
I've chosen to place the code for spawn spots in a section of its own, needing no code to be placed in either of the Main initialization or Main execution sections, as I've shown in Structure. Because all of the handling of spawn spots is performed in other sections, such as the LoadTilemap function in the Tilemap section.
Video mode detection So far we've been using 640x480 as the normal resolution. What if we want to use a higher, or better, wider resolution, but we don't know if the computer's video card supports it? Let's test and add exactly this feature. As in the Graphics resolution chapter, I used the CountGfxModes Blitz function to display all available modes. Now I'm gonna wrap a function around it, to check if a single resolution is listed.
Function ResAvail ( Width , Height )
    For ColorDepth = 16 To 32 Step 8
        If GfxModeExists ( Width , Height , ColorDepth ) Then Return ColorDepth
    Next
End Function
This function tests for 16bit, 24bit and 32bit color depths, which (in theory ) should support all video cards. Let's add a bit of a test bed for this function.
; Blitz graphics window mode states
Const WindowMode_Autodetect = 0
Const WindowMode_Fullscreen = 1
Const WindowMode_Windowed   = 2
Const WindowMode_Scaled     = 3

..

; Graphics resolution
Global ScreenWidth
Global ScreenHeight
Global WindowMode
Global ColorDepth

..

; Windowed in debug mode, otherwise fullscreen
WindowMode = WindowMode_AutoDetect

; Prefered
ScreenWidth = 1280
ScreenHeight = 720
ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
If Not ColorDepth
    ; Alternative
    ScreenWidth  = 800
    ScreenHeight = 600
    ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
    If Not ColorDepth
        ; Bare minimum: Small view!
        ScreenWidth  = 640
        ScreenHeight = 480
        ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
    End If
End If

Graphics ScreenWidth , ScreenHeight , ColorDepth , WindowMode
WaitKey
End
This program checks for 1280x720, then 800x600 and finally 640x480. So we can now use this technique for the game, and add a loong list of desired resolutions The top (definition) of this code could be placed in a section for Graphics handling. While the bottom of this code (execution) could be placed somewhere at the beginning of the Main initialization section, because all other graphics depend on it, such as LoadAnimImage and LoadImage.
Sounds overlapping Somewhere along the way I found a problem when for example 2 things exploded at the same time. Some other games also have this same thing, where, if too many of the same sounds are played at the same time, the sound is extremely loud and causes extreme distortion, which sounds not just ugly to me, but in all honesty, is very painful for my ears! So I've come up with a solution, where I track the channels manually, and see if a new sound to be played is already playing. I've added a minimum overlap time, so the same sound can only be played if this time has elapsed since the sound was first started. I'm using an object structure to keep track of each channel.
; Sound channel tracker
Type ActiveChannel

    ; Channel containing the sound playing
    Field ChannelHandle

    ; Reference to the sound being played
    ; (each sound has a different address)
    Field SoundHandle

    ; Time when started playing
    Field Created

    ; How long to wait before being able to play same sound.
    Field TimeOut

End Type
And using a single function I can: - Update the currently playing sounds. - Play the new sound, if it doesn't overlap with an existing sound.
Function PlaySample ( SoundHandle , TimeOut = False )
    Local FindChannel.ActiveChannel
    Local FoundChannel.ActiveChannel
    Local Playing = False
    Local PlayNew = False

    ; Check all previously playing sounds/channels
    For FindChannel = Each ActiveChannel
        ; If this channel is still playing
        If ChannelPlaying ( FindChannel\ChannelHandle )
            ; And the sound being played is identical to the new sound to play.
            If FindChannel\SoundHandle = SoundHandle
                ; Remember that it is already being played
                Playing = True
                ; And store it for later access
                FoundChannel = FindChannel
            End If
        ; This channel is no longer playing
        Else
            ; Remove it from the list
            Delete FindChannel
        End If
    Next

    ; The new sound to play is already playing (it was found in the list)
    If Playing
        ; If the time since creation exceeds the channel's overlap (time out)
        If MilliSecs - FoundChannel\Created >= FoundChannel\TimeOut
            ; Playing the new sound is allowed (overlapping)
            PlayNew = True
        End If
    ; The new sound to play is not yet playing
    Else
        ; So playing the new sound is allowed (without hesitation)
        PlayNew = True
    End If

    ; Allowed to play new sound
    If PlayNew
        ; Create an object to track the channel
        FoundChannel = New ActiveChannel

        ; Remember the time of creation
        FoundChannel\Created = MilliSecs
        ; Record the overlapping time
        FoundChannel\TimeOut = TimeOut
        ; Also remember which sound is used
        FoundChannel\SoundHandle = SoundHandle
        ; Play the sound (and keep track)
        FoundChannel\ChannelHandle = PlaySound ( SoundHandle )

        ; Finally return the channel,
        ; so you can optionally modify its properties.
        Return FoundChannel\ChannelHandle
    End If
End Function
The working of this routine is fairly straight-forward, as you can see It is included in a test program Sound Overlap.BB, located in the Tutorial files index, which you can check out to see how to operate the routine.
Local sound = LoadSound ( "Spawn.WAV" )
If Not sound Then RuntimeError "Unable to load sound"

Repeat
    ; press space to play sound with overlap protection
    If KeyDown ( 57 )
        PlaySample sound , 500
    End If
Until KeyHit ( 1 )
End
Note that PlaySample returns False if the sound is already playing. But if you place 'the line with Return on it' outside the If, PlaySample will return the currently playing channel instead. I decided to put this funky system in a separate section called Audio in the Definition area of the code, as you can see in Structure. By the way, you may be wondering why I "forgot" the braces behind the MilliSecs in the code above. Allow me to elaborate
MilliSecs Some parts of this game use MilliSecs() to keep track of time. Such as how long bullets should stay alive. But, because some Blitz functions, like MilliSecs(), take a bit of time to be executed, I've created a clever yet easy optimization, to prevent the game speed from being affected in a bad way, in case MilliSecs() needs to be called very often. I'm using a Global variable called MilliSecs, just without the braces ().
Global MilliSecs
And I set this to the value of MilliSecs() every frame, e.g. in the main loop (Main execution section). Which means that this function will never be called any more than once per frame nor any less, with a very reasonable timing accuracy.
MilliSecs = MilliSecs ()
Now I can just use MilliSecs, without the braces (), in any place, Functions included, and at any time, just like when using MilliSecs() directly. For more information about MilliSecs() take a look at Part 7 - Game On of my BlitzBasic Full Foundation tutorial specifically in the Timing sub-section.
Bullets Let's add some more gameplay elements, like bullets! For ease of bullet management, using another list of bullets, using Types for bullet objects. Note that this is all very similar to the techniques used in my previous tutorials, such as: - Vectors and Sine waves - Project Z: One - Project Z: Two
Type Bullet

    ; Time of creation
    Field TimeCreated
    ; Duration of life
    Field TimeToLive

    ; Position
    Field PosX#
    Field PosY#
    ; Velocity
    Field VelX#
    Field VelY#

End Type
The timing is done using the MilliSecs global. TimeCreated will be set to MilliSecs when the bullet is created. And TimeToLive will define the amount of time (in milliseconds) this bullet will keep flying.
; Bullet time to live in milliseconds
Const BulletTime = 5000
In fact, the TimeToLive field here is duplicate at the moment because it is always the same as the value of the BulletTime constant. But while I was creating this, I thought that for the explosion effect I could re-use the bullet code. So I can easily expand on this soon. Apart from timing, I'm using another velocity vector for the bullet's (constant) displacement. So, time to create a bullet..
Function CreateBullet ( PosX , PosY , VelX# , VelY# )
    Local This.Bullet

    This = New Bullet
    This\PosX = PosX
    This\PosY = PosY
    This\VelX = VelX
    This\VelY = VelY

    This\TimeCreated = MilliSecs

    ; Use default time to live
    This\TimeToLive = BulletTime

End Function
This function doesn't calculate anything, like where the bullet should be flying to, or how fast the bullet should be going. First, to make the bullets fly, I'll also need to know the speed, which I'm measuring in pixels per frame.
; Bullet velocity in pixels
Const BulletSpeed    =    4
E.g. the position of the bullet is altered 4 pixels every time, while it is still alive. This can be done in an UpdateBullets function.
Function UpdateBullets ()
    Local This.Bullet

    For This = Each Bullet

        ; Update position
        This\PosX = This\PosX + This\VelX
        This\PosY = This\PosY + This\VelY

        ; Time to die
        If MilliSecs - This\TimeCreated >= This\TimeToLive
            Delete This
        End If

    Next

End Function
This should all be very straight-forward. But, what does the bullet look like anyway? <----- Bullet.BMP I'm using a very simple 2x2 image for a retro-style look.
; Simple bullet image
Global BulletImage

..

; Load bullet image
BulletImage = LoadImage ( "Bullet.BMP" )
MidHandle BulletImage
Logically, the player will be able to fire using the ship, so the bullet should be created exactly at the tip of the ship image. Looking at the ship image, this is approximately 24 pixels away from the center of the ship image.
; Bullet spawning distance from player image hotspot in pixels
Const BulletDistance =   24
And ofcourse a simple drawing function.
Function DrawBullets ()
    Local This.Bullet

    For This = Each Bullet
        DrawImage BulletImage , This\PosX , This\PosY
    Next

End Function
Now we can fire a bullet in a certain direction and speed. For example, if the ship is located at (PosX,PosY) pointing upwards, the fire code would look something like this:
CreateBullet PosX , PosY-BulletDistance , 0 , -BulletSpeed
Let's use my favorite vector functions and add a ShipAngle, which would be the direction of the ship.
; Ship is pointing upwards
Const ShipAngle = 0

..

; Ship position
Local ShipPosX
Local ShipPosY

; Bullet starting position & speed vector
Local BulletPosX
Local BulletPosY
Local BulletVelX#
Local BulletVelY#

..

; Calculate where to create bullet in front of ship
BulletPosX = ShipPosX + VectorX ( BulletDistance , ShipAngle )
BulletPosY = ShipPosY + VectorY ( BulletDistance , ShipAngle )

; Initial bullet velocity vector
BulletVelX = VectorX ( BulletSpeed , ShipAngle )
BulletVelY = VectorY ( BulletSpeed , ShipAngle )

CreateBullet BulletPosX , BulletPosY , BulletVelX , BulletVelY
Let's put all this together in a test program, including all the required definitions, and see if it is to our liking.
; Trigonometry: return horizontal vector
Function VectorX# ( Distance# , Angle# )
    Return Sin ( Angle ) * Distance
End Function

; Trigonometry: return vertical vector
Function VectorY# ( Distance# , Angle# )
    Return Sin ( Angle - 90 ) * Distance
End Function

; Trigonometry: return distance using vector
Function VectorDistance# ( X# , Y# )
    Return Sqr ( X * X + Y * Y )
End Function

; Trigonometry: return angle using vector
Function VectorAngle# ( X# , Y# )
    Return 180 - ATan2 ( X , Y )
End Function

;-------------------------------------------------------------------------------

; Blitz graphics window mode states
Const WindowMode_AutoDetect = 0
Const WindowMode_FullScreen = 1
Const WindowMode_Windowed   = 2
Const WindowMode_Scaled     = 3

; Desired screen settings
Const ScreenWidth  = 640
Const ScreenHeight = 480
Const ColorDepth   = 0
Const WindowMode   = WindowMode_AutoDetect

; Bullet control
Const KeySpace =  57
; Game control
Const KeyEsc   =   1

;-------------------------------------------------------------------------------

; Bullet spawning distance from player image hotspot in pixels
Const BulletDistance =   24

; Bullet time to live in milliseconds
Const BulletTime = 5000

; Bullet velocity in pixels
Const BulletSpeed    =    4

; Ship is pointing upwards
Const ShipAngle = 0

;-------------------------------------------------------------------------------

Type Bullet

    ; Time of creation
    Field TimeCreated
    ; Duration of life
    Field TimeToLive

    ; Position
    Field PosX#
    Field PosY#
    ; Velocity
    Field VelX#
    Field VelY#

End Type

;-------------------------------------------------------------------------------

; Smart timing wrapper
Global MilliSecs

; Simple bullet image
Global BulletImage

;-------------------------------------------------------------------------------

Function CreateBullet ( PosX , PosY , VelX# , VelY# )
    Local This.Bullet

    This = New Bullet
    This\PosX = PosX
    This\PosY = PosY
    This\VelX = VelX
    This\VelY = VelY

    This\TimeCreated = MilliSecs

    ; Use default time to live
    This\TimeToLive = BulletTime

End Function


Function UpdateBullets ()
    Local This.Bullet

    For This = Each Bullet

        ; Update position
        This\PosX = This\PosX + This\VelX
        This\PosY = This\PosY + This\VelY

        ; Time to die
        If MilliSecs - This\TimeCreated >= This\TimeToLive
            Delete This
        End If

    Next

End Function


Function DrawBullets ()
    Local This.Bullet

    For This = Each Bullet
        DrawImage BulletImage , This\PosX , This\PosY
    Next

End Function

;-----------------------------------------------------------------------

; Original ship image (pointing upwards)
Local ShipImage

; Bullet starting position & speed vector
Local BulletPosX
Local BulletPosY
Local BulletVelX#
Local BulletVelY#

; Ship position
Local ShipPosX
Local ShipPosY

;-------------------------------------------------------------------------------

; Setup screen
Graphics ScreenWidth , ScreenHeight , ColorDepth , WindowMode
SetBuffer BackBuffer ()

; Load bullet image
BulletImage = LoadImage ( "Bullet.BMP" )
MidHandle BulletImage

; Load and center ship image
ShipImage = LoadImage ( "Ship Pre Final.PNG" )
MidHandle ShipImage

; Place ship in the lower half in the center of the screen
ShipPosX = ScreenWidth  / 2
ShipPosY = ScreenHeight * 3 / 4

Repeat

    ; Timing

    MilliSecs = MilliSecs ()

    ; Input

    If KeyDown ( KeySpace )
        ; Calculate where to create bullet in front of ship
        BulletPosX = ShipPosX + VectorX ( BulletDistance , ShipAngle )
        BulletPosY = ShipPosY + VectorY ( BulletDistance , ShipAngle )

        ; Initial bullet velocity vector
        BulletVelX = VectorX ( BulletSpeed , ShipAngle )
        BulletVelY = VectorY ( BulletSpeed , ShipAngle )

        CreateBullet BulletPosX , BulletPosY , BulletVelX , BulletVelY
    EndIf

    ; Update

    UpdateBullets

    ; Render

    Cls

    DrawImage ShipImage , ShipPosX , ShipPosY
    DrawBullets

    Flip

Until KeyHit( KeyEsc )

; Blitz automagically cleans up
End
If you run Bullet Physics 1.BB (from the Tutorial files index) and press Space, a string of bullets should be spawned, flying upwards. So everything seems to work as it should. To implement this into the Ship Physics code would only require a little bit of renaming of variables here and there. A sound playing when firing is also added in a snap, which code would be placed near the call to the CreateBullet function, looking something like this:
; Play fire sound
Channel = PlaySample ( FireSound , 10 )
ChannelPan Channel , ( PlayerNumber - 1 ) * 2 - 1
This code uses both the sound overlap prevention code (10 ms overlap) and the 'algorithm' to hear each player on separate stereo channels, which I've explained earlier in Sounds overlapping and Sound panning. For the final code I would create a new section for all of the Bullets code, which can rest entirely in the Definition area shown in Structure. This would include constants definitions, a type object definition, some global variables and a bunch of functions. The UpdateBullets function handles just the motion/physics, while the DrawBullets function only takes care of the rendering. In fact, the exact same thing applies to the (player) ship. I can easily call all of these from the main loop, in the Main execution area of the code, which is similar to the example program above.
Pre-rendered rotated ship Just like in Project Z: Two I'm using a ship that rotates, which can be pre-rendered before the game starts. In that game you can see the image being pre-rendered during the loading process. In X-NON I want to pre-render it to images, so also the loading time will be shorter (though making the total game filesize bigger). I've designed a special Image Rotator program for this, with various customizable options. Take a look at Image Rotator.BB, in the Tutorial files index which includes inline comments. The process is similar to that in Project Z: Two though.
;-------------------------------------------------------------------------------
;
;   Image Rotator V2
;
;,,,,
    ;   Definition
;----

    Const SourceImageFile$  = "Ship Pre Final.PNG"
    Const SourceImageWidth  = 64
    Const SourceImageHeight = 64
    Const SourceFrames      = 1
    Const SourceFrameOffset = 0
    Const SourceFrameToUse  = 0  ; First frame
    Const SourceMask        = False
    Const SourceMaskRed     = 255
    Const SourceMaskGreen   = 0
    Const SourceMaskBlue    = 255
    Const RemoveMaskColor   = True

    Const DestinationFile$  = "Ship Images\Ship "
    Const RotationInterval  = 5
    Const AntiAliasing      = True
    Const Scaling           = True
    Const ScalingTo#        = 1.1
    Const ScalingFrom#      = 1 / ScalingTo

;,,,,
    ;   Processing
;----

    Graphics 640 , 480 , 0 , 2
    TFormFilter AntiAliasing

    Local SourceImage
    Local SourceAnimStrip

    ; Single or multi image
    SourceAnimStrip = LoadAnimImage ( SourceImageFile ,
                                      SourceImageWidth , SourceImageHeight ,
                                      SourceFrameOffset , SourceFrames )
    ; Grab image to use from strip
    SourceImage = CreateImage ( SourceImageWidth , SourceImageHeight )
    SetBuffer ImageBuffer ( SourceImage )
    DrawBlock SourceAnimStrip , 0 , 0 , SourceFrameToUse
    FreeImage SourceAnimStrip

    Local NoMaskImage

    ; Apply virtual masking
    If SourceMask
        MaskImage SourceImage , SourceMaskRed , SourceMaskGreen , SourceMaskBlue
        ; Remove masking color (replace with black)
        If RemoveMaskColor
            NoMaskImage = CreateImage ( ImageWidth ( SourceImage ) ,
                                        ImageHeight ( SourceImage ) )
            SetBuffer ImageBuffer ( NoMaskImage )
            DrawImage SourceImage , 0 , 0
            FreeImage SourceImage
            SourceImage = NoMaskImage
        End If
    End If

    ; Centered hotspot
    MidHandle SourceImage

    ; Upsampling (filter dependent)
    If Scaling Then ScaleImage SourceImage , ScalingTo , ScalingTo

    Local ScreenMidX
    Local ScreenMidY

    SetBuffer BackBuffer ()
    ScreenMidX = GraphicsWidth ()  / 2
    ScreenMidY = GraphicsHeight () / 2

    Local RotatedImage
    Local Angle

    ; Rendering
    For Angle = 0 To 360 Step RotationInterval
        ; Make fresh copy of original to be rotated
        RotatedImage = CopyImage ( SourceImage )
        MidHandle RotatedImage
        ; Rotate with or without filtering
        RotateImage RotatedImage , Angle
        ; Scale if requested
        If Scaling Then ScaleImage RotatedImage , ScalingFrom , ScalingFrom
        ; Render to screen for user to see progress
        DrawImage RotatedImage , ScreenMidX , ScreenMidY
        Flip False
        ; Save image to disk
        SaveImage RotatedImage , DestinationFile + Angle + ".BMP"
        ; Image saved, done
        FreeImage RotatedImage
    Next

    End

;,,,,
    ;
;----
;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
It takes a single image (pointing upwards), and rotates it at an interval of your choice. In this case: 5. It then produces separated rotated images, with optional customizable smoothing, in the range of 0..360 degrees. E.g. Ship.PNG -> Ship [0..360].PNG I'm also using a separate folder for the rotated ship images, to keep the large amount of files separate from the other files. You can take a look at the resulting files in the Ship Images folder. Since I'm using a different ship for each player, I'll need to create 2 sets of rotated ships, but just changing a few parameters is quick enough. As you know, I'm giving each player a number, like 1 and 2. And I'm using a rotation interval of 5 for reasonably precise rotation (visually). You could use a Constant in the program, if you'd ever want to change it.
Const RotationInterval = 5  ; degrees
But I'm going to hardcode this into the program, because I'm happy with it and I don't need to change it. This value relies on what's inside the images anyway. Just like in Project Z: Two I'm using an array to store the rotated images. And because there are 2 players, I just add another dimension, one for each player.
; ( 1-based player number , 0-based angle )
Dim    PlayerImages( 2 , 360 )
As previously described in the Specification I decided to use a green version of the ship for player 1. And a red version for player 2. The difference is extremely subtle. Now, using our handy Image Rotator we can quickly turn these into 2 rotated ship image sets. Then we can use a simple For..Next construct to load both sets, ready for use!
; Load pre-rotated player images
For PlayerNumber = 1 To 2
    For Angle = 0 To 360 Step 5
        PlayerImages( PlayerNumber , Angle ) =
            LoadImage ( "Ships\" + "Ship " + PlayerNumber + " - " + Angle + ".BMP" )
        MidHandle PlayerImages( PlayerNumber , Angle )
    Next
Next
You know, rotating the ship in Project Z: Two also occured in steps of 5 degrees, e.g. when rotating the ship while playing. But what if in X-NON I want a different speed? E.g. the visual angle would be different from the value of PlayerAngle.
PlayerAngle = 3
DrawImage PlayerImages( PlayerNumber , PlayerAngle ) , ..
Because there is no frame at angle 3 this wouldn't work. So one solution is to render all the angles, but this is not needed if there is a way to round the angle up or down to the nearest 5-angle value. I've found a way to do this by first scaling a value down, making the value lose its significance inbetween the stepsize, and then scaling it up again.
; Return angle divided in steps
Function LimitAngle ( Angle , Stepsize )
    Return Int ( Floor ( Float Angle / Stepsize ) ) * Stepsize
End Function
Now I can use this function anywhere by wrapping it around the raw angle.
PlayerAngle = 3
DrawImage PlayerImages( PlayerNumber , LimitAngle ( PlayerAngle ) ) , ..
Now, if you would merge some of this together in a single program, you should get something that looks like a game already I have composed a mix of the above parts and some random bits which I have not explained yet in Game composition 1.BB, located in the Tutorial files index, to give you a good (in-game) look at some of the things we have dealt with so far. The example program also includes: - Some funky debugging features, to test the gravity effect. - Sound effects for thrusting and shooting. - An alternate set of tiles. - And a new tilemap for testing. There are more new parts of code that I will explain in the upcoming chapters. But if you take a look just at the code structure, and particularly the sections of that program, you can see that there's a Player section now. It contains a bunch of constants, a type definition, some global variables and a bunch of functions. Maybe you have noticed that this is just like the Bullets section, which we've handled previously.
Frame limiting To make the game run with the same speed on faster computers, I'm using Blitz's own CreateTimer and WaitTimer functions. In Project Z: Two I have used the 'skipping' approach also using only these 2 functions, so that the game would run with a similar speed on a slower computer as well. But because X-NON's code was written for a pretty old computer (~200MHz or lower), I don't think it will be a problem now
; Game speed limiter
Global GameTimer

..

; Setup game speed limiter at 60 updates per second
GameTimer = CreateTimer( 60 )

..

Repeat

    ..

    ; Sync game speed
    WaitTimer GameTimer

Until ..
Looking at the code above, the WaitTimer command will only slow down the program when a particular computer executes the main loop faster than the specified update frequency. For example, take some very simple code that moves a dot across the screen.
Graphics 640,480
SetBuffer BackBuffer ()

Local X = 0

Repeat

    X = X + 1
    If X >= 640
        X = 0
    End If

    Plot X,240

    Flip
    Cls

Until KeyHit ( 1 )
End
Adding the frame limiting code would make it run with (almost) the same speed on computers that are faster than the one you created and tested the program on. To actually know how fast your game is running, try adding some code to measure the number of frames rendered (or the number of main loop cycles) in 1 second. E.g. frames per second (fps).
Local fps
Local frames
Local lastsec

..

Repeat

    ..

    frames = frames + 1
    If MilliSecs () >= lastsec + 1000
        lastsec = MilliSecs ()
        fps = frames
        frames = 0
    End If

    Color 255 , 255 , 255
    Text 0 , 0 , fps

    Flip False
    Cls
Use Flip False to disable vertical sync in order to run without any delays.
Viewpoint As you know, I'm splitting the screen in two; having a left and right side for maximum 'squareness'. For this to work in the program, I'm going to use a technique that resembles using a camera. In fact, this is nothing more complex than using a simple X and Y coordinate. So splitting the screen vertically would give me something like you can see in the following program:
; Screen dimensions (for example)
Const ScreenWidth  = 640
Const ScreenHeight = 480

; Player 1 (left)
Rect 0 , 0 , ScreenWidth/2 , ScreenHeight

; Player 2 (right)
Rect ScreenWidth/2 , 0 , ScreenWidth/2 , ScreenHeight
In the program I want to keep track of both players independently. That's why I've chosen to use yet another Type structure to reference each player. For the sake of the camera, I can simply use an X and Y for each player, like this:
Type Player

    ; Current position
    Field PosX#
    Field PosY#

End Type
Now I can, for example, use each Player pointer to reference a player viewpoint. I'm going to use an array so I can reference each player directly as well as via a For..Each.
; ( 1-based player number )
Dim Player.Player( 2 )

; Create the left player in the upper left corner of the screen
Player( 1 ) = New Player
Player( 1 )\PosX = 0
Player( 1 )\PosY = 0

; Create the right player in the upper left corner of the split
Player( 2 ) = New Player
Player( 2 )\PosX = ScreenWidth/2
Player( 2 )\PosY = 0
The coordinates I'm currently using (also in the program above) are still screen-based, rather than game world-based. In Relative positioning in 10 steps I show how you can go from 1 coordinate system to another. This is also what I'll be doing here. Because in the game world the coordinates will be different from the screen coordinates. For example; if I have coordinate (0,0) in the game world and I want to move the camera (or player) around, this coordinate will still be (0,0) in the game world, but very different on the screen! If you do not fully grasp this concept, I highly recommend taking another peek at Relative positioning in 10 steps. First, as an example, I'm going to draw each bullet relative to a player's point of view. As we already have a Player pointer and a DrawBullets function, we can tie them together in a snap.
Function DrawBullets ( Orientation.Player )

    ; Something with Orientation\PosX and Orientation\PosY

End Function

..

DrawBullets Player( 1 )
DrawBullets Player( 2 )
The code above can basically be a template for every viewpoint-based drawing function. For example for drawing tiles I can use the same technique. Looking at the original game design in each viewpoint the following things should be drawn: - Players - Bullets - Tiles This means that even a DrawPlayer function should use the same approach. Though the math involved is quite simple:
Screen = GameWorld - Camera
In addition, I want to center the camera in each viewpoint, e.g.:
; Player 1 (left view)
Rect 0 , 0 , ScreenWidth/2 , ScreenHeight
; View point (left)
Plot ScreenWidth/4 , ScreenHeight/2

; Player 2 (right)
Rect ScreenWidth/2 , 0 , ScreenWidth/2 , ScreenHeight
; View point (right)
Plot ScreenWidth/2 + ScreenWidth/4 , ScreenHeight/2
To make this really easy and hassle-free, I'm going to use the Origin command, so if I draw something at (0,0) it will be offset to my favorite origin
; Centered in left view
Origin ScreenWidth/4 , ScreenHeight/2
Plot 0 , 0

; Centered in right view
Origin ScreenWidth/2 + ScreenWidth/4 , ScreenHeight/2
Plot 0 , 0
In the code above I can replace Plot 0 , 0 with all drawing functions, e.g. for the tilemap, bullets and players. Well, you may be wondering, if we're drawing everything on the screen twice, for example drawing a tilemap on the left side, and then on the right side, isn't the tilemap on the right side going to overlap the left side? Indeed! If it wasn't for the ViewPort command, I would be pulling my hair out
; Left player view
; - Limit all drawing operations to this area
ViewPort 0 , 0 , ScreenWidth/2 , ScreenHeight
; - Offset all drawing operations to this (view) point
Origin ScreenWidth/4 , ScreenHeight/2
; .. Drawing functions

; Right player view
; - Limit all drawing operations to this area
ViewPort ScreenWidth/2 , 0 , ScreenWidth/2 , ScreenHeight
; - Offset all drawing operations to this (view) point
Origin ScreenWidth/2 + ScreenWidth/4 , ScreenHeight/2
; .. Drawing functions
That should be it! Later on, you can place the code above directly into the main loop in the Main execution section.
Bullet viewpoint So now we can focus on the drawing functions, starting with DrawBullets, where we left off.
Function DrawBullets ( Orientation.Player )

    ; Something with Orientation\PosX and Orientation\PosY
Note that, at the moment, you can write the above also like this:
Function DrawBullets ( OrientationX , OrientationY )

    ; Something with OrientationX and OrientationY
Which would turn a call to DrawBullets into this:
DrawBullets Player( 1 )\PosX , Player( 1 )\PosY
Let us merge the 'camera formula' with the drawing goodness:
Screen = GameWorld - Camera
ScreenBullet\PosX = WorldBullet\PosX - Player\PosX
ScreenBullet\PosY = WorldBullet\PosY - Player\PosY
Take a look at our current DrawBullets function and see if you can find the corresponding variables.
Function DrawBullets ()
    Local This.Bullet

    For This = Each Bullet
        DrawImage BulletImage , This\PosX , This\PosY
    Next

End Function
It looks like Player PosX and Player PosY are Orientation PosX and Orientation PosY. And WorldBullet PosX and WorldBullet PosY are This PosX and This PosY. While ScreenBullet PosX and ScreenBullet PosY do not have variables associated with them. So all that should look like this:
ScreenBullet\PosX = This\PosX - Orientation\PosX
ScreenBullet\PosY = This\PosY - Orientation\PosY
Adding it to the function..
Function DrawBullets ( Orientation.Player )
    Local This.Bullet

    For This = Each Bullet
        DrawImage BulletImage , This\PosX - Orientation\PosX , This\PosY - Orientation\PosY
    Next

End Function

Tilemap viewpoint Now I can do the same thing with the tilemap drawing function. In the Tilemap tutorial all of the tiles were drawn, e.g. also those tiles not even visible on the screen. To make the drawing tiles function faster, I've added an algorithm that excludes drawing tiles that are not on the screen. Actually, in our case, the viewport is half of the screen width. By viewport I mean the visible area of the screen for each player, as shown in previous programs. The algorithm is in fact the same one as I used in the Tilemap tutorial. Although, there it was used for collision detection but I can use it here also for drawing. Both the function definition and function call are the same as the DrawBullet function;
Function DrawTilemap ( Orientation.Player )

    ; Something with Orientation\PosX and Orientation\PosY

End Function

..

DrawTilemap Player( 1 )
Hopping over into the DrawTilemap function, I'm gonna add each element of the algorithm step by step, so you'll recognize it from the Tilemap tutorial, which is actually very similar. First, the player view orientation, which I'm going to store in separate variables, so if I need to alter them, I only have to do it in 1 location (!), which is a little bit like having a Constant at the the start of the program to reference something by.
Local PosX
Local PosY

..

; Use a relative display position
PosX = Orientation\PosX
PosY = Orientation\PosY
Ofcourse you can also reference them directly, as I have done for DrawBullets. So PosX and PosY are now the orientation. The tilemap should be drawn starting from the upperleft corner of the view to the lowerright corner of the view. Then we can translate (scale) that world coordinate to tilemap scale (by division) as also explained in Universal scaling revealed and the Tilemap tutorial.
Local StartTileX
Local StartTileY

..

; Use top-left of player window as offset
; centering the player in the middle of the window
; taking split screen into account
; Then find the tile that's there at this moment
; Add one tile to overlap the window edge
StartTileX = ( PosX - ScreenWidth /2/2 ) / TileWidth  - 1
StartTileY = ( PosY - ScreenHeight/2   ) / TileHeight - 1
And basically the same thing for the lowerright corner.
Local EndTileX
Local EndTileY

..

; Use the player window size as the range
; again centering the player in the middle
; and taking split screen into account
; Then find the tile that's there at this moment
; Add one tile to overlap the window edge
EndTileX = ( PosX + ScreenWidth /2/2 ) / TileWidth  + 1
EndTileY = ( PosY + ScreenHeight/2   ) / TileHeight + 1
Imagine flying around in the world, and the player's view crosses the boundaries of the tilemap. Some games forcefully limit the view to the boundaries of the tilemap, which Gravity Force also does. The technique used to accomplish that is explained in the final step of Relative positioning in 10 steps. Personally I prefer the 'unbound' approach where you can also flee from the tilemap if you wanted to To add this to the DrawTilemap function, simply limit the start and end tile variables to the tilemap boundaries, without making any adjustments to the orientation.
; Make sure that the starting and ending tiles
; are not out of tilemap range (edges)
If StartTileX < 0 Then StartTileX = 0
If StartTileY < 0 Then StartTileY = 0
If EndTileX > TilemapWidth -1 Then EndTileX = TilemapWidth -1
If EndTileY > TilemapHeight-1 Then EndTileY = TilemapHeight-1
Here, the player can fly to the edge of the tilemap and will be able to see 'across' it which is my intention for this game. All that remains now is adding the drawing part, with the juicy part being a conversion of a tilemap coordinate to a screen coordinate.
Local TileX
Local TileY
Local TilePosX
Local TilePosY
Local FrameNumber

..

; Now go through only those tiles that are within the player's window
For TileX = StartTileX To EndTileX
    For TileY = StartTileY To EndTileY

        TilePosX = TileX * TileWidth  - PosX
        TilePosY = TileY * TileHeight - PosY

        FrameNumber = Tilemap( TileX+1 , TileY+1 )

        DrawImage TileStrip , TilePosX , TilePosY , FrameNumber

    Next
Next
This routine should draw all tiles to the screen. Even 'empty' tiles. So I prefer to skip the drawing of 'empty' tiles, to, again, speed up the routine, and basically, because it's not needed to draw tiles that are completely transparent, coming from the direction of common sense. Looking at the tilestrip, I have defined the first tile/frame, which is frame number 0 in the DrawImage command, as a completely transparent tile. Which means simply wrapping the juicy bit in an If.
FrameNumber = Tilemap( TileX+1 , TileY+1 )
; Is non-zero
If FrameNumber
    TilePosX = TileX * TileWidth  - PosX
    TilePosY = TileY * TileHeight - PosY

    DrawImage TileStrip , TilePosX , TilePosY , FrameNumber
End If
To summarize and for clarity, here is the combined DrawTilemap function:
Function DrawTilemap ( Orientation.Player )
    Local StartTileX
    Local StartTileY
    Local EndTileX
    Local EndTileY
    Local TileX
    Local TileY
    Local TilePosX
    Local TilePosY
    Local FrameNumber
    Local PosX
    Local PosY

    ; Use a relative display position
    PosX = Orientation\PosX
    PosY = Orientation\PosY

    ; Use top-left of player window as offset
    ; centering the player in the middle of the window
    ; taking split screen into account
    ; Then find the tile that's there at this moment
    ; Add one tile to overlap the window edge
    StartTileX = ( PosX - ScreenWidth /2/2 ) / TileWidth  - 1
    StartTileY = ( PosY - ScreenHeight/2   ) / TileHeight - 1

    ; Use the player window size as the range
    ; again centering the player in the middle
    ; and taking split screen into account
    ; Then find the tile that's there at this moment
    ; Add one tile to overlap the window edge
    EndTileX = ( PosX + ScreenWidth /2/2 ) / TileWidth  + 1
    EndTileY = ( PosY + ScreenHeight/2   ) / TileHeight + 1

    ; Make sure that the starting and ending tiles
    ; are not out of tilemap range (edges)
    If StartTileX < 0 Then StartTileX = 0
    If StartTileY < 0 Then StartTileY = 0
    If EndTileX > TilemapWidth -1 Then EndTileX = TilemapWidth -1
    If EndTileY > TilemapHeight-1 Then EndTileY = TilemapHeight-1

    ; Now go through only those tiles that are within the player's window
    For TileX = StartTileX To EndTileX
        For TileY = StartTileY To EndTileY

            FrameNumber = Tilemap( TileX+1 , TileY+1 )
            ; Is non-zero
            If FrameNumber
                TilePosX = TileX * TileWidth  - PosX
                TilePosY = TileY * TileHeight - PosY

                DrawImage TileStrip , TilePosX , TilePosY , FrameNumber
            End If

        Next
    Next

End Function
If you're a little confused about all of the 0 and 1-based conversions, take a look at the lower parts of the Tilemap tutorial. The DrawTilemap function, just like the DrawBullets and DrawPlayer functions, can be called straight from the main loop in the Main execution section of the code, as shown in Structure.
Player viewpoint The last drawing portion that remains are the players, for which the technique is no different. An interesting feature of the player drawing function is that the drawing angle (image frame) is completely independent from the actual ship rotation angle. So the drawing angle is the image frame to draw. And the actual ship rotation angle can be a field named Angle in a Player object.
Type Player
    ..
    ; Current ship angle
    Field Angle#
    ..
End Type
This technique was previously explained in Pre-rendered rotated ship if you're wondering what this is about So let's add a DrawPlayers function which combines that technique as well as the camera viewpoint.
Function DrawPlayers ( Main.Player )
    Local This.Player
    Local PosX
    Local PosY

    For This = Each Player

        ; But it's not the player being drawn
        If This <> Main
            ; Make this player relative to the one being drawn
            PosX = This\PosX - Main\PosX
            PosY = This\PosY - Main\PosY
        ; This player is being drawn
        Else
            ; Center it
            PosX = 0
            PosY = 0
        End If

        ; Limit the angle to increments of 5, starting from 0
        ; to match with the image frames stored in the array
        DrawImage PlayerImages( This\Number , LimitAngle ( This\Angle , 5 ) ) , PosX , PosY

    Next

End Function
Maybe you have noticed that I've added the field Number in the Player object, which was also used in Sound panning and Pre-rendered rotated ship.
Type Player
    ..
    ; Unique player number (1 or 2)
    Field Number
    ..
End Type
Now that we have a complete set of drawing functions, e.g. DrawPlayers, DrawBullets and DrawTilemap, we can put them in the main loop, e.g. in the Main execution section. Let's combine it with the code from the Viewpoint chapter, to get the full rendering code for both players.
; Render game
; - Player 1
ViewPort 0 , 0 , ScreenWidth / 2 , ScreenHeight
Origin ScreenWidth / 4 , ScreenHeight / 2
DrawTilemap Player( 1 )
DrawBullets Player( 1 )
DrawPlayers Player( 1 )

; - Player 2
ViewPort ScreenWidth / 2 , 0 , ScreenWidth / 2 , ScreenHeight
Origin ScreenWidth / 2 + ScreenWidth / 4 , ScreenHeight / 2
DrawTilemap Player( 2 )
DrawBullets Player( 2 )
DrawPlayers Player( 2 )

Creating a player (ship) Previously, we've created an array of Player objects. This allows you to walk through (iterate) the list of players both by number (PlayerNumber) and by the order of the objects using a pointer (For..Each). Adding to that, I will use both ways to reference the players. To control each player easily, I'm going to create a bunch of functions to handle unique parts of the players. For example functions to create a player object, spawn a player ship, control a player ship, destroy a player ship, and so forth. These functions can all be placed in the Player section in the Definition area of the code. We can create functions in any way we like. So I'll add a new function when I think it would make the game: - clearer and easier to read - easier to understand and modify as well as: - decreasing the amount of code - and decreasing the time it takes to add features. I'll start with a CreatePlayer function so we'll have something to work with.
Function CreatePlayer.Player ( PlayerNumber )
    Local This.Player

    This = New Player
    This\Number = PlayerNumber

    ; Center view on tilemap by default
    This\PosX = (TilemapWidth -1) * TileWidth  / 2
    This\PosY = (TilemapHeight-1) * TileHeight / 2

    Return This
End Function

..

; Setup players
Player( 1 ) = CreatePlayer ( 1 )
Player( 2 ) = CreatePlayer ( 2 )
Setting up the players also refers to the Player object array:
; ( 1-based player number )
Dim Player.Player( 2 )
And to summarize, here's the Player object, which should now include all the new fields that I have previously brought into play:
Type Player

    ; Unique player number (1 or 2)
    Field Number

    ; Current ship angle
    Field Angle#
    ; Current ship heading (direction)
    Field Heading#
    ; Current ship speed (velocity)
    Field Speed#

    ; Current position
    Field PosX#
    Field PosY#

End Type
Maybe you can figure out by now which of the above code should be placed in the Player section and which should be placed in the Main initialization section, as defined in Structure. For example the actual creation of the players, e.g. when the program is run, should occur in the Main initialization section. Even the remark Setting up the players is very descriptive, in order to figure out where to put the code.
(Re)spawning the player (ship) Looking at the CreatePlayer function, when a player (ship) is first created, it is put in the center of the tilemap. The idea is that when the game starts, no players are spawned yet, so having the screen focussed at the center of the tilemap seems like a decent idea. Once a player presses the firebutton, its ship should be spawned at a random spawn spot. But if a ship is able to spawn, this means that a ship can be dead too.
Type Player
    ..
    ; True or False
    Field Dead
    ..
End Type

..

Function CreatePlayer.Player ( PlayerNumber )
    ..
    This\Dead = True
Knowing this, we can, for example, use a RespawnPlayer function, which positions the ship at a random spawn spot and toggles Dead to False.
Function RespawnPlayer ( This.Player )
    Local Here.SpawnSpot
    Local Channel

    ; Find a random spawn spot
    Here = RandomSpawnSpot ()

    ; Convert tilemap scale (array) to pixel scale (world)
    This\PosX = (Here\PosX-1) * TileWidth
    This\PosY = (Here\PosY-1) * TileHeight

    ; Point up and reset speed
    This\Angle = 0
    This\Speed = 0
    This\Heading = 0

    ; Make alive and kicking
    This\Dead = False

    ; Play respawn sound
    Channel = PlaySample ( SpawnSound , 10 )
    ChannelPan Channel , ( This\Number - 1 ) * 2 - 1

End Function
I've chosen to use an external function to place the code for Find a random spawn spot in, to increase or maintain readability and reusability. That RandomSpawnSpot function would return a reference to a spawn spot object which I can then use to convert its tilemap coordinate to a game world coordinate. Another interesting thing is the respawn sound. I'm using a bunch of Global variables to store the sounds in the game, just like in Project Z: Two.
; Effects
Global SpawnSound

..

; Load effects
SpawnSound = LoadSound ( "Spawn.WAV" )
I think grouping the initialization code together in one function is a good idea, as mentioned in Structure. Because then I can place the creation of the players as well as the loading of the effects in such an InitGame function to make the code more clear. Did you notice that the code to load a sound is actually code that can be placed in the Main initialization section, just like calls to LoadImage? Okay, so let's take a look at the RandomSpawnSpot function. Because we already know the number of spawn spots, as they are increased in the LoadTilemap function which is described in Spawn spots, we can simply pick a random number within that range and count up that number of times while walking through the list of spawn spots.
; Grab a random spawnspot from the list
Function RandomSpawnSpot.SpawnSpot ()
    Local Number
    Local Times
    Local This.SpawnSpot

    ; Start at the front
    This = First SpawnSpot

    ; Pick a random one using the known number of spawn spots
    Times = Rand ( 0 , SpawnSpots - 1 )

    ; Count until reached target value
    For Number = 1 To Times
        ; Next spawn spot
        This = After This
    Next

    Return This
End Function
Because we're using random numbers now, I'm also adding the following line of code:
; Reset randomizer
; (set number of milliseconds since system boot as randomizer base value)
SeedRnd MilliSecs ()
For more information about this code take a look at Part 6: Variable Collections of my Blitz Basic Full Foundation tutorial. The random number generator seeding code can also be placed in the Main initialization section.
Player control Now that we can (re)spawn a player (ship) let's add the ability to control it.
; Player 1 (left)
Const KeyW     =  17
Const KeyA     =  30
Const KeyS     =  31
Const KeyD     =  32

; Player 2 (right)
Const KeyUp    = 200
Const KeyDown  = 208
Const KeyLeft  = 203
Const KeyRight = 205

..

Function PlayersInput ()
    Local Player.Player
    Local Thrust
    Local RotateLeft
    Local RotateRight
    Local Fire

    For Player = Each Player

        Select Player\Number
            Case 1: Thrust      = KeyS
                    RotateLeft  = KeyA
                    RotateRight = KeyD
                    Fire        = KeyW
            Case 2: Thrust      = KeyDown
                    RotateLeft  = KeyLeft
                    RotateRight = KeyRight
                    Fire        = KeyUp
        End Select

        If KeyDown ( Thrust )      Then RespawnPlayer Player
        If KeyDown ( RotateLeft  ) Then RotatePlayerLeft  Player
        If KeyDown ( RotateRight ) Then RotatePlayerRight Player
        If KeyHit  ( Fire        ) Then FirePlayer        Player
    Next

End Function
This function can be called from the main loop in the Main execution section:
; Control both ships
PlayersInput
Now, both players should have the ability to control their ship. The new functions RotatePlayerLeft, RotatePlayerRight and FirePlayer should handle what their names suggest
Player (ship) thrust Maybe you have noticed that something is missing in the PlayersInput function to fully control a (player) ship. I mean, we can respawn, rotate and fire. But not thrust! Wait a minute.. That also means we need a thrust sound playing for each player, which should be audible when the ship thrusts, and stop playing when the ship stops thrusting (or dies). Let's add that first.
Global ThrustSound

..

ThrustSound = LoadSound ( "Thrust.WAV" )
LoopSound ThrustSound
As each player has the ability to thrust, it should also be audible individually, using the left/right stereo panning.
Type Player
    ..
    ; Request to thrust ship
    Field Thrusting
    ; Sound channel handle
    Field ThrustChannel
    ..
End Type
The Thrusting field controls the 'request to thrust' as I'm going to use another function to handle the physics for the ships in which I will then be able to know when the player has pressed the Thrust key. The ThrustChannel field should be set to the ThrustSound playing, which should start playing, like, when the Player is created. So I'm adding the following code to the CreatePlayer function:
; Prepare thrust sound
This\ThrustChannel = PlaySound ( ThrustSound )
PauseChannel This\ThrustChannel
ChannelPan This\ThrustChannel , ( PlayerNumber - 1 ) * 2 - 1
This is the pointer to the Player object. I'm pausing the sound right after starting it, because it should be audible only when the ship thrusts. If you're unfamiliar with (some of) this code, take a look at Thrust sound and Sound panning. Now we can thrust audibly by simply calling ResumeChannel which should take place in the PlayersInput function. When the ship is not dead, and the player presses the Thrust key, the ship should be respawned. But when the ship is dead, and the player presses the Thrust key, the ship should thrust instead. We can expand the Thrust key If in the PlayersInput function so that it reflects these new things.
If KeyDown ( Thrust )
    If Player\Dead
        RespawnPlayer Player
    Else
        Player\Thrusting = True
        ; Play thrust sound
        ResumeChannel Player\ThrustChannel
    End If
Else
    ; Stop thrust sound
    PauseChannel Player\ThrustChannel
End If
You could also, for example, instead of pausing the sound, set its volume to 0.0 (silent) using ChannelVolume. And resume it again by calling ChannelVolume with full volume, e.g. 1.0.
Rotating a player (ship) Let's move on to the functions that rotate a ship.
; Player (ship) rotation speed in degrees
Const RotateSpeed#   =    4

..

Function RotatePlayerLeft ( This.Player )

    If Not This\Dead
        This\Angle = This\Angle - RotateSpeed
        If This\Angle < 0 Then This\Angle = This\Angle + 360
    End If

End Function

..

Function RotatePlayerRight ( This.Player )

    If Not This\Dead
        This\Angle = This\Angle + RotateSpeed
        If This\Angle >= 360 Then This\Angle = This\Angle - 360
    End If

End Function
Very straight forward stuff. Can't rotate when dead. And limit the angle to [0..360]. The RotateSpeed constant is open for tweaking
Player (ship) fire Onto the FirePlayer function, which should create a bullet from the tip of the ship image flying in the ship's direction. Even though the function name suggests something different The algorithm to calculate all of this is almost identical to the one used in Project Z: Two. We can use the code from the Bullets chapter with only a few changes.
Global FireSound

..

FireSound = LoadSound ( "Fire.WAV" )

..

Function FirePlayer ( This.Player )
    Local PosX
    Local PosY
    Local VelX#
    Local VelY#
    Local Channel

    If Not This\Dead
        ; Calculate where to create bullet in front of ship
        PosX = This\PosX + VectorX ( BulletDistance , This\Angle )
        PosY = This\PosY + VectorY ( BulletDistance , This\Angle )

        ; Initial bullet velocity vector
        VelX = VectorX ( BulletSpeed , This\Angle )
        VelY = VectorY ( BulletSpeed , This\Angle )

        ; Add player velocity vector to bullet velocity vector (realism)
        VelX = VelX + VectorX ( This\Speed , This\Heading )
        VelY = VelY + VectorY ( This\Speed , This\Heading )

        CreateBullet PosX , PosY , VelX , VelY

        ; Play fire sound
        Channel = PlaySample ( FireSound , 10 )
        ChannelPan Channel , ( This\Number - 1 ) * 2 - 1
    End If

End Function
The realistic (versus the unrealistic) bullet physics are also described in Project Z: Two. Do a search for PlayerFireBullet.
Kill a player (ship) You may have noticed that almost every player function contains a check to see if the ship is alive. The DrawPlayers function is still missing that check!
..

For This = Each Player

    ; Player is dead
    If Not This\Dead

        ..
Just to make sure we're not drawing any dead players (or ships). Talking about dead ships, let's make it so.
Global ExplodeSound

..

ExplodeSound = LoadSound ( "Explode.WAV" )

..

Function KillPlayer ( This.Player )
    Local Channel

    This\Dead = True

    ; Stop thrust sound
    PauseChannel This\ThrustChannel

    ; Create a bunch of shrapnel pieces in random directions
    CreateBulletRay This\PosX , This\PosY

    ; Play explode sound
    Channel = PlaySample ( ExplodeSound , 50 )
    ChannelPan Channel , ( This\Number - 1 ) * 2 - 1

End Function
In case the Thrust sound is still playing, pause it. Also play a special explosion sound. And create a ray of bullets, as in the original design (Specification). The CreateBulletRay function does almost exactly the same thing as the SpawnShrapnel function in Project Z: Two.
Function CreateBulletRay ( InitialPosX , InitialPosY , InitialVelX# = 0 , InitialVelY# = 0 )
    Local Angle
    Local RandomAngle
    Local VelX#
    Local VelY#
    Local PosX#
    Local PosY#

    ; Divide 360 degrees in sections of 5 degrees
    For Angle = 0 To 360 Step 360/5

        ; Then choose a random angle inside that peace of pie
        RandomAngle = Angle + Rand ( 360/5 )

        ; Velocity: use half of bullet speed plus the initial speed (optional)
        VelX = InitialVelX + VectorX ( BulletSpeed / 2 , RandomAngle )
        VelY = InitialVelY + VectorY ( BulletSpeed / 2 , RandomAngle )

        ; Position: use half of ship tip distance plus the offset position
        PosX = InitialPosX + VectorX ( BulletDistance / 2 , RandomAngle )
        PosY = InitialPosY + VectorY ( BulletDistance / 2 , RandomAngle )

        ; Finally create it, but it's not a normal bullet
        CreateBullet PosX , PosY , VelX , VelY , False
    Next

End Function
The main difference is that here I'm not creating the bullets from (or on) a single point. Instead, I'm creating them in a circle a little bit away from that single point right from the start. This gives the impression of an explosion without the use of an explosion image. Something else is different, being the call to CreateBullet. There's a new (5th) parameter! This is because I want a bullet ray to consist of bullets that each stay alive for a random amount of time. Or at least a little more random than normal bullets, which adds a little more 'explosionness' to it. Here is the difference:
Function CreateBullet ( PosX , PosY , VelX# , VelY# , NormalBullet = True )

    ..

    This\TimeCreated = MilliSecs

    ; Bullet
    If NormalBullet

        ; Use default time to live
        This\TimeToLive = BulletTime

    ; Shrapnel
    Else

        ; Use slightly randomized default time to live
        This\TimeToLive = BulletTime / 2 + Rand ( BulletTime )

    End If

End Function

Updating a player (ship) All that remains now is a function to handle the juicy bit of the ship's motion, a.k.a. the ship's "Asteroids" physics, also explained above in Ship "Asteroids" physics. We can also add the gravity here, which we used in Ship gravity, which is also very open for tweaking
; Player (ship) thrust speed in pixels
Const ThrustSpeed#   =    0.12

; Player (ship) maximum velocity in pixels
Const MaxSpeed       =    5

; Downward gravity strength in pixels (on ship)
Const GravityPull#   =    0.03

..

Function UpdatePlayer ( This.Player )
    Local ThrustX#
    Local ThrustY#
    Local SpeedX#
    Local SpeedY#

    If Not This\Dead
        If This\Thrusting
            This\Thrusting = False

            ; Thrust vector
            ThrustX = VectorX ( ThrustSpeed , This\Angle )
            ThrustY = VectorY ( ThrustSpeed , This\Angle )
        Else
            ThrustX = 0
            ThrustY = 0
        End If

        ; Thrust vector + old velocity vector = new velocity vector
        SpeedX = VectorX ( This\Speed , This\Heading ) + ThrustX
        SpeedY = VectorY ( This\Speed , This\Heading ) + ThrustY

        ; Add gravity vector to velocity vector (180 degrees = down)
        SpeedX = SpeedX + VectorX ( GravityPull , 180 )
        SpeedY = SpeedY + VectorY ( GravityPull , 180 )

        ; Update speed and heading with new velocity vector
        This\Speed = VectorDistance ( SpeedX , SpeedY )
        This\Heading = VectorAngle ( SpeedX , SpeedY )

        ; Limit velocity (speed of ship)
        If This\Speed > MaxSpeed
            This\Speed = MaxSpeed

            ; Recalculate limited velocity vector
            SpeedX = VectorX ( This\Speed , This\Heading )
            SpeedY = VectorY ( This\Speed , This\Heading )
        End If

        ; Update position with velocity vector
        This\PosX = This\PosX + SpeedX
        This\PosY = This\PosY + SpeedY

    End If

End Function
If any of the above code is unclear, please refer to the chapter references above or the Knowledge reference. The UpdatePlayer function, just like the UpdateBullets function, can be called from the main loop in the Main execution section, as you can see in Structure.
; Update game
UpdatePlayer Player( 1 )
UpdatePlayer Player( 2 )
UpdateBullets

Music For the music I liked creating a cover of the original Gravity Force tune so I have a cool MP3 track now that I can put into play.
; Soundtrack
Global GameMusic

; Background music channel handle
Global MusicChannel

..

; Load and play music
GameMusic = LoadSound ( "TheChance - Gravity Force.MP3" )
LoopSound GameMusic
MusicChannel = PlaySound ( GameMusic )
; - At half volume, to put it in the background
ChannelVolume MusicChannel , 0.5
The reason I did not use PlayMusic is because I found it to be a little buggy for some tracks, and less controllable as well. I would place the above code very early in the program, in the Main initialization section, to be able to enjoy listening to the music as soon as possible
(Media) file locations After a while the amount of external files for the game started to become significant, and a little organization would improve my oversight. So, I've created a bunch of (sub-)folders to store all the (media) files in, like Audio, Tilemaps and Graphics. But in the code, every call to LoadImage, LoadSound and LoadTilemap still use the old (root) location, e.g. the same place where the source code file X-NON.BB is stored. By adding a bunch of constants corresponding to the bunch of folders, I'll never have to worry about this again
; Locations of external files
Const AudioFolder$  = "Audio\"
Const GraphicsFolder$ = "Graphics\"
Const TilemapsFolder$ = "Tilemaps\"
For example, the music code would, after transformation, look like this:
GameMusic = LoadSound ( AudioFolder + "TheChance - Gravity Force.MP3" )
Another example, the rotated player ship images:
PlayerImages( PlayerNumber , Angle ) =
    LoadImage ( GraphicsFolder + "Ships\" + "Ship " + PlayerNumber + " - " + Angle + ".BMP" )
So these ship images are actually in a sub-folder of the GraphicsFolder now.
Ship vs ship collision As described in the design Specification, we need to check for 4 types of collisions: - Ship vs Ship: Kill both - Bullet vs Ship: Kill both - Tilemap vs Ship: Kill ship - Tilemap vs Bullet: Kill bullet Let's start with Ship vs Ship, as that's probably the easiest one. The first condition is that both player have to be alive:
; Both players must be alive
If ( Not Player( 1 )\Dead ) And ( Not Player( 2 )\Dead )
Then we need the images of each player for a pixel-perfect collision check, which we can just grab from the DrawPlayers code.
Local PlayerImg1
Local PlayerImg2

..

PlayerImg1 = PlayerImages( 1 , LimitAngle ( Player( 1 )\Angle , 5 ) )
PlayerImg2 = PlayerImages( 2 , LimitAngle ( Player( 2 )\Angle , 5 ) )
We can first perform an ImagesOverlap check to see if the players are actually near each other, which speeds up the collision checking considerably.
If ImagesOverlap ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY ,
                   PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY )
Followed by ImagesCollide, including the number of the first frame in the image (e.g. 0).
If ImagesCollide ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY , 0 ,
                   PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY , 0 )
Finally both (player) ships should perish in the occurence of a fatal clash. E.g. kill the ship if the images collide.
; And kill them if so
KillPlayer Player( 1 )
KillPlayer Player( 2 )
Putting it all together would give us this:
Function PlayerCollideWithPlayer ()
    Local PlayerImg1
    Local PlayerImg2

    ; Both players must be alive
    If ( Not Player( 1 )\Dead ) And ( Not Player( 2 )\Dead )

        PlayerImg1 = PlayerImages( 1 , LimitAngle ( Player( 1 )\Angle , 5 ) )
        PlayerImg2 = PlayerImages( 2 , LimitAngle ( Player( 2 )\Angle , 5 ) )

        If ImagesOverlap ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY ,
                           PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY )
            If ImagesCollide ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY , 0 ,
                               PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY , 0 )
                ; And kill them if so
                KillPlayer Player( 1 )
                KillPlayer Player( 2 )
            End If
        End If
    End If

End Function
Calling this function, as well as any other collision functions, would happen somewhere in the main loop in the Main execution section of the final program.
Ship vs bullet collision The ship vs bullet collision routine is probably a little bit harder, because both ships have to be compared with every bullet in existence. Even though this collision routine is a lot easier than the one used in Project Z: Two, it is identical in nature. Let's start by adding a For..Each loop for each player ship, checking if it is actually alive, and grabbing the rotation image to check.
Local Player.Player
Local PlayerImg

..

For Player = Each Player

    If Not Player\Dead

        PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) )
I prefer putting the player in the outer loop, although you can put it in the inner loop as well, if you want to Because now I'm adding a similar loop for each bullet right below the previous code, directly followed by the image collision checks, just like in the ship to ship collision routine. Only now, using the BulletImage.
Local This.Bullet

..

For This = Each Bullet

    If ImagesOverlap ( PlayerImg , Player\PosX , Player\PosY ,
                       BulletImage , This\PosX , This\PosY )
        If ImagesCollide ( PlayerImg , Player\PosX , Player\PosY , 0 ,
                           BulletImage , This\PosX , This\PosY , 0 )
When the images collide, again, both the bullet and the (player) ship should be killed.
Delete This
KillPlayer Player
; This player is dead, don't check for any more bullets
Exit
If a collision occurs, the routine should exit the inner (bullet) loop immediately because the current player in the outer loop isn't alive anymore anyway. To summarize, here is the full routine:
Function PlayersCollideWithBullets ()
    Local Player.Player
    Local This.Bullet
    Local PlayerImg

    For Player = Each Player

        If Not Player\Dead

            PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) )

            For This = Each Bullet

                If ImagesOverlap ( PlayerImg , Player\PosX , Player\PosY ,
                                   BulletImage , This\PosX , This\PosY )
                    If ImagesCollide ( PlayerImg , Player\PosX , Player\PosY , 0 ,
                                       BulletImage , This\PosX , This\PosY , 0 )

                        Delete This
                        KillPlayer Player
                        ; This player is dead, don't check for any more bullets
                        Exit

                    End If
                End If

            Next

        End If

    Next

End Function

Tilemap vs ship collision This tilemap vs ship collision routine will, again, be very much like the collision detection algorithm explained in the Tilemap tutorial. It is also very similar to our DrawTilemap function, so you'll probably recognize most (if not all) of it. I'm gonna start it in exactly the same way as the previous collision function.
Local Player.Player
Local PlayerImg

..

For Player = Each Player

    If Not Player\Dead

        PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) )
To check the player position with the tilemap I'm changing the scale from game world to tile map. Because the player ship is near the size of a single tile, I can do a check with all adjacent tiles, e.g. the tiles right next to the tile under the player. This results in a 3x3 block of tiles under the player position.
Local TileX
Local TileY
Local StartTileX
Local StartTileY
Local EndTileX
Local EndTileY

..

; Tile under player
TileX = Player\PosX / TileWidth
TileY = Player\PosY / TileHeight

; 3x3 tile block under player
StartTileX = TileX - 1
StartTileY = TileY - 1
EndTileX = TileX + 1
EndTileY = TileY + 1
Adding some boundary checking, in case the ship goes off the tilemap, or near the tilemap's boundaries.
; Limit to edges of tilemap
If StartTileX < 0 Then StartTileX = 0
If StartTileY < 0 Then StartTileY = 0
If EndTileX > TilemapWidth -1 Then EndTileX = TilemapWidth -1
If EndTileY > TilemapHeight-1 Then EndTileY = TilemapHeight-1
Now it's just a matter of walking through the 3x3 block of tiles. Also, I only want to check with tiles that are not empty, so I'll check only tiles that have a non-zero value.
For TileX = StartTileX To EndTileX
    For TileY = StartTileY To EndTileY

        ; Is non-zero
        If Tilemap( TileX+1 , TileY+1 )
Now I'm gonna scale the tile position back to world coordinates, so I can perform image checks. Same as before, starting with ImagesOverlap, followed by ImagesCollide. Only this time, using the player ship image and the TileStrip, along with the image frames contained within the TileStrip.
Local TilePosX
Local TilePosY

..

TilePosX = TileX * TileWidth
TilePosY = TileY * TileHeight

If ImagesOverlap ( PlayerImg , Player\PosX , Player\PosY ,
                   TileStrip , TilePosX , TilePosY )
    If ImagesCollide ( PlayerImg , Player\PosX , Player\PosY , 0 ,
                       TileStrip , TilePosX , TilePosY , Tilemap( TileX+1 , TileY+1 ) )
When the images collide, just the player ship should be killed. No disappearing tiles or anything
KillPlayer Player
Putting it all together:
Function PlayersCollideWithTilemap ()
    Local Player.Player
    Local TileX
    Local TileY
    Local StartTileX
    Local StartTileY
    Local EndTileX
    Local EndTileY
    Local TilePosX
    Local TilePosY
    Local PlayerImg

    For Player = Each Player

        If Not Player\Dead

            PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) )

            ; Tile under player
            TileX = Player\PosX / TileWidth
            TileY = Player\PosY / TileHeight

            ; 3x3 tile block under player
            StartTileX = TileX - 1
            StartTileY = TileY - 1
            EndTileX = TileX + 1
            EndTileY = TileY + 1

            ; Limit to edges of tilemap
            If StartTileX < 0 Then StartTileX = 0
            If StartTileY < 0 Then StartTileY = 0
            If EndTileX > TilemapWidth -1 Then EndTileX = TilemapWidth -1
            If EndTileY > TilemapHeight-1 Then EndTileY = TilemapHeight-1

            For TileX = StartTileX To EndTileX
                For TileY = StartTileY To EndTileY

                    ; Is non-zero
                    If Tilemap( TileX+1 , TileY+1 )
                        TilePosX = TileX * TileWidth
                        TilePosY = TileY * TileHeight

                        If ImagesOverlap ( PlayerImg , Player\PosX , Player\PosY ,
                                           TileStrip , TilePosX , TilePosY )
                            If ImagesCollide ( PlayerImg , Player\PosX , Player\PosY , 0 ,
                                               TileStrip , TilePosX , TilePosY , Tilemap( TileX+1 , TileY+1 ) )

                                KillPlayer Player

                            End If
                        End If
                    End If

                Next
            Next

        End If

    Next

End Function

Tilemap vs bullet collision Well, this should be a snap, as the tilemap vs bullet collision routine is almost identical to the tilemap vs ship collision routine. First we can simply replace all player things with bullet things. I'll show the changes that I've made:
Local This.Bullet

..

For This = Each Bullet

    TileX = This\PosX / TileWidth
    TileY = This\PosY / TileHeight

..

If ImagesOverlap ( BulletImage , This\PosX , This\PosY ,
                   TileStrip , TilePosX , TilePosY )
    If ImagesCollide ( BulletImage , This\PosX , This\PosY , 0 ,
                       TileStrip , TilePosX , TilePosY , Tilemap( TileX+1 , TileY+1 ) )

        Delete This
So far so good. The bullet should disappear on tile-impact. But looking at the place in the code where the bullet gets deleted, I see that the object that This refers to, no longer exists in the inner loop. So the next time the contents of This is checked, Blitz would crash, unless we exit the inner loop just like before. Because there are now 2 inner loops inside of the bullet loop, we can use a variable to guide us out.
Local ExitLoops
So when the inner loops start:
ExitLoops = False
And when we delete the bullet:
ExitLoops = True
Exit
Exit would exit the first inner loop (TileY). So at the bottom of the TileX loop:
    Next  ; TileY

    If ExitLoops Then Exit

Next  ; TileX
This should carry us safely through the nested loops. To spice up the game just a little more, I want to add a sound when a bullet hits a tile. So where the bullet is deleted, I add another line:
; Play collide sound
PlaySample CollideSound , 50
Finally placing the Audio definition:
Global CollideSound
And Main initialization code:
CollideSound = LoadSound ( AudioFolder + "Collide.WAV" )
For extra clarity, here's the combined function:
Function BulletsCollideWithTilemap ()
    Local This.Bullet
    Local TileX
    Local TileY
    Local StartTileX
    Local StartTileY
    Local EndTileX
    Local EndTileY
    Local TilePosX
    Local TilePosY
    Local ExitLoops

    For This = Each Bullet

        TileX = This\PosX / TileWidth
        TileY = This\PosY / TileHeight

        StartTileX = TileX - 1
        StartTileY = TileY - 1
        EndTileX = TileX + 1
        EndTileY = TileY + 1

        If StartTileX < 0 Then StartTileX = 0
        If StartTileY < 0 Then StartTileY = 0
        If EndTileX > TilemapWidth -1 Then EndTileX = TilemapWidth -1
        If EndTileY > TilemapHeight-1 Then EndTileY = TilemapHeight-1

        ExitLoops = False
        For TileX = StartTileX To EndTileX
            For TileY = StartTileY To EndTileY

                ; Is non-zero
                If Tilemap( TileX+1 , TileY+1 )
                    TilePosX = TileX * TileWidth
                    TilePosY = TileY * TileHeight

                    If ImagesOverlap ( BulletImage , This\PosX , This\PosY ,
                                       TileStrip , TilePosX , TilePosY )
                        If ImagesCollide ( BulletImage , This\PosX , This\PosY , 0 ,
                                           TileStrip , TilePosX , TilePosY , Tilemap( TileX+1 , TileY+1 ) )

                            Delete This
                            ; Play collide sound
                            PlaySample CollideSound , 50

                            ExitLoops = True
                            Exit

                        End If
                    End If
                End If

            Next
            If ExitLoops Then Exit
        Next

    Next

End Function
So now we have 4 collision functions which we can stick in the main loop:
; Collisions
PlayerCollideWithPlayer
PlayersCollideWithTilemap
PlayersCollideWithBullets
BulletsCollideWithTilemap

Game control Let's take a look at the game flow we have at the moment. And compare that with the original design Specification. When the game starts, players are created, but not spawned yet. Once a player presses the fire button, its ship is spawned, and can fly it around. Once the ship dies, a ray of bullets is spawned, the ship disappears, and the player can press the fire button again to repeat the process. So it appears that everything is in place! Before summarizing the main loop, I'm going to add a simple Pause feature. First, a definition for the Miscellaneous section, which I've sneakily smuggled in Followed by the value's initialization to be placed in the Main initialization section.
; Game states
Global GamePaused

..

; Set default game states
GamePaused = False
Let's pick the 'P' key to Pause the game, and add Esc as the quit key while we're at it.
; Game control
Const KeyP     =  25
Const KeyEsc   =   1
I'm putting that in the Miscellaneous section as well. And this is some new code for in the main loop:
If KeyHit ( KeyP )
    GamePaused = Not GamePaused
End If

..

Until KeyHit ( KeyEsc )
While the game is paused, none of the objects should be updated, no collisions should occur, and the ships should no longer receive input from the player. So this affects the input functions, the object update functions and the collision functions. Simply wrapping them up should do the trick.
If Not GamePaused
    ; Control both ships
    PlayersInput

    ; Update game
    UpdatePlayer Player( 1 )
    UpdatePlayer Player( 2 )
    UpdateBullets

    ; Collisions
    PlayerCollideWithPlayer
    PlayersCollideWithTilemap
    PlayersCollideWithBullets
    BulletsCollideWithTilemap
End If
After having added all of the previous bits of code, the game's main loop will look like this:
; Main loop
Repeat

    ; Sync time
    MilliSecs = MilliSecs ()

    If KeyHit ( KeyP )
        GamePaused = Not GamePaused
    End If

    If Not GamePaused
        ; Control both ships
        PlayersInput

        ; Update game
        UpdatePlayer Player( 1 )
        UpdatePlayer Player( 2 )
        UpdateBullets

        ; Collisions
        PlayerCollideWithPlayer
        PlayersCollideWithTilemap
        PlayersCollideWithBullets
        BulletsCollideWithTilemap
    End If

    ; Render game
    ; - Player 1
    ViewPort 0 , 0 , ScreenWidth / 2 , ScreenHeight
    Origin ScreenWidth / 4 , ScreenHeight / 2
    DrawTilemap Player( 1 )
    DrawBullets Player( 1 )
    DrawPlayers Player( 1 )

    ; - Player 2
    ViewPort ScreenWidth / 2 , 0 , ScreenWidth / 2 , ScreenHeight
    Origin ScreenWidth / 2 + ScreenWidth / 4 , ScreenHeight / 2
    DrawTilemap Player( 2 )
    DrawBullets Player( 2 )
    DrawPlayers Player( 2 )

    ; - Screen
    Viewport 0 , 0 , ScreenWidth , ScreenHeight
    Origin 0 , 0
    Color 0 , 0 , 0
    Rect ScreenWidth/2 , 0 , 1 , ScreenHeight
    Flip
    Cls

    ; Sync game speed
    WaitTimer GameTimer

Until KeyHit ( KeyEsc )

; Blitz cleans up
End
I've also added a vertical line in the center to add extra accent to the view limits. Note that the order of the things in the main loop can seriously affect the working of the game. That's why I prefer to stick to one particular order in most of my programs, which looks like this: Time related Input related Objects related Rendering related Here are some examples for each of these. Time related examples: - Set MilliSecs to MilliSecs(). - Calculate the number of Frames Per Second. Input related examples: - Call PlayersInput(). - Pause or unpause the game. Object related examples: - Update the player ships. - Handle physics. - Collision detection. Rendering related examples: - Set viewports. - Draw backgrounds and objects. Sometimes parts that are rendering related can also be time related, like WaitTimer in the main loop. So for me it makes sense because from a run-time point of view the time related section is executed right after the rendering related section. Feel free to experiment!
Fixing bugs While expanding the code, I've noticed a few bugs (or features ). The 'pause game'-bug When you fly around shooting, and suddenly pause the game, wait for a little bit, and unpause the game again, bullets will be gone instantly. This is because MilliSecs() is not paused when the game is paused. This can be fixed for example by adding the duration of the Pause to the value of the TimeCreated field in every Bullet object.
Global PauseStart

..

If KeyHit ( KeyP )
    If Not GamePaused
        PauseStart = MilliSecs
    Else
        BulletTime MilliSecs - PauseStart
    End If
    GamePaused = Not GamePaused
End If

..

Function BulletTime ( TimeShift )
    Local This.Bullet

    For This = Each Bullet

        This\TimeCreated = This\TimeCreated + TimeShift
    Next

End Function
I have not added this to the final game, so I'll leave it up to you to test it to see if it actually works Input buffer bugs Sometimes when you press keys (like Esc) while the game is being paused, it will cause havoc (like instant quit) once the game is unpaused. I like fixing these particular bugs by placing a FlushKeys right after unpausing the game.
If KeyHit ( KeyP )
    GamePaused = Not GamePaused
    FlushKeys  ; Forget pressed keys
End If
The same goes for having finished loading up the game, at the end of the InitGame function.
; Clear any pressed keys
FlushKeys
E.g. no sudden and instant quits after loading until the game has actually finished loading. Superfast respawn When your ship crashes, you can instantly press the Thrust/Respawn key again to instantly respawn at a new spawn spot. I feel this is a little too flexible for this game, especially in the middle of a dogfight! So I've decided to add some code to prevent a player from respawning its ship for a certain amount of time after having crashed. So this would affect the RespawnPlayer and KillPlayer functions. Similar to a PauseStart global variable I'll need to keep track of the time the player crashed. But here, both players can crash, so I'll add another field to the Player object.
Type Player

    ..

    ; Time of death
    Field DiedWhen
Now I can set the 'time of death' when the ship crashes, e.g. in the KillPlayer function.
Function KillPlayer ( This.Player )

    ..

    This\DiedWhen = MilliSecs
Then, in the RespawnPlayer function, I can check if the time to wait has elapsed. Let's make the 'time for forced meditation' 2 seconds.
Function RespawnPlayer ( This.Player )

    ..

    ; How much time to wait (in ms) before being able to spawn after dying
    If MilliSecs - This\DiedWhen >= 2000
This value is open for tweaking And could also have been placed in the Definition area of the code using a Constant. Specifically these 2 seconds allow for the 'forced' display of the nice explosion effect when a ship crashes. Spawn-crashing Imagine both players spawning simultaneously. The game will spawn both players regardless, and at a completely random spawn spot. This means that sometimes you can spawn exactly where the other player already is! One way to fix this is for example by remembering the last spawn spot used, or creating a list of randomized spawn spots. But I've chosen a different approach, which also works when having just 1 spawn spot. I'm going to check for a collision before actually spawning the player. This means that I would have to put a call to PlayerCollideWithPlayer inside the RespawnPlayer function (!). PlayerCollideWithPlayer currently takes into account the occurence of both players being dead already, so I have to modify it first. But it is already being used for normal collision detection in the main loop. So I will duplicate the function, while being able to re-use existing code and keeping full backward-compatibility with the current collision detection.
Function PlayerCollideWithPlayer ()

    ; Both players must be alive
    If ( Not Player( 1 )\Dead ) And ( Not Player( 2 )\Dead )
        ; Then see if they collide
        If PlayersCollide ()
            ; And kill them if so
            KillPlayer Player( 1 )
            KillPlayer Player( 2 )
        End If
    End If

End Function
The effect of the PlayerCollideWithPlayer function is seemingly still the same, as I have just moved the actual collision check to the new PlayersCollide function.
Function PlayersCollide ()
    Local PlayerImg1
    Local PlayerImg2

    PlayerImg1 = PlayerImages( 1 , LimitAngle ( Player( 1 )\Angle , 5 ) )
    PlayerImg2 = PlayerImages( 2 , LimitAngle ( Player( 2 )\Angle , 5 ) )

    If ImagesOverlap ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY ,
                       PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY )
        If ImagesCollide ( PlayerImg1 , Player( 1 )\PosX , Player( 1 )\PosY , 0 ,
                           PlayerImg2 , Player( 2 )\PosX , Player( 2 )\PosY , 0 )
            Return True
        End If
    End If

End Function
There's actually no really new code. Only the extraction of the collision detection into a new function. Now the idea is to allow respawn only if: - Both players are dead. - One player is dead and there's no collision. The first juicy bit that would go into RespawnPlayer is nearly the same bit as in the PlayerCollideWithPlayer function.
Function RespawnPlayer ( This.Player )

    ..

    Local Respawn

    ..

    ; Prevent spawning over another player, if a player is alive
    If ( Not Player( 1 )\Dead ) Or ( Not Player( 2 )\Dead )
        If Not PlayersCollide ()
            Respawn = True
        End If
    Else
        Respawn = True
    End If
Only the And has changed into Or, to alter the conditions (to respawn). I'm also using a local variable named Respawn to keep most of the existing code intact, and to increase the clarity of the code. Finally wrapping the resurrection code in the conditional Respawn sauce.
If Respawn
    ; Make alive and kicking
    This\Dead = False
I'm leaving most of the existing code intact, like the adjustment of the (player) ship position to the new (random) location of a spawn spot when you press the Respawn button. So if a (re)spawn fails, the camera viewpoint is altered anyway, and you can instantly see 'the reason' for not being able to spawn.
Background image After playing the game some more, it seemed to me that the plain black background was a bit dull for my taste. So I've decided to create a background image called Star Background.PNG with lots of dimmed stars. Adding some code to the program for defining and loading the image.
; Stars in the background
Global BackgroundImage

..

; Load background image
BackgroundImage = LoadImage ( GraphicsFolder + "Star Background.PNG" )
So now I can replace the Cls command with TileBlock to display the image across the entire screen regardless of the resolution.
    ; - Screen
    Viewport 0 , 0 , ScreenWidth , ScreenHeight
    Origin 0 , 0
    Color 0 , 0 , 0
    Rect ScreenWidth/2 , 0 , 1 , ScreenHeight
    Flip
    TileBlock
This isn't a very clean location for the TileBlock command, but I'll change that in a bit. I like adding things in a quick and dirty way first. That way I can see very quickly if it's something I want to keep. So when I tested the game with this new code added, it still seemed a bit dull to me. Wouldn't it be cool to have a moving background, much like parallax scrolling? This would mean that the background motion would depend on the player speed or position. For example the tilemap 'travels' at the same speed as the player. Imagine making the background 'travel' at half the speed of the player. And because camera orientation means reversing the coordinates, where (0,0) is the origin, as previously shown in Viewpoint, the entire code is no more complicated than this:
TileBlock BackgroundImage , -Player( 1 )\PosX/2 , -Player( 1 )\PosY/2
I'm adding this for each player.
; Render game
; - Player 1
Viewport 0 , 0 , ScreenWidth / 2 , ScreenHeight

TileBlock BackgroundImage , -Player( 1 )\PosX/2 , -Player( 1 )\PosY/2

Origin ScreenWidth / 4 , ScreenHeight / 2
DrawTilemap Player( 1 )
DrawBullets Player( 1 )
DrawPlayers Player( 1 )

; - Player 2
Viewport ScreenWidth / 2 , 0 , ScreenWidth / 2 , ScreenHeight

TileBlock BackgroundImage , -Player( 2 )\PosX/2 , -Player( 2 )\PosY/2

Origin ScreenWidth / 2 + ScreenWidth / 4 , ScreenHeight / 2
DrawTilemap Player( 2 )
DrawBullets Player( 2 )
DrawPlayers Player( 2 )
Now I can completely remove the other TileBlock below the call to Flip.
More video modes Let's expand the number of resolutions that are supported by the game, to increase the chance of getting the maximum width for each player's view. I've added the previous resolution detection code, as previously described in Video mode detection, in the InitGame function. So that's also where I've added a large number of different new resolutions.
; Resolution constants from Blitz
Const WindowMode_AutoDetect = 0
Const WindowMode_FullScreen = 1
Const WindowMode_Windowed   = 2
Const WindowMode_Scaled     = 3

..

; Graphics resolution
Global ScreenWidth
Global ScreenHeight
Global WindowMode
Global ColorDepth

..

; Check if a resolution is listed in the video card's supported videomodes
Function ResAvail ( Width , Height )
    Local ColorDepth

    For ColorDepth = 16 To 32 Step 8
        If GfxModeExists ( Width , Height , ColorDepth )
            Return ColorDepth
        End If
    Next
End Function

..

Function InitGame ()

    ..

    ; Windowed in debug mode, otherwise fullscreen
    WindowMode = WindowMode_AutoDetect

    ; Autodetect optimal resolution
    ; - Wide screen medium resolution (2.13x wide)
    ScreenWidth  = 1024
    ScreenHeight = 480
    ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
    If Not ColorDepth

      ; - Wide screen high resolution (1.78x wide)
      ScreenWidth = 1280
      ScreenHeight = 720
      ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
      If Not ColorDepth

        ; - Wide screen medium-low resolution (1.77x wide)
        ScreenWidth  = 848
        ScreenHeight = 480
        ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
        If Not ColorDepth

          ; - Wide screen low resolution (1.5x wide)
          ScreenWidth  = 720
          ScreenHeight = 480
          ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
          If Not ColorDepth

            ; - CRT screen high resolution (1.33x wide)
            ScreenWidth  = 1024
            ScreenHeight = 768
            ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
            If Not ColorDepth

              ; - CRT screen medium resolution (1.33x wide)
              ScreenWidth  = 800
              ScreenHeight = 600
              ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
              If Not ColorDepth

                ; - CRT screen low resolution (1.33x wide)
                ScreenWidth  = 640
                ScreenHeight = 480
                ColorDepth = ResAvail ( ScreenWidth , ScreenHeight )
                ; - If this resolution will not work either,
                ;   Blitz will show an error message anyway
              End If
            End If
          End If
        End If
      End If
    End If

    ; Try the detected screen resolution
    Graphics ScreenWidth , ScreenHeight , ColorDepth , WindowMode

    ..

Final structure Also, I've further structured the code to follow the guideline as explained in Structure where I've ended up with the following categories: - Graphics handling - Audio - Player - Bullet - Tilemap - Spawn spots - Collision - Mathematics - Miscellaneous - Main sub-routines - Main execution Maybe you can also see that it is a little bit different from the original design Specification, but it still follows the original design Structure. And, to me, it's very easy to both read and understand
Verifying loaded files A number of times when I started the game I forgot to put some external files in the right place. So then the game crashed with some weird and hard to understand error message. If some person tries to run this game without the external files in the right place, it should not crash but give a descriptive error message. I've come up with a very simple function that checks if something was loaded properly. And if it wasn't, it will show a custom error message.
Function Verify ( TargetHandle , ErrorMessage$ = "probs :(" )
    If Not TargetHandle Then RuntimeError ErrorMessage
End Function
For example to check if the intro music is loaded properly, I would call Verify after attempting to load the music.
; Start music first
GameMusic = LoadSound ( AudioFolder + "TheChance - Gravity Force.MP3" )
Verify GameMusic , "Unable to load intro music"
Blitz (very flexibly) allows the loading of sounds, images and other files without succeeding, as the programmer can then decide what the consequence should be. So I've added a call to Verify for the loading of all sounds and images, in the same way as the above intro music. For example the code for loading the rotated images would then look like this:
Local PlayerNumber
Local Angle

..

; Load pre-rotated player images
For PlayerNumber = 1 To 2
    For Angle = 0 To 360 Step 5
        PlayerImages( PlayerNumber , Angle ) =
            LoadImage ( GraphicsFolder + "Ships\" +
                        "Ship " + PlayerNumber + " - " + Angle + ".BMP" )
        Verify PlayerImages( PlayerNumber , Angle ) ,
            "Unable to load ship image " + PlayerNumber + "," + Angle
Then I came to the loading of the tilemap, and discovered that LoadTilemap only returns False if the tilemap file doesn't exist (or can't be loaded).
Local TilemapLoaded

..

; Load tile map data
TilemapLoaded = LoadTilemap ( TilemapsFolder + "Test Tilemap 1.TXT" )
Verify TilemapLoaded , "Unable to load tilemap"
But what if the tilemap itself doesn't contain any spawn spots at all? The game would certainly crash because the RespawnPlayer function depends on the existence of a spawn spot. So then I figured I can just alter the LoadTilemap function in a very quick way, to return False if the number of spawn spots is zero.
Function LoadTilemap ( FileName$ )

    ..

    CloseFile FileHandle

    ; Return true if any spawn spots found, otherwise false
    If SpawnSpots > 0
        Return True
    Else
        Return False
    End If

End Function
Did you know both pieces of code above can be compressed a little?
; Load tile map data
Verify LoadTilemap ( TilemapsFolder + "Test Tilemap 1.TXT" ) , "Unable to load tilemap"

..

; Return true if any spawn spots found, otherwise false
Return SpawnSpots > 0
But if you prefer the previous style, please do follow your preference Note that there are a few more ways to crash the game For example by altering the tilemap file and putting in some bogus data But I'll leave it up to you to figure out how to prevent the game from crashing then. For example you can range-check the values like TilemapWidth and TilemapHeight after having been loaded in with ReadLine.
Splash screen To make the game look a little more professional, and to prevent the 'game explorer' from being thrown straight into the game, I'm adding an intro screen while the game is being loaded. It looks like a tilemap, but it's just an image which I've saved as X-NON Logo.PNG. Actually, I've created it using tile frames from the file LogoTiles.BMP using a tilemap editor All of the code required for loading and drawing the logo can be placed in the InitGame function.
Local IntroImage

..

; Show splash screen / front-end
IntroImage = LoadImage ( GraphicsFolder + "X-NON Logo.PNG" )
Verify IntroImage , "Unable to load intro image"
MidHandle IntroImage
DrawImage IntroImage , ScreenWidth / 2 , ScreenHeight / 2
I'm placing the logo in the center of the screen so that it doesn't matter in which resolution you run the game. On fast computers loading all of the external files takes a fraction of a second. And I want to show the logo for at least a little while longer, say.. 3 seconds. I'm gonna use a simple timer, just like we've done before in the Bullets chapter.
; Remember when started loading
LoadStart = MilliSecs ()
The above code would be placed somewhere in the beginning of the InitGame function.
; If loading took less than 3 seconds,
; wait until those same 3 seconds are up.
; This, in order to make the game logo visible
; for at least a little while.
While MilliSecs () - LoadStart < 3000
Wend
And this code would be placed somewhere at the end of the InitGame function.
Tilemap editor In the beginning I thought to use only a text editor to create tilemaps for the game, But at some point I discovered that some things that I wanted to do just were not possible with those tools. For example, the final tilemap, which is in Arena 1.TXT, I have created with the help of various other programs. In the folder Extra you will find some of these programs as well as multiple tilemap editors and some extra files. Note that I have not tutorialized those programs so you're on your own to discover what's what and how to operate them, which should not be a problem ASCII Converter.BB This allows you to convert images to ASCII files and back, so you can edit tilemaps using drawing programs! Editor (simple).BB The tilemap file you want to work on is specified directly in the code itself. It is loaded when the program is run, and also saved when the you quit the program. These are the keys you can use in the program: Space: Toggle transparent drawing tile Cursor keys: Move map (+Shift): Move map faster Enter: Select tile for drawing Left mouse button: Draw selected tile Right mouse button: Erase tile Middle mouse button: Pickup tile from tilemap Escape: Exit and save Editor (elaborate).BB This program works as a 'normal' tilemap editor should by being able to load and save from within the program. Inside the code you can easily change the tileset to use, for example the tileset for the tilemap or the logo. Keyboard shortcuts: Tab: Show grid overlay Space: Toggle drawing tile transparency and hide empty tiles N: New tilemap (width,height) L: Load tilemap (filename) S: Save tilemap (filename) Cursor keys: Move tilemap (+Shift): Move tilemap faster Enter: Select tile to draw Keypad cursor keys: Move tile selection Left mouse button: Paint tile Right mouse button: Erase tile Middle mouse button: Pickup tile Escape: Exit
Expansion If you haven't already, take a look at X-NON.BB in the Tutorial files index to see the final resulting code of this tutorial, if you want Here are some more features that I thought would be interesting to be added to the game at a later stage: - Ship health; making the ship flash when hit, needing more than 1 hit to be killed. - A simple scoring system; so you don't have to count yourself - A simple menu interface; so you can change some settings, like speed, gravity, controls, etc. - New tilemaps; for some more variation. - New tiles with more spice; to make the experience more vivid. - New ships; allowing players to choose a ship that fits their own style. You can also go overboard and pick some of these: - Tilemap editor integrated into the game; so you can add or modify maps with ease. - Single player missions; if you don't have a partner to play with, you can fly through a tilemap maze with various obstacles to overcome. - Network play (like DirectPlay); to play with anyone you like, without leaving your home.
Bonus Originally the idea was to have a game that was normalized into extremes utilizing advanced object oriented-like variable structures and their accompanying methods. Actually, I have a lot of projects which are built this way (too many!), but it occured to me that this was a little extreme for a game of this size You see, I sometimes have the tendency to make things more complex than they have to be. So I immensely simplified the game structure a "few" times. If you're interested, the full blown project package is available, containing all of these layers of simplification, as well as all stages of construction (and rewrites), weighing at ~50 Megabytes. I've included one of the most useful bits from the full blown project package combined in a single bonus tutorial file, which is called Plan Snippets.txt located in the Extra folder. It contains considerable background information and even more details behind the construction of X-NON using a home-made and normalized technique (!) which, in essence, can also be applied to any other development process. Despite the current 'simplicity' of this tutorial, if anything is unclear about this tutorial or if you have questions anyway, just let me know! You can contact me via my contact information page, or simply by sending an email to TheChange@yahoo.com. Have fun! And thank you for reading!
Index The making of X-NON Introduction Knowledge reference Concept Construct Specification Direction Structure Graphics resolution Orientation Ship gravity Sound panning Ship "Asteroids" physics Thrust sound Tilemap Spawn spots Video mode detection Sounds overlapping MilliSecs Bullets Pre-rendered rotated ship Frame limiting Viewpoint Bullet viewpoint Tilemap viewpoint Player viewpoint Creating a player (ship) (Re)spawning the player (ship) Player control Player (ship) thrust Rotating a player (ship) Player (ship) fire Kill a player (ship) Updating a player (ship) Music (Media) file locations Ship vs ship collision Ship vs bullet collision Tilemap vs ship collision Tilemap vs bullet collision Game control Fixing bugs Background image More video modes Final structure Verifying loaded files Splash screen Tilemap editor Expansion Bonus Index