> 文章列表 > Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

00 网址 来源

siki学院的(1年有限期到期前下载的项目,现在已经过期,所以自己理清项目)
所以更多的不是学习这个项目,而是学习理清该类型的项目的思路
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
。。。。
Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)【B站的第一季的部分视频】
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

00 插件

Cinemachine Camera
自己的代码库,用到的主要是对Component类组件的拓展

00 知识点

StateMachineBehaviour的使用
Trigger写的一个游戏框架 有 System,Model,Command,Event,IOC(单例的容器)等概念

00 了解 修饰符

modify :做了修改
stars:改改后,值得收藏的代码
watch:进去跑一下逻辑
bug:自己修改后报错的地方
scene:场景,以场景为分割线划分内容

01 总体代码---------------------

VS解决方案视图,自己做的类图

枚举类型是自己拆出来的,后面有
。。。
.cd是类图。
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

modify partial 分开脚本、手动控制 Init()

.cd是类图。类图只能看个大概的继承关系,最好的是将引用到的脚本尽可能地集合起来。
比如CharacterFigthAI,用 partial 分开脚本,用 手动控制 Init() 代替 Awake()、Start()
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

modify 动态添加

这种写法能做清晰地知道脚本要控制哪些UI,哪些按钮、Slider有监听事件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;//*
//创建人: Trigger 
//功能说明:战斗UI管理
//* /// <summary>战斗UI管理</summary>
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);}

watch 脚本入口

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());}
}

modify 枚举类型

将业务具体代码(SpecificCode)的枚举类型全部提到一个文件夹下。
另一个是架构代码,我先不动。
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

modify 结构体

将业务具体代码(SpecificCode)的struct类型(不继承,因为有的:ICommand)全部提到一个文件夹下。(后面推的时候觉得应该做的,不是一开始就知道要这样做)
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

modify 各种字符串变量

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";/// <summary>屏障</summary>public const string SHIELD = "Shield";public const string ITEM = "Item";
}

modify FightSystem

汇总到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);}

bug 紧凑

像这种没有空行的,不知道是原来就这样,还是被软件重置了
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

stars 简写

个人习惯

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

02 Scene 进入游戏----------------

watch 开头动画

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using static ExtendTweenMethods;public class LoginScene : MonoBehaviour
{/// <summary>bgTrans的父节点,拖拽赋值</summary>public Transform bg;public Transform[] bgTrans;public Vector3[] targetPos;public ExtendTweenMethods.Tween[] tweens;// Start is called before the first frame updatevoid Start(){InitBgTrans();InitTargetPos();InitTweens();}#region 辅助private void InitBgTrans(){//4张背景图(从左到右)的transbgTrans = new Transform[4];for (int i = 0; i < bgTrans.Length; i++){bgTrans[i] = bg.GetChild(i);}        }private void InitTargetPos(){//4张背景图012(从)的目标位置123targetPos = 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);//左边跑50tweens[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
}

在这里插入图片描述

stars DeepFindChild

因为想把

GameStartInstance.DeepFindChild(Transform t, string childName);

改成

t.FindChildDeep( string childName);

少写很多。需要改以下

public class GameStartInstance : MonoBehaviour
{....../// <summary>/// 深度查找子对象transform引用/// </summary>/// <param name="root">父对象</param>/// <param name="childName">具体查找的子对象名称</param>/// <returns></returns>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
{/// <summary>/// 深度查找子对象transform引用/// </summary>/// <param name="root">父对象</param>/// <param name="childName">具体查找的子对象名称</param>/// <returns></returns>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;}
}

03 Scene 世界-----------------------

bug 原来的世界场景

右边的原版的场景直接露出来(包括白色图片(转场用的))
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch 总体ui

除了聊天框,其他的UI都没做
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

stars 删掉GameRes

从继承于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;/// <summary>/// GameStartInstance中调用/// </summary>public static void Init(){resDict = new Dictionary<string, Object>();resArrayDict = new Dictionary<string, Object[]>();}/// <summary>/// 文件路径/// </summary>/// <typeparam name="T"></typeparam>/// <param name="resPath"></param>/// <returns></returns>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;}}/// <summary>/// 文件夹路径/// </summary>/// <typeparam name="T"></typeparam>/// <param name="resPath">文件夹下所有</param>/// <returns></returns>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;}}

stars Component.trasform.xxx

    /// <summary>位置。少写个.transform</summary>public static Vector3 Position(this Component c) {return c.transform.position;}

之后可以这样用
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

modify 单例GameStartInstance

单例要么 大写开头,要么 _小写开头。觉得是不是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;}}#endregionprivate 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);}}

bug 战斗时的Nav不激活报Null

之前

也是防止紫色的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);    }

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch 人物行走

位置

两个Nav区域向SceneSystem中的CharacterAI发起请求,stopping

在这里插入图片描述

watch NotWalkableArea 、SetWillStoppingStateCommand

NotWalkableArea 发起Command,具体是SetWillStoppingStateCommand
SetWillStoppingStateCommand 向 SceneSystem 中的发起请求,控制其中的 CharacterAI.willStopping。所以CharacterAI是控制人物行走的脚本,它挂载人物上。上面也挂有动画切换的脚本CharacterAnimatorController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>检测玩家是否点击到不可走区域</summary>
public class NotWalkableArea : MonoBehaviour,IController
{private void OnMouseDown(){this.SendCommnd<SetWillStoppingStateCommand>();}
}
using UnityEngine;
/// <summary>
///  遇“墙”停下来
/// </summary>
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......

watch 单击双击

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
//*
//创建人: Trigger 
//功能说明:非战斗状态下的寻路
//*        、/// <summary>非战斗状态下的寻路</summary>
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);}}/// <summary>单击人物追过去</summary>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);}}/// <summary>双击人物跟随鼠标</summary>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
}

watch 人物遇敌 NormalModeMananger

位置

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

提取要点

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%)会不会遇敌。
也就是

    /// <summary>时间概率遇敌</summary>public void JudgeEnterTheFight(){if (Time.time - EnterFightTimer >= 8){if (Random.Range(0, 5) >= 1){//进入战斗EnterOrExitFightMode(true);}else{//重新计时SetEnterFightState(true);}}}

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

总结

IBelongToArchitecture是最低层的接口,基此有各种功能接口。
IController,ICommand,ISystem又会实现所需要的各种功能接口。

遇敌具体代码

public class SceneSystem : ISceneSystem
{/// <summary>时间概率遇敌</summary>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);//重新计时}}}......

stars&bug 世界到战斗的转场

modify 手动控制顺序

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 生命/// <summary>需要StartArchitecture.Instance;</summary>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);}}}/// <summary>/// 相机截图/// </summary>/// <param name="camera">截屏相机</param>/// <param name="rect">截屏区域</param>/// <returns></returns>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;}/// <summary>/// 设置截屏UI所需材质的值/// </summary>/// <param name="texture"></param>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)));}
}

Scene 战斗-----------------------

bug Invoke不好用的地方

是字符串,导致ctrl+单击,点不到那里
所以一个静态类来汇总(可能会有更好的方法)
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

/文件:InvokeMethod.cs作者:WWS日期:2023/04/04 20:58:16功能:*/public static class InvokeMethod
{public const string PlayIdleAniamtion = "PlayIdleAniamtion";
}

bug 战斗时的道具,技能面板

bug演示

嫌弃碍眼隐藏,导致运行点击时这连个面板加载不了。
也是跟转场一样,在转场中有一起解决
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

看到UI下刮油脚本的Panel

转场,FightUI,ItemUI,SkillUI,一共四个
在这里插入图片描述

去相应的脚本SetActive

watch 遇敌战斗SceneSystem(由上面人物遇敌引入)

转入的代码

重点是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转过来
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

查它的引用,有FightSystem

modify CharacterFightAI代码太多,改用partial

原来的CharacterFightAI被拆分了
这几个都是 见名知义 的,就不写summary了
在这里插入图片描述

bug ExtendResources

加载一个audio的路径,想把该方法写进同名(功能性趋同)但不同命名空间(一根据实际工程,一块复用性高)的ExtendResources,办不到。
同名,就要用partial,partial就要同一个命名空间。

暂时在实际工程下新建一个类来存储改方法。

watch 这3块

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch FightSystem的目录

Cammand、Behaviour、总控脚本和拆分出来的CharacterFightAI(就是CharacterFightAI.xxx)

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

敌人、角色的生成

在FightSystem找到了实例脚本CreateCharactersCommand ,倒查引用过来的
并且结果挂在FightNavMesh上
CreateCharactersCommand
CreateCharactersCommand
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

CreateCharactersCommand

using UnityEngine;
using System.Linq;
/// <summary>
/// 创建人:Trigger<para/> 
/// 命令名称:在战斗开始后生成战斗敌人与玩家<para/> 
/// 参数:
/// </summary>
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;}/// <summary>/// 实例角色/// </summary>/// <param name="fightAI"></param>/// <param name="parent">父节点</param>/// <param name="localPos">局部坐标</param>/// <returns></returns>CharacterFightAI InstantiateCharacter(CharacterFightAI fightAI,Transform parent){CharacterFightAI ai = GameObject.Instantiate(fightAI, parent);ai.transform.localPosition = Vector3.zero;return ai;}#endregion}

