您现在的位置是:首页 >技术交流 >Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)网站首页技术交流
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
00 网址 来源
siki学院的(1年有限期到期前下载的项目,现在已经过期,所以自己理清项目)
所以更多的不是学习这个项目,而是学习理清该类型的项目的思路
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
。。。。
Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)【B站的第一季的部分视频】
00 插件
Cinemachine Camera
自己的代码库,用到的主要是对Component类组件的拓展
00 知识点
StateMachineBehaviour的使用
Trigger写的一个游戏框架 有 System,Model,Command,Event,IOC(单例的容器)等概念
00 了解 修饰符
modify :做了修改
stars:改改后,值得收藏的代码
watch:进去跑一下逻辑
bug:自己修改后报错的地方
scene:场景,以场景为分割线划分内容
01 总体代码---------------------
VS解决方案视图,自己做的类图
枚举类型是自己拆出来的,后面有
。。。
.cd是类图。
modify partial 分开脚本、手动控制 Init()
.cd是类图。类图只能看个大概的继承关系,最好的是将引用到的脚本尽可能地集合起来。
比如CharacterFigthAI,用 partial 分开脚本,用 手动控制 Init() 代替 Awake()、Start()
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)的枚举类型全部提到一个文件夹下。
另一个是架构代码,我先不动。
modify 结构体
将业务具体代码(SpecificCode)的struct类型(不继承,因为有的:ICommand)全部提到一个文件夹下。(后面推的时候觉得应该做的,不是一开始就知道要这样做)
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 紧凑
像这种没有空行的,不知道是原来就这样,还是被软件重置了
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 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
}
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 原来的世界场景
右边的原版的场景直接露出来(包括白色图片(转场用的))
watch 总体ui
除了聊天框,其他的UI都没做
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;
}
之后可以这样用
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;
}
}
#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);
}
}
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);
}
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
位置
提取要点
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);
}
}
}
总结
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+单击,点不到那里
所以一个静态类来汇总(可能会有更好的方法)
/****************************************************
文件:InvokeMethod.cs
作者:WWS
日期:2023/04/04 20:58:16
功能:
*****************************************************/
public static class InvokeMethod
{
public const string PlayIdleAniamtion = "PlayIdleAniamtion";
}
bug 战斗时的道具,技能面板
bug演示
嫌弃碍眼隐藏,导致运行点击时这连个面板加载不了。
也是跟转场一样,在转场中有一起解决
看到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转过来
查它的引用,有FightSystem
modify CharacterFightAI代码太多,改用partial
原来的CharacterFightAI被拆分了
这几个都是 见名知义 的,就不写summary了
bug ExtendResources
加载一个audio的路径,想把该方法写进同名(功能性趋同)但不同命名空间(一根据实际工程,一块复用性高)的ExtendResources,办不到。
同名,就要用partial,partial就要同一个命名空间。
暂时在实际工程下新建一个类来存储改方法。
watch 这3块
watch FightSystem的目录
Cammand、Behaviour、总控脚本和拆分出来的CharacterFightAI(就是CharacterFightAI.xxx)
敌人、角色的生成
在FightSystem找到了实例脚本CreateCharactersCommand ,倒查引用过来的
并且结果挂在FightNavMesh上
CreateCharactersCommand
CreateCharactersCommand
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引用,因为它是该文件下的脚本的父类
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
}
watch 人物放技能、防御、使用物品
如下,一个面板,三个按钮
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();
}
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
01 bug NumCanvas
伤害数值的脚本
忘记忘记怎么改的了
02 watch 尝试找移动的代码
03 watch 不报错也这样
04 watch DefendAndDodgeFightBehaviour.ToDenfendPos()
是被攻击者的动作
DefendAndDodgeFightBehaviour.ToDenfendPos()
05 watch 报错时将角色的State手动改为Dead会退出战斗,其他状态没有变化
06 bug发生和原来的,有一个脚本 在场景中引用的 情况不同
01 查脚本AttackStateBehaviour引用
上面是我改的,下面是原版的
该脚本不继承于Component,不能加到节点上
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 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)
//}
}
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时脚本丢失(拖拽复制的缺点)
之前以下丢失了
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 运行中位置、看向、角色名、角色模型(很少可能全一样,而且主角写死了是骨精灵)都错误
那就是初始位置和看向都没有及时初始化
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 不明没赋值的提示
图中的组件明明有赋值,同样的也有其它组件也这样写。
但是只有这个显示没赋值的提示。
不影响运行,但看着难受。