赞
踩
目录
本项目游戏是3D游戏设计的一个期中大作业,其中具体的实验要求如下所示:
本游戏项目的代码仓库地址为:github的仓库地址
本游戏项目的演示视频地址为:本打靶(射箭)游戏演示视频
动作 | 条件 | 结果 |
WASD | 玩家(弩弓)在设计好的地形上 | 玩家(弩弓)前后左右移动 |
按下鼠标右键 | 无 | 天空盒发生变化 |
玩家(弩弓) 移动 | 玩家(弩弓)与树木、靶子发生碰撞 | 玩家(弩弓)停止移动 |
按下鼠标左键 | 玩家(弩弓)在射击位置上 | 弩弓开始蓄力 |
长按鼠标左键 | 无 | 弩弓的拉力不断增加直至所设定的上限 |
松开鼠标左键 | 无 | 弩弓根据长按的时间所计算的拉力来发射弩箭 |
鼠标移动 | 无 | 玩家(第一人称)的视觉范围发生变化 |
弩箭射中靶子 | 弩箭的碰撞检测体与靶子的碰撞检测体发生碰撞 | 总得分根据我们为每一个靶子所设定的分数进行增加 |
在本游戏项目中,我们采用的弩弓与弩箭预制体为导入的资源包里面的预制体和model,采用的是资源商店里面下载的Classical Crossbow。如图所示:
通过不同大小、不同形状的基础物体来构成。最外面的绿色区域为Cube,白色区域为Cylinder,而最里面的靶心(红色)区域也是为Cylinder,通过修改其半径来显示不同的Cylinder,同时还需要为靶子不同的部分构建不同的碰撞检测体。最后将这一个构成的集合物体建成预制体,以供生成后面的靶子。
在本次游戏项目中,我们还需要实现天空盒的切换,而我在这里采用的方法就是通过按动鼠标右键来切换天空盒。我们首先需要构建天空盒,我们可以直接从资源商店中导入相应的资源,我们导入的是资源是8K Skybox Pack Free,在这一个资源中,我们可以看到有很多个天空盒供我们自己选择,我们随便选择两种来实现天空盒的切换即可。我们采用的是天空盒3和天空盒10。
我们首先通过鼠标左键添加3D对象中的Terrain,然后根据Inspector界面中的不同选择来对地形进行修改和描绘,可以增高或降低地形,可以为地形增加不同的纹理。我们还可以为地形种树,我们选择不同的树木,然后点击Terrain即可实现种树的功能。一样的,我们还可以选择种草,我们选择不一样的小草,然后点击Terrain即可实现种草的功能。我们可以通过不同的选项来实现对地形进行不同的修改功能。
在本游戏项目中,主要的动画控制为弩箭的蓄力、发射等,我们导入的资源中,它已经自带了一些动作包以及一个动画控制器,我们可以直接采用才动画控制器,然后在这个的基础上进行修改即可。
1. Start(): 游戏开始时的初始化操作。设置时间缩放为正常速度,获取弓的动画控制器,锁定鼠标光标。
2. Update(): 每帧执行的更新操作:
检测是否按下了 Escape 键来切换光标锁定状态。
检测是否按下了鼠标左键来切换光标锁定状态。
检查射击区域是否存在,如果不存在则隐藏箭的数量文本并返回。
如果射击区域存在,显示箭的数量文本,并更新箭的数量显示。
检查是否可以射击箭,并且当前还有剩余的箭。
如果按下鼠标左键,重置拉动开始时间,触发弓的拉动动画,并调用 FindBullet() 函数清除场 景中的箭。
如果按住鼠标左键,增加拉动箭头的时间,并将该时间值设置为弓的拉动动画的参数。
如果松开鼠标左键,将拉动距离设为拉动开始时间,重置拉动开始时间,触发弓的射击动 画,并调用 ShootArrow() 函数发射箭,并在1.5秒后调用 FindShootingArea() 函数查找射击 区域。
3. ShootArrow(): 发射箭的函数。实例化箭游戏对象,获取箭的刚体组件,根据拉动距离设置箭的速度,减少射击区域的箭数量,并更新箭的数量显示。
4. FindBullet(): 清除场景中的箭的函数。通过标签找到所有的箭游戏对象,然后销毁它们。
5. FindShootingArea(): 查找射击区域的函数。通过标签找到所有的射击区域游戏对象,检查它们是否还有剩余的箭。如果没有任何射击区域剩余箭了,解锁光标,显示游戏结束的 UI,并将时间缩放设为0,即暂停游戏。
6. LockCursor(bool a): 锁定/解锁光标的函数。根据传入的布尔值来切换光标的锁定状态和可见性。如果传入 true,则锁定光标并隐藏;如果传入 false,则解锁光标并显示。
- using UnityEngine;
- using UnityEngine.UI;
-
- public class Bow : MonoBehaviour
- {
- //导入箭的预制体
- public GameObject arrowPrefab;
- //箭的Transform组件
- public Transform arrowSpawnPoint;
- //弓的最大拉动距离
- public float maxPullDistance = 3f;
- //弓的最大拉动力度
- public float maxPullForce = 100f;
- //弓的最小拉动时间
- public float minPullTime = 1f;
- //弓的最大拉动时间
- public float maxPullTime = 5f;
- //箭的飞行速度
- public float arrowFlightSpeed = 10f;
-
- //开始的拉动时间
- private float pullStartTime;
- //拉动的距离
- private float pullDistance;
- //弓的动画控制器
- private Animator anim;
-
- //射击区域
- public ShootingArea shootingArea;
- //箭的数量text
- public Text arrowCountTxt;
- //箭的数量UI
- public GameObject arrowCount;
-
- //游戏介绍的UI
- public GameObject over;
-
- void Start()
- {
- Time.timeScale = 1;
- anim = GetComponent<Animator>();
- LockCursor(true);
-
- }
-
- private void Update()
- {
- if (Input.GetKeyDown(KeyCode.Escape) && Cursor.visible) { LockCursor(false); }
-
- if (Input.GetMouseButtonDown(0) && Cursor.visible == false) { LockCursor(true); }
-
- if (shootingArea == null)
- {
- arrowCount.SetActive(false);
- return;
- }
- else
- {
- arrowCount.SetActive(true);
- arrowCountTxt.text = "箭数:" + shootingArea.arrowCount;
- }
-
-
- if (shootingArea.isArrow && shootingArea.arrowCount > 0)
- {
- if (Input.GetMouseButtonDown(0))
- {
- pullStartTime = 0;
- anim.SetTrigger("hold");
- //调用该函数清除场景中的箭
- FindBullet();
- }
- else if (Input.GetMouseButton(0))
- {
- //增加拉动箭头的时间
- pullStartTime += Time.deltaTime;
- //将拉动箭头的时间设置为前面计算得到的时间
- anim.SetFloat("holdTime", pullStartTime);
- }//玩家松开鼠标左键释放箭
- else if (Input.GetMouseButtonUp(0))
- {
- pullDistance = pullStartTime;
- pullStartTime = 0;
- anim.SetTrigger("shoot");
- ShootArrow();
- Invoke("FindShootingArea", 1.5f);
- }
- }
- }
-
- private void ShootArrow()
- {
- // 实例化箭
- GameObject arrow = Instantiate(arrowPrefab, arrowSpawnPoint.position, arrowSpawnPoint.rotation);
- Rigidbody arrowRigidbody = arrow.GetComponent<Rigidbody>();
- //根据拉动距离给箭设定速度
- arrowRigidbody.velocity = transform.forward * pullDistance * 30f;
- shootingArea.arrowCount -= 1;
- arrowCountTxt.text = "箭数:" + shootingArea.arrowCount;
- }
-
- public void FindBullet()
- {
- var bullets = GameObject.FindGameObjectsWithTag("Bullet");
- for (int i = 0; i < bullets.Length; i++)
- {
- Destroy(bullets[i]);
- }
- }
-
- public void FindShootingArea()
- {
-
- var ShootingAreas = GameObject.FindGameObjectsWithTag("ShootingArea");
- var temp = 0;
- for (int i = 0; i < ShootingAreas.Length; i++)
- {
- if (ShootingAreas[i].transform.GetComponent<ShootingArea>().arrowCount > 0)
- {
- temp++;
- }
- }
- if (temp <= 0)
- {
- LockCursor(false);
- over.SetActive(true);
- Time.timeScale = 0;
- }
- }
-
- public void LockCursor(bool a)
- {
- if (a)
- {
- Cursor.lockState = CursorLockMode.Locked;
- Cursor.visible = false;
- }
- else
- {
- Cursor.lockState = CursorLockMode.None;
- Cursor.visible = true;
- }
- }
- }
1. Start(): 游戏开始时的初始化操作。获取角色的 CharacterController 组件。
2. Update(): 每帧执行的更新操作。调用 Move() 函数来处理角色的移动逻辑。
3. Move(): 处理角色移动的函数:
通过输入获取水平方向(X轴)和垂直方向(Z轴)的按键输入值。
根据输入值计算出移动方向,并将其乘以移动速度和时间增量,得到最终的移动向量。
使用 CharacterController 的 Move() 方法来移动角色,传入计算得到的移动向量。
根据重力值和时间增量,更新垂直方向上的速度(velocity.y)。
再次使用 CharacterController 的 Move() 方法来应用垂直方向上的速度变化,使角色受到重 力影响下落或上升。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class PlayerMove : MonoBehaviour
- {
- //人物控制器
- private CharacterController controller;
- //人物移动速度
- public float speed = 2f;
- public float gravity = -15f;
- Vector3 velocity;
-
- private void Start()
- {
- controller = GetComponent<CharacterController>();
- }
-
-
- // Update is called once per frame
- void Update()
- {
- Move();
- }
-
-
- public void Move()
- {
- //键盘输入
- float x = Input.GetAxis("Horizontal");
- float z = Input.GetAxis("Vertical");
-
- Vector3 move = transform.right * x + transform.forward * z;
-
- controller.Move(move * speed * Time.deltaTime);
-
- velocity.y += gravity * Time.deltaTime;
-
- controller.Move(velocity * Time.deltaTime);
- }
- }
1. arrowCount: 用于记录射击区域内剩余的箭的数量,初始值为10。
2. isArrow: 用于表示射击区域内是否有可用的箭。
3. isPlayer: 用于记录玩家是否在射击区域内。
4. OnTriggerStay(Collider other): 当有物体停留在射击区域内时触发的函数:
检查如果玩家已经在射击区域内,直接返回,不执行后续操作。
检查与射击区域碰撞的物体是否具有 "Player" 标签。
如果是玩家物体发生碰撞:
更新相关的变量,将 isPlayer 和 isArrow 设置为 true。
获取玩家物体上的 Bow 脚本,并将射击区域设置为当前的脚本。
5. OnTriggerExit(Collider other): 当有物体离开射击区域时触发的函数:
检查离开触发器的物体是否具有 "Player" 标签。
如果是玩家物体离开:
更新相关变量,将 isPlayer 设置为 false。
检查玩家物体上的 Bow 脚本的 shootingArea 是否为 null。
如果不为 null,将射击区域内的箭矢数量赋值给玩家物体上的 Bow 脚本的射击区域的箭矢数 量。
将 isArrow 设置为 false。
将玩家物体上的 Bow 脚本的射击区域设置为 null。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class ShootingArea : MonoBehaviour
- {
- //将该区域的可用箭数初始化为10
- public int arrowCount = 10;
- //设置一个变量记录该区域是否有箭
- public bool isArrow;
- //记录玩家是否在区域内
- private bool isPlayer;
-
- private void OnTriggerStay(Collider other)
- {
- //如果玩家已经在区域内
- if (isPlayer) return;
- //如果该区域的触发器与标签为player的玩家发生碰撞
- if (other.gameObject.tag == "Player")
- {
- //更新相关的变量
- isPlayer = true;
- isArrow = true;
- //获取玩家物体上的脚本,并且将射击区域设置为当前的脚本
- other.gameObject.transform.GetComponent<Bow>().shootingArea = this;
- }
- }
-
-
- private void OnTriggerExit(Collider other)
- {
- //如果触发器与标签为player的玩家离开碰撞
- if (other.gameObject.tag == "Player")
- {
- //更新相关变量
- isPlayer = false;
- if (other.gameObject.transform.GetComponent<Bow>().shootingArea != null)
- {
- //将射击区域内的箭矢数量赋值给玩家物体上的Bow脚本的射击区域的箭矢数量
- arrowCount = other.gameObject.transform.GetComponent<Bow>().shootingArea.arrowCount;
- }
- isArrow = false;
- //将玩家物体上的Bow脚本的射击区域设置为null
- other.gameObject.transform.GetComponent<Bow>().shootingArea = null;
- }
- }
- }
1. score: 靶子的得分,初始值为1。
2. isSportsTarget: 表示靶子是否为运动靶子。
3. point: 靶子的位置点的Transform组件。
4. indexTarget: 靶子的索引。
5. Start(): 在脚本启动时进行初始化操作。获取靶子的父对象作为位置点。
6. OnCollisionEnter(Collision collision): 当靶子弩箭弹发生碰撞时触发的函数:
检查碰撞的物体是否具有 "Bullet" 标签。
如果是弩箭发生碰撞:
调用 CalculateScore() 函数计算得分。
将碰撞的弩箭的刚体设为运动学(isKinematic),停止其物理模拟。
将碰撞点的位置稍微偏移并将弩箭放置在靶子的位置点下。
将弩箭的父对象设置为位置点。
7. CalculateScore(): 计算得分的函数:
检查靶子的标签。
如果靶子的标签为 "Bullseye",表示击中了靶心:
检查靶子是否为运动靶子。
如果是运动靶子,将得分加10,并显示相应的提示信息。
如果不是运动靶子,将得分加8,并显示相应的提示信息。
如果靶子的标签为 "Circle",表示击中了白色区域:
检查靶子是否为运动靶子。
如果是运动靶子,将得分加5,并显示相应的提示信息。
如果不是运动靶子,将得分加3,并显示相应的提示信息。
- using UnityEngine;
-
- public class Target : MonoBehaviour
- {
- //得分
- public int score = 1;
- //是否为运动靶子
- public bool isSportsTarget;
- //靶子的位置点
- private Transform point;
- //靶子的索引
- public int indexTarget;
-
- private void Start()
- {
- //获取靶子的父对象作为位置点
- point = transform.parent;
- }
-
- private void OnCollisionEnter(Collision collision)
- {
- if (collision.gameObject.CompareTag("Bullet"))
- {
- // 调用函数计算得分
- CalculateScore();
- collision.transform.GetComponent<Rigidbody>().isKinematic = true;
- collision.transform.position = new Vector3(collision.contacts[0].point.x, collision.contacts[0].point.y, collision.contacts[0].point.z - Random.Range(-0.3f, -0.5f));
- collision.gameObject.transform.parent = point;
- }
- }
-
- private void CalculateScore()
- {
- //如果靶子的标签为Bullseye,即为靶心
- if (gameObject.tag == "Bullseye")
- {
- // 如果为运动靶子
- if (isSportsTarget)
- {
- // 将得分加3
- Tips.Instance.SetScore(10);
- Tips.Instance.SetText("在" + indexTarget + "号射击位上射中" + indexTarget + "号靶子,加10分");
-
- }
- else
- {
- //如果不是运动靶子,将得分加2
- Tips.Instance.SetScore(8);
- Tips.Instance.SetText("在" + indexTarget + "号射击位上射中" + indexTarget + "号靶子,加3分");
- }
- }
- // 如果靶子的标签为Circle,即为白色区域
- else if (gameObject.tag == "Circle")
- {
- //如果为运动靶子
- if (isSportsTarget)
- {
- // 将得分加2
- Tips.Instance.SetScore(5);
- Tips.Instance.SetText("在" + indexTarget + "号射击位上射中" + indexTarget + "号靶子,加5分");
- }
- else
- {
- // 如果不是运动靶子,就将得分加1
- Tips.Instance.SetScore(3);
- Tips.Instance.SetText("在" + indexTarget + "号射击位上射中" + indexTarget + "号靶子,加3分");
- }
- }
- }
- }
-
1. speed: 靶子的移动速度。
2. distance: 靶子的移动距离。
3. startPosition: 靶子的起始位置。
4. direction: 靶子的移动方向,初始值为1。
5. Start(): 在脚本启动时进行初始化操作。记录靶子的起始位置。
6. Update(): 在每一帧更新时执行的函数:
计算下一帧的位置,根据当前位置、移动速度、移动方向和时间间隔来计算。
判断下一帧的位置与起始位置之间的距离是否超过设定的移动距离。
如果超过移动距离,改变移动方向(乘以-1),以实现来回移动。
更新靶子的位置为计算得到的下一帧位置。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class TargetMove : MonoBehaviour
- {
- //靶子的移动速度
- public float speed = 5f;
- //靶子的移动距离
- public float distance = 10f;
-
- //靶子的起始位置
- private Vector3 startPosition;
- //靶子的移动方向
- private float direction = 1f;
-
- void Start()
- {
- //记录起始位置
- startPosition = transform.position;
- }
-
- void Update()
- {
- //计算下一帧的位置
- Vector3 nextPosition = transform.position + new Vector3(speed * direction * Time.deltaTime, 0f, 0f);
-
- // 判断是否超出移动范围,超出则改变移动方向
- if (Vector3.Distance(startPosition, nextPosition) > distance)
- {
- direction *= -1f;
- }
-
- // 更新位置
- transform.position = nextPosition;
- }
- }
1. Instance: Tips类的静态实例,用于在其他脚本中访问Tips类的实例。
2. tips: 提示框的游戏对象。
3. tipsText: 提示文本的Text组件。
4. score: 当前的得分。
5. scoreText: 显示得分的Text组件。
6. Awake(): 在脚本被加载时执行的函数。将Tips类的实例设置为当前实例。
7. SetText(string str): 设置提示文本的函数:
接收一个字符串参数,用于设置提示文本的内容。
将提示文本设置为接收的字符串。
激活提示框的游戏对象,使其可见。
8. SetScore(int score): 设置得分的函数:
接收一个整数参数,表示要增加的得分。
将接收的得分加到当前的得分上。
将得分显示在得分文本中,格式为 "分数: 得分值"。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class Tips : MonoBehaviour
- {
- public static Tips Instance;
- public GameObject tips;
- public Text tipsText;
-
- private int score;
- public Text scoreText;
-
- private void Awake()
- {
- Instance = this;
- }
-
- public void SetText(string str)
- {
- tipsText.text = str;
- tips.SetActive(true);
- }
-
- public void SetScore(int score)
- {
- this.score += score;
- scoreText.text = "分数:" + this.score;
- }
- }
1. skybox1: 第一个天空盒的材质。
2. skybox2: 第二个天空盒的材质。
3. isSkybox1Active: 当前激活的天空盒是否为天空盒1的标志,初始值为true。
4. Update(): 在每一帧更新时执行的函数:
检测是否按下鼠标右键(按钮编号为1)。
如果按下右键,调用SwitchSkybox()函数切换天空盒。
5. SwitchSkybox(): 切换天空盒的函数:
切换天空盒的状态,将isSkybox1Active的值取反。
如果isSkybox1Active为true,将渲染设置中的天空盒材质设置为第一个天空盒材质。
如果isSkybox1Active为false,将渲染设置中的天空盒材质设置为第二个天空盒材质。
- using UnityEngine;
-
- public class SkyboxSwitcher : MonoBehaviour
- {
- //第一个天空盒
- public Material skybox1;
- //第二个天空盒
- public Material skybox2;
-
- //当前激活的天空盒是否为天空盒1
- private bool isSkybox1Active = true;
-
- private void Update()
- {
- //如果按下C键,切换天空盒
- if (Input.GetMouseButtonDown(1))
- {
- SwitchSkybox();
- }
- }
-
- private void SwitchSkybox()
- {
- //切换天空盒的状态
- isSkybox1Active = !isSkybox1Active;
-
- if (isSkybox1Active)
- {
- //设置渲染设置中的天空盒材质为第一个天空盒材质
- RenderSettings.skybox = skybox1;
- }
- else
- {
- //设置渲染设置中的天空盒材质为第二个天空盒材质
- RenderSettings.skybox = skybox2;
- }
- }
- }
1. mouseXSensitivity: 鼠标在X轴上的灵敏度,用于控制视角旋转的速度。
2. player: 用于存储玩家对象的Transform组件。
3. xRotation: 控制视角绕X轴旋转的角度。
4. Start(): 在脚本启动时执行的函数:
获取父级对象的Transform组件并赋值给player变量。
5. Update(): 在每一帧更新时执行的函数:
获取鼠标在X轴和Y轴上的移动距离(输入值)并乘以鼠标灵敏度和时间间隔。
将Y轴移动距离(mouseY)累加到xRotation上,用于控制视角绕X轴旋转。
限制xRotation的值在-45到10之间,以限制视角的上下旋转幅度。
将当前的xRotation应用于摄像机的本地旋转,实现视角的上下旋转。
将X轴移动距离(mouseX)乘以玩家对象的向上方向(Vector3.up)应用于玩家对象的旋 转,实现视角的左右旋转。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- //鼠标控制视角
- public class CameraMove : MonoBehaviour
- {
- //鼠标x轴灵敏度
- public float mouseXSensitivity = 25f;
- //人物
- private Transform player;
- //旋转角度
- float xRotation = 0f;
-
- private void Start()
- {
- player = transform.parent.transform;
- }
-
-
- // Update is called once per frame
- void Update()
- {
- float mouseX = Input.GetAxis("Mouse X") * mouseXSensitivity * Time.deltaTime;
- float mouseY = Input.GetAxis("Mouse Y") * mouseXSensitivity * Time.deltaTime;
- xRotation -= mouseY;
- //y轴最大旋转角度为正负90;
- xRotation = Mathf.Clamp(xRotation, -45f, 10f);
- transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
- player.Rotate(Vector3.up * mouseX);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。