用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

07-08 1520阅读

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

GitHub

//

官网的

全民飞机大战(第一季)-----框架设计篇(Unity 2017.3)

全民飞机大战(第二季)-----游戏逻辑篇(Unity 2017.3)

全民飞机大战(第三季)-----完善功能篇(Unity 2017.3)

全民飞机大战(第四季)-----新手引导篇

//

B站各放几集

全民飞机大战(第一季)-----框架设计篇(Unity 2017.3)

全民飞机大战(第二季)-----游戏逻辑篇(Unity 2017.3)

全民飞机大战(第三季)-----完善功能篇(Unity 2017.3)

全民飞机大战(第四季)-----新手引导篇

bug QF处理单例类中的使用this.Getxxx的

Single.GetModel不行,会一直跳空,使用System

using QFramework;
using QFramework.AirCombat;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AniMgr : NormalSingleton
{
	public void PlaneDestroyAni(Vector3 pos)
	{
		
		var go = PoolMgr.Single.Spawn(ResourcesPath.EFFECT_FRAME_ANI);
		var view = go.GetOrAddComponent();
		view.Init();
		view.SetScale(Vector3.one*0.5f);
		view.SetPos(pos);
	}
	
	public void BulletDestroyAni(Vector3 pos)
	{
		var go = PoolMgr.Single.Spawn(ResourcesPath.EFFECT_FRAME_ANI);
		var view = go.GetOrAddComponent();
		view.Init();
		view.SetScale(Vector3.one*0.1f);
		view.SetPos(pos);
	}
}
public interface IAniSystem : ISystem { }
public class AniSystem : AbstractSystem, IAniSystem
{
    protected override void OnInit()
    {
        this.RegisterEvent(OnPlaneDestroyAni);
        this.RegisterEvent(OnBulletDestroyAni);
    }
    public void OnPlaneDestroyAni(PlaneDestroyAniEvent e)
    {
        Vector3 pos = e.pos;
        var go = this.GetSystem().Spawn(ResourcesPath.EFFECT_FRAME_ANI);
        var view = go.GetOrAddComponent();
        view.Init();
        view.SetScale(Vector3.one * 0.5f);
        view.SetPos(pos);
    }
    public void OnBulletDestroyAni(BulletDestroyAniEvent e)
    {
        Vector3 pos = e.pos;
        var go = this.GetSystem().Spawn(ResourcesPath.EFFECT_FRAME_ANI);
        var view = go.GetOrAddComponent();
        view.Init();
        view.SetScale(Vector3.one * 0.1f);
        view.SetPos(pos);
    }
}

