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
#### Probability Machines That’s where probability machines come in. A probability machine is really just an extension of a finite state machine, allowing the application to simulate personalities based on a certain distribution factor. Using this technique, we can make some sprites appear more passive, some more aggressive, some a little bit sneakier, etc. It works by choosing the next state based on a weighted distribution as identified by the type of personality we’re modeling. For example, let’s say we have five states, and we want to model an aggressive behavior. Whenever it comes time to change states, we want to choose the next state based on a percentage chance as illustrated below. Table 12–1: Aggressive personality percentage chance for state selection State | Percentage
--- | ---
Chase | 50%
Evade | 10%
Wait | 10%
Random Movement | 10%
Patterned Movement | 20%
So, in this example, an aggressive personality would choose the chase state 50% of the time. A more timid personality would likely choose the evade state more often. Other personality types would result in varying distributions. One way to implement such a system would be to create an array of 10 elements, where each element holds a state (either as an integer index or an enumerated type). This array would then be populated by each state a number of times equal to its probability of selection divided by 10. Over time, each state would be selected according to its probability. For example, if the chase state had a 50% chance of selection, it would be in the array five times, and over time, it would indeed have a 50% chance of being selected when the state changed. This is the method by which personalities are implemented in the following example. Listing 12–5: Personalities through probability machines
    type

       .

       .

       .

      {this defines the different states}

      TSpriteStates = (ssChasing, ssEvading, ssRandomMoves, ssPatternMoves, ssWait,

                       ssPlayer);

      TStateNames = array[ssChasing..ssWait] of string;

      {this defines the different personalities}

      TPersonalities = (perAggressive, perDefensive, perTactical);

      TPersonalityNames = array[perAggressive..perTactical] of string;

      {this defines the probabilities tables that implement the personalities}

      TProbabilityTable = array[0..9] of TSpriteStates;

      TPersonality = array[TPersonalities] of TProbabilityTable;

      {our sprite class}

      TSprite = class

        XPos, YPos: Integer;

        CurPattern,

        CurPatternIdx: Integer;

        PatternIdxChange,

        CurPatternTimer,

        StateChange,

        StateTimer,

        PersonalityChange,

        PersonalityTimer: Longint;

        State: TSpriteStates;

        Personality: TPersonalities;

        procedure ChangeState;

        procedure Move;

        procedure Draw(Canvas: TCanvas);

      end;

      TPatterns = array[0..7, 0..31] of Integer;

    const

      {these probability tables implement the behavior of each personality.

       each state is entered into each table a number of times equal to its

       percentage chance of selection divided by 10 (i.e., 50% chance of selection =

       5 times in the table)}

      AggressivePersonality: TProbabilityTable = (ssChasing, ssChasing, ssChasing,

                                                  ssChasing, ssChasing, ssChasing,

                                                  ssPatternMoves, ssPatternMoves,

                                                  ssRandomMoves, ssWait);

      DefensivePersonality: TProbabilityTable = (ssEvading, ssEvading, ssEvading,

                                                 ssEvading, ssEvading, ssEvading,

                                                 ssRandomMoves, ssRandomMoves,

                                                 ssPatternMoves, ssWait);

      TacticalPersonality: TProbabilityTable = (ssEvading, ssEvading, ssRandomMoves,

                                                ssRandomMoves, ssWait, ssWait,

                                                ssPatternMoves, ssPatternMoves,

                                                ssChasing, ssChasing);

      {this allows us to easily display the name of the current

       state and personality}

      StateNames: TStateNames = (‘Chasing’, ‘Evading’, ‘RandomMoves’,

                                 ‘PatternMoves’, ‘Wait’);

      PersonalityNames: TPersonalityNames = (‘Aggressive’, ‘Defensive’, ‘Tactical’);

      {this table implements patterned movement. moves are stored as directions

       in this example, with 0 equal to north (or straight up movement) incrementing

       in a clockwise direction}

      Patterns: TPatterns = (

      (0, 0, 0, 1, 1, 1, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7,

       1, 1, 1, 0, 0, 0),

      (1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,

       2, 2, 2, 1, 1, 1),

      (2, 2, 2, 3, 3, 3, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1,

       3, 3, 3, 2, 2, 2),

      (3, 3, 3, 4, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2,

       4, 4, 4, 3, 3, 3),

      (4, 4, 4, 5, 5, 5, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3,

       5, 5, 5, 4, 4, 4),

      (5, 5, 5, 6, 6, 6, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4,

       6, 6, 6, 5, 5, 5),

      (6, 6, 6, 7, 7, 7, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5,

       7, 7, 7, 6, 6, 6),

      (7, 7, 7, 0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6,

       0, 0, 0, 7, 7, 7)

      );

      {these indicate sprite positions in the array of sprites}

      ENEMYIDX = 0;

      PLAYERIDX = 1;

    var

       .

       .

       .

      {track the sprites}

      Sprites: array[ENEMYIDX..PLAYERIDX] of TSprite;

      {our personality table}

      Personalities: TPersonality;

    implementation

       .

       .

       .

    procedure TfrmDXAppMain.FormDestroy(Sender: TObject);

    begin

       .

       .

       .

      {free the sprite objects}

      Sprites[ENEMYIDX].Free;

      Sprites[PLAYERIDX].Free

    end;

    procedure TfrmDXAppMain.FormActivate(Sender: TObject);

    begin

       .

       .

       .

      {seed the random number generator}

      Randomize;

      {initialize the enemy sprite}

      Sprites[ENEMYIDX] := TSprite.Create;

      Sprites[ENEMYIDX].XPos := 320;

      Sprites[ENEMYIDX].YPos := 240;

      Sprites[ENEMYIDX].State := ssWait;

      Sprites[ENEMYIDX].StateChange := 10;

      Sprites[ENEMYIDX].StateTimer := timeGetTime;

      Sprites[ENEMYIDX].PatternIdxChange := 100;

      Sprites[ENEMYIDX].Personality := perAggressive;

      Sprites[ENEMYIDX].PersonalityChange := 25000;

      Sprites[ENEMYIDX].PersonalityTimer := timeGetTime;

      {initialize the player sprite}

      Sprites[PLAYERIDX] := TSprite.Create;

      Sprites[PLAYERIDX].XPos := 320;

      Sprites[PLAYERIDX].YPos := 478;

      Sprites[PLAYERIDX].State := ssPlayer;

       .

       .

       .

    end;

       .

       .

       .

    procedure TfrmDXAppMain.DrawSurfaces;

    var

      SrfcDc: HDC;

      TempCanvas: TCanvas;

      iCount: Integer;

    begin

      {erase the last frame of animation}

      ColorFill(FBackBuffer, clBlack, nil);

      {move the player sprite based on which cursor keys are depressed}

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

        Sprites[PLAYERIDX].XPos := Sprites[PLAYERIDX].XPos – 2;

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

        Sprites[PLAYERIDX].XPos := Sprites[PLAYERIDX].XPos + 2;

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

        Sprites[PLAYERIDX].YPos := Sprites[PLAYERIDX].YPos – 2;

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

        Sprites[PLAYERIDX].YPos := Sprites[PLAYERIDX].YPos + 2;

      {move both sprites (note that this simply clips the player sprite to

       the screen boundaries, as the player sprite’s position has already

       been updated)}

      for iCount := ENEMYIDX to PLAYERIDX do

        Sprites[iCount].Move;

      {retrieve a device context and create a canvas}

      FBackBuffer.GetDC(SrfcDc);

      TempCanvas := TCanvas.Create;

      try

        {use the canvas for easy drawing functionality}

        TempCanvas.Handle := SrfcDc;

        TempCanvas.Font.Color := clWhite;

        {draw the sprites}

        for iCount := ENEMYIDX to PLAYERIDX do

          Sprites[iCount].Draw(TempCanvas);

        {display the current state and personality of the enemy sprite}

        TempCanvas.Brush.Style := bsClear;

        TempCanvas.TextOut(0, 0, ‘Personality: ’+

                           PersonalityNames[Sprites[ENEMYIDX].Personality]);

        TempCanvas.TextOut(0, 15, ‘State: ’+StateNames[Sprites[ENEMYIDX].State]);

      finally

        {clean up}

        TempCanvas.Handle := 0;

        FBackBuffer.ReleaseDC(SrfcDc);

        TempCanvas.Free;

      end;

    end;

       .

       .

       .

    procedure TSprite.ChangeState;

    var

      Distance: Integer;

    begin

      {set the state randomly based on the personality}

      State := Personalities[Personality][Random(10)];

      {if this is the tactical state...}

      if Personality = perTactical then

      begin

        {determine the distance to the player}

        Distance := Abs(XPos – Sprites[PLAYERIDX].XPos)+

                    Abs(YPos – Sprites[PLAYERIDX].YPos);

        {if the player is too close, we’ll evade the player}

        if Distance < 100 then

          State := ssEvading

        else

        {if the player is too far away, we’ll move toward the player}

        if Distance > 300 then

          State := ssChasing

      end;

      {if this is the pattern move state...}

      if (State = ssPatternMoves) then

      begin

        {choose a random pattern}

        CurPattern := Random(8);

        {initialize to the start of the pattern}

        CurPatternIdx := 0;

        {retrieve the current time}

        CurPatternTimer := timeGetTime;

        {32 steps in a pattern multiplied by the time per each step should

         give enough time to complete the entire pattern}

        StateChange := PatternIdxChange * 32;

      end

      else

        {...otherwise, change state again in 1 to 3 seconds}

        StateChange := Random(2000)+1000;

      {begin timing the state}

      StateTimer := timeGetTime;

    end;

    procedure TSprite.Draw(Canvas: TCanvas);

    begin

      {draw enemy sprites in blue, player sprites in red}

      if State <> ssPlayer then

        Canvas.Brush.Color := clBlue

      else

        Canvas.Brush.Color := clRed;

      {draw a small dot for the sprite}

      Canvas.Pen.Style := psClear;

      Canvas.Brush.Style := bsSolid;

      Canvas.Ellipse(XPos, YPos, XPos + 3, YPos + 3);

    end;

    procedure TSprite.Move;

    const

      {initialize a horizontal and vertical velocity}

      XVel: Integer = 1;

      YVel: Integer = 1;

    begin

      {if this sprite is an enemy sprite...}

      if State <> ssPlayer then

      begin

        {if it is time to change the personality, then change it}

        if timeGetTime – PersonalityTimer > PersonalityChange then

        begin

          {change to the next personality}

          if Personality = perTactical then

            Personality := perAggressive

          else

            Personality := Succ(Personality);

          {reset the personality change timer}

          PersonalityTimer := timeGetTime;

        end;

        {if it is time to change the state, then change it}

        if timeGetTime – StateTimer > StateChange then

          ChangeState;

        {for each state...}

        case State of

          ssChasing       : begin

                              {we’re chasing the player, so move the sprite

                               closer to the player sprite’s position}

                              if Sprites[PLAYERIDX].XPos > XPos then

                                XPos := XPos + 1;

                              if Sprites[PLAYERIDX].XPos < XPos then

                                XPos := XPos – 1;

                              if Sprites[PLAYERIDX].YPos > YPos then

                                YPos := YPos + 1;

                              if Sprites[PLAYERIDX].YPos < YPos then

                                YPos := YPos – 1;

                            end;

          ssEvading       : begin

                              {we’re evading the player, so move the sprite

                               farther away from the player sprite’s position}

                              if Sprites[PLAYERIDX].XPos > XPos then

                                XPos := XPos – 1;

                              if Sprites[PLAYERIDX].XPos < XPos then

                                XPos := XPos + 1;

                              if Sprites[PLAYERIDX].YPos > YPos then

                                YPos := YPos – 1;

                              if Sprites[PLAYERIDX].YPos < YPos then

                                YPos := YPos + 1;

                            end;

          ssRandomMoves   : begin

                              {we’re moving randomly, but we don’t want to move

                               each frame of animation or the sprite will look

                               like it’s having a seizure}

                              if timeGetTime – CurPatternTimer > PatternIdxChange*3

    						 then

                              begin

                                {modify the horizontal and vertical velocity}

                                XVel := Random(3) – 1;

                                YVel := Random(3) – 1;

                                {reset the timer}

                                CurPatternTimer := timeGetTime;

                              end;

                              {add the current velocity to the horizontal and

                               vertical positions}

                              XPos := XPos + XVel;

                              YPos := YPos + YVel;

                            end;

          ssPatternMoves  : begin

                              {if it is time to change the current pattern step

                               index...}

                              if timeGetTime–CurPatternTimer > PatternIdxChange then

                              begin

                                {increment to the next step}

                                CurPatternIdx := CurPatternIdx + 1;

                                {if we’ve reached the end of this pattern...}

                                if CurPatternIdx > 31 then

                                begin

                                  {choose a new pattern}

                                  CurPattern := Random(8);

                                  {initialize to the first step of the pattern}

                                  CurPatternIdx := 0;

                                end;

                                {reset the pattern timer}

                                CurPatternTimer := timeGetTime;

                              end;

                              {as stated above, pattern movement is implemented

                               as a direction, so modify the sprite’s position

                               according to the direction at this pattern’s

                               movement step}

                              case Patterns[CurPattern, CurPatternIdx] of

                                0 : begin

                                      XPos := XPos + 1;

                                    end;

                                1 : begin

                                      XPos := XPos + 1;

                                      YPos := YPos + 1;

                                    end;

                                2 : begin

                                      YPos := YPos +1;

                                    end;

                                3 : begin

                                      XPos := XPos – 1;

                                      YPos := YPos + 1;

                                    end;

                                4 : begin

                                      XPos := XPos – 1;

                                    end;

                                5 : begin

                                      XPos := XPos – 1;

                                      YPos := YPos – 1;

                                    end;

                                6 : begin

                                      YPos := YPos – 1;

                                    end;

                                7 : begin

                                      XPos := XPos + 1;

                                      YPos := YPos – 1;

                                    end;

                              end;

                            end;

        end;

      end;

      {clip to the boundaries of the screen}

      if XPos < 0 then

        XPos := 0;

      if XPos > DXWIDTH – 21 then

        XPos := DXWIDTH – 21;

      {we’ll keep sprites from moving up where the status is displayed}

      if YPos < 30 then

        YPos := 30;

      if YPos > DXHEIGHT – 3 then

        YPos := DXHEIGHT – 3;

    end;

    initialization

      {set up the personality tables}

      Personalities[perAggressive] := AggressivePersonality;

      Personalities[perDefensive] := DefensivePersonality;

      Personalities[perTactical] := TacticalPersonality;

    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.