Unity3D仿星露谷物语开发64之NPC跨Scene移动

发布于:2025-06-20 ⋅ 阅读:(20) ⋅ 点赞:(0)

1、目标

让NPC实现在不同Scene中移动。

2、架构

如果从场景1 -> 场景2 -> 场景3,则SceneRoute的fromSceneName为场景1,toSceneName为场景3,List<ScenePath>对应场景1、场景2、场景3的路径。

3、编写代码

(1)创建ScenePath.cs脚本

在Assets -> Scripts -> Scene下创建ScenePath.cs脚本 

[System.Serializable]
public class ScenePath 
{
    public SceneName SceneName;
    public GridCoordinate fromGridCell;
    public GridCoordinate toGridCell;
}

(2)创建SceneRoute.cs脚本

在Assets -> Scripts -> Scene下创建SceneRoute.cs脚本 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class SceneRoute
{
    public SceneName fromSceneName;
    public SceneName toSceneName;
    public List<ScenePath> scenePathList;
}

(3)创建SO_SceneRouteList.cs脚本

在Assets -> Scripts -> Scene下创建SO_SceneRouteList.cs脚本 

using System.Collections.Generic;
using UnityEngine;


[CreateAssetMenu(fileName ="so_SceneRouteList", menuName ="Scriptable Objects/Scene/Scene Route List")]
public class SO_SceneRouteList : ScriptableObject
{
    public List<SceneRoute> sceneRouteList;
}

(4)修改Settings.cs脚本 

添加如下两行代码:

(5)修改NPCManager.cs脚本

添加以下两行代码:

在Awake()下添加如下代码:

添加如下函数:

 public SceneRoute GetSceneRoute(string fromSceneName, string toScceneName)
 {
     SceneRoute sceneRoute;

     // Get scene route from dictionary
     if(sceneRouteDictionary.TryGetValue(fromSceneName + toScceneName, out sceneRoute))
     {
         return sceneRoute;
     }
     else
     {
         return null;
     }
 }

(6)修改NPCPath.cs脚本

修改BuildPath函数如下:

    public void BuildPath(NPCScheduleEvent npcScheduleEvent)
    {
        ClearPath();

        // If schedule event is for the same scene as the current NPC scene
        if (npcScheduleEvent.toSceneName == npcMovement.npcCurrentScene)
        {
            Vector2Int npcCurrentGridPosition = (Vector2Int)npcMovement.npcCurrentGridPosition;

            Vector2Int npcTargetGridPosition = (Vector2Int)npcScheduleEvent.toGridCoordinate;

            // Build path and add movement steps to movement step stack
            NPCManager.Instance.BuildPath(npcScheduleEvent.toSceneName, npcCurrentGridPosition, npcTargetGridPosition, npcMovementStepStack);
        }
        // else if the scehdule event is for a location in another scene
        else if (npcScheduleEvent.toSceneName != npcMovement.npcCurrentScene)
        {
            SceneRoute sceneRoute;

            // Get scene route matching Schedule
            sceneRoute = NPCManager.Instance.GetSceneRoute(npcMovement.npcCurrentScene.ToString(), npcScheduleEvent.toSceneName.ToString());

            // has a valid scene route been found
            if(sceneRoute != null)
            {
                // Loop through scene paths in reverse order(因为npcMovementStepStack是栈结构)
                for(int i = sceneRoute.scenePathList.Count - 1; i >= 0; i--)
                {
                    int toGridX, toGridY, fromGridX, fromGridY;

                    ScenePath scenePath = sceneRoute.scenePathList[i];

                    // check if this is the final destination
                    if(scenePath.toGridCell.x >= Settings.maxGridWidth || scenePath.toGridCell.y >= Settings.maxGridHeight)
                    {
                        // if so use final destination(999999的表示取用户值)
                        toGridX = npcScheduleEvent.toGridCoordinate.x;
                        toGridY = npcScheduleEvent.toGridCoordinate.y;
                    }
                    else
                    {
                        // else use scene path to position
                        toGridX = scenePath.toGridCell.x;
                        toGridY = scenePath.toGridCell.y;   
                    }

                    // check if this is the starting position
                    if(scenePath.fromGridCell.x >= Settings.maxGridWidth || scenePath.fromGridCell.y >= Settings.maxGridHeight)
                    {
                        // if so use npc position
                        fromGridX = npcMovement.npcCurrentGridPosition.x;
                        fromGridY = npcMovement.npcCurrentGridPosition.y;
                    }
                    else
                    {
                        // else use scene path from position
                        fromGridX = scenePath.fromGridCell.x;
                        fromGridY = scenePath.fromGridCell.y;
                    }

                    Vector2Int fromGridPosition = new Vector2Int(fromGridX, fromGridY);
                    Vector2Int toGridPosition = new Vector2Int(toGridX, toGridY);

                    // Build path and add movement steps to movement step stack
                    NPCManager.Instance.BuildPath(scenePath.sceneName, fromGridPosition, toGridPosition, npcMovementStepStack);
                }
            }
        }


        // if stack count > 1, update times and then pop off 1st item which is the starting position
        if (npcMovementStepStack.Count > 1)
        {
            UpdateTimesOnPath();
            npcMovementStepStack.Pop(); // discard starting step

            // Set schedule event details in NPC movement
            npcMovement.SetScheduleEventDetails(npcScheduleEvent);
        }
    }

