Need Help With Having an NPC Follow the Hero

Started by lefthandedhero, May 02, 2023, 07:36:06 PM

Previous topic - Next topic
May 02, 2023, 07:36:06 PM Last Edit: May 21, 2023, 04:38:46 PM by lefthandedhero
Hello.

I am new to using the Solarus engine. I want to create an NPC that follows the hero as a companion by closely following behind the hero and following the same path as the hero; the type of movement that's used in A Link to the Past when Link escorts Zelda out of Hyrule Castle and when Marin accompanies Link for part of Link's Awakening, and is at as old as the classic snake game genre.

Any suggestions for implementing this in Solarus?


UPDATE 1: After watching some videos of the section of A Link to the Past where Link escorts Zelda out of Hryule Castle for reference, I observed some details of Zelda's movement that may help: upon entering any new area or exiting a set of stairs, Zelda always starts one in-game step behind Link, stays still after Link's first in-game step, then starts moving after Link's second in-game step, with Zelda's steps being the step Link took two steps previously. When Link stops moving, Zelda immediately stops moving at the same time, and when Link resumes moving, Zelda immediately resumes moving.

After observing this, the plan I have created is to essentially store the hero's movements in a linked list of maximum length 1: upon entering a new map section, the linked list will be empty. When the hero moves, their "step" is pushed into the list, and once the list is full, the non-playing character does each step that is "popped" out of the linked list. When the hero stops moving, the non-playing character stops moving.

I know how to create a linked list in Lua, so there are two things I still need:
1. The ideal method of storing each in-game step in the linked list.
2. How to implement, "while (the hero is moving) {}".

Any suggestions for solving those two things?

Hello, you can use the hero:on_position_changed() event, which is called each frame Link has moved.


-- <initialization stuff where you get the hero and NPC entities>

function hero:on_position_changed(x, y, layer)
--update your list here and move the NPC accordingly

end

An alternative way is to make a target movement towards the hero, and stop that movement when the distance between the NPC and the hero gets below some threshold. I think it is done that way in the Zelda A Link to the Dream project.
Your choice, I guess it depends on whether you want the NPC to follow exactly the same path as the hero or not.

Quote from: PhoenixII54 on May 05, 2023, 11:30:22 AM
Hello, you can use the hero:on_position_changed() event, which is called each frame Link has moved.


-- <initialization stuff where you get the hero and NPC entities>

function hero:on_position_changed(x, y, layer)
--update your list here and move the NPC accordingly

end


Thank you very much. This is a big help.

Quote from: Christopho on May 05, 2023, 03:20:57 PM
An alternative way is to make a target movement towards the hero, and stop that movement when the distance between the NPC and the hero gets below some threshold. I think it is done that way in the Zelda A Link to the Dream project.
Your choice, I guess it depends on whether you want the NPC to follow exactly the same path as the hero or not.

Thank you. I do want the NPC to follow the same path as the hero; it's important for another component of the game that I'm trying to make that the NPC follow the same path. But I may fall back on that method if I can't get my current plan to work.

Using the advice above, I created the following code. Note that "Follower" is the name of the NPC:


-- Table to store the hero's old postion (List[0] = x, List[1] = y, List[2] = layer):
  List = {}
  -- Follower Movement:
  function hero:on_position_changed(x, y, layer)
    -- If not the hero's first movement:
    if List[0] ~= nil then
      -- Move Follower to the old coordinates
      local movement = sol.movement.create("target")
      -- Set the previous position as the target
      movement:set_target(List[0], List[1])
      -- Make the movement speed match the hero
      movement:set_speed(88)
      -- Move
      movement:start(Follower)
    end
    -- Record the hero's new position
    List[0] = x
    List[1] = y
  end


The current code has two main problems:

1. Follower does not start moving until the moment the hero stops moving, when the idea was that the follower would move while the hero moves and stop when the hero stops.

2. Follower does not stop moving, as if the current target is the hero's current position when it should be the hero's previous position.

Where have I gone wrong in my implementation that is causing these problems?

I don't see any obvious mistake (maybe i would have organized the list in the style 1 entry = an {x=x,y=y} pair but that's just a coding preference) so my guess is that your movement gets overwritten each frame you move and doesn't have time to even start until you stop moving.
So what you should try to do is to detect if the follower NPC already has a movement and update it's target (if possible) instead of recreating a new one each frame, and only create it again if it it reached its actual destination when the hero has stopped moving.

