MovingBallWithParticles

by George Wright: Y10 Age ~14

Introduction

George developed this motion graphic remarkably quickly now that he has, with plenty of practice, become a skilful coder. If the program does not work in your current browser after clicking on the display, try 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. Technical features and the Smart Pascal code of Version 2 follow the program in action.

Output

MovingBallWithParticles.html

Technical Features

The program benefits from:

  • object-oriented code throughout;
  • well-planned code separated into units;
  • thorough comments;
  • use of C-style operators such as +=;
  • use of inbuilt routines such as High, RandomInt, Round, FillRectF, Ellipse, MoveTo, LineTo, Translate, Rotate and BeginPath.

Code of Main Unit

unit MovingBallWithParticles;
{
    Copyright (c) 2014 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 
  SmartCL.System, SmartCL.Components, SmartCL.Application, SmartCL.Game,
  SmartCL.GameApp, SmartCL.Graphics, UBall;

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

var
  Ball : TBall;

implementation

procedure TApplication.ApplicationStarting;
begin
  inherited;
  // Initialize refresh interval
  GameView.Delay := 20;
  GameView.StartSession(False);
  // Create our ball
  Ball := TBall.Create(10, 10, 20, 20, randomInt(20), randomInt(20));
end;

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

procedure TApplication.PaintView(Canvas: TW3Canvas);
begin
  // Clear background
  Canvas.FillStyle := 'white';
  Canvas.FillRectF(0, 0, GameView.Width, GameView.Height);
  {Draw our framerate on the screen
  Canvas.Font := '10pt verdana';
  Canvas.FillStyle := 'rgb(0, 0, 0)';
  Canvas.FillText('FPS:' + IntToStr(GameView.FrameRate), 10, 20);}
  // Update our ball
  Ball.Update(GameView.Width, GameView.Height);
  // Draw our ball
  Ball.Draw(Canvas);
end;

end.

Code of uParticle

unit UParticle;
{
    Copyright (c) 2014 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
  SmartCL.System, SmartCL.Graphics;

type TParticle = class(TObject)
   X : float; // The X position of this particular particle
   Y : float; // The Y position of this particular particle
   Xvol : float; // How fast it is moving in the X direction
   Yvol : float; // How fast it is moving in the Y direction
   Angle : float; // The angle it is rotated to
   Spin : float; // How much spin it has, will be added to the angle
   Colour : array [0..2] of integer; // The R, G, B values to be used to form a colour
   Size : float; // The size multiplier
   TTL : integer; // TTL = Time To Live; when it reaches 0 it will be killed off by the emitter
   constructor Create(newx, newy, newxvol, newyvol, newangle, newspin, newsize : float; newcolour : array [0..2] of integer; newttl : integer);
   procedure Update;
   procedure Draw(Canvas : TW3Canvas);
end;

implementation

constructor TParticle.Create(newx, newy, newxvol, newyvol, newangle, newspin, newsize : float; newcolour : array [0..2] of integer; newttl : integer);
begin
  X := newx;
  Y := newy;
  Xvol := newxvol;
  Yvol := newyvol;
  Angle := newangle;
  Spin := newspin;
  Colour := newcolour;
  Size := newsize;
  TTL := newttl;
end;

procedure TParticle.Update();
begin
  dec(TTL); // Decreasing its time left, meaning it can be terminated at 0
  X += Xvol; // Adds the Xvol to the x so it moves
  Y += Yvol; // Adds the Yvol to the y so it moves
  Angle += Spin; // Adds the Spin to the angle so it appears to spin
end;

procedure TParticle.Draw(Canvas : TW3Canvas);
begin
  // Sets the Fillstyle
  Canvas.FillStyle := 'rgb(' + IntToStr(Colour[0]) + ', ' +
                           IntToStr(Colour[1]) + ', ' + IntToStr(Colour[2]) + ')';

  // Set 0, 0 point to the centre of the star
  Canvas.Translate((10 * Size) + X, (10 * Size) + Y);
  // Rotate the star
  Canvas.Rotate(Angle * PI / 180);
  // Restore the 0, 0 point back to 0, 0
  Canvas.Translate(-((10 * Size) + X), -((10 * Size) + Y));
  // Draw the 5-point star
  Canvas.BeginPath();
  Canvas.MoveToF((10 * Size) + X, (0 * Size) + Y);
  Canvas.LineToF((13 * Size) + X, (8 * Size) + Y);
  Canvas.LineToF((20 * Size) + X, (8 * Size) + Y);
  Canvas.LineToF((14 * Size) + X, (12 * Size) + Y);
  Canvas.LineToF((16 * Size) + X, (20 * Size) + Y);
  Canvas.LineToF((10 * Size) + X, (16 * Size) + Y);
  Canvas.LineToF((4 * Size) + X, (20 * Size) + Y);
  Canvas.LineToF((6 * Size) + X, (12 * Size) + Y);
  Canvas.LineToF((0 * Size) + X, (8 * Size) + Y);
  Canvas.LineToF((6 * Size) + X, (8 * Size) + Y);
  Canvas.ClosePath;
  // Colour it in
  Canvas.Fill;
  Canvas.Stroke;
  // Set 0, 0 point to the centre of the star
  Canvas.Translate((10 * Size) + X, (10 * Size) + Y);
  // Set it back to 0 rotation
  Canvas.Rotate(-(Angle * PI / 180));
  // Restore the 0, 0 point back to 0, 0
  Canvas.Translate(-((10 * Size) + X), -((10 * Size) + Y));
end;

end.

Code of uEmitter

unit UEmitter;
{
    Copyright (c) 2014 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
  SmartCL.System, SmartCL.Graphics, UParticle, UColourShifter;

type TParticleEmitter = class(TObject)
  Particles : array of TParticle; // Contains all the particles and their data
  EmitterX : float; // The starting X of all particles, the X origin point
  EmitterY : float; // The starting Y of all particles, the Y origin point
  LeftToSpawn : float; // Particles waiting to be spawned
  CurrentCol : array [0 .. 2] of integer;
  ColourToEdit : integer;
  IncrementCol : boolean;
  ColourShifter : TColourShifter; // The colour changer
  constructor Create(newx, newy : float);
  procedure Update(updateX, updateY : float; howManyToSpawn : float = 1); // Updates all the particles and makes any new ones
  procedure DrawStars(Canvas : TW3Canvas); // Draws all the stars
  function GenerateNewParticle : TParticle; // Will generate a new particle
end;

implementation

constructor TParticleEmitter.Create(newx, newy : float);
begin
  EmitterX := newx;
  EmitterY := newy;
  LeftToSpawn := 0;
  ColourShifter := TColourShifter.Create(0, 0, 0);
end;

procedure TParticleEmitter.Update(updateX, updateY : float; howManyToSpawn : float = 1);
begin
  // Set the x and y
  EmitterX := updateX;
  EmitterY := updateY;
  // Iterate over the particles
  for var i := 0 to High(Particles) do
    begin
      // Update the particle
      Particles[i].Update();
      // If the particle is meant to be dead kill it
      if (Particles[i].TTL <= 0) then
        begin
          // Respawn the particle
          Particles[i] := GenerateNewParticle();
          LeftToSpawn -= 1;
        end;
      end;

  LeftToSpawn += howManyToSpawn;

  for var i := 0 to trunc(LeftToSpawn) do
    begin
      // Generate a new particle
      Particles[High(Particles) + 1] := GenerateNewParticle();
      // Store that a particle was created
      LeftToSpawn -= 1
    end;
end;

procedure TParticleEmitter.DrawStars(Canvas: TW3Canvas);
begin
  // Iterate over each particle
  for var i := 0 to High(Particles) do
    // Draw the particle
    Particles[i].Draw(Canvas);
end;

function TParticleEmitter.GenerateNewParticle() : TParticle;
var
  pX, pY, pXvol, pYvol, pAngle, pSpin, pSize : float;
  pColour : array [0..2] of integer;
  pTTL : integer;
  returnP : TParticle;
begin
  // Set the new particle's X and Y
  pX := EmitterX;
  pY := EmitterY;
  // Set the new particle's x-velocity and y-velocity
  pXvol := randomint(10) - 5;
  pYvol := randomint(4) - 2;
  // Set the new particle's angle and spin
  pAngle := randomint(360);
  pSpin := randomint(60)- 30;
  // Set the new particle's colour
  pColour := ColourShifter.Colour;
  // Shift the colour
  ColourShifter.ShiftColour();
  // Set the new particle's size
  pSize := randomint(200) / 100;
  // Set the new particle's TTL
  pTTL := randomint(5) + 25;
   // Create the new particle
   returnP := TParticle.Create(pX, pY, pXvol, pYvol, pAngle, pSpin, pSize, pColour, pTTL);
  // Return the new particle
  exit(returnP);
end;

end.


Code of uBall

unit UBall;
{
    Copyright (c) 2014 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 
  SmartCL.System, SmartCL.Graphics, UEmitter, UColourShifter;

type TBall = class(TObject)
  X : float; // The x position
  Y : float; // The y position
  Width : float; // The width of the ball
  Height : float; // The height of the ball
  XSpeed : float; // The x speed of the ball
  YSpeed : float; // The y speed of the ball
  Emitter : TParticleEmitter; // The particle emitter
  ColourShifter : TColourShifter; // The colour changer
  constructor Create(newX, newY, newWidth, newHeight, newXSpeed, newYSpeed : float);
  procedure Update(screenWidth, screenHeight : float);
  procedure Draw(Canvas : TW3Canvas);
end;

implementation

constructor TBall.Create(newX, newY, newWidth, newHeight, newXSpeed, newYSpeed : float);
begin
  X := newX;
  Y := newY;
  Width := newWidth;
  Height := newHeight;
  XSpeed := newXSpeed;
  YSpeed := newYSpeed;
  Emitter := TParticleEmitter.Create(X, Y);
  ColourShifter := TColourShifter.Create(175, 0, 0);
end;

procedure TBall.Update(screenWidth, screenHeight : float);
begin
  // Move the ball
  X += XSpeed;
  Y += YSpeed;
  // Check for x bounce
  if (X <= 0) or (X + Width >= screenWidth) then
    XSpeed := -XSpeed;
  // Check for y bounce
  if (Y <= 0) or (Y + Height >= screenHeight) then
    YSpeed := -YSpeed;
  // Update the particles
  Emitter.Update(X, Y, 2);
end;

procedure TBall.Draw(Canvas : TW3Canvas);
begin
  // Draw the particles
  Emitter.DrawStars(Canvas);
  // Set the fillstyle
  Canvas.FillStyle := ColourShifter.RGBString;
  Canvas.StrokeStyle := 'black';
  // Design the ball
  Canvas.BeginPath;
  Canvas.Ellipse(X, Y, X + Width, Y + Height);
  // Draw the ball
  Canvas.Fill;
  Canvas.Stroke;
  // Shift the colour
  ColourShifter.ShiftColour(2.055);
end;

end.

Code of UColourShifter

unit UColourShifter;
{
    Copyright (c) 2014 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
  SmartCL.System;

type TColourShifter = class(TObject)
  Colour: array [0 .. 2] of integer;
  ColourToEdit: integer;
  IncrementColour: boolean;
  ShiftsLeft: float;
  constructor Create(newRed, newGreen, newBlue: integer);
  procedure ShiftColour(timesToShift : float = 1);
  function RGBString: string;
end;

implementation

constructor TColourShifter.Create(newRed, newGreen, newBlue : integer);
begin
  Colour[0] := newRed;
  Colour[1] := newGreen;
  Colour[2] := newBlue;
  ColourToEdit := 0;
  IncrementColour := true;
  ShiftsLeft := 0;
end;

procedure TColourShifter.ShiftColour(timesToShift : float = 1);
begin
  // Add the amount of shifts
  ShiftsLeft += timesToShift;
  // Do the correct amount of shifts
  while (ShiftsLeft > 0) do
    begin
      // Change the colour value
      if IncrementColour then
        inc(Colour[ColourToEdit])
      else
        dec(Colour[ColourToEdit]);
      // Change the column to change the colour
      if Colour[ColourToEdit] > 255 then
        begin
          dec(Colour[ColourToEdit]);
          inc(ColourToEdit);
        end
      else if Colour[ColourToEdit] < 0 then
        begin
          inc(Colour[ColourToEdit]);
          inc(ColourToEdit);
        end;
      // Make sure the colour to edit is within the bounds of the array
      if ColourToEdit = 3 then
        begin
          ColourToEdit := 0;
          IncrementColour := not IncrementColour;
        end;
      ShiftsLeft -= 1;
    end;
end;

function TColourShifter.RGBString: string;
begin
  var rgb : string;
  rgb := 'rgb(' + IntToStr(Colour[0]) + ', ' + IntToStr(Colour[1]) + ', ' + IntToStr(Colour[2]) + ')';
  exit(rgb);
end;

end.



Programming - a skill for life!

Five programs including RandomPlatformScroller and TowerOfArcher by George Wright