当前位置:   article > 正文

3D游戏设计第七次作业——Shoot Target(打靶)

3D游戏设计第七次作业——Shoot Target(打靶)

展示链接:打靶子(ShootTarget)_网络游戏热门视频

源码链接:传送门

游戏玩法

玩家将操控十字弩在游戏场景中移动,如果移动到了指定场景,玩家可以通过点击鼠标左键蓄力射出弓箭,当弓箭射中靶子之后,玩家可以获得相应的分数。值得注意的是,每个指定位置可以使用的弓箭的数量是有限的,当箭被射完后,玩家将无法再射箭。按R键可以重置游戏。

游戏画面

游戏场景的构建

地形构建

在层级选项卡中点击鼠标右键,选中3D对象,找到并创建地形

选中创建的地形,在检查器中找到Terrain属性,将这个位置调整为Raise or Lower Terrain

然后使用笔刷在画面中点点画画,以创造不同的地形

然后在检查器中找到Terrain属性,将这个位置调整为Paint Texture,选择编辑地形层,然后导入图片材料

在绘制好的地形上的不同位置涂上不同的图片。

生成树

选中创建的地形,在检查器中找到Terrain属性,选择箭头所指的绘制树,再点击编辑树以导入树的预制体,然后在地形上画出预制体即可。

生成草

和生成树一样。

制作靶子

创建一个空对象,然后在空对象中创建五个圆柱体,5个圆柱体数据如下:

记得给每个圆柱体加上Mesh Collider的碰撞组件。

游戏代码实现

首先我们创建了一个随时间改变天空盒的脚本ChangeSky.cs

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class ChangeSky : MonoBehaviour
  5. {
  6. public Material[] skyboxMaterials;
  7. int index;
  8. // Start is called before the first frame update
  9. void Start()
  10. {
  11. index = skyboxMaterials.Length - 1;
  12. StartCoroutine(ChangeSkyBox());
  13. }
  14. IEnumerator ChangeSkyBox()
  15. {
  16. while (true)
  17. {
  18. // 随机选择一个天空盒子材质
  19. index = (index + 1) % skyboxMaterials.Length;
  20. RenderSettings.skybox = skyboxMaterials[index];
  21. // 等待一段时间后再切换天空盒子
  22. yield return new WaitForSeconds(10);
  23. }
  24. }
  25. }

生成靶子和射击区域TargetGenerator.cs

  1. using System.Collections.Generic;
  2. using Unity.VisualScripting;
  3. using UnityEngine;
  4. public class TargetGenerator : MonoBehaviour
  5. {
  6. public GameObject targetPrefab; // 目标对象的预制体
  7. public GameObject shootAreaPrefab; // 设计区域预制体
  8. public int numberOfTargets = 6; // 要生成的目标数量
  9. public int ArrowCount = 6;
  10. public List<int> arrows = new List<int>();
  11. void Start()
  12. {
  13. GenerateTargets();
  14. }
  15. void GenerateTargets()
  16. {
  17. for (int i = 0; i < numberOfTargets; i++)
  18. {
  19. // 随机生成目标的位置
  20. Vector3 randomPosition;
  21. randomPosition = new Vector3((i + 1) * 10 + Random.Range(-2.5f, 2.5f) - 60, Random.Range(2f, 2.5f), Random.Range(-17f, 3f));
  22. // 使用预制体在随机位置生成目标
  23. GameObject target = Instantiate(targetPrefab, randomPosition, Quaternion.identity);
  24. GameObject shootArea = Instantiate(shootAreaPrefab, new Vector3(randomPosition.x, 0, randomPosition.z - 15f), Quaternion.identity);
  25. // 设置目标的名称
  26. target.name = "Target" + (i + 1);
  27. shootArea.name = "ShootArea" + (i + 1);
  28. if(i % 2 != 0)
  29. {
  30. // 添加 Animator 组件并设置动画控制器
  31. Animator targetAnimator = target.GetComponent<Animator>();
  32. if (targetAnimator != null)
  33. {
  34. // 这里假设你有一个名为 "TargetController" 的动画控制器
  35. if(i == 1)
  36. {
  37. targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target");
  38. shootArea.transform.position = new Vector3(0, 0, -14f);
  39. }else if(i == 3)
  40. {
  41. targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target1");
  42. shootArea.transform.position = new Vector3(25f, 0, -7f);
  43. }
  44. else
  45. {
  46. targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target2");
  47. shootArea.transform.position = new Vector3(10f, 0, -16f);
  48. }
  49. }
  50. }
  51. arrows.Add(ArrowCount);
  52. }
  53. }
  54. // 调用这个函数来删除指定标签的游戏对象
  55. private void DeleteObjectsWithLabel(string label)
  56. {
  57. // 获取场景中所有的游戏对象
  58. GameObject[] allObjects = GameObject.FindObjectsOfType<GameObject>();
  59. // 遍历游戏对象数组
  60. foreach (GameObject obj in allObjects)
  61. {
  62. // 检查游戏对象是否有标签,并且标签匹配
  63. if (obj.CompareTag(label))
  64. {
  65. // 删除游戏对象
  66. Destroy(obj);
  67. }
  68. }
  69. }
  70. public void Reset()
  71. {
  72. arrows.Clear();
  73. DeleteObjectsWithLabel("Target");
  74. DeleteObjectsWithLabel("ShootArea");
  75. GenerateTargets();
  76. }
  77. }

