当前位置:   article > 正文

Unity 3D射箭游戏_预制体实例化射箭厂

预制体实例化射箭厂

一、实现功能

 地形:使用地形组件,上面有草、树;
 天空盒:使用天空盒,天空可随时间变化
 固定靶:有一个以上固定的靶标;
 运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
 射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击;
 驽弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
 游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
 碰撞与计分:在射击位,射中固定靶+10分,移动靶+20分。

二、代码介绍

2.1总体框架

导入了assets store的三个资源包,分别是天空盒、靶子、弓箭资源包,ArrowController用于射箭碰撞检测控制,BowController用于控制拉弓蓄力及射击,BowMovement用于控制弓箭(摄像头)移动及视角转动,SwitchSky用于控制天空盒随时间变化。

2.2天空盒变化

新建一个空物体挂载SwitchSky,配置mats数组为需要切换的天空盒材质,ChangeTime为天空盒切换时间,这里的三个天空盒Material均为资源包中下载的。

ChangeBox() 是一个公共方法。它在指定的时间间隔内被 InvokeRepeating() 方法重复调用。在这个方法中,它通过改变 currentIndexnextIndex 来更新当前和下一个天空盒的索引,并将 RenderSettings.skybox 设置为 mats 数组中的下一个天空盒材质。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SwitchSky : MonoBehaviour
  5. {
  6. public Material[] mats;
  7. private int currentIndex = 0;
  8. private int nextIndex = 1;
  9. public int changeTime; // 更换天空盒子的秒数
  10. void Start()
  11. {
  12. RenderSettings.skybox = mats[currentIndex];
  13. InvokeRepeating("ChangeBox", 0, changeTime);
  14. }
  15. public void ChangeBox()
  16. {
  17. currentIndex = nextIndex;
  18. nextIndex = (nextIndex + 1) % mats.Length;
  19. RenderSettings.skybox = mats[currentIndex]; // 更换天空盒材质
  20. }
  21. }

2.3地形配置

导入资源包后将任意Demo拖入对象列表即可获得对应天空盒和地形,根据游戏需要设置地形范围大小,环境中还有草、栅栏、树、石头等,这些物体的预制体中需要添加Collider,选择合适的Collider以保证玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍。

2.4固定靶、移动靶

直接拖入资源包中的靶子预制体即可使用,靶子数量根据个人游戏设计添加。

固定靶添加标签staticTarget,移动靶添加标签MovingTarget,地形添加标签Ground.

移动靶分为左右移动和上下移动两种,以上下移动的动画UpDownTarget为例,新建Animation——UpDownTarget,拉到需要做成移动靶的靶子对象上自动建立Animator组件,Animation通过编辑Curves或录制设计上下移动的动画。

2.5射击位

新建两个任意形状对象作为射击区域,这里选择建立两个圆形区域,白色圆形区域附近有两个移动靶,而绿色移动区域均为固定靶,两个射击区对象均添加标签Ground.后续代码实现仅在射击位附近可以拉弓射击。

2.6拉弓动画及拉弓射击控制

弓箭CrossBow的动画控制器CrossbowAni设置如下,Empty到EmptyPull的转移条件为isPulling = Ture,EmptyPull到Shoot的转移条件为Fire = True. 

射击控制由BowController实现,可以检测

1.Isinarea() 方法用于检查玩家是否处于特定区域内。

        检查当前玩家是否位于预设的两个区域中的任意一个内,通过计算玩家位置和两个区域点之间的距离来判断。

如果玩家在指定区域内:

  • 按下空格键时触发开始拉弓动作,设置 isPullingtrue
  • 持续按住空格键记录按键时间,并映射为拉弓的强度 pullStrength
  • 松开空格键时结束拉弓动作。
  • 点击鼠标左键触发射箭动作,通过 Fire() 方法发射箭矢,并根据 pullStrength 来设置箭矢的发射力度。

