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 | | | | | | | | | | | | | `---------------^---------------' |
Spawn (loud) Thrust (soft) Fire (loud) Bullet collide (soft) Explode (loud) |
,-----, | ^ |\ | | | Fire | | | ,-----+-----+-----, | < | v | > |\ | | | | | Rotate Left - Thrust - Rotate Right | | | | | t-----t-----t-----< | \ \ \ \| `-----^-----^-----' |
/\ Ship o Bullet [] Tile X Destroy |
/\ + /\ = X o + /\ = X [] + /\ = /\ X [] + o = o X |
Modes = CountGfxModes () For Mode = 1 To Modes DebugLog GfxModeWidth ( Mode ) + "x" + GfxModeHeight ( Mode ) + "x" + GfxModeDepth ( Mode ) Next WaitKey End |
; 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 |
Y = Y + gravity |
Y = Y - thrust |
Const Gravity = 1 Const Thrust = 3 |
If KeyDown ( 57 ) ; Space ; Thrusting! End If |
If Y > 480 Then Y = 480 |
; 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 |
; Thrust using space, upwards If KeyDown ( 57 ) Then speedY = speedY - Thrust ; Apply gravity speedY = speedY + Gravity |
Y = Y + speedY |
If Y > 480 Y = 480 ; Stop the ship speedY = 0 End If |
; 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 |
; 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 |
; Panning ChannelPan Channel , -1 ; Left ChannelPan Channel , 0 ; Center ChannelPan Channel , 1 ; Right |
; Player identification Local PlayerNumber |
; Left player If PlayerNumber = 1 ; Left panning ChannelPan Channel , -1 ; Right player ElseIf PlayerNumber = 2 ; Right panning ChannelPan Channel , 1 EndIf |
source offset = 1 source scale = 1 |
target offset = -1 target scale = 2 |
target value = source value - 1 |
target = ( source - 1 ) * 2 |
target = ( source - 1 ) * 2 + -1 |
ChannelPan Channel , ( PlayerNumber - 1 ) * 2 - 1 |
; 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 () |
; 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 |
; Ship position Local PosX# Local PosY# .. ; Start in center of screen PosX = ScreenWidth / 2 PosY = ScreenHeight / 2 |
; Ship motion control Const KeyUp = 200 Const KeyDown = 208 Const KeyLeft = 203 Const KeyRight = 205 ; Game control Const KeyEsc = 1 |
; Current ship angle Local 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 |
; 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 |
Dim Images( 360 ) |
; 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 |
; Current ship heading (direction) Local Heading# ; Current ship speed (velocity) Local Speed# |
; Ship speed vector Local SpeedX# Local SpeedY# |
; Ship thrust speed in pixels per frame Const ThrustSpeed# = 0.12 .. ; Ship thrust vector Local ThrustX# Local ThrustY# |
; - 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 |
; - 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 ) |
; 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 |
; - Finally add the velocity vector to the ship position PosX = PosX + SpeedX PosY = PosY + SpeedY |
; 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 |
; 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 |
; Test channel pause/resume Repeat If KeyDown ( 57 ) ; Space ResumeChannel Channel Else PauseChannel Channel End If Until KeyHit ( 1 ) ; Esc |
; Size of a single tile Const TileWidth = 32 Const TileHeight = 32 ; Number of tiles in anim image Const TileCount = 42 |
; 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 ) |
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 |
20 7 !!!!!!!!!!!!!!!!!!!! ! ! ! ! # % ' ) + - / ! ! ! ! " $ & ( * , . 0 ! ! ! !!!!!!!!!!!!!!!!!!!! |
LoadTilemap "Test Tilemap 1.TXT" |
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) |
; 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 |
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 |
Function LoadTilemap ( FileName$ ) .. ; Reset tilemap Dim Tilemap( TilemapWidth , TilemapHeight ) ; And spawn spots Delete Each SpawnSpot SpawnSpots = 0 |
Function ResAvail ( Width , Height ) For ColorDepth = 16 To 32 Step 8 If GfxModeExists ( Width , Height , ColorDepth ) Then Return ColorDepth Next End 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 |
; 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 |
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 |
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 |
Global MilliSecs |
MilliSecs = MilliSecs () |
Type Bullet ; Time of creation Field TimeCreated ; Duration of life Field TimeToLive ; Position Field PosX# Field PosY# ; Velocity Field VelX# Field VelY# End Type |
; Bullet time to live in milliseconds Const BulletTime = 5000 |
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 |
; Bullet velocity in pixels Const BulletSpeed = 4 |
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 |
; Simple bullet image Global BulletImage .. ; Load bullet image BulletImage = LoadImage ( "Bullet.BMP" ) MidHandle BulletImage |
; Bullet spawning distance from player image hotspot in pixels Const BulletDistance = 24 |
Function DrawBullets () Local This.Bullet For This = Each Bullet DrawImage BulletImage , This\PosX , This\PosY Next End Function |
CreateBullet PosX , PosY-BulletDistance , 0 , -BulletSpeed |
; 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 |
; 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 |
; Play fire sound Channel = PlaySample ( FireSound , 10 ) ChannelPan Channel , ( PlayerNumber - 1 ) * 2 - 1 |
;------------------------------------------------------------------------------- ; ; 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 ;,,,, ; ;---- ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, |
Const RotationInterval = 5 ; degrees |
; ( 1-based player number , 0-based angle ) Dim PlayerImages( 2 , 360 ) |
; 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 |
PlayerAngle = 3 DrawImage PlayerImages( PlayerNumber , PlayerAngle ) , .. |
; Return angle divided in steps Function LimitAngle ( Angle , Stepsize ) Return Int ( Floor ( Float Angle / Stepsize ) ) * Stepsize End Function |
PlayerAngle = 3 DrawImage PlayerImages( PlayerNumber , LimitAngle ( PlayerAngle ) ) , .. |
; Game speed limiter Global GameTimer .. ; Setup game speed limiter at 60 updates per second GameTimer = CreateTimer( 60 ) .. Repeat .. ; Sync game speed WaitTimer GameTimer Until .. |
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 |
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 |
; 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 |
Type Player ; Current position Field PosX# Field PosY# End Type |
; ( 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 |
Function DrawBullets ( Orientation.Player ) ; Something with Orientation\PosX and Orientation\PosY End Function .. DrawBullets Player( 1 ) DrawBullets Player( 2 ) |
Screen = GameWorld - Camera |
; 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 |
; 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 |
; 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 |
Function DrawBullets ( Orientation.Player ) ; Something with Orientation\PosX and Orientation\PosY |
Function DrawBullets ( OrientationX , OrientationY ) ; Something with OrientationX and OrientationY |
DrawBullets Player( 1 )\PosX , Player( 1 )\PosY |
Screen = GameWorld - Camera |
ScreenBullet\PosX = WorldBullet\PosX - Player\PosX ScreenBullet\PosY = WorldBullet\PosY - Player\PosY |
Function DrawBullets () Local This.Bullet For This = Each Bullet DrawImage BulletImage , This\PosX , This\PosY Next End Function |
ScreenBullet\PosX = This\PosX - Orientation\PosX ScreenBullet\PosY = This\PosY - Orientation\PosY |
Function DrawBullets ( Orientation.Player ) Local This.Bullet For This = Each Bullet DrawImage BulletImage , This\PosX - Orientation\PosX , This\PosY - Orientation\PosY Next End Function |
Function DrawTilemap ( Orientation.Player ) ; Something with Orientation\PosX and Orientation\PosY End Function .. DrawTilemap Player( 1 ) |
Local PosX Local PosY .. ; Use a relative display position PosX = Orientation\PosX PosY = Orientation\PosY |
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 |
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 |
; 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 |
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 |
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 |
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 |
Type Player .. ; Current ship angle Field Angle# .. End Type |
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 |
Type Player .. ; Unique player number (1 or 2) Field Number .. End Type |
; 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 ) |
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 ) |
; ( 1-based player number ) Dim Player.Player( 2 ) |
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 |
Type Player .. ; True or False Field Dead .. End Type .. Function CreatePlayer.Player ( PlayerNumber ) .. This\Dead = True |
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 |
; Effects Global SpawnSound .. ; Load effects SpawnSound = LoadSound ( "Spawn.WAV" ) |
; 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 |
; Reset randomizer ; (set number of milliseconds since system boot as randomizer base value) SeedRnd MilliSecs () |
; 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 |
; Control both ships PlayersInput |
Global ThrustSound .. ThrustSound = LoadSound ( "Thrust.WAV" ) LoopSound ThrustSound |
Type Player .. ; Request to thrust ship Field Thrusting ; Sound channel handle Field ThrustChannel .. End Type |
; Prepare thrust sound This\ThrustChannel = PlaySound ( ThrustSound ) PauseChannel This\ThrustChannel ChannelPan This\ThrustChannel , ( PlayerNumber - 1 ) * 2 - 1 |
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 |
; 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 |
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 |
.. For This = Each Player ; Player is dead If Not This\Dead .. |
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 |
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 |
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 |
; 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 |
; Update game UpdatePlayer Player( 1 ) UpdatePlayer Player( 2 ) UpdateBullets |
; 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 |
; Locations of external files Const AudioFolder$ = "Audio\" Const GraphicsFolder$ = "Graphics\" Const TilemapsFolder$ = "Tilemaps\" |
GameMusic = LoadSound ( AudioFolder + "TheChance - Gravity Force.MP3" ) |
PlayerImages( PlayerNumber , Angle ) = LoadImage ( GraphicsFolder + "Ships\" + "Ship " + PlayerNumber + " - " + Angle + ".BMP" ) |
; Both players must be alive If ( Not Player( 1 )\Dead ) And ( Not Player( 2 )\Dead ) |
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 ) |
; And kill them if so KillPlayer Player( 1 ) KillPlayer Player( 2 ) |
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 |
Local Player.Player Local PlayerImg .. For Player = Each Player If Not Player\Dead PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) ) |
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 ) |
Delete This KillPlayer Player ; This player is dead, don't check for any more bullets Exit |
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 |
Local Player.Player Local PlayerImg .. For Player = Each Player If Not Player\Dead PlayerImg = PlayerImages( Player\Number , LimitAngle ( Player\Angle , 5 ) ) |
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 |
; 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 ) |
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 ) ) |
KillPlayer Player |
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 |
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 |
Local ExitLoops |
ExitLoops = False |
ExitLoops = True Exit |
Next ; TileY If ExitLoops Then Exit Next ; TileX |
; Play collide sound PlaySample CollideSound , 50 |
Global CollideSound |
CollideSound = LoadSound ( AudioFolder + "Collide.WAV" ) |
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 |
; Collisions PlayerCollideWithPlayer PlayersCollideWithTilemap PlayersCollideWithBullets BulletsCollideWithTilemap |
; Game states Global GamePaused .. ; Set default game states GamePaused = False |
; Game control Const KeyP = 25 Const KeyEsc = 1 |
If KeyHit ( KeyP ) GamePaused = Not GamePaused End If .. Until KeyHit ( KeyEsc ) |
If Not GamePaused ; Control both ships PlayersInput ; Update game UpdatePlayer Player( 1 ) UpdatePlayer Player( 2 ) UpdateBullets ; Collisions PlayerCollideWithPlayer PlayersCollideWithTilemap PlayersCollideWithBullets BulletsCollideWithTilemap End If |
; 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 |
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 |
If KeyHit ( KeyP ) GamePaused = Not GamePaused FlushKeys ; Forget pressed keys End If |
; Clear any pressed keys FlushKeys |
Type Player .. ; Time of death Field DiedWhen |
Function KillPlayer ( This.Player ) .. This\DiedWhen = MilliSecs |
Function RespawnPlayer ( This.Player ) .. ; How much time to wait (in ms) before being able to spawn after dying If MilliSecs - This\DiedWhen >= 2000 |
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 |
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 |
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 |
If Respawn ; Make alive and kicking This\Dead = False |
; Stars in the background Global BackgroundImage .. ; Load background image BackgroundImage = LoadImage ( GraphicsFolder + "Star Background.PNG" ) |
; - Screen Viewport 0 , 0 , ScreenWidth , ScreenHeight Origin 0 , 0 Color 0 , 0 , 0 Rect ScreenWidth/2 , 0 , 1 , ScreenHeight Flip TileBlock |
TileBlock BackgroundImage , -Player( 1 )\PosX/2 , -Player( 1 )\PosY/2 |
; 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 ) |
; 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 .. |
Function Verify ( TargetHandle , ErrorMessage$ = "probs :(" ) If Not TargetHandle Then RuntimeError ErrorMessage End Function |
; Start music first GameMusic = LoadSound ( AudioFolder + "TheChance - Gravity Force.MP3" ) Verify GameMusic , "Unable to load intro music" |
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 |
Local TilemapLoaded .. ; Load tile map data TilemapLoaded = LoadTilemap ( TilemapsFolder + "Test Tilemap 1.TXT" ) Verify TilemapLoaded , "Unable to load tilemap" |
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 |
; 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 |
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 |
; Remember when started loading LoadStart = MilliSecs () |
; 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 |