游戏名:飞碟(Hit UFO)游戏
- 游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
- 游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
游戏效果预览图:
一,飞碟的基本参数初始化
根据实验要求,飞碟应具有如下参数:飞碟的尺寸大小、颜色、运动速度、出现位置和运动方向。实现中将这些参数初始化于名为UFO的namespace中:
//UFO的基本参数
namespace UFO {//初始化UFO所需参数
public class Disk : MonoBehaviour {
public float size; //大小
public Color color; //颜色
public float speed; //速度
public Vector3 position; //初始位置
public Vector3 direction; //运动方向
}
}
二,飞碟的生产、回收与参数设置
根据实验要求,在此处使用了工厂模式,借此管理不同飞碟的生产与回收。
需要注意的是,在调整飞碟参数时需要反复启动项目,确保游戏运行正常;
在直接修改参数前,需要注意飞碟所受外力与速度、质量的关系。
//设置飞碟参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
// 工厂模式
public class DiskFactory : MonoBehaviour {
private List<Disk> toDelete = new List<Disk>();//生成后,先放进Delete队列,等待发射
private List<Disk> toUse = new List<Disk>();
// 可选6种颜色
public Color[] colors = {Color.white, Color.yellow, Color.red, Color.blue, Color.green, Color.black};
public GameObject GetDisk(int round) {
//根据回合数对飞碟设置属性并返回
GameObject newDisk = null;
if (toUse.Count > 0)
{
//队列toUse中有飞碟,则弹出一个
newDisk = toUse[0].gameObject;
toUse.Remove(toUse[0]);
}
else
{
//队列toUse中没有飞碟,则加载一个UFO模型
newDisk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"), Vector3.zero, Quaternion.identity);
newDisk.AddComponent<Disk>();
}
// 飞碟的速度
newDisk.GetComponent<Disk>().speed = 2.0f * round + 4.3f;
// 飞碟尺寸:随 round 越来越小
newDisk.GetComponent<Disk>().size = (1.0f - round * 0.01f);
// 飞碟颜色随机
int color = UnityEngine.Random.Range(0, 6);
newDisk.GetComponent<Disk>().color = colors[color];
// 飞碟的发射方向
float RanX = UnityEngine.Random.Range(-3f, 3f);
float RanY = UnityEngine.Random.Range(1.1f, 1.3f);
float RanZ = UnityEngine.Random.Range(-0.4f, 0.4f);
newDisk.GetComponent<Disk>().direction = new Vector3(-RanX, RanY, RanZ);
// 飞碟的初始位置
RanX = RanX * UnityEngine.Random.Range(4.5f, 6f);
RanY = 2.6f - RanY;
RanZ = -RanZ * 2;
newDisk.GetComponent<Disk>().position = new Vector3(RanX, RanY, RanZ);
//飞碟在发射完之后放入待删除队列
toDelete.Add(newDisk.GetComponent<Disk>());
newDisk.SetActive(false);
newDisk.name = newDisk.GetInstanceID().ToString();
return newDisk;
}
public void FreeDisk(GameObject disk) {
Disk cycledDisk = null;//每次只处理一个飞碟
foreach (Disk toCycle in toDelete) {
if (disk.GetInstanceID() == toCycle.gameObject.GetInstanceID()) {
cycledDisk = toCycle;//赋值过去给处理
}
}
if (cycledDisk != null) {
cycledDisk.gameObject.SetActive(false);
toUse.Add(cycledDisk);
toDelete.Remove(cycledDisk);
}
}
}
三,飞碟的运动管理
飞碟进入处理队列,开始运动时,就需要运动的管理。鼠标的点击命中了飞碟时,分数(在后文会定义)增加。代码实现如下:
//飞碟的运动管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
public class ActionManager : MonoBehaviour {
public Vector3 direction; // 运动方向
public float speed; // 初速度
public GameObject cam;
public void diskFly(Vector3 direction,float speed) { // 赋予飞碟初速度和方向
this.direction = direction;
this.speed = speed;
}
void Start() {
cam = GameObject.Find("Main Camera");
}
//每一帧都检测是否点击到了物体
void Update() {
this.gameObject.transform.position += speed * direction * Time.deltaTime;
if (Input.GetButtonDown("Fire1")) { // 鼠标集中飞碟的结果
Debug.Log("Fired Pressed");
Debug.Log(Input.mousePosition);
Vector3 mp = Input.mousePosition;
Camera ca;
if (cam != null)
ca = cam.GetComponent<Camera>();
else
ca = Camera.main;
Ray ray = ca.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit hit in hits) {
print(hit.transform.gameObject.name);
Debug.Log("hit " + hit.collider.gameObject.name + "!");
if(hit.collider.gameObject.name == "Sphere")
{
Director.getInstance().currentSceneController.getSceneController().addHit(); // 击中数加一
}
Singleton<DiskFactory>.Instance.FreeDisk(hit.transform.gameObject); // 飞碟消失
//Debug.Log(Director.getInstance().currentSceneController.getSceneController().getScore());
Director.getInstance().currentSceneController.getSceneController().addScore(); // 分数加一
continue;
}
}
}
}
四,游戏全局的控制
根据实验要求,在此处使用场景单实例模式,用于游戏全局的管理
//用于控制全局的controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
//运用场景单实例(Singleton)的方法
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
public static T Instance {
get {
if (instance == null) {
instance = (T)FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError ("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
}
}
return instance;
}
}
}
//其中的ISceneController继承于BaseCode中,用于加载场景中的UFO
public class FirstSceneController : MonoBehaviour, ISceneController {
public int diskFlyTimes; // 已经发射的飞碟个数,每回合10个,最多30个
public float time; // 时间,用于控制飞碟发射间隔
public int round; // 当前回合数
public Queue<GameObject> diskQueue = new Queue<GameObject>(); // 飞碟队列
public SceneController sceneCtrl;//
void Start() {
// 当前场景控制器
Director.getInstance().currentSceneController = this;
this.gameObject.AddComponent<DiskFactory>();
this.gameObject.AddComponent<UserGUI>();
Director.getInstance().currentSceneController.Init(); // 初始化FirstSceneController相关数据
}
// 初始化每个回合的飞碟队列,每个回合的飞碟属性不同,总数都为10个
void initQueue() {
for(int i = 0; i < 10; i++)
diskQueue.Enqueue(Singleton<DiskFactory>.Instance.GetDisk(round));
}
void Update() {
// round = sceneCtrl.getRound();
time += Time.deltaTime;
// 发射飞碟的间隔回合数成反比
if(time >= 2.0f-0.25*round) {
if(diskFlyTimes >= 30) { //游戏结束
//Invoke("Reset", 1.5f);
Reset();
} else if ((diskFlyTimes % 10) == 0 ) { //更新回合(此步骤必须在发射飞碟前面)
round++; //在initQueue()之前
sceneCtrl.addRound(); //回合数增加
initQueue(); //初始化新的飞盘队列
}
if (diskFlyTimes < 30) {
time = 0;
ThrowDisk(); //发射飞盘
diskFlyTimes++; //飞盘数增加
sceneCtrl.addTotal(); //综费盘数增加
}
}
}
public void ThrowDisk() {
if(diskQueue.Count > 0) {
GameObject disk = diskQueue.Dequeue();
disk.GetComponent<Renderer>().material.color = disk.GetComponent<Disk>().color;
disk.transform.position = disk.GetComponent<Disk>().position;
disk.transform.localScale = disk.GetComponent<Disk>().size * disk.transform.localScale;
disk.SetActive(true);
disk.AddComponent<ActionManager>();
disk.GetComponent<ActionManager>().diskFly(disk.GetComponent<Disk>().direction, disk.GetComponent<Disk>().speed);
}
}
public void Init() {//声明于接口ISceneController中
time = 0;
diskFlyTimes = 0;
round = 0;
diskQueue.Clear(); //清空飞盘队列
sceneCtrl = new SceneController(); //SceneController元素归0
}
public SceneController getSceneController() { //返回SceneController
return sceneCtrl;
}
void Reset() { //游戏重置。该函数会在飞碟数大于30时由update()调用,修改UserGUI中的参数reset
this.gameObject.GetComponent<UserGUI>().reset = 1;
}
}
五,游戏得分统计
游戏得分的本质即为有多少次点击命中了飞碟。在该段代码中,仅定义了方法,而具体调用则由前文的三和后文的六调用。
//专门用于定义方法,控制分数变化的controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
//用于记录分数、回合等信息
public class SceneController {
public int round; // 回合数
public int total ; // 总飞碟数
public int score ; // 得到的分数
public int hit;
private static SceneController sceneCtrl;
public SceneController() { // 用于SceneController归0
score = 0;
total = 0;
score = 0;
hit = 0;
}
public static SceneController getInstance() {
if (sceneCtrl == null) {
sceneCtrl = new SceneController();
}
return sceneCtrl;
}
public void addRound() {
round++;
}
public void addHit()
{
hit++;
}
public void addTotal() {
total++;
}
public void addScore() {
score++;
}
public int getRound() {
return round;
}
public int getHit()
{
return hit;
}
public int getTotal() {
return total;
}
public int getScore() {
return score;
}
}
六,用户交互界面
这段代码主要负责与用户交互的一切行为,包括显示分数、游戏重新开始等。
//用户交互界面,主要负责分数和按钮显示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
public class UserGUI : MonoBehaviour {
public int reset;//记录是否需要重置游戏
GUIStyle style;
GUIStyle buttonStyle;
void Start() {
reset = 0;
style = new GUIStyle();
style.fontSize = 45;
style.normal.textColor = Color.green;
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 40;
buttonStyle.normal.textColor = Color.green;
}
void Update() {}
private void OnGUI() {
if(reset == 1) {
if(GUI.Button(new Rect(280, 200, 150, 90), "Reset", buttonStyle)) {
Director.getInstance().currentSceneController.Init();
reset = 0;
}
}
int round = Director.getInstance().currentSceneController.getSceneController().getRound();
int total = Director.getInstance().currentSceneController.getSceneController().getTotal();
int score = Director.getInstance().currentSceneController.getSceneController().getScore();
int hit = Director.getInstance().currentSceneController.getSceneController().getHit();
string text = "Round: " + round.ToString() + "\nTotal: " + total.ToString() + "\nHit: " + hit.ToString() + "\nScores: " + score.ToString() ;
GUI.Label(new Rect(10, 10, Screen.width, 50),text,style);
}
}
七,游戏游玩实录与源文件
注:我的录制软件没法录入鼠标,因此点击效果并不明显。
视频链接:hit the UFO_哔哩哔哩_bilibili
源文件如下:3d-game-design