Unity之FPS

目录

🎮MouseLook摄像机旋转脚本

🎮PickUpItem武器拾取脚本

🎮PlayerController玩家控制器

🎮Inventory武器库

🎮Weapon武器抽象类

🎮Weapon_AutomaticGun武器脚本


其实这个教程很早就收藏了就是被20个小时时长给吓到了一直没开始。终于在七月中旬的时候开始动手跟着做了。


最近一直加班也很忙,参与的第一个项目计划在年底上线,也是有项目经验的人了哈哈。然后拖拖拉拉的一直跟着教程做,只是个半成品敌人部分还没做,以我目前的精力就先做到这里了。原教程链接:第一人称射击游戏教程2.0【已完结】_哔哩哔哩_bilibiliicon-default.png?t=O83Ahttps://www.bilibili.com/video/BV1J8411d75s/?spm_id_from=333.999.0.0&vd_source=6b664fb3236a2f45f99b9eb3868cdf31


 教程讲的很细,有C#和Unity基础的做到我现在这样没什么太大的问题,但教程讲的也有点慢,所以时长才会达到二十小时,我看的时候是1.5倍速看的。

主要功能:枪械有AK、消音m4、格洛克、霰弹枪、狙击枪;玩家行走、奔跑、跳跃、下蹲;

不同枪械不同的装弹方式、瞄准状态、准星动态变化、全自动半自动模式切换、武器库切换武器、随机弹道、武器的检视功能。

演示视频:

20240920

由于间隔时间太长本篇笔记就不从头开始讲了,只把核心脚本代码和Demo源码放出来,脚本代码也有对应的注释,感兴趣的话大家可以结合原教程博主和下面的核心代码一起看。


MouseLook摄像机旋转脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 摄像机旋转
/// 玩家左右旋转控制实现左右移动
/// 摄像机上下旋转控制视线上下移动
/// </summary>
public class MouseLook : MonoBehaviour
{[Tooltip("视野灵敏度")]public float mouseSenstivity = 400f;public Transform playerBody;  //玩家位置private float yRotation = 0f; //摄像机上下旋转的数值private CharacterController characterController;[Tooltip("当前摄像机的初始高度")] public float height = 1.8f;private float interpolationSpeed = 12f;// Start is called before the first frame updatevoid Start(){Cursor.lockState = CursorLockMode.Locked;//playerBody = transform.GetComponent<PlayerController>().transform;characterController = GetComponentInParent<CharacterController>();}// Update is called once per framevoid Update(){float mouseX = Input.GetAxis("Mouse X") * mouseSenstivity * Time.deltaTime;float mouseY = Input.GetAxis("Mouse Y") * mouseSenstivity * Time.deltaTime;yRotation -= mouseY;//上下旋转//限制旋转大小yRotation = Mathf.Clamp(yRotation, -60f, 60f);transform.localRotation = Quaternion.Euler(yRotation,0f,0f);//摄像机上下旋转playerBody.Rotate(Vector3.up * mouseX);//玩家左右移动//当人物下蹲或站立时,摄像机的高度也会发生变化float heightTarget = characterController.height * 0.9f;height = Mathf.Lerp(height, heightTarget, interpolationSpeed * Time.deltaTime);//设置下蹲站立时的摄像机高度transform.localPosition = Vector3.up * height; }
}

PickUpItem武器拾取脚本

using UnityEngine;/// <summary>
/// 武器拾取
/// </summary>
public class PickUpItem : MonoBehaviour
{[Tooltip("武器旋转的速度")] private float rotateSpeed;[Tooltip("武器编号")] public int itemID;private GameObject weaponModel;// Start is called before the first frame updatevoid Start(){rotateSpeed = 100f;}// Update is called once per framevoid Update(){transform.eulerAngles += new Vector3(0, rotateSpeed * Time.deltaTime, 0);}private void OnTriggerEnter(Collider other){if (other.name == "Player"){PlayerController player = other.GetComponent<PlayerController>();//查找获取 Inventory 物体下的各个武器物体weaponModel = GameObject.Find("Player/Assult_Rife_Arm/Inventory/").gameObject.transform.GetChild(itemID).gameObject;//Debug.Log(weaponModel.name);player.PickUpWeapon(itemID,weaponModel);Destroy(gameObject);}}
}

PlayerController玩家控制器

