您现在的位置是:首页 >技术交流 >Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)网站首页技术交流

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

weixin_39538253 2023-05-14 08:00:02
简介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 不明没赋值的提示

图中的组件明明有赋值,同样的也有其它组件也这样写。
但是只有这个显示没赋值的提示。
不影响运行,但看着难受。
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。