当前位置:   article > 正文

使用unity实现第一人称射箭小游戏

使用unity实现第一人称射箭小游戏

前言

这是中山大学软件工程学院2023年3D游戏编程与设计的第七次作业,也欢迎大家学习参考交流
github个人主页: innitinnit (github.com)

游戏规则以及操作

        这是一个第一人称的射击类小游戏。

        玩家需要通过键盘以及鼠标来控制角色射击目标,在有限的射击次数中达到目标分数则获得游戏胜利。游戏中的射击目标分为动态与静态两种,并且规定只有在变换区域后可以进行射击。单次填装后,射击次数为10次。

        -移动(wasd)

        -蓄力(长按鼠标左键进行拉弓,松开左键进行停止蓄力;根据长按时间长短来决定发射弓箭力度)

        -射击(点击鼠标右键)

        -进入静态靶区域(数字键1)

        -进入动态靶区域(数字2)

        -返回初始位置(字母B)

        -切换天空样式(数字键4)

游戏截图

游玩过程演示

1

逻辑与代码

0.0使用资源包及其下载地址

-Low-Poly Simple Nature Pack:Low-Poly Simple Nature Pack | 3D 风景 | Unity Asset Store

-Fantasy Skybox FREE:Fantasy Skybox FREE | 2D 天空 | Unity Asset Store

-十字弩:Classical Crossbow | 3D 武器 | Unity Asset Store

1.0 地形

2.0 天空盒

        玩家在游玩过程中可通过按下按键4可进行天空盒的切换。

2.0.1 代码

        编写一个SkyBoxController,挂载到场景中的空对象Sky Box中:

  1. public class SkyBoxController : MonoBehaviour
  2. {
  3. public Material[] skyboxes; // 存储多个天空盒材质
  4. private int currentSkyboxIndex = 0; // 当前天空盒的索引
  5. // Update is called once per frame
  6. void Update()
  7. {
  8. // 通过按键4触发天空盒切换
  9. if (Input.GetKeyDown(KeyCode.Alpha4))
  10. {
  11. ChangeSkybox();
  12. }
  13. }
  14. void ChangeSkybox()
  15. {
  16. currentSkyboxIndex = (currentSkyboxIndex + 1) % skyboxes.Length;
  17. RenderSettings.skybox = skyboxes[currentSkyboxIndex];
  18. }
  19. }

        设置参数,此处使用的是

3.0 射击靶

统一使用以下代码,完成箭矢与射击靶的互动:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. // 靶子的控制器
  5. public class TargetController : MonoBehaviour // 用于处理靶子的碰撞
  6. {
  7. public int RingScore = 0; //当前靶子或环的分值
  8. public ScoreRecorder sc_recorder;
  9. void Start(){
  10. sc_recorder = Singleton<ScoreRecorder>.Instance;
  11. }
  12. void Update(){
  13. }
  14. void OnCollisionEnter(Collision collision) // 检测碰撞
  15. {
  16. Transform arrow = collision.gameObject.transform; // 得到箭身
  17. if(arrow == null) return;
  18. if(arrow.tag == "arrow"){
  19. //将箭的速度设为0
  20. arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);
  21. //使用运动学运动控制
  22. arrow.GetComponent<Rigidbody>().isKinematic = true;
  23. arrow.transform.rotation = Quaternion.Euler(0, 0, 0); // 使箭的旋转角度为0
  24. arrow.transform.parent = this.transform; // 将箭和靶子绑定
  25. sc_recorder.RecordScore(RingScore); //计分
  26. arrow.tag = "onTarget"; //标记箭为中靶
  27. }
  28. }
  29. }
3.1 固定靶

见5.0中的代码

3.2 运动靶

同上

4.0 射击位

使用transport()函数,传送后可以进行射击。

5.0 驽弓动画

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class ArrowController : MonoBehaviour, ISceneController, IUserAction
  5. {
  6. public CCShootManager arrow_manager; //箭的动作管理者
  7. public ArrowFactory factory; //箭的工厂
  8. public GameObject main_camera; // 主相机
  9. public ScoreRecorder recorder; // 计分对象
  10. public GameObject bow; // 弓弩
  11. public GameObject target1, target2, target3, target4; // 四种不同的靶子和一个巨型靶子
  12. public GameObject arrow; // 当前射出的箭
  13. public string message = ""; // 用于显示的信息
  14. private int arrow_num = 0; // 装载的箭的数量
  15. public Animator ani; // 动画控制器
  16. private List<GameObject> arrows = new List<GameObject>(); // 箭的队列
  17. private List<GameObject> flyed = new List<GameObject>(); // 飞出去的箭的队列
  18. public float longPressDuration = 2.0f; // 设置长按持续时间
  19. private bool isLongPressing = false; // 是否正在长按
  20. private float pressTime = 0f; // 长按的时间
  21. public int state = 0; // 0-普通状态,1-拉弓状态,2-蓄力状态
  22. public float power = 0f; // 拉弓的力度
  23. //加载箭和靶子
  24. public void LoadResources()
  25. {
  26. main_camera = transform.GetChild(5).gameObject; // 获取摄像机
  27. bow = this.gameObject;
  28. // 加载靶子
  29. for(int i = 0; i < 3; ++i) // 3 * 2 = 12个靶子
  30. {
  31. target1 = LoadTarget("target", 10 + i, new Vector3(100+20*i, 20, 140+5*i)); // 静态小靶子
  32. target2 = LoadTarget("big_target", 5-i, new Vector3(100+20*i, 10, 140-5*i)); // 静态大靶子
  33. }
  34. target3 = LoadTarget("target_move", 15, new Vector3(0,0,0)); // 慢速移动靶子, 使用动画,自带位置
  35. target4 = LoadTarget("target_quick", 20, new Vector3(0,0,0)); // 快速移动靶子,使用动画,自带位置
  36. }
  37. GameObject LoadTarget(string name, int score, Vector3 pos) // 加载靶子
  38. {
  39. GameObject target = GameObject.Instantiate(Resources.Load("Prefabs/"+name, typeof(GameObject))) as GameObject; // 从预制中获得靶子
  40. target.transform.position = pos;
  41. target.AddComponent<TargetController>(); // 给靶子添加TargetController组件
  42. target.GetComponent<TargetController>().RingScore = score;
  43. return target;
  44. }
  45. //进行资源加载
  46. void Start()
  47. {
  48. arrow = null;
  49. SSDirector director = SSDirector.getInstance();
  50. director.currentSceneController = this;
  51. director.currentSceneController.LoadResources();
  52. gameObject.AddComponent<ArrowFactory>();
  53. gameObject.AddComponent<ScoreRecorder>();
  54. gameObject.AddComponent<UserGUI>();
  55. gameObject.AddComponent<BowController>();
  56. factory = Singleton<ArrowFactory>.Instance;
  57. recorder = Singleton<ScoreRecorder>.Instance;
  58. arrow_manager = this.gameObject.AddComponent<CCShootManager>() as CCShootManager;
  59. ani = GetComponent<Animator>();
  60. }
  61. void Update()
  62. {
  63. Shoot(); // 射击
  64. HitGround(); // 检测箭是否射中地面
  65. for(int i = 0; i < flyed.Count; ++i) // 用于维护已经射出去的箭的队列
  66. {
  67. GameObject t_arrow = flyed[i];
  68. if(flyed.Count > 10 || t_arrow.transform.position.y < -10) // 箭的数量大于10或者箭的位置低于-10
  69. { // 删除箭
  70. flyed.RemoveAt(i);
  71. factory.RecycleArrow(t_arrow);
  72. }
  73. }
  74. if(Input.GetKeyDown(KeyCode.R)) // 按R换弹
  75. { //从工厂得到箭
  76. LoadArrow();
  77. message = "弩箭已填充完毕";
  78. }
  79. }
  80. public void Shoot() // 射击
  81. {
  82. if(arrows.Count > 0){ // 确保有箭
  83. if(!gameObject.GetComponent<BowController>().canshoot && (Input.GetMouseButtonDown(0) || Input.GetButtonDown("Fire2"))){
  84. message = "请按1、2到指定地点射击\n按B返回";
  85. }
  86. else{
  87. aniShoot();
  88. }
  89. }
  90. else{
  91. message = "请按R键以装载弓箭";
  92. }
  93. }
  94. public void aniShoot(){ // 射击的动画
  95. if (Input.GetMouseButtonDown(0) && state==0) // 监测鼠标左键按下
  96. {
  97. message = "";
  98. transform.GetChild(4).gameObject.SetActive(true); // 设置动作中的箭可见
  99. isLongPressing = true;
  100. ani.SetTrigger("pull");
  101. pressTime = Time.time;
  102. state = 1;
  103. }
  104. if (Input.GetMouseButtonUp(0) && state==1) // 监测鼠标左键抬起
  105. {
  106. isLongPressing = false;
  107. float duration = Time.time - pressTime;
  108. if (duration < longPressDuration){ // 执行普通点击操作
  109. power = duration/2;
  110. }
  111. else{ // 拉满了
  112. power = 1.0f;
  113. }
  114. ani.SetFloat("power", power);
  115. ani.SetTrigger("hold");
  116. state = 2;
  117. }
  118. if (isLongPressing && Time.time - pressTime > longPressDuration) // 长按但是未抬起,且持续时间超过设定值
  119. {
  120. // 长按操作
  121. isLongPressing = false;
  122. power = 1.0f;
  123. ani.SetFloat("power", power);
  124. ani.SetTrigger("hold");
  125. }
  126. if (Input.GetButtonDown("Fire2") && state==2){ // 鼠标右键,攻击2
  127. transform.GetChild(4).gameObject.SetActive(false); // 设置动作中的箭不可见
  128. ani.SetTrigger("shoot");
  129. arrow = arrows[0];
  130. arrow.SetActive(true);
  131. flyed.Add(arrow);
  132. arrows.RemoveAt(0);
  133. arrow_manager.ArrowShoot(arrow, main_camera.transform.forward,power);
  134. ani.SetFloat("power", 1.0f); // 恢复力度
  135. arrow_num -= 1;
  136. state = 0;
  137. }
  138. }
  139. public void LoadArrow(){ // 获得10支箭
  140. arrow_num = 10;
  141. while(arrows.Count!=0){ // 清空队列
  142. factory.RecycleArrow(arrows[0]);
  143. arrows.RemoveAt(0);
  144. }
  145. for(int i=0;i<10;i++){
  146. GameObject arrow = factory.GetArrow();
  147. arrows.Add(arrow);
  148. }
  149. }
  150. public void HitGround(){ // 检测箭是否射中地面
  151. RaycastHit hit;
  152. if (arrow!=null && Physics.Raycast(arrow.transform.position, Vector3.down, out hit, 2f))
  153. {
  154. // 如果射线与地面相交
  155. if (hit.collider.gameObject.name == "Terrain")
  156. {
  157. arrow.tag = "ground";
  158. //将箭的速度设为0
  159. arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);
  160. //使用运动学运动控制
  161. arrow.GetComponent<Rigidbody>().isKinematic = true;
  162. }
  163. }
  164. }
  165. //返回当前分数
  166. public int GetScore(){
  167. return recorder.score;
  168. }
  169. //得到剩余的箭的数量
  170. public int GetArrowNum(){
  171. return arrow_num;
  172. }
  173. // 显示的信息
  174. public string GetMessage(){
  175. return message;
  176. }
  177. }

