笔记 【UI游戏界面】上下页切换,委托类型作为方法的参数和LAMBDA表达式的实际使用(附:计算所有数据最高的方法,GetChild方法优缺点等)

【持续更新ing】
小joe视频传送门 【UI游戏界面】上下页切换,委托类型作为方法的参数和LAMBDA表达式的实际使用(附:计算所有数据最高的方法,GetChild方法优缺点等)

如何通过UI,制作一个简单的画廊手册,如何通过「Lambda表达式」压缩不必要的方法声明,起到【匿名函数】的作用? 「委托」在一般情况下,又是如何进行使用的?
如何通过Unity,完成一个 功能型的游戏界面。

首先,我们讲创建一个单独的PlayerStatus类型,
在Inspector窗口 自定义编辑,每个玩家的基本信息。
第二步:实现右侧界面的,上下页按钮。
期间确保【数组】可取的最大范围。
第三步:计算所有玩家中 击杀数,阵亡数、夺旗数 最高的玩家。
将委托作为方法的参数,写成【模板方法】的形式,传递到方法中,
最后:介绍「Lambda表达式」进一步简化我们的代码。

PlayerStatus脚本(Status:状态) 并不需要继承自MonoBehaviour,它不需要以组件形式,添加在任何游戏对象身上。

public string playerName; //声明字符串类型的变量

不建议变量名取做:name ,如果写成了name,编译器也会给提示,因为我们C#所有的类型都是以Object类型为自己的基类型,也就是都继承自Object。而我们的Object类型中也有一个成员变量,也叫做name。 建议不要使用name作为变量的名字了。
//killNum 击杀数, deathNum 阵亡数, flagNum夺旗数;

要把类中的一些属性在Inspector窗口中显示出来,需要在类的上方,添加**特性[System.Serializable] ** 将这个类 标记为 可序列化的、可以在Inspector窗口中显示出来的。(也可以引入Syetem名称空间后,写成截图的形式)
我们就将玩家的基本信息,以 数组的形式 存储在了GM游戏管理器的脚本当中(也可以写成List的形式,这里都是可以的)

也是保证整个项目,有且只有一个GM类的实例(即使在场景转换中)。这里呢,我们首先设置为【单例模式】

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public PlayerStatus[] playerStatus;
   private void Awake()
    {
                if (instance == null)
                {
                    instance = this;
                }
                else
                {  if (instance != null)
                        { Destroy(gameObject); }
                }
        DontDestroyOnLoad(gameObject);
    }
}

添加了特性【Header】,在Inspector窗口,给接下来要定义的字段(变量)加上一段
粗体的描述文字。 增加了Inspector窗口的【阅读性】,
有一种不同变量之间“分组”的感觉。

VerticalLayout Group组件,可以让子物体UI等间距布局,

        slots[i].GetComponentInChildren<Text>().text = killNum + "/" + deathNum + "/" + flagNum;

需要写很多+ 加号 和“” 引号, 通过String.Format来“拼接”我们的字符串(字符串格式化),格式字符串中的{0}会被替换成 格式字符串之后的 第一位参数:每个玩家的击杀数
(使用C#中的$符号,也可以直接写成{killNum})

public int currentIndex;//表示右侧UI 显示的当前玩家【序列号】,也可以理解为我们【当前的页数】,可以不进行初始化,因为它的默认值就是为零

做出部分高亮,部分半透明的效果。在Slot游戏对象添加了一个【CanvasGroup】组件,

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public PlayerStatus[] playerStatus;

    private void Awake()
    {
                if (instance == null)
                {
                    instance = this;
                }
                else
                {  if (instance != null)
                        { Destroy(gameObject); }
                }
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        //测试看看返回值是不是我们所有玩家中 击杀数最高的玩家
     Debug.Log(  "Kill Most is"+GetTopKillNum());
    }
    //计算 「击杀数」最高的玩家  ↓ 这个方法也就是我们的「模板方法」
    public string GetTopKillNum()
    {
        int bestRecord = 0; //本场击杀数「击杀数」的最高记录
        string topName = "";

        foreach (PlayerStatus player in playerStatus)
        {
            int tempNum = player.killNum;  //临时存储「当前遍历的这个玩家」的击杀数
           
            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }

        return topName;
    }


}


