題目
編寫一個簡單的鼠標打飛碟(Hit UFO)游戲
游戲內容要求:
游戲有 n 個 round,每個 round 都包括10 次 trial; 每個 trial的飛碟的色彩、大小、發射位置、速度、角度、同時出現的個數都可能不同。它們由該 round 的 ruler 控制; 每個 trial的飛碟有隨機性,總體難度隨 round 上升;
游戲的要求:
使用帶緩存的工廠模式管理不同飛碟的生產與回收,該工廠必須是場景單實例的!具體實現見參考資源 Singleton 模板類 近可能使用前面MVC 結構實現人機交互與游戲模型分離 如果你的使用工廠有疑問,參考:彈藥和敵人:減少,重用和再利用
實踐內容
游戲架構
RoundController:游戲的導演,總控制器,其中的shoot負責檢查是否擊中飛碟 RoundActionManager:動作管理者,負責管理動作的產生 UserGUI:負責渲染整個頁面的布局,主要是功能按鈕的實現 ScoreRecorder:負責分數的計算,根據飛碟的大小,速度,顏色,計算打中的得分 DiskDate:掛在飛碟預制上的組件,規定了飛碟的屬性 DiskFactory:負責生產不同大小,速度,顏色的飛碟
具體的源碼如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum State { WIN, LOSE, PAUSE, CONTINUE, START };
public interface ISceneController
{State state {
get ;
set ; }
void LoadResources();
void Pause();
void Resume();
void Restart();
}
public class RoundController : MonoBehaviour, IUserAction, ISceneController
{
public DiskFactory diskFactory;
public RoundActionManager actionManager;
public ScoreRecorder scoreRecorder;
private List<GameObject> disks;
private int round;
private GameObject shootAtSth;GameObject explosion;
public State state {
get ;
set ; }
public int leaveSeconds;
public int count;IEnumerator DoCountDown(){
while (leaveSeconds >=
0 ){
yield return new WaitForSeconds(
1 );leaveSeconds--;}}
void Awake(){SSDirector director = SSDirector.getInstance();director.setFPS(
60 );director.currentScenceController =
this ;LoadResources();diskFactory = Singleton<DiskFactory>.Instance;scoreRecorder = Singleton<ScoreRecorder>.Instance;actionManager = Singleton<RoundActionManager>.Instance;leaveSeconds =
60 ;count = leaveSeconds;state = State.PAUSE;disks =
new List<GameObject>();}
void Start(){round =
1 ;LoadResources();}
void Update(){LaunchDisk();Judge();RecycleDisk();}
public void LoadResources (){Camera.main.transform.position =
new Vector3(
0 ,
0 , -
30 );}
public void shoot ()//用戶在游戲狀態為開始或者繼續時,才能左鍵射擊{
if (Input.GetMouseButtonDown(
0 ) && (state == State.START || state == State.CONTINUE)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;
if (Physics.Raycast(ray,
out hit)){
if ((SSDirector.getInstance().currentScenceController.state == State.START || SSDirector.getInstance().currentScenceController.state == State.CONTINUE)){shootAtSth = hit.transform.gameObject;}}}}
public void LaunchDisk ()//每秒自動發射飛碟{
if (count - leaveSeconds ==
1 ){count = leaveSeconds;GameObject disk = diskFactory.GetDisk(round);Debug.Log(disk);disks.Add(disk);actionManager.addRandomAction(disk);}}
public void RecycleDisk ()//檢查需不需要回收飛碟{
for (
int i =
0 ; i < disks.Count; i++){
if (disks[i].transform.position.z < -
18 ){diskFactory.FreeDisk(disks[i]);disks.Remove(disks[i]);}}}
public void Judge ()//判斷游戲狀態,是否射中以及夠不夠分數進入下一回合{
if (shootAtSth !=
null && shootAtSth.transform.tag ==
"Disk" && shootAtSth.activeInHierarchy){scoreRecorder.Record(shootAtSth);diskFactory.FreeDisk(shootAtSth);shootAtSth =
null ;}
if (scoreRecorder.getScore() >
500 * round){round++;leaveSeconds = count =
60 ;}
if (round ==
3 ) {StopAllCoroutines();state = State.WIN;}
else if (leaveSeconds ==
0 && scoreRecorder.getScore() <
500 * round) {StopAllCoroutines();state = State.LOSE;}
else state = State.CONTINUE;}
public void Pause (){state = State.PAUSE;StopAllCoroutines();
for (
int i =
0 ; i < disks.Count; i++){disks[i].SetActive(
false );}}
public void Resume (){StartCoroutine(DoCountDown()); state = State.CONTINUE;
for (
int i =
0 ; i < disks.Count; i++){disks[i].SetActive(
true );}}
public void Restart (){scoreRecorder.Reset();Application.LoadLevel(Application.loadedLevelName);SSDirector.getInstance().currentScenceController.state = State.START;}}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISSActionCallback
{
void actionDone(SSAction source);
}
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 ; }
public virtual void Start (){
throw new System.NotImplementedException();}
public virtual void Update (){
throw new System.NotImplementedException();}
}
public class MoveToAction : SSAction
{
public Vector3 target;
public float speed;
private MoveToAction () { }
public static MoveToAction
getAction (Vector3 target,
float speed){MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>();action.target = target;action.speed = speed;
return action;}
public override void Update (){
this .transform.position = Vector3.MoveTowards(
this .transform.position, target, speed * Time.deltaTime);
if (
this .transform.position == target){
this .destroy =
true ;
this .callback.actionDone(
this );}}
public override void Start () { }}
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -
1 ;
public int currentAction =
0 ;
public static SequenceAction
getAction (
int repeat,
int currentActionIndex, List<SSAction> sequence){SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();action.sequence = sequence;action.repeat = repeat;action.currentAction = currentActionIndex;
return action;}
public override void Update (){
if (sequence.Count ==
0 )
return ;
if (currentAction < sequence.Count){sequence[currentAction].Update();}}
public void actionDone (SSAction source){source.destroy =
false ;
this .currentAction++;
if (
this .currentAction >= sequence.Count){
this .currentAction =
0 ;
if (repeat >
0 ) repeat--;
if (repeat ==
0 ){
this .destroy =
true ;
this .callback.actionDone(
this );}}}
public override void Start (){
foreach (SSAction action
in sequence){action.gameObject =
this .gameObject;action.transform =
this .transform;action.callback =
this ;action.Start();}}
void OnDestroy(){
foreach (SSAction action
in sequence){DestroyObject(action);}}
}
public class SSActionManager : MonoBehaviour
{
private Dictionary<
int , SSAction> actions =
new Dictionary<
int , SSAction>();
private List<SSAction> waitingToAdd =
new List<SSAction>();
private List<
int > watingToDelete =
new List<
int >();
protected void Update (){
foreach (SSAction ac
in waitingToAdd){actions[ac.GetInstanceID()] = ac;}waitingToAdd.Clear();
foreach (KeyValuePair<
int , SSAction> kv
in actions){SSAction ac = kv.Value;
if (ac.destroy){watingToDelete.Add(ac.GetInstanceID());}
else if (ac.enable){ac.Update();}}
foreach (
int key
in watingToDelete){SSAction ac = actions[key];actions.Remove(key);DestroyObject(ac);}watingToDelete.Clear();}
public void RunAction (GameObject gameObject, SSAction action, ISSActionCallback whoToNotify){action.gameObject = gameObject;action.transform = gameObject.transform;action.callback = whoToNotify;waitingToAdd.Add(action);action.Start();}}
public class RoundActionManager : SSActionManager, ISSActionCallback
{
public RoundController scene;
public MoveToAction action1, action2;
public SequenceAction saction;
float speed;
public void addRandomAction (GameObject gameObj){
int [] X = { -
20 ,
20 };
int [] Y = { -
5 ,
5 };
int [] Z = { -
20 , -
20 };Vector3 starttPos =
new Vector3(UnityEngine.Random.Range(-
20 ,
20 ),UnityEngine.Random.Range(-
5 ,
5 ),UnityEngine.Random.Range(
50 ,
10 ));gameObj.transform.position = starttPos;Vector3 randomTarget =
new Vector3(X[UnityEngine.Random.Range(
0 ,
2 )],Y[UnityEngine.Random.Range(
0 ,
2 )],Z[UnityEngine.Random.Range(
0 ,
2 )]);MoveToAction action = MoveToAction.getAction(randomTarget, gameObj.GetComponent<DiskData>().speed);RunAction(gameObj, action,
this );}
protected void Start (){scene = (RoundController)SSDirector.getInstance().currentScenceController;scene.actionManager =
this ;}
protected new void Update (){
base .Update();}
public void actionDone (SSAction source){Debug.Log(
"Done" );}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction
{
void shoot();
}
public class UserGUI : MonoBehaviour
{
private IUserAction action;
private float width, height;
private string countDownTitle;
void Start(){countDownTitle =
"Start" ;action = SSDirector.getInstance().currentScenceController
as IUserAction;}
float castw(
float scale){
return (Screen.width - width) / scale;}
float casth(
float scale){
return (Screen.height - height) / scale;}
void OnGUI(){width = Screen.width /
12 ;height = Screen.height /
12 ;GUI.Label(
new Rect(castw(
2 f) +
20 , casth(
6 f) -
20 ,
50 ,
50 ), ((RoundController)SSDirector.getInstance().currentScenceController).leaveSeconds.ToString());GUI.Button(
new Rect(
580 ,
10 ,
80 ,
30 ), ((RoundController)SSDirector.getInstance().currentScenceController).scoreRecorder.getScore().ToString());
if (SSDirector.getInstance().currentScenceController.state != State.WIN && SSDirector.getInstance().currentScenceController.state != State.LOSE&& GUI.Button(
new Rect(
10 ,
10 ,
80 ,
30 ), countDownTitle)){
if (countDownTitle ==
"Start" ){countDownTitle =
"Pause" ;SSDirector.getInstance().currentScenceController.Resume();}
else {countDownTitle =
"Start" ;SSDirector.getInstance().currentScenceController.Pause();}}
if (SSDirector.getInstance().currentScenceController.state == State.WIN){
if (GUI.Button(
new Rect(castw(
2 f), casth(
6 f), width, height),
"Win!" )){SSDirector.getInstance().currentScenceController.Restart();}}
else if (SSDirector.getInstance().currentScenceController.state == State.LOSE){
if (GUI.Button(
new Rect(castw(
2 f), casth(
6 f), width, height),
"Lose!" )){SSDirector.getInstance().currentScenceController.Restart();}}}
void Update(){action.shoot();}}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : MonoBehaviour
{
private float score;
public float getScore (){
return score;}
public void Record (GameObject disk){score += (
100 - disk.GetComponent<DiskData>().size * (
20 - disk.GetComponent<DiskData>().speed));Color c = disk.GetComponent<DiskData>().color;
switch (c.ToString()){
case "red" :score +=
50 ;
break ;
case "green" :score +=
40 ;
break ;
case "blue" :score +=
30 ;
break ;
case "yellow" :score +=
10 ;
break ;}}
public void Reset (){score =
0 ;}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskData : MonoBehaviour
{
public float size;
public Color color;
public float speed;
}
using System
.Collections
using System
.Collections .Generic
using UnityEnginepublic class DiskFactory : MonoBehaviour
{private List<GameObject> used = new List<GameObject>()private List<GameObject> free = new List<GameObject>()//顏色數組用于隨機分配顏色private Color[] color = { Color
.red , Color
.green , Color
.blue , Color
.yellow }//生產飛碟,先從回收部分取,若回收的部分為空,才從資源加載新的飛碟public GameObject GetDisk(int ruler){GameObject a_diskif (free
.Count >
0 ){a_disk = free[
0 ]free
.Remove (free[
0 ])}else{a_disk = GameObject
.Instantiate (Resources
.Load (
"Prefabs/Disk" )) as GameObjectDebug
.Log (a_disk)}switch (ruler){case
1 :a_disk
.GetComponent <DiskData>()
.size = UnityEngine
.Random .Range (
0 ,
6 )a_disk
.GetComponent <DiskData>()
.color = color[UnityEngine
.Random .Range (
0 ,
4 )]a_disk
.GetComponent <DiskData>()
.speed = UnityEngine
.Random .Range (
10 ,
15 )a_disk
.transform .localScale = new Vector3(a_disk
.GetComponent <DiskData>()
.size *
2 , a_disk
.GetComponent <DiskData>()
.size *
0.1 f, a_disk
.GetComponent <DiskData>()
.size *
2 )a_disk
.GetComponent <Renderer>()
.material .color = a_disk
.GetComponent <DiskData>()
.color break case
2 :a_disk
.GetComponent <DiskData>()
.size = UnityEngine
.Random .Range (
0 ,
4 )a_disk
.GetComponent <DiskData>()
.color = color[UnityEngine
.Random .Range (
0 ,
4 )]a_disk
.GetComponent <DiskData>()
.speed = UnityEngine
.Random .Range (
15 ,
20 )a_disk
.transform .localScale = new Vector3(a_disk
.GetComponent <DiskData>()
.size *
2 , a_disk
.GetComponent <DiskData>()
.size *
0.1 f, a_disk
.GetComponent <DiskData>()
.size *
2 )a_disk
.GetComponent <Renderer>()
.material .color = a_disk
.GetComponent <DiskData>()
.color break }a_disk
.SetActive (true)used
.Add (a_disk)return a_disk}//回收飛碟public void FreeDisk(GameObject disk){for (int i =
0 {if (used[i] == disk){disk
.SetActive (false)used
.Remove (used[i])free
.Add (disk)}}}
}
難點說明
工廠模式:主要是為了節省內存,提高效率,因此每次加載預制,在被擊中后不是銷毀,而且用一個鏈表儲存起來,在生產新的飛碟時,判斷該鏈表中是否存在可以用的飛碟,如果有則直接用,如果無才重新加載一個新的預制 另外一個是利用射線組件,來實現點擊打中目標,具體實現代碼在RoundController文件中的shoot函數里面
總結
以上是生活随笔 為你收集整理的3d学习笔记(四)——打飞碟小游戏 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。