6.0 游走

        此处使用一个胶囊状3d物体作为玩家控制角色,因为是第一人称视角所以玩家无法看到自己的角色。

        这一部分中,我们需要完成:

-角色视角随鼠标移动(CameraController.cs)

-使用键盘可以操控角色移动(PlayerController.cs)

6.0.1 代码

CameraController.cs(挂载至Camera):

  1. public class CameraController : MonoBehaviour
  2. {
  3. public Transform player;//获取玩家旋转的值来进行视角旋转
  4. private float mouseX, mouseY;//获取鼠标移动的值
  5. public float mouseSensitivity;//鼠标灵敏度
  6. public float xRotation;
  7. private void Update()
  8. {
  9. mouseX=Input.GetAxis("Mouse X")*mouseSensitivity*Time.deltaTime;
  10. mouseY=Input.GetAxis("Mouse Y")*mouseSensitivity*Time.deltaTime;
  11. xRotation -= mouseY;
  12. xRotation = Mathf.Clamp(xRotation, -70, 70);//使玩家无法无限制地上下旋转视角
  13. player.Rotate(Vector3.up * mouseX);
  14. transform.localRotation=Quaternion.Euler(xRotation, 0,0);
  15. }
  16. }

PlayerController.cs(挂载至Player):

  1. public class PlayerController : MonoBehaviour
  2. {
  3. private CharacterController cc;
  4. public float moveSpeed;
  5. public float jumpSpeed;
  6. private float horizontalMove, verticalMove;
  7. private Vector3 dir;
  8. public float gravity;
  9. private Vector3 velocity;
  10. //用于检测玩家是否在地面上
  11. public Transform groundCheck;
  12. public float checkRadius;
  13. public LayerMask groundLayer;
  14. public bool isGround;
  15. private void Start()
  16. {
  17. cc=GetComponent<CharacterController>();
  18. }
  19. private void Update()
  20. {
  21. isGround=Physics.CheckSphere(groundCheck.position,checkRadius,groundLayer);//玩家是否碰撞地面
  22. if (isGround && velocity.y < 0)
  23. {
  24. velocity.y = -2f;
  25. }
  26. horizontalMove = Input.GetAxis("Horizontal")*moveSpeed;
  27. verticalMove = Input.GetAxis("Vertical") * moveSpeed;
  28. dir=transform.forward*verticalMove+transform.right*horizontalMove;
  29. cc.Move(dir*Time.deltaTime);
  30. //跳跃
  31. if(Input.GetButtonDown("Jump")&& isGround)
  32. {
  33. velocity.y = jumpSpeed;
  34. }
  35. velocity.y-=gravity*Time.deltaTime;
  36. cc.Move(velocity*Time.deltaTime);
  37. }
  38. }

设置参数:        

        Player:

        使用Character Controller组件,使角色具有碰撞体属性。

7.0 碰撞与计分

7.1碰撞

设置collider即可完成。

7.2计分

见5.0部分代码(record相关部分)

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

闽ICP备14008679号