Hit Disk
1. 游戏内容与要求:
- 游戏有n个round,每个round包括10次trial
- 每个trial的飞碟色彩、大小,发射位置,速度、角度、同时出现的个数可能不同。它们由该round的ruler控制
- 每个trial的飞碟具有随机性,总体难度随round上升
- 鼠标点中得分,得分规则按照色彩、大小、速度不同计算,规则可以自由设定
- 使用带缓存的工厂管理模式管理不同的飞碟的生产与回收,该工厂必须是场景单实例的
- 尽可能使用MVC结构实现人机交互与游戏模型分离
2. 游戏设计
3. 细节实现
-
单个disk的参数设置,预制的Disk资源的参数可以被修改//
public class DiskData : MonoBehaviour { // Start is called before the first frame update public Vector3 size; //大小 public Color color; //颜色,绿、红、蓝。 public float speed; //速度随回合数升高递增 public Vector3 direction; //方向 public int score;//代表的分数随回合数升高递增 }
-
DiskFactory类管理所有的disk,实现对象池的设置,被点击的飞碟不会被销毁,而是进入回收队列,当需要时再次被使用,减少游戏对象的创建与销毁的开销。
//飞碟创建和回收队列 public GameObject disk; //飞碟预制体 private List<DiskData> used = new List<DiskData>(); //正在被使用的飞碟列表 private List<DiskData> free = new List<DiskData>(); //空闲的飞碟列表
获取飞碟GetDisk()方法:
判断空闲飞碟队列是否为空,不为空直接使用,否则clone一个新的Disk对象,之后通过round参数判断当前的回合数,对Disk的参数(初始速度,颜色,方向等)进行修改并把飞碟加入等待队列。
public GameObject GetDisk(int round){ disk = null; if (free.Count > 0){ //空闲列表存在飞碟,直接使用 disk = free[0].gameObject; free.Remove(free[0]); } else{ //不存在,进行预制 disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity); disk.AddComponent<DiskData>(); } //round代表回合数 switch (round){ case 0: { disk.GetComponent<DiskData>().color = Color.green; disk.GetComponent<DiskData>().size = new Vector3(5,0,5); disk.GetComponent<DiskData>().speed = Random.Range(10f, 12f); float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1; disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0); disk.GetComponent<DiskData>().score = 1; disk.GetComponent<Renderer>().material.color = Color.green; break; } case 1: { disk.GetComponent<DiskData>().color = Color.red; disk.GetComponent<DiskData>().speed = Random.Range(15f, 18f); float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1; disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0); disk.GetComponent<DiskData>().score = 2; disk.GetComponent<Renderer>().material.color = Color.red; break; } case 2: { disk.GetComponent<DiskData>().color = Color.blue; disk.GetComponent<DiskData>().speed = Random.Range(10f, 15f); float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1; disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0); disk.GetComponent<DiskData>().score = 3; disk.GetComponent<Renderer>().material.color = Color.blue; break; } } used.Add(disk.GetComponent<DiskData>()); return disk; }
回收飞碟FreeDisk()方法:
检查所有Used队列中的Disk,如果不在工作状态则回收到Free队列
public void FreeDisk(GameObject disk){ for (int i = 0; i < used.Count; i++){ if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID()){ used[i].gameObject.SetActive(false); free.Add(used[i]); used.Remove(used[i]); break; } } }
-
运用模板Singleten,可以为每个 MonoBehaviour子类 创建一个对象的实例
模板已在课件中实现,直接使用
调用方法:任意位置使用
Singleton<YourMonoType>.Instance
获得该对象。 -
FirstController类继承了MonoBehaviour, ISceneController, IUserAction类,控制游戏资源的加载,游戏的开始,暂停,结束,Disk对象的发送,打击,收回等,用户交互的计分,回合控制。
资源加载:
readonly float roundTime = 40f; private float speed = 1.5f; //发射一个飞碟的时间间隔 readonly int[] passScore = { 20, 50 }; private List<GameObject> disks = new List<GameObject>(); //飞碟队列 private int currentRound = 0; //回合 private float time = 0f; //记录时间间隔 private float currrentTime = 0f; private GameState gameState = GameState.START; public UserGUI userGUI; public ScoreRecorder scoreRecorder; //计分 public DiskFactory diskFactory; //工厂 public FlyActionManager actionManager; //动作管理
通过Singleten模板实现游戏对象的实例化
void Start() { SSDirector director = SSDirector.GetInstance(); director.CurrentScenceController = this; diskFactory = Singleton<DiskFactory>.Instance; scoreRecorder = Singleton<ScoreRecorder>.Instance; actionManager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager; userGUI = gameObject.AddComponent<UserGUI>() as UserGUI; gameState = GameState.START; time = 0f; currentRound = 0; currrentTime = 0; LoadResources(); }
判断并修改场景中Disk的状态:
void Update() { if (gameState == GameState.RUNNING ) { for (int i = 0; i < disks.Count; i++) { //飞碟飞出摄像机视野也没被打中 if ((disks[i].transform.position.y <= -4.5) && disks[i].gameObject.activeSelf == true) { diskFactory.FreeDisk(disks[i]); disks.Remove(disks[i]); scoreRecorder.Miss(); } } if (time > speed) { time = 0; SendDisk(); } else { time += Time.deltaTime; } if (currrentTime > roundTime) { currrentTime = 0; if (currentRound < 2 && GetScore() >= passScore[currentRound]) { currentRound++; time = 0f; } else { GameOver(); } } else { currrentTime += Time.deltaTime; } } }
飞碟打击动作的实现:Hit()方法
public void Hit(Vector3 pos) { Ray ray = Camera.main.ScreenPointToRay(pos); RaycastHit[] hits; hits = Physics.RaycastAll(ray); bool isHit = false; for (int i = 0; i < hits.Length; i++) { RaycastHit hit = hits[i]; if (hit.collider.gameObject.GetComponent<DiskData>() != null) { for (int j = 0; j < disks.Count; j++) //射中的物体在used列表中 { if (hit.collider.gameObject.GetInstanceID() == disks[j].gameObject.GetInstanceID()) { isHit = true; } } if (!isHit) //如果没有打中,返回 { return; } disks.Remove(hit.collider.gameObject); //从队列中移除 scoreRecorder.Record(hit.collider.gameObject); //记分员记录分数 StartCoroutine(WaitingParticle(0.08f, hit, diskFactory, hit.collider.gameObject)); //等几秒后回收Disk break; } } }
飞碟发射实现:sendDisk()方法
private void SendDisk() { GameObject disk = diskFactory.GetDisk(currentRound); disks.Add(disk); disk.SetActive(true); float positionX = 16; float ranY = Random.Range(1f, 4f); float ranX = Random.Range(-1f, 1f) < 0 ? -1 : 1; disk.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0); Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * positionX, ranY, 0); disk.transform.position = position; float angle = Random.Range(15f, 20f); actionManager.UFOFly(disk, angle); if (disk.GetComponent<DiskData>().color == Color.blue) { GameObject disk1 = Instantiate(disk); GameObject disk2 = Instantiate(disk); disks.Add(disk1); disk1.SetActive(true); disk1.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0); disk1.transform.position = position; actionManager.UFOFly(disk1, Random.Range(15f, 28f)); disks.Add(disk2); disk2.SetActive(true); disk2.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0); disk2.transform.position = position; actionManager.UFOFly(disk2, Random.Range(15f, 28f)); } }
还实现了Restart()、GetScore()、Gettime()、GetRound()、Pause()方法等,不再列出。
-
FlyAction()实现飞碟的飞行管理
public class FlyActionManager : SSActionManager //本游戏管理器 { private UFOFlyAction fly; //飞行动作,这次只有单独动作,没有组合动作 public FirstController sceneController; protected void Start() { sceneController = (FirstController)SSDirector.GetInstance().CurrentScenceController; sceneController.actionManager = this; } public void UFOFly(GameObject disk, float angle) { fly = UFOFlyAction.GetSSAction(disk.GetComponent<DiskData>().direction, angle, disk.GetComponent<DiskData>().speed); this.RunAction(disk, fly, this); } public void Pause() { fly.run = false; } public void Run() { fly.run = true; } }
-
ScoreRecorder类实现计分管理
规则:
round1:每个Disk一分,Miss之后总分减一
round2:每个Disk两分,Miss之后总分减二
round3:每个Disk三分,Miss之后总分减三
游戏结束后总分复位
public class ScoreRecorder : MonoBehaviour{ // Start is called before the first frame update public int score; //分数 void Start(){ score = 0; } //记录分数 public void Record(GameObject disk) { score = disk.GetComponent<DiskData>().score + score; } public void Miss() { if (score >= 2) score -= 2; else score = 0; } //重置分数 public void Reset() { score = 0; } }
-
SequenceAction类实现Disk队列的组合动作
public class SequenceAction : SSAction, ISSActionCallback{ public List<SSAction> sequence; //动作的列表 public int repeat = -1; //-1就是无限循环做组合中的动作 public int start = 0; //当前做的动作的索引 public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence){ SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>(); action.repeat = repeat; action.sequence = sequence; action.start = start; return action; } public override void Update(){ if (sequence.Count == 0) return; if (start < sequence.Count){ sequence[start].Update(); } } public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null) { source.destroy = false; //先保留这个动作,如果是无限循环动作组合之后还需要使用 this.start++; if (this.start >= sequence.Count){ this.start = 0; if (repeat > 0) repeat--; if (repeat == 0) { this.destroy = true; //整个组合动作就删除 this.callback.SSActionEvent(this); //告诉组合动作的管理对象组合做完了 } } } public override void Start(){ foreach (SSAction action in sequence){ action.gameobject = this.gameobject; action.transform = this.transform; action.callback = this; action.Start(); } } void OnDestroy(){ } }
-
其它SSAction、SSDirection等类的实现较为简单,不再给出。
4. 效果展示:
5. 不足与改进
- 不同的回合内飞碟的大小未改变。
- 可以增加点击后的爆炸效果等。