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

00 网址 来源

01 Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)
01 Unity2D 回合制游戏案例 - 梦幻西游(第一季 战斗逻辑篇)【B站的第一季的部分视频】
02 Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)
03 Unity2D 商业游戏案例《梦幻西游》(番外篇 - 场景系统)

。。。。。。
siki学院的(1年有限期到期前下载的项目,现在已经过期,所以自己理清项目,这篇文章做的是【第二季】的)
所以更多的不是学习这个项目,而是学习理清该类型的项目的思路。

。。。。
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第1张图片

工程

发的是我改过的,不是原版的。
总体572M,压缩后223M(zip)
github和网盘有些不一样,是路径太长,删了一个文件夹

github上
百度网盘,提取码:xu90

untiy打开的是2,不是1,这样的目录结构是为了以后想往里面放什么不会污染代码工程
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第2张图片

00 插件

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

00 知识点

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

00 了解 修饰符

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

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

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

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

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

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

modify 动态添加

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


//*****************************************
//创建人: Trigger 
//功能说明:战斗UI管理
//***************************************** 

/// 战斗UI管理
public class FightUIManager : MonoBehaviour,IController
{
    private Button defendCommandBtn;
    private Button skillCommandBtn;
    private Button useItemCommandBtn;
    private GameObject fightCommandPanelGo;

    public void Init()
    {
        gameObject.SetActive(true);
        fightCommandPanelGo =   transform.FindChildDeep( "Emp_FightCommand").gameObject;
        fightCommandPanelGo.SetActive(true);
        skillCommandBtn =       transform.FindChildDeep( "Btn_Skill").GetComponent<Button>();
        useItemCommandBtn =     transform.FindChildDeep( "Btn_UseItem").GetComponent<Button>();
        defendCommandBtn =      transform.FindChildDeep( "Btn_Defend").GetComponent<Button>();

        //
        skillCommandBtn.onClick.AddListener(ClickSkillBtn);
        useItemCommandBtn.onClick.AddListener(ClickUseItemBtn);
        defendCommandBtn.onClick.AddListener(ClickDefendBtn);

        //
        this.RegistEvent<OpenOrCloseFightCommandPanelEvent>(OpenOrCloseFightCommandPanel);
        fightCommandPanelGo.SetActive(false);
    }

watch 脚本入口

GameStartInstance调用了XYQArchitecture.Init()

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:XYQ游戏架构
//***************************************** 
public class XYQArchitecture : Architecture<XYQArchitecture>
{
    protected override void Init()
    {
        RegistSystem<ISystem>(new UISystem());
        RegistSystem<ISkillSystem>(new SkillSystem());
        RegistSystem<IFightSystem>(new FightSystem());
        RegistSystem<ISceneSystem>(new SceneSystem());

        RegistModel<IPlayerDataModel>(new PlayerDataModel());
        RegistModel<ISkillDataModel>(new SkillDataModel());
        RegistModel<ICharacterDataModel>(new CharacterDataModel());
    }
}

modify 枚举类型

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

modify 结构体

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

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

modify FightSystem

汇总到GameObjectPath,GameObjectName

    public void Init()
    {
        CharacterPrefab = ExtendResources.Get<GameObject>(GameObjectPath.Prefab_CharacterFight).GetComponent<CharacterFightAI>();
        Transform tf = GameObject.Find(GameObjectName.FightNavMesh).transform;
        PlayerInitPosTrans = tf.Find(GameObjectName.PlayerPos);
        GetPositionsTrans(tf, GameObjectName.EnemyPos, ref enemyInitPosTrans);
        GetPositionsTrans(tf, GameObjectName.PlayerDieStartMovePath, ref playerDieStartMovePath);
        GetPositionsTrans(tf, GameObjectName.PlayerDieEndMovePath, ref playerDieEndMovePath);
        GetPositionsTrans(tf, GameObjectName.EnemyDieStartMovePath, ref enemyDieStartMovePath);
        GetPositionsTrans(tf, GameObjectName.EnemyDieEndMovePath, ref enemyDieEndMovePath);
    }

bug 紧凑

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

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
{
    /// bgTrans的父节点,拖拽赋值
    public Transform bg;
    public Transform[] bgTrans;
    public Vector3[] targetPos;
    public ExtendTweenMethods.Tween[] tweens;

    // Start is called before the first frame update
    void Start()
    {
        InitBgTrans();
        InitTargetPos();
        InitTweens();

    }

    #region 辅助


    private void InitBgTrans()
    {
        //4张背景图(从左到右)的trans
        bgTrans = new Transform[4];
        for (int i = 0; i < bgTrans.Length; i++)
        {
            bgTrans[i] = bg.GetChild(i);
        }        
    }
    private void InitTargetPos()
    {
        //4张背景图012(从)的目标位置123
        targetPos = new Vector3[bgTrans.Length];
        for (int i = 0; i < bgTrans.Length-1; i++)
        {
            targetPos[i] = bgTrans[i + 1].position; 
        }
        //右边两张不动,左边两个一直两班倒(因为23是为了给01确定目标位置,不移动的)
        targetPos[0] = bgTrans[0].position; //单独空出targetPos[0]来给右边的换到左边        
    }

    void InitTweens()
    { 
        tweens = new ExtendTweenMethods.Tween[2];
        tweens[0]= bgTrans[0].DoMove( target:targetPos[1], time:50, loop:100);//左边跑50
        tweens[1] = bgTrans[1].DoMove(target: targetPos[1], time:25, loop:1).SetOnComplete(() => //右边跑25     
        {
            bgTrans[1].position = targetPos[0];//右边放左边
            bgTrans[1].DoMove(targetPos[1], 50, 100);//继续Move
        });    
    }

    void KillTweens(Tween[] tweens )
    { 
        for (int i = 0; i < tweens.Length; i++)
        {
            tweens[i].Kill();
        }    
    }

    public void LoadGame()
    {
        KillTweens(tweens);
        SceneManager.LoadScene(1);
    }
    public void ExitGame()
    {
        Application.Quit();
    }
    #endregion
}

stars DeepFindChild

因为想把

GameStartInstance.DeepFindChild(Transform t, string childName);

改成

t.FindChildDeep( string childName);

少写很多。需要改以下

public class GameStartInstance : MonoBehaviour
{
	......
    /// 
    /// 深度查找子对象transform引用
    /// 
    /// 父对象
    /// 具体查找的子对象名称
    /// 
    public static Transform DeepFindChild(Transform root, string childName)
    {
        Transform result = null;
        result = root.Find(childName);
        if (!result)
        {
            foreach (Transform item in root)
            {
                result = DeepFindChild(item, childName);
                if (result != null)
                {
                    return result;
                }
            }
        }
        return result;
    }
}

改成

public static class ExtendComponent
{
    /// 
    /// 深度查找子对象transform引用
    /// 
    /// 父对象
    /// 具体查找的子对象名称
    /// 
    public static Transform FindChildDeep(this Transform root, string childName)
    {
        Transform result = null;
        result = root.Find(childName);
        if (!result)
        {
            foreach (Transform item in root)
            {
                result = FindChildDeep(item, childName);
                if (result != null)
                {
                    return result;
                }
            }
        }
        return result;
    }
}

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

bug 原来的世界场景

右边的原版的场景直接露出来(包括白色图片(转场用的))

watch 总体ui

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

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;

    /// 
    /// GameStartInstance中调用
    /// 
    public static void Init()
    {
        resDict = new Dictionary<string, Object>();
        resArrayDict = new Dictionary<string, Object[]>();
    }

    /// 
    /// 文件路径
    /// 
    /// 
    /// 
    /// 
    public static T Get<T>(this Resources resources, string resPath) where T : Object
    {
        if (resDict.ContainsKey(resPath))
        {
            return resDict[resPath] as T;
        }
        else
        {
            var res = Resources.Load(resPath);
            resDict.Add(resPath, res);
            return res as T;
        }
    }


    /// 
    /// 文件夹路径
    /// 
    /// 
    /// 文件夹下所有
    /// 
    public static T[] GetAll<T>(this Resources resources, string resPath) where T : Object
    {
        Object[] objArray;
        if (resDict.ContainsKey(resPath))
        {
            objArray = resArrayDict[resPath];
        }
        else
        {
            var res = Resources.LoadAll(resPath);
            resArrayDict.Add(resPath, res);
            objArray = res;
        }
        T[] TArray = new T[objArray.Length];
        for (int i = 0; i < TArray.Length; i++)
        {
            TArray[i] = objArray[i] as T;
        }
        return TArray;
    }

}


stars Component.trasform.xxx

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

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

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碍眼想隐藏它
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第10张图片
bug unity里面文件夹名字不能包含 . ,编译不通过加不了脚本

之后

Init()被GameStartInstance调用

public class FightLogicController : MonoBehaviour, IController
{
    private bool hasInit;

    public void Init()
    {
        transform.Find("FightBG").gameObject.SetActive(true);
        hasInit = true;
        gameObject.SetActive(false);    


    }

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

watch 人物行走

位置

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

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

watch NotWalkableArea 、SetWillStoppingStateCommand

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// 检测玩家是否点击到不可走区域
public class NotWalkableArea : MonoBehaviour,IController
{
    private void OnMouseDown()
    {
        this.SendCommnd<SetWillStoppingStateCommand>();
    }
}
using UnityEngine;
/// 
///  遇“墙”停下来
/// 
public struct SetWillStoppingStateCommand : ICommand
{
    public void Execute(object dataObj)
    {
        this.GetSystem<ISceneSystem>().PlayerNormalAI.willStopping = true;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:场景系统
//***************************************** 
public class SceneSystem : ISceneSystem
{


    #region 字属
	......
    public CharacterAI PlayerNormalAI { private set; get; }
	......
    #endregion
    ......

watch 单击双击

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
//*****************************************
//创建人: Trigger 
//功能说明:非战斗状态下的寻路
//*****************************************        、


/// 非战斗状态下的寻路
public class CharacterAI : MonoBehaviour
{
	......
    private void GetNotWalkableAreaMovePoint()
    {
        if (willStopping)
        {
            Ray2D ray = new Ray2D(transform.position, targetPos - transform.position);
            RaycastHit2D raycastHit2D = Physics2D.Raycast(ray.origin, ray.direction);
            if (raycastHit2D)
            {
                targetPos = raycastHit2D.point;
                targetPos -= 0.1f * (targetPos - transform.position);
            }
            willStopping = false;
            targetPos.z = transform.position.z;
            meshAgent.SetDestination(targetPos);
        }
    }

    /// 单击人物追过去
    private void ClickMouse()
    {
        targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        targetPos.z = transform.position.z;
        meshAgent.SetDestination(targetPos);
        //
        if (Time.time - createEffectTimer >= 0.05f)
        {
            createEffectTimer = Time.time;
            Instantiate(clickEffectGo, targetPos, Quaternion.identity);
        }
    }


    /// 双击人物跟随鼠标
    private void DoubleClickMouse()
    {
        if (followMouse)//开启开关,人物跟随鼠标移动
        {
            ClickMouse();
        }
        else
        {
            if (Time.time-followMouseTimer>=0.4f)
            {
                //已超出规定时间,重新计时
                followMouseTimer = Time.time;
                clickCount = 0;
            }
            else
            {
                //在时间间隔内
                if (clickCount>1)
                {
                    //双击
                    followMouse = true;
                }
            }
        }
    }
    #endregion
}

watch 人物遇敌 NormalModeMananger

位置

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

提取要点

IController
JudgeEnterTheFightCommand

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:游戏管理(判断是否进入战斗)
//***************************************** 
public class NormalModeMananger :MonoBehaviour,IController
{
    private void Update()
    {
        this.SendCommnd<JudgeEnterTheFightCommand>();
    }
}

看类图

NormalModeMananger 也就是IController做了SendCommand,具体的Command是去GetSystem,并且调用其中的方法。
具体的System是SceneSystem:ISystem,具体方法是吗,每隔8秒给个概率(80%)会不会遇敌。
也就是

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

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

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第15张图片
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第16张图片

总结

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

遇敌具体代码

public class SceneSystem : ISceneSystem
{
    /// 时间概率遇敌
    public void JudgeEnterTheFight()
    {
        if (Input.GetKeyDown(KeyCode.E))  //价格快捷键,方便测试
        {
            EnterOrExitFightMode(true); 
        }
        //
        if (Time.time - EnterFightTimer >= 8)
        {
            if (Random.Range(0, 5) >= 1)
            {
                EnterOrExitFightMode(true); //进入战斗
            }
            else
            {
                SetEnterFightState(true);//重新计时
            }
        }
    }
    ......

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 生命

    /// 需要StartArchitecture.Instance;
    public void Init()
    {
        cameraCapture = transform.FindChildDeep("RawImage_CaptureCamera").GetComponent<CameraCapture>()  ;
        fightUIManager=transform.FindChildDeep("Emp_FightUI").GetComponent<FightUIManager>();
        itemUIManager=transform.FindChildDeep("Emp_ItemUI").GetComponent<ItemUIManager>();
        skillUIManager=transform.FindChildDeep("Emp_SkillUI").GetComponent<SkillUIManager>();

        cameraCapture.Init();
        fightUIManager.Init();
        itemUIManager.Init();
        skillUIManager.Init();
    }


    #endregion 
    }

初始界面,方便看

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

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

转场效果代码

以下原代码

/****************************************************

	文件:
	作者:WWS
	日期:2023/03/25 13:17:08
	功能:摄像机截屏。作为正常转战斗的转场

*****************************************************/

//*****************************************
//创建人: Trigger 
//功能说明:摄像机截屏
//***************************************** 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


public class CameraCapture : MonoBehaviour,IController
{
    private RawImage ri;
    private float blurValue;
    void Start()
    {
        ri = GetComponent<RawImage>();
        gameObject.SetActive(false);
        this.RegistEvent<CaptrueCameraAndSetMaterialValueEvent>(CaptrueCameraAndSetMaterialValue);
    }

    void Update()
    {
        if (ri.gameObject.activeSelf)
        {
            blurValue += Time.deltaTime;
            ri.material.SetFloat("_Blur", blurValue);//模糊
            ri.color -= new Color(0, 0, 0, Time.deltaTime);
            if (ri.color.a <= 0)
            {
                ri.gameObject.SetActive(false);
            }
        }
    }

    /// 
    /// 相机截图
    /// 
    /// 截屏相机
    /// 截屏区域
    /// 
    public Texture2D CaptureCamera(Camera camera, Rect rect)
    {
        RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, 0);
        camera.targetTexture = rt;
        camera.Render();
        RenderTexture.active = rt;
        Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height);
        screenShot.ReadPixels(rect, 0, 0);
        screenShot.Apply();
        camera.targetTexture = null;
        RenderTexture.active = null;
        GameObject.Destroy(rt);
        return screenShot;
    }
    /// 
    /// 设置截屏UI所需材质的值
    /// 
    /// 
    public void SetMaterialValue(Texture texture)
    {
        ri.material.SetTexture("_MainTex", texture);
        ri.color = new Color(ri.color.r, ri.color.g, ri.color.b, 1);
        ri.gameObject.SetActive(true);
        blurValue = 0;
    }

    private void CaptrueCameraAndSetMaterialValue(object obj)
    {
        SetMaterialValue(CaptureCamera(Camera.main,new Rect(0,0,800,600)));
    }
}

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

bug Invoke不好用的地方

方法一 汇总方法名(不好用 )

是字符串,导致ctrl+单击,点不到那里
所以一个静态类来汇总(可能会有更好的方法)
在这里插入图片描述

/****************************************************

	文件:InvokeMethod.cs
	作者:WWS
	日期:2023/04/04 20:58:16
	功能:

*****************************************************/

public static class InvokeMethod
{
    public const string PlayIdleAniamtion = "PlayIdleAniamtion";
}

方法二 处理Action(目前只能处理void方法)

用法是

