目录
8 添加生命系统
演示效果
9 使用触发器实现吃血包
9.1 添加一个可以被吃掉的血包
10 使用触发器添加伤害区域
10.1 设置伤害区域
10.2 设置无敌时间
关于无敌时间的讨论
10.3 平铺伤害区域/平铺精灵
1 精灵渲染器Draw Mode -> Tiled
2 精灵素材中Mesh Type -> Full Rect
3 2D碰撞体组件 -> 勾选Auto Tilling
11 添加敌人
11.1 敌人来回移动
11.2 敌人伤害
Collision2D 2D碰撞类
学习的教程
【unity2021入门教程】68-2D游戏开发教程系列-03-RubyAdventure2DRpg官方教程-16-完善触发器代码_哔哩哔哩_bilibili
已进行步骤
【Unity入门计划】制作RubyAdventure01-玩家的创建&移动_flashinggg的博客-CSDN博客
【Unity入门计划】制作RubyAdventure02-处理瓦片地图&碰撞_flashinggg的博客-CSDN博客
先在之前的Ruby Controller脚本中,加上一个简单的生命上限&规定初始生命值
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()
{
...
}
//控制速度,比update快一点
void FixedUpdate()
{
ChangeHealth(1);
}
//更改生命值
//amount是游戏中加血/减血的操作
void ChangeHealth(int amount)
{
//限制当前生命值范围为[0,maxHealth]
currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
//输出
Debug.Log("当前生命值为:" + currentHealth + "/" + maxHealth);
}
}
之前写过一个触发器的简单博客:【Unity入门计划】基本概念(2)-触发器 Trigger_flashinggg的博客-CSDN博客
它是一个特殊的碰撞体,只检测碰撞,不阻止移动。
关于数据保护内容,我另开了一个博客进行简单的记录介绍:【Unity入门计划】Unity实例-C#如何通过封装实现对数据成员的保护
先从Project中加入一个可以充当血包的精灵,同时创建一个预制件,给他一个2D碰撞体并设置为触发器。
挂一个检测触发并进行相应设置的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthCollectible : MonoBehaviour
{
//设置每次碰撞加的血量
public int amount = 1;
private int CollideTimes;
//添加触发器事件,每次碰撞触发器时执行其中的代码
private void OnTriggerEnter2D(Collider2D other)
{
CollideTimes++;
Debug.Log($"和当前物体发生碰撞的是:{other},当前是第{CollideTimes}次发生碰撞!");
//获取Ruby对象的脚本组件,还是用GetComponent方法
RubyController rubyController=other.GetComponent();
if(rubyController != null)
{
//血量+1
if (rubyController.health
同时RubyController中也要定义一个属性,访问私有变量currentHealth。
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
internal int currentHealth;
//C#中支持面向对象程序设计中的封装,实现对数据成员进行保护
//数据成员变量本身是私有的,只能通过某一种方法或者属性访问
//属性是共有的,可以通过取值器--get,赋值器--set,设定对应字段的访问规则
//满足规则才能访问该成员变量
public int health { get { return currentHealth; } }
这里只贴出了结果代码,具体过程直接参考对应的教程即可。
还是跟之前一样,添加伤害区域精灵,变成预制体,勾选Is Trigger,再挂脚本实现一些进入后减血的操作。
挂一个减血脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DamageZone : MonoBehaviour
{
public int damageHealth = -1;
public int damageSpeed;
private double TruedamageHealth = -0.02;
private double True = 0;
int round=1;
private void OnTriggerStay2D(Collider2D other)
{
RubyController rubyController=other.GetComponent();
if(rubyController != null)
{
True += damageSpeed*TruedamageHealth;
if (round==1||True < round * damageHealth)
{
round++;
rubyController.ChangeHealth(damageHealth);
}
}
}
}
这里我优化了一下,加入了可以控制减血速度的变量,详情可以看:【Unity入门计划】用双血条方法控制伤害区域减血速度_flashinggg的博客-CSDN博客
教程中把无敌时间设置在了玩家减血的方法里,意思就是无论伤害来自敌人还是来自陷阱,只要受到伤害,就会进入2秒的无敌时间。
先讨论一下这个方法是否无敌:
因此,我认为对于一个基础教程的游戏项目制作,把脚本直接挂在玩家对象上是可行的,意味着敌人也是基础的敌人、没有可以“连招攻击”的敌人,也没有叠加伤害一说。
如果希望敌人进行的连招攻击会对玩家造成叠加伤害,最好是将无敌时间的脚本挂在陷阱、敌人身上,便于修改。
这里因为只是跟着教程学习,因此我就跟着教程思路走,还是把脚本挂在玩家身上。
加入了无敌时间系统的玩家脚本代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RubyController : MonoBehaviour
{
//设置无敌时间
public float timeInvincible = 2.0f;
//设置判断是否无敌的bool变量
bool isInvincible;
//规定一个无敌时间计数器,倒计时
float invincibleTimer;
//声明刚体对象
Rigidbody2D rigidbody2D;
//在方法外声明水平和数值量
float horizontal;
float vertical;
//设置最大生命值(生命上限)
public int maxHealth = 5;
//当前生命值,默认为0
private int currentHealth;
//C#中支持面向对象程序设计中的封装,实现对数据成员进行保护
//数据成员变量本身是私有的,只能通过某一种方法或者属性访问
//属性是共有的,可以通过取值器--get,赋值器--set,设定对应字段的访问规则
//满足规则才能访问该成员变量
public int health { get { return currentHealth; } }
private void Start()
{
//游戏运行前,获取当前游戏对象的刚体组件
//这里用到了GetComponent获取当前所在对象的吗某个组件 - GetComponent<>()
rigidbody2D = GetComponent();
//初始化当前生命值
currentHealth = maxHealth;
}
// 每帧都会执行一次Update函数
void Update()
{
//判断是否无敌
if (isInvincible)
{
//是,则计时器开始倒计时:deltaTime获取两帧之间的时间间隔
invincibleTimer -= Time.deltaTime;
if (invincibleTimer < 0)
{
isInvincible = false;//倒计时结束,不再是无敌时间
}
}
//声明新变量
//获取水平轴并储存在新变量中
horizontal = Input.GetAxis("Horizontal");
//获取垂直轴
vertical = Input.GetAxis("Vertical");
}
//控制速度,比update快一点
void FixedUpdate()
{
Vector2 position = transform.position;
//对x轴移动距离做了细微的更改
position.x += horizontal * 3.0f * Time.deltaTime;
//对y轴也做相应更改
position.y += vertical * 3.0f * Time.deltaTime;
rigidbody2D.position = position;
}
//更改生命值
//amount是游戏中加血/减血的操作
public void ChangeHealth(int amount)
{
if (amount < 0)//if >0加血,则不进入无敌时间
{
if (isInvincible)//当前处于无敌状态,则不减血
{
return;//直接中段当前方法
}
//不是无敌状态,接下来会减血,因此提前改成true
isInvincible = true;
//重置计时器
invincibleTimer = timeInvincible;
}
//限制当前生命值范围为[0,maxHealth]
currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
//输出
Debug.Log("当前生命值为:" + currentHealth + "/" + maxHealth);
}
}
有时候需要平铺伤害区域,Unity自带有平铺精灵的功能。
还记得精灵渲染器里的Draw Mode,默认是Simple,选择Tiled
在对应的精灵素材属性中,Mesh Type选择Full Rect
再进入Scene中调整当前游戏对象,T键框选,发现可以实现平铺了。
还需要将碰撞体也平铺对应
此时就完成了伤害区域平铺的操作了。
首先需要在官网下载敌人Robot的图片。
世界交互 - 伤害区域和敌人 - Unity Learn
同样的操作,再Unity中把他变成精灵,挂上Order的脚本。
关于来回移动代码的编写,可以看看我的这篇博客:【Unity入门计划】2D游戏实现敌人来回移动控制脚本
这里想要实现Ruby碰撞敌人后,掉血的操作。由于需要真实的碰撞感,因此不能使用之前的OnTriggerEnter,而是采取碰撞检测。
//碰撞检测
//添加敌人伤害的逻辑
private void OnCollisionEnter2D(Collision2D other)
{
//获取和当前collision碰撞的对象的脚本组件
RubyController player = other.gameObject.GetComponent();
if (player != null)
{
player.ChangeHealth(Hurt);
}
}
官方文档:UnityEngine.Collision2D - Unity 脚本 API
在之前的一篇【Unity入门计划】基本概念(2)-触发器 Trigger中就有说到碰撞检测的三种类别。
在【Unity入门计划】Collision2D类&Collider2D类这篇博客中针对涉及到两个不同类的碰撞检测和触发检测,我对它们的回调函数在脚本中的使用进行一个简单的讨论。