Unity3D 学习笔记 - ShootThatUFO! 打飞碟小游戏设计

注:本游戏设计环境为Unity 3D 4.6

 

游戏要求:

  1. 假设有一支枪在摄像机位置,在放置三个小球作为距离标记,调整视角直到小球在下中部
  2. 将鼠标所在平面坐标,转换为子弹(球体)射出的角度方向。子弹使用物理引擎,初速度恒定。
  3. 游戏要分多个 round , 飞碟数量每个 round 都是 n 个,但色彩,大小;发射位置,速度,角度,每次发射数量按预定规则变化。
  4. 用户按空格后,321倒数3秒,飞碟飞出(物理引擎控制),点击鼠标,子弹飞出。飞碟落地,或被击中,则准备下一次射击。
  5. 以下是一些技术要求:

    • 飞碟用一个带缓存的工厂生产,template 中放置预制的飞碟对象

 

一、游戏UML类图设计

 

游戏最核心的几个部分是:

SceneController

GameModel

ActionManager

UFOFactory

BulletFactory

Judge

UserInterface

 

没有在UML类图中出现的两部分:

SelfDestruct,挂载在Bullet Prefab上作为飞碟预设

public class SelfDestruct : MonoBehaviour {

    public float t = 0;

    // Update is called once per frame
    void Update () {
        t += Time.deltaTime;

        if (t > 1.5f) {
            this.gameObject.GetComponent<shootBullet> ().Free();
            BulletFactory.getInstance().releaseBullet(this.gameObject);

            SceneController.GetInstance().trials++;
            SceneController.GetInstance().okToShoot = true;
        }
    }
}

 

Detect Collision,挂载在UFO Prefab上作为子弹预设

public class DetectCollision : MonoBehaviour {
    
    UFOFactory ufoFactory = UFOFactory.getInstance();
    BulletFactory bulletFactory = BulletFactory.getInstance();

    void OnTriggerEnter (Collider other){
        SceneController.GetInstance ().onHittingUFO ();
        ufoFactory.releaseUFO (this.gameObject);

        other.gameObject.GetComponent<SelfDestruct> ().t = 0f;
        bulletFactory.releaseBullet (other.gameObject);

        SceneController.GetInstance ().okToShoot = true;
    }
    
}

 

二、核心类分析

 

SceneController 场景控制类

在这个类存放了许多相关的游戏数据:游戏状态,关数,分数,最高得分,与关数相关的场景数据;同时也存放了主摄像机的实例

这个类扩展了两个接口:IJudgeEvents, IUserAction,分别由Judge,UserInterface使用

namespace Com.ShootThatUFO {
    
    public interface IJudgeEvents {
        void onHittingUFO ();

        void onNotHittingUFO ();

        int getTrial();

        void gameFailed();

        void updateHighscore();

        void setGameStatus (int gameStatus);

        int getPoint ();

        int getHighscore();

        int getRound ();
    }

    public interface IUserActions {

        //Game Status, 0 for init, 1 for in game, 2 for mid round, 3 for failed, 4 for game cleared
        int getGameStatus ();

        void gameStart();

        int getRound ();

        int getUFONum ();

        int getPoint ();

        int getTrial ();

        int getHighscore ();

        void resetGame ();
    }

    public class SceneController : System.Object, IJudgeEvents, IUserActions {
        
        //Global Variables
        public static float GROUND = 0f;

        //Init
        public Camera mainCam;

        SceneController () {
            mainCam = UnityEngine.Camera.main;
            mainCam.transform.position = new Vector3 (0f, 2.56f, -10f);
            mainCam.transform.eulerAngles = new Vector3 (6.475f, 0f, 0f);
            mainCam.fieldOfView = 76f;
            mainCam.backgroundColor = Color.black;

            Physics.gravity = new Vector3 (0, -9.8f, 0);

            GameObject lightObj = new GameObject ();
            lightObj.name = "light";
            Light light = lightObj.AddComponent<Light> ();
            light.type = LightType.Directional;

        }
        //Game Data
        public int gameStatus = 0;
        public float gameStartCountDown = 3f;
        public bool okToShoot = true;

        public int round = 0;
        public int point = 0;
        public int trials = 0;
        public int ufoToToss = 20;
        public int highscore = 0;

