【游戏开发】unity教程4 打飞碟小游戏

15 篇文章 0 订阅
7 篇文章 0 订阅

github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw4/Disk

视频传送门:https://space.bilibili.com/472759319

打飞碟小游戏

这次的代码架构同样采用了MVC模式,与之前的牧师与魔鬼基本相同,这里就不重复叙述了,感兴趣的可以看上上篇博文

这里主要还是介绍一下firstController的变化以及新应用的工厂模式和真正负责飞碟移动的Emit类。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using MyGame;
using UnityEngine.SceneManagement;

public class FirstController : MonoBehaviour, ISceneController, IUserAction {

    public ActionManager MyActionManager { get; set; }
    public DiskFactory factory { get; set; }
    public RecordController scoreRecorder;
    public UserGUI user;    
    
    void Awake() {
        Director diretor = Director.getInstance();
        diretor.sceneCtrl = this;                              
    }

    // Use this for initialization
    void Start() {
        Begin();
    }
	
	// Update is called once per frame
	void Update () {
        
	}

    public void Begin() {
        MyActionManager = gameObject.AddComponent<ActionManager>() as ActionManager;
        scoreRecorder = gameObject.AddComponent<RecordController>();
        user = gameObject.AddComponent<UserGUI>();
        user.Begin();
    }

    public void Hit(DiskController diskCtrl) {   
        // 0=playing 1=lose 2=win 3=cooling     
        if (user.game == 0) {            
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            
            if (Physics.Raycast(ray, out hit)) {
                //hit.collider.gameObject.SetActive(false);
                Debug.Log("Hit");
                factory.freeDisk(hit.collider.gameObject);
                hit.collider.gameObject.GetComponent<DiskController>().hit = true;
                scoreRecorder.add(hit.collider.gameObject.GetComponent<DiskController>());
            }

        }
    }

    public void PlayDisk() {
        MyActionManager.playDisk(user.round);
    }

    public void Restart() {
        SceneManager.LoadScene("scene");
    }

    public int Check() {
        return 0;
    }
}

FirstController同样是负责着所有其他的controller和userGUI,这都和之前相同;新加入的DiskFactory我们一会儿再介绍。这里主要讲一下Hit函数。所谓ScreenPointToRay就是从Camera出发连接到鼠标点击位置的一条射线,然后如果射线经过了我们目标的GameObject,就算是击中了。当一个飞碟被击中时,我们首先将这个Object的Active设为False,从而将击中的消息传回给Action;然后FreeDisk是将这个实例从放到free列表中等待下一次调用(其实在FreeDisk中我们已经有了设置Active的操作,这里将其注释在这里是为了提醒我们它的重要性)。不知道free列表是什么东西没关系,我们继续看DiskFactory的代码。

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

public class DiskFactory : MonoBehaviour {
    private static DiskFactory _instance;
    public FirstController sceneControler { get; set; }
    GameObject diskPrefab;
    public DiskController diskData;
    public List<GameObject> used;
    public List<GameObject> free;
    // Use this for initialization

    public static DiskFactory getInstance() {
        return _instance;
    }

    private void Awake() {
        if (_instance == null) {
            _instance = Singleton<DiskFactory>.Instance;
            _instance.used = new List<GameObject>();
            _instance.free = new List<GameObject>();
            diskPrefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk"), new Vector3(40, 0, 0), Quaternion.identity);
        }
    }


    public void Start() {
        sceneControler = (FirstController)Director.getInstance().sceneCtrl;
        sceneControler.factory = _instance;      
    }

    public GameObject getDisk(int round) { // 0=playing 1=lose 2=win 3=cooling
        if (sceneControler.scoreRecorder.Score >= round * 4) {
            if (sceneControler.user.round < 3) {
                sceneControler.user.round++;
                sceneControler.user.num = 0;
                sceneControler.scoreRecorder.Score = 0;
            }
            else {
                sceneControler.user.game = 2; // 赢了
                return null;
            }
        }
        else {
            if (sceneControler.user.num >= 10) {
                sceneControler.user.game = 1; // 输了
                return null;
            }            
        }

        GameObject newDisk;
        RoundController diskOfCurrentRound = new RoundController(sceneControler.user.round);        
        if (free.Count == 0) {// if no free disk, then create a new disk
            newDisk = GameObject.Instantiate(diskPrefab) as GameObject;
            newDisk.AddComponent<ClickGUI>();
            diskData = newDisk.AddComponent<DiskController>();
        }
        else {// else let the first free disk be the newDisk
            newDisk = free[0];
            free.Remove(free[0]);
            newDisk.SetActive(true);
        }
        diskData = newDisk.GetComponent<DiskController>();
        diskData.color = diskOfCurrentRound.color;
        //Debug.Log(diskData);

        newDisk.transform.localScale = diskOfCurrentRound.scale * diskPrefab.transform.localScale;
        newDisk.GetComponent<Renderer>().material.color = diskData.color;
        
        used.Add(newDisk);
        return newDisk;
    }

    public void freeDisk(GameObject disk1) {
        used.Remove(disk1);
        disk1.SetActive(false);
        free.Add(disk1);
        return;
    }

