Search Tips Advanced Search |
| Delphi Graphics and Game Programming Exposed with DirectX 7.0 by John Ayres Wordware Publishing, Inc. ISBN: 1556226373 Pub Date: 12/01/99
Search this book:
Previous | Table of Contents | Next
Modeling aggression is simple enough. The application simply examines the horizontal and vertical position of the enemy unit and the player unit, and adjusts the enemy unit’s horizontal and vertical positions to bring it close to the player. For example, if the player unit’s horizontal position is less than the enemy’s, subtracting 1 from the enemy’s horizontal position brings it closer to the player. If the player’s horizontal position is greater than the enemy’s, adding 1 to the enemy’s horizontal position brings it closer to the player. The same holds true for the vertical position. This is illustrated below. Figure 12–1: Chasing the player Modeling evasion is just as simple. Indeed, if we simply reverse the signs of the values being added to the horizontal and vertical position of the enemy, it will run from the player instead of chasing after it. This is illustrated below. Figure 12–2: Evading the player Of course, most games would need to take into account obstacles and line of sight, but the basic principle holds true. The following example illustrates how to implement chase and evasion algorithms. The player’s dot is controlled by the cursor keys, and the enemy dot will either chase or flee from the user based on its current status, which is toggled by the Enter key. Listing 12–2: Aggression and evasion in action
    type

       .

       .

       .

      TSprite = class

        XPos, YPos: Integer;

        procedure MoveSprite(XOffset, YOffset: Integer);

      end;

    var

       .

       .

       .

      Enemy, Player: TSprite;

      Chase: Boolean;

    implementation

       .

       .

       .

    procedure TfrmDXAppMain.FormActivate(Sender: TObject);

    begin

       .

       .

       .

      {create the two sprites}

      Enemy := TSprite.Create;

      Player := TSprite.Create;

      {initialize the enemy and player sprite positions}

      Enemy.XPos := 320;

      Enemy.YPos := 0;

      Player.XPos := 320;

      Player.YPos := 477;

      {start out in aggression/chase mode}

      Chase := TRUE;

       .

       .

       .

    end;

       .

       .

       .

    procedure TfrmDXAppMain.DrawSurfaces;

    var

      SrfcDc: HDC;

      TempCanvas: TCanvas;

      EnemyOffset: Integer;

    begin

      {erase the last frame of animation}

      ColorFill(FBackBuffer, clBlack, nil);

      {if the Enter key was pressed, toggle aggression/evasion}

      if (GetAsyncKeyState(VK_RETURN) and $8000) = $8000 then

        Chase := not Chase;

      {move the player sprite based on the cursor keys}

      if (GetAsyncKeyState(VK_LEFT) and $8000) = $8000 then

        Player.MoveSprite(–2, 0);

      if (GetAsyncKeyState(VK_RIGHT) and $8000) = $8000 then

        Player.MoveSprite(2, 0);

      if (GetAsyncKeyState(VK_UP) and $8000) = $8000 then

        Player.MoveSprite(0, – 2);

      if (GetAsyncKeyState(VK_DOWN) and $8000) = $8000 then

        Player.MoveSprite(0, 2);

      {determine the appropriate movement for chasing or evading}

      if Chase then

        EnemyOffset := 1

      else

        EnemyOffset := –1;

      {move the enemy accordingly}

      if Player.XPos > Enemy.XPos then

        Enemy.MoveSprite(EnemyOffset, 0);

      if Player.XPos < Enemy.XPos then

        Enemy.MoveSprite(–EnemyOffset, 0);

      if Player.YPos > Enemy.YPos then

        Enemy.MoveSprite(0, EnemyOffset);

      if Player.YPos < Enemy.YPos then

        Enemy.MoveSprite(0, –EnemyOffset);

      {retrieve a device context and create a canvas}

      FBackBuffer.GetDC(SrfcDc);

      TempCanvas := TCanvas.Create;

      try

        {set up the canvas}

        TempCanvas.Handle := SrfcDc;

        TempCanvas.Font.Color := clWhite;

        {draw the two sprites as small dots}

        TempCanvas.Pen.Style := psClear;

        TempCanvas.Brush.Style := bsSolid;

        TempCanvas.Brush.Color := clBlue;

        TempCanvas.Ellipse(Player.XPos, Player.YPos, Player.XPos+3, Player.YPos+3);

        TempCanvas.Brush.Color := clRed;

        TempCanvas.Ellipse(Enemy.XPos, Enemy.YPos, Enemy.XPos+3, Enemy.YPos+3);

        {indicate which state the enemy sprite is in}

        TempCanvas.Brush.Style := bsClear;

        if Chase then

          TempCanvas.TextOut(0, 0, ‘Chasing’)

        else

          TempCanvas.TextOut(0, 0, ‘Evading’);

          TempCanvas.TextOut(0, 440, ‘Press Enter to toggle state’);

      finally

        {clean up}

        TempCanvas.Handle := 0;

        FBackBuffer.ReleaseDC(SrfcDc);

        TempCanvas.Free;

      end;

    end;

    procedure TSprite.MoveSprite(XOffset, YOffset: Integer);

    begin

      {update the sprite’s position}

      XPos := XPos + XOffset;

      YPos := YPos + YOffset;

      {clip to the screen boundaries}

      if XPos < 0 then

        XPos := 0;

      if XPos > DXWIDTH – 1 then

        XPos := DXWIDTH – 1;

      if YPos < 0 then

        YPos := 0;

      if YPos > DXHEIGHT – 1 then

        YPos := DXHEIGHT – 1;

    end;
