与游戏世界交互
编写一个简单的鼠标打飞碟(Hit UFO)游戏
1.游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
2. 游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 尽可能使用前面 MVC 结构实现人机交互与游戏模型分离
游戏的UML图如下:
可以看出用了之前的牧师与魔鬼的很多设计,比如导演类和动作分离。主要的区别就是工厂类UFOFactory,用于飞碟的生产。
这个类使用了重复利用的思想,维护两个列表,一个是正在使用中的飞碟,另一个是空闲的飞碟。每次需要产生飞碟的时候就从空闲飞碟队列中弹出第一个,当有飞碟被打中或者飞到屏幕之外后会被重新加入到空闲队列中。如果空闲飞碟队列为空,就新创建一个飞碟并随即生成颜色和位姿。工厂类的代码如下:
public class UFOFactory : MonoBehaviour {
public List<GameObject> used_UFO = new List<GameObject>();
public List<GameObject> free_UFO = new List<GameObject>();
public void GenUFO()
{
GameObject ufo;
if(free_UFO.Count == 0)
{
ufo = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"), Vector3.zero, Quaternion.identity);
}
else
{
ufo = free_UFO[0];
free_UFO.RemoveAt(0);
}
float x = Random.Range(-10.0f, 10.0f);
ufo.transform.position = new Vector3(x, 0, 0);
ufo.transform.Rotate(new Vector3(x < 0? -x*9 : x*9, 0, 0));
float random = Random.Range (0f, 9f);
Color red = new Color (1f, 0f, 0f);
Color green = new Color (0f, 1f, 0f);
Color blue = new Color (0f, 0f, 1f);
Color color = new Color(0f,0f,0f);
if (random >= 0 && random <= 3)
color = red;
else if (random > 3 && random <= 6)
color = green;
else if (random > 6 && random <= 9)
color = blue;
ufo.transform.GetComponent<Renderer>().material.color = color;
used_UFO.Add(ufo);
}
public void Recycleufo(GameObject usedUFO)
{
usedUFO.transform.position = new Vector3 (0, 0, -10);
free_UFO.Add(usedUFO);
}
}
除此之外还有与牧师与魔鬼不同的就是用来实现单例的模板类Singleton,用于实现场景单例模式。生产飞碟的工厂是每个场景都有且仅有一个,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;
}
}
}
其他的类就与牧师与魔鬼类似,首先是控制运动的类,由于飞碟的运动不需要分阶段进行,所以直接使用原来的运动类,从当前位置以speed运动到target:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction
{
public Vector3 target;
public float speed;
public GameObject ufo;
public static CCMoveToAction GetSSAction(Vector3 target, float speed )
{
CCMoveToAction action = CreateInstance<CCMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update()
{
if(enable)
{
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed);
if (this.transform.position == target)
{
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
}
动作管理类,维护动作的列表,每次根据情况从中取出动作进行运动:
public class CCActionManager : SSActionManager, ISSActionCallback {
public FirstSceneController sceneController;
public List<CCMoveToAction> seq = new List<CCMoveToAction>();
public UserClickAction userClickAction;
public UFOFactory factory;
protected new void Start()
{
sceneController = (FirstSceneController)SSDirector.getInstance().current;
sceneController.actionManager = this;
factory = Singleton<UFOFactory>.Instance;
}
protected new void Update()
{
if(factory.used_ufos.Count > 0)
{
GameObject disk = factory.used_ufos[0].gameObject;
float x = Random.Range(-10, 10);
CCMoveToAction moveToAction = CCMoveToAction.GetSSAction(new Vector3(x, 12, 0), (Mathf.CeilToInt(FirstSceneController.times / 10) + 1) * Time.deltaTime);
if (disk.transform.GetComponent<Renderer>().material.color == Color.red)
moveToAction = CCMoveToAction.GetSSAction(new Vector3(x, 12, 0), 5 * (Mathf.CeilToInt(FirstSceneController.times / 10) + 1) * Time.deltaTime);
else if(disk.transform.GetComponent<Renderer>().material.color == Color.green)
moveToAction = CCMoveToAction.GetSSAction(new Vector3(x, 12, 0), 4 * (Mathf.CeilToInt(FirstSceneController.times / 10) + 1) * Time.deltaTime);
else if(disk.transform.GetComponent<Renderer>().material.color == Color.blue)
moveToAction = CCMoveToAction.GetSSAction(new Vector3(x, 12, 0), 3 * (Mathf.CeilToInt(FirstSceneController.times / 10) + 1) * Time.deltaTime);
seq.Add(moveToAction);
this.RunAction(disk, moveToAction, this);
factory.used_ufos.RemoveAt(0);
}
if (Input.GetMouseButtonDown(0) && sceneController.flag == 0)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitGameObject;
if (Physics.Raycast(ray, out hitGameObject))
{
GameObject gameObject = hitGameObject.collider.gameObject;
if (gameObject.tag == "ufo")
{
foreach(var k in seq)
{
if (k.gameObject == gameObject)
k.transform.position = k.target;
}
if(gameObject.transform.GetComponent<Renderer>().material.color==Color.red)
userClickAction = UserClickAction.GetSSAction(3);
else if(gameObject.transform.GetComponent<Renderer>().material.color==Color.green)
userClickAction = UserClickAction.GetSSAction(2);
else if(gameObject.transform.GetComponent<Renderer>().material.color==Color.blue)
userClickAction = UserClickAction.GetSSAction(1);
this.RunAction(gameObject, userClickAction, this);
}
}
}
base.Update();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed, int intParam = 0, string strParam = null, Object objParam = null)
{
factory.Recycleufo(source.gameObject);
seq.Remove(source as CCMoveToAction);
source.destory = true;
if (FirstSceneController.times >= 30)
sceneController.flag = 1;
}
public void CheckEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed, int intParam = 0, string strParam = null, Object objParam = null)
{
}
public void Pause()
{
if(sceneController.flag == 0)
{
foreach (var k in seq)
{
k.enable = false;
}
sceneController.flag = 2;
}
else if(sceneController.flag == 2)
{
foreach (var k in seq)
{
k.enable = true;
}
sceneController.flag = 0;
}
}
}
在这个类里面我实现了不同飞碟不同速度和分数的功能,方法都是类似的,即创建动作时根据动作主体的颜色来改变参数。创建运动动作时就在传速度时乘以一个速度加成,创建点击动作实例时就传入参数,这个参数根据点击飞碟的颜色而不同,在点击动作中有FirstSceneController类,可以根据传入的add_score增加分数。点击动作实现如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserClickAction : SSAction {
public int add_score = 0;
public static UserClickAction GetSSAction(int s)
{
UserClickAction action = CreateInstance<UserClickAction>();
action.add_score = s;
return action;
}
public override void Start(){
}
public override void Update()
{
if(enable)
{
FirstSceneController sc = SSDirector.getInstance().current as FirstSceneController;
sc.score = sc.score + Mathf.CeilToInt(FirstSceneController.times/10) + add_score*Mathf.FloorToInt(120 / (transform.rotation.x + 30));
destory = true;
}
}
}
最后可以再用上次作业学习的天空盒做一个场景,游戏运行的效果图如图: