TowerOfArcher

by George Wright: Y10 Age ~14

Introduction

Click here to play TowerOfArcher. If the program does not work in your current browser, try Chrome.

George quickly became adept at using images in Smart Pascal and he developed this game remarkably quickly; he has shown the qualities of a fine game-making competitor. As usual, any feedback is welcome.

Gameplay

When playing TowerOfArcher, you will see instructions on the start-up screen. You might find the following points helpful.
  • See procedure TEnemy.Hit to see how the damage to an enemy depends on its speed.
  • You start with no gold and need to kill an enemy to obtain some. You might need to hit it several times to kill it.
  • In order to shoot a long way, you need to "drag the bowstring back" from the right of the screen. (You do not need to start from the bow).
  • You might find that when you right-click to cancel a shot a menu appears. Clicking the middle button of a mouse might be a better alternative.

With practice you will become a more skilful archer and your games will last longer.

Technical Features

The program benefits from:

  • object-oriented code throughout;
  • use of inheritance e.g. TAirUnit and TGroundUnit are descendants of TEnemy;
  • handling of touch events;
  • well-planned code separated into units;
  • thorough comments;
  • use of C-style operators such as +=;
  • use of inbuilt routines such as High, RandomInt, Round, Ceil, Trunc, Sqr, Sqrt, Sin, Cos, ArcTan2, FillRectF, Ellipse, MoveToF, LineToF, FillTextF, Translate, Rotate, ContainsPoint and BeginPath;
  • use of the inbuilt types TRect and TW3EventRepeater.

George acknowledges that his game was inspired by Bow Master.

Code of Main Unit

unit UTowerArcher;
{
    Copyright (c) 2015 George Wright

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}

interface

uses
  W3GameApp, W3Graphics, W3Image,
  UDrawing, UMouseInputs, UPlayer, USpawner, UGameVariables, UGameItems, UPlayerData, UShop, UTextures, UScalingInfo;

type
  TApplication = class(TW3CustomGameApplication)
  protected
    procedure ApplicationStarting; override;
    procedure ApplicationClosing; override;
    procedure PaintView(Canvas: TW3Canvas); override;
  end;

procedure InitializeVariables();
procedure UpdateArrows();
procedure UpdateEnemies();
procedure EvaluateLoadedContent();

var
  ContentLoaded : boolean;
  Loaded : array of boolean;
  FirstLoop : boolean;

implementation

procedure TApplication.ApplicationStarting;
begin
  inherited;
  // Initialize textures
  ArrowTexture := TW3Image.Create(nil);
  ArrowTexture.LoadFromURL("res\Arrow.png");
  BowTexture := TW3Image.Create(nil);
  BowTexture.LoadFromURL("res\Bow.png");
  ArcherTexture := TW3Image.Create(nil);
  ArcherTexture.LoadFromURL("res\Archer.png");
  GroundUnitTexture := TW3Image.Create(nil);
  GroundUnitTexture.LoadFromURL("res\GroundEnemy.png");
  FrozenGroundUnitTexture := TW3Image.Create(nil);
  FrozenGroundUnitTexture.LoadFromURL("res\GroundEnemyFrozen.png");
  AirUnitTexture := TW3Image.Create(nil);
  AirUnitTexture.LoadFromURL("res\AirEnemy.png");
  FrozenAirUnitTexture := TW3Image.Create(nil);
  FrozenAirUnitTexture.LoadFromURL("res\AirEnemyFrozen.png");
  TowerTexture := TW3Image.Create(nil);
  TowerTexture.LoadFromURL("res\Tower.png");

  // Tell the program the content has not loaded
  ContentLoaded := false;
  Loaded := [ false, false, false, false, false, false, false, false ];

  // Add event handlers so loaded content is registered
  ArrowTexture.OnLoad := procedure(o : TObject) begin Loaded[0] := true; end;
  BowTexture.OnLoad := procedure(o : TObject) begin Loaded[1] := true; end;
  ArcherTexture.OnLoad := procedure(o : TObject) begin Loaded[2] := true; end;
  GroundUnitTexture.OnLoad := procedure(o : TObject) begin Loaded[3] := true; end;
  FrozenGroundUnitTexture.OnLoad := procedure(o : TObject) begin Loaded[4] := true; end;
  AirUnitTexture.OnLoad := procedure(o : TObject) begin Loaded[5] := true; end;
  FrozenAirUnitTexture.OnLoad := procedure(o : TObject) begin Loaded[6] := true; end;
  TowerTexture.OnLoad := procedure(o : TObject) begin Loaded[7] := true; end;

  // Initialize the shop
  Shop := TShop.Create();

  // Add the mouse and touch input handlers
  GameView.OnMouseDown := MouseDownHandler;
  GameView.OnMouseUp := MouseUpHandler;
  GameView.OnMouseMove := MouseMoveHandler;
  GameView.OnTouchBegin := TouchDownHandler;
  GameView.OnTouchEnd := TouchUpHandler;
  GameView.OnTouchMove := TouchMoveHandler;

  // Initialize refresh interval
  GameView.Delay := 20;

  // Start the redraw-cycle with frame counter disabled
  GameView.StartSession(False);