FightLogicController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
//*
//创建人: Trigger 
//功能说明:游戏战斗逻辑控制
//* /// <summary>游戏战斗逻辑控制</summary>
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>();}}

GameStartInstance

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:游戏入口实例,初始化并管理所有的管理者,提供mono方法
//* /// <summary>游戏入口实例,初始化并管理所有的管理者,提供mono方法</summary>
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);}}

watch CharacterFightAI(FightSystem的Behaviour的挂载点)

CharacterFightAIBehaviour没被CharacterFightAI引用,因为它是该文件下的脚本的父类

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch 战斗时角色的UI面板

也就是把战斗系统的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 生命/// <summary>需要StartArchitecture.Instance;</summary>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 
}

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch 人物放技能、防御、使用物品

如下,一个面板,三个按钮
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

    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管理
//* /// <summary>战斗UI管理</summary>
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 辅助/// <summary>/// 使用技能指令按钮事件/// </summary>private void ClickSkillBtn(){this.SendEvent<OpenOrCloseSkillPanelEvent>(true);}/// <summary>/// 使用物品指令按钮事件/// </summary>public void ClickUseItemBtn(){this.SendCommnd<CloseAllFightUIPanelCommand>();this.SendEvent<OpenOrCloseFightBagPanelEvent>(true);}/// <summary>/// 防御指令按钮事件/// </summary>public void ClickDefendBtn(){this.SendCommnd<CloseAllFightUIPanelCommand>();this.SendCommnd<SetCharacterActCodeCommand>(new SetCharacterActCodeCommandParams(){actCode=ActCode.DEFEND});}/// <summary>/// 关闭或开启战斗指令面板/// </summary>/// <param name="obj"></param>private void OpenOrCloseFightCommandPanel(object obj){fightCommandPanelGo.SetActive((bool)obj);}#endregion}

stars FindButtonDeep

为了达到以下效果,所以有FindButtonDeep

transform.FindButtonDeep( "Btn_SkillHengSaoQianJun");

SkillUIManager

/// <summary>技能UI管理</summary>
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");......

ExtendComponent(各种Component的拓展方法)

    /// <summary>/// 深度查找子对象transform引用/// </summary>/// <param name="root">父对象</param>/// <param name="childName">具体查找的子对象名称</param>/// <returns></returns>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>();}/// <summary>/// 深度查找子对象transform引用/// </summary>/// <param name="root">父对象</param>/// <param name="childName">具体查找的子对象名称</param>/// <returns></returns>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;}

stars SpriteRenderer

为了这种效果

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);}

watch 人物平A、技能攻击

modify CharacterMouseDetection

进入可以知道直接点击敌人,就是平A。
所以找以下的单击类CharacterMouseDetection 。
重点是SetCharacterActCodeCommandParams(struct)、SetPlayerTargetAICommand。

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.EventSystems;
//*
//创建人: Trigger 
//功能说明:人物鼠标检测
//* 
/// <summary>人物鼠标检测(Detection侦查察觉检测)</summary>
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 辅助/// <summary>/// 当前人物是否可以点击/// </summary>/// <returns></returns>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  }

SetCharacterActCodeCommandParams

/// <summary>
/// 设置玩家行为码的命令参数
/// </summary>
public struct SetCharacterActCodeCommandParams
{/// <summary>动作码</summary>public ActCode actCode;/// <summary>行动信息参数</summary>public ActObj actObj;
}

SetPlayerTargetAICommand

SetPlayerTargetAICommand 中对 PlayerTargetAI 进行赋值,那就是有相关的判空引用。
只有一个引用 fightSystem.PlayerAI.targetAI ,找targetAI 。

using UnityEngine;
/// <summary>
/// 创建人:Trigger 
/// 命令名称:设置玩家目标AI
/// 参数:CharacterFightAI
/// </summary>
public struct SetPlayerTargetAICommand : ICommand
{public void Execute(object dataObj){this.GetSystem<IFightSystem>().PlayerTargetAI = (CharacterFightAI)dataObj;}
}

AttackFightBehaviour

targetAI 22处引用,有关攻击只有 在AttackFightBehaviour(里面只有进行音效和动画处理)

    /// <summary>/// 攻击行为/// </summary>public void AttackBehaviour(){AudioSourceManager.Instance.PlayCharacterSound("Attack",gameObject.name);cac.PlayAttackAnimation();}

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