using UnityEngine;
using UnityEngine.UI;public class PlayerController : MonoBehaviour
{private CharacterController characterController;public Vector3 moveDirection;   //人物控制方向private AudioSource audioSource;[Header("玩家数值")] private float Speed;[Tooltip("行走速度")] public float walkSpeed;[Tooltip("奔跑速度")] public float runSpeed;[Tooltip("下蹲速度")] public float crouchSpeed;[Tooltip("玩家生命值")] public float playerHealth;[Tooltip("跳跃的力")] public float jumpForce;[Tooltip("下落的力")] public float fallForce;[Tooltip("下蹲时玩家的高度")] public float crouchHeight;[Tooltip("站立时玩家的高度")] public float standHeight;[Header("键位设置")] [Tooltip("奔跑")] private KeyCode runInputName = KeyCode.LeftShift;[Tooltip("跳跃")] private KeyCode jumpInputName = KeyCode.Space;[Tooltip("下蹲")] private KeyCode crouchInputName = KeyCode.LeftControl;[Header("玩家属性判断")] public MovementState state;private CollisionFlags collisionFlags;public bool isWalk;public bool isRun;public bool isJump;public bool isGround;//玩家是否在地面上public bool isCanCrouch; //判断玩家是否可以下蹲public bool isCrouching; //判断玩家是否处于下蹲状态private bool playerIsDead; //判断玩家是否死亡private bool isDamage; //判断玩家是否受到伤害public LayerMask crouchLayerMask;public Text playerHealthUI;[Header("音效")] [Tooltip("行走音效")] public AudioClip walkSound;[Tooltip("奔跑音效")] public AudioClip runningSound;private Inventory inventory;// Start is called before the first frame updatevoid Start(){characterController = GetComponent<CharacterController>();audioSource = GetComponent<AudioSource>();walkSpeed = 4f;runSpeed = 6f;crouchSpeed = 2f;jumpForce = 0f;fallForce = 10f;isGround = true;crouchHeight = 1f;standHeight = characterController.height;inventory = GetComponentInChildren<Inventory>();playerHealth = 100f;playerHealthUI.text = "生命值:" + playerHealth;}// Update is called once per framevoid Update(){CanCrouch();if (Input.GetKey(crouchInputName)){Crouch(true);}else{Crouch(false);}PlayerFootSoundSet();Moving();Jump();}public void Moving(){//GetAxis 不急停  GetAxisRaw  急停float h = Input.GetAxisRaw("Horizontal");float v = Input.GetAxisRaw("Vertical");isRun = Input.GetKey(runInputName);isWalk = (Mathf.Abs(h) > 0 || Mathf.Abs(v) > 0) ? true : false;if (isRun && isGround && isCanCrouch && !isCrouching){state = MovementState.running;Speed = runSpeed;}else if(isGround){state = MovementState.walking;Speed = walkSpeed;if (isCrouching){state = MovementState.crouching;Speed = crouchSpeed;}}//同时按下蹲和奔跑时,人物应该是下蹲速度if (isRun && isCrouching){state = MovementState.crouching;Speed = crouchSpeed;}moveDirection = (transform.right * h + transform.forward * v).normalized;characterController.Move(moveDirection * Speed * Time.deltaTime); //人物移动}public void Jump(){if(!isCanCrouch) return; //不能进行站立时也不能进行跳跃操作isJump = Input.GetKeyDown(jumpInputName);//按下跳跃键并且在地面上才会进行跳跃if (isJump && isGround){isGround = false;jumpForce = 5f;}//如果当前没有按下空格并且在检测地面上,那么isGround判断为falseelse if(!isJump && isGround){isGround = false;jumpForce = -2f;}//此时玩家已经跳起来了if (!isGround){jumpForce = jumpForce - fallForce * Time.deltaTime;Vector3 jump = new Vector3(0,jumpForce * Time.deltaTime,0); //将向上的力转换为V3坐标collisionFlags = characterController.Move(jump); //调用角色控制器移动方法,向上方模拟跳跃/*判断玩家是否在地面上  *CollisionFlags: characterController 内置的碰撞位置标识号* CollisionFlags.Below: 在地面上*/if (collisionFlags == CollisionFlags.Below){isGround = true;jumpForce = 0f;}//玩家什么都没有碰到说明不在地面上// if (isGround && collisionFlags == CollisionFlags.None)// {//     isGround = false;// }}}//判断人物是否可以下蹲//isCanCrouch: true说明头上没有物体可以下蹲public void CanCrouch(){//获取人物头顶的高度V3位置Vector3 sphereLocation = transform.position + Vector3.up * standHeight;//Debug.Log("sphereLocation:" + sphereLocation);//判断头顶和他自己碰撞的个数是否等于0isCanCrouch = (Physics.OverlapSphere(sphereLocation,characterController.radius,crouchLayerMask).Length) == 0;//// Collider[] colis = Physics.OverlapSphere(sphereLocation, characterController.radius, crouchLayerMask);// for (int i = 0; i < colis.Length; i++)// {//     Debug.Log("colis:" + colis[i].name);// }// Debug.Log("colis:" + colis.Length);//Debug.Log("isCanCrouch:" + isCanCrouch);}//下蹲public void Crouch(bool newCrouching){if(!isCanCrouch) return; //不可下蹲时(在隧道),不能进行站立isCrouching = newCrouching;characterController.height = isCrouching ? crouchHeight : standHeight; //根据下蹲状态设置下蹲characterController.center = characterController.height / 2.0f * Vector3.up; //将角色控制器的中心位置Y,从头顶往下减少一半的高度}//人物移动音效public void PlayerFootSoundSet(){//sqrMagnitude 返回该向量的平方长度if (isGround && moveDirection.sqrMagnitude > 0){audioSource.clip = isRun ? runningSound : walkSound;if (!audioSource.isPlaying){//播放行走或者奔跑音效audioSource.Play();}}else{if (audioSource.isPlaying){//暂停播放行走或奔跑音效audioSource.Pause();}}//下蹲时不播放新增音效if (isCrouching){if (audioSource.isPlaying){audioSource.Pause();}}}/// <summary>/// 拾取武器/// </summary>public void PickUpWeapon(int itemID,GameObject weapon){//捡到武器后,在武器库里添加,否则补充弹药if (inventory.weapons.Contains(weapon)){weapon.GetComponent<Weapon_AutomaticGun>().bulletLeft =weapon.GetComponent<Weapon_AutomaticGun>().bulletMag * 5;weapon.GetComponent<Weapon_AutomaticGun>().UpdateAmmoUI();Debug.Log("集合里已存在此枪械,补充弹药");return;}else{inventory.AddWeapon(weapon);}}/// <summary>/// 玩家生命值/// </summary>/// <param name="damage">接收到的伤害值</param>public void PlayerHealth(float damage){playerHealth -= damage;isDamage = true;playerHealthUI.text = "生命值:" + playerHealth;if (playerHealth <= 0){playerIsDead = true;playerHealthUI.text = "玩家死亡";Time.timeScale = 0; //游戏暂停}}public enum MovementState{walking,running,crouching,idle}
}

