Using Procedures in Smart Pascal

Introduction

A procedure is a named sequence of statements. Some procedures have parameters to enable values to be passed to them to make the procedures more flexible and others (which are easier to code) do not. In order to run the sequence of statements you call the procedure simply by writing its identifier (followed by the names of any parameters in parentheses).

You will have already used procedures set up for you in Smart Pascal. In a canvas project you will have put code into the ApplicationStarting procedure to initialize variables before the PaintView procedure is called for the first time. Using procedures can make your code easier to read. The main code can call a few well-named procedures and then you can drill down as necessary to see the detailed code in each. Josh Blake sets a good example with SpaceInvaders, which you can return to when you have mastered some simpler programs.

Our short procedures set up a rudimentary splash screen which you might show as you load images for your game.

Procedures without Parameters

In the first example, procedure SetUpSplash makes the code slightly easier to read from the top down. We could have put the procedure just above the implementation keyword instead of below it, or made it a procedure of the TCanvasProject class as shown in the second version of the demonstration. We call procedure SetUpSplash simply by using its name as a statement in the ApplicationStarting procedure.

unit Unit1;

interface

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

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

implementation

var
  FrameNumber, EndSplash: integer;
  SplashMessage, SplashFont, SplashFontColour: string;

procedure SetUpSplash;
begin
  EndSplash := 200;
  SplashMessage := 'ProcDemo: Loading images';
  SplashFont := 'bold 40px Times';
  SplashFontColour := 'red';
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  SetUpSplash;
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

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

procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FrameNumber);
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  if FrameNumber <= EndSplash then
    begin
      // Draw splash message near the middle of the screen
      Canvas.Font := SplashFont;
      Canvas.FillStyle := SplashFontColour;
      Canvas.FillTextF(SplashMessage, (GameView.Width - Trunc(Canvas.MeasureText(SplashMessage).Width)) div 2,
                                       GameView.Height div 2, MAX_INT);
    end
  else
    begin
      //Game code
    end;
end;

end.

The next demonstration has similar functionality to the first, but places variables and the procedure within the TCanvasProject class. This is the preferred method because it prevents the procedure from being called in error from other parts of an application as it gains in complexity. It also enables easy access to the name of the project that has been stored internally. The procedure declaration is in the private part of the class in the interface section. In the implementation section, the procedure heading now needs the prefix TCanvasProject. to show that it is part of the class.

We place the procedures in most of our demonstrations entirely within the implementation section, because we think this is somewhat easier for beginners, and leave it as an exercise for the confident student to make the procedures work inside TCanvasProject.

unit Unit1;

interface

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

type
  TCanvasProject = class(TW3CustomGameApplication)
  private
    FrameNumber, EndSplash: integer;
    SplashMessage, SplashFont, SplashFontColour: string;
    procedure SetUpSplash;
  protected
    procedure ApplicationStarting; override;
    procedure ApplicationClosing; override;
    procedure PaintView(Canvas: TW3Canvas); override;
  end;

implementation

procedure TCanvasProject.SetUpSplash;
begin
  EndSplash := 200;
  //Append message to project name
  SplashMessage := name + ': Loading images';
  SplashFont := 'bold 40px Times';
  SplashFontColour := 'red';
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  SetUpSplash;
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;
 
procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FrameNumber);
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  if FrameNumber <= EndSplash then
    begin
      // Draw splash message near the middle of the screen
      Canvas.Font := SplashFont;
      Canvas.FillStyle := SplashFontColour;
      Canvas.FillTextF(SplashMessage, (GameView.Width - Trunc(Canvas.MeasureText(SplashMessage).Width)) div 2,
                                       GameView.Height div 2, MAX_INT);
    end
  else
    begin
      //Game code
    end;
end;

end.

Procedures with One Parameter

We adapt our first example so that it has the duration of the SplashScreen (in frames) as its one integer parameter. In the same way as for an ordinary variable declaration, we must provide its type after a colon. When we call the procedure with SplashScreen(200), the compiler will check that the argument we supply (200) is of the required type.

unit Unit1;

interface

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

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

implementation

var
  FrameNumber, EndSplash: integer;
  SplashMessage, SplashFont, SplashFontColour: string;

procedure SetUpSplash(Duration: integer) ;
begin
  EndSplash := Duration;
  SplashMessage := 'OneParamDemo: Loading images';
  SplashFont := 'bold 40px Times';
  SplashFontColour := 'red';
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  SetUpSplash(200);
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;
 
procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FrameNumber);
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  if FrameNumber <= EndSplash then
    begin
      // Draw splash message near the middle of the screen
      Canvas.Font := SplashFont;
      Canvas.FillStyle := SplashFontColour;
      Canvas.FillTextF(SplashMessage, (GameView.Width - Trunc(Canvas.MeasureText(SplashMessage).Width)) div 2,
                                       GameView.Height div 2, MAX_INT);
    end
  else
    begin
      //Game code
    end;