控制十字弩的运动,射击CrossbowMove.cs

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class CrossbowMove : MonoBehaviour
  5. {
  6. Camera cam;
  7. CharacterController playerController;
  8. Vector3 direction;
  9. public float speed = 5;
  10. public float jumpPower = 3;
  11. public float gravity = 9.8f;
  12. public float mousespeed = 10f;
  13. public float minmouseY = -45f;
  14. public float maxmouseY = 45f;
  15. float RotationY = 0f;
  16. float RotationX = 0f;
  17. float time;
  18. bool canShoot = false;
  19. int pos = -1;
  20. // Start is called before the first frame update
  21. void Awake()
  22. {
  23. cam = Camera.main;
  24. cam.transform.parent = this.transform;
  25. cam.transform.localPosition = new Vector3(0, 1, 0);
  26. cam.transform.localRotation = Quaternion.Euler(45, 0, 0);
  27. playerController = this.GetComponent<CharacterController>();
  28. canShoot = false;
  29. }
  30. // Update is called once per frame
  31. void Update()
  32. {
  33. HandleRotationInput();
  34. // Handle ground movement and jumping
  35. if (IsGrounded())
  36. {
  37. HandleGroundMovement();
  38. }
  39. ApplyGravity();
  40. // Move the player
  41. playerController.Move(direction * Time.deltaTime * speed);
  42. // Shoot
  43. Shoot();
  44. }
  45. void HandleRotationInput()
  46. {
  47. float mouseX = Input.GetAxis("Mouse X") * mousespeed;
  48. float mouseY = -Input.GetAxis("Mouse Y") * mousespeed; // Inverted for more intuitive camera control
  49. RotationX += mouseX;
  50. RotationY = Mathf.Clamp(RotationY + mouseY, minmouseY, maxmouseY);
  51. // Apply rotation using Quaternion
  52. transform.rotation = Quaternion.Euler(RotationY, RotationX, 0);
  53. cam.transform.localRotation = Quaternion.Euler(RotationY / 4, 0, 0);
  54. }
  55. bool IsGrounded()
  56. {
  57. float raycastDistance = 5f; // 调整为适当的值
  58. float characterHeight = 1.1f; // 角色的高度,调整为适当的值
  59. // 发射射线向下检测地面
  60. if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, raycastDistance))
  61. {
  62. // 计算地面和角色的高度差
  63. float groundHeight = hit.point.y;
  64. float heightDifference = transform.position.y - groundHeight;
  65. // 如果高度差小于等于角色高度的一半,则认为角色在地面上
  66. return heightDifference <= characterHeight;
  67. }
  68. // 如果射线未击中地面,则认为角色不在地面上
  69. return false;
  70. }
  71. void HandleGroundMovement()
  72. {
  73. float horizontal = Input.GetAxis("Horizontal");
  74. float vertical = Input.GetAxis("Vertical");
  75. // Calculate movement direction based on rotation
  76. Vector3 forward = transform.forward;
  77. Vector3 right = transform.right;
  78. // Calculate movement input
  79. Vector3 moveDirection = horizontal * right + vertical * forward;
  80. moveDirection.Normalize();
  81. // Jumping
  82. if (Input.GetKeyDown(KeyCode.Space))
  83. {
  84. moveDirection.y = jumpPower;
  85. }
  86. // Set the direction
  87. direction = moveDirection;
  88. }
  89. void ApplyGravity()
  90. {
  91. // Apply gravity
  92. direction.y -= gravity * Time.deltaTime;
  93. }
  94. void Shoot()
  95. {
  96. TargetGenerator t = FindObjectOfType<TargetGenerator>();
  97. if (canShoot && t.arrows[pos] > 0)
  98. {
  99. View view = FindObjectOfType<View>();
  100. if (Input.GetMouseButtonDown(0))
  101. {
  102. time = 0;
  103. }
  104. else if (Input.GetMouseButton(0))
  105. {
  106. time += Time.deltaTime;
  107. float power = Mathf.Clamp01(GetComponent<Animator>().GetFloat("Power") + time / 100);
  108. GetComponent<Animator>().SetFloat("Power", power);
  109. view.UpdatePower(power);
  110. }
  111. else if (Input.GetMouseButtonUp(0))
  112. {
  113. GetComponent<Animator>().SetTrigger("Shoot");
  114. GetComponent<Animator>().SetFloat("Power", 0.5f);
  115. view.UpdatePower(0f);
  116. // Instantiate arrow with a slight offset in the Y-axis
  117. GameObject arrowPrefab = Resources.Load<GameObject>("Prefabs/Arrow");
  118. Vector3 arrowSpawnPosition = transform.position + new Vector3(0, 1.0f, 0); // Adjust the Y-axis offset
  119. GameObject arrow = Instantiate(arrowPrefab, arrowSpawnPosition, transform.rotation);
  120. GameObject subCamPrefab = Resources.Load<GameObject>("Prefabs/subCamera");
  121. Vector3 subCamSpawnPosition = arrow.transform.position + new Vector3(0, 0.2f, -0.5f);
  122. Quaternion rotation = Quaternion.Euler(0, 0, 0);
  123. GameObject subCam = Instantiate(subCamPrefab, subCamSpawnPosition, rotation);
  124. subCam.transform.SetParent(arrow.transform);
  125. // Add Rigidbody component to the arrow
  126. Rigidbody arrowRigidbody = arrow.GetComponent<Rigidbody>();
  127. // Set velocity to the arrow based on the character's forward direction and power
  128. float arrowSpeed = Mathf.Min(10.0f + time * 5, 20f); // Adjust the speed as needed
  129. arrowRigidbody.velocity = transform.forward * arrowSpeed;
  130. t.arrows[pos]--;
  131. }
  132. }
  133. }
  134. private void OnTriggerStay(Collider other)
  135. {
  136. if (other.CompareTag("ShootArea"))
  137. {
  138. // 获取碰撞的具体圆柱体名称
  139. string shootAreaName = other.name.Substring("ShootArea".Length);
  140. // 提取圆柱体名称中的数字
  141. int shootAreaNumber;
  142. if (int.TryParse(shootAreaName, out shootAreaNumber))
  143. {
  144. pos = shootAreaNumber - 1;
  145. }
  146. canShoot = true;
  147. View view = FindObjectOfType<View>();
  148. TargetGenerator t = FindObjectOfType<TargetGenerator>();
  149. view.UpdateShoot(canShoot);
  150. view.UpdateArrowCount(t.arrows[pos]);
  151. }
  152. }
  153. private void OnTriggerExit(Collider other)
  154. {
  155. if (other.CompareTag("ShootArea"))
  156. {
  157. pos = -1;
  158. canShoot = false;
  159. View view = FindObjectOfType<View>();
  160. view.UpdateShoot(canShoot);
  161. }
  162. }
  163. }