    public void Restart() {
        used.Clear();
        free.Clear();
    }
}

这就是所谓的工厂模式了。当游戏对象的创建与销毁成本较高,且游戏涉及大量游戏对象的创建与销毁时,必须考虑减少销毁次数,比如这次的打飞碟游戏,或者像其他类型的射击游戏,其中子弹或者中弹对象的创建与销毁是很频繁的。工厂模式将已经创建好正在使用的实例存在一个used列表中,然后当使用完成(被击中)就将其放在free列表中,等待下一次调用;当我们需要一个新的实例的时候,首先检查free列表,当其中没有限制的实例时我们才创建一个新的。getDisk和freeDisk就实现了上面所叙述的逻辑,是核心的代码。

最后是Emit类。

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

public class Emit : SSAction
{
    public FirstController sceneControler = (FirstController)Director.getInstance().sceneCtrl;
    public Vector3 target;     
    public float speed;     
    private float distanceToTarget;    
    float startX;
    float targetX;
    float targetY;

    public override void Start() {
        speed = sceneControler.user.round * 5;
        GameObject.GetComponent<DiskController>().speed = speed;

        startX = 6 - Random.value * 12;

        if (Random.value > 0.5) {
            targetX = 36 - Random.value * 36;
            targetY = 25 - Random.value * 25;
        }
        else {
            targetX = -36 + Random.value * 36;
            targetY = -25 + Random.value * 25;
        }

        this.Transform.position = new Vector3(startX, 0, 0);
        target = new Vector3(targetX, targetY, 30);
        //Debug.Log(target);
        distanceToTarget = Vector3.Distance(this.Transform.position, target);
    }

    public static Emit GetSSAction() {
        Emit action = ScriptableObject.CreateInstance<Emit>();
        return action;
    }

    public override void Update() {
        Vector3 targetPos = target;
        if(!GameObject.activeSelf){
            this.destroy = true;
            return;
        }

        //facing the target
        GameObject.transform.LookAt(targetPos);

        //calculate the starting angel  
        float angle = Mathf.Min(1, Vector3.Distance(GameObject.transform.position, targetPos) / distanceToTarget) * 45;
        GameObject.transform.rotation = GameObject.transform.rotation * Quaternion.Euler(Mathf.Clamp(-angle, -42, 42), 0, 0);
        float currentDist = Vector3.Distance(GameObject.transform.position, target);
        //Debug.Log("****************************");
        //Debug.Log(startX);
        //Debug.Log(target);
        //Debug.Log("****************************");
        GameObject.transform.Translate(Vector3.forward * Mathf.Min(speed * Time.deltaTime, currentDist));
        if (this.Transform.position == target) {
            sceneControler.scoreRecorder.miss();
            Debug.Log("here in miss!!");
            GameObject.SetActive(false);
            GameObject.transform.position = new Vector3(startX, 0, 0);
            sceneControler.factory.freeDisk(GameObject);
            this.destroy = true;
            this.Callback.ActionDone(this);
        }
    }
}

我们在Start函数中保证了飞碟出现的位置和目标方向的随机性。然后在Update函数中首先计算移动的角度,然后根据速度给出当前的位移,然后进行一次判断,如果当前位置已经是终点了,那么我们首先setActive告诉外层的actionManager之前的运动可以取消了,然后将当前的实例free掉。在Update函数开始返回前也需要设置一下destroy是为了在hit后也可以告诉actionManager取消当前运动,与后面那个并不是重复操作。

最后我们来看一眼actionControl的核心ActionMananger,其他的就不全部贴上来了。

public class ActionManager : SSActionManager {
    public FirstController sceneController;
    public DiskFactory diskFactory;
    public RecordController scoreRecorder;
    public Emit EmitDisk;
    public GameObject Disk;
    int count = 0;

    protected void Start() {
        sceneController = (FirstController)Director.getInstance().sceneCtrl;
        diskFactory = sceneController.factory;
        scoreRecorder = sceneController.scoreRecorder;
        sceneController.MyActionManager = this;
    }

    protected new void Update() {
        if (sceneController.user.round <= 3 && sceneController.user.game == 0) {
            count++;
            if (count == 60 * sceneController.user.round) {
                playDisk(sceneController.user.round);
                sceneController.user.num++;
                count = 0;
            }
            base.Update();
        }
    }

    public void playDisk(int round) {
        EmitDisk = Emit.GetSSAction();
        Disk = diskFactory.getDisk(round);
        this.AddAction(Disk, EmitDisk, this);
        Disk.GetComponent<DiskController>().action = EmitDisk;
    }
    
}

Update实现了每60帧*round后发出一个飞碟。之所以这样设计是因为最后一轮的飞碟速度太快,这样能够适当降低游戏难度。playDisk函数就是从工厂中获取一个飞碟,然后和下一个应该出现的飞碟的移动方向和特征一起传给AcitionManager。

另外为了有空战的感觉,我还加入了在AssetStore下载的StarField天空盒,最终的效果如下图所示。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值