Game Object Unit

unit GameObjectUnit;

interface

uses
  SysUtils,
  // JEDI-SDL
  sdl,
  sdlutils,

  AdvancedMathUnit,
  GraphicsUnit,
  GameConstantsUnit;

type
  TTank = class(TObject)
    X, Y: Real;
    TrackSize: Cardinal;
    TurretX, TurretY,
    TurretLength: Integer;
    TurretEndX, TurretEndY: Real; // New!
    AimAngle, AimPower: Integer;
    Facing: Integer; // New!
    Color: Cardinal;
    Sprite: PSDL_Surface;
    constructor Init(oTrackSize: Cardinal; oTurretX, oTurretY, oTurretLength: Integer; oColor: Cardinal; ImageFile: String);
    procedure ChangeTurret(NewAngle: Integer); // New!
    procedure Draw(GameScreen: PSDL_Surface);
  end;
  TBattlefield = class(TObject)
    Width, Height: Integer;
    LandHeight: Array[0 .. 1024] of Integer;
    LandColor, SkyColor: Cardinal;

    isBGImage: Boolean;
    Background: PSDL_Surface; // Background Graphic

    constructor Init(ScreenWidth, ScreenHeight: Integer; Land, Sky: Cardinal; useBGImage: Boolean; BGImageFile: String);
    procedure GenerateLand(Highest, Lowest, Variation: Integer);
    procedure SmoothenLand(SmoothSize: Integer); // Tries to smoothen rough generated land!
    procedure PlaceTank(x, gap: Integer; var Tank: TTank);
    procedure ScatterTanks(var Tanks: Array of TTank; NumberOfTanks, MinTankDist, DirtGap: Integer);
    procedure DrawSky(GameScreen: PSDL_Surface);
    procedure DrawLand(GameScreen: PSDL_Surface);
  end;
  TShot = class(TObject)
    StartX, StartY: Real;
    OldX, OldY: Real;
    X, Y, VelX, VelY: Real;
    Power, Angle: Real;
    Damage: Integer;
    Remove: Boolean;
    constructor Init(oX, oY, oPower, oAngle: Real; oDamage: Integer); overload;
    constructor Init(Tank: TTank; oDamage: Integer); overload;
    procedure Update(Level: TBattlefield);
    procedure Draw(GameScreen: PSDL_Surface);
  end;


implementation


// TTank
constructor TTank.Init(oTrackSize: Cardinal; oTurretX, oTurretY, oTurretLength: Integer; oColor: Cardinal; ImageFile: String);
begin
    X := 0;
    Y := 0;
    TrackSize := oTrackSize;

    TurretX := oTurretX;
    TurretY := oTurretY;
    TurretLength := oTurretLength;

    ChangeTurret(45); // Default Value
    AimPower := 500; // Default Value

    Color := oColor;

    Facing := 1;

    // Load Sprite File
    Sprite := LoadImage('images/' + ImageFile, True);
end;
procedure TTank.ChangeTurret(NewAngle: Integer);
begin
     AimAngle := NewAngle;

     // Check for Aim Wrap-around
     if (AimAngle > 180) then
        AimAngle := 0;
     if (AimAngle < 0) then
        AimAngle := 180;

     // Check Tank Direction
     if (AimAngle < 90) then
        Facing := 1;
     if (AimAngle > 90) then
        Facing := -1;

     // Calculate Turret End
     TurretEndX := RotateXDeg(TurretLength, 0, AimAngle);
     TurretEndY := RotateYDeg(TurretLength, 0, AimAngle);
end;
procedure TTank.Draw(GameScreen: PSDL_Surface);
var
  SrcRect: TSDL_Rect;
  DestRect: TSDL_Rect;

  TempFrame: PSDL_Surface;
begin
     // Create Temp Frame for Animation
     TempFrame := SDL_AllocSurface(SDL_SWSURFACE, Sprite.w, Sprite.h, GameScreen.format.BitsPerPixel, 0, 0, 0, 0);
     SrcRect := SDLRect(0, 0, Sprite.w, Sprite.h);
     DestRect := SDLRect(0, 0, Sprite.w, Sprite.h);
     SDL_BlitSurface(Sprite, @SrcRect, TempFrame, @DestRect);

     // Flip Tank Body around to match tank's facing!
     if (Facing < 0) then
        SDL_FlipRectH(TempFrame, @DestRect);
     SDL_SetColorKey(TempFrame, (SDL_SRCCOLORKEY or SDL_RLEACCEL), PUInt32(TempFrame.pixels)^);

     // Draw Turret
     SDL_DrawLine(GameScreen, Round(X + (TurretX * Facing)), Round(GameScreen.h - 1 - Y + TurretY),
                  Round(X + (TurretX * Facing) + TurretEndX), Round(GameScreen.h - 1 - Y + TurretY - TurretEndY), Color);

     // Draw Body
     DestRect := SDLRect(Round(X - Sprite.w / 2), Round(GameScreen.h - 1 - Y - Sprite.h), Sprite.w, Sprite.h);
     SDL_BlitSurface(TempFrame, @SrcRect, GameScreen, @DestRect);

     // Free Temp Frame
     SDL_FreeSurface(TempFrame);
end;
// End of TTank


// TBattlefield
constructor TBattlefield.Init(ScreenWidth, ScreenHeight: Integer; Land, Sky: Cardinal; useBGImage: Boolean; BGImageFile: String);
begin
     Width := ScreenWidth;
     Height := ScreenHeight;

     LandColor  := Land;
     SkyColor   := Sky;
     isBGImage := useBGImage;

     // Load Background
     if (useBGImage) then
        Background := LoadImage('images/' + BGImageFile, False);
end;
procedure TBattlefield.GenerateLand(Highest, Lowest, Variation: Integer);
var i: Integer;
    rand: Real;
begin
     LandHeight[0] := Lowest + Round(Random * (Highest - Lowest));
     for i := 1 to Width - 1 do
     begin
          repeat
            rand := Random;
            LandHeight[i] := Round(LandHeight[i - 1] + (rand * Variation) - Variation / 2);
            if (LandHeight[i] < Lowest) then
               LandHeight[i] := Lowest;
          until (LandHeight[i] <= Highest);
     end;
end;
procedure TBattlefield.SmoothenLand(SmoothSize: Integer);
var i, j: Integer;
    Mass, NumOfMassSamples: Integer;
begin
     for i := 0 to Width - 1 do
     begin
          // Get average height of selected area...
          Mass := 0;
          NumOfMassSamples := 0;
          for j := i - SmoothSize to i + SmoothSize do
              if (j > 0) and (j < Width - 1) then // Samples must be in bounds!
              begin
                   inc(NumOfMassSamples);
                   Mass := Mass + LandHeight[j];
              end;

          // Resize LandHeight element
          LandHeight[i] := Round(Mass / NumOfMassSamples);
     end;
end;
procedure TBattlefield.PlaceTank(x, gap: Integer; var Tank: TTank);
var
  i: Integer;
  Lowest: Cardinal;
begin
     // Snap Tank back on-screen
     if (x - Tank.TrackSize < 0) then
        x := Tank.TrackSize;
     if (x + Tank.TrackSize > Width - 1) then
        x := Width - 1 - Tank.TrackSize;

     // Find lowest part of the land
     Lowest := LandHeight[x];
     for i := x - Tank.TrackSize to x + Tank.TrackSize do
         if (LandHeight[i] < Lowest) then
            Lowest := LandHeight[i];

     // Level the ground around the tank
     for i := x - Tank.TrackSize - gap to x + Tank.TrackSize + gap do
         if (i >= 0) or (i <= Width - 1) then
            LandHeight[i] := Lowest;

     // Relocate Tank
     Tank.X := x;
     Tank.Y := Lowest;
