Unity笔记-12-练习项目武器模块-第二版
策划-武器模块
如果弹匣内装有子弹,可以发射;否则,等待更换弹匣;
发射子弹时,播放音效,动画,显示火花;
玩家的枪可以单发也可以连发;
玩家子弹
- 击中敌人后减少敌人
HP ,后续将加入根据击中敌人的位置而减少不同程度的HP - 子弹飞行到目标点,销毁,并创建相应特效
敌人子弹
- 击中玩家后,玩家
HP 减少 - 子弹飞行到目标点,销毁,并创建相应特效
- 朝玩家头部发射,飞行速度较慢,方便玩家躲避
需求分析
创建脚本:玩家枪Gun ,提供开火,更换弹匣功能
创建脚本:单发模式SingleGun ,继承自Gun ,根据玩家输入调用相应开火方式
创建脚本:连发模式AutomaticGun ,继承自Gun ,根据玩家输入调用相应开火方式
创建脚本:敌人枪EnemyGun ,提供自动开火功能,无限弹药
——————————————————————————————————————
创建脚本:子弹Bullet ,计算攻击的目标点,执行移动,创建接触特效;
玩家检测环境和敌人;敌人检测环境;
创建脚本:玩家子弹PlayerBullet ,继承自Bullet ,击中敌人后扣除HP
创建脚本:敌人子弹EnemyBullet ,击中玩家后扣除HP
注:以上所有特效,声音相关暂不实现
脚本实现与解释说明
玩家部分
玩家状态
并提供玩家这一唯一对象的引用,以便其他所有对象都能获得玩家对象
提供受伤,死亡等方法
public class PlayerDemo : MonoBehaviour
{
//提供当前的对象引用
public static PlayerDemo Instance { get; private set; }
public float HP;
private void Start()
{
Instance = this;
HP = 100;
}
/// <summary>
/// 受伤
/// </summary>
/// <param name="attackNumber">伤害数字</param>
public void Damage(int attackNumber)
{
HP -= attackNumber;
if (HP <= 0)
{
Death();
}
}
/// <summary>
/// 死亡
/// </summary>
private void Death()
{
Destroy(this.gameObject);
}
}
枪
BoxMagezine-弹匣
弹匣类,提供弹匣容量,子弹总数,当前子弹数等数据,挂在枪模型对象的父空对象上
public class BoxMagezineDemo : MonoBehaviour
{
/// <summary>
/// 弹匣容量
/// </summary>
public int boxMagazine = 50;
/// <summary>
/// 当前弹匣内的子弹数
/// </summary>
public int currentBullet;
/// <summary>
/// 总子弹数
/// </summary>
public int allBullet;
}
GunAnimation-枪动画
枪动画类,提供枪的一系列动画播放功能
AnimationTool 类,为动画与动画工具类,后续会说明,挂在枪模型的父空对象上
public class GunAnimation : MonoBehaviour
{
/// <summary>
/// 开火动画
/// </summary>
public string fireAnimation="Gunfire";
/// <summary>
/// 更换弹匣动画
/// </summary>
public string updateAmmoAnimation="GunUpdateAmmo";
/// <summary>
/// 缺少子弹动画
/// </summary>
public string lackBulletAnimation="GunLackBullet";
/// <summary>
/// 播放工具
/// </summary>
public AnimationTool action;
private void Awake()
{
action = new AnimationTool(GetComponentInChildren<Animation>());
}
}
BulletLight-子弹火光
枪口火光对象脚本,提供闪烁火光特效功能,挂在火光对象上,火光对象为枪模型父空对象的孩子,也就是枪模型对象的兄弟
public class BulletLightDemo : MonoBehaviour
{
private GameObject bulletLight;
private void Start()
{
bulletLight = GetResource();
}
/// <summary>
/// 火光闪烁
/// </summary>
public void DisplayLight()
{
Destroy(Instantiate(bulletLight, transform.position, Quaternion.identity),0.20F);
}
private GameObject GetResource()
{
return Resources.Load<GameObject>("GunTest/Bullet/BulletLight");
}
}
Gun-枪
玩家射击模式的父类,提供射击,填充弹匣等功能,注意:此脚本不挂在对象上,挂在对象上的是继承此类的射击模式:SingleGun ,AutomaticGun ,射击模式脚本挂在枪模型对象的父空对象上
—————————————————————————————————————————————————————
射击
分以下两个个步骤:1.准备阶段:是否能够射击;2.创建子弹;
准备阶段:
- 弹匣里是否有子弹
- 当前是否在播放换弹匣动画
/// <summary>
/// 子弹准备
/// </summary>
/// <returns></returns>
private bool Ready()
{
if (boxMagezine.currentBullet <= 0)
{
anim.action.Play(anim.lackBulletAnimation);//如果没有子弹则播放动画提示,并返回false
return false;
}
if (anim.action.isPlay(anim.updateAmmoAnimation))//如果在换子弹,返回false
return false;
return true;
}
创建子弹
- 准备完毕
- 创建子弹
- 播放射击动画以及火光对象
创建子弹对象方法 :Object.Instantiate(对象模版,创建点,初始朝向) 方法
/// <summary>
/// 开火
/// </summary>
/// <param name="direction">枪口方向</param>
protected virtual void Firing(Vector3 direction)
{
//准备子弹
//判断是否做好发射准备
if (!Ready()) return;
//玩家枪发射,枪口方向
//创建子弹
//这里朝向可以用Quaternion.LookRotation,但是由于我这里的Z轴不是子弹朝向所以只能另外去改
GameObject Bullet = Instantiate(bullet, transform.TransformPoint(Vector3.right), Quaternion.identity);
//由于我这里的子弹朝向为Y轴,因此我要把枪口方向赋给子弹对象的Y轴方向
Bullet.GetComponent<Transform>().up = direction;
//播放射击动画
anim.action.Play(anim.fireAnimation);
//播放火光对象
bulletLight.DisplayLight();
//播放音频
//audioSource.PlayOneShot(audio);
//弹匣内子弹数减1
boxMagezine.currentBullet--;
}
—————————————————————————————————————————————————————
填充弹匣
当总子弹数不为0或者当前弹匣内子弹数小于弹匣容量时,允许填充弹匣
填满弹匣子弹数,并从总子弹数里减去对应的数量,如果无法填满,那么把剩余所有的子弹数都填充给弹匣,并将总子弹数归零
/// <summary>
/// 填充弹匣
/// </summary>
protected void UpdateAmmo()
{
if (boxMagezine.allBullet > 0&& boxMagezine.currentBullet < boxMagezine.boxMagazine)
{
anim.action.Play(anim.updateAmmoAnimation);//播放填充弹匣动画
int add = boxMagezine.boxMagazine - boxMagezine.currentBullet;
if (add <= boxMagezine.allBullet)
{
boxMagezine.allBullet -= add;
boxMagezine.currentBullet += add;
}
else
{
boxMagezine.currentBullet += boxMagezine.allBullet;
boxMagezine.allBullet = 0;
}
}
else
{
Debug.Log("FullorNone");
//提示已经没有子弹了或弹匣已满
}
}
—————————————————————————————————————————————————————
更换开火模式
/// <summary>
/// 切换开火模式
/// </summary>
private void ChangeFiringMethod()
{
if (Input.GetKeyDown(KeyCode.B))
{
//切换模式
GetComponent<SingleGunDemo>().enabled = !GetComponent<SingleGunDemo>().enabled;
GetComponent<AutomaticGunDemo>().enabled = !GetComponent<AutomaticGunDemo>().enabled;
}
}
—————————————————————————————————————————————————————
初始化Gun的各类资源与说明
主要字段
/// <summary>
/// 子弹类型
/// </summary>
protected GameObject bullet;
/// <summary>
/// 弹匣
/// </summary>
private BoxMagezineDemo boxMagezine;
/// <summary>
/// 枪动画脚本组件
/// </summary>
private GunAnimation anim;
/// <summary>
/// 枪口火光对象
/// </summary>
private BulletLightDemo bulletLight;
初始化
protected virtual void Start()
{
//初始化弹匣
boxMagezine = GetComponent<BoxMagezineDemo>();
//初始化火光对象
bulletLight = transform.GetChild(1).GetComponent<BulletLightDemo>();
//初始化动画对象
anim = GetComponent<GunAnimation>();
//初始化总子弹数
boxMagezine.allBullet = 1000;
//获得子弹资源
bullet = GetResource();
//初始默认单点模式
GetComponent<SingleGunDemo>().enabled = true;
GetComponent<AutomaticGunDemo>().enabled = false;
}
从资源文件里读取资源
需要通过资源路径获得,这里要写相对路径,自己在Assets 里创建一个Resources 文件,注意R 要大写
具体方法: Resources.Load<GameObject>("GunTest/Bullet/PlayerBullet");
注意,这里的路径是相对路径,要从自己创建的Resources 里的文件夹开始写,例如我这里的玩家子弹预制件的路径为:
Assets/Resources/GunTest/Bullet/PlayerBullet ,于是我应该在这个方法里写GunTest/Bullet/PlayerBullet 路径。
/// <summary>
/// 获取子弹资源文件
/// </summary>
/// <returns>资源对象</returns>
private GameObject GetResource()
{
return Resources.Load<GameObject>("GunTest/Bullet/PlayerBullet");
}
SingleGun-单发模式
挂到枪模型的父空对象上
/// <summary>
/// 单发模式
/// </summary>
public class SingleGunDemo : GunDemo
{
/// <summary>
/// 初始化
/// </summary>
protected override void Start()
{
//初始化父类资源
base.Start();
}
private void Update()
{
//调用父类更新
base.Update();
//射击
Fire();
}
/// <summary>
/// 射击
/// </summary>
private void Fire()
{
if (Input.GetKeyDown(KeyCode.O))
{
base.Firing(transform.right);
}
if (Input.GetKeyDown(KeyCode.R))
{
base.UpdateAmmo();
}
}
}
AutomaticGun-连发模式
挂到枪模型的父空对象上
与单发模式类似,唯一的不同在于GetKey 方法的选择上
/// <summary>
/// 连发模式
/// </summary>
public class AutomaticGunDemo :GunDemo
{
/// <summary>
/// 初始化
/// </summary>
protected override void Start()
{
//初始化父类资源
base.Start();
}
private void Update()
{
//调用父类更新
base.Update();
//射击
Fire();
}
/// <summary>
/// 射击
/// </summary>
private void Fire()
{
if (Input.GetKey(KeyCode.O))
{
base.Firing(transform.right);
}
if (Input.GetKeyDown(KeyCode.R))
{
base.UpdateAmmo();
}
}
}
子弹
Bullet-子弹
所有玩家子弹类型的父类,提供射线检测等功能
各类参数定义
/// <summary>
/// 层
/// </summary>
public LayerMask mask;
/// <summary>
/// 射线检测的物体
/// </summary>
protected RaycastHit hit;
/// <summary>
/// 击到的目标位置
/// </summary>
public Vector3 targetPos;
射线检测
射线检测方法:Physics.Raycast(起点,方向向量, out 输出检测的物体信息, 距离,层)
这里要将方向向量设置为子弹朝向,我这里子弹建模的时候方向为y 轴所以为transform.up ,一般为transform.forward
检测到物体后,将对应位置赋值给到targetPos ,储存目标位置,若没有检测到,则位置朝向移动100个单位
/// <summary>
/// 计算目标点
/// </summary>
public void BulletHit()
{
if (Physics.Raycast(this.transform.position,transform.up, out hit, 100, mask))
{
//检测到了
targetPos = hit.point;
}
else
{
targetPos = transform.position +transform.up* 100;
}
//在目标点创建特效
}
PlayerBullet-玩家子弹
继承Bullet ,提供子弹飞向目标点,判断是否击中等功能
初始化子弹目标点
调用父类的BulletHit 方法,获得目标点
private void Start()
{
BulletHit();
}
判断是否命中敌人
通过对象身上的标签判断是否为敌人,如果是则调用对方的受伤方法扣除HP
/// <summary>
/// 接触判断
/// </summary>
private void Touch()
{
if ((targetPos - transform.position).sqrMagnitude < 0.1f)
{
if (hit.collider.tag == "Enemy")//如果是敌人
{
hit.collider.GetComponent<EnemyDemo>().Damage(10);//扣血
DestroyImmediate(this.gameObject);
}
else
{
Destroy(this.gameObject);
}
}
}
子弹飞往目标点
注意,如果没有检测到任何单位,hit 为null ,因此要加入空判断,防止异常,子弹飞到目标点后即刻摧毁对象
/// <summary>
/// 子弹飞往目标点
/// </summary>
private void Move()
{
transform.position = Vector3.MoveTowards(transform.position, targetPos, 50 * Time.deltaTime);
if (hit.collider != null)
{
Touch();
}
else
{
if ((transform.position - targetPos).sqrMagnitude < 0.1f)
{
Destroy(this.gameObject);
}
}
}
敌人部分
敌人状态
提供敌人受伤,死亡等方法
public class EnemyDemo : MonoBehaviour
{
[HideInInspector]
public EnemyProduce produce;
public float HP;
private void Start()
{
HP = 100;
}
/// <summary>
/// 受伤
/// </summary>
/// <param name="attackNumber">伤害数字</param>
public void Damage(int attackNumber)
{
HP -= attackNumber;
if (HP <= 0)
{
Death();
}
}
/// <summary>
/// 死亡
/// </summary>
private void Death()
{
Destroy(this.gameObject);
//设置路线变为可用
this.GetComponent<EnemyAI>().wayLine.IsUsable = true;
//告诉生成器,该单位已经死亡,当前存活数量-1
produce.aliveCount--;
}
}
枪
EnemyGun-敌人枪
敌人弹药无限,子弹速度较慢,便于玩家躲避
各类参数定义与初始化
/// <summary>
/// 子弹资源
/// </summary>
private GameObject bullet;
/// <summary>
/// 攻击时机
/// </summary>
private float firingTime;
/// <summary>
/// 攻击间隔
/// </summary>
private float attackTime;
/// <summary>
/// 初始化
/// </summary>
private void Start()
{
firingTime = 0;
attackTime = 1.5F;
bullet = GetResources();
}
获得子弹资源
/// <summary>
/// 获得子弹资源
/// </summary>
/// <returns></returns>
private GameObject GetResources()
{
return Resources.Load<GameObject>("GunTest/Bullet/EnemyBullet");
}
攻击时机判定
有两种方法:
- 每次判定攻击时机大于攻击间隔,把渲染帧时间累加到攻击时机,如果大于攻击间隔则进行攻击,将攻击时机归零
- 每次判定攻击时机小于游戏时间,每次攻击后,把攻击时机增加一个攻击间隔
/// <summary>
/// 攻击时机
/// </summary>
private void AttackTime()
{
if (firingTime > attackTime)
{
Firing();
firingTime = 0;
}
else
{
firingTime += Time.deltaTime;
}
}
射击
/// <summary>
/// 射击
/// </summary>
private void Firing()
{
GameObject Bullet = Object.Instantiate(bullet, transform.TransformPoint(Vector3.right), Quaternion.identity);
Bullet.GetComponent<Transform>().up = transform.right;//基于子弹朝向
}
子弹
EnemyBullet-敌人子弹
提供子弹飞行,触发等方法,这里的敌人子弹不使用射线检测,并且控制低速
public class EnemyBulletDemo : MonoBehaviour
{
//敌人子弹为了使得玩家能够避开,不使用射线,而使用触发器,设置较慢的速度以让玩家能够有机会避开
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")//如果是玩家
{
//如果与玩家接触
other.GetComponent<PlayerDemo>().Damage(1);
}
Destroy(this.gameObject);
}
private void Update()
{
transform.Translate(0, Time.deltaTime * 5, 0);
Destroy(this.gameObject, 5);
}
}
|