我觉得看视频太慢了,还是看别人源码更快……
Github 源码:
https://github.com/664235822/DarkSouls-Demo
其中关于战斗的主要代码:
BattleManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CapsuleCollider))]
public class BattleManager : IActorManagerInterface
{
private void OnTriggerEnter(Collider other)
{
WeaponController targetController = other.GetComponentInParent<WeaponController>();
if (targetController == null) return;
GameObject attacker = targetController.weaponManager.actorManager.gameObject;
GameObject receiver = actorManager.actorController.model.gameObject;
bool attackValid = CheckAngleTarget(receiver, attacker, 45.0f);
bool counterValid = CheckAnglePlayer(receiver, attacker, 30.0f);
if (other.gameObject.layer == LayerMask.NameToLayer("Weapon"))
{
actorManager.TryDoDamage(targetController, attackValid, counterValid);
}
}
///
/// 检查格挡
///
///
///
///
///
public static bool CheckAnglePlayer(GameObject player, GameObject target, float playerAngle)
{
Vector3 counterDir = target.transform.position - player.transform.position;
float counterAngle = Vector3.Angle(player.transform.forward, counterDir);
float counterAngle2 = Vector3.Angle(target.transform.forward, player.transform.forward);
bool counterValid = (counterAngle < playerAngle && Mathf.Abs(counterAngle2 - 180.0f) < playerAngle);
return counterValid;
}
public static bool CheckAngleTarget(GameObject player, GameObject target, float targetAngle)
{
Vector3 attackingDir = player.transform.position - target.transform.position;
float attackingAngle = Vector3.Angle(target.transform.forward, attackingDir);
bool attackValid = (attackingAngle < targetAngle);
return attackValid;
}
}
ActorManager.cs
public class ActorManager : MonoBehaviour
{
public ActorController actorController;
public BattleManager battleManager;
public WeaponManager weaponManager;
public StateManager stateManager;
public DirectorManager directorManager;
public InteractionManager interactionManager;
public void TryDoDamage(WeaponController target, bool attackValid, bool counterValid)
{
// 如果自己正在无敌状态,那么目标不会受到伤害
if (stateManager.isImmortal)
{
}
// 如果自己允许使用 counter,并且该次攻击 counter 角度成立,那么目标进入被眩晕流程
else if (stateManager.isCounterBack && counterValid)
{
target.weaponManager.actorManager.Stunned();
}
// 如果自己正在防御,那么自己进入格挡流程
// counter 的优先级高于防御
else if (stateManager.isDefence)
{
Blocked();
}
// 以上情况均不成立,进入伤害判断
else
{
// 如果自己血量大于 0,并且攻击有效,那么可以对自己造成伤害
if (stateManager.HP > 0 && attackValid)
{
// 思考:这里是在武器伤害的部分检查死亡
// 等到流程多了之后,应该在别的地方判断死亡
// 比如在 HP 的 setter 的地方判断死亡
// 还有就是,之后应该将受到攻击的流程和死亡的流程不放在同一个逻辑层级里面
// 对自己造成伤害的值就是目标的武器的伤害值
stateManager.AddHP(-1 * target.WeaponData.ATK);
// 如果自己血量被扣除之后仍然大于 0,那么自己进入受攻击流程
if (stateManager.HP > 0)
{
// 进入被攻击流程
Hit();
}
// 如果自己血量被扣除之后小于 0,那么自己进入死亡流程
else
{
// 死亡流程
Die();
}
}
}
}
private void Blocked()
{
actorController.IssueTrigger("blocked");
}
private void Hit()
{
actorController.IssueTrigger("hit");
}
public void Stunned()
{
actorController.IssueTrigger("stunned");
}
public void SetIsCounterBack(bool value)
{
stateManager.isCounterBack = value;
}
private void Die()
{
actorController.IssueTrigger("die");
actorController.playerInput.inputEnabled = false;
if (actorController.cameraController.lockState)
{
actorController.cameraController.lockState = false;
}
actorController.cameraController.enabled = false;
if (!actorController.cameraController.isAI)
StartCoroutine(Reborn());
else
StartCoroutine(DestroyCoroutine());
}
private IEnumerator Reborn()
{
yield return new WaitForSeconds(3.0f);
SceneManager.LoadScene("GameScene");
}
private IEnumerator DestroyCoroutine()
{
yield return new WaitForSeconds(3.0f);
Destroy(gameObject);
}
public void Lock(bool value)
{
actorController.SetBool("lock", value);
}
这个地方我注释了一下,感觉,诶,好像还挺简单的
但是他动画状态机也是很好的,可以抄一抄hhh
一开始我是
Manager 是 public 的
Controller 是 private 的
ThirdPerson 是包含多个 Manager 和 Controller 的集合
这就保证了必要的数据的共享,以及独立的逻辑的封装
无论在什么地方更改属性,都会触发 UI 更新,这就需要 setter
更进一步,无论在什么地方更改属性,所有 Controller 都能立即收到通知,这就需要基于委托的事件
做事件的时候,我一开始是写成
Assets/MeowACT/Scripts/ThirdPerson/ThirdPersonEventManager.cs
///
/// 第三人称事件管理器
///
public class ThirdPersonEventManager
{
// 事件相关
///
/// 事件委托类型
///
public delegate void ThirdPersonEvent(object[] args);
///
/// 开始冲刺
///
public event ThirdPersonEvent BeginSprintEvent;
///
/// 开始冲刺之后一帧
///
public event ThirdPersonEvent AfterBeginSprintEvent;
///
/// 结束冲刺
///
public event ThirdPersonEvent EndSprintEvent;
///
/// 开始近战攻击
///
public event ThirdPersonEvent BeginMeleeAttackEvent;
///
/// 结束近战攻击
///
public event ThirdPersonEvent EndMeleeAttackEvent;
///
/// 尝试造成伤害
///
public event ThirdPersonEvent TryDoDamageEvent;
///
/// 结束连击
///
public event ThirdPersonEvent EndComboEvent;
///
/// 血量改变
///
public event ThirdPersonEvent OnHPChangeEvent;
///
/// 体力改变
///
public event ThirdPersonEvent OnNRGChangeEvent;
///
/// 开始自动恢复体力
///
public event ThirdPersonEvent BeginNRGRecoverEvent;
///
/// 结束自动恢复体力
///
public event ThirdPersonEvent EndNRGRecoverEvent;
///
/// 第三人称事件管理器的主人
///
public ThirdPerson Owner;
///
/// 第三人称事件管理器的构造函数
///
/// 第三人称事件管理器的主人
public ThirdPersonEventManager(ThirdPerson owner)
{
Owner = owner;
}
///
/// 触发开始冲刺事件
///
///
public void FireBeginSprintEvent(object[] args)
{
BeginSprintEvent?.Invoke(args);
}
///
/// 触发开始冲刺之后一帧事件
///
///
public void FireAfterBeginSprintEvent(object[] args)
{
AfterBeginSprintEvent?.Invoke(args);
}
///
/// 触发结束冲刺事件
///
///
public void FireEndSprintEvent(object[] args)
{
EndSprintEvent?.Invoke(args);
}
///
/// 触发开始近战攻击事件
///
///
public void FireBeginMeleeAttackEvent(object[] args)
{
BeginMeleeAttackEvent?.Invoke(args);
}
///
/// 触发结束近战攻击事件
///
///
public void FireEndMeleeAttackEvent(object[] args)
{
EndMeleeAttackEvent?.Invoke(args);
}
public void FireOnHPChangeEvent(object[] args)
{
OnHPChangeEvent?.Invoke(args);
}
public void FireOnNRGChangeEvent(object[] args)
{
OnNRGChangeEvent?.Invoke(args);
}
///
/// 由动画触发的动画事件:尝试攻击
///
public void FireTryDoDamageEvent()
{
TryDoDamageEvent?.Invoke(null);
}
///
/// 由动画触发的动画事件:结束连击
///
public void FireEndComboEvent()
{
EndComboEvent?.Invoke(null);
}
这样写写到后面会有很多个 FireXXXEvent 函数,但是我又没办法,因为如果我要使用
Assets/MeowACT/Scripts/ThirdPerson/ThirdPersonEventManager.cs
public void Fire(ThirdPersonEvent tpEvent, object[] args)
{
tpEvent?.Invoke(args);
}
然后在需要激活事件的地方
Assets/MeowACT/Scripts/ThirdPerson/ThirdPerson.cs
///
/// 由动画触发的动画事件,还需要在 mgr 中触发事件:尝试攻击
///
public void FireTryDoDamageEvent()
{
EventManager.Fire(EventManager.TryDoDamageEvent, null);
}
的话,就会出现 在定义事件的类的外部,事件只能为左值 的错误
后来根据那个 animator 的 stringtohash 想到了字典hhhh这就简单了
Assets/MeowACT/Scripts/ThirdPerson/ThirdPersonEventManager.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 26/03/2022 14:46
// 最后一次修改于: 26/03/2022 15:26
// 版权所有: ThinkDifferentStudio
// 描述:
// ----------------------------------------------
using System.Collections.Generic;
namespace MeowACT
{
///
/// 第三人称事件管理器
///
public class ThirdPersonEventManager
{
// 事件相关
///
/// 事件委托类型
///
public delegate void ThirdPersonEvent(object[] args);
///
/// 开始冲刺
///
public event ThirdPersonEvent BeginSprintEvent;
///
/// 开始冲刺之后一帧
///
public event ThirdPersonEvent AfterBeginSprintEvent;
///
/// 结束冲刺
///
public event ThirdPersonEvent EndSprintEvent;
///
/// 开始近战攻击
///
public event ThirdPersonEvent BeginMeleeAttackEvent;
///
/// 结束近战攻击
///
public event ThirdPersonEvent EndMeleeAttackEvent;
///
/// 尝试造成伤害
///
public event ThirdPersonEvent TryDoDamageEvent;
///
/// 结束连击
///
public event ThirdPersonEvent EndComboEvent;
///
/// 血量改变
///
public event ThirdPersonEvent OnHPChangeEvent;
///
/// 体力改变
///
public event ThirdPersonEvent OnNRGChangeEvent;
///
/// 开始自动恢复体力
///
public event ThirdPersonEvent BeginNRGRecoverEvent;
///
/// 结束自动恢复体力
///
public event ThirdPersonEvent EndNRGRecoverEvent;
///
/// 事件字典
///
public Dictionary<string, ThirdPersonEvent> EventMap = new Dictionary<string, ThirdPersonEvent>();
///
/// 第三人称事件管理器的主人
///
public ThirdPerson Owner;
///
/// 第三人称事件管理器的构造函数
///
/// 第三人称事件管理器的主人
public ThirdPersonEventManager(ThirdPerson owner)
{
Owner = owner;
// 初始化事件字典
EventMap.Add("BeginSprintEvent", BeginSprintEvent);
EventMap.Add("AfterBeginSprintEvent", AfterBeginSprintEvent);
EventMap.Add("EndSprintEvent", EndSprintEvent);
EventMap.Add("BeginMeleeAttackEvent", BeginMeleeAttackEvent);
EventMap.Add("EndMeleeAttackEvent",EndMeleeAttackEvent);
EventMap.Add("TryDoDamageEvent", TryDoDamageEvent);
EventMap.Add("EndComboEvent", EndComboEvent);
EventMap.Add("OnHPChangeEvent", OnHPChangeEvent);
EventMap.Add("OnNRGChangeEvent", OnNRGChangeEvent);
EventMap.Add("BeginNRGRecoverEvent", BeginNRGRecoverEvent);
EventMap.Add("EndNRGRecoverEvent", EndNRGRecoverEvent);
}
public void Fire(string eventName, object[] args)
{
EventMap[eventName]?.Invoke(args);
}
}
}
运行之后发现 EventMap 初始化之后值全为 null
很奇怪了,这就说明我放进去的值不是引用类型……但是我也不知道怎么给我的 delegate void 加引用,所以我就用笨方法,每次重设事件的时候我都重新设置一遍 EventMap
Assets/MeowACT/Scripts/ThirdPerson/ThirdPersonEventManager.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 26/03/2022 14:46
// 最后一次修改于: 26/03/2022 21:26
// 版权所有: CheapMiaoStudio
// 描述:
// ----------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace MeowACT
{
///
/// 第三人称事件管理器
///
public class ThirdPersonEventManager
{
// 事件相关
///
/// 事件委托类型
///
public delegate void ThirdPersonEvent(object[] args);
///
/// 开始冲刺
///
public event ThirdPersonEvent BeginSprintEvent
{
add => EventMap["BeginSprintEvent"] = value;
remove => EventMap["BeginSprintEvent"] = value;
}
///
/// 开始冲刺之后一帧
///
public event ThirdPersonEvent AfterBeginSprintEvent
{
add => EventMap["AfterBeginSprintEvent"] = value;
remove => EventMap["AfterBeginSprintEvent"] = value;
}
///
/// 结束冲刺
///
public event ThirdPersonEvent EndSprintEvent
{
add => EventMap["EndSprintEvent"] = value;
remove => EventMap["EndSprintEvent"] = value;
}
///
/// 开始近战攻击
///
public event ThirdPersonEvent BeginMeleeAttackEvent
{
add => EventMap["BeginMeleeAttackEvent"] = value;
remove => EventMap["BeginMeleeAttackEvent"] = value;
}
///
/// 结束近战攻击
///
public event ThirdPersonEvent EndMeleeAttackEvent
{
add => EventMap["EndMeleeAttackEvent"] = value;
remove => EventMap["EndMeleeAttackEvent"] = value;
}
///
/// 尝试造成伤害
///
public event ThirdPersonEvent TryDoDamageEvent
{
add => EventMap["TryDoDamageEvent"] = value;
remove => EventMap["TryDoDamageEvent"] = value;
}
///
/// 结束连击
///
public event ThirdPersonEvent EndComboEvent
{
add => EventMap["EndComboEvent"] = value;
remove => EventMap["EndComboEvent"] = value;
}
///
/// 血量改变
///
public event ThirdPersonEvent OnHPChangeEvent
{
add => EventMap["OnHPChangeEvent"] = value;
remove => EventMap["OnHPChangeEvent"] = value;
}
///
/// 体力改变
///
public event ThirdPersonEvent OnNRGChangeEvent
{
add => EventMap["OnNRGChangeEvent"] = value;
remove => EventMap["OnNRGChangeEvent"] = value;
}
///
/// 开始自动恢复体力
///
public event ThirdPersonEvent BeginNRGRecoverEvent
{
add => EventMap["BeginNRGRecoverEvent"] = value;
remove => EventMap["BeginNRGRecoverEvent"] = value;
}
///
/// 结束自动恢复体力
///
public event ThirdPersonEvent EndNRGRecoverEvent
{
add => EventMap["EndNRGRecoverEvent"] = value;
remove => EventMap["EndNRGRecoverEvent"] = value;
}
///
/// 事件字典
///
public Dictionary<string, ThirdPersonEvent> EventMap = new Dictionary<string, ThirdPersonEvent>();
///
/// 第三人称事件管理器的主人
///
public ThirdPerson Owner;
///
/// 第三人称事件管理器的构造函数
///
/// 第三人称事件管理器的主人
public ThirdPersonEventManager(ThirdPerson owner)
{
Owner = owner;
// 事件字典的键初始化
EventMap.Add("BeginSprintEvent", null);
EventMap.Add("AfterBeginSprintEvent", null);
EventMap.Add("EndSprintEvent", null);
EventMap.Add("BeginMeleeAttackEvent", null);
EventMap.Add("EndMeleeAttackEvent",null);
EventMap.Add("TryDoDamageEvent", null);
EventMap.Add("EndComboEvent", null);
EventMap.Add("OnHPChangeEvent", null);
EventMap.Add("OnNRGChangeEvent", null);
EventMap.Add("BeginNRGRecoverEvent", null);
EventMap.Add("EndNRGRecoverEvent", null);
}
public void Fire(string eventName, object[] args)
{
Debug.Log(EventMap[eventName] == null);
EventMap[eventName]?.Invoke(args);
}
}
}
后来才知道 Action 和 Func 其实就是匿名的委托
匿名哦匿名
这样我就不用 public delegate void ThirdPersonEvent(object[] args);
了,这个 ThirdPersonEvent
被 Action
代替了
Assets/MeowACT/Scripts/ThirdPerson/ThirdPersonEventManager.cs
// ----------------------------------------------
// 作者: 廉价喵
// 创建于: 26/03/2022 14:46
// 最后一次修改于: 27/03/2022 9:07
// 版权所有: CheapMiaoStudio
// 描述:
// ----------------------------------------------
using System;
using System.Collections.Generic;
using UnityEngine;
namespace MeowACT
{
///
/// 第三人称事件管理器
///
public class ThirdPersonEventManager
{
// 事件相关
///
/// 开始冲刺
///
public event Action<object[]> BeginSprintEvent
{
add => eventMap["BeginSprintEvent"] = value;
remove => eventMap["BeginSprintEvent"] = value;
}
///
/// 开始冲刺之后一帧
///
public event Action<object[]> AfterBeginSprintEvent
{
add => eventMap["AfterBeginSprintEvent"] = value;
remove => eventMap["AfterBeginSprintEvent"] = value;
}
///
/// 结束冲刺
///
public event Action<object[]> EndSprintEvent
{
add => eventMap["EndSprintEvent"] = value;
remove => eventMap["EndSprintEvent"] = value;
}
///
/// 开始近战攻击
///
public event Action<object[]> BeginMeleeAttackEvent
{
add => eventMap["BeginMeleeAttackEvent"] = value;
remove => eventMap["BeginMeleeAttackEvent"] = value;
}
///
/// 结束近战攻击
///
public event Action<object[]> EndMeleeAttackEvent
{
add => eventMap["EndMeleeAttackEvent"] = value;
remove => eventMap["EndMeleeAttackEvent"] = value;
}
///
/// 尝试造成伤害
///
public event Action<object[]> TryDoDamageEvent
{
add => eventMap["TryDoDamageEvent"] = value;
remove => eventMap["TryDoDamageEvent"] = value;
}
///
/// 结束连击
///
public event Action<object[]> EndComboEvent
{
add => eventMap["EndComboEvent"] = value;
remove => eventMap["EndComboEvent"] = value;
}
///
/// 血量改变
///
public event Action<object[]> OnHPChangeEvent
{
add => eventMap["OnHPChangeEvent"] = value;
remove => eventMap["OnHPChangeEvent"] = value;
}
///
/// 体力改变
///
public event Action<object[]> OnNRGChangeEvent
{
add => eventMap["OnNRGChangeEvent"] = value;
remove => eventMap["OnNRGChangeEvent"] = value;
}
///
/// 开始自动恢复体力
///
public event Action<object[]> BeginNRGRecoverEvent
{
add => eventMap["BeginNRGRecoverEvent"] = value;
remove => eventMap["BeginNRGRecoverEvent"] = value;
}
///
/// 结束自动恢复体力
///
public event Action<object[]> EndNRGRecoverEvent
{
add => eventMap["EndNRGRecoverEvent"] = value;
remove => eventMap["EndNRGRecoverEvent"] = value;
}
///
/// 事件字典
///
private Dictionary<string, Action<object[]>> eventMap = new Dictionary<string, Action<object[]>>();
///
/// 第三人称事件管理器的主人
///
public ThirdPerson Owner;
///
/// 第三人称事件管理器的构造函数
///
/// 第三人称事件管理器的主人
public ThirdPersonEventManager(ThirdPerson owner)
{
Owner = owner;
// 事件字典的键初始化
eventMap.Add("BeginSprintEvent", null);
eventMap.Add("AfterBeginSprintEvent", null);
eventMap.Add("EndSprintEvent", null);
eventMap.Add("BeginMeleeAttackEvent", null);
eventMap.Add("EndMeleeAttackEvent",null);
eventMap.Add("TryDoDamageEvent", null);
eventMap.Add("EndComboEvent", null);
eventMap.Add("OnHPChangeEvent", null);
eventMap.Add("OnNRGChangeEvent", null);
eventMap.Add("BeginNRGRecoverEvent", null);
eventMap.Add("EndNRGRecoverEvent", null);
}
public void Fire(string eventName, object[] args)
{
eventMap[eventName]?.Invoke(args);
}
}
}