unity制作FPS射击游戏

文章目录

  • 介绍
  • 鼠标移动控制视角
  • 行走、奔跑、跳跃、下蹲
  • 射击、后坐力、射速、瞄准、弹痕、枪火、抛壳
  • 手臂摇摆
  • 手枪
  • 切枪效果
  • 动画状态机
  • 玩家血量
  • 新地图
  • 场景颜色渐变
  • 激光墙
  • 获取钥匙
  • 滑动门
  • NPC属性
  • 攻击逻辑
  • 终点传送门


介绍

角色动作方面包括行走、奔跑、跳跃、武器切换、弹夹更换、武器展示、弹壳抛出效果、射击效果、全自动与半自动射击效果、瞄准效果、后坐力效果、弹痕效果等多种动作。

非玩家角色(NPC)具备多个动画状态,包括固定路径巡逻、行走、奔跑、寻路攻击等多种行为。

太空地图中拥有滑动门、激光枪、钥匙、传送门、电脑设备等多种功能性组件。

玩家可手持手枪或步枪,与NPC交火,取得钥匙,然后前往传送门以完成任务。在此过程中,游戏中丰富的特效将会呈现,包括玩家受伤特效、射击效果、钥匙获得特效、场景加载特效以及场景结束特效。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


鼠标移动控制视角

  1. 声明了鼠标灵敏度、玩家位置和摄像机旋转角度的变量。
  2. 在游戏开始时,锁定光标在窗口中心并隐藏。
  3. 在每帧更新中,根据鼠标移动量和灵敏度计算摄像机的旋转。
  4. 限制摄像机旋转在垂直方向上的角度,避免过度旋转。
  5. 将计算出的旋转应用于摄像机和玩家位置,实现视角控制。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 摄像机的旋转
/// 玩家左右旋转控制视线左右移动
/// 摄像机上下旋转控制视线上下移动
/// </summary>
public class MouseLook : MonoBehaviour
{public float mouseSensitivity=100f;//鼠标灵敏度public Transform playerBody;//玩家的位置private float xRotation=0f;// Start is called before the first frame updatevoid Start(){//将光标锁定在该游戏窗口的中心,并且隐藏光标Cursor.lockState = CursorLockMode.Locked;}// Update is called once per framevoid Update(){float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;xRotation -= mouseY;//将上下旋转的轴值进行累计xRotation = Mathf.Clamp(xRotation,-90f,90f);//限制轴值的累计(这里就能发现上90度和下90度角正好相对于了90的轴值)transform.localRotation = Quaternion.Euler(xRotation, 0f,0f);playerBody.Rotate(Vector3.up * mouseX);}
}

行走、奔跑、跳跃、下蹲

在这里插入图片描述

这脚本控制玩家的基本动作和声音效果:

  1. 变量和属性:

    • 定义了变量和属性,如移动速度、跳跃力度、重力、地面检测等。
  2. 移动和跳跃:

    • 使用Moveing()方法实现玩家移动,包括获取移动方向、用CharacterController.Move()移动、模拟跳跃和在空中减小高度。
  3. 下蹲:

    • 通过Crouch()方法修改CharacterController的高度实现下蹲。
  4. 声音效果:

    • 使用PlayFootStepSound()方法播放移动音效,根据行走和奔跑状态播放不同音效。
  5. 其他功能:

    • 检测是否在斜坡上,施加额外力量;检测是否在地面上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制玩家移动
/// </summary>
public class PlayerMovement : MonoBehaviour
{private CharacterController characterController;public float walkSpeed = 10f;// 移动速度public float runSpeed=15f;//奔跑速度public float jumpForce = 3f;//跳跃力度private Vector3 velocity;//设置玩家Y轴的一个冲量变化private Vector3 moveDirction; //设置移动方向public float gravity = -9f; //设置重力public Transform groundCheck;//地面检测物体private float groundDistance = 0.4f;//与地面的距离public LayerMask groundMask;private bool isJump;//判断是否在跳跃private bool isGround;//判断是否在地面上public bool isWalk;//判断是否在行走public bool isRun;//判断是否在奔跑private bool isCrouch;//判断是否蹲下[SerializeField] private float slopeForce=6.0f; //走斜坡施加的力(是一个乘量)[SerializeField] private float slopeForceRayLength=2.0f; //斜坡射线长度(自定义量)[Header("键位设置")][SerializeField] [Tooltip("跳跃按键")] private string jumpInputName = "Jump";[SerializeField] [Tooltip("奔跑按键")] private KeyCode runInputName;[SerializeField] [Tooltip("下蹲按键")] private KeyCode crouchInputName;private AudioSource audioSource;public AudioClip walkingSound;public AudioClip runingSound;void Start(){characterController = GetComponent<CharacterController>();audioSource = GetComponent<AudioSource>();audioSource.clip = walkingSound;audioSource.loop = true;}// Update is called once per framevoid Update(){CheckGround();Moveing();Crouch();}/// <summary>/// 判断是否在斜坡上/// </summary>/// <returns></returns>public bool OnSlope(){if (isJump)return false;RaycastHit hit;//向下打出射线(检查是否在斜坡上)if (Physics.Raycast(transform.position, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength)){//如果触碰到的点的法线,不是在(0,1,0)这个方向上的,那么就人物处在斜坡上if (hit.normal != Vector3.up){return true;}}return false;}/// <summary>/// 地面检测/// </summary>public void CheckGround() {//在 groundCheck 位置上做一个球体检测判断是否处在地面上isGround = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);//如果处在地面上,重力设置成一个固定值if (isGround && velocity.y < 0){velocity.y = -2f;}}/// <summary>/// 跳跃/// </summary>public void Jump() {isJump = Input.GetButtonDown(jumpInputName);//施加跳跃的力 if (isJump && isGround){velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);//velocity.y = 20f;}}/// <summary>/// 下蹲/// </summary>public void Crouch() {isCrouch = Input.GetKey(crouchInputName);if (isCrouch){characterController.height = 1f;}else{characterController.height =1.8f;}   }/// <summary>/// 移动/// </summary>public void Moveing(){float speed;float h = Input.GetAxis("Horizontal");float v = Input.GetAxis("Vertical");isRun = Input.GetKey(runInputName);isWalk = (Mathf.Abs(h) > 0 || Mathf.Abs(v) > 0) ? true : false;speed = isRun ? runSpeed : walkSpeed; //设置行走或奔跑的速度moveDirction = (transform.right * h + transform.forward * v).normalized;//设置玩家移动方向(将移动速度进行规范化,防止斜向走速度变大)characterController.Move(moveDirction * speed * Time.deltaTime);//移动velocity.y += gravity * Time.deltaTime;//不在地面上(空中,累加重力值)characterController.Move(velocity * Time.deltaTime); //施加重力Jump();//如果处在斜面上移动if (OnSlope()){//向下增加力量characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);}PlayFootStepSound();}///播放移动的音效public void PlayFootStepSound() {if (isGround && moveDirction.sqrMagnitude > 0.9f){audioSource.clip = isRun ? runingSound : walkingSound;//设置行走或者奔跑的音效if (!audioSource.isPlaying){audioSource.Play();}}else{if (audioSource.isPlaying){audioSource.Pause();}}}}

射击、后坐力、射速、瞄准、弹痕、枪火、抛壳

武器射击:控制射击位置、射程、子弹数量、射速、射击精度等参数,并播放相关音效、动画和特效。

武器装弹:控制子弹数量、弹匣容量、备弹数量、自动装弹、手动装弹等功能,并播放相关音效、动画和UI界面的更新。

武器切换:控制游戏中玩家切换主武器、副武器、全自动和半自动射击模式的功能,并更新UI界面的状态。

武器精度:根据射击模式(全自动或半自动)控制射击精度,实现游戏中的真实射击体验。

界面更新:控制UI界面的更新,包括弹匣数量、备弹数量、射击模式、准星等界面元素的实时更新。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// 武器射击
/// </summary>
public class WeaponController : MonoBehaviour
{public PlayerMovement PM;public Transform shooterPoint; // 射击的位置(枪口)public int range = 100; // 武器射程public int bulletsMag = 31; // 一个弹匣数量public int bulletLeft = 300; // 备弹public int currentBullets; // 当前子弹数public ParticleSystem muzzleFlash; // 枪口火焰特效public GameObject hitParticles; // 子弹击中粒子特效public GameObject bulletHole; // 弹孔public Light muzzleFlashLight; // 枪口火焰灯光public float fireRate = 0.1f; // 射速public float bulletdamage = 10.0f; // 单发子弹伤害private float fireTimer; // 计时器private float SpreadFactor; // 射击的一点偏移量[Header("键位设置")][SerializeField] [Tooltip("填装子弹按键")] private KeyCode reloadInputName;[SerializeField] [Tooltip("查看武器按键")] private KeyCode inspectInputName;[SerializeField] [Tooltip("主武器按键")] private KeyCode AutoRifleKey;[SerializeField] [Tooltip("副武器按键")] private KeyCode HandGunKey;[SerializeField] [Tooltip("自动半自动切换按键")] private KeyCode GunShootModelInputName;private Animator anim;/*音效参数*/private AudioSource audioSource;public AudioClip AK47ShoundClip; /*枪声音效片段*/public AudioClip reloadAmmoLeftClip; // 换子弹1音效片段public AudioClip reloadOutOFAmmoClip; // 换子弹2音效片段(拉枪栓)private bool isReloading; // 判断是否在装弹private bool isAiming; // 判断是否在瞄准public Transform casingSpawnPoint; // 子弹壳抛出的位置public Transform casingPrefab; // 子弹壳预制体private Camera mainCamera;public ChooseGunController CGC; // 声明切换武器类的实例/*使用枚举区分全自动和半自动模式*/public enum ShootMode { AutoRifle, SemiGun };public ShootMode shootingMode;private bool GunShootInput; // 根据全自动和半自动 射击的键位输入发生改变private int modeNum = 1; // 模式切换的一个中间参数(1:全自动模式,2:半自动模式)private string shootModelName;/*UI的设置*/public Image crossHairUI;public Text ammoTextUI;public Text ShootModelTextUI;public Transform mainCameraTransform;public float recoilAmount = 2.0f; // 后坐力的强度public float recoilRecoverySpeed = 5.0f; // 后坐力恢复速度private Vector3 originalCameraPosition; // 原始相机位置,用于恢复后坐力private Quaternion originalCameraRotation;private void Start(){// 获取标签为"maincamera"的相机的Transform组件GameObject mainCameraObject = GameObject.FindGameObjectWithTag("MainCamera");if (mainCameraObject != null){mainCameraTransform = mainCameraObject.transform;originalCameraPosition = mainCameraTransform.localPosition; // 记录原始相机位置originalCameraRotation = mainCameraTransform.localRotation;}audioSource = GetComponent<AudioSource>();anim = GetComponent<Animator>();currentBullets = bulletsMag;mainCamera = Camera.main;shootingMode = ShootMode.AutoRifle; // AK47步枪默认是全自动模式shootModelName = "AK47";UpdateAmmoUI();}private void Update(){if (health.isDead){anim.SetBool("Run", false);anim.SetBool("Walk", false);anim.SetBool("Aim", false);return;}// 切换模式(全自动和半自动)if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 1){modeNum = 1;shootModelName = "全自动";shootingMode = ShootMode.AutoRifle;ShootModelTextUI.text = shootModelName;}else if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 0){modeNum = 0;shootModelName = "半自动";shootingMode = ShootMode.SemiGun;ShootModelTextUI.text = shootModelName;}/*控制射击模式的转换  后面就要用代码去动态控制了*/switch (shootingMode){case ShootMode.AutoRifle:GunShootInput = Input.GetMouseButton(0);fireRate = 0.1f;break;case ShootMode.SemiGun:GunShootInput = Input.GetMouseButtonDown(0);fireRate = 0.2f;break;}if (GunShootInput && currentBullets > 0){GunFire();}else{muzzleFlashLight.enabled = false;}// 计时器加时间if (fireTimer < fireRate){fireTimer += Time.deltaTime;}anim.SetBool("Run", PM.isRun); // 播放跑步动画anim.SetBool("Walk", PM.isWalk);// 获取动画状态机第一层动画的状态AnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(0);// 两种换子弹的东湖if (info.IsName("reload_ammo_left") || info.IsName("reload_out_of_ammo")){isReloading = true;}else{isReloading = false;}if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletsMag && bulletLeft > 0){Reload();}SpreadFactor = (isAiming) ? 0f : 0.01f;DoingAim();if (Input.GetKeyDown(inspectInputName)){anim.SetTrigger("Inspect");}// 切换主武器(自动步枪)if (Input.GetKeyDown(AutoRifleKey)){CGC.ChangeWeapon(0);}// 切换副武器(手枪)if (Input.GetKeyDown(HandGunKey)){CGC.ChangeWeapon(1);}if (mainCameraTransform != null){mainCameraTransform.localPosition = Vector3.Lerp(mainCameraTransform.localPosition, originalCameraPosition, Time.deltaTime * recoilRecoverySpeed);}}// 更新UIpublic void UpdateAmmoUI(){ammoTextUI.text = currentBullets + " / " + bulletLeft;ShootModelTextUI.text = shootModelName;}/// <summary>/// 瞄准的逻辑/// </summary>public void DoingAim(){if (Input.GetMouseButton(1) && !isReloading && !PM.isRun){isAiming = true;anim.SetBool("Aim", isAiming);crossHairUI.gameObject.SetActive(false);mainCamera.fieldOfView = 25; // 瞄准的时候摄像机视野变小}else{isAiming = false;anim.SetBool("Aim", isAiming);crossHairUI.gameObject.SetActive(true);mainCamera.fieldOfView = 60; // 瞄准的时候摄像机视野恢复}}/// <summary>/// 射击逻辑/// </summary>public void GunFire(){// 控制射速,当前弹夹打光了,正在装子弹,正在奔跑 就不可以发射了if (fireTimer < fireRate || currentBullets <= 0 || isReloading || PM.isRun) return;RaycastHit hit;Vector3 shootDirection = shooterPoint.forward;// 改成这个,shootDirection shooterPoint这个游戏物体进行小的偏移(TransformDirection 将local坐标转换为世界坐标)shootDirection = shootDirection + shooterPoint.TransformDirection(new Vector3(Random.Range(-SpreadFactor, SpreadFactor), Random.Range(-SpreadFactor, SpreadFactor)));if (Physics.Raycast(shooterPoint.position, shootDirection, out hit, range)){Debug.Log(hit.transform.name + "打到了");if (hit.transform.CompareTag("zombie")){zombiehealth zombieHealth = hit.transform.GetComponent<zombiehealth>();if (zombieHealth != null){zombieHealth.TakeDamage(10f);}GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(hitParticleEffect, 1f);}else{GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(hitParticleEffect, 1f);GameObject bulletHoleEffect = Instantiate(bulletHole, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(bulletHoleEffect, 0.8f);}if (mainCameraTransform != null){Vector3 recoilVector = Vector3.up * 0.3f;mainCameraTransform.localPosition += recoilVector;}}if (!isAiming){anim.CrossFadeInFixedTime("fire", 0.1f); // 播放普通开火动画(使用动画的淡出淡入效果)}else{// 瞄准状态下,播放瞄准的开火动画anim.Play("aim_fire", 0, 0f);}muzzleFlash.Play(); // 播放火光特效muzzleFlashLight.enabled = true;PlayerShootSound(); // 播放射击音效// 实例抛弹壳Instantiate(casingPrefab, casingSpawnPoint.transform.position, casingSpawnPoint.transform.rotation);currentBullets--;UpdateAmmoUI();fireTimer = 0f; // 重置计时器}public void PlayerShootSound(){audioSource.clip = AK47ShoundClip;audioSource.Play();}/// <summary>/// 填装弹药逻辑/// </summary>public void Reload(){if (bulletLeft <= 0) return;DoReloadAnimation();// 计算需要填充的子弹int bulletToLoad = bulletsMag - currentBullets;// 计算备弹扣除的子弹int bulletToReduce = (bulletLeft >= bulletToLoad) ? bulletToLoad : bulletLeft;bulletLeft -= bulletToReduce; // 备弹减少currentBullets += bulletToReduce; // 当前子弹增加UpdateAmmoUI();}// 播放装弹动画public void DoReloadAnimation(){if (currentBullets > 0){anim.Play("reload_ammo_left", 0, 0);audioSource.clip = reloadAmmoLeftClip;audioSource.Play();}if (currentBullets == 0){anim.Play("reload_out_of_ammo", 0, 0);audioSource.clip = reloadOutOFAmmoClip;audioSource.Play();}}
}

手臂摇摆

这个脚本通过监听鼠标输入,在武器手臂模型的位置上添加摇摆效果,增加了武器的真实感和动态性。实现了武器摇摆效果,具体包括:

  1. 摇摆参数:定义了摇摆的幅度(amout)、平滑值(smoothAmout)和最大幅度(maxAmout)。

  2. 初始位置:记录武器初始位置。

  3. Start 函数:在开始时获取武器的初始位置。

  4. Update 函数:每帧更新,根据鼠标输入控制武器的手臂模型位置。

  5. 摇摆计算:根据鼠标的移动输入,计算出摇摆的偏移量。

  6. 限制范围:限制摇摆的偏移量在一定范围内。

  7. 位置更新:根据摇摆的偏移量和初始位置,平滑地更新武器的位置,实现摇摆效果。

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 武器摇摆
/// </summary>
public class WeaponSway : MonoBehaviour
{/*摇摆的参数*/public float amout; //摇摆幅度public float smoothAmout;//一个平滑值public float maxAmout;//最大摇摆幅度private Vector3 originPostion; //初始位置// Start is called before the first frame updatevoid Start(){//自身位置(相对于父级物体变换得位置)originPostion = transform.localPosition;}// Update is called once per framevoid Update(){//设置武器手臂模型位置得值,float movementX = -Input.GetAxis("Mouse X") * amout;float movementY = -Input.GetAxis("Mouse Y") * amout;//限制movementX = Mathf.Clamp(movementX, -maxAmout, maxAmout);movementY = Mathf.Clamp(movementY, -maxAmout, maxAmout);Vector3 finallyPostion = new Vector3(movementX, movementY, 0);//手柄位置变换transform.localPosition = Vector3.Lerp(transform.localPosition, finallyPostion + originPostion, Time.deltaTime * smoothAmout);}
}

手枪

这脚本实现了一个武器射击系统,包括:

