Chapter 10. A Simple Side-Scrolling Shooting Game

This chapter will describe a simple game, develop a basic plan, and then cover its implementation. The implementation will be done in a pragmatic iterative style. A high-level first pass will get the game structure working. This will then be refined until it approaches the original description of the game.

A Simple Game

A simple game will demonstrate all the techniques that have been covered so far. We’ll build a 2D scrolling shooter game. This type of game is quite simple to make and then easy to expand by adding more features. The pragmatic way to develop this game is to create a working game as quickly as possible, but it’s still important to plan up front what these first stages will be. Figure 10.1 shows a high-level overview of the game flow.

High-level game flow.

Figure 10.1. High-level game flow.

The idea for this game is to create a simple yet complete game. Therefore, there will be a start screen, then an inner game, and finally a game over screen. The inner game will have a spaceship the player can move. The player should be able to press a button to fire a bullet that comes out of the front of the ship. There only needs to be one level, but more would be nice. The level will begin when the player goes from the start state to the inner game. After a set amount of time, the level ends. If the player is still alive at the end of the level, then that counts as a win; otherwise, the player loses the game. The level needs a number of enemies that advance towards the player and are able to shoot bullets. Enemies can be given a health value and take multiple shots to be destroyed. When destroyed they should explode.

By reading this quick description of the game, it’s easy to start building up a list of classes and interactions. A good way to start the technical plan is to draw some boxes for the main classes and some arrows for the major interactions. We’ll need three main game states, and the inner game state will be the most complicated. By looking at the game description, you can see that some of the important classes needed include Player, Level, Enemy, and Bullet. The level needs to contain and update the players, enemies, and bullets. Bullets should collide with enemies and players.

The inner game is where the player will fly the spaceship and blow up the oncoming enemies. The player ship will not actually move through space; instead, the movement will be faked. The player can move the player anywhere on the screen but the “camera” will stay fixed dead center. To give the impression of speeding through space, the background will be scrolled in the opposite direction the player is traveling. This greatly simplifies any player tracking or camera code.

This is a small game so we can start coding with this rather informal description. All game code goes in the game project and any code we generate that might be useful for multiple projects can go in the engine library. A more ambitious game plan might require a few small test programs—game states are very good for sketching out such code ideas.

The First Implementation Pass

The high-level view has broken the game down into three states. This first coding pass will create these three states and make them functional.

Create a new Windows Forms Application project. I’ve called the project Shooter, but feel free to choose whatever name you want. You are probably familiar with how to set up a project, but here is a quick overview. The solution will be set up in a very similar way to the EngineTest project in the previous chapters. The Shooter project uses the following references: Tao.DevIL, Tao. OpenGL, Tao.Platform.Windows, and System.Drawing. It will also need a reference to the Engine project. To do this, the Engine project should be added to the solution (right-click the Solution folder, choose Add > Existing Project, find the Engine project, and select it). Once the Engine project exists in the solution then the Shooter project can add it as a reference. To add the Engine project as a reference right-click the Shooter project references folder and choose Add Reference, navigate to the Projects tab, and choose the Engine project.

The Shooter project will use OpenGL, so in the Form editor, drag and drop a SimpleOpenGLControl onto the form and set its Dock property to “Fill.” Right-click the Form1.cs and choose View Code. This file needs a game loop and initialization code added, which is supplied below.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Engine;
using Engine.Input;
using Tao.OpenGl;
using Tao.DevIl;

namespace Shooter
{
 public partial class Form1 : Form
 {
  bool            _fullscreen       = false;
  FastLoop        _fastLoop;
  StateSystem     _system           = new StateSystem();
  Input           _input            = new Input();
  TextureManager  _textureManager   = new TextureManager();
  SoundManager    _soundManager     = new SoundManager();
public Form1()
{
 InitializeComponent();
 simpleOpenGlControl1.InitializeContexts();

 _input.Mouse = new Mouse(this, simpleOpenGlControl1);
 _input.Keyboard = new Keyboard(simpleOpenGlControl1);

 InitializeDisplay();
 InitializeSounds();
 InitializeTextures();
 InitializeFonts();
 InitializeGameState();

 _fastLoop = new FastLoop(GameLoop);
}

private void InitializeFonts()
{
 // Fonts are loaded here.
}

private void InitializeSounds()
{
 // Sounds are loaded here.
}
private void InitializeGameState()
{
 // Game states are loaded here
}

private void InitializeTextures()
{
 // Init DevIl
 Il.ilInit();
 Ilu.iluInit();
 Ilut.ilutInit();
 Ilut.ilutRenderer(Ilut.ILUT_OPENGL);

 // Textures are loaded here.
}
private void UpdateInput(double elapsedTime)
{
 _input.Update(elapsedTime);
}

private void GameLoop(double elapsedTime)
{
 UpdateInput(elapsedTime);
 _system.Update(elapsedTime);
 _system.Render();
 simpleOpenGlControl1.Refresh();
}

private void InitializeDisplay()
{
 if (_fullscreen)
 {
  FormBorderStyle = FormBorderStyle.None;
  WindowState = FormWindowState.Maximized;
 }
 else
 {
 ClientSize = new Size(1280, 720);
 }
 Setup2DGraphics(ClientSize.Width, ClientSize.Height);
}

protected override void OnClientSizeChanged(EventArgs e)
{
 base.OnClientSizeChanged(e);
 Gl.glViewport(0, 0, this.ClientSize.Width, this.ClientSize. Height);
 Setup2DGraphics(ClientSize.Width, ClientSize.Height);
}
private void Setup2DGraphics(double width, double height)
{
 double halfWidth = width / 2;
 double halfHeight = height / 2;
 Gl.glMatrixMode(Gl.GL_PROJECTION);
 Gl.glLoadIdentity();
 Gl.glOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, -100, 100);
   Gl.glMatrixMode(Gl.GL_MODELVIEW);
   Gl.glLoadIdentity();
  }
 }
}

In this Form.cs code, a Keyboard object is created and assigned to the Input object. For the code to work a Keyboard member must be added to the Input class as below.

public class Input
{
 public Mouse Mouse { get; set; }
 public Keyboard Keyboard { get; set; }
 public XboxController Controller { get; set; }

The following DLL files will need to be added to the binDebug and binRelease folders: alut.dll, DevIL.dll, ILU.dll, ILUT.dll, OpenAL32.dll, and SDL.dll. The project is now ready to use for developing a game.

This is the first game we’ve created, and it would be nice if the form title bar said something other than “Form1” when the game was running. It’s easy to change this text in Visual Studio. In the solution explorer double-click the file Form1.cs; this will open the form designer. Click the form and go to the properties window. (If you can’t find the properties window then go to the menu bar and choose View > Properties Window.) This will list all the properties associated with the form. Find the property labeled Text, and change the value to Shooter, as shown in Figure 10.2.

Changing the form title.

Figure 10.2. Changing the form title.

The Start Menu State

The first state to create is the start menu. For a first pass, the menu only needs two options: Start Game and Exit. These options are a kind of button; this state therefore needs two buttons and some title text. A mock-up for this screen is shown in Figure 10.3.

Title Screen mock-up.

Figure 10.3. Title Screen mock-up.

The title will be created using the Font and Text classes defined earlier in the book. There’s a font on the CD called “title font”; it’s a 48pt font with a suitably video game look. Add the .fnt and .tga files to the project and set the properties of each so that they are copied to the bin directory when the project is built.

The font file needs to be loaded in the Form.cs. If we were dealing with many fonts, it might be worth creating a FontManager class, but because we’re only using one or two they can just be stored as member variables. Here is the code to load the font files.

private void InitializeTextures()
{
 // Init DevIl
 Il.ilInit();
 Ilu.iluInit();
 Ilut.ilutInit();
 Ilut.ilutRenderer(Ilut.ILUT_OPENGL);

 // Textures are loaded here.
 _textureManager.LoadTexture("title_font" "title_font.tga");
}

Engine.Font _titleFont;
private void InitializeFonts()
{
_titleFont = new Engine.Font(_textureManager.Get("title_font"),
          FontParser.Parse("title_font.fnt"));
}

The font texture is loaded in the IntializeTextures function and this is used when the font object is created in the IntializeFonts method.

The title font can then be passed into the StartMenuState constructor. Add the following new StartMenuState to the Shooter project.

class StartMenuState : IGameObject
{
 Renderer  _renderer = new Renderer();
 Text   _title;

public StartMenuState(Engine.Font titleFont)
{
 _title = new Text("Shooter", titleFont);
 _title.SetColor(new Color(0, 0, 0, 1));
 // Center on the x and place somewhere near the top
 _title.SetPosition(-_title.Width/2,300);
}
public void Update(double elapsedTime) { }

public void Render()
{
 Gl.glClearColor(1, 1, 1, 0);
 Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
 _renderer.DrawText(_title);
 _renderer.Render();
 }
}

The StartMenuState uses the font passed into the constructor to make the title text. The text is colored black, and then it’s centered horizontally. The render loop clears the screen to white and draws the text. To run the state it needs to be added to the state system and set as the default state.

private void InitializeGameState()
{
 // Game states are loaded here
 _system.AddState("start_menu", new StartMenuState(_titleFont));
 _system.ChangeState("start_menu");
}

Run the program and you should see something similar to Figure 10.4.

Rendering in the title.

Figure 10.4. Rendering in the title.

This stage is just a first pass. The title page can be refined and made prettier later. At the moment, functionality is the most important thing. To finish the title screen, the start and exit options are needed. These will be buttons, which means a button class will need to be made.

The buttons will be presented in a vertical list. At all times one of the buttons will be the selected button. If the user presses Enter on the keyboard or the A button on the gamepad, then the currently selected button will be pressed.

A button needs to know when it’s selected; this is also known as having focus. Buttons also need to know what to do when they’ve been pressed; this is a great place to use a delegate. A button can be passed a delegate in the constructor and call that when it’s pressed, executing any code we want. The button will also need methods to set its position. These requirements for the button describe something like the following class. The Button class is reusable so it can be added to the Engine project so it may be used in future projects.

 public class Button
 {
  EventHandler _onPressEvent;
  Text _label;
  Vector _position = new Vector();

 public Vector Position
 {
  get { return _position; }
  set
  {
   _position = value;
   UpdatePosition();
  }
 }

 public Button(EventHandler onPressEvent, Text label)
 {
  _onPressEvent = onPressEvent;
   _label = label;
  _label.SetColor(new Color(0, 0, 0, 1));
  UpdatePosition();
 }

 public void UpdatePosition()
 {
  // Center label text on position.
  _label.SetPosition(_position.X - (_label.Width / 2),
          _position.Y + (_label.Height / 2));
 }

 public void OnGainFocus()
 {
  _label.SetColor(new Color(1, 0, 0, 1));
 }

 public void OnLoseFocus()
 {
  _label.SetColor(new Color(0, 0, 0, 1));
 }

 public void OnPress()
 {
  _onPressEvent(this, EventArgs.Empty);
 }

 public void Render(Renderer renderer)
 {
   renderer.DrawText(_label);
  }
}

The button class doesn’t directly handle the user input; instead, it relies on whichever piece of code uses it to pass on relevant input events. The OnGain-Focus and OnLoseFocus methods will be used to change the appearance of the button depending on the focus. This will let the user know which button he currently has selected. When the button position is changed, the label text position is also updated and centered. EventHandler is used to hold the function that will be called when the button is pressed. EventHandler describes a delegate that takes an object and event argument’s enum.

Player input is detected by a class called Menu; it informs the buttons if they are selected or pressed. The Menu class contains a list of buttons, and only one button may have focus at any one time. The user can navigate the menu with the control pad or keyboard. The OnGainFocus and OnLoseFocus will change the button label text; this will let us know which button currently has the focus.

The color will be red when focused; otherwise, it will be black. Alternatively, the text could be enlarged, a background image could change, or some other values could be tweened in or out, but not now as this is the very first pass.

The menu will list the buttons vertically in a column, so a good name might be VerticalMenu. VerticalMenu is another reusable class so it can be added to the Engine project. The menu will need methods for adding buttons and a Render method.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Input; // Input needs to be added for gamepad input.
using System.Windows.Forms; // Used for keyboard input


namespace Engine


{
  public class VerticalMenu
  {
    Vector _position =  new Vector();
    Input.Input _input;
    List<Button> _buttons =  new List<Button>();
    public double Spacing { get; set; }


    public VerticalMenu(double x, double y, Input.Input input)
    {
     _input =  input;
     _position =  new Vector(x, y, 0);
     Spacing =  50;
    }


    public void AddButton(Button button)