end;

end.

Procedures with Multiple Parameters

In this example of using several parameters, note the use of the semicolon to separate the different types of parameter. When calling the procedure, you must be careful to supply the parameters in the correct order with a comma as the separator. Code insight is very helpful, giving details of the required parameters when you type the procedure name followed by an opening bracket:

Help from code insight

Help from code insight

unit Unit1;

interface

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

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

implementation

var
  FrameNumber, EndSplash: integer;
  SplashMessage, SplashFont, SplashFontColour: string;

procedure SetUpSplash(Duration: integer; Msg, Font, Colour: String);
begin
  EndSplash := Duration;
  SplashMessage := Msg;
  SplashFont := Font;
  SplashFontColour := Colour;
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  SetUpSplash(200, 'MultipleParamDemo: Loading images', 'bold 40px Times', 'red');
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;
 
procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FrameNumber);
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  if FrameNumber <= EndSplash then
    begin
      // Draw splash message near the middle of the screen
      Canvas.Font := SplashFont;
      Canvas.FillStyle := SplashFontColour;
      Canvas.FillTextF(SplashMessage, (GameView.Width - Trunc(Canvas.MeasureText(SplashMessage).Width)) div 2,
                                       GameView.Height div 2, MAX_INT);
    end
  else
    begin
      //Game code
    end;
end;

end.

Supplying a Variable as an Argument

In these examples we have supplied literal, constant values as arguments to procedures. Usually you will pass variables to procedures. This example supplies the string variable Msg. We demonstrate that we cannot change the value of the supplied global variable from within the procedure. Output of the variable Msg directly to the top left of the screen shows that the code to set it to an empty string has no effect on the original variable. (The local copy of the variable is changed as expected, however, as shown by the ShowMessage output). We have passed the parameter by value. If we want to change the value of a global variable we must pass it by reference instead as shown in the next section.

(We create an object named DummyProj of type TConsoleProject with the sole purpose of accessing the stored application name).
unit Unit1;

interface

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

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

implementation

var
  FrameNumber, EndSplash: integer;
  SplashMessage, SplashFont, SplashFontColour: string;
  DummyProj: TCanvasProject;
  Msg: String;

procedure SetUpSplash(Duration: integer; Msg, Font, Colour: String);
begin
  EndSplash := Duration;
  SplashMessage := Msg;
  SplashFont := Font;
  SplashFontColour := Colour;
  ShowMessage('Msg value: ' + Msg);
  Msg := ''; //No effect on global variable but local copy is changed
  ShowMessage('Msg value: ' + Msg);
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  DummyProj := new   TCanvasProject;
  Msg := dummyProj.Name + ': Loading images';
  SetUpSplash(200, Msg, 'bold 40px Times', 'red');
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  ShowMessage('Message value: ' + Msg);
  GameView.EndSession;
  inherited;
end;
 
procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FrameNumber);
  // Clear background
  Canvas.FillStyle := 'rgb(0, 0, 99)';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  if FrameNumber <= EndSplash then
    begin
      // Draw splash message near the middle of the screen
      Canvas.Font := SplashFont;
      Canvas.FillStyle := SplashFontColour;
      Canvas.FillTextF(SplashMessage, (GameView.Width - Trunc(Canvas.MeasureText(SplashMessage).Width)) div 2,
                                       GameView.Height div 2, MAX_INT);
      Canvas.FillTextF(Msg, 50, 50, MAX_INT);
    end
  else
    begin
      //Game code
    end;
end;

end.

Using a Var Parameter

In this example, we change the value of the variables FontColour and BackColor by passing them by reference to the RandomColour procedure. The keyword var preceding a variable name causes it to be passed by reference instead of by value.

unit Unit1;

interface

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

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

implementation

var
  FontColour, BackColour: string;

procedure RandomColour(var Chosen : string);
var
  Colours : array [0 .. 5] of string = ['red', 'pink', 'white', 'black', 'blue', 'green'];
begin
  Chosen := Colours[RandomInt(6)];
end;

procedure TCanvasProject.ApplicationStarting;
begin
  inherited;
  RandomColour(FontColour);
  repeat
    RandomColour(BackColour);
  until BackColour <> FontColour;
  GameView.Delay := 20;
  GameView.StartSession(True);
end;

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

procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  // Clear background
  Canvas.FillStyle := BackColour;
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  // Draw our framerate on the screen
  Canvas.Font := '40pt verdana';
  Canvas.FillStyle := FontColour;
  Canvas.FillTextF('FPS:' + IntToStr(GameView.FrameRate), 10, 50, MAX_INT);
end;

end.

 

Programming - a skill for life!

How to learn the Pascal language the fun way by making games in Smart Mobile Studio