一.项目目标
1. 游戏场景
- 地形(2分):使用地形组件,上面有山、路、草、树;(可使用第三方资源改造)
- 声音(2分):使用声音组件,播放背景音 与 箭射出的声效;
- 摄像机(2分):使用多摄像机,制作 鸟瞰图 或 瞄准镜图 使得游戏更加易于操控;
- 射击位(2分):地图上应标记若干射击位,仅在射击位附近或区域可以拉弓射击,每个位置有 n 次机会;
- 运动靶(2分):使用动画运动,有一个以上运动靶标,运动轨迹,速度使用动画控制;(注:射中后需要有效果或自然落下)
- 固定靶(2分):使用静态物体,有一个以上固定的靶标;(注:射中后状态不会变化)
- 天空盒(2分):使用天空盒,天空可随 玩家位置 或 时间变化 或 按特定按键切换天空盒;
2.运动与物理与动画
- 驽弓动画(2分):使用 动画机 与 动画融合, 实现十字驽蓄力半拉弓,然后 hold,择机 shoot。
- 碰撞与计分(2分):使用 计分类 管理规则,在射击位射中靶标得相应分数,规则自定;(注:应具有现场修改游戏规则能力)
- 射击效果(2分):使用 物理引擎 或 动画 或 粒子,运动靶被射中后产生适当效果。
- 游走(2分):使用第一人称组件,玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;(注:建议使用 unity 官方案例)
项目演示视频
第一人称射击打靶游戏演示视频
二.项目设计
1. 角色设计
从表面上看,游戏的主角是一只十字弩;但从本质上说,游戏的主角是一个白色方块。十字弩是白色方块的子对象,而主摄像机又是十字弩的子对象,两两锚定一个固定的位置偏移量。玩家通过控制白色方块的移动来控制十字弩和主摄像机的移动,从而实现“第一人称”的游戏效果。

2. 标靶设计
(1)固定靶:分大小两种,集中在射击区域1,击中加分,可多次击中。
(2)移动靶:分为快速移动靶和慢速移动靶,由动画控制器控制移动,集中在射击区域2。快速移动靶的移动轨迹是不规则的曲线,慢速移动靶的移动轨迹是折线。移动靶被击中之后会加大量分数,同时爆炸消失。
(3)可爱的史莱姆:仅生成在射击区域3,击中会有特殊音效,同时减分。可多次击中。
(4)飞龙(在代码状被称为“Bird”):生成在中心区域和射击区域4。飞龙们会随机在场上飞行(但不会飞到场外)。击中飞龙会加分,同时引发爆炸。
3. 场景设计
自己手搓的游戏场景,整体类似于一个盆地,群山环绕(四周都是山体墙),有路有草有树。内部又被山体分为了四个部分,对应四个射击场地。地形资源,包括树、草和纹理贴图都是从Unity资源商城免费获取的。
有天空盒,可以通过数字键1~5切换。

4. 动作设计
(1)角色动作设计:角色可以WASD移动,空格跳跃,R键装填弓箭,Q键放大视角瞄准,F键突进(跳跃加突进效果更好),R键释放技能(只有粒子效果和音效无实际作用)。移动和释放技能时会有音效。
(2)十字弩射击设计:十字弩可以左键蓄力,射击的力度由蓄力时间决定。蓄力没有达到极限时,放开左键即可射出弩箭;蓄力达到极限时,弩箭会进入挂起状态,按下右键即可射出弩箭。射击时会有音效。
(3)传送设计:按下数字键1~4可以传送到对应的射击区域。
5. 效果设计
(1)粒子效果
(2)准星效果
(3)音效设计
三.项目实现
1. 文件组织形式
套用经典的动作分离模式,代码部分分为三部分——动作部分(主要是蓄力射箭)Actions,控制器部分Controllers,视图部分Views。
2. 重点代码介绍
(1)BirdMovementControl:控制飞龙(鸟)在场景内的随机移动,在移动参数上进行加权,从而尽可能避免产生可能出界的动作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BirdMovementController : MonoBehaviour
{
public float moveSpeed = 6f; // 移动速度
public float moveRange = 20f; // 水平移动范围
public float moveRange_2 = 5f; // 垂直移动范围
public Vector3 targetPosition ; // 目标位置
// public Vector3 offset = new Vector3(40f, 0f, 40f);
void Start()
{
// 初始目标位置
SetNewTargetPosition();
}
void Update()
{
// 移动到目标位置
MoveTowardsTarget();
// 如果接近目标位置,则设置新的目标位置
if (Vector3.Distance(transform.position, targetPosition) < 0.1f)
{
SetNewTargetPosition();
}
}
void SetNewTargetPosition()
{
// 在指定范围内随机生成新的目标位置
float x = Random.Range(-moveRange, moveRange - 15);
float z = Random.Range(-moveRange + 15, moveRange);
float y = Random.Range( 0 , moveRange_2);
targetPosition = new Vector3(x, y, z) + transform.position;
}
void MoveTowardsTarget()
{
// 朝向目标
transform.LookAt(targetPosition);
// 平滑移动
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
}
}
(2)BirdControl:飞龙的控制器,用于控制弩箭与飞龙发生碰撞之后的操作,包括更改弩箭速度、状态和标签,最后销毁弩箭和飞龙对象并在原地生成爆炸粒子特效。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BirdController : MonoBehaviour
{
public int RingScore = 10; // 鸟的分值
public ScoreRecorder sc_recorder;
private bool isHit = false; // 标记鸟是否被射中
public GameObject explosion;
void Start()
{
explosion = GameObject.Instantiate(Resources.Load("Prefabs/explosionPrefab", typeof(GameObject))) as GameObject;
sc_recorder = Singleton<ScoreRecorder>.Instance;
}
void Update()
{
}
public void Hit()
{
if (isHit) return; // 如果已经被射中,返回
isHit = true; // 设置为被射中状态
// 在原地生成爆炸特效
Instantiate(explosion, transform.position, Quaternion.identity);
// 销毁鸟
Destroy(gameObject);
}
void OnCollisionEnter(Collision collision) // 检测碰撞
{
Transform arrow = collision.gameObject.transform; // 得到箭身
if (arrow == null) return;
if (arrow.tag == "arrow")
{
//将箭的速度设为0
arrow.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
//使用运动学运动控制
arrow.GetComponent<Rigidbody>().isKinematic = true;
arrow.transform.rotation = Quaternion.Euler(0, 0, 0); // 使箭的旋转角度为0
arrow.transform.parent = this.transform; // 将箭和靶子绑定
sc_recorder.RecordScore(RingScore); //计分
arrow.tag = "onTarget"; //标记箭为中靶
Hit(); // 调用Hit()函数
}
}
}
(3)CrosshairController:准星控制器。将准星所在的UI对象与主摄像机绑定,从而实现正确的准星效果。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CrosshairController : MonoBehaviour
{
public Camera mainCamera; // 要跟随的摄像机
public Vector3 offset; // UI 相对于摄像机的位置偏移
void Start()
{
if (mainCamera == null)
{
mainCamera = Camera.main; // 如果没有指定摄像机,则使用主摄像机
}
if (offset == Vector3.zero)
{
offset = new Vector3(10, -10, 10); // 调整这个偏移量来控制 UI 与摄像机的位置关系
}
}
void Update()
{
// 设置 Canvas 的位置为摄像机的位置加上偏移
transform.position = mainCamera.transform.position + offset;
// 使 Canvas 始终面朝摄像机
transform.LookAt(mainCamera.transform.position);
transform.forward = -transform.forward; // 使 UI 面向摄像机
}
}
(4)BowController:最重要的代码文件之一,用于控制十字弩的运动,包括移动、跳跃、转动、传送、冲刺突进 等。其实,按照规范的动作分离模型,动作类应该另起一文件写在Actions文件夹内,但出于控制文件总数和方便后期修改的考虑,我并没有这么做(全部分离的话光脚本文件总数就会达到几十个,对于一个轻量级的小项目来说不方便修改)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 挂载在弩上,控制弩的移动和视角的旋转、以及传送到指定的射击位置
public class BowController : MonoBehaviour
{
public float moveSpeed = 10; //速度:每秒移动10个单位长度
public float angularSpeed = 30; //角速度
public float jumpForce = 200f; //跳跃冲量
public float dashForce = 200f; //冲刺冲量
public float horizontalRotateSensitivity = 3; //水平视角灵敏度
public float verticalRotateSensitivity = 3; //垂直视角灵敏度
private float xRotation = 0f; // x旋转角度
public bool canshoot = false; // 是否可以射箭
public bool isAiming = false;
private Rigidbody rb; // 创建刚体组件
public Vector3 offset = new Vector3(0f, 8f, 0f); // 子对象偏移量
public Vector3 offset_Camera = new Vector3(0.5f, -0.5f, 0f); // 瞄准视角偏移量
void Start()
{
rb = transform.parent.GetComponent<Rigidbody>(); // 获取实例
}
void FixedUpdate()
{
transform.position = transform.parent.position + offset; // 绑定父子对象位置
Move();
View();
transport();
aimFocus();
}
void Move() // 移动
{
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
transform.parent.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed);
transform.parent.Translate(Vector3.right * h * Time.deltaTime * moveSpeed);
if (Input.GetKeyDown(KeyCode.Space)) // 跳跃
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
if (Input.GetKeyDown(KeyCode.F)) // 冲刺
{
// 根据当前朝向施加冲量
Vector3 force = transform.forward * dashForce; // 计算冲量方向
rb.AddForce(force, ForceMode.Impulse);
}
}
void SetCursorToCentre() // 锁定鼠标到屏幕中心
{
//锁定鼠标后再解锁,鼠标将自动回到屏幕中心
Cursor.lockState = CursorLockMode.Locked;
Cursor.lockState = CursorLockMode.None;
Cursor.visible = false; //隐藏鼠标
}
void View() // 控制视角
{
SetCursorToCentre(); //锁定鼠标到屏幕中心
float mouseX = Input.GetAxis("Mouse X") * Time.deltaTime * angularSpeed* horizontalRotateSensitivity;
transform.parent.Rotate(Vector3.up * mouseX); // 水平旋转
float mouseY = Input.GetAxis("Mouse Y") * Time.deltaTime * angularSpeed * verticalRotateSensitivity;
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -45f, 45f); // 限制垂直视角
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f); // 弩上下移动
}
void aimFocus()
{
if (Input.GetKey(KeyCode.Q))
{
if (!isAiming)
{
isAiming = true; // 设置瞄准状态
Camera.main.fieldOfView = 20; // 设置瞄准视野
}
}
else
{
if (isAiming)
{
isAiming = false;
Camera.main.fieldOfView = 40;
}
}
}
void transport() // 传送到指定的射击位置
{
if (Input.GetKeyDown(KeyCode.Alpha1)) // 静态靶子区域
{
canshoot = true;
transform.parent.rotation = Quaternion.Euler(0, 0, 0);
transform.parent.position = new Vector3(120, 1, 70);
RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial1");
}
if (Input.GetKeyDown(KeyCode.Alpha2)) // 动态靶子区域
{
canshoot = true;
transform.parent.rotation = Quaternion.Euler(0, 180, 0);
transform.parent.position = new Vector3(-120, 1, -70);
RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial2");
}
if (Input.GetKeyDown(KeyCode.Alpha3)) // 静态大靶子区域
{
canshoot = true;
transform.parent.rotation = Quaternion.Euler(0, 0, 0);
transform.parent.position = new Vector3(-120, 1, 70);
RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial3");
}
if (Input.GetKeyDown(KeyCode.Alpha4)) // 打鸟区域
{
canshoot = true;
transform.parent.rotation = Quaternion.Euler(0, 180, 0);
transform.parent.position = new Vector3(120, 1, -70);
RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial4");
}
if (Input.GetKeyDown(KeyCode.Alpha5)) // 切换天空盒和解除射击锁定状态
{
canshoot = true;
RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial5");
}
if (Input.GetKeyDown(KeyCode.B)) // 回到原始位置
{
canshoot = false;
transform.parent.position = new Vector3(0, 1, -8);
}
}
}

6045

被折叠的 条评论
为什么被折叠?