watch 人物混乱

从 CharacterFightAI 拆分出来的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*
//创建人: Trigger 
//功能说明:人物战斗AI
//* /// <summary>人物战斗AI</summary>
public partial class CharacterFightAI : MonoBehaviour,IController
{#region 生命private void OnDestroy(){if (chaosTween != null){chaosTween.Pause();chaosTween.Kill();}}#endregion  #region 混乱/// <summary>/// 混乱移动/// </summary>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
}

watch 人物回复、扣血

从 CharacterFightAI.UseSkillOrItem.cs 得到使用物品和技能来回复

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:人物战斗状态数据显示
//* /// <summary>人物战斗状态数据显示</summary>
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 辅助/// <summary>/// 显示跟血量变化相关的内容/// </summary>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);}}/// <summary>/// 显示跟蓝耗变化相关的内容/// </summary>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}

watch 人物闪避、防御

CharacterFightAI 中拆分的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*
//创建人: Trigger 
//功能说明:人物战斗AI
//* /// <summary>人物战斗AI</summary>
public partial class CharacterFightAI : MonoBehaviour,IController
{#region 闪避与防御/// <summary>/// 闪避行为/// </summary>public void DodgeBehaviour(){defendAndDodgeFightBehaviour.DodgeBehaviour();}/// <summary>/// 防御行为/// </summary>public void DefendBehaviour(){defendAndDodgeFightBehaviour.DefendBehaviour();}/// <summary>/// 移动到防御位置并返回/// </summary>/// <param name="animationTime">动画时间</param>/// <param name="callBack">需要在动画完成后进行的额外回调</param>public void ToDenfendPos(float animationTime, UnityAction callBack = null){defendAndDodgeFightBehaviour.ToDenfendPos(animationTime, callBack);}#endregion
}
using UnityEngine;
/// <summary>
/// 创建人:Trigger 
/// 命令名称:人物死亡命令
/// 参数:
/// </summary>
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;}
}

watch 人物受击、死亡

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:人物受击与死亡行为
//* /// <summary>人物受击与死亡行为</summary>
public class HitAndDieFightBehaviour : CharacterFightAIBehaviour
{/// <summary> 受击行为 </summary>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();}/// <summary> 判断是否死亡 </summary>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); //受击}}/// <summary> 死亡行为 </summary>public void DieBehaviour(){this.SendCommnd<CharacterDieCommand>();fromAI.characterState = CharacterState.DEAD;fromAI.SetMovingState(false);        //主要目的是为了放横扫一类的技能没有在原始位置,要回去fromAI.ResetState();       }
}

结束当前回合

watch 链接起来FightSystem的所有Command

watch 敌人动作

CharacterFightAI
设置了ActCode、 ActObj,传给 CharacterFightAI

using UnityEngine;
/// <summary>
/// 创建人:Trigger 
/// 命令名称:随机敌人AI的行动命令
/// 参数:
/// </summary>
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 修改后角色攻击后不返回(没学过)

bug在伤害数值和伤害特效出现后发生的原地动画不返回
AttackFightBehaviour.AttackTarget()打点
0会返回,但是一直是1
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

01 bug NumCanvas

伤害数值的脚本
忘记忘记怎么改的了
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

02 watch 尝试找移动的代码

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

03 watch 不报错也这样

04 watch DefendAndDodgeFightBehaviour.ToDenfendPos()

是被攻击者的动作
DefendAndDodgeFightBehaviour.ToDenfendPos()

05 watch 报错时将角色的State手动改为Dead会退出战斗,其他状态没有变化

06 bug发生和原来的,有一个脚本 在场景中引用的 情况不同

01 查脚本AttackStateBehaviour引用

上面是我改的,下面是原版的
该脚本不继承于Component,不能加到节点上

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

02 AttackStateBehaviour.cs

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 stateoverride 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 stateoverride 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)//}
}

03 原版的UnityEngine.StateMachineBehaviour与Animator

发现它是加载在动画器上的状态上
在这里插入图片描述

04 我的UnityEngine.StateMachineBehaviour与Animator

可以看到报错了
在这里插入图片描述

modify 拆分CharacterAnimatorController

public partial class CharacterFightAI : MonoBehaviour,IController
{......public CharacterState characterState;
/// <summary>角色状态</summary>
public enum CharacterState
{NORMAL,/// <summary>休息状态</summary>REST,/// <summary>混乱状态</summary>CHAOS,DEAD
}

找到ActCode.ATTACK;

fromAI.toAI.HitBehaviour();条件改清楚

CharacterAnimatorController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:控制人物动画切换
//* /// <summary>控制人物动画切换</summary>
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);} }
}