    {
     double _currentY =  _position.Y;

     if (_buttons.Count != 0)
     {
       _currentY =  _buttons.Last().Position.Y;
       _currentY -= Spacing;
     }
     else
     {
       // It's the first button added it should have
       // focus
       button.OnGainFocus();
     }


     button.Position =  new Vector(_position.X, _currentY, 0);
     _buttons.Add(button);
    }
    public void Render(Renderer renderer)
    {
     _buttons.ForEach(x => x.Render(renderer));
    }
  }
}

The position of the buttons is handled automatically. Each time a button is added, it is put below the other buttons on the Y axis. The Spacing member determines how far apart the buttons are spaced, and this defaults to 50 pixels. The menu itself also has a position that allows the buttons to be moved around as a group. The position is only set in the constructor. The VerticalMenu doesn’t allow its position to be changed after it is constructed because this would require an extra method to rearrange all the buttons for the new position. This would be nice functionality to have, but it’s not necessary. The Render method uses C#’s new lamba operator to render all the buttons.

The menu class doesn’t handle user input yet, but before adding that, let’s hook the menu up to StartMenuState so we can see if everything is working. The label on buttons will use a different font than the one used for the title. Find general_font.fnt and general_font.tga on the CD and add them to the project. Then this new font needs to be set up in the Form.cs file.

// In form.cs
private void InitializeTextures()
{
  // Init DevIl
  Il.ilInit();
  Ilu.iluInit();
  Ilut.ilutInit();
  Ilut.ilutRenderer(Ilut.ILUT_OPENGL);


  // Textures are loaded here.
  _textureManager.LoadTexture("title_font", "title_font.tga");
  _textureManager.LoadTexture("general_font", "general_font.tga");
}


Engine.Font _generalFont;
Engine.Font _titleFont;
private void InitializeFonts()
{
  // Fonts are loaded here.
  _titleFont =  new Engine.Font(_textureManager.Get("title_font"),
            FontParser.Parse("title_font.fnt"));


  _generalFont =  new Engine.Font(_textureManager.Get("general_font"),
       FontParser.Parse("general_font.fnt"));
}

This new general font can now be passed through to the StartMenuState in the constructor and will be used to construct the vertical menu. The Input class is also passed along at this point and therefore the using Engine.Input statement must be added to the other using statements at the top of the Start-MenuState.cs file.

Engine.Font _generalFont;
Input _input;
VerticalMenu _menu;


public StartMenuState(Engine.Font titleFont, Engine.Font generalFont,
Input input)
{
  _input =  input;
  _generalFont =  generalFont;
  InitializeMenu();

The actual menu creation is done in the InitializeMenu function because this stops it from cluttering up the StartMenuState constructor. The StartMenuState creates a vertical menu centered on the X axis and 150 pixels up on the Y axis. This positions the menu neatly below the title text.

private void InitializeMenu()
{
  _menu =  new VerticalMenu(0, 150, _input);
  Button startGame =  new Button(
    delegate(object o, EventArgs e)
    {
     // Do start game functionality.
    },
    new Text("Start", _generalFont));


  Button exitGame =  new Button(
    delegate(object o, EventArgs e)
    {
     // Quit
     System.Windows.Forms.Application.Exit();
    },
    new Text("Exit", _generalFont));


  _menu.AddButton(startGame);
  _menu.AddButton(exitGame);
}

Two buttons are created: one for exit and one for start. It’s easy to see how additional buttons could be added (e.g., load saved game, credits, settings, or website would all be fairly trivial to add using this system). The exit button delegate is fully implemented, and when called, it will exit the program. The start menu button functionality is empty for the time being; it will be filled in when we make the inner game state.

The vertical menu is now being successfully created, but it won’t be visible until it’s added to the render loop.

public void Render()
{
  Gl.glClearColor(1, 1, 1, 0);
  Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
  _renderer.DrawText(_title);
  _menu.Render(_renderer);
  _renderer.Render();
}

Run the program and the menu will be rendered under the title.

Only the input handling remains to be implemented. The gamepad will navigate the menu with the left control stick or the keyboard. This requires some extra logic to decide when a control stick has been flicked up or down. This logic is shown below in the HandleInput function, which belongs to the VerticalMenu class. You may have to make a change to the Input class to make the Controller member public so it’s accessible from outside the Engine project.

bool _inDown = false;
bool _inUp =  false;
int _currentFocus =  0;
public void HandleInput()
{
  bool controlPadDown =  false;
  bool controlPadUp = false;

  float invertY =  _input.Controller.LeftControlStick.Y * -1;

  if (invertY< -0.2)
  {
    // The control stick is pulled down
    if (_inDown == false)
    {
     controlPadDown = true;
     _inDown = true;
    }
  }
  else
  {
    _inDown = false;
  }

  if (invertY> 0.2)
  {
    if (_inUp == false)
    {
     controlPadUp =  true;
     _inUp =  true;
    }
  }
  else
  {
    _inUp =  false;
  }


  if (_input.Keyboard.IsKeyPressed(Keys.Down)
  || controlPadDown)
  {
    OnDown();
  }
  else if(_input.Keyboard.IsKeyPressed(Keys.Up)
    || controlPadUp)
  {
    OnUp();
  }
}

The HandleInput function needs to be called in the StartMenuState. Update method. If you don’t add this call then none of the input will be detected. HandleInput detects the particular input that the vertical menu is interested in and then calls other functions to deal with it. At the moment there are only two functions, OnUp and OnDown; these will change the currently focused menu item.

private void OnUp()
{
  int oldFocus = _currentFocus;
  _currentFocus ++;
  if (_currentFocus == _buttons.Count)
  {
    _currentFocus =  0;
  }
  ChangeFocus(oldFocus, _currentFocus);
}


private void OnDown()
{
  int oldFocus = _currentFocus;
  _currentFocus-;
  if (_currentFocus == -1)
  {
    _currentFocus =  (_buttons.Count - 1);
  }
  ChangeFocus(oldFocus, _currentFocus);
}


private void ChangeFocus (int from, int to)
{
  if (from != to)
  {
    _buttons[from].OnLoseFocus();
    _buttons[to].OnGainFocus();
  }
}

By pressing up or down on the keyboard, the focus is moved up and down the buttons on the vertical menu. The focus also wraps around. If you are at the top of the menu and press up, then the focus will wrap around and go to the bottom of the menu. The ChangeFocus method reduces repeated code; it tells one button it’s lost focus and another button that it’s gained focus.

Buttons can now be selected, but there is no code to handle buttons being pressed. The VerticalMenu class needs to be modified to detect when the A button on the gamepad or the Enter key on the keyboard is pressed. Once this is detected, the currently selected button delegate is called.

// Inside the HandleInput function
  else if(_input.Keyboard.IsKeyPressed(Keys.Up)
    || controlPadUp)
  {
    OnUp();
  }
  else if (_input.Keyboard.IsKeyPressed(Keys.Enter)
    || _input.Controller.ButtonA.Pressed)
  {
    OnButtonPress();
  }
}
private void OnButtonPress()
{
  _buttons[_currentFocus].OnPress();
}

Run the code and use the keyboard or gamepad to navigate the menu. Pressing the exit button will exit the game, but pressing the start button will currently do nothing. The start button needs to change the state to the inner game state. This means that StartMenuState needs access to the state system.

private void InitializeGameState ()
{
  _system.AddState("start_menu", new StartMenuState(_titleFont,
_generalFont, _input, _system));

The StartMenuState constructor will also need to be modified, and it will keep a reference to the state system.

StateSystem _system;
public StartMenuState(Engine.Font titleFont, Engine.Font generalFont,
Input input, StateSystem system)
{
  _system =  system;

This can be used by the start button to change states when it is pressed. The start button is set up in the InitializeMenu method and needs to be modified like so.

Button startGame =  new Button(
  delegate(object o, EventArgs e)
  {
    _system.ChangeState("inner_game");
  },
  new Text("Start", _generalFont));

The inner_game state doesn’t exist yet but that’s what we’ll develop next. For a first pass, the start menu is now complete. Running the program will produce something similar to Figure 10.5.

First pass of the start game menu.

Figure 10.5. First pass of the start game menu.

Subsequent passes can change this menu as needed, adding more animation, demo modes, or whatever you like!

The Inner Game State

For the first pass, the inner game is going to be as simple as possible. It will wait a few seconds and then change to the game over state. It needs to pass some information over to the game over state to report if the player won or lost the game.

A PersistantGameData class will be used to store information about the player, including if he had just lost or won a game. Eventually the inner game will allow the player to play a shooting game, but not in this first pass.

The inner game level will last for a fixed period of time; if the player is alive when the time is up the player wins. The time a level takes is described by a LevelDescription class. For now, the only thing this class contains is how long the level will last.

class LevelDescription
{
  // Time a level lasts in seconds.
  public double Time { get; set; }
}

The PersistentGameData class will have a description of the current level and information about whether the player has just won that level.

class PersistantGameData
{
  public bool JustWon { get; set; }
  public LevelDescription CurrentLevel { get; set; }
  public PersistantGameData()
  {
    JustWon =  false;
  }
}

The JustWon member is set to false in the constructor because the player cannot have won a game before the game data is created. The persistent game data class needs to be created in the Form.cs file. Add a new function to be called from the constructor called InitializeGameData; it should be called just after InitializeTextures and just before the game fonts are created.

PersistantGameData _persistantGameData =  new PersistantGameData();
private void InitializeGameData()
{
  LevelDescription level =  new LevelDescription();
  level.Time =  1; // level only lasts for a second
  _persistantGameData.CurrentLevel =  level;
}

With this class set up it’s now easy to design the InnerGameState.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;
using Engine.Input;
using Tao.OpenGl;


namespace Shooter
{
  class InnerGameState : IGameObject
  {
    Renderer _renderer =  new Renderer();
    Input _input;
    StateSystem _system;
    PersistantGameData _gameData;
    Font _generalFont;


    double _gameTime;


    public InnerGameState(StateSystem system, Input input, Persis-
tantGameData gameData, Font generalFont)
    {
     _input =  input;
     _system =  system;
     _gameData =  gameData;
     _generalFont = generalFont;
     OnGameStart();
    }


    public void OnGameStart()
    {
     _gameTime =  _gameData.CurrentLevel.Time;
    }


    #region IGameObject Members


    public void Update(double elapsedTime)
    {
     _gameTime -= elapsedTime;

     if (_gameTime<= 0)
     {
       OnGameStart();
       _gameData.JustWon = true;
       _system.ChangeState("game_over");
     }
    }


    public void Render()
    {
     Gl.glClearColor(1, 0, 1, 0);
     Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
     _renderer.Render();
    }
    #endregion
  }
}

The constructor takes in the state system and persistent game data. Using these classes the InnerGameState can determine when the game is over and change the game state. The constructor also takes in the input and general font as these will be of use when adding the second pass of functionality. The constructor calls OnGameStart, which sets the gameTime that will determine how long the level lasts. There will be no level content at this time so the level time is set to 1 second.

The Update function counts down the level time. When the time is up it and the state changes to game_over. The gameTime is reset by calling OnGameState and because the player is still alive then, the JustWon flag is set in the persistent data object. The inner game state Render function clears the screen to a pink color so it’s obvious when the state change occurs.

The InnerGameState class should be used to add another state to the state system in the Form.cs file.

  _system.AddState("inner_game", new InnerGameState(_system, _input,
_persistantGameData, _generalFont));

That’s it for the first pass of the inner game.

The Game Over State

The gameover state is a simple state that tells the player that the game has ended and if he won or lost. The state determines if the player won or lost the game by using the PersistentGameData class. The state will display its information for a short time and then return the player to the start menu. The player can return to the start menu earlier by pressing a button and forcing the GameOverState to finish.

%using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;
using Engine.Input;

using Tao.OpenGl;


namespace Shooter
{
  class GameOverState : IGameObject
  {
    const double _timeOut = 4;
    double _countDown = _timeOut;

    StateSystem _system;
    Input _input;
    Font _generalFont;
    Font _titleFont;
    PersistantGameData _gameData;
    Renderer _renderer =  new Renderer();

    Text _titleWin;
    Text _blurbWin;


    Text _titleLose;
    Text _blurbLose;


    public GameOverState(PersistantGameData data, StateSystem system,
Input input, Font generalFont, Font titleFont)
    {
      _gameData =  data;
      _system =  system;
      _input =  input;
      _generalFont = generalFont;
      _titleFont =  titleFont;

      _titleWin =  new Text("Complete!", _titleFont);
      _blurbWin =  new Text("Congratulations, you won!", _generalFont);
      _titleLose =  new Text("Game Over!", _titleFont);
      _blurbLose =  new Text("Please try again...", _generalFont);

      FormatText(_titleWin, 300);
      FormatText(_blurbWin, 200);


      FormatText(_titleLose, 300);
      FormatText(_blurbLose, 200);
    }

    private void FormatText(Text _text, int yPosition)
    {
     _text.SetPosition(-_text.Width / 2, yPosition);
     _text.SetColor(new Color(0, 0, 0, 1));
    }


    #region IGameObject Members

    public void Update(double elapsedTime)
    {
     _countDown -= elapsedTime;

     if ( _countDown <= 0 ||
        _input.Controller.ButtonA.Pressed ||


        _input.Keyboard.IsKeyPressed(System.Windows.Forms.Keys.
Enter))
     {
       Finish();
     }
    }


    private void Finish()
    {
     _gameData.JustWon =  false;
     _system.ChangeState("start_menu");
     _countDown =  _timeOut;
    }


    public void Render()
    {
     Gl.glClearColor(1, 1, 1, 0);
     Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
     if (_gameData.JustWon)
     {
       _renderer.DrawText(_titleWin);
       _renderer.DrawText(_blurbWin);
     }
     else
     {
       _renderer.DrawText(_titleLose);
       _renderer.DrawText(_blurbLose);
     }

     _renderer.Render();
    }
    #endregion
  }
}

This class needs to be loaded like the rest in the form.cs class.

private void InitializeGameState()
{
  // Game states are loaded here
  _system.AddState("start_menu", new StartMenuState(_titleFont,
_generalFont, _input, _system));
  _system.AddState("inner_game", new InnerGameState(_system, _input,
_persistantGameData, _generalFont));
  _system.AddState("game_over", new GameOverState(_persistantGameData,
_system, _input, _generalFont, _titleFont));
  _system.ChangeState("start_menu");
}

The GameOverState creates a title and message for winning and losing the game. It then uses the persistent data JustWon member to decide which message to display. It also has a counter, and the state eventually times out returning the user to the start menu.

The creation of these three states completes the first pass of the game. The game, while currently not very fun, is already in a complete state. The next section will add more detail to the inner game and refine the overall structure to make it look better.

Developing the Inner Game

The inner game currently doesn’t allow any interaction and times out after a few seconds. To make the inner game state more game-like, a PlayerCharacter needs to be introduced and the player needs to be able to move the character around. This will be the first goal. It’s important to create a game in a series of small achievable goals that are well defined; it makes it much easier to write the code. In this case, the PlayerCharacter will be some type of spaceship.

Once the first goal is reached then the player needs to feel as though he is advancing through a level. This will be done by scrolling the background texture. The next small goal is to let the player shoot bullets. Bullets need something to hit, so the enemies will also be needed. Each goal is a small step that leads logically on to the next. It’s very quick to build up a game in this manner.

Moving the Player Character

The player will be represented as a spaceship created using a sprite and a texture. The spaceship will be controlled by the arrow keys or the left control stick on the gamepad.

The code for controlling the PlayerCharacter won’t go directly into the InnerGameState class. The InnerGameState class is meant to be a light, easy to understand class. The bulk of the level code will be stored in a class called Level. Each time the player plays a level, a new level object is made and the old one is replaced. Creating a new level object each time ensures that there’s no strange error caused by leftover data from a previous play through.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;
using Engine.Input;
using System.Windows.Forms;
using System.Drawing;


namespace Shooter
{
  class Level
  {
    Input _input;
    PersistantGameData _gameData;
    PlayerCharacter _playerCharacter;
    TextureManager _textureManager;


    public Level(Input input, TextureManager textureManager,
PersistantGameData gameData)
    {
     _input =  input;
     _gameData =  gameData;
     _textureManager =  textureManager;
     _playerCharacter =  new PlayerCharacter(_textureManager);
    }


    public void Update(double elapsedTime)
    {
     // Get controls and apply to player character
    }


    public void Render(Renderer renderer)
    {
     _playerCharacter.Render(renderer);
    }
  }
}

This code describes a level. It takes the input and persistent game data in the constructor. The input object is used to move the PlayerCharacter. The persistent game data can be used to keep track of such things as the score or any other data that should be recorded over a number of levels. The texture manager is used to create the player, enemies, and background sprites.

The PlayerCharacter class will contain a sprite that represents the player spaceship. The CD contains a sprite called spaceship.tga in the Assets directory. This needs to be added to the project and its properties changed so that it is copied into the build directory. Load this texture in the form.cs InitializeTextures method.

private void InitializeTextures()
{
  // Init DevIl
  Il.ilInit();
  Ilu.iluInit();
  Ilut.ilutInit();
  Ilut.ilutRenderer(Ilut.ILUT_OPENGL);


  _textureManager.LoadTexture("player_ship", "spaceship.tga");

Now that the player sprite is loaded into the TextureManager, the PlayerCharacter class can be written.

public class PlayerCharacter
{
  Sprite _spaceship =  new Sprite();

  public PlayerCharacter(TextureManager textureManager)
  {
    _spaceship.Texture =  textureManager.Get("player_ship");
    _spaceship.SetScale(0.5, 0.5); // spaceship is quite big, scale
it down.
  }
  public void Render(Renderer renderer)
  {
    renderer.DrawSprite(_spaceship);
  }
}

The PlayerCharacter class at this stage only renders out the spaceship. To see this is in action a Level object needs to be created in the InnerGameState and hooked up to the Update and Render methods. The structure of the level class has its own Render and Update methods so it’s very easy to plug into the InnerGame State. The Level class makes use of the TextureManager class and this means the InnerGameState must change its constructor so that it takes in a TextureManager object. In the form.cs file the textureManager object needs to be passed into the InnerGameState constructor.

class InnerGameState : IGameObject
{
  Level _level;
  TextureManager _textureManager;
// Code omitted


  public InnerGameState( StateSystem system, Input input, TextureManager
textureManager,
            PersistantGameData gameData, Font generalFont)
  {
  _ textureManager =  textureManager;
// Code omitted


  public void OnGameStart()
  {
    _level =  new Level(_input, _textureManager, _gameData);
    _gameTime =  _gameData.CurrentLevel.Time;
  }


// Code omitted
  public void Update(double elapsedTime)
  {
    _level.Update(elapsedTime);


// Code omitted
  public void Render()
  {
    Gl.glClearColor(1, 0, 1, 0);
    Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
    _level.Render(_renderer);

Run the code and start the game. The spaceship will flash, giving a brief glance of this new player sprite and then suddenly the game will end. To test the InnerGameState thoroughly, the level length must be increased. The level length is set in the form.cs file in the InitializeGameData function. Find the code and make the length longer; 30 seconds is probably fine.

The spaceship movement is going to be very simple, with no acceleration or physics modeling. The control stick and arrow keys map directly to the movement of the ship. The PlayerCharacter class needs a new method called Move.

double _speed =  512; // pixels per second
public void Move(Vector amount)
{
  amount *= _speed;
  _spaceship.SetPosition(_spaceship.GetPosition() +  amount);
}

The Move method takes in a vector that gives the direction and amount to move the spaceship. The vector is then multiplied by the speed value to increase the movement length. The new vector is then added to the current position of the ship to create a new position in space, and the sprite is moved there. This is how all basic movement is done in arcade-style games. The movement can be given a different feel by modeling more physical systems such as acceleration and friction, but we will stick with the basic movement code.

The spaceship is moved around by the values in the Input class, which is handled in the Level Update loop.

public void Update(double elapsedTime)
{
  // Get controls and apply to player character
  double _x =  _input.Controller.LeftControlStick.X;
  double _y =  _input.Controller.LeftControlStick.Y * - 1;
  Vector controlInput =  new Vector(_x, _y, 0);

  if (Math.Abs(controlInput.Length()) < 0.0001)
  {
    // If the input is very small, then the player may not be using
    // a controller; he might be using the keyboard.
    if (_input.Keyboard.IsKeyHeld(Keys.Left))
    {
     controlInput.X = -1;
    }


    if (_input.Keyboard.IsKeyHeld(Keys.Right))
    {
     controlInput.X = 1;
    }


    if (_input.Keyboard.IsKeyHeld(Keys.Up))
    {
     controlInput.Y = 1;
    }


    if (_input.Keyboard.IsKeyHeld(Keys.Down))
    {
     controlInput.Y = -1;
    }
  }

  _playerCharacter.Move(controlInput * elapsedTime);
}

The controls are quite simple for the gamepad. A vector is created that describes how the control stick is pushed (The Y axis is reversed by multiplying the value by minus 1 so that the ship will go up when you push up rather than down). This vector is then multiplied by the elapsed time so that the movement will be constant no matter the frame rate. The scaled vector is then used to move the ship. There is also support for the keyboard. The values of the control stick are checked, and if the control stick doesn’t seem to have moved, then the keyboard keys are checked. It’s assumed if you’re not moving the control stick, then you might be playing on the keyboard. The keyboard is less granular than the control stick; it can only give up, down, left, and right as absolute, 0 or 1, values. The keyboard input is used to make a vector so that it can be treated in the same way as the control stick input. IsKeyHeld is used instead of IsKeyPressed because we assume that if the user is holding down the left key, he wants to continue to move left rather than move left once and stop.

Run the program and you will be able to move the ship around the screen. The movement goal is complete!

Faking Movement with a Scrolling Background

Adding a background is going to be fairly easy. There are two basic starfield textures on the CD in the Assets directory called background.tga and background_p. tga. Add these files to the solution and alter the properties so they’re copied to the build directory as you’ve done for all the other textures. Then load them into the texture manager in the form.cs InitializeTextures function.

_textureManager.LoadTexture("background", "background.tga");
_textureManager.LoadTexture("background_layer_1",
"background_p.tga");

Two backgrounds have been chosen so that they can be layered on top of each other to make a more interesting effect than would be achievable with only one texture.

This background is going to be animated by using UV scrolling. This can all be done by making a new class called ScrollingBackground. This scrolling background class could also be reused to make the start and game over menu more interesting, but the priority at the moment is the inner game.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;

namespace Shooter
{
  class ScrollingBackground
  {
    Sprite _background =  new Sprite();

    public float Speed { get; set; }
    public Vector Direction { get; set; }
    Point _topLeft =  new Point(0, 0);
    Point _bottomRight =  new Point(1, 1);

    public void SetScale(double x, double y)
    {
     _background.SetScale(x, y);
    }

    public ScrollingBackground(Texture background)
    {
     _background.Texture =  background;
     Speed =  0.15f;
     Direction =  new Vector(1, 0, 0);
    }

    public void Update(float elapsedTime)
    {
     _background.SetUVs(_topLeft, _bottomRight);
     _topLeft.X += (float)(0.15f * Direction.X * elapsedTime);
     _bottomRight.X += (float)(0.15f * Direction.X * elapsedTime);
     _topLeft.Y += (float)(0.15f * Direction.Y * elapsedTime);
     _bottomRight.Y += (float)(0.15f * Direction.Y * elapsedTime);
    }

    public void Render(Renderer renderer)
    {
     renderer.DrawSprite(_background);
    }
  }
}

An interesting thing to note about the scrolling background class is that it has a direction vector (called Direction). This vector can be used to alter the direction of the scrolling. In a normal shooting game, a background usually scrolls right to left. Altering the scrolling direction can cause the background to scroll in any direction desired. This would be useful in a space exploration game where the background could move in the opposite direction to the player’s movement.

The scrolling class also has a Speed member; this isn’t strictly necessary as the speed of the scrolling could be encoded as the magnitude of the vector, but separating the speed makes it simpler to alter. The direction and speed are used to move the U,V data of the vertices in the Update method.

This background can now be added into the Level class.

ScrollingBackground _background;
ScrollingBackground _backgroundLayer;


public Level(Input input, TextureManager textureManager, PersistantGa-
meData gameData)
{
  _input =  input;
  _gameData =  gameData;
  _textureManager =  textureManager;

  _background =  new ScrollingBackground(textureManager.Get
("background"));
  _background.SetScale(2, 2);
  _background.Speed =  0.15f;

  _backgroundLayer =  new ScrollingBackground(textureManager.Get
("background_layer_1"));
  _backgroundLayer.Speed =  0.1f;
  _backgroundLayer.SetScale(2.0, 2.0);

These two background objects are created in the constructor and each is scaled by two. The backgrounds are scaled up because the texture is about half the size of the screen area. The texture is scaled to make the texture large enough to entirely cover the playing area without leaving gaps at the edges.

The two backgrounds scroll at different speeds. This produces what is known as a parallax effect. The human brain understands the 3D world through a number of different cues known as depth cues. For instance, each eye sees a slightly different angle of the world, and the differences between these views can be used to determine the third dimension. This is known as the binocular cue.

Parallax is another one of these cues; simply put, objects further from the viewer appear to move slower than those closer to the view. Think of driving in a car with a large mountain in the distance. The mountain appears to move very slowly, but trees next to the road fly past. This is a depth cue, and the brain knows that the mountain is far away.

Parallax is easy to fake. A fast scrolling star field appears to have stars close to the spaceship; another background moving more slowly appears to have stars far away. The background classes merely need to move at different speeds, and this gives the background a feeling of depth.

The background objects need to be rendered and updated. This requires more code changes.

public void Update(double elapsedTime)
{
  _background.Update((float)elapsedTime);
  _backgroundLayer.Update((float)elapsedTime);


// A little later in the code


public void Render(Renderer renderer)
{
  _background.Render(renderer);
  _backgroundLayer.Render(renderer);

Run the code and check out the parallax effect. It’s quite subtle with the given star fields, so feel free to modify the images or add several more layers.

The ship now appears to be zooming along in space, and everything suddenly feels a lot more game-like. The next task is to add an enemy.

Adding Some Simple Enemies

The enemies will be represented by a sprite and therefore they will use the Sprite class. The enemy sprite should be different from the player sprite so add a new sprite texture called spaceship2.tga from the CD Assets directory. Change its properties so that it will be copied to the in directories when the program is built.

This snippet of code loads the texture into the texture manager.

Scrolling backgrounds.

Figure 10.6. Scrolling backgrounds.

_textureManager.LoadTexture("enemy_ship", "spaceship2.tga");

Once this has been added, a class can be constructed to simply represent the enemy.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;


namespace Shooter
{
  class Enemy
  {
    Sprite _spaceship =  new Sprite();
    double _scale =  0.3;
    public Enemy(TextureManager textureManager)
    {
     _spaceship.Texture =  textureManager.Get("enemy_ship");
     _spaceship.SetScale(_scale, _scale);
     _spaceship.SetRotation(Math.PI); // make it face the player
     _spaceship.SetPosition(200, 0); // put it somewhere easy to see
    }


    public void Update(double elapsedTime)
    {
    }


    public void Render(Renderer renderer)
    {
     renderer.DrawSprite(_spaceship);
    }


  }
}

The enemies will be rendered and controlled by the Level class. It’s very likely we’ll have more than one enemy at a time, so it’s best to make a list of enemies.

class Level
{
  List<Enemy> _enemyList =  new List<Enemy>();

  // A little later in the code


  public Level(Input input, TextureManager textureManager, Persis-
tantGameData gameData)
  {
    _input =  input;
    _gameData =  gameData;
    _textureManager =  textureManager;
    _enemyList.Add(new Enemy(_textureManager));


  // A little later in the code


  public void Update(double elapsedTime)
  {
    _background.Update((float)elapsedTime);
    _backgroundLayer.Update((float)elapsedTime);
    _enemyList.ForEach(x => x.Update(elapsedTime));
// A little later in the code
public void Render(Renderer renderer)
{
  _background.Render(renderer);
  _backgroundLayer.Render(renderer);
  _enemyList.ForEach(x => x.Render(renderer));

This code is all pretty standard. A list of enemies is created, and an enemy is added to it. The list is then updated and rendered using the lambda syntax. Run the program now and you should see two spaceships: the player spaceship and an enemy facing it. With the current code, the player can fly through the enemy ship with no reaction. If the player crashes into the enemy ship then the player should take some damage or the state should change to the game over state. Before any of that can happen, the collision needs to be detected.

The collision detection will be the simple rectangle-rectangle collision explored earlier in the book. Before coding the collision it may be useful to visualize the bounding box around the enemy. This is quite simple to code using OpenGL’s immediate mode and GL_LINE_LOOP. Add the following code to the Enemy class.

public RectangleF GetBoundingBox()
{
  float width   =  (float)(_spaceship.Texture.Width * _scale);
  float height   =  (float)(_spaceship.Texture.Height * _scale);
  return new RectangleF( (float)_spaceship.GetPosition().X - width / 2,
            (float)_spaceship.GetPosition().Y - height / 2,
            width, height);
}


// Render a bounding box
public void Render_Debug()
{
  Gl.glDisable(Gl.GL_TEXTURE_2D);


  RectangleF bounds =  GetBoundingBox();
  Gl.glBegin(Gl.GL_LINE_LOOP);
  {
    Gl.glColor3f(1, 0, 0);
    Gl.glVertex2f(bounds.Left, bounds.Top);
    Gl.glVertex2f(bounds.Right, bounds.Top);
    Gl.glVertex2f(bounds.Right, bounds.Bottom);
    Gl.glVertex2f(bounds.Left, bounds.Bottom);


  }
  Gl.glEnd();
  Gl.glEnable(Gl.GL_TEXTURE_2D);
}

C#’s RectangleF class is used; therefore, the System.Drawing library needs to be added to the using statements at the top of Enemy.cs. The function GetBoundingBox uses the sprite to calculate a bounding box around it. The width and height are scaled according to the sprite, so even if the sprite is scaled, the bounding box will be correct. The RectangleF constructor takes in the x and y position of the top-left corner, and then the width and height of the rectangle. The position of the sprite is its center, so to get the top-left corner, half the width and height must be subtracted from the position.

The Render_Debug method draws a red box around the sprite. The Render_Debug method should be called from the Enemy.Render method. This debug function can be removed at any time.

public void Render(Renderer renderer)
{
  renderer.DrawSprite(_spaceship);
  Render_Debug();
}

Run the code and a red box will be drawn around the enemy, as can be seen in Figure 10.7. Visual debug routines are a great way to understand what your code is really doing.

An enemy bounding box.

Figure 10.7. An enemy bounding box.

The GetBoundingBox function can be used to determine if the enemy is colliding with anything else. At the moment, the player ship doesn’t have a GetBoundingBox function, and the principle of DRY (Don’t Repeat Yourself) means you shouldn’t just copy this code! Instead, a new parent class should be created that centralizes this functionality; then the Enemy and PlayerCharacter can both inherit from this.

Before the Enemy and the PlayerCharacter classes are generalized, this Sprite class needs to be modified. To make the bounding box drawing functions simpler, the sprite should have some methods to report the current scale.

public class Sprite
{
  double _scaleX =  1;
  double _scaleY =  1; public double ScaleX
  {
    get
    {
     return _scaleX;
    }
  }


  public double ScaleY
  {
    get
    {
     return _scaleY;
    }
  }

Changing the Sprite class is a change to the Engine library, which is a change that shouldn’t be taken lightly. In this case, it is a good change that will be beneficial to any future project using the Engine library. With the Sprite method updated, the Entity class can be created back in the Shooter project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;
using Tao.OpenGl;
using System.Drawing;
namespace Shooter
{
  public class Entity
  {
    protected Sprite _sprite = new Sprite();

    public RectangleF GetBoundingBox()
    {
      float width =  (float)(_sprite.Texture.Width * _sprite.ScaleX);
      float height =  (float)(_sprite.Texture.Height * _sprite.ScaleY);
      return new RectangleF((float)_sprite.GetPosition().X - width / 2,
                (float)_sprite.GetPosition().Y - height / 2,
                width, height);
    }


    // Render a bounding box
    protected void Render_Debug()
    {
      Gl.glDisable(Gl.GL_TEXTURE_2D);


      RectangleF bounds = GetBoundingBox();
      Gl.glBegin(Gl.GL_LINE_LOOP);
      {
       Gl.glColor3f(1, 0, 0);
       Gl.glVertex2f(bounds.Left, bounds.Top);
       Gl.glVertex2f(bounds.Right, bounds.Top);
       Gl.glVertex2f(bounds.Right, bounds.Bottom);
       Gl.glVertex2f(bounds.Left, bounds.Bottom);

      }
     Gl.glEnd();
     Gl.glEnable(Gl.GL_TEXTURE_2D);
    }


  }
}

The Entity class contains a sprite and some code to render that sprite’s bounding box.

With this entity definition, the Enemy class can be greatly simplified.

public class Enemy : Entity
{
  double _scale =  0.3;
  public Enemy(TextureManager textureManager)
  {
    _sprite.Texture =  textureManager.Get("enemy_ship");
    _sprite.SetScale(_scale , _scale);
    _sprite.SetRotation(Math.PI); // make it face the player
    _sprite.SetPosition(200, 0); // put it somewhere easy to see
  }


  public void Update(double elapsedTime)
  {
  }


  public void Render(Renderer renderer)
  {
    renderer.DrawSprite(_sprite);
    Render_Debug();
  }


  public void SetPosition(Vector position)
  {
  _ sprite.SetPosition(position);
  }
}

The Enemy is now a type of Entity and no longer needs its own reference to a sprite. This same refactoring can be applied to the PlayerCharacter class.

public class PlayerCharacter : Entity
{
  double _speed =  512; // pixels per second

  public void Move(Vector amount)
  {
    amount *= _speed;
    _sprite.SetPosition(_sprite.GetPosition() + amount);
  }


  public PlayerCharacter(TextureManager textureManager)
  {
    _sprite.Texture = textureManager.Get("player_ship");
    _sprite.SetScale(0.5, 0.5); // spaceship is quite big, scale it down.
  }


  public void Render(Renderer renderer)
  {
    Render_Debug();
    renderer.DrawSprite(_sprite);
  }
}

Run the code again, and now both the enemy and player will have appropriate bounding boxes.

For now, the rule will be that if the PlayerCharacter hits an enemy, the game ends. This can be refined later by giving the player some health. To get a game working as fast as possible, it will for now be instant death.

The first change is in the InnerGameState; it needs to recognize when the player has died and therefore failed to complete the current level.

public void Update(double elapsedTime)
{
  _level.Update(elapsedTime);
  _gameTime -= elapsedTime;

  if (_gameTime <= 0)
  {
    OnGameStart();
    _gameData.JustWon =  true;
    _system.ChangeState("game_over");
  }


  if (_level.HasPlayerDied())
  {
    OnGameStart();
    _gameData.JustWon =  false;
    _system.ChangeState("game_over");
  }
}

Here the Level class has been given an extra function, HasPlayerDied, that reports if the player has died. In this case, the player’s death is checked after the gameTime. This means that if the time runs out, but the player died in the last possible second, he still won’t win the level.

In the level class, the HasPlayerDied method needs to be implemented. It’s just a simple wrapper around the current PlayerCharacter’s state.

public bool HasPlayerDied()
{
  return _playerCharacter.IsDead;
}

The death flag is contained in the PlayerCharacter class.

bool _dead =  false;
public bool IsDead
{
  get
  {
    return _dead;
  }
}

When the player collides with an enemy, this death flag can be set and the game will end with the player losing the level. The level needs some code to process the collisions that happen between the enemy craft and the player. This collision processing is done in the level class, which has access to the PlayerCharacter and the list of enemies.

private void UpdateCollisions
()
{
  foreach (Enemy enemy in _enemyList)
  {
    if (enemy.GetBoundingBox().IntersectsWith(_playerCharacter.
GetBoundingBox()))
    {
     enemy.OnCollision(_playerCharacter);
     _playerCharacter.OnCollision(enemy);
    }
  }
}


public void Update(double elapsedTime)
{
  UpdateCollisions();

The collision processing code is called each frame by the level’s Update method. The collisions are determined by iterating through the list of enemies and checking if their bounding box intersects with the player. The intersection is worked out using C#’s RectangleF IntersectsWith method. If the bounding box of the player and enemy do intersect, then OnCollision is called for the player and the enemy. The Player.OnCollision method is passed the enemy object. It collides with it and the Enemy.OnCollision is passed the player object. There is no test for enemies colliding with other enemies; it’s assumed this is no problem if it happens in the game.

The OnCollision class needs to be implemented for both the Enemy and PlayerCharacter classes. Here is the skeleton method that needs to be added to the Enemy class.

internal void OnCollision(PlayerCharacter player)
{
  // Handle collision with player.
}

Unlike Enemy, the PlayerCharacter class actually has some functionality. Its implementation is as follows.

internal void OnCollision(Enemy enemy)
{
  _dead =  true;
}

When the player collides with the enemy, its dead flag is set to true, which will cause the game to end. The game is now partially playable with an outcome for losing or winning. From this point on, the refinements to the game will start to make it more fun to play.

Introducing Simple Weapons

Weapons in the game mainly take the form of different types of bullets. A good goal to aim for is to have the player shoot a bullet each time the A button or spacebar is pressed. Eventually the enemies will also be firing bullets, which is important to bear in mind when creating the bullet system.

To experiment with bullets, another texture is needed. Find bullet.tga on the CD in Assets directory and add it to the project, remembering to set the properties as before. Then this texture needs to be loaded into the texture manager.

_textureManager.LoadTexture("bullet", "bullet.tga");

Once the texture is loaded, the next logical class to create is the Bullet class. This will have a bounding box and a sprite so it too can inherit from Entity. The class should be created in the Shooter project.

public class Bullet : Entity
{
  public bool Dead { get; set; }
  public Vector Direction { get; set; }
  public double Speed { get; set; }


  public double X
  {
    get { return _sprite.GetPosition().X; }
  }


  public double Y
  {
    get { return _sprite.GetPosition().Y; }
  }


  public void SetPosition(Vector position)
  {
    _sprite.SetPosition(position);
  }


  public void SetColor(Color color)
  {
    _sprite.SetColor(color);
  }


  public Bullet(Texture bulletTexture)
  {
    _sprite.Texture =  bulletTexture;

    // Some default values
    Dead =  false;
    Direction =  new Vector(1, 0, 0);
    Speed =  512;// pixels per second
  }
  public void Render(Renderer renderer)
  {
    if (Dead)
    {
     return;
    }
    renderer.DrawSprite(_sprite);
  }


  public void Update(double elapsedTime)
  {
    if (Dead)
    {
     return;
    }
    Vector position =  _sprite.GetPosition();
    position += Direction * Speed * elapsedTime;
    _sprite.SetPosition(position);
  }
}

The bullet has three members: the direction the bullet will travel, the speed it will travel, and a flag to tell if the bullet is dead or not. There are also position setters and getters for the bullet sprite. There is also a setter for the color; it makes sense to allow the bullets to be colored. The player bullets will only hurt the enemies and the enemy bullets will only hurt the player. To let the player know which bullets are which, they are given different colors.

You may see the position getter and setter and color setter and wonder if it would be better just to make the sprite class public. Then if we wanted to change the position or color, we could just alter the bullet sprite directly. Every situation is different but as a general rule, it’s better to keep more data private and provide an interface to the data that needs to be changed. Also bullet.SetColor() is more straightforward to read than bullet.Sprite.SetColor().

The constructor takes in a texture for the bullet and sets some default values for the color, direction, and speed. The speed is measured in pixels per second. The final two methods are Render and Update. The Update loop updates the position of the bullet using the direction and speed. The position increase is scaled by the amount of time since the last frame, so the movement will be consistent on any speed of computer. The render is quite straightforward; it just draws the bullet sprite. Both the render and update loops do nothing if the bullet has its dead flag set to true.

A lot of bullets are going to be flying about and there needs to be a certain amount of logic to deal with that. Bullets that leave the screen need to be turned off. A BulletManager class is a fine place to put all this logic. There are two ways to write a BulletManager class: the simple straightforward way and the memory-efficient way. The BulletManager introduced here is the straightforward type; when an enemy is destroyed on screen its reference is removed from the BulletManager and the object is destroyed in code; freeing any memory it was using. Every time the player fires, a new bullet is created. This is basic, but creating and deleting lots of objects in the game loop is a bad thing; it will make your code slow if you do it too much. Creation and deletion of objects tends to slow operations.

A more memory-efficient method of managing the bullets is to have a big list of, say, 1,000 bullets. Most of the bullets are dead; every time the user fires the list is searched and a dead bullet is brought to life. No new objects need to be created. If all 1,000 bullets are alive, then either the player can’t fire or a heuristic (such as bullet that’s been alive longest) is used to kill one of the current bullets and let the player use that one. Recycling bullets in this way is a better way to write the BulletManager. Once you’ve seen the simple manager in action, you can always have a go at converting it to the more memory-efficient one yourself.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;


namespace Shooter
{
  public class Bullet : Entity
  {
    public bool Dead { get; set; }
    public Vector Direction { get; set; }
    public double Speed { get; set; }


    public double X
    {
      get { return _sprite.GetPosition().X; }
    }


    public double Y
    {
      get { return _sprite.GetPosition().Y; }
    }


    public void SetPosition(Vector position)
    {
      _sprite.SetPosition(position);
    }


    public void SetColor(Color color)
    {
      _sprite.SetColor(color);
    }


    public Bullet(Texture bulletTexture)
    {
      _sprite.Texture =  bulletTexture;

      // Some default values
      Dead =  false;
      Direction = new Vector(1, 0, 0);
      Speed = 512;// pixels per second
    }
    public void Render(Renderer renderer)
    {
     if (Dead)
     {
       return;
     }
     renderer.DrawSprite(_sprite);
    }


    public void Update(double elapsedTime)
    {
     if (Dead)
     {
       return;
     }
     Vector position =  _sprite.GetPosition();
     position += Direction * Speed * elapsedTime;
     _sprite.SetPosition(position);
    }
  }


}

The BulletManager only has two member variables: a list of bullets it’s managing and a rectangle representing the screen bounds. Remember to include the using System.Drawing statement at the top of the file so that the RectangleF class can be used. The screen bounds are used to determine if a bullet has left the screen and can be destroyed.

The constructor takes in a rectangle, playArea, describing the playing area and assigns it to the _bounds member. The Shoot method is used to add a bullet to the BulletManager. Once a bullet is added, the BulletManager tracks it until it leaves the play area or hits a ship. The Update method updates all the bullets being tracked and then checks if any are out of bounds; finally, it deletes any bullets that have the Dead flag set to true.

The CheckOutOfBounds function uses the rectangle intersection test between the bullet and playing area to determine if it’s off-screen. The RemoveDeadBullets performs an interesting trick; it iterates through the list of bullets backwards and removes any bullets that are dead. Foreach can’t be used here and neither can forward iteration; if you were doing forward iteration and removed a bullet, then the list would become shorter by one element, and when the loop got to the end of the list, it would have an out of bounds error. Reversing the iteration of the loop fixes this problem. The length of the list doesn’t matter; it will always head to 0.

The Render method is quite standard; it just renders out all of the bullets.

This BulletManager is best placed in the Level class. If you don’t have a using System.Drawing statement at the top of the Level.cs file then you will need to add one before you can use the RectangleF class.

class Level
{
  BulletManager _bulletManager =  new BulletManager(new RectangleF(-1300
/ 2, -750 / 2, 1300, 750));

The BulletManager is given a playing area. This is a little bigger than the actual window size. This provides a buffer so that the bullets are totally off-screen before they are destroyed. The BulletManager then needs to be added to the Update and Render methods in the Level class.

public void Update(double elapsedTime)
{
  UpdateCollisions();
  _bulletManager.Update(elapsedTime);


// A little later in the code


public void Render(Renderer renderer)
{
  _background.Render(renderer);
  _backgroundLayer.Render(renderer);

  _enemyList.ForEach(x => x.Render(renderer));
  _playerCharacter.Render(renderer);
  _bulletManager.Render(renderer);
}

The BulletManager is rendered last so that bullets will be rendered on top of everything. At this point, the BulletManager is fully integrated, but there’s no way to test it without giving the player a way to fire bullets. For this to happen, the PlayerCharacter class needs access to the manager. In the Level constructor, pass the BulletManager into the PlayerCharacter constructor.

_playerCharacter =  new PlayerCharacter(_textureManager,
_bulletManager);

The PlayerCharacter class code then needs to be altered to accept and store a reference to the BulletManager.

BulletManager _bulletManager;
Texture _bulletTexture;
public PlayerCharacter(TextureManager textureManager, BulletManager
bulletManager)
{
  _bulletManager =  bulletManager;
  _bulletTexture =  textureManager.Get("bullet");

The PlayerCharacter constructor also stores the bulletTexture that will be used when firing bullets. To fire a bullet, a bullet object needs to be created and positioned so that it starts near the player and then passes into the BulletManager. A new Fire method in the PlayerCharacter class will be responsible for this.

Vector _gunOffset =  new Vector(55, 0, 0);
public void Fire()
{
  Bullet bullet =  new Bullet(_bulletTexture);
  bullet.SetColor(new Color(0, 1, 0, 1));
  bullet.SetPosition(_sprite.GetPosition() +  _gunOffset);
  _bulletManager.Shoot(bullet);
}

The bullet is created using the bulletTexture that was set up in the constructor. It’s then colored green, but you can choose any color you want. The position of the bullet is set so that it is the same position as the player’s ship, but with an offset so that the bullet appears to come from the front of the ship. If there was no offset, the bullet would appear right in the middle of the ship sprite and this would look a little weird. The bullet direction isn’t altered because forward on the X axis is the default value. The default speed is also fine. Finally, the bullet is given to the BulletManager and is officially fired using the Shoot method.

The player can now fire bullets, but there is no code to detect input and call the Fire method. All the input for the player is handled in the Level class in the Update method. It’s a bit messy to have the input code in the root of the Update method, so I’ve extracted a new function called UpdateInput; this helps keep things a bit tidier.

public void Update(double elapsedTime)
{
  UpdateCollisions();
  _bulletManager.Update(elapsedTime);


  _background.Update((float)elapsedTime);
  _backgroundLayer.Update((float)elapsedTime);
  _enemyList.ForEach(x => x.Update(elapsedTime));

  // Input code has been moved into this method
  UpdateInput(elapsedTime);


}


private void UpdateInput(double elapsedTime)
{
  if (_input.Keyboard.IsKeyPressed(Keys.Space) || _input.Controller.
ButtonA.Pressed)
  {
    _playerCharacter.Fire();
  }
  // Pre-existing input code omitted.

Take all the input code out of the Update loop and put it at the end of the new UpdateInput method. This UpdateInput method is then called from the Update method. Some new code has also been added to handle the player firing. If the space bar on the keyboard or the A button on the gamepad is pressed, then the player fires a bullet. Run the program and try the spaceship’s new firing abilities.

The new bullets can be seen in Figure 10.8. The bullets are created every time the player hits the fire button. For gameplay reasons, it’s probably best to slow this down a little and give the spaceship a small recovery time between shots. Modify the PlayerCharacter class as follows.

Firing bullets.

Figure 10.8. Firing bullets.

Vector _gunOffset =  new Vector(55, 0, 0);
static readonly double FireRecovery =  0.25;
double _fireRecoveryTime =  FireRecovery;
public void Update(double elapsedTime)
{
  _fireRecoveryTime =  Math.Max(0, (_fireRecoveryTime - elapsedTime));
}


public void Fire()
{
  if (_fireRecoveryTime> 0)
  {
    return;
  }
  else
  {
    _fireRecoveryTime =  FireRecovery;
  }


  Bullet bullet =  new Bullet(_bulletTexture);
  bullet.SetColor(new Color(0, 1, 0, 1));
  bullet.SetPosition(_sprite.GetPosition() +  _gunOffset);
  _bulletManager.Shoot(bullet);
}

To count down the recovery time, the PlayerCharacter class needs an Update method to be added. The Update method will count down the recovery time, but it never goes below 0. This is done by using the Math.Max function. With the recovery time set, the Fire command returns immediately if the spaceship is still recovering from the last shot. If the recovery time is 0, then the ship can fire and the recovery time is reset so it can start counting down again.

The Level also needs a minor change; it needs to call the PlayerCharacter’s Update method.

public void Update(double elapsedTime)
{
  _playerCharacter.Update(elapsedTime);


  _background.Update((float)elapsedTime);
  _backgroundLayer.Update((float)elapsedTime);


  UpdateCollisions();
  _enemyList.ForEach(x => x.Update(elapsedTime));
  _bulletManager.Update(elapsedTime);


  UpdateInput(elapsedTime);
}

Run the program again and you won’t be able to fire as fast. This is your game, so tweak the recovery rate to whatever feels right to you!

Damage and Explosions

The bullets have been added, but they sail right through the enemy inflicting no damage; it’s time to change that. The enemy should know when it’s been hit and respond appropriately. We’ll start by handling the collisions and then create an animated explosion.

The collision code is handled in the Level class, in the UpdateCollisions function. This function needs to be extended to also handle collisions between bullets and enemies.

private void UpdateCollisions()
{
  foreach (Enemy enemy in _enemyList)
  {
    if (enemy.GetBoundingBox().IntersectsWith(_playerCharacter.
GetBoundingBox()))
    {
     enemy.OnCollision(_playerCharacter);
     _playerCharacter.OnCollision(enemy);
    }


    _bulletManager.UpdateEnemyCollisions(enemy);
  }
}

One extra line has been added to the end of the for loop. The BulletManager is asked to check any collisions between the bullets and the current enemy. UpdateEnemyCollisions is a new function for the BulletManager, so it needs to be implemented.

internal void UpdateEnemyCollisions(Enemy enemy)
{
  foreach (Bullet bullet in _bullets)
  {
    if(bullet.GetBoundingBox().IntersectsWith(enemy.GetBounding-
Box()))
    {
     bullet.Dead =  true;
     enemy.OnCollision(bullet);
    }
  }
}

The collision between the bullet and enemy is determined by checking the intersection between the bounding boxes. If the bullet has hit the enemy, then the bullet is destroyed and the enemy is notified about the collision.

If a bullet hits an enemy, there are a number of different ways to react. The enemy could be immediately destroyed and explode, or the enemy could take damage, requiring a few more shots to be destroyed. Assigning health levels to the enemy is probably something we’d like in the future, so we may as well do it now. Let’s add a Health member variable to the enemy and then we can implement the OnCollision function for bullets.

public int Health { get; set; }
public Enemy(TextureManager textureManager)
{
  Health =  50; // default health value.
  //Remaining constructor code omitted

The Enemy class already has one OnCollision method, but that is for colliding with the PlayerCharacter. We will create a new overloaded OnCollision method that is only concerned about colliding with bullets. When a bullet hits the enemy, it will take some damage and lower its health value. If the health of the enemy drops below 0, then it will be destroyed. If the player shoots the enemy and causes some damage, there needs to be some visual feedback to indicate the enemy has taken a hit. A good way to present this feedback is to flash the enemy yellow for a fraction of a second.

static readonly double HitFlashTime =  0.25;
double _hitFlashCountDown =  0;
internal void OnCollision(Bullet bullet)
{
  // If the ship is already dead then ignore any more bullets.
  if (Health == 0)
  {
    return;
  }


  Health =  Math.Max(0, Health - 25);
  _hitFlashCountDown =  HitFlashTime; // half
  _sprite.SetColor(new Engine.Color(1, 1, 0, 1));


  if (Health == 0)
  {
    OnDestroyed();
  }
}


private void OnDestroyed()
{
  // Kill the enemy here.
}

The OnDestroyed function is a placeholder for now; we’ll worry about how the enemy is destroyed a little later. In the OnCollision function, the first if statement checks if the ship is already at 0 health. In this case, any additional damage is ignored; the player has already killed the enemy and the game doesn’t need to acknowledge any more shots. Next the Health is reduced by 25, an arbitrary damage number, to represent the damage of a single bullet hit. Math. Max is used to ensure that the health never falls below 0. The ship should flash yellow when hit. The countdown is set to represent how long the flash should take. The ship sprite is set to a yellow color, which in RGBA is 1,1,0,1. Finally, the health is checked, and if it equals 0, then the placeholder OnDestroyed method is called. This is the function where the explosion will be triggered.

To cause the ship to flash, the Update loop will also need to be modified. It needs to count down the flash and change the color from yellow to white.

public void Update(double elapsedTime)
{
  if (_hitFlashCountDown != 0)
  {
    _hitFlashCountDown =  Math.Max(0, _hitFlashCountDown - elapsedTime);
    double scaledTime =  1 - (_hitFlashCountDown / HitFlashTime);
    _sprite.SetColor(new Engine.Color(1, 1, (float)scaledTime, 1));
  }
}

The Update loop modifies the flash color of the enemy spaceship. If the flash countdown has already dropped to 0, then the flash has finished and doesn’t need to be updated. If the _hitFlashCountDown doesn’t equal 0, then it is reduced by the amount of time that has passed since the last frame. Math.Max is used again to ensure the count doesn’t fall below 0. The countdown is then scaled to get a value from 0 to 1, indicating how far through the flash we currently are; 0 indicates the flash has just started and 1 indicates it’s finished. This number is inversed by subtracting it from 1 so that 1 indicates that the flash has just started and 0 indicates that it’s just finished. This scaled number is then used to move the blue channel of the color from 0 to 1. This will flash the ship from yellow to white.

Run the program and shoot the enemy ship a few times; it will flash yellow a few times and then stop responding because it’s been destroyed. Enemy ships shouldn’t just stop responding; they should explode!

The easiest way to produce a good explosion is to use an animated sprite. Figure 10.9 shows a keyframe texture map of an explosion. This texture was created using a procedural explosion generator available for free from Positech games (http://www.positech.co.uk/content/explosion/explosiongenerator.html).

Animated explosion texture map.

Figure 10.9. Animated explosion texture map.

Figure 10.9 has 16 frames in total; four frames in height and four frames in length. An animated sprite can be created by reading in this texture and changing the U,V coordinates so that it moves from the first frame to the last frame as time passes. An animated sprite is really just a different type of sprite, so to create it, we can extend the existing Sprite class. An animated sprite is something that can be used by many different games, so it should be created in the Engine project rather than the game project.

public class AnimatedSprite : Sprite
{
  int _framesX;
  int _framesY;
  int _currentFrame =  0;
  double _currentFrameTime =  0.03;
  public double Speed { get; set; } // seconds per frame
public bool Looping { get; set; }
public bool Finished { get; set; }


public AnimatedSprite()
{
  Looping =  false;
  Finished =  false;
  Speed =  0.03; // 30 fps-ish
  _currentFrameTime =  Speed;
}


public System.Drawing.Point GetIndexFromFrame(int frame)
{
  System.Drawing.Point point =  new System.Drawing.Point();
  point.Y =  frame / _framesX;
  point.X =  frame - (point.Y * _framesY);
  return point;
}


private void UpdateUVs()
{
  System.Drawing.Point index =  GetIndexFromFrame(_currentFrame);
  float frameWidth =  1.0f / (float)_framesX;
  float frameHeight =  1.0f / (float)_framesY;
  SetUVs(new Point(index.X * frameWidth, index.Y * frameHeight),
    new Point((index.X +  1) * frameWidth, (index.Y +  1) * frameHeight));
}


public void SetAnimation(int framesX, int framesY)
{
  _framesX = framesX;
  _framesY = framesY;
  UpdateUVs();
}


private int GetFrameCount()
{
  return _framesX * _framesY;
}


public void AdvanceFrame()
{
    int numberOfFrames =  GetFrameCount();
    _currentFrame =  (_currentFrame +  1) % numberOfFrames;
  }
  public int GetCurrentFrame()
  {
    return _currentFrame;
  }


  public void Update(double elapsedTime)
  {
    if (_currentFrame == GetFrameCount() - 1 && Looping == false)
    {
     Finished =  true;
     return;
    }


    _currentFrameTime -= elapsedTime;
    if (_currentFrameTime< 0)
    {
     AdvanceFrame();
     _currentFrameTime =  Speed;
     UpdateUVs();
    }
  }
}

This AnimatedSprite class works exactly the same as the Sprite class, except for the AnimatedSprite class can be told how many frames the texture has in X and Y dimensions. When the Update loop is called, the frame is changed over time.

This class has quite a few members, but they are mostly used for describing the animation and tracking its progress. The number of frames in X and Y dimension are described by the _framesX and _framesY member variables. For the Figure 10.9 example, both these variables would be set to four. The _currentFrame variable is the frame that the sprite U,Vs are currently set to. The _currentFrameTime is the amount of time that will be spent on the current frame before the animation advances to the next frame. Speed is a measure of how much time is spent on each frame in seconds. Looping determines if the animation should loop, and Finished is a flag that is set to true once the animation has ended.

The constructor of the AnimatedSprite sets some default values. A freshly created sprite doesn’t loop, and has its Finished flag set to false, its frame speed set to about 30 frames per second, and the _currentFrameTime is set to 0.03 seconds, which will make the animation run at 30 frames per second.

The GetIndexFromFrame method takes an index as shown in Figure 10.10 and returns an X,Y coordinate of the index position. For example, index 0 would return 0,0 and index 15 would return 3,3. The index number is broken into an X and Y coordinate by dividing the index by the row length; this gives the number of rows and therefore the Y coordinate of the index. The X coordinate is then whatever is left of the index when the Y rows are removed. This function is very useful when translating, calculating the U,Vs for a certain frame.

Animated explosion texture map with frame index.

Figure 10.10. Animated explosion texture map with frame index.

UpdateUVs uses the current frame index to change the U,Vs so the sprite correctly represents that frame. It first gets the X,Y coordinates of the current frame using GetIndexFromFrame. Then it calculates the width and height of an individual frame. As texture coordinates range from 0 to 1, the width and height of a single frame is calculated by dividing the number of frames along the X and the Y by 1. Once the dimensions of a single frame are calculated, the positions of the U,Vs can be worked out by multiplying the frame width and height by the X,Y coordinates of the current frame; this gets the top-left point of the frame on the texture map. The SetUVs method requires a TopLeft and BottomRight point. The BottomRight position is calculated from the TopLeft position by adding an extra frame width and height.

SetAnimation is the method used to set the number of frames along the X and Y of the texture map. It makes a call to UpdateUVs so that the sprite is updated to display the correct frame. GetFrameCount gets the total number of frames in the animation. The AdvanceFrame method moves the animation to the next frame if it comes to the end of the frames; then the frame index wraps around to 0 again. The wrap around is done using modulus—the % operator. The modulus operator computes the remainder that results from performing integer division. The best way to understand the use of the modulus operator is to provide an example you are probably already familiar with: time. A clock face has 12 numbers, and it works in modulo 12: 13:00 hours in modulo 12 is 1 o’clock. In our case, the modulo is equal to the total number of frames in the animation.

The Update method is responsible for updating the current frame and making the explosion appear to animate. If Looping is set to false and the current frame is the last frame, then the Update method returns immediately and the Finished flag is set to true. If the animation hasn’t finished or is looping, then the frame countdown, _currentFrameTime, is updated, and if it goes below 0, the frame needs to be changed. The frame is updated by making a call to AdvanceFrame, resetting the _currentFrameTime, and finally updating the U,Vs.

With the AnimatedSprite class added to the Engine project, the explosion animation can be tested. Find the explode.tga file on the CD in the Assets folder and add it to the project, setting the properties as usual. It can then be loaded in the form.cs file with the other textures.

_textureManager.LoadTexture("explosion", "explode.tga");

A quick way to test the animation is to load it directly into the Level as an animated sprite.

AnimatedSprite _testSprite =  new AnimatedSprite();public Level(Input
input, TextureManager textureManager, PersistantGameData gameData)
{
    _testSprite.Texture =  textureManager.Get("explosion");
    _testSprite.SetAnimation(4, 4);


// a little later in the code
public void Update(double elapsedTime)
{
  _testSprite.Update(elapsedTime);


// a little later in the code


public void Render(Renderer renderer)
{
  // Background and other sprite code omitted.
  renderer.DrawSprite(_testSprite);
  renderer.Render();
}

Running the program and entering a level will now play the explosion animation once. This confirms everything is working fine (see Figure 10.11).

The explosion in the game.

Figure 10.11. The explosion in the game.

Managing Explosions and Enemies

In the last section, we got an example explosion working, but it really needs to be set off only when enemies are destroyed. To this end, two new systems need to be created: one to handle the explosions and general game effects, and one to handle the oncoming enemies.

The explosions should be handled in a similar way to the bullets—creating a dedicated manager that handles the creation and destruction of the explosions. In the future of your project, it’s possible you’ll want more effects—smokes, sparks, or even power ups—than explosions. The EffectsManager class should be created in the Shooter project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;


namespace Shooter
{
  public class EffectsManager
  {
    List<AnimatedSprite> _effects =  new List<AnimatedSprite>();
    TextureManager _textureManager;


    public EffectsManager(TextureManager textureManager)
    {
     _textureManager =  textureManager;
    }


    public void AddExplosion(Vector position)
    {
     AnimatedSprite explosion =  new AnimatedSprite();
     explosion.Texture =  _textureManager.Get("explosion");
     explosion.SetAnimation(4, 4);
     explosion.SetPosition(position);
     _effects.Add(explosion);
    }


    public void Update(double elapsedTime)
    {
     _effects.ForEach(x => x.Update(elapsedTime));
     RemoveDeadExplosions();
    }


    public void Render(Renderer renderer)
    {
     _effects.ForEach(x => renderer.DrawSprite(x));
    }


    private void RemoveDeadExplosions()
    {
     for (int i =  _effects.Count - 1; i>= 0; i-)
     {
       if (_effects[i].Finished)
       {
        _effects.RemoveAt(i);
       }
     }
    }


  }
}

This EffectManager allows an explosion to be set off, runs the explosion animation until it ends, and then removes the explosion effect. You may notice it’s very similar to the BulletManager class. These separate managers could all be combined in one generalized manager, but by keeping them separate, the interactions between the game objects can be specific and more efficient. Explosions don’t care about collision detection with enemies or players but bullets do. In separate managers, it’s easy to separate out the particular requirements of each object; explosions only need to run an animation, whereas bullets need to check for intersection with all of the enemies. Separate managers work great when only a limited number of objects are in the game, but if there are going to be many different entities, then a more generalized entity manager is a better choice.

The EffectsManager needs to be initialized in the Level class and hooked up to the render and update loops.

EffectsManager _effectsManager;
public Level(Input input, TextureManager textureManager, PersistantGa-
meData gameData)
{
  _input =  input;
  _gameData =  gameData;
  _textureManager =  textureManager;
  _effectsManager =  new EffectsManager(_textureManager);

// code omitted


public void Update(double elapsedTime)
{
  _effectsManager.Update(elapsedTime);


// code omitted


public void Render(Renderer renderer)
{
  // Background, sprites and bullet code omitted
  _effectsManager.Render(renderer);
  renderer.Render();
}

The ExplosionManager is now hooked up and can be used to launch several explosions at once. For the enemies to launch explosions when they die, they need access to the manager, which can be passed into the constructor.

EffectsManager _effectsManager;


public Enemy(TextureManager textureManager, EffectsManager
effectsManager)
{
  _effectsManager =  effectsManager;

The enemy can now set off an explosion when it dies.

private void OnDestroyed()
{
  // Kill the enemy here.
  _effectsManager.AddExplosion(_sprite.GetPosition());
}

In the Level.cs file, the EffectsManager needs to be passed into the Enemy constructor. Once this is done, shooting the enemy a couple of times in the game will cause an explosion when the enemy is destroyed.

Next, the enemies will get their own manager; this will be the final manager needed to create a full, working game.

public class EnemyManager
{
  List<Enemy> _enemies =  new List<Enemy>();
  TextureManager _textureManager;
  EffectsManager _effectsManager;
  int _leftBound;

  public List<Enemy> EnemyList
  {
    get
    {
     return _enemies;
    }
  }


  public EnemyManager(TextureManager textureManager, EffectsManager
effectsManager, int leftBound)
  {
    _textureManager = textureManager;
    _effectsManager = effectsManager;
    _leftBound =  leftBound;

    // Add a test enemy.
    Enemy enemy =  new Enemy(_textureManager, _effectsManager);
    _enemies.Add(enemy);
  }


  public void Update(double elapsedTime)
  {
    _enemies.ForEach(x => x.Update(elapsedTime));
    CheckForOutOfBounds();
    RemoveDeadEnemies();
  }


  private void CheckForOutOfBounds()
  {
    foreach (Enemy enemy in _enemies)
    {
     if (enemy.GetBoundingBox().Right< _leftBound)
      {
        enemy.Health =  0; // kill the enemy off
      }
    }
  }


  public void Render(Renderer renderer)
  {
    _enemies.ForEach(x => x.Render(renderer));
  }


  private void RemoveDeadEnemies()
  {
    for (int i =  _enemies.Count - 1; l> = 0; i-)
    {
      if (_enemies[i].IsDead)
      {
        _enemies.RemoveAt(i);
      }
    }
  }
}

An extra function needs to be added to the Enemy class to check if the enemy has been destroyed.

class Enemy : Entity
{
  public bool IsDead
  {
    get { return Health == 0; }
  }

The IsDead method of the Enemy class returns true if the enemy’s health is equal to 0; otherwise, it returns false. The EnemyManager, like the BulletManager, has an out of bounds check, but it’s a little different. Enemies in a scrolling shooter game tend to start off on the far right of the screen and then move past the player exiting to the left. The out of bounds check compares the right-most point of the enemy bounding box against the left-most part of the screen. This removes enemies that the player fails to destroy and that escape off the left of the screen.

The Level class now needs to be modified to introduce this new manager and get rid of the old list.

// List<Enemy> _enemyList =  new List<Enemy>();< - Removed
EnemyManager _enemyManager;


public Level(Input input, TextureManager textureManager, PersistantGa-
meData gameData)
{
  _input =  input;
  _gameData =  gameData;
  _textureManager =  textureManager;

  _background =  new ScrollingBackground(textureManager.Get
("background"));
  _background.SetScale(2, 2);
  _background.Speed =  0.15f;

  _backgroundLayer =  new ScrollingBackground(textureManager.Get
("background_layer_1"));
  _backgroundLayer.Speed =  0.1f;
  _backgroundLayer.SetScale(2.0, 2.0);


  _playerCharacter =  new PlayerCharacter(_textureManager,
_bulletManager);


  _effectsManager = new EffectsManager(_textureManager);
  // _enemyList.Add(new Enemy(_textureManager, _effectsManager));
<- Removed
  _enemyManager =  new EnemyManager(_textureManager, _effectsMana-
ger, -1300);
}

The collision processing needs to change a little as well; it will now use the list of enemies in the EnemyManager when checking for enemy collisions.

private void UpdateCollisions()
{
  foreach (Enemy enemy in _enemyManager.EnemyList)

To be able to see the enemies, the Update and Render loops need to be modified.

public void Update(double elapsedTime)
{
  // _enemyList.ForEach(x => x.Update(elapsedTime));<- Remove this line
  _enemyManager.Update(elapsedTime);


// Code omitted


public void Render(Renderer renderer)
{
  _background.Render(renderer);
  _backgroundLayer.Render(renderer);

  //_enemyList.ForEach(x => x.Render(renderer));<- remove this line
  _enemyManager.Render(renderer);

Run the program now. Shooting the enemy a couple of times will make it explode and disappear. This has started to become much more game-like. The most obvious failings at the moment are that there is only one enemy and it doesn’t move.

Level Definitions

The current level lasts for 30 seconds and has one enemy at the start—this isn’t a very interesting level. If there was some system for defining levels, then it would be easier to add a bit more excitement to this level. The level definition is a list of enemies to spawn at certain times. A level definition will therefore need some way to define enemies; the following code is a good starting point. The EnemyDef class should be added to the Engine project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine;
namespace Shooter
{
  class EnemyDef
  {
    public string EnemyType { get; set; }
    public Vector StartPosition { get; set; }
    public double LaunchTime { get; set; }
    public EnemyDef()
    {


     EnemyType =  "cannon_fodder";
     StartPosition =  new Vector(300, 0, 0);
     LaunchTime =  0;
    }


    public EnemyDef(string enemyType, Vector startPosition, double
launchTime)
    {
     EnemyType =  enemyType;
     StartPosition =  startPosition;
     LaunchTime =  launchTime;
    }


  }
}

There is a string that describes the enemy type. In the code, we might provide several different types of enemies: small fast ones, big slow ones, etc. The default enemy type is cannon fodder, and that’s what we’ve got now. The start position is off the right of the screen. The launch time is the time at which the enemy will appear in the level. The level time counts down from some large number to 0. If the gameTime goes lower than the launch time, then an enemy object will be created and it will be launched into the level.

The EnemyManager is the class that will handle the enemy spawning. This means the constructor needs to be modified, and a list of upcoming enemies needs to be added.

List<EnemyDef> _upComingEnemies =  new List<EnemyDef>();
public EnemyManager(TextureManager textureManager, EffectsManager
effectsManager, int leftBound)
{
  _textureManager = textureManager;
  _effectsManager = effectsManager;
  _leftBound =  leftBound;
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", new Vector(300,
300, 0), 25));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", new Vector(300,
-300, 0), 30));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", new Vector(300, 0,
0), 29));


  // Sort enemies so the greater launch time appears first.
  _upComingEnemies.Sort(delegate(EnemyDef firstEnemy, EnemyDef
secondEnemy)
  {
    return firstEnemy.LaunchTime.CompareTo(secondEnemy.LaunchTime);
  });
}

The _upcomingEnemies list is a list of enemy definitions sorted by launch time. The greater the launch time, the higher in the list the definition appears. Each frame the top item of the list is checked to see if it’s ready to launch. Only the top enemy definition needs to be checked because the list is sorted. If the list wasn’t sorted, then every item in the list would need to be checked to decide which of the enemy definitions had a launch time greater than the current gameTime, and therefore needed to be launched next.

This enemy launching is done in the Update loop of the EnemyManager, which calls the new method UpdateEnemySpawns.

private void UpdateEnemySpawns(double gameTime)
{
  // If no upcoming enemies then there's nothing to spawn.
  if (_upComingEnemies.Count == 0)
  {
    return;
  }


  EnemyDef lastElement =  _upComingEnemies[_upComingEnemies.Count - 1];
  if (gameTime< lastElement.LaunchTime)
  {
    _upComingEnemies.RemoveAt(_upComingEnemies.Count - 1);
    _enemies.Add(CreateEnemyFromDef(lastElement));
  }
}


private Enemy CreateEnemyFromDef(EnemyDef definition)
{
  Enemy enemy =  new Enemy(_textureManager, _effectsManager);
  enemy.SetPosition(definition.StartPosition);
  if (definition.EnemyType == "cannon_fodder")
  {
    // The enemy type could be used to alter the health or texture
    // but we're using the default texture and health for the cannon
fodder type
  }
  else
  {
    System.Diagnostics.Debug.Assert(false, "Unknown enemy type.");
  }


  return enemy;
}


public void Update(double elapsedTime, double gameTime)
{
  UpdateEnemySpawns(gameTime);

The Update methods in the EnemyManager and Level class have been modified to take in a gameTime parameter. The gameTime is a number that counts down to zero, at which point the level will end. This value is used to determine when to create new enemies. The InnerGameState has to pass this gameTime value into the Update method of the Level object, and the Level passes it on to the EnemyManager.

// In Level.cs
public void Update(double elapsedTime, double gameTime)
{
  _enemyManager.Update(elapsedTime, gameTime);
// In InnerGameState.cs
public void Update(double elapsedTime)
{
  _level.Update(elapsedTime, _gameTime);

The gameTime is passed all the way from the inner game state down to the UpdateEnemySpawns function in the EnemyManager. UpdateEnemy-Spawns first checks if there are any upcoming enemies in the _upcomingEnemies list; if there aren’t, then the method does nothing. If there are some upcoming enemies, the code checks the top of the list to see if it’s ready to be launched. If the enemy definition is ready to be launched, then it’s removed from the _upcomingEnemies list and the definition is used to make a new enemy object. The newly created enemy is then added to the _enemies list, spawning it in the game world.

CreateEnemyFromDef does pretty much what it says; it takes an EnemyDef object and returns an Enemy object. There’s only one type of enemy at the moment so it’s quite a simple function, but there’s a lot of scope for adding new enemy types.

Run the program now and as the level time ticks down, three enemies will spawn in the level.

Enemy Movement

Enemies in a scrolling shooter should sweep in from the right of the screen and attempt to exit to the right without getting blown up. The enemy advance is shown in Figure 10.12. The player bullets already have movement code so the enemies could reuse that code. This would work, but the enemy movement would be pretty boring; they’d move from right to left in a straight line. Enemy movement should be far more interesting, and the easiest way to do this is to give each enemy a predefined path with a number of way points. The enemy will hit all the way points and then exit to the left.

The enemy advance.

Figure 10.12. The enemy advance.

A path can be described easily as a series of points that lead from the right of the screen to the left of the screen. Figure 10.13 shows a path made up of points that could describe an enemy’s path through the playing area.

A path of points.

Figure 10.13. A path of points.

This path can be joined together to produce something like Figure 10.14. This shows the path the enemy would use, but the corners are very jagged. It would be nice if we could get something smoother. Splines are a nice way of creating smooth paths. Figure 10.15 shows a Catmull-Rom spline; this type of spline is guaranteed to pass through all the control points. Edwin Catmull who worked at Pixar and helped create Toy Story co-invented this type of spline with Raphael Rom.

Linear interpolation of a path.

Figure 10.14. Linear interpolation of a path.

Spline path.

Figure 10.15. Spline path.

The spline is obviously smoother, but it does require another class to be created. Splines are a mathematical description of a curve.

Catmull-Rom splines are simply a way to get a position, t, between any two of the points that make up the spline. In Catmull-Rom splines, the two points on either side of t are used in the calculation, as are their two neighbors, as shown in Figure 10.16.

Catmull-Rom splines.

Figure 10.16. Catmull-Rom splines.

Once some position can be obtained for a value of t (0-1) between any two neighboring points, then this can be extended so that t (0-1) can be mapped on to the entire line, not just one section. The calculation to get a position from t and four points is as follows.

Catmull-Rom splines.

This looks a little intimidating; three matrices are multiplied by a scalar that weighs all four points and decides how the t value is transformed into a position. It’s not important to understand exactly how this works (though you are encouraged to investigate!); it’s good enough to know what results will occur when you apply it.

Here is the C# implementation of a Catmull-Rom spline. This class should be added to the Engine project as it will be useful for more than this project. The spline code works in 3D so it can also be useful for tasks such as manipulating cameras or moving 3D entities along a path. The interface for this spline class is based on Radu Gruian’s C++ Overhauser code (http://www.codeproject.com/KB/recipes/Overhauser.aspx —the Code Project website may require you to register before it allows you to view the article. Registration is free. ).

public class Spline
{
  List<Vector> _points =  new List<Vector>();
  double _segmentSize =  0;

  public void AddPoint(Vector point)
  {
    _points.Add(point);
    _segmentSize =  1 / (double)_points.Count;
  }
  private int LimitPoints(int point)
  {
    if(point< 0)
    {
     return 0;
    }
    else if (point> _points.Count - 1)
    {
     return _points.Count - 1;
    }
    else
    {
     return point;
    }
  }


  // t ranges from 0 - 1
  public Vector GetPositionOnLine(double t)
  {
    if (_points.Count<= 1)
    {
     return new Vector(0,0,0);
    }


    // Get the segment of the line we're dealing with.
    int interval =  (int)(t / _segmentSize);

    // Get the points around the segment
    int p0 =  LimitPoints(interval - 1);
    int p1 =  LimitPoints(interval);
    int p2 =  LimitPoints(interval +    1);
    int p3 =  LimitPoints(interval +    2);

    // Scale t to the current segment
    double scaledT =  (t - _segmentSize * (double)interval) / _segmentSize;
    return CalculateCatmullRom(scaledT, _points[p0], _points[p1],
_points[p2], _points[p3]);
  }
  private Vector CalculateCatmullRom(double t, Vector p1, Vector p2,
Vector p3, Vector p4)
  {
    double t2 =  t * t;
    double t3 =  t2 * t;

    double b1 =  0.5 * (-t3 +  2 * t2 - t);
    double b2 =  0.5 * (3 * t3 - 5 * t2 + 2);
    double b3 =  0.5 * (-3 * t3 +  4 * t2 +  t);
    double b4 =  0.5 * (t3 - t2);

    return (p1 * b1 +  p2 * b2 +  p3 * b3 +  p4 * b4);
  }
}

This spline class is very simple to use. Any number of points can be added and the spline will join them together. The line is indexed from 0 to 1; a position on the line of 0.5 will return whatever point in space the middle of the line crosses. This makes the spline very easy to use with the earlier tween class. The spline requires all control points to be evenly spaced to give uniform values of t.

Each enemy is given a new Path class that will guide it across the level. This Path class is specific to the shooting game and should be created in the Shooter project.

public class Path
{
  Spline _spline =  new Spline();
  Tween _tween;

  public Path(List<Vector> points, double travelTime)
  {
    foreach (Vector v in points)
    {
     _spline.AddPoint(v);
    }
    _tween =  new Tween(0, 1, travelTime);
  }


  public void UpdatePosition(double elapsedTime, Enemy enemy)
  {
    _tween.Update(elapsedTime);
    Vector position =  _spline.GetPositionOnLine(_tween.Value());
    enemy.SetPosition(position);
  }
}

The class constructor takes in a time and a list of points; from this it creates a spline and a tween object. The travelTime determines how long the enemy will take to travel the path defined by the spline. The UpdatePosition method updates the tween and gets a new position from the spline, which is used to reposition the enemy. The following code modifies the Enemy to use the Path class.

public Path Path { get; set; }
public void Update(double elapsedTime)
{
  if (Path != null)
  {
    Path.UpdatePosition(elapsedTime, this);
  }
  if (_hitFlashCountDown != 0)
  {
    _hitFlashCountDown =  Math.Max(0, _hitFlashCountDown - elapsedTime);
    double scaledTime =  1 - (_hitFlashCountDown / HitFlashTime);
    _sprite.SetColor(new Engine.Color(1, 1, (float)scaledTime, 1));
  }
}

Now that all enemies have paths, the StartPosition variable from the EnemyDef can be removed, as the path will define where the enemy starts. Enemies can move through the level, but to do this they need to be given a path. In the EnemyManager, when an enemy is created, it needs to be given a path. In the following the cannon_fodder enemy type is given a path that goes from right to left, veering upwards as it reaches the middle. The time for the enemy to follow the full path takes ten seconds.

private Enemy CreateEnemyFromDef(EnemyDef definition)
{
  Enemy enemy =  new Enemy(_textureManager, _effectsManager);
  //enemy.SetPosition(definition.StartPosition);<- this line can be
removed
  if (definition.EnemyType == "cannon_fodder")
  {
    List<Vector> _pathPoints =  new List<Vector>();
    _pathPoints.Add(new Vector(1400, 0, 0));
    _pathPoints.Add(new Vector(0, 250, 0));
    _pathPoints.Add(new Vector(-1400, 0, 0));


    enemy.Path =  new Path(_pathPoints, 10);
  }
  else
  {
    System.Diagnostics.Debug.Assert(false, "Unknown enemy type.");
  }


  return enemy;
}

Now a more interesting level can be defined by editing the EnemyManager constructor.

public EnemyManager(TextureManager textureManager, EffectsManager
effectsManager, int leftBound)
{
  _textureManager = textureManager;
  _effectsManager = effectsManager;
  _leftBound = leftBound;

  _textureManager = textureManager;
  _effectsManager = effectsManager;
  _leftBound = leftBound;
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 30));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 29.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 29));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 28.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 25));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 24.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 24));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 23.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 20));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 19.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 19));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 18.5));


  // Sort enemies so the greater launch time appears first.
  _upComingEnemies.Sort(delegate(EnemyDef firstEnemy, EnemyDef
secondEnemy)
  {
    return firstEnemy.LaunchTime.CompareTo(secondEnemy.LaunchTime);


  });
}

The enemies now use paths to describe how they move through the level so a start position for each enemy definition is no longer required. This means the EnemyDef class needs to be rewritten.

public class EnemyDef
{
  public string EnemyType { get; set; }
  public double LaunchTime { get; set; }


  public EnemyDef()
  {
    EnemyType =  "cannon_fodder";
    LaunchTime =  0;
  }


  public EnemyDef(string enemyType, double launchTime)
  {
    EnemyType =  enemyType;
    LaunchTime =  launchTime;
  }
}

Run the code again, and you will see a stream of enemies arcing through the top half of the screen, as shown in Figure 10.17.

More interesting levels.

Figure 10.17. More interesting levels.

At this point, it might be nice to add a few more enemy types to spice up the level.

private Enemy CreateEnemyFromDef(EnemyDef definition)
{
  Enemy enemy =  new Enemy(_textureManager, _effectsManager);

  if (definition.EnemyType == "cannon_fodder")
  {
    List<Vector> _pathPoints =  new List<Vector>();
    _pathPoints.Add(new Vector(1400, 0, 0));
    _pathPoints.Add(new Vector(0, 250, 0));
    _pathPoints.Add(new Vector(-1400, 0, 0));
  enemy.Path =  new Path(_pathPoints, 10);
}
else if (definition.EnemyType == "cannon_fodder_low")
{
  List<Vector> _pathPoints =  new List<Vector> ();
  _pathPoints.Add(new Vector(1400, 0, 0));
  _pathPoints.Add(new Vector(0, -250, 0));
  _pathPoints.Add(new Vector(-1400, 0, 0));


  enemy.Path =  new Path(_pathPoints, 10);
}
else if (definition.EnemyType == "cannon_fodder_straight")
{
  List<Vector> _pathPoints =  new List<Vector> ();
  _pathPoints.Add(new Vector(1400, 0, 0));
  _pathPoints.Add(new Vector(-1400, 0, 0));


  enemy.Path =  new Path(_pathPoints, 14);
}
else if (definition.EnemyType == "up_l")
{
  List<Vector> _pathPoints =  new List<Vector> ();
  _pathPoints.Add(new Vector(500, -375, 0));
  _pathPoints.Add(new Vector(500, 0, 0));
  _pathPoints.Add(new Vector(500, 0, 0));
  _pathPoints.Add(new Vector(-1400, 200, 0));


  enemy.Path =  new Path(_pathPoints, 10);
}
else if (definition.EnemyType == "down_l")
{
  List<Vector> _pathPoints =  new List<Vector> ();
  _pathPoints.Add(new Vector(500, 375, 0));
  _pathPoints.Add(new Vector(500, 0, 0));
  _pathPoints.Add(new Vector(500, 0, 0));
  _pathPoints.Add(new Vector(-1400, -200, 0));
  enemy.Path =  new Path(_pathPoints, 10);
}
else
{
  System.Diagnostics.Debug.Assert(false, "Unknown enemy type.");
   }


  return enemy;
}

Each of these enemies has an interesting path and can be put together to form a more interesting level. Here is some new Level set up code for the EnemyManager constructor.

public EnemyManager(TextureManager textureManager, EffectsManager
effectsManager, int leftBound)
{
  _textureManager = textureManager;
  _effectsManager = effectsManager;
  _leftBound =  leftBound;

  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 30));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 29.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 29));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 28.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 30));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 29.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 29));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 28.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 25));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 24.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 24));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder", 23.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 20));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 19.5));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 19));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_low", 18.5));


  _upComingEnemies.Add(new EnemyDef("cannon_fodder_straight", 16));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_straight", 15.8));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_straight", 15.6));
  _upComingEnemies.Add(new EnemyDef("cannon_fodder_straight", 15.4));
  _upComingEnemies.Add(new EnemyDef("up_l", 10));
  _upComingEnemies.Add(new EnemyDef("down_l", 9));
   _upComingEnemies.Add(new EnemyDef("up_l", 8));
  _upComingEnemies.Add(new EnemyDef("down_l", 7));
  _upComingEnemies.Add(new EnemyDef("up_l", 6));
  // Sort enemies so the greater launch time appears first.
  _upComingEnemies.Sort(delegate(EnemyDef firstEnemy, EnemyDef
secondEnemy)
  {
    return firstEnemy.LaunchTime.CompareTo(secondEnemy.LaunchTime);


  });


}

