Hello everyone.
I'm trying to experiment with my latest game. I want it to play out as if it were a normal adventure game but with a ghost that randomly appears every now and then and chases you if you get too close.
What I'm trying to achieve is changing the state of the ghost (or even create a state machine if I can). So far I have it set down to say if the player is 30 pixels away from the ghost, the ghost chases the player. If the player is >30 pixels, it stops chasing you and returns to it's random movement.
function repeatedly_execute()
{
//CHOST CHASE PLAYER
if (player.x - cGhost.x < 30 && player.y - cGhost.y < 30)
{
EnemyChase = true;//Set state
cGhost.SetWalkSpeed(-1, -1);
cGhost.FollowCharacter(player, 0, 0);
}
else //GHOST RANDOM MOVEMENT
{
EnemyChase = false;//Set state
cGhost.SetWalkSpeed(-3, -3);
int walkx = Random(System.ViewportWidth + cGhost.x);
int walky = Random(System.ViewportHeight + cGhost.y);
if (GetWalkableAreaAt (walkx, walky) == 1)
{
cGhost.Walk(walkx + 73, walky - 73, eNoBlock, eWalkableAreas);
}
}
I'm not exactly sure how this sorta thing works with AGS, but any help would be greatly appreciated! :)
IMHO the simplest way to implement states is this:
1. Make "state" variable which defines the state the actor currently is in.
2. Write "UpdateState" function (accepts actor ID or pointer, anything that lets you read the state and control it), and run UpdateState for each actor from rep-exec (room's or global).
3. In UpdateState make a switch based on state (or a big if/else if list).
4. Under every case first of all test for conditions of changing state; if change condition does not trigger, run state actions.
Arbitrary example:
void UpdateState(Character* actor)
{
switch (CharStates[actor.ID])
{
case IDLE_STATE:
// first test state-change condition:
if ( CalcDistance(actor, player) < THRESHOLD )
{
CharStates[actor.ID] = CHASE_STATE;
break;
}
// do idle animation, etc
break;
case CHASE_STATE:
// first test state-change condition:
if ( CalcDistance(actor, player) >= TOO_FAR_AWAY)
{
CharStates[actor.ID] = IDLE_STATE;
break;
}
// do moves, animation, play spooky sounds etc
break;
}
}
Thanks for your reply CW. As all the logic makes sense to me the implementation does not. Not too familiar with AGS scripting, still getting the hang of it compared to GMS.
I'm getting an error stating: EnemyScript.asc(8): Error (line 8): CharStates is not an array
Not sure what this is referring to be honest.
You need a suitable global array (and enum):
// global script header
enum eCharState {
IDLE_STATE, CHASE_STATE
};
// top of global script
eCharState CharStates[MAX_CHARACTERS];
My code was an example pseudo-code, it was not complete or usable as-is. I invented some constants, variables and functions. You may replace these with your own. For example, if you have only 1 ghost, then you do not need array and may make 1 regular int variable.
Spoiler
int GhostState;
void UpdateGhostState()
{
switch (GhostState)
{
case IDLE_STATE:
// first test state-change condition:
if ( CalcDistance(cGhost, player) < THRESHOLD )
{
GhostState = CHASE_STATE;
break;
}
// do idle animation, etc
break;
case CHASE_STATE:
// first test state-change condition:
if ( CalcDistance(cGhost, player) >= TOO_FAR_AWAY)
{
GhostState = IDLE_STATE;
break;
}
// do moves, animation, play spooky sounds etc
break;
}
}
Oh I understood that. Sorry I didn't reply sooner, I got it working with some tinkering. I did notice that the state changes are a little slow with the ghost character, this possibly could be caused by the animations if they are blocking the code somewhere(I think?). I'm using the built in FollowCharacter code so that may be the case? It also seems to travel to the players last known co-ordinates if the player stops moving rather than a continuous chase or to the players updated position. Is this something to do with AGS's path finding or something else? I did however find a few modules about following characters so I may have to use one of those.
I've found it is better to just send the chasing character to the player's current x,y location on every loop in re-exec than to use the built-in "follow" function if your goal is an aggressive chasing enemy.
It most probably is better. This may be the case where I may just have to do that.