对箭的控制ArrowBehavior.cs

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Windows.WebCam;
  5. public class ArrowBehavior : MonoBehaviour
  6. {
  7. private bool isFixed = false; // 用于标记箭是否已经固定在胶囊体上
  8. private void OnCollisionStay(Collision collision)
  9. {
  10. if (!isFixed)
  11. {
  12. // 检测碰撞对象是否是圆柱体
  13. if (collision.collider.CompareTag("Target"))
  14. {
  15. // 获取碰撞的具体圆柱体名称
  16. string cylinderName = collision.collider.name;
  17. // 提取圆柱体名称中的数字
  18. int cylinderNumber;
  19. if (int.TryParse(cylinderName, out cylinderNumber))
  20. {
  21. // 在这里可以根据具体的圆柱体编号执行相应的逻辑
  22. Debug.Log("Arrow hit capsule: " + cylinderNumber);
  23. transform.SetParent(collision.transform.parent.transform);
  24. // 固定箭在圆柱体上
  25. FixArrowToCapsule(collision.contacts[0].point);
  26. StartCoroutine(DisableSubCamAfterDelay(transform.GetChild(3).gameObject, 2.0f));
  27. // 获取记分系统并调用加分方法
  28. ScoreManager scoreManager = FindObjectOfType<ScoreManager>();
  29. View view = FindObjectOfType<View>();
  30. if (scoreManager != null)
  31. {
  32. string TargetName = collision.transform.parent.name.Substring("Target".Length);
  33. int TargetNumber;
  34. if (int.TryParse(TargetName, out TargetNumber))
  35. {
  36. if(TargetNumber %2 == 0)
  37. {
  38. scoreManager.IncreaseScore(cylinderNumber + TargetNumber);
  39. }
  40. else
  41. {
  42. scoreManager.IncreaseScore(cylinderNumber);
  43. }
  44. view.UpdateScore(scoreManager.score);
  45. view.UpdateMaxScore(scoreManager.score);
  46. view.AddHitTarget(collision.transform.parent.name, cylinderNumber);
  47. }
  48. }
  49. }
  50. }
  51. else if (collision.transform.name == "Ground")
  52. {
  53. Destroy(this.gameObject);
  54. }
  55. }
  56. }
  57. private void FixArrowToCapsule(Vector3 contactPoint)
  58. {
  59. // 固定标记设为 true
  60. isFixed = true;
  61. // 可以执行其他固定逻辑,例如禁用箭的 Rigidbody 组件等
  62. Rigidbody arrowRigidbody = GetComponent<Rigidbody>();
  63. if (arrowRigidbody != null)
  64. {
  65. arrowRigidbody.isKinematic = true; // 禁用 Rigidbody 的运动
  66. }
  67. // 将箭的位置调整到碰撞点
  68. transform.GetChild(2).position = contactPoint;
  69. transform.GetChild(1).gameObject.SetActive(false);
  70. transform.rotation = Quaternion.identity;
  71. }
  72. private IEnumerator DisableSubCamAfterDelay(GameObject subCam, float delay)
  73. {
  74. yield return new WaitForSeconds(delay);
  75. // 禁用subCam
  76. subCam.gameObject.SetActive(false);
  77. }
  78. }

记分ScoreManager.cs

  1. using UnityEngine;
  2. public class ScoreManager : MonoBehaviour
  3. {
  4. public int score;
  5. public ScoreManager()
  6. {
  7. score = 0;
  8. }
  9. public void IncreaseScore(int s)
  10. {
  11. // 箭射中目标,增加分数
  12. score += s;
  13. }
  14. public void Reset()
  15. {
  16. score = 0;
  17. }
  18. }

界面View.cs

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. using UnityEngine;
  5. public class View : MonoBehaviour
  6. {
  7. private int score = 0;
  8. private float power = 0.0f;
  9. private float maxPower = 1.0f;
  10. private bool shoot = false;
  11. private int arrowCount = 6;
  12. private int maxScore = 0;
  13. // 记录击中箭靶的列表
  14. private List<string> hitTargets = new List<string>();
  15. private List<int> hitPoints = new List<int>();
  16. // 用于滚动的位置
  17. private Vector2 scrollPosition = Vector2.zero;
  18. private void OnGUI()
  19. {
  20. // 设置GUI样式
  21. GUIStyle style = new GUIStyle();
  22. style.fontSize = 24; // 设置默认字体大小
  23. style.normal.textColor = Color.black;
  24. // 显示分数
  25. GUI.Label(new Rect(10, 10, 200, 30), "得分: " + score, style);
  26. // 显示最高分数
  27. GUI.Label(new Rect(120, 10, 200, 30), "最高分: " + maxScore, style);
  28. // 显示蓄力条
  29. DrawPowerBar();
  30. // 绘制红点在屏幕中央
  31. float dotSize = 10f;
  32. float dotPositionX = Screen.width / 2 - dotSize / 2;
  33. float dotPositionY = Screen.height / 2 - dotSize / 2;
  34. Texture2D redDotTexture = new Texture2D(1, 1);
  35. redDotTexture.SetPixel(0, 0, Color.red);
  36. redDotTexture.Apply();
  37. GUI.DrawTexture(new Rect(dotPositionX, dotPositionY, dotSize, dotSize), redDotTexture);
  38. // 重置游戏
  39. GUI.Label(new Rect(Screen.width - 210, Screen.height - 55, 200, 30), "按R键重置游戏", style);
  40. // 设置列表的位置和大小
  41. float listPositionX = 10f;
  42. float listPositionY = 60f;
  43. float listItemHeight = 20f; // 设置列表项默认高度
  44. float listHeight = 8 * listItemHeight; // 固定列表高度,超过这个高度将出现滚动条
  45. // 使用ScrollView实现滚动
  46. scrollPosition = GUI.BeginScrollView(new Rect(listPositionX, listPositionY, 200, listHeight), scrollPosition, new Rect(0, 0, 200, hitTargets.Count * listItemHeight));
  47. // 遍历击中箭靶的列表,并显示记录
  48. for (int i = 0; i < hitTargets.Count; i++)
  49. {
  50. // 设置记录的字体大小为16
  51. GUIStyle recordStyle = new GUIStyle(style);
  52. recordStyle.fontSize = 16;
  53. recordStyle.normal.textColor = Color.black;
  54. GUI.Label(new Rect(0, i * listItemHeight, 200, listItemHeight), "恭喜射中" + hitTargets[i] + "的" + hitPoints[i] + "环", recordStyle);
  55. }
  56. // 结束ScrollView
  57. GUI.EndScrollView();
  58. if (!shoot)
  59. {
  60. // 设置字体
  61. GUIStyle shootStyle = new GUIStyle(style);
  62. shootStyle.fontSize = 16;
  63. shootStyle.normal.textColor = Color.red;
  64. GUI.Label(new Rect(10, Screen.height - 30f, 90.0f, 30.0f), "您现在不能射击,请前往指定区域", shootStyle);
  65. }
  66. else
  67. {
  68. // 设置字体
  69. GUIStyle shootStyle = new GUIStyle(style);
  70. shootStyle.fontSize = 16;
  71. shootStyle.normal.textColor = Color.magenta;
  72. GUI.Label(new Rect(10, Screen.height - 30f, 90.0f, 30.0f), "您现在能射击了!!!", shootStyle);
  73. shootStyle.normal.textColor = Color.yellow;
  74. GUI.Label(new Rect(Screen.width / 2 - 45.0f, 30.0f, 90.0f, 30.0f), "此处还剩箭:" + arrowCount + "只", shootStyle);
  75. }
  76. }
  77. // 提供一个方法用于更新分数
  78. public void UpdateScore(int newScore)
  79. {
  80. score = newScore;
  81. }
  82. public void UpdateMaxScore(int newScore)
  83. {
  84. if(score > maxScore)
  85. {
  86. maxScore = score;
  87. }
  88. }
  89. public void UpdatePower(float newPower)
  90. {
  91. power = newPower;
  92. }
  93. public void UpdateShoot(bool newShoot)
  94. {
  95. shoot = newShoot;
  96. }
  97. public void UpdateArrowCount(int newArrowCount)
  98. {
  99. arrowCount = newArrowCount;
  100. }
  101. // 添加箭靶击中记录
  102. public void AddHitTarget(string targetName,int hitpos)
  103. {
  104. hitTargets.Add(targetName);
  105. hitPoints.Add(hitpos);
  106. }
  107. // 绘制蓄力条
  108. private void DrawPowerBar()
  109. {
  110. float barLength = 200f;
  111. float barHeight = 20f;
  112. float barPositionX = (Screen.width - barLength) - 10;
  113. float barPositionY = Screen.height - barHeight - 5;
  114. // 绘制蓄力条的背景
  115. GUI.Box(new Rect(barPositionX, barPositionY, barLength, barHeight), "Power");
  116. // 计算蓄力条的填充比例
  117. float fillRatio = power / maxPower;
  118. // 绘制蓄力条的填充部分
  119. GUI.Box(new Rect(barPositionX, barPositionY, barLength * fillRatio, barHeight), GUIContent.none);
  120. }
  121. public void Reset()
  122. {
  123. score = 0;
  124. power = 0;
  125. maxPower = 1.0f;
  126. shoot = false;
  127. arrowCount = 6;
  128. hitTargets.Clear();
  129. hitPoints.Clear();
  130. scrollPosition = Vector2.zero;
  131. }
  132. }