       this.Delay(PlayIdleAnimation, 0.1f);

达到的效果
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第19张图片

    /// 假设这里的脚本是B,A来调用B时,action是应该在A还是B
    public static void Delay(this MonoBehaviour monoBehaviour, System.Action action, float seconds)
    {

        //  string actionName = action.ToString() ;//这种直接给类型System.Action
        string actionName = action.Method.ToString() ;  //Void XXX()
        actionName = actionName.Substring(5) ; //XXX()
        actionName = actionName.Replace("()","") ; //XXX

        //Debug.LogFormat("actionName=={0}", actionName);
        try
        {
             monoBehaviour.Invoke( actionName, seconds);
        }
        catch (Exception)   
        {
            throw new System.Exception("Delay异常");
        }
      
    }

bug CSDN我这是发布后修改的

每次保存,直接退出编辑页面,很不好。应该不退出,每次都要点重新编辑进来

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

bug演示

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

看到UI下刮油脚本的Panel

转场,FightUI,ItemUI,SkillUI,一共四个
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第21张图片

去相应的脚本SetActive

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

转入的代码

重点是OpenOrCloseFightCommandPanelEvent

    public void EnterOrExitFightMode(bool enter)
    {
        if (enter)
        {
            this.SendEvent<CaptrueCameraAndSetMaterialValueEvent>();
            AudioSourceManager.Instance.PlayMusic("Fight/FightBG" + Random.Range(1, 4));
        }
        else
        {
            AudioSourceManager.Instance.PlayMusic("DongHaiWan");
        }
        NormalModeGo.SetActive(!enter);
        FightModeGo.SetActive(enter);
        SetEnterFightState(!enter);
        EnterFightTimer = Time.time;
        CanEnterFight = !enter;
        Vector3 pos = Camera.main.transform.position;
        pos.z = 0;
        FightModeGo.transform.position = pos;
        this.SendEvent<OpenOrCloseFightCommandPanelEvent>(enter);   
    }

类图

由 UISystem 的 FightUI 的 OpenOrCloseFightCommandPanelEvent转过来
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第22张图片

查它的引用,有FightSystem

modify CharacterFightAI代码太多,改用partial

原来的CharacterFightAI被拆分了
这几个都是 见名知义 的,就不写summary了
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第23张图片

bug ExtendResources

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

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

watch 这3块

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

watch FightSystem的目录

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

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

敌人、角色的生成

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

CreateCharactersCommand

using UnityEngine;
using System.Linq;
/// 
/// 创建人:Trigger 
/// 命令名称:在战斗开始后生成战斗敌人与玩家 
/// 参数:
/// 
public struct CreateCharactersCommand : ICommand
{
    public void Execute(object dataObj)
    {
        //玩家
        IFightSystem ifs = this.GetSystem<IFightSystem>();
        CharacterFightAI playerAI= InstantiateCharacter(ifs.CharacterPrefab,ifs.PlayerInitPosTrans);
        ifs.PlayerAI = playerAI;
        playerAI.isPlayer = true;
        ifs.CurrentActAIsList.Add(playerAI);
        InitCharacterData(playerAI);


        //敌人
        int num = Random.Range(1,10);
        for (int i = 0; i < num; i++)
        {
            CharacterFightAI enemyAI = InstantiateCharacter(ifs.CharacterPrefab, ifs.EnemyInitPosTrans[i]); 
            ifs.EnemyAIList.Add(enemyAI);
            ifs.CurrentActAIsList.Add(enemyAI);
            enemyAI.isPlayer = false;
            InitCharacterData(enemyAI);
        }
        ifs.CurrentActAIsList = ifs.CurrentActAIsList.OrderByDescending(p => p.actRate).ToList();
    }



    #region 辅助


