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的指定位置。