目录
1 实例概述
1.1 玩家脚本
1.2 触发器脚本
2 通过当前血量判断是否销毁血包
2.1 将private修改成public实现跨类调用
运行效果
2.2 使用internal修饰符
运行效果
2.3 将血量封装起来,定义属性访问
该方法优化点在于
3 C#中get和set访问器
3.1 定义属性
get{} 取值器
set{} 赋值器
3.2 自动实现的属性
3.3 结合案例
学习的教程
【unity2021入门教程】68-2D游戏开发教程系列-03-RubyAdventure2DRpg官方教程-16-完善触发器代码_哔哩哔哩_bilibili
为什么会写这么一篇博客呢?在跟着教程学习制作RubyAdventure项目的过程中,进行到了“吃血包加血”的脚本编写,其中涉及到需要在“血包”类脚本中调用“玩家Ruby”脚本里的成员变量的操作,其中在进行脚本优化时就涉及到了封装、对数据成员保护的概念,正好有一个参考案例,就打算记录一下C#封装的实操。
需要实现玩家Ruby在场景中移动,吃掉“血包”草莓并加血的操作。
当前项目中玩家挂了一个脚本,里面写入了赋予的最大生命值maxHealth和当前生命值currentHealth,全部脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RubyController : MonoBehaviour
{
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
private int currentHealth;
private void Start()
{
//初始化当前生命值
currentHealth = maxHealth;
}
// 每帧都会执行一次Update函数
void Update()
{
...
}
//更改生命值
//amount是游戏中加血/减血的操作
void ChangeHealth(int amount)
{
//限制当前生命值范围为[0,maxHealth]
currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
//输出
Debug.Log("当前生命值为:" + currentHealth + "/" + maxHealth);
}
}
在游戏场景中,我们加入了一个游戏对象CollectibleHealth(“血包”,以下简称草莓),挂了一个2D碰撞体组件,勾选Is Trigger当作触发器使用。
给草莓挂一个脚本,通过添加碰撞检测后调用的OnTriggerEnter()内容实现
的游戏情景。
脚本具体如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthCollectible : MonoBehaviour
{
//设置每次碰撞加的血量
public int amount = 1;
private int CollideTimes;
//添加触发器事件,每次碰撞触发器时执行其中的代码
//其中,在这里对于草莓来说,other指的就是玩家Ruby
private void OnTriggerEnter2D(Collider2D other)
{
CollideTimes++;//碰撞次数+1
Debug.Log($"和当前物体发生碰撞的是:{other},当前是第{CollideTimes}次发生碰撞!");
//获取Ruby对象的脚本组件,还是用GetComponent方法
RubyController rubyController=other.GetComponent();
if(rubyController != null)
{
//血量+1
rubyController.ChangeHealth(amount);
//销毁血包
Destroy(gameObject);
}
else
{
Debug.LogError("未获取到当前游戏对象");
}
}
}
由于在Ruby脚本中,当前血量currentHealth的修饰符是private,为了实现跨类调用,选择直接把currentHealth暴露出来,变成public,就可以调用了,于是代码分别做以下修改:
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
//将生命值暴露出来,private -> public
public int currentHealth;
if(rubyController != null)
{
//血量+1
rubyController.ChangeHealth(amount);
if(rubyController.currentHealth < rubyController.maxHealth)
{
//销毁血包
Destroy(gameObject);
}
}
else
{
Debug.LogError("未获取到当前游戏对象");
}
在项目中,currentHealth这个本应该是内部的变量被暴露在了项目属性栏中。
与public相比,用internal修饰符的成员变量可以在同一应用程序集内部访问。而public可以跨程序集访问。
同一程序集中,二者效果相同。
同时,在Unity中,public会将变量同时暴露在inspector属性栏中,internal则不会。
对应的修改成:
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
//private -> internal
internal int currentHealth;
if(rubyController != null)
{
//血量+1
rubyController.ChangeHealth(amount);
if(rubyController.currentHealth < rubyController.maxHealth)
{
//销毁血包
Destroy(gameObject);
}
}
else
{
Debug.LogError("未获取到当前游戏对象");
}
可以看到,属性栏中是不会将currentHealth展示出来的。
但是,我认为无论是internal还是public,都是十分不安全的!这给了任何人可以随意修改内部值——玩家血量的机会,不利于项目的维护。
这里就涉及到一个面向对象程序设计的概念——封装,对于封装,我的另一篇博客有简单的介绍,这里就不再赘述:【Unity入门计划】了解C#或Unity中的类和对象_flashinggg的博客-CSDN博客
直接介绍方法,同样还是修改两个类:
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
public int currentHealth;
//C#中支持面向对象程序设计中的封装,实现对数据成员进行保护
//数据成员变量本身是私有的,只能通过某一种方法或者属性访问
//属性是共有的,可以通过取值器--get,赋值器--set,设定对应字段的访问规则
//满足规则才能访问该成员变量
public int health { get { return currentHealth; } }
这里用到了get——取值器,后面会详细介绍C#的访问器。
if(rubyController != null)
{
//血量+1
rubyController.ChangeHealth(amount);
if (rubyController.health
需要访问的数据成员类型仍旧是私有的,只是在类中定义一个属性用于访问,但数据成员变量本身是私有的,从而实现了对数据成员的保护。
这点是十分重要的!对于一些内部的变量,不要轻易地设置成public!
C# get和set访问器:获取和设置字段(属性)的值 (biancheng.net)
游戏对象的属性常与字段连用.C#提供了get访问器和set访问器,方便获取和设置字段的值,定义属性的语法框架如下:
public 数据类型 属性名称
{
get
{
获取属性的语句块;
return 值;
}
set
{
设置属性得到语句块;
}
}
get作为取值器,用于获取属性的值,需要在get语句最后使用return关键字返回获取属性的关系值。
如果在属性定义中省略了get{}访问器,则无法再该类外的其他类获取私有类型的字段值,这时也被称为只写属性。
set{}作为赋值器,用于给对应字段设置值,这里需要用到一个特殊的值value,这个value就是给当前字段设置的值。
本小节叙述来自于:C#——get方法和set方法(属性) - 简书 (jianshu.com)
在某些情况下,get和set访问器仅向支持字段赋值或仅从其中检索值,不包括任何逻辑,通过使用自动实现的属性,既能够简化代码,还能让C#编译器透明地提供支持字段。
如果属性具有get和set访问器,则必须自动实现这两个访问器。自动实现的属性通过以下方式定义:使用get和set关键字,但不提供任何实现。
例如:
public class Goods //商品类
{
public string Name//商品类的名称,自动实现的属性
{
get; set;
}
public decimal Price
{
get; set;
}
}
上述例子中就提到了,定义一个int类型的属性,属性名为health,用了get{}访问器获取了私有类型currentHealth的字段值,并用关键字return返回。
public int health
{
get
{
return currentHealth;
}
}
如果需要set值,可以是:
public int health
{
get
{
return currentHealth;
}
set
{
currentHealth = 1;
}
}
但由于上述案例中,currentHealth本身不需要被赋值,不然就跟public差不多了,所以这里没有set。