官方教程:世界交互 - 可收集对象 - 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,
你希望脚本检测 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
解决掉血过快,让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。
这时会看到有黄色的警告,在project的Environment文件夹中点击Damageable,将Mesh Type更改为Full Rect即可。
先在官方教程中下载敌人的图片Bot,存入Ruby图片的那个文件夹。
敌人跟主角一样,都需要碰撞和移动,所以给它添加Rigidbody2D 和 BoxCollider2D。记得也要给它FreezeRotation Z,防止它歪了。
修改一下它的碰撞体在脚底,重力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里变成预制件。