    private void InitCharacterData(CharacterFightAI cfa)
    {
        IFightSystem ifs = this.GetSystem<IFightSystem>();
        if (cfa.isPlayer)
        {
            cfa.actRate = 8;
            cfa.SetDieMovePath(ifs.PlayerDieStartMovePath,ifs.PlayerDieEndMovePath);
            cfa.tag = "Player";
            cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(2);           
        }
        else
        {
            cfa.actRate = Random.Range(1,10);
            cfa.SetDieMovePath(ifs.EnemyDieStartMovePath, ifs.EnemyDieEndMovePath);
            cfa.tag = "Enemy";
            cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(Random.Range(1,6));
        }
        cfa.name = cfa.characterInfo.pathName;
    }


   /// 
   /// 实例角色
   /// 
   /// 
   /// 父节点
   /// 局部坐标
   /// 
    CharacterFightAI InstantiateCharacter(CharacterFightAI fightAI,Transform parent)
    {
        CharacterFightAI ai = GameObject.Instantiate(fightAI, parent);
        ai.transform.localPosition = Vector3.zero;

        return ai;
    }
    #endregion

}

FightLogicController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
//*****************************************
//创建人: Trigger 
//功能说明:游戏战斗逻辑控制
//***************************************** 

/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
    private bool hasInit;

    public void Init()
    {
        transform.Find("FightBG").gameObject.SetActive(true);
        hasInit = true;
        gameObject.SetActive(false);    
    }

    private void OnEnable()
    {
        if (hasInit)
        {
            this.SendCommnd<CreateCharactersCommand>();
            this.SendCommnd<ResetFightLogicStateCommand>();
        }
    }

GameStartInstance

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:游戏入口实例,初始化并管理所有的管理者,提供mono方法
//***************************************** 

/// 游戏入口实例,初始化并管理所有的管理者,提供mono方法
public class GameStartInstance : MonoBehaviour
{

    private List<ISingleton> singletonsList;
    //
    private StartArchitecture startArchitectureInstance;
    public bool startArchitecture;
    public FightLogicController fightLogicController; //拖拽赋值




    #region 单例
    private static GameStartInstance _instance;      

    public static GameStartInstance Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new GameStartInstance();
            }
            return _instance;
        }

        set
        {
            _instance = value;
        }
    }


    #endregion


    #region 生命


    private void Awake()
    {

        ExtendResources.Init();


        if (startArchitecture)
        {
            startArchitectureInstance = StartArchitecture.Instance;
            singletonsList = new List<ISingleton>()
            {
                AudioSourceManager.Instance.Init(),
            };
            startArchitectureInstance.SetGameArchitecture(new XYQArchitecture());
            //
            _instance = this;
            GetComponent<UIMgr>().Init();            
            fightLogicController.Init();
            //
            DontDestroyOnLoad(gameObject);
        }


    }

watch CharacterFightAI(FightSystem的Behaviour的挂载点)

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

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

Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第28张图片
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第29张图片

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 生命

    /// 需要StartArchitecture.Instance;
    public void Init()
    {
        cameraCapture = transform.FindChildDeep("RawImage_CaptureCamera").GetComponent<CameraCapture>()  ;
        fightUIManager=transform.FindChildDeep("Emp_FightUI").GetComponent<FightUIManager>();
        itemUIManager=transform.FindChildDeep("Emp_ItemUI").GetComponent<ItemUIManager>();
        skillUIManager=transform.FindChildDeep("Emp_SkillUI").GetComponent<SkillUIManager>();

        cameraCapture.Init();
        fightUIManager.Init();
        itemUIManager.Init();
        skillUIManager.Init();
    }


    #endregion 
}

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

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管理
//***************************************** 

/// 战斗UI管理
public class FightUIManager : MonoBehaviour,IController
{
    private Button defendCommandBtn;
    private Button skillCommandBtn;
    private Button useItemCommandBtn;
    private GameObject fightCommandPanelGo;

    public void Init()
    {
        gameObject.SetActive(true);
        fightCommandPanelGo =   transform.FindChildDeep( "Emp_FightCommand").gameObject;
        fightCommandPanelGo.SetActive(true);
        skillCommandBtn =       transform.FindChildDeep( "Btn_Skill").GetComponent<Button>();
        useItemCommandBtn =     transform.FindChildDeep( "Btn_UseItem").GetComponent<Button>();
        defendCommandBtn =      transform.FindChildDeep( "Btn_Defend").GetComponent<Button>();

        //
        skillCommandBtn.onClick.AddListener(ClickSkillBtn);
        useItemCommandBtn.onClick.AddListener(ClickUseItemBtn);
        defendCommandBtn.onClick.AddListener(ClickDefendBtn);

        //
        this.RegistEvent<OpenOrCloseFightCommandPanelEvent>(OpenOrCloseFightCommandPanel);
        fightCommandPanelGo.SetActive(false);
    }


    #region 辅助


    /// 
    /// 使用技能指令按钮事件
    /// 
    private void ClickSkillBtn()
    {
        this.SendEvent<OpenOrCloseSkillPanelEvent>(true);
    }


    /// 
    /// 使用物品指令按钮事件
    /// 
    public void ClickUseItemBtn()
    {
        this.SendCommnd<CloseAllFightUIPanelCommand>();
        this.SendEvent<OpenOrCloseFightBagPanelEvent>(true);
    }


    /// 
    /// 防御指令按钮事件
    /// 
    public void ClickDefendBtn()
    {
        this.SendCommnd<CloseAllFightUIPanelCommand>();
        this.SendCommnd<SetCharacterActCodeCommand>
        (
            new SetCharacterActCodeCommandParams()
            {
                actCode=ActCode.DEFEND
            }
        );
    }


    /// 
    /// 关闭或开启战斗指令面板
    /// 
    /// 
    private void OpenOrCloseFightCommandPanel(object obj)
    {
        fightCommandPanelGo.SetActive((bool)obj);
    }


    #endregion

}


stars FindButtonDeep

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

transform.FindButtonDeep( "Btn_SkillHengSaoQianJun");

SkillUIManager

/// 技能UI管理
public class SkillUIManager : MonoBehaviour,IController
{
    private Button[] skillBtns;

    public void Init()
    {
        gameObject.SetActive(true);
        skillBtns = new Button[4];
        skillBtns[0] = transform.FindButtonDeep( "Btn_SkillHengSaoQianJun");
        skillBtns[1] = transform.FindButtonDeep( "Btn_SkillJiJiWaiWai");
        skillBtns[2] = transform.FindButtonDeep( "Btn_SkillFanJianZhiJi");
        skillBtns[3] = transform.FindButtonDeep( "Btn_SkillHuaYu");
        ......

ExtendComponent(各种Component的拓展方法)

    /// 
    /// 深度查找子对象transform引用
    /// 
    /// 父对象
    /// 具体查找的子对象名称
    /// 
    public static Button FindButtonDeep(this Transform root, string childName)
    {
        Transform result = null;
        result = root.Find(childName);
        if (!result)
        {
            foreach (Transform item in root)
            {
                result = FindChildDeep(item, childName);
                if (result != null)
                {
                    return result.GetComponent<Button>();
                }
            }
        }
        return result.GetComponent<Button>();
    }

    /// 
    /// 深度查找子对象transform引用
    /// 
    /// 父对象
    /// 具体查找的子对象名称
    /// 
    public static Transform FindChildDeep(this Transform root, string childName)
    {
        Transform result = null;
        result = root.Find(childName);
        if (!result)
        {
            foreach (Transform item in root)
            {
                result = FindChildDeep(item, childName);
                if (result != null)
                {
                    return result;
                }
            }
        }
        return result;
    }

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 
//功能说明:人物鼠标检测
//***************************************** 
/// 人物鼠标检测(Detection侦查察觉检测)
public class CharacterMouseDetection : CharacterFightAIBehaviour
{  
    private Color initColor; 

    protected override void Awake()
    {
        base.Awake();
        initColor = spriteRenderer.color;
    }


    #region 系统


    private void OnMouseDown()
    {
        if (IfCanClick())
        {
            this.SendCommnd<SetTargetAICommand>(fromAI); //当前点击的对象fromAI是Player的目标
            this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.NORMAL);//鼠标样式
            //
            if (this.GetSystem<ISkillSystem>().UsingSkill)
            {
                Attack( SkillAttackPara() );
            }
            else
            {
                Attack( NormalAttackPara() );
            }
        }
    }


    private void OnMouseOver()
    {
        Color color = new Color
        (
            initColor.r * Mathf.Pow(2, 1),//n次幂 
            initColor.g * Mathf.Pow(2, 1), 
            initColor.b * Mathf.Pow(2, 1)
        );

        spriteRenderer.SetColor( color);
        if (IfCanClick())
        {
            if (this.GetSystem<ISkillSystem>().UsingSkill)
            {
                this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.SKILL);
            }
            else
            {
                this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.ATTACK);
            }
        }
        else
        {
            if (!EventSystem.current.IsPointerOverGameObject())
            {
                this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.FORBID);
            }
        }
    }


    private void OnMouseExit()
    {
        spriteRenderer.SetColor(initColor);
        this.SendEvent<SetCurrentCursorStateEvent>(CursorIconState.NORMAL);
    }
    #endregion




    #region 辅助




    /// 
    /// 当前人物是否可以点击
    /// 
    /// 
    public bool IfCanClick()
    {
        bool isRightTarget = false;
        ISkillSystem iks = this.GetSystem<ISkillSystem>();
        if (iks.CurrentSkillID == 5)
        {
            isRightTarget = gameObject.CompareTag("Player");
        }
        else
        {
            isRightTarget = gameObject.CompareTag("Enemy");

        }
        return isRightTarget 
            && !fightSystem.IsPerformingLogic 
            && !EventSystem.current.IsPointerOverGameObject();
    }




    void Attack(SetCharacterActCodeCommandParams para)
    {
        this.SendCommnd<SetCharacterActCodeCommand>( para );
        this.SendCommnd<SetPlayerTargetAICommand>(fromAI);
        fightSystem.PlayerAI.toAI = null;
    }

    SetCharacterActCodeCommandParams NormalAttackPara()
    {
        Vector3 tarPos = fightSystem.PlayerAI.GetCurrentAITargetPos();

        return new SetCharacterActCodeCommandParams()
        {
            actCode = ActCode.ATTACK,
            actObj = new ActObj()
            {
                attackPos = tarPos
            }
        };
    }

    SetCharacterActCodeCommandParams SkillAttackPara()
    {          
        int skillID = this.GetSystem<ISkillSystem>().CurrentSkillID;
        Vector3 tarPos = fightSystem.PlayerAI.GetCurrentAITargetPos();
        return new SetCharacterActCodeCommandParams()
        {
            actCode = ActCode.SKILL,
            actObj = new ActObj()
            {
                attackPos = tarPos,
                skillInfo = this.GetModel<ISkillDataModel>().GetSkillInfo(skillID)
            }
        };
    }

    #endregion  



}

SetCharacterActCodeCommandParams

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

SetPlayerTargetAICommand

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

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

AttackFightBehaviour

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

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

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

watch 人物混乱

从 CharacterFightAI 拆分出来的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*****************************************
//创建人: Trigger 
//功能说明:人物战斗AI
//***************************************** 

/// 人物战斗AI
public partial class CharacterFightAI : MonoBehaviour,IController
{


    #region 生命


    private void OnDestroy()
    {
        if (chaosTween != null)
        {
            chaosTween.Pause();
            chaosTween.Kill();
        }
    }
    #endregion  


    #region 混乱


    /// 
    /// 混乱移动
    /// 
    public void DoChaosMoveTween()
    {
        if (chaosTween != null)
        {
            return;
        }
        DoChaosMove();
    }



    private void DoChaosMove()
    {
        chaosTween = animatorTrans
            .DoMove(GetChaosMoveTarget(), 0.05f) //左摇
            .SetOnComplete
            (
                () =>
                {
                    chaosTween = animatorTrans
                    .DoMove(GetRendererInitPos(), 0.05f) //右晃
                    .SetOnComplete(DoChaosMove);
                }
            );
    }


    private Vector3 GetChaosMoveTarget()
    {
        Vector3 startPos = GetRendererInitPos();
        Vector2 randomPos = Random.insideUnitCircle * 0.1f;
        return startPos + new Vector3(randomPos.x, randomPos.y, startPos.z);
    }


    private Vector3 GetRendererInitPos()
    {
        return transform.TransformPoint( new Vector3(0, 0.3f, 0) );
    }
    #endregion
}

watch 人物回复、扣血

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:人物战斗状态数据显示
//***************************************** 

/// 人物战斗状态数据显示
public class CharacterFightValueState : CharacterFightAIBehaviour
{


    private CharacterCanvas characterCanvas;



    #region 生命


    void Start()
    {
        characterCanvas = GetComponentInChildren<CharacterCanvas>();
        if (fromAI.isPlayer)
        {
            fromAI.HP = PlayerData().CurrentMaxHP.Value;
            fromAI.currentHP = PlayerData().CurrentHP.Value;
            fromAI.MP = PlayerData().MaxMP.Value;
            fromAI.currentMP = PlayerData().CurrentMP.Value;
        }
        else
        {
            characterCanvas.HideSlider();
        }

        string characterName = this.GetModel<ICharacterDataModel>().GetCharacterInfo(fromAI.characterInfo.ID).name;
        characterCanvas.SetCharacterName( characterName );
        fromAI.currentHP = fromAI.HP;
    }
    #endregion




    #region 辅助


    /// 
    /// 显示跟血量变化相关的内容
    /// 
    public void ShowHPValueChange(int changeValue)
    {
        NumCanvas nc = Instantiate(
            ExtendResources.Get<GameObject>("Prefabs/CharacterNumCanvas"),
            spriteRenderer.Position(),
            Quaternion.identity
        ).GetComponent<NumCanvas>();
        nc.ShowNum(changeValue);
        //
        fromAI.currentHP += changeValue;
        if (fromAI.currentHP >= fromAI.HP)
        {
            fromAI.currentHP = fromAI.HP;
        }
        
        if (fromAI.isPlayer)
        {
            characterCanvas.SetHPSliderValue((float)fromAI.currentHP / fromAI.HP);
            this.SendCommnd<ChangePlayerHPCommand>(changeValue);
        }
    }

    /// 
    /// 显示跟蓝耗变化相关的内容
    /// 
    public void ShowMPValueChange(int changeValue)
    {
        fromAI.currentMP += changeValue;
        if (fromAI.currentMP >= fromAI.MP)
        {
            fromAI.currentMP = fromAI.MP;
        }
        if (fromAI.isPlayer)
        {
            this.SendCommnd<ChangePlayerMPCommand>(changeValue);
        }
    }

    IPlayerDataModel PlayerData()
    {
        return this.GetModel<IPlayerDataModel>();
    }
    #endregion


}

watch 人物闪避、防御

CharacterFightAI 中拆分的

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
//*****************************************
//创建人: Trigger 
//功能说明:人物战斗AI
//***************************************** 

/// 人物战斗AI
public partial class CharacterFightAI : MonoBehaviour,IController
{


    #region 闪避与防御
    /// 
    /// 闪避行为
    /// 
    public void DodgeBehaviour()
    {
        defendAndDodgeFightBehaviour.DodgeBehaviour();
    }


    /// 
    /// 防御行为
    /// 
    public void DefendBehaviour()
    {
        defendAndDodgeFightBehaviour.DefendBehaviour();
    }


    /// 
    /// 移动到防御位置并返回
    /// 
    /// 动画时间
    /// 需要在动画完成后进行的额外回调
    public void ToDenfendPos(float animationTime, UnityAction callBack = null)
    {
        defendAndDodgeFightBehaviour.ToDenfendPos(animationTime, callBack);
    }
    #endregion
}
using UnityEngine;
/// 
/// 创建人:Trigger 
/// 命令名称:人物死亡命令
/// 参数:
/// 
public struct CharacterDieCommand : ICommand
{
    public void Execute(object dataObj)
    {
        IFightSystem fightSystem = this.GetSystem<IFightSystem>();
        AudioSourceManager.Instance.PlayCharacterSound("FlyAway");
        Time.timeScale = 1;
        fightSystem.DieCount++;
        fightSystem.EnterCurrentRound = false;
    }
}

watch 人物受击、死亡

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:人物受击与死亡行为
//***************************************** 

/// 人物受击与死亡行为
public class HitAndDieFightBehaviour : CharacterFightAIBehaviour
{


    ///  受击行为 
    public void HitBehaviour(bool canDodge = true, bool canDefend = true)
    {
        meshAgent.isStopped = true;
        
        if (Random.Range(0, 3) >= 2 && canDodge)//闪避
        {
            fromAI.DodgeBehaviour();//闪避成功
            return;
        }
        
        if (fromAI.actCode == ActCode.DEFEND && canDefend)//防御
        {
            fromAI.DefendBehaviour();
            return;
        }
        //受击
        int random = Random.Range(60,120);
        fromAI.ShowHPValueChange(-random);
        fromAI.SetCurrentLookDir();
        cac.PlayHitAnimation();
        this.SendCommnd<CreateHitEffectCommand>();
        JudgeIfDie();
    }


    ///  判断是否死亡 
    public void JudgeIfDie()
    {
        if (fromAI.currentHP <= 0)
        {
            //死亡
            cac.PlayDieAnimation();
            Time.timeScale = 0.3f;
            transform.DoMove(fromAI.GetTargetPosTrans(fromAI.currentLookDir).position, 0.2f)
               .SetOnComplete(
                () =>
                {
                    meshAgent.isStopped = false;
                    meshAgent.speed = 15;
                }
            );
        }
        else
        {       
            fromAI.ToDenfendPos(0.2f); //受击
        }
    }


    ///  死亡行为 
    public void DieBehaviour()
    {
        this.SendCommnd<CharacterDieCommand>();
        fromAI.characterState = CharacterState.DEAD;
        fromAI.SetMovingState(false);        
        //主要目的是为了放横扫一类的技能没有在原始位置,要回去
        fromAI.ResetState();       
    }
}

结束当前回合

watch 链接起来FightSystem的所有Command

watch 敌人动作

CharacterFightAI
设置了ActCode、 ActObj,传给 CharacterFightAI

using UnityEngine;
/// 
/// 创建人:Trigger 
/// 命令名称:随机敌人AI的行动命令
/// 参数:
/// 
public struct RandomEnemyActCommand : ICommand
{
    public void Execute(object obj)
    {
        IFightSystem sys = this.GetSystem<IFightSystem>();
        CharacterFightAI fromAI = sys.CurrentAI;
        fromAI.toAI = sys.PlayerAI;
        Vector3 tarPos= fromAI.GetCurrentAITargetPos(); 
        //
        ActCode ac = (ActCode)Random.Range(0,4);
        ActObj ao = new ActObj();
        //
        switch (ac)
        {
            case ActCode.ATTACK:
                ao.attackPos = tarPos;
                break;
            case ActCode.DEFEND:
                break;
            case ActCode.SKILL:
                { 
                    int skillID = Random.Range(1, 6);
                    switch (skillID)
                    {
                        case 1:
                            ao.attackPos = tarPos;
                            break;
                        case 2:
                        case 4:
                            skillID = 3;
                            break;
                        case 5:
                            this.SendCommnd<GetRandomCharacterCommand>();
                            break;
                        default: break;
                    }               
                    ao.skillInfo = this.GetModel<ISkillDataModel>().GetSkillInfo(skillID);                
                }
                break;
            case ActCode.USEITEM:
                ao.itemID = 0;
                break;
            default: break;
        }
        fromAI.SetActCodeAndObjValue(ac, ao);
    }
}

bug 修改后角色攻击后不返回(没学过)

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

01 bug NumCanvas

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

02 watch 尝试找移动的代码

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

03 watch 不报错也这样

04 watch DefendAndDodgeFightBehaviour.ToDenfendPos()

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

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

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

01 查脚本AttackStateBehaviour引用

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

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

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

