01 Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)
01 Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)【B站的第一季的部分视频】
02 Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
03 Unity2D 商业游戏案例《梦幻西游》(番外篇 - 场景系统)
。。。。。。
siki学院的(1年有限期到期前下载的项目,现在已经过期,所以自己理清项目,这篇文章做的是【第二季】的)
所以更多的不是学习这个项目,而是学习理清该类型的项目的思路。
发的是我改过的,不是原版的。
总体572M,压缩后223M(zip)
github和网盘有些不一样,是路径太长,删了一个文件夹
github上
百度网盘,提取码:xu90
untiy打开的是2,不是1,这样的目录结构是为了以后想往里面放什么不会污染代码工程
Cinemachine Camera
自己的代码库,用到的主要是对Component类组件的拓展
StateMachineBehaviour的使用
Trigger写的一个游戏框架 有 System,Model,Command,Event,IOC(单例的容器)等概念
modify :做了修改
stars:改改后,值得收藏的代码
watch:进去跑一下逻辑
bug:自己修改后报错的地方
scene:场景,以场景为分割线划分内容
.cd是类图。类图只能看个大概的继承关系,最好的是将引用到的脚本尽可能地集合起来。
比如CharacterFigthAI,用 partial 分开脚本,用 手动控制 Init() 代替 Awake()、Start()
这种写法能做清晰地知道脚本要控制哪些UI,哪些按钮、Slider有监听事件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
//*****************************************
//创建人: Trigger
//功能说明:战斗UI管理
//*****************************************
/// 战斗UI管理
public class FightUIManager : MonoBehaviour,IController
{
private Button defendCommandBtn;
private Button skillCommandBtn;
private Button useItemCommandBtn;
private GameObject fightCommandPanelGo;
public void Init()
{
gameObject.SetActive(true);
fightCommandPanelGo = transform.FindChildDeep( "Emp_FightCommand").gameObject;
fightCommandPanelGo.SetActive(true);
skillCommandBtn = transform.FindChildDeep( "Btn_Skill").GetComponent<Button>();
useItemCommandBtn = transform.FindChildDeep( "Btn_UseItem").GetComponent<Button>();
defendCommandBtn = transform.FindChildDeep( "Btn_Defend").GetComponent<Button>();
//
skillCommandBtn.onClick.AddListener(ClickSkillBtn);
useItemCommandBtn.onClick.AddListener(ClickUseItemBtn);
defendCommandBtn.onClick.AddListener(ClickDefendBtn);
//
this.RegistEvent<OpenOrCloseFightCommandPanelEvent>(OpenOrCloseFightCommandPanel);
fightCommandPanelGo.SetActive(false);
}
GameStartInstance调用了XYQArchitecture.Init()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:XYQ游戏架构
//*****************************************
public class XYQArchitecture : Architecture<XYQArchitecture>
{
protected override void Init()
{
RegistSystem<ISystem>(new UISystem());
RegistSystem<ISkillSystem>(new SkillSystem());
RegistSystem<IFightSystem>(new FightSystem());
RegistSystem<ISceneSystem>(new SceneSystem());
RegistModel<IPlayerDataModel>(new PlayerDataModel());
RegistModel<ISkillDataModel>(new SkillDataModel());
RegistModel<ICharacterDataModel>(new CharacterDataModel());
}
}
将业务具体代码(SpecificCode)的枚举类型全部提到一个文件夹下。
另一个是架构代码,我先不动。
将业务具体代码(SpecificCode)的struct类型(不继承,因为有的:ICommand)全部提到一个文件夹下。(后面推的时候觉得应该做的,不是一开始就知道要这样做)
Invoke的方法名,路径,节点名,资源名,路径等用静态类和cosnt存起来,方便查引用和变量汇总
举例Tags(标签)
/****************************************************
文件:Tags.cs
作者:lenovo
邮箱:
日期:2022/7/15 12:57:58
功能:
*****************************************************/
public static class Tags
{
public const string Canvas = "Canvas";
public const string PLAYER = "Player";
public const string BULLET = "Bullet";
public const string ENEMY = "Enemy";
/// 屏障
public const string SHIELD = "Shield";
public const string ITEM = "Item";
}
汇总到GameObjectPath,GameObjectName
public void Init()
{
CharacterPrefab = ExtendResources.Get<GameObject>(GameObjectPath.Prefab_CharacterFight).GetComponent<CharacterFightAI>();
Transform tf = GameObject.Find(GameObjectName.FightNavMesh).transform;
PlayerInitPosTrans = tf.Find(GameObjectName.PlayerPos);
GetPositionsTrans(tf, GameObjectName.EnemyPos, ref enemyInitPosTrans);
GetPositionsTrans(tf, GameObjectName.PlayerDieStartMovePath, ref playerDieStartMovePath);
GetPositionsTrans(tf, GameObjectName.PlayerDieEndMovePath, ref playerDieEndMovePath);
GetPositionsTrans(tf, GameObjectName.EnemyDieStartMovePath, ref enemyDieStartMovePath);
GetPositionsTrans(tf, GameObjectName.EnemyDieEndMovePath, ref enemyDieEndMovePath);
}
个人习惯
attack,atk
button,btn
commnd,cmd
config,cfg
count,cnt
current,cur
from和to,from攻击者,to被攻击者。表示攻击这个行为,从 from 到 to
list,lst
manager,mgr
object,obj
request,req
response,rsp
system,sys(可能与sync比较近)
target,tar
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using static ExtendTweenMethods;
public class LoginScene : MonoBehaviour
{
/// bgTrans的父节点,拖拽赋值
public Transform bg;
public Transform[] bgTrans;
public Vector3[] targetPos;
public ExtendTweenMethods.Tween[] tweens;
// Start is called before the first frame update
void Start()
{
InitBgTrans();
InitTargetPos();
InitTweens();
}
#region 辅助
private void InitBgTrans()
{
//4张背景图(从左到右)的trans
bgTrans = new Transform[4];
for (int i = 0; i < bgTrans.Length; i++)
{
bgTrans[i] = bg.GetChild(i);
}
}
private void InitTargetPos()
{
//4张背景图012(从)的目标位置123
targetPos = new Vector3[bgTrans.Length];
for (int i = 0; i < bgTrans.Length-1; i++)
{
targetPos[i] = bgTrans[i + 1].position;
}
//右边两张不动,左边两个一直两班倒(因为23是为了给01确定目标位置,不移动的)
targetPos[0] = bgTrans[0].position; //单独空出targetPos[0]来给右边的换到左边
}
void InitTweens()
{
tweens = new ExtendTweenMethods.Tween[2];
tweens[0]= bgTrans[0].DoMove( target:targetPos[1], time:50, loop:100);//左边跑50
tweens[1] = bgTrans[1].DoMove(target: targetPos[1], time:25, loop:1).SetOnComplete(() => //右边跑25
{
bgTrans[1].position = targetPos[0];//右边放左边
bgTrans[1].DoMove(targetPos[1], 50, 100);//继续Move
});
}
void KillTweens(Tween[] tweens )
{
for (int i = 0; i < tweens.Length; i++)
{
tweens[i].Kill();
}
}
public void LoadGame()
{
KillTweens(tweens);
SceneManager.LoadScene(1);
}
public void ExitGame()
{
Application.Quit();
}
#endregion
}
因为想把
GameStartInstance.DeepFindChild(Transform t, string childName);
改成
t.FindChildDeep( string childName);
少写很多。需要改以下
public class GameStartInstance : MonoBehaviour
{
......
///
/// 深度查找子对象transform引用
///
/// 父对象
/// 具体查找的子对象名称
///
public static Transform DeepFindChild(Transform root, string childName)
{
Transform result = null;
result = root.Find(childName);
if (!result)
{
foreach (Transform item in root)
{
result = DeepFindChild(item, childName);
if (result != null)
{
return result;
}
}
}
return result;
}
}
改成
public static class ExtendComponent
{
///
/// 深度查找子对象transform引用
///
/// 父对象
/// 具体查找的子对象名称
///
public static Transform FindChildDeep(this Transform root, string childName)
{
Transform result = null;
result = root.Find(childName);
if (!result)
{
foreach (Transform item in root)
{
result = FindChildDeep(item, childName);
if (result != null)
{
return result;
}
}
}
return result;
}
}
右边的原版的场景直接露出来(包括白色图片(转场用的))
从继承于MonoBehaviour改成 this Resource,和一个被启动脚本GameStartInstance调用的实例启动方法。
删除掉GameRes(先把GameResCtrl+R,Ctrl+R成)
节省挂脚本的事,查看和控制顺序。
现在Resources是sealed类,不能用this大法,也不能partical大法。只能塞进一个新建的静态类
/****************************************************
文件:
作者:WWS
日期:2022/10/31 15:25:09
功能:追要对Unity的Componetn组件的拓展方法(this大法)
静态类不能有实例构造器。
静态类不能有任何实例成员。
静态类不能使用abstract或sealed修饰符。
静态类默认继承自System.Object根类,不能显式指定任何其他基类。
*****************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using Object= UnityEngine.Object;
public static class ExtendResources
{
private static Dictionary<string, Object> resDict;
private static Dictionary<string, Object[]> resArrayDict;
///
/// GameStartInstance中调用
///
public static void Init()
{
resDict = new Dictionary<string, Object>();
resArrayDict = new Dictionary<string, Object[]>();
}
///
/// 文件路径
///
///
///
///
public static T Get<T>(this Resources resources, string resPath) where T : Object
{
if (resDict.ContainsKey(resPath))
{
return resDict[resPath] as T;
}
else
{
var res = Resources.Load(resPath);
resDict.Add(resPath, res);
return res as T;
}
}
///
/// 文件夹路径
///
///
/// 文件夹下所有
///
public static T[] GetAll<T>(this Resources resources, string resPath) where T : Object
{
Object[] objArray;
if (resDict.ContainsKey(resPath))
{
objArray = resArrayDict[resPath];
}
else
{
var res = Resources.LoadAll(resPath);
resArrayDict.Add(resPath, res);
objArray = res;
}
T[] TArray = new T[objArray.Length];
for (int i = 0; i < TArray.Length; i++)
{
TArray[i] = objArray[i] as T;
}
return TArray;
}
}
/// 位置。少写个.transform
public static Vector3 Position(this Component c)
{
return c.transform.position;
}
单例要么 大写开头,要么 _小写开头。觉得是不是Mono,单例都只有一个,所以用常用的_instance,Instance区分公私。
public class GameStartInstance : MonoBehaviour
{
public static GameStartInstance MonoInstance;
改成
public class GameStartInstance : MonoBehaviour
{
#region 单例
private static GameStartInstance _instance;
public static GameStartInstance Instance
{
get
{
if (_instance == null)
{
_instance = new GameStartInstance();
}
return _instance;
}
set
{
_instance = value;
}
}
#endregion
private void Awake()
{
ExtendResources.Init();
if (startArchitecture)
{
startArchitectureInstance = StartArchitecture.Instance;
singletonsList = new List<ISingleton>()
{
AudioSourceManager.Instance.Init(),
};
startArchitectureInstance.SetGameArchitecture(new XYQArchitecture());
//
_instance = this;
GetComponent<UIMgr>().Init();
fightLogicController.Init();
//
DontDestroyOnLoad(gameObject);
}
}
也是防止紫色的FightNav碍眼想隐藏它
bug unity里面文件夹名字不能包含 . ,编译不通过加不了脚本
Init()被GameStartInstance调用
public class FightLogicController : MonoBehaviour, IController
{
private bool hasInit;
public void Init()
{
transform.Find("FightBG").gameObject.SetActive(true);
hasInit = true;
gameObject.SetActive(false);
}
两个Nav区域向SceneSystem中的CharacterAI发起请求,stopping
NotWalkableArea 发起Command,具体是SetWillStoppingStateCommand
SetWillStoppingStateCommand 向 SceneSystem 中的发起请求,控制其中的 CharacterAI.willStopping。所以CharacterAI是控制人物行走的脚本,它挂载人物上。上面也挂有动画切换的脚本CharacterAnimatorController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// 检测玩家是否点击到不可走区域
public class NotWalkableArea : MonoBehaviour,IController
{
private void OnMouseDown()
{
this.SendCommnd<SetWillStoppingStateCommand>();
}
}
using UnityEngine;
///
/// 遇“墙”停下来
///
public struct SetWillStoppingStateCommand : ICommand
{
public void Execute(object dataObj)
{
this.GetSystem<ISceneSystem>().PlayerNormalAI.willStopping = true;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:场景系统
//*****************************************
public class SceneSystem : ISceneSystem
{
#region 字属
......
public CharacterAI PlayerNormalAI { private set; get; }
......
#endregion
......
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
//*****************************************
//创建人: Trigger
//功能说明:非战斗状态下的寻路
//***************************************** 、
/// 非战斗状态下的寻路
public class CharacterAI : MonoBehaviour
{
......
private void GetNotWalkableAreaMovePoint()
{
if (willStopping)
{
Ray2D ray = new Ray2D(transform.position, targetPos - transform.position);
RaycastHit2D raycastHit2D = Physics2D.Raycast(ray.origin, ray.direction);
if (raycastHit2D)
{
targetPos = raycastHit2D.point;
targetPos -= 0.1f * (targetPos - transform.position);
}
willStopping = false;
targetPos.z = transform.position.z;
meshAgent.SetDestination(targetPos);
}
}
/// 单击人物追过去
private void ClickMouse()
{
targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
targetPos.z = transform.position.z;
meshAgent.SetDestination(targetPos);
//
if (Time.time - createEffectTimer >= 0.05f)
{
createEffectTimer = Time.time;
Instantiate(clickEffectGo, targetPos, Quaternion.identity);
}
}
/// 双击人物跟随鼠标
private void DoubleClickMouse()
{
if (followMouse)//开启开关,人物跟随鼠标移动
{
ClickMouse();
}
else
{
if (Time.time-followMouseTimer>=0.4f)
{
//已超出规定时间,重新计时
followMouseTimer = Time.time;
clickCount = 0;
}
else
{
//在时间间隔内
if (clickCount>1)
{
//双击
followMouse = true;
}
}
}
}
#endregion
}
IController
JudgeEnterTheFightCommand
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:游戏管理(判断是否进入战斗)
//*****************************************
public class NormalModeMananger :MonoBehaviour,IController
{
private void Update()
{
this.SendCommnd<JudgeEnterTheFightCommand>();
}
}
NormalModeMananger 也就是IController做了SendCommand,具体的Command是去GetSystem,并且调用其中的方法。
具体的System是SceneSystem:ISystem,具体方法是吗,每隔8秒给个概率(80%)会不会遇敌。
也就是
/// 时间概率遇敌
public void JudgeEnterTheFight()
{
if (Time.time - EnterFightTimer >= 8)
{
if (Random.Range(0, 5) >= 1)
{
//进入战斗
EnterOrExitFightMode(true);
}
else
{
//重新计时
SetEnterFightState(true);
}
}
}
IBelongToArchitecture是最低层的接口,基此有各种功能接口。
IController,ICommand,ISystem又会实现所需要的各种功能接口。
public class SceneSystem : ISceneSystem
{
/// 时间概率遇敌
public void JudgeEnterTheFight()
{
if (Input.GetKeyDown(KeyCode.E)) //价格快捷键,方便测试
{
EnterOrExitFightMode(true);
}
//
if (Time.time - EnterFightTimer >= 8)
{
if (Random.Range(0, 5) >= 1)
{
EnterOrExitFightMode(true); //进入战斗
}
else
{
SetEnterFightState(true);//重新计时
}
}
}
......
bug1 一开始是全白的,因为要被引用,不能先SetActive(false)(尝试隐藏方便看,但运行没转场了)。
bug2 UI夏有几个脚本没引用,控制顺序不明显。
所以将一下脚本挂在GameStartInstance 的节点上,并被引用。
4个UI的脚本的Init(),就是将Awake()和Start()合并成Init()。
public class GameStartInstance : MonoBehaviour
{
......
#region 生命
private void Awake()
{
ExtendResources.Init();
if (startArchitecture)
{
startArchitectureInstance = StartArchitecture.Instance;
singletonsList = new List<ISingleton>()
{
AudioSourceManager.Instance.Init(),
};
startArchitectureInstance.SetGameArchitecture(new XYQArchitecture());
GetComponent<UIMgr>().Init();
DontDestroyOnLoad(gameObject);
}
}
/****************************************************
文件:UIMgr.cs
作者:lenovo
邮箱:
日期:2023/3/27 14:19:2
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class UIMgr : MonoBehaviour
{
#region 字段
public CameraCapture cameraCapture;
public FightUIManager fightUIManager;
public ItemUIManager itemUIManager;
public SkillUIManager skillUIManager;
#endregion
#region 生命
/// 需要StartArchitecture.Instance;
public void Init()
{
cameraCapture = transform.FindChildDeep("RawImage_CaptureCamera").GetComponent<CameraCapture>() ;
fightUIManager=transform.FindChildDeep("Emp_FightUI").GetComponent<FightUIManager>();
itemUIManager=transform.FindChildDeep("Emp_ItemUI").GetComponent<ItemUIManager>();
skillUIManager=transform.FindChildDeep("Emp_SkillUI").GetComponent<SkillUIManager>();
cameraCapture.Init();
fightUIManager.Init();
itemUIManager.Init();
skillUIManager.Init();
}
#endregion
}
以下原代码
/****************************************************
文件:
作者:WWS
日期:2023/03/25 13:17:08
功能:摄像机截屏。作为正常转战斗的转场
*****************************************************/
//*****************************************
//创建人: Trigger
//功能说明:摄像机截屏
//*****************************************
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CameraCapture : MonoBehaviour,IController
{
private RawImage ri;
private float blurValue;
void Start()
{
ri = GetComponent<RawImage>();
gameObject.SetActive(false);
this.RegistEvent<CaptrueCameraAndSetMaterialValueEvent>(CaptrueCameraAndSetMaterialValue);
}
void Update()
{
if (ri.gameObject.activeSelf)
{
blurValue += Time.deltaTime;
ri.material.SetFloat("_Blur", blurValue);//模糊
ri.color -= new Color(0, 0, 0, Time.deltaTime);
if (ri.color.a <= 0)
{
ri.gameObject.SetActive(false);
}
}
}
///
/// 相机截图
///
/// 截屏相机
/// 截屏区域
///
public Texture2D CaptureCamera(Camera camera, Rect rect)
{
RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, 0);
camera.targetTexture = rt;
camera.Render();
RenderTexture.active = rt;
Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height);
screenShot.ReadPixels(rect, 0, 0);
screenShot.Apply();
camera.targetTexture = null;
RenderTexture.active = null;
GameObject.Destroy(rt);
return screenShot;
}
///
/// 设置截屏UI所需材质的值
///
///
public void SetMaterialValue(Texture texture)
{
ri.material.SetTexture("_MainTex", texture);
ri.color = new Color(ri.color.r, ri.color.g, ri.color.b, 1);
ri.gameObject.SetActive(true);
blurValue = 0;
}
private void CaptrueCameraAndSetMaterialValue(object obj)
{
SetMaterialValue(CaptureCamera(Camera.main,new Rect(0,0,800,600)));
}
}
是字符串,导致ctrl+单击,点不到那里
所以一个静态类来汇总(可能会有更好的方法)
/****************************************************
文件:InvokeMethod.cs
作者:WWS
日期:2023/04/04 20:58:16
功能:
*****************************************************/
public static class InvokeMethod
{
public const string PlayIdleAniamtion = "PlayIdleAniamtion";
}
用法是
this.Delay(PlayIdleAnimation, 0.1f);
/// 假设这里的脚本是B,A来调用B时,action是应该在A还是B
public static void Delay(this MonoBehaviour monoBehaviour, System.Action action, float seconds)
{
// string actionName = action.ToString() ;//这种直接给类型System.Action
string actionName = action.Method.ToString() ; //Void XXX()
actionName = actionName.Substring(5) ; //XXX()
actionName = actionName.Replace("()","") ; //XXX
//Debug.LogFormat("actionName=={0}", actionName);
try
{
monoBehaviour.Invoke( actionName, seconds);
}
catch (Exception)
{
throw new System.Exception("Delay异常");
}
}
每次保存,直接退出编辑页面,很不好。应该不退出,每次都要点重新编辑进来
嫌弃碍眼隐藏,导致运行点击时这连个面板加载不了。
也是跟转场一样,在转场中有一起解决
转场,FightUI,ItemUI,SkillUI,一共四个
重点是OpenOrCloseFightCommandPanelEvent
public void EnterOrExitFightMode(bool enter)
{
if (enter)
{
this.SendEvent<CaptrueCameraAndSetMaterialValueEvent>();
AudioSourceManager.Instance.PlayMusic("Fight/FightBG" + Random.Range(1, 4));
}
else
{
AudioSourceManager.Instance.PlayMusic("DongHaiWan");
}
NormalModeGo.SetActive(!enter);
FightModeGo.SetActive(enter);
SetEnterFightState(!enter);
EnterFightTimer = Time.time;
CanEnterFight = !enter;
Vector3 pos = Camera.main.transform.position;
pos.z = 0;
FightModeGo.transform.position = pos;
this.SendEvent<OpenOrCloseFightCommandPanelEvent>(enter);
}
由 UISystem 的 FightUI 的 OpenOrCloseFightCommandPanelEvent转过来
原来的CharacterFightAI被拆分了
这几个都是 见名知义 的,就不写summary了
加载一个audio的路径,想把该方法写进同名(功能性趋同)但不同命名空间(一根据实际工程,一块复用性高)的ExtendResources,办不到。
同名,就要用partial,partial就要同一个命名空间。
暂时在实际工程下新建一个类来存储改方法。
Cammand、Behaviour、总控脚本和拆分出来的CharacterFightAI(就是CharacterFightAI.xxx)
在FightSystem找到了实例脚本CreateCharactersCommand ,倒查引用过来的
并且结果挂在FightNavMesh上
CreateCharactersCommand
CreateCharactersCommand
using UnityEngine;
using System.Linq;
///
/// 创建人:Trigger
/// 命令名称:在战斗开始后生成战斗敌人与玩家
/// 参数:
///
public struct CreateCharactersCommand : ICommand
{
public void Execute(object dataObj)
{
//玩家
IFightSystem ifs = this.GetSystem<IFightSystem>();
CharacterFightAI playerAI= InstantiateCharacter(ifs.CharacterPrefab,ifs.PlayerInitPosTrans);
ifs.PlayerAI = playerAI;
playerAI.isPlayer = true;
ifs.CurrentActAIsList.Add(playerAI);
InitCharacterData(playerAI);
//敌人
int num = Random.Range(1,10);
for (int i = 0; i < num; i++)
{
CharacterFightAI enemyAI = InstantiateCharacter(ifs.CharacterPrefab, ifs.EnemyInitPosTrans[i]);
ifs.EnemyAIList.Add(enemyAI);
ifs.CurrentActAIsList.Add(enemyAI);
enemyAI.isPlayer = false;
InitCharacterData(enemyAI);
}
ifs.CurrentActAIsList = ifs.CurrentActAIsList.OrderByDescending(p => p.actRate).ToList();
}
#region 辅助
private void InitCharacterData(CharacterFightAI cfa)
{
IFightSystem ifs = this.GetSystem<IFightSystem>();
if (cfa.isPlayer)
{
cfa.actRate = 8;
cfa.SetDieMovePath(ifs.PlayerDieStartMovePath,ifs.PlayerDieEndMovePath);
cfa.tag = "Player";
cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(2);
}
else
{
cfa.actRate = Random.Range(1,10);
cfa.SetDieMovePath(ifs.EnemyDieStartMovePath, ifs.EnemyDieEndMovePath);
cfa.tag = "Enemy";
cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(Random.Range(1,6));
}
cfa.name = cfa.characterInfo.pathName;
}
///
/// 实例角色
///
///
/// 父节点
/// 局部坐标
///
CharacterFightAI InstantiateCharacter(CharacterFightAI fightAI,Transform parent)
{
CharacterFightAI ai = GameObject.Instantiate(fightAI, parent);
ai.transform.localPosition = Vector3.zero;
return ai;
}
#endregion
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
//*****************************************
//创建人: Trigger
//功能说明:游戏战斗逻辑控制
//*****************************************
/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
private bool hasInit;
public void Init()
{
transform.Find("FightBG").gameObject.SetActive(true);
hasInit = true;
gameObject.SetActive(false);
}
private void OnEnable()
{
if (hasInit)
{
this.SendCommnd<CreateCharactersCommand>();
this.SendCommnd<ResetFightLogicStateCommand>();
}
}
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:游戏入口实例,初始化并管理所有的管理者,提供mono方法
//*****************************************
/// 游戏入口实例,初始化并管理所有的管理者,提供mono方法
public class GameStartInstance : MonoBehaviour
{
private List<ISingleton> singletonsList;
//
private StartArchitecture startArchitectureInstance;
public bool startArchitecture;
public FightLogicController fightLogicController; //拖拽赋值
#region 单例
private static GameStartInstance _instance;
public static GameStartInstance Instance
{
get
{
if (_instance == null)
{
_instance = new GameStartInstance();
}
return _instance;
}
set
{
_instance = value;
}
}
#endregion
#region 生命
private void Awake()
{
ExtendResources.Init();
if (startArchitecture)
{
startArchitectureInstance = StartArchitecture.Instance;
singletonsList = new List<ISingleton>()
{
AudioSourceManager.Instance.Init(),
};
startArchitectureInstance.SetGameArchitecture(new XYQArchitecture());
//
_instance = this;
GetComponent<UIMgr>().Init();
fightLogicController.Init();
//
DontDestroyOnLoad(gameObject);
}
}
CharacterFightAIBehaviour没被CharacterFightAI引用,因为它是该文件下的脚本的父类
也就是把战斗系统的UI(FightUIManager)放UI系统。
入口脚本GameStartInstance调用UIMgr(我加进来的一个总的管理),里面就有要找的FightUIManager
/****************************************************
文件:UIMgr.cs
作者:lenovo
邮箱:
日期:2023/3/27 14:19:2
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class UIMgr : MonoBehaviour
{
#region 字段
CameraCapture cameraCapture;
FightUIManager fightUIManager;
ItemUIManager itemUIManager;
SkillUIManager skillUIManager;
#endregion
#region 生命
/// 需要StartArchitecture.Instance;
public void Init()
{
cameraCapture = transform.FindChildDeep("RawImage_CaptureCamera").GetComponent<CameraCapture>() ;
fightUIManager=transform.FindChildDeep("Emp_FightUI").GetComponent<FightUIManager>();
itemUIManager=transform.FindChildDeep("Emp_ItemUI").GetComponent<ItemUIManager>();
skillUIManager=transform.FindChildDeep("Emp_SkillUI").GetComponent<SkillUIManager>();
cameraCapture.Init();
fightUIManager.Init();
itemUIManager.Init();
skillUIManager.Init();
}
#endregion
}
private Button defendCommandBtn;
private Button skillCommandBtn;
private Button useItemCommandBtn;
private GameObject fightCommandPanelGo;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
//*****************************************
//创建人: Trigger
//功能说明:战斗UI管理
//*****************************************
/// 战斗UI管理
public class FightUIManager : MonoBehaviour,IController
{
private Button defendCommandBtn;
private Button skillCommandBtn;
private Button useItemCommandBtn;
private GameObject fightCommandPanelGo;
public void Init()
{
gameObject.SetActive(true);
fightCommandPanelGo = transform.FindChildDeep( "Emp_FightCommand").gameObject;
fightCommandPanelGo.SetActive(true);
skillCommandBtn = transform.FindChildDeep( "Btn_Skill").GetComponent<Button>();
useItemCommandBtn = transform.FindChildDeep( "Btn_UseItem").GetComponent<Button>();
defendCommandBtn = transform.FindChildDeep( "Btn_Defend").GetComponent<Button>();
//
skillCommandBtn.onClick.AddListener(ClickSkillBtn);
useItemCommandBtn.onClick.AddListener(ClickUseItemBtn);
defendCommandBtn.onClick.AddListener(ClickDefendBtn);
//
this.RegistEvent<OpenOrCloseFightCommandPanelEvent>(OpenOrCloseFightCommandPanel);
fightCommandPanelGo.SetActive(false);
}
#region 辅助
///
/// 使用技能指令按钮事件
///
private void ClickSkillBtn()
{
this.SendEvent<OpenOrCloseSkillPanelEvent>(true);
}
///
/// 使用物品指令按钮事件
///
public void ClickUseItemBtn()
{
this.SendCommnd<CloseAllFightUIPanelCommand>();
this.SendEvent<OpenOrCloseFightBagPanelEvent>(true);
}
///
/// 防御指令按钮事件
///
public void ClickDefendBtn()
{
this.SendCommnd<CloseAllFightUIPanelCommand>();
this.SendCommnd<SetCharacterActCodeCommand>
(
new SetCharacterActCodeCommandParams()
{
actCode=ActCode.DEFEND
}
);
}
///
/// 关闭或开启战斗指令面板
///
///
private void OpenOrCloseFightCommandPanel(object obj)
{
fightCommandPanelGo.SetActive((bool)obj);
}
#endregion
}
为了达到以下效果,所以有FindButtonDeep
transform.FindButtonDeep( "Btn_SkillHengSaoQianJun");
/// 技能UI管理
public class SkillUIManager : MonoBehaviour,IController
{
private Button[] skillBtns;
public void Init()
{
gameObject.SetActive(true);
skillBtns = new Button[4];
skillBtns[0] = transform.FindButtonDeep( "Btn_SkillHengSaoQianJun");
skillBtns[1] = transform.FindButtonDeep( "Btn_SkillJiJiWaiWai");
skillBtns[2] = transform.FindButtonDeep( "Btn_SkillFanJianZhiJi");
skillBtns[3] = transform.FindButtonDeep( "Btn_SkillHuaYu");
......
///
/// 深度查找子对象transform引用
///
/// 父对象
/// 具体查找的子对象名称
///
public static Button FindButtonDeep(this Transform root, string childName)
{
Transform result = null;
result = root.Find(childName);
if (!result)
{
foreach (Transform item in root)
{
result = FindChildDeep(item, childName);
if (result != null)
{
return result.GetComponent<Button>();
}
}
}
return result.GetComponent<Button>();
}
///
/// 深度查找子对象transform引用
///
/// 父对象
/// 具体查找的子对象名称
///
public static Transform FindChildDeep(this Transform root, string childName)
{
Transform result = null;
result = root.Find(childName);
if (!result)
{
foreach (Transform item in root)
{
result = FindChildDeep(item, childName);
if (result != null)
{
return result;
}
}
}
return result;
}
为了这种效果
public class CharacterMouseDetection : CharacterFightAIBehaviour
{
......
//spriteRenderer.material.SetColor("_Color", color);
spriteRenderer.SetColor( color);
public static class ExtendComponent
{
......
public static void SetColor(this SpriteRenderer spriteRenderer, Color color)
{
spriteRenderer.material.SetColor("_Color", color);
}
进入可以知道直接点击敌人,就是平A。
所以找以下的单击类CharacterMouseDetection 。
重点是SetCharacterActCodeCommandParams(struct)、SetPlayerTargetAICommand。
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.EventSystems;
//*****************************************
//创建人: Trigger
//功能说明:人物鼠标检测
//*****************************************
/// 人物鼠标检测(Detection侦查察觉检测)
public class CharacterMouseDetection : CharacterFightAIBehaviour
{
private Color initColor;
protected override void Awake()
{
base.Awake();
initColor = spriteRenderer.color;
}
#region 系统
private void OnMouseDown()
{
if (IfCanClick())
{
this.SendCommnd<SetTargetAICommand>(fromAI); //当前点击的对象fromAI是Player的目标
this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.NORMAL);//鼠标样式
//
if (this.GetSystem<ISkillSystem>().UsingSkill)
{
Attack( SkillAttackPara() );
}
else
{
Attack( NormalAttackPara() );
}
}
}
private void OnMouseOver()
{
Color color = new Color
(
initColor.r * Mathf.Pow(2, 1),//n次幂
initColor.g * Mathf.Pow(2, 1),
initColor.b * Mathf.Pow(2, 1)
);
spriteRenderer.SetColor( color);
if (IfCanClick())
{
if (this.GetSystem<ISkillSystem>().UsingSkill)
{
this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.SKILL);
}
else
{
this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.ATTACK);
}
}
else
{
if (!EventSystem.current.IsPointerOverGameObject())
{
this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.FORBID);
}
}
}
private void OnMouseExit()
{
spriteRenderer.SetColor(initColor);
this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.NORMAL);
}
#endregion
#region 辅助
///
/// 当前人物是否可以点击
///
///
public bool IfCanClick()
{
bool isRightTarget = false;
ISkillSystem iks = this.GetSystem<ISkillSystem>();
if (iks.CurrentSkillID == 5)
{
isRightTarget = gameObject.CompareTag("Player");
}
else
{
isRightTarget = gameObject.CompareTag("Enemy");
}
return isRightTarget
&& !fightSystem.IsPerformingLogic
&& !EventSystem.current.IsPointerOverGameObject();
}
void Attack(SetCharacterActCodeCommandParams para)
{
this.SendCommnd<SetCharacterActCodeCommand>( para );
this.SendCommnd<SetPlayerTargetAICommand>(fromAI);
fightSystem.PlayerAI.toAI = null;
}
SetCharacterActCodeCommandParams NormalAttackPara()
{
Vector3 tarPos = fightSystem.PlayerAI.GetCurrentAITargetPos();
return new SetCharacterActCodeCommandParams()
{
actCode = ActCode.ATTACK,
actObj = new ActObj()
{
attackPos = tarPos
}
};
}
SetCharacterActCodeCommandParams SkillAttackPara()
{
int skillID = this.GetSystem<ISkillSystem>().CurrentSkillID;
Vector3 tarPos = fightSystem.PlayerAI.GetCurrentAITargetPos();
return new SetCharacterActCodeCommandParams()
{
actCode = ActCode.SKILL,
actObj = new ActObj()
{
attackPos = tarPos,
skillInfo = this.GetModel<ISkillDataModel>().GetSkillInfo(skillID)
}
};
}
#endregion
}
///
/// 设置玩家行为码的命令参数
///
public struct SetCharacterActCodeCommandParams
{
/// 动作码
public ActCode actCode;
/// 行动信息参数
public ActObj actObj;
}
SetPlayerTargetAICommand 中对 PlayerTargetAI 进行赋值,那就是有相关的判空引用。
只有一个引用 fightSystem.PlayerAI.targetAI ,找targetAI 。
using UnityEngine;
///
/// 创建人:Trigger
/// 命令名称:设置玩家目标AI
/// 参数:CharacterFightAI
///
public struct SetPlayerTargetAICommand : ICommand
{
public void Execute(object dataObj)
{
this.GetSystem<IFightSystem>().PlayerTargetAI = (CharacterFightAI)dataObj;
}
}
targetAI 22处引用,有关攻击只有 在AttackFightBehaviour(里面只有进行音效和动画处理)
///
/// 攻击行为
///
public void AttackBehaviour()
{
AudioSourceManager.Instance.PlayCharacterSound("Attack",gameObject.name);
cac.PlayAttackAnimation();
}
从 CharacterFightAI 拆分出来的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*****************************************
//创建人: Trigger
//功能说明:人物战斗AI
//*****************************************
/// 人物战斗AI
public partial class CharacterFightAI : MonoBehaviour,IController
{
#region 生命
private void OnDestroy()
{
if (chaosTween != null)
{
chaosTween.Pause();
chaosTween.Kill();
}
}
#endregion
#region 混乱
///
/// 混乱移动
///
public void DoChaosMoveTween()
{
if (chaosTween != null)
{
return;
}
DoChaosMove();
}
private void DoChaosMove()
{
chaosTween = animatorTrans
.DoMove(GetChaosMoveTarget(), 0.05f) //左摇
.SetOnComplete
(
() =>
{
chaosTween = animatorTrans
.DoMove(GetRendererInitPos(), 0.05f) //右晃
.SetOnComplete(DoChaosMove);
}
);
}
private Vector3 GetChaosMoveTarget()
{
Vector3 startPos = GetRendererInitPos();
Vector2 randomPos = Random.insideUnitCircle * 0.1f;
return startPos + new Vector3(randomPos.x, randomPos.y, startPos.z);
}
private Vector3 GetRendererInitPos()
{
return transform.TransformPoint( new Vector3(0, 0.3f, 0) );
}
#endregion
}
从 CharacterFightAI.UseSkillOrItem.cs 得到使用物品和技能来回复
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:人物战斗状态数据显示
//*****************************************
/// 人物战斗状态数据显示
public class CharacterFightValueState : CharacterFightAIBehaviour
{
private CharacterCanvas characterCanvas;
#region 生命
void Start()
{
characterCanvas = GetComponentInChildren<CharacterCanvas>();
if (fromAI.isPlayer)
{
fromAI.HP = PlayerData().CurrentMaxHP.Value;
fromAI.currentHP = PlayerData().CurrentHP.Value;
fromAI.MP = PlayerData().MaxMP.Value;
fromAI.currentMP = PlayerData().CurrentMP.Value;
}
else
{
characterCanvas.HideSlider();
}
string characterName = this.GetModel<ICharacterDataModel>().GetCharacterInfo(fromAI.characterInfo.ID).name;
characterCanvas.SetCharacterName( characterName );
fromAI.currentHP = fromAI.HP;
}
#endregion
#region 辅助
///
/// 显示跟血量变化相关的内容
///
public void ShowHPValueChange(int changeValue)
{
NumCanvas nc = Instantiate(
ExtendResources.Get<GameObject>("Prefabs/CharacterNumCanvas"),
spriteRenderer.Position(),
Quaternion.identity
).GetComponent<NumCanvas>();
nc.ShowNum(changeValue);
//
fromAI.currentHP += changeValue;
if (fromAI.currentHP >= fromAI.HP)
{
fromAI.currentHP = fromAI.HP;
}
if (fromAI.isPlayer)
{
characterCanvas.SetHPSliderValue((float)fromAI.currentHP / fromAI.HP);
this.SendCommnd<ChangePlayerHPCommand>(changeValue);
}
}
///
/// 显示跟蓝耗变化相关的内容
///
public void ShowMPValueChange(int changeValue)
{
fromAI.currentMP += changeValue;
if (fromAI.currentMP >= fromAI.MP)
{
fromAI.currentMP = fromAI.MP;
}
if (fromAI.isPlayer)
{
this.SendCommnd<ChangePlayerMPCommand>(changeValue);
}
}
IPlayerDataModel PlayerData()
{
return this.GetModel<IPlayerDataModel>();
}
#endregion
}
CharacterFightAI 中拆分的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*****************************************
//创建人: Trigger
//功能说明:人物战斗AI
//*****************************************
/// 人物战斗AI
public partial class CharacterFightAI : MonoBehaviour,IController
{
#region 闪避与防御
///
/// 闪避行为
///
public void DodgeBehaviour()
{
defendAndDodgeFightBehaviour.DodgeBehaviour();
}
///
/// 防御行为
///
public void DefendBehaviour()
{
defendAndDodgeFightBehaviour.DefendBehaviour();
}
///
/// 移动到防御位置并返回
///
/// 动画时间
/// 需要在动画完成后进行的额外回调
public void ToDenfendPos(float animationTime, UnityAction callBack = null)
{
defendAndDodgeFightBehaviour.ToDenfendPos(animationTime, callBack);
}
#endregion
}
using UnityEngine;
///
/// 创建人:Trigger
/// 命令名称:人物死亡命令
/// 参数:
///
public struct CharacterDieCommand : ICommand
{
public void Execute(object dataObj)
{
IFightSystem fightSystem = this.GetSystem<IFightSystem>();
AudioSourceManager.Instance.PlayCharacterSound("FlyAway");
Time.timeScale = 1;
fightSystem.DieCount++;
fightSystem.EnterCurrentRound = false;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:人物受击与死亡行为
//*****************************************
/// 人物受击与死亡行为
public class HitAndDieFightBehaviour : CharacterFightAIBehaviour
{
/// 受击行为
public void HitBehaviour(bool canDodge = true, bool canDefend = true)
{
meshAgent.isStopped = true;
if (Random.Range(0, 3) >= 2 && canDodge)//闪避
{
fromAI.DodgeBehaviour();//闪避成功
return;
}
if (fromAI.actCode == ActCode.DEFEND && canDefend)//防御
{
fromAI.DefendBehaviour();
return;
}
//受击
int random = Random.Range(60,120);
fromAI.ShowHPValueChange(-random);
fromAI.SetCurrentLookDir();
cac.PlayHitAnimation();
this.SendCommnd<CreateHitEffectCommand>();
JudgeIfDie();
}
/// 判断是否死亡
public void JudgeIfDie()
{
if (fromAI.currentHP <= 0)
{
//死亡
cac.PlayDieAnimation();
Time.timeScale = 0.3f;
transform.DoMove(fromAI.GetTargetPosTrans(fromAI.currentLookDir).position, 0.2f)
.SetOnComplete(
() =>
{
meshAgent.isStopped = false;
meshAgent.speed = 15;
}
);
}
else
{
fromAI.ToDenfendPos(0.2f); //受击
}
}
/// 死亡行为
public void DieBehaviour()
{
this.SendCommnd<CharacterDieCommand>();
fromAI.characterState = CharacterState.DEAD;
fromAI.SetMovingState(false);
//主要目的是为了放横扫一类的技能没有在原始位置,要回去
fromAI.ResetState();
}
}
CharacterFightAI
设置了ActCode、 ActObj,传给 CharacterFightAI
using UnityEngine;
///
/// 创建人:Trigger
/// 命令名称:随机敌人AI的行动命令
/// 参数:
///
public struct RandomEnemyActCommand : ICommand
{
public void Execute(object obj)
{
IFightSystem sys = this.GetSystem<IFightSystem>();
CharacterFightAI fromAI = sys.CurrentAI;
fromAI.toAI = sys.PlayerAI;
Vector3 tarPos= fromAI.GetCurrentAITargetPos();
//
ActCode ac = (ActCode)Random.Range(0,4);
ActObj ao = new ActObj();
//
switch (ac)
{
case ActCode.ATTACK:
ao.attackPos = tarPos;
break;
case ActCode.DEFEND:
break;
case ActCode.SKILL:
{
int skillID = Random.Range(1, 6);
switch (skillID)
{
case 1:
ao.attackPos = tarPos;
break;
case 2:
case 4:
skillID = 3;
break;
case 5:
this.SendCommnd<GetRandomCharacterCommand>();
break;
default: break;
}
ao.skillInfo = this.GetModel<ISkillDataModel>().GetSkillInfo(skillID);
}
break;
case ActCode.USEITEM:
ao.itemID = 0;
break;
default: break;
}
fromAI.SetActCodeAndObjValue(ac, ao);
}
}
bug在伤害数值和伤害特效出现后发生的原地动画不返回
AttackFightBehaviour.AttackTarget()打点
0会返回,但是一直是1
是被攻击者的动作
DefendAndDodgeFightBehaviour.ToDenfendPos()
上面是我改的,下面是原版的
该脚本不继承于Component,不能加到节点上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AttackStateBehaviour : UnityEngine.StateMachineBehaviour
{
private CharacterFightAI fightAI;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (!fightAI)
{
fightAI = animator.transform.GetComponentInParent<CharacterFightAI>();
}
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
//override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
fightAI.SetSkillMoveAction();
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
public partial class CharacterFightAI : MonoBehaviour,IController
{
......
public CharacterState characterState;
/// 角色状态
public enum CharacterState
{
NORMAL,
/// 休息状态
REST,
/// 混乱状态
CHAOS,
DEAD
}
找到ActCode.ATTACK;
fromAI.toAI.HitBehaviour();条件改清楚
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:控制人物动画切换
//*****************************************
/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{
private Animator animator;
public bool isMoving;
private CharacterFightAI fightAI;
private void Awake()
{
animator = GetComponentInChildren<Animator>();
fightAI = GetComponentInParent<CharacterFightAI>();
}
private void Start()
{
if (fightAI)
{
string path = "Character/" + fightAI.characterInfo.pathName + "/Fight/FightController";
animator.runtimeAnimatorController = ExtendResources.Get<RuntimeAnimatorController>(path);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:控制人物动画切换
//*****************************************
/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{
/// 挂在Timeline时间轴上的帧的时间
private void AttackAnimationEvent()
{
fightAI.AttackTarget();
}
/// 挂在Timeline时间轴上的帧的时间
private void UseSkillOrItemAnimationEvent()
{
fightAI.UseSkillOrItemAction();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger
//功能说明:控制人物动画切换
//*****************************************
/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{
///
/// 播放位移动画
///
/// 当前位置
/// 导航点
/// 最终目标点
public void PlayLocomotionAnimation(Vector3 curPos,Vector3 navPos,Vector3 tarPos)
{
Vector3 lookDir = navPos-curPos;
if (lookDir.magnitude<=0.0001f)
{
lookDir = tarPos - curPos;
}
if (Vector3.Distance(curPos,tarPos)>0.3f)
{
animator.SetFloat("LookX", lookDir.normalized.x);
animator.SetFloat("LookY", lookDir.normalized.y);
animator.SetBool("MoveState",true);
isMoving = true;
}
else if(Vector3.Distance(curPos, tarPos) < 0.15f)
{
animator.SetBool("MoveState", false);
isMoving = false;
}
}
public void PlayHitAnimation()
{
SetLookDir(fightAI.currentLookDir);
animator.SetTrigger("Hit");
}
public void PlayDieAnimation()
{
animator.SetBool("Die",true);
}
public void PlayMoveAnimation(float moveValue)
{
SetLookDir(fightAI.currentLookDir);
animator.SetInteger("MoveValue",1);
}
public void PlayIdleAnimation()
{
SetLookDir(fightAI.currentLookDir);
animator.SetInteger("MoveValue", 0);
}
public void PlayAttackAnimation()
{
SetLookDir(fightAI.currentLookDir);
animator.SetTrigger("Attack");
}
public void PlaySkillAnimation()
{
SetLookDir(fightAI.currentLookDir);
animator.SetTrigger("Skill");
}
public void PlayDefendAnimation()
{
SetLookDir(fightAI.currentLookDir);
animator.SetTrigger("Defend");
}
public void SetLookDir(Vector2 lookDir)
{
animator.SetFloat("LookDirX", lookDir.x);
animator.SetFloat("LookDirY", lookDir.y);
}
}
拆分CharacterAnimatorController时脚本丢失(拖拽复制的缺点)
之前以下丢失了
修改代码后发生,SceneSystem.Init()报空
实际主角是 骨精灵 ,这里是 侠客, 脚本缺少CharacterAnimationController,脚本没激活
总结就是没初始化成功,后面的敌人也直接没有实例
。。。。
原因是CharacterAnimationController,预制体没有应该用AddComopnent而不是GetComopnent
GetOrAddComponent是自己的库
。。。。
CharacterFightAI
private void Awake()
{
animatorTrans = GetComponentInChildren<Animator>().transform;
cac = gameObject.GetOrAddComponent<CharacterAnimatorController>();
characterCanvas = GetComponentInChildren<CharacterCanvas>();
cac.Init();
characterCanvas.Init();
///
/// 获取或增加组件。
///
/// 要获取或增加的组件。
/// 目标对象。
/// 获取或增加的组件。
public static T GetOrAddComponent<T>(this GameObject gameObject) where T : Component
{
T component = gameObject.GetComponent<T>();
if (component == null)
{
component = gameObject.AddComponent<T>();
}
return component;
}
Init()在CharacterFightAI中调用
其中fromAI.characterInfo数据为空
/// 人物战斗状态数据显示
public class CharacterFightValueState : CharacterFightAIBehaviour
{
public override void Init()
{
base.Init();
characterCanvas = GetComponentInChildren<CharacterCanvas>();
if (fromAI.isPlayer)
{
fromAI.HP = PlayerData().CurrentMaxHP.Value;
fromAI.currentHP = PlayerData().CurrentHP.Value;
fromAI.MP = PlayerData().MaxMP.Value;
fromAI.currentMP = PlayerData().CurrentMP.Value;
}
else
{
characterCanvas.HideSlider();
}
string characterName = this.GetModel<ICharacterDataModel>().GetCharacterInfo(fromAI.characterInfo.ID).name;
characterCanvas.SetCharacterName( characterName );
fromAI.currentHP = fromAI.HP;
}
CharacterFightAI.characterInfo
/// 人物战斗AI
public partial class CharacterFightAI : MonoBehaviour,IController
{
#region 字属
......
public CharacterInfo characterInfo;
为0,就是没赋值过,默认为0
public class CharacterDataModel : ICharacterDataModel
{
private Dictionary
private Dictionary
新建 CharacterInfo GetCharacterInfo(CharacterInfo characterInfo )来找准位置
/// 人物战斗状态数据显示
public class CharacterFightValueState : CharacterFightAIBehaviour
{
public override void Init()
{
base.Init();
characterCanvas = GetComponentInChildren<CharacterCanvas>();
if (fromAI.isPlayer)
{
fromAI.HP = PlayerData().CurrentMaxHP.Value;
fromAI.currentHP = PlayerData().CurrentHP.Value;
fromAI.MP = PlayerData().MaxMP.Value;
fromAI.currentMP = PlayerData().CurrentMP.Value;
}
else
{
characterCanvas.HideSlider();
}
fromAI.currentHP = fromAI.HP;
//
string characterName = GetCharacterInfo(fromAI.characterInfo).name;
characterCanvas.SetCharacterName( characterName );
}
#endregion
#region 辅助
///
/// 方便找bug,不然报错在接口,不清晰
///
CharacterInfo GetCharacterInfo(CharacterInfo characterInfo )
{
if (characterInfo.ID >= 0)
{
throw new System.Exception("ID异常:"+characterInfo.ID);
}
return this.GetModel<ICharacterDataModel>().GetCharacterInfo(characterInfo.ID);
}
01
实例角色,就会调用CharacterFightAI
找CharacterInfo 的逻辑也在 CharacterFightAI 上,也会被调用。
02
而CreateCharacterCommand的命令中,赋值 CharacterInfo 在 实例角色 之后。导致找 CharacterInfo 的逻辑发生时,CharacterInfo 还没赋值,所以报错。
03
所以改成 Init(),手动控制顺序。Init 就是 原来的 Awake(),Start()等
cfa.characterInfo = this.GetModel().GetCharacterInfo(Random.Range(1, 6));
}
…
cfa.Init();
using UnityEngine;
using System.Linq;
///
/// 命令名称:在战斗开始后生成战斗敌人与玩家
///
public struct CreateCharactersCommand : ICommand
{
public void Execute(object dataObj)
{
//玩家
IFightSystem ifs = this.GetSystem<IFightSystem>();
ifs.PlayerAI = GameObject.Instantiate(ifs.CharacterPrefab, ifs.PlayerInitPosTrans);
ifs.PlayerAI.transform.localPosition = Vector3.zero;
ifs.PlayerAI.isPlayer = true;
ifs.CurrentActAIsList.Add(ifs.PlayerAI);
InitCharacterData(ifs.PlayerAI);
//敌人
int num = Random.Range(1, 10);
for (int i = 0; i < num; i++)
{
ifs.EnemyAIList.Add(GameObject.Instantiate(ifs.CharacterPrefab, ifs.EnemyInitPosTrans[i]));
ifs.EnemyAIList[i].transform.localPosition = Vector3.zero;
ifs.CurrentActAIsList.Add(ifs.EnemyAIList[i]);
ifs.EnemyAIList[i].isPlayer = false;
InitCharacterData(ifs.EnemyAIList[i]);
}
ifs.CurrentActAIsList = ifs.CurrentActAIsList.OrderByDescending(p => p.actRate).ToList();
}
private void InitCharacterData(CharacterFightAI cfa)
{
IFightSystem ifs = this.GetSystem<IFightSystem>();
if (cfa.isPlayer)
{
cfa.actRate = 8;
cfa.SetDieMovePath(ifs.PlayerDieStartMovePath, ifs.PlayerDieEndMovePath);
cfa.tag = Tags.PLAYER;
cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(2);
}
else
{
cfa.actRate = Random.Range(1, 10);
cfa.SetDieMovePath(ifs.EnemyDieStartMovePath, ifs.EnemyDieEndMovePath);
cfa.tag = Tags.ENEMY;
cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(Random.Range(1, 6));
}
cfa.name = cfa.characterInfo.pathName;
cfa.Init();
}
}
打点发现两个Command后localPosition都是Vector.Zero
之后走的时候CinemachineVirtualCamera的ConnectCM,所以很可能是相机的问题
public class FightLogicController : MonoBehaviour, IController
{
......
private void OnEnable()
{
if (hasInit)
{
this.SendCommnd<CreateCharactersCommand>();
this.SendCommnd<ResetFightLogicStateCommand>();
}
}
namespace NavMeshComponents.Extensions
{
public abstract class NavMeshExtension: MonoBehaviour
{
......
protected virtual void Awake()
{
ConnectToVcam(true);
}
这个跳回Test.Update()时,t.localPosition已经不为Vector3.zero
Transform t = this.GetSystem<IFightSystem>().PlayerAI.transform;
print("localPos" + t.localPosition);
该节点FightNavMesh全部取消static(与原来的做对比)
可以看到,双击绿球节点(玩家父节点),Scene中跳的位置是不同的
也就是initPos的赋值没有在球的位置变化后进行
原版是。initPos的赋值在FightNavMesh的激活OnEnabled中进行.
…
造成的原因是我不想用Start()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
//*****************************************
//创建人: Trigger
//功能说明:游戏战斗逻辑控制
//*****************************************
/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
///
/// FightNavMesh是否已经激活(一般位置锁着玩家移动有所变化),
/// 以便后面角色创建initPos
///
private bool hasInit = false;
/// 因为初始不激活,其它脚本找不到FightNavMesh
public GameObject FightNavMesh { get { return gameObject; } }
private void OnEnable()
{
//TODO FindTop解决
//this.SendCommnd(this.FightNavMesh);
hasInit = true;
}
void Update()
{
if (hasInit)
{
this.SendCommnd<CreateCharactersCommand>();
this.SendCommnd<ResetFightLogicStateCommand>();
hasInit = false;
}
if (Input.GetMouseButtonDown(1))//鼠标右键
{
this.SendEvent<CancelUsingSkillEvent>();
}
this.SendCommnd<UpdateFightLogicCommand>();
}
}
///
/// 找到 自身 下的 所有子节点 children
///
///
///
///
public static Transform[] GetChildren(this Transform parent, out Transform[] children)
{
children = new Transform[parent.childCount];
for (int i = 0; i < children.Length; i++)
{
children[i] = parent.GetChild(i);
}
return children;
}
///
/// 找到 自身 的 叫 parentName的子节点 下的所有子节点 children
///
///
///
///
///
public static Transform[] GetChildrenDeep(this Transform t, string parentName, out Transform[] children)
{
Transform parent = t.FindChildDeep(parentName);
children = new Transform[parent.childCount];
for (int i = 0; i < children.Length; i++)
{
children[i] = parent.GetChild(i);
}
return children;
}
/****************************************************
文件:
作者:WWS
日期:2023/04/10 20:05:40
功能:AnimatorPara动画器的参数汇总
*****************************************************/
using UnityEngine;
public static class AnimatorPara
{
public const string LookX = "LookX";
public const string LookY = "LookY";
public const string LookDirX = "LookDirX";
public const string LookDirY = "LookDirY";
public const string MoveState = "MoveState";
public const string Hit = "Hit";
public const string Die = "Die";
public const string MoveValue = "MoveValue";
public const string Attack = "Attack";
public const string Skill = "Skill";
public const string Defend = "Defend";
}
发生过敌人位置不对,敌人的技能接连很快释放。但是重启unity后就好了。
图中的组件明明有赋值,同样的也有其它组件也这样写。
但是只有这个显示没赋值的提示。
不影响运行,但看着难受。
FightNavMesh 初始不激活才不会挡Scene,美观点,所以不能通过GameObject.Find来找FightNavMesh 。
所以让FightNavMesh 去找需要它的脚本。
或者用下一知识点的的FindTop
…
最后还是直接用FindTop
//挂在 未激活的命名为FightNavMesh的节点
/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
......
public GameObject FightNavMesh { get { return gameObject; } }
public void Init()
{
this.SendCommnd<SetFightNavMeshGoCommand>(this.FightNavMesh);
/****************************************************
文件:GetFightNavMeshGo.cs
作者:lenovo
邮箱:
日期:2023/5/19 5:36:53
功能:获得这个初始不激活的根节点FightNavMesh
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class SetFightNavMeshGoCommand : ICommand
{
public void Execute(object obj)
{
GameObject go = obj as GameObject;
this.GetSystem<ISceneSystem>().FightModeGo = go;
}
}
两种写法,
用gameObject.FindTop 就返回 GameObject
用transform .FindTop 就返回 Transform
///
/// 场景中根节点
/// 未激活也可以找到
///
public static GameObject FindTop(this GameObject curGo, string tarName)
{
GameObject[] gos = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
foreach (GameObject go in gos)
{
if (go.name == tarName)
{
return go;
}
}
return null;
}
///
/// 场景中根节点
/// 不激活也可以找到
public static Transform FindTop(this Transform t, string tarName)
{
GameObject[] gos = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
foreach (GameObject go in gos)
{
if (go.name == tarName)
{
return go.transform;
}
}
return null;
}
跑
遇敌
平A
反间(用完敌人抖动)
使用物品