Inventory武器库

using System.Collections.Generic;
using System.Linq;
using UnityEngine;/// <summary>
/// 武器库
/// 人物的武器切换,添加,去除功能
/// </summary>
public class Inventory : MonoBehaviour
{//武器库public List<GameObject> weapons = new List<GameObject>();//当前使用的武器编号public int currentWeaponID;// Start is called before the first frame updatevoid Start(){currentWeaponID = -1;}// Update is called once per framevoid Update(){ChargeCurrentWeaponID();}/// <summary>/// 更新武器编号/// </summary>public void ChargeCurrentWeaponID(){//鼠标滚轮向下 -0.1  向上 0.1 不动 0if (Input.GetAxis("Mouse ScrollWheel") < 0){//下一把武器ChargeWeapon(currentWeaponID + 1);}else if(Input.GetAxis("Mouse ScrollWheel") > 0){//上一把武器ChargeWeapon(currentWeaponID - 1);}//通过数字键盘来切换武器for (int i = 0; i < 10; i++){if (Input.GetKeyDown(KeyCode.Alpha0 + i)){int num = 0;if (i == 10){num = 10;}else{num = i - 1;}/*  只有数字小于武器列表个数的时候才能进一步处理如果有3把武器,但按下4-9,那么是无意义的,不处理*/if (num<weapons.Count){ChargeWeapon(num);}}}}/// <summary>/// 武器切换/// </summary>/// <param name="weaponID">武器编号</param>public void ChargeWeapon(int weaponID){if(weapons.Count == 0 )return;//如果切换到最大索引编号的枪的时候,就调出第一把枪//如果切换到最小索引编号的枪的时候,就调出最后一把枪//IndexOf: 获取列表中元素首次出现的索引//Max list里取最大元素if (weaponID > weapons.Max(weapons.IndexOf)){weaponID = weapons.Min(weapons.IndexOf);}else if (weaponID < weapons.Min(weapons.IndexOf)){weaponID = weapons.Max(weapons.IndexOf);}if (weaponID == currentWeaponID){//只有一种武器不进行切换return;}//更新武器索引currentWeaponID = weaponID;//根据武器编号,显示出对应的武器for (int i = 0; i < weapons.Count; i++){if (weaponID == i){weapons[i].gameObject.SetActive(true);}else{weapons[i].gameObject.SetActive(false);}}}/// <summary>/// 拾取武器/// </summary>public void AddWeapon(GameObject weapon){if (weapons.Contains(weapon)){print("集合已存在此枪械");return;}else{if (weapons.Count < 10) //最多携带3把枪{weapons.Add(weapon);ChargeWeapon(currentWeaponID+1);//显示武器weapon.gameObject.SetActive(true);}}}/// <summary>/// 丢弃武器/// </summary>public void ThrowWeapon(GameObject weapon){if (!weapons.Contains(weapon) || weapons.Count == 0){print("没有这个武器,无法抛弃");return;}else{weapons.Remove(weapon);//集合中删除对应的武器ChargeWeapon(currentWeaponID-1);weapon.gameObject.SetActive(false);}}
}