end;

procedure TApplication.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;

procedure TApplication.PaintView(Canvas: TW3Canvas);
begin
  // Scale the canvas
  ScaleCanvas(GameView.Width, GameView.Height);
  Canvas.Scale(Scale, Scale);

  // Canvas and clear the screen
  ClearScreen(Canvas);

  if ContentLoaded then
    begin
      // Draw player and scenery
      DrawScenery(Canvas);
      DrawPlayer(Player, Canvas);

      // Draw the arrows and enemies
      DrawArrow(Arrows, Canvas);
      DrawEnemy(Enemies, Canvas);

      // Update the arrows and enemies if not in the shop
      if not Paused then
        begin
          UpdateArrows();
          UpdateEnemies();

          // Draw the mouse to origin line if preparing to fire, not paused and not dead
          if Lives > 0 then
            begin
              DrawMouseDragLine(Player, Canvas);
            end;
            
          // Draw a circle over the mouse showing if the player can shoot
          DrawCanShoot(Player, Canvas);
        end
      else
        begin
          // Draw shop/pause screen if it is presently open
          DrawPauseScreen(Canvas);
        end;

      // Draw the information for the player
      DrawHUD(Canvas);

      // Draw the game over screen if dead
      if Lives <= 0 then
        begin
          DrawGameOver(Canvas);
        end;
    end
  else
    begin
      EvaluateLoadedContent();

      DrawLoadingScreen(Canvas);
    end;

  // If the game is over and the restart button has been clicked then restart the game
  if (Lives <= 0) and (RestartClicked) then
    begin
      PauseEnemySpawners();
      InitializeVariables();
    end;

  // Scale the canvas back to normal
  Canvas.Scale(1 / Scale, 1 / Scale);
end;

procedure InitializeVariables();
begin
  // Initialize the variables
  MaxPower := 30;
  ArrowDamage := 10;
  ArrowsFreeze := false;
  ArrowFreezeDuration := 0;
  TimeBetweenShots := 2000;
  PauseButtonCoordinates := [ 10, 10, 110, 50 ];
  RestartButtonCoordinates := [ Round(GAMEWIDTH / 2 - 50), 200, Round(GAMEWIDTH / 2 + 50), 250 ];
  RestartClicked := false;
  Paused := true;
  Lives := 10;
  Money := 0;

  // Initialize the spawner variables
  GroundDelay := 9000;
  AirDelay := 15000;
  Difficulty := 1000;

  // Reset game items and the shop
  Arrows.Clear();
  Enemies.Clear();
  Shop.ResetItems();

  // Spawn a ground unit
  SpawnGroundUnit();

  // Initialize the player
  Player := TPlayer.Create(TowerTexture.Handle.width - 15 - ArcherTexture.Handle.width, GAMEHEIGHT - TowerTexture.Handle.height - ArcherTexture.Handle.height);
end;

procedure UpdateArrows();
begin
  for var i := 0 to High(Arrows) do
    begin
      if (Arrows[i].Active) then
        begin
          // Get the current x and y positions for the collision engine
          var prevX := Arrows[i].X;
          var prevY := Arrows[i].Y;

          // Move the arrow
          Arrows[i].Move();

          // Check the collisions
          Arrows[i].CheckCollisions(Enemies, prevX, prevY);
        end;
    end;
end;

procedure UpdateEnemies();
begin
  for var i := 0 to High(Enemies) do
    begin
      if not Enemies[i].Dead then
        begin
          // Only move the enemy if it's not frozen
          if not Enemies[i].Frozen then
            begin
              Enemies[i].Move();
            end;
        end;
    end;
end;

procedure EvaluateLoadedContent();
begin
  // Evaluate if everything is loaded
  for var i := 0 to High(Loaded) do
    begin
      // Break the procedure if the content is not loaded
      if not Loaded[i] then
        begin
          exit;
        end;
    end;

  // If the procedure has not ended the content is loaded so check the shop
  if Shop.Loaded then
    begin
      // State the content has all loaded
      ContentLoaded := true;

      // Initialize variables now content has been loaded
      InitializeVariables();
    end;
end;

end.


See the code of the other 17 units (arranged in alphabetical order) on the following two pages: UAirUnit, UArcher, UArrow, UDrawing,UEnemy, UGameItems, UGameVariables, UGroundUnit, UMouseInputs, UPlayer, UPlayerData, UScalingInfo, UShop, UShopItem, UShopData, USpawner and UTextures.

If you would like to develop the game further, you can download a zip file containing the project file, units, images and README.txt.

Programming - a skill for life!

Five programs including RandomPlatformScroller and TowerOfArcher by George Wright