Walking NPC with Animation

Started by Khameton, Sun 10/08/2025 13:57:09

Previous topic - Next topic

Khameton

I'm trying to create an NPC cat in the background that walks along specified coordinates and plays a short animation when it reaches them. I used the code from this post, and it works perfect for walking. Is there an easy way to add a non-blocking animation when each coordinate is reached? I would be very grateful for a code example. Thanks.

Here is my code:
Code: ags
int xx[5];
int yy[5];
int Timer = 0;

function room_Load()
{
  cCat.Walk(250, 120, eNoBlock, eWalkableAreas);
  xx[0] = 250; // Start
  yy[0] = 120;
  xx[1] = 80; // POI 1
  yy[1] = 140;
  xx[2] = 120; // POI 2
  yy[2] = 100;
  xx[3] = 190; // POI 3
  yy[3] = 30;
  xx[4] = 220; // POI 4
  yy[4] = 150;
}

function room_RepExec() {
  if (Timer) { // if the character is waiting...
    Timer--; // decrease our timer
    if (!Timer) cCat.Walk(xx[0], yy[0], eNoBlock, eWalkableAreas);
    return;
  }
  if (cCat.Moving) return;
  if ((cCat.x == xx[0]) && (cCat.y == yy[0])) { // Start
    int chance = Random(99) + 1; // 1-100
    if (chance <= 25) cCat.Walk(xx[1], yy[1], eNoBlock, eWalkableAreas); // 25% chance of walking to POI 1-2-3-4
    if (chance >= 26 && chance <= 50) cCat.Walk(xx[2], yy[2], eNoBlock, eWalkableAreas);
    if (chance >= 51 && chance <= 75) cCat.Walk(xx[3], yy[3], eNoBlock, eWalkableAreas);  
    if (chance >= 76 && chance <= 100) cCat.Walk(xx[4], yy[4], eNoBlock, eWalkableAreas);
  }
  else if ((cCat.x == xx[1]) && (cCat.y == yy[1])) { // POI 1
    int chance = Random(99) + 1; // 1-100
    if (chance <= 33) cCat.Walk(xx[2], yy[2], eNoBlock, eWalkableAreas); // 33% chance of walking to POI 2-3-4
    if (chance >= 34 && chance <= 66) cCat.Walk(xx[3], yy[3], eNoBlock, eWalkableAreas);
    if (chance >= 67 && chance <= 100) cCat.Walk(xx[4], yy[4], eNoBlock, eWalkableAreas);  
  }
  else if ((cCat.x == xx[2]) && (cCat.y == yy[2])) { // POI 2
    int chance = Random(99) + 1; // 1-100
    if (chance <= 33) cCat.Walk(xx[1], yy[1], eNoBlock, eWalkableAreas); // 33% chance of walking to POI 1-3-4
    if (chance >= 34 && chance <= 66) cCat.Walk(xx[3], yy[3], eNoBlock, eWalkableAreas);
    if (chance >= 67 && chance <= 100) cCat.Walk(xx[4], yy[4], eNoBlock, eWalkableAreas); 
  }
  else if ((cCat.x == xx[3]) && (cCat.y == yy[3])) { // POI 3
    int chance = Random(99) + 1; // 1-100
    if (chance <= 33) cCat.Walk(xx[1], yy[1], eNoBlock, eWalkableAreas); // 33% chance of walking to POI 1-2-4
    if (chance >= 34 && chance <= 66) cCat.Walk(xx[2], yy[2], eNoBlock, eWalkableAreas);
    if (chance >= 67 && chance <= 100) cCat.Walk(xx[4], yy[4], eNoBlock, eWalkableAreas); 
  }
  else if ((cCat.x == xx[4]) && (cCat.y == yy[4])) { // POI 4
    int chance = Random(99) + 1; // 1-100
    if (chance <= 33) cCat.Walk(xx[1], yy[1], eNoBlock, eWalkableAreas); // 33% chance of walking to POI 1-2-3
    if (chance >= 34 && chance <= 66) cCat.Walk(xx[2], yy[2], eNoBlock, eWalkableAreas);
    if (chance >= 67 && chance <= 100) cCat.Walk(xx[3], yy[3], eNoBlock, eWalkableAreas); 
  }
}

