目录
Hit UFO
一、游戏规则
- 点击飞碟来摧毁它
- 当飞碟飞出屏幕时,损失生命值,生命值归零时游戏结束
- 摧毁飞碟会获得点数,随着点数的升高,游戏难度加大
- 可以通过Easy,Normal,Hard选项手动调节基础游戏难度(也就是倍速)
- 通过Pause暂停游戏;
- 通过Start,Restart按钮开始和重置游戏
二、gitee
三、程序实现
本次作业中,还是采用了前几次作业的UML架构,所以顶层文件结构基本没有变化;
3.1 SSDirector.cs
和前几次的基本相同,作为导演类来监视游戏的运行,由于只有一个界面,所以其实连页面转换的作用也莫得,代码和之前一样;
public class SSDirector : System.Object
{
private static SSDirector _instance;
public ISceneController CurrentScenceController { get; set; }
public static SSDirector GetInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
3.2 SSAction.cs
动作基类
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
3.3 SSActionManager.cs
动作管理类:实现动作的加入,调用,销毁
public class SSActionManager : MonoBehaviour, ISSActionCallback
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction>();
private List<int> waitingDelete = new List<int>();
protected void Update()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
Destroy(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null)
{
}
}
3.4 SequenceAction.cs
实现动作的排队执行(见上一次作业的动作分离)
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1;
public int start = 0;
public override void Update()
{
if (sequence.Count == 0) return;
if (start < sequence.Count && Time.timeScale != 0)
{
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();
}
}
}
3.5 Interface.cs
程序接口,包括场景接口,用户行为接口,动作管理接口
public interface ISceneController
{
void LoadResources();
}
public interface IUserAction
{
void Hit(Vector3 pos);
int GetScore();
void GameOver();
void ReStart();
void BeginGame();
}
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
3.6 Singleton.cs
实现工厂的场景单实例,使用课件上的代码实现
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;
}
}
}
3.7 UFOFlyAction.cs
实现飞碟的飞行
- 实现飞碟的类抛物线运行方式;
- 调用update来实现飞碟的实际运动
public override void Update()
{
Debug.Log("UPdate, time.scale = "+ Time.timeScale);
if (Time.timeScale == 0) return;
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;
if (this.transform.position.y < -10)
{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
3.8 DiskFactory.cs
工厂类:用于在需要飞碟时,实例化新的飞碟,并在使用完成时,将对应的disk收回
public GameObject GetDisk(int round)
{
int choice = 0;
int scope1 = 1, scope2 = 4, scope3 = 7;
float start_y = -10f;
string tag;
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);
}
if(choice <= scope1)
{
tag = "disk1";
}
else if(choice <= scope2 && choice > scope1)
{
tag = "disk2";
}
else
{
tag = "disk3";
}
for(int i=0;i<free.Count;i++)
{
if(free[i].tag == tag)
{
disk_prefab = free[i].gameObject;
free.Remove(free[i]);
break;
}
}
if(disk_prefab == null)
{
if (tag == "disk1")
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
disk_prefab.GetComponent<Renderer>().material.color = Color.green;
}
else if (tag == "disk2")
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
disk_prefab.GetComponent<Renderer>().material.color = Color.blue;
}
else
{
disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
disk_prefab.GetComponent<Renderer>().material.color = Color.red;
}
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
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;
}
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;
}
}
}
3.9 FirstController.cs
主控制器,挂载在GameObject上进行初始化,进行资源的加载以及游戏用户行动函数的实现;
- 首先还是和之前的相似,在start加载初始元素
void Start ()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
disk_factory = Singleton<DiskFactory>.Instance;
score_recorder = Singleton<ScoreRecorder>.Instance;
action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
}
- 之后再update中根据是否点击了开始,来选择加载开始界面或者是游戏界面
void Update ()
{
if(game_start)
{
if (game_over)
{
CancelInvoke("LoadResources");
}
if (!playing_game)
{
InvokeRepeating("LoadResources", 1f, speed);
playing_game = true;
}
SendDisk();
if (score_recorder.score >= score_round2 && round == 1)
{
round = 2;
speed = speed - 0.6f;
CancelInvoke("LoadResources");
playing_game = false;
}
else if (score_recorder.score >= score_round3 && round == 2)
{
round = 3;
speed = speed - 0.5f;
CancelInvoke("LoadResources");
playing_game = false;
}
}
}
- sendDisk函数用于随机发射飞碟:
private void SendDisk()
{
float position_x = 16;
if (disk_queue.Count != 0)
{
GameObject disk = disk_queue.Dequeue();
disk_notshot.Add(disk);
disk.SetActive(true);
float ran_y = Random.Range(1f, 4f);
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk.GetComponent<DiskData>().direction = new Vector3(ran_x, ran_y, 0);
Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
disk.transform.position = position;
float power = Random.Range(10f, 15f);
float angle = Random.Range(15f, 28f);
action_manager.UFOFly(disk,angle,power);
}
for (int i = 0; i < disk_notshot.Count; i++)
{
GameObject temp = disk_notshot[i];
if (temp.transform.position.y < -10 && temp.gameObject.activeSelf == true)
{
disk_factory.FreeDisk(disk_notshot[i]);
disk_notshot.Remove(disk_notshot[i]);
user_gui.ReduceBlood();
}
}
}
- Hit使用Ray射线来进行击中飞碟的判断
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<DiskData>() != null)
{
for (int j = 0; j < disk_notshot.Count; j++)
{
if (hit.collider.gameObject.GetInstanceID() == disk_notshot[j].gameObject.GetInstanceID())
{
not_hit = true;
}
}
if(!not_hit)
{
return;
}
disk_notshot.Remove(hit.collider.gameObject);
score_recorder.Record(hit.collider.gameObject);
StartCoroutine(WaitingParticle(0.08f, hit, disk_factory, hit.collider.gameObject));
break;
}
}
}
3.10 ScoreRecorder
用于记录得分情况
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;
}
}
3.11 UserGUI
实现提示语和积分的记录,以及暂停和难度选择等按钮
void OnGUI ()
{
bold_style.normal.textColor = new Color(1, 0, 0);
bold_style.fontSize = 16;
text_style.normal.textColor = new Color(0,0,0, 1);
text_style.fontSize = 16;
score_style.normal.textColor = new Color(1,0,1,1);
score_style.fontSize = 16;
over_style.normal.textColor = new Color(1, 0, 0);
over_style.fontSize = 25;
if (game_start)
{
Debug.Log("Time scale:" + Time.timeScale);
//用户射击
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.Hit(pos);
}
GUI.Label(new Rect(10, 5, 200, 50), "分数:" + action.GetScore().ToString(), text_style);
if (action.GetScore() < 10)
{
GUI.Label(new Rect(10, 55, 200, 50), "Round: 1" , text_style);
}else if (action.GetScore() >= 10 && action.GetScore() <20)
{
GUI.Label(new Rect(10, 55, 200, 50), "Round: 2", text_style);
}
else
{
GUI.Label(new Rect(10, 55, 200, 50), "Round: 3", text_style);
}
if (GUI.Button(new Rect(Screen.width / 2 - 100, 20, 50, 50), "Pause"))
{
Time.timeScale = 0;
}
if (GUI.Button(new Rect(Screen.width / 2 - 30 , 20, 50, 50), "Easy"))
{
Time.timeScale = 0.5f;
}
if (GUI.Button(new Rect(Screen.width / 2 + 40, 20, 50, 50), "Normal"))
{
Time.timeScale = 1;
}
if (GUI.Button(new Rect(Screen.width / 2 + 100, 20, 50, 50), "Hard"))
{
Time.timeScale = 2;
}
GUI.Label(new Rect(Screen.width - 120, 5, 50, 50), "生命:" + life, text_style);
//游戏结束
if (life == 0)
{
high_score = high_score > action.GetScore() ? high_score : action.GetScore();
GUI.Label(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 250, 100, 100), "Game Over", over_style);
GUI.Label(new Rect(Screen.width / 2 - 10, Screen.width / 2 - 200, 50, 50), "Rank:"+ action.GetScore(), text_style);
//GUI.Label(new Rect(Screen.width / 2 + 50, Screen.width / 2 - 200, 50, 50), high_score.ToString(), text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 150, 100, 50), "Restart"))
{
life = 6;
action.ReStart();
return;
}
action.GameOver();
}
}
else
{
GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 350, 100, 100), "Hit UFO!", over_style);
GUI.Label(new Rect(Screen.width / 2 -10, Screen.width / 2 - 300, 400, 100), "Rule:", text_style);
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 280, 400, 100), "Use mouse hit the UFO", text_style);
GUI.Label(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 260, 400, 100), "Button Pause to stop Game", text_style);
GUI.Label(new Rect(Screen.width / 2 - 160, Screen.width / 2 - 240, 400, 100), "Button Easy to change into easy game, UFO speed = 0.5x", text_style);
GUI.Label(new Rect(Screen.width / 2 - 165, Screen.width / 2 - 220, 400, 100), "Button Normal to change into Normal game, UFO speed = 1x", text_style);
GUI.Label(new Rect(Screen.width / 2 - 165, Screen.width / 2 - 200, 400, 100), "Button Normal to change into Hard game, UFO speed = 2x", text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2-150, 100, 50), "Start"))
{
game_start = true;
action.BeginGame();
}
}
}
四、效果展示
五、编写一个简单的自定义 Component
- 实现自定义组件,编辑并赋予飞碟一些属性
我们尝试自制几个飞碟的预设,并在游戏中工厂类”生产“飞碟时,加入我们自己编写的Component预设,我们再组件中,设定了飞碟的颜色,大小,位置和分值,可以通过组件直接调整飞碟对应属性
首先先自己做飞碟的预设:这里因为大小等信息我们通过Component来进行调整,所以我们的预设做的比较模糊就可以了,不需要调整具体形状
再预设中加入我们自己的Component,来及进行对应属性的直接管理
具体代码如下:
public class DiskData : MonoBehaviour
{
public int score;
public Color color = Color.blue;
public Vector3 direction;
public Vector3 scale;
}
运行时截图: