Smart Mobile Studio
  • News
  • Forums
  • Download
  • Store
  • Showcases
    • Featured demos
    • The Smart Contest 2013, Round 1 – Graphics
  • Documentation
    • Get the book
    • System requirements
    • Prerequisites
    • Getting started
      • Introduction
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • Themes and styles
    • Project types
      • Visual project
      • Game project
      • Console project
    • Layout manager
    • Networking
      • TW3HttpRequest
      • TW3JSONP
      • Loading files
  • About

Game programming – Part 2

Posted on 30.04.2013 by Jon Lennart Posted in Developers log, News

In the first installment of this article we had a look at how to load and display a tile based map. In this second post we will take it one step further by implementing scroll (movement) management – which is an essential part of our chosen genre of games.

Scrolling, what’s that?

The word “scroll” is not that common these days. People instead use words like “sliding” and “gliding” to describe pretty much the same thing. But we will stick to the old school term “scroll”, which is losely related to the english word “stroll”, to walk, to move at a steady pace.

What we want to scroll is naturally the map itself. We can achieve this by 3 means:

  1. Re-draw the map at different positions for each display
  2. Draw the map to an offscreen (hidden) bitmap and copy parts onto the display
  3. Use CSS to scroll the map

The current game project in Smart Mobile Studio is a pure 2d canvas project. Mixing CSS with raw pixel graphics at this point would be problematic and to some degree a poor choise. We could easily use the sprite3d api (sprite3d.pas) to create a css based game, but for this tutorial which focuses on “the classical way” of doing things, we will stick to method number 2.

Small levels

Im going to make use of the fact that mobile games are usually short, with quick matches and smaller levels than we traditionally have on the PC or mac. We will use this to our advantage and pre-draw an entire level onto a background bitmap. We will then copy slices of this onto the visible canvas – giving the illusion of scrolling along.

Scroll management class

Right, we want to keep things simple and straight to the point. We have the code needed to draw a map onto a canvas, and we now want to pre-draw the entire world onto a background bitmap, and copy our graphics from that instead of constantly drawing from the tileset. Here is the bare-bone scrolling manager so far:

{.$DEFINE USE_LIVE_TILE_DRAWING}
type
  TScrollManager = class(TObject)
  private
    // map-data and tileset classes
    FMapData: TMapData;
    FTileset: TTileset;

    // current x/y pos on display
    FXpos:  Integer;
    FYPos:  Integer;

    // total size of map in pixels
    FWorldWidth:  Integer;
    FWorldHeight: Integer;

    // size of target viewport
    FViewWidth:   Integer;
    FViewHeight:  Integer;

    // tiles required to fill viewport
    FBlocksReqX:  Integer;
    FBlocksReqY:  Integer;

    // our cache bitmap and canvas
    FContext:   TW3GraphicContext;
    FCanvas:    TW3Canvas;

  public
    constructor Create(Const aMapData: TMapData;
      aTileset: TTileset); virtual;

    procedure DrawTo(const Canvas: TW3Canvas;
      const startX, startY: Integer);

    function getCurrentX: Integer;
    function getCurrentY: Integer;
    procedure MoveTo(dx, dy: Integer);
    procedure MoveBy(x, y: Integer);
    procedure UpdateMetrics(const ViewWidth: Integer;
      const ViewHeight: Integer);

    property WorldWidth: Integer read FWorldWidth;
    property WorldHeight: Integer read FWorldHeight;

    property ViewWidth: Integer read FViewWidth;
    property ViewHeight: Integer read FViewHeight;
  end;

//#######################################################################
// TScrollManager
//#######################################################################

constructor TScrollManager.Create(Const aMapData: TMapData;
            aTileset: TTileset);
begin
  inherited Create;
  if assigned(aMapData) then
    FMapData := aMapData
  else
    raise Exception.Create('Scrollmanager mapdata was NIL error');

  if assigned(aTileset) then
    FTileset := aTileset
  else
    raise Exception.Create('Scrollmanager tileset was NIL error');
end;

procedure TScrollManager.UpdateMetrics(
  const ViewWidth: Integer;
  const ViewHeight: Integer);
var
  x, y:  Integer;
begin
  // calculate total size of "world"
  FWorldWidth := FMapData.Width * CNT_TILE_WIDTH;
  FWorldHeight := FMapData.Height * CNT_TILE_HEIGHT;

  // cache display size
  FViewWidth := ViewWidth;
  FViewHeight := ViewHeight;

  // calculate # of tiles required to fill display horizontaly
  FBlocksReqX := ViewWidth div CNT_TILE_WIDTH;
  if FBlocksReqX * CNT_TILE_WIDTH < ViewWidth then
    inc(FBlocksReqX);

  // caclulate # of tiles required to fill display vertically
  FBlocksReqY := viewHeight div CNT_TILE_HEIGHT;
  if FBlocksReqY * CNT_TILE_HEIGHT < viewHeight then
    inc(FBlocksReqY);

  // release pre-rendered context if already allocated
  if assigned(FContext) then
  begin
    FCanvas.free;
    FContext.free;
    FCanvas := NIL;
    FContext := NIL;
  end;

  // pre-allocate entire level as a single bitmap
  // and pre-render everything
  if  FTileset.Ready and FMapData.Ready then
  begin
    FContext := TW3GraphicContext.Create(NIL);
    FContext.Allocate(FWorldWidth,FWorldHeight);
    FCanvas := TW3Canvas.Create(FContext);
    for y := 0 to FMapData.height-1 do
    for x := 0 to FMapData.width-1 do
    FTileSet.Draw(FCanvas,x * 32, y * 32, FMapData.Data[x,y]);
  end;
end;

procedure TScrollManager.DrawTo(
  const Canvas: TW3Canvas;
  const StartX, StartY: Integer);
{$IFDEF USE_LIVE_TILE_DRAWING}
var
  x,y:  Integer;
  dx,dy:  Integer;
  xoff: Integer;
  yOff: Integer;
  mTemp:  Integer;
  srcXPos:  Integer;
  srcYpos:  Integer;
  mTileId:  Integer;
{$ENDIF}
begin
  if canvas <> NIL then
  begin

    {$IFNDEF USE_LIVE_TILE_DRAWING}
    try
      Canvas.DrawImageF(FContext.Handle,
      FXpos,FYpos, FViewWidth-1,FViewHeight-1,
      StartX,StartY,FViewWidth-1,FViewHeight-1);
    except
      on e: exception do
      Begin
        showmessage(e.message);
        application.terminate;
        exit;
      end;
    end;
    {$ELSE}

    (* pixel offset left-edge *)
    mTemp := FXPos div CNT_TILE_WIDTH;
    FXOff := FXPos - (mTemp * CNT_TILE_WIDTH);

    (* pixel offset top-edge *)
    mTemp := FYPos div CNT_TILE_HEIGHT;
    FYOff := FYPos - (mTemp * CNT_TILE_HEIGHT);

    (* map Xpos *)
    srcXpos := FXpos div CNT_TILE_WIDTH;

    srcYPos := FYpos div CNT_TILE_HEIGHT;

    for y := 0 to FBlocksReqY do
    begin
      if srcYpos + y > FMapData.height then
      break;

      for x := 0 to FBlocksReqX do
      Begin
        if srcXpos + x > FMapData.Width then
        break;

        dx := StartX + (x * CNT_TILE_WIDTH) - FXOff;
        dy := startY + (y * CNT_TILE_HEIGHT) - FYOff;
        mTileId := FMapData.Data[srcXpos + x, srcYpos + y];

        if (mTileId>=0) then
        FTileset.Draw(Canvas,dx,dy,mTileId);
      end;
    end;
    {$ENDIF}
  end;
end;

function TScrollManager.getCurrentX: Integer;
begin
  Result := FXpos;
end;

function TScrollManager.getCurrentY: Integer;
begin
  Result := FYPos;
end;

procedure TScrollManager.MoveTo(dx, dy: Integer);
begin
  FXPos := dx;
  FYpos := dy;
end;

procedure TScrollManager.MoveBy(x, y: Integer);
begin
  inc(FXPos,x);
  inc(FYpos,y);
end;

