打飞碟小游戏--作业要求
编写一个简单的鼠标打飞碟(Hit UFO)游戏
-
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
-
游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
作业实现
游戏场景如下
最终项目的cs文件如下
本次代码编写同样采取了MVC模式,其中导演类SSDirector,SSAction与上次牧师与魔鬼的实验基本相同,照搬了上次的代码
Disk类
Disk是飞碟的实现,定义了飞碟的初始位置,x方向的速度,飞碟的颜色和对应的得分等属性
public class Disk : MonoBehaviour{
//方向
public Vector3 direction { get { return direction; } set { gameObject.transform.Rotate(value); } }
//飞碟初始位置
public Vector3 StartPoint {get{return gameObject.transform.position; } set { gameObject.transform.position = value;}}//开始点
public Color color {get{return gameObject.GetComponent<Renderer>().material.color;}set{gameObject.GetComponent<Renderer>().material.color = value;}}//颜色
public float Speed {get;set;}//速度
public int score {get;set;}//得分
}
DiskFactory类
飞碟的工厂类定义了飞碟的创建和回收工作,创建时给飞碟分配初始的位置和初速度,并且对其飞行的角度进行随机设定。
public class DiskFactory {
public GameObject disk = null;
private List<Disk> used = new List<Disk>();//正在使用的飞碟
private List<Disk> free = new List<Disk>();//未被激活的飞碟
public static DiskFactory diskfactory = new DiskFactory();
public DiskFactory(){
//Debug.Log("333");
disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk2"));
disk.AddComponent<Disk>();
disk.SetActive(false);
}
public Disk GetDisk(int round){
FreeDisk();
float random_speed;//飞碟速度
int random_num;
int _score;//飞碟得分
//根据round随机飞碟数量和速度
if (round == 1){
random_num = Random.Range(0, 3);
random_speed = Random.Range(20, 30);
}
else if (round == 2){
random_num = Random.Range(0, 5);
random_speed = Random.Range(60, 90);
}
else {
random_num = Random.Range(0, 7);
random_speed = Random.Range(90, 120);
}
//设定飞船属性
Vector3 random_start;
Color _color;
if(random_num <= 3){
_score = 1;
random_start = new Vector3(Random.Range(-60, -90), Random.Range(30,90), 0);
_color = Color.white;
}
else if(random_num <= 5 && random_num > 3){
_score = 3;
random_start = new Vector3(Random.Range(-30, -10), Random.Range(30,70), 0);
_color = Color.black;
}
else{
_score = 5;
random_start = new Vector3(Random.Range(10, 30), Random.Range(30, 70), 0);
_color = Color.red;
}
//从空闲列表中获取同类型
GameObject diskdata = null;
if (free.Count > 0){
diskdata = free[0].gameObject;
free.Remove(free[0]);
}
else{
diskdata = GameObject.Instantiate<GameObject>(disk, Vector3.zero, Quaternion.identity);
}
diskdata.SetActive(true);
Disk newdisk;
newdisk = diskdata.AddComponent<Disk>();
//添加属性
newdisk.Speed = random_speed;
float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
newdisk.direction = new Vector3(RanX, 1, 0);
newdisk.score = _score;
newdisk.StartPoint = random_start;
newdisk.color = _color;
used.Add(newdisk); //添加到使用列表
return newdisk;
}
//飞碟回收
public void FreeDisk(){
for(int i = 0;i < used.Count;i ++){
//如果是要回收的
if(!used[i].gameObject.activeSelf){
free.Add(used[i]);
used.Remove(used[i]);
break;
}
}
}
}
每次创建飞碟时首先判断空闲列表中是否有空的飞碟,是则从空闲列表中获取,否则才创建新的飞碟,这样飞碟的总数就不会一直增加,实现完成的效果如下,可以看到飞碟被不断回收利用
CCFlyAction类
模仿上次牧师与魔鬼的CCAction类,定义了飞碟的飞行动作,大致是一个类平抛运动
public class CCFlyAction : SSAction {
public float speedx;
public float speedy = 0;//y方向初速度
public float gravity = 10;//添加重力
// Use this for initialization
public override void Start () {}
public static CCFlyAction getAction(float speed){
CCFlyAction action = CreateInstance<CCFlyAction>();
action.speedx = speed;//初始化初速度
return action;
}
// Update is called once per frame
public override void Update () {
this.transform.position += new Vector3(speedx*Time.deltaTime, -speedy * Time.deltaTime + 0.5f * gravity * Time.deltaTime * Time.deltaTime,0);
speedy += gravity*Time.deltaTime;//类平抛运动
//模拟飞碟飞行完成没有被击毁,注意要比扣血的y小
if(transform.position.y <= -8){
destory = true;
callback.SSActionEvent(this);//反馈
}
}
}
CCFlyActionManager类
场记工作的分担,给特定的飞碟添加飞行动作
public class CCFlyActionManager : SSActionManger,ISSActionCallback {
public SSActionEventType Complete = SSActionEventType.Competeted;
public void SSActionEvent(SSAction source) {
//count--;
Complete = SSActionEventType.Competeted;
source.gameObject.SetActive(false);
}
public void UFOfly(Disk disk){
Complete = SSActionEventType.Started;
CCFlyAction action = CCFlyAction.getAction(disk.Speed);
addAction(disk.gameObject, action, this);//激活对象,添加动作
}
}
ScoreManager类
管理分数的加减,打到对应的飞碟增加对应的分数
public class ScoreManager : MonoBehaviour {
public int score;
void Start (){
score = 0;
}
public void Record(Disk disk){
score += disk.score;
}
public void Reset(){
score = 0;
}
}
UserGUI类
控制页面的分数和回合数输出,和场记类进行交互,原先写了一个生命值属性,即在该类中增加一个blood属性,每一次在场记类里判断没打到飞碟就减少生命值,但由于飞碟速度的原因难度太大,没有时间调节bug就删了该属性,增加了一个达到超过100分则判断获胜并用UILabel展示
public class UserGUI : MonoBehaviour {
private UserAction action;
public int blood = 5;
private bool is_play = false;
// Use this for initialization
void Start () {
action = SSDirector.getInstance().currentScenceController as UserAction;
blood = 5;
is_play = true;
action.reStart();
}
// Update is called once per frame
void OnGUI () {
//给用户击中事件
if(is_play) {
if (Input.GetButtonDown("Fire1")){
Vector3 pos = Input.mousePosition;
action.hit(pos);
}
}
GUIStyle style = new GUIStyle();//获胜或失败
GUIStyle bloodStyle = new GUIStyle();
GUIStyle scoreStyle = new GUIStyle();
GUIStyle buttonStyle = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
style.normal.textColor = Color.red;
buttonStyle = new GUIStyle("Button");
buttonStyle.fontSize = 40;
scoreStyle.fontSize = 30;
scoreStyle.normal.textColor = Color.red;
bloodStyle.fontSize = 30;
bloodStyle.normal.textColor = Color.red;
/*GUI.Label(new Rect(Screen.width - 300, 5, 50, 50), "Blood:", bloodStyle);
//显示当前血量
for (int i = 0; i < blood; i++){
GUI.Label(new Rect(Screen.width - 180 + 20 * i, 5, 10, 10), "O", bloodStyle);
}*/
GUI.Label(new Rect(Screen.width - 300, Screen.height / 2-180, 100, 50), "Score: " + action.getScore().ToString(),scoreStyle);
GUI.Label(new Rect(Screen.width - 150, Screen.height / 2-180, 100, 50), "Round: " + action.getRound().ToString(),scoreStyle);
/*if(blood == 0){
GUI.Label(new Rect(Screen.width/2-50,Screen.height/2-70,100,50),"GameOver!",style);
action.endGame();
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart",buttonStyle)){
blood = 5;
action.reStart();
}
}*/
//超过100分则获胜
if(action.getScore() >= 100){
GUI.Label(new Rect(Screen.width/2-50,Screen.height/2-70,100,50),"You win!",style);
action.endGame();
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart",buttonStyle)){
blood = 5;
action.reStart();
}
}
}
/*public void ReduceBlood(){
if(blood > 0)
blood--;
}*/
}
FirstController类
场记类的作用和牧师与魔鬼相同,即管理游戏中的一切事物,这里需要注意的点是需要增加一个计数器,在指定的帧才释放飞碟,否则每一帧都释放难度太大,该游戏中场记的作用主要是判断游戏的开始和停止,和ScoreManager配合计算得分,和DiskFactory配合判断是否需要回收飞碟,给定一个升级round的分数线
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using myinterface;
public class FirstController : MonoBehaviour, ISceneController, UserAction{
public CCFlyActionManager fly_manager;
public DiskFactory DF;
public UserGUI user_gui;
public ScoreManager score_manager;
public int round = 1;
public bool start = false;//游戏开始
public bool over = false;//游戏结束
private int score2 = 10;//升级分数
private int score3 = 25;
private List<Disk> disk_notshot = new List<Disk>(); //没有被打中的飞碟队列
// Use this for initialization
void Start () {
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
DF = DiskFactory.diskfactory;
//score_manager = new ScoreManager();
score_manager = Singleton<ScoreManager>.Instance;
/*if(score_manager == null){
Debug.Log("333");
}*/
fly_manager = gameObject.AddComponent<CCFlyActionManager>() as CCFlyActionManager;
user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
//start = true;
}
// Update is called once per frame
int count = 0;
void Update () {
//出现飞碟的时间延迟
int val = 0;
switch (round){
case 1:
val = Random.Range(60, 80);
break;
case 2:
val = Random.Range(45, 60);
break;
case 3:
val = Random.Range(30, 45);
break;
}
if(start == true){
count ++;
if(count >= val){
Disk d = DF.GetDisk(round);
fly_manager.UFOfly(d);
disk_notshot.Add(d);//加入未打中的对列并检测
count = 0;
}
for (int i = 0; i < disk_notshot.Count; i++){
Disk temp = disk_notshot[i];
//Debug.Log("333");
//飞碟飞出摄像机视野也没被打中
if (temp.transform.position.y <= -6 && temp.gameObject.activeSelf == true){
//DF.FreeDisk(disk_notshot[i]);
disk_notshot.Remove(disk_notshot[i]);
//玩家血量-1
//user_gui.ReduceBlood();
//Debug.Log(user_gui.blood);
}
}
if (score_manager.score >= score2 && round == 1){
round++;
}
if (score_manager.score >= score3 && round == 2){
round++;
}
}
}
public void LoadResources(){
}
public int getRound(){
return round;
}
public void hit(Vector3 pos){
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray);
bool not_hit = false;
for (int i = 0; i < hits.Length; i++){
RaycastHit hit = hits[i];
//击中
if (hit.collider.gameObject.GetComponent<Disk>() != null){
not_hit = true;
}
if(!not_hit)
return;
score_manager.Record(hit.collider.gameObject.GetComponent<Disk>());
//显示爆炸粒子效果
Transform explode = hit.collider.gameObject.transform.GetChild(0);
explode.GetComponent<ParticleSystem>().Play();
StartCoroutine(WaitingParticle(0.08f, hit, DF, hit.collider.gameObject));
}
}
//暂停几秒后回收飞碟
IEnumerator WaitingParticle(float wait_time, RaycastHit hit, DiskFactory disk_factory, GameObject d)
{
yield return new WaitForSeconds(wait_time);
//等待之后执行的动作
hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
//DF.FreeDisk(d);
}
public int getScore(){
return score_manager.score;
}
public void reStart(){
score_manager.score = 0;
round = 1;
start = true;
}
public void endGame(){
start = false;
}
}
最后加一个天空盒让场景好看一点
预设物体时需要添加碰撞器才能触发点击效果
完整代码在这里github