Udemy-Unity制作类暗黑破坏神-第一部分全部代码

2024-10-09
146看过
这套教程分为四章,第一部分主要讲诉Unity的基础以及简单RPG开发逻辑,都是比较基础的内容,其实教程整体来看很多地方做的比较粗糙.不过作为入门教程也不能要求太高,总体上很适合刚上手unity新人入门,我后面懒得每P都记录,所以就直接附上第一部分完结的源代码。有需要自己对着参考吧,为了方便有学习的新人理解,用AI写了注释

Mover
  1. using RPG.Attributes;
  2. using RPG.Core;
  3. using RPG.Saving;
  4. using UnityEngine;
  5. using UnityEngine.AI;
  6. namespace RPG.Movement
  7. {
  8.     //移动   
  9.     //关于角色的移动逻辑,角色的移动主要使用Unity自带的寻路组件 NavMeshAgent
  10.     public class Mover : MonoBehaviour, IAction, ISaveable
  11.     {
  12.         [SerializeField]
  13.         Transform target;
  14.         [SerializeField]
  15.         float maxSpeed = 6f;// 最大移动速度
  16.         NavMeshAgent navMeshAgent;// 导航网格代理,用于处理移动
  17.         Health health;// 角色的生命值组件
  18.         private void Awake()
  19.         {
  20.             health = GetComponent<Health>();
  21.             navMeshAgent = GetComponent<NavMeshAgent>();
  22.         }
  23.         void Update()
  24.         {
  25.             navMeshAgent.enabled = !health.IsDead();// 如果角色死亡,则禁用导航代理
  26.             UpdateAnimator();// 更新动画状态
  27.         }
  28.          //开始移动行为
  29.         public void StartMoveAction(Vector3 destination, float speedFraction)
  30.         {
  31.             GetComponent<ActionScheduler>().StartAction(this);// 启动移动动作
  32.             MoveTo (destination, speedFraction);// 开始移动到目标位置
  33.         }
  34.         
  35.         //移动状态
  36.         public void MoveTo(Vector3 destination, float speedFraction)
  37.         {
  38.             navMeshAgent.destination = destination;// 设置导航代理的目标位置
  39.             navMeshAgent.speed = maxSpeed * Mathf.Clamp01(speedFraction);// 根据速度比例设置移动速度
  40.             navMeshAgent.isStopped = false;// 开始移动
  41.         }
  42.         /// <summary>
  43.         /// 取消移动  
  44.         /// </summary>
  45.         public void Cancel()
  46.         {
  47.             navMeshAgent.isStopped = true;// 停止移动
  48.         }
  49.         //更新动画
  50.         private void UpdateAnimator()
  51.         {
  52.             Vector3 velocity = navMeshAgent.velocity;// 获取导航代理的速度
  53.            
  54.             Vector3 localVelocity =
  55.                 transform.InverseTransformDirection(velocity);// 将全局速度转换为本地坐标系
  56.             float speed = localVelocity.z;
  57.             GetComponent<Animator>().SetFloat("forwardSpeed", speed);// 更新动画控制器中的前进速度
  58.         }
  59.         //捕获状态  返回当前坐标
  60.         public object CaptureState()
  61.         {
  62.             return new SerializableVector3(transform.position);
  63.         }
  64.         //恢复状态
  65.         public void RestoreState(object state)
  66.         {
  67.             SerializableVector3 position = (SerializableVector3) state;// 将状态转换为可序列化的向量
  68.             // 为避免与导航代理冲突,先禁用导航代理
  69.             GetComponent<NavMeshAgent>().enabled = false;
  70.             transform.position = position.ToVector();
  71.             // 在设置位置后重新启用导航代理
  72.             GetComponent<NavMeshAgent>().enabled = true;
  73.         }
  74.     }
  75. }
复制代码
PlayerController
  1. using System; // 引入系统命名空间
  2. using RPG.Attributes; // 引入角色属性相关的命名空间
  3. using RPG.Combat; // 引入战斗相关的命名空间
  4. using RPG.Movement; // 引入移动相关的命名空间
  5. using UnityEngine; // 引入Unity引擎相关的命名空间
  6. using UnityEngine.AI; // 引入Unity的导航系统相关的命名空间
  7. using UnityEngine.EventSystems; // 引入UI事件系统相关的命名空间
  8. namespace RPG.Control // 定义RPG控制相关的命名空间
  9. {
  10.     public class PlayerController : MonoBehaviour // 玩家控制器类
  11.     {
  12.         Health health; // 角色的生命值组件
  13.         // 用于在Unity编辑器中显示的光标映射结构体
  14.         [Serializable]
  15.         struct CursorMapping
  16.         {
  17.             public CursorType type; // 光标类型
  18.             public Texture2D texture; // 光标的纹理
  19.             public Vector2 hotspot; // 光标的热点
  20.         }
  21.         [SerializeField]
  22.         CursorMapping[] cursorMappings = null; // 光标映射数组,配置不同光标类型
  23.         [SerializeField]
  24.         float maxNavMeshProjectionDistance = 1f; // 最大导航网格投影距离
  25.         [SerializeField]
  26.         float maxNavPathLength = 40f; // 最大导航路径长度
  27.         private void Awake()
  28.         {
  29.             health = GetComponent<Health>(); // 初始化健康组件
  30.         }
  31.         void Update()
  32.         {
  33.             // 检测是否与UI交互,如果是则返回
  34.             if (InteractWithUI()) return;
  35.             // 如果角色死亡,设置光标为无效状态并返回
  36.             if (health.IsDead())
  37.             {
  38.                 SetCursor(CursorType.None);
  39.                 return;
  40.             }
  41.             // 检测与组件交互,如果有交互则返回
  42.             if (InteractWithComponent()) return;
  43.             // 检测移动交互,如果有交互则返回
  44.             if (InteractWithMovement()) return;
  45.             // 如果没有交互,设置光标为无效状态
  46.             SetCursor(CursorType.None);
  47.         }
  48.         private bool InteractWithComponent()
  49.         {
  50.             RaycastHit[] hits = RaycastAllSorted(); // 获取排序后的射线检测结果
  51.             foreach (RaycastHit hit in hits)
  52.             {
  53.                 // 获取当前对象的所有可射线检测组件
  54.                 IRaycastable[] raycastables =
  55.                     hit.transform.GetComponents<IRaycastable>();
  56.                 foreach (IRaycastable raycastable in raycastables)
  57.                 {
  58.                     // 如果组件处理了射线检测
  59.                     if (raycastable.HandleRaycast(this))
  60.                     {
  61.                         SetCursor(raycastable.GetCursorType()); // 设置光标为对应类型
  62.                         return true; // 返回交互成功
  63.                     }
  64.                 }
  65.             }
  66.             return false; // 没有交互,返回false
  67.         }
  68.         RaycastHit[] RaycastAllSorted()
  69.         {
  70.             // 进行射线检测,获取所有碰撞体
  71.             RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
  72.             float[] distances = new float[hits.Length]; // 存储每个碰撞体与射线起点的距离
  73.             for (int i = 0; i < hits.Length; i++)
  74.             {
  75.                 distances[i] = hits[i].distance; // 记录距离
  76.             }
  77.             Array.Sort(distances, hits); // 根据距离排序碰撞体
  78.             return hits; // 返回排序后的碰撞体数组
  79.         }
  80.         private bool InteractWithUI()
  81.         {
  82.             // 检测鼠标是否悬停在UI元素上
  83.             if (EventSystem.current.IsPointerOverGameObject())
  84.             {
  85.                 SetCursor(CursorType.UI); // 设置光标为UI类型
  86.                 return true; // 返回交互成功
  87.             }
  88.             return false; // 没有UI交互,返回false
  89.         }
  90.         private void SetCursor(CursorType type)
  91.         {
  92.             // 根据类型获取光标映射并设置光标
  93.             CursorMapping mapping = GetCursorMapping(type);
  94.             Cursor.SetCursor(mapping.texture, mapping.hotspot, CursorMode.Auto);
  95.         }
  96.         private CursorMapping GetCursorMapping(CursorType type)
  97.         {
  98.             // 根据光标类型获取对应的映射
  99.             foreach (CursorMapping mapping in cursorMappings)
  100.             {
  101.                 if (mapping.type == type)
  102.                 {
  103.                     return mapping; // 找到对应类型,返回映射
  104.                 }
  105.             }
  106.             return cursorMappings[0]; // 如果没有找到,返回默认映射
  107.         }
  108.         private bool InteractWithMovement()
  109.         {
  110.             Vector3 target; // 定义目标位置
  111.             bool hasHit = RaycastNavMesh(out target); // 射线检测导航网格
  112.             if (hasHit)
  113.             {
  114.                 if (Input.GetMouseButton(0)) // 如果鼠标左键按下
  115.                 {
  116.                     GetComponent<Mover>().StartMoveAction(target, 1f); // 开始移动到目标位置
  117.                 }
  118.                 SetCursor(CursorType.Movement); // 设置光标为移动类型
  119.                 return true; // 返回交互成功
  120.             }
  121.             return false; // 没有移动交互,返回false
  122.         }
  123.         // out关键字表示该方法有两个返回规则
  124.         // 1. 返回bool值
  125.         // 2. 更新目标位置
  126.         private bool RaycastNavMesh(out Vector3 target)
  127.         {
  128.             target = new Vector3(); // 初始化目标位置
  129.             // 射线检测地形
  130.             RaycastHit hit;
  131.             bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
  132.             if (!hasHit) return false; // 没有检测到,返回false
  133.             // 找到最近的导航网格点
  134.             NavMeshHit navMeshHit;
  135.             bool hasCastToNavMesh =
  136.                 NavMesh
  137.                     .SamplePosition(hit.point,
  138.                     out navMeshHit,
  139.                     maxNavMeshProjectionDistance,
  140.                     NavMesh.AllAreas);
  141.             if (!hasCastToNavMesh) return false; // 如果没有有效的导航点,返回false
  142.             target = navMeshHit.position; // 更新目标位置
  143.             NavMeshPath path = new NavMeshPath();
  144.             // 计算从当前位置到目标位置的导航路径
  145.             bool hasPath =
  146.                 NavMesh
  147.                     .CalculatePath(transform.position,
  148.                     target,
  149.                     NavMesh.AllAreas,
  150.                     path);
  151.             if (!hasPath) return false; // 如果没有路径,返回false
  152.             // 如果路径不完整,返回false
  153.             if (path.status != NavMeshPathStatus.PathComplete) return false;
  154.             // 如果路径长度超过最大限制,返回false
  155.             if (GetPathLength(path) > maxNavPathLength) return false;
  156.             // 返回true,表示成功找到有效路径
  157.             return true;
  158.         }
  159.         private float GetPathLength(NavMeshPath path)
  160.         {
  161.             float total = 0; // 初始化路径长度
  162.             if (path.corners.Length < 2) return 0; // 如果路径角点少于2,返回0
  163.             // 计算路径长度
  164.             for (int i = 0; i < path.corners.Length - 1; i++)
  165.             {
  166.                 total += Vector3.Distance(path.corners[i], path.corners[i + 1]); // 累加各段距离
  167.             }
  168.             return total; // 返回总路径长度
  169.         }
  170.         private static Ray GetMouseRay()
  171.         {
  172.             return Camera.main.ScreenPointToRay(Input.mousePosition); // 从鼠标位置获取射线
  173.         }
  174.     }
  175. }
复制代码
AIController
  1. using System; // 引入系统命名空间
  2. using GameDevTV.Utils; // 引入GameDevTV工具相关的命名空间
  3. using RPG.Attributes; // 引入角色属性相关的命名空间
  4. using RPG.Combat; // 引入战斗相关的命名空间
  5. using RPG.Core; // 引入核心功能相关的命名空间
  6. using RPG.Movement; // 引入移动相关的命名空间
  7. using UnityEngine; // 引入Unity引擎相关的命名空间
  8. namespace RPG.Control // 定义RPG控制相关的命名空间
  9. {
  10.     public class AIController : MonoBehaviour // AI控制器类
  11.     {
  12.         [SerializeField]
  13.         float chaseDistance = 5f; // 追击距离
  14.         [SerializeField]
  15.         float suspicionTime = 3f; // 怀疑持续时间
  16.         [SerializeField]
  17.         PatrolPath patrolPath; // 巡逻路径
  18.         [SerializeField]
  19.         float waypointTolerance = 1f; // 路点容忍度
  20.         [SerializeField]
  21.         float waypointDwellTime = 3f; // 在路点停留时间
  22.         [SerializeField]
  23.         [Range(0, 1)]
  24.         float patrolSpeedFraction = .2f; // 巡逻速度比例
  25.         int currentWaypointIndex = 0; // 当前路点索引
  26.         Fighter fighter; // 战斗组件
  27.         GameObject player; // 玩家对象
  28.         Health health; // 生命值组件
  29.         Mover mover; // 移动组件
  30.         LazyValue<Vector3> guardPosition; // 懒加载守卫位置
  31.         float timeSinceLastSawPlayer = Mathf.Infinity; // 上次看到玩家的时间
  32.         float timeSinceArrivedAtWaypoint = Mathf.Infinity; // 到达路点的时间
  33.         private void Awake()
  34.         {
  35.             fighter = GetComponent<Fighter>(); // 初始化战斗组件
  36.             health = GetComponent<Health>(); // 初始化生命值组件
  37.             mover = GetComponent<Mover>(); // 初始化移动组件
  38.             player = GameObject.FindWithTag("Player"); // 查找玩家对象
  39.             guardPosition = new LazyValue<Vector3>(GetGuardPosition); // 初始化守卫位置
  40.         }
  41.         private Vector3 GetGuardPosition()
  42.         {
  43.             return transform.position; // 返回当前守卫位置
  44.         }
  45.         private void Start()
  46.         {
  47.             guardPosition.ForceInit(); // 强制初始化懒加载的守卫位置
  48.         }
  49.         private void Update()
  50.         {
  51.             if (health.IsDead()) // 如果角色死亡,直接返回
  52.             {
  53.                 return;
  54.             }
  55.             // 如果在攻击范围内并且可以攻击玩家,执行攻击行为
  56.             if (InAttackRangeOfPlayer() && fighter.CanAttack(player))
  57.             {
  58.                 AttackBehavior();
  59.             }
  60.             // 如果在怀疑时间内,执行怀疑行为
  61.             else if (timeSinceLastSawPlayer < suspicionTime)
  62.             {
  63.                 SuspicionBehaviour();
  64.             }
  65.             // 否则执行巡逻行为
  66.             else
  67.             {
  68.                 PatrolBehaviour();
  69.             }
  70.             UpdateTimers(); // 更新计时器
  71.         }
  72.         private void AttackBehavior()
  73.         {
  74.             timeSinceLastSawPlayer = 0; // 重置上次看到玩家的时间
  75.             fighter.Attack(player); // 执行攻击玩家
  76.         }
  77.         private void UpdateTimers()
  78.         {
  79.             // 更新计时器
  80.             timeSinceLastSawPlayer += Time.deltaTime;
  81.             timeSinceArrivedAtWaypoint += Time.deltaTime;
  82.         }
  83.         private void PatrolBehaviour()
  84.         {
  85.             Vector3 nextPosition = guardPosition.value; // 获取下一个目标位置
  86.             if (patrolPath) // 如果有巡逻路径
  87.             {
  88.                 if (AtWayPoint()) // 检查是否到达当前路点
  89.                 {
  90.                     timeSinceArrivedAtWaypoint = 0f; // 重置到达路点的时间
  91.                     CycleWaypoint(); // 循环到下一个路点
  92.                 }
  93.                 nextPosition = GetCurrentWaypoint(); // 获取当前路点位置
  94.             }
  95.             // 如果在路点停留时间超过设定时间,开始移动
  96.             if (timeSinceArrivedAtWaypoint > waypointDwellTime)
  97.             {
  98.                 mover.StartMoveAction(nextPosition, patrolSpeedFraction); // 开始移动到目标位置
  99.             }
  100.         }
  101.         private Vector3 GetCurrentWaypoint()
  102.         {
  103.             return patrolPath.GetWaypoint(currentWaypointIndex); // 获取当前路点
  104.         }
  105.         private void CycleWaypoint()
  106.         {
  107.             currentWaypointIndex = patrolPath.GetNextIndex(currentWaypointIndex); // 循环更新当前路点索引
  108.         }
  109.         private bool AtWayPoint()
  110.         {
  111.             // 计算与当前路点的距离
  112.             float distanceToWaypoint = Vector3.Distance(transform.position, GetCurrentWaypoint());
  113.             return distanceToWaypoint < waypointTolerance; // 判断是否到达当前路点
  114.         }
  115.         private void SuspicionBehaviour()
  116.         {
  117.             GetComponent<ActionScheduler>().CancelCurrentAction(); // 取消当前行动
  118.         }
  119.         private bool InAttackRangeOfPlayer()
  120.         {
  121.             // 计算与玩家的距离
  122.             float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
  123.             return distanceToPlayer < chaseDistance; // 判断是否在攻击范围内
  124.         }
  125.         // Unity调用的方法,用于在编辑器中显示调试信息
  126.         private void OnDrawGizmosSelected()
  127.         {
  128.             Gizmos.color = Color.blue; // 设置Gizmos颜色为蓝色
  129.             Gizmos.DrawWireSphere(transform.position, chaseDistance); // 绘制追击范围的圆形
  130.         }
  131.     }
  132. }
复制代码



PatrolPath
  1. using System.Collections; // 引入集合相关命名空间
  2. using System.Collections.Generic; // 引入泛型集合相关命名空间
  3. using UnityEngine; // 引入Unity引擎相关命名空间
  4. namespace RPG.Control // 定义RPG控制相关的命名空间
  5. {
  6.     public class PatrolPath : MonoBehaviour // 巡逻路径类
  7.     {
  8.         const float wayPointGizmoRadius = 0.3f; // 路点在Gizmos中显示的半径
  9.         private void OnDrawGizmos() // 在编辑器中绘制Gizmos
  10.         {
  11.             for (int i = 0; i < transform.childCount; i++) // 遍历所有子对象
  12.             {
  13.                 int j = GetNextIndex(i); // 获取下一个路点的索引
  14.                 Gizmos.DrawSphere(GetWaypoint(i), wayPointGizmoRadius); // 绘制当前路点的球体
  15.                 Gizmos.DrawLine(GetWaypoint(i), GetWaypoint(j)); // 绘制当前路点与下一个路点之间的连线
  16.             }
  17.         }
  18.         public int GetNextIndex(int i) // 获取下一个路点的索引
  19.         {
  20.             // 如果当前路点是最后一个路点,则返回第一个路点的索引
  21.             if (i + 1 == transform.childCount)
  22.             {
  23.                 return 0;
  24.             }
  25.             return i + 1; // 否则返回下一个路点的索引
  26.         }
  27.         public Vector3 GetWaypoint(int i) // 获取指定索引的路点位置
  28.         {
  29.             return transform.GetChild(i).position; // 返回子对象的
  30.         }
  31.     }
  32. }
复制代码


IRaycastable

  1. namespace RPG.Control
  2. {
  3.     public interface IRaycastable
  4.     {
  5.         CursorType GetCursorType();
  6.         bool HandleRaycast(PlayerController callingController);
  7.     }
  8. }
复制代码
CursorType

  1. namespace RPG.Control
  2. {
  3.     public enum CursorType
  4.     {
  5.         None,
  6.         Movement,
  7.         Combat,
  8.         UI,
  9.         Pickup
  10.     }
  11. }
复制代码
ActionScheduler

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace RPG.Core
  5. {
  6.     public class ActionScheduler : MonoBehaviour
  7.     {
  8.         IAction currentAction;
  9.         public void StartAction(IAction action)
  10.         {
  11.             if (currentAction == action)
  12.             {
  13.                 return;
  14.             }
  15.             if (currentAction != null)
  16.             {
  17.                 currentAction.Cancel();
  18.                 print($"Canceling {currentAction}");
  19.             }
  20.             currentAction = action;
  21.         }
  22.         public void CancelCurrentAction()
  23.         {
  24.             StartAction(null);
  25.         }
  26.     }
  27. }
复制代码
CameraFacing

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace RPG.Core
  5. {
  6.     public class CameraFacing : MonoBehaviour
  7.     {
  8.         void LateUpdate()
  9.         {
  10.             transform.forward = Camera.main.transform.forward;
  11.         }
  12.     }
  13. }
复制代码
DestroyAfterEffect

  1. using UnityEngine;
  2. namespace RPG.Core
  3. {
  4.     public class DestroyAfterEffect : MonoBehaviour
  5.     {
  6.         [SerializeField]
  7.         GameObject targetToDestroy = null;
  8.         void Update()
  9.         {
  10.             if (!GetComponent<ParticleSystem>().IsAlive())
  11.             {
  12.                 if (targetToDestroy != null)
  13.                 {
  14.                     Destroy (targetToDestroy);
  15.                 }
  16.                 else
  17.                 {
  18.                     Destroy(this.gameObject);
  19.                 }
  20.             }
  21.         }
  22.     }
  23. }
复制代码
FollowCamera

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace RPG.Core
  5. {
  6.     public class FollowCamera : MonoBehaviour
  7.     {
  8.         [SerializeField]
  9.         Transform target;
  10.         void LateUpdate()
  11.         {
  12.             transform.position = target.position;
  13.         }
  14.     }
  15. }
复制代码
IAction

  1. namespace RPG.Core
  2. {
  3.     public interface IAction
  4.     {
  5.         void Cancel();
  6.     }
  7. }
复制代码
PersistentObjectSpawner

  1. using UnityEngine;
  2. namespace RPG.Core
  3. {
  4.     public class PersistentObjectSpawner : MonoBehaviour
  5.     {
  6.         [SerializeField]
  7.         GameObject persistentObjectPrefab;
  8.         
  9.         static bool hasSpawned = false;
  10.         private void Awake()
  11.         {
  12.             if (hasSpawned)
  13.             {
  14.                 return;
  15.             }
  16.             SpawnPersistentObjects();
  17.             hasSpawned = true;
  18.         }
  19.         private void SpawnPersistentObjects()
  20.         {
  21.             GameObject persistentObject = Instantiate(persistentObjectPrefab);
  22.             DontDestroyOnLoad (persistentObject);
  23.         }
  24.     }
  25. }
复制代码
CombatTarget

  1. using RPG.Attributes;
  2. using RPG.Control;
  3. using UnityEngine;
  4. namespace RPG.Combat
  5. {
  6.     [RequireComponent(typeof (Health))]
  7.     public class CombatTarget : MonoBehaviour, IRaycastable
  8.     {
  9.         public CursorType GetCursorType()
  10.         {
  11.             return CursorType.Combat;
  12.         }
  13.         public bool HandleRaycast(PlayerController callingController)
  14.         {
  15.             if (!callingController.GetComponent<Fighter>().CanAttack(gameObject)
  16.             )
  17.             {
  18.                 return false;
  19.             }
  20.             if (Input.GetMouseButton(0))
  21.             {
  22.                 callingController.GetComponent<Fighter>().Attack(gameObject);
  23.             }
  24.             return true;
  25.         }
  26.     }
  27. }
复制代码
EnemyHealthDisplay

  1. using System;
  2. using RPG.Attributes;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. namespace RPG.Combat
  6. {
  7.     public class EnemyHealthDisplay : MonoBehaviour
  8.     {
  9.         Fighter fighter;
  10.         private void Awake()
  11.         {
  12.             fighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
  13.         }
  14.         private void Update()
  15.         {
  16.             if (fighter.GetTarget() == null)
  17.             {
  18.                 GetComponent<Text>().text = "N/A";
  19.                 return;
  20.             }
  21.             Health health = fighter.GetTarget();
  22.             GetComponent<Text>().text =
  23.                 String
  24.                     .Format("{0:0.0}/{1:0}",
  25.                     health.GetHealthPoints(),
  26.                     health.GetMaxHealthPoints());
  27.         }
  28.     }
  29. }
复制代码
Fighter

  1. using System; // 引入系统命名空间
  2. using System.Collections.Generic; // 引入泛型集合相关命名空间
  3. using GameDevTV.Utils; // 引入GameDevTV工具相关命名空间
  4. using RPG.Attributes; // 引入角色属性相关的命名空间
  5. using RPG.Core; // 引入核心功能相关的命名空间
  6. using RPG.Movement; // 引入移动相关的命名空间
  7. using RPG.Saving; // 引入存档相关的命名空间
  8. using RPG.Stats; // 引入角色状态相关的命名空间
  9. using UnityEngine; // 引入Unity引擎相关的命名空间
  10. namespace RPG.Combat // 定义RPG战斗相关的命名空间
  11. {
  12.     public class Fighter : MonoBehaviour, IAction, ISaveable, IModifierProvider // 战斗者类
  13.     {
  14.         [SerializeField]
  15.         float timeBetweenAttacks = 1f; // 攻击之间的时间间隔
  16.         [SerializeField]
  17.         Transform rightHandTransform = null; // 右手的变换组件
  18.         [SerializeField]
  19.         Transform leftHandTransform = null; // 左手的变换组件
  20.         [SerializeField]
  21.         WeaponConfig defaultWeapon = null; // 默认武器配置
  22.         Health target; // 当前目标的生命值组件
  23.         float timeSinceLastAttack = Mathf.Infinity; // 自上次攻击以来的时间
  24.         WeaponConfig currentWeaponConfig; // 当前武器配置
  25.         LazyValue<Weapon> currentWeapon; // 当前武器
  26.         private void Awake()
  27.         {
  28.             currentWeaponConfig = defaultWeapon; // 初始化当前武器配置为默认武器
  29.             currentWeapon = new LazyValue<Weapon>(SetupDefaultWeapon); // 懒加载当前武器
  30.         }
  31.         private Weapon SetupDefaultWeapon()
  32.         {
  33.             return AttachWeapon(defaultWeapon); // 附加默认武器
  34.         }
  35.         private void Start()
  36.         {
  37.             currentWeapon.ForceInit(); // 强制初始化当前武器
  38.         }
  39.         private void Update()
  40.         {
  41.             timeSinceLastAttack += Time.deltaTime; // 更新自上次攻击以来的时间
  42.             // 如果没有攻击目标,什么也不做
  43.             if (target == null || target.IsDead())
  44.             {
  45.                 return;
  46.             }
  47.             // 如果目标存在
  48.             if (!GetIsInRange()) // 如果不在攻击范围内
  49.             {
  50.                 // 移动到目标位置
  51.                 GetComponent<Mover>().MoveTo(target.transform.position, 1f);
  52.             }
  53.             else
  54.             {
  55.                 // 停止移动
  56.                 GetComponent<Mover>().Cancel();
  57.                 AttackBehaviour(); // 执行攻击行为
  58.             }
  59.         }
  60.         public void EquipWeapon(WeaponConfig weapon) // 装备武器
  61.         {
  62.             currentWeaponConfig = weapon; // 更新当前武器配置
  63.             currentWeapon.value = AttachWeapon(weapon); // 附加新武器
  64.         }
  65.         private Weapon AttachWeapon(WeaponConfig weapon) // 附加武器
  66.         {
  67.             Animator animator = GetComponent<Animator>(); // 获取动画组件
  68.             return weapon
  69.                 .Spawn(rightHandTransform, leftHandTransform, animator); // 在手上生成武器
  70.         }
  71.         public Health GetTarget() // 获取当前目标的生命值组件
  72.         {
  73.             return target;
  74.         }
  75.         public bool CanAttack(GameObject combatTarget) // 判断是否可以攻击目标
  76.         {
  77.             if (combatTarget == null)
  78.             {
  79.                 return false; // 目标为空时返回false
  80.             }
  81.             Health targetToTest = combatTarget.GetComponent<Health>(); // 获取目标的生命值组件
  82.             return !targetToTest.IsDead(); // 如果目标未死亡,则返回true
  83.         }
  84.         public void Attack(GameObject combatTarget) // 开始攻击目标
  85.         {
  86.             GetComponent<ActionScheduler>().StartAction(this); // 启动当前行动
  87.             target = combatTarget.GetComponent<Health>(); // 设置目标的生命值组件
  88.         }
  89.         private void AttackBehaviour() // 攻击行为
  90.         {
  91.             transform.LookAt(target.transform); // 朝向目标
  92.             if (timeSinceLastAttack > timeBetweenAttacks) // 如果已经超过攻击间隔
  93.             {
  94.                 TriggerAttack(); // 触发攻击
  95.                 timeSinceLastAttack = 0f; // 重置上次攻击时间
  96.             }
  97.         }
  98.         private void TriggerAttack() // 触发攻击动画
  99.         {
  100.             GetComponent<Animator>().ResetTrigger("stopAttack"); // 重置停止攻击触发器
  101.             GetComponent<Animator>().SetTrigger("attack"); // 设置攻击触发器
  102.         }
  103.         // 动画事件
  104.         void Hit()
  105.         {
  106.             if (!target) return; // 如果没有目标则返回
  107.             float damage = GetComponent<BaseStats>().GetStat(Stat.Damage); // 获取伤害值
  108.             if (currentWeapon.value != null) // 如果当前武器不为空
  109.             {
  110.                 currentWeapon.value.OnHit(); // 执行武器的命中逻辑
  111.             }
  112.             // 检查当前武器是否有投射物
  113.             if (currentWeaponConfig.HasProjectile())
  114.             {
  115.                 currentWeaponConfig.LaunchProjectile(
  116.                     rightHandTransform, // 右手变换
  117.                     leftHandTransform, // 左手变换
  118.                     target, // 目标
  119.                     gameObject, // 攻击者
  120.                     damage // 伤害值
  121.                 );
  122.             }
  123.             else // 否则直接造成伤害
  124.             {
  125.                 target.TakeDamage(gameObject, damage); // 目标受到伤害
  126.             }
  127.         }
  128.         // 动画中的射击事件
  129.         void Shoot()
  130.         {
  131.             Hit(); // 调用命中方法
  132.         }
  133.         private bool GetIsInRange() // 检查是否在攻击范围内
  134.         {
  135.             return Vector3
  136.                 .Distance(transform.position, target.transform.position) <
  137.             currentWeaponConfig.GetRange(); // 比较距离和武器范围
  138.         }
  139.         public void Cancel() // 取消当前攻击
  140.         {
  141.             StopAttack(); // 停止攻击
  142.             target = null; // 清空目标
  143.             GetComponent<Mover>().Cancel(); // 取消移动
  144.         }
  145.         private void StopAttack() // 停止攻击动画
  146.         {
  147.             GetComponent<Animator>().ResetTrigger("attack"); // 重置攻击触发器
  148.             GetComponent<Animator>().SetTrigger("stopAttack"); // 设置停止攻击触发器
  149.         }
  150.         public object CaptureState() // 捕获当前状态
  151.         {
  152.             return currentWeaponConfig.name; // 返回当前武器的名称
  153.         }
  154.         public void RestoreState(object state) // 恢复状态
  155.         {
  156.             string weaponName = (string)state; // 将状态转换为武器名称
  157.             WeaponConfig weapon = Resources.Load<WeaponConfig>(weaponName); // 从资源中加载武器配置
  158.             EquipWeapon(weapon); // 装备武器
  159.         }
  160.         public IEnumerable<float> GetAdditiveModifiers(Stat stat) // 获取加成修正
  161.         {
  162.             if (stat == Stat.Damage) // 如果是伤害修正
  163.             {
  164.                 yield return currentWeaponConfig.GetDamage(); // 返回当前武器的伤害
  165.             }
  166.         }
  167.         public IEnumerable<float> GetPercentageModifiers(Stat stat) // 获取百分比修正
  168.         {
  169.             if (stat == Stat.Damage) // 如果是伤害修正
  170.             {
  171.                 yield return currentWeaponConfig.GetPercentageBonus(); // 返回当前武器的百分比加成
  172.             }
  173.         }
  174.     }
  175. }
复制代码
Projectile

  1. using RPG.Attributes; // 引入角色属性相关命名空间
  2. using RPG.Core; // 引入核心功能相关命名空间
  3. using UnityEngine; // 引入Unity引擎相关命名空间
  4. using UnityEngine.Events; // 引入事件相关命名空间
  5. namespace RPG.Combat // 定义RPG战斗相关的命名空间
  6. {
  7.     public class Projectile : MonoBehaviour // 投射物类
  8.     {
  9.         [SerializeField]
  10.         float speed = 1f; // 投射物的速度
  11.         [SerializeField]
  12.         bool isHoming = false; // 是否为追踪模式
  13.         [SerializeField]
  14.         GameObject hitEffect = null; // 碰撞效果的游戏对象
  15.         [SerializeField]
  16.         float maxLifeTime = 10f; // 投射物的最大生命周期
  17.         [SerializeField]
  18.         GameObject[] destroyOnHit = null; // 碰撞时需要销毁的对象
  19.         [SerializeField]
  20.         float lifeAfterImpact = 2f; // 碰撞后的存活时间
  21.         [SerializeField]
  22.         UnityEvent onHit; // 碰撞事件
  23.         Health target = null; // 目标的生命值组件
  24.         GameObject instigator = null; // 发射者的游戏对象
  25.         float damage = 0f; // 伤害值
  26.         private void Start()
  27.         {
  28.             transform.LookAt(GetAimLocation()); // 初始时朝向目标位置
  29.         }
  30.         void Update()
  31.         {
  32.             if (target == null) // 如果目标为空,退出更新
  33.             {
  34.                 return;
  35.             }
  36.             // 如果是追踪模式且目标未死亡
  37.             if (isHoming && !target.IsDead())
  38.             {
  39.                 transform.LookAt(GetAimLocation(); // 朝向目标位置
  40.             }
  41.             transform.Translate(Vector3.forward * speed * Time.deltaTime); // 根据速度移动
  42.         }
  43.         private void OnTriggerEnter(Collider other) // 碰撞检测
  44.         {
  45.             // 如果碰撞对象不是目标,则返回
  46.             if (other.GetComponent<Health>() != target)
  47.             {
  48.                 return;
  49.             }
  50.             // 如果目标已死亡,则返回
  51.             if (target.IsDead())
  52.             {
  53.                 return;
  54.             }
  55.             target.TakeDamage(instigator, damage); // 对目标造成伤害
  56.             speed = 0; // 停止投射物移动
  57.             onHit.Invoke(); // 触发碰撞事件
  58.             // 如果存在碰撞效果,则实例化
  59.             if (hitEffect != null)
  60.             {
  61.                 Instantiate(hitEffect, GetAimLocation(), transform.rotation);
  62.             }
  63.             // 销毁指定的对象
  64.             foreach (GameObject toDestroy in destroyOnHit)
  65.             {
  66.                 Destroy(toDestroy);
  67.             }
  68.             // 在撞击后的生存时间内销毁投射物
  69.             Destroy(this.gameObject, lifeAfterImpact);
  70.         }
  71.         // 设置目标、发射者和伤害值
  72.         public void SetTarget(
  73.             Health target,
  74.             GameObject instigator,
  75.             float damage
  76.         )
  77.         {
  78.             this.target = target; // 设置目标
  79.             this.instigator = instigator; // 设置发射者
  80.             this.damage = damage; // 设置伤害值
  81.             // 在最大生命周期内销毁投射物
  82.             Destroy(gameObject, maxLifeTime);
  83.         }
  84.         private Vector3 GetAimLocation() // 获取目标的位置
  85.         {
  86.             CapsuleCollider targetCapsule =
  87.                 target.GetComponent<CapsuleCollider>(); // 获取目标的胶囊碰撞器
  88.             if (targetCapsule == null)
  89.             {
  90.                 return target.transform.position; // 如果没有胶囊碰撞器,返回目标位置
  91.             }
  92.             // 返回目标位置的顶部
  93.             return target.transform.position +
  94.             Vector3.up * targetCapsule.height / 2;
  95.         }
  96.     }
  97. }
复制代码
Weapon

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace RPG.Combat
  5. {
  6.     public class Weapon : MonoBehaviour
  7.     {
  8.         public void OnHit()
  9.         {
  10.             print($"Weapon Hit {gameObject.name}");
  11.         }
  12.     }
  13. }
复制代码
WeaponConfig

  1. using RPG.Attributes; // 引入角色属性相关命名空间
  2. using UnityEngine; // 引入Unity引擎相关命名空间
  3. namespace RPG.Combat // 定义RPG战斗相关的命名空间
  4. {
  5.     [
  6.         CreateAssetMenu(
  7.             fileName = "Weapon", // 创建的资产文件名
  8.             menuName = "Weapons/Make New Weapon", // 菜单中显示的名称
  9.             order = 0) // 菜单中的顺序
  10.     ]
  11.     public class WeaponConfig : ScriptableObject // 武器配置类,继承自ScriptableObject
  12.     {
  13.         [SerializeField]
  14.         AnimatorOverrideController animatorOverride = null; // 动画覆盖控制器
  15.         [SerializeField]
  16.         Weapon equippedPrefab = null; // 装备的武器预制件
  17.         [SerializeField]
  18.         float weaponDamage = 5f; // 武器伤害
  19.         [SerializeField]
  20.         float percentageBonus = 0; // 伤害百分比加成
  21.         [SerializeField]
  22.         float weaponRange = 2f; // 武器攻击范围
  23.         [SerializeField]
  24.         bool isRightHanded = true; // 是否为右手武器
  25.         [SerializeField]
  26.         Projectile projectile = null; // 武器的投射物
  27.         const string weaponName = "Weapon"; // 武器名称常量
  28.         // 实例化并装备武器
  29.         public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
  30.         {
  31.             DestroyOldWeapon(rightHand, leftHand); // 销毁旧的武器
  32.             Weapon weapon = null; // 初始化武器变量
  33.             if (equippedPrefab != null) // 如果有装备的预制件
  34.             {
  35.                 Transform handTransform = GetTransform(rightHand, leftHand); // 获取手部变换
  36.                 weapon = Instantiate(equippedPrefab, handTransform); // 实例化武器
  37.                 weapon.gameObject.name = weaponName; // 设置武器名称
  38.             }
  39.             var overrideController =
  40.                 animator.runtimeAnimatorController as
  41.                 AnimatorOverrideController; // 获取动画覆盖控制器
  42.             if (animatorOverride != null) // 如果有动画覆盖
  43.             {
  44.                 animator.runtimeAnimatorController = animatorOverride; // 设置动画控制器为覆盖控制器
  45.             }
  46.             else if (overrideController) // 如果已经有覆盖控制器
  47.             {
  48.                
  49.                 animator.runtimeAnimatorController =
  50.                     overrideController.runtimeAnimatorController; // 更新为默认动画控制器
  51.             }
  52.             return weapon; // 返回装备的武器
  53.         }
  54.         // 销毁旧武器
  55.         private void DestroyOldWeapon(Transform rightHand, Transform leftHand)
  56.         {
  57.             // 查找旧武器
  58.             Transform oldWeapon = rightHand.Find(weaponName);
  59.             if (oldWeapon == null)
  60.             {
  61.                 oldWeapon = leftHand.Find(weaponName); // 如果右手没有找到,查找左手
  62.             }
  63.             // 如果没有找到旧武器
  64.             if (oldWeapon == null)
  65.             {
  66.                 return; // 直接返回
  67.             }
  68.             // 销毁旧武器
  69.             oldWeapon.name = "DESTROYING"; // 设置名称为销毁中
  70.             Destroy(oldWeapon.gameObject); // 销毁游戏对象
  71.         }
  72.         // 获取手部变换
  73.         private Transform GetTransform(Transform rightHand, Transform leftHand)
  74.         {
  75.             Transform handTransform;
  76.             if (isRightHanded) // 如果是右手
  77.             {
  78.                 handTransform = rightHand; // 使用右手变换
  79.             }
  80.             else // 如果是左手
  81.             {
  82.                 handTransform = leftHand; // 使用左手变换
  83.             }
  84.             return handTransform; // 返回手部变换
  85.         }
  86.         // 检查武器是否有投射物
  87.         public bool HasProjectile()
  88.         {
  89.             return projectile != null; // 返回投射物是否为null
  90.         }
  91.         // 发射投射物
  92.         public void LaunchProjectile(
  93.             Transform rightHand,
  94.             Transform leftHand,
  95.             Health target,
  96.             GameObject instigator,
  97.             float calculatedDamage
  98.         )
  99.         {
  100.             // 实例化投射物
  101.             Projectile projectileInstance =
  102.                 Instantiate(projectile,
  103.                 GetTransform(rightHand, leftHand).position,
  104.                 Quaternion.identity);
  105.             // 设置投射物的目标、发射者和伤害值
  106.             projectileInstance.SetTarget(target, instigator, calculatedDamage);
  107.         }
  108.         // 获取武器攻击范围
  109.         public float GetRange()
  110.         {
  111.             return weaponRange; // 返回武器范围
  112.         }
  113.         // 获取武器的百分比加成
  114.         public float GetPercentageBonus()
  115.         {
  116.             return percentageBonus; // 返回百分比加成
  117.         }
  118.         // 获取武器的基础伤害
  119.         public float GetDamage()
  120.         {
  121.             return weaponDamage; // 返回武器伤害
  122.         }
  123.     }
  124. }
复制代码
WeaponPickup

  1. using System; // 引入系统命名空间
  2. using System.Collections; // 引入集合命名空间
  3. using RPG.Control; // 引入角色控制相关命名空间
  4. using UnityEngine; // 引入Unity引擎相关命名空间
  5. namespace RPG.Combat // 定义RPG战斗相关的命名空间
  6. {
  7.     public class WeaponPickup : MonoBehaviour, IRaycastable // 武器拾取类,继承自MonoBehaviour,并实现IRaycastable接口
  8.     {
  9.         [SerializeField]
  10.         WeaponConfig weapon = null; // 武器配置,序列化以便在Inspector中设置
  11.         [SerializeField]
  12.         float respawnTime = 5f; // 武器重生时间
  13.         // 当玩家进入触发器时调用
  14.         private void OnTriggerEnter(Collider other)
  15.         {
  16.             // 检查进入的对象是否为玩家
  17.             if (other.gameObject.tag == "Player")
  18.             {
  19.                 Pickup(other.GetComponent<Fighter>()); // 拾取武器
  20.             }
  21.         }
  22.         // 拾取武器的方法
  23.         private void Pickup(Fighter fighter)
  24.         {
  25.             fighter.EquipWeapon(weapon); // 为战斗者装备武器
  26.             StartCoroutine(HideForSeconds(respawnTime)); // 启动协程隐藏武器
  27.         }
  28.         // 协程:在指定时间后隐藏武器
  29.         private IEnumerator HideForSeconds(float seconds)
  30.         {
  31.             ShowPickup(false); // 隐藏武器
  32.             yield return new WaitForSeconds(seconds); // 等待指定的时间
  33.             ShowPickup(true); // 重新显示武器
  34.         }
  35.         // 控制武器的显示与隐藏
  36.         private void ShowPickup(bool shouldShow)
  37.         {
  38.             GetComponent<Collider>().enabled = shouldShow; // 启用或禁用触发器
  39.             // 方法1:设置子物体的激活状态
  40.             // transform.GetChild(0).gameObject.SetActive(shouldShow);
  41.             //
  42.             // 方法2:遍历所有子物体,设置它们的激活状态
  43.             foreach (Transform child in transform)
  44.             {
  45.                 child.gameObject.SetActive(shouldShow); // 设置子物体的显示状态
  46.             }
  47.         }
  48.         // 实现IRaycastable接口的方法,处理射线检测
  49.         public bool HandleRaycast(PlayerController callingController)
  50.         {
  51.             // 如果鼠标左键被按下,拾取武器
  52.             if (Input.GetMouseButtonDown(0))
  53.             {
  54.                 Pickup(callingController.GetComponent<Fighter>());
  55.             }
  56.             return true; // 返回true表示处理成功
  57.         }
  58.         // 获取光标类型,用于拾取武器时的光标显示
  59.         public CursorType GetCursorType()
  60.         {
  61.             return CursorType.Pickup; // 返回拾取光标类型
  62.         }
  63.     }
  64. }
复制代码


Fader

  1. using System.Collections; // 引入集合命名空间
  2. using UnityEngine; // 引入Unity引擎相关命名空间
  3. namespace RPG.SceneManagement // 定义RPG场景管理相关的命名空间
  4. {
  5.     public class Fader : MonoBehaviour // Fader类,负责场景的淡入淡出效果
  6.     {
  7.         CanvasGroup canvasGroup; // 用于控制UI元素的透明度
  8.         Coroutine currentActiveFade; // 当前正在执行的淡入淡出协程
  9.         private void Start() // Unity生命周期方法,在对象初始化时调用
  10.         {
  11.             canvasGroup = GetComponent<CanvasGroup>(); // 获取CanvasGroup组件
  12.         }
  13.         public void FadeOutImmediate() // 立即淡出方法
  14.         {
  15.             canvasGroup.alpha = 1; // 将透明度设置为1(完全可见)
  16.         }
  17.         public Coroutine FadeOut(float time) // 淡出方法,返回一个协程
  18.         {
  19.             return Fade(1, time); // 调用Fade方法,目标透明度为1
  20.         }
  21.         public Coroutine FadeIn(float time) // 淡入方法,返回一个协程
  22.         {
  23.             return Fade(0, time); // 调用Fade方法,目标透明度为0
  24.         }
  25.         // target = 目标透明度
  26.         public Coroutine Fade(float target, float time) // 淡入淡出通用方法
  27.         {
  28.             if (currentActiveFade != null) // 检查是否有正在执行的淡入淡出
  29.             {
  30.                 StopCoroutine(currentActiveFade); // 停止当前的协程
  31.             }
  32.             currentActiveFade = StartCoroutine(FadeRoutine(target, time)); // 启动新的淡入淡出协程
  33.             return currentActiveFade; // 返回当前协程
  34.         }
  35.         private IEnumerator FadeRoutine(float target, float time) // 实际的淡入淡出过程
  36.         {
  37.             while (!Mathf.Approximately(canvasGroup.alpha, target)) // 当当前透明度与目标透明度不相等时
  38.             {
  39.                 // 根据时间逐步调整透明度
  40.                 canvasGroup.alpha =
  41.                     Mathf
  42.                         .MoveTowards(canvasGroup.alpha,
  43.                         target,
  44.                         Time.deltaTime / time); // 逐步接近目标透明度
  45.                 yield return null; // 等待下一帧
  46.             }
  47.         }
  48.     }
  49. }
复制代码
Portal

  1. using System; // 引入系统命名空间
  2. using System.Collections; // 引入集合命名空间
  3. using System.Collections.Generic; // 引入泛型集合命名空间
  4. using RPG.Control; // 引入RPG控制相关命名空间
  5. using UnityEngine; // 引入Unity引擎相关命名空间
  6. using UnityEngine.AI; // 引入导航相关命名空间
  7. using UnityEngine.SceneManagement; // 引入场景管理相关命名空间
  8. namespace RPG.SceneManagement // 定义RPG场景管理相关的命名空间
  9. {
  10.     public class Portal : MonoBehaviour // Portal类,负责场景传送
  11.     {
  12.         // 目标场景标识符枚举
  13.         enum DestinationIdentifier
  14.         {
  15.             Sandbox_End_Sandbox2_Start, // 从沙盒结束到沙盒2开始
  16.             Sandbox_Start_Sandbox2_End // 从沙盒开始到沙盒2结束
  17.         }
  18.         [SerializeField]
  19.         int sceneToLoad = -1; // 要加载的场景编号
  20.         [SerializeField]
  21.         Transform spawnPoint; // 玩家出生点
  22.         [SerializeField]
  23.         DestinationIdentifier destination; // 目的地标识符
  24.         [SerializeField]
  25.         float fadeOutTime = 1f; // 淡出时间
  26.         [SerializeField]
  27.         float fadeInTime = 2f; // 淡入时间
  28.         [SerializeField]
  29.         float fadeWaitTime = 0.5f; // 淡出后的等待时间
  30.         private void OnTriggerEnter(Collider other) // 当玩家进入传送门触发器
  31.         {
  32.             if (other.tag == "Player") // 检查是否为玩家
  33.             {
  34.                 StartCoroutine(Transition()); // 开始场景转换协程
  35.             }
  36.         }
  37.         private IEnumerator Transition() // 场景转换协程
  38.         {
  39.             if (sceneToLoad < 0) // 检查场景编号是否有效
  40.             {
  41.                 Debug.LogError("Scene to load not set."); // 输出错误信息
  42.                 yield break; // 结束协程
  43.             }
  44.             DontDestroyOnLoad(gameObject); // 在场景加载时不销毁该对象
  45.             Fader fader = FindObjectOfType<Fader>(); // 查找Fader对象
  46.             SavingWrapper wrapper = FindObjectOfType<SavingWrapper>(); // 查找保存管理器
  47.             // 禁用当前场景中的玩家控制器
  48.             PlayerController playerController =
  49.                 GameObject
  50.                     .FindWithTag("Player")
  51.                     .GetComponent<PlayerController>();
  52.             playerController.enabled = false;
  53.             yield return fader.FadeOut(fadeOutTime); // 执行淡出效果
  54.             wrapper.Save(); // 保存当前游戏状态
  55.             yield return SceneManager.LoadSceneAsync(sceneToLoad); // 异步加载目标场景
  56.             // 在新场景开始时
  57.             PlayerController newPlayerController =
  58.                 GameObject
  59.                     .FindWithTag("Player")
  60.                     .GetComponent<PlayerController>();
  61.             newPlayerController.enabled = false; // 禁用玩家控制器
  62.             wrapper.Load(); // 加载保存的游戏状态
  63.             Portal otherPortal = GetOtherPortal(); // 获取对应的另一个传送门
  64.             UpdatePlayer(otherPortal); // 更新玩家位置
  65.             wrapper.Save(); // 再次保存游戏状态
  66.             yield return new WaitForSeconds(fadeWaitTime); // 等待指定时间
  67.             fader.FadeIn(fadeInTime); // 执行淡入效果
  68.             // 启用新场景中的玩家控制器
  69.             newPlayerController.enabled = true;
  70.             Destroy(gameObject); // 销毁传送门对象
  71.         }
  72.         private void UpdatePlayer(Portal otherPortal) // 更新玩家在新场景中的位置
  73.         {
  74.             GameObject player = GameObject.FindWithTag("Player"); // 查找玩家对象
  75.             player
  76.                 .GetComponent<NavMeshAgent>()
  77.                 .Warp(otherPortal.spawnPoint.position); // 将玩家传送到新出生点
  78.             player.transform.rotation = otherPortal.spawnPoint.rotation; // 旋转玩家与出生点一致
  79.         }
  80.         private Portal GetOtherPortal() // 获取对应的另一个传送门
  81.         {
  82.             foreach (var portal in FindObjectsOfType<Portal>()) // 遍历所有Portal对象
  83.             {
  84.                 if (portal == this) // 如果是当前对象,则跳过
  85.                 {
  86.                     continue;
  87.                 }
  88.                 if (portal.destination != destination) // 检查目的地是否一致
  89.                 {
  90.                     continue;
  91.                 }
  92.                 return portal; // 返回对应的传送门
  93.             }
  94.             return null; // 如果没有找到,返回null
  95.         }
  96.     }
  97. }
复制代码
SavingWrapper

  1. using System.Collections; // 引入集合命名空间
  2. using RPG.Saving; // 引入保存相关命名空间
  3. using UnityEngine; // 引入Unity引擎相关命名空间
  4. namespace RPG.SceneManagement // 定义RPG场景管理相关的命名空间
  5. {
  6.     public class SavingWrapper : MonoBehaviour // SavingWrapper类,用于处理保存和加载场景
  7.     {
  8.         const string defaultSaveFile = "save"; // 默认保存文件名
  9.         [SerializeField]
  10.         float fadeInTime = 0.2f; // 淡入时间
  11.         private void Awake() // 在对象激活时调用
  12.         {
  13.             StartCoroutine(LoadLastScene()); // 开始加载上一个场景的协程
  14.         }
  15.         IEnumerator LoadLastScene() // 协程:加载上一个场景
  16.         {
  17.             // 异步加载最后一个保存的场景
  18.             yield return GetComponent<SavingSystem>()
  19.                     .LoadLastScene(defaultSaveFile);
  20.             Fader fader = FindObjectOfType<Fader>(); // 查找Fader对象
  21.             fader.FadeOutImmediate(); // 立即执行淡出效果
  22.             yield return fader.FadeIn(fadeInTime); // 执行淡入效果
  23.         }
  24.         void Update() // 每帧更新
  25.         {
  26.             if (Input.GetKeyDown(KeyCode.L)) // 如果按下L键
  27.             {
  28.                 Load(); // 加载保存
  29.             }
  30.             if (Input.GetKeyDown(KeyCode.S)) // 如果按下S键
  31.             {
  32.                 Save(); // 保存游戏
  33.             }
  34.             if (Input.GetKeyDown(KeyCode.D)) // 如果按下D键
  35.             {
  36.                 Delete(); // 删除保存
  37.             }
  38.         }
  39.         public void Save() // 保存方法
  40.         {
  41.             GetComponent<SavingSystem>().Save(defaultSaveFile); // 调用保存系统保存数据
  42.         }
  43.         public void Load() // 加载方法
  44.         {
  45.             GetComponent<SavingSystem>().Load(defaultSaveFile); // 调用保存系统加载数据
  46.         }
  47.         public void Delete() // 删除保存方法
  48.         {
  49.             GetComponent<SavingSystem>().Delete(defaultSaveFile); // 调用保存系统删除数据
  50.         }
  51.     }
  52. }
复制代码
ISaveable

  1. namespace RPG.Saving
  2. {
  3.     public interface ISaveable
  4.     {
  5.         object CaptureState();
  6.         void RestoreState(object state);
  7.     }
  8. }
复制代码
SaveableEntity

  1. using System; // 引入基本命名空间
  2. using System.Collections.Generic; // 引入集合命名空间
  3. using RPG.Core; // 引入RPG核心命名空间
  4. using UnityEditor; // 引入Unity编辑器相关命名空间
  5. using UnityEngine; // 引入Unity引擎相关命名空间
  6. using UnityEngine.AI; // 引入Unity导航系统相关命名空间
  7. namespace RPG.Saving // 定义RPG保存相关的命名空间
  8. {
  9.     [ExecuteAlways] // 使该类在编辑模式和播放模式下都能执行
  10.     public class SaveableEntity : MonoBehaviour // SaveableEntity类,表示可保存的实体
  11.     {
  12.         [SerializeField] string uniqueIdentifier = ""; // 唯一标识符
  13.         static Dictionary<string, SaveableEntity> globalLookup = new Dictionary<string, SaveableEntity>(); // 全局查找字典
  14.         public string GetUniqueIdentifier() // 获取唯一标识符
  15.         {
  16.             return uniqueIdentifier;
  17.         }
  18.         public object CaptureState() // 捕获状态
  19.         {
  20.             Dictionary<string, object> state = new Dictionary<string, object>(); // 创建状态字典
  21.             foreach (ISaveable saveable in GetComponents<ISaveable>()) // 获取所有可保存组件
  22.             {
  23.                 // 将每个可保存组件的状态添加到状态字典
  24.                 state[saveable.GetType().ToString()] = saveable.CaptureState();
  25.             }
  26.             return state; // 返回状态字典
  27.         }
  28.         public void RestoreState(object state) // 恢复状态
  29.         {
  30.             Dictionary<string, object> stateDict = (Dictionary<string, object>)state; // 将状态转换为字典
  31.             foreach (ISaveable saveable in GetComponents<ISaveable>()) // 获取所有可保存组件
  32.             {
  33.                 string typeString = saveable.GetType().ToString(); // 获取组件类型字符串
  34.                 if (stateDict.ContainsKey(typeString)) // 如果状态字典包含该类型
  35.                 {
  36.                     saveable.RestoreState(stateDict[typeString]); // 恢复状态
  37.                 }
  38.             }
  39.         }
  40. #if UNITY_EDITOR
  41.         private void Update() // 编辑器更新方法
  42.         {
  43.             if (Application.IsPlaying(gameObject)) return; // 如果正在播放,则返回
  44.             if (string.IsNullOrEmpty(gameObject.scene.path)) return; // 如果场景路径为空,则返回
  45.             SerializedObject serializedObject = new SerializedObject(this); // 创建序列化对象
  46.             SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier"); // 查找唯一标识符属性
  47.             
  48.             // 如果唯一标识符为空或不唯一
  49.             if (string.IsNullOrEmpty(property.stringValue) || !IsUnique(property.stringValue))
  50.             {
  51.                 property.stringValue = System.Guid.NewGuid().ToString(); // 生成新的唯一标识符
  52.                 serializedObject.ApplyModifiedProperties(); // 应用修改
  53.             }
  54.             globalLookup[property.stringValue] = this; // 将当前对象添加到全局查找字典
  55.         }
  56. #endif
  57.         private bool IsUnique(string candidate) // 检查唯一性
  58.         {
  59.             if (!globalLookup.ContainsKey(candidate)) return true; // 如果字典中不存在该候选标识符,则返回true
  60.             if (globalLookup[candidate] == this) return true; // 如果字典中的对象是当前对象,则返回true
  61.             if (globalLookup[candidate] == null) // 如果字典中的对象为空
  62.             {
  63.                 globalLookup.Remove(candidate); // 从字典中移除该标识符
  64.                 return true; // 返回true
  65.             }
  66.             if (globalLookup[candidate].GetUniqueIdentifier() != candidate) // 如果字典中的对象的唯一标识符与候选标识符不一致
  67.             {
  68.                 globalLookup.Remove(candidate); // 从字典中移除该标识符
  69.                 return true; // 返回true
  70.             }
  71.             return false; // 否则返回false
  72.         }
  73.     }
  74. }
复制代码
SavingSystem

  1. using System; // 引入基本命名空间
  2. using System.Collections; // 引入集合命名空间
  3. using System.Collections.Generic; // 引入泛型集合命名空间
  4. using System.IO; // 引入输入输出相关命名空间
  5. using System.Runtime.Serialization.Formatters.Binary; // 引入二进制格式化器
  6. using System.Text; // 引入字符串处理相关命名空间
  7. using UnityEngine; // 引入Unity引擎相关命名空间
  8. using UnityEngine.SceneManagement; // 引入场景管理相关命名空间
  9. namespace RPG.Saving // 定义RPG保存相关的命名空间
  10. {
  11.     public class SavingSystem : MonoBehaviour // 保存系统类
  12.     {
  13.         // 加载上一个场景
  14.         public IEnumerator LoadLastScene(string saveFile)
  15.         {
  16.             Dictionary<string, object> state = LoadFile(saveFile); // 加载保存文件
  17.             int buildIndex = SceneManager.GetActiveScene().buildIndex; // 获取当前场景的构建索引
  18.             if (state.ContainsKey("lastSceneBuildIndex")) // 如果状态包含最后场景的构建索引
  19.             {
  20.                 buildIndex = (int)state["lastSceneBuildIndex"]; // 更新构建索引
  21.             }
  22.             yield return SceneManager.LoadSceneAsync(buildIndex); // 异步加载场景
  23.             RestoreState(state); // 恢复状态
  24.         }
  25.         // 保存游戏状态
  26.         public void Save(string saveFile)
  27.         {
  28.             Dictionary<string, object> state = LoadFile(saveFile); // 加载保存文件
  29.             CaptureState(state); // 捕获当前状态
  30.             SaveFile(saveFile, state); // 保存状态到文件
  31.         }
  32.         // 加载游戏状态
  33.         public void Load(string saveFile)
  34.         {
  35.             RestoreState(LoadFile(saveFile)); // 恢复从保存文件加载的状态
  36.         }
  37.         // 删除保存文件
  38.         public void Delete(string saveFile)
  39.         {
  40.             File.Delete(GetPathFromSaveFile(saveFile)); // 删除指定的保存文件
  41.         }
  42.         // 加载保存文件
  43.         private Dictionary<string, object> LoadFile(string saveFile)
  44.         {
  45.             string path = GetPathFromSaveFile(saveFile); // 获取保存文件路径
  46.             if (!File.Exists(path)) // 如果文件不存在
  47.             {
  48.                 return new Dictionary<string, object>(); // 返回空字典
  49.             }
  50.             using (FileStream stream = File.Open(path, FileMode.Open)) // 打开文件流
  51.             {
  52.                 BinaryFormatter formatter = new BinaryFormatter(); // 创建二进制格式化器
  53.                 return (Dictionary<string, object>)
  54.                 formatter.Deserialize(stream); // 反序列化并返回状态字典
  55.             }
  56.         }
  57.         // 保存状态到文件
  58.         private void SaveFile(string saveFile, object state)
  59.         {
  60.             string path = GetPathFromSaveFile(saveFile); // 获取保存文件路径
  61.             print("Saving to " + path); // 输出保存路径
  62.             using (FileStream stream = File.Open(path, FileMode.Create)) // 创建文件流
  63.             {
  64.                 BinaryFormatter formatter = new BinaryFormatter(); // 创建二进制格式化器
  65.                 formatter.Serialize(stream, state); // 序列化状态并保存
  66.             }
  67.         }
  68.         // 捕获当前游戏状态
  69.         private void CaptureState(Dictionary<string, object> state)
  70.         {
  71.             foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>()) // 遍历所有可保存实体
  72.             {
  73.                 state[saveable.GetUniqueIdentifier()] = saveable.CaptureState(); // 捕获状态并添加到字典
  74.             }
  75.             state["lastSceneBuildIndex"] = SceneManager.GetActiveScene().buildIndex; // 保存当前场景的构建索引
  76.         }
  77.         // 恢复游戏状态
  78.         private void RestoreState(Dictionary<string, object> state)
  79.         {
  80.             foreach (SaveableEntity saveable in FindObjectsOfType<SaveableEntity>()) // 遍历所有可保存实体
  81.             {
  82.                 string id = saveable.GetUniqueIdentifier(); // 获取唯一标识符
  83.                 if (state.ContainsKey(id)) // 如果状态字典包含该标识符
  84.                 {
  85.                     saveable.RestoreState(state[id]); // 恢复状态
  86.                 }
  87.             }
  88.         }
  89.         // 获取保存文件路径
  90.         private string GetPathFromSaveFile(string saveFile)
  91.         {
  92.             return Path.Combine(Application.persistentDataPath, saveFile + ".sav"); // 生成完整的保存文件路径
  93.         }
  94.     }
  95. }
复制代码
SerializableVector3

  1. using UnityEngine;
  2. namespace RPG.Saving
  3. {
  4.     [System.Serializable]
  5.     public class SerializableVector3
  6.     {
  7.         float x, y, z;
  8.         public SerializableVector3(Vector3 vector)
  9.         {
  10.             x = vector.x;
  11.             y = vector.y;
  12.             z = vector.z;
  13.         }
  14.         public Vector3 ToVector()
  15.         {
  16.             return new Vector3(x, y, z);
  17.         }
  18.     }
  19. }
复制代码
CinematicTrigger

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Playables;
  5. namespace RPG.Cinematics
  6. {
  7.     public class CinematicTrigger : MonoBehaviour
  8.     {
  9.         bool alreadyTriggered = false;
  10.         private void OnTriggerEnter(Collider other)
  11.         {
  12.             if (!alreadyTriggered && other.gameObject.tag == "Player")
  13.             {
  14.                 alreadyTriggered = true;
  15.                 GetComponent<PlayableDirector>().Play();
  16.             }
  17.         }
  18.     }
  19. }
复制代码
CinematicControlRemover

  1. using RPG.Control;
  2. using RPG.Core;
  3. using UnityEngine;
  4. using UnityEngine.Playables;
  5. public class CinematicControlRemover : MonoBehaviour
  6. {
  7.     GameObject player;
  8.     private void Awake()
  9.     {
  10.         player = GameObject.FindWithTag("Player");
  11.     }
  12.     private void OnEnable()
  13.     {
  14.         // event + observer pattern in C#
  15.         GetComponent<PlayableDirector>().played += DisableControl;
  16.         GetComponent<PlayableDirector>().stopped += EnableControl;
  17.     }
  18.     private void OnDisable()
  19.     {
  20.         GetComponent<PlayableDirector>().played -= DisableControl;
  21.         GetComponent<PlayableDirector>().stopped -= EnableControl;
  22.     }
  23.     void DisableControl(PlayableDirector pd)
  24.     {
  25.         player.GetComponent<ActionScheduler>().CancelCurrentAction();
  26.         player.GetComponent<PlayerController>().enabled = false;
  27.     }
  28.     void EnableControl(PlayableDirector pd)
  29.     {
  30.         player.GetComponent<PlayerController>().enabled = true;
  31.     }
  32. }
复制代码
BaseStats

  1. using System; // 引入基本命名空间
  2. using System.Collections; // 引入集合命名空间
  3. using System.Collections.Generic; // 引入泛型集合命名空间
  4. using GameDevTV.Utils; // 引入自定义工具类命名空间
  5. using UnityEngine; // 引入Unity引擎相关命名空间
  6. namespace RPG.Stats // 定义RPG统计相关的命名空间
  7. {
  8.     public class BaseStats : MonoBehaviour // 基础统计类
  9.     {
  10.         [Range(1, 99)]
  11.         [SerializeField]
  12.         int startingLevel = 1; // 初始等级
  13.         [SerializeField]
  14.         CharacterClass characterClass; // 角色职业
  15.         [SerializeField]
  16.         Progression progression = null; // 进展系统
  17.         [SerializeField]
  18.         GameObject levelUpParticleEffect = null; // 升级时的粒子效果
  19.         [SerializeField]
  20.         bool shouldUseModifiers = false; // 是否使用属性修饰符
  21.         public event Action onLevelUp; // 升级事件
  22.         LazyValue<int> currentLevel; // 当前等级
  23.         Experience experience; // 经验组件
  24.         private void Awake() // 初始化方法
  25.         {
  26.             experience = GetComponent<Experience>(); // 获取经验组件
  27.             currentLevel = new LazyValue<int>(CalculateLevel); // 延迟计算当前等级
  28.         }
  29.         private void Start() // 开始时调用
  30.         {
  31.             currentLevel.ForceInit(); // 强制初始化当前等级
  32.         }
  33.         private void OnEnable() // 启用时调用
  34.         {
  35.             if (experience != null)
  36.             {
  37.                 experience.onExperienceGained += UpdateLevel; // 注册经验获得事件
  38.             }
  39.         }
  40.         private void OnDisable() // 禁用时调用
  41.         {
  42.             if (experience != null)
  43.             {
  44.                 experience.onExperienceGained -= UpdateLevel; // 取消注册经验获得事件
  45.             }
  46.         }
  47.         private void UpdateLevel() // 更新等级
  48.         {
  49.             int newLevel = CalculateLevel(); // 计算新等级
  50.             if (newLevel > currentLevel.value) // 如果新等级高于当前等级
  51.             {
  52.                 currentLevel.value = newLevel; // 更新当前等级
  53.                 LevelUpEffect(); // 播放升级效果
  54.                 onLevelUp?.Invoke(); // 触发升级事件
  55.             }
  56.         }
  57.         private void LevelUpEffect() // 播放升级效果
  58.         {
  59.             Instantiate(levelUpParticleEffect, transform); // 实例化粒子效果
  60.         }
  61.         public float GetStat(Stat stat) // 获取属性值
  62.         {
  63.             return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * // 基础属性 + 加性修饰符
  64.                    (1 + (GetPercentageModifier(stat) / 100)); // 百分比修饰符
  65.         }
  66.         private float GetPercentageModifier(Stat stat) // 获取百分比修饰符
  67.         {
  68.             if (!shouldUseModifiers) // 如果不使用修饰符
  69.             {
  70.                 return 0;
  71.             }
  72.             float total = 0; // 初始化总值
  73.             foreach (IModifierProvider provider in GetComponents<IModifierProvider>()) // 遍历所有修饰符提供者
  74.             {
  75.                 foreach (float modifier in provider.GetPercentageModifiers(stat)) // 获取百分比修饰符
  76.                 {
  77.                     total += modifier; // 累加修饰符
  78.                 }
  79.             }
  80.             return total; // 返回总修饰符
  81.         }
  82.         private float GetBaseStat(Stat stat) // 获取基础属性值
  83.         {
  84.             return progression.GetStat(stat, characterClass, GetLevel()); // 从进展系统获取基础属性
  85.         }
  86.         private float GetAdditiveModifier(Stat stat) // 获取加性修饰符
  87.         {
  88.             if (!shouldUseModifiers) // 如果不使用修饰符
  89.             {
  90.                 return 0;
  91.             }
  92.             float total = 0; // 初始化总值
  93.             foreach (IModifierProvider provider in GetComponents<IModifierProvider>()) // 遍历所有修饰符提供者
  94.             {
  95.                 foreach (float modifier in provider.GetAdditiveModifiers(stat)) // 获取加性修饰符
  96.                 {
  97.                     total += modifier; // 累加修饰符
  98.                 }
  99.             }
  100.             return total; // 返回总修饰符
  101.         }
  102.         public int GetLevel() // 获取当前等级
  103.         {
  104.             return currentLevel.value; // 返回当前等级值
  105.         }
  106.         private int CalculateLevel() // 计算当前等级
  107.         {
  108.             Experience experience = GetComponent<Experience>(); // 获取经验组件
  109.             if (experience == null) // 如果没有经验组件
  110.             {
  111.                 return startingLevel; // 返回初始等级
  112.             }
  113.             float currentXP = experience.GetPoint(); // 获取当前经验值
  114.             int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass); // 获取最大等级
  115.             for (int level = 1; level <= penultimateLevel; level++) // 遍历所有等级
  116.             {
  117.                 float XPToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level); // 获取升级所需经验
  118.                 if (XPToLevelUp > currentXP) // 如果当前经验不足以升级
  119.                 {
  120.                     return level; // 返回当前等级
  121.                 }
  122.             }
  123.             return penultimateLevel + 1; // 返回下一等级
  124.         }
  125.     }
  126. }
