Unity3D学习:射击小游戏——飞碟世界

游戏规则:游戏有三个关卡,每个关卡有四次发射机会,每次发射的飞碟大小颜色速度方向位置都不同,有50分初始分,每打中一个加10分,掉落一个在地减10分,第一二关都是90分过关,第三关70分过关,第一关每次发射一个飞碟,第二关两个,第三关三个。空格发射飞碟,鼠标左键射击飞碟。如图(虽然UI还没怎么优化过,将就一下了,还有没有录第三关也是为了让gif短一点,因为上传的gif有内存限制):


下面进入正题,怎么制作呢?

回收与再利用:首先这种涉及到类似于子弹啊,雨滴等物体的游戏,这种资源最好能回收与再利用,这样就可以节约资源,省的一直创建新对象,别看创建一个对象对游戏影响不怎么大,在一些大型游戏中这是很重要的思想的,因为一个场景往往涉及到成千上万的这种小物体,如果每一个都要创建一遍才能用,会导致卡顿,速度跟不上。

工厂模式:物体的生成都由一个工厂管理,需要什么只需要跟工厂说出你要什么,它会自己实现生成给你,里面如何生成不用你操心,实际上也有抽象的意思。就像这次的游戏,我数据设置好了,预设也传给了工厂,所以在需要加载飞碟是,只用调用一个getDisk()就可以了,不需要操心怎么生成。当然回收与再利用也是在工厂中实现的,也就是回收仓库也是在工厂里。

射线碰撞:用鼠标点击屏幕,如何获取我所点到的物体以及我的鼠标坐标呢?就是用从摄像机中发射出射线,然后鼠标点的位置作为方向,这样就可以选中第一个被射击中的物体,同时也可以记录出鼠标所点位置。

下面是这游戏制作代码的架构图:



SceneController类:负责把各个类之间对接,作为内部设置与用户界面的连接。

GameModel类:管理飞碟的基础数据。

RoundController类:管理关卡数据,每一个关卡对应需要不同的飞碟数据,在这里会给飞碟根据不通关卡加载不同属性。

Recorder类:管理记录加分减分。

Userface类:管理用户界面以及界面逻辑。

DiskFactory类:利用传进来的飞碟预设根据需要生产飞碟存于仓库,回收与再利用飞碟。

下面是代码:

工厂类(看注释应该就能看懂):

public class DiskFactory : System.Object
    {
        private static DiskFactory _instance;
        private GameObject prefabs_disk;
        public static List<GameObject> used = new List<GameObject>();
        public static List<GameObject> unused = new List<GameObject>();
        public static DiskFactory getInstance()             //单例化工厂
        {
            if (_instance == null)
            {
                _instance = new DiskFactory();
            }
            return _instance;
        }
        public void SetObject(GameObject x)     //把预设传入工厂
        {
            prefabs_disk = x;
        }
        public GameObject getDisk() //提供从工厂获取飞碟的借口
        {
            GameObject t;
            if (unused.Count == 0)
                t = GameObject.Instantiate(prefabs_disk);
            else
            {
                t = unused[0];
                unused.Remove(t);
            }
            used.Add(t);
            return t;
        }
        public void free(GameObject t)  //提供从外界回收飞碟到工厂的接口
        {
            t.GetComponent<Rigidbody>().velocity = Vector3.zero;
            t.transform.localScale = prefabs_disk.transform.localScale;
            t.SetActive(false);
            used.Remove(t);
            unused.Add(t);
        }
    }

接下来是SceneController类,它的工作实际上就是把各个类的功能合并起来,需要什么功能就它就调用某个类的功能来实现:

public class SceneController : System.Object, IQuery, IUserInterface
    {
        private static SceneController _instance;
        private RoundController _RC;
        private GameModel _gameModel;
        private Recorder _Recorder;
        public static SceneController getInstance()         //单例化
        {
            if (_instance == null)
            {
                _instance = new SceneController();
            }
            return _instance;
        }
        public void setdisk(GameObject t)               //把飞碟预设传入工厂
        {
            DiskFactory.getInstance().SetObject(t);
        }
        public int getRound()
        {
            return _RC.round;
        }
        public int getScore()
        {
            return _Recorder.score;
        }
        public void setGameModel(GameModel obj) { _gameModel = obj; }
        internal GameModel getGameModel() { return _gameModel; }
        public void setRecorder(Recorder obj) { _Recorder = obj; }
        internal Recorder getRecorder() { return _Recorder; }
        public void setRoundController(RoundController obj) { _RC = obj; }
        internal RoundController getRoundController() { return _RC; }
        public void emitDisk() { _gameModel.emitReady(); }
        public bool isShooting() { return _gameModel.shooting; }
        public void nextRound(int i)            //进入下一关卡
        {
            if (i == 1 || i == 2)
            {
                _Recorder.reset();
                _RC.round++;
                _RC.loadRoundData();
            }
            else if (i == 0) _RC.round = 0;
        }
    }