        public float[] intervalPerRound = {2f, 2f, 1f, 1f, 0.5f};
        public float[] tossRange = {10f, 10f, 15f, 20f, 20f};
        public float[] tossSpeed = {20f, 20f, 25f, 25f, 30f};
        public float[] ufoScale = {0.5f, 0.8f, 1f};
        public Color[] ufoColor = {Color.green, Color.yellow, Color.red};
        public float[] tossMaxAngle = {0.6f,0.6f,0.6f,0.6f,0.7f};

        public void updateHighscore (){
            highscore = point;
        }

        //UI Interface
        public int getGameStatus (){
            return gameStatus;
        }

        public void gameStart (){
            gameStatus = 1;
        }

        public int getRound (){
            return round;
        }

        public int getUFONum (){
            return ufoToToss;
        }

        public int getPoint (){
            return point;
        }

        public int getTrial (){
            return trials;
        }

        public void resetGame (){
            round = 0;
            point = 0;
            trials = 0;
            ufoToToss = 20;
            _base_code.gameModel.t = 0f;
        }

        public int getHighscore (){
            return highscore;
        }

        //Judge Interface
        public void onHittingUFO (){
            point += 10;
        }

        public void onNotHittingUFO (){
            trials++;
        }

        public void gameFailed (){
            gameStatus = 3;
        }

        public void setGameStatus (int _gameStatus){
            this.gameStatus = _gameStatus;
        }

        //Singleton
        private static SceneController _instance;
        private BaseCode _base_code;

        
        public static SceneController GetInstance() {
            if (_instance == null) {
                _instance = new SceneController ();
            }
            return _instance;
        }
        
        public BaseCode getBaseCode(){
            return _base_code;
        }
        
        internal void setBaseCode(BaseCode bc){
            if (_base_code == null) {
                _base_code = bc;
            }
        }

    }

//End of Namespace
}

 

GameModel 游戏模型

这个类是游戏中最核心的部分,和SceneController, ActionManager, UFOFactory, BulletFactory聚合,负责投出飞碟,获取用户鼠标输入,发射子弹

飞碟投出颜色,大小,射出角度,速度,方向,均有此类随机产生,随机范围由SceneController中静态决定

public class GameModel : MonoBehaviour, IU3dActionCompleted {

    SceneController sceneController;
    public ActionManager actionManager;
    public UFOFactory ufoFactory;
    public BulletFactory bulletFactory;
    public float t = 0f;

    // Use this for initialization
    void Start () {
        sceneController = SceneController.GetInstance ();
        actionManager = ActionManager.GetInstance ();
        ufoFactory = UFOFactory.getInstance ();

        actionManager = ActionManager.GetInstance ();
        ufoFactory = UFOFactory.getInstance ();
        bulletFactory = BulletFactory.getInstance ();

    }

    public void OnActionCompleted (U3dAction action){
        sceneController.okToShoot = true;
        ufoFactory.releaseUFO (action.gameObject);
    }

    // Update is called once per frame
    void Update () {

        
        if (Input.GetMouseButtonDown (0) && sceneController.getGameStatus() == 1 && sceneController.okToShoot) {

                Ray ray = sceneController.mainCam.ScreenPointToRay (Input.mousePosition);

                GameObject bullet = bulletFactory.getNewBullet();
                actionManager.applyShootBullet(bullet, ray.direction);
                sceneController.okToShoot = false;
            }
        
        if (sceneController.getGameStatus() == 1){

            t += Time.deltaTime;
            if (t > sceneController.intervalPerRound[sceneController.round]) {
                t = 0;
                if (sceneController.ufoToToss != 0){
                    Vector3 tossTo = new Vector3 (Random.Range(-sceneController.tossRange[sceneController.round], 
                                                               sceneController.tossRange[sceneController.round]), 
                                                  SceneController.GROUND, 10f);
                    
                    float tossAngle = Random.Range(0.56f, sceneController.tossMaxAngle[sceneController.round]);
                    
                    float tossSpeed = Random.Range(15f, sceneController.tossSpeed[sceneController.round]);
                    
//                    Debug.Log ("Tossing! " + tossTo.x + " " + tossAngle + " " + tossSpeed);
                    
                    GameObject aUFO = ufoFactory.getNewUFO (sceneController.ufoScale[Random.Range(0, 3)], 
                                                            sceneController.ufoColor[Random.Range(0, 3)]);
                    actionManager.applyU3dTossUFO (aUFO, 
                                                   aUFO.transform.position, 
                                                   tossTo, 
                                                   tossAngle, 
                                                   tossSpeed, 
                                                   this);
                    sceneController.ufoToToss--;
                } else {
                    sceneController.round++;
                    sceneController.ufoToToss = 20;
                    sceneController.gameStatus = 2;
                }
            }

        }
    }
}

 