2.Fire() 方法:

  • firePoint 位置实例化箭矢预制体,并设置箭矢的初始位置和方向。
  • 给箭矢添加了 Rigidbody 组件,并根据 holdForce(拉弓力度)施加力,使箭矢飞行
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using TMPro;
  4. using UnityEngine;
  5. public class BowController : MonoBehaviour
  6. {
  7. Animator animator;
  8. bool isPulling = false;
  9. float pullDuration = 0f;
  10. public float maxPullDuration = 2f; // 最长拉弓时间
  11. public float arrowSpeed = 1f; // 箭矢速度
  12. public GameObject arrowObj;
  13. float pullStrength;
  14. public Transform firePoint;
  15. public Camera maincam;
  16. void Start()
  17. {
  18. // 获取弓上的Animator组件
  19. animator = GetComponent<Animator>();
  20. }
  21. void Update()
  22. {
  23. if (Isinarea())
  24. {
  25. Debug.Log("在区域内");
  26. // 当按下空格键时触发状态切换
  27. if (Input.GetKeyDown(KeyCode.Space))
  28. {
  29. pullDuration = 0;
  30. animator.SetBool("Fire", false);
  31. animator.SetFloat("Power", 0);
  32. isPulling = true;
  33. animator.SetBool("isPulling", true);
  34. //animator.SetBool("Holding", false);
  35. }
  36. // 持续按下空格键时记录按下时间,决定拉弓的强度
  37. if (isPulling)
  38. {
  39. pullDuration += Time.deltaTime;
  40. // 将按下的时间映射到0到1的范围,作为拉弓强度的参数
  41. pullStrength = Mathf.Clamp01(pullDuration / maxPullDuration);
  42. animator.SetFloat("Power", pullStrength);
  43. }
  44. if (Input.GetKeyUp(KeyCode.Space))
  45. {
  46. isPulling = false;
  47. //animator.SetBool("Holding", true);
  48. animator.SetBool("isPulling", false);
  49. }
  50. // 当点击鼠标左键时触发射击
  51. if (Input.GetMouseButtonDown(0))
  52. {
  53. animator.SetBool("Fire", true);
  54. animator.SetFloat("Power", 0);
  55. // animator.SetBool("Holding", false);
  56. Fire(pullStrength);
  57. }
  58. }
  59. }
  60. public void Fire(float holdForce)
  61. {
  62. GameObject arrow = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/Arrow"));
  63. arrow.AddComponent<ArrowController>();
  64. ArrowController arrowController = arrow.GetComponent<ArrowController>();
  65. arrowController.cam = maincam;
  66. arrow.transform.position = firePoint.transform.position;
  67. arrow.transform.rotation = Quaternion.LookRotation(this.transform.forward);
  68. Rigidbody rd = arrow.GetComponent<Rigidbody>();
  69. if (rd != null)
  70. {
  71. rd.AddForce(this.transform.forward * 60 * holdForce);
  72. Debug.Log(arrow.transform.rotation);
  73. }
  74. }
  75. bool Isinarea()
  76. {
  77. Vector3 currentPosition = transform.position;
  78. Vector3 area1 = new Vector3(-99.12f, 14.86f, 153.65f);
  79. Vector3 area2 = new Vector3(-93.8506f, 15.3659f, 83.4803f);
  80. float distance1 = Vector3.Distance(currentPosition, area1); // 计算目标点和玩家位置之间的距离
  81. float distance2 = Vector3.Distance(currentPosition, area2);
  82. float radius = 10f; // 圆的半径
  83. if (distance1 <= radius || distance2 <= radius)
  84. {
  85. return true;
  86. }
  87. else
  88. {
  89. return false;
  90. }
  91. }
  92. }

2.7游走控制

1.变量定义:

  • public Transform tourCamera;:用于存储游览相机的 Transform 组件。
  • moveSpeed, rotateSpeed, shiftRate:控制相机移动和旋转的速度参数。
  • minDistance:控制相机与不可穿透表面的最小距离。

2.Update() 方法:

  • GetDirection():检测玩家输入,并获取相机移动的方向。
  • 通过 Physics.Raycast() 方法检查是否离不可穿透表面过近,如果是,消除垂直于不可穿透表面的运动速度分量,以防止相机穿透表面。
  • 限制相机的最大和最小高度。
  • 使用 Translate() 方法移动相机。

3.GetDirection() 方法:

  • 根据玩家输入的键盘按键来设置相机在各个方向上的移动速度,实现弓箭在地图上游走。
  • 通过 Input.GetMouseButton(1) 检测鼠标右键是否按下,如果按下,根据鼠标移动来控制相机的旋转。

4.V3RotateAround() 方法:

  • 用于计算一个 Vector3 绕旋转中心旋转指定角度后所得到的向量。
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class BowMovement : MonoBehaviour
  5. {
  6. // 在场景中游览的相机(不要给相机加碰撞器!)
  7. public Transform tourCamera;
  8. #region 相机移动参数
  9. public float moveSpeed = 20.0f;
  10. public float rotateSpeed = 150.0f;
  11. public float shiftRate = 2.0f;// 按住Shift加速
  12. public float minDistance = 0.5f;// 相机离不可穿过的表面的最小距离(小于等于0时可穿透任何表面)
  13. #endregion
  14. #region 运动速度和其每个方向的速度分量
  15. private Vector3 direction = Vector3.zero;
  16. private Vector3 speedForward;
  17. private Vector3 speedBack;
  18. private Vector3 speedLeft;
  19. private Vector3 speedRight;
  20. private Vector3 speedUp;
  21. private Vector3 speedDown;
  22. #endregion
  23. void Start()
  24. {
  25. if (tourCamera == null) tourCamera = gameObject.transform;
  26. }
  27. void Update()
  28. {
  29. GetDirection();
  30. // 检测是否离不可穿透表面过近ss
  31. RaycastHit hit;
  32. while (Physics.Raycast(tourCamera.position, direction, out hit, minDistance))
  33. {
  34. // 消去垂直于不可穿透表面的运动速度分量
  35. float angel = Vector3.Angle(direction, hit.normal);
  36. float magnitude = Vector3.Magnitude(direction) * Mathf.Cos(Mathf.Deg2Rad * (180 - angel));
  37. direction += hit.normal * magnitude;
  38. }
  39. if (tourCamera.localPosition.y > 25.0f)
  40. {
  41. tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 25.0f, tourCamera.localPosition.z);
  42. }
  43. if (tourCamera.localPosition.y < 12.0f)
  44. {
  45. tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 12.0f, tourCamera.localPosition.z);
  46. }
  47. tourCamera.Translate(direction * moveSpeed * Time.deltaTime, Space.World);
  48. }
  49. private void GetDirection()
  50. {
  51. #region 加速移动
  52. if (Input.GetKeyDown(KeyCode.LeftShift)) moveSpeed *= shiftRate;
  53. if (Input.GetKeyUp(KeyCode.LeftShift)) moveSpeed /= shiftRate;
  54. #endregion
  55. #region 键盘移动
  56. // 复位
  57. speedForward = Vector3.zero;
  58. speedBack = Vector3.zero;
  59. speedLeft = Vector3.zero;
  60. speedRight = Vector3.zero;
  61. speedUp = Vector3.zero;
  62. speedDown = Vector3.zero;
  63. // 获取按键输入
  64. if (Input.GetKey(KeyCode.W)) speedForward = tourCamera.forward;
  65. if (Input.GetKey(KeyCode.S)) speedBack = -tourCamera.forward;
  66. if (Input.GetKey(KeyCode.A)) speedLeft = -tourCamera.right;
  67. if (Input.GetKey(KeyCode.D)) speedRight = tourCamera.right;
  68. if (Input.GetKey(KeyCode.E)) speedUp = Vector3.up;
  69. if (Input.GetKey(KeyCode.Q)) speedDown = Vector3.down;
  70. direction = speedForward + speedBack + speedLeft + speedRight + speedUp + speedDown;
  71. #endregion
  72. #region 鼠标旋转
  73. if (Input.GetMouseButton(1))
  74. {
  75. // 转相机朝向
  76. tourCamera.RotateAround(tourCamera.position, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
  77. tourCamera.RotateAround(tourCamera.position, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
  78. // 转运动速度方向
  79. direction = V3RotateAround(direction, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
  80. direction = V3RotateAround(direction, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
  81. }
  82. #endregion
  83. }
  84. /// <summary>
  85. /// 计算一个Vector3绕旋转中心旋转指定角度后所得到的向量。
  86. /// </summary>
  87. /// <param name="source">旋转前的源Vector3</param>
  88. /// <param name="axis">旋转轴</param>
  89. /// <param name="angle">旋转角度</param>
  90. /// <returns>旋转后得到的新Vector3</returns>
  91. public Vector3 V3RotateAround(Vector3 source, Vector3 axis, float angle)
  92. {
  93. Quaternion q = Quaternion.AngleAxis(angle, axis);// 旋转系数
  94. return q * source;// 返回目标点
  95. }
  96. }

2.8碰撞与计分

碰撞逻辑在ArrowController实现,分数改变传递到ui.score变量显示。

1.变量定义:

  • private Rigidbody rb;:箭矢的刚体组件,用于控制箭矢的物理行为。
  • public float Score = 0;:箭矢的得分,可能是在箭矢击中目标时增加的分数。
  • public Camera cam;:用于获取相机以访问 Main 脚本。
  • private Main ui;:对游戏界面的引用,用于更新得分。

2.Start() 方法:

  • 获取箭矢的刚体组件。
  • 获取相机上的 Main 脚本。

3.OnCollisionEnter(Collision collision) 方法:

  • 当箭矢与其他物体碰撞时触发。
  • 如果是 "staticTarget" 标签,分数增加 10 分,箭矢销毁。
  • 如果是 "MovingTarget" 标签,分数增加 20 分,箭矢销毁。
  • 如果是 "Ground" 标签,箭矢直接销毁。
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.SocialPlatforms.Impl;
  5. public class ArrowController : MonoBehaviour
  6. {
  7. // Start is called before the first frame update
  8. private Rigidbody rb;
  9. public float Score = 0;
  10. public Camera cam;
  11. private Main ui;
  12. void Start()
  13. {
  14. rb = GetComponent<Rigidbody>();
  15. ui = cam.GetComponent<Main>();
  16. }
  17. // Update is called once per frame
  18. void Update()
  19. {
  20. }
  21. private void OnCollisionEnter(Collision collision)
  22. {
  23. if (collision.gameObject.CompareTag("staticTarget"))
  24. {
  25. rb.velocity = Vector3.zero;
  26. rb.angularVelocity = Vector3.zero;
  27. ui.Score += 10;
  28. Destroy(gameObject);
  29. }
  30. else if (collision.gameObject.CompareTag("MovingTarget"))
  31. {
  32. rb.velocity = Vector3.zero;
  33. rb.angularVelocity = Vector3.zero;
  34. ui.Score += 20;
  35. Destroy(gameObject);
  36. }
  37. else if (collision.gameObject.CompareTag("Ground"))
  38. {
  39. rb.velocity = Vector3.zero;
  40. rb.angularVelocity = Vector3.zero;
  41. Destroy(gameObject);
  42. }
  43. }
  44. }

Main实现显示分数和游戏提示,挂载在主摄像机Cam_fps(1)上。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Main : MonoBehaviour
  5. {
  6. // Start is called before the first frame update
  7. public float Score = 0;
  8. void Start()
  9. {
  10. }
  11. // Update is called once per frame
  12. void Update()
  13. {
  14. }
  15. private void OnGUI()
  16. {
  17. GUIStyle style = new GUIStyle();
  18. style.fontSize = 15;
  19. style.normal.textColor = Color.white;
  20. // 定义游戏介绍文本内容
  21. string introText = "天空10s自动切换\nwasd --移动弓弩\nSpace --蓄力拉弓\n鼠标左键 --射箭\n鼠标右键 --调整角度";
  22. // 在屏幕左上角绘制游戏介绍文本
  23. GUI.Label(new Rect(10, 10, 300, 100), introText, style);
  24. style.fontSize = 24;
  25. style.normal.textColor = Color.white;
  26. // 在屏幕右上角显示分数
  27. GUI.Label(new Rect(Screen.width - 150, 20, 150, 30), "Score: " + Score, style);
  28. }
  29. }

三、游戏演示

参考博客:unity射箭小游戏-CSDN博客

演示视频:Unity3D射箭小游戏_哔哩哔哩_bilibili

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/716730
推荐阅读
相关标签
  

闽ICP备14008679号