[Solved] Multiple sprites following player and avoiding obstacles

Hi there,

I’m looking for advice on how to approach this problem:

  • a variable number of “follower” sprites that queue behind the player: sprite 1 behind the player, sprite 2 behind sprite 1 and so on
  • the player and the sprite move across a top-down tiled map
  • followers should avoid obstacle tiles between them and the next sprite
  • the player and followers move orthogonally (no diagonals)

What would be the best approach? Raycasting, path finding or something else?

Thanks for any suggestions

Edit: to clarify my worry is that path finding (A* or breath-first search) are too expensive for this case and, before I invest in learning them, I want to be sure they are a viable solution

Depends on how dynamic your obstacles are. If your followers can just do what the player did, only slightly delayed…

And to get a pathfinding performance problem you would really have to have a crazy amount of followers :smiley:

Yes, all obstacles are static (walls and such) and yes, I want my followers just to do what player does only delayed. I plan to have max 10-20 followers. So should I look into pathfinding or are there simpler solutions? Thanks

Just store your player actions.
Player goes right → up → up → right → left
Follower1 goes … right → up → up → right
Follower2 goes … … right → up → up
etc.

No need for pathfinding since you know exactly what to do.

Thank you! This sounds what I was looking for.
In pseudo code:

  • on keyboard input modify player’s velocity and store it’s direction
  • start a timer
  • after time, modify the follower velocity by passing the player’s direction

That’s an option, but you can’t be sure your velocity and direction result in the same position. Physics is not very deterministic.
I would just store the actual player position every frame, for 10 seconds or so, which gives an array of 600 positions (at 60Hz). Push the new player position, and shift the array.
Follower1 then reads position[570],
Follower2 then reads position[540],
Follower3 then reads position[510],
etc.

No need for timers.Every follower is just 0.5 sec (30 frames) behind its leader.

1 Like

Thank you! That’s a clever idea. It’s like building a timeline or history of positions and the followers are always 0.5s late. I’ve spent some hours yesterday and today trying to implement it, but I’m not getting everything right. If I keep pushing and shifting every frame my array is stuck at 1 position, but if I log all the position once at the beginning (like I’m doing now) obviously I don’t get the followers to move until the the arrays has reached a length of 600 (I now get an error when trying to access this.currentPosition too early)

export default class Play extends Phaser.Scene {
     create() {
        this.currentPosition = [];
        this.timer = 0;
        //create player, followers and so on
     }
    update(time, delta) {
        if(this.timer < 10000) {                 
                 this.currentPosition.push([this.player.container.x, this.player.container.y]);
                 this.timer += delta;
            }
            if(this.player.container.body.velocity.x != 0 || this.player.container.body.velocity.y != 0) {
                 this.currentPosition.shift();
                 this.currentPosition.push([this.player.container.x, this.player.container.y]);
            }

            for (var i = this.followers.getLength() - 1; i >= 0; i--) {
        	 var p = this.currentPosition[this.currentPosition.length - 30 * i];
             var pp = new Phaser.Math.Vector2(p[0],p[1])
             this.scene.physics.moveToObject(followers[i].container, pp);
		}
     }
}

So initialize the array:

var positions = [];
positions[600] = [x,y];

Lose the timer and player movement check.
Just every frame push, and shift.
If (position[570] == “undefined”) the follower shouldn’t move yet.

And you shouldn’t use physics to update the follower, set the body x,y.

Thank you for taking the time, I appreciate that. Now it works how I wanted. I kept the player movement check, otherwise all the followers would overlap the player once it stopped moving. With the check they queue behind the player if it stops.

export default class Play extends Phaser.Scene {
     create() {
        this.currentPosition = [];
	    this.currentPosition[600] = [this.player.container.x, this.player.container.y];
     }

update(time,delta) {
   if(this.player.container.body.velocity.x != 0 || this.player.container.body.velocity.y != 0) {
         this.currentPosition.shift();
         this.currentPosition.push([this.player.container.x, this.player.container.y]);	                 
         for (var i = 0; i < this.enemies.getChildren().length; i++) {
             var p = this.currentPosition[this.currentPosition.length - 30 * (i+1)];
    	      if(p != null) {
    	           this.enemies.getChildren()[i].container.body.x = p[0];
    	           this.enemies.getChildren()[i].container.body.y = p[1];
    	     }
         }
    }
}

You’re very welcome.
Help the community by showing your results, if you can :smile:

Btw, this only works for 60Hz, if you run this on tablets or a 144Hz or so monitor you’ll run into problems, but that’s for later I guess :slight_smile:

PS. You just shouldn’t use the last 30 frames. Now if the player stops, the followers stop. Lose the player movement check, and let them bunch up 30 frames behind…

Sure, here it is: https://twitter.com/piotrone/status/1370451208985571328

Thanks for the heads up, I’m not planning to go on tablets, but I assume I could read the current frame rate and adjust the values accordingly.

I’m not sure I follow. I want them to stop when the player stops, otherwise they’ll reach the player’s position and overlap with the player, hiding it.

Btw, I don’t know what’s Discourse’s etiquette and if I should mark as solution one of your posts or one of mine with the code, let me know what you prefer

1 Like

I think the easiest way is something like this:
If the player is not moving:
positions.splice(570, 0, positions[570]); positions.shift();
This copies 570 and moves the last 30 positions right, and then shifts like normal.
That way all the followers bunch up at follower1. They never reach the player.
Once the player moves again, go back to push and shift.

Whichever you feel answers your question best. If you feel your code is more clear then my abstract :slight_smile: It’s just for the convenience of those after us.

1 Like

@Milton I finally published the game that uses your solution for the queue we are discussing here. Your user name is in the credits and links to your Discourse profile.

It’s a free browser game of about 10 minutes of gameplay. The mechanic that uses the queue solution is a bit hidden, but it’s there: The Lives of Others by Piotr.one

Thanks a lot again!

1 Like