Using Inheritance in Smart Mobile Studio

On this page we demonstrate how you can start with a base class (TGameCharacter) containing the fields and methods required from all characters in a game and then inherit these fields and methods in the derived classes TPlayer and TMob.

The code follows the application in action. You gain a point for each frame in which the player intersects with the mobile. If the application does not work in your current browser, please use another such as Chrome. If you see no display at school, the security system might have blocked it. You can try instead this direct link to the program running on its own page.

InheritanceDemo
unit Unit1;

interface

uses 
  System.Types, SmartCL.System, SmartCL.Components, SmartCL.Application,
  SmartCL.Game, SmartCL.GameApp, SmartCL.Graphics;

type
  TGameCharacter = class
  protected
    Rect: TRect;
    Colour: string;
  public
    procedure Draw(Canv: TW3Canvas);
  end;

  TMob = class(TGameCharacter)
  private
    DeltaX, DeltaY: integer; // Changes in X and Y each move
  public
    constructor Create(StartRect: TRect; StartColour: string; dX, dY: integer);
    procedure Move;
    procedure Negate(Horizontal: Boolean);
  end;

  TPlayer = class(TGameCharacter)
  private
    DestinationX, DestinationY: integer;
  public
    constructor Create(PlrStartRect: TRect; PlrStartColour: string);
    procedure Move;
  end;

type
  TCanvasProject = class(TW3CustomGameApplication)
  private
    Mob: TMob;
    Player: TPlayer;
    IntersectRect: TRect;
    Score, Seconds: integer;
    StartTime, CurrentTime: TDateTime;
    FractionOfDay: real;
  protected
    procedure ApplicationStarting; override;
    procedure ApplicationClosing; override;
    procedure PaintView(Canvas: TW3Canvas); override;
  end;

implementation

procedure TGameCharacter.Draw(Canv: TW3Canvas);
begin
  Canv.FillStyle := Colour;
  Canv.FillRect(Rect);
end;

constructor TMob.Create(StartRect: TRect; StartColour: string; dX, dY: integer);
begin
  Rect:= StartRect;
  Colour := StartColour;
  DeltaX := dX;
  DeltaY := dY;
end;

procedure TMob.Move;
begin
  Rect.MoveBy(DeltaX, DeltaY);
end;

procedure TMob.Negate(Horizontal: Boolean);
begin
  if Horizontal then
    DeltaX := -DeltaX
  else
    DeltaY := -DeltaY;
end;

constructor TPlayer.Create(PlrStartRect: TRect; PlrStartColour: string);
begin
  Rect := PlrStartRect;
  Colour := PlrStartColour;
end;

procedure TPlayer.Move;
begin
  Rect.MoveBy(DestinationX - Rect.CenterPoint.X, DestinationY - Rect.CenterPoint.Y);
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  Player := new TPlayer(TRect.CreateSized(300, 200, 10, 10), 'yellow');

  GameView.OnMouseMove := procedure(o : TObject; ss : TShiftState; x, y : integer)
    begin
      Player.DestinationX := x;
      Player.DestinationY := y;
    end;

  Mob := new TMob(TRect.CreateSized(20, 1, 15, 10), 'red', 2, 2);
  GameView.Delay := 10;
  StartTime := Now;
  GameView.StartSession(False);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;
 
procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRect(0, 0, GameView.Width, GameView.Height);
  // Draw mobile and player
  Mob.Draw(Canvas);
  Player.Draw(Canvas);
  Canvas.FillStyle := 'green';
  Canvas.FillRect(IntersectRect);
  CurrentTime := Now;
  FractionOfDay := CurrentTime - StartTime;
  Seconds := Round(FractionOfDay * 24 * 60 * 60);
  Canvas.Font := '10pt verdana';
  Canvas.FillStyle := 'rgb(255, 255, 255)';
  Canvas.FillTextF('Score: ' + IntToStr(Score)+ ' in ' +
                   IntToStr(Seconds) + ' seconds: ', 10, 20, MAX_INT);
  // Move mobile and player ready for next frame
  if (Mob.Rect.ContainsCol(0)) or (Mob.Rect.ContainsCol(GameView.Width)) then
    Mob.Negate(true);  // Reverse component of direction along x axis
  if (Mob.Rect.ContainsRow(0)) or (Mob.Rect.ContainsRow(GameView.Height)) then
    Mob.Negate(false); // Reverse component of direction along y axis
  Mob.Move;
  Player.Move;
  if Mob.Rect.Intersect(Player.Rect, IntersectRect) then
    inc(Score);
