编写一个简单的鼠标打飞碟(Hit UFO)游戏
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 尽可能使用前面 MVC 结构实现人机交互与游戏模型分离
UML:
伪代码:
getDisk(ruler)
BEGIN
IF (free list has disk) THEN
a_disk = remove one from list
ELSE
a_disk = clone from Prefabs
ENDIF
Set DiskData of a_disk with the ruler
Add a_disk to used list
Return a_disk
END
FreeDisk(disk)
BEGIN
Find disk in used list
IF (not found) THEN THROW exception
Move disk from used to free list
END
代码实现:
- DiskData:包括射击飞碟的得分、飞碟颜色、初始的位置、大小。
public class DiskData : MonoBehaviour{
public int score = 1;
public Color color = Color.white;
public Vector3 direction;
public Vector3 scale = new Vector3( 1 ,0.25f, 1);
}
- 飞碟管理员DiskFactory:负责接收SSDirector的请求,使用模板模式根据预制和规则制作飞碟、回收飞碟。
维护两个List,分别储存正在使用的飞碟和空闲的飞碟:
public GameObject disk_prefab = null; //飞碟预制体
private List<DiskData> used = new List<DiskData>(); //正在被使用的飞碟列表
private List<DiskData> free = new List<DiskData>(); //空闲的飞碟列表
GetDisk:根据规则在空闲列表中选择飞碟,若空闲列表中没有要选择的飞碟,则重新实例化飞碟。
public GameObject GetDisk(int round){
int choice = 0;
int scope1 = 1, scope2 = 4, scope3 = 7; //随机的范围
float start_y = -10f; //刚实例化时的飞碟的竖直位置
string name;
disk_prefab = null;
//根据回合,随机选择要飞出的飞碟
if (round == 1) choice = Random.Range(0, scope1);
else if(round == 2) choice = Random.Range(0, scope2);
else choice = Random.Range(0, scope3);
//将要选择的飞碟的name
if(choice <= scope1) name = "disk1";
else if(choice <= scope2 && choice > scope1) name = "disk2";
else name = "disk3";
//寻找相同name的空闲飞碟
for(int i=0;i<free.Count;i++) if(free[i].name == name){
disk_prefab = free[i].gameObject;
free.Remove(free[i]);
break;
}
//如果空闲列表中没有,则重新实例化飞碟
if(disk_prefab == null){
if (name == "disk1") disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
else if (name == "disk2") disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
else disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
//给新实例化的飞碟赋予其他属性
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<DiskData>().color;
disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
}
//添加到使用列表中
used.Add(disk_prefab.GetComponent<DiskData>());
return disk_prefab;
}
FreeDisk:回收飞碟。
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;
}
}
- Singleton.cs
ublic 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;
}
}
}
- 记分员ScoreRecorder:start初始化分数为0,每击落一个飞碟加上对应的分数,重新开始时重置分数。
public class ScoreRecorder : MonoBehaviour{
public int score; //分数
void Start (){
score = 0;
}
//记录分数
public void Record(GameObject disk){
int temp = disk.GetComponent<DiskData>().score;
score = temp + score;
}
//重置分数
public void Reset(){
score = 0;
}
}
- FlyDisk:抛出飞碟,继承SSAction实现。
public static UFOFlyAction GetSSAction(Vector3 direction, float angle, float power){
//初始化物体将要运动的初速度向量
UFOFlyAction action = CreateInstance<UFOFlyAction>();
if (direction.x == -1) action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
else action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
return action;
}
public override void Update(){
//计算物体的向下的速度,v=at
time += Time.fixedDeltaTime;
gravity_vector.y = 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;
//如果物体y坐标小于-10,动作就做完了
if (this.transform.position.y < -10){
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
运行结果:
击中爆炸效果:
生命值为0时,游戏结束: