编写一个简单的鼠标打飞碟(Hit UFO)游戏
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
游戏的要求: - 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
动作管理器
动作管理器可以在上一次作业的基础上进行修改。
ISSActionCallback
为动作接口SSAction
为动作父类,规定所有Action的属性和方法DiskFlyAction
继承自SSAction
,规定了一个Disk的动作,动作即飞碟的移动,由两个参数决定:受力角度和受力大小。构造函数初始化初始速度,Update
函数模拟物体的坐标移动过程,当disk在画面之外(通过坐标判定),就callback
通知动作做完。
public override void Update()
{
time += Time.fixedDeltaTime;
gravity_vector.y = 0; //gravity * time;
transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
transform.eulerAngles = current_angle;
//动作做完
if (this.transform.position.y < -10 || this.transform.position.y > 10)
{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
SequenceAction
函数不用改动,由于Disk是直线运动也用不到。SSActionManager
函数。动作管理器,管理Action List中的每个Action(不用关心是单一Action还是连续Action)。FirstActionManager
。这个函数封装了Action的相关类的操作,使得FirstController
可以简单地调用FirstActionManager中的函数。
public class FirstActionManager : SSActionManager
{
public DiskFlyAction fly; //飞碟飞行的动作
public FirstController scene_controller; //当前场景的场景控制器
protected void Start()
{
scene_controller = (FirstController)Director.GetInstance().currentSceneController;
scene_controller.actionManager = this;
}
//飞碟飞行
public void diskFly(GameObject disk, float angle, float power)
{
fly = DiskFlyAction.GetSSAction(angle, power); //disk.GetComponent<Disk>().direction, angle, power);
this.RunAction(disk, fly, this);
}
}
飞碟
Disk类只简单记录飞碟的属性,它会作为一个组件添加到具体的GameObject上
public class Disk : MonoBehaviour
{
public float size;
public Color color;
//move
public float angle;
public float power;
}
飞碟工厂
负责飞碟的生产和回收。管理两个List,usingDisks存放正在使用的Disk,freeDisks管理使用了过后回收的Disk。
回收:从usingDisks中去掉并加到freeDisks中。
public void freeDisk(GameObject usedDisk)
{
if(usedDisk != null)
{
usedDisk.SetActive(false);
usingDisks.Remove(usedDisk);
freeDisks.Add(usedDisk);
}
}
生产:
如果freeDisks中有飞碟,则不用再实例化新的Disk,直接从中取即可;否则需要再实例化一个新的Disk,实例化之后还需要命名,给飞碟添加Disk、Rigidbody组件,但是工厂不负责设置这些组件的属性,工厂只负责把Disk生产出来。
public GameObject getDisk(int round)
{
GameObject newDisk = null;
if (freeDisks.Count > 0)
{
newDisk = freeDisks[0].gameObject;
freeDisks.Remove(freeDisks[0]);
}
else
{
newDisk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
newDisk.AddComponent<Disk>();
newDisk.name = nameIndex.ToString();
nameIndex++;
}
return newDisk;
}
工厂是场景单实例的
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;
}
}
}
规则管理器
Ruler管理游戏规则,它与游戏轮数Round紧密相关。
Ruler根据Round设置Disk的属性,基本规则是Round越大,飞碟速度越快、大小越小,而对于颜色、出现位置、角度这些属性,与Round无关。
public class Ruler
{
public int[] score;
public Ruler()
{
score = new int[10];
}
public void setDiskProperty(GameObject disk, int round)
{
disk.transform.position = this.setRandomInitPos();
disk.GetComponent<Renderer>().material.color = setRandomColor();
disk.transform.localScale = setScale(round);
disk.GetComponent<Disk>().angle = setRandomAngle();
disk.GetComponent<Disk>().power = setPower(round);
}
public Vector3 setRandomInitPos()
{
float x = Random.Range(-10f, 10f);
float y = Random.Range(-1f, 5f);
float z = Random.Range(-3f, 3f);
return new Vector3(x, y, z);
}
public Vector4 setRandomColor()
{
int r = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
int g = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
int b = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
return new Vector4(r, g, b, 1);
}
public Vector3 setScale(int round)
{
float x = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
float y = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
float z = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
return new Vector3(x, y, z);
}
public float setRandomAngle()
{
return Random.Range(-360f, 360f);
}
public float setPower(int round)
{
if (round == 1)
{
return 2;
}
return 3 * round;
}
}
同时规定相邻两个Disk的出现间隔,round越大间隔越短
public float setInterval(int round)
{
return (float)(2 - 0.2 * round);
}
另外,Ruler还负责判断游戏是否可以进入下一轮/失败,round越大,要求进入下一轮的分数也越大。一轮10个trial,最高10分。
public int getTargetThisRound(int round)
{
if (round != -1)
{
return 5 + round > 10 ? 10 : 5 + round;
}
return 0;
}
public bool enterNextRound(int round)
{
if (round != -1 && this.score[round - 1] >= (5 + round > 10 ? 10 : 5 + round))
{
return true;
}
return false;
}
界面
接收用户的输入
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
//Debug.Log("Fire1");
Vector3 pos = Input.mousePosition;
action.hit(pos);
}
}
显示Round和Score、输赢信息。
void OnGUI()
{
GUI.skin.label.font = blue_font;
GUI.Label(new Rect(Screen.width / 2 - 50, 20, 180, 50), " Hit UFO ");
GUI.Label(new Rect(Screen.width / 2 - 30, 50, 180, 50), "score: " + score.ToString());
GUI.Label(new Rect(Screen.width / 2 + 60, 50, 180, 50), "goal: " + targetThisRound.ToString());
if (round != -1)
{
GUI.Label(new Rect(Screen.width / 2 - 120, 50, 100, 50), "Round: " + round.ToString());
}
else if (round == -1)
{
GUI.Label(new Rect(Screen.width / 2 - 120, 50, 100, 50), "You Lose!");
}
if (GUI.Button(new Rect(Screen.width / 2 - 40, 240, 70, 30), "Restart"))
{
action.restart();
}
}
Controller
这个文件不是一个功能明确的类,里面放了所有的接口函数和导演类,都和之前的一样,其中玩家与游戏交互的接口包含玩家的两个动作:
public interface UserAction
{
void hit(Vector3 pos);
void restart();
}
FirstController
加载资源并控制整个游戏的流程。
加载资源
void Awake()
{
Director director = Director.GetInstance();
director.currentSceneController = this;
director.currentSceneController.loadResources();
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
this.gameObject.AddComponent<DiskFactory>();
actionManager = gameObject.AddComponent<FirstActionManager>() as FirstActionManager;
}
public void loadResources()
{
ruler = new Ruler();
}
在Update函数中,设定每隔一段时间就抛出一个disk,每个round总共抛出10个Disk;同时判断是否到达晋级下一轮的条件或是否失败。
void Update () {
if (ruler.enterNextRound(round))
{
round++;
trial = 0;
getDisksForNextRound();
userGUI.score = this.score = 0;
userGUI.targetThisRound = ruler.getTargetThisRound(round);
}
else if (!ruler.enterNextRound(round) && trial == 11)
{
round = -1;
}
if (this.round >= 1)
{
if (interval > ruler.setInterval(round))
{
if(trial < 10)
{
throwDisk();
interval = 0;
trial++;
}
else if (trial == 10)
{
trial++;
}
}
else
{
interval += Time.deltaTime;
}
}
userGUI.round = this.round;
}
每一轮的开始,FirstController会从diskFactory中一次性拿够10个Disk放入queue中。
public void getDisksForNextRound()
{
DiskFactory diskFactory = Singleton<DiskFactory>.Instance;
int numDisk = 10;
for (int i = 0; i < numDisk; i++)
{
GameObject disk = diskFactory.getDisk(round);
disksQueue.Enqueue(disk);
}
}
然后抛出时,从queue中拿出一个,通过ruler为其设定好星官属性,然后将其交给动作管理器抛出。
public void throwDisk()
{
if (disksQueue.Count != 0)
{
GameObject disk = disksQueue.Dequeue();
ruler.setDiskProperty(disk, round);
disk.SetActive(true);
actionManager.diskFly(disk, disk.GetComponent<Disk>().angle, disk.GetComponent<Disk>().power);
}
}