复制代码
CharacterClass

  1. namespace RPG.Stats
  2. {
  3.     public enum CharacterClass
  4.     {
  5.         Player,
  6.         Grunt,
  7.         Mage,
  8.         Archer
  9.     }
  10. }
复制代码
Experience

  1. using System;
  2. using RPG.Saving;
  3. using UnityEngine;
  4. namespace RPG.Stats
  5. { }
  6. public class Experience : MonoBehaviour, ISaveable
  7. {
  8.     [SerializeField]
  9.     float experiencePoints = 0;
  10.     public event Action onExperienceGained;
  11.     public void GainExperience(float experience)
  12.     {
  13.         experiencePoints += experience;
  14.         onExperienceGained();
  15.     }
  16.     public object CaptureState()
  17.     {
  18.         return experiencePoints;
  19.     }
  20.     public void RestoreState(object state)
  21.     {
  22.         this.experiencePoints = (float) state;
  23.     }
  24.     public float GetPoint()
  25.     {
  26.         return experiencePoints;
  27.     }
  28. }
复制代码
ExperienceDisplay

  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.UI;
  4. namespace RPG.Stats
  5. {
  6.     public class ExperienceDisplay : MonoBehaviour
  7.     {
  8.         Experience experience;
  9.         private void Awake()
  10.         {
  11.             experience =
  12.                 GameObject.FindWithTag("Player").GetComponent<Experience>();
  13.         }
  14.         private void Update()
  15.         {
  16.             GetComponent<Text>().text =
  17.                 String.Format("{0:0.0}", experience.GetPoint());
  18.         }
  19.     }
  20. }
复制代码
IModifierProvider

  1. using System.Collections.Generic;
  2. namespace RPG.Stats
  3. {
  4.     public interface IModifierProvider
  5.     {
  6.         IEnumerable<float> GetAdditiveModifiers(Stat stat);
  7.         IEnumerable<float> GetPercentageModifiers(Stat stat);
  8.     }
  9. }
复制代码
LevelDisplay

  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.UI;
  4. namespace RPG.Stats
  5. {
  6.     public class LevelDisplay : MonoBehaviour
  7.     {
  8.         BaseStats baseStats;
  9.         private void Awake()
  10.         {
  11.             baseStats =
  12.                 GameObject.FindWithTag("Player").GetComponent<BaseStats>();
  13.         }
  14.         private void Update()
  15.         {
  16.             GetComponent<Text>().text =
  17.                 String.Format("{0:0.0}", baseStats.GetLevel());
  18.         }
  19.     }
  20. }