end;

end.

Override

Here we modify the code above (omitting the scoring system) to illustrate the use of the keywords virtual, override, and abstract.

You can replace a routine declared as virtual in a base class by replacing it with another of the same signature by appending override to the declaration of the routine in the derived class. The keyword inherited (followed by any arguments in parentheses) in the overriding routine represents all of the code of the overridden routine. Procedure Draw, overridden in TPlayer, illustrates the syntax.

If a method is not coded at all in the base class but will be coded differently in derived classes, you can declare it as virtual and abstract in the base class. (This ensures that derived classes will not omit the method). We have included procedure Move in the base class of this demonstration. Note that the derived classes now need to declare Move with the override appendage even though they have no actual method to override!

unit Unit1;

interface

uses
  System.Types, SmartCL.System, SmartCL.Components, SmartCL.Application,
  SmartCL.Game, SmartCL.GameApp, SmartCL.Graphics;

type
  TGameCharacter = class
  protected
    Rect: TRect;
    Colour: string;
  public
    procedure Draw(Canv: TW3Canvas); virtual;
    procedure Move; virtual; abstract;
  end;

  TMob = class(TGameCharacter)
  private
    DeltaX, DeltaY: integer; // Changes in X and Y each move
  public
    constructor Create(StartRect: TRect; StartColour: string; dX, dY: integer);
    procedure Move; override;
    procedure Negate(Horizontal: Boolean);
  end;

  TPlayer = class(TGameCharacter)
  private
    DestinationX, DestinationY: integer;
    constructor Create(PlrStartRect: TRect; PlrStartColour: string);
    procedure Move; override;
    procedure Draw(Canv: TW3Canvas); override;
  end;

type
  TCanvasProject = class(TW3CustomGameApplication)
  private
    Mob: TMob;
    Player: TPlayer;
  protected
    procedure ApplicationStarting; override;
    procedure ApplicationClosing; override;
    procedure PaintView(Canvas: TW3Canvas); override;
  end;

implementation

procedure TGameCharacter.Draw(Canv: TW3Canvas);
begin
  Canv.FillStyle := Colour;
  Canv.FillRect(Rect);
end;

constructor TMob.Create(StartRect: TRect; StartColour: string; dX, dY: integer);
begin
  Rect:= StartRect;
  Colour := StartColour;
  DeltaX := dX;
  DeltaY := dY;
end;

procedure TMob.Move;
begin
  Rect.MoveBy(DeltaX, DeltaY);
end;

procedure TMob.Negate(Horizontal: Boolean);
begin
  if Horizontal then
    DeltaX := -DeltaX
  else
    DeltaY := -DeltaY;
end;

constructor TPlayer.Create(PlrStartRect: TRect; PlrStartColour: string);
begin
  Rect := PlrStartRect;
  Colour := PlrStartColour;
end;

procedure TPlayer.Draw(Canv: TW3Canvas);
begin
  inherited(Canv);
  Canv.LineWidth := 3;
  Canv.StrokeStyle := 'green';
  Canv.StrokeRect(Rect);
end;

procedure TPlayer.Move;
begin
  Rect.MoveBy(DestinationX - Rect.CenterPoint.X, DestinationY - Rect.CenterPoint.Y);
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  Player := new TPlayer(TRect.CreateSized(300, 200, 10, 10), 'yellow');

  GameView.OnMouseMove := procedure(o : TObject; ss : TShiftState; x, y : integer)
    begin
      Player.DestinationX := x;
      Player.DestinationY := y;
    end;

  Mob := new TMob(TRect.CreateSized(20, 1, 15, 10), 'red', 2, 2);
  GameView.Delay := 10;
  GameView.StartSession(False);
end;

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

procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRect(0, 0, GameView.Width, GameView.Height);
  //Draw mobile and player
   Mob.Draw(Canvas);
   Player.Draw(Canvas);
  // Move mobile and player ready for next frame
  if (Mob.Rect.ContainsCol(0)) or (Mob.Rect.ContainsCol(GameView.Width)) then
    Mob.Negate(true);  // Reverse component of direction along x axis
  if (Mob.Rect.ContainsRow(0)) or (Mob.Rect.ContainsRow(GameView.Height)) then
    Mob.Negate(false); // Reverse component of direction along y axis
  Mob.Move;
  Player.Move;
end;

end.
Programming - a skill for life!

How to write object-oriented code (including constructors, inheritance and properties) in Smart Pascal