ActionManager 动作控制类

控制动作的发生,包括UFO的投出,子弹的发射。并未通过复杂数学来实现,而是在void setting()中为rigidbody添加Impulse类型力。

要注意的是,在回收动作时,要为rigidbody的velocity, angularVelocity设Vector3.zero,否则下次再添加Impulse时会出问题。

public class ActionManager :System.Object {
    private static ActionManager _instance;
    
    public static ActionManager GetInstance(){
        if (_instance == null) {
            _instance = new ActionManager();
        }
        return _instance;
    }

    public U3dAction applyU3dTossUFO (GameObject obj, Vector3 _startPoint, Vector3 _finishPoint, float _initialAngle, float _speed, IU3dActionCompleted _completed){

        U3dTossUFO UTU = obj.AddComponent<U3dTossUFO> ();
        UTU.setting (_startPoint, _finishPoint, _initialAngle, _speed, _completed);
        return UTU;
    }

    public U3dAction applyU3dTossUFO (GameObject obj, Vector3 _startPoint, Vector3 _finishPoint, float _initialAngle, float _speed){
        return applyU3dTossUFO (obj, _startPoint, _finishPoint, _initialAngle, _speed, null);
    }

    public U3dAction applyShootBullet (GameObject obj, Vector3 direction){
        shootBullet sb = obj.AddComponent<shootBullet> ();
        sb.setting (direction);
        return sb;
    }
}

public class U3dActionException : System.Exception {}

public interface IU3dActionCompleted {
    void OnActionCompleted (U3dAction action);
}

public class U3dAction : MonoBehaviour {
    public void Free(){}
}

public class U3dActionAuto : U3dAction {}

public class U3dActionMan : U3dAction {}


public class U3dTossUFO : U3dActionAuto {

    public Vector3 startPoint;
    public Vector3 finishPoint;

    public float forceStrength;
    public float initialAngle;

    private IU3dActionCompleted monitor = null;

//        initialAngle in Radial
    public void setting (Vector3 _startPoint, Vector3 _finishPoint, float _initialAngle, float _speed, IU3dActionCompleted _monitor){
        this.startPoint = _startPoint;

        this.finishPoint = _finishPoint;

        this.initialAngle = _initialAngle;

        this.forceStrength = _speed;

        this.monitor = _monitor;

        Vector3 force = Vector3.Slerp(Vector3.Normalize(finishPoint - startPoint), Vector3.up, initialAngle);

        Debug.Log (force + " " + forceStrength);

        this.gameObject.rigidbody.AddForce (force * forceStrength, ForceMode.Impulse);
    }

    void FixedUpdate() {

        if (transform.position.y < SceneController.GROUND + 0.1f) {
            if (monitor != null){
                monitor.OnActionCompleted (this);
            }
            this.Free();
        }

    }

    public void Free(){
        this.gameObject.rigidbody.velocity = Vector3.zero;
        this.gameObject.rigidbody.angularVelocity = Vector3.zero;
        Destroy (this);
    }

}

public class shootBullet : U3dActionAuto {

    Vector3 direction;
    float t = 0;

    public void setting (Vector3 direction){
        this.gameObject.rigidbody.AddForce (direction * 50f, ForceMode.Impulse);
    }

    void FixedUpdate() {
    }

    void OnTriggerEnter() {
        this.Free ();
    }

    public void Free(){
        this.gameObject.rigidbody.velocity = Vector3.zero;
        this.gameObject.rigidbody.angularVelocity = Vector3.zero;
        Destroy (this);
    }

}

 

UFOFactory 飞碟工厂

维持两个List<GameObject>分别保存用过的飞碟,正在使用的飞碟,仅在没有用过的飞碟来重用的时候才Instantiate新的飞碟

外部通过get, release两个方法来获取,消除飞碟

public class UFOFactory : System.Object {

    //Singleton Implementation
    private static UFOFactory _instance = null;

    public static UFOFactory getInstance() {
        if (_instance == null) {
            _instance = new UFOFactory();
        }
        return _instance;
    }

    //Initalizing
    private GameObject _UFOPrefab;
    private GameObject _ExplosionPrefab;

    UFOFactory (){
//            GameObject UFOPrimitive = GameObject.CreatePrimitive (PrimitiveType.Cylinder);
//            UFOPrimitive.transform.localScale = new Vector3 (1f, 0.1f, 1f);
//            UFOPrimitive.AddComponent<DetectCollision> ();
//            Rigidbody uRB = UFOPrimitive.AddComponent<Rigidbody> ();
//            uRB.drag = 1f;
//            UFOPrimitive.collider.isTrigger = true;


//            UnityEditor.AssetDatabase.CreateFolder ("Assets/Resources/Prefab");
//            _UFOPrefab = UnityEditor.PrefabUtility.CreatePrefab ("Assets/Resources/Prefab/UFOPrefab.prefab", UFOPrimitive);


        _UFOPrefab = Resources.Load ("Prefab/UFOPrefab") as GameObject;
//            UnityEngine.GameObject.Destroy (UFOPrimitive);

        _ExplosionPrefab = Resources.Load ("Exploson7", typeof (GameObject)) as GameObject;

        usedUFO = new List<GameObject> ();
        usingUFO = new List<GameObject> ();
    }

    //List of Gameobjects
    private List<GameObject> usedUFO;
    private List<GameObject> usingUFO;

    public GameObject getNewUFO (float scale, Color color) {

        GameObject aUFO;

        if (usedUFO.Count == 0) {
            aUFO = UnityEngine.GameObject.Instantiate(_UFOPrefab, new Vector3 (0f, SceneController.GROUND + 0.11f, -10f), Quaternion.identity) as GameObject;
            usingUFO.Add (aUFO);
            aUFO.tag = "Finish";
        }
        else {
            aUFO = usedUFO[0];
            usedUFO.Remove(aUFO);
            usingUFO.Add(aUFO);
            aUFO.SetActive (true);
        }

        aUFO.transform.position = new Vector3 (0f, SceneController.GROUND + 0.11f, -10f);
        aUFO.transform.localScale = new Vector3 (1f, 0.07f, 1f);
        aUFO.transform.localScale = aUFO.transform.localScale * scale;
        aUFO.renderer.material.color = color;

        return aUFO;
    }

    public void releaseUFO (GameObject UFOtoRelease){
        usingUFO.Remove (UFOtoRelease);
        usedUFO.Add (UFOtoRelease);
        UFOtoRelease.SetActive (false);

        UnityEngine.Object.Instantiate (_ExplosionPrefab, UFOtoRelease.transform.position, Quaternion.identity);
    }

}

 

BulletFactory 子弹工厂

与UFO工厂类似

public class BulletFactory : System.Object {
    
    //Singleton Implementation
    private static BulletFactory _instance = null;
    
    public static BulletFactory getInstance() {
        if (_instance == null) {
            _instance = new BulletFactory();
        }
        return _instance;
    }
    
    //Initalizing
    private GameObject _BulletPrefab;
    
    BulletFactory (){
//            GameObject BulletPrimitive = GameObject.CreatePrimitive (PrimitiveType.Sphere);
//            UFOPrimitive.transform.localScale = new Vector3 (0.3f, 0.3f, 0.3f);
//            Rigidbody bRB = BulletPrimitive.AddComponent<Rigidbody> ();
//            BulletPrimitive.AddComponent<SelfDestruct> ();

//            _BulletPrefab = UnityEditor.PrefabUtility.CreatePrefab ("Assets/Resources/Prefab/bulletPrefab.prefab", BulletPrimitive);

        usedBullet = new List<GameObject> ();
        usingBullet = new List<GameObject> ();

    }
    
    //List of Gameobjects
    private List<GameObject> usedBullet;
    private List<GameObject> usingBullet;
    
    public GameObject getNewBullet () {
        GameObject bullet;

        if (usedBullet.Count == 0) {
            bullet = UnityEngine.Object.Instantiate (Resources.Load ("Prefab/Bullet", typeof (GameObject))) as GameObject;
            usingBullet.Add (bullet);
        }
        else {
            Debug.Log ("Here");
            bullet = usedBullet[0];
            bullet.transform.position = UnityEngine.Camera.main.transform.position;
            bullet.SetActive(true);
            usedBullet.Remove(bullet);
            usingBullet.Add (bullet);
        }

        bullet.GetComponent<SelfDestruct> ().t = 0f;
        return bullet;
    }
    
