Game-unity hw5
文章目录
作业要求
编写一个简单的鼠标打飞碟(Hit UFO)游戏
- 游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
- 游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
游戏设计
游戏一共5轮,每一轮生成10个飞碟,难度随着轮次增加。
一共有三种颜色的飞碟,飞行速度从慢到快:黄色、红色、黑色,击中得到的分数分别是10,20,40。
第一轮只有黄色;第二轮有红色和黄色,红色出现的概率是25%;第三轮也是有红色和黄色,红色出现 概率是50%;第四轮有黄色、红色和黑色,红色出现概率是3/8,黑色出现的概率是25%;第五轮也是有 三种颜色,红色出现的概率30%,黑色出现的概率40%。
五轮结束后展示总共得分。
代码设计
动作管理
CCFlyAction.cs
该脚本主要实现飞碟的飞行动作:抛物线运动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCFlyAction : SSAction
{
/**
* acceleration是重力加速度,为9.8
*/
float acceleration;
/**
* horizontalSpeed是飞碟水平方向的速度
*/
float horizontalSpeed;
/**
* direction是飞碟的初始飞行方向
*/
Vector3 direction;
/**
* time是飞碟已经飞行的时间
*/
float time;
public override void Start()
{
enable = true;
acceleration = 9.8f;
time = 0;
horizontalSpeed = gameobject.GetComponent<DiskObj>().speed;
direction = gameobject.GetComponent<DiskObj>().direction;
}
// Update is called once per frame
public override void Update()
{
if (gameobject.activeSelf)
{
/**
* 计算飞碟的累计飞行时间
*/
time += Time.deltaTime;
/**
* 飞碟在竖直方向的运动
*/
transform.Translate(Vector3.down * acceleration * time * Time.deltaTime);
/**
* 飞碟在水平方向的运动
*/
transform.Translate(direction * horizontalSpeed * Time.deltaTime);
/**
* 当飞碟的y坐标比-4小时,飞碟落地
*/
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
public static CCFlyAction GetSSAction()
{
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
return action;
}
}
CCActionManager.cs
该脚本是动作管理器,每一轮把10个飞碟的飞行动作储存起来(体现在StartThrow函数)。
![1](D:\课件\3D游戏\5\img\1.png)using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback
{
public FirstSceneControl sceneController;
public List<CCFlyAction> Fly;
public int DiskNumber = 0;
/**
* used是用来保存正在使用的动作
* free是用来保存还未被激活的动作
*/
private List<SSAction> used = new List<SSAction>();
private List<SSAction> free = new List<SSAction>();
/**
* GetSSAction这个函数是用来获取CCFlyAction这个动作的,
* 每次首次判断free那里还有没有未使用的CCFlyActon这个动作,
* 有就从free那里获取,没有就生成一个CCFlyAction
*/
SSAction GetSSAction()
{
SSAction action = null;
if (free.Count > 0)
{
action = free[0];
free.Remove(free[0]);
}
else
{
action = ScriptableObject.Instantiate<CCFlyAction>(Fly[0]);
}
used.Add(action);
return action;
}
public void FreeSSAction(SSAction action)
{
SSAction tmp = null;
foreach (SSAction i in used)
{
if (action.GetInstanceID() == i.GetInstanceID())
{
tmp = i;
}
}
if (tmp != null)
{
tmp.reset();
free.Add(tmp);
used.Remove(tmp);
}
}
protected void Start()
{ Debug.Log("Start");
sceneController = (FirstSceneControl)Director.getInstance().currentSceneControl;
sceneController.actionManager = this;
Fly.Add(CCFlyAction.GetSSAction());
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
UnityEngine.Object objectParam = null)
{
if (source is CCFlyAction)
{
DiskNumber--;
DiskFactory df = Singleton<DiskFactory>.Instance;
df.FreeDisk(source.gameobject);
FreeSSAction(source);
}
}
public void StartThrow(Queue<GameObject> diskQueue)
{
foreach (GameObject tmp in diskQueue)
{
//RunAction用于初始化动作。
RunAction(tmp, GetSSAction(), (ISSActionCallback)this);
}
}
}
对象管理
首先创建一个扁平圆柱体作为飞碟的预制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNwD0SZ2-1603811973266)(./img/1.png)]
飞碟的游戏对象为DiskObj,其声明如下:
public class DiskObj : MonoBehaviour {
public Vector3 size;
public Color color;
public float speed;
public Vector3 direction;
}
DiskFactory.cs
DiskFactory为一个带缓存的工厂,用于生产和回收飞碟。
分别声明一个名为used和一个名为free的Diskobj游戏对象序列,used储存即将飞出的飞碟,free储存已被击中或未被击中而掉落地面的飞碟。
以生成飞碟的函数GetDisk为例,如果free序列有空闲飞碟,则直接将其放入待用飞碟序列,若无,则根据轮次生成不同颜色的飞碟并加入到used序列。
public GameObject GetDisk(int round)
{
GameObject newDisk = null;
//若有闲置的飞碟,则将其放入待用飞碟序列
if (free.Count > 0)
{
newDisk = free[0].gameObject;
free.Remove(free[0]);
}
else
{
newDisk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
newDisk.AddComponent<DiskObj>();
}
//根据轮数生成飞碟
int selectedColor = Random.Range(0, round * 200);
if (selectedColor > 600)
{
round = 2;
}
else if (selectedColor > 300)
{
round = 1;
}
else
{
round = 0;
}
switch (round)
{
case 0:
{
newDisk.GetComponent<DiskObj>().color = Color.yellow;
newDisk.GetComponent<DiskObj>().speed = 4.0f;
float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent<Renderer>().material.color = Color.yellow;
break;
}
case 1:
{
newDisk.GetComponent<DiskObj>().color = Color.red;
newDisk.GetComponent<DiskObj>().speed = 6.0f;
float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent<Renderer>().material.color = Color.red;
break;
}
case 2:
{
newDisk.GetComponent<DiskObj>().color = Color.black;
newDisk.GetComponent<DiskObj>().speed = 7.0f;
float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent<Renderer>().material.color = Color.black;
break;
}
}
used.Add(newDisk.GetComponent<DiskObj>());
newDisk.name = newDisk.GetInstanceID().ToString();
return newDisk;
}
Singleton.cs
由于按照要求,工厂必须是场景单实例的,所以需要一个Singleton
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
}
}
}
场景管理
FirstSceneControl.cs
本游戏只有一个场景,因此只需要要一个场景管理器。
场景管理器需要记录当前游戏的状态(一轮开始、一轮结束、一轮正在进行、游戏暂停、游戏开始、游戏结束)。每当一轮开始,通知动作管理器存入十个飞碟的飞出动作。每过1秒飞出一个飞碟。并且设置以鼠标点击位置发出射线,与飞碟碰撞则表明击中飞碟。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneControl
{
void LoadResources();
}
public class FirstSceneControl : MonoBehaviour ,ISceneControl , IUserAction
{
public CCActionManager actionManager { get; set; }
public ScoreRecorder scoreRecorder { get; set; }
public Queue<GameObject> diskQueue = new Queue<GameObject>();
private int diskNumber;
private int currentRound = 0;
public int round;
private float time = 0;
private GameState gameState = GameState.START;
void Awake()
{
Director director = Director.getInstance();
director.currentSceneControl = this;
diskNumber = 1;
round = 5;
this.gameObject.AddComponent<ScoreRecorder>();
this.gameObject.AddComponent<DiskFactory>();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
//director.currentSceneControl.LoadResources();
}
private void Update()
{
if (actionManager.DiskNumber == 0 && gameState == GameState.RUNNING)
{
gameState = GameState.ROUND_FINISH;
if(currentRound==5){
gameState = GameState.END;
}
}
if (actionManager.DiskNumber == 0 && gameState == GameState.ROUND_START)
{
currentRound = currentRound + 1;
NextRound();
actionManager.DiskNumber = 1;
gameState = GameState.RUNNING;
}
if (time > 1)
{
ThrowDisk();
time = 0;
}
else
{
time += Time.deltaTime;
}
}
private void NextRound()
{
DiskFactory df = Singleton<DiskFactory>.Instance;
for (int i = 0; i < diskNumber; i++)
{
diskQueue.Enqueue(df.GetDisk(currentRound));
}
actionManager.StartThrow(diskQueue);
}
void ThrowDisk()
{
if (diskQueue.Count != 0)
{
GameObject disk = diskQueue.Dequeue();
/**
* 以下几句代码是随机确定飞碟出现的位置
*/
Vector3 position = new Vector3(0, 0, 0);
float y = UnityEngine.Random.Range(0f, 3f);
position = new Vector3(-disk.GetComponent<DiskObj>().direction.x * 7, y, 0);
disk.transform.position = position;
disk.SetActive(true);
}
}
public void LoadResources()
{
//DiskFactory df = Singleton<DiskFactory>.Instance;
//df.init(diskNumber);
//GameObject greensward = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/greensward"));
}
public void GameOver()
{
GUI.color = Color.red;
GUI.Label(new Rect(700, 300, 400, 400), "GAMEOVER");
}
public int GetRound()
{
return currentRound;
}
public int GetScore()
{
return scoreRecorder.score;
}
public GameState getGameState()
{
return gameState;
}
public void setGameState(GameState gs)
{
gameState = gs;
}
public void hit(Vector3 pos)
{
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray);
for (int i = 0; i < hits.Length; i++)
{
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<DiskObj>() != null)
{
scoreRecorder.Record(hit.collider.gameObject);
/**
* 如果飞碟被击中,那么就移到地面之下,由工厂负责回收
*/
hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
}
}
}
}
交互管理
UserGUI.cs
用于用户交互,游戏一开始需要玩家点击Start按钮开始游戏,每一轮需要点击Next Round进入下一轮,在游戏进行时显示实时得分和轮数,当游戏结束显示得分。
这里只放出OnGUI函数
private void OnGUI()
{
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.hit(pos);
}
GUIStyle myStyle = new GUIStyle();
myStyle.fontSize = 20;
GUI.Label(new Rect(5, 5, 200, 200), "Score:" + action.GetScore().ToString(), myStyle);
GUI.Label(new Rect(5, 50, 200, 200), "Round:" + action.GetRound().ToString(), myStyle);
if (isFirst && GUI.Button(new Rect(360, 150, 90, 50), "Start"))
{
isFirst = false;
action.setGameState(GameState.ROUND_START);
}
if (!isFirst && action.getGameState() == GameState.ROUND_FINISH && GUI.Button(new Rect(350, 150, 120, 60), "Next Round"))
{
action.setGameState(GameState.ROUND_START);
}
if (action.getGameState()==GameState.END)
{
myStyle.fontSize = 40;
GUI.Label(new Rect(220, 150, 400, 200), "GameOver!Score:" + action.GetScore().ToString(), myStyle);
}
}
得分管理
ScoreRecorder.cs
用于计分的脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : MonoBehaviour
{
/**
* score是玩家得到的总分
*/
public int score;
/**
* scoreTable是一个得分的规则表,每种飞碟的颜色对应着一个分数
*/
private Dictionary<Color, int> scoreTable = new Dictionary<Color, int>();
// Use this for initialization
void Start()
{
score = 0;
scoreTable.Add(Color.yellow, 10);
scoreTable.Add(Color.red, 20);
scoreTable.Add(Color.black, 40);
}
public void Record(GameObject disk)
{
score += scoreTable[disk.GetComponent<DiskObj>().color];
}
public void Reset()
{
score = 0;
}
}
游戏运行结果