获得 击杀数、死亡数、夺旗数

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public PlayerStatus[] playerStatus;

    private void Awake()
    {
                if (instance == null)
                {
                    instance = this;
                }
                else
                {  if (instance != null)
                        { Destroy(gameObject); }
                }
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        //测试看看返回值是不是我们所有玩家中 击杀数最高的玩家
     Debug.Log(  "Kill Most is"+GetTopKillNum());
        Debug.Log("Death Most is"+GetTopDeathNum());
    }
    //计算 「击杀数」最高的玩家  ↓ 这个方法也就是我们的「模板方法」
    public string GetTopKillNum()
    {
        int bestRecord = 0; //本场击杀数「击杀数」的最高记录
        string topName = "";

        foreach (PlayerStatus player in playerStatus)
        {
            int tempNum = player.killNum;  //临时存储「当前遍历的这个玩家」的击杀数
           
            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }

        return topName;
    }




    //计算【阵亡数】最多的玩家姓名
    public string GetTopDeathNum()
    {
        int bestRecord = 0; //本场击杀数「击杀数」的最高记录
        string topName = "";

        foreach (PlayerStatus player in playerStatus)
        {
            int tempNum = player.deathNum;  //临时存储「当前遍历的这个玩家」的击杀数

            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }

        return topName;
    }


    //计算【夺旗数】最多的玩家姓名
    public string GetTopflagNum()
    {
        int bestRecord = 0; //本场击杀数「击杀数」的最高记录
        string topName = "";

        foreach (PlayerStatus player in playerStatus)
        {
            int tempNum = player.deathNum;  //临时存储「当前遍历的这个玩家」的击杀数

            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }

        return topName;
    }


}

↑ 1、这个脚本中【方法成员】膨胀, 第二、由于极度的膨胀,而导致未来脚本非常不好维护。

        foreach (PlayerStatus player in playerStatus)
        {
            int tempNum = player.killNum;  //临时存储「当前遍历的这个玩家」的击杀数
           
            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }


其中 三种计算数目的方法,只有一处是不确定的。这里的 int tempNum = player.XXXX
在我们初学任何编程语言的时候,都会介绍通过【参数】来实现 方法处理不同的未知数。
我们现在希望 通过一个方法来计算:所有玩家中,某个成员变量中最高的玩家姓名,方法名字可以改成GetTopNum。
上面三个函数的共同点在于,它们都是玩家的一个成员变量,而且这个成员变量数据类型,是Int类型的,并且赋值给局部变量tempNum当中,
所以换句话说就是:我们这里 不确定的是一个Int类型的值,
我们是否可以创建一个方法,这个方法返回一个int类型的值,表述某个玩家的某个成员变量。 然后我们通过 委托类型、这个委托类型会返回一个int类型的值,来传入到这个方法的参数当中。 尝试通过一个模板方法来解决所有的问题。
可以先自定义委托,再改成C#类库为我们提供好的Action委托也好、或者Func委托,我们要返回的呢,就是这么一串东西。

我们所封装的这个方法,将会返回 某个玩家 具体的一个「击杀数」、
因为我们Foreach循环的内部,我们是通过player去确定是遍历当中的某个玩家的,player是我们不确定的因素。 形式参数的参数类型 是PlayerStatus类型,参数名自定义,

public delegate int GetTopScoreDelegate(PlayerStatus playerStatus);
//返回值应该是 某个玩家具体的一个击杀数、阵亡数,或者夺旗数
// 参数 是 PlayerStatus 类型

        foreach (PlayerStatus player in playerStatus)
        {
            //int tempNum = player.killNum;  //临时存储「当前遍历的这个玩家」的击杀数
            //↓  我们通过委托类型的参数【间接调用】它所封装的方法。(等会再写)
            int tempNum = delegateNum ();  //←这里是空的,下面有补充

            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }

我们想 间接的调用、委托所封装的方法的话,那么 这个委托类型的参数,它的参数列表是一个PlayerStatus类型的变量。
int tempNum = delegateNum (player);
这里 我们把这里的player,也就是我们刚才说的某个玩家,传递进去。
这就是我们的临时变量tempNum将会【被赋值】某个委托所封装的方法。
这个方法【参数列表】为PlayerStatus类型,【返回】一个整数类型的数值,给我们的临时变量tempNum当中,
那如果我们想计算玩家的【哪个成员变量】的最大值,我们只需要在调用我们模板方法的时候,在参数中传入相对应的「类型兼容」的其他方法就可以了。
这个方法必须是一个【参数列表】为PlayerStatus类型,【返回值】为int类型的方法。
即:这个方法,将会和我们 自定义委托类型 保持「类型兼容」。
现在,就开始去写委托所 指向、所封装的这个方法。
比如说:我们要返回的是 某个玩家的击杀数,

    public int GetPlayerKillNum(PlayerStatus playerStatus)
    {
        return playerStatus.killNum;//返回某个玩家的击杀数
    }

    public int GetPlayerDeathNum(PlayerStatus playerStatus)
    {
        return playerStatus.deathNum;//返回某个玩家的击杀数
    }
    public int GetPlayerFlagNum(PlayerStatus playerStatus)
    {
        return playerStatus.flagNum;//返回某个玩家的击杀数
    }

我们现在写好了这三个方法后, 就将会作为参数 传入我们的模板方法:GetTopNum中,
我们先实现其功能,再回头分析一下,这个模板方法和这三个方法之间的关系。

与委托类型「类型兼容」的方法是什么呢?
也就是我们的 GetTopScoreDelegate 可以指向「类型兼容」的GetPlayerKillNum方法, 然后我们在方法参数中,传入这个实例,我们省去了在这个步骤,
而是直接的将GetPlayerKillNum方法,作为参数 传递到这个模板方法中。

这样就使代码不再冗长,使代码可以重复使用,Reuse/代码的复用,它可以提高我们的工作率,并且减少Bug的引入。
所以 「模板方法」的好处是什么呢? 好处就是, 一旦我们把代码写成了这样之后,我们的GetTopNum方法,就不需要再去动它了。我们只需要的是不断扩张,我们之后想比较的成员变量,就可以计算出,玩家每个成员中最高的玩家的名字。(比如说 我们之后增加了【金币数】GetTopNum方法就不用动,只需要新增一个小方法,如果用Lambda表达式的话,小方法也可以省去)
我们不用去管计算的是哪个数据,我们只需要把方法封装在一个委托类型的变量中,作为参数来传递,传到我们的模板方法中。我们的模板方法就一定可以计算出 某一个成员变量 它的最高值 这个玩家的名字了。

using UnityEngine;


public delegate int GetTopScoreDelegate(PlayerStatus playerStatus);
//返回值应该是 某个玩家具体的一个击杀数、阵亡数,或者夺旗数
// 参数 是 PlayerStatus 类型

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public PlayerStatus[] playerStatus;
    GetTopScoreDelegate getTopScoreDelegate;
    private void Awake()
    {
                if (instance == null)
                {
                    instance = this;
                }
                else
                {  if (instance != null)
                        { Destroy(gameObject); }
                }
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        //测试看看返回值是不是我们所有玩家中 击杀数最高的玩家
     Debug.Log(  "Kill Most is"+GetTopNum(GetPlayerKillNum));
        Debug.Log("Death Most is"+ GetTopNum(GetPlayerDeathNum));
        Debug.Log("Flag Most is" + GetTopNum(GetPlayerFlagNum));

    }

    public int GetPlayerKillNum(PlayerStatus playerStatus)
    {
        return playerStatus.killNum;//返回某个玩家的击杀数
    }

    public int GetPlayerDeathNum(PlayerStatus playerStatus)
    {
        return playerStatus.deathNum;//返回某个玩家的击杀数
    }
    public int GetPlayerFlagNum(PlayerStatus playerStatus)
    {
        return playerStatus.flagNum;//返回某个玩家的击杀数
    }

    // ↓ 这个方法也就是我们的「模板方法」
    public string GetTopNum(GetTopScoreDelegate delegateNum)
    {
        int bestRecord = 0; 
        string topName = "";

        foreach (PlayerStatus player in playerStatus)
        {
            //int tempNum = player.killNum;  //临时存储「当前遍历的这个玩家」的击杀数
            //↓  我们通过委托类型的参数【间接调用】它所封装的方法。(等会再写)
            int tempNum = delegateNum (player);

            if (tempNum >= bestRecord)  // bestRecord最开始是0 
            {
                bestRecord = tempNum;
                topName = player.playerName;
            }
        }
        return topName;
    }


    

}

边声明,边调用。

 Debug.Log(  "Kill Most is"+GetTopNum((playerStatus)=> playerStatus.killNum));

[HideInInspector] 保持public但是在Inspector窗口隐藏。
[SerializeField]保持 private正好相反

使用Func委托 能省略自定义声明委托的一些步骤
public delegate int GetTopScoreDelegate(PlayerStatus playerStatus);
GetTopScoreDelegate getTopScoreDelegate;

其他修改的地方

        topKillName = GetTopNum(GetPlayerKillNum);
        topDeathName = GetTopNum(GetPlayerDeathNum);
        topFlagName = GetTopNum(GetPlayerFlagNum);

    public string GetTopNum(Func<PlayerStatus,int> func)
     int tempNum = func (player);

下期视频【冒泡算法】升序降序排列玩家各项数据以及DOTween和LeanTween的使用介绍与缺点(附:泛型,模板方法,SWAP问题,十大常用Array方法,富文本等)
和笔记 笔记 【冒泡算法】升序降序排列玩家各项数据以及DOTween和LeanTween的使用介绍与缺点(附:泛型,模板方法,SWAP问题,十大常用Array方法,富文本等)

posted @ 2022-08-02 19:12  专心Coding的程侠  阅读(77)  评论(0编辑  收藏  举报