发现它是加载在动画器上的状态上
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第37张图片

04 我的UnityEngine.StateMachineBehaviour与Animator

可以看到报错了
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第38张图片

modify 拆分CharacterAnimatorController

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

找到ActCode.ATTACK;

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

CharacterAnimatorController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:控制人物动画切换
//***************************************** 

/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{
    private Animator animator;
    public bool isMoving;
    private CharacterFightAI fightAI;



    private void Awake()
    {
        animator = GetComponentInChildren<Animator>();
        fightAI = GetComponentInParent<CharacterFightAI>();        
    }


    private void Start()
    {
        if (fightAI)
        {
            string path = "Character/" + fightAI.characterInfo.pathName + "/Fight/FightController";
            animator.runtimeAnimatorController = ExtendResources.Get<RuntimeAnimatorController>(path);
        } 
    }
}

CharacterAnimatorController.AnimationEvent.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:控制人物动画切换
//***************************************** 

/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{

    /// 挂在Timeline时间轴上的帧的时间
    private void AttackAnimationEvent()
    {
        fightAI.AttackTarget();
    }


    /// 挂在Timeline时间轴上的帧的时间
    private void UseSkillOrItemAnimationEvent()
    {
        fightAI.UseSkillOrItemAction();
    }
}

CharacterAnimatorController.PlayAnimation

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//*****************************************
//创建人: Trigger 
//功能说明:控制人物动画切换
//***************************************** 

/// 控制人物动画切换
public partial class CharacterAnimatorController : MonoBehaviour
{
   /// 
   /// 播放位移动画
   /// 
   /// 当前位置
   /// 导航点
   /// 最终目标点
    public void PlayLocomotionAnimation(Vector3 curPos,Vector3 navPos,Vector3 tarPos)
    {
        Vector3 lookDir = navPos-curPos;
        if (lookDir.magnitude<=0.0001f)
        {
            lookDir = tarPos - curPos;
        }


        if (Vector3.Distance(curPos,tarPos)>0.3f)
        {
            animator.SetFloat("LookX", lookDir.normalized.x);
            animator.SetFloat("LookY", lookDir.normalized.y);
            animator.SetBool("MoveState",true);
            isMoving = true;
        }
        else if(Vector3.Distance(curPos, tarPos) < 0.15f)
        {
            animator.SetBool("MoveState", false);
            isMoving = false;
        }
    }



    public void PlayHitAnimation()
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetTrigger("Hit");
    }

    public void PlayDieAnimation()
    {
        animator.SetBool("Die",true);
    }

    public void PlayMoveAnimation(float moveValue)
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetInteger("MoveValue",1);
    }

    public void PlayIdleAnimation()
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetInteger("MoveValue", 0);
    }

    public void PlayAttackAnimation()
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetTrigger("Attack");
    }

    public void PlaySkillAnimation()
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetTrigger("Skill");
    }

    public void PlayDefendAnimation()
    {
        SetLookDir(fightAI.currentLookDir);
        animator.SetTrigger("Defend");
    }

    public void SetLookDir(Vector2 lookDir)
    {
        animator.SetFloat("LookDirX", lookDir.x);
        animator.SetFloat("LookDirY", lookDir.y);
    }
}

bug CharacterFightAI报空

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

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

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

stars GetOrAddComponent

    /// 
    /// 获取或增加组件。
    /// 
    /// 要获取或增加的组件。
    /// 目标对象。
    /// 获取或增加的组件。
    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数据为空

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

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

04 characterInfo在CharacterDataModel 中初始化

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

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

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

/// 人物战斗状态数据显示
public class CharacterFightValueState : CharacterFightAIBehaviour
{
  public override  void Init()
    {
        base.Init();
        characterCanvas = GetComponentInChildren<CharacterCanvas>();
        if (fromAI.isPlayer)
        {
            fromAI.HP = PlayerData().CurrentMaxHP.Value;
            fromAI.currentHP = PlayerData().CurrentHP.Value;
            fromAI.MP = PlayerData().MaxMP.Value;
            fromAI.currentMP = PlayerData().CurrentMP.Value;
        }
        else
        {
            characterCanvas.HideSlider();
        }
        fromAI.currentHP = fromAI.HP;
        //
        string characterName = GetCharacterInfo(fromAI.characterInfo).name;
        characterCanvas.SetCharacterName( characterName );

    }
    #endregion

    #region 辅助

    /// 
    /// 方便找bug,不然报错在接口,不清晰
    /// 
    CharacterInfo GetCharacterInfo(CharacterInfo characterInfo )
    {
        if (characterInfo.ID >= 0)
        {
            throw new System.Exception("ID异常:"+characterInfo.ID);
        }
        return this.GetModel<ICharacterDataModel>().GetCharacterInfo(characterInfo.ID);
    }

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;

/// 
/// 命令名称:在战斗开始后生成战斗敌人与玩家
/// 

public struct CreateCharactersCommand : ICommand
{
    public void Execute(object dataObj)
    {
        //玩家
        IFightSystem ifs = this.GetSystem<IFightSystem>();
        ifs.PlayerAI = GameObject.Instantiate(ifs.CharacterPrefab, ifs.PlayerInitPosTrans);
        ifs.PlayerAI.transform.localPosition = Vector3.zero;
        ifs.PlayerAI.isPlayer = true;
        ifs.CurrentActAIsList.Add(ifs.PlayerAI);
        InitCharacterData(ifs.PlayerAI);
        //敌人
        int num = Random.Range(1, 10);
        for (int i = 0; i < num; i++)
        {
            ifs.EnemyAIList.Add(GameObject.Instantiate(ifs.CharacterPrefab, ifs.EnemyInitPosTrans[i]));
            ifs.EnemyAIList[i].transform.localPosition = Vector3.zero;
            ifs.CurrentActAIsList.Add(ifs.EnemyAIList[i]);
            ifs.EnemyAIList[i].isPlayer = false;
            InitCharacterData(ifs.EnemyAIList[i]);
        }
        ifs.CurrentActAIsList = ifs.CurrentActAIsList.OrderByDescending(p => p.actRate).ToList();
    }

    private void InitCharacterData(CharacterFightAI cfa)
    {
        IFightSystem ifs = this.GetSystem<IFightSystem>();
        if (cfa.isPlayer)
        {
            cfa.actRate = 8;
            cfa.SetDieMovePath(ifs.PlayerDieStartMovePath, ifs.PlayerDieEndMovePath);
            cfa.tag = Tags.PLAYER;
            cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(2);
        }
        else
        {
            cfa.actRate = Random.Range(1, 10);
            cfa.SetDieMovePath(ifs.EnemyDieStartMovePath, ifs.EnemyDieEndMovePath);
            cfa.tag = Tags.ENEMY;
            cfa.characterInfo = this.GetModel<ICharacterDataModel>().GetCharacterInfo(Random.Range(1, 6));
        }
        cfa.name = cfa.characterInfo.pathName;
        cfa.Init();
    }
}

bug 位置不对

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

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

02 逐步打点

打点发现两个Command后localPosition都是Vector.Zero
之后走的时候CinemachineVirtualCamera的ConnectCM,所以很可能是相机的问题

02 两个Command

public class FightLogicController : MonoBehaviour, IController
{
	......