  1. 武器属性:定义射程、弹匣容量等武器属性。
  2. 射击特效:创建火焰、粒子和弹孔特效。
  3. 射击模式:区分全自动和半自动射击,设置键位。
  4. 音效管理:播放射击和换弹音效。
  5. 瞄准与UI:按鼠标右键瞄准,显示准心和弹药数量。
  6. 射击逻辑:根据模式、键位和弹药数量触发射击。
  7. 装弹逻辑:播放动画、音效并填装弹药。
  8. 后坐力效果:模拟枪口抖动。
  9. 武器切换:按键切换主副武器。
  10. 更新UI:显示子弹数和射击模式。
  11. 瞄准逻辑:控制瞄准状态下视野和动画。

这些功能共同构建了玩家可以与武器进行交互的复杂系统。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class HandGunController : MonoBehaviour
{// 引用其他脚本和游戏对象public PlayerMovement PM;public Transform shooterPoint; // 武器射击的位置(枪口)// 武器参数public int range = 100; // 武器射程public int bulletsMag = 31; // 一个弹匣数量public int currentBullets; // 当前子弹数public int bulletLeft = 300; // 备弹// 枪口特效和音效public ParticleSystem muzzleFlash; // 枪口火焰特效public GameObject hitParticles; // 子弹击中粒子特效public GameObject bulletHole; // 弹孔public Light muzzleFlashLight; // 枪口火焰灯光public AudioClip HandGunSound; // 枪声音效片段public AudioClip reloadAmmoLeftClip; // 换子弹1音效片段public AudioClip reloadOutOFAmmoClip; // 换子弹2音效片段(拉枪栓)// 武器射速和键位设置public float fireRate = 0.1f; // 射速private float fireTimer; // 计时器private float SpreadFactor; // 射击的一点偏移量[SerializeField] private KeyCode reloadInputName; // 填装子弹按键[SerializeField] private KeyCode inspectInputName; // 查看武器按键[SerializeField] private KeyCode AutoRifleKey; // 主武器按键[SerializeField] private KeyCode HandGunKey; // 副武器按键[SerializeField] private KeyCode GunShootModelInputName; // 自动半自动切换按键// 控制器和状态private Animator anim; // 动画控制器private AudioSource audioSource; // 音效控制器private bool isReloading; // 是否在装弹private bool isAiming; // 是否在瞄准// 子弹壳抛出public Transform casingSpawnPoint; // 子弹壳抛出的位置public Transform casingPrefab; // 子弹壳预制体private Camera mainCamera; // 主摄像机public ChooseGunController CGC; // 切换武器类的实例// 射击模式和UIpublic enum ShootMode { AutoRifle, SemiGun }; // 射击模式枚举public ShootMode shootingMode; // 当前射击模式private bool GunShootInput; // 根据射击模式改变的射击键位输入private int modeNum = 0; // 模式切换的参数(1:全自动模式,2:半自动模式)private string shootModelName; // 射击模式名称// UI元素public Image crossHairUI; // 十字准心public Text ammoTextUI; // 弹药文本public Text ShootModelTextUI; // 射击模式文本private void Start(){audioSource = GetComponent<AudioSource>();anim = GetComponent<Animator>();currentBullets = bulletsMag;mainCamera = Camera.main;shootingMode = ShootMode.SemiGun; // G18手枪默认是半自动模式shootModelName = "clock";UpdateAmmoUI();}private void Update(){// 切换射击模式(全自动和半自动)if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 1){modeNum = 1;shootModelName = "全自动";shootingMode = ShootMode.AutoRifle;ShootModelTextUI.text = shootModelName;}else if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 0){modeNum = 0;shootModelName = "半自动";shootingMode = ShootMode.SemiGun;ShootModelTextUI.text = shootModelName;}// 根据射击模式确定射击输入和射速switch (shootingMode){case ShootMode.AutoRifle:GunShootInput = Input.GetMouseButton(0);fireRate = 0.08f;break;case ShootMode.SemiGun:GunShootInput = Input.GetMouseButtonDown(0);fireRate = 0.2f;break;}// 射击逻辑if (GunShootInput && currentBullets > 0){GunFire();}else{muzzleFlashLight.enabled = false;}// 计时器更新if (fireTimer < fireRate){fireTimer += Time.deltaTime;}anim.SetBool("Run", PM.isRun);anim.SetBool("Walk", PM.isWalk);AnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(0);// 判断是否在换子弹动画中if (info.IsName("reload_ammo_left") || info.IsName("reload_out_of_ammo")){isReloading = true;}else{isReloading = false;}// 换子弹逻辑if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletsMag && bulletLeft > 0){Reload();}SpreadFactor = (isAiming) ? 0f : 0.01f;DoingAim();// 查看武器动画if (Input.GetKeyDown(inspectInputName)){anim.SetTrigger("Inspect");}// 切换武器if (Input.GetKeyDown(AutoRifleKey)){CGC.ChangeWeapon(0); // 切换到主武器(自动步枪)}if (Input.GetKeyDown(HandGunKey)){CGC.ChangeWeapon(1); // 切换到副武器(手枪)}}// 更新UIpublic void UpdateAmmoUI(){ammoTextUI.text = currentBullets + " / " + bulletLeft;ShootModelTextUI.text = shootModelName;}// 瞄准逻辑public void DoingAim(){if (Input.GetMouseButton(1) && !isReloading && !PM.isRun){isAiming = true;anim.SetBool("Aim", isAiming);crossHairUI.gameObject.SetActive(false);mainCamera.fieldOfView = 40;}else{isAiming = false;anim.SetBool("Aim", isAiming);crossHairUI.gameObject.SetActive(true);mainCamera.fieldOfView = 60;}}// 射击逻辑public void GunFire(){if (fireTimer < fireRate || currentBullets <= 0 || isReloading || PM.isRun) return;RaycastHit hit;Vector3 shootDirection = shooterPoint.forward;shootDirection = shootDirection + shooterPoint.TransformDirection(new Vector3(Random.Range(-SpreadFactor, SpreadFactor), Random.Range(-SpreadFactor, SpreadFactor)));if (Physics.Raycast(shooterPoint.position, shootDirection, out hit, range)){Debug.Log(hit.transform.name + "打到了");if (hit.transform.CompareTag("zombie")){zombiehealth zombieHealth = hit.transform.GetComponent<zombiehealth>();if (zombieHealth != null){zombieHealth.TakeDamage(20f);}GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(hitParticleEffect, 1f);}else{GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(hitParticleEffect, 1f);GameObject bulletHoleEffect = Instantiate(bulletHole, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));Destroy(bulletHoleEffect, 0.8f);}}if (!isAiming){anim.CrossFadeInFixedTime("fire", 0.1f);}else{anim.Play("aim_fire", 0, 0f);}muzzleFlash.Play();muzzleFlashLight.enabled = true;PlayerShootSound();Instantiate(casingPrefab, casingSpawnPoint.transform.position, casingSpawnPoint.transform.rotation);currentBullets--;UpdateAmmoUI();fireTimer = 0f;}// 播放射击音效public void PlayerShootSound(){audioSource.clip = HandGunSound;audioSource.Play();}// 填装子弹逻辑public void Reload(){if (bulletLeft <= 0) return;DoReloadAnimation();int bulletToLoad = bulletsMag - currentBullets;int bulletToReduce = (bulletLeft >= bulletToLoad) ? bulletToLoad : bulletLeft;bulletLeft -= bulletToReduce;currentBullets += bulletToReduce;UpdateAmmoUI();}// 播放填装子弹动画public void DoReloadAnimation(){if (currentBullets > 0){anim.Play("reload_ammo_left", 0, 0);audioSource.clip = reloadAmmoLeftClip;audioSource.Play();}if (currentBullets == 0){anim.Play("reload_out_of_ammo", 0, 0);audioSource.clip = reloadOutOFAmmoClip;audioSource.Play();}}
}

在这里插入图片描述


切枪效果

这脚本实现了武器切换功能,主要包括:

  1. 武器集合:使用列表(List)存储多个武器游戏对象。

  2. 初始索引:在开始时初始化索引。

  3. 武器切换方法:根据传入的武器索引,控制显示对应的武器并隐藏其他武器。

  4. 循环遍历:遍历武器列表,根据索引设置武器的显示状态。

  5. 子武器处理:根据切换情况,更新对应子武器的弹药UI显示。

这个脚本通过列表管理多个武器,并根据切换情况动态显示和隐藏武器,实现了角色切换武器的功能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 武器切换
/// </summary>
public class ChooseGunController : MonoBehaviour
{//武器设置为集合,存放多个武器public List<GameObject> Weapons=new List<GameObject>();private int index;// Start is called before the first frame updatevoid Start(){index = 0;}//切换武器方法(全部武器挂载人物手上,默认隐藏,,谁需要就显示显示出来)public void ChangeWeapon(int WeaponIndex){for (int i = 0; i < Weapons.Count; i++){if (WeaponIndex == i){Weapons[i].gameObject.SetActive(true);if (WeaponIndex == 0){Weapons[i].gameObject.GetComponentInChildren<WeaponController>().UpdateAmmoUI();      // print(Weapons[i].gameObject.name);}else if (WeaponIndex == 1){Weapons[i].gameObject.GetComponentInChildren<HandGunController>().UpdateAmmoUI();//print(Weapons[i].gameObject.name);}}else{Weapons[i].gameObject.SetActive(false);}}}}

动画状态机

行走、奔跑、跳跃、开枪、换弹、切枪、瞄准

在这里插入图片描述


玩家血量

这脚本管理玩家的生命和死亡:

  • 有声音效果:受伤和死亡音效。
  • 跟踪生命值和死亡状态。
  • 控制死亡效果:播放音效、变暗场景、延迟后重载。
  • 当玩家受伤时,减少生命值和出血量。
  • 逐渐降低场景的颜色饱和度。
  • 如果玩家死亡,重新加载当前场景。
using UnityEngine;
using UnityStandardAssets.ImageEffects;
using UnityEngine.SceneManagement;public class Health : MonoBehaviour
{public AudioClip hitSound;  // 受伤音效public AudioClip deathSound;  // 死亡音效public float maxHealth = 100f;  // 最大生命值public float currentHealth = 100f;  // 当前生命值private float timer = 0;  // 计时器public static bool isDead = false;  // 是否死亡private ColorCorrectionCurves colorCurves;  // 颜色校正曲线private MouseLook mouth;  // 鼠标观察private void Start(){BleedBehavior.BloodAmount = 0;  // 出血行为的血量colorCurves = Camera.main.GetComponent<ColorCorrectionCurves>();  // 获取颜色校正组件mouth = Camera.main.GetComponent<MouseLook>();  // 获取鼠标观察组件}void Update(){if (isDead){LevelReset();  // 重置关卡}}// 受伤方法public void TakeDamage(float damage){if (isDead){return;  // 如果已经死亡,不处理伤害}currentHealth -= damage;  // 减少生命值if (currentHealth <= 0f){Die();  // 死亡}else{currentHealth -= damage;AudioSource.PlayClipAtPoint(hitSound, transform.position);  // 播放受伤音效}BleedBehavior.BloodAmount += Mathf.Clamp01(damage / currentHealth);  // 增加出血量BleedBehavior.BloodAmount = 0.52f;  // 固定出血量(这一行可能需要调整或删除)}// 死亡方法private void Die(){isDead = true;  // 设置死亡状态AudioSource.PlayClipAtPoint(deathSound, transform.position);  // 播放死亡音效//this.GetComponent<PlayerMovement>().enabled = false;mouth.enabled = false;colorCurves.enabled = true;Invoke("ReloadCurrentScene", 6f);  // 延迟重载当前场景}// 重置关卡方法public void LevelReset(){//timer += Time.deltaTime;colorCurves.saturation -= (Time.deltaTime / 1.5f);  // 减少饱和度//colorCurves.saturation = Mathf.Max(0, colorCurves.saturation);}// 重新加载当前场景方法private void ReloadCurrentScene(){SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);isDead = false;}
}

新地图

创建太空站地图。

在这里插入图片描述
在这里插入图片描述


场景颜色渐变

在这里插入图片描述

using UnityEngine;
using UnityEngine.UI;public class FadeOut : MonoBehaviour
{public float speed = 1.0f;private Image image;private float alpha = 1.0f;public AudioClip enter;private void Start(){image = GetComponent<Image>();GetComponent<AudioSource>().PlayOneShot(enter);}private void Update(){alpha = Mathf.Lerp(alpha, 0.0f, speed * Time.deltaTime);image.color = new Color(image.color.r, image.color.g, image.color.b, alpha);}
}

激光墙

玩家通过激光墙会受到伤害。
给激光墙添加光源、空间音效。

在这里插入图片描述

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Laser : MonoBehaviour
{public int damage = 30;  // 伤害值public float damageDelay = 1f;  // 伤害延迟private float lastDamageTime = 0f;  // 上次伤害时间private GameObject player;  // 玩家对象void Start(){player = GameObject.FindGameObjectWithTag("Player");  // 获取玩家对象}void OnTriggerEnter(Collider other){if (other.gameObject == player && Time.time > lastDamageTime + damageDelay){player.GetComponent<Health>().TakeDamage(damage);  // 对玩家造成伤害lastDamageTime = Time.time;  // 更新上次伤害时间}}
}

获取钥匙

玩家获取钥匙才能打开后续的门。

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class card : MonoBehaviour
{public AudioClip keyGrab;private GameObject player;void Start(){player = GameObject.FindGameObjectWithTag("Player");}void OnTriggerEnter(Collider other){if (other.gameObject == player){AudioSource.PlayClipAtPoint(keyGrab, transform.position);Destroy(this.gameObject);}}
}

滑动门

玩家触碰到门,门会打开。

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class door : MonoBehaviour
{private GameObject player;public AudioClip enter;public AudioClip exit;public float speed = 1f;private bool isOpen = false;private bool isMoving = false;private Vector3 targetPosition;private void Start(){player = GameObject.FindGameObjectWithTag("Player");//targetPosition = transform.position;}private void Update(){if (isMoving){// 修复:更新门的位置而不是碰撞体的位置this.gameObject.transform.position = Vector3.Lerp(transform.position, targetPosition, speed * Time.deltaTime);if (Vector3.Distance(transform.position, targetPosition) < 0.01f){isMoving = false;}}}private void OnTriggerEnter(Collider other){if (other.gameObject == player && !isOpen){isOpen = true;targetPosition = new Vector3(transform.position.x, 5.3f, transform.position.z);AudioSource.PlayClipAtPoint(enter, transform.position);isMoving = true;}}private void OnTriggerExit(Collider other){if (other.gameObject == player && isOpen){isOpen = false;targetPosition = new Vector3(transform.position.x, 1f, transform.position.z);AudioSource.PlayClipAtPoint(exit, transform.position);isMoving = true;}}
}

NPC属性

在这里插入图片描述

  1. Start(): 在脚本启动时进行初始化,获取组件引用、设置初始参数、获取玩家对象,禁用导航代理,获取动画控制器。

  2. Update(): 在每帧更新中处理椭圆运动、朝向、接近原始位置的计算,以及判断玩家是否在视野范围内,根据情况设置移动方式和动画。

  3. OnTriggerEnter(Collider other): 当碰撞体进入触发器范围时,检查碰撞对象标签是否为玩家,如果是,设置攻击动画、播放音效,并对玩家造成伤害。

  4. OnTriggerExit(Collider other): 当碰撞体离开触发器范围时,检查碰撞对象标签是否为玩家,如果是,恢复奔跑动画。

脚本实现了僵尸的椭圆运动、追踪玩家、攻击玩家等基本逻辑。

using UnityEngine;
using UnityEngine.AI;public class zombie : MonoBehaviour
{public float distance = 5f;  // 移动距离public float speed = 1f;  // 移动速度public float eccentricity = 0.8f;  // 离心率public float rotationSpeed = 5f;  // 旋转速度public float scale = 1f;  // 移动倍率public float maxDistance = 5f; // 最大距离,当玩家距离小于该值时,设置isInSight为truepublic int damage = 30;private Vector3 startPosition;private float elapsedTime = 0f;private GameObject player;private bool isInSight ;public AudioClip Sound;public AudioClip bit;private Animator zombieanimator;private NavMeshAgent agent;public 	bool isEllipseMovementEnabled ; // 是否允许椭圆运动public  bool canmove ; // 是否允许椭圆运动public zombiehealth zomhealth;private void Start(){zomhealth=this.GetComponent<zombiehealth>();isInSight = false;isEllipseMovementEnabled = true;canmove = false;startPosition = transform.position;player = GameObject.FindGameObjectWithTag("Player");agent = GetComponent<NavMeshAgent>();agent.enabled = false;zombieanimator = GetComponent<Animator>();}private void Update(){if (isEllipseMovementEnabled&&!zomhealth.isDead){elapsedTime += Time.deltaTime * speed;// 根据参数计算椭圆轨迹上的位置float x = distance * Mathf.Cos(elapsedTime * scale);float z = distance * eccentricity * Mathf.Sin(elapsedTime * scale);Vector3 targetPosition = startPosition + new Vector3(x, 0f, z);// 平滑移动transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * speed);// 计算椭圆曲线上的切线向量float dx = -distance * scale * Mathf.Sin(elapsedTime * scale);float dz = distance * eccentricity * scale * Mathf.Cos(elapsedTime * scale);Vector3 tangent = new Vector3(dx, 0f, dz).normalized;// 计算平滑的旋转Quaternion targetRotation = Quaternion.LookRotation(tangent, Vector3.up);transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);// 检查是否接近原始位置float distanceToStart = Vector3.Distance(transform.position, startPosition);if (distanceToStart < 0.1f){// 返回原始位置,重置时间elapsedTime = 0f;}}if (!isInSight){float playerDistance = Vector3.Distance(transform.position, player.transform.position);if (playerDistance < maxDistance&&!zomhealth.isDead){isEllipseMovementEnabled = false; // 停止椭圆运动agent.enabled = true;canmove=true;isInSight = true;AudioSource.PlayClipAtPoint(Sound, transform.position);zombieanimator.SetBool("run",true);}}if(canmove){agent.SetDestination(player.gameObject.transform.position);}}void OnTriggerEnter(Collider other){if (other.gameObject.tag == "Player"){//	zombieanimator.SetBool("run",false);zombieanimator.SetBool("attack",true);AudioSource.PlayClipAtPoint(bit, transform.position);player.GetComponent<health>().TakeDamage(damage);}}void OnTriggerExit(Collider other){if (other.gameObject.tag == "Player"){zombieanimator.SetBool("run",true);zombieanimator.SetBool("attack",false);//zombieanimator.SetBool("attack",true);}}}

攻击逻辑

玩家开枪能对怪兽造成伤害,怪兽接触到玩家也能造成伤害,这脚本管理了僵尸的生命和死亡。

在这里插入图片描述

  1. 生命管理:

    • 跟踪最大生命值和当前生命值。
    • 接收伤害并减少生命值,如果已死亡则不再处理。
  2. 死亡效果:

    • 播放死亡声音,禁用移动和导航代理。
    • 设置死亡动画,1.5秒后销毁僵尸对象。
using UnityEngine;
using UnityEngine.AI;public class zombiehealth : MonoBehaviour
{public AudioClip deathSound;        // 死亡时播放的声音public float maxHealth = 100f;       // 僵尸的最大生命值public float currentHealth;          // 当前生命值public bool isDead;                  // 僵尸是否已死亡private NavMeshAgent agent;          // 导航网格代理组件的引用private Animator zombieanimator;     // 动画控制器组件的引用public zombie zom;                   // 僵尸脚本的引用private void Start(){zom = this.GetComponent<zombie>();                  // 获取僵尸脚本组件isDead = false;                                     // 初始化死亡状态为否currentHealth = maxHealth;                          // 设置初始生命值agent = GetComponent<NavMeshAgent>();               // 获取导航网格代理组件zombieanimator = GetComponent<Animator>();          // 获取动画控制器组件}public void TakeDamage(float damage){if (isDead){return;                                         // 如果已死亡,退出函数}currentHealth -= damage;                            // 减少生命值if (currentHealth <= 0f){Die();                                          // 如果生命值小于等于0,触发死亡}else{currentHealth -= damage;                        // 多余的减少生命值操作}}private void Die(){GetComponent<AudioSource>().PlayOneShot(deathSound); // 播放死亡声音isDead = true;                                      // 设置死亡状态为真zom.canmove = false;                                // 禁用僵尸移动agent.enabled = false;                              // 禁用导航网格代理zombieanimator.SetBool("dead", true);               // 在动画控制器中设置"dead"参数为真Destroy(this.gameObject, 1.5f);                     // 1.5秒后销毁僵尸对象}
}

终点传送门

在这里插入图片描述
这脚本实现了玩家离开游戏场景的效果:

  1. 声音与特效:

    • 定义退出声音(ex)和退出音乐(exitmusic)。
    • 获取玩家对象和主摄像机的色彩校正曲线特效(ColorCorrectionCurves)组件。
  2. 重置场景:

    • 在离开触发区域后,增加色彩饱和度,逐渐变暗场景。
    • 播放退出声音和音乐,延迟后重新加载当前场景。
  3. 场景重载:

    • 重新加载当前游戏场景。

这个脚本通过渐变色彩和播放音效,为玩家离开游戏场景创造了一个独特的效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityStandardAssets.ImageEffects;public class exit : MonoBehaviour
{public AudioClip ex;private GameObject player;public float delayTime=5f;private ColorCorrectionCurves colorCurves;private bool isend=false;public AudioClip exitmusic;// Start is called before the first frame updatevoid Start(){player = GameObject.FindGameObjectWithTag("Player");colorCurves = Camera.main.GetComponent<ColorCorrectionCurves>();}void Update(){if (isend){LevelReset();}}public void LevelReset(){//timer += Time.deltaTime;colorCurves.saturation += (Time.deltaTime);colorCurves.saturation = Mathf.Max(0, colorCurves.saturation);}void OnTriggerEnter(Collider other){if (other.gameObject == player){isend=true;colorCurves.enabled=true;AudioSource.PlayClipAtPoint(ex, transform.position);Invoke("playexitmusic", 1.5f);Invoke("ReloadCurrentScene", delayTime);}}private void ReloadCurrentScene(){SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);}private void playexitmusic(){AudioSource.PlayClipAtPoint(exitmusic, transform.position);}
}

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

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

相关文章

windows开机运行jar

windows开机自启动jar包&#xff1a; 一、保存bat批处理文件 echo off %1 mshta vbscript:CreateObject("WScript.Shell").Run("%~s0 ::",0,FALSE)(window.close)&&exit java -jar E:\projects\ruoyi-admin.jar > E:\server.log 2>&1 &…

RTC晶振两端要不要挂电容

发现GD32的RTC晶振两端需要挂电容&#xff0c;STM32的RTC晶振两端不需要挂电容。 STM32的RTC晶振两端&#xff0c;不需要挂电容&#xff0c;这样晶振启振很容易&#xff0c;挂大了&#xff0c;却难启动&#xff0c;且温度越低&#xff0c;启动越难。 有人说负载电容为6pF的晶振…

基于Java+Swing实现超级玛丽游戏

基于JavaSwing实现超级玛丽游戏 一、系统介绍二、功能展示三、其他系统 一、系统介绍 超级玛丽小游戏的JAVA程序&#xff0c;进入游戏后首先按空格键开始&#xff0c;利用方向键来控制的马里奥的移动&#xff0c;同时检测马里奥与场景中的障碍物和敌人的碰撞&#xff0c;并判断…

gitlab CI/CD 安装 gitlab runner

一、为什么需要安装gitlab runner &#xff1f; 极狐GitLab Runner 极狐GitLab Runner 是在流水线中运行作业的应用&#xff0c;与极狐GitLab CI/CD 配合运作。 说白了就是你部署的一个agent。 二、如何安装&#xff1f; 1.介绍通过helm部署github runner 2.helm添加仓库 h…

FTP Cadence中心库的创建与配置 (中)

在上节介绍的ftp服务器根目录下 一、创建一个文件夹 Cadence_lib&#xff0c;并把其设置为 共享。 共享给用户hw 和用户layout&#xff0c;并设置对应权限。 点击“共享”按钮。 二、在Cadence_lib文件夹下&#xff0c;添加如下文件夹&#xff0c;及文件。 其中 “00DataShee…

leetcode 738. 单调递增的数字

2023.8.4 这题用暴力法会超时&#xff0c;我就没试了&#xff0c;采用了个挺巧的方法&#xff0c;为了方便需要先将整数n转换为字符串的形式&#xff0c;然后从后向前遍历&#xff0c;当两个数字非递增时&#xff0c;将前一个数字--&#xff0c;后一个数字的位置记录在index中&…

2023年第三届工业自动化、机器人与控制工程国际会议 | IET独立出版 | EI检索

会议简介 Brief Introduction 2023年第三届工业自动化、机器人与控制工程国际会议&#xff08;IARCE 2023&#xff09; 会议时间&#xff1a;2023年10月27 -30日 召开地点&#xff1a;中国成都 大会官网&#xff1a;www.iarce.org 2023年第三届工业自动化、机器人与控制工程国际…

一文学透设计模式

设计模式是什么&#xff1f; 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案&#xff0c;代表了解决一些问题的最佳实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 说白了&#xff0c;设计模式对于软件开发人员来说就…

PtahDAO:全球首个DAO治理资产信托计划的金融平台

金融科技是当今世界最具创新力和影响力的领域之一&#xff0c;区块链技术作为金融科技的核心驱动力&#xff0c;正在颠覆传统的金融模式&#xff0c;为全球用户提供更加普惠、便捷、安全的金融服务。在这个变革的浪潮中&#xff0c;PtahDAO&#xff08;普塔道&#xff09;作为全…

Flutter iOS 集成使用 fluter boost

在 Flutter项目中集成完 flutter boost&#xff0c;并且已经使用了 flutter boost进行了路由管理&#xff0c;这时如果需要和iOS混合开发&#xff0c;这时就要到 原生端进行集成。 注意&#xff1a;之前建的项目必须是 Flutter module项目&#xff0c;并且原生项目和flutter m…

opencv-33 图像平滑处理-中值滤波cv2.medianBlur()

中值滤波是一种常见的图像处理滤波技术&#xff0c;用于去除图像中的噪声。它的原理是用一个滑动窗口&#xff08;也称为卷积核&#xff09;在图像上移动&#xff0c;对窗口中的像素值进行排序&#xff0c;然后用窗口中像素值的中值来替换中心像素的值。这样&#xff0c;中值滤…

Spring源码篇(九)自动配置扫描class的原理

文章目录 前言ClassLoader如何加载jar包里的class自动配置扫描class的原理spring中的加载方式源码总结 前言 spring是怎样通过ComponentScan&#xff0c;或者自动配置扫描到了依赖包里class的&#xff1f; ClassLoader 这里涉及到了class Loader的机制&#xff0c;有些复杂&…