end;
procedure TBattlefield.ScatterTanks(var Tanks: Array of TTank; NumberOfTanks, MinTankDist, DirtGap: Integer);
var
  i, j: Integer;
  x: Integer;
  SpaceClear: Boolean;
begin
     for i := 0 to NumberOfTanks - 1 do
     begin
          repeat
            SpaceClear := True;

            // Generate random location
            x := Random(Width);
            
            // Check if location is off screen
            if (x - Tanks[i].TrackSize < 0) or
               (x + Tanks[i].TrackSize > Width - 1) then
               SpaceClear := False;

            // Check with other tanks
            for j := 0 to NumberOfTanks - 1 do
                if (i <> j) then
                begin
                     // Location taken by other tank
                     if ((x + Tanks[i].TrackSize >= Tanks[j].X - Tanks[j].TrackSize - MinTankDist) and
                         (x + Tanks[i].TrackSize <= Tanks[j].X + Tanks[j].TrackSize + MinTankDist)) or
                        ((x - Tanks[i].TrackSize >= Tanks[j].X - Tanks[j].TrackSize - MinTankDist) and
                         (x - Tanks[i].TrackSize <= Tanks[j].X + Tanks[j].TrackSize + MinTankDist)) then
                        SpaceClear := False;
                end;
          until (SpaceClear);
          // Place Tank
          PlaceTank(x, DirtGap, Tanks[i]);
     end;
end;
procedure TBattlefield.DrawLand(GameScreen: PSDL_Surface);
var
  i: Integer;
begin
     // Land
     for i := 0 to Width - 1 do
         SDL_DrawLine(GameScreen, i, Height - 1, i, Height - 1 - LandHeight[i], LandColor);
end;
procedure TBattlefield.DrawSky(GameScreen: PSDL_Surface);
begin
     if (isBGImage) then
         DrawBackgound(GameScreen, Background)
     else
         SDL_FillRect(GameScreen, PSDLRect(0, 0, 800, 600), SkyColor);
end;
// End of TBattlefield


// TShot
constructor TShot.Init(oX, oY, oPower, oAngle: Real; oDamage: Integer); overload;
begin
     StartX := oX;
     StartY := oY;

     X := oX;
     Y := oY;

     Power := oPower;
     Angle := oAngle;

     VelX := Power * getCOS[Round(Angle)] * ShotPrecision;
     VelY := Power * getSIN[Round(Angle)] * ShotPrecision;

     Damage := oDamage;

     Remove := False;
end;
constructor TShot.Init(Tank: TTank; oDamage: Integer); overload;
begin
     StartX := Tank.X + (Tank.TurretX * Tank.Facing) + Tank.TurretEndX;
     StartY := Tank.Y - Tank.TurretY + Tank.TurretEndY;

     X := StartX;
     Y := StartY;

     Power := Tank.AimPower / 10;
     Angle := Tank.AimAngle;

     VelX := Power * getCOS[Round(Angle)] * ShotPrecision;
     VelY := Power * getSIN[Round(Angle)] * ShotPrecision;

     Damage := oDamage;

     Remove := False;
end;
procedure TShot.Update(Level: TBattlefield);
begin
     OldX := X;
     OldY := Y;

     X := X + VelX;
     Y := Y + VelY;

     VelY := VelY - Gravity;

     // Level Boundaries
     if (X < 0) or (X > Level.Width - 1) or
        (Y <= Level.LandHeight[Round(X)]) then
        Remove := True;
end;
procedure TShot.Draw(GameScreen: PSDL_Surface);
begin
     if (Round(X) >= 0) and (Round(X) < GameScreen.w) and (Round(GameScreen.h - Y) >= 0) and (Round(GameScreen.h - Y) < GameScreen.h) then
        SDL_PutPixel(GameScreen, Round(X), Round(GameScreen.h - Y), $ffffff);
end;
// End of TShot

end.
Programming - a skill for life!

Part 4 of republished guest tutorial by Jason McMillen