本系列文章是根据官方视频教程而写下的学习笔记,原官方视频教程网址:https://unity3d.com/cn/learn/tutorials/s/tanks-tutorial
系列其他笔记传送门
Unity官方教程《Tanks》学习笔记(一)
Unity官方教程《Tanks》学习笔记(二)
Unity官方教程《Tanks》学习笔记(三)
Unity官方教程《Tanks》学习笔记(五)
创建子弹
本小节的目标是创建子弹,完善子弹的爆炸、音效等效果,并且利用脚本对子弹进行控制。
首先,在models文件夹下,找到Shell模型,把它拖拽到Hierarchy根目录下,我们对它进行编辑:
①创建Capsule Collider,勾选“Is Trigger”,把Direction选择为Z-Axis;更改Center的坐标为(0,0,0.2);更改Radius为0.15以及Height为0.55。
Capsule Collider实际上是一个胶囊碰撞器,由一个圆柱体连接两个半球体组成,在上面修改了该碰撞器的半径以及高度后,我们可以观察到子弹的碰撞边界是这样的:
②创建Rigidbody,为子弹添加刚体,因为子弹要与坦克产生碰撞,也就需要刚体。如果没有刚体,那么子弹就不会有物理效果。
③创建Light组件。
④在Prefabs文件夹下,找到ShellExplosion预制件,拖拽它到Shell中,成为Shell的子对象。选择ShellExplosion,添加Audio Source,音效选择为ShellExplosion,取消勾选Play On Awake。
⑤ 在Scripts/Shell文件夹内,找到ShellExplosion脚本,把它拖拽到Shell下。该脚本控制了Shell的行为,双击打开该脚本,开始编辑:
using UnityEngine;
public class ShellExplosion : MonoBehaviour
{
public LayerMask m_TankMask; //Player的层级
public ParticleSystem m_ExplosionParticles; //爆炸的粒子系统
public AudioSource m_ExplosionAudio; //Audio
public float m_MaxDamage = 100f; //最大伤害
public float m_ExplosionForce = 1000f;
public float m_MaxLifeTime = 2f;
public float m_ExplosionRadius = 5f; //子弹爆炸半径
private void Start()
{
Destroy(gameObject, m_MaxLifeTime); //在子弹的存活时间过后,自动销毁
}
/**
* 当子弹与其他物体发生碰撞并且碰撞器的Is Trigger勾选的情况下,会调用该函数
*/
private void OnTriggerEnter(Collider other)
{
// Find all the tanks in an area around the shell and damage them.
/**
* Physics.OverlapSphere(Vector3 position,float radius,int layerMask mask)
* @parameter position 球体的球心
* @parameter radius 球体的半径
* @parameter mask 只有该层级与球体碰撞才会被选择
* 该函数用于返回在球体范围内与球体产生碰撞的特定层级的碰撞器
*/
Collider[] colliders = Physics.OverlapSphere(transform.position,m_ExplosionRadius,m_TankMask);
for(int i = 0;i < colliders.Length;i++){
Rigidbody targetRigidbody = colliders[i].GetComponent(); //寻找碰撞器的刚体
if(!targetRigidbody){
continue;
}
//为符合条件的受撞体添加爆炸力
targetRigidbody.AddExplosionForce(m_ExplosionForce,transform.position,m_ExplosionRadius);
//获取受撞体的TankHealth脚本
TankHealth targetHealth = targetRigidbody.GetComponent();
if(!targetHealth){
continue;
}
//计算伤害并扣除血量
float damage = CalculateDamage(targetRigidbody.position);
targetHealth.TakeDamage(damage);
}
//把粒子系统与Shell的关联解除
m_ExplosionParticles.transform.parent = null;
//播放爆炸效果及音效
m_ExplosionParticles.Play();
m_ExplosionAudio.Play();
//粒子系统的爆炸效果播放完毕后,删除该object
Destroy(m_ExplosionParticles.gameObject,m_ExplosionParticles.main.duration);
Destroy(gameObject); //回收Shell
}
private float CalculateDamage(Vector3 targetPosition)
{
// Calculate the amount of damage a target should take based on it's position.
//1、创建一个向量,由Shell指向目标
Vector3 explosionToTarget = targetPosition - transform.position;
//2、计算Shell与目标之间的距离
float explosionDistance = explosionToTarget.magnitude;
//3、根据上一步的距离计算出伤害权重
float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;
//4、根据伤害权重计算出最终伤害
float damage = relativeDistance * m_MaxDamage;
//5、最低伤害为0,这是因为第三步的数值有可能是负值,这样就相当于没有伤害
damage = Mathf.Max(0f,damage);
return damage;
}
}
接着,我们初始化该脚本使用的公有变量:
这里要 注意的是,Tank Mask的选取必须是Players,否则后面子弹的爆炸将不会作用于坦克。
完成以上的步骤之后,把Shell拖拽到Prefabs文件夹下,成为预制件,然后删除掉Hierarchy根目录下的Shell,并保存当前场景。
发射子弹
接下来就要实现子弹的发射功能,并且子弹可以蓄能,蓄能越久射击距离也就越长,因此也就需要有一个蓄能状态的指示。
首先,选中Hierarchy层级下的Tank,为他新建一个子对象,Create Empty,命名为FireTransform。该对象主要是规定子弹的射出位置。把FireTransform的transform设置为如下:
接着,在Tank的Canvas下,新建一个Slider,命名为AimSlider。选定AimSlider,对它的Slider组件进行一些调整:
①取消勾选Interactable
②Transition设置为None
③Direction设置为Bottom to Top
④Min Value设置为15
⑤Max Value设置为30
⑥Rect Transform属性可以通过拖动改变形状,也可以通过设置数值的形式指定,这里设置为(1,-9,-1,1,3)
如下图所示:
下一步是展开AimSlider的子对象,把其中的Background和Handle Slide Area删除,只保留Fill Area。同时选中AimSlider和Fill Area,点击Anchor Presets,按住Alt键以选中右下角的选项:
展开Fill Area,选中Fill,把Height设置为0,Source Image选择为Aim Arrow:
接下来就是利用脚本对坦克的射击行为作出控制,在/Scripts/Tank文件夹下找到Tank Shooting脚本,把它拖拽到Tank中,然后我们打开编辑它:
using UnityEngine;
using UnityEngine.UI;
public class TankShooting : MonoBehaviour
{
public int m_PlayerNumber = 1; //玩家序号
public Rigidbody m_Shell; //炮弹
public Transform m_FireTransform; //发射点位置
public Slider m_AimSlider; //蓄能条
public AudioSource m_ShootingAudio; //Audio
public AudioClip m_ChargingClip; //蓄能的音效
public AudioClip m_FireClip; //发射的音效
public float m_MinLaunchForce = 15f; //最小发射力量
public float m_MaxLaunchForce = 30f; //最大发射力量
public float m_MaxChargeTime = 0.75f; //最大充能时间
private string m_FireButton;
private float m_CurrentLaunchForce;
private float m_ChargeSpeed;
private bool m_Fired;
/**
* 初始化
*/
private void OnEnable()
{
m_CurrentLaunchForce = m_MinLaunchForce;
m_AimSlider.value = m_MinLaunchForce;
}
private void Start()
{
//保存当前player发射按键的字符串
m_FireButton = "Fire" + m_PlayerNumber;
//充能速度
m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;
}
private void Update()
{
// Track the current state of the fire button and make decisions based on the current launch force.
m_AimSlider.value = m_MinLaunchForce;
//如果充能超过最大充能并且还没发射子弹
if(m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired){
m_CurrentLaunchForce = m_MaxLaunchForce;
Fire();
}
//根据按键判断是否按下了开火键
else if(Input.GetButtonDown(m_FireButton)){
m_Fired = false;
m_CurrentLaunchForce = m_MinLaunchForce; //从最小充能开始
m_ShootingAudio.clip = m_ChargingClip; //切换成充能音效
m_ShootingAudio.Play();
}
//持续按下开火键的过程中,不断增加充能
else if(Input.GetButton(m_FireButton) && !m_Fired){
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
m_AimSlider.value = m_CurrentLaunchForce;
}
//用户松开开火键,那么开火
else if(Input.GetButtonUp(m_FireButton) && !m_Fired){
Fire();
}
}
private void Fire()
{
// Instantiate and launch the shell.
m_Fired = true;
//实例化一个炮弹
Rigidbody shellInstance = Instantiate(m_Shell,m_FireTransform.position,m_FireTransform.rotation) as Rigidbody;
shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward;
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play();
m_CurrentLaunchForce = m_MinLaunchForce;
}
}
接着,就是对脚本的公有变量进行赋值:
完成初始化之后,点击右上角的Apply,使得改动在预制件中生效。
现在可以开始游戏测试一下了。测试完毕之后,把Hierarchy层的Tank删除掉,保存当前场景。