1. Hello Guest! It's with a heavy heart that we must announce the removal of the Legacy GMC Archive. If you wish to save anything from it, now's the time! Please see this topic for more information.
    Dismiss Notice

Help needed with npc moving from a room to another

Discussion in 'Programming' started by TulioHenry, Nov 27, 2019.

Tags:
  1. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    Well i was thinking in adding a way that npc would be changing rooms in certain time of the day and you would be able to follow them during this process, like in games as Stardew Valley.

    Like the npc would exit from somewhere in a room, reach the trigger to go to the next room, and in the other room he would follow another path to the desired place, but if you follow him you will see where he goes.

    The problem is that i have certain knowledge in path creation, but i don't know how i can keep track of the npc position and how i can say to him follow the other path while the player is in another room.
     
  2. Paskaler

    Paskaler Member

    Joined:
    Jul 4, 2016
    Posts:
    317
    I think, you could have some sort of global controller object, which can keep track of all NPCs that leave the room. You could then possibly use the formula: time = distance / speed to calculate how much time it would take for them to get to their destination in the other room. The controller object would then tick the time variable down until it reaches zero for each NPC not in the room. Once the player enters the room you could extract the time for each NPC present in the room to figure out where on the path the NPC should be.

    For storing the NPCs in the controller, I think an array would suffice, the array size would be the room count, and at every index of array, you could store a sub array with two indices: the first some unique identifier for the NPC(I don't think the built in id variable would work, as it might not be consistent across rooms) and the second the time remaining.
     
  3. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    2,009
    If you want a complete simulation of where NPCs are while transiting in other rooms, then as explained you need some kind of controlling object that calculates that simulation to you. Plus each NPC of course needs a schedule of in-game times when they need to leave for their next destination.

    I haven't played Stardew Valley so I'll have to ask, is it possible to catch NPCs in mid-transit? Because you could also do it in a more cheaty way. Let's assume a schedule controller does its checks and discovers that it is time for NPC_4 to leave from Room_B to adjoining Room_C. Next it checks if player is in Room_B or Room_C. If not, the NPC is directly placed to their destination in Room_C. If player is in Room_B, the NPC instance is instructed to start moving towards door to Room_B and once reaching the door, is placed to their destination in Room_C. But if player is in Room_C, the NPC is instead placed to the edge of the room and told to walk to their destination.

    In other words, walking simulation would occur only if player is there to observe it. In other cases, the simulation is skipped and the NPC jumps to their destination instantly. With routes that pass through multiple rooms, all would be checked for player presence and the NPC would start walking in the room where player is detected, if necessary.
     
  4. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    if i'm not wrong in stardew valley you can catch the NPC in mid-transit and that the thing i want to know how to do.

    About the schedule of the NPC, what i need to do is to create the master controler of the npc or i need to create the schedule manager separately from it?
     
  5. Laura Bravo

    Laura Bravo Member

    Joined:
    Jun 25, 2016
    Posts:
    31
    simplest way, assuming the player cant stop the NPC, is just have the room check based off in game time weather the NPC is in the room or not. than just have him appear in the door way and walk to the other. If player walks in on him have NPC 1/4 the way to other door and continue on. If it is really really important to have exact location (which its likely not) than you need to figure out how far he travels in x amount of time and place him said distance compared to time. If the player can change the time/route and its really important he be followed exactly than need to make an object that tracks his movement and how much time he needs in each room but mostly such things are not that important even big games that do it for gimmick such as the guards in skyrim running all the way from white run to warn the village really only have that one gimmick NPC and only if you follow them so they don't get out of sight.
     
  6. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    2,009
    I did something simpler but similar a good while ago (and expanded it a bit at later date). The idea was that I had a castle divided to a number of rooms, and guards patrolling on routes through those rooms. The basis for the whole thing was patrol route definitions and an object that managed their use. A route might've been defined as an array something like this: (that was on GMS1.4 so array designation style was different but the result was the same)
    Code:
    enum Action {
        Goto,
        Pause,
        Roomchange,
        ...
    }
    
    Route_1 = [
        [Action.Goto, 200, 100],
        [Action.Goto, 5, 100],
        [Action.Roomchange, rm_courtyard, 800, 200],
        [Action.Goto, 700, 200]
        ...
    ];
    Patrol_Routes[0] = Route_1;
    ...
    
    Which should be pretty self-explanatory. The route array contains a number of arrays that describe actions to take and data associated with that action. In case of goto action, the destination coordinates, and with roomchage, the target room and spawn point. Then I'd have data in the same object that defined guards:
    Code:
    enum Guards {
        Guard_1,
        Guard_2.
        ...
    }
    
    Guard1_Data = [rm_throne_room, 200, 200, 0, 0]; // Current room, current x, current y, route to follow, current route command.
    Guards_Data[Guards.Guard_1] = Guard1_Data;
    ...
    
    The controller object would then work like this. Every step the Guards_Data array is looped through and each guard in turn is handled. The code fetches definition of the route to follow, and looks at the guard's current route command. If the command is a goto command, it changes guard's current position towards given coordinates. If the position is reached, the current command value increases by one. If the command is a roomchange command, guard's current room is switched to room given in command and position is switched to spawn position given in the command. Then current command value increases by one. The routes were constructed to loop around, so when last command was reached, it just started again from the first.

    As you can see, the guards primarily existed as pure data. There was a generic obj_guard and it would be used in two cases. First, whenever the controller object executed a roomchange command, it checked if target room was current room (that is, where the player is). If so, it would spawn the obj_guard into the room at spawn coordinates. Second, whenever player moved into a new room all the guards would be checked and if any was in the room, obj_guard would be placed into its given coordinates.

    Whenever obj_guard instance spawned, it received the Guards enumerator value it represented. Every step, the guard would read the Guards_Data array from enumerator's position and update its x and y coordinates to what the controller object had calculated (that is, obj_guard had no movement code). It also checked if it existed in current room and if not, it would despawn. (The roomchange commands were all placed where a route reached a door.)

    The guards had no collision code either so that player couldn't block them, as keeping them on schedule was the primary consideration. They had reactions to stop the player when doing something inappropriate, so player could not be allowed to block them and mess their routes to create larger time windows where there were no guards in a room. (When one reacted to player all guards in fact stopped updating, so that timing would not get messed up.)
     
    Last edited: Nov 29, 2019
  7. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    I think i'm getting the idea.

    I could create the npc controller just to track the position of the npcs between rooms, like i could make a grid and store the id, "name", X, Y of that npc.
    so in the schedule manager the scripts would change the variable in the grid, and if the player is in the room it would create the instance and continue its path.

    Like that the Npc tracking and moving , need to be independent of where the player is.

    but the problem is :
    - how can i change the value inside the grid
    - how can i make the instance spawn and continue to do the path, like the player enter the room when the NPC1 is going home, he is mid-transit, the value of X,Y are increasing in the grid to reach the destination, then it's instace needs to be create, and i need to covert that x,y into the obj's
     
  8. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    2,009
    Instead of a grid, arrays in arrays is a more flexible approach. And a schedule is just data so it can be defined in the same object that controls NPCs. But let's start from the beginning. Let's assume an obj_npc_controller that is in charge of NPC schedules. We define a list of people (NPCs) in game (create event):
    Code:
    enum People {
        Jane,
        John,
        Length
    }
    
    This is the same as writing
    Code:
    enum People {
        Jane = 0,
        John = 1,
        Length = 2
    }
    
    except we let GMS dynamically assign the numbers. Which is an advantage if you want to insert Bob between John and Jane. This means each person now has a unique value:
    Code:
    var Person = People.Jane; // Person is zero.
    var Person = People.John; // Persons is one.
    
    Now we create an array to hold their data, define an inner array for all the date and enumerate its positions for easy handling.
    Code:
    People_List = array_create(People.Length, 0); // People.Length equals two due to length of the enumerator. This is useful because now you don't need to change this when you add more NPCs.
    
    enum People_Data {
        Name,
        Room,
        X,
        Y,
        Schedule_Pointer,
        Path_Current,
        Path_Position
    }
    
    People_List[People.Jane] = ["Jane Doe", rm_house_1, 100, 100, 0, 0];
    People_List[People.John] = ["John Doe", rm_house_1, 200, 200, 0, 0];
    
    I would use scripts for the setup but I'm not going to confuse you with even more concepts here. Now that we have data set up we can create some scripts to handle the data. To retrieve a person's data we can set up scr_person_get:
    Code:
    ///@function scr_person_get(person)
    ///@description Retrives data of given person.
    ///@param person Person enumerator value.
    
    return obj_npc_controller.People_List[argument0];
    
    When we want to, for example, get some of certain NPC's data we can do:
    Code:
    var This = scr_person_get(People.Jane);
    var This_Name = This[People_Data.Name]; // This_Name is now "Jane Doe."
    var This_Room = This[people_Data.Room]; // This_Room is now rm_house_1.
    
    To change data we can set up some more specialized scripts, for example to change room and coordinates:
    Code:
    ///@function scr_person_room_set(person, room)
    ///@description Sets a person's current room.
    ///@param person Person enumerator.
    ///@param room Room value
    
    var Person = scr_person_get(argument0);
    Person[@ Person_Data.Room] = argument1;
    
    // So to set Jane to rm_courtyard:
    scr_person_room_set(Person.Jane, rm_courtyard);
    
    ///@function scr_person_position_set(person, x, y)
    ///@description Sets a person's position.
    ///@param person Person enumerator.
    ///@param x X-coordinate.
    ///@param y Y-coordinate.
    
    var Person = scr_person_get(argument0);
    Person[@ Person_Data.X] = argument1;
    Person[@ Person_Data.Y] = argument2;
    
    The controller step event would be looping through NPCs constantly, so its main operational loop would be:
    Code:
    for(var i = 0; i < People.Length; i++){
        ....
    }
    
    Because scheduling would have other stuff involved with it than just movement, you should define paths separately in the obj_npc_controller so they can be called on-demand. Simply like
    Code:
    Path_1[0] = [100, 100];
    Path_1[1] = [200, 100];
    Path_1[2] = [200, 300];
    ...
    Paths[0] = Path_1;
    
    So that a path defines a route in a single room from exit to exit. This way you don't need to define routes that overlap for the most part, but instead string together fragments to construct a complete route, saving your time and fingers. I'm skipping over scheduling as that's a big thing by itself, but once the code has determined that an NPC would be moving along a path and needs to update its position, it would go something like: (we're assuming a constant walk speed to all NPCs)
    Code:
    // The main loop.
    for(var i = 0; i < People.Length; i++){
        ....
        // We have figure that the NPC needs to move down a path
        // Arrived will receive true if path has been completed.
        var Arrived = scr_person_move(i);
    }
    
    ///@script scr_path_position_get(path, position)
    ///@description Retrieves given path's given position's coordinates.
    ///@param path Path number.
    ///@param position Position number.
    
    var Path = obj_npc_controller.Paths[argument0];
    return Path[argument1];
    
    
    ///@script scr_path_length_get(path)
    ///@description Retrives the number of points on path.
    ///@param path Path number
    
    var Path = obj_npc_controller.Paths[argument0];
    return array_length_1d(Path);
    
    
    ///@script scr_person_move(person)
    ///@description Moves an NPC towards a path point. No collision detection, no avoidance, just a dumb movement script.
    ///@param person Person enumerator.
    
    var ID = argument0;
    var Speed = 2;
    var This_Person = scr_person_get(ID);
    var Destination = scr_path_position_get(This_Person[Person_Data.Path_Current], This_Person[Person_Data.Path_Position]);
    var xx = This_Person[Person_Data.X];
    var yy = This_Person[Person_Data.Y];
    
    // Horizontal.
    if(xx != Destination[0]){
        var Move = Destination[0] - xx;
        if(Speed < abs(Move)) xx += Speed * sign(Move);
        else xx = Destination[0];
    }
    // Vertical
    if(yy != Destination[1]){
        var Move = Destination[1] - yy;
        if(Speed < abs(Move)) yy += Speed * sign(Move);
        else yy = Destination[1];
    }
    
    // Set new position.
    scr_person_position_set(ID, xx, yy);
    
    // Arrived to point
    if(xx == Destination[0] && yy == Destination[1]){
        This_Person[@ Person_Data.Path_Position]++;
        var Length = scr_path_length_get(This_Person[Person_Data.Path_Current]);
        if(This_Person[Person_Data.Path_Position] < Length)){
            // Not last path point.
            return false;
        } else {
            // Last path point.
            return true;
        }
    }
    
    Well, that turned into lengthier post than I thought... (all code untested)
     
    Last edited: Nov 30, 2019
  9. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    I think i got it now, but as everything is just data how can i create the object when the player enters the room?
    Knowing that i will have the obj of the npc storing the data of dialogues, and other things specific of that npc.
     
  10. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    2,009
    When player enters a room, you can check its current inhabitants and spawn them. You can do it in room creation code, calling a script something like this:
    Code:
    ///@function scr_npc_spawn()
    ///@description Checks which NPCs are in room and spawns them.
    
    for(var i = 0; i < People.Length; i++){
        var This = scr_person_get(i);
        // If in current room.
        if(This[People_Data.Room] == room){
            // Create character (see below).
        }
    }
    
    You can do similar check when obj_npc_controller moves the NPC to another room, if new room is current room, create the NPC.

    You have two alternatives how to handle the NPCs. One is to create a generic object, obj_npc and give it a variable for identification, lets call it Identity, and set default value to -1. The creation code I left out above would be:
    Code:
    var NPC = instance_create_layer(This[People_Data.X], This[People_Data.Y], "layer_characters", obj_npc);
    NPC.Identity = i; // Variable i equals the current People enumerator being handled, since we're looping through them all.
    
    You can then use this Identity variable to identify the NPC, when you need to start dialogue, fetch the correct sprite, etc. That's how I have done my dialogue system. I have an obj_dialogue that controls it all, and when player approaches an NPC and presses interaction button, the NPC instance passes its Identity value to obj_dialogue, which stores all dialogue information, and uses it to display correct text.

    The other method would be to create a custom object for each NPC, store that to their data and use it to create the correct instance. The People data would then look like
    Code:
    enum People_Data {
       Name,
       Room,
       X,
       Y,
       Schedule_Pointer,
       Path_Current,
       Path_Position,
       Object      // <-- This here is new.
    }
    
    People_List[People.Jane] = ["Jane Doe", rm_house_1, 100, 100, 0, 0, obj_jane_doe];
    People_List[People.John] = ["John Doe", rm_house_1, 200, 200, 0, 0, obj_john_doe];
    
    And the creation code would read and use it like
    Code:
    var NPC = instance_create_layer(This[People_Data.X], This[People_Data.Y], "layer_characters", This[People_Data.Object]);
    
    Hopefully this all is clear enough, as I've thrown a lot of code your way.
     
  11. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    i think i got it now, Thanks everyone!!!
     
  12. TulioHenry

    TulioHenry Member

    Joined:
    Nov 27, 2019
    Posts:
    7
    So i've tried making the npc manager and i found 2 errors that i don't know what is happening
    - the one error "variable index out of range" in the scr_move_npc.
    - the second is that the npcs aren't moving, neither outside or inside the player room

    //the npc Manager create code
    Code:
    enum Npc{
        Jake,
        Mia,
        Length
    }
    
    enum Npc_Data {
        Name,
        Room,
        X,
        Y,
        Schedule_Pointer,
        Path_Current,
        Path_Position,
        Object
    }
    
    npc_list = array_create(Npc.Length, 0);
    
    npc_list[@ Npc.Jake] = ["Jake",rmStart,450,300,0,0,0,oJake];
    npc_list[@ Npc.Mia] = ["Mia",rmStart,300,300,0,0,0,oMia];
    
    enum Paths{
        Path_1,
        Length
    }
    
    
    path_list = array_create(Paths.Length, 0);
    
    path_list[@ Paths.Path_1] = [
          [300,200],
          [300,100],
          [200,100],
          [200,500]
    ]
    // Step event
    Code:
    for(var i = 0; i < Npc.Length; i++){
       
        var arrived = NpcMove(i);
       
    }
    
    
    and room start event
    Code:
    for(var i = 0; i < Npc.Length; i++){
       
        var person = NpcGetValue(i);
       
        if(person[Npc_Data.Room] == room){
            var xx = person[Npc_Data.X];
            var yy = person[Npc_Data.Y];
            var obj = person[Npc_Data.Object]
            var npc = instance_create_layer(xx,yy,"Instances",obj);
            npc.identity = i;
           
           
        }
       
    }
    the script that is coming the error
    Code:
    ///@desc NpcMove
    ///@arg Npc_id
    
    
    var ID = argument0;
    var Speed = 3;
    var This_Person = NpcGetValue(ID);
    var Destination = NpcPathGetPosition(This_Person[Npc_Data.Path_Current], This_Person[Npc_Data.Path_Position]);
    var xx = This_Person[Npc_Data.X];
    var yy = This_Person[Npc_Data.Y];
    
    // Horizontal.
    if(xx != Destination[0]){
        var Move = Destination[0] - xx;
        if(Speed < abs(Move)) xx += Speed * sign(Move);
        else xx = Destination[0];
    }
    // Vertical
    if(yy != Destination[1]){
        var Move = Destination[1] - yy;
        if(Speed < abs(Move)) yy += Speed * sign(Move);
        else yy = Destination[1];
    }
    
    // Set new position.
    NpcSetPosition(ID, xx, yy);
    
    // Arrived to point
    if(xx == Destination[0] && yy == Destination[1]){
        This_Person[@ Npc_Data.Path_Position]++;
        var Length = NpcPathGetLength(This_Person[Npc_Data.Path_Current]);
        if(This_Person[Npc_Data.Path_Position] < Length){
            // Not last path point.
            return false;
        } else {
            // Last path point.
            return true;
        }
    }
    and another question, when we create the npc object how can i tell him to continue following the path?
     
    Last edited: Dec 6, 2019

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice