本次打飞碟小游戏,基本上是在上两周面向对象上编程实现的一个小游戏。在课程群里面,老师分享了一个优秀博客,此处引入,作为借鉴。
在本次作业中,我的脚本结构主要是参考上一次动作分离版牧师与魔鬼,保留SSDirector、SSAction与SSActionManager。在这次写代码的过程中,我发现我定义的接口太混乱,所以将所有的接口又再次提出放到了Interface中,这样比较方便debug。在本次代码中,新引入的就是Singleton,以及DiskData与DiskFactory。在此处的DiskFactory是用来管理飞碟出场的,并且是单例的;DiskData是用来对每一个飞碟加入自己的属性,即得分与大小比例,然后加入飞碟的预制中。
以下是上周动作分离设计的UML图:
在这个游戏中,稍稍改良,在CCActionManager改为FlyActionManager,下面的CCActionSequence改为了一个动作发生——UFOFlyAction。
在这位同学的博客中,我借鉴了他的爆炸的预设和skybox,好吧,是因为我实在懒得挑了,而且这个天空盒的画风真的太赞了~向这位同学学习学习~
在这个游戏中,一共有三个关卡,第一个关卡只有红色的飞碟,一个飞碟是1分。当得到15分后,即进入第二关,新加入黄色飞碟,击中一个得2分。当得到30分之后,即进入第三关,新加入蓝色飞碟,击中一个得3分。当错过了10个飞碟的话,游戏结束。
- 首先介绍FirstController:
继承MonoBehavior,ISceneController,IUserAction。主要函数如下:
void Start ()
{
//获取各个实例及各个类
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
diskFactory = Singleton.Instance;
scoreRecorder = Singleton.Instance;
actionManager = gameObject.AddComponent() as FlyActionManager;
userGUI = gameObject.AddComponent() as UserGUI;
}
void Update ()
{
if(isGameStart)
{
if (isGameOver)
{
CancelInvoke("LoadResources");
}
if (!isPlayGame)
{
InvokeRepeating("LoadResources", 1f, speed);
isPlayGame = true;
}
SendDisk();
if (scoreRecorder.score >= scoreRound2 && round == 1)
{
round = 2;
speed = speed - 0.6f;
CancelInvoke("LoadResources");
isPlayGame = false;
}
else if (scoreRecorder.score >= scoreRound3 && round == 2)
{
round = 3;
speed = speed - 0.5f;
CancelInvoke("LoadResources");
isPlayGame = false;
}
}
}
//加载资源
public void LoadResources()
{
diskQueue.Enqueue(diskFactory.GetDisk(round));
}
//发送飞碟
private void SendDisk()
{
float position_x = 16;
if (diskQueue.Count != 0)
{
GameObject disk = diskQueue.Dequeue();
diskNotHit.Add(disk);
disk.SetActive(true);
float ran_y = Random.Range(1f, 4f);
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk.GetComponent().direction = new Vector3(ran_x, ran_y, 0);
Vector3 position = new Vector3(-disk.GetComponent().direction.x * position_x, ran_y, 0);
disk.transform.position = position;
float power = Random.Range(10f, 15f);
float angle = Random.Range(15f, 28f);
actionManager.UFOFly(disk,angle,power);
}
for (int i = 0; i < diskNotHit.Count; i++)
{
GameObject temp = diskNotHit[i];
if (temp.transform.position.y < -10 && temp.gameObject.activeSelf == true)
{
diskFactory.FreeDisk(diskNotHit[i]);
diskNotHit.Remove(diskNotHit[i]);
userGUI.BloodReduce();
}
}
}
//击打飞碟
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() != null)
{
for (int j = 0; j < diskNotHit.Count; j++)
{
if (hit.collider.gameObject.GetInstanceID() == diskNotHit[j].gameObject.GetInstanceID())
{
not_hit = true;
}
}
if(!not_hit)
{
return;
}
diskNotHit.Remove(hit.collider.gameObject);
scoreRecorder.Record(hit.collider.gameObject);
Transform explode = hit.collider.gameObject.transform.GetChild(0);
explode.GetComponent().Play();
StartCoroutine(WaitingParticle(0.08f, hit, diskFactory, hit.collider.gameObject));
break;
}
}
}
//统计得分
public int GetScore()
{
return scoreRecorder.score;
}
//重新开始
public void Restart()
{
isGameOver = false;
isPlayGame = false;
scoreRecorder.score = 0;
round = 1;
speed = 2f;
}
//游戏结束
public void GameOver()
{
isGameOver = true;
}
IEnumerator WaitingParticle(float wait_time, RaycastHit hit, DiskFactory disk_factory, GameObject obj)
{
yield return new WaitForSeconds(wait_time);
hit.collider.gameObject.transform.position = new Vector3(0, -9, 0);
disk_factory.FreeDisk(obj);
}
public void BeginGame()
{
isGameStart = true;
}
- ScoreRecord:
这个脚本主要是实现计分功能,其代码实现很简单:
public class ScoreRecorder : MonoBehaviour {
public int score;
// Use this for initialization
void Start () {
score = 0;
}
public void Record(GameObject disk)
{
int temp = disk.GetComponent().score;
score += temp;
}
public void Reset()
{
score = 0;
}
}
- 飞碟自身的函数和飞碟工厂的脚本实现如下:
public class DiskData : MonoBehaviour {
public int score = 1;
public Vector3 direction;
public Vector3 scale = new Vector3(1, 1, 1);
}
public class DiskFactory : MonoBehaviour {
public GameObject diskPrefab = null;
private List used = new List();
private List free = new List();
public GameObject GetDisk(int round)
{
int choice = 0;
int scope = 1, scope1 = 4, scope2 = 7;
float startY = -10f;
string tag;
diskPrefab = null;
if(round == 1)
choice = Random.Range(0, scope);
else if(round == 2)
choice = Random.Range(0, scope1);
else if(round == 3)
choice = Random.Range(0, scope2);
if(choice <= scope)
tag = "disk1";
else if(choice <= scope && choice > scope1)
tag = "disk2";
else
tag = "disk3";
for(int i = 0; i < free.Count; i++)
{
if(free[i].tag == tag)
{
diskPrefab = free[i].gameObject;
free.Remove(free[i]);
break;
}
}
if(diskPrefab == null)
{
if (tag == "disk1")
diskPrefab = Instantiate(Resources.Load("Prefabs/disk1"), new Vector3(0, startY, 0), Quaternion.identity);
else if(tag == "disk2")
diskPrefab = Instantiate(Resources.Load("Prefabs/disk2"), new Vector3(0, startY, 0), Quaternion.identity);
else if(tag == "disk3")
diskPrefab = Instantiate(Resources.Load("Prefabs/disk3"), new Vector3(0, startY, 0), Quaternion.identity);
float ranX = Random.Range(-1f, -1f) < 0 ? -1 : 1;
diskPrefab.GetComponent().direction = new Vector3(ranX, startY, 0);
diskPrefab.transform.localScale = diskPrefab.GetComponent().scale;
}
used.Add(diskPrefab.GetComponent());
return diskPrefab;
}
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:
这个脚本我是按照老师课件上来写的,用来实现飞碟工厂的单例:
public class Singleton : 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;
}
}
}
- 接下来还有一个实现界面的UserGUI:
void OnGUI ()
{
boldStyle.normal.textColor = new Color(1, 0, 0);
boldStyle.fontSize = 20;
textStyle.normal.textColor = new Color(0,0,0, 1);
textStyle.fontSize = 20;
scoreStyle.normal.textColor = new Color(1,0,1,1);
scoreStyle.fontSize = 20;
overStyle.normal.textColor = new Color(1, 0, 0);
overStyle.fontSize = 25;
if (gameStart)
{
//用户射击
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.Hit(pos);
}
GUI.Label(new Rect(10, 5, 200, 50), "Score:", textStyle);
GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), scoreStyle);
GUI.Label(new Rect(Screen.width - 120, 5, 50, 50), "Life:", textStyle);
//显示当前血量
for (int i = 0; i < life; i++)
{
GUI.Label(new Rect(Screen.width - 75 + 10 * i, 5, 50, 50), "X", boldStyle);
}
//游戏结束
if (life == 0)
{
score = action.GetScore();
GUI.Label(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 250, 100, 100), "Game Over", overStyle);
GUI.Label(new Rect(Screen.width / 2 - 10, Screen.width / 2 - 200, 50, 50), "Score:", textStyle);
GUI.Label(new Rect(Screen.width / 2 + 50, Screen.width / 2 - 200, 50, 50), score.ToString(), textStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 150, 100, 50), "Restart"))
{
life = 10;
action.Restart();
return;
}
action.GameOver();
}
}
else
{
GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 350, 100, 100), "Hit UFO!", overStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 25, Screen.width / 2 - 250, 100, 50), "Game Start"))
{
gameStart = true;
action.BeginGame();
}
}
}
public void BloodReduce()
{
if(life > 0)
life--;
}
以上就是主要函数了,总体代码请看我的github,这是传送门