Unity学习|Unity 2D 游戏学习笔记(6)

官方教程:世界交互 - 可收集对象 - Unity Learn
这次我们实现收集对象、伤害区域。
四、收集对象 做一个吃草莓加血的功能,首先Ruby得有血量,顺便把速度也改成可调整的,注释后加*的代表改动或新的代码。

using System.Collections; using System.Collections.Generic; using UnityEngine; public class RubyControl2 : MonoBehaviour { //总血量* public int maxHealth = 5; //当前血量* int currentHealth; //速度* public float speed = 3.0f; //创建一个刚体变量 Rigidbody2D rigidbody2d; //因为要在不同函数中使用这两个变量,所以要声明在最外侧 float horizontal; float vertical; // Start is called before the first frame update void Start() { //将脚本中的Rigidbody2D中的属性赋值给rigidbody2d rigidbody2d = GetComponent(); //开始先满血* currentHealth = maxHealth; }void Update() { //利用GetAxis函数,调用用户输入的轴的值 horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); } private void FixedUpdate() { //得到脚本中Ruby刚体的位置信息 Vector2 position = rigidbody2d.position; //每一帧,x的位置的加速度乘以用户输入移动的量* position.x += speed * horizontal * Time.deltaTime; position.y += speed * vertical * Time.deltaTime; //将移动的距离返回给Ruby刚体 rigidbody2d.MovePosition(position); }//生命值变化* void ChangeHealth(int amount) { //使用函数Mathf中的方法clamp,让第一个参数的值小于第二个参数的值时,等于第二个参数; //第一个参数的值大于第三个参数的值时,等于第三个参数。 currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //在控制台显示血量 Debug.Log(currentHealth + "/" + maxHealth); } }

Ruby的血量有了,接下来需要找到草莓,在Art-->Sprites-->VRX中的CollectibleHealth,将它拖入Hierarchy中,给它添加Box Collider 2D,勾选Is Trigger(触发器),这样就可以记录触碰但是可以穿过草莓。
我们给草莓编写一个加血脚本,在Scripts文件夹中创建一个新C# Script,命名为HealthCollectible,

【Unity学习|Unity 2D 游戏学习笔记(6)】你希望脚本检测 Ruby 与可收集的生命值游戏对象发生碰撞的情况,并向她提供一些生命值。为此,请使用以下特殊函数名称:
void OnTriggerEnter2D(Collider2D other) 提示:确保函数名称和参数类型的拼写正确,因为 Unity 在需要调用函数时会使用这些名称在脚本中“查找”函数。
与 Unity 每帧调用 Update 函数的方式相同,当检测到新的刚体进入触发器时,Unity 将在第一帧调用此 OnTriggerEnter2D 函数。名为 other 的参数将包含刚进入触发器的碰撞体。记得要将RubyController中的ChangeHealth前加上public。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class HealthCollectible : MonoBehaviour { private void OnTriggerEnter2D(Collider2D other) { //访问进入触发器的碰撞体的游戏对象上的RubyController组件 RubyControl2 controller = other.GetComponent(); //因为触发条件是有物体碰触,所以当不是Ruby碰触时,Controller为空 if(controller != null) { //加1血 controller.ChangeHealth(1); //吃完草莓消失 Destroy(gameObject); } } }

将血量改成1血,运行游戏吃草莓发现控制台返回2/5,并且草莓消失。
但是当Ruby满血时,不让它吃草莓防止浪费。实现很简单只需要这样
在RubyControl中:
public int health { get { return currentHealth; } } //当前血量 int currentHealth;

在HealthCollectible中:
if(controller != null) { if(controller.health < controller.maxHealth) { //加1血 controller.ChangeHealth(1); //吃完草莓消失 Destroy(gameObject); }}

为什么要这么写?因为游戏很多属性不适合用public公开,这样可能与其他脚本里重复或者很容易被其他人更改,所以我们以只读的方式在RubyControl中重新定义了个health,这样它既无法被更改,又可以被其他脚本读取到。
现在运行游戏,会发现满血时触碰草莓就没反应了。
五、伤害区域 这次做一个进到刺里持续掉血的功能。首先找到游戏里的Assets > Art > Sprites > Environment 中为区域找到一个名为 Damageable 的精灵,将其拖进Hierarchy窗口中,给它添加Box Collider 2D,勾选Is Trigger。
然后我们在Scripts文件夹中创建新C# Script编写它的脚本。
public class DamageZone : MonoBehaviour { //要注意一定是2D private void OnTriggerEnter2D(Collider2D other) { //获取到控制Ruby的组件 RubyControl2 Controller = other.GetComponent(); //如果Ruby进入到伤害范围内 if(Controller != null) { //让它血-1 Controller.ChangeHealth(-1); } } }

运行后就可以实现进入掉血了,但是没有持续掉血效果。通过设定无敌时间来实现该效果。
可以通过将函数名称从 OnTriggerEnter2D 更改为 OnTriggerStay2D 来解决此问题。刚体在触发器内的每一帧都会调用此函数,而不是在刚体刚进入时仅调用一次。
但是按照帧率减血,一下就死了,而且你可能还注意到,当你停止移动 Ruby 时,你在控制台上不会收到任何消息,因此 Ruby 站着不动时不会受到伤害。
要解决最后这个问题,你需要打开角色预制件,然后在 Rigidbody2D 组件中将 Sleeping Mode 设置为 Never Sleep
Unity学习|Unity 2D 游戏学习笔记(6)
文章图片

解决掉血过快,让Ruby有两秒的无敌时间就行了。代码和注释如下,更改代码用*标注。
public class RubyControl2 : MonoBehaviour { //无敌时间长度* public float timeInvincible = 2.0f; //判断是否无敌* bool isInvincible; * //当前消耗的无敌时间(计时器)* float invincibleTimer; //总血量 public int maxHealth = 5; //速度 public float speed = 3.0f; public int health { get { return currentHealth; } } //当前血量 int currentHealth; //创建一个刚体变量 Rigidbody2D rigidbody2d; //因为要在不同函数中使用这两个变量,所以要声明在最外侧 float horizontal; float vertical; // Start is called before the first frame update void Start() { //将脚本中的Rigidbody2D中的属性赋值给rigidbody2d rigidbody2d = GetComponent(); //开始先满血 currentHealth = maxHealth; }void Update() { //利用GetAxis函数,调用用户输入的轴的值 horizontal = Input.GetAxis("Horizontal"); vertical = Input.GetAxis("Vertical"); //如果是无敌的* if(isInvincible) { //减时间 invincibleTimer -= Time.deltaTime; //当无敌时间消耗光,让是否无敌改为否 if(invincibleTimer < 0) { isInvincible = false; } } } private void FixedUpdate() { //得到脚本中Ruby刚体的位置信息 Vector2 position = rigidbody2d.position; //每一帧,x的位置的加速度乘以用户输入移动的量 position.x += speed * horizontal * Time.deltaTime; position.y += speed * vertical * Time.deltaTime; //将移动的距离返回给Ruby刚体 rigidbody2d.MovePosition(position); }public void ChangeHealth(int amount) { //如果是减血,则判断是否无敌时间* if(amount < 0) { //如果是无敌,就不减血了,返回 if (isInvincible) return; //如果不是无敌时间,把状态改为无敌, isInvincible = true; //添加无敌时间 invincibleTimer = timeInvincible; } //使用函数Mathf中的方法clamp,让第一个参数的值小于第二个参数的值时,等于第二个参数; //第一个参数的值大于第三个参数的值时,等于第三个参数。 currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth); //在控制台显示血量 Debug.Log(currentHealth + "/" + maxHealth); } }

更改过后,发现已经实现了无敌时间。
如果想扩大碰触的面积,直接拉伸图形会导致图形变大变丑失去比例等等,点击Damageable,在他的Inspector窗口中将Draw Mode更改为Tiled,并且将Tile Mode修改为Adaptive。
Unity学习|Unity 2D 游戏学习笔记(6)
文章图片

这时会看到有黄色的警告,在project的Environment文件夹中点击Damageable,将Mesh Type更改为Full Rect即可。
Unity学习|Unity 2D 游戏学习笔记(6)
文章图片

六、敌人 先在官方教程中下载敌人的图片Bot,存入Ruby图片的那个文件夹。
敌人跟主角一样,都需要碰撞和移动,所以给它添加Rigidbody2D 和 BoxCollider2D。记得也要给它FreezeRotationZ,防止它歪了。
修改一下它的碰撞体在脚底,重力Gravity scale设为0。怪物一般都来回移动,搞个脚本给它,新建C#Script在Scripts文件夹里,命名为EnemyController。
左右移动的脚本如下:
public class EnemyController : MonoBehaviour { public float speed = 3.0f; //用来选择是纵向移动还是横向移动 public bool vertical; //计时器 public float changeTime = 3.0f; //计时器的当前值 float timer; //用来控制方向 int direction = 1; Rigidbody2D robot; // Start is called before the first frame update void Start() { //获得机器人的组件对象 robot = GetComponent(); //开始时给计时器固定的时间 timer = changeTime; } private void Update() { //计时 timer -= Time.deltaTime; //当计时结束,反向,重新计时 if(timer < 0) { direction = -direction; timer = changeTime; } }// Update is called once per frame void FixedUpdate() { //机器人刚体的位置信息给position Vector2 position = robot.position; //如果勾选了vertical,就纵向移动 if(vertical) { position.y += Time.deltaTime * speed * direction; } else { position.x += Time.deltaTime * speed * direction; }robot.MovePosition(position); } }

接下来给敌人增加伤害
private void OnCollisionEnter2D(Collision2D other) { RubyControl2 player = other.gameObject.GetComponent(); if(player != null) { player.ChangeHealth(-1); } }


另一个变化是,没有使用 other.GetComponent,而是使用的 other.gameObject.GetComponent。这是因为此处的 other 类型是 Collision2D,而不是 Collider2D。Collision2D 没有 GetComponent 函数,但是它包含大量有关碰撞的数据,例如与敌人碰撞的游戏对象。因此,在这个游戏对象上调用 GetComponent。
这样,敌人就设置好了,把它拖进Prefab里变成预制件。

    推荐阅读