Weapon武器抽象类

using UnityEngine;public abstract class Weapon : MonoBehaviour
{public abstract void GunFire();public abstract void Reload();public abstract void ExpandingCrossUpdate(float expanDegree);public abstract void DoReloadAnimation();public abstract void AimIn();public abstract void AimOut();
}

Weapon_AutomaticGun武器脚本

using UnityEngine;
using Random = UnityEngine.Random;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine.UI;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;//武器音效内部类
[System.Serializable]
public class SoundClips
{public AudioClip shootSound; //开火音效public AudioClip silencerShootSound; //开火音效带消音器public AudioClip reloadSoundAmmotLeft; //换子弹音效public AudioClip reloadSoundOutOfAmmo; //换子弹并拉枪栓(一个弹匣打完)public AudioClip aimSound; //瞄准音效
}public class Weapon_AutomaticGun : Weapon
{public Animator animator;private PlayerController playerController;private Camera mainCamera;public Camera gunCamera;public bool IS_AUTORIFLE; //是否是自动武器public bool IS_SEMIGUN; //是否半自动武器[Header("武器部件位置")] [Tooltip("射击的位置")] public Transform ShootPoint; //射线打出的位置 public Transform BulletShootPoint; //子弹特效打出的位置[Tooltip("子弹壳抛出的位置")] public Transform CasingBulletSpawnPoint;[Header("子弹预制体和特效")] public Transform bulletPrefab; //子弹public Transform casingPrefab; //子弹抛壳[Header("枪械属性")] [Tooltip("武器射程")] private float range;[Tooltip("武器射速")] public float fireRate;private float originRate; //原始射速private float SpreadFactor; //射击的一点偏移量private float fireTimer; //计时器  控制武器射速private float bulletForce; //子弹发射的力[Tooltip("当前武器的每个弹匣子弹数")] public int bulletMag;[Tooltip("当前子弹数")] public int currentBullets;[Tooltip("备弹")] public int bulletLeft;public bool isSilencer; //是否装备消音器private int shotgunFragment = 8;//1次打出的子弹数[Header("特效")] public Light muzzleflashLight; //开火灯光private float lightDuration; //灯光持续时间public ParticleSystem muzzlePatic; //灯光火焰粒子特效1public ParticleSystem sparkPatic; //灯光火焰粒子特效2(火星子)vpublic int minSparkEmission = 1;public int maxSparkEmission = 7;[Header("音源")] private AudioSource mainAudioSource;public SoundClips soundClips;//private AudioSource shootAudioSource;[Header("UI")] public Image[] crossQuarterImgs; //准心public float currentExoanedDegree; //当前准心的开合度private float crossExpanedDegree; //每帧准心开合度private float maxCrossDegree;//最大开合度public Text ammoTextUI;public Text shootModeTextUI;public PlayerController.MovementState state;private bool isReloading; //判断是否在换弹[Header("键位设置")] [SerializeField] [Tooltip("填装子弹按键")] private KeyCode reloadInputName = KeyCode.R;[SerializeField] [Tooltip("自动半自动切换按键")] private KeyCode gunShootModelInputName = KeyCode.V;[SerializeField] [Tooltip("检视武器按键")] private KeyCode inspectInputName = KeyCode.F;//切换全自动半自动public ShootMode shootingMode;private bool GunShootInput;private int modeNum; //模式切换的一个中间参数(1:全自动,2:半自动)private string shootModeName;//瞄准private bool isAiming;private Vector3 sniperingFiflePosition; //枪默认的初始位置public Vector3 sniperingFifleOnPosition; //开始瞄准的模型位置//------------------------------修改瞄准时的声音播放问题,只针对本项目,与B站教程无关private bool IsAimInPlay = true;[Header("狙击镜设置")] [Tooltip("狙击镜材质")] public Material scopeRenderMaterial;[Tooltip("当没有进行瞄准时狙击镜的颜色")] public Color fadeColor;[Tooltip("当瞄准时狙击镜的颜色")] public Color defaultColor;private void Awake(){playerController = GetComponentInParent<PlayerController>();mainAudioSource = GetComponent<AudioSource>();animator = GetComponent<Animator>();mainCamera = Camera.main;}private void Start(){muzzleflashLight.enabled = false;crossExpanedDegree = 50f;maxCrossDegree = 300f;lightDuration = 0.02f;range = 300f;bulletLeft = bulletMag * 5;currentBullets = bulletMag;bulletForce = 100f;originRate = 0.1f;//随机弹道精准度SpreadFactor = 0.05f;UpdateAmmoUI();//根据不同枪械,游戏刚开始时进行不同射击模式设置if (IS_AUTORIFLE){modeNum = 1;shootModeName = "全自动";shootingMode = ShootMode.AutoRifle;UpdateAmmoUI();}if (IS_SEMIGUN){modeNum = 0;shootModeName = "半自动";shootingMode = ShootMode.SemiGun;UpdateAmmoUI();}//isAiming是否是瞄准状态isAiming = false;sniperingFiflePosition = transform.localPosition;}private void Update(){// 自动枪械鼠标输入方式 可以在 GetMouseButton 和 GetMouseButtonDown 里切换if (IS_AUTORIFLE){//切换射击模式(全自动和半自动)if (Input.GetKeyDown(gunShootModelInputName) && modeNum != 1){modeNum = 1;shootModeName = "全自动";shootingMode = ShootMode.AutoRifle;UpdateAmmoUI();}else if(Input.GetKeyDown(gunShootModelInputName) && modeNum != 0){modeNum = 0;shootModeName = "半自动";shootingMode = ShootMode.SemiGun;UpdateAmmoUI();}//控制射击模式的转换  后面就要用代码去动态控制了switch (shootingMode){case ShootMode.AutoRifle:GunShootInput = Input.GetMouseButton(0);fireRate = originRate;break;case ShootMode.SemiGun:GunShootInput = Input.GetMouseButtonDown(0);fireRate = 0.2f;break;}}else{//半自动枪械鼠标输入方式改为GetMouseButtonDownGunShootInput = Input.GetMouseButtonDown(0);}state = playerController.state;//这里实时获取任务的移动状态(行走,奔跑,下蹲)if (state == PlayerController.MovementState.walking && Vector3.SqrMagnitude(playerController.moveDirection)>0 && state != PlayerController.MovementState.running && state != PlayerController.MovementState.crouching){//移动时的准心开合度ExpandingCrossUpdate(crossExpanedDegree);}else if (state != PlayerController.MovementState.walking && state == PlayerController.MovementState.running &&state != PlayerController.MovementState.crouching){//奔跑时的准心开合度(2倍)ExpandingCrossUpdate(crossExpanedDegree*2);}else{//站立或者下蹲时,不调整准心开合度ExpandingCrossUpdate(0);}if (GunShootInput && currentBullets > 0){//霰弹枪射击1次同时打出8次射线,其余枪械正常1次射线if (IS_SEMIGUN && gameObject.name == "4"){shotgunFragment = 8;}else{shotgunFragment = 1;}//开枪射击GunFire();   }//腰射和瞄准射击精准不同SpreadFactor = (isAiming) ? 0.01f : 0.1f;if (Input.GetKeyDown(inspectInputName)){animator.SetTrigger("inspect");}//计时器if (fireTimer < fireRate){fireTimer += Time.deltaTime;}//playerController.isWalk;animator.SetBool("Run",playerController.isRun);animator.SetBool("Walk",playerController.isWalk);//两种换子弹的动画(包括霰弹枪换弹动画逻辑)AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);if (info.IsName("reload01") || info.IsName("reload") || info.IsName("reload_open") ||info.IsName("reload_insert") ||info.IsName("reload_insert 1") ||info.IsName("reload_insert 2") ||info.IsName("reload_insert 3") ||info.IsName("reload_insert 4") ||info.IsName("reload_insert 5") ||info.IsName("reload_close")){isReloading = true;}else{isReloading = false;}//根据当前霰弹枪子弹数填装的数量判断结束insert动画//解决了本来放2个子弹,但实际上添加子弹的动画播放了3次的问题if ((info.IsName("reload_insert") ||info.IsName("reload_insert 1") ||info.IsName("reload_insert 2") ||info.IsName("reload_insert 3") ||info.IsName("reload_insert 4") ||info.IsName("reload_insert 5")) && currentBullets == bulletMag){//当前霰弹枪子弹填装完毕,随时结束换弹判断animator.Play("reload_close");isReloading = false;}//按R换弹if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletMag && bulletLeft > 0 && !isReloading && !isAiming){DoReloadAnimation();}//鼠标右键进入瞄准if (Input.GetMouseButton(1) && !isReloading && !playerController.isRun){isAiming = true;animator.SetBool("Aim",isAiming);//瞄准时需要微调一下枪的模型位置transform.localPosition = sniperingFifleOnPosition;AimIn();}else if(Input.GetMouseButtonUp(1)){isAiming = false;animator.SetBool("Aim",isAiming);transform.localPosition = sniperingFiflePosition;AimOut();}}//射击public override void GunFire(){//控制射速或当前没有子弹的时候就不可以发射了//animator.GetCurrentAnimatorStateInfo(0).IsName("take_out")  获取第0层动画中名为take_out动画的状态if (fireTimer < fireRate || playerController.isRun || currentBullets <= 0 || animator.GetCurrentAnimatorStateInfo(0).IsName("take_out") || isReloading) return;//调用协程控制开火灯光StartCoroutine(MuzzleFlashLightCon());muzzlePatic.Emit(1);//每次发射一个枪口火焰sparkPatic.Emit(Random.Range(minSparkEmission,maxSparkEmission));//发射枪口火星粒子StartCoroutine(Shoot_Crss()); //增大准心大小if (!isAiming){//播放普通开火动画(使用动画的淡入淡出效果)animator.CrossFadeInFixedTime("fire",0.1f);}else{//瞄准状态下,播放瞄准开火动画animator.Play("aim_fire",0,0);}for (int i = 0; i < shotgunFragment; i++){RaycastHit hit;Vector3 shootDirection = ShootPoint.forward;//射击方向shootDirection = shootDirection + ShootPoint.TransformDirection(new Vector3(Random.Range(-SpreadFactor,SpreadFactor),Random.Range(-SpreadFactor,SpreadFactor)));//射线参数: 发射点,方向,碰撞信息,最大距离(射线检测的方式从屏幕正中心射出)if (Physics.Raycast(ShootPoint.position, shootDirection, out hit, range)){Transform bullet;if (IS_AUTORIFLE || (IS_SEMIGUN && gameObject.name=="2")){//实例化出子弹拖尾特效(拖尾特效里包含击中和弹孔特效)bullet = (Transform)Instantiate(bulletPrefab, BulletShootPoint.transform.position,BulletShootPoint.transform.rotation);}else{//霰弹枪特殊处理下,将子弹限制位置设定到 hit.pointbullet = Instantiate(bulletPrefab, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));}//给子弹拖尾向前的力(加上射线打出去的偏移值)bullet.GetComponent<Rigidbody>().velocity = (bullet.transform.forward + shootDirection)*bulletForce;//Debug.Log("打到了" + hit.transform.gameObject.name);}   }//实例抛弹壳Instantiate(casingPrefab, CasingBulletSpawnPoint.transform.position, CasingBulletSpawnPoint.transform.rotation);//根据是否装备消音器,切换不同的射击音效mainAudioSource.clip = isSilencer ? soundClips.silencerShootSound : soundClips.shootSound;//mainAudioSource.clip = soundClips.shootSound;mainAudioSource.Play(); //播放射击音效fireTimer = 0f; //重置计时器currentBullets--; //子弹数量减少UpdateAmmoUI();//ExpendCross(30);}//设置开火的灯光IEnumerator MuzzleFlashLightCon(){muzzleflashLight.enabled = true;yield return new WaitForSeconds(lightDuration);muzzleflashLight.enabled = false;}//换弹逻辑public override void Reload(){if(bulletLeft <= 0) return;//计算需要填充的子弹int bulletToload = bulletMag - currentBullets;//计算备弹扣除的子弹数int bulletToReduce = bulletLeft >= bulletToload ? bulletToload : bulletLeft;bulletLeft -= bulletToReduce;//备弹减少currentBullets += bulletToReduce;//当前子弹增加UpdateAmmoUI();}/// <summary>/// 霰弹枪换弹逻辑/// </summary>public void ShotGunReload(){if (currentBullets < bulletMag){currentBullets++;bulletLeft--;UpdateAmmoUI();}else{animator.Play("reload_close");return;}if(bulletLeft <= 0) return;}//根据指定大小,来增加或减小准心的开合度public override void ExpandingCrossUpdate(float expanDegree){if (currentExoanedDegree < expanDegree-5){ExpendCross(150 * Time.deltaTime);}else if(currentExoanedDegree > expanDegree + 5){ExpendCross(-300 * Time.deltaTime);}}//换弹(播放动画)public override void DoReloadAnimation(){if (!(IS_SEMIGUN && (gameObject.name == "4" || gameObject.name == "6"))){if (currentBullets > 0 && bulletLeft > 0){animator.Play("reload01",0,0);Reload();mainAudioSource.clip = soundClips.reloadSoundAmmotLeft;mainAudioSource.Play();}if (currentBullets == 0 && bulletLeft > 0){animator.Play("reload",0,0);Reload();mainAudioSource.clip = soundClips.reloadSoundOutOfAmmo;mainAudioSource.Play();}   }else{if(currentBullets == bulletMag) return;//霰弹枪换子弹动画触发animator.SetTrigger("shotgun_reload");}}//进入瞄准,隐藏准心,摄像机视野变近public override void AimIn(){float currentVelocity = 0f;for (int i = 0; i < crossQuarterImgs.Length; i++){crossQuarterImgs[i].gameObject.SetActive(false);}//狙击枪瞄准的时候,改变gunCamera的视野和瞄准镜的颜色if (IS_SEMIGUN && (gameObject.name == "6")){scopeRenderMaterial.color = defaultColor;gunCamera.fieldOfView = 10;}//瞄准的时候摄像机视野变近mainCamera.fieldOfView = Mathf.SmoothDamp(30,60,ref currentVelocity,0.1f);if (IsAimInPlay == true){IsAimInPlay = false;mainAudioSource.clip = soundClips.aimSound;mainAudioSource.Play();   }}//退出瞄准,显示准心,摄像机视野恢复public override void AimOut(){float currentVelocity = 0f;for (int i = 0; i < crossQuarterImgs.Length; i++){crossQuarterImgs[i].gameObject.SetActive(true);}//狙击枪瞄准的时候,改变gunCamera的视野和瞄准镜的颜色if (IS_SEMIGUN && (gameObject.name == "6")){scopeRenderMaterial.color = fadeColor;gunCamera.fieldOfView = 35;}mainCamera.fieldOfView = Mathf.SmoothDamp(60,30,ref currentVelocity,0.1f);if (IsAimInPlay == false){IsAimInPlay = true;mainAudioSource.clip = soundClips.aimSound;mainAudioSource.Play();   }}//改变准心的开合度,并且记录当前准心开合度public void ExpendCross(float add){crossQuarterImgs[0].transform.localPosition += new Vector3(-add,0,0); //左准心crossQuarterImgs[1].transform.localPosition += new Vector3(add,0,0); //右准心crossQuarterImgs[2].transform.localPosition += new Vector3(0,add,0); //上准心crossQuarterImgs[3].transform.localPosition += new Vector3(0,-add,0); //下准心currentExoanedDegree += add;//保持当前准心开合度currentExoanedDegree = Mathf.Clamp(currentExoanedDegree, 0, maxCrossDegree);//限制准心开合度大小}//协程,调用准心开合度,1帧执行了7次//只负责射击时瞬间增大准心public IEnumerator Shoot_Crss(){yield return null;for (int i = 0; i < 7; i++){ExpendCross(Time.deltaTime*500);}}//更新子弹的UIpublic void UpdateAmmoUI(){ammoTextUI.text = currentBullets + "/" + bulletLeft;shootModeTextUI.text = shootModeName;}public enum ShootMode{AutoRifle,SemiGun}
}

