目录
🎮MouseLook摄像机旋转脚本
🎮PickUpItem武器拾取脚本
🎮PlayerController玩家控制器
🎮Inventory武器库
🎮Weapon武器抽象类
🎮Weapon_AutomaticGun武器脚本
其实这个教程很早就收藏了就是被20个小时时长给吓到了一直没开始。终于在七月中旬的时候开始动手跟着做了。
最近一直加班也很忙,参与的第一个项目计划在年底上线,也是有项目经验的人了哈哈。然后拖拖拉拉的一直跟着教程做,只是个半成品敌人部分还没做,以我目前的精力就先做到这里了。原教程链接:第一人称射击游戏教程2.0【已完结】_哔哩哔哩_bilibilihttps://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 update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
//playerBody = transform.GetComponent<PlayerController>().transform;
characterController = GetComponentInParent<CharacterController>();
}
// Update is called once per frame
void 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 update
void Start()
{
rotateSpeed = 100f;
}
// Update is called once per frame
void 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 update
void 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 frame
void 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判断为false
else 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);
//判断头顶和他自己碰撞的个数是否等于0
isCanCrouch = (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 update
void Start()
{
currentWeaponID = -1;
}
// Update is called once per frame
void Update()
{
ChargeCurrentWeaponID();
}
/// <summary>
/// 更新武器编号
/// </summary>
public void ChargeCurrentWeaponID()
{
//鼠标滚轮向下 -0.1 向上 0.1 不动 0
if (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; //灯光火焰粒子特效1
public ParticleSystem sparkPatic; //灯光火焰粒子特效2(火星子)v
public 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
{
//半自动枪械鼠标输入方式改为GetMouseButtonDown
GunShootInput = 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.point
bullet = 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);
}
}
//更新子弹的UI
public void UpdateAmmoUI()
{
ammoTextUI.text = currentBullets + "/" + bulletLeft;
shootModeTextUI.text = shootModeName;
}
public enum ShootMode
{
AutoRifle,
SemiGun
}
}
游戏素材资源包下载地址:【免费】FPS游戏资源包FPS游戏资源包资源-CSDN文库
Demo源码资源下载地址:五种武器FPS游戏Demo资源-CSDN文库