Try out the level; you may find it a little challenging so this is a great time to play around with the code and balance the game a little. Try increasing the player firing rate or the spaceship speed.

Enemy Attacks

Enemies move in interesting ways across the level, but they’re still quite passive. The player can blast away at them and they do nothing! The enemies should have some kind of recourse, so in this section we’ll look at turning the tables a little.

There’s already a BulletManager, and this currently handles only the player bullets. The enemy bullets will only affect the player, and the player bullets will only affect the enemies. For this reason, it’s easiest to have separate lists of bullets. This means a few of the functions need to be generalized to accept a list of bullets.

public class BulletManager
{
  List<Bullet> _bullets =  new List<Bullet>();
  List<Bullet> _enemyBullets =  new List<Bullet>();

// Code omitted


  public void Update(double elapsedTime)
  {
    UpdateBulletList(_bullets, elapsedTime);
    UpdateBulletList(_enemyBullets, elapsedTime);
  }
  public void UpdateBulletList(List<Bullet> bulletList, double
elapsedTime)
  {
    bulletList.ForEach(x =>  x.Update(elapsedTime));
    CheckOutOfBounds(_bullets);
    RemoveDeadBullets(bulletList);
  }

  private void CheckOutOfBounds(List<Bullet> bulletList)
  {
    foreach (Bullet bullet in bulletList)
    {
      if (!bullet.GetBoundingBox().IntersectsWith(_bounds))
      {
       bullet.Dead =  true;
      }
    }
  }