    public void releaseBullet (GameObject bullettoRelease){
        bullettoRelease.SetActive (false);
        usingBullet.Remove (bullettoRelease);
        usedBullet.Add (bullettoRelease);
    }
    
}

 

Judge 裁判类

通过检测SceneController中的关键游戏数据,来判断游戏状态

//这些关键数据是否存放在裁判类中会更好?

public class Judge : MonoBehaviour{

    IJudgeEvents iJudege;

    void Start (){
        iJudege = SceneController.GetInstance() as IJudgeEvents;
    }

    void Update (){

        if (iJudege.getPoint() > iJudege.getHighscore()) {
            iJudege.updateHighscore();
        }
        
        if (iJudege.getRound() == 5) {
            iJudege.setGameStatus(4);
        }

        if (iJudege.getTrial() > 9) {
            iJudege.gameFailed();
        }
    }

}

 

 

UserInterface 界面类

控制游戏界面,同时接受用户启动游戏的命令(空格)

public class UserInterface : MonoBehaviour {

    IUserActions userAction;
    public bool spaceHitted = false;
    public float gameStartTimer = 3f;

    // Use this for initialization
    void Start () {
        userAction = SceneController.GetInstance () as IUserActions;
    }

    void OnGUI () {
        // Make a background box

        if (userAction.getGameStatus() == 1) {
            GUI.Label (new Rect(20,20,80,20), "Round " + (userAction.getRound() + 1));
            GUI.Label (new Rect (20, 40, 80, 20), userAction.getUFONum () + " to Go");
        }

        if (userAction.getGameStatus() == 0 || userAction.getGameStatus() == 2) {
            GUI.Label (new Rect(20,20,300,20), "Shoot That UFO! Hit Space Bar to Begin!");
            GUI.Label (new Rect (20, 40, 100, 20), "Time to start: " + gameStartTimer.ToString("0.0"));
            GUI.Label (new Rect (20, 100, 100, 20), "Next Round: " + (userAction.getRound() + 1));
        }

        if (userAction.getGameStatus () == 3) {
            GUI.Label (new Rect (20, 20, 200, 20), "Failed! Hit Spacebar to Restart.");
            GUI.Label (new Rect (20, 40, 100, 20), "Time to start: " + gameStartTimer.ToString("0.0"));
        }

        if (userAction.getGameStatus () != 4) {
            GUI.Label (new Rect (20, 80, 150, 20), "You have " + (10f - userAction.getTrial()) + " bullets left.");
            GUI.Label (new Rect (700, 20, 100, 20), "Highscore: " + userAction.getHighscore());
        }

        if (userAction.getGameStatus () == 4) {
            GUI.Label (new Rect(20,20,150,20), "Game Cleared!");
        }

        GUI.Label (new Rect (20, 60, 150, 20), "Your score is " + userAction.getPoint());
    }

    // Update is called once per frame
    void FixedUpdate () {

        if (userAction.getGameStatus () != 1) {
            if (Input.GetKey (KeyCode.Space)) {
                spaceHitted = true;
                if (userAction.getGameStatus () == 3){
                    userAction.resetGame();
                }
            }
            if (spaceHitted) {
                gameStartTimer -= Time.deltaTime;
            }
            if (gameStartTimer < 0f) {
                userAction.gameStart();
                gameStartTimer = 3f;
                spaceHitted = false;
            }
        }
        
    }
}

 

BaseCode 基础代码

将此代码挂载到游戏中,此代码将挂载Judge, GameModel, UserInterface到主摄像机中

public class BaseCode : MonoBehaviour {

    SceneController sceneController;
    public Judge judge;
    public GameModel gameModel;
    public UserInterface ui;

    // Use this for initialization
    void Start () {
        sceneController = SceneController.GetInstance ();

        sceneController.setBaseCode (this);

        gameModel = UnityEngine.Camera.main.gameObject.AddComponent <GameModel> ();

        judge = UnityEngine.Camera.main.gameObject.AddComponent <Judge> ();

        ui = UnityEngine.Camera.main.gameObject.AddComponent <UserInterface> ();
    }

}

 

posted on 2016-04-10 22:34  Wangsta  阅读(875)  评论(0编辑  收藏  举报

导航