前言
在上篇文章,我们已经实现了对怪物造成伤害的功能,但此时我们只有发射导弹
一种攻击方式。为了增加游戏的可玩性,我们将制作炸弹
,玩家可以通过放置炸弹来对范围内的怪物造成伤害
。在开始制作炸弹
之前,我们先梳理一下和炸弹
有关的需求。
和
炸弹
有关的需求:
炸弹
会对爆炸半径
内的怪物
和角色
造成一定的伤害
炸弹
会对爆炸半径
内的怪物
和角色
产生一个冲击力
炸弹
被释放后,会先燃烧一段时间引信
,然后再爆炸- 角色可以在原地释放
炸弹
、也可以通过火箭筒向前发射炸弹
炸弹
属于大威力道具,因此炸弹
的放置有冷却时间
限制,且炸弹
的数量是有限的
制作炸弹Prefab
清楚了炸弹
的需求之后,我们先来制作炸弹
并实现炸弹
爆炸对怪物
和角色
造成伤害的功能。首先,我们将Assets\Sprites\Props
下的prop_bomb
拖拽到Hierarchy
窗口中,然后将其重命名为Bomb
。此时,可以看到场景里面出现了一个炸弹
,但尺寸比较大。这里,我们需要先将Assets\Sprites\Props
下的prop_bomb
的Pixel Per Unit
改为500
。
接着,因为炸弹
能对角色造成伤害,为了在检测时提高效率,我们不能将Player
的Layer
设置为Default
,因为Layer
为Default
的物体很多,这会让我们增加很多不必要的判断。我们首先新建一个名为Player
的Layer
,然后在Layer Collision Matrix
中取消Player-Setting
项的勾选,不让Player
和Setting
这两个Layer
的物体产生任何物理交互。最后,我们将物体Player
的Layer
设置为Player
,并将其修改Apply
至Player
的Prefab上。
接着,我们在Assets\Scripts\Weapons
新建一个名为Bomb
的C#脚本,然后编辑Bomb.cs
如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class Bomb : MonoBehaviour {
[Tooltip("炸弹产生的伤害值")]
public float DamageAmount = 50f;
[Tooltip("爆炸半径")]
public float BombRadius = 10f;
[Tooltip("爆炸时产生的冲击力")]
public float BombForce = 800f;
[Tooltip("炸弹爆炸时的音效")]
public AudioClip BoomClip;
[Tooltip("引信燃烧的时间")]
public float FuseTime = 1.5f;
[Tooltip("燃烧引信的音效")]
public AudioClip FuseClip;
[Tooltip("炸弹爆炸时的特效")]
public GameObject BombExplosion;
private LayerMask m_LayerMask;
private AudioSource m_AudioSource;
private void Awake() {
// 获取引用
m_AudioSource = GetComponent<AudioSource>();
// 取消默认播放
m_AudioSource.playOnAwake = false;
}
private void Start() {
// 设置LayerMask检测Enemy和Player这两个Layer
m_LayerMask = 1 << LayerMask.NameToLayer("Enemy") | 1 << LayerMask.NameToLayer("Player");
if (transform.root == transform){
// 如果不是附着在其他物体上,就开始执行燃烧引信的协程
StartCoroutine(BombDetonation());
}
}
// 燃烧引信的协程
private IEnumerator BombDetonation() {
// 设置燃烧引信的音效并播放
if(FuseClip != null) {
m_AudioSource.clip = FuseClip;
m_AudioSource.Play();
} else {
Debug.LogWarning("请设置FuseClip");
}
// 等待FuseTime时间
yield return new WaitForSeconds(FuseTime);
// 等待FuseTime时间之后,执行爆炸函数
Explode();
}
// 爆炸函数
public void Explode() {
// 获取一定范围内的所有Layer为Enemy或者Player物体
Collider2D[] objects = Physics2D.OverlapCircleAll(transform.position, BombRadius, m_LayerMask);
foreach(Collider2D obj in objects) {
// 对怪物造成伤害
if(obj.tag == "Enemy") {
obj.GetComponent<Enemy>().TakeDamage(this.transform, BombForce, DamageAmount);
continue;
}
// 对角色造成伤害
if(obj.CompareTag("Player")) {
obj.GetComponent<PlayerHealth>().TakeDamage(this.transform, BombForce, DamageAmount);
}
}
// 实例化爆炸特效
if(BombExplosion != null) {
Instantiate(BombExplosion, this.transform.position, Quaternion.identity);
} else {
Debug.LogWarning("请设置BombExplosion");
}
// 播放爆炸音效
if(BoomClip != null) {
AudioSource.PlayClipAtPoint(BoomClip, transform.position);
} else {
Debug.LogWarning("请设置BoomClip");
}
Destroy(gameObject);
}
}
代码说明:
m_LayerMask
: 为了让大家能理解LayerMask
的本质,我这里没有使用LayerMask.GetMask
这个静态方法来获取Layer Mask
,而是直接使用了位运算
StartCoroutine
:用于开始执行一个Coroutines(协程)
,Unity中的协程是基于C#提供的IEnumerator(迭代器)
实现的。Unity的协程
功能非常强大,使用起来也比较简单。我们只需要先定义一个返回类型为IEnumerator的方法
,然后在这个方法里面使用yield语句
来设置协程等待的条件,并使用StartCoroutine
来执行该方法。那么当程序执行到yield语句
这里的时候,就会停止执行后面的代码,然后每帧检查是否满足条件
,一旦满足条件就继续往下执行。Physics2D.OverlapCircleAll
:Unity提供的api,用于获取在某个圆形范围内的所有Collider2D
。
编辑完毕之后,我们将Bomb.cs
添加到Hierarchy
窗口的Bomb
物体上,可以看到Unity自动帮我们在Bomb
物体上添加了AudioSource
组件。为了让炸弹
具有物理属性,能和场景里其他物体发生物理碰撞,我们还需要为Bomb
物体添加Rigidbody2D
和Circle Collider2D
组件。然后,我们对每个组件进行以下的设置:
Bomb
物体的组件设置:
Sprite Renderer
:Sorting Layer
: WeaponsOrder In Layer
: 0
Bomb
:DamageAmount
: 50BombRadius
: 10BombForce
: 800BoomClip
:Assets\Audio\FX
下的bigboom
FuseTime
: 1.5fFuseClip
:Assets\Audio\FX
下的bombfuse
Circle Collider2D
:Is Trigger
: falseOffset
: (0, 0)Radius
: 0.5
Rigidbody2D
Gravity Scale
: 3.1
设置完毕之后,保存修改,然后将Bomb
物体从Hierarchy
窗口拖拽至Assets\Prefabs\Weapons
下将其做成Pfefab。接着,我们运行游戏,可以听到炸弹
燃烧引信和爆炸的音效,也能看到角色和怪物被炸弹炸飞并受到伤害的效果。但炸弹
在燃烧引信
和爆炸
的时候,没有任何的特效,无法让玩家直观地意识到炸弹在燃烧引信
和炸弹爆炸了
。因此,我们还需要为炸弹
添加引信燃烧特效
和爆炸特效
。
为炸弹添加爆炸特效
接下来,我们首先为炸弹
添加爆炸
特效。首先,我们将Assets\Sprites\FX
下的Circle
拖拽到Hierarchy
窗口中,并将Circle
物体重命名为BombExplosion
。接着,我们将Assets\Scripts\Utility
下的Destroyer.cs
添加到BombExplosion
物体上,并修改Destroyer.cs
如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Destroyer : MonoBehaviour {
[Tooltip("是否在Awake时执行销毁操作")]
public bool DestroyOnAwake = false;
[Tooltip("销毁延迟时间")]
public float AwakeDestroyDelay = 0f;
private void Awake() {
if(DestroyOnAwake) {
Destroy(this.gameObject, AwakeDestroyDelay);
}
}
// 销毁自身
private void DestroyGameObject() {
Destroy(gameObject);
}
}
接着,我们设置BombExplosion
物体的组件如下:
BombExplosion
物体的组件设置:
Sprite Renderer
:Sorting Layer
: WeaponsOrder In Layer
: 3
Destroyer
:Destroy On Awake
: trueAwake Destroy Delay
: 0.1
修改完成之后,我们将BombExplosion
物体拖拽至Assets\Prefabs\Weapons
文件夹下将其制作为Prefab。然后我们删除场景中的BombExplosion
物体,选中Bomb
的Prefab,将其Bomb.cs
下的Bomb Explosion
属性设置为BombExplosion
的Prefab。最后保存修改,运行游戏,可以看到炸弹爆炸时产生了爆炸特效。
为炸弹添加引信燃烧特效
为炸弹
添加完爆炸
特效之后,我们为炸弹
添加引信燃烧
特效。首先,我们在Bomb
物体下创建一个名为Sparks
的Empty Gameobject
,然后我们为其添加Particle System
组件。
Sparks
物体的Particle System
组件设置:
- Main Module:
Start Lifetime
: 0.5Start Size(Random Between Two Constants)
: (0.2, 1)Start Rotation(Random Between Two Constants)
: (0, 360)Gravity Modefier
: 1Simulation Space
: WorldScaling Mode
: ShapeMax Particles
: 100
- Emission:
Rate Over Time
: 40
- Shape:
Angle
: 36Radius
: 0.01Arc
: 0.01Randomize Direction
: 1
- Size over Lifetime:
Size(Random Between Two Constants)
: (0.4, 0.6)
- Rotation over Lifetime:
Angular Velocity(Random Between Two Constants)
: (0, 180)
- Texture Sheet Animation
Tiles
: (2, 2)
- Render
Material
:Assets\Materials
下的ExplosionParticle
Sorting Layer
: WeaponsOrder In Layer
: 4
修改完成之后,我们还需要调整Sparks
的Transform
组件。
Sparks
物体的Transform
组件设置:
Position
: (0.47, 0.53, 0)Rotation
: (0, 90, 0)
最后,我们将Bomb
物体上的修改Apply
至其Prefab上,然后运行游戏,可以看到炸弹
已经有了引信燃烧
特效了。
释放炸弹
为炸弹
添加好引信燃烧
和炸弹爆炸
特效之后,我们需要让角色能够释放炸弹。首先,我们删除场景中的Bomb
物体。接着,我们修改PlayerAttack.cs
如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// [RequireComponent(typeof(Animator))]
[RequireComponent(typeof(PlayerController))]
public class PlayerAttack : MonoBehaviour {
[Tooltip("导弹Prefab")]
public Missile MissilePrefab;
[Tooltip("导弹发射点")]
public Transform ShootingPoint;
[Tooltip("发射导弹的音效")]
public AudioClip ShootEffect;
[Tooltip("炸弹Prefab")]
public Rigidbody2D BombPrefab;
[Tooltip("炸弹的初始数量")]
public int InitBombNumber = 4;
[Tooltip("使用火箭筒抛射炸弹的力")]
public float ProjectileBombForce = 1000f;
private int m_CurrentBombNumber;
// private Animator m_Animator;
private PlayerController m_PlayerCtrl;
private void Awake() {
// 获取引用
// m_Animator = GetComponent<Animator>();
m_PlayerCtrl = GetComponent<PlayerController>();
// 检查关键属性是否赋值
if(MissilePrefab == null) {
Debug.LogError("请设置MissilePrefab");
}
if(ShootingPoint == null) {
Debug.LogError("请设置ShootingPoint");
}
if(BombPrefab == null) {
Debug.LogError("请设置BombPrefab");
}
}
private void Start() {
m_CurrentBombNumber = InitBombNumber;
}
private void Update() {
if (Input.GetButtonDown("Fire1")) {
// 发射导弹
Fire();
}
if (Input.GetButtonDown("Fire2")) {
// 放置炸弹
LayBomb();
}
if (Input.GetButtonDown("Fire3")) {
// 抛射炸弹
ProjectileBomb();
}
}
// 发射导弹
private void Fire() {
// // 播放射击动画
// m_Animator.SetTrigger("Shoot");
// 播放射击音效
AudioSource.PlayClipAtPoint(ShootEffect, ShootingPoint.position);
// 创建导弹
Missile instance = Instantiate(MissilePrefab, ShootingPoint.position, Quaternion.identity) as Missile;
// 如果角色跟导弹的朝向不一致,就翻转导弹
if(m_PlayerCtrl.FacingRight ^ instance.FacingRight) {
instance.Flip();
}
}
// 放置炸弹
private void LayBomb() {
if(m_CurrentBombNumber <= 0) {
return;
}
// 放置炸弹
Instantiate(BombPrefab, this.transform.position, Quaternion.identity);
// 减少炸弹数量
m_CurrentBombNumber --;
}
// 抛射炸弹
private void ProjectileBomb() {
if(m_CurrentBombNumber <= 0) {
return;
}
// 抛射炸弹
Rigidbody2D body = Instantiate(BombPrefab, ShootingPoint.position, Quaternion.identity) as Rigidbody2D;
if(m_PlayerCtrl.FacingRight) {
body.AddForce(Vector2.right * ProjectileBombForce);
} else {
body.AddForce(Vector2.left * ProjectileBombForce);
}
// 减少炸弹数量
m_CurrentBombNumber --;
}
}
修改完成之后,我们将BombPrefab
设置为Bomb
的Prefab,运行游戏,此时我们可以通过单击鼠标右键
来在原地释放炸弹
以及通过单击鼠标滚轮
来向前抛射炸弹
。最后,我们将Player
的修改Apply
至其Prefab上,保存场景的修改。
后言
至此,我们已完成实现放置炸弹的功能的所有工作。本篇文章涉及到的一些数值参数,大家可以根据自己的喜好进行修改。最后,本篇文章所做的修改,可以在PotatoGloryTutorial这个仓库的essay11
分支下看到,读者可以clone这个仓库到本地进行查看。