项目实训(十一)——FPS游戏(第一人称射击游戏)初步开发

一、前言

我与另外两个组员合作进行了FPS游戏(第一人称射击游戏)的开发,这个游戏对应于我们在项目开始设想的PVP玩家对战游戏。玩家之间的之间对战会让游戏变得更加紧张刺激,还能够增强玩家之间的感情。
我在这个游戏中主要负责的工作是枪械特效、枪械声音、射击反馈震屏、子弹散射、枪械射击弹孔、ui子弹数量、pun控制输入同步、remote开枪效果同步。

二、实现枪械射击

首先,我们是在weapon文件夹下完成对枪械的相关脚本的创建和编写的。
在这里插入图片描述
创建IWeapon脚本用来管理接口,命名空间归类到Scripts.Weapon下,防止出现冲突:

namespace Scripts.Weapon
{
    
    
   public interface IWeapon
   {
    
    
      void DoAttack();
   }
}

然后编写Firearms脚本,这是枪械整体共用的部分。其中把Firearms枪械类抽象掉,因为这不是具体的枪械,并且可以挂在game object上:
获取枪口的位置,还要知道子弹抛出的位置。抛出弹壳使用粒子变量,避免太多的实例化过程。
需要一个弹夹AmmoInMag,设置容纳的子弹数。并且设置每把枪最大的子弹携带数。此外开枪需要设置动画。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Scripts.Weapon
{
    
    
    public abstract class Firearms : MonoBehaviour, IWeapon
    {
    
    
        public GameObject BulletPrefab;

        public Camera EyeCamera;
        public Camera GunCamera;

        public Transform MuzzlePoint;
        public Transform CasingPoint;

        public ParticleSystem MuzzleParticle;
        public ParticleSystem CasingParticle;


        public AudioSource FirearmsShootingAudioSource;
        public AudioSource FirearmsReloadAudioSource;
        public FirearmsAudioData FirearmsAudioData;
        public ImpactAudioData ImpactAudioData;
        public GameObject BulletImpactPrefab;

        public float FireRate;

        public int AmmoInMag = 30;
        public int MaxAmmoCarried = 120;

        public float SpreadAngle;

        [SerializeField] internal Animator GunAnimator;

        public List<ScopeInfo> ScopeInfos;

        public ScopeInfo BaseIronSight;

        protected ScopeInfo rigoutScopeInfo;

        public int GetCurrentAmmo => CurrentAmmo;
        public int GetCurrentMaxAmmoCarried => CurrentMaxAmmoCarried;
        
        protected int CurrentAmmo;
        protected int CurrentMaxAmmoCarried;
        protected float LastFireTime;
        protected AnimatorStateInfo GunStateInfo;
        protected float EyeOriginFOV;
        protected float GunOriginFOV;
        protected bool IsAiming;
        protected bool IsHoldingTrigger;
        private IEnumerator doAimCoroutine;
        private Vector3 originalEyePosition;
        protected Transform gunCameraTransform;
        protected virtual void Awake()
        {
    
    
            CurrentAmmo = AmmoInMag;
            CurrentMaxAmmoCarried = MaxAmmoCarried;
            GunAnimator = GetComponent<Animator>();
            EyeOriginFOV = EyeCamera.fieldOfView;
            GunOriginFOV = GunCamera.fieldOfView;
            doAimCoroutine = DoAim();
            gunCameraTransform = GunCamera.transform;
            originalEyePosition = gunCameraTransform.localPosition;
            rigoutScopeInfo = BaseIronSight;
        }
        public void DoAttack()
        {
    
    
            Shooting();
        }


        protected abstract void Shooting();
        protected abstract void Reload();

        //protected abstract void Aim();


        protected bool IsAllowShooting()
        {
    
    
            return Time.time - LastFireTime > 1 / FireRate;
        }


        protected Vector3 CalculateSpreadOffset()
        {
    
    
            float tmp_SpreadPercent = SpreadAngle / EyeCamera.fieldOfView;

            return tmp_SpreadPercent * UnityEngine.Random.insideUnitCircle;
        }


        protected IEnumerator CheckReloadAmmoAnimationEnd()
        {
    
    
            while (true)
            {
    
    
                yield return null;
                GunStateInfo = GunAnimator.GetCurrentAnimatorStateInfo(2);
                if (GunStateInfo.IsTag("ReloadAmmo"))
                {
    
    
                    if (GunStateInfo.normalizedTime >= 0.9f)
                    {
    
    
                        int tmp_NeedAmmoCount = AmmoInMag - CurrentAmmo;
                        int tmp_RemainingAmmo = CurrentMaxAmmoCarried - tmp_NeedAmmoCount;
                        if (tmp_RemainingAmmo <= 0)
                        {
    
    
                            CurrentAmmo += CurrentMaxAmmoCarried;
                        }
                        else
                        {
    
    
                            CurrentAmmo = AmmoInMag;
                        }

                        CurrentMaxAmmoCarried = tmp_RemainingAmmo <= 0 ? 0 : tmp_RemainingAmmo;

                        yield break;
                    }
                }
            }
        }

        protected IEnumerator DoAim()
        {
    
    
            while (true)
            {
    
    
                yield return null;

                float tmp_EyeCurrentFOV = 0;
                EyeCamera.fieldOfView =
                    Mathf.SmoothDamp(EyeCamera.fieldOfView,
                        IsAiming ? rigoutScopeInfo.EyeFov : EyeOriginFOV,
                        ref tmp_EyeCurrentFOV,
                        Time.deltaTime * 2);

                float tmp_GunCurrentFOV = 0;
                GunCamera.fieldOfView =
                    Mathf.SmoothDamp(GunCamera.fieldOfView,
                        IsAiming ? rigoutScopeInfo.GunFov : GunOriginFOV,
                        ref tmp_GunCurrentFOV,
                        Time.deltaTime * 2);

                Vector3 tmp_RefPosition = Vector3.zero;
                gunCameraTransform.localPosition = Vector3.SmoothDamp(gunCameraTransform.localPosition,
                    IsAiming ? rigoutScopeInfo.GunCameraPosition : originalEyePosition,
                    ref tmp_RefPosition,
                    Time.deltaTime * 2);
            }
        }

        internal void Aiming(bool _isAiming)
        {
    
    
            IsAiming = _isAiming;

            GunAnimator.SetBool("Aim", IsAiming);
            if (doAimCoroutine == null)
            {
    
    
                doAimCoroutine = DoAim();
                StartCoroutine(doAimCoroutine);
            }
            else
            {
    
    
                StopCoroutine(doAimCoroutine);
                doAimCoroutine = null;
                doAimCoroutine = DoAim();
                StartCoroutine(doAimCoroutine);
            }
        }

        internal void SetupCarriedScope(ScopeInfo _scopeInfo)
        {
    
    
            rigoutScopeInfo = _scopeInfo;
        }

        internal void HoldTrigger()
        {
    
    
            DoAttack();
            IsHoldingTrigger = true;
        }

        internal void ReleaseTrigger()
        {
    
    
            IsHoldingTrigger = false;
        }

        internal void ReloadAmmo()
        {
    
    
            Reload();
        }
    }


    [System.Serializable]
    public class ScopeInfo
    {
    
    
        public string ScopeName;
        public GameObject ScopeGameObject;
        public float EyeFov;
        public float GunFov;
        public Vector3 GunCameraPosition;
    }
}