Quote from: PhoenixII54 on May 09, 2023, 11:40:17 AM
I don't see any obvious mistake (maybe i would have organized the list in the style 1 entry = an {x=x,y=y} pair but that's just a coding preference) so my guess is that your movement gets overwritten each frame you move and doesn't have time to even start until you stop moving.
So what you should try to do is to detect if the follower NPC already has a movement and update it's target (if possible) instead of recreating a new one each frame, and only create it again if it it reached its actual destination when the hero has stopped moving.

Thank you. That is most likely the reason for the first problem. I am currently working on modifying it as suggested.

May 21, 2023, 06:01:55 PM #7 Last Edit: May 23, 2023, 11:15:49 PM by lefthandedhero
Using the suggestions above, I have refined the code. Here is the current version:

-- Follower's Movement 
  -- Initialize table to store the hero's old coordinates <List[0] = x, List[1] = y>:
  List = {}
  -- Obtain the hero's movement speed
  local speed = hero:get_walking_speed()
  -- Initialize the movement
  local movement = sol.movement.create("target")
  movement:set_target(Follower:get_position())
  movement:set_speed(speed)
  movement:start(Follower)
  -- Movement pathing:
  function hero:on_position_changed(x, y, layer)
  -- Update list and move accordingly
    -- If this is not the hero's first movement:
    if List[0] ~= nil then
      -- Move the NPC to the old coordinates
      movement:set_target(List[0], List[1])
    end
    -- Record the hero's current position:
    List[0] = x
    List[1] = y
  end


Initially, the movement works exactly as I would like. However, there are three problems that appear after the hero either stops moving or changes direction:

1. The NPC "Follower" continues moving after reaching the target position.
2. It is impossible for the hero to move backwards since Follower is in the way.
3. When I move the hero in a new direction, I want Follower to continue moving forward before then moving in the new direction, as I want Follower to follow the exact same path as the hero. Follower does not do this; I suspect this problem is related to problem 1.

Three questions:
1. How do I remove collision between the NPC Follower and the hero?
2. Any suggestions for how to make Follower stop moving when the hero stops moving?
3. Any idea what could be causing the third problem?

Incidentally, after playing Ocean's Heart recently, I reached a point in the game where an NPC follows the hero, and the NPC does exactly what I'm trying to get Follower to do: they follow the exact path the hero just walked. Does anyone know how Ocean's Heart did this?

UPDATE 2: I added the line, "Follower:set_traversable(true)", so now the hero can pass through Follower. The other two problems remain; Follower continues moving either left-and-right if facing up or down or up-and-down if facing left or right after the hero stops moving, and the movement only works the way that I want it to until the hero changes direction:

When I have the hero go in a new direction, Follower should move to the last place the hero was in the first direction before moving in the new direction, since I want Follower to follow the same path as the hero. Instead, Follower stands still for a while and then catches up. Any idea what is causing this?

There's another problem: despite Follower's speed being set to the hero's speed in the code, when I tested it, Follower slowly begins falling behind.

UPDATE 3: I set Follower to ignore obstacles to see if that was the reason it stood still whenever the hero turned to the left or right. It was the reason, and it revealed a different problem: despite the existence of the table being to record where the hero was previously, whenever the hero stops moving, follower stops at the hero's current position, not the hero's position a step previously.

Not sure, but what i would guess is that the on_position_changed event gets triggered the next frame after you stopped moving, so maybe try to have one extra entry in the table and use two indices: one for the current hero step and one for the
NPC that is always 2 steps behind the hero -modulo will be your friend.
By the way, OH uses a standard .solarus zip file so you should be able to check the source code

Quote from: PhoenixII54 on May 29, 2023, 09:02:36 AM
Not sure, but what i would guess is that the on_position_changed event gets triggered the next frame after you stopped moving, so maybe try to have one extra entry in the table and use two indices: one for the current hero step and one for the
NPC that is always 2 steps behind the hero -modulo will be your friend.
By the way, OH uses a standard .solarus zip file so you should be able to check the source code

Thank you.

I asked the developer of Ocean's Heart for the code they used for this, and they provided it, so I have replaced my code with it.

September 06, 2023, 11:52:13 AM #10 Last Edit: September 11, 2023, 06:48:30 AM by Pamala Reyna
Try moving the NPC to the hero position 1 or 2 frames earlier. This produces the following delay effect.  Bad Time Simulator