复制代码
Progression

  1. using System; // 引入基本命名空间
  2. using System.Collections.Generic; // 引入泛型集合命名空间
  3. using UnityEngine; // 引入Unity引擎相关命名空间
  4. namespace RPG.Stats // 定义RPG统计相关的命名空间
  5. {
  6.     [
  7.         CreateAssetMenu(
  8.             fileName = "Progression", // 资源文件名
  9.             menuName = "Stats/New Progression", // 菜单名称
  10.             order = 0) // 菜单排序
  11.     ]
  12.     public class Progression : ScriptableObject // 角色进展类
  13.     {
  14.         [SerializeField]
  15.         ProgressionCharacterClass[] characterClasses = null; // 角色职业数组
  16.         Dictionary<CharacterClass, Dictionary<Stat, float[]>> lookupTable = null; // 查找表
  17.         // 获取指定角色职业在特定等级的属性值
  18.         public float GetStat(Stat stat, CharacterClass characterClass, int level)
  19.         {
  20.             BuildLookup(); // 构建查找表
  21.             float[] levels = lookupTable[characterClass][stat]; // 获取该职业的属性等级数组
  22.             if (levels.Length < level) // 如果等级超出范围
  23.             {
  24.                 return 0; // 返回0
  25.             }
  26.             return levels[level - 1]; // 返回对应等级的属性值
  27.         }
  28.         // 获取指定角色职业的属性等级总数
  29.         public int GetLevels(Stat stat, CharacterClass characterClass)
  30.         {
  31.             BuildLookup(); // 构建查找表
  32.             float[] levels = lookupTable[characterClass][stat]; // 获取该职业的属性等级数组
  33.             return levels.Length; // 返回属性等级总数
  34.         }
  35.         private void BuildLookup() // 构建查找表的方法
  36.         {
  37.             if (lookupTable != null) // 如果查找表已经构建
  38.             {
  39.                 return; // 直接返回
  40.             }
  41.             lookupTable = new Dictionary<CharacterClass, Dictionary<Stat, float[]>>(); // 初始化查找表
  42.             foreach (ProgressionCharacterClass progressionClass in characterClasses) // 遍历每个职业
  43.             {
  44.                 var statLookupTable = new Dictionary<Stat, float[]>(); // 创建属性查找表
  45.                 foreach (ProgressionStat progressionStat in progressionClass.stats) // 遍历每个职业的属性
  46.                 {
  47.                     statLookupTable[progressionStat.stat] = progressionStat.levels; // 将属性及其等级值添加到查找表
  48.                 }
  49.                 lookupTable[progressionClass.characterClass] = statLookupTable; // 将职业及其属性查找表添加到主查找表
  50.             }
  51.         }
  52.         [Serializable] // 可序列化类
  53.         class ProgressionCharacterClass // 职业进展类
  54.         {
  55.             public CharacterClass characterClass; // 角色职业
  56.             public ProgressionStat[] stats; // 职业对应的属性数组
  57.         }
  58.         [Serializable] // 可序列化类
  59.         class ProgressionStat // 属性进展类
  60.         {
  61.             public Stat stat; // 属性类型
  62.             public float[] levels; // 属性在各等级的值
  63.         }
  64.     }
  65. }
复制代码
Stats


  1. using UnityEngine;
  2. namespace RPG.Stats
  3. {
  4.     public enum Stat
  5.     {
  6.         Health,
  7.         ExperienceReward,
  8.         ExperienceToLevelUp,
  9.         Damage
  10.     }
  11. }
复制代码
Health

  1. using System; // 引入基本命名空间
  2. using GameDevTV.Utils; // 引入游戏开发工具相关命名空间
  3. using RPG.Core; // 引入RPG核心相关命名空间
  4. using RPG.Saving; // 引入RPG保存系统相关命名空间
  5. using RPG.Stats; // 引入RPG统计相关命名空间
  6. using UnityEngine; // 引入Unity引擎相关命名空间
  7. using UnityEngine.Events; // 引入Unity事件相关命名空间
  8. namespace RPG.Attributes // 定义RPG属性相关的命名空间
  9. {
  10.     public class Health : MonoBehaviour, ISaveable // 健康类,继承自MonoBehaviour并实现ISaveable接口
  11.     {
  12.         [SerializeField]
  13.         float regenerationPercentage = 70; // 生命值恢复百分比
  14.         [SerializeField]
  15.         TakeDamageEvent takeDamage; // 受伤事件
  16.         [SerializeField]
  17.         UnityEvent onDie; // 死亡事件
  18.         [Serializable]
  19.         public class TakeDamageEvent : UnityEvent<float> { } // 受伤事件类,传入伤害值
  20.         LazyValue<float> healthPoints; // 延迟加载的生命值
  21.         bool isDead = false; // 是否死亡的标志
  22.         private void Awake() // 初始化方法
  23.         {
  24.             healthPoints = new LazyValue<float>(GetInitialHealth); // 设置初始生命值
  25.         }
  26.         private float GetInitialHealth() // 获取初始生命值
  27.         {
  28.             return GetComponent<BaseStats>().GetStat(Stat.Health); // 从BaseStats组件获取生命值
  29.         }
  30.         private void Start() // 开始时调用
  31.         {
  32.             healthPoints.ForceInit(); // 强制初始化生命值
  33.         }
  34.         private void OnEnable() // 启用时调用
  35.         {
  36.             GetComponent<BaseStats>().onLevelUp += RegenerateHealth; // 注册等级提升事件
  37.         }
  38.         private void OnDisable() // 禁用时调用
  39.         {
  40.             GetComponent<BaseStats>().onLevelUp -= RegenerateHealth; // 取消注册等级提升事件
  41.         }
  42.         private void RegenerateHealth() // 恢复生命值的方法
  43.         {
  44.             float regenerateHealthPoints =
  45.                 GetComponent<BaseStats>().GetStat(Stat.Health) *
  46.                 (regenerationPercentage / 100); // 计算恢复的生命值
  47.             healthPoints.value =
  48.                 Mathf.Max(healthPoints.value, regenerateHealthPoints); // 更新生命值
  49.         }
  50.         public bool IsDead() // 检查是否死亡
  51.         {
  52.             return isDead; // 返回死亡状态
  53.         }
  54.         public void TakeDamage(GameObject instigator, float damage) // 受伤的方法
  55.         {
  56.             healthPoints.value = Mathf.Max(healthPoints.value - damage, 0); // 计算剩余生命值
  57.             if (healthPoints.value == 0) // 如果生命值为0
  58.             {
  59.                 onDie.Invoke(); // 调用死亡事件
  60.                 Die(); // 执行死亡逻辑
  61.                 AwardExperience(instigator); // 奖励经验给攻击者
  62.             }
  63.             else
  64.             {
  65.                 takeDamage.Invoke(damage); // 调用受伤事件
  66.             }
  67.         }
  68.         public float GetHealthPoints() // 获取当前生命值
  69.         {
  70.             return healthPoints.value; // 返回当前生命值
  71.         }
  72.         public float GetMaxHealthPoints() // 获取最大生命值
  73.         {
  74.             return GetComponent<BaseStats>().GetStat(Stat.Health); // 从BaseStats获取最大生命值
  75.         }
  76.         private void AwardExperience(GameObject instigator) // 奖励经验的方法
  77.         {
  78.             Experience experience = instigator.GetComponent<Experience>(); // 获取攻击者的经验组件
  79.             if (experience == null) // 如果没有经验组件
  80.             {
  81.                 return; // 返回
  82.             }
  83.             experience
  84.                 .GainExperience(GetComponent<BaseStats>()
  85.                     .GetStat(Stat.ExperienceReward)); // 奖励经验
  86.         }
  87.         public float GetPercentage() // 获取生命值百分比
  88.         {
  89.             return 100 * GetFraction(); // 返回生命值百分比
  90.         }
  91.         public float GetFraction() // 获取生命值比例
  92.         {
  93.             return (
  94.             healthPoints.value / GetComponent<BaseStats>().GetStat(Stat.Health) // 当前生命值与最大生命值的比例
  95.             );
  96.         }
  97.         private void Die() // 死亡逻辑
  98.         {
  99.             if (isDead) // 如果已经死亡
  100.             {
  101.                 return; // 返回
  102.             }
  103.             isDead = true; // 设置为死亡状态
  104.             GetComponent<Animator>().SetTrigger("die"); // 播放死亡动画
  105.             GetComponent<ActionScheduler>().CancelCurrentAction(); // 取消当前动作
  106.         }
  107.         public object CaptureState() // 捕捉当前状态
  108.         {
  109.             return healthPoints.value; // 返回当前生命值
  110.         }
  111.         public void RestoreState(object state) // 恢复状态
  112.         {
  113.             healthPoints.value = (float)state; // 恢复生命值
  114.             if (healthPoints.value == 0) // 如果生命值为0
  115.             {
  116.                 Die(); // 执行死亡逻辑
  117.             }
  118.         }
  119.     }
  120. }
复制代码
HealthBar

  1. using UnityEngine;
  2. namespace RPG.Attributes
  3. {
  4.     public class HealthBar : MonoBehaviour
  5.     {
  6.         [SerializeField]
  7.         Health healthComponent = null;
  8.         [SerializeField]
  9.         RectTransform foreground = null;
  10.         [SerializeField]
  11.         Canvas rootCanvas = null;
  12.         void Update()
  13.         {
  14.             if (
  15.                 Mathf.Approximately(healthComponent.GetFraction(), 0) ||
  16.                 Mathf.Approximately(healthComponent.GetFraction(), 1)
  17.             )
  18.             {
  19.                 rootCanvas.enabled = false;
  20.                 return;
  21.             }
  22.             foreground.localScale =
  23.                 new Vector3(healthComponent.GetFraction(), 1, 1);
  24.         }
  25.     }
  26. }
复制代码
HealthDisplay

  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.UI;
  4. namespace RPG.Attributes
  5. {
  6.     public class HealthDisplay : MonoBehaviour
  7.     {
  8.         Health health;
  9.         private void Awake()
  10.         {
  11.             health = GameObject.FindWithTag("Player").GetComponent<Health>();
  12.         }
  13.         private void Update()
  14.         {
  15.             GetComponent<Text>().text =
  16.                 String
  17.                     .Format("{0:0.0}/{1:0}",
  18.                     health.GetHealthPoints(),
  19.                     health.GetMaxHealthPoints());
  20.         }
  21.     }
  22. }
复制代码
DamageText

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.UI;
  6. namespace RPG.UI.DamageText
  7. {
  8.     public class DamageText : MonoBehaviour
  9.     {
  10.         [SerializeField]
  11.         Text damageText = null;
  12.         public void SetValue(float amount)
  13.         {
  14.             damageText.text = String.Format("{0:0}", amount);
  15.         }
  16.     }
  17. }
复制代码
DamageTextSpawner

  1. using UnityEngine;
  2. namespace RPG.UI.DamageText
  3. {
  4.     public class DamageTextSpawner : MonoBehaviour
  5.     {
  6.         [SerializeField]
  7.         DamageText damageTextPrefab = null;
  8.         private void Start()
  9.         {
  10.             Spawn(11f);
  11.         }
  12.         public void Spawn(float damageAmount)
  13.         {
  14.             DamageText instance =
  15.                 Instantiate<DamageText>(damageTextPrefab, transform);
  16.             instance.SetValue (damageAmount);
  17.         }
  18.     }
  19. }
复制代码
Destroyer

  1. using UnityEngine;
  2. namespace RPG.UI.DamageText
  3. {
  4.     public class Destroyer : MonoBehaviour
  5.     {
  6.         [SerializeField]
  7.         GameObject targetToDestroy = null;
  8.         public void DestroyTarget()
  9.         {
  10.             // Destroy (targetToDestroy);
  11.         }
  12.     }
  13. }
复制代码




Scripts.zip

53.33 KB, 阅读权限: 10, 下载次数: 0

全部源码

回复

举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表