主要内容
前言
Show You The Code
结语
正文
1. 前言
对于刚入门的小萌新而言,面向程序中的封装可能会让大家感到十分困惑,甚至觉得这是十分没有必要的、累赘的。
我认为产生这样的感觉的原因究其根本是因为封装之概念不够直观,如果没有针对实际问题去应用的话当然是难以理解。在最近看了小萌新写的关于游戏的HP相关代码后,感觉确实不够理想,便写此博客浅谈我对封装相关的拙见。
希望这篇能够帮助到你,如果有问题请在留言区反馈,感谢!
2. Show You The Code
首先看一下萌新最开始的大致代码:
public class HP{
public static float chp = 100;
private float thp = 100;
public Slider hpSlider;
private void Update(){
hpSlider.value = chp/thp;
}
}
简单看一眼,Update函数每一帧都要调用,因此不管chp是否更新了,每一帧hpSlider的value属性都会进行更新,这时候发现了有很多很多赋值其实是白费的,因此我要求萌新接着去修改代码,这回代码如下:
public class HP{
private void Update(){
if(hp改变){
hpSlider.value = chp/thp;
}
}
}
然而,即使是这样优化了一下,每一帧还是会进行一次判断,这样的代码依旧是产生了浪费。
那么,要怎么解决呢?
思考一下,到底是什么时候我们才需要执行那一段代码呢?
答案当然是:
- HP被初始化赋值的时候
- 受到伤害的时候
- 恢复HP的时候
总而言之,其实就是: HP的值发生改变的时候
。
那么,我要怎样知道 HP发生了改变
呢?
如果针对于一个对象的public字段而言,其他的类的对象都可以任意访问到这个字段的话,这个字段在不知道何处被改动了的时候,按道理这时候我们其实并无法很方便的知道HP是否发生了改变,如下代码所示:
public class HP{
public int HP;
}
public class OtherClass{
private HP hp;
public void ChangeHP(int val){
hp.HP = 100;
OnHpChange();
/// 除非手动执行OnHpChange()
/// 否则代码仍需使用Update一直去访问hp.HP看其是否发生改变
}
public void OnHpChange(){}
}
在此我们发现了,如果我们在要改变HP的其他的每个类里都写一个OnHpChange函数,然后再改变HP的时候去调用这个函数,也能解决这个问题。只是。。。如果有很多类的对象都要去改变这个HP呢?那么我们是不是可能写很多的重复代码呢?
那么,让我们把注意力集中于问题:如何想办法在 HP发生了改变的时候执行某些逻辑
这个问题上。
我们目前明确了一点:
- 如果HP是一个 public 的字段的话,其他类可以任意改变它的话,那么这样会不好方便的知道HP是否改变了,或者会产生大量重复代码。
可是如果不能随便访问这个字段,我们给他们加上一层逻辑呢?
下面看看如下代码:
public class Health{
public readonly MaxHP;
private int nowHP;
public int NowHP{
get{
return nowHP;
}
set{
if(nowHP != value){
OnHPChange();
}
}
}
public void OnHPChange(){}
}
这样的话,其他的类如果要对nowHP进行赋值的话,首先会执行一层判断是否nowHP发生改变的逻辑。
这样就很方便的解决了之前所提到的问题。
不过,不要高兴的太早,再来思考一个问题:在一个游戏中,会有哪些物件具有HP呢?
我思考的话大致有以下这些:
- 玩家
- 敌人
- 障碍物
那么,这些物件当HP发生改变的时候,他们的OnHPChange方法里的逻辑明显是不同的,但是我们发现,其核心代码是很相似的,关键的不同之处就在于OnHPChange方法。
那么,这要怎么解决呢?
我思考的解决方案有以下两种:
- 继承Health类
- 委托
首先试一下继承这种方法:
public abstract class BaseHealth{
public readonly MaxHP;
private int nowHP;
public int NowHP{
get{
return nowHP;
}
set{
if(nowHP != value){
OnHPChange();
}
}
}
public abstract void OnHPChange();
}
public class PlayerHealth:BaseHealth{
public override void OnHPChange(){
/// 玩家HP改变的事件
}
}
public class EnemyHealth:BaseHealth{
public override void OnHPChange(){
/// 敌人HP改变的事件
}
}
我们发现,这样我们就重复利用到了BaseHealth类里面的大部分代码,而只用去实现OnHPChange方法就可以解决之前的问题了。
不过,尽管使用继承已经足够方便了。不过,我认为仍存在以下问题:
- 每需要改变一次逻辑,就要重新实现一个继承BaseHealth的类。
- 继承会产生编译时依赖,不够灵活。
- 多用组合,少用继承。
- 当只用继承的时候,在Unity中去获取其他物体的话,比如PlayerHealth方法要去获得HPSlider上的Slider组件的时候,需要去使用GameObject.Find()方法或者一些其他的方法,会产生大量耦合。
接着,我们采用委托机制去简单实现我们的需求:
public class Health
{
// private 字段防止不通过属性器去修改hp的值
private int hp;
public int HP
{
get { return hp; }
set
{
if(value <= 0)
{
hp = 0;
/// ?.表示如果onDead不为null的话,则调用该委托
onDead?.Invoke();
onHPChange?.Invoke(hp, MaxHP);
}
else if(value != hp)
{
hp = value;
onHPChange?.Invoke(hp,MaxHP);
}
}
}
public readonly int MaxHP;
private Action onDead;
private Action<int, int> onHPChange;
/// 构造函数
public Health(int initHP)
{
hp = MaxHP = initHP;
}
/// 注册死亡事件
public void RegisterOnDeadAction(Action action)
{
if (onDead != null)
{
onDead += action;
}
else
{
onDead = action;
}
}
// 注册HP改变的事件
public void RegisterOnHPChangeAction(Action<int,int> action)
{
if (onHPChange != null)
{
onHPChange += action;
}
else
{
onHPChange = action;
}
/// 每注册一个事件,都要执行一下
if (action!=null)
{
action(HP, MaxHP);
}
}
public void ChangeHP(int detalHP)
{
HP += detalHP;
}
}
public class Enemy : MonoBehaviour
{
private Health health;
private Slider slider;
/// <summary>
/// 使用协程来进行测试
/// </summary>
private IEnumerator Start()
{
/// 初始化
health = new Health(3);
slider = GetComponent<Slider>();
health.RegisterOnDeadAction(() => { Debug.Log("Ahhh~ I'm dead!"); });
health.RegisterOnHPChangeAction((nowHP, maxHP) => {
slider.value = (float)nowHP / maxHP;
});
health.RegisterOnHPChangeAction((nowHP, maxHP) => {
Debug.Log("Now I have " + nowHP + " HP!");
});
///开始每秒逐渐扣一滴血
while(health.HP != 0)
{
yield return new WaitForSeconds(1f);
health.ChangeHP(-1);
};
}
}
该示例代码我会放在Github上供大家 下载 ,大家导入Unity打开场景点击开始便可以看到效果。
稍微研究下代码应该就可以理解,不做详细讲解。
然后我们发现这样写会有以下优势:
- 符合多用组合,少用继承的思想。
- 使用事件注册机制,耦合性低,灵活性高。
目前来说就做这么多讲解,希望能对大家有所帮助。
3. 结语
首先必须要承认的是,笔者并非什么大佬,只能写这篇文章发表下自己的拙见,如果文章有不够深刻不够严谨之处,希望各位谅解或者指出,定会学习改正。
另外,当然一篇博客难以讲清楚全部的东西,今后有时间的话会继续进行分享,希望自己能坚持下去。