(7)修改NPCMovement.cs脚本

修改FixedUpdate函数如下:

    private void FixedUpdate()
    {
        if (sceneLoaded)
        {
            if(npcIsMoving == false)
            {
                // set npc current and next grid position - to take into account the npc might be animating
                npcCurrentGridPosition = GetGridPosition(transform.position);
                npcNextGridPosition = npcCurrentGridPosition;

                if(npcPath.npcMovementStepStack.Count > 0)
                {
                    NPCMovementStep npcMovementStep = npcPath.npcMovementStepStack.Peek();

                    npcCurrentScene = npcMovementStep.sceneName;

                    // if NPC is about thhe move to a new scene  reset position to starting point in new scene and update the step times
                    if(npcCurrentScene != npcPreviousMovementStepScene)
                    {
                        npcCurrentGridPosition = (Vector3Int)npcMovementStep.gridCoordinate;
                        npcNextGridPosition = npcCurrentGridPosition;
                        transform.position = GetWorldPosition(npcCurrentGridPosition);
                        npcPreviousMovementStepScene = npcCurrentScene;
                        npcPath.UpdateTimesOnPath();
                    }

                    // If NPC is in current scene then set NPC to active to make visible, pop the movement step off the stack 
                    // and then call method to move NPC
                    if(npcCurrentScene.ToString() == SceneManager.GetActiveScene().name)
                    {
                        SetNPCActiveInScene();

                        npcMovementStep = npcPath.npcMovementStepStack.Pop();

                        npcNextGridPosition = (Vector3Int)npcMovementStep.gridCoordinate;

                        TimeSpan npcMovementStepTime = new TimeSpan(npcMovementStep.hour, npcMovementStep.minute, npcMovementStep.second);

                        MoveToGridPosition(npcNextGridPosition, npcMovementStepTime, TimeManager.Instance.GetGameTime());
                    }

                    // else if NPC is not in current scene then set NPC to inactive to make invisible
                    // - once the movement step time is less than game time (in the past) then pop movement step off the stack and set NPC position to movement step position
                    else
                    {
                        SetNPCInactiveInScene();

                        npcCurrentGridPosition = (Vector3Int)npcMovementStep.gridCoordinate;
                        npcNextGridPosition = npcCurrentGridPosition;
                        transform.position = GetWorldPosition(npcCurrentGridPosition);

                        TimeSpan npcMovementStepTime = new TimeSpan(npcMovementStep.hour, npcMovementStep.minute, npcMovementStep.second);
                        TimeSpan gameTime = TimeManager.Instance.GetGameTime();

                        if(npcMovementStepTime < gameTime)
                        {
                            npcMovementStep = npcPath.npcMovementStepStack.Pop();

                            npcCurrentGridPosition = (Vector3Int)npcMovementStep.gridCoordinate;
                            npcNextGridPosition = npcCurrentGridPosition;
                            transform.position = GetWorldPosition(npcCurrentGridPosition);
                        }
                    }
                }
                // else if no more NPC movement steps
                else
                {
                    ResetMoveAnimation();

                    SetNPCFacingDirection();

                    SetNPCEventAnimation();
                }
            }
        }
    }

在InitialisedNPC函数中添加一行代码:

(8)修改AStarTest.cs脚本

修改两行代码如下:改为可配置的方式。

4、创建so_SceneRouteList实例

在Assets -> Scriptable Object Assets下新建目录命名为Scene,然后右键 -> Scriptable Objects -> Scene -> Scene Route List得到so_SceneRouteList实例。

测量每个场景的出入口位置:

Scene1_Farm:

位置1:(-10,-7)

位置2:(39,-1)

Scene2_Field:

位置:(-39,-1)

Scene3_Cabin:

位置为(-3,-7)

配置so_SceneRouteList实例如下:

5、配置NPCManager对象

6、运行游戏

在NPCManager对象的AStarTest中配置测试信息:

NPC会自动走到Scene3_Cabin的指定位置。


网站公告

今日签到

点亮在社区的每一天
去签到