3D游戏编程4--打飞碟

视频演示

编写一个简单的鼠标打飞碟(Hit UFO)游戏

游戏内容要求

  • 游戏有 n 个 round,每个 round 都包括10 次 trial
  • 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制
  • 每个 trial 的飞碟有随机性,总体难度随 round 上升
  • 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定

    游戏的要求

  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!

  • 尽可能使用前面 MVC 结构实现人机交互与游戏模型分离

游戏实现

总的来说,这次代码写的比较随意,冗余部分过多,而且没有很好的组织结构,函数的布置相对来说有点混乱,所以为了弥补这方面的不足,尽量做了好看的效果,加了一点粒子系统,当然也是参考了往届的博客。

文件结构

文本结构

文件结构

资源结构

资源结构

预制结构

预制结构

c#文件

DiskFactory

单例模式,得到飞碟id,得到飞碟对象,释放飞碟对象,为了避免开销大的问题,没有用destory,而是设置为diskList[id].SetActive(false);

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DiskFactory : System.Object
{
    private static DiskFactory _instance;
    private static List<GameObject> diskList; 
    public GameObject disk;      

    public static DiskFactory getInstance()
    {
        if (_instance == null)
        {
            _instance = new DiskFactory();
            diskList = new List<GameObject>();
        }
        return _instance;
    }

    public int getDiskId()
    {
        for (int i = 0; i < diskList.Count; ++i)
        {
            if (!diskList[i].activeInHierarchy)
            {
                return i;
            }
        }
        diskList.Add(GameObject.Instantiate(disk) as GameObject);
        return diskList.Count - 1;
    }

    public GameObject getDiskObject(int id)
    {
        if (id > -1 && id < diskList.Count)
        {
            return diskList[id];
        }
        return null;
    }


    public void free(int id)
    {
        if (id > -1 && id < diskList.Count)
        {

            diskList[id].GetComponent<Rigidbody>().velocity = Vector3.zero;
            //diskList[id].rigidbody.velocity = Vector3.zero;

            diskList[id].transform.localScale = disk.transform.localScale;
            diskList[id].SetActive(false);
        }
    }
}

DiskFactoryController

初始化disk,这次程序中的所有初始化,都是在类中声明为public,然后将预制拖到文件当中,而没有用Instantiate函数,感觉这样比较符合我的编程习惯。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactoryController : MonoBehaviour
{
    public GameObject disk;

    void Awake()
    {
        DiskFactory.getInstance().disk = disk;
    }
}

FirstController

包括场景的设置,发射飞碟的提示,发射飞碟,释放飞碟,得分。

  • 发射飞碟,大小,颜色,位置,方向都是随机的,但是没有与分数挂钩,都是打中一个得十分,每回合打完第十次进入下一回合,打完三回合,结束游戏。
  • FixedUpdate中用timeToNextEmission -= Time.deltaTime实现倒计时。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;


public class FirstController : MonoBehaviour
{
    public Color[] TotalColor = {Color.black,Color.blue,Color.cyan,Color.green,Color.grey,Color.red,Color.yellow };
    public float EmissionDelay = 1f;
    public float timeToNextEmission;
    private bool isCounting;
    private bool isShooting;
    public bool getIsCounting() { return isCounting; }
    public bool getIsShooting() { return isShooting; }

    private List<GameObject> disks = new List<GameObject>();   
    private List<int> diskIds = new List<int>();               
    private int diskScale;                  
    private Color diskColor;               
    private Vector3 emissionPosition;          
    private Vector3 emissionDirection;         
    private float emissionSpeed;
    private int emissionNumber;                 
    private bool isEmissionEnable;               

    private SceneController scene;
    private RoundController roundController;

    void Awake()
    {
        scene = SceneController.getInstance();
        scene.setFirstController(this);
    }

    public void setting(int scale, Color color, Vector3 emitPos, Vector3 emitDir, float speed, int num)
    {
        diskScale = scale;
        diskColor = color;
        emissionPosition = emitPos;
        emissionDirection = emitDir;
        emissionSpeed = speed;
        emissionNumber = num;
    }


    public void MakeEmissionDiskable()
    {
        if (!isCounting && !isShooting)
        {
            timeToNextEmission = EmissionDelay;
            isEmissionEnable = true;
        }
    }

