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.xdnf.cn/news/145094.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

9.20哈好

函数体 #include"SeqList.h"void SeqList::init(int n) {this->ptrnew data[n];this->len0;this->sizen; }bool SeqList::empty() {return this->len0; }bool SeqList::full() {return this->sizethis->len; }void SeqList::push_back(data e) {i…

未来通信抢先看!遨游通讯2024年中国国际信息通信展亮点剧透

2024年中国国际信息通信展览会将于9月25日-27日在北京国家会议中心举行&#xff0c;本届展会以“推动数实深度融合&#xff0c;共筑新质生产力”为主题。在通信技术日新月异的今天&#xff0c;卫星通信、人工智能、低碳节能等技术理念正引领着通信行业迈向新的高度。遨游通讯作…

计算机毕业设计 基于Python的汽车销售管理系统 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

string类的模拟实现以及oj题

前言 上篇博客实现了string类的begin()、end()、构造函数、析构函数、c_str、size()、clear()、capacity()、[ ]、reserve()、push_back、append()、insert()、。这篇博客实现剩下的一些重要功能。 string类的模拟实现 string.h #include<iostream> #include<stri…

(c++)线程的创建、互斥锁的使用、线程数组

1.创建10个线程&#xff0c;每个线程都做10万次全局变量num1操作&#xff0c;然后输出这个全局变量&#xff0c;预想结果应该是100万。但是线程可能在cpu分配的一个时间片中做不完10万次1的操作&#xff0c;这时候cpu会被其他线程抢占&#xff0c;由于num1不是一个原子操作&…

每日OJ题_牛客_WY22 Fibonacci数列(斐波那契)

目录 牛客_WY22 Fibonacci数列&#xff08;斐波那契&#xff09; 解析代码 牛客_WY22 Fibonacci数列&#xff08;斐波那契&#xff09; Fibonacci数列_牛客题霸_牛客网 解析代码 求斐波那契数列的过程中&#xff0c;判断⼀下&#xff1a;何时 n 会在两个 fib 数之间。 #in…

vulnhub(11):derpnstink(hydra爆破用户名和密码、验证的文件上传)

端口 nmap主机发现 nmap -sn 192.168.159.120/24 ​ Nmap scan report for 192.168.159.120 Host is up (0.00020s latency). ​ 120是新出现的机器&#xff0c;他就是靶机 nmap端口扫描 nmap -Pn 192.168.159.120 -p- --min-rate 10000 -oA nmap/scan 扫描开放端口保存到 nma…

2024.9.20营养小题【2】(动态分配二维数组)

这道题里边涉及到了动态分配二维数组的知识点&#xff0c;不刷这道题我也不知道这个知识点&#xff0c;算是一个比较进阶一点的知识点了。 参考&#xff1a;C语言程序设计_动态分配二维数组_哔哩哔哩_bilibili【C/C 数据结构 】二维数组结构解析 - 知乎 (zhihu.com)

数据结构—(java)反射,枚举,lambda表达式

文章目录 反射反射的定义&#xff1a;反射相关的类&#xff1a;反射相关的方法&#xff1a;反射示例&#xff1a;获取Class类对象创建指定类的对象反射私有属性&#xff1a;反射私有方法&#xff1a;反射私有的构造方法 枚举枚举的意义枚举类的实现枚举类的使用&#xff1a;Enu…

机器学习算法与实践_03概率论与贝叶斯算法笔记

1、概率论基础知识介绍 人工智能项目本质上是一个统计学项目&#xff0c;是通过对 样本 的分析&#xff0c;来评估/估计 总体 的情况&#xff0c;与数学知识相关联 高等数学 ——> 模型优化 概率论与数理统计 ——> 建模思想 线性代数 ——> 高性能计算 在机器学…

MySQL篇(窗口函数/公用表达式(CTE))(持续更新迭代)

目录 讲解一&#xff1a;窗口函数 一、简介 二、常见操作 1. sumgroup by常规的聚合函数操作 2. sum窗口函数的聚合操作 三、基本语法 1. Function(arg1,..., argn) 1.1. 聚合函数 sum函数&#xff1a;求和 min函数 &#xff1a;最小值 1.2. 排序函数 1.3. 跨行函数…

2024年港澳台华侨生联考分数线继续更新来啦

导读 在最近的一系列分享中&#xff0c;我们和大家一同分享了2024年港澳台华侨生联考的分数线。今天我们继续和大家一起分享一些2024年港澳台联考的高校录取分数线吧&#xff01; 首都师范大学 首都师范大学和首都医科大学作为被低估的两所高校&#xff0c;这两年的分数线也是…

数据结构之二叉树(1)

数据结构之二叉树&#xff08;1&#xff09; 一、树 1、树的概念与结构 &#xff08;1&#xff09;树是一种非线性的数据结构&#xff0c;由n(n>0)个有限结点组成一个具有层次关系的集合。 &#xff08;2&#xff09;树有一个特殊的结点&#xff0c;叫做根结点&#xff…

【记录】C++学习路线

一、记录心得&#xff1a; 目前自己的状况是刚上大三&#xff0c;学校是双非一本&#xff0c;教的主流方向是 J A V A JAVA JAVA开发方向&#xff0c;还有就是嵌入式方向&#xff0c;这两个方向自己都不是很感兴趣&#xff0c;所以从大一开始就自学 C C C&#xff0c;加入 A…

图的应用(拓扑排序)

自己设计一个不少于6个结点的带权有向无环图&#xff0c;并画出其邻接矩阵的样子 用一维数组将你设计的有向无环图的邻接矩阵进行压缩存储 文字描述&#xff1a;基于你压缩存储的数组&#xff0c;如何判断结点 i、j 之间是否有边&#xff1f; 基于你设计的带权有向无环图&#…

flash_attention简要笔记

优化效果 原来&#xff0c;attention部分的计算量和中间激活占用显存的复杂度都是 O ( N 2 ) O(N^2) O(N2) 计算量部分原来QK矩阵乘和attn_scoreV矩阵乘的计算量&#xff0c;复杂度都是 O ( N 2 ) O(N^2) O(N2)&#xff1b;中间激活因为中间有一个attn_score&#xff0c;所以复…

基于yolov8的战斗机类型识别检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 YOLOv8是Ultralytics公司推出的最新一代对象检测模型&#xff0c;它在目标检测领域展现了前所未有的先进性能。基于YOLOv8的战斗机类型识别检测系统&#xff0c;通过结合深度学习技术和卷积神经网络&#xff08;CNN&#xff09;&#xff0c;实现了对战斗机图像的…

八股文-多线程、并发

八股文-多线程、并发 最近学到了一种方法&#xff0c;可以用于简历项目经验编写以及面试题目的回答 STAR法则&#xff1a;在什么背景下&#xff0c;你需要解决什么问题&#xff0c;你做了啥&#xff0c;得到了什么结果 情境&#xff08;Situation&#xff09;&#xff1a; 描…

软件测试分类篇(上)

目录 引言&#xff1a; 一、为什么要对软件测试进行分类 二、按照测试目标分类 1. 界面测试 2. 功能测试 3. 性能测试 4. 可靠性测试 5. 安全性测试 6. 易用性测试 三、按照执行方式分类 1. 静态测试 2. 动态测试 四、按照测试方法分类 1. 白盒测试 2. 黑盒测试 …

HTTP 教程

HTTP/HTTPS 简介 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;和 HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff0c;超文本传输安全协议&#xff09;是用于在网络中传输信息的两种主要协议。它们定义了客户端和服务器…