modify MsgEvent时间的注册发送`在这里插入代码片

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

modify typeof中的参数,怎么作为一个方法的参数来传入

目前做不到注释中的做法

    public static void RepeatConstException(this Type type) 
    {
        //var type = typeof(className);//不知道className怎么做参数
        var hs = new HashSet();
        var fis = type.GetFields();
        foreach (var fi in fis)
        {
            var value = fi.GetRawConstantValue();
            if (value is int)
            {
                if (!hs.Add((int)value))
                {
                    Debug.LogError($"{type.Name}中有重复项,重复值为:{value}");
                }
            }
            else
            {
                Debug.LogError($"属性:{fi.Name}.类型错误,此类所有常量必须是int类型");
            }
        }
    }

效果

using System.Collections;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
/// 检测MsgEvent是否有重复项
public class MsgEventTest : ITest
{
    public IEnumerator Execute()
    {
        (typeof(MsgEvent)).RepeatConstException();
        yield return null;
    }
}

bug QF this.GetSystem为空

注释的那句是不行的,所以在接口中要写抽象方法

        ISceneSystem sys = this.GetSystem();
        //SceneSystem sys = this.GetSystem();
        sys.AddSceneLoaded(SceneName.Game, callBack =>

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

Audio

先替换掉AudioMgr

可以省略掉缓存字典、列表,不用自己做

官方案例

三种喇叭

Music

Sound

Voice

using UnityEngine;
using UnityEngine.UI;
namespace QFramework.Example
{
    public class AudioExample : MonoBehaviour
    {
        private void Awake()
        {
            var btnPlayHome         = transform.Find("BtnPlayHome").GetComponent();
            var btnPlayGame         = transform.Find("BtnPlayGame").GetComponent();
            var btnPlaySound        = transform.Find("BtnPlaySoundClick").GetComponent();
            var btnPlayVoiceA       = transform.Find("BtnPlayVoice").GetComponent();
            var btnSoundOn          = transform.Find("BtnSoundOn").GetComponent();
            var btnSoundOff         = transform.Find("BtnSoundOff").GetComponent();
            var btnMusicOn          = transform.Find("BtnMusicOn").GetComponent();
            var btnMusicOff         = transform.Find("BtnMusicOff").GetComponent();
            var btnVoiceOn          = transform.Find("BtnVoiceOn").GetComponent();
            var btnVoiceOff         = transform.Find("BtnVoiceOff").GetComponent();
            var btnStopAllSound     = transform.Find("BtnStopAllSounds").GetComponent();
            
            var musicVolumeSlider   = transform.Find("MusicVolume").GetComponent();
            var voiceVolumeSlider   = transform.Find("VoiceVolume").GetComponent();
            var soundVolumeSlider   = transform.Find("SoundVolume").GetComponent();
            btnPlayHome.onClick.AddListener(() => { AudioKit.PlayMusic("resources://home_bg"); });
            btnPlayGame.onClick.AddListener(() => { AudioKit.PlayMusic("resources://game_bg"); });
            btnPlaySound.onClick.AddListener(() => { AudioKit.PlaySound("resources://button_clicked"); });
            btnPlayVoiceA.onClick.AddListener(() => { AudioKit.PlayVoice("resources://hero_hurt"); });
            btnSoundOn.onClick.AddListener(() => { AudioKit.Settings.IsSoundOn.Value = true; });
            btnSoundOff.onClick.AddListener(() => { AudioKit.Settings.IsSoundOn.Value = false; });
            btnMusicOn.onClick.AddListener(() => { AudioKit.Settings.IsMusicOn.Value = true; });
            btnMusicOff.onClick.AddListener(() => { AudioKit.Settings.IsMusicOn.Value = false; });
            btnVoiceOn.onClick.AddListener(() => { AudioKit.Settings.IsVoiceOn.Value = true; });
            btnVoiceOff.onClick.AddListener(() => { AudioKit.Settings.IsVoiceOn.Value = false; });
            
            btnStopAllSound.onClick.AddListener(() =>
            {
                AudioKit.StopAllSound();
            });
            AudioKit.Settings.MusicVolume.RegisterWithInitValue(v => musicVolumeSlider.value = v);
            AudioKit.Settings.VoiceVolume.RegisterWithInitValue(v => voiceVolumeSlider.value = v);
            AudioKit.Settings.SoundVolume.RegisterWithInitValue(v => soundVolumeSlider.value = v);
            
            musicVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.MusicVolume.Value = v; });
            voiceVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.VoiceVolume.Value = v; });
            soundVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.SoundVolume.Value = v; });
        }
    }
}

播放Music

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

    public void PlayBGM()
    {
        AudioKit.PlayMusic( $"resources://Audio/BGM/{BGAudio.Game_BGM.Enum2String()}"  );
    }

播放Sound

角色出场语音的切换

    public void PlayPlayerVoice(string name, bool loop = false)
    {
        _curPlayer = name; 
        AudioKit.PlayVoice($"resources://Audio/Player/{name}");   // 这个会保持一个喇叭
        //AudioKit.PlaySound($"resources://Audio/Player/{name}"); // 这个不会保持一个喇叭
    }

bug QF报错

找不到clip会报错,但是这里存在 GameAudio.Null,表示不用播放。

所以做一个return

    public void PlaySound(string name, bool loop = false)
    {
        if(name==GameAudio.Null)
        {
            return;
        }
        AudioKit.PlaySound($"{_path}{name}", loop);    
    }

GameAudio枚举改成类

bug Attribute与Summary冲突

可以看到Summary不显示

图2,1放在2前面,可以看到3有显示

。。。

这种需要把summary放在最前面

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug GameLayer生成的节点暴露在根节点下

正常在如下位置。

原来的,把GameLayerMgr跪在GameRoot下

现在把GameLayerMgr挂在Mgr下,GameLayer的三个枚举还是挂在原 来的GameRoot下

用GameObject.Find而不是transform.FindTop

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

watch 改的顺序

LoadCreaterData

。。。

LifeCycleMgr

LifeCycleAddConfig

LifeCycleConfig

。。。

LoadMgr

ILoader

ResourcesLoader

ABLoader

CoroutineMgr

LifeCycleAddConfig

UILayerMgr

UIManager

watch 引导

GuideMgrBase

watch 敌人生成

IEnemyCreator

LevelData

EnemyLevelData

EnemyCreateMgr

GameProcessMgr.Init()

GameProcessMgr.UpdateFunc()

GameProcessMgr中的_start,会监听一个开始游戏的Event,该event来自于StateMdoel

所以生成敌人,需要改变StateMdoel中GameState的值

Message消息

MessageMgr,SubMsgMgr

MessageSystem,IMessageSystem

ActionMgr

modify KeysUtil => KeysUtil:IKeysUtil

改成

IKeysUtil:IUtil

KeysUtil:IKeysUtil,ICanGetModel

modify DataMgr(存储) => StorageUitl

IDataMemory改成IStorage ,可以看到飞机大战的接口设计得更泛用,所以用飞机大战的接口设计

01 只用PlayerPrefs

对应的PlayerPrefsMemory:IDataMemory改名为PlayerPrefsStorage : IStorage(完全照抄PlayerPrefsMemory,就不展示了)

。。。

对应的JsonMemory:IDataMemory,是空的,就不加了 => 不是空的,因为用了拓展DataMgr的方式来操作

    public interface IStorage : IUtility
    {
        //老版的定义
        //void SaveInt(string key, int value);
        //int LoadInt(string key, int defaultValue = 0);
        //void SaveString(string key, string value);
        //string LoadString(string key, string defaultValue = "");
        //void SaveFloat(string key, float value);
        //float LoadFloat(string key, float defaultValue = 0.0f);
        T Get(string key);
        void Set(string key, T value);
        object Getobject(string key);
        void Setobject(string key, object value);
        void Clear(string key);
        void ClearAll();
        bool ContainsKey(string key);
    }

02 接口方法多样,PlayerPrefs+Json

后面发现PlayerPrefs+Json两种方式、StorageUtil。这三者对IStorage 中的方法是杂着来的。

比如这四个其实是PlayerPrefs+Json共用的,适合放在StorageUtil(后面改成StorageSystem)中

        T Get(string key);
        void Set(string key, T value);
        object Getobject(string key);
        void Setobject(string key, object value);

而这三个,PlayerPrefs+Json实现的方法是不同的,所以不能都:IStorage,所以要将IStorage 拆掉。

        void Clear(string key);
        void ClearAll();
        bool ContainsKey(string key);

完成类似这种效果

    public interface IContainsKey
    {
        bool ContainsKey(string key);
    }
    public interface IClear
    {
        void Clear(string key);
    }
    public interface IClearAll
    {
        void ClearAll();
    }
    public interface ISet
    {
        void Set(string key, T value);
    }
    public class PlayerPrefsStorage : IClear,IClearAll,IContainsKey 
    {
        #region 实现
        public void Clear(string key)
        {
            PlayerPrefs.DeleteKey(key);
        }
        public void ClearAll()
        {
            PlayerPrefs.DeleteAll();
        }
        public bool ContainsKey(string key)
        {
            bool has = PlayerPrefs.HasKey(key);
            return has;
        }
        #endregion
    }

03 Json处理

原本采用this的拓展方法,this了PlayerPrefsMemory中的字典。两个子集怎么能交叉呢?所以索性把相关操作提高到StorageSystem。JsonMemory(实际是DataUtil(拓展的方式操作Json),因为JsonMemory里面啥都没有)改成接口

    public interface ISetJsonData
    {
        void SetJsonData(string key, JsonData value);
    }

04 StorageSystem

不行,因为Util(System不能在Model中使用,不用Systm就没有Init重写),所以又改回来

05 StorageUtil

    public class StorageUitl :  IStorageUtil 
    {
        #region 字属
        private static readonly Dictionary _defaultValues = new Dictionary
        {
            {typeof(int), default(int)},
            {typeof(string), ""},
            {typeof(float), default(float)}
        };
        //这两个原来是readonly,但我需要在OnInit中体现出初始赋值的过程(有可能别的初始读取方式),所以改了static
       // 但是因为Util(System不能在Model中使用,不用Systm就没有Init重写),所以又改回来
        private readonly Dictionary _dataGetter = new Dictionary
            {
                {typeof(int), key => PlayerPrefs.GetInt(key, (int) _defaultValues[typeof(int)])},
                {typeof(string), key => PlayerPrefs.GetString(key, (string) _defaultValues[typeof(string)])},
                {typeof(float), key => PlayerPrefs.GetFloat(key, (float) _defaultValues[typeof(float)])}
            };
        private readonly Dictionary _dataSetter = new Dictionary
            {
                {typeof(int), (key, value) => PlayerPrefs.SetInt(key, (int) value)},
                {typeof(string), (key, value) => PlayerPrefs.SetString(key, (string) value)},
                {typeof(float), (key, value) => PlayerPrefs.SetFloat(key, (float) value)}
            };
        private string _className = "PlayerPrefsMemory";
        PlayerPrefsStorage _pp=new PlayerPrefsStorage();
        //JsonStorage _json; //采用接口的方式
        #endregion
        #region 实现
        public T Get(string key) //0level
        {
            var type = typeof(T);
            var td = TypeDescriptor.GetConverter(type);
            if (_dataGetter.ContainsKey(type))
            {
                //根据字符串找类型
                return (T)td.ConvertTo(_dataGetter[type](key), type);//0
            }
            Debug.LogError(_className + "中无此类型数据,类型名:" + typeof(T).Name);
            return default(T);
        }
        public object Getobject(string key)
        {
            if (ContainsKey(key))
            {
                foreach (var pair in _dataGetter)
                {
                    var value = pair.Value(key);
                    if (!value.Equals(_defaultValues[pair.Key]))
                    {
                        return value;
                    }
                }
            }
            else
            {
                //Debug.Log(_className + "内不包含对于键值(所以改数据会转Json):" + key);
            }
            return null;
        }
        public void Set(string key, T value)
        {
            var type = typeof(T);
            if (_dataSetter.ContainsKey(type))
                _dataSetter[type](key, value); //0level,0
            else
                Debug.LogError(_className + "中无此类型数据,数据为 key:" + key + " value:" + value);
        }
        public void Setobject(string key, object value)
        {
            var success = false;
            foreach (var pair in _dataSetter)
            {
                if (value.GetType() == pair.Key)
                {
                    pair.Value(key, value);
                    success = true;
                }
            }
            if (!success)
            {
                Debug.LogError(_className + "未找到当前值的类型,赋值失败,value:" + value);
            }
        }
        public void Clear(string key)
        {
            _pp.Clear(key);
           // _json.Clear(key);
        }
        public void ClearAll()
        {
            _pp.ClearAll();
            //_json.ClearAll();
        }
        public bool ContainsKey(string key)
        {
            return _pp.ContainsKey(key);//|| _json.ContainsKey(key);
        }
        #endregion
        #region 实现 ISetJsonData
        public  void SetJsonData(string key, JsonData data)
        {
            #region 说明
            /**
            {
                "planes": [
                  {
                    "planeId": 0,
                    "level": 0,
                    "attackTime":1,
                    "attack": { "name":"攻击","value":5,"cost":200,"costUnit":"star","grouth":10,"maxVaue": 500},
                    "fireRate": { "name":"攻速","value":80,"cost":200,"costUnit":"star","grouth":1,"maxVaue": 100},
                    "life": { "name":"生命","value":100,"cost":200,"costUnit":"star","grouth":50,"maxVaue": 1000},
                    "upgrades": { "name":"升级","coefficient": 2,"max":4,"0": 100,"1": 200,"2": 300,"3": 400,"costUnit":"diamond"}
                        }, ......
                ],
                "planeSpeed": 1.2
                } 
            **/
            #endregion
            //key=0level,0attackTime(0就是planeId)
            IJsonWrapper jsonWrapper = data;
            switch (data.GetJsonType())
            {
                case JsonType.None:
                    Debug.Log("当前jsondata数据为空");
                    break;
                case JsonType.Object:
                    SetObjectData(key, data);
                    break;
                case JsonType.String:
                    Set(key, jsonWrapper.GetString());
                    break;
                case JsonType.Int:
                    Set(key, jsonWrapper.GetInt()); //0level ,0
                    break;
                case JsonType.Long:
                    Set(key, (int)jsonWrapper.GetLong());
                    break;
                case JsonType.Double:
                    Set(key, (float)jsonWrapper.GetDouble());
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
        private void SetObjectData(string oldkey, JsonData data)
        {
            foreach (var key in data.Keys)
            {
                var newKey = oldkey + key;
                if (!ContainsKey(newKey))
                {
                    SetJsonData(newKey, data[key]);
                }
            }
        }
        #endregion  
    }

modify ConfigMgr => ConfigSystem

ConfigMgr有初始化操作

modify ReadMgr => ReadUtil

IReader,JsonReader,ReaderConfig都放在一个文件上

ReadMgr没有初始化操作,所以改改成ReadUtil

public interface IReaderUtil : QFramework.IUtility
{
    public IReader GetReader(string path);
}
public class ReaderUtil : IReaderUtil
{
    private readonly Dictionary_readerDic = new Dictionary();
    /// 通过路径获取一个填充好数据Configd(json)的IReader
    public IReader GetReader(string path)
    {
        IReader reader = null;
        if (_readerDic.ContainsKey(path))
        {
            reader = _readerDic[path];
        }
        else
        {
            reader = ReaderConfig.GetReader(path);
            LoadMgr.Single.LoadConfig(path, reader.SetData);
            if (reader != null)
            {
                _readerDic[path] = reader;
            }
            else
            { 
                Debug.LogError("ReaderMgr未获取到对应reader,路径:" + path);
            }
        }
        return reader;
    }
}

modify LoadMgr => LoadSystem

有初始化,所以用LoadSystem

modify ItemFactory改成ItemFactoryUtil

modify GameUtil改成GameUtil:IGameUtil

GameUtil太长就不贴了。改这玩意,一是有什么方法在接口中看得清楚;而是是在启动中能看得清楚(都Register在一起了)

。。。

主要就是去掉方法的static

public interface IGameUtil : QFramework.IUtility
{
    public  Vector2 GetCameraSize();
    public Vector2 GetCameraMin();
    public Vector2 GetCameraMax();
    public Camera GetCamera();
    public SubMsgMgr GetSubMsgMgr(Transform trans);
    public void ShowWarnning();
    public int GetInt(object value);
    public List InitEnemyCreator(EnemyType type
        , Transform parent
        , AllEnemyData allEnemyData
        , EnemyTrajectoryDataMgr trajectoryData
        , LevelData levelData);
    public GameProcessNormalEvent GetNormalEvent(Action spawnAction
        , Func spawnedNum
        , int spawnTotalNum);
    public GameProcessTriggerEvent GetTriggerEvent(float prg
        , Action action
        , bool needPauseProcess
        , Func isEnd);
}

miodify 消息MessageMgr => MessageSystem

IMessageSystem

MessageSystem:IMessageSystem

SubMsgMgr : MonoBehaviour,IMessageSystem

MessageMgr : NormalSingleton, IMessageSystem

ActionMgr

同统一改成

IMessageSystem

MessageSystem:IMessageSystem

ActionMgr

modify 消息机制底层委托的结构

 	private Dictionary mTypeEvents 
    public class EasyEvent : IEasyEvent
    {
        private Action mOnEvent = () => { };
        。。。
    public class EasyEvent : IEasyEvent
    {
        private Action mOnEvent = e => { };

而飞机大战用了HashSet

/// 维护了一个HashSet
public class ActionMgr
{
    #region 字属构造
    /// 委托链 
    private HashSet _actionHs;
    private Action _action;
    public ActionMgr()
    {
        _actionHs = new HashSet();
        _action = null;
    }
    #endregion  
    public void Add(Action action)
    {
        if (_actionHs.Add(action))
        {
            _action += action;
        }
    }
    public void Remove(Action action)
    {
        if (_actionHs.Remove(action))
        {
            _action -= action;
        }
    }
    public void Execute(T t)
    {
        _action.DoIfNotNull(t);
    }
    public bool Contains(Action action)
    {
        return _actionHs.Contains(action);
    }
}

modify 移动

因为觉得和KeyCode 有关,所以扔到InputSystem(原InputMgr)去了

    public void AddListener(KeyCode code, KeyState state, Action callback)
     public void RemoveListener(KeyCode code, KeyState state, Action callback)

详细看下一条

modify InputMgr => InputSystem

有初始化

watch 先贴一下组织的

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

#region 接口
public interface IInputModule
{
    void AddListener(KeyCode code);
    void AddMouseListener(int code);
    void RemoveListener(KeyCode code);
    void RemoveMouseListener(int code);
}
/// 按键与按键状态的组合key
public interface IInputUtil : QFramework.IUtility
{
    public string GetKey(KeyCode code, KeyState state);
    public string GetKey(int code, KeyState state);
}
#endregion
#region InputSystem
public interface IInputSystem : QFramework.ISystem, IInputModule, IInputUtil
{
     void AddListener(KeyCode keyCode, KeyState KeyState, Action callback);
     void RemoveListener(KeyCode keyCode, KeyState KeyState, Action callback);
}
public class InputSystem : QFramework.AbstractSystem,IInputSystem, IUpdate,ICanGetSystem
{
    private readonly InputModule _module= new InputModule();
    private readonly bool _updating = false;
                                  
    protected override void OnInit()
    {
        _module.AddSendEvent(SendKey);
        _module.AddSendEvent(SendMouse);
    }
    #region pub IInputUtil
    public string GetKey(KeyCode code, KeyState state)
    {
        return code + state.ToString();
    }
    public string GetKey(int code, KeyState state)
    {
        return code + state.ToString();
    }
    #endregion  
    #region pub IInputModule
    public void AddListener(KeyCode code)
    {
        _module.AddListener(code);
        AddUpdate();
    }
    public void AddMouseListener(int code)
    {
        _module.AddMouseListener(code);
        AddUpdate();
    }
    public void RemoveListener(KeyCode code)
    {
        _module.RemoveListener(code);
        RemoveUpdate();
    }
    public void RemoveMouseListener(int code)
    {
        _module.RemoveMouseListener(code);
        RemoveUpdate();
    }
    #endregion
    #region pub InputSystem
    public void AddListener(KeyCode keyCode, KeyState keyState, Action callback)
    {
        var key = GetKey(keyCode, keyState);
      this.GetSystem().AddListener(key, callback);
    }
    public void RemoveListener(KeyCode keyCode, KeyState keyState, Action callback)
    {
        var key = GetKey(keyCode, keyState);
        this.GetSystem().RemoveListener(key, callback);
    }
    #endregion  
    #region pub IUpdate
    public int Timing { get; set; }
    public int Time { get; }
    public void UpdateFunc()
    {
        _module.Execute();
    }
    #endregion  
    #region pri
    private void SendKey(KeyCode code, KeyState state)
    {
        this.GetSystem().DispatchMsg(GetKey(code, state), state);
    }
    private void SendMouse(int code, KeyState state)
    {
        this.GetSystem().DispatchMsg(GetKey(code, state), state);
    }
    private void AddUpdate()
    {
        if (!_updating)
            LifeCycleMgr.Single.Add(LifeName.UPDATE, this);
    }
    private void RemoveUpdate()
    {
        if (_module.ListenerCount == 0)
            LifeCycleMgr.Single.Remove(LifeName.UPDATE, this);
    }
    #endregion
}
#endregion
#region InputModule
public class InputModule : IInputModule
{
    #region 字属构造
    private readonly Dictionary _keyCodeDic;
    private readonly Dictionary _mouseDic;
    private Action _keyEvent;
    private Action _mouseEvent;
    public InputModule()
    {
        _keyCodeDic = new Dictionary();
        _mouseDic = new Dictionary();
    }
    public int ListenerCount
    {
        get
        {
            if (_keyCodeDic == null || _mouseDic == null)
                return 0;
            return _keyCodeDic.Count + _mouseDic.Count;
        }
    }
    #endregion
    #region 实现
    public void AddListener(KeyCode code)
    {
        if (_keyCodeDic.ContainsKey(code))
            _keyCodeDic[code] += 1;
        else
            _keyCodeDic.Add(code, 1);
    }
    public void AddMouseListener(int code)
    {
        if (_mouseDic.ContainsKey(code))
            _mouseDic[code] += 1;
        else
            _mouseDic.Add(code, 1);
    }
    public void RemoveListener(KeyCode code)
    {
        if (_keyCodeDic.ContainsKey(code))
        {
            _keyCodeDic[code] -= 1;
            if (_keyCodeDic[code] 
            Debug.LogError("当前移除指令并没有被监听,Keycode:" + code);
        }
    }
    public void RemoveMouseListener(int code)
    {
        if (_mouseDic.ContainsKey(code))
        {
            _mouseDic[code] -= 1;
            if (_mouseDic[code] 
            Debug.LogError("当前移除指令并没有被监听,Keycode:" + code);
        }
    }
    #endregion
    #region 辅助
    public void AddSendEvent(Action
        _keyEvent = keyEvent;
    }
    public void AddSendEvent(Action
        _mouseEvent = keyEvent;
    }
    public void Execute()
    {
        if (_keyEvent == null || _mouseEvent == null)
        {
            Debug.LogError("输入监听模块发送消息事件不能为空");
            return;
        }
        foreach (var pair in _keyCodeDic)
        {
            if (Input.GetKeyDown(pair.Key)) _keyEvent(pair.Key, KeyState.DOWN);
            if (Input.GetKey(pair.Key)) _keyEvent(pair.Key, KeyState.PREE);
            if (Input.GetKeyUp(pair.Key)) _keyEvent(pair.Key, KeyState.UP);
        }
        foreach (var pair in _mouseDic)
        {
            if (Input.GetMouseButtonDown(pair.Key)) _mouseEvent(pair.Key, KeyState.DOWN);
            if (Input.GetMouseButton(pair.Key)) _mouseEvent(pair.Key, KeyState.PREE);
            if (Input.GetMouseButtonUp(pair.Key)) _mouseEvent(pair.Key, KeyState.UP);
        }
    }
    #endregion
}
public enum KeyState
{
    DOWN,
    ///  CoroutineSystem(还有Delay版的) 

类似于这样改,不展示全部

#region CoroutineSystem
public interface ICoroutineSystem :QFramework.ISystem
{
    int ExecuteOnce(IEnumerator routine);
    void Delay(float time, Action callBack);
    void Restart(int id);
    void StartExecute(int id);
    void Pause(int id);
    void Continue(int id);
    void Stop(int id);
}
/// 维护了两个同类型字典,一个跑一次,一个存起来
///   Dictionary <int, CoroutineController  >
/// 
public class CoroutineSystem : QFramework.AbstractSystem,ICoroutineSystem
{

modify LifeCycleMgr => LifeCycleSystem

bug QFramework.AbstractSystem与MonoBehaviour

不能同时:QFramework.AbstractSystem与MonoBehaviour,选择:QFramework.AbstractSystem,然后添加一个MonoBehaviour

watch LifeCycleSystem

using QFramework;
using QFramework.AirCombat;
using System;
using System.Collections.Generic;
using UnityEngine;
#region LifeCycleSystem
public interface ILifeCycleSystem : QFramework.ISystem
{
   void Add(LifeName name, object o);
   void Remove(LifeName name, object o);
   void RemoveAll(object o);
}
/// 有Json数据和PlayerPrefers数据的初始化
public class LifeCycleSystem : QFramework.AbstractSystem, ILifeCycleSystem
{
    private LifeCycleSystemMono _mono;
    protected override void OnInit()
    {
        var cfg = new LifeCycleAddConfig();
        cfg.Init();
        Add2LifeCycleConfig(cfg);
        LifeCycleConfig.Do(LifeName.INIT);
        //
        Transform t = Camera.main.transform.FindTopOrNewPath(GameObjectPath.System_LifeCycleSystem);
       _mono= t.GetOrAddComponent();
        _mono.DoUpdate(Update);
    }
    #region 生命
     void Update()
    {
       
        if (this.GetModel().E_GameState == GameState.PAUSE )
        {
            return;
        }
        LifeCycleConfig.Do(LifeName.UPDATE);
    }
    #endregion  
    #region pub   ILifeCycleSystem
    public void Add(LifeName name, object o)
    {
        LifeCycleConfig.Add(name, o);
    }
    public void Remove(LifeName name, object o)
    {
        LifeCycleConfig.Remove(name, o);
    }
    public void RemoveAll(object o)
    {
        LifeCycleConfig.RemoveAll( o);
    }
    #endregion
    #region pri
    private void Add2LifeCycleConfig(LifeCycleAddConfig cfg)
    {
        if (true)//尝试私有化 LifeCycleConfig.LifeCycleDic的写法
        { 
            foreach (object o in cfg.LifeCycleArrayLst)
            {
                //TODD :LifeCycleMgr,不确定这样改会不会报错
                LifeCycleConfig.Add(o);
            }          
        }else
        { 
            foreach (var o in cfg.LifeCycleArrayLst)
            { 
                foreach (var cycle in LifeCycleConfig.LifeCycleFuncDic)
                { 
                  //  if (cycle.Value.Add(o))
                    { 
                        break;
                    }        
                }        
            }        
        }
    }
    #endregion
    #region 重写
    public IArchitecture GetArchitecture()
    {
        return AirCombatApp.Interface;
    }
    #endregion
}
#endregion

#region ILifeCycle
public interface ILifeCycle
{
    bool NeedAdd(object obj);
    void Remove(object obj);
    void Execute(Action execute);
}
/// 维护一个List<object> 
public class LifeCycle : ILifeCycle
{
    private readonly List _objLst = new List();
    public bool NeedAdd(object o)
    {
        if (o is T)
        {
            if (_objLst.Contains(o))
            {
                return false;
            }
            else
            {
                _objLst.Add(o);
                return true;
            }
        }
        return false;
    }
    public void Remove(object o)
    {
        _objLst.Remove(o);
    }
    public void Execute(Action execute)
    {
        for (int i = 0; i  

modify GuideMgr => GuideSystem

stars FindTopOrNewPath

Camera.main基本都有

DoUpdate是UniRx的

跑通一次,就不多试了

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

        Transform t = Camera.main.transform.FindTopOrNewPath(GameObjectPath.System_LifeCycleSystem);
       _mono= t.GetOrAddComponent();
        _mono.DoUpdate(Update);
    public static MonoBehaviour DoUpdate(this MonoBehaviour mono,Action action)
    {
        Observable
            .EveryUpdate()
            .Subscribe(_ => action())
            .AddTo(mono)
            .DisposeWhenGameObjectDestroyed(mono);
        return mono;
    }
    /// A/B/C => A B(父节点A) C(父节点B) 。返回了C
    public static Transform FindTopOrNewPath(this Transform t,string path)
    {
        string topName = path.TrimName(TrimNameType.SlashFirst);//A/B/C  => A
        Transform top = t.FindTop(topName);
        if (top.IsNullObject())//  return (UnityEngine.Object)obj == null;
        {
            top = new GameObject(topName).transform;
        }
        string[] names=path.Split('/');//A/B/C  => A B C
        Transform[] ts = new Transform[names.Length];
        ts[0]=top;
        for (int i = 1; i  

modify UIManager => UISystem

主要看接口中属性的使用(Canvas)

public interface IUISystem : QFramework.ISystem
{
 	Transform Canvas { get; set; }
    IView Show(string tarPath);
    void Back();
    void Hide(string name);
    Transform GetViwePrefab(string path);
    Transform GetCurrentViewPrefab();
    DialogView ShowDialog(string content
        , Action trueAction = null
        , Action falseAcion = null);
}
public class UISystem : AbstractSystem, IUISystem
{
    #region 字属
	......
     Transform IUISystem.Canvas { get { return _canvas; }  set { _canvas = value; } }
     Transform _canvas {  get;  set; }
     ......

star SimpleSingleton

/// 为空就会New() 
public class SimpleSingleton where T :  new()
{
    protected static T _single;
    public static T Single
    {
        get
        {
            if (_single == null)
            {
                var t = new T();
                _single = t;
            }
            return _single;
        }
    }
}

GuideDataMgr => GuideStorageUtil

因为是存储

    public interface IGetBool
    {
        bool GetBool(T key);
    }
    /// Key值,Value值,两个Equals
    public interface ISetIntKEV
    {
        void SetInt(int value);
    }
#region GuideStorageUtil
public interface IGuideStorageUtil : QFramework.IUtility,IGetBool,ISetIntKEV 
{
}
public class GuideStorageUtil : IGuideStorageUtil
{
    public void SaveData(int key, bool value = true)
    {
        PlayerPrefs.SetInt(key.ToString(), Convert.ToInt32(value));
    }
    public bool GetBool(T key)
    {
        int result = PlayerPrefs.GetInt(key.ToString(), Convert.ToInt32(false));
        return Convert.ToBoolean(result) ;
    }
    public void SetInt( int kv)
    {
        PlayerPrefs.SetInt(kv.ToString(), Convert.ToInt32(kv));
    }
}
#endregion

modify PathMgr

一开始想把Path做成Pool。

因为IPath中的方法都有了,没必要再用PathMgr来套上一层吧。

。。。

但Path的具体实现才是Spawn的对象。

比如一个直线阵列的Path被回收后,第二次不能Spawn为W阵列的Path。

IPath是接口。PathBase是抽象类。都不能被实例。

。。。

考虑改名,就一个IPath,好意思带Mgr后缀。

改名

Unit,部门,单元,不怎么合适

One,直观但不雅

Ctrl,占用了:QFramework.IController

noun,概念,不准确

IPath是一个移动阵列,根据接口有类型,方向,成员初始位置

Wave,一波敌人

class SumPath
{
    IPath iPath;
    PathBase pathBase;
    StraightPath straightPath;
    WPath wPath;
    StayOnTopPath pathOnTopPath;
    EllipsePath ellipsePath;
}
/// 
/// 路径接口,提供具体的路径的计算方法
/// 
public interface IPath
{                                              
    void Init(Transform trans,ITrajectoryData trajectory);
    Vector3 GetInitPos(int id);
    Vector2 GetDir();
    bool FollowCamera();
}
public abstract class PathBase : IPath
{
    protected PathState _state;
    protected ITrajectoryCalc _trajectoryCalc;
    protected Transform _trans;
    public virtual void Init(Transform trans, ITrajectoryData trajectoryData)
    {
        _trans = trans;
    }
    public abstract Vector3 GetInitPos(int id);
    public abstract Vector2 GetDir();
    public abstract bool FollowCamera();
}
#region Path  :  PathBase (具体实现)

modify TrajectoryData改名

TrajectoryData相关,改成PathData,与IPath,PathBase对应统一

TrajectoryData相关,

有ITrajectoryData,实例类:ITrajectoryData

有EnemyTrajectoryDataMgr

watch 类的静态方法 静态类的静态方法

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

LoadCreatorData => ILoadCreatorDataSystem

modify PlaneEnemyCreator、MissileEnemyCreator隔离MonoBehaviour

为了管理OnDestroy(原本LifeName就有Init,Upadte)。

如果加上Destroy,就能隔离需要OnDestroy的类

modify GameUtil的一些方法转Command

有一些是Camera的

有一些是初始Plane数据的,觉得不搭,而且以后会混杂,所以转成Command

InitEnemyCreatorLstCommand

    public class InitEnemyCreatorLstCommand : AbstractCommand
    {
        EnemyType enemyType;
        AllEnemyData allEnemyData;
        PathDataMgr pathDataMgr;
        LevelData levelData;
    public InitEnemyCreatorLstCommand(EnemyType enemyType, AllEnemyData allEnemyData, PathDataMgr pathDataMgr, LevelData levelData)
        {
            this.enemyType = enemyType;
            this.allEnemyData = allEnemyData;
            this.pathDataMgr = pathDataMgr;
            this.levelData = levelData;
        }
        protected override  List OnExecute()
        {
            List list = new List();
            foreach (PlaneCreatorData data in levelData.PlaneCreaterDatas) //这里可以到LoadCreaterData这看
            {
                if (data.Type == enemyType) //ever error;data都是normal
                {
                    list.Add(SpawnCreator(data, allEnemyData, pathDataMgr));
                }
                //Debug.Log($"data.EnemyType == enemyType=>{data.Type}=={enemyType}");
            }
            if (list.IsNotNull() && list.Count > 0)
            {
                return list;
            }
            else
            {
                throw new System.Exception($"Creater初始化失败:{enemyType}");
            }
        }
        private IEnemyCreator SpawnCreator(
            PlaneCreatorData data
            , AllEnemyData allEnemyData
            , PathDataMgr trajectoryData)
        {
            var creater = new PlaneEnemyCreator();
            creater.Init(data, allEnemyData, trajectoryData);
            return creater;
        }
    }

modify :MonoBehaviour的后缀又是Mgr,改成Component

避免混乱

modify PoolSystem SpawnPlaneSystem

bug 敌人初始位置不准

位置初始看带队的第一架飞机,对PathMgr 的初始化。

public class PlaneEnemyView : PlaneView,IUpdate  ,ICanGetSystem
{
    private PathMgr _pathMgr;

所以不会将PathMgr 放在PlaneEnemyView ,所以创建它的 PlaneEnemyCreator

放在这里。

    /// 在PlaneEnemyCreator中有
    private PlaneEnemyView InitPlaneEnemyView(int posIdxInRange, IPathData pathData)
    {
        var plane = this.GetSystem().Spawn(ResourcesPath.PREFAB_PLANE);
        // 需要 plane
        if (posIdxInRange == 0)  
        {
            _pathMgr = new PathMgr();
            _pathMgr.Init(plane.transform, _enemyData, pathData);
        }
        // 需要_pathMgr
        var view = plane.GetOrAddComponent();
        view.Init(posIdxInRange, _enemyType, _enemyData, _sprite, pathData,_pathMgr);
      
        return view;
    }

原来的放在这里。这里挺迷的,带队的将自身传进去,后面的算位移偏差就行了。

没必要多new几个PathMgr

public class PlaneEnemyCreator : ......
{
    private void InitComponent(EnemyData data,EnemyType type, Sprite sprite, ITrajectoryData trajectoryData)
    {
		......
        //路径初始化
        _path = new PathMgr(); 
        _path.Init(transform,data,trajectoryData);

bug 队伍生成间隔时间段太快

原版的是

01 撞机直接死

02 每队伍第一架飞机接触屏幕底部后,就会生成另外一队

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug 玩家子弹的结束位置不对

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

判断条件的问题

之前是_sR.bounds.max.y,所以那样

    if (_dir == Vector2.up) return _sR.bounds.min.y 
        Camera _camera;
        Vector2 _dir;
        SpriteRenderer _sR;
        public InCameraBorderCommand(Transform t, Vector2 dir)
        {
            _camera = t.MainOrOtherCamera();
            _sR = t.gameObject.GetComponent
            if (_dir == Vector2.up) return _sR.bounds.min.y  _destroyCase.Injure(bullet.GetAttack())) ;

_destroyCase.DoIfNotNull(_destroyCase.Dead);

位置是PlaneCollideMsgComponent

。。。

意思是_destroyCase!=null,就执行后面括号里面的Action。主要是第一句 _destroyCase.DoIfNotNull(() => _destroyCase.Injure(bullet.GetAttack())) ;的写法,要加() => ,表示这是一个Func,而不只是object

。。。

对比第二句,第二句应该是C#的语法默认设置,省事 () =>方法()

    /// o不为空,就执行cb
    public static object DoIfNotNull(this object o,Action cb)
    {
        if (o != null)
        {
            cb();
        }
        return o;
    }

出处

    public void ColliderMsg(Transform other)
    {
        var bullet = other.GetComponentInChildren();
        if (other.tag == Tags.BULLET
            && bullet != null
            && _selfBullet != null
            && bullet.ToTags.Contains(_selfBullet.From) //BulletComponent
        )
        {    
            _destroyCase.DoIfNotNull(() => _destroyCase.Injure(bullet.GetAttack())) ;
        }
        else if (_selfBullet != null && _selfBullet.ContainsTo(other.tag))
        {
            _destroyCase.DoIfNotNull(_destroyCase.Dead);
        }

star Character Injure

/****************************************************
    文件:Test_ExtendCharacter.cs
	作者:lenovo
    邮箱: 
    日期:2024/1/10 17:19:6
	功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;
 
namespace Demo00_00
{
    public class Test_ExtendCharacter : MonoBehaviour
    {
        #region 属性
        [SerializeField] float _curLife;
        [SerializeField] float _change;
        [SerializeField] Slider _slider;
        #endregion
        /// 首次载入
        void Awake()
        {
            _slider=GetComponentInChildren();
            _slider.onValueChanged.AddListener(Injure);
        }
        void Injure(float change)
        {
            _change = change;
            _curLife.Injure(_change, () => Debug.Log("受伤"), () => Debug.Log("死亡"));
        }
    }
}

    public static int Injure(ref this int cur, int change, Action injureAction, Action deadAction)
    {
        if (cur 
            cur = 0;
            return cur;
        }
        var after = cur - change;
        if (after 
            after = 0;
            deadAction();
        }
        else
        {
            injureAction();
        }
        cur = after;
        return cur;
    }

        GameObject[] gos = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
        foreach (GameObject go in gos)
        {
            if (go.name == tarName)
            {
                return go.transform;
            }
        }
        return null;
    }
    public static Transform FindTop(this Transform t, string tarName)
    {
        GameObject[] gos = GameObject.FindObjectsOfType
            if (go.name == tarName)
            {
                return go.transform;
            }
        }
        return null;
    }

        //EnemyData data = os[0] as EnemyData;//没用
        //EnemyData data = (EnemyData)os[0]  ; //没用
        if (_enemyData.IsNull())
        {
           // Debug.LogError("异常:EnemyData值:" + os[0].ToString());   
        }    
        else 
        {
            //_enemyData = data;
            //_lifeComponent = gameObject.GetOrAddComponent
    public int id;
    public double attackTime;
    public int attack;
    public double fireRate;
    public int life;
    public double speed;
    //
    public TrajectoryType trajectoryType;
    /// 
        string str = "";
        str += "\t" + id;
        str += "\t" + attack;
        str += "\t" + life;
        str += "\t" + trajectoryID;
        str += "\t" + starNum;
        str += "\t" + score;
        str += "\t" + itemCount;
        str += "\t" + attackTime;
        str += "\t" + fireRate;
        str += "\t" + speed;
        str += "\t" + trajectoryType.ToString();
        str += "\t";
        foreach (var item in bulletType)
        {
            str += item.ToString() + ",";
        }
        str += "\t";
        foreach (var item in itemRange)
        {
            str += item.ToString() + ",";
        }
        return str;
    }
}

        if (name == GameAudio.Null)
        {
            return;
        }
        AudioKit.PlaySound($"{_path}{name}");
    }

            return;
        }

        GameObject go = gameObject;
        go.GetOrAddComponent
    private IEffectView _effectView;
    private void CollideEvent()
    {
        AudioMgr.Single.PlayOnce(GetGameAudio().ToString());
        _effectView.Stop(ItemLogic);
    }
    protected virtual void ItemLogic()
    {
        Destroy(gameObject);
    }

    private IEffectView _effectView;
    protected override void InitComponent()
    {
        gameObject.GetOrAddComponent
        _cur = null;
        foreach (IEnemyCreater tmp in list)
        {
            if (_cur == null || _cur.GetSpawnRatio()  tmp.GetSpawnRatio())
            {
                if (!tmp.IsSpawning())
                {
                    _cur = tmp;
                }
            }
        }
        return _cur;
    }

           foreach (IEnemyCreator tmp in list)
           {
               bool changeCreator = false;//是否切换当前的Creator
               //空或有小的prg都换,写这么长是为了方便打点
               if (curCreator != null && curCreator!=tmp)//不能自己和自己做对比
               {
                   changeCreator = curCreator.GetSpawningPrg()  tmp.GetSpawningPrg();
               }
               else if (curCreator == null)
               {
                   changeCreator = true;
               }
               else
               {
                   changeCreator = false;
               }
               if (changeCreator) //换了
               {
                   if (!tmp.IsSpawning())//正在
                   {
                       curCreator = tmp;  
                       return curCreator;
                   }
               }
              
           }
           return curCreator;//没换
       }

bug unity脚本不显示图标

有的:Mono脚本不能放在一起,出问题

watch EllipseTrajectory

椭圆,玩家子弹升级后的样式

bug 一队生成两个后马上生成第二队,也是两个

OutTop的原因,顶部不算进超范围

出现原因是子弹也需要这个组件, 所以需要把它改成可选项

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

   public class AutoDespawnOtherCollideCameraBorderCommand : AbstractCommand
   {
       /// 回收到pool的key,这里用path
       string _poolKey;
       /// 因为一般以图片消失视觉最近人
       SpriteRenderer _sr;
       /// 需要despawn的物体
       Transform _despawnTrans;
       /// 测试用到,不用传参
       Dir _dir;
       public AutoDespawnOtherCollideCameraBorderCommand(string path, SpriteRenderer sr, Transform despawnTrans)
       {
           _poolKey = path;
           _sr = sr;
           _despawnTrans =  despawnTrans;
       }
       protected override void OnExecute()
       {
           
           if (JudgeBeyondBorder())
           {
               //this.GetSystem().DespawnWhileKeyIsName(_despawnTrans.gameObject, _poolKey)  ;
               this.GetSystem().Despawn(_despawnTrans.gameObject, _poolKey);
           }
       }
       #region pri
       private bool JudgeBeyondBorder()
       {
           if (_sr.IsNull())
           {
               return true;
           }
           //判断底边界限
           //因为都是运动的,有偏差
          // if (OutTop())       { _dir = Dir.TOP; return true; }  //这条没有
           if (OutBottom())    { _dir = Dir.BOTTOM; return true; }
           if (OutLeft())      { _dir = Dir.LEFT; return true; }
           if (OutRight())     { _dir = Dir.RIGHT; return true; }
           return false;
       }
       bool OutLeft()
       {
           float x1 = _sr.BoundsMinX();
           float x2 = this.GetUtility().GetCameraMinPoint().x;
           if (x1  x2)
           {
               //Debug.Log(x1 + "," + x2);
               return true;
           }
           return false; 
       }
       bool OutTop()
       {
           float y1 = _sr.BoundsMinY();
           float y2 = this.GetUtility().GetCameraMaxPoint().y;
           if (y1 > y2)
           { 
               //Debug.Log(y1+","+y2);
               return true;
           }
           return false;
       }
       bool OutBottom()
       {
           float y1 = _sr.BoundsMaxY();
           float y2 = this.GetUtility().GetCameraMinPoint().y;
           if (y1  

bug 两个Normal敌人Creator生成的飞机位置靠中间

PathMgr的初始化问题,它是每个敌人都会new一个,不是队伍层级的,所以我注释还有"leaderPlane"等的遗留注释

主要是这一块.竖屏,所以可以用creatorPos的x值,y需要跟随相机变化,这样就设置了飞机的初始位置

		enemyTrans.SetPosX(creatorPos.x);	//x需要creator传过来,不动的的,所以不能取y
		enemyTrans.SetPosY(  GetY(enemyTrans) );  //y需要跟随相机的移动(竖屏),加上一点点偏移

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

/// 管理一架飞机,路径的初始位置,方向等
public class PathMgr     :ICanGetUtility ,ICanSendQuery
{
	#region 字属构造
	private IPath _path;
	/// 
	/// 根据飞机的初始位置,生成相应的pathMgr
	/// 所以需要全部生成完再移动,这样避免第一种情况
	/// 
	public PathMgr(Transform enemyTrans, EnemyData enemyData, IPathData pathData,Vector3 creatorPos)
	{
		//以下是顶部左右两个creator的straight轨迹的飞机
		enemyTrans.SetPosX(creatorPos.x);	//x需要creator传过来,不动的的,所以不能取y
		enemyTrans.SetPosY(  GetY(enemyTrans) );  //y需要跟随相机的移动(竖屏),加上一点点偏移
		_path = PathFactory.GetPath(enemyData.trajectoryType) ;
		_path.Init(enemyTrans, pathData);//这里trans穿进去了
	}
	#endregion
	/// leaderPlane的出场位置
	/// 需要Sprite
 //   public void InitComponentEnemy(Transform enemyTrans, EnemyData enemyData, IPathData _pathData)
	//{
 //   }
	float GetY(Transform enemyTrans)
	{
		float posY = this.GetUtility().GetCameraMaxPoint().y;
		float height = enemyTrans.GetComponent().BoundsHeight() / 2.0f;
		return  posY+height ;
	}

watch 敌人生成的流程

01 有两个事件(就是多少进度,回调相应方法), 大约是NormalEvent(Normal敌人的), TriggerEvent(Normal外的敌人的,火箭,Boss,精英怪)

// Normal的

02 GameProcessSystem会Update这两种Event的列表

03 在NormalEvent中, 进入条件是在场飞机数,少于5架(被击毁或者超出范围),就去触发Event中相应的Creator

04 Creator(Config中有两个Normal的),会北CreatorNMgr比较彼此的生成队伍进度(就是A生成一队,下一次B生成一队)

05 队伍正在生成就继续生成,不允许换下一队.

bug 小兵死完,只有警报声,警报界面没出现,Boss没出现

调试准备

这个看BossCreatorMgr.UpdateFunc(可以自定义帧数的Update)

调试时先注册一个调试事件(用户QF的Command)控制_start,

	public void Init()
	{
		_start = false;
		this.RegisterEvent(_ =>
		{
			_start = true;
		});
		this.GetSystem().Add(LifeName.UPDATE, this);
	}
	public void FrameUpdate()
	{
		if (!_start)
			return;
		if (true)
		//if (this.GetSystem().ActiveCount(ResourcesPath.PREFAB_PLANE) == 0)
		{
			this.GetUtility().ShowWarnning();
			SpawnAQueueBoss();
          //  this.GetSystem().Delay(Const.WAIT_BOSS_TIME, SpawnAQueueBoss);
			_start = false;
		}
	}

bug Boss 打两下就死了

因为Boss 打两下就会去超过相机右边边界,所以就死了

边界检测自动销毁的问题,Boss所有方向的边界都不能取触发自动销毁

Boss传入的 _excludeDirs = Dir.TOP, Dir.BOTTOM, Dir.LEFT, Dir.RIGHT

	public class AutoDespawnOtherCollideCameraBorderCommand : AbstractCommand
	{
		/// 回收到pool的key,这里用path
		string _poolKey;
		/// 因为一般以图片消失视觉最近人
		SpriteRenderer _sr;
		/// 需要despawn的物体
		Transform _despawnTrans;
		/// 有的物体的方向不需要进行自动销毁. 一般敌人忽略顶部,Bosss略全部
		Dir[] _excludeDirs;
		Dir _dir;
		public AutoDespawnOtherCollideCameraBorderCommand(string path, SpriteRenderer sr, Transform despawnTrans,Dir[] excludeDirs=null)
		{
			_poolKey = path;
			_sr = sr;
			_despawnTrans =  despawnTrans;
			_excludeDirs = excludeDirs;
		}
		protected override void OnExecute()
		{
			
			if (JudgeBeyondBorder())
			{
				//this.GetSystem().DespawnWhileKeyIsName(_despawnTrans.gameObject, _poolKey)  ;
				this.GetSystem().Despawn(_despawnTrans.gameObject, _poolKey);
			}
		}
		#region pri
		private bool JudgeBeyondBorder()
		{
			if (_sr.IsNull())
			{
				return true;
			}
			//判断底边界限
			//因为都是运动的,有偏差
			if (_excludeDirs != null)
			{
				if (!_excludeDirs.Contains(Dir.TOP)) if (OutTop()) { _dir = Dir.TOP; return true; }  //这条没有
				if (!_excludeDirs.Contains(Dir.BOTTOM)) if (OutBottom()) { _dir = Dir.BOTTOM; return true; }
				if (!_excludeDirs.Contains(Dir.LEFT))	if (OutLeft()) { _dir = Dir.LEFT; return true; }
                if (!_excludeDirs.Contains(Dir.RIGHT))	if (OutRight()) { _dir = Dir.RIGHT; return true; }
			}
			else  //一碰就死
			{
                if (OutTop()) { _dir = Dir.TOP; return true; }  //这条没有
                if (OutBottom()) { _dir = Dir.BOTTOM; return true; }
                if (OutLeft()) { _dir = Dir.LEFT; return true; }
                if (OutRight()) { _dir = Dir.RIGHT; return true; }
            }
			return false;
		}
		bool OutLeft()
		{
			float x1 = _sr.BoundsMinX();
			float x2 = this.GetUtility().GetCameraMinPoint().x;
			if (x1  x2)
			{
				//Debug.Log(x1 + "," + x2);
				return true;
			}
			return false; 
		}
		bool OutTop()
		{
			float y1 = _sr.BoundsMinY();
			float y2 = this.GetUtility().GetCameraMaxPoint().y;
			if (y1 > y2)
			{ 
				//Debug.Log(y1+","+y2);
				return true;
			}
			return false;
		}
		bool OutBottom()
		{
			float y1 = _sr.BoundsMaxY();
			float y2 = this.GetUtility().GetCameraMinPoint().y;
			if (y1  

bug Boss没出现

触发了超过相机范围顶部的组件

上面说了,增加对边界检测的一些方向的排除

bug 警报界面没出现

初始化问题,y,scale都不正常

bug 子弹的几种bug

散乱弹

原因

子弹我写了两个脚本,BulletEnemyCtrl,BulletPlayerCtrl中的CollideMsgFromBulletComponent有的加错了,

导致一个Ctrl物体有两个CollideMsgFromBulletComponent,(此时还没写子弹不能打子弹)

触发了这个

	/// 挂在Bullet的Collide
	public class CollideMsgFromBulletCommand : AbstractCommand
	{
		private IDespawnCase _selfDestroyCase;
		private IBullet _selfBullet;
		Transform _other;
		public CollideMsgFromBulletCommand(IDespawnCase destroyCase, IBullet selfBullet, Transform other)
		{
			_selfDestroyCase = destroyCase;
			_selfBullet = selfBullet;
			_other = other;
		}
		protected override void OnExecute()
		{
			IBullet otherBullet = _other.GetComponentInChildren(); //分开节点方便看,这个不是子弹是飞机上的子弹信息节点
			if (_selfBullet == null)//自身不能被撞击
			{
				return;
			}
			if (_selfBullet.Owner == otherBullet.Owner)  // 01自己的子弹不能打自己 02初始位置时就触发了自己
			{
				return;
			}
			if (_other.gameObject.CompareTag(Tags.BULLET))	//子弹不能打子弹
			{
				return;
			}
			//只要是飞机类(),都会受子弹攻击
			if (   otherBullet != null   //敌人或者玩家等可以碰撞的
				&& otherBullet.ContainsShootBulletOwner(_selfBullet.Owner)) //伤了
			{
				_selfDestroyCase.DoIfNotNull(() =>
				{
					_selfDestroyCase.Injure(-otherBullet.GetAttack()); //扣血,所以减
				});
			}
			else if ( _selfBullet.ContainsDeadDestroyTag(_other.tag))  //死了
			{
				_selfDestroyCase.DoIfNotNull(() =>
				{
					_selfDestroyCase.Dead();
				});
			}
		}                                    
	}

bug 图

有的地方没敌人,可以看出子弹是不饱满的,有的地方时缺的

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

不动弹

IUpdate中Frame的问题

Frame不设置时默认为0,也就是每一个逻辑帧都会跑一次

设置为30时,30个逻辑帧才会跑一次

也就是如果我把子弹的Frame设置为30,同样需要把速度设置为 *30才会达到默认0的效果(实际只有速度跟得上,视觉都是散乱的)

所以Frame(原名Time),实际完整意思是 FramesPerCount(多少逻辑帧才会进行一次, 每一次逻辑会跑多少个逻辑帧)

//

解决方法就是不设置Frame,默认为0,每个逻辑帧都跑一次

bug代码

        {
            LifeName.UPDATE,() =>
            {
                 ILifeCycle life =_lifeCycleDic[LifeName.UPDATE];
                life.Execute((IUpdate update) =>
                {
                    if (update.Framing  

bug 图

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

测试Frame=30的效果

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

天外弹

bug图

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug代码

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug 子弹对象池的父节点下有10个子弹是不能用到的

bug 所在

BulletPool_后面是 总数量 Active的数量

可以看到总数量为10,但实际有20个物体,

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

调用分析

对象池预加载的初始数量设置10(配置文件写的),我特意命名为Load

这个特意命名的方法只会在Pool初始化时调用,怀疑绷里调用两次

01 SceneConfigSystem调用了一次,就是场景记载好后的回调

02 总架构又调用一次

03 实际还有SceneConfig也调用了(但没调用SceneConfig)

导致第一次生成的物体的引用被断掉了.

所以注释掉02,03的调用

//

出现这问题的原因是战斗中的物体生成,因该是战斗时才调用,所以想弄成可调用的Init,而不是QF中实现的OnInit(注册在架构中)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

效果

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

modify 子弹朝向

watch 朝向

做子弹朝向时(旋转角度与四元数,欧拉角的设置),有个旋转的效果挺好玩的

        public void FrameUpdate()
        {
            Vector3 e = transform.rotation.eulerAngles;
            transform.Rotate( new Vector3(e.x,  e.y,  (_dir.y/_dir.x).Atan().Radian2Degree()));
            _moveOther.Move(_dir); 
        }

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

watch 最终效果反推

最左边的是20(初始弧度是-80) ,中间的是0,最右边的是-20(初始弧度是80),由此推出了下一条的效果

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

modify 反推的代码

使用如下,

效果是在"watch 朝向"

        #region IUpdate
        public int Framing { get; set; }
        public int Frame {  get;   }
        public void FrameUpdate()
        {
            transform.FaceTo(_dir,Dir.UP);
            ......
        }
        #endregion
    #region Face
    public static  Transform FaceTo(this Transform t, Vector2 dir, Dir eDir)
    {
        Vector3 e = t.rotation.eulerAngles;
        float radian = (dir.y / dir.x).Atan();//求弧度
        float degree = (radian).Radian2Degree();// 求角度    -80 (90) 80 
        float degreeOffset = 0; //朝向带来的偏移
        switch ( eDir )
        {        
            case  Dir.UP :  degreeOffset = 90; break; //        -80 (90) 80     =>     10 0 -10  
            case  Dir.DOWN :  degreeOffset = -90; break; //    -80 (-90) -100   =>     0 -10   
            case  Dir.LEFT :  degreeOffset = 180; break; //    170 (180) 190    =>     10 0 -10  
            case  Dir.RIGHT :  degreeOffset = 0; break; //     -10 (0)   10     =>     10 0 -10  
            default: break;
        }
        if (degree  

watch 希腊字母

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

public static partial class ExtendGreekAlphabet
{
    // https://baike.baidu.com/item/%CF%89/7451083?fr=ge_ala
    public static void ExampleGreekAlphabet()
    {
        //Debug.Log(EGreekAlphabet.α);
        //Debug.Log(EGreekAlphabet.β);
        //Debug.Log(EGreekAlphabet.Δ);
        //Debug.Log(EGreekAlphabet.Π);
        string str="";
        for (int i = 0; i 测试过VS+Unity可以打印
    public enum EGreekAlphabet
    {
        α, Α, alpha, 阿尔法,
        β, Β, beta, 贝塔,
        Γ, γ, gamma, 伽马,
        Δ, δ, delta,德尔塔 ,
        Ε, ε, epsilon, 伊普西龙,
        Ζ, ζ, zeta,捷塔 ,
        Η, η, eta, 艾塔 ,
        Θ, θ, theta,西塔,
        Ι, ι, iota,伊奥塔,
        Κ, κ, kappa,卡帕,
        Λ, λ, lambda, 兰姆达,
        Μ, μ, mu,缪,
        Ν, ν, nu, 纽,
        Ξ, ξ, xi,克西,
        Ο, ο, omicron,欧米克戎,
        Π, π, pi,派,
        Ρ, ρ, rho,柔,
        Σ, σ, sigma, 西格玛,
        Τ, τ, tau,陶,
        Υ, υ, upsilon,宇普西龙,
        Φ, φ, phi,发爱,
        Χ, χ, chi, 开,
        Ψ, ψ, psi, 普西,
        Ω, ω, omega ,欧米伽 ,
        COUNT
    }
}

bug 所有敌人的子弹没生成

生成位置在相机范围外,属于超出范围会被回收掉

watch 在边界外

注释掉敌人子弹的超界销毁,观察坐标变化

如下图看到y值有问题

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug 枪口位置的加减

不用看, 之前错了是因为muzzle进行了位置重置, 子节点会随着父节点旋转

bug 敌人发射得太早了,敌人一生成就发射

可以设置开始发射的条件 ( 我设置的是飞机完全相机视图 ).

2图的 object[] args 是原本设定的参数形式

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

watch 计算位子偏移量

/// 
/// 因为理解错误,敌人发生过Y值过高的bug
/// 
这里椭圆中心为Vector3,与muzzle的作用在外面计算 /// private Vector3[] GetPointOffsetArr(int colCnt, float boundsSizeX ,Dir muzzleDir) { if (_pointArr != null && _pointArr.Length == colCnt ) //跟之前的列数一样 .这一段决定了是初始时的坐标,所以外面要加上muzzle的实时坐标,来更新位置 { return _pointArr; } if (colCnt == 0) { throw new System.Exception("数值不能为0异常"); } _pointArr = new Vector3[colCnt]; if (colCnt == 1) { _pointArr[0] = Vector3.zero; return _pointArr; } // float xRadius = boundsSizeX / 2.0f; float yRadius = 0.3f; Ellipse ellipse = new Ellipse(xRadius, yRadius, Vector2.zero);; // float xHalf = boundsSizeX / 4; //自定义初始一行子弹的初始宽度 float xWidth = xHalf * 2; //初始一行子弹的宽度 float offset = xWidth / (colCnt-1);//2个,间隔及时全部;3个,间隔就是一半;4个,间隔就是1/3;5个,间隔就是1/4 float xMin = -xHalf -offset; // 初始一行子弹第一个的x值 ;-offset是方便后面循环,相当于第0或-1个子弹,反正就是第1的前面 float x = xMin + ellipse.Top.x; //从左到右的第一个x float y; // if (muzzleDir == Dir.DOWN) { for (int i = 0; i

watch 根据偏移量计算实际位置

	/// 多少列子弹射击方向.向霰弹枪一样,发射一圈又一圈
	public Vector3[] GetPointArr(int columCnt, Vector3 muzzlePos, float boundsSizeX, Dir muzzleDir)
	{
		Vector3[] posArr = GetPointOffsetArr(columCnt, boundsSizeX,muzzleDir);
		//更新位置
		Vector3[]  tempArr = new Vector3[columCnt];
		for (int i = 0; i  

watch 自定义的椭圆类

    /// 
    /// 椭圆 (x/a).Pow2()+(y/b).Pow2()=1 ,(k,h)=(0,0)时
    /// 椭圆 ((x-k)/a).Pow2()+((y-h)/b).Pow2()=1
    /// 椭圆(Ellipse)是平面内到定点F1、F2的距离之和等于常数(大于|F1F2|)的动点P的轨迹,
    /// 
F1、F2称为椭圆的两个焦点。 ///
其数学表达式为:|PF1|+|PF2|=2a(2a>|F1F2|)。 /// public class Ellipse : ICircumference, ISquare { /** * 标准方程 * 参数方程 * 弦长公式 * 二级结论 * 焦点c, 长轴a,短轴b,半短轴d,半长轴e, 偏心率f, 离心率g, 整体椭圆系数h, 偏心系数i, 椭圆比例系数j, 椭球系数k, 焦距系数l, 圆心距离系数m, 偏心轴距离系数n, 椭球的半短轴系数o, 椭球的半长轴系数p, 偏心轴系数q, 椭球的比例系数r, 椭球的离心率系数s, 椭球的偏心率系数t, 椭球的整体椭圆系数u, 椭球的焦距系数v, 椭球的离心轴距离系数w, 椭球的圆心距离系数x, 椭球的偏心轴距离系数y, 椭球的偏心轴距离系数z。 **/ #region 本质参数 public Vector2 Pos { get; } public Vector2 Center { get { return Pos; } } /// 焦点1 小的 public Vector2 Focus1 { get { if (XHalfAxis > YHalfAxis) //焦点x轴 { return new Vector2(Center.x - (float)c, Center.y); } else if (XHalfAxis = YHalfAxis) { return YHalfAxis; } else if (XHalfAxis = YHalfAxis) { return XHalfAxis; } else if (XHalfAxis v1 = a.Pow2(); ///
v2 = 1 - ((y-h) / b).Pow2(); ///
v3 = (v1 * v2).Sqrt(); /// public double[] GetXArr(double y) { double[] xArr= new double[2]; double v1 = a.Pow2(); double v2 = 1 - ((y-h) / b).Pow2(); double v3 = (v1 * v2).Sqrt(); // xArr[0] = k - v3; xArr[1] = k + v3; return xArr; } /// /// 根据x求两个y ,先小后大 ///
v1 = b.Pow2(); ///
v2 = 1 - (( x - k) / a).Pow2(); ///
v3 = (v1*v2).Sqrt(); /// public double[] GetYArr(double x) { double[] yArr = new double[2]; double v1 = b.Pow2(); double v2 = 1 - (( x - k) / a).Pow2(); double v3 = (v1*v2).Sqrt(); //double sqrt = h+/- (b.Pow2-(x-k/a).Pow2).sqrt yArr[0] = h - v3 ; yArr[1] = h + v3 ; return yArr; } public float[] GetYArr(float x) { return GetYArr((double)x).ToFloatArray(); } public float GetYTop(float x) { return GetYArr((double)x).ToFloatArray()[1]; } public float GetYBottom(float x) { return GetYArr((double)x).ToFloatArray()[0]; } public float[] GetXArr(float y) { return GetXArr((double)y).ToFloatArray(); } public float GetXLeft(float y) { return GetXArr((double)y).ToFloatArray()[0]; } public float GetXRight(float y) { return GetXArr((double)y).ToFloatArray()[1]; } float ICircumference.Circumference() { throw new System.NotImplementedException(); } float ISquare.Square() { throw new System.NotImplementedException(); } #endregion }

bug 第一轮正常,第二轮及其以后子弹过快发射

对椭圆行径的子弹位置的方法理解错误

01 最底层的方法是构造一个中心为Vector3.zero的椭圆,代入x求出y,此时的(x,y)是子弹对枪口位置的偏移量,不是实际位置,

实际位置也不该揉乱在这里

02 上一层的方法就是对枪口位置进行加减

//

如下图,以向上飞的Player为例

01 黑箭头为飞机, 飞机与椭圆的交点为 椭圆顶部的端点, 也是枪口的位置

02 矩形为子弹实例的位置的x值的取值范围(代码中取图片宽度的一半,即boundsSizeX/2.0f)

03 一列子弹,就是中间一列;

2列子弹,就在两端, 2列有1个间隔;

3列子弹,就在两端和中间, 3列有2个间隔;

同理可得,4列子弹, 就有3个间隔

以上可以得到所有x值,代入椭圆类,可以得到顶部的y值

04 x 和 y,组成了子弹偏移量

05 再上一层方法就是,加上枪口位置,相当于对椭圆进行了移动

//

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

watch 玩家子弹的生成位置

Top端点是枪口的位置,椭圆的x直径是飞机图片的宽度,y直径是自定义的0.6f,

设置枪口位置为muzzlePos,所以中心是 (muzzlePos.x, muzzlePos.y - 0.6/2.0f)

//

这就是这里的由来

原来的类名是EllipseTrajectory, 我新建了一个类Ellipse

			Vector3 center = new Vector3(_muzzleTrans.position.x, _muzzleTrans.position.y-0.3f); 
			Ellipse ellipse = new Ellipse(boundsSizeX/2.0f, 0.3f,center );

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

	private Vector3[] GetPointArr(int count, float boundsSizeX,BulletType bulletType)
	{
	 
		if (_pointArr != null && _pointArr.Length == count) //分别是1列,2列,3列的设置
		{
			return _pointArr;
		}
		else  //注释一3列为标准
		{
			_pointArr = new Vector3[count];
			Vector3 center = new Vector3(_muzzleTrans.position.x, _muzzleTrans.position.y-0.3f); 
			Ellipse ellipse = new Ellipse(boundsSizeX/2.0f, 0.3f,center );
			//
			float offset = boundsSizeX / 4;//自定义
			float minX = -offset;	   // 初始一行子弹第一个的x值
            float validX = offset * 2; //初始一行子弹的宽度           
			//
			//int piece = count + 1; //间隔总会少一个,所以
			float offsetX = validX / count;
			//
			Vector3 top = _muzzleTrans.position;
            float x = minX + top.x; //从左到右的第一个x
			float y = 0f;
			for (int i = 0; i  

效果

可以看到玩家和敌人射出子弹的 时机 和 路径 都正确了

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug muzzle被初始化

在生成位置以外修改了Transfrom,建议能放一起放一起,避免到处找

之前子弹位置出错,尝试修改加的

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug EnemyLevelData索引越界

打败Boss后CurLevel++,从0变成2

而EnemyLevelData只有2组数据,越界了

就是++调用了两次

一个MonoBehaviour的GameEvent监听了MsgEvent.EVENT_ONCE_START

自定义的GameProgress也监听了

watch& Boss的射击

拆成了几个组件

ShootCtrl一把武器,以下所有武器组件的管理者

ShootCtrls如果有多把武器

//

//武器组件

BulletSound音效

BulletLoad每轮子弹加载时间, 计时加载时间,计数剩余子弹

BulletShoot每颗子弹射击间隔,计时射击时间

BulletModel子弹相关数据,比如子弹加速度方向

BulletPointsCalcEllipse子弹初始位置,发现大部分是Ellipse椭圆,所以标识出来

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

射击调慢时

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

射击调快时

可以看到两种子弹,也就是两把枪共用一个枪口的原因

初步怀疑是两把枪

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

modify 尝试恢复成两把枪(设置Sprite)

1 是一轮子弹的装载量(用完需要加载,需要LoadTime来架子啊下一轮)

2 是射击速度(每隔射击时间,就射出一颗子弹)

3 是多枪口时(比如Boss, 枪口初始位置一般都是时飞机头,子弹位置有单独的BulletPathCalc组件来计算)

4 时预制体默认设置的单枪口(多枪口时不用它,可能Muzzles的局部位置用到它.其实比较麻烦)

//

这时命名为枪口Muzzle已经不合适了,比如Muzzxles节点下的第一个Muzzle,有3个射击位置,每个射击位置有2个射击方向

游戏"群星"看起来是命名为"武器接口",有大中小(SML)型武器

//

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

modify 后面把一把枪也整合到多把枪的代码中,这样统一点

1是单枪口

2是多枪口

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

watch 精英怪的W路径(找Boss撞击时看到的)

02 发现PathState

PathMgr.GetDir()

PathBase中PathState字段最接近

其中WPath:PathBase具体实现了

所以在IPath接口写了GetPathState()

PathBase写了virtual,返回NULL

WPath写了override

而EnterPath2Path返回NULL

public enum PathState
{
    NULL,//自己加的
    ENTER,
    FORWARD_MOVING,
    BACK_MOVING
}

02 WPath会使用到PathState

02 打点发现WPath是精英怪的

watch Boss的撞击(采用自定义类出现的彩蛋)

01 watch

可以观察到时直来直往的

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

01 整理Path相关

IPath

EnterPath:IPath, 管理IEnterPath的实现类,这个类的命名可能不是很准确(我用的名字是EnetrPath2Path)

IEnterPath,上到下,左右互到,主要是fromPos, toPos

IPathData

IPathCalc

//再次修改

IPta, 原来的IPath的部分方法,因为PathMgr也用到了

IPathBase, 原来的IPath

EnterPathMgr, 原来的EnterPath2Path

其它不变

//举例Boss描述

Boss的PathMgr,管理着EllipsePath

EllipsePath的抽象父类PathBase声明了PathState, IPathData, IPathCalc

EllipsePath引用了EnterPathMgr

EnterPath管理着各种EnterPath

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

01 watch Path与Trajectory名字的取用,暂时用Path

    /// 无时间信息的路径
    public const string Path = "Path";
    /// 轨迹,路径(Path)和轨迹(Trajectory)的区别就在于,轨迹还包含了时间信息
    public const string Trajectory = "Trajectory";

02 显示Boss的PathMgr用到的PathName

方法一 在实现类中直接返回, 比较累, 每个实现类都要重写

方法二 各种EnterPath原来用的是接口, 所以用抽象类来写, 接口的还是接口, 减少修改

public interface IPathName
{
    string PathName();
}
public abstract class PathNameBase : IPathName
{
    public virtual string PathName()
    {
        return this.GetType().Name;
    }
}

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

02 Boss的路径的变化

右边Inspector观察PathName

发现Boss显示EnterPath的Up2DownEnterPath, 到达指定位置后转EllipsePath

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

Ration那个是出现在屏幕中百分之几的高度

XRadius时椭圆x半轴长, yRadius是y半轴长(说是半径又不适合,半径跟圆心相关)

可以看到Precision就是在椭圆边上打多少个点

//

自己看了,索引0和19时同一个点(-1,5.3)//5.3=椭圆中心位置高度4.

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

03 Boss移动时抖一下的索引

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

03 Boss撞击的索引

撞击时索引在13,14(15)

13到14时撞过去,14到15是返回来

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

04 在返回来看那个椭圆边上的点的位置数组, 整体的移动

上图

红色部分的左右分别是椭圆上半圈和椭圆下半圈的y

蓝色是索引从-1到1(椭圆上半圈),再从1到-1((椭圆下半圈)的x

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

为什么不是y=0,因为是在屏幕的上半部分,yRatio=0.8,屏幕百分之80的高度

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

解释路径的算法

_data.Center.y如果采用自定义的Ellipse.Cenetr.y,中心y为5.3,Boss就会一直转圈圈

如果用 _data.Center.y( 6*0.8=4.8 )就会撞击

	if ((yArr[0] - _data.Center.y).Abs()  

在索引i=5,最接近中心y轴,所以取小y=4.8(椭圆中心=5.3, 大y=5.8, yRadius=0.5看配置文件).

取上一个索引,i=4,y=5.8, dir=4.8-5.8=-1左右的向量y轴移动

//

在索引i=14,最接近中心y轴,没有设置就是默认为0

取上一个索引i=13,y=4.8, dir=0-4.8=-4.8左右的向量y轴移动

(可以尝试posArr[14] = new Vector3(0,-10)😉, dir=-10-4.8=-14.8左右的向量y轴移动看看效果如下

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

	/// 图形边长的点坐标	
	private Vector3[] InitSidePointPosArr(EllipsePathData ellipse)
	{
		#region 数据
		/**
		"ELLIPSE": [
		{
			"YRatioInScreen": 0.8,
			"XRadius": 1,
			"YRadius": 0.5,
			"Precision": 20
		}        
		*/
		#endregion  
		int precision = (ellipse.Precision).MultipleMore( 4);//一圈的精确度 20
		float xLeft = _ellipse.Left.x;
		//x轴上的坐标分成多少份,举例,顶点为4个,x轴上坐标要被分成2份
		float xTmp = xLeft;//这个循环中变量
		float[] yArr;
		Vector3[] posArr = new Vector3[precision];
		int halfPrecision = precision / 2;  //上下两份,理解为半圈的精确度 10
		float xOffset = (float)_ellipse.XAxis / halfPrecision; // 2/10=0.2
		int symIdx = 0;//对称的索引,精确度20个点, 0对19, 1对18, 2对17
		//posArr[14] = new Vector3(0,-10);//如果想尝试
		for (int i = 0; i  _data.Center.y)//上面的
				//{
				//    posArr[i] = new Vector3(xTmp, yArr[0]);
				//    posArr[symIdx] = new Vector3(xTmp, yArr[1]);  //对称的
				//}            
			}
			xTmp += xOffset;
		}
		return posArr;
	}

bug Boss出现后一动不动

PathMgr管理两种Path

以Boss举例,开始时U盘DownEnterPath,冲过指定位置(这是个变量和Camera相关), 就是常规的Path,Boss打点一下好像是EllipsePath

问题就在变量上, 所以 _toY改成方法或属性mToY .

需要改的还有Left2RightEnterPath, RIght2LeftEnterPath

public class Up2DownEnterPath : IEnterPath, ICanGetUtility
{
    private Transform _trans;
    private float _halfHeight;
    private Vector3 _fromPos;
    private float _fromY;
    private float mToY 
    { get { return this.GetUtility().CameraMinPoint().y + _halfHeight; } }
    ......

watch Factory

发现用枚举来new不同对象时用Factory(静态类)命名的很多

EnetrPathFactory

PathFactory

modify PathMgr合IPath=>PathBase=>实现类有很多相似之处

#region IPath ,PathBase
/// 这名字原本是IPathBase的,有部分方法PathMgr完全一样,所以再拆分组合
public interface IPath
{ 
	Vector3 GetFromPos(int id);
	Vector2 GetDir();
	PathState GetPathState();
	bool FollowCamera();
}
/// 
/// 路径接口,提供具体的路径的计算方法
/// 
public interface IPathBase :IPath
{
	void Init(Vector3 startPos, SpriteRenderer sr, IPathData pathData);
	void Init(Transform t, IPathData pathData);
}

bug&star 情况不统一导致的枪口位置

非Boss敌人生成会进行旋转向下,枪口位置动态变化

但是Boss生成不用旋转, 图片直接就是向下的原图,那么预制体初始枪口的位置(位置稍微靠上)就有问题了

需要动态设置和回收时重置

//

localPosition是属性,直接操作不了

Y反转如下,XZ和position类似

    public static Transform ReverseLocalPosY(this Transform t)
    {
        Vector3 v = t.localPosition;
        v.ReverseY();
        t.localPosition = v;
        return t;
    }
    public static Vector3 ReverseY(ref this Vector3 v)
    {
        float y = v.y;
        v.y = -y; 
        return v; 
    }

modify 拆分CollideMsgComponent

原来这两个脚本上合在一个脚本的

…//默认英文标点符号,方便敲代码

目的是为了Trigger2DComponent 的复用,以及节点清晰

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

Trigger2DComponent

/****************************************************
    文件:Trigger2DComponent.cs
    作者:lenovo
    邮箱: 
    日期:2024/4/6 19:37:56
    功能:
*****************************************************/
using QFramework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
/// 提供一个Trigger,处理外部的事件
[RequireComponent(typeof(Collider2D)) ]   
public class Trigger2DComponent : MonoBehaviour  
{
    public List EnterLst = new List();
    //public List> ExitLst = new List>();
    //public List> StayLst = new List>();
    public Trigger2DComponent InitComponent(Rigidbody2D rigidbody2D)
    {
        rigidbody2D.gravityScale = 0;
        return this;
    }
    private void OnTriggerEnter2D(Collider2D otherCollider)
    {
        //foreach (var item in EnterLst)//这种报错
        //{
           // item(otherCollider);
        //}
        for (int i = 0; i  

CollideMsgComponent

/****************************************************
	文件:CollideMsgComponent.cs
	作者:lenovo
	邮箱: 
	日期:2024/6/10 14:33:6
	功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
	
															 
public class CollideMsgComponent : MonoBehaviour
{
	Trigger2DComponent _trigger2DComponent;
	public CollideMsgComponent InitComponent(Trigger2DComponent trigger2DComponent)
	{
		_trigger2DComponent	= trigger2DComponent;	
		trigger2DComponent.EnterLst.Add(A);
		return this;
	}
	private void A(Collider2D otherCollider)
	{
		//Debug.Log(CommonClass.Log_ClassFunction() + $"\n:{otherCollider.gameObject.name}碰撞{gameObject.name}");
		Transform self = _trigger2DComponent.transform;
		Transform other = otherCollider.transform;
		//
		List selfMsg = self.GetComponentsInChildren().ToList();
		List otherMsg = otherCollider.GetComponentsInChildren().ToList();
		//需要判空
		selfMsg.ForEach(colliderMsg => colliderMsg.CollideMsg(other));
		otherMsg.ForEach(colliderMsg => colliderMsg.CollideMsg(self));
	}
}

star IConfig,IConfigPath

modify 使用 一个配置文件对应一个类

使用类似,这样就可以方便地找到数据模型类对应的配置文件

多个数据模型类对应一个配置文件,就用抽象类,在抽象类里面写路径

public class EnemyData   :IJson
{
    public int id;
    public double attackTime;
    public int attack;
    public double fireRate;
	......
    public string ConfigPath()
    {
      return ResourcesPath.CONFIG_ENEMY;
    }

modify 使用 一个配置文件对应多个类 结合基类

#region ICreaterData
/// LevelEnemyDataConfig
public interface ICreatorData
{
    
}
public abstract class CreatorDataBase : ICreatorData
{
    public string ConfigPath()
    {
        return ResourcesPath.CONFIG_LEVEL_ENEMY_DATA;
    }
}
public class PlaneCreatorData : CreatorDataBase    //json数据没乱改字段名
{    
......
/// 导弹
public class MissileCreatorData : CreatorDataBase
{
......

star IConfig,IConfigPath

IConfig表示这个类关系配置文件,不能单独改

IConfigPath表示这个类有相应的配置文件路径

#region IConfig
/// 
/// 啥都没有,表明这有关配置文件,不要随便改字段名
/// 
或者可以加config路径方便查找。但我暂不需要 /// public interface IConfig { } public interface IJson : IConfig { } #endregion #region IConfigPath public interface IConfigPath { string ConfigPath(); } public interface IJsonPath : IConfigPath { } ......

star MultipleMore MultipleLess 最接近num的factor的最倍数

public static partial class ExtendMathNumber //最小最小倍数
{
    /// 补足,返回factor的倍数,大于等于num的factor的最小倍数
    public static int MultipleMore(this int num, int factor)
    {
        if (num 8,9=>12
            }
        }               
    }
    /// 砍掉,返回factor的倍数,小于等于num的factor的最大倍数
    public static int MultipleLess(this int num, int factor)
    {
        if (num 1, (4,5)=> 1
        {
            return 1;
        }
        else
        {
            if (num % factor == 0)
            {
                return num;
            }
            else
            {
                return (num / factor) * factor ; //(7,4)=>4,(9,4)=>8
            }
        }
    }
}

star 一轮

    /// 一轮后重新计算
    /// 比如12生肖,13年后还是1
    public static int Round(this int num, int round)
    { 
           return num%round; 
    }
}

star 向量指向

吃屎喝我自定义的枚举Dir冲突后,选择改成EDir(EnumDir的意思),这个永远不会冲突

	    /// 符合直觉点
    public static Vector3 Dir(this Vector3 fromDir, Vector3 toDir)
    {
        return toDir - fromDir;
    }
    public static Vector3 Dir(this Vector3[] posArr, int fromIdx, int toIdx)
    {
        return posArr[toIdx] - posArr[fromIdx];
    }

star Camera深度模式下的宽和高,整理Camera

public static partial class ExtendCamera//深度模式下的宽和高
{   
    
    /// 宽/高
    public static float Aspect(this Camera camera)
    {
        return camera.aspect;
    }
    #region Size Height Width
    public static float OrthographicHeight(this Camera camera)
    {
        return   camera.orthographicSize * 2.0f;
        //return (camera.OrthographicMaxPoint() - camera.OrthographicMinPoint()).y;
    }
    public static float OrthographicWidth(this Camera camera)
    {
        return camera.Aspect() * camera.OrthographicHeight();
        //return (camera.OrthographicMaxPoint() - camera.OrthographicMinPoint()).x;
    }
    public static Vector2 OrthographicSize(this Camera camera)
    {
        return new Vector2(camera.OrthographicWidth(),camera.OrthographicHeight());
    }
    #endregion
    #region min max Point
    public static Vector2 OrthographicMaxPoint(this Camera camera)
    {
        var pos = camera.transform.position;
        var size = camera.OrthographicSize();
        var maxPoint = ExtendVector.Add(pos, size * 0.5f);
        return maxPoint;
    }
    public static Vector2 OrthographicMinPoint(this Camera camera)
    {
        var pos = camera.transform.position;
        var size = camera.OrthographicSize();
        var minPoint = ExtendVector.Sub(pos, size * 0.5f);
        return minPoint;
    }
    #endregion
}

bug Boss死了但是显示游戏输了

GameProgressSystem监听属性是,不监听就默认 lose

this.GetModel().IsFinishOneLevel;

找到Boss死亡的位置

GameProcessSystem监听MsgEvent.EVENT_LEVEL_END, 触发LevelEnd

所以找SendMsg的地方

//

有跳到SendMsg的地方

有跳到LevelEnd方法

bug Boss血扣了但是血条有时没变化

测试发现_minHp=0,没变化

猜测从对象池去除对象的血条Bar没处理好初始化,

Boss2000血,看到有50血的情况出现, 出现0的情况更多(2000血,应该是200,400, 600… 结果看都是0,0,0)

//

LifeComponent会SendMsg hp合hpMax,很可能LifeItem 的监听慢于SendMsg ,导致初始化失败,血条情况还是老的(上一次死在对象池).

01 视觉上, 所以EnemyLifeComponent .OnDisable()记得Show,不然取出的老的飞机有时会缺血块

02 数值上, 手动控制LifeItem的Init,一是没必要重复计算eachLife,二是触发一次血块(监听的初始值问题,你发了,但我还没AddListener)

modify EnemyLifeComponent的

public class EnemyLifeComponent : MonoBehaviour
{
	private LifeComponent _lifeComponent;
	private int LifeItemCount { get { return transform.childCount; } }
	public EnemyLifeComponent Init(LifeComponent lifeComponent, SpriteRenderer sr)
	{
		_lifeComponent = lifeComponent;
		float tarDRealRatio = GetRatio(sr);
		transform.localScale *= tarDRealRatio;//缩放10个血块
		transform.position=	InitPos(sr); //血块Pos
        InitLifeItems();
		return this;
	}
    private void OnDisable()
    {
        foreach (Transform trans in transform) //预制体自带了10个LifeItem
        {
            trans.Show();//这样也行,不用特意地Init,(基类操作了),Show加上去自动Show
        }
    }
    #region pri
    private void InitLifeItems()
	{
		int eachLife = _lifeComponent.LifeMax / LifeItemCount;//摘出来,防重复计算
		foreach (Transform trans in transform) //预制体自带了10个LifeItem
		{	
			trans.GetOrAddComponent().Init(eachLife);//这样也行,不用特意地Init,(基类操作了),Show加上去自动Show
		}
	}

modify LifeItem的

public class EnemyLifeItem : SetPosZByLayerLevelView	 ,ICanGetSystem
{
	[SerializeField]	MessageMgrComponent _messageMgrComponent;
    /// 不确定InitComponent是否跑了
    [SerializeField] bool _initComponent=false;
	[SerializeField] int _minHp=0;
	public override Entity2DLayer E_Entity2DLayer
	{
		get
		{
			return Entity2DLayer.EFFECT;//层级Posz在Effect上,父节点不在Effect上
		}
	}
    public void Init(int eachLife)
    {
        _messageMgrComponent = transform.GetComponentInParentRecent();
        _messageMgrComponent.AddListener(MsgEvent.EVENT_HP, UpdateLife);
		//
        //比如2000血分成10块,那就是200血/块
        _minHp = transform.GetSiblingIndex() * eachLife;//第3节点第3块,就是600血
		_initComponent = true;
    }
    ......

bug Boss有时子弹打不了,也撞不死

出现在马上按"B"键生成Boss

现在不会了,恢复不到Bug时候

bug Boss的LifeItem有时看不到

将基类 transform.SePosZ(z); 改成 transform.SetLocalPosZ(z);

起码不会出现-4.2, 2.77775之类的

//

仔细看,LifeItem的父节点EnemyLifeComponent进行了缩放,

整个EnemyLfie预制体的scale也不是(1,1,1)

导致PosZ有时小于飞机的PosZ,那就被挡住了

//

LiefItem是比较特殊的, 它的祖宗节点是PLANE下的飞机, PosZ是EFFECT相同的PosZ

/// 根据层次枚举设置posZ
public abstract class SetLocalPosZByLayerLevelView : GameLevelViewBase
{
    protected override void OnEnable()
    {
        float z = E_Entity2DLayer.Enum2Int();
        transform.SetLocalPosZ(z);
        Init();
    }
}

bug 碰撞逻辑做得有点乱

出现在调试去掉玩家的CollideMsgFromPlaneComponent和单/多枪口整合时(枪口挂着CollideMsgFromBulletComponent)

处理好了暂且用着

modify 定义Attribute从定义到装入字典的整个过程

使用情景

使得

Dictionary

Dictionary

之类的操作更加规范方便

//

主要时尝试统合两个Attribute, 但是静态类就用不了接口

接口是暂时统合的结果

//

需要注意的是IBulletModelUtil需要在Pool之前注册,Pool生成子弹,

子弹需要BulletModel, 顺序不正确,撞击就报空了

//

两个Util, BulletModelUtil, BindPrefabUtil还是分开比较好

使用举例

using IAddTypeByAttribute = ExtendAttribute.IAddTypeByAttribute;
public class CustomAttributesUtil : IInit  ,ICanGetUtility
{
    public void Init()
    {
        ExtendAttribute.InitData(this.GetUtility().Init);
        ExtendAttribute.InitData(this.GetUtility().Init);
    }
    #region 实现
    public IArchitecture GetArchitecture()
    {
        return AirCombatApp.Interface;
    }
    #endregion
}
#region BulletModelUtil
public interface IBulletModelUtil :IUtility, IAddTypeByAttribute
{
    IBulletModel GetBulletModel(BulletType type);
}
public   class BulletModelUtil : IBulletModelUtil
{
    private static Dictionary _bulletDic = new Dictionary();
    #region IBulletUtil
    /// 
    ///  bulletType,类前标签
    ///  type ,实现类BossBullet:IBullet
    ///  这里字典Dic,A是attribute中的一个字段类型
    /// 
    public  void Init(Attribute atb, Type type)
    {
        BulletAttribute after = atb as BulletAttribute;
        if (!_bulletDic.ContainsKey(after.E_BulletType))
        {
            _bulletDic.Add(after.E_BulletType, ExtendAttribute.GetInstance(type));
        }
        else
        {
            Debug.LogError("当前数据绑定类型存在重复,重复的类名称为:" + _bulletDic[after.E_BulletType] + "和" + type);
        }
    }
    public  IBulletModel GetBulletModel(BulletType type)
    {
        if (_bulletDic.ContainsKey(type))
        {
            return _bulletDic[type];
        }
        else
        {
            Debug.LogError("BulletUtil当前未绑定对应类型的数据,类型为:" + type);
            return null;
        }
    }
    #endregion
}
#endregion
#region BindPrefabUtil
public interface IBindPrefabUtil : IUtility, IAddTypeByAttribute
{
    List GetType(string path);
}
public  class BindPrefabUtil : IBindPrefabUtil
{
    private static readonly Dictionary _pathDic = new Dictionary();
    private static readonly Dictionary _priorityDic = new Dictionary();
    #region 辅助
    /// 初始化内部字典
    public   void Init(Attribute atb, Type type)
    {
        BindPrefabAttribute after= atb as BindPrefabAttribute;
        string path = after.Path;
        if (!_pathDic.ContainsKey(path))
        {
            _pathDic.Add(path, new List());
        }
        if (!_pathDic[path].Contains(type))
        {
            _pathDic[path].Add(type);
            _priorityDic.Add(type, after.Priority);
            _pathDic[path].Sort(new BindPriorityComparer());
        }
    }
    /// 根据路径返回类型
    public  List GetType(string path)
    {
        if (_pathDic.ContainsKey(path))
        {
            return _pathDic[path];
        }
        Debug.LogError("当前数据中未包含路径:" + path);
        return null;
    }
    #endregion
    #region 内部类
    /// 预制体优先级比较器
    class BindPriorityComparer : IComparer
    {
        public int Compare(Type x, Type y)
        {
            if (x == null)
            {
                return 1;
            }
            if (y == null)
            {
                return -1;
            }
            return _priorityDic[x] - _priorityDic[y];
        }
    }
    #endregion
}
#endregion

复用模块

public static partial class ExtendAttribute
{
    public interface IAddTypeByAttribute
    {
        void Init(Attribute atb, Type type);
    }
    /// T用接口,比如IBulletModel,实际是实现了接口的类
    public static T GetInstance(Type type) where T : class
    {
        object instance = Activator.CreateInstance(type);
        if (instance is T)
        {
            return instance as T;
        }
        else
        {
            Debug.LogError($"当前绑定类未继承{typeof(T)}接口,类名为:" + type);
            return null;
        }
    }
    /// 
    ///  T xxxAttribute
    ///  Type 系统类
    /// 
    /// 
    /// 
    public static void InitData(Action cb) where T : Attribute
    {
        Type[] types;
        types = ExtendReflection.GetTypes();
        foreach (var type in types)
        {
            foreach (var attribute in Attribute.GetCustomAttributes(type, true))
            {
                if (attribute is T)
                {
                    T data = attribute as T;
                    cb(data, type);
                }
            }
        }
    }
}

bug Boss死后curLevel+=2

01 一个是DeadEnemyCommand,Boss时会发送一次LevelEnd事件

02 触发了GameProgressSystem对该事件的监听,触发了LevelEnd方法

03 LevelEnd方法会修改StateModel的值,修改值又会再发送一次LevelEnd事件

04 敌人关卡数据只有两关,所以就超出范围了

//

总结是逻辑错误

修改01,直接修改StateModel中LevelState的值,让StateMOdel自己去发送LevelEnd事件

修改02,方法中不能有LevelState=LevelState.End,这都死循环了

//

下图是2秒刷新一次数据的脚本,主要 看杀死Boss后CurLevel

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

AirCombatInspector

/****************************************************
    文件:AirCombatInspector.cs
	作者:lenovo
    邮箱: 
    日期:2024/6/19 12:46:38
	功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
 
namespace  QFramework.AirCombat
{
    public class AirCombatInspector : MonoBehaviour   ,IController
    {
        #region 属性
        [SerializeField] float _timing = 0f;
        [SerializeField] IAirCombatAppModel _model; 
        [SerializeField] IAirCombatAppStateModel _stateModel; 
        [Header("Plane")]
        [SerializeField] int SelectPlaneID;
        /// 子弹样式
        [SerializeField] int TmpPlaneLevel;
        [Header("Hero")]
        /// 实际使用的是在父节点中的索引
        [SerializeField] int SelectHeroID;
        [Header("Game")]
        [SerializeField] GameState E_GameState;
        [Header("Level")]
        [SerializeField] LevelState E_LevelState;
        /// 实际使用的是在父节点中的索引
        [SerializeField] int SelectedLevel;
        [SerializeField] int CurLevel;
        /// 已经通关数.+1就是最大的可以解锁的关卡
        [SerializeField] int PassedLevel;
        [SerializeField] bool IsFinishOneLevel; 
        #endregion
        #region 生命
        /// 首次载入且Go激活
        void Start()
        {
            _timing = 0f;
            _model = this.GetModel();
            _stateModel = this.GetModel();
        }
         /// 固定更新
        void FixedUpdate()
        {
            _timing = this.Timer(_timing,2f,()=>
            {
                IsFinishOneLevel = _stateModel.IsFinishOneLevel;
                SelectedLevel = _stateModel.SelectedLevel;
                CurLevel = _stateModel.CurLevel;
                PassedLevel = _model.PassedLevel;
                E_GameState = _stateModel.E_GameState;
                E_LevelState = _stateModel.E_LevelState;
            }); 
        }
        
        public IArchitecture GetArchitecture()
        {
            return AirCombatApp.Interface;
        }
        #endregion
    }
}

整理GameProgressSystem两关间的切换

定义与周期:IUnRegisterList

实现:IUnRegisterList的生命

{

public List UnregisterList{ get { return _unregisterList; } }

List _unregisterList = new List();

public void DestroyFunc()

{

this.UnRegisterAll();

}

}

Model.Register的使用

放在GameProgressSystem的override OnInit()中

GameState.Start, End是开始战斗到死了/自行退出的周期

LevelState.Start, End是塞在GameState.Start, End的小周期

    private void OnInited()
    {
        this.GetSystem().Add(LifeName.UPDATE, this);
        this.GetSystem().Add(LifeName.DESTROY, this);
        //以下都是状态改变,以及之后的回调,所以在回调中不要写条件大的状态
        this.GetModel().E_GameState.Register(state =>
        {
            if (state != GameState.START) return;
            OnGameStart();
        }).AddToUnregisterList(this);
        this.GetModel().E_GameState.Register(state =>
        {
            if (state != GameState.END) return;
            OnGameEnd();
        }).AddToUnregisterList(this);
        this.GetModel().E_LevelState.Register(state =>
        {
            if (state != LevelState.START) return;
            OnLevelStart();
        }).AddToUnregisterList(this);
        this.GetModel().E_LevelState.Register(state =>
        {
            if (state != LevelState.END) return;
            OnLevelEnd();
        }).AddToUnregisterList(this);
    }
    #region OnStateXXX
    
    private void OnGameStart()
    {
        this.GetModel().TmpPlaneLevel.Value = this.GetModel().PlaneLevel;
        this.GetModel().E_LevelState.Value = LevelState.START;
    }
    
    /// OnLevelEnd(注意是后,方法是因变量)后会执行的
        this.GetSystem
        StateModel.IsFinishOneLevel.Value = false;
        this.GetSystem
        ClearData();
        StateModel.CurLevel.Value++;
        StateModel.IsFinishOneLevel.Value = true;
        this.GetSystem
        {
            StateModel.E_LevelState.Value = LevelState.START;
        });
    }
    #endregion

watch RegisterEvent 与MessageSystem的选择

监听的增加和移除,显然QF的管理比较容易

//

01 事件的发送,左边需要自定义事件

0201 QF如果在Model根据值的不同(圈圈里面),进行Switch,也需要自定义事件

0202 QF如果在(黑色线位置),就是注册监听时,在各自的方法写

state=> if(state!=)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

        Msg.AddListener(MsgEvent.EVENT_GAME_START, OnGameStart);
        //
        this.GetModel().E_GameState.Register(state =>
        {
            if (state != GameState.START)
            {
                return;
            }
        });

star ForeachAction

测试一种场景, 没报错

    private void OnTriggerEnter2D(Collider2D otherCollider)
    {
        EnterLst.ForeachAction(otherCollider);
    }
    public static void ForeachAction(this List lst, T t)
    {
        if (lst==null || lst.Count ==0)
        {
            return;
        }
        for (int i = 0; i  

star RR(this (float, float) os)

测试一种场景, 没报错

一开始是类内方法RR(float min,float max),不泛用.

//

用数组,列表要声明,比较麻烦,偶然发现能这样写

(float, float) 参数名

参数名.Item1, 参数名.Item2

    protected override IEffect[] GetEffects(Transform transform)
    {
		......
        slowSpeed = (startSpeed > 0) ? (0.3f, 1f).RR() : (-0.3f, -1f).RR();
		......
        
        return effects;
    }
    float RR(float min,float max)
    {
        return UnityEngine.Random.Range(min, max);
    }
public static partial class ExtendMathNumber
{
    public static float RR(this (float, float) os)
    {
        return UnityEngine.Random.Range(os.Item1, os.Item2);
    }
    

modify 部分代码插件化

尝试失败,以后再试.

主要原因是 QF要使用Command等需要 return AirCombatApp.Interface. 不符合目的

modify 初始化顺序

bug 属性

_mono在System初始化时赋值,但是发生_mono使用时为空

改为属性

public class CoroutineSystem : QFramework.AbstractSystem,ICoroutineSystem  
{
	......
    private   MonoBehaviour _mono;
    MonoBehaviour Mono
    {
        get
        {
            if (_mono == null)
            {
                // _mono = new MonoBehaviour(); //这种是不行,为null
                Transform t = this.GetSystem().GetOrAdd(GameObjectPath.System_CoroutineSystem);
                _mono = t.GetOrAddComponent(); //这种是不行,为null.readonly只能在和实例和构造时使用
                _mono.CkeckNull();
            }
            return _mono;
        }
    }

bug 模块间顺序

01 之前直接设置GameState.Start, 游戏开始跑, 但是GameDataMgr初始化未完成,导致取AllBulletModel报错

//

01 现在改成,GameDataMgr初始化完成, 会被NSOrderSystem 监听

02 GameStateStartCommand 设置GameState.Start时用一个协程, 只有在NSOrderSystem 的相关监听属性为true时,才会设置GameState.Start

NSOrderSystem

/****************************************************
    文件:SCOrderSystem.cs
	作者:lenovo
    邮箱: 
    日期:2024/7/1 13:9:14
	功能:发现很多单例,System,实例类等的依赖顺序,导致空指针
        所以做这个用event来控制
        即使不优雅,也算个汇总,方便以后改
//
        SIOrderSystem的SI,  Singleton+Instance,单例类常用的两个属性名
        ST,SC,static class,择用
*****************************************************/
using QFramework;
using QFramework.AirCombat;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class NSOrderSystem : NormalSingleton ,ICanRegisterEvent
{
    public bool GameDataMgrInited ;
    public void Init()
    {
        GameDataMgrInited = false;
        this.RegisterEvent(_=> GameDataMgrInited = true) ;
    }
    public IArchitecture GetArchitecture()
    {
        return AirCombatApp.Interface;
    }
}

GameStateStartCommand

    /// 改变状态有时需要等待其他模块的加载 
    public class GameStateStartCommand : AbstractCommand
    {
        protected override void OnExecute()
        {
          this.GetSystem().StartInner(GameStart());
        }
        IEnumerator GameStart()
        {             
            //第一种写法
            while ( !NSOrderSystem.Single.GameDataMgrInited)//等待条件
            {
                yield return new WaitForEndOfFrame(); //等一帧 
            }
             //上面条件改变跳出后,执行后面的
            this.GetModel().E_GameState.Value = GameState.START;//NULL!=START触发广播    
        }
    }

watch 奖励Item

01 进行打印日志,除了Star,只出现了AddBullet

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

02 Item来源于

    public static readonly string CONFIG_ENEMY = CONFIG_FOLDER + "/EnemyConfig.json";

03 数据时 “itemRange”: [0,0], || “itemRange”: [1,1], || “itemRange”: [2,3],

04 itenRange只有一处使用地方.

结合 出货率 “itemProbability”, 下面else的默认,所以ADD_BULLET次数多,逻辑上可以说得通

        private ItemType GetItemType(ItemType[] itemRange)
        {
            if (itemRange.Length == 2)
            {
                int index =  ((int)itemRange[0], (int)itemRange[1] + 1).RR();
                return index.Int2Enum();
            }
            else
            {
                return ItemType.ADD_BULLET;
            }
        }

05 ItemType的定义

public enum ItemType
{
	ADD_BULLET,
	ADD_EXP,
	SHIELD,
	POWER
}

bug Star的出局

01 如果用原来的的枚举RewardType, Star得单拎出来处理, 不优雅

所以新加了EItemType, 整体见 代码一段

02 用法如下, 代码二段 (E_ItemType定义抽象类里面)

#region 说明
/// 原名ItemType,但它不处理Star(不好统一处理),所以让名,改为RewardType
public enum RewardType
{
	ADD_BULLET,
	ADD_EXP,
	SHIELD,
	POWER
}
/// 杀死敌人后的Item奖励.本来就用ItemType,弃用
public enum EItemType
{
	STAR,
    /// 实际是一列变两列三列
  	ADD_BULLET,
    ADD_EXP,
    SHIELD,
    /// 原来叫POWER(改过Bomb),图片路径是Assets / Resources / Picture / Item / Power.png
    POWER,
    //TODO 
    BOSSBULLET1
}
#endregion
public static class SEffectContainerFactory
{
    public static IEffectContainer New(EItemType type)
    {
        switch (type)
        {
            case EItemType.STAR: return new StarEffectContainer();//属于必得奖励,不归ItemType管理
            case EItemType.ADD_EXP: return new DefaultEffectContainer();
            case EItemType.ADD_BULLET: return new DefaultEffectContainer();
            case EItemType.SHIELD: return new DefaultEffectContainer(); 
            case EItemType.POWER: return new DefaultEffectContainer();
            //TODO  :Boss1BulletEffectContainer
            // case Boss1BulletEffectContainer: return new StarEffectContainer();// 有交集,先放这里待处理
            default: return new DefaultEffectContainer();
        }
    }
}
public class AddExpItemView : ItemInPlaneLevelViewBase, ICanGetModel
{
    public override EItemType E_ItemType { get { return EItemType.ADD_EXP; } }
    protected override IEffectContainer GetEffectContainer()
    {
        return SEffectContainerFactory.New(E_ItemType);
    }
    ......

bug 奖励特效的交集

没必要分两个ADD_EXP, ADD_BULLET

/// 
/// 原来ADD_EXP, ADD_BULLET两个类都是一样的类体函数,参数完全没变,
/// 直接统一default
public class DefaultEffectContainer : EffectContainerBase
{
    protected override IEffect[] GetEffects(Transform transform)
    {
        IEffect[] effects = new IEffect[1];
        SlowSpeedEffect ySlow = new SlowSpeedEffect();
        ySlow.Init(transform, Vector2.up, 0, (2f, 4f).RR(), -5f);
        effects[0] = ySlow;
        return effects;
    }
}

watch Power,Shield使用没写,就只写一个监听的数量变化

可以看UI右下角的显示和最右边的Inspector

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug Model的xxx在xxxMax的限制

比如等级不大于最高等级之类,在Model中怎么处理

方式一(没有用) 加一个赋值限制的回调

缺点:

01 一不小心用.Value赋值

02 赋值限制可能有顺序问题(就是赋值限制的回调为空时,就开始赋值)

03 污染了Model,臃肿了

//

在QF这里这里写,定义一个Func(Func). 和一个limtValue(最大值,最小值)

	public class BindableProperty : IBindableProperty
	{
		......
		private Action mOnValueChanged = (v) => { };
        #endregion
        
        #region pub
        /// 
		/// 也就是单独的一个mValue = newValue
		/// 暂时不清楚目的
		/// 
        public void SetValueWithoutEvent(T newValue)
		{
			mValue = newValue;
		}

方式二 用Command

至于详细到 change(变化量),set(变后值),要不要进行?我这里 change==set,因为c在26字母靠前,方便找

缺点:

01 一不小心用.Value赋值

    public class ChangeTmpPlaneLevelCommand : AbstractCommand
    {
        int _tmpPlaneLevel;
        public ChangeTmpPlaneLevelCommand(int tmpPlaneLevel)
        {
            _tmpPlaneLevel = tmpPlaneLevel;
        }
        protected override void OnExecute()
        {
            int planeLevelMax = this.GetModel().PlaneLevelMax;
            if (_tmpPlaneLevel 
                this.GetModel
    "fireRate": 1,
    "bulletSpeed": 5,
    "trajectoryType": 0,
    "trajectory": [
      [
        90
      ],
      [
        95,
        85
      ],
      [
        100,
        90,
        80
      ],
      [
        105,
        95,
        85,
        75
      ],
      [
        110,
        100,
        90,
        80,
        70
      ]
    ]
  },

        if (Input.GetKeyDown(KeyCode.L))
        {
            this.SendCommand(new ChangeTmpPlaneLevelCommand(_tmpPlaneLevel));
        }
   ......

	#region 属性
	//[SerializeField] private bool _invincible;
	public bool Invincible { get = this.GetModel().Invincible; set => this.GetModel().Invincible.Value = value; }
	[SerializeField] private float _invincibleTimer;
	[SerializeField] private float _invincibleTime;
	[SerializeField] private SpriteRenderer _sr;
	/// 有点像状态机的Enter
	[SerializeField] private bool _onInvincibleEnter;
	[SerializeField] private bool _fade;
	   #endregion
	#region 生命
	public InvincibleComponent InitParas(params object[] os)
	{
		if (os != null && os.Length == 2)
		{
			_invincibleTime = (float)os[0];
			_sr = (SpriteRenderer)os[1];
			Invincible =false;
			_onInvincibleEnter = false;
			this.GetSystem().Add(LifeName.UPDATE,this);
			return this;
		}
		throw new System.Exception("异常:参数");
	}
	public int Framing { get; set; }
	public int Frame { get; }
	float t2;
	public void FrameUpdate()
	{ 
		if (Invincible)//霸体就进行计时
		{
			if (!_onInvincibleEnter)  //相当OnStateEnter
            { 
				//int loopCnt = 1;
				//float t1 = _invincibleTime ;   //时间或者帧数
				// t2 = (t1/ loopCnt) / 2f;//来回
				_sr.DOFade(0.5f, 0.1f) ;
				_onInvincibleEnter = true;
			}
			//
			_invincibleTimer = this.Timer(_invincibleTimer, _invincibleTime, () =>
			{
				//相当OnStateExit
				_invincibleTimer = 0f;
                Invincible = false;
				_onInvincibleEnter = false;
				_sr.DOFade(1f, 0.1f);
			});
		}  
	}
	#endregion
	public IArchitecture GetArchitecture()
	{
		return AirCombatApp.Interface;
	}
}

CollideMsgFromPlaneCommand

    /// 挂在Plane(敌人,玩家)的CollideMsg
    public class CollideMsgFromPlaneCommand : AbstractCommand
    {
        private IDespawnCase _destroyCase;
        private IBullet _selfBullet;
        //挂Tag的几节点
        Transform _other;
        Transform _self;
        InvincibleComponent _invincibleComponent;
        public CollideMsgFromPlaneCommand(IDespawnCase destroyCase, IBullet selfBullet, Transform self, Transform other, InvincibleComponent invincibleComponent=null)
        {
            _destroyCase = destroyCase ?? throw new ArgumentNullException(nameof(destroyCase));
            _selfBullet = selfBullet ?? throw new ArgumentNullException(nameof(selfBullet));
            _other = other ?? throw new ArgumentNullException(nameof(other));
            _self = other ?? throw new ArgumentNullException(nameof(self));
            _invincibleComponent = invincibleComponent ;
        }
        protected override void OnExecute()
        {
            IBullet otherBullet = _other.GetComponentInChildren(); //
            if (_other.tag == Tags.BULLET && otherBullet != null && _selfBullet != null
                && otherBullet.ContainsDamageTo(_selfBullet.Owner)) //BulletEnemyCtrl//受伤
            {
                _destroyCase.DoIfNotNull(() =>
                {
                    if (_self.CompareTag(Tags.PLAYER)) STest.PlayerCollidedByBulletCnt++;
                    // STest.Injure(_destroyCase, otherBullet, _other, _invincibleComponent);//也就是以下
                    _destroyCase.DoIfNotNull(() =>
                    {
                        if (_invincibleComponent != null)
                        {
                            //_destroyCase.Injure(-otherBullet.GetAttack());
                            _destroyCase.Injure(-1);
                            _invincibleComponent.Invincible = true;
                        }
                        else
                        {
                            _destroyCase.Injure(-otherBullet.GetAttack());
                        }
                    });
                    return;
                    _destroyCase.Injure(-otherBullet.GetAttack());
                });
            }
            else if (_selfBullet != null && _selfBullet.ContainsDeadFrom(_other.tag)) //死亡
            {
                if (_self.CompareTag(Tags.PLAYER)) STest.PlayerCollidedByPlaneCnt++;
                //STest.IsBossPlane(_other.GetComponentInChildren()); //也是以下
                bool invincible = this.GetModel().Invincible;
                _destroyCase.DoIfNotNull(() =>
                {
                    if (_other.CompareTag(Tags.ENEMY))//other是敌人,所以这是玩家
                    {
                        //_destroyCase.Injure(-otherBullet.GetAttack());石
                        _destroyCase.Injure(-1);
                        this.GetModel().Invincible.Value = true;
                    }
                    else if (_other.CompareTag(Tags.PLAYER))//敌人
                    {
                        if (invincible)//玩家无敌闪避,撞不死敌人
                        {
                            return;
                        }
                        _destroyCase.Dead();//敌人直接死
                    }
                });
                return;//测试太菜了,不能直接死
                _destroyCase.DoIfNotNull(() =>
                {
                    _destroyCase.Dead();
                });
            }
            else if (_other.tag == Tags.ITEM)   //Buff debuff
            {
                this.SendCommand(new CollideItemCommand(_other));
            }
        }
    }

star Color与Hex互转

Hex是因为unity的Color里面有十六进制,赋值起来相对于 RGB格式更加方便

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

public static partial class ExtendColor //16互转Color
{
    public static Color Hex2Color(this string hex)
    {
        if (hex.Length != 7 && !hex.Contains("#"))  //照理说末尾的/n应该是8
        {
            throw new System.Exception("异常:Hex2Color");
        }
        try
        {
            Color color;
            ColorUtility.TryParseHtmlString(hex, out color);
            //int len=hex.Length;
            return color;
        }
        catch (Exception)
        {
            throw new System.Exception("异常:Hex2Color");
        }
    }
    /// 
    public static string Color2Hex(this Color color)
    {
        try
        {
            string hex = ColorUtility.ToHtmlStringRGB(color);
            return hex;
        }
        catch (Exception)
        {
            throw new System.Exception("异常:Color2Hex");
        }
    }
}

bug PlayerPrefs.DeleteAll();没有立即删除

写了一个测试脚本, this.GetUtility().ClearAll();底层是PlayerPrefs.DeleteAll();

有时停止运行再开始运行,设置了开始ClearAll(),但是值还是不变(0level, 意思是id为0的飞机的等级的key). 升级后清空还是非0级,

//

方式一 停止运行再开始运行的时间间隔长一点,可以避免这个未及时清空

方式二 或者存一个key的列表,ClearAll重写为foreach(key列表)逐个Delete

namespace QFramework.AirCombat
{
    public class AirCombatInspectorCtrl : MonoBehaviour ,IController
    {
        [SerializeField] PlanePlayerCtrl Player;
        [SerializeField] bool _listenPlayer;
        [SerializeField] int _tmpPlaneLevel;
        //
        /// 升满级了,就不能突出飞机升级图片的变化
        [SerializeField] bool _clearAllPlaterPres;
        public IArchitecture GetArchitecture()
        {
            return AirCombatApp.Interface;
        }
        private void Start()
        {
            { //InitpLayer之前监听
                _listenPlayer = false;
                this.RegisterEvent(_ => _listenPlayer = true);
            }
            //
            if (_clearAllPlaterPres)
            {
                this.GetUtility().ClearAll();
            }
        }
        ......

modify 两个PlaneLevel的分清

01 一个是升级就改飞机图片,每种id的飞机四张图 id_0 id_1 id_2 id_3,所以初始等于3(按索引值计算,条件范围包括3)

02 一个时战斗时升级就加子弹列数,原来叫TmpPlaneLevel,我改成 PlaneBulletLevel和PlaneBulletLevelMax .主要看配置文件BulletConfig的数组长度,我加了一个,所以是4(也是按索引值计算,从0开始)

//

之前测试飞机选择就出错了,两个Level混一起,在等于4时,就越界了(max3)

	public interface IAirCombatAppModel : IModel
	{
		.....
        /// 
        /// ,0_0 0_1 0_2 0_3, 一个id四个level图	  ,
        /// 注意和PlaneBulletLevelMax区别开来
        /// 
        BindableProperty SelectedPlaneSpriteLevelMax { get; }
        /// 该你来拿子弹列数的haul,玩家是12345,数组索引是01234,所以等于
        BindableProperty PlaneBulletLevelMax { get; }
{
  "PLAYER": {
    "fireRate": 1,
    "bulletSpeed": 5,
    "trajectoryType": 0,
    "trajectory": [
      [
        90
      ],
      [
        95,
        85
      ],
      [
        100,
        90,
        80
      ],
      [
        105,
        95,
        85,
        75
      ],
      [
        110,
        100,
        90,
        80,
        70
      ]
    ]
  },

bug 超出界限自动销毁在左右上的缓冲距离

//左右要多一个身位,玩家看不到再销毁

	public class AutoDespawnOtherCollideCameraBorderCommand : AbstractCommand
	{
		......
		bool OutLeft()
		{
			float x1 = _sr.BoundsMinX();
			float x2 = this.GetUtility().CameraMinPoint().x- _sr.BoundsSize().x;     //左右要多一个身位,玩家看不到再销毁
            if (x1  x2)
			{
				//Debug.Log(x1 + "," + x2);
				return true;
			}
			return false;
		}
		#endregion
	}

bug 火箭没有发生Collide玩家

从PlaneEnemyCtrl赋值大部分

碰撞的结果就是那个命令 CollideMsgFromPlaneCommand, 触发IDestroyCase的Injure()或Dead()(因为测试,统一改成扣一点血)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

/// 导弹
public class MissileView : PlaneLevelView,IUpdate, QFramework.IController    ,ICollidePlayer
{
	[SerializeField]private int _numOfWarning;
	[SerializeField]private float _eachWarningTime;
	[SerializeField] private Action _endAction;
	[Header("主体")]
	[SerializeField] SpriteRenderer _sr;
	[SerializeField]Trigger2DComponent _trigger2DComponent;
    [SerializeField] BulletType _bulletType;
    [SerializeField] IBulletModel _bulletModel;
    [SerializeField] string _despawnKey;
	[Header("Bullet")]
    [SerializeField] private Transform _bulletTrans;
    [SerializeField] BulletModelComponent _bulletModelComponent;
    [Header("Move")]
	[SerializeField] private Transform _moveTrans;
	[SerializeField]private float _bulletSpeed;
	[SerializeField]private float _cameraSpeed;
	[SerializeField] MoveOtherComponent _moveOther;
	[SerializeField]private CameraMoveSelfComponent _cameraMove;
	[SerializeField]private bool _startMove;
	[SerializeField] private Vector2 _dir;
	[Header("Life")]
    [SerializeField] private Transform _lifeTrans;
    [SerializeField] AutoDespawnOtherComponent _autoDespawnOtherComponent;
    [SerializeField] DespawnBulletComponent _despawnBulletComponent;
    [Header("碰撞Collider")]
	[SerializeField] private Transform _colliderTrans;
	[SerializeField] private CollideMsgComponent _collideMsgComponent;
	//取决将火箭导弹看成是子弹还是飞机敌人
	//[SerializeField] private CollideMsgFromBulletComponent _collideMsgFromBulletComponent;
	[SerializeField] private CollideMsgFromPlaneComponent _collideMsgFromPlaneComponent;
	#region 生命	
	protected override void InitComponent()
	{
		GameObject go = gameObject;
		{
			_bulletType=BulletType.POWER;
			_despawnKey = ResourcesPath.PREFAB_ENEMY_MISSILE;
            _bulletModel = this.GetUtility().GetBulletModel(_bulletType);
            _trigger2DComponent = go.GetOrAddComponent().InitComponent();
			_sr = go.GetComponentOrLogError();
        }
        go.GetOrAddComponent();
		transform.FindOrNew(GameObjectName.Collider).GetOrAddComponent().Init(CollidePlayer);
		//导弹自身还有另外的MoveComponent,所以不能GetOrAdd
		float _cameraSpeed = this.GetModel().CameraSpeed;
		_cameraMove = go.GetOrAddComponent().InitComponent(_cameraSpeed);
		//
		_moveTrans = transform.FindOrNew(GameObjectName.Move);
		{
			//_dir = _pathCalc.GetDir().normalized;
			_dir = Vector2.down; ;
            _moveOther = MoveOtherComponent.InitMoveComponentKeepDesption(gameObject, _moveTrans.gameObject, _moveOther, _bulletSpeed, ISpeed.SpeedDes.BULLETSPEED);
		}
		_lifeTrans = transform.FindOrNew(GameObjectName.Life);
		{
			_despawnBulletComponent = _lifeTrans.GetOrAddComponent().Init(transform, _despawnKey);
			_autoDespawnOtherComponent = _lifeTrans.GetOrAddComponent().Init(transform, _despawnKey, _sr);
		}
		_colliderTrans = transform.FindOrNew(GameObjectName.Collider);	//自己就是子弹||自己就是飞机的一种
		{
			_collideMsgComponent = _colliderTrans.GetOrAddComponent().InitComponent(_trigger2DComponent);
			//_bulletEffectComponent = _bulletTrans.GetOrAddComponent().Init(_bulletType, _bulletTrans);
			//_collideMsgFromBulletComponent = _colliderTrans.GetOrAddComponent().InitComponent(_bulletModel, _despawnBulletComponent);
			_collideMsgFromPlaneComponent = _colliderTrans.GetOrAddComponent().InitComponent(_bulletModel, _despawnBulletComponent);
			_bulletModelComponent = _colliderTrans.GetOrAddComponent().Init(_bulletModel);
		}
	}

bug 分辨率

左右最好统一

//

因为做UI预制体,认的是左边那个

如果右边那个不是一样的,会出问题(除了锚点靠边靠四角较少问题,其它的很容易越界,翻转,大小缩放不理想)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

bug Can’t remove RectTransform because Image (Script), Image (Script) depends on it

Unity报错:Destroy销毁物体失败

完结

HeroSelect

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

Strength强化

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

LevelSelect 和Loading

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

Battle

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

子弹列数的奖励(概率低,上面没吃到)

用QFramework重构飞机大战(Siki Andy的)(下02)(06-0? 游戏界面及之后的所有面板)

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]