Khris

Try this (untested!):

Code: ags
int xx[4];
int yy[4];
bool cCatWasMoving;

function room_Load()
{
  xx[0] = 80; // POI 1
  yy[0] = 140;
  xx[1] = 120; // POI 2
  yy[1] = 100;
  xx[2] = 190; // POI 3
  yy[2] = 30;
  xx[3] = 220; // POI 4
  yy[3] = 150;
  cCat.Walk(250, 120);
}

function room_RepExec() {
  // as soon as the cat has reached its current POI target, this block should run
  if (!cCat.Moving && cCatWasMoving) {
    // play animation
    cCat.LockView(CAT_VIEW2);
    cCat.Animate(0, 5, eOnce, eNoBlock);
  }

  // as soon as the animation has finished, this block should run
  if (!cCat.Moving && !cCat.Animating) {
    cCat.UnlockView();
    // send to random POI
    int target = Random(3);
    // pick new target cat's already there
    while (cCat.x == xx[target] && cCat.y == yy[target]) target = Random(3);
    cCat.Walk(xx[target], yy[target]);
  }

  cCatWasMoving = cCat.Moving;
}

The POI code can be shortended considerably as you can see. I also removed the Timer variable because it didn't do anything.
Also, eNoBlock and eWalkableAreas are the default values iirc and can be omitted.

Khameton

It works well, pretty much what i wanted. But now there's a new problem - when each POI is reached, specific events should occur (poi1: plays with a ball, poi2: sleeps, poi3: says "meow", etc.). How to link a specific coordinate to a specific animation/say command?

And now for the next question. How to add a 3-5 second pause after UnlockView and before moving to next POI? The Wait command blocks my main character, and i can't figure out where to put the timer check.

Khris

Here's the new version:

Code: ags
int xx[4];
int yy[4];
bool cCatWasMoving;

function room_Load()
{
  xx[0] = 80; // POI 1
  yy[0] = 140;
  xx[1] = 120; // POI 2
  yy[1] = 100;
  xx[2] = 190; // POI 3
  yy[2] = 30;
  xx[3] = 220; // POI 4
  yy[3] = 150;
  cCat.Walk(250, 120);
}

function room_RepExec() {

  // is cat at poi?
  int poi = -1;
  for (int i = 0; i < 4; i++) {
    if (cCat.x == xx[i] && c.Cat.y == yy[i]) poi = i;
  }

  // as soon as the cat has reached its current POI target, this block should run
  if (!cCat.Moving && cCatWasMoving) {
    if (poi > -1) {
      switch(poi) {
        case 0:
           // play animation
           cCat.LockView(CAT_VIEW2);
           cCat.Animate(0, 5, eOnce, eNoBlock);
           break;
        case 1:
           // etc
      }
      SetTimer(1, GetGameSpeed() * 3); // three second pause
    }
  }

  // as soon as the animation has finished and the timer has expired, this block should run
  if (!cCat.Moving && !cCat.Animating && IsTimerExpired(1)) {
    cCat.UnlockView();
    // send to random POI
    int target = Random(3);
    // pick new target if cat's already there
    while (target == poi) target = Random(3);
    cCat.Walk(xx[target], yy[target]);
  }

  cCatWasMoving = cCat.Moving;
}


Khameton

#4
QuoteHere's the new version

Now the cat goes to x250, y120 and nothing else happens. In my head it should look something like this:


Khris

Right, the timer has to expire once at the start of the sequence I guess.
Try adding SetTimer(1,1); to room_Load.

Khameton

Yes! It works perfectly now, although it's clearly not beginner level. Thank you very much :)

SMF spam blocked by CleanTalk