游戏机制:
打飞碟是一款益智游戏,玩家需要在界面中点击飞出来的飞碟以将其消除,不同颜色的飞碟飞行速度不一样,对应消除的得分也不一样。该游戏一共10轮,每轮持续10s,分为“运动学模式”和“重力学模式”两种模式。
UML图:
Controller制作代码:
DiskData.cs
飞碟参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskData : MonoBehaviour
{
public float speed;
public Vector3 direction;
public int score;
}
DiskFactory.cs
实现生成多个飞碟对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskFactory : MonoBehaviour
{
private List<DiskData> used;
private List<DiskData> free;
public GameObject disk;
// Start is called before the first frame update
void Start()
{
used = new List<DiskData>();
free = new List<DiskData>();
disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"));
disk.SetActive(false);
}
// Update is called once per frame
void Update()
{
}
public GameObject GetDisk(int round){
GameObject d;
if(free.Count > 0){
d = free[0].gameObject;
free.Remove(free[0]);
}
else{
d = GameObject.Instantiate<GameObject>(disk, Vector3.zero, Quaternion.identity);
d.AddComponent<DiskData>();
}
d.gameObject.name = "Disk";
// 根据不同的round设定不同的初始数值
// round越大,难度越高,则可以通过设置不同的速度改变难度
float uspeed = 2 + round;
// scale
Vector3 uscale = new Vector3(6, 1, 6);
// 随机初始方向
float dir_y = Random.Range(-0.5f, 0.5f);
int dir_x = Random.Range(0, 2);
// 随机颜色,不同的颜色有不同的速度
int color = Random.Range(1, 5);
// 红色
if(color == 1){
d.GetComponent<Renderer>().material.color = Color.red;
d.GetComponent<DiskData>().speed = 0.4f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 2;
d.GetComponent<Transform>().localScale = 0.7f*uscale;
}
// 黄色
else if(color == 2){
d.GetComponent<Renderer>().material.color = Color.yellow;
d.GetComponent<DiskData>().speed = 0.8f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 4;
d.GetComponent<Transform>().localScale = 0.6f*uscale;
}
// 蓝色
else if(color == 3){
d.GetComponent<Renderer>().material.color = Color.blue;
d.GetComponent<DiskData>().speed = 1.2f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 6;
d.GetComponent<Transform>().localScale = 0.5f*uscale;
}
// 绿色
else{
d.GetComponent<Renderer>().material.color = Color.green;
d.GetComponent<DiskData>().speed = 1.6f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 8;
d.GetComponent<Transform>().localScale = 0.4f*uscale;
}
used.Add(d.GetComponent<DiskData>());
return d;
}
public void FreeDisk(GameObject d){
foreach(DiskData dd in used){
if(dd.gameObject.GetInstanceID() == d.GetInstanceID()){
d.SetActive(false);
used.Remove(dd);
free.Add(dd);
break;
}
}
}
public void Clear(){
foreach(DiskData dd in used){//销毁在飞飞碟
dd.gameObject.transform.position = new Vector3(-100, 0, 0); // 将飞碟的位置设置为边界外,这样就能及时销毁
Destroy(dd.gameObject);
}
foreach(DiskData dd in free){//销毁空闲飞碟
dd.gameObject.transform.position = new Vector3(-100, 0, 0); // 将飞碟的位置设置为边界外,这样就能及时销毁
Destroy(dd.gameObject);
}
used.Clear();
free.Clear();
}
}
FirstController.cs
对游戏进行初始化和各项总控制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
// Start is called before the first frame update
public DiskFactory diskFactory;
public ScoreRecorder scoreRecorder;
public FlyActionManager flyActionManager;
public UserGUI userGUI;
int round;
float current_time;
float index_time;
int round_disk_count;
static float round_time = 10;
static int max_disk = 30;
void Start()
{
SSDirector.GetInstance().CurrentSceneController = this;
gameObject.AddComponent<DiskFactory>();
gameObject.AddComponent<CCActionManager>();
gameObject.AddComponent<PhysicalActionManager>();
userGUI = gameObject.AddComponent<UserGUI>();
diskFactory = Singleton<DiskFactory>.Instance;
flyActionManager = Singleton<CCActionManager>.Instance;
LoadResources();
}
// Update is called once per frame
void Update()
{
if(userGUI.status == 1){
if(round <= 10){
current_time += Time.deltaTime;
index_time += Time.deltaTime;
if(current_time < round_time){ // 当前轮次没有结束
if(index_time > 1){
index_time = 0;
userGUI.round = round;
int disk_count = Random.Range(1,4);
while(disk_count > 0){
float x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
float y = Random.Range(0f, 8f);
SendDisk(x, y);
round_disk_count++;
disk_count--;
if(round_disk_count == max_disk){
break;
}
}
}
}
else{
round_disk_count = 0;
current_time = 0;
index_time = 0;
round++;
userGUI.round = round;
}
}
if(round >= 11){
userGUI.status = 2;
}
}
}
public void LoadResources(){
scoreRecorder = new ScoreRecorder();
scoreRecorder.Reset();
round = 1;
current_time = 0;
index_time = 0;
round_disk_count = 0;
}
public void SendDisk(float x, float y){
GameObject disk = diskFactory.GetDisk(round);
// float rand_y = Random.Range(0f, 8f);
// float rand_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk.transform.position = new Vector3(x, y, 0);
disk.SetActive(true);
flyActionManager.FlyDisk(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);
}
public void GetHit(Vector3 position){
Ray ray = Camera.main.ScreenPointToRay(position);
RaycastHit[] hits = Physics.RaycastAll(ray);
for(int i = 0; i < hits.Length; i++){
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<DiskData>() != null){
GameObject disk = hit.collider.gameObject;
disk.transform.position = new Vector3(0, -20, 0);
diskFactory.FreeDisk(disk);
scoreRecorder.Record(disk);
userGUI.score = scoreRecorder.score;
}
}
}
public void ClearAll(){
GameObject[] gameObjects = FindObjectsOfType(typeof(GameObject)) as GameObject[]; // 获取所有的GameObject
foreach(GameObject gmobj in gameObjects){
if(gmobj.name == "Disk"){
gmobj.gameObject.SetActive(false);
}
}
diskFactory.Clear();
}
public void Restart(){
round = 1;
current_time = 0;
index_time = 0;
round_disk_count = 0;
scoreRecorder.Reset();
userGUI.score = scoreRecorder.score;
userGUI.round = round;
ClearAll();
}
public void SetMode(int mode){
if(mode == 0){
flyActionManager = Singleton<CCActionManager>.Instance;
}
else if(mode == 1){
flyActionManager = Singleton<PhysicalActionManager>.Instance;
}
}
}
ISceneController.cs
场景资源加载分离
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController
{
void LoadResources();
}
IUserAction.cs
用户操作分离
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction {
// void MoveBoat();
// void MoveRole(Role roleModel);
// void Check();
void Restart();
void SetMode(int mode);
void GetHit(Vector3 position);
}
ScoreRecorder.cs
分数记录器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : System.Object
{
public int score;
// Start is called before the first frame update
void Start()
{
score = 0;
}
// Update is called once per frame
void Update()
{
}
public void Record(GameObject disk){
score += disk.GetComponent<DiskData>().score;
}
public void Reset(){
score = 0;
}
}
Singleton.cs
单实例模板
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;
}
}
}
SSDirector.cs
场景设置分离
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object
{
static SSDirector _instance;
public ISceneController CurrentSceneController {get; set;}
public static SSDirector GetInstance() {
if (_instance == null) {
_instance = new SSDirector();
}
return _instance;
}
}
Actions制作代码:
CCActionManager.cs
运动动作管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback, FlyActionManager
{
public FirstController controller;
public CCFlyAction fly;
// Start is called before the first frame update
public void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
}
// Update is called once per frame
// public void Update()
// {
// }
public void FlyDisk(GameObject disk, float speed, Vector3 direction){
fly = CCFlyAction.GetSSAction(direction, speed);
RunAction(disk, fly, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
Object objectParam = null){
controller.diskFactory.FreeDisk(source.gameObject);
}
}
CCFlyAction.cs
飞行动作
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCFlyAction : SSAction
{
Vector3 direction;
float speed;
public static CCFlyAction GetSSAction(Vector3 direction, float speed){
CCFlyAction index = ScriptableObject.CreateInstance<CCFlyAction>();
index.direction = direction;
index.speed = speed;
return index;
}
// Start is called before the first frame update
public override void Start()
{
// gameObject.AddComponent<Rigidbody>();
gameObject.GetComponent<Rigidbody>().isKinematic = true;
}
// Update is called once per frame
public override void Update()
{
transform.Translate(direction*speed*Time.deltaTime);
if(this.transform.position.x < -35 || this.transform.position.x > 35 ||
this.transform.position.y < -10 || this.transform.position.y > 30){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
CCMoveToAction.cs
移动后消除判断
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction
{
public Vector3 target; // 移动后的目标位置
public float speed;
// Update is called once per frame
public override void Update()
{
this.transform.localPosition = Vector3.MoveTowards(this.transform.localPosition, target, speed * Time.deltaTime);
// 如果游戏对象不存在或者当前位置已在目标位置上,则不移动
if(this.transform.localPosition == target || this.gameObject == null){
this.destroy = true; // 标记为销毁
this.callback.SSActionEvent(this); // 回调函数
return;
}
}
public static CCMoveToAction GetSSAction(Vector3 target, float speed){
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
}
CCSequenceAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCSequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1;
public int start = 0;
// Start is called before the first frame update
public override void Start()
{
// 初始化列表中的动作
foreach(SSAction action in sequence){
action.gameObject = this.gameObject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}
// Update is called once per frame
public override void Update()
{
if(sequence.Count <= 0){
return;
}
if(sequence.Count > 0 && start < sequence.Count){
sequence[start].Update();
}
else{
return;
}
}
public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence){
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
action.repeat = repeat;
action.start = start;
action.sequence = sequence;
return action;
}
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(this.repeat > 0){
this.repeat--;
}
else{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
}
void OnDestroy(){
}
}
FlyActionManager.cs
动作总管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface FlyActionManager
{
void FlyDisk(GameObject disk, float speed, Vector3 direction);
}
ISSActionCallback.cs
动作回调
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
PhysicalActionManager.cs
物理学动作管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysicalActionManager : SSActionManager, ISSActionCallback, FlyActionManager
{
public FirstController controller;
public PhysicalFlyAction fly;
// Start is called before the first frame update
public void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
}
// Update is called once per frame
// void Update()
// {
// }
public void FlyDisk(GameObject disk, float speed, Vector3 direction){
fly = PhysicalFlyAction.GetSSAction(direction, speed);
RunAction(disk, fly, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
Object objectParam = null){
controller.diskFactory.FreeDisk(source.gameObject);
}
}
PhysicalFlyAction.cs
物理学动作
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysicalFlyAction : SSAction
{
Vector3 direction;
float speed;
public static PhysicalFlyAction GetSSAction(Vector3 direction, float speed){
PhysicalFlyAction index = ScriptableObject.CreateInstance<PhysicalFlyAction>();
index.direction = direction;
index.speed = speed;
return index;
}
// Start is called before the first frame update
public override void Start()
{
// gameObject.AddComponent<Rigidbody>();
gameObject.GetComponent<Rigidbody>().isKinematic = false;
gameObject.GetComponent<Rigidbody>().velocity = speed*direction; // 水平初速度
}
// Update is called once per frame
public override void Update()
{
if(transform != null){
transform.Translate(direction*speed*Time.deltaTime);
if(this.transform.position.y < -10){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
}
SSAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameObject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set;}
protected SSAction() {}
// Start is called before the first frame update
public virtual void Start()
{
throw new System.NotImplementedException();
}
// Update is called once per frame
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
SSActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction> ();
private List<int> waitingDelete = new List<int>();
// Start is called before the first frame update
protected void Start()
{
}
// Update is called once per frame
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();
}
}
View制作代码:
UserGUI.cs
加载GUI界面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
GUIStyle topicStyle;
GUIStyle dataStyle;
public int round;
public int score;
public int status;
public int mode;
private string[] modeStrings = {"运动学模式", "重力学模式"};
private IUserAction userAction;
// Start is called before the first frame update
void Start()
{
round = 1;
score = 0;
status = 0;
mode = 0;
userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
SetStyle();
}
// Update is called once per frame
void Update()
{
}
void SetStyle(){
topicStyle = new GUIStyle();
topicStyle.normal.textColor = Color.black;
topicStyle.fontSize = 60;
dataStyle = new GUIStyle();
dataStyle.normal.textColor = Color.black;
dataStyle.fontSize = 30;
}
void OnGUI(){
GUI.Label(new Rect(300, 20, 50, 40), "Hit UFO", topicStyle);
if(status == 0){
if(GUI.Button(new Rect(10, 10, 100, 40), "Start Game")){
status = 1;
userAction.Restart();
}
// GUI.Box(new Rect(100, 100, 300, 200), "模式:");
// mode = 0;
mode = GUI.Toolbar(new Rect (10, 60, 200, 30), mode , modeStrings);
userAction.SetMode(mode);
}
else if(status == 1){
GUI.Label(new Rect(20, 20, 100, 50), "score: "+ score, dataStyle);
GUI.Label(new Rect(20, 80, 100, 50), "round: "+ round, dataStyle);
if(Input.GetButtonDown("Fire1")){
userAction.GetHit(Input.mousePosition);
}
if(GUI.Button(new Rect(20, 160, 100, 40), "Restart")){
status = 0;
userAction.Restart();
}
}
else if(status == 2){
GUI.Label(new Rect(320, 100, 50, 40), "Game Over", dataStyle);
GUI.Label(new Rect(320, 200, 50, 40), "total score: " + score, dataStyle);
if (GUI.Button(new Rect(330, 330, 100, 40), "Restart"))
{
status = 0;
userAction.Restart();
}
}
}
}