一、实现功能
地形:使用地形组件,上面有草、树;
天空盒:使用天空盒,天空可随时间变化
固定靶:有一个以上固定的靶标;
运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击;
驽弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
碰撞与计分:在射击位,射中固定靶+10分,移动靶+20分。
二、代码介绍
2.1总体框架
导入了assets store的三个资源包,分别是天空盒、靶子、弓箭资源包,ArrowController用于射箭碰撞检测控制,BowController用于控制拉弓蓄力及射击,BowMovement用于控制弓箭(摄像头)移动及视角转动,SwitchSky用于控制天空盒随时间变化。
2.2天空盒变化
新建一个空物体挂载SwitchSky,配置mats数组为需要切换的天空盒材质,ChangeTime为天空盒切换时间,这里的三个天空盒Material均为资源包中下载的。
ChangeBox()
是一个公共方法。它在指定的时间间隔内被 InvokeRepeating()
方法重复调用。在这个方法中,它通过改变 currentIndex
和 nextIndex
来更新当前和下一个天空盒的索引,并将 RenderSettings.skybox
设置为 mats
数组中的下一个天空盒材质。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SwitchSky : MonoBehaviour
{
public Material[] mats;
private int currentIndex = 0;
private int nextIndex = 1;
public int changeTime; // 更换天空盒子的秒数
void Start()
{
RenderSettings.skybox = mats[currentIndex];
InvokeRepeating("ChangeBox", 0, changeTime);
}
public void ChangeBox()
{
currentIndex = nextIndex;
nextIndex = (nextIndex + 1) % mats.Length;
RenderSettings.skybox = mats[currentIndex]; // 更换天空盒材质
}
}
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()
方法用于检查玩家是否处于特定区域内。
检查当前玩家是否位于预设的两个区域中的任意一个内,通过计算玩家位置和两个区域点之间的距离来判断。
如果玩家在指定区域内:
- 按下空格键时触发开始拉弓动作,设置
isPulling
为true
。 - 持续按住空格键记录按键时间,并映射为拉弓的强度
pullStrength
。 - 松开空格键时结束拉弓动作。
- 点击鼠标左键触发射箭动作,通过
Fire()
方法发射箭矢,并根据pullStrength
来设置箭矢的发射力度。
2.Fire()
方法:
- 在
firePoint
位置实例化箭矢预制体,并设置箭矢的初始位置和方向。 - 给箭矢添加了
Rigidbody
组件,并根据holdForce
(拉弓力度)施加力,使箭矢飞行
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class BowController : MonoBehaviour
{
Animator animator;
bool isPulling = false;
float pullDuration = 0f;
public float maxPullDuration = 2f; // 最长拉弓时间
public float arrowSpeed = 1f; // 箭矢速度
public GameObject arrowObj;
float pullStrength;
public Transform firePoint;
public Camera maincam;
void Start()
{
// 获取弓上的Animator组件
animator = GetComponent<Animator>();
}
void Update()
{
if (Isinarea())
{
Debug.Log("在区域内");
// 当按下空格键时触发状态切换
if (Input.GetKeyDown(KeyCode.Space))
{
pullDuration = 0;
animator.SetBool("Fire", false);
animator.SetFloat("Power", 0);
isPulling = true;
animator.SetBool("isPulling", true);
//animator.SetBool("Holding", false);
}
// 持续按下空格键时记录按下时间,决定拉弓的强度
if (isPulling)
{
pullDuration += Time.deltaTime;
// 将按下的时间映射到0到1的范围,作为拉弓强度的参数
pullStrength = Mathf.Clamp01(pullDuration / maxPullDuration);
animator.SetFloat("Power", pullStrength);
}
if (Input.GetKeyUp(KeyCode.Space))
{
isPulling = false;
//animator.SetBool("Holding", true);
animator.SetBool("isPulling", false);
}
// 当点击鼠标左键时触发射击
if (Input.GetMouseButtonDown(0))
{
animator.SetBool("Fire", true);
animator.SetFloat("Power", 0);
// animator.SetBool("Holding", false);
Fire(pullStrength);
}
}
}
public void Fire(float holdForce)
{
GameObject arrow = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/Arrow"));
arrow.AddComponent<ArrowController>();
ArrowController arrowController = arrow.GetComponent<ArrowController>();
arrowController.cam = maincam;
arrow.transform.position = firePoint.transform.position;
arrow.transform.rotation = Quaternion.LookRotation(this.transform.forward);
Rigidbody rd = arrow.GetComponent<Rigidbody>();
if (rd != null)
{
rd.AddForce(this.transform.forward * 60 * holdForce);
Debug.Log(arrow.transform.rotation);
}
}
bool Isinarea()
{
Vector3 currentPosition = transform.position;
Vector3 area1 = new Vector3(-99.12f, 14.86f, 153.65f);
Vector3 area2 = new Vector3(-93.8506f, 15.3659f, 83.4803f);
float distance1 = Vector3.Distance(currentPosition, area1); // 计算目标点和玩家位置之间的距离
float distance2 = Vector3.Distance(currentPosition, area2);
float radius = 10f; // 圆的半径
if (distance1 <= radius || distance2 <= radius)
{
return true;
}
else
{
return false;
}
}
}
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
绕旋转中心旋转指定角度后所得到的向量。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BowMovement : MonoBehaviour
{
// 在场景中游览的相机(不要给相机加碰撞器!)
public Transform tourCamera;
#region 相机移动参数
public float moveSpeed = 20.0f;
public float rotateSpeed = 150.0f;
public float shiftRate = 2.0f;// 按住Shift加速
public float minDistance = 0.5f;// 相机离不可穿过的表面的最小距离(小于等于0时可穿透任何表面)
#endregion
#region 运动速度和其每个方向的速度分量
private Vector3 direction = Vector3.zero;
private Vector3 speedForward;
private Vector3 speedBack;
private Vector3 speedLeft;
private Vector3 speedRight;
private Vector3 speedUp;
private Vector3 speedDown;
#endregion
void Start()
{
if (tourCamera == null) tourCamera = gameObject.transform;
}
void Update()
{
GetDirection();
// 检测是否离不可穿透表面过近ss
RaycastHit hit;
while (Physics.Raycast(tourCamera.position, direction, out hit, minDistance))
{
// 消去垂直于不可穿透表面的运动速度分量
float angel = Vector3.Angle(direction, hit.normal);
float magnitude = Vector3.Magnitude(direction) * Mathf.Cos(Mathf.Deg2Rad * (180 - angel));
direction += hit.normal * magnitude;
}
if (tourCamera.localPosition.y > 25.0f)
{
tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 25.0f, tourCamera.localPosition.z);
}
if (tourCamera.localPosition.y < 12.0f)
{
tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 12.0f, tourCamera.localPosition.z);
}
tourCamera.Translate(direction * moveSpeed * Time.deltaTime, Space.World);
}
private void GetDirection()
{
#region 加速移动
if (Input.GetKeyDown(KeyCode.LeftShift)) moveSpeed *= shiftRate;
if (Input.GetKeyUp(KeyCode.LeftShift)) moveSpeed /= shiftRate;
#endregion
#region 键盘移动
// 复位
speedForward = Vector3.zero;
speedBack = Vector3.zero;
speedLeft = Vector3.zero;
speedRight = Vector3.zero;
speedUp = Vector3.zero;
speedDown = Vector3.zero;
// 获取按键输入
if (Input.GetKey(KeyCode.W)) speedForward = tourCamera.forward;
if (Input.GetKey(KeyCode.S)) speedBack = -tourCamera.forward;
if (Input.GetKey(KeyCode.A)) speedLeft = -tourCamera.right;
if (Input.GetKey(KeyCode.D)) speedRight = tourCamera.right;
if (Input.GetKey(KeyCode.E)) speedUp = Vector3.up;
if (Input.GetKey(KeyCode.Q)) speedDown = Vector3.down;
direction = speedForward + speedBack + speedLeft + speedRight + speedUp + speedDown;
#endregion
#region 鼠标旋转
if (Input.GetMouseButton(1))
{
// 转相机朝向
tourCamera.RotateAround(tourCamera.position, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
tourCamera.RotateAround(tourCamera.position, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
// 转运动速度方向
direction = V3RotateAround(direction, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
direction = V3RotateAround(direction, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
}
#endregion
}
/// <summary>
/// 计算一个Vector3绕旋转中心旋转指定角度后所得到的向量。
/// </summary>
/// <param name="source">旋转前的源Vector3</param>
/// <param name="axis">旋转轴</param>
/// <param name="angle">旋转角度</param>
/// <returns>旋转后得到的新Vector3</returns>
public Vector3 V3RotateAround(Vector3 source, Vector3 axis, float angle)
{
Quaternion q = Quaternion.AngleAxis(angle, axis);// 旋转系数
return q * source;// 返回目标点
}
}
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" 标签,箭矢直接销毁。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SocialPlatforms.Impl;
public class ArrowController : MonoBehaviour
{
// Start is called before the first frame update
private Rigidbody rb;
public float Score = 0;
public Camera cam;
private Main ui;
void Start()
{
rb = GetComponent<Rigidbody>();
ui = cam.GetComponent<Main>();
}
// Update is called once per frame
void Update()
{
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("staticTarget"))
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
ui.Score += 10;
Destroy(gameObject);
}
else if (collision.gameObject.CompareTag("MovingTarget"))
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
ui.Score += 20;
Destroy(gameObject);
}
else if (collision.gameObject.CompareTag("Ground"))
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
Destroy(gameObject);
}
}
}
Main实现显示分数和游戏提示,挂载在主摄像机Cam_fps(1)上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
// Start is called before the first frame update
public float Score = 0;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnGUI()
{
GUIStyle style = new GUIStyle();
style.fontSize = 15;
style.normal.textColor = Color.white;
// 定义游戏介绍文本内容
string introText = "天空10s自动切换\nwasd --移动弓弩\nSpace --蓄力拉弓\n鼠标左键 --射箭\n鼠标右键 --调整角度";
// 在屏幕左上角绘制游戏介绍文本
GUI.Label(new Rect(10, 10, 300, 100), introText, style);
style.fontSize = 24;
style.normal.textColor = Color.white;
// 在屏幕右上角显示分数
GUI.Label(new Rect(Screen.width - 150, 20, 150, 30), "Score: " + Score, style);
}
}
三、游戏演示
参考博客:unity射箭小游戏-CSDN博客
演示视频:Unity3D射箭小游戏_哔哩哔哩_bilibili文章来源:https://www.toymoban.com/news/detail-755049.html
文章来源地址https://www.toymoban.com/news/detail-755049.html
到了这里,关于Unity 3D射箭游戏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!