两个用户与SceneController的接口(在SceneController实现):

public interface IUserInterface         //Scenecontroller与用户界面的接口
    {
        void emitDisk();
        void setdisk(GameObject t);
    }
    public interface IQuery         //Scenecontroller与用户界面的接口
    {
        bool isShooting();
        int getRound();
        int getScore();
    }

以上三个部分我都放在一个自定义的namespace里面(name_game);

Userface用户界面实现UI以及提供用户接口逻辑:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using name_game;
public class UserInterface : MonoBehaviour {
    public int changes = 4;         //发射机会
    public Text roundText;          //显示目前关卡数
    public Text scoreText;          //显示目前分数
    public Text mainText;          //主显示板,显示规则以及提示
    public Text changeText;         //显示目前剩余发射机会
    public Camera ca;
    private int states = 0;      //0为初始界面,1为规则界面,2为游戏中 
    private IUserInterface userInt;     // 用户接口  
    private IQuery queryInt;      // 查询接口  
    public GameObject disktem;
    public ParticleSystem _exposion;    //爆炸粒子
    void Start()
    {
        userInt = SceneController.getInstance() as IUserInterface;
        queryInt = SceneController.getInstance() as IQuery;
        _exposion = GameObject.Instantiate(_exposion) as ParticleSystem;
        changes = 4;
        states = 0;
        userInt.setdisk(disktem);
        mainText.text = "欢迎来到飞碟世界,请按空格进入游戏";
    }
    void Update()
    {
        if ((queryInt.getRound() == 1 || queryInt.getRound() == 2 )&& changes == 0 && !queryInt.isShooting()) //判断一二关的结果
        {
            if (queryInt.getScore() >= 90)
            {
                mainText.text = "You win!(空格进入下一关)";
                if (Input.GetKeyDown("space"))
                {
                    states = 1;
                    changes = 4;
                    SceneController.getInstance().nextRound(1);
                }
            }
            else
            {
                mainText.text = "You lose!(空格重新开始)";
                if (Input.GetKeyDown("space"))
                {
                    states = 0;
                    changes = 4;
                    SceneController.getInstance().nextRound(0);
                }
            }
        }
        if (queryInt.getRound() == 3 && changes == 0 && !queryInt.isShooting()) //判断第三关的结果
        {
            if (queryInt.getScore() >= 70) mainText.text = "You win!You have passed all arounds(空格重新开始游戏)";
            else mainText.text = "You lose!(空格重新开始)";
            if (Input.GetKeyDown("space"))
            {
                states = 0;
                changes = 4;
                SceneController.getInstance().nextRound(0);
            }
        }
        changeText.text = "Changes: " + changes.ToString();
        if (changes>0&&Input.GetKeyDown("space")) {
            if(states==0)
            {
                states = 1;
                if(queryInt.getRound()==0) SceneController.getInstance().nextRound(1);
                mainText.text = "你有50初始分,每一关有四次发射机会,每次发射射出的飞碟数量以及速度还有大小颜色都不同,每一轮发射中每一个飞碟掉落在地都会扣10分,打中一个加10分,第一二关90分以上过关第三关70分以上过关,空格发射飞碟,鼠标左键射击,请按空格进入第一关";
            } else if(states==1)
            {
                states = 2;
                mainText.text = "";
            } else if(states==2)            //这个状态下才能发射
            {
                if ((!queryInt.isShooting())&&changes!=0)
                {
                    userInt.emitDisk();
                    changes--;
                }
            }
        }
        if(queryInt.isShooting()&&Input.GetMouseButtonDown(0))      //判断是否射击中目标,并且实现爆炸粒子效果
        {
            Ray ray = ca.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "disk")
            {
                _exposion.transform.position = hit.collider.gameObject.transform.position;
                _exposion.GetComponent<Renderer>().material.color = hit.collider.gameObject.GetComponent<Renderer>().material.color;
                _exposion.Play();
                hit.collider.gameObject.SetActive(false);
            }
        }
        roundText.text = "Round: " + queryInt.getRound().ToString();
        scoreText.text = "Score: " + queryInt.getScore().ToString();
    }
}