    private void OnEnable()
    {
        if (hasInit)
        {
            this.SendCommnd<CreateCharactersCommand>();
            this.SendCommnd<ResetFightLogicStateCommand>();
        }
    }

02 CinemachineVirtualCamera

namespace NavMeshComponents.Extensions
{
    public abstract class NavMeshExtension: MonoBehaviour
    {
	......

        protected virtual void Awake()
        {
            ConnectToVcam(true);
        }

02 UnityEngine.UI.CanvasScaler.Canvas_preWillRenderCanvases()

这个跳回Test.Update()时,t.localPosition已经不为Vector3.zero

            Transform t = this.GetSystem<IFightSystem>().PlayerAI.transform;
            print("localPos" + t.localPosition);

03 球的实际位置跟球看上去的位置不一样

该节点FightNavMesh全部取消static(与原来的做对比)
可以看到,双击绿球节点(玩家父节点),Scene中跳的位置是不同的
Unity2D 商业游戏案例 - 梦幻西游(第二季 框架设计篇)_第42张图片

04 发现玩家初始位置是绿球最原来的位置

也就是initPos的赋值没有在球的位置变化后进行
原版是。initPos的赋值在FightNavMesh的激活OnEnabled中进行.

造成的原因是我不想用Start()

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
//*****************************************
//创建人: Trigger 
//功能说明:游戏战斗逻辑控制
//***************************************** 

/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
    /// 
    /// FightNavMesh是否已经激活(一般位置锁着玩家移动有所变化),
    /// 以便后面角色创建initPos
    /// 
    private bool hasInit = false;
    /// 因为初始不激活,其它脚本找不到FightNavMesh
    public GameObject FightNavMesh { get { return gameObject; } }



    private void OnEnable()
    {
        //TODO FindTop解决
        //this.SendCommnd(this.FightNavMesh);
        hasInit = true;
    }


    void Update()
    {

        if (hasInit)
        {
            this.SendCommnd<CreateCharactersCommand>();
            this.SendCommnd<ResetFightLogicStateCommand>();
            hasInit = false;
        }

        if (Input.GetMouseButtonDown(1))//鼠标右键
        {
            this.SendEvent<CancelUsingSkillEvent>();
        }

        this.SendCommnd<UpdateFightLogicCommand>();
    }
}

watch VS的调用堆栈——看当前脚本内的调用顺序;控制某个脚本的所有断点

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

stars 找子节点数组,GetChildren,GetChildrenDeep

    /// 
    /// 找到 自身 下的 所有子节点  children
    /// 
    /// 
    /// 
    /// 
    public static Transform[] GetChildren(this Transform parent, out Transform[] children)
    {

        children = new Transform[parent.childCount];
        for (int i = 0; i < children.Length; i++)
        {
            children[i] = parent.GetChild(i);
        }

        return children;
    }


    /// 
    ///  找到 自身 的 叫 parentName的子节点 下的所有子节点 children 
    /// 
    /// 
    /// 
    /// 
    /// 
    public static Transform[] GetChildrenDeep(this Transform t, string parentName,  out Transform[] children)
    {
        Transform parent = t.FindChildDeep(parentName);
        children = new Transform[parent.childCount];
        for (int i = 0; i < children.Length; i++)
        {
            children[i] = parent.GetChild(i);
        }

        return children;
    }

starts modify AnimatorPara.cs

/****************************************************

	文件:
	作者:WWS
	日期:2023/04/10 20:05:40
	功能:AnimatorPara动画器的参数汇总

*****************************************************/

using UnityEngine;

public static class AnimatorPara
{
    public const string LookX = "LookX";
    public const string LookY = "LookY";
    public const string LookDirX = "LookDirX";
    public const string LookDirY = "LookDirY";
    public const string MoveState = "MoveState";
    public const string Hit = "Hit";
    public const string Die = "Die";
    public const string MoveValue = "MoveValue";
    public const string Attack = "Attack";
    public const string Skill = "Skill";
    public const string Defend = "Defend";
}

bug 重启

发生过敌人位置不对,敌人的技能接连很快释放。但是重启unity后就好了。

bug 不明没赋值的提示

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

modify

FightNavMesh 初始不激活才不会挡Scene,美观点,所以不能通过GameObject.Find来找FightNavMesh 。
所以让FightNavMesh 去找需要它的脚本。
或者用下一知识点的的FindTop

最后还是直接用FindTop

//挂在 未激活的命名为FightNavMesh的节点

/// 游戏战斗逻辑控制
public class FightLogicController : MonoBehaviour, IController
{
	......
    public GameObject FightNavMesh { get { return gameObject; } }

    public void Init()
    {
        this.SendCommnd<SetFightNavMeshGoCommand>(this.FightNavMesh);
/****************************************************
    文件:GetFightNavMeshGo.cs
	作者:lenovo
    邮箱: 
    日期:2023/5/19 5:36:53
	功能:获得这个初始不激活的根节点FightNavMesh
*****************************************************/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
 

public class SetFightNavMeshGoCommand : ICommand
{
    public void Execute(object obj)
    {
        GameObject go = obj as GameObject;
        this.GetSystem<ISceneSystem>().FightModeGo = go;
    }
}


stars FindTop

两种写法,
用gameObject.FindTop 就返回 GameObject
用transform .FindTop 就返回 Transform

	/// 
    /// 场景中根节点
    /// 未激活也可以找到
    /// 
	public static GameObject FindTop(this GameObject curGo, string tarName)
	{
		GameObject[] gos = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
		foreach (GameObject go in gos)
		{
			if (go.name == tarName)
			{ 
				return go;
			}
		}
		return null;
	}

    /// 
    /// 场景中根节点
    /// 不激活也可以找到
    public static Transform FindTop(this Transform t, string tarName)
    {
        GameObject[] gos = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
        foreach (GameObject go in gos)
        {
            if (go.name == tarName)
            {
                return go.transform;
            }
        }
        return null;
    }

-------------------------------------------

watch 最后跑一遍证明当前阶段是跑通的


遇敌
平A
反间(用完敌人抖动)
使用物品

你可能感兴趣的:(Unity,C#,Siki,游戏,ui,unity)