本節主要內容:修改子彈預製體,優化動畫播放,修復奔跑bug,添加子彈槍名文本信息,導入新的資源包和人物模型。
2020/4/2
這些天一直在忙着家裏的事情,所以沒有更新,今天繼續倒騰我的畢業設計。
1 將子彈運動這一實際函數放到Bullet腳本當中,在Bullet中設置一個子彈速度m_BulletSpeed,在Inspector界面賦值200即可。同時,凍結子彈剛體的X、Y軸上的旋轉以免子彈在擊中物體後改變方向。
2 在AimIn和Idle之間加一個Transition,設置條件爲Aim爲false時轉換,動畫播放80%時切換爲Idle。這樣做的目的是如果只是一瞬間按下右鍵開鏡,那麼不需要播放完整的AimIn動畫就回撤到Idle,要不然會顯得很奇怪。
3 還有一個問題就是快速的連續按下右鍵,視野會出現抽搐的現象,所以需要控制一下。我的解決辦法是,取消之前的自動放大視野的協程,改爲右鍵瞄準狀態下按住左Shift屏息時放大視野。
protected override void Aim()
{
m_Animator.SetBool("Aim", m_IsAiming);
// Player is holding breath to zoom in the field of view.
if (m_IsAiming && Input.GetKey(KeyCode.LeftShift))
{
FOVZoom(m_TargetFOV);
}
else
{
FOVZoom(m_OriginFOV);
}
}
// Zooms the view of field of the camera if player aims.
private void FOVZoom(float _targetFOV)
{
float _currentFOVVelocity = 0;
m_Camera.fieldOfView = Mathf.SmoothDamp(m_Camera.fieldOfView,
_targetFOV, ref _currentFOVVelocity, Time.deltaTime);
}
4 修復了奔跑會打斷開槍過程的bug,實際上應爲開槍可以打斷奔跑的狀態。
5 在Player下添加Canvas畫布,Canvas下添加Image,source image改爲crosshair_medium,並將寬高設置爲15,沿着Y軸的偏移設爲-5,如圖所示。
6 添加槍械後坐力函數。由於在X軸和Y上都會產生旋轉,Z軸上也會產生小範圍的旋轉偏移。所以要獲取子彈射出後的Z軸上的偏移,然後修正爲0。該函數在每次執行DoAttack()函數後執行。
private void RecoilMuzzle()
{
// Produce a small range of shake on the X and Y axes.
float _randomRecoilAngleX = Random.Range(m_RecoilAngleX.x, m_RecoilAngleX.y);
float _randomRecoilAngleY = Random.Range(m_RecoilAngleY.x, m_RecoilAngleY.y);
m_RecoilPart.transform.rotation *= Quaternion.Euler(-_randomRecoilAngleX, _randomRecoilAngleY, 0f);
// Fixed rotation around the Z axis.
float _offestZ = m_RecoilPart.transform.rotation.eulerAngles.z;
m_RecoilPart.transform.rotation *= Quaternion.Euler(0f, 0f, -_offestZ);
}
7 添加武器名稱、子彈文本。
8 導入新的Low Poly Pack,將模型用Assault_Rifle_01_FPSController替換,然後更換腳本組件調整位置又是一晚上。。。
更新一下今天重做的腳本吧。
AssaultRifle
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace Scripts.Weapon
{
public class AssaultRifle : FireArms
{
// Fields.
private IEnumerator m_ReloadCheckCoroutine;
protected override void Start()
{
base.Start();
m_ReloadCheckCoroutine = CheckIfReloadOver();
}
private void Update()
{
ChangeStatue();
// Presses left mouse button to shoot.
if (Input.GetMouseButton(0) && !m_IsReloading)
{
m_IsFiring = true;
DoAttack();
RecoilMuzzle();
}
else
{
m_IsFiring = false;
}
// Enters the aiming state when the player holds down the right mouse button.
if (Input.GetMouseButton(1))
{
m_IsAiming = true;
m_Crosshair.color = Color.clear;
m_Crosshair.sprite = null;
}
else // Exits the aiming state when the player releases the right mouse button.
{
m_IsAiming = false;
m_Crosshair.color = Color.red;
m_Crosshair.sprite = m_OriginSprite;
}
Aim();
// Presses R button to reload magazine.
if (Input.GetKeyDown(KeyCode.R))
{
// Cann't shoot when the magazine if full.
if (m_MagazineBulletsLeft == m_MagazineCapacity)
{
return;
}
m_IsReloading = true;
Reload();
}
m_AmmoStatueText.text = m_MagazineBulletsLeft + " / " + m_CartridgeBagBulletsLeft;
}
protected override void Shooting()
{
// No shooting until the interval is reached, or magazine if full.
if (m_MagazineBulletsLeft <= 0 || !AllowToFire())
{
return;
}
m_LastFireTime = Time.time; // Note last fire time.
m_Animator.Play("Fire", m_IsAiming ? 1 : 0, 0); // Play fire animation.
// Only if player can shoot plays the sound.
m_ShootingAudioSource.clip = m_FirearmsAudioClips.m_ShootSound;
m_ShootingAudioSource.Play();
CreatBulletPrefab();
CreatCasingPrefab();
}
// Spawn bullet from bullet spawnpoint.
private void CreatBulletPrefab()
{
// Play the muzzle effect.
m_MuzzleEffect.Play();
Instantiate(m_BulletPrefab, m_BulletSpawnPoint.position, m_BulletSpawnPoint.rotation);
m_MagazineBulletsLeft -= 1;
}
//Spawn casing prefab at spawnpoint.
private void CreatCasingPrefab()
{
Instantiate(m_CasingPrefab, m_CasingSpawnPoint.position, m_CasingSpawnPoint.rotation);
}
// This method recoil muzzle to simluate real shooting scene.
private void RecoilMuzzle()
{
// Produce a small range of shake on the X and Y axes.
float _randomRecoilAngleX = Random.Range(m_RecoilAngleX.x, m_RecoilAngleX.y);
float _randomRecoilAngleY = Random.Range(m_RecoilAngleY.x, m_RecoilAngleY.y);
m_RecoilPart.transform.rotation *= Quaternion.Euler(-_randomRecoilAngleX, _randomRecoilAngleY, 0f);
// Fixed rotation around the Z axis.
float _offestZ = m_RecoilPart.transform.rotation.eulerAngles.z;
m_RecoilPart.transform.rotation *= Quaternion.Euler(0f, 0f, -_offestZ);
}
protected override void Aim()
{
m_Animator.SetBool("Aim", m_IsAiming);
// Player is holding breath to zoom in the field of view.
if (m_IsAiming && Input.GetKey(KeyCode.LeftShift))
{
FOVZoom(m_TargetFOV);
}
else
{
FOVZoom(m_OriginFOV);
}
}
// Zooms the view of field of the camera if player aims.
private void FOVZoom(float _targetFOV)
{
float _currentFOVVelocity = 0;
m_Camera.fieldOfView = Mathf.SmoothDamp(m_Camera.fieldOfView,
_targetFOV, ref _currentFOVVelocity, Time.deltaTime);
}
protected override void Reload()
{
// Runs out of ammunition and player can not fire.
if (m_CartridgeBagBulletsLeft <= 0)
{
return;
}
m_IsAiming = false; // The FOV should be the origin value if player start reloading.
// Set the reload layer's weight to 1 to play the reload animation.
m_Animator.SetLayerWeight(2, 1);
m_Animator.SetTrigger(m_MagazineBulletsLeft > 0 ? "ReloadLeft" : "ReloadOut");
m_ReloadAudioSource.clip = m_MagazineBulletsLeft > 0 ?
m_FirearmsAudioClips.m_ReloadAmmoLeftSound : m_FirearmsAudioClips.m_ReloadOutOfAmmoSound;
m_ReloadAudioSource.Play();
// Fills in the magazine until the animation is nearly over.
if (m_ReloadCheckCoroutine == null)
{
m_ReloadCheckCoroutine = CheckIfReloadOver();
StartCoroutine(CheckIfReloadOver());
}
else
{
StopCoroutine(CheckIfReloadOver());
m_ReloadCheckCoroutine = null;
m_ReloadCheckCoroutine = CheckIfReloadOver();
StartCoroutine(CheckIfReloadOver());
}
}
private IEnumerator CheckIfReloadOver()
{
while (true)
{
yield return null;
// Get the playing animation's info in specified layer.
m_AnimatorStateInfo = m_Animator.GetCurrentAnimatorStateInfo(2);
if (m_AnimatorStateInfo.IsTag("Reload"))
{
if (m_AnimatorStateInfo.normalizedTime >= 0.95f)
{
// Bullets left int the cartridge bag can fill in a magazine.
if (m_CartridgeBagBulletsLeft > m_MagazineCapacity)
{
int _reloadAmount = m_MagazineCapacity - m_MagazineBulletsLeft; // Amount of reload.
m_MagazineBulletsLeft = m_MagazineCapacity; // Reload the arms.
m_CartridgeBagBulletsLeft -= _reloadAmount; // Reduce the bullets in cartridge bag.
}
else // Bullets left int the cartridge bag can not fill in a magazine.
{
m_MagazineBulletsLeft = m_CartridgeBagBulletsLeft;
m_CartridgeBagBulletsLeft = 0;
}
// Set the reload layer's weight back.
m_Animator.SetLayerWeight(2, 0);
m_IsReloading = false;
yield break;
}
}
}
}
}
}
FireArms
using UnityEngine;
using UnityEngine.UI;
namespace Scripts.Weapon
{
public abstract class FireArms : MonoBehaviour, IWeapon
{
[Header("Ammunition Settings")]
public int m_MagazineCapacity = 35; // Amount of bullets in a magazine
public int m_CartridgeBagCapacity = 210; // Maximum number of bullets player can carry.
[SerializeField] protected int m_MagazineBulletsLeft; // Current number of bullets left in the magazine
[SerializeField] protected int m_CartridgeBagBulletsLeft; // Current number of bullets left in cartridge bag;
[Header("Fire Settings")]
public float m_FireRate; // Amount of bullets the gun can fire per second.
public float m_FireInterval; // Interval between two shoots.
public Vector2 m_RecoilAngleX; // Angle at which the muzzle is raised around X after each shoot.
public Vector2 m_RecoilAngleY; // Angle at which the muzzle is raised around Y after each shoot.
[Header("Spawn Position")]
public Transform m_BulletSpawnPoint; // The position where the bullet shoot.
public Transform m_CasingSpawnPoint; // The position where the casings are thrown.
public Transform m_RecoilPart; // The part affected by th recoil.
[Header("Prefabs")]
public ParticleSystem m_MuzzleEffect;
// public ParticleSystem m_CasingEffect;
public GameObject m_BulletPrefab;
public GameObject m_CasingPrefab;
[Header("Audio")]
public AudioSource m_ShootingAudioSource;
public AudioSource m_ReloadAudioSource;
public AssaultRifleAudioClipsData m_FirearmsAudioClips;
[Header("Status")]
public bool m_IsFiring = false;
public bool m_IsReloading = false; // Player can't shot while he is reloading.
public bool m_IsAiming = false;
public bool m_IsWalking = false;
public bool m_IsRunning = false;
public bool m_IsHoldingBreath = false;
[Header("Camera")]
public Camera m_Camera;
public float m_TargetFOV;
[Header("UI")]
public string m_WeaponName;
public Image m_Crosshair;
public Text m_WeaponNameText;
public Text m_AmmoStatueText;
// Fields.
protected float m_LastFireTime;
protected float m_OriginFOV;
protected float m_CurrentBreathHoldingTime; // Current time player has held the breath.
// References.
protected Animator m_Animator;
protected AnimatorStateInfo m_AnimatorStateInfo;
protected Sprite m_OriginSprite;
protected virtual void Start()
{
m_Animator = GetComponent<Animator>();
SetUp();
}
private void SetUp()
{
// Initialize variables.
m_MagazineBulletsLeft = m_MagazineCapacity;
m_CartridgeBagBulletsLeft = m_CartridgeBagCapacity;
m_FireInterval = 1 / m_FireRate;
m_OriginFOV = m_Camera.fieldOfView;
m_WeaponNameText.text = m_WeaponName;
m_OriginSprite = m_Crosshair.sprite;
}
protected void ChangeStatue()
{
// Play animations according to the key pressed by the player.
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
{
// Player cannot run while he is aiming or firing.
if (Input.GetKey(KeyCode.LeftShift) && !m_IsAiming && !m_IsFiring)
{
m_IsWalking = false;
m_IsRunning = true;
}
else
{
m_IsRunning = false;
m_IsWalking = true;
}
}
else
{
m_IsWalking = false;
m_IsRunning = false;
}
m_Animator.SetBool("Walk", m_IsWalking);
m_Animator.SetBool("Run", m_IsRunning);
}
public void DoAttack()
{
Shooting();
}
// Control the interval of player shooting.
// This method will return true if player can shoot again.
protected bool AllowToFire()
{
return Time.time - m_LastFireTime > m_FireInterval;
}
protected abstract void Shooting();
protected abstract void Reload();
protected abstract void Aim();
}
}