Did you notice the {$DEFINE} constant? I included two drawing routines. The first one is the version I have mentioned so far, which copies from a pre-rendered bitmap. The second draws the present display using tiles. The latter can be handy if you want to test or somehow need to interact more closely with the tiles. One example is if you want to draw the tile-id over the tile so you can see the id number while working. Just leave the define inactive (as it is now) if this doesnt bother you.

Next up we need to alter the main-drawing loop of our app so that it uses the new technique (we also add the new class as a field of our TApplication instance). So our main drawing loop now looks like this:

procedure TApplication.PaintView(Canvas: TW3Canvas);
var
  wd,hd:  Integer;
begin
  // cache width/height to local variables
  wd:=GameView.width;
  hd:=GameView.height;

  // Sanity check, resources ready etc?
  if FTileset.Ready
  and FMapData.Ready
  and (wd>1)
  and (hd>1)
  and not application.Terminated then
  begin
    // Clear background
    Canvas.FillStyle := 'rgb(0, 0, 99)';
    Canvas.FillRectF(0, 0, wd, hd);

    // Draw our framerate on the screen
    Canvas.Font := '10pt verdana';
    Canvas.FillStyle := 'rgb(255, 255, 255)';
    Canvas.FillTextF('FPS:' + IntToStr(GameView.FrameRate), 10, 20, MAX_INT);

    // Check if display is altered, update map-view metrics
    // if size or orientation has changed.
    if (FScrollManager.ViewWidth<>wd)
    or (FScrollManager.ViewHeight<>hd) then
    begin
      FScrollManager.UpdateMetrics(wd,hd);
    end;

    // update xpos by 1 pixel (scroll right)
    FScrollManager.MoveBy(1,0);

    // copy from pre-rendered level
    FScrollManager.DrawTo(Canvas,0,0);
  end;
end;

Ok, hit F9 and we get the same output as before – but this time it moves!

A simple 100x100 tiled scrolling map

A simple 100×100 tiled scrolling map

Navigation

Having a map that scrolls constantly to the right isnt really that exciting. I know some games deploy fixed scrolling on the iphone but I personally prefer to control a character myself. Which brings us to another very important piece of our game – namely controls.

The iPhone and Android devices dont have “real” keys. Nor do we want to have the keyboard dialog spring into action during our game. So we have to come up with some form of virtual joystick. Once we have that in place we can continue with the task of controling the map – and finally add a character, gravity, jumping routines and terrain checking.  Stay tuned for Part 3!

« Game programming – Part 1
Smart Contest 2013 – Round #2 »

Pages

  • About
  • Feature Matrix
  • Forums
  • News
  • Release History
  • Download
  • Showcases
    • The Smart Contest 2013, Round 1 – Graphics
  • Store
  • Documentation
    • Creating your own controls
    • Debugging, exceptions and error handling
    • Differences between Delphi and Smart
    • Get the book
    • Getting started
      • Introduction
      • Local storage, session storage and global storage
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • pmSmart Box Model
      • Themes and styles
    • Layout manager
    • Networking
      • Loading files
      • TW3HttpRequest
      • TW3JSONP
    • Prerequisites
    • Real data, talking to sqLite
    • System requirements
    • Project types
      • Visual project
      • Game project
      • Console project

Archives

  • December 2019
  • December 2018
  • November 2018
  • July 2018
  • June 2018
  • February 2018
  • September 2017
  • April 2017
  • November 2016
  • October 2016
  • September 2016
  • April 2016
  • March 2016
  • January 2016
  • October 2015
  • September 2015
  • July 2015
  • April 2015
  • January 2015
  • December 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • March 2014
  • February 2014
  • January 2014
  • December 2013
  • November 2013
  • October 2013
  • August 2013
  • July 2013
  • June 2013
  • May 2013
  • April 2013
  • March 2013
  • February 2013
  • January 2013
  • December 2012
  • November 2012
  • August 2012
  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • November 2011
  • October 2011
  • September 2011

Categories

  • Announcements (25)
  • Developers log (119)
  • Documentation (26)
  • News (104)
  • News and articles (16)

WordPress

  • Register
  • Log in
  • WordPress

Subscribe

  • Entries (RSS)
  • Comments (RSS)
© Optimale Systemer AS