本周的作业是写一个鼠标打飞碟的游戏,具体的实现用工厂模式和前两次作业中用到的MVC模式来实现,从而实现人机交互与游戏模型分离的作用。
游戏总共有3个回合,每个回合的难度依次递增(虽然这次游戏设计的回合有点傻.....飞碟出现的速度也比较慢,轨道比较单一),但是最主要的作用是通过这次作业来充分了解工厂模式,和更加熟练的操作以前的MVC模式。
主要涉及到的新知识如下:
创建空对象并添加组件的方法如下:
new GameObject();
new GameObject(string name);
new GameObject(string name, params Type[] components);
创建基础类型游戏对象的方法如下:
GameObject CreatePrimitive(PrimitiveType type);
从已知对象或预制克隆:
Instantiate(brick, new Vector3(x,y,0),Quaterinion.identity);
检查一个对象是否拥有数据属性
GetComponent()
游戏工厂模式设计
工厂模式的UML图如下所示:
下面就是本次游戏的代码,头几个类基本上都是照搬前几次作业的类,工厂和飞碟等几个类则是参照老师的PPT,关键的场记和动作则是参照老师给的优秀博客的师兄的代码:师兄博客传送门
导演类的代码:
public class Director : System.Object {
public ISceneController currentSceneController { get; set; }
private static Director director;
private Director() {}
public static Director getInstance() {
if (director == null) {
director = new Director();
}
return director;
}
}
实现单实例的模版代码:
public class Singleton : 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;
}
}
}
记分器代码:
public class ScoreRecorder : MonoBehaviour {
private int Score;
private Dictionary ScoreDictionary = new Dictionary();
void Start() {
Score = 0;
ScoreDictionary.Add(Color.yellow,1);
ScoreDictionary.Add(Color.red,2);
ScoreDictionary.Add(Color.black,4);
}
public void Record(GameObject disk) {
Score += ScoreDictionary[disk.GetComponent().getDiskColor()];
Score += 2;
}
public void MinRecord() {
Score -= 2;
}
public int getScore(){
return Score;
}
public void Reset() {
Score = 0;
}
}
动作基类代码:
public class SSAction : ScriptableObject {
public bool enable = false;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }
protected SSAction() {}
public virtual void Start () {
throw new System.NotImplementedException();
}
// Update is called once per frame
public virtual void Update () {
throw new System.NotImplementedException();
}
public void reset() {
enable = false;
destroy = false;
gameobject = null;
transform = null;
callback = null;
}
}
动作管理器代码:
public class SSActionManager : MonoBehaviour {
private Dictionary actions = new Dictionary();
private List waitingAdd = new List();
private List waitingDelete = new List();
// Use this for initialization
protected void Start() {}
// Update is called once per frame
protected void Update() {
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair kv in actions) {
SSAction ac = kv.Value;
if (ac.destroy) {
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.enable) {
ac.Update();
}
}
foreach (int key in waitingDelete) {
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
}
UI代码
public class UserGUI : MonoBehaviour {
private IUserAction action;
public CCActionManager actionManager;
GUIStyle style;
void Start() {
action = Director.getInstance().currentSceneController as IUserAction;
}
private void OnGUI() {
style = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
if (Input.GetButtonDown("Fire1")) {
Vector3 position = Input.mousePosition;
action.hit(position);
}
GUI.Button(new Rect(Screen.width / 15, Screen.height / 15, 140, 70), "Score: " + action.getScore().ToString(), style);
GUI.Button(new Rect(Screen.width / 15, Screen.height / 7, 140, 70), "Round: " + action.getCurrentRound().ToString(), style);
GUI.Label(new Rect(Screen.width / 2 + 500, Screen.height / 15, 180, 110), "Rule:\nYellow: 1 point\nRed: 2 points\nBlack: 3 points\nDrop: -2 points\nFail: Score < 0");
//µ±ÓÎÏ·»¹Ã»¿ªÊ¼£¬»òÕßÓÎÏ·ÒѾ½áÊø£¬¿ÉÒÔÑ¡Ôñ¿ªÊ¼
if (!action.checkWhetherIsStart() && GUI.Button(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 55, 180,110),"Start")) {
action.setGameStart();
action.setGameState(GameState.ROUND_START);
}
//µ±ÓÎϷûÓнáÊø£¬²¢ÇÒÓÎÏ·ÒѾ¿ªÊ¼£¬²¢ÇÒµ±Ç°»ØºÏÒѾ½áÊø£¬³öÏָð´Å¥£¬Ñ¡Ôñ½øÈëÏÂÒ»»ØºÏ
if (!action.isGameOver() && action.checkWhetherIsStart() && action.getGameState() == GameState.ROUND_FINISH &&
GUI.Button(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 55, 180,110), "Next Round")) {
action.setFirstCurrent();
action.setGameState(GameState.ROUND_START);
}
}
}
界面与场景控制器的接口代码:
public enum GameState { GAME_START, ROUND_START, ROUND_FINISH, GAME_RUNNING, GAME_OVER}
public interface IUserAction {
int getScore();
int getCurrentRound();
void setFirstCurrent();
void setGameState(GameState _gameState);
void setGameStart();
void hit(Vector3 pos);
bool checkWhetherIsStart();
bool isGameOver();
GameState getGameState();
}
动作与动作管理器之间通信的接口代码:
public enum SSActionEventType:int { Started, Competeted }
public interface ISSActionCallback {
void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
Object objectParam = null);
}
管理具体动作的动作管理器CCActionManager代码:
public class CCActionManager : SSActionManager, ISSActionCallback {
public FirstSceneController sceneController;
public List Fly;
private int DiskNumber = 0;
private List Used = new List();
private List Free = new List();
public void setDiskNumber(int _diskNumber) {
DiskNumber = _diskNumber;
}
public int getDiskNumber() {
return DiskNumber;
}
//GetSSAction,首先从工厂里面找,如果没有的话就创造
SSAction GetSSAction() {
SSAction action = null;
if (Free.Count > 0) {
action = Free[0];
Free.Remove(Free[0]);
} else {
action = ScriptableObject.Instantiate(Fly[0]);
}
Used.Add(action);
return action;
}
//FreeSSAction
public void FreeSSAction(SSAction action) {
SSAction temp = null;
foreach (SSAction disk in Used) {
if (action.GetInstanceID() == disk.GetInstanceID()) {
temp = disk;
}
}
if (temp != null) {
temp.reset();
Free.Add(temp);
Used.Remove(temp);
}
}
protected new void Start() {
sceneController = (FirstSceneController)Director.getInstance().currentSceneController;
sceneController.actionManager = this;
Fly.Add(CCFlyAction.GetSSAction());
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int Param = 0,
string strParam = null,
UnityEngine.Object objectParam = null) {
if (source is CCFlyAction) {
DiskNumber--;
DiskFactory factory = Singleton.Instance;
factory.FreeDisk(source.gameobject);
FreeSSAction(source);
}
}
public void StartThrow(Queue diskQueue) {
foreach (GameObject temp in diskQueue) {
//if (GetSSAction() != null)
RunAction(temp, GetSSAction(), (ISSActionCallback)this);
}
}
}
飞碟的动作代码:
public class CCFlyAction : SSAction {
public ScoreRecorder scoreRecorder { get; set; }
private float acceleration = 3.0f;
private float horizontalSpeed;
private float lowerBound = -4;
private float flyTime;
private Vector3 direction;
public override void Start(){
enable = true;
flyTime = 0;
horizontalSpeed = gameobject.GetComponent().getDiskSpeed();
direction = gameobject.GetComponent().getDiskDirection();
scoreRecorder = Singleton.Instance;
}
public override void Update(){
if (gameobject.activeSelf) {
flyTime += Time.deltaTime;
transform.Translate(Vector3.down * acceleration * flyTime * Time.deltaTime);
transform.Translate(direction * horizontalSpeed * Time.deltaTime);
if(checkWhetherShouldRecycle()){
scoreRecorder.MinRecord();
}
}
}
private bool checkWhetherShouldRecycle() {
if (this.transform.position.y < lowerBound){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
return true;
}
return false;
}
public static CCFlyAction GetSSAction(){
CCFlyAction action = ScriptableObject.CreateInstance();
return action;
}
}
飞碟的数据代码:
public class DiskData : MonoBehaviour {
private Vector3 disk_size;
private Vector3 disk_direction;
private Color disk_color;
private float disk_speed;
public void setDiskSize(Vector3 _size) { disk_size = _size; }
public void setDiskColor(Color _color) { disk_color = _color; }
public void setDiskSpeed(float _speed) { disk_speed = _speed; }
public void setDiskDirection(Vector3 _direction) { disk_direction = _direction; }
public Vector3 getDiskSize() { return disk_size; }
public Vector3 getDiskDirection() { return disk_direction; }
public Color getDiskColor() { return disk_color; }
public float getDiskSpeed() { return disk_speed; }
}
飞碟工厂的代码:
public class DiskFactory : MonoBehaviour {
public GameObject Disk_Product;
//Used用来保存被激活的飞碟,Free用来保存空闲的飞碟
private List Used = new List();
private List Free = new List();
//Awake
private void Awake() {
Disk_Product = GameObject.Instantiate(Resources.Load("Prefabs/DiskModel"),
Vector3.zero, Quaternion.identity);
Disk_Product.SetActive(false);
}
//GetDisk
public GameObject GetDisk(int Game_Round) {
GameObject NewDiskProduct = null;
if(Free.Count > 0) {
NewDiskProduct = Free[0].gameObject;
Free.Remove(Free[0]);
} else {
NewDiskProduct = GameObject.Instantiate(Disk_Product, Vector3.zero,
Quaternion.identity);
NewDiskProduct.AddComponent();
}
//控制飞碟产生的频率
int From = 0;
if (Game_Round == 1) {
From = 100;
}
if (Game_Round == 2) {
From = 250;
}
int TheDiskColor = Random.Range(From, Game_Round*499);
if (TheDiskColor > 500) {
Game_Round = 2;
} else if (TheDiskColor > 300) {
Game_Round = 1;
} else {
Game_Round = 0;
}
//根据回合控制生成飞碟的属性
if (Game_Round == 0) {
setDiskProp(NewDiskProduct,Color.yellow,2.0f);
} else if (Game_Round == 1) {
setDiskProp(NewDiskProduct,Color.red,3.0f);
} else if (Game_Round == 2) {
setDiskProp(NewDiskProduct,Color.black,4.0f);
}
Used.Add(NewDiskProduct.GetComponent());
NewDiskProduct.name = NewDiskProduct.GetInstanceID().ToString();
return NewDiskProduct;
}
//setDiskProp.
public void setDiskProp(GameObject Disk, Color _Color, float _Speed) {
Disk.GetComponent().setDiskColor(_Color);
Disk.GetComponent().setDiskSpeed(_Speed);
float RanX = UnityEngine.Random.Range(-1.0f,1.0f) < 0 ? -1 : 1;
Disk.GetComponent().setDiskDirection(new Vector3(RanX, 1, 0));
Disk.GetComponent().material.color = _Color;
}
//FreeDisk.
public void FreeDisk(GameObject Disk) {
DiskData temp = null;
foreach (DiskData disk in Used) {
if (Disk.GetInstanceID() == disk.gameObject.GetInstanceID()) {
temp = disk;
}
}
if (temp != null) {
temp.gameObject.SetActive(false);
Free.Add(temp);
Used.Remove(temp);
}
}
}
场记代码,管理具体的场景的控制器:
public interface ISceneController {}
public class FirstSceneController : MonoBehaviour, ISceneController, IUserAction {
public CCActionManager actionManager { get; set; }
public ScoreRecorder scoreRecorder { get; set; }
private bool isStart = false;
private bool FirstCurrent = true;
GUIStyle gameOverStyle;
public Queue diskQueue = new Queue();
private int RoundCount = 1;
private int diskNumber;
private int currentRound = 0;
UserGUI IUserGUI;
public int maxRound = 3;
private float diskGapTime = 0;
private GameState gameState = GameState.GAME_START;
void Awake () {
Director director = Director.getInstance();
director.currentSceneController = this;
diskNumber = 10;
this.gameObject.AddComponent();
this.gameObject.AddComponent();
scoreRecorder = Singleton.Instance;
IUserGUI = gameObject.AddComponent() as UserGUI;
}
private void Start() {
gameOverStyle = new GUIStyle();
gameOverStyle.fontSize = 40;
gameOverStyle.alignment = TextAnchor.MiddleCenter;
diskNumber = 10;
}
private void Update() {
if (gameState == GameState.ROUND_START && isStart){
Debug.Log("Hello");
if (actionManager.getDiskNumber() == 0) {
actionManager.setDiskNumber(10);
NextRound();
gameState = GameState.GAME_RUNNING;
}
if (isStart) {
currentRound = (currentRound + 1) % maxRound;
if (!FirstCurrent) RoundCount = (RoundCount + 1) % maxRound;
}
}
if (actionManager.getDiskNumber() == 0 && gameState == GameState.GAME_RUNNING) {
isGameOver();
gameState = GameState.ROUND_FINISH;
Debug.Log("In Two!");
}
if (diskGapTime > 1.5f){
if (diskQueue.Count != 0) {
GameObject disk = diskQueue.Dequeue();
Vector3 position = new Vector3(0, 0, 0);
float y = UnityEngine.Random.Range(0f, 4f);
position = new Vector3(-disk.GetComponent().getDiskDirection().x * 7, y, 0);
disk.transform.position = position;
disk.SetActive(true);
}
diskGapTime = 0;
}
else{
diskGapTime += Time.deltaTime;
}
}
private void OnGUI(){
if(isGameOver()){
GUI.Label(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 200, 180, 130), "Game Over!", gameOverStyle);
}
}
/*µ±·ÖÊýСÓÚ0£¬ÓÎÏ·½áÊø*/
public bool isGameOver(){
if (scoreRecorder.getScore() < 0) {
//scoreRecorder.Reset();
gameState = GameState.GAME_OVER;
isStart = false;
return true;
}
return false;
}
private void NextRound() {
DiskFactory df = Singleton.Instance;
for (int i = 0; i < diskNumber; i++) {
diskQueue.Enqueue(df.GetDisk(currentRound));
}
actionManager.StartThrow(diskQueue);
}
/*SetÀà·½·¨*/
public void setGameStart() {
scoreRecorder.Reset();
isStart = true;
}
public bool checkWhetherIsStart() {
return isStart;
}
public void setGameState(GameState _gameState) {
gameState = _gameState;
}
public void setFirstCurrent(){
FirstCurrent = false;
}
/*GetÀà·½·¨*/
public int getCurrentRound(){
return RoundCount ;
}
public GameState getGameState(){
return gameState;
}
public int getScore() {
return scoreRecorder.getScore();
}
/*ÉäÏß*/
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];
Physics.Raycast(ray, out hit);
Debug.DrawLine(ray.origin, hit.point);
if (hit.collider.gameObject.GetComponent() != null) {
scoreRecorder.Record(hit.collider.gameObject);
hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
//DiskFactory df = Singleton.Instance;
//df.FreeDisk(hit.collider.gameObject);
}
}
}
}
最后实现的效果如下图所示:
可以参照演示视频:演示视频