游戏素材资源包下载地址:【免费】FPS游戏资源包FPS游戏资源包资源-CSDN文库

Demo源码资源下载地址:五种武器FPS游戏Demo资源-CSDN文库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS Next开发----使用XComponent自定义绘制

XComponent组件作为一种绘制组件&#xff0c;通常用于满足用户复杂的自定义绘制需求&#xff0c;其主要有两种类型"surface和component。对于surface类型可以将相关数据传入XComponent单独拥有的NativeWindow来渲染画面。 由于上层UI是采用arkTS开发&#xff0c;那么想要…

鸿蒙手势交互(四:多层手势)

四、多层手势 指父子组件嵌套时&#xff0c;父子组件均绑定了手势或事件。有两种&#xff0c;一种默认多层级手势事件&#xff0c;一种自定义多层级手势事件。 默认多层级手势事件&#xff1a;需要分清两个概念&#xff0c;触摸事件&#xff0c;手势与事件 触摸事件&#xf…

Parallels Desktop 20 for Mac中文版发布了?会哪些新功能

Parallels Desktop 20 for Mac 正式发布&#xff0c;完全支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;并且在企业版中引入了全新的管理门户。 据介绍&#xff0c;新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新&#xff0c;最大的亮点是全新推出的 Parallels…

智慧火灾应急救援航拍检测数据集(无人机视角)

智慧火灾应急救援。 无人机&#xff0c;直升机等航拍视角下火灾应急救援检测数据集&#xff0c;数据分别标注了火&#xff0c;人&#xff0c;车辆这三个要素内容&#xff0c;29810张高清航拍影像&#xff0c;共31GB&#xff0c;适合森林防火&#xff0c;应急救援等方向的学术研…

【C++ Primer Plus习题】16.10

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> #include <string> #include <…

高质量的翻译:应用程序可用性和成功的关键

在日益全球化的应用市场中&#xff0c;开发一款优秀的产品只是成功的一半。另一半&#xff1f;确保你的用户&#xff0c;无论他们在哪里或说什么语言&#xff0c;都能无缝理解和使用它。这就是高质量翻译的用武之地——不是事后的想法&#xff0c;而是应用程序可用性和最终成功…

2-100 基于matlab的水果识别

基于matlab的水果识别。从面积特征、似圆形特征&#xff0c;颜色(rgb值和hsv值)特征对图像中的梨子、苹果、桃子、香蕉和菠萝进行特征提取&#xff0c;边缘检测识别&#xff0c;最后按照筛选出来的特征对水果进行识别。程序已调通&#xff0c;可直接运行。 下载源程序请点链接…

一天认识一个硬件之连接线