    void emissionDisks()
    {
        scene.setTrial(scene.getTrial()+1);
        for (int i = 0; i < emissionNumber; ++i)
        {
            diskIds.Add(DiskFactory.getInstance().getDiskId());
            disks.Add(DiskFactory.getInstance().getDiskObject(diskIds[i]));
            diskScale = Random.Range(1,3);
            disks[i].transform.localScale *= diskScale;
            int chooseColor = Random.Range(0, 7);
            disks[i].GetComponent<Renderer>().material.color = TotalColor[chooseColor];
            disks[i].transform.position = new Vector3(Random.Range(-2.5f,2.5f), emissionPosition.y + i, emissionPosition.z);
            disks[i].SetActive(true);
            emissionDirection.x = emissionDirection.x * Random.Range(-1, 1);
            disks[i].GetComponent<Rigidbody>().AddForce(emissionDirection * Random.Range(emissionSpeed * 5, emissionSpeed * 10) / 10, ForceMode.Impulse);
        }
        if(scene.getTrial() == 10)
        {
            scene.nextRound();
        }
        if(scene.getRound() == 4)
        {
            scene.gameOver();
        }
    }

    void freeADisk(int i)
    {
        DiskFactory.getInstance().free(diskIds[i]);
        disks.RemoveAt(i);
        diskIds.RemoveAt(i);
    }

    void FixedUpdate()
    {
        if (timeToNextEmission > 0)
        {
            isCounting = true;
            timeToNextEmission -= Time.deltaTime;
        }
        else
        {
            isCounting = false;
            if (isEmissionEnable)
            {
                emissionDisks();
                isEmissionEnable = false;
                isShooting = true;
            }
        }
    }

    void Update()
    {
        for (int i = 0; i < disks.Count; i++)
        {
            if (!disks[i].activeInHierarchy)
            {  
                scene.getScoreController().hitDisk(); 
                freeADisk(i);
            }
            else if (disks[i].transform.position.y < 0)
            {   
                scene.getScoreController().hitGround(scene.getRound()); 
                freeADisk(i);
            }
        }
        if (disks.Count == 0)
        {
            isShooting = false;
        }
    }
}

RoundController

回合控制,一共设置三关,除了速度,数量是真正预设的,其他的都会被被发射之前随机设置,第一关每次一个,速度为2,第二关每次两个,速度为4,第三关每次3此,速度为8。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class RoundController : MonoBehaviour
{
    private Color color;
    private Vector3 emissionPositon;
    private Vector3 emissionDiretion;
    private float speed;
    private SceneController scene;
    public int trial = 0;

    void Awake()
    {
        SceneController.getInstance().setRoundController(this);
        scene = SceneController.getInstance();
    }

    void Start()
    {
        scene.nextRound();
    }

    public void loadRoundData(int round)
    {
        trial = 0;
        switch (round)
        {
            case 1:    
                color = Color.green;
                emissionPositon = new Vector3(-2.5f, 0.2f, -5f);
                emissionDiretion = new Vector3(24.5f, 40.0f, 67f);
                speed = 2;
                SceneController.getInstance().getFirstController().setting(1, color, emissionPositon, emissionDiretion.normalized, speed, 1);
                break;
            case 2:    
                color = Color.red;
                emissionPositon = new Vector3(2.5f, 0.2f, -5f);
                emissionDiretion = new Vector3(-24.5f, 35.0f, 67f);
                speed = 4;
                SceneController.getInstance().getFirstController().setting(1, color, emissionPositon, emissionDiretion.normalized, speed, 2);
                break;
            case 3:
                color = Color.cyan;
                emissionPositon = new Vector3(2.5f, 0.2f, -5f);
                emissionDiretion = new Vector3(-24.5f, 35.0f, 67f);
                speed = 8;
                SceneController.getInstance().getFirstController().setting(1, color, emissionPositon, emissionDiretion.normalized, speed, 3);
                break;
        }
    }
}

SceneController

实现一些基本函数,没有在SceneController中实现其他的类的实例化,但是在用到的其他类的Awake函数中,调用set…函数,使得可以用其他类的函数,

using UnityEngine;
using System.Collections;

public interface GameInterface
{
    void MakeEmissionDiskable();
    bool isCounting();
    bool isShooting();
    int getRound();
    int getScore();
    int getTimeToNextEmissionTime();
    void nextRound();
    void setScore(int point);
    void setRound(int input);
    int getTrial();
    void setTrial(int input);
    void gameOver();
}