三、枪械特效配置

首先在Assets里面找到之前以及导入的资源包,将其中的Bullet_GoldFire_Small_MuzzleFlare预制体拖拽到场景中。这个预制体是开火的特效实现。然后将它移动到枪械的Components物体里面作为子物体。
在这里插入图片描述
随后将其拖拽到枪口的位置,点击restart,即可看到开火的效果。
在这里插入图片描述
此外还需要将它的layer设置为Gun。
在Hierachy面板打开枪,也就是Assault_Rifle_01_FPSController,然后找到Bullet_GoldFire_Small_MuzzleFlare:
在这里插入图片描述
在旁边的inspector面板可以对这个开火的特效进行一些设置。
在这里插入图片描述
点击AK47,将枪焰效果物体拖到相应的位置,之前编写好的脚本就可以按照程序执行这个开火效果了,这个效果的播放次数和播放时间也将由脚本进行控制。
在这里插入图片描述
对于子弹射出的效果,需要将Particle System物体移动到出弹口的位置。然后就会有弹壳弹出的效果。
在这里插入图片描述
同样的,这个效果的layer也要设置为gun。其属性也可以在inspector面板进行更改。
后来发现弹壳的朝向是错误的,于是将rotation进行了修改,这样就没有问题了。
在这里插入图片描述

四、枪械声音

首先找到AssultRife的节点,然后打开AssultRife脚本,跳转到Fireams枪械脚本,在其中加入两个和枪械声音相关的变量。

 		public AudioSource FirearmsShootingAudioSource;//射击
        public AudioSource FirearmsReloadAudioSource;//换弹夹
       

新建一个脚本,命名为FirearmsAudioData枪械声音。然后将audio都编写进来。


namespace Scripts.Weapon
{
    
    
    [CreateAssetMenu(menuName = "FPS/Firearms Audio Data")]
    public class FirearmsAudioData : ScriptableObject
    {
    
    
        public AudioClip ShootingAudio;
        public AudioClip ReloadLeft;//换子弹的声音
        public AudioClip ReloadOutOf;//弹夹打空,换一个全新的弹夹
    }
}

[CreateAssetMenu(menuName = “FPS/Firearms Audio Data”)]
这个类继承ScriptableObject。
会发现在create菜单的最上方出现了刚才新建的选项。
在这里插入图片描述
新建后,就得到了一个可以播放的声音对象,可以在旁边进行音频的赋值。
在这里插入图片描述
然后在Firearms里面新增一个公开的变量。把枪械音源写到里面去,使用public进行修饰,这样才能给它进行赋值。

public FirearmsAudioData FirearmsAudioData;

找到枪械,就可以为它赋上Firearms Audio Data值。
在AssultRife脚本里完成播放开枪音源的逻辑的编写。

 	FirearmsShootingAudioSource.clip = FirearmsAudioData.ShootingAudio;
    FirearmsShootingAudioSource.Play();

同样地,实现换弹夹的声音播放。需要注意的是,要防止换弹夹的声音把开枪的声音掐断。由于是有两种音源,所以要进行一下判断。

		protected override void Reload()
        {
    
    
            GunAnimator.SetLayerWeight(2, 1);
            GunAnimator.SetTrigger(CurrentAmmo > 0 ? "ReloadLeft" : "ReloadOutOf");

            FirearmsReloadAudioSource.clip =
                CurrentAmmo > 0
                    ? FirearmsAudioData.ReloadLeft
                    : FirearmsAudioData.ReloadOutOf;

            FirearmsReloadAudioSource.Play();

猜你喜欢

转载自blog.csdn.net/Fancy145/article/details/124238115