Edit Tutorial6
Edit Tutorial6
In order to make the pawn can turn dead, you need to add the order to be executed
when some predetermined conditions met. This is accomplished through the
AttributeOrder function. The following function call (placed at the start() function)
make the monster have a health value of 10 and will execute the order “Death” when
health goes to 0.
AttributeOrder("enemy_health", 10, "Death"); // give monster
health
The above order simply instruct the system to play the motion “die_1” and announce
the event “Enemy1Dead”. Now compile the level and go to attack the terrorist for
several times to see the death sequence.
1
World Editing 6
Introduction to 3D Game Development
AttackStart[ ()
{
debug( "Stop! Come here!!");
}]
In the above, HostilePlayer(true); is the call that tells the system that the
monster is will attack player. And SetFOV(90); function defines the field of view of
the monster. Next call FindTargetOrder(300, "FoundTarget", "health");
2
World Editing 6
Introduction to 3D Game Development
will set up the FoundTarget function to be executed when it spots the player. The first
parameter 300 is the sight distance of the monster i.e. how far it can see in front of it.
Together the above statements will set up the vision of a monster. The FoundTarget
function will be executed once the player is getting into the sight of the player. It
consists of simply another function call, LowLevel("AttackStart");
Now this needs a little explanation. Basically pawn scripting inside RF can be broadly
classified into two categories: high level and low level call. All the scripts order we
have discussed before is high level ones, meaning that they may take several frames ,
or even a number of seconds to finished. For the low level script order, it refers to the
fact that such calls will be executed on a per-frame basis. An implication is that these
scripts should not use up a lot of CPU cycles or otherwise the program will run very
slow.
The LowLevel call “AttackStart” simply display the message “Stop! Come here! “.
We would like the pawn to have more intelligence when it spots the player. A typical
behavior is chasing. It is relatively easy to assign chasing action in RF scripts. The
following AttackStart scripts will implement the chasing action.
AttackStart[ ()
{
self.ThinkTime = 0.1; // start thinking on next frame
UpdateEnemyVis(); // upadte position of enemy
self.ideal_yaw = enemy_yaw; // set yaw direction to fly
ChangeYaw(); // rotate to face enemy
self.yaw_speed = 40;
walkmove(current_yaw, 40);
SetHoldAtEnd(true); // stop current animation
if (self.animate_at_end)
{
Animate("slump"); // select jumping animation
SetHoldAtEnd(true); // repeat animation
}
} ]
The first call simply indicated that frequency of the low level function to be executed.
A value of 0.1 means 10 times per second, according to the documentation of RF.
As the player is constantly moving, thus the monster need to update the position of the
player so as to calculate the needed actions. The call UpdateEnemyVis() will do this.
After this call, a parameter enemy_yaw will keep the yaw rotation which will adjust
the monster’s orientation so as to face opposite to the player. We will use this rotation
to adjust the monster so that it will align itself to face the player. The ChangeYaw()
will actually do the adjustment whereas the following call walkmove() will translate
the monster 40 texels in the current yaw direction. The result is that the monster will
chase along the player.
3
World Editing 6
Introduction to 3D Game Development
Now our pawn can run towards us and keep chasing us. But it still lack the attack
capability to make the game interesting. In general, there are two kinds of attack:
melee and missile(ranged). Range attack is more difficult as the monster would also
be capable of melee attack also. In this tutorial, we will study the simpler one, which
is melee attack. Range attack is left as an exercise to you, you may refer to the robot.s
script file for possible missile attack implementation.
To make the monster attack, the condition is that the monster is close enough to its
enemy, which is the player in this case. The idea is:
if enemy close enough
Attack the enemy
else
Get closer to the enemy
Also to make it more realistic, we add the feature that if the enemy is dead (due to the
attack of the monster), the monster will come back to its usual life, which is patrol
again.
If enemy is dead
Return to patrol
RF provide the script variable self.enemy_range, which is a read only attribute that
tells how close is the enemy to the pawn entity. In addition, to indicate the attack is
going on, we have to show the attack animation. As the required actions are getting
more complicated now, we put most of the actions in another low level order and
make the attackStart call it after its execution. The complete script (enemy2.s) is as
follow:
{
start[ ()
{
Console(true);
AttributeOrder("enemy_health", 10, "Death"); // give monster
health
HostilePlayer(true); // will attack player
SetFOV(90); // field of view of monster
FindTargetOrder(300, "FoundTarget", "health"); // order executed
// when spots player
PlayAnimation("idle", true, "");
Delay("idle", 3, "");
RotateMoveToPoint("walk", 100, 20, true, "footsteps\\m1.wav");
MoveToPoint("walk", 50, "footsteps\\m1.wav");
NewOrder("Patrol");
}]
FoundTarget[ ()
{
LowLevel("AttackStart"); // attack functions are low level
}]
4
World Editing 6
Introduction to 3D Game Development
AttackStart[ ()
{
self.ThinkTime = 0.; // start thinking on next frame
self.think = "Attack";
lowhealth = self.health; // save health level when
// starting attack
self.yaw_speed = 40;
attack_state = 1; // do a melee attack
attack_sound_time = time;
Animate("slump");
SetHoldAtEnd(false); // repeat animation
}]
Attack[ ()
{
self.ThinkTime = 0.1; // Pawn's AI will be run 10x per second
UpdateEnemyVis(); // update position of enemy
if (self.health<=0)
{
HighLevel("Death"); // dead
return 0;
}
self.ideal_yaw = enemy_yaw; // check enemy orientation
ChangeYaw(); // rotate to face enemy
self.ideal_pitch = enemy_pitch;
ChangePitch();
if (walkmove(current_yaw, 40) = false) // if move towards
// player is blocked
{ // assume no obstacle now
debug("move towards player blocked");
if (self.enemy_range<60)
{
SetHoldAtEnd(true); // transit to attack animation
if (self.animate_at_end)
{
Animate("shoot");
SetHoldAtEnd(false);
}
Damage(2, "health"); // keep damaging target per frame
// damage!)
}
if (EnemyExist("health") < 3) // enemy vanish
{
debug("resume patrol now"); // resume patrolling
Animate("walk");
SetHoldAtEnd(false);
HighLevel("resumePatrol"); // enemy is gone or dead
return 1;
}
}
else
{
if (self.animate_at_end)
{
debug("chasing");
Animate("slump");
SetHoldAtEnd(true);
}
}
} ]
5
World Editing 6
Introduction to 3D Game Development
Patrol [ ()
{
NextPoint();
RotateMoveToPoint("walk", 100, 20, true, "footsteps\\m1.wav");
MoveToPoint("walk", 100, "footsteps\\m1.wav");
RestartOrder();
} ]
resumePatrol[ ()
{
FindTargetOrder(300, "FoundTarget", "health");
RotateMoveToPoint("walk", 100, 20, true, "footsteps\\m1.wav");
MoveToPoint("walk", 100, "footsteps\\m1.wav");
NewOrder("Patrol");
}]
Death[ ()
{
AnimateStop("die", 3.58, "");
SetEventState("Enemy1Dead", true);
}]
I have “bolded” the new additions in the above script. We use the call Damage(2,
"health"); to inflict damage on the enemy by 2 life points. Also we use
EnemyExist("health") < 3 to check whether the player is alive or not. If the
return value is less than 3, then it means the target simply no longer exists or
irrelevant (check out on the Pawn scripting reference of RF documentation under the
folder “doc”).
As we may have to switch between attack and running animation(for running towards
the enemy) in successive frames, all the animation playback is on a single turn basis
i.e. each animation is play only once and we will pick according animation base on
the situation e.g. if we are close enough, we will play the attack animation, otherwise
the running animation will be played. Thus the set of scripts
if (self.animate_at_end)
{
Animate("slump");
SetHoldAtEnd(true);
}
is used extensively throughout the script code. The above call means that we first
check whether the playback is at the end of not. If yes, then we will switch the
according animation. This will make the motion transition more smooth to the player.
Finally if the player is dead, the monster has to go back to its own post. The code
if (EnemyExist("health") < 3) // enemy vanish
{
debug("resume patrol now"); // resume patrolling
Animate("walk");
SetHoldAtEnd(true);
HighLevel("resumePatrol"); // enemy is gone or dead
return 0;
6
World Editing 6
Introduction to 3D Game Development
}
instruct to the monster to do so.As patrolling code is high level script, we use the
HighLevel(“resumePatrol”) to perform the switching.
Last note is that in the test run, you would probably see that the attack damage is
performed at such a fast rate that the player is die very fast. Why? How could it be
fixed? This is also left as an exercise to the reader. You may check the robot.s file for
the solution.