Recorder类:

public class Recorder : MonoBehaviour {
    public int score = 50;          //记录当前所得分数
    void Awake () {
        SceneController.getInstance().setRecorder(this);
    }
    public void scoreADisk()        //得分
    {
        score += 10;
    }
    public void reset()             //重置
    {
        score = 50;
    }
    public void failADisk()         // 失分  
    {
        score -= 10;
    }
}

RoundController类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using name_game;
public class RoundController : MonoBehaviour {
    private Color color;
    private Vector3 emitPos;
    private Vector3 emitDir;
    private float speed;
    public int round = 0;
    void Awake()
    {
        SceneController.getInstance().setRoundController(this);
    }
    void Start () {
        emitPos = new Vector3(0f, 5f, -6f);
        emitDir = new Vector3(10f, 15f, 70f);
        speed = 15;
    }
    public void loadRoundData()                 //加载每一关卡飞碟数据
    {
        if(round==1)
        {
            color = Color.black;
            SceneController.getInstance().getGameModel().setting(3, color, emitPos, emitDir.normalized, speed, 1);
        } else
            if(round==2)
        {
            color = Color.red;
            SceneController.getInstance().getGameModel().setting(2, color, emitPos, emitDir.normalized, speed, 2);
        } else if(round==3)
        {
            color = Color.green;
            SceneController.getInstance().getGameModel().setting(2, color, emitPos, emitDir.normalized, speed, 3);
        }
    }
}

GameModel类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using name_game;
public class GameModel : MonoBehaviour
{
    public bool shooting=false;             //标记飞碟飞行期间的状态
    private int diskScale;                  //飞碟大小
    private Color diskColor;                //飞碟颜色
    private Vector3 emitPosition;           // 发射位置  
    private Vector3 emitDirection;          // 发射方向  
    private float emitSpeed = 0;                // 发射速度  
    private int emitNumber;                 // 发射数量  
    private bool emitEnable;
    private List<GameObject> disksToEmit = new List<GameObject>();
    void Awake()
    {
        SceneController.getInstance().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 emitReady()             //判断是否可以发射
    {
        if (!shooting)
        {
            emitEnable = true;
        }
    }
    void emitDisks()                //从工厂生产或者取出飞碟并且装入实现发射逻辑
    {
        for (int i = 0; i < emitNumber; ++i)    //每一个飞碟发射前都随机一次方向和发射位置
        {
            disksToEmit.Add(DiskFactory.getInstance().getDisk());
            disksToEmit[i].transform.localScale *= diskScale;
            disksToEmit[i].GetComponent<Renderer>().material.color = diskColor;
            disksToEmit[i].transform.position = new Vector3(Random.Range(emitPosition.x-1f, emitPosition.x+1f), emitPosition.y, emitPosition.z);
            disksToEmit[i].SetActive(true);
            emitDirection = new Vector3(Random.Range(emitDirection.x-0.3f, emitDirection.x + 0.3f), emitDirection.y, emitDirection.z);
            disksToEmit[i].GetComponent<Rigidbody>().velocity = emitDirection * Random.Range(emitSpeed, emitSpeed+5f);
        }
    }
    void freeDisk(GameObject adisk)             //调用工厂来回收飞碟
    {
        DiskFactory.getInstance().free(adisk);
        disksToEmit.Remove(adisk);
    }


    void FixedUpdate()
    {
        if (emitEnable)
        {
            emitDisks(); // 发射飞碟  
            emitEnable = false;
            shooting = true;
        }
    }
    void Update()
    {
        for (int i = 0; i < disksToEmit.Count; ++i)         //实现判断是否射击中
        {
            if (!disksToEmit[i].activeInHierarchy)
            {  // 飞碟不在场景中  
                SceneController.getInstance().getRecorder().scoreADisk();  // 得分  
                freeDisk(disksToEmit[i]);
            }
            else if (disksToEmit[i].transform.position.y < 0)
            {    // 飞碟在场景中但落地  
                SceneController.getInstance().getRecorder().failADisk();   // 失分  
                freeDisk(disksToEmit[i]);
            }
        }
        if (disksToEmit.Count == 0)
        {
            shooting = false;
        }
    }
}

还有一点就是要给飞碟的预设就如刚体组件,在Add Component->physics->Rigidbody就可以了

Text的话可以create一个Canvas,在里面创建并且调整各个提示框的位置以及大小

粒子系统就直接create->Particle System创建,然后样式再根据自己需要调一下。这次的游戏就大功告成了~~~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值