public class SceneController : System.Object, GameInterface
{
    private static SceneController _instance;
    private RoundController _roundController;
    private FirstController _firstController;
    private ScoreController _scoreController;
    private UserInterface _userInterface;

    private int _round;
    private int _score;
    private int _trial;
    public bool isGameOver;

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

    public void setFirstController(FirstController input)
    {
        _firstController = input;
    }
    internal FirstController getFirstController()
    {
        return _firstController;
    }

    public void setScoreController(ScoreController input)
    {
        _scoreController = input;
    }
    internal ScoreController getScoreController()
    {
        return _scoreController;
    }
    internal UserInterface getUserInterface()
    {
        return _userInterface;
    }
    public void setUserInterface(UserInterface input)
    {
        _userInterface = input;
    }
    public void setRoundController(RoundController input)
    {
        _roundController = input;
    }
    internal RoundController getRoundController()
    {
        return _roundController;
    }


    public void MakeEmissionDiskable()
    {
        _firstController.MakeEmissionDiskable();
    }


    public bool isCounting()
    {
        return _firstController.getIsCounting();
    }
    public bool isShooting()
    {
        return _firstController.getIsShooting();
    }
    public int getRound()
    {
        return _round;
    }
    public void setRound(int input)
    {
        _round = input;
    }
    public int getScore()
    {
        return _score;
    }
    public int getTimeToNextEmissionTime()
    {
        return (int)_firstController.timeToNextEmission + 1;
    }
    public void setScore(int input)
    {
        _score = input;
    }
    public void nextRound()
    {
        _roundController.loadRoundData(++_round);
    }
    public int getTrial()
    {
        return _roundController.trial;
    }
    public void setTrial(int input)
    {
        _roundController.trial = input;
    }
    public void gameOver()
    {
        isGameOver = true;
        _userInterface.gameOver();
    }
}

ScoreController

分数管理,击中一个10分,一个落地,第一关扣5分,第二关扣20分,第三关扣45分。

using UnityEngine;
using System.Collections;

public class ScoreController : MonoBehaviour
{
    private int oneDiskScore = 10;
    private int oneDiskFail = 5;

    private SceneController scene;

    void Awake()
    {
        scene = SceneController.getInstance();
        scene.setScoreController(this);
    }


    public void hitDisk()
    {
        scene.setScore(scene.getScore() + oneDiskScore);
    }

    public void hitGround(int input)
    {
        scene.setScore(scene.getScore() - oneDiskFail * input * input);
    }
}

UserInterface

用户界面,包括射击子弹,粒子系统,界面提示。游戏结束后,按space空格键可以重新开始,每次射击有0.25秒的间隔。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

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;
    public bool isGameOver = false;
    private float nextFireTime;

    private GameInterface gameInterface;
    private SceneController scene;

    public void Awake()
    {
        scene = SceneController.getInstance();
        scene.setUserInterface(this);
    }
    void Start()
    {
        bullet = GameObject.Instantiate(bullet) as GameObject;
        explosion = GameObject.Instantiate(explosion) as ParticleSystem;
        gameInterface = SceneController.getInstance() as GameInterface;
    }

    public void gameOver()
    {
        isGameOver = true;
        mainText.text = "GameOver,put down SPACE to Reset";
    }

    void Update()
    {
        if(Input.GetKeyDown("space") && isGameOver)
        {
            scene.setRound(0);
            scene.nextRound();
            isGameOver = false;
        }
        if (!isGameOver)
        {
            if (gameInterface.isCounting())
            {
                //mainText.text = ((int)gameInterface.getTimeToNextEmissionTime()).ToString();
                mainText.text = "Trial " + (gameInterface.getTrial() + 1).ToString();
            }
            else
            {

                gameInterface.MakeEmissionDiskable();
                if (gameInterface.isShooting())
                {
                    mainText.text = "";
                }

                if (gameInterface.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.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.renderer.material.color = hit.collider.gameObject.renderer.material.color;
                        explosion.Play();

                        hit.collider.gameObject.SetActive(false);
                    }
                }
            }
            roundText.text = "Round: " + gameInterface.getRound().ToString();
            scoreText.text = "Score: " + gameInterface.getScore().ToString();

            if (round != gameInterface.getRound())
            {
                round = gameInterface.getRound();
                mainText.text = "Round " + round.ToString() + " !";
            }
        }
    }
}

资源

视频及资源

猜你喜欢

转载自blog.csdn.net/yaoxh6/article/details/79926693