您当前的位置: 首页 >  flashinggg unity

【Unity入门计划】Unity实例-C#如何通过封装实现对数据成员的保护

flashinggg 发布时间:2022-08-07 17:04:46 ,浏览量:1

目录

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#封装的实操。

1 实例概述

需要实现玩家Ruby在场景中移动,吃掉“血包”草莓并加血的操作。

1.1 玩家脚本

当前项目中玩家挂了一个脚本,里面写入了赋予的最大生命值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);
    }
}
1.2 触发器脚本

在游戏场景中,我们加入了一个游戏对象CollectibleHealth(“血包”,以下简称草莓),挂了一个2D碰撞体组件,勾选Is Trigger当作触发器使用。

edd43227e9314f249a574d771e3f0a04.png

给草莓挂一个脚本,通过添加碰撞检测后调用的OnTriggerEnter()内容实现

  • 血包被玩家碰到后被吃掉
  • 玩家血量增加1
  • Debug返回指定语句

 的游戏情景。

脚本具体如下

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("未获取到当前游戏对象");
        }
    }
}
2 通过当前血量判断是否销毁血包 2.1 将private修改成public实现跨类调用

由于在Ruby脚本中,当前血量currentHealth的修饰符是private,为了实现跨类调用,选择直接把currentHealth暴露出来,变成public,就可以调用了,于是代码分别做以下修改:

  • RubyController类
    //设置最大生命值(生命上限)
    public int maxHealth = 5;
    //当前生命值,默认为0
    //将生命值暴露出来,private -> public
    public int currentHealth;
  • HealthCollectible类
        if(rubyController != null)
        {
            //血量+1
            rubyController.ChangeHealth(amount);
            if(rubyController.currentHealth < rubyController.maxHealth)
            {
                //销毁血包
                Destroy(gameObject);
            }
        }
        else
        {
            Debug.LogError("未获取到当前游戏对象");
        }
运行效果

在项目中,currentHealth这个本应该是内部的变量被暴露在了项目属性栏中。

89006ec29daa4beabe7425ce96dd7142.png

2.2 使用internal修饰符

与public相比,用internal修饰符的成员变量可以在同一应用程序集内部访问。而public可以跨程序集访问。

同一程序集中,二者效果相同。

同时,在Unity中,public会将变量同时暴露在inspector属性栏中,internal则不会。

对应的修改成:

  •  RubyController类
    //设置最大生命值(生命上限)
    public int maxHealth = 5;
    //当前生命值,默认为0
    //private -> internal
    internal int currentHealth;
  •  HealthCollectible类
        if(rubyController != null)
        {
            //血量+1
            rubyController.ChangeHealth(amount);
            if(rubyController.currentHealth < rubyController.maxHealth)
            {
                //销毁血包
                Destroy(gameObject);
            }
        }
        else
        {
            Debug.LogError("未获取到当前游戏对象");
        }
运行效果

可以看到,属性栏中是不会将currentHealth展示出来的。

15647198f3cd4fba90ffeb7f44275c50.png

但是,我认为无论是internal还是public,都是十分不安全的!这给了任何人可以随意修改内部值——玩家血量的机会,不利于项目的维护。

2.3 将血量封装起来,定义属性访问

这里就涉及到一个面向对象程序设计的概念——封装,对于封装,我的另一篇博客有简单的介绍,这里就不再赘述:【Unity入门计划】了解C#或Unity中的类和对象_flashinggg的博客-CSDN博客

直接介绍方法,同样还是修改两个类:

  • RubyController
    //设置最大生命值(生命上限)
    public int maxHealth = 5;

    //当前生命值,默认为0
    public int currentHealth;

    //C#中支持面向对象程序设计中的封装,实现对数据成员进行保护
    //数据成员变量本身是私有的,只能通过某一种方法或者属性访问
    //属性是共有的,可以通过取值器--get,赋值器--set,设定对应字段的访问规则
    //满足规则才能访问该成员变量
    public int health { get { return currentHealth; } }

这里用到了get——取值器,后面会详细介绍C#的访问器。

  • HealthCollectible
        if(rubyController != null)
        {
            //血量+1
            rubyController.ChangeHealth(amount);
            if (rubyController.health            
关注
打赏
1688896170
查看更多评论
0.0612s