Unity项目4 3D求生枪手如何为?

摘要:视频教程: https:www.bilibili.comvideoBV16F7zzqEJF?spm_id_from=333.788.videopod.sections&vd_source=25b783f5f9
视频教程: https://www.bilibili.com/video/BV16F7zzqEJF?spm_id_from=333.788.videopod.sections&vd_source=25b783f5f945c4507229e9dec657b5bb 1. 项目初始化 创建项目“ServivalShooter” 导入包“Survival Shooter.unitypackage” 导入环境、灯光预设,创建背景音乐物体,隐藏 Unity 自带灯光 2. 玩家导入、移动 导入玩家 给“Player”添加脚本“PlayerMovement” using UnityEngine; /// <summary> /// 控制玩家角色的移动 /// </summary> public class PlayerMovement : MonoBehaviour { /// <summary> /// 玩家移动速度 /// </summary> public float speed = 6f; /// <summary> /// 玩家刚体组件的引用 /// </summary> private Rigidbody rb; /// <summary> /// 在对象激活时获取刚体组件 /// </summary> private void Awake() { rb = GetComponent<Rigidbody>(); } /// <summary> /// 在固定时间间隔内处理物理移动 /// </summary> private void FixedUpdate() { // 获取水平和垂直方向的输入 float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); // 计算移动向量 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical); // 移动玩家到新的位置 rb.MovePosition(transform.position + movement * speed * Time.fixedDeltaTime); } } 3. 摄像机跟随 CameraFollow.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraFollow : MonoBehaviour { public float smoothing = 5f; private GameObject player; private Vector3 offset; private void Awake() { player = GameObject.FindGameObjectWithTag("Player"); } // Start is called before the first frame update void Start() { offset = transform.position - player.transform.position; } // Update is called once per frame void Update() { } private void FixedUpdate() { transform.position = Vector3.Lerp(transform.position, offset + player.transform.position, smoothing * Time.deltaTime); } } 4. 玩家跟随鼠标朝向 设置摄像机 设置地面 PlayerMovement.cs using UnityEngine; /// <summary> /// 控制玩家角色的移动和旋转 /// </summary> public class PlayerMovement : MonoBehaviour { /// <summary> /// 玩家移动速度(单位:米/秒) /// </summary> public float speed = 6f; /// <summary> /// 玩家刚体组件的引用,用于物理移动 /// </summary> private Rigidbody rb; /// <summary> /// 在脚本激活时初始化,获取刚体组件 /// </summary> private void Awake() { // 获取附加到同一游戏对象的Rigidbody组件 rb = GetComponent<Rigidbody>(); } /// <summary> /// 固定时间间隔调用的物理更新方法(默认每秒50次) /// </summary> private void FixedUpdate() { // 获取水平和垂直方向的输入(WASD或方向键) float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); // 处理移动和旋转 Move(h, v); // 根据输入移动玩家 Turning(); // 使玩家朝向鼠标位置 } /// <summary> /// 根据输入移动玩家 /// </summary> /// <param name="h">水平输入(-1到1)</param> /// <param name="v">垂直输入(-1到1)</param> void Move(float h, float v) { // 创建移动向量(忽略Y轴,因为是在平面上移动) Vector3 movement = new Vector3(h, 0.0f, v); // 使用刚体移动玩家位置,确保物理效果正确 // Time.fixedDeltaTime确保移动速度与帧率无关 rb.MovePosition(transform.position + movement * speed * Time.fixedDeltaTime); } /// <summary> /// 使玩家朝向鼠标所在位置 /// </summary> void Turning() { // 从主摄像机创建一条射线,方向指向鼠标屏幕位置 Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition); // 获取地面层的图层,用于射线检测 int floorLayer = LayerMask.GetMask("Ground"); RaycastHit floorHit; // 发射射线检测地面碰撞 bool isTouchFloor = Physics.Raycast(cameraRay, out floorHit, 100, floorLayer); if (isTouchFloor) { // 计算从玩家位置到鼠标点击位置的向量 Vector3 playerToMouse = floorHit.point - transform.position; // 确保玩家只在水平面上旋转(Y轴设为0) playerToMouse.y = 0; // 创建目标旋转(使玩家朝向鼠标位置) Quaternion targetRotation = Quaternion.LookRotation(playerToMouse); // 应用旋转到刚体 rb.MoveRotation(targetRotation); } } } 5. 静置、移动动画 增加动画控制器 设置静置动画、移动动画切换 增加参数 isWalking 静置切换为移动 移动切换为静置 编辑代码 PlayerMovement.cs 使用控制器改变 isWalking 参数值进而控制动画切换 using UnityEngine; /// <summary> /// 控制玩家角色的移动 /// </summary> public class PlayerMovement : MonoBehaviour { /// <summary> /// 玩家移动速度 /// </summary> public float speed = 6f; /// <summary> /// 玩家刚体组件的引用 /// </summary> private Rigidbody rb; /// <summary> /// 玩家动画组件的引用 /// </summary> private Animator anim; /// <summary> /// 在对象激活时获取刚体和动画组件 /// </summary> private void Awake() { rb = GetComponent<Rigidbody>(); anim = GetComponent<Animator>(); } /// <summary> /// 在固定时间间隔内处理物理移动、旋转和动画 /// </summary> private void FixedUpdate() { // 获取水平和垂直方向的输入 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); // 移动角色 Move(h, v); // 角色朝向鼠标指向位置 Turing(); // 播放行走动画 Animating(h, v); } /// <summary> /// 根据输入移动角色 /// </summary> /// <param name="h">水平输入</param> /// <param name="v">垂直输入</param> void Move(float h, float v) { // 计算移动向量 Vector3 movement = new Vector3(h, 0.0f, v); // 移动玩家到新的位置 rb.MovePosition(transform.position + movement * speed * Time.fixedDeltaTime); } /// <summary> /// 让角色朝向鼠标指向的地面位置 /// </summary> void Turing() { // 创建一条从主摄像机到鼠标位置的射线 Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition); // 获取地面层的LayerMask int floorLayer = LayerMask.GetMask("Ground"); RaycastHit floorHit; // 射线检测是否击中地面 bool isTouchFloor = Physics.Raycast(cameraRay, out floorHit, 100, floorLayer); if (isTouchFloor) { // 计算角色到鼠标点击点的方向向量 Vector3 v3 = floorHit.point - transform.position; v3.y = 0; // 保持水平方向 // 计算朝向该方向的旋转 Quaternion q = Quaternion.LookRotation(v3); // 旋转角色 rb.MoveRotation(q); } } /// <summary> /// 根据输入设置动画参数,控制行走动画 /// </summary> /// <param name="h">水平输入</param> /// <param name="v">垂直输入</param> void Animating(float h, float v) { // 判断是否有输入 bool isWalking = h != 0 || v != 0; // 设置动画参数 anim.SetBool("isWalking", isWalking); } } 6. 发射效果 声音 灯光 粒子效果 新建脚本挂载 MyPlayerShooting.cs using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 控制玩家射击行为的脚本 /// </summary> public class MyPlayerShooting : MonoBehaviour { /// <summary> /// 枪械音效组件 /// </summary> private AudioSource gunAudio; /// <summary> /// 距离上次射击经过的时间 /// </summary> private float shootTime = 0f; /// <summary> /// 射击间隔(秒) /// </summary> private float shootDelay = 0.15f; /// <summary> /// 枪口闪光持续时间比例 /// </summary> private float lightRate = 0.2f; /// <summary> /// 枪口闪光灯组件 /// </summary> private Light shootLight; /// <summary> /// 射击线渲染组件 /// </summary> private LineRenderer shootLine; /// <summary> /// 射击粒子特效组件 /// </summary> private ParticleSystem shootParticleSystem; /// <summary> /// 获取所需组件的引用 /// </summary> private void Awake() { gunAudio = GetComponent<AudioSource>(); shootLight = GetComponent<Light>(); shootLine = GetComponent<LineRenderer>(); shootParticleSystem = GetComponent<ParticleSystem>(); } // Start 在游戏开始前调用(此处未使用) void Start() { } /// <summary> /// 每帧更新,处理射击逻辑和特效显示 /// </summary> void Update() { // 累加时间 shootTime += Time.deltaTime; // 检测射击输入且满足射击间隔 if (Input.GetButton("Fire1") && shootTime >= shootDelay) { Shooting(); } // 控制枪口闪光和射线显示时间 if (shootTime >= shootDelay * lightRate) { shootLight.enabled = false; shootLine.enabled = false; } } /// <summary> /// 执行射击操作,包括音效、特效和射线显示 /// </summary> private void Shooting() { // 重置射击计时 shootTime = 0; // 播放射击音效 gunAudio.Play(); // 显示枪口闪光 shootLight.enabled = true; // 设置射线起点为枪口位置 shootLine.SetPosition(0, transform.position); // 设置射线终点为前方100单位 shootLine.SetPosition(1, transform.position + transform.forward * 100); // 显示射线 shootLine.enabled = true; // 播放粒子特效 shootParticleSystem.Play(); } } 7. 添加敌人 添加敌人模型,并添加相应组件 设置组件 安装“AI Navigation”包 设置并烘焙可导航区域 编写脚本 MyEnemyMovement.cs,实现敌人跟随玩家 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MyEnemyMovement : MonoBehaviour { private Transform player; private NavMeshAgent nav; private void Awake() { player = GameObject.FindGameObjectWithTag("Player").transform; nav = GetComponent<NavMeshAgent>(); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { nav.SetDestination(player.position); } } 添加并设置移动动画 8. 攻击敌人 8.1. 线条绘制 射线命中敌人,射线止于敌人处,未命中敌人则射出 100 个单位长度 设置敌人图层 编写设计代码 MyPlayerShooting.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class MyPlayerShooting : MonoBehaviour { private float shootDelay = 0.15f;//发射延迟 private float shootTime = 0f;//发射时间 private float shootRate = 0.2f;//发射关闭效果延迟比例 private Light shootLight;//发射灯光 private LineRenderer shootLine;//发射激光射线 private ParticleSystem shootParticle;//发射粒子效果 private AudioSource shootAduio;//枪声音 //开枪发射射线相关的变量 private Ray shootRay; private RaycastHit shootHit; private int shootMask; private void Awake() { shootLight = GetComponent<Light>(); shootLine = GetComponent<LineRenderer>(); shootParticle = GetComponent<ParticleSystem>(); shootAduio = GetComponent<AudioSource>(); shootMask = LayerMask.GetMask("Shootable"); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //上次发射后经过时间 shootTime = shootTime + Time.deltaTime; //如果点击鼠标左键且满足发射延迟则进行发射 if (Input.GetButton("Fire1") && shootTime >= shootDelay) { Shooting(); } //经过0.03秒则关闭发射效果 //if(shootTime >= shootDelay * shootRate) if(shootTime >= 0.03) { //关闭发射灯光效果 shootLight.enabled = false; //关闭发射射线效果 shootLine.enabled = false; } } void Shooting() { shootTime = 0f; //开启发射灯光效果 shootLight.enabled = true; //设置发射射线位置 shootLine.SetPosition(0, transform.position); shootLine.SetPosition(1, transform.position + 100 * transform.forward); //开启发射射线效果 shootLine.enabled = true; //播放发射粒子效果 shootParticle.Play(); //播放发射声音 shootAduio.Play(); //发射射线,检测是否击中敌人 shootRay.origin = transform.position; shootRay.direction = transform.forward; //如果击中敌人,则获取敌人脚本并调用其受伤方法 if (Physics.Raycast(shootRay, out shootHit, 100, shootMask)) { shootLine.SetPosition(1, shootHit.point); } else { //如果没有击中敌人,则设置射线终点为100米远 shootLine.SetPosition(1, transform.position + 100 * transform.forward); } } } 8.2. 声音、特效 敌人物体添加组件血量脚本、被攻击粒子特效、被攻击声音 编写脚本 MyEnemyHealth.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class MyEnemyHealth : MonoBehaviour { public int hp = 100; private ParticleSystem enemyParticleSystem; private AudioSource audioSource; private void Awake() { enemyParticleSystem = GetComponent<ParticleSystem>(); audioSource = GetComponent<AudioSource>(); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } public void TakeDamage(int damage) { hp -= damage;// 减少生命值 audioSource.Play();// 播放受伤音效 enemyParticleSystem.transform.position = transform.position;// 设置粒子效果位置 enemyParticleSystem.Play();// 播放粒子效果 } } 修改脚本 MyPlayerShooting.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class MyPlayerShooting : MonoBehaviour { private float shootDelay = 0.15f;//发射延迟 private float shootTime = 0f;//发射时间 private float shootRate = 0.2f;//发射关闭效果延迟比例 private Light shootLight;//发射灯光 private LineRenderer shootLine;//发射激光射线 private ParticleSystem shootParticle;//发射粒子效果 private AudioSource shootAduio;//枪声音 //开枪发射射线相关的变量 private Ray shootRay; private RaycastHit shootHit; private int shootMask; private void Awake() { shootLight = GetComponent<Light>(); shootLine = GetComponent<LineRenderer>(); shootParticle = GetComponent<ParticleSystem>(); shootAduio = GetComponent<AudioSource>(); shootMask = LayerMask.GetMask("Shootable"); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { //上次发射后经过时间 shootTime = shootTime + Time.deltaTime; //如果点击鼠标左键且满足发射延迟则进行发射 if (Input.GetButton("Fire1") && shootTime >= shootDelay) { Shooting(); } //经过0.03秒则关闭发射效果 //if(shootTime >= shootDelay * shootRate) if(shootTime >= 0.03) { //关闭发射灯光效果 shootLight.enabled = false; //关闭发射射线效果 shootLine.enabled = false; } } void Shooting() { shootTime = 0f; //开启发射灯光效果 shootLight.enabled = true; //设置发射射线位置 shootLine.SetPosition(0, transform.position); shootLine.SetPosition(1, transform.position + 100 * transform.forward); //开启发射射线效果 shootLine.enabled = true; //播放发射粒子效果 shootParticle.Play(); //播放发射声音 shootAduio.Play(); //发射射线,检测是否击中敌人 shootRay.origin = transform.position; shootRay.direction = transform.forward; //如果击中敌人,则获取敌人脚本并调用其受伤方法 if (Physics.Raycast(shootRay, out shootHit, 100, shootMask)) { shootLine.SetPosition(1, shootHit.point); MyEnemyHealth enemyHealth = shootHit.collider.GetComponent<MyEnemyHealth>(); enemyHealth.TakeDamage(10); //假设每次射击造成10点伤害 } else { //如果没有击中敌人,则设置射线终点为100米远 shootLine.SetPosition(1, transform.position + 100 * transform.forward); } } } 8.3. 受到伤害 收到玩家攻击,血量减少,血量为 0 时播放死亡动画,死亡后不能动 MyEnemyHealth.cs using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class MyEnemyHealth : MonoBehaviour { public int hp = 100; public AudioClip deathClip; private ParticleSystem enemyParticleSystem; private AudioSource audioSource; private Animator animator; private CapsuleCollider capsuleCollider; private void Awake() { enemyParticleSystem = GetComponent<ParticleSystem>(); audioSource = GetComponent<AudioSource>(); animator = GetComponent<Animator>(); capsuleCollider = GetComponent<CapsuleCollider>(); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } public void TakeDamage(int damage) { hp -= damage;// 减少生命值 if (hp <= 0) Death(); audioSource.Play();// 播放受伤音效 enemyParticleSystem.transform.position = transform.position;// 设置粒子效果位置 enemyParticleSystem.Play();// 播放粒子效果 } private void Death() { hp = 0; // 确保生命值不小于0 audioSource.clip = deathClip; audioSource.Play(); animator.SetTrigger("death"); capsuleCollider.enabled = false;// 禁用碰撞体 } public void StartSinking() { } } MyEnemyMovement.cs using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MyEnemyMovement : MonoBehaviour { private Transform player; private NavMeshAgent nav; private MyEnemyHealth enemyHealth; private void Awake() { player = GameObject.FindGameObjectWithTag("Player").transform; nav = GetComponent<NavMeshAgent>(); enemyHealth = GetComponent<MyEnemyHealth>(); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if(enemyHealth.hp <= 0) return; // 如果敌人已死亡,则不进行移动 nav.SetDestination(player.position); } } 8.4. 死亡 MyEnemyHealth.cs using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MyEnemyHealth : MonoBehaviour { public int hp = 100; public AudioClip deathClip; private ParticleSystem enemyParticleSystem; private AudioSource audioSource; private Animator animator; private CapsuleCollider capsuleCollider; private bool isSinking = false; private void Awake() { enemyParticleSystem = GetComponent<ParticleSystem>(); audioSource = GetComponent<AudioSource>(); animator = GetComponent<Animator>(); capsuleCollider = GetComponent<CapsuleCollider>(); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (isSinking) { transform.Translate(Vector3.down * Time.deltaTime * 2f);// 向下移动,模拟沉没效果 } } public void TakeDamage(int damage) { hp -= damage;// 减少生命值 if (hp <= 0) Death(); audioSource.Play();// 播放受伤音效 enemyParticleSystem.transform.position = transform.position;// 设置粒子效果位置 enemyParticleSystem.Play();// 播放粒子效果 } private void Death() { hp = 0; // 确保生命值不小于0 audioSource.clip = deathClip;// 设置死亡音效 audioSource.Play();// 播放死亡音效 animator.SetTrigger("death");// 设置死亡动画触发器 capsuleCollider.enabled = false;// 禁用胶囊碰撞器 GetComponent<Rigidbody>().isKinematic = true; // 设置刚体为静态 GetComponent<NavMeshAgent>().enabled = false; // 禁用导航网格代理 StartSinking(); } public void StartSinking() { isSinking = true; Destroy(gameObject, 2f); // 2秒后销毁敌人对象 } }