CharacterAnimatorController.AnimationEvent.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:控制人物动画切换
//* /// <summary>控制人物动画切换</summary>
public partial class CharacterAnimatorController : MonoBehaviour
{/// <summary>挂在Timeline时间轴上的帧的时间</summary>private void AttackAnimationEvent(){fightAI.AttackTarget();}/// <summary>挂在Timeline时间轴上的帧的时间</summary>private void UseSkillOrItemAnimationEvent(){fightAI.UseSkillOrItemAction();}
}

CharacterAnimatorController.PlayAnimation

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*
//创建人: Trigger 
//功能说明:控制人物动画切换
//* /// <summary>控制人物动画切换</summary>
public partial class CharacterAnimatorController : MonoBehaviour
{/// <summary>/// 播放位移动画/// </summary>/// <param name="curPos">当前位置</param>/// <param name="navPos">导航点</param>/// <param name="tarPos">最终目标点</param>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);}
}

bug CharacterFightAI报空

拆分CharacterAnimatorController时脚本丢失(拖拽复制的缺点)
之前以下丢失了
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

bug 进入战斗实例失败

修改代码后发生,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();

在这里插入图片描述

stars GetOrAddComponent

    /// <summary>/// 获取或增加组件。/// </summary>/// <typeparam name="T">要获取或增加的组件。</typeparam>/// <param name="gameObject">目标对象。</param>/// <returns>获取或增加的组件。</returns>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;}

bug Exception: 不存在ID为0的人物

01 ID是fromAI.characterInfo.ID

Init()在CharacterFightAI中调用
其中fromAI.characterInfo数据为空

/// <summary>人物战斗状态数据显示</summary>
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;}

02 fromAI在基类 CharacterFightAIBehaviour 中

03 CharacterFightAI.characterInfo

CharacterFightAI.characterInfo

/// <summary>人物战斗AI</summary>
public partial class CharacterFightAI : MonoBehaviour,IController
{#region 字属......public CharacterInfo characterInfo;

04 characterInfo在CharacterDataModel 中初始化

为0,就是没赋值过,默认为0
public class CharacterDataModel : ICharacterDataModel
{
private Dictionary<int, CharacterInfo> characterInfoDict;
private Dictionary<string, CharacterInfo> characterInfoStrDict;

05 bug 应为是接口,报错的位置没挑到实际的位置

新建 CharacterInfo GetCharacterInfo(CharacterInfo characterInfo )来找准位置

/// <summary>人物战斗状态数据显示</summary>
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 辅助/// <summary>/// 方便找bug,不然报错在接口,不清晰/// </summary>CharacterInfo GetCharacterInfo(CharacterInfo characterInfo ){if (characterInfo.ID >= 0){throw new System.Exception("ID异常:"+characterInfo.ID);}return this.GetModel<ICharacterDataModel>().GetCharacterInfo(characterInfo.ID);}

06 bug 手动控制顺序

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;/// <summary>
/// 命令名称:在战斗开始后生成战斗敌人与玩家
/// </summary>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();}
}

bug 位置不对

01 运行中位置、看向、角色名、角色模型(很少可能全一样,而且主角写死了是骨精灵)都错误

那就是初始位置和看向都没有及时初始化
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)

02 错误位置,赋值与使用的顺序

01 基类CharacterFightAIBehaviour有
protected CharacterFightAI fromAI;
02 报的错误就是
fromAI.characterInfo.ID为0,数据字典(一些json数据)ID是1-5。也就是fromAI的初始characterInfo有问题
03 fromAI就是同节点的CharacterFightAI

。。。。
CharacterFightValueState

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;}

stars 找子节点数组,GetChildren,GetChildrenDeep

    /// <summary>/// 找到 自身 下的 所有子节点  children/// </summary>/// <param name="parent"></param>/// <param name="children"></param>/// <returns></returns>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;}/// <summary>///  找到 自身 的 叫 parentName的子节点 下的所有子节点 children /// </summary>/// <param name="t"></param>/// <param name="parentName"></param>/// <param name="children"></param>/// <returns></returns>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;}

starts modify AnimatorPara.cs

/文件:作者: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";
}

bug 重启

发生过敌人位置不对,敌人的技能接连很快释放。但是重启unity后就好了。
在这里插入图片描述

bug 不明没赋值的提示

图中的组件明明有赋值,同样的也有其它组件也这样写。
但是只有这个显示没赋值的提示。
不影响运行,但看着难受。
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)