  private void RemoveDeadBullets(List<Bullet> bulletList)
  {
    for (int i =  bulletList.Count - 1; i> = 0; i-)
    {
      if (bulletList[i].Dead)
      {
       bulletList.RemoveAt(i);
      }
    }
  }


  internal void Render(Renderer renderer)
  {
    _bullets.ForEach(x => x.Render(renderer));
    _enemyBullets.ForEach(x =>  x.Render(renderer));
  }

The above code introduces a second list for enemy bullets; it now requires a function that will let the enemy shoot bullets and a function to check if any of them hit the player.

public void EnemyShoot(Bullet bullet)
{
  _enemyBullets.Add(bullet);
}


public void UpdatePlayerCollision(PlayerCharacter playerCharacter)
{
  foreach (Bullet bullet in _enemyBullets)
  {
    if(bullet.GetBoundingBox().IntersectsWith(playerCharacter.
GetBoundingBox()))
    {
     bullet.Dead =  true;
     playerCharacter.OnCollision(bullet);
    }
  }
}

The UpdatePlayerCollision is quite similar to the existing UpdateEnemyCollision method and eventually they should be combined, but for this iteration of the game development, it’s easier if they stay separate. The PlayerCharacter class needs a new OnCollision method that takes in a bullet object.

internal void OnCollision(Bullet bullet)
{
  _dead =  true;
}

The PlayerCharacter now has two collision methods: one for bullets and one for enemies. The PlayerCharacter dies if he touches an enemy or a bullet so these methods are redundant. The reason they have been written this way is to make extending the game easier. It’s important to know what the player is colliding with. If the player is given a health value, then colliding with an enemy may cause more damage than a bullet. If missiles, mines, or various types of power-up are added, they too can have an extra collision method to deal with that case.

Shooter is a very strict game. If the player hits an enemy, he immediately loses. The same is true if he hits a bullet. The BulletManager now needs an extra call in the Update loop of the Level class to test if an enemy bullet has hit the player.

private void UpdateCollisions()
{
  _bulletManager.UpdatePlayerCollision(_playerCharacter);

For the enemies to use their newfound shooting powers, they need access to the BulletManager class. This can be passed into the EnemyManager and into each individual enemy from there. Here it’s passed into the EnemyManager from the Level class constructor.

public Level(Input input, TextureManager textureManager, Persistant
GameData gameData)
{
  _input =  input;
  _gameData =  gameData;
  _textureManager =  textureManager;
  _effectsManager =  new EffectsManager(_textureManager);
  _enemyManager =  new EnemyManager(_textureManager, _effectsManager,
_bulletManager, -1300);

In the following code, the EnemyManager stores a reference to the Bullet-Manager and uses it when constructing enemies.

BulletManager _bulletManager;
public EnemyManager(TextureManager textureManager, EffectsManager
effectsManager, BulletManager bulletManger, int leftBound)
{
  _bulletManager =  bulletManger;

// Code omitted


private Enemy CreateEnemyFromDef(EnemyDef definition)
{
  Enemy enemy =  new Enemy(_textureManager, _effectsManager,
_bulletManager);

The enemies now have the BulletManager and with it the power to start shooting bullets. The question now is when should the enemies shoot? They can’t shoot every frame or the game would be far too hard. The enemies shouldn’t all fire at the same time or it will be far too difficult. The trick is to set the firing times randomly for each enemy.

public double MaxTimeToShoot { get; set; }
public double MinTimeToShoot { get; set; }
Random _random =  new Random();
double _shootCountDown;


public void RestartShootCountDown()
{
  _shootCountDown =  MinTimeToShoot +  (_random.NextDouble() *
MaxTimeToShoot);
}


BulletManager _bulletManager;
Texture _bulletTexture;
public Enemy(TextureManager textureManager, EffectsManager
effectsManager, BulletManager bulletManager)
{
  _bulletManager =  bulletManager;
  _bulletTexture =  textureManager.Get("bullet");
  MaxTimeToShoot =  12;
  MinTimeToShoot =  1;
  RestartShootCountDown();


// Code omitted
public void Update(double elapsedTime)
{
  _shootCountDown =  _shootCountDown - elapsedTime;
  if (_shootCountDown<= 0)
  {
    Bullet bullet =  new Bullet(_bulletTexture);
    bullet.Speed =  350;
    bullet.Direction =  new Vector(-1, 0, 0);
    bullet.SetPosition(_sprite.GetPosition());
    bullet.SetColor(new Engine.Color(1, 0, 0, 1));
    _bulletManager.EnemyShoot(bullet);
    RestartShootCountDown();
  }

When the enemy is created, it sets a timer for the next time it will shoot. The timer is set using C#’s Random class and a minimum and maximum time. The timer will be set somewhere in between these minimum and maximum values. All ships will shoot at different times. The RestartShootCountDown method sets the random time when the enemy will shoot. Math.NextDouble returns a random number from 0 to 1, which is scaled between the MinTimeToShoot and MaxTimeToShoot member variables.

The Update loop ticks down the _shootCountDown, and once it is equal to or below 0 the enemy fires a bullet. The bullet is made to be slower than the player bullets and it’s shot in the opposite direction. The enemy bullets are also colored red so it’s obvious they’re different from the players. Once the enemy shoots, the _shootCountDown timer is reset.

The enemies shoot towards the left of the screen. You may want to make it a little harder and have the enemies aim at the player. To do this, the enemies must have a reference to the PlayerCharacter. Then it’s a simple matter of working out the direction of the player in reference to the enemy ship. If you decide to add aiming to the enemies, here’s a little snippet of code that might help.

Vector currentPosition =  _sprite.GetPosition();
Vector bulletDir =   _playerCharacter.GetPosition() - currentPosition;
bulletDir =  bulletDir.Normalize(bulletDir);
bullet.Direction =  bulletDir;

This concludes this second refinement of the game. The enemies can fire on the player and move about in interesting ways. The enemies can be destroyed and will explode in a satisfying ball of flame.

Continuing Iterations

After two basic iterations of development, we have a wonderful but basic side-scrolling shooter. There is massive scope for developing this project into something totally individual. The project is yours now and you can develop it as you want. If you feel a little lost, here are some suggestions.

  • A very simple first step is to introduce a new enemy type; just add an extra else if and perhaps modify a path or the health. Once you’ve done that, consider making a new enemy texture and changing the new enemy to use this texture. This will suddenly make the game a lot more interesting.

  • A score is important in scrolling shooters. The score can be displayed using the Text class. The score should increase every time the player destroys an enemy.

  • Sound is also very simple to add. A sound manager needs to be created as it was earlier in the book, and a number of suitable sounds could be generated for shooting, exploding, and taking damage. Then you just need to find the places where explode, damage, and shoot events occur and make a call to the sound manager to play the correct sound. The main bulk of the work is passing the sound manager through all the objects so that it can be used where it’s needed.

  • Regarding the code, there is a large number of managers and a few scattered functions with similar code. The code could be made tighter and easier to extend if these managers were generalized and any repeated code was removed. A good starting point is to see what similar methods each of the managers use and then consider extending the Entity class so one general EntityManager could be created.

  • The game’s single level is defined in the EnemyManager constructor. This isn’t very extendable. A good starting project might be to define the level definitions in a text file. On load-up the program can read the text and load the level definition. The level definition could be very simple, such as

    cannon_fodder, 30
    cannon_fodder, 29.5
    cannon_fodder, 29
    cannon_fodder, 28.5
    
  • Each line has an enemy type and a launch time separated by a comma. This is very easy to parse and read into a Level definition class. The level data should probably be stored in a PersistantGameData class.

  • Once you have one level loaded, it’s easy to make a new level file, and then suddenly you have the potential for a multi-level game. When one level is successfully finished, instead of returning the StartGameState, the state could return to the InnerGameState but using the next level. If the game has multiple levels, then it would good if the game was able to save the player’s progress through these levels. A very simple way to save the game data would be to write out the score and current level to a text file.

  • Instead of having a linear level progression (1,2,3,4), the user could be presented with an overworld map to select which levels he’d like to complete next. An overworld map generally indicates all the levels as nodes linked by paths. Such a system has been used in some of the Super Mario games. An overworld map makes it very easy to introduce secret paths and levels that are discovered by doing particularly well in the previous level.

  • If the player is hit by an enemy spaceship or bullet, the PlayerCharacter dies and it’s game over. It would be preferable to give the player some margin for error, perhaps by giving the spaceship some health—allowing it to take damage like the enemies. The health could be represented by a health bar on screen that goes down each time the ship takes a hit. You may also want to introduce the concept of lives—the player starts with several lives, each allowing one more go at the level before the game is lost.

  • As levels progress, they tend to get more difficult. To help the player, you could give him better weapons and items to repair any damage to his ship. In scrolling shooting games, power-ups and other items tend to be dropped by enemies. A new item class needs to be created, and it can be added to the scene (possibly via the EffectsManager) to be picked up by the player. A health pack can repair some amount of the damage the player has received. New weapons can deal more damage or perhaps there can be two bullets every time the player shoots instead of one.

  • You could also add alternate weapons that are triggered by different buttons. Bombs or lasers, for instance, could be secondary weapons that have a limited number of shots.

  • RPG elements are a very popular way to add a greater degree of depth. Enemies could drop money (or scrap that could later be sold for money). After each level the player could buy new weapons, upgrade existing ones, or even buy a new type of ship. You may even want to allow players to place weapons at different locations on the ship by altering the PlayerCharacter’s _gunOffset member.

  • The RPG elements could be taken even further by adding a layer of narrative to the game. This could be done during the level with text boxes and scripted movements of enemies and the player. Story elements could also be added to an overworld map or after each level.

  • Large boss enemies at the end of the level are also a staple of the side-scrolling shooter. You could make the boss enemy an aggregate of several different types of enemies. Then parts of the boss can be destroyed, but the PlayerCharacter only wins when all the boss parts are destroyed.

  • The scrolling space backgrounds are quite dull and could be made more lively with animated space debris, far away supernova, and planets. The scrolling background can be altered at any time, so with a little work it would be easy to give the impression the spaceship was traveling toward the surface of a planet.

  • As a final suggestion, you could add a local multiplayer mode. This is pretty easy to do. A second game controller or the keyboard would need its input to be redirected to a second PlayerCharacter. Some logic would also need to be changed so that if one player died the other could keep on playing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.49.108