赞
踩
现已经完成对世界的基本装饰了,接下来去实现更多玩法,比如角色的生命值。
添加角色生命值系统能更好的反应角色与世界的互动,比如角色的生命值的减少或者提供一些道具恢复生命值。
打开角色脚本,修改代码如下:
- public class RubyController : MonoBehaviour
- {
- public int maxHealth = 5;
- int currentHealth;
-
- Rigidbody2D rigidbody2d;
- float horizontal;
- float vertical;
-
- // 在第一次帧更新之前调用 Start
- void Start()
- {
- rigidbody2d = GetComponent<Rigidbody2D>();
-
- currentHealth = maxHealth;
-
- }
-
- // 每帧调用一次 Update
- void Update()
- {
- horizontal = Input.GetAxis("Horizontal");
- vertical = Input.GetAxis("Vertical");
- }
-
- void FixedUpdate()
- {
- Vector2 position = rigidbody2d.position;
- position.x = position.x + 3.0f* horizontal * Time.deltaTime;
- position.y = position.y + 3.0f * vertical * Time.deltaTime;
-
- rigidbody2d.MovePosition(position);
- }
-
- void ChangeHealth(int amount)
- {
- currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
- Debug.Log(currentHealth + "/" + maxHealth);
- }
-
- }
在这里我们创建了maxHealth来储存角色的最大生命值,而currentHealth用来更新角色当前的生命值,由于在游戏开始的时候角色即是最大生命值。
- void Start()
- {
- rigidbody2d = GetComponent<Rigidbody2D>();
-
- currentHealth = maxHealth;
- }
此时我们设置好了生命值,就需要增加函数来更改生命值。
- void ChangeHealth(int amount)
- {
- currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
- Debug.Log(currentHealth + "/" + maxHealth);
- }
让我们看下函数内的主体,这里使用了Mathf.Clamp的内置函数来设置当前的生命值,这是为了防止生命值超过我们设置的最大值和低于我们的最小值(即防止生命值出现负数)
Camp可以确保第一个参数(currentHealth + amount)绝不会小于第二个参数(0),也绝对不会大于第三个参数(maxHealth)。
此时我们回到Unity编译器,启动用游戏并点击Console窗口进行查看,可以看到我们们的生命值与最大生命值。
接下来我们介绍触发器的使用,触发器是一种特殊类型的碰撞体。触发器不会阻止移动,但是物理系统仍会检查角色是否会与触发器碰撞。当你的角色进入触发器时,你将收到一条消息,以便你可以处理该事件。
在Project窗口中,选择Assets > Art > Sprites > VFX ,并找到CollectibleHealth。
将这个游戏对象导入到场景中,并调整PPU值设置为适当大小。
将Box Collider 2D组件添加到新游戏对象,调整碰撞体大小,以便更好的适应精灵。
现在,如果我们运行游戏,角色与这个物体也会出现碰撞,但是我们需要的是拾取它,所以不能有这种碰撞。所以我们点击这个物体,在Inspector中,找到Box Collider 2D组件。启用Is Trigger属性复选框。
现在,我们需要添加一下代码来处理碰撞。
- 在Project窗口中,选择Assets > Scripts,右键选择Create > C# Script。
- 重命名脚本为HealthCollectible。在Hierarchy中,选择CollectibleHealth游戏对象,将HealthCollectible脚本从Project窗口中拖放到Inspector,从而将这个脚本作为组件挂载到游戏对象上。
- 双击脚本文件,删除Start和Update函数,因为我们不需要再游戏开始时或者游戏每一帧中处理任何事情。
我们需要此脚本检测与角色的碰撞情况,并为角色增加生命值,故添加以下函数。
void OnTriggerEnter2D(Collider2D other)
提示:确保函数名称和参数类型的拼写正确,因为 Unity 在需要调用函数时会使用这些名称在脚本中“查找”函数。
与 Unity 每帧调用 Update 函数的方式相同,当检测到新的刚体进入触发器时,Unity 将在第一帧调用此 OnTriggerEnter2D 函数。名为 other 的参数将包含刚进入触发器的碰撞体。
在HealthCollectible脚本中修改如下代码:
- void OnTriggerEnter2D(Collider2D other)
- {
- RubyController controller = other.GetComponent<RubyController>();
-
- if (controller != null)
- {
- controller.ChangeHealth(1);
- Destroy(gameObject);
- }
- }
Destroy 是 Unity 的一个内置函数,可销毁你作为参数传递给这个函数的任何对象;在此示例中为 gameObject。这是将脚本附加到的游戏对象(可收集的生命值包)
返回到Unity编译器中,控制台上会报错:“RubyController.ChangeHealth(int)' is inaccessible due to its protection level”
打开RubyController脚本。对于函数和变量都可以在其之前添加“public”来使这个变量或者函数显示在Inspector中,默认情况下,HealthCollectible脚本无法访问Ruby中的函数,所以我们需要将ChangeHealth函数前添加“public”。在Start函数中,将currentHealth设置为1,使角色最初具有最小的生命值。
- void Start()
- {
- rigidbody2d = GetComponent<Rigidbody2D>();
- currentHealth = maxHealth;
- currentHealth = 1;
- }
-
- // 每帧调用一次 Update
- void Update()
- {
- horizontal = Input.GetAxis("Horizontal");
- vertical = Input.GetAxis("Vertical");
- }
-
- void FixedUpdate()
- {
- Vector2 position = rigidbody2d.position;
- position.x = position.x + speed * horizontal * Time.deltaTime;
- position.y = position.y + speed * vertical * Time.deltaTime;
-
- rigidbody2d.MovePosition(position);
- }
-
- public void ChangeHealth(int amount)
- {
- currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
- Debug.Log(currentHealth + "/" + maxHealth);
- }
现在完成了可收集生命值包,并且生命值绝不会超过maxHealth。顺便将我们制作好的HealthCollectible游戏对象创建一个预制件,我们可以用这个预制件放置多个对象了。
不过在这里存在一点小问题,就是当角色的生命值满时去拾取生命值包也可以拾取,所以我们需要增加一项来检测Ruby是否需要生命值。故我们打开HealthCollectible脚本修改代码
- void OnTriggerEnter2D(Collider2D other)
- {
- RubyController controller = other.GetComponent<RubyController>();
-
- if (controller != null)
- {
- if(controller.currentHealth < controller.maxHealth)
- {
- controller.ChangeHealth(1);
- Destroy(gameObject);
- }
- }
- }
这个脚本中添加了另一项测试以查看 Ruby 的生命值是否小于最大生命值。如果相等,该测试的结果将为 false,并且脚本将跳过 if 语句(因为无需更改生命值或销毁可收集对象)。
但在切换回 Unity 之前请先等等!你可能已经猜到在控制台中会有一个错误,这是正常的;currentHealth 是私有变量,因此你无法从外部访问这个变量。
你可以像以前一样将这个变量设置为公共 (public) 变量。但是,将所有内容都公开会导致错误,尤其是对于某些内容,你可能并不希望从任何位置都可以访问和更改它们。 例如,如果将 currentHealth 设置为 public,则可以在另一个脚本中将这个变量更改为 10,即使 maxHealth 仅为 5 的情况下也可以这样修改。为避免这种情况,最好只允许通过函数进行更改。这样做便于执行检查,就像 ChangeHealth 函数一样,可以使用钳制功能确保生命值绝不会小于 0,也不会大于 maxHealth。
因此,你不想将这个变量公开(以防止其他脚本进行更改),但是仍然希望允许其他脚本读取 currentHealth 的值(但这些脚本无法写入值)。为此,你可以使用属性。
- public class RubyController : MonoBehaviour
- {
- public float speed = 3.0f;
-
- public int maxHealth = 5;
-
- public int health { get { return currentHealth; }}
- int currentHealth;
-
- Rigidbody2D rigidbody2d;
-
- // 在第一次帧更新之前调用 Start
- void Start()
- {
你通过以下要素开始了像变量一样的属性定义:
- 访问级别(public)
- 类型(int)
- 名称(health)
但是,此处添加了两个 { } 代码块,而未使用 ; 来结束代码行。
在第一个代码块中,你使用了 get 关键字来获取第二个代码块中的任何内容。
第二个代码块就像普通函数一样,因此只需返回 currentHealth 值。
编译器完全像函数一样处理此代码行,因此你可以在 get 代码块内的函数中编写所需的任何内容(例如声明变量、执行计算和调用其他函数)。
打开HealthCollectible脚本,在代码中找到if语句:
if(controller.health < controller.maxHealth)
属性的用法很像变量,而不是像函数。此处,health 向你提供 currentHealth。但是,只有你读取 health 时,才有这样的作用。
如果我们尝试修改代码为:
controller.health = 10;
Unity 的控制台中会显示一个错误。这是因为你只为 health 指定了“get”操作,因此它是只读的。
注意:set 关键字的原理与 get 完全相同,但作用是使属性变为可写。如果提供 set 而没有 get,则只能写入属性,而无法读取属性。
最终我们的HealthCollectible脚本代码如下:
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class HealthCollectible : MonoBehaviour
- {
- void OnTriggerEnter2D(Collider2D other)
- {
- RubyController controller = other.GetComponent<RubyController>();
-
- if (controller != null)
- {
- if(controller.health < controller.maxHealth)
- {
- controller.ChangeHealth(1);
- Destroy(gameObject);
- }
- }
- }
- }
在下一章节中,我们将学会如何设计敌人和伤害区域!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。