編制一個射飛碟遊戲。html
具體要求以下:緩存
1 假設有一支槍在攝像機位置(0,1,-10),在(0,0,0-10-20)放置三個小球做爲距離標記,調整視角直到小球在下中部dom
2 將鼠標所在平面座標,轉換爲子彈(球體)射出的角度方向。子彈使用物理引擎,初速度恆定。(U3d 座標變換: http://www.cnblogs.com/tekkaman/p/3809409.html )ide
Vector3 mp = Input.mousePosition; //get Screen Position函數
print (mp.ToString());this
Vector3 mp1 = cam.camera.ScreenToViewportPoint (mp);spa
mp1.z = 10; //距攝像頭 10 位置立面.net
mp1 = cam.camera.ViewportToWorldPoint (mp1);設計
print (mp1.ToString());3d
3 遊戲要分多個 round , 飛碟數量每一個 round 都是 n 個,但色彩,大小;發射位置,速度,角度,每次發射數量按預約規則變化。
4 用戶按空格後,321倒數3秒,飛碟飛出(物理引擎控制),點擊鼠標,子彈飛出。飛碟落地,或被擊中,則準備下一次射擊。
5 如下是一些技術要求:
◦ 子彈僅須要一個,不用時處於 deactive 狀態
◦ 飛碟用一個帶緩存的工廠生產,template 中放置預製的飛碟對象
◦ 程序類圖設計大體以下:
具體實現:
腳本實現子彈射擊
腳本掛在在攝像機上
子彈射擊的思路:當用戶點擊鼠標時,從攝像機到鼠標建立一條射線,射線的方向便是子彈發射的方向,子彈採用剛體組件,所以發射子彈只須要給子彈施加一個力。子彈對象只有一個,下一次發射子彈時,必須改變子彈的位置(雖然有了剛體組件不建議修改transform,但也沒有其它方法改變子彈位置了吧)。爲了避免讓子彈繼承上一次發射的速度,必須將子彈的速度歸零重置。
子彈的擊中判斷:採用射線而不是物理引擎,由於物理引擎在高速物體碰撞時常常不能百分百檢測獲得。
完成飛碟工廠
建立新的命名空間Com.Mygame,單例類DiskFactory和SceneController都定義其中。飛碟工廠類的目的是管理飛碟實例,同時對外屏蔽飛碟實例的的提取和回收細節,對於須要使用飛碟的其餘對象,只能使用工廠類提供的3個函數,分別是getDisk()、getDiskObject()、free()。
using UnityEngine; using System.Collections; using System.Collections.Generic; using Com.Mygame; namespace Com.Mygame { public class DiskFactory: System.Object { private static DiskFactory _instance; private static List<GameObject> diskList; public GameObject diskPrefab; public static DiskFactory getInstance() { if (_instance == null) { _instance = new DiskFactory(); diskList = new List<GameObject>(); } return _instance; } // 獲取可用飛碟id public int getDisk() { for (int i = 0; i < diskList.Count; ++i) { if (!diskList[i].activeInHierarchy) { return i; // 飛碟空閒 } } // 無空閒飛碟,則實例新的飛碟預設 diskList.Add(GameObject.Instantiate(diskPrefab) as GameObject); return diskList.Count-1; } // 獲取飛碟對象 public GameObject getDiskObject(int id) { if (id >= 0 && id < diskList.Count) { return diskList[id]; } return null; } // 回收飛碟 public void free(int id) { if (id >= 0 && id < diskList.Count) { // 重置飛碟速度 diskList[id].GetComponent<Rigidbody>().velocity = Vector3.zero; // 重置飛碟大小 diskList[id].transform.localScale = diskPrefab.transform.localScale; diskList[id].SetActive(false); } } } } public class DiskFactoryBaseCode : MonoBehaviour { public GameObject disk; void Awake () { // 初始化預設對象 DiskFactory.getInstance().diskPrefab = disk; } }
完成遊戲場景
場景類是整個飛碟射擊遊戲的核心類,主要負責飛碟動做的處理。參考師兄的設計:首先須要倒計時功能,能夠經過幾個整型變量和布爾變量完成。另外須要飛碟發射功能,經過setting函數保存好飛碟的發射信息,每次倒計時完成後,經過emitDisks獲取飛碟對象,並經過發射信息初始化飛碟,再給飛碟一個力就能夠發射了。而飛碟的回收在Update裏完成,一種是飛碟被擊中(飛碟不在場景中)了,須要調用Judge得到分數。另外一種是飛碟在場景中,可是掉在地上了,須要調用Judge丟失分數。
using UnityEngine; using System.Collections; using System.Collections.Generic; using Com.Mygame; public class GameModel : MonoBehaviour { public float countDown = 3f; public float timeToEmit; private bool counting; private bool shooting; public bool isCounting() { return counting; } public bool isShooting() { return shooting; } private List<GameObject> disks = new List<GameObject> (); private List<int> diskIds = new List<int> (); private int diskScale; private Color diskColor; private Vector3 emitPosition; private Vector3 emitDirection; private float emitSpeed; private int emitNumber; private bool emitEnable; private SceneController scene; void Awake() { scene = SceneController.getInstance (); scene.setGameModel (this); } public void setting(int scale, Color color, Vector3 emitPos, Vector3 emitDir, float speed, int num) { diskScale = scale; diskColor = color; emitPosition = emitPos; emitDirection = emitDir; emitSpeed = speed; emitNumber = num; } public void prepareToEmitDisk() { if (!counting && !shooting) { timeToEmit = countDown; emitEnable = true; } } void emitDisks() { for (int i = 0; i < emitNumber; i++) { diskIds.Add (DiskFactory.getInstance ().getDisk ()); disks.Add (DiskFactory.getInstance ().getDiskObject (diskIds [i])); disks [i].transform.localScale *= diskScale; disks [i].GetComponent<Renderer> ().material.color = diskColor; disks [i].transform.position = new Vector3 (emitPosition.x, emitPosition.y + i, emitPosition.z); disks [i].SetActive (true); disks [i].GetComponent<Rigidbody> ().AddForce (emitDirection * Random.Range (emitSpeed * 5, emitSpeed * 10) / 10, ForceMode.Impulse); } } void freeDisk(int i) { DiskFactory.getInstance ().free (diskIds [i]); disks.RemoveAt (i); diskIds.RemoveAt (i); } void FixedUpdate() { if (timeToEmit > 0) { counting = true; timeToEmit -= Time.deltaTime; } else { counting = false; if (emitEnable) { emitDisks (); emitEnable = false; shooting = true; } } } // Use this for initialization void Start () { } // Update is called once per frame void Update () { for (int i = 0; i < disks.Count; i++) { if (!disks [i].activeInHierarchy) { scene.getJudge ().scoreADisk (); freeDisk (i); } else if (disks [i].transform.position.y < 0) { scene.getJudge ().failADisk (); freeDisk (i); } } if (disks.Count == 0) { shooting = false; } } }
場景控制器
場景控制類主要實現接口定義和保存注入對象。另外它有兩個私有變量round和point,分別記錄遊戲正在進行的回合,以及玩家目前的得分。
using UnityEngine; using System.Collections; using Com.Mygame; namespace Com.Mygame { // Com.Mygame內添加 public interface IUserInterface { void emitDisk(); } public interface IQueryStatus { bool isCounting(); bool isShooting(); int getRound(); int getPoint(); int getEmitTime(); } public class SceneController : System.Object, IQueryStatus, IUserInterface { private static SceneController _instance; private GameModel _gameModel; private SceneControllerBaseCode _baseCode; private int _round; private int _point; public static SceneController getInstance() { if (_instance == null) { _instance = new SceneController (); } return _instance; } public void setSceneControllerBaseCode (SceneControllerBaseCode obj) { _baseCode = obj; } internal SceneControllerBaseCode getSceneControllerBC() { return _baseCode; } public void setGameModel(GameModel obj) { _gameModel = obj; } // 當前程序或派生類可用 internal GameModel getGameModel() { return _gameModel; } public void emitDisk() { _gameModel.prepareToEmitDisk (); } public bool isCounting() { return _gameModel.isCounting (); } public bool isShooting() { return _gameModel.isShooting (); } public int getRound() { return _round; } public int getPoint() { return _point; } public int getEmitTime() { return (int)_gameModel.timeToEmit + 1; } public void setPoint(int point) { _point = point; } public void nextRound() { _point = 0; } } } public class SceneControllerBaseCode : MonoBehaviour { private Color color; private Vector3 emitPos; private Vector3 emitDir; private float speed; void Awake() { SceneController.getInstance().setSceneControllerBaseCode(this); } void Start() { color = Color.green; emitPos = new Vector3(-2.5f, 0.2f, -5f); emitDir = new Vector3(24.5f, 40.0f, 67f); speed = 4; SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1); } }
完善UserInterface
using UnityEngine; using UnityEngine.UI; using System.Collections; using Com.Mygame; public class UserInterface : MonoBehaviour { public Text mainText; public Text scoreText; public Text roundText; private int round; public GameObject bullet; public ParticleSystem explosion; public float fireRate = .25f; public float speed = 500f; private float nextFireTime; private IUserInterface userInt; private IQueryStatus queryInt; // Use this for initialization void Start () { bullet = GameObject.Instantiate (bullet) as GameObject; explosion = GameObject.Instantiate (explosion) as ParticleSystem; userInt = SceneController.getInstance () as IUserInterface; queryInt = SceneController.getInstance () as IQueryStatus; } // Update is called once per frame void Update () { if (queryInt.isCounting ()) { mainText.text = ((int)queryInt.getEmitTime ()).ToString (); } else { if (Input.GetKeyDown (KeyCode.Space)) { userInt.emitDisk (); } if (queryInt.isShooting ()) { mainText.text = " "; } else { mainText.text = "Press space"; } if (queryInt.isShooting() && Input.GetMouseButtonDown (0) && Time.time > nextFireTime) { nextFireTime = Time.time + fireRate; Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition); bullet.GetComponent<Rigidbody> ().velocity = Vector3.zero; bullet.transform.position = transform.position; bullet.GetComponent<Rigidbody> ().AddForce (ray.direction * speed, ForceMode.Impulse); RaycastHit hit; if (Physics.Raycast (ray, out hit) && hit.collider.gameObject.tag == "Disk") { explosion.transform.position = hit.collider.gameObject.transform.position; explosion.GetComponent<Renderer> ().material.color = hit.collider.gameObject.GetComponent<Renderer> ().material.color; explosion.Play (); hit.collider.gameObject.SetActive (false); } } } roundText.text = " Round: " + queryInt.getRound ().ToString (); scoreText.text = " Score: " + queryInt.getPoint ().ToString (); if (round != queryInt.getRound ()) { round = queryInt.getRound (); mainText.text = "Round: " + round.ToString() + "!"; } } }
補充遊戲規則–Judge計分系統
遊戲規則單獨做爲一個類,有利於往後修改。這裏須要處理的規則無非就兩個,得分和失分。另外,得分須要判斷是否能晉級下一關。能就調用接口函數nextRound()。
using UnityEngine; using System.Collections; using Com.Mygame; public class Judge : MonoBehaviour { public int oneDiskScore = 10; public int oneDiskFail = 10; public int disksToWin = 4; private SceneController scene; void Awake() { scene = SceneController.getInstance(); scene.setJudge(this); } void Start() { scene.nextRound(); // 默認開始第一關 } // 擊中飛碟得分 public void scoreADisk() { scene.setPoint(scene.getPoint() + oneDiskScore); if (scene.getPoint() == disksToWin*oneDiskScore) { scene.nextRound(); } } // 掉落飛碟失分 public void failADisk() { scene.setPoint(scene.getPoint() - oneDiskFail); } }
在場景控制器中添加相應裁判的代碼
// Com.Mygame內添加 public interface IjudgeEvent { void nextRound(); void setPoint(int point); } // 類內部添加而且類繼承IjudgeEvent private Judge _judge; public void setJudge(Judge obj) { _judge = obj; } internal Judge getJudge() { return _judge; }
在GameModel中調用裁判計分功能
void Update () { for (int i = 0; i < disks.Count; i++) { if (!disks [i].activeInHierarchy) { scene.getJudge ().scoreADisk (); freeDisk (i); } else if (disks [i].transform.position.y < 0) { scene.getJudge ().failADisk (); freeDisk (i); } } if (disks.Count == 0) { shooting = false; } }
設置關卡
在SceneControllerBaseCode中添加關卡信息,經過添加loadRoundData來完成每一個關卡對遊戲對象屬性的設置。
public void loadRoundData(int round) { switch(round) { case 1: // 第一關 color = Color.green; emitPos = new Vector3(-2.5f, 0.2f, -5f); emitDir = new Vector3(24.5f, 40.0f, 67f); speed = 4; SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1); break; case 2: // 第二關 color = Color.red; emitPos = new Vector3(2.5f, 0.2f, -5f); emitDir = new Vector3(-24.5f, 35.0f, 67f); speed = 4; SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 2); break; case 3: // 第二關 color = Color.yellow; emitPos = new Vector3(2.5f, 0.2f, -5f); emitDir = new Vector3(-24.5f, 35.0f, 67f); speed = 4; SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 3); break; } }
遊戲效果:
參考連接: