笔记 【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方法,富文本等)