We could have just as easily made the chase/evade decision based upon whether or not the player was actively moving. If the player’s sprite positioned has changed, we could have switched to evade mode; if the player hadn’t moved, we could go right to chase mode. This would certainly keep players on their toes, and in the event our enemy units could kill a player, this would certainly discourage anyone from waiting around somewhere for very long! #### Patterned Movement Chasing the player, running from the player, and moving around randomly can get pretty boring after a while. Typically, enemy units need to exhibit more complex movement, and one way to simulate this is through movement patterns. For example, take the old arcade game Galaxian. When the enemies appeared in each round, they flew onto the screen in a complex flight path. Although their movements were a bit predictable, it made their appearance more menacing and deliberate. We can use patterned movement in our games to model similar complex, deliberate movements and make our enemies appear to move about more intentionally. Indeed, we could create several patterns and choose one randomly (or in a more context–sensitive manner), as illustrated in Figure 12–3. Figure 12–3: Movement patterns There are several methods by which patterned movement can be implemented based on the type of game and the sprite engine. If we had a simple, 2–D shooter, our patterns could simply be based off of a series of horizontal and vertical offsets that move the unit in the desired manner. These offsets would be relative to the current position of the sprite, so the patterns could be used anywhere within the game world.
| Tip: It is relatively easy to flip the movement pattern horizontally or vertically, thus increasing the flexibility of this technique.
To implement this method of patterned movement, we could make an array of TPoints, filling each entry with the horizontal or vertical offset for that position within the pattern. As we move the sprite, we keep track of an index into this array of offsets, incrementing the offset at a preset time to advance to the next movement within the pattern. Once the pattern is complete, we can choose another array (or another row within a multidimensional array) and reset the index to 0, thus starting a new pattern. This technique is illustrated below. Listing 12–3: Implementing patterned movement
    TSprite = record

        XPos, YPos: Integer;

        CurPattern,

        CurPatternIdx: Integer;

        PatternIdxChange,

        CurTimer: Longint;

      end;

      TPatterns = array[0..5, 0..31] of TPoint;

    const

      {in this example, the patterns are stored as direct X and Y offsets, but

       any method of specifying a direction and/or offset will do}

      Patterns: TPatterns = (

      ((x: 1; y: 0), (x: 1; y: 0), (x: 1; y: 0), (x: 0; y: 1), (x: 0; y: 1), ...

      ((x: –1; y: –1), (x: –1; y: –1), (x: –1; y: –1), (x: –1; y: –1), ...

      ((x: 1; y: 0), (x: 1; y: 0), (x: 1; y: 1), (x: 1; y: 1), (x: 0; y: 1), ...

      ((x: 1; y: 0), (x: 1; y: 0), (x: 1; y: 0), (x: 1; y: 0), (x: 0; y: –1), ...

      ((x: 1; y: 0), (x: 1; y: 0), (x: 1; y: 0), (x: 1; y: –1), (x: 0; y: –1), ...

      ((x: 0; y: –1), (x: 0; y: –1), (x: 0; y: –1), (x: 0; y: –1), ...

    var

       .

       .

       .

      Enemy: TSprite;

    implementation

       .

       .

       .

    procedure TfrmDXAppMain.FormActivate(Sender: TObject);

    begin

       .

       .

       .

      {initialize the enemy sprite}

      Randomize;

      Enemy.XPos := 320;

      Enemy.YPos := 240;

      Enemy.CurPattern := Random(6);    // start at a random pattern

      Enemy.CurPatternIdx := 0;

      Enemy.PatternIdxChange := 100;    // change the pattern index often

      Enemy.CurTimer := timeGetTime;

       .

       .

       .

    end;

       .

       .

       .

    procedure TfrmDXAppMain.DrawSurfaces;

    var

      SrfcDc: HDC;

      TempCanvas: TCanvas;

    begin

      {erase the last frame of animation}

      ColorFill(FBackBuffer, clBlack, nil);

      {if it is time to change the pattern index...}

      if timeGetTime – Enemy.CurTimer > Enemy.PatternIdxChange then

      begin

        {...move to the next step in the pattern}

        Enemy.CurPatternIdx := Enemy.CurPatternIdx + 1;

        {if we have moved the entire pattern...}

        if Enemy.CurPatternIdx > 31 then

        begin

          {...choose a new pattern}

          Enemy.CurPattern := Random(6);

          Enemy.CurPatternIdx := 0;

        end;

        {track for the next pattern index change}

        Enemy.CurTimer := timeGetTime;

      end;

      {update the enemy sprite’s position based on the current step with

       the pattern}

      Enemy.XPos := Enemy.XPos + Patterns[Enemy.CurPattern, Enemy.CurPatternIdx].X;

      Enemy.YPos := Enemy.YPos + Patterns[Enemy.CurPattern, Enemy.CurPatternIdx].Y;

      {clip the enemy to the screen boundaries}

      if Enemy.XPos < 0 then

        Enemy.XPos := 0;

      if Enemy.XPos > DXWIDTH – 1 then

        Enemy.XPos := DXWIDTH – 1;

      if Enemy.YPos < 0 then

        Enemy.YPos := 0;

      if Enemy.YPos > DXHEIGHT – 1 then

        Enemy.YPos := DXHEIGHT – 1;

      {retrieve a device context and create a canvas}

      FBackBuffer.GetDC(SrfcDc);

      TempCanvas := TCanvas.Create;

      try

        {set up the canvas}

        TempCanvas.Handle := SrfcDc;

        TempCanvas.Font.Color := clWhite;

        {draw the enemy sprite}

        TempCanvas.Pen.Style := psClear;

        TempCanvas.Brush.Style := bsSolid;

        TempCanvas.Brush.Color := clRed;

        TempCanvas.Ellipse(Enemy.XPos, Enemy.YPos, Enemy.XPos + 3, Enemy.YPos + 3);

        {indicate the pattern number}

        TempCanvas.Brush.Style := bsClear;

        TempCanvas.TextOut(0, 0, ‘Pattern: ’+IntToStr(Enemy.CurPattern));

      finally

        {clean up}

        TempCanvas.Handle := 0;

        FBackBuffer.ReleaseDC(SrfcDc);

        TempCanvas.Free;

      end;

    end;
Previous | Table of Contents | Next

Products | Contact Us | About Us | Privacy | Ad Info | Home Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.