重置游戏ResetManager.cs

  1. using UnityEngine;
  2. public class ResetManager : MonoBehaviour
  3. {
  4. // 引用View、ScoreManager和TargetGenerator脚本
  5. private View view;
  6. private ScoreManager scoreManager;
  7. private TargetGenerator targetGenerator;
  8. private void Awake()
  9. {
  10. view = FindObjectOfType<View>();
  11. scoreManager = FindObjectOfType<ScoreManager>();
  12. targetGenerator = FindObjectOfType<TargetGenerator>();
  13. }
  14. void Update()
  15. {
  16. // 当按下R键时触发Reset函数
  17. if (Input.GetKeyDown(KeyCode.R))
  18. {
  19. // 调用View.cs中的Reset函数
  20. if (view != null)
  21. {
  22. view.Reset();
  23. }
  24. // 调用ScoreManager.cs中的Reset函数
  25. if (scoreManager != null)
  26. {
  27. scoreManager.Reset();
  28. }
  29. // 调用TargetGenerator.cs中的Reset函数
  30. if (targetGenerator != null)
  31. {
  32. targetGenerator.Reset();
  33. }
  34. }
  35. }
  36. }

动画制作

CTRL+6唤出动画选项卡

选中要添加动画的游戏对象,编辑即可

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

闽ICP备14008679号