我们在日常工作生活中经常会用到许多连接线&#xff0c;比如视频线&#xff0c;USB线&#xff0c;但是他们的区别在哪里&#xff0c;可能太不清楚&#xff0c;今天就来给大家分享一下。 HDMI线 特点&#xff1a;HDMI线是一种全数字化视频和声音发送接口&#xff0c;可以发送未…

PCL 点云圆柱邻域搜索

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xff09; 一、概述 本文将介绍如何使用PCL库进…

Snapchat API 访问:Objective-C 实现示例

Snapchat 是一个流行的社交媒体平台&#xff0c;它允许用户发送和接收短暂存在的图片和视频。对于开发者来说&#xff0c;访问 Snapchat API 可以为应用程序添加独特的社交功能。本文将介绍如何在 Objective-C 中实现对 Snapchat API 的访问&#xff0c;并提供一个详细的代码示…

spring boot启动报错:so that it conforms to the canonical names requirements

springboot 2.x的版本中对配置文件中的命名规范有了强制性的要求&#xff0c;如下图所示中的dataSource属性属于驼峰格式&#xff0c;但是在springboot 2.x中不允许使用驼峰形式。 根据错误提示可知将其使用 - 来分割即可 错误信息的含义&#xff1a;“Canonical names should…

LLM - 理解 多模态大语言模型(MLLM) 的 指令微调(Instruction-Tuning) 与相关技术 (四)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142237871 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 完备(F…

最新版本TensorFlow训练模型TinyML部署到ESP32入门实操

最新版本TensorFlow训练模型TinyML入门实操 1.概述 这篇文章介绍微型嵌入式设备的机器学习TinyML&#xff0c;它们的特点就是将训练好的模型部署到单片机上运行。 2.TensorFlow深度学习原理 TensorFlow开源项目是由google研发的一个嵌入式机器学习工具&#xff0c;通过调用…

鸿蒙媒体开发系列07——AVRecorder音频录制

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、概述 在HarmonyOS系统中&#xff0c;多种API都提供了音频录制开发的支持&#x…

2024永久激活版 Studio One 6 Pro for mac 音乐创作编辑软件 完美兼容

Studio One 6是一款功能强大的音乐制作软件&#xff0c;由PreSonus公司开发。它提供了全面的音频录制、编辑、混音和母带处理工具&#xff0c;适用于音乐制作人、音频工程师和创作人员。 Studio One 6拥有直观的用户界面&#xff0c;使用户能够快速而流畅地进行音乐创作。它采…

ubuntu安装emqx

目录 1.预先下载好emqx压缩包 2.使用tar命令解压 3.进入bin目录 5.放开访问端口18083 6.从通过ip地址访问emqx后台 7.默认用户名密码为admin/public 8.登录后台 9.资源包绑定在此博文可自取 1.预先下载好emqx压缩包 2.使用tar命令解压 sudo tar -xzvf emqx-5.0.8-el8-…

莱卡相机sd内存卡格式化了怎么恢复数据

在数字化时代&#xff0c;相机已成为我们记录生活、捕捉瞬间的重要设备。而SD内存卡&#xff0c;作为相机的存储媒介&#xff0c;承载着我们的珍贵记忆和重要数据。然而&#xff0c;有时由于误操作、系统错误或其他原因&#xff0c;我们可能会不小心格式化SD内存卡&#xff0c;…

一个基于VB的期刊信息管理系统

一个基本的期刊信息管理系统的示例&#xff0c;使用 Visual Basic (VB.NET) 编写。这个示例将展示如何创建一个简单的期刊信息管理系统&#xff0c;其中包括添加、查看、编辑和删除期刊的功能。 系统需求 添加期刊&#xff1a;允许用户输入期刊的信息&#xff08;如标题、作者…

OpenAI GPT o1技术报告阅读(3)-英文阅读及理解

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 这次我们继续看一个英文阅读理解的案例。 原问题&#xff1a; The following passage is the draft of an excerpt from a contempora…

条件编译代码记录

#include <iostream>// 基类模板 template<typename T> class Base { public:void func() {std::cout << "Base function" << std::endl;} };// 特化的子类 template<typename T> class Derived : public Base<T> { public:void…