一、要求
- 1-3分钟视频:视频呈现游戏主要游玩过程;
- 地形:使用地形组件,上面有草、树;
- 天空盒:使用天空盒,天空可随玩家位置 或 时间变化 或 按特定按键切换天空盒;
- 固定靶:有一个以上固定的靶标;
- 运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
- 射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击,每个位置有 n 次机会;
- 驽弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
- 游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
- 碰撞与计分:在射击位,射中靶标的相应分数,规则自定;
二、代码介绍
2.1天空盒的切换
实现按shift键换天空盒,注意把天空盒的材质拖到mats数组中。
using UnityEngine;
public class Skychange : MonoBehaviour
{
public Material[] mats;
private int index = 0;
public int changeTime;//更换天空盒子的秒数
// Start is called before the first frame update
void Start()
{
RenderSettings.skybox = mats[index];
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift))
{
// 切换到下一个天空盒
index = (index + 1) % mats.Length;
RenderSettings.skybox = mats[index];
}
}
}
2.2运动靶的运动动画
这里使用动画,直接右键-create-animation,然后拖到想要运动的靶上。双击动画,按下录制,在关键地方加帧调整位置,弄好之后结束录制即可。
2.3箭的动画,蓄力拉弓与射出
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())
{
// 当按下空格键时触发状态切换
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);
}
}
bool Isinarea()
{
Vector3 currentPosition = transform.position;
Vector3 area1 = new Vector3(400.1f, -0.78f, 350.6f);
Vector3 area2 = new Vector3(488.7f, -0.78f,371.3f);
float distance1 = Vector3.Distance(currentPosition, area1); // 计算目标点和玩家位置之间的距离
float distance2 = Vector3.Distance(currentPosition, area2);
float radius = 12.5f; // 圆的半径
if (distance1 <= radius|| distance2 <= radius)
{
return true;
}
else
{
return false;
}
}
}
2.4游走
为了使弓弩走的时候,摄像机的视野随之调整,可以设置一个空对象,然后maincamera为该空对象的子对象,弓弩为maincamera的子对象。然后下面是游走的代码,将其挂载在空对象上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BowMovement : MonoBehaviour
{
// 在场景中游览的相机(不要给相机加碰撞器!)
public Transform tourCamera;
#region 相机移动参数
public float moveSpeed = 10.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;
// 防止相机边缘穿透
//if (tourCamera.GetComponent<Camera>().nearClipPlane > minDistance / 3)
//{
// tourCamera.GetComponent<Camera>().nearClipPlane /= 3;
//}
}
void Update()
{
GetDirection();
// 检测是否离不可穿透表面过近
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 > 3.3f)
{
tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 3.3f, tourCamera.localPosition.z);
}
if (tourCamera.localPosition.y < 2.8f)
{
tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 2.8f, 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.4碰撞计分
这个代码实现显示提示信息和显示分数,该代码挂载到摄像机上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class showMeg : 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 = 10;
style.normal.textColor = Color.red;
// 定义游戏介绍文本内容
string introText = "wasd --移动弓弩\nshift --切换天空\nSpace --蓄力拉弓\n鼠标左键 --射箭\n鼠标右键 --调整角度";
// 在屏幕左上角绘制游戏介绍文本
GUI.Label(new Rect(10, 10, 300, 100), introText, style);
style.fontSize = 24;
style.normal.textColor = Color.blue;
// 在屏幕右上角显示分数
GUI.Label(new Rect(Screen.width - 150, 20, 150, 30), "Score: " + Score, style);
}
}
以下是修改分数的逻辑,整个就是检测碰撞,如果碰到静态靶,分数+10,如果碰到动态靶,分数+20,同时销毁箭。
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 showMeg ui;
void Start()
{
rb = GetComponent<Rigidbody>();
ui = cam.GetComponent<showMeg>();
}
// 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);
}
}
}