一些准则:
根据VIEW->SYSTEM->MODEL的分层架构
初始架构:
app.
using FrameworkDesign;
namespace ShootingEditor2D(项目的命名空间)
{
public class ShootingEditor2D (游戏名称): Architecture<ShootingEditor2D>
{
protected override void Init()
{
}
}
}
该脚本放到scripts文件夹下。
其他model\system等,注册在app.
using FrameworkDesign;
namespace ShootingEditor2D
{
public class ShootingEditor2D : Architecture<ShootingEditor2D>
{
protected override void Init()
{
this.RegisterModel<IPlayerModel>(new PlayerModel());
this.RegisterSystem<IStatSystem>(new StatSystem());
}
}
}
功能列表
在VIEW层和SYSTEM层之间通信
创建VIEW
创建VIEW中的关键角色:UIController,方便快速对UI的显示进行处理。
*注意,VIEW脚本可以获取任何来自SYSTEM和MODEL的信息,以此来更新自己,比如监测是否子弹足够。
namespace ShootingEditor2D
{
public class Gun : MonoBehaviour,IController // +
{
private Bullet mBullet;
private GunInfo mGunInfo; // +
private void Awake()
{
mBullet = transform.Find("Bullet").GetComponent<Bullet>();
mGunInfo = this.GetSystem<IGunSystem>().CurrentGun; // +
}
public void Shoot()
{
if (mGunInfo.BulletCount.Value > 0) // +
{
var bullet = Instantiate(mBullet.transform, mBullet.transform.position, mBullet.transform.rotation);
// 统一缩放值
bullet.transform.localScale = mBullet.transform.lossyScale;
bullet.gameObject.SetActive(true);
this.SendCommand<ShootCommand>(); // +
}
}
public IArchitecture GetArchitecture() // +
{
return ShootingEditor2D.Interface;
}
private void OnDestroy() // +
{
mGunInfo = null; //+
}
}
}
namespace ShootingEditor2D
{
public class UIController : MonoBehaviour,IController
{
private IStatSystem mStatSystem;
private IPlayerModel mPlayerModel;
private void Awake()
{
mStatSystem = this.GetSystem<IStatSystem>();
mPlayerModel = this.GetModel<IPlayerModel>();
}
/// <summary>
/// 自定义字体大小
/// </summary>
private readonly Lazy<GUIStyle> mLabelStyle = new Lazy<GUIStyle>(()=>new GUIStyle(GUI.skin.label)
{
fontSize = 40
});
private void OnGUI()
{
GUI.Label(new Rect(10,10,300,100),$"生命:{mPlayerModel.HP.Value}/3",mLabelStyle.Value);
GUI.Label(new Rect(Screen.width - 10 - 300,10,300,100),$"击杀数量:{mStatSystem.KillCount.Value}",mLabelStyle.Value);
}
public IArchitecture GetArchitecture()
{
return ShootingEditor2D.Interface;
}
}
}
创建VIEW->SYSTEM的通信方式:命令Command
namespace ShootingEditor2D
{
public class KillEnemyCommand : AbstractCommand
{
protected override void OnExecute()
{
this.GetSystem<IStatSystem>().KillCount.Value++;
}
}
}
protected override void OnExecute()
{
var gunSystem = this.GetSystem<IGunSystem>();
gunSystem.CurrentGun.BulletCountInGun.Value--;
gunSystem.CurrentGun.GunState.Value = GunState.Shooting;
}
确定在什么运行情况下发出该命令Command。比如,一个小小的子弹销毁时,子弹知道要发出。
namespace ShootingEditor2D
{
public class Bullet : MonoBehaviour,IController // +
{
private Rigidbody2D mRigidbody2D;
private void Awake()
{
mRigidbody2D = GetComponent<Rigidbody2D>();
}
private void Start()
{
mRigidbody2D.velocity = Vector2.right * 10;
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Enemy"))
{
this.SendCommand<KillEnemyCommand>(); // +
Destroy(other.gameObject);
}
}
public IArchitecture GetArchitecture() // +
{
return ShootingEditor2D.Interface;
}
}
}
再比如,如果玩家碰到怪物就掉血,那么针对这个功能可以写一个脚本,挂在再玩家身上
namespace ShootingEditor2D
{
public class AttackPlayer : MonoBehaviour,IController
{
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Player"))
{
this.SendCommand<HurtPlayerCommand>();
}
}
public IArchitecture GetArchitecture()
{
return ShootingEditor2D.Interface;
}
}
VIEW自己分内的逻辑就自己处理了,比如怪物碰到玩家自己消失。如果不需要记录MODEL以数据的话,就自己删除。
可以建立各种各样的VIEW,别客气。
比如碰到了子弹库来补充弹药:
public class GunPickItem : MonoBehaviour,IController
{
public string Name;
public int BulletCountInGun;
public int BulletCountOutGun;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
this.SendCommand(new PickGunCommand(Name,BulletCountInGun,BulletCountOutGun));
}
}
public IArchitecture GetArchitecture()
{
return ShootingEditor2D.Interface;
}
}
条件没问题的话,发送就行。至于这个pickgun要如何运作,有点复杂,但由command交给system去处理,处理之后,发送回一个event,表示枪支变化:
command这样写:
public class PickGunCommand : AbstractCommand
{
private readonly string mName;
private readonly int mBulletInGun;
private readonly int mBulletOutGun;
public PickGunCommand(string name, int bulletInGun, int bulletOutGun)
{
mName = name;
mBulletInGun = bulletInGun;
mBulletOutGun = bulletOutGun;
}
protected override void OnExecute()
{
this.GetSystem<IGunSystem>()
.PickGun(mName, mBulletInGun, mBulletOutGun);
}
}
system这样写
using System.Collections.Generic;
using System.Linq;
using FrameworkDesign;
namespace ShootingEditor2D
{
public interface IGunSystem : ISystem
{
GunInfo CurrentGun { get; }
void PickGun(string name, int bulletCountInGun, int bulletCountOutGun); //+
}
public class OnCurrentGunChanged // +
{
public string Name { get; set; }
}
public class GunSystem : AbstractSystem, IGunSystem
{
protected override void OnInit()
{
}
private Queue<GunInfo> mGunInfos = new Queue<GunInfo>(); // +
public GunInfo CurrentGun { get; } = new GunInfo()
{
BulletCountInGun = new BindableProperty<int>()
{
Value = 3
},
BulletCountOutGun = new BindableProperty<int>()
{
Value = 1
},
Name = new BindableProperty<string>()
{
Value = "手枪"
},
GunState = new BindableProperty<GunState>()
{
Value = GunState.Idle
}
};
public void PickGun(string name, int bulletCountInGun, int bulletCountOutGun) // +
{
// 当前枪是同类型
if (CurrentGun.Name.Value == name)
{
CurrentGun.BulletCountOutGun.Value += bulletCountInGun;
CurrentGun.BulletCountOutGun.Value += bulletCountOutGun;
}
// 已经拥有这把枪了
else if (mGunInfos.Any(info => info.Name.Value == name))
{
var gunInfo = mGunInfos.First(info => info.Name.Value == name);
gunInfo.BulletCountOutGun.Value += bulletCountInGun;
gunInfo.BulletCountOutGun.Value += bulletCountOutGun;
}
else
{
// 复制当前的枪械信息
var currentGunInfo = new GunInfo
{
Name = new BindableProperty<string>()
{
Value = CurrentGun.Name.Value
},
BulletCountInGun = new BindableProperty<int>()
{
Value = CurrentGun.BulletCountInGun.Value
},
BulletCountOutGun = new BindableProperty<int>()
{
Value = CurrentGun.BulletCountOutGun.Value
},
GunState = new BindableProperty<GunState>()
{
Value = CurrentGun.GunState.Value
}
};
// 缓存
mGunInfos.Enqueue(currentGunInfo);
// 新枪设置为当前枪
CurrentGun.Name.Value = name;
CurrentGun.BulletCountInGun.Value = bulletCountInGun;
CurrentGun.BulletCountOutGun.Value = bulletCountOutGun;
CurrentGun.GunState.Value = GunState.Idle;
// 发送换枪事件
this.SendEvent(new OnCurrentGunChanged()
{
Name = name
});
}
}
}
}
SYSTEM中的数据变化如何告知VIEW?——为SYSTEM中的数据套用BindableProperty!
namespace ShootingEditor2D
{
public enum GunState
{
Idle,
Shooting,
Reload,
EmptyBullet,
CoolDown
}
public class GunInfo
{
[Obsolete("请使用 BulletCountInGame",true)] // 第二个参数改成了 true
public BindableProperty<int> BulletCount
{
get => BulletCountInGun;
set => BulletCountInGun = value;
}
public BindableProperty<int> BulletCountInGun;
public BindableProperty<string> Name;
public BindableProperty<GunState> GunState;
public BindableProperty<int> BulletCountOutGun;
}
}
也可以设好初始值(但这个和架构无关)
public GunInfo CurrentGun { get; } = new GunInfo()
{
BulletCountInGun = new BindableProperty<int>()
{
Value = 3
},
BulletCountOutGun = new BindableProperty<int>() // +
{
Value = 1
},
Name = new BindableProperty<string>() // +
{
Value = "手枪"
},
GunState = new BindableProperty<GunState>() // +
{
Value = GunState.Idle
}
};
事件EVENT:作为SYSTEM通知VIEW的方式。VIEW要自己Register
比如:这样
public class UIController : MonoBehaviour, IController
{
private IStatSystem mStatSystem;
private IPlayerModel mPlayerModel;
private IGunSystem mGunSystem;
private int mMaxBulletCount;
private void Awake()
{
mStatSystem = this.GetSystem<IStatSystem>();
mPlayerModel = this.GetModel<IPlayerModel>();
mGunSystem = this.GetSystem<IGunSystem>();
// 查询代码
mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value));
this.RegisterEvent<OnCurrentGunChanged>(e =>
{
mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(e.Name));
}).UnRegisterWhenGameObjectDestroyed(gameObject); // +
}
MODEL:作为数据记录层
using System.Collections.Generic;
using FrameworkDesign;
namespace ShootingEditor2D
{
public interface IGunConfigModel : IModel
{
GunConfigItem GetItemByName(string name);
}
public class GunConfigItem
{
public GunConfigItem(string name, int bulletMaxCount, float attack, float frequency, float shootDistance,
bool needBullet, float reloadSeconds, string description)
{
Name = name;
BulletMaxCount = bulletMaxCount;
Attack = attack;
Frequency = frequency;
ShootDistance = shootDistance;
NeedBullet = needBullet;
ReloadSeconds = reloadSeconds;
Description = description;
}
public string Name { get; set; }
public int BulletMaxCount { get; set; }
public float Attack { get; set; }
public float Frequency { get; set; }
public float ShootDistance { get; set; }
public bool NeedBullet { get; set; }
public float ReloadSeconds { get; set; }
public string Description { get; set; }
}
public class GunConfigModel : AbstractModel, IGunConfigModel
{
private Dictionary<string, GunConfigItem> mItems = new Dictionary<string, GunConfigItem>()
{
{ "手枪", new GunConfigItem("手枪", 7, 1, 1, 0.5f, false, 3, "默认强") },
{ "冲锋枪", new GunConfigItem("冲锋枪", 30, 1, 6, 0.34f, true, 3, "无") },
{ "步枪", new GunConfigItem("步枪", 50, 3, 3, 1f, true, 1, "有一定后坐力") },
{ "狙击枪", new GunConfigItem("狙击枪", 12, 6, 1, 1f, true, 5, "红外瞄准+后坐力大") },
{ "火箭筒", new GunConfigItem("火箭筒", 1, 5, 1, 1f, true, 4, "跟踪+爆炸") },
{ "霰弹枪", new GunConfigItem("霰弹枪", 1, 1, 1, 0.5f, true, 1, "一次发射 6 ~ 12 个子弹") },
};
protected override void OnInit()
{
}
public GunConfigItem GetItemByName(string name)
{
return mItems[name];
}
}
}
其他好东西:
1.时间冷却系统
timeSystem.AddDelayTask(0.33f, () =>
{
gunSystem.CurrentGun.GunState.Value = GunState.Idle;
});
using System;
using System.Collections.Generic;
using FrameworkDesign;
using UnityEngine;
namespace ShootingEditor2D
{
public interface ITimeSystem : ISystem
{
float CurrentSeconds { get; }
void AddDelayTask(float seconds, Action onFinish);
}
public enum DelayTaskState
{
NotStart,
Started,
Finish
}
public class DelayTask
{
public float Seconds { get; set; }
public Action OnFinish { get; set; }
public float StartSeconds { get; set; }
public float FinishSeconds { get; set; }
public DelayTaskState State { get; set; }
}
public class TimeSystem : AbstractSystem,ITimeSystem
{
public class TimeSystemUpdateBehaviour : MonoBehaviour
{
public event Action OnUpdate;
private void Update()
{
OnUpdate?.Invoke();
}
}
protected override void OnInit()
{
var updateBehaviourGameObj = new GameObject(nameof(TimeSystemUpdateBehaviour));
UnityEngine.Object.DontDestroyOnLoad(updateBehaviourGameObj);
// 如果需要销毁,可以缓存为成员变量
var updateBehaviour = updateBehaviourGameObj.AddComponent<TimeSystemUpdateBehaviour>();
updateBehaviour.OnUpdate += OnUpdate;
}
public float CurrentSeconds { get;private set; } = 0.0f;
private LinkedList<DelayTask> mDelayTasks = new LinkedList<DelayTask>();
private void OnUpdate()
{
CurrentSeconds += Time.deltaTime;
if (mDelayTasks.Count > 0)
{
var currentNode = mDelayTasks.First;
while (currentNode != null)
{
var delayTask = currentNode.Value;
var nextNode = currentNode.Next;
if (delayTask.State == DelayTaskState.NotStart)
{
delayTask.State = DelayTaskState.Started;
delayTask.StartSeconds = CurrentSeconds;
delayTask.FinishSeconds = CurrentSeconds + delayTask.Seconds;
} else if (delayTask.State == DelayTaskState.Started)
{
if (CurrentSeconds > delayTask.FinishSeconds)
{
delayTask.State = DelayTaskState.Finish;
delayTask.OnFinish.Invoke();
delayTask.OnFinish = null;
mDelayTasks.Remove(currentNode); // 删除节点
}
}
currentNode = nextNode;
}
}
}
public void AddDelayTask(float seconds, Action onFinish)
{
var delayTask = new DelayTask()
{
Seconds = seconds,
OnFinish = onFinish,
State = DelayTaskState.NotStart,
};
mDelayTasks.AddLast(new LinkedListNode<DelayTask>(delayTask));
}
}
}
2、Query查询类
// 查询代码
var gunConfigModel = this.GetModel<IGunConfigModel>();
var gunConfigItem = gunConfigModel.GetItemByName(mGunSystem.CurrentGun.Name.Value);
mMaxBulletCount = gunConfigItem.BulletMaxCount;
上面的查询显得很臃肿
可以这样:
mGunSystem = this.GetSystem<IGunSystem>();
// 查询代码
mMaxBulletCount = new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value).Do(); // -+
做法就是:写一个查询类
using FrameworkDesign;
namespace ShootingEditor2D
{
public class MaxBulletCountQuery : IBelongToArchitecture,ICanGetModel
{
private readonly string mGunName;
public MaxBulletCountQuery(string gunName)
{
mGunName = gunName;
}
public int Do()
{
var gunConfigModel = this.GetModel<IGunConfigModel>();
var gunConfigItem = gunConfigModel.GetItemByName(mGunName);
return gunConfigItem.BulletMaxCount;
}
public IArchitecture GetArchitecture()
{
return ShootingEditor2D.Interface;
}
}
}
通过一些修改可以直接通过架构Arch来发送查询,做到这样(代码略)
mGunSystem = this.GetSystem<IGunSystem>();
// 查询代码
mMaxBulletCount = this.SendQuery(newMaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value)); // -+
3.凉鞋的话
(GamePix 独立游戏学院 - 让独立游戏不再难做 - Powered By EduSoho)欢迎来这里购买决定版的QFRAMEWORK课程。