3DUnity编程与设计_HW5

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

  • 游戏内容要求:
    1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
    2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
    3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
    4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  • 游戏的要求:
    • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
    • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

 

本次实验的重点时是使用工厂类,生成飞碟,同时根据飞碟的激活状态的切换来减少实例的创建与销毁次数,以此来提高效率。

飞碟的动作很简单,想着一个方向位移就好了,所以采用上次作业的动作分离的方法,为动作单写一个类,然后在加载预制好的飞碟实例时,后加入到等待执行动作的列表中去,然后执行动作,等执行完动作后,并不销毁实例,而是设为未激活状态,下次需要再次生成实例时,就先看是否有未激活的飞碟,如果有就直接调用,否则再去创建,这样就能减少实例化的次数了。

所以首先创建一个飞碟的类,飞碟的属性有颜色,飞行速度,飞行方向以及飞行的起始地点。代码如下:
 

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

public class Disk : MonoBehaviour
{
    public Vector3 StartPoint { get { return gameObject.transform.position; } set { gameObject.transform.position = value; } }
    public Color color { get { return gameObject.GetComponent().material.color; } set { gameObject.GetComponent().material.color = value; } }
    public float speed { get; set; }
    public Vector3 Direction { get { return Direction; } set { gameObject.transform.Rotate(value); } }
}

之后便是实现一个工厂类,负责实例化飞碟,并负责管理飞碟的激活状态,如正在使用就设为激活状态,使用完毕后就设为非激活状态然后存储在一个数组里,所以每次准备实例化飞碟时要先检查是否有未激活的飞碟在等待使用,优先使用未激活的飞碟。实例化的时候,要随机设置飞碟的起始位置,颜色,飞行速度以及方向,这里利用随机数来实现。之后要将实例化好的飞碟对象加入到动作的待执行列表里,等待执行飞行动作。代码如下:

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


public class DiskFactory
{ 
    public GameObject diskPrefab;
    public static DiskFactory DF = new DiskFactory();

    private Dictionary used = new Dictionary();//used是用来保存正在使用的飞碟 
    private List free = new List();//free是用来保存未激活的飞碟 

    private DiskFactory()//单例,只能建立一个工厂
    {
        diskPrefab = GameObject.Instantiate(Resources.Load("prefabs/disk"));//获取预制的游戏对象
        diskPrefab.AddComponent();//为对象绑定脚本
        diskPrefab.SetActive(false);//初始化状态为未激活
    }

    public void FreeDisk()
    {
        foreach (Disk x in used.Values)
        {
            if (!x.gameObject.activeSelf)//将处于未激活状态的非得加入未激活列表
            {
                free.Add(x);
                used.Remove(x.GetInstanceID());
                return;
            }
        }
    }

    public Disk GetDisk(int round)
    {
        FreeDisk();
        GameObject newDisk = null;
        Disk diskdata;
        if (free.Count > 0)
        {
            //从之前生产的Disk中拿出可用的
            newDisk = free[0].gameObject;
            free.Remove(free[0]);
        }
        else
        {
            //克隆预制对象,生产新Disk
            newDisk = GameObject.Instantiate(diskPrefab, Vector3.zero, Quaternion.identity);
        }
        newDisk.SetActive(true);//设为激活状态
        diskdata = newDisk.AddComponent();//绑定脚本

        int swith;

        /** 
         * 根据回合数来生成相应的飞碟,难度逐渐增加。
         */
        float speedrand;//随机速度
        if (round == 1)
        {
            swith = Random.Range(0, 3);
            speedrand = Random.Range(30, 40);
        }
        else if (round == 2)
        {
            swith = Random.Range(0, 4);
            speedrand = Random.Range(40, 50);
        }
        else
        {
            swith = Random.Range(0, 6);
            speedrand = Random.Range(50, 60);
        }

        switch (swith)//飞碟随机的颜色,出发点,飞行方向
        {

            case 0:
                {
                    diskdata.color = Color.yellow;
                    diskdata.speed = speedrand;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(-130, -110), Random.Range(30, 90), Random.Range(110, 140));
                    break;
                }
            case 1:
                {
                    diskdata.color = Color.red;
                    diskdata.speed = speedrand + 10;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(-130, -110), Random.Range(30, 80), Random.Range(110, 130));
                    break;
                }
            case 2:
                {
                    diskdata.color = Color.black;
                    diskdata.speed = speedrand + 15;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(-130, -110), Random.Range(30, 70), Random.Range(90, 120));
                    break;
                }
            case 3:
                {
                    diskdata.color = Color.yellow;
                    diskdata.speed = -speedrand;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(130, 110), Random.Range(30, 90), Random.Range(110, 140));
                    break;
                }
            case 4:
                {
                    diskdata.color = Color.red;
                    diskdata.speed = -speedrand - 10;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(130, 110), Random.Range(30, 80), Random.Range(110, 130));
                    break;
                }
            case 5:
                {
                    diskdata.color = Color.black;
                    diskdata.speed = -speedrand - 15;
                    float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    diskdata.Direction = new Vector3(RanX, 1, 0);
                    diskdata.StartPoint = new Vector3(Random.Range(130, 110), Random.Range(30, 70), Random.Range(90, 120));
                    break;
                }
        }
        used.Add(diskdata.GetInstanceID(), diskdata); //添加到使用中
        diskdata.name = diskdata.GetInstanceID().ToString();
        return diskdata;
    }
}

之后便是实现飞碟的动作了,首先要实现飞行的移动函数,根据飞碟对象传来的参数,设置好飞行的速度,以及移动的方向变量,代码如下:

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


public class CCMoveToAction : SSAction//飞碟的动作类
{
    public float xspeed;
    public float yspeed = 0;

    private CCMoveToAction() { }//单例
    public static CCMoveToAction getAction(float speedx)
    {
        CCMoveToAction action = CreateInstance();
        action.xspeed = speedx;
        return action;
    }

    public override void Update()//飞碟的移动
    {
        this.transform.position += new Vector3(xspeed * Time.deltaTime, -yspeed * Time.deltaTime + (float)-0.5 * 10 * Time.deltaTime * Time.deltaTime, 0);
        xspeed += 10 * Time.deltaTime;
        if (transform.position.y <= 1)
        {
            destroy = true;
            CallBack.SSActionCallback(this);
        }
    }

    public override void Start()
    {

    }
}

之后便是管理对象的动作了,要使每个飞碟执行之前实现的动作类,所以传入一个飞碟的对象后,就用飞碟自带的属性来设置将要执行的动作的属性,之后便将对应好飞碟的动作加入到待执行队列中,等待执行。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class CCActionManager : SSActionManager, SSActionCallback
{
    int count = 0;//记录所有在移动的碟子的数量
    public SSActionEventType Complete = SSActionEventType.Completed;

    public void MoveDisk(Disk Disk)
    {
        count++;
        Complete = SSActionEventType.Started;
        CCMoveToAction action = CCMoveToAction.getAction(Disk.speed);
        addAction(Disk.gameObject, action, this);
    }

    public void SSActionCallback(SSAction source) //运动事件结束后的回调函数
    {
        count--;
        Complete = SSActionEventType.Completed;
        source.gameObject.SetActive(false);
    }

    public bool IsAllFinished() //主要为了防止游戏结束时场景还有对象但是GUI按钮已经加载出来
    {
        if (count == 0) return true;
        else return false;
    }
}

这样每个对象及其要执行的动作便对应好了,之后便是管理整个场景里所有的对象的动作的执行了,像之前说的一样,动作起加入到待执行队列中去,然后从待执行的队列中取出一个动作加入到执行中的动作数组中去,该数组中存储的动作都是正在执行的动作,当动作执行完后,将动作实例加入到待删队列中,在该队列中,把动作和飞碟设为未激活状态,之后移出待删队列。代码如下:
 

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

public class SSActionManager :MonoBehaviour//管理动作类的实例,将新生成的对象加入动作列表,将使用过的对象销毁
{
    private Dictionary actions = new Dictionary();
    private List waitingToAdd = new List();
    private List watingToDelete = new List();

    protected void Update()
    {
        foreach (SSAction ac in waitingToAdd)
        {
            actions[ac.GetInstanceID()] = ac;
        }
        waitingToAdd.Clear();

        foreach (KeyValuePair kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                watingToDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in watingToDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            Object.Destroy(ac);
        }
        watingToDelete.Clear();
    }

    public void addAction(GameObject gameObject, SSAction action, SSActionCallback ICallBack)
    {
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.CallBack = ICallBack;
        waitingToAdd.Add(action);
        action.Start();
    }
}

之后便是在场景控制器中,调用飞碟的工厂类,让工厂制造飞碟,然后飞碟执行动作。代码如下:

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

public class FirstSceneController : MonoBehaviour , ISceneController, UserAction
{
    int score = 0;
    int round = 1;//回合数
    int tral = 0;//飞碟计数
    bool start = false;
    CCActionManager Manager;
    DiskFactory DF;

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentScenceController = this;
        DF = DiskFactory.DF;//在场景中实例化一个飞碟工厂
        Manager = GetComponent();//管理飞碟的动作
    }

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    int count = 0;
    void Update()
    {
        if (start == true)//开始游戏
        {
            count++;
            if (count >= 80)//每80帧出现一个飞碟
            {
                count = 0;

                if (DF == null)
                {
                    Debug.LogWarning("DF is NUll!");
                    return;
                }
                tral++;
                Disk d = DF.GetDisk(round);//得到飞碟实例
                Manager.MoveDisk(d);//飞碟开始移动
                if (tral == 10)//毎十个飞碟算一轮
                {
                    round++;
                    tral = 0;
                }
            }
        }
    }

    public void LoadResources()
    {

    }

    public void Hit(Vector3 pos)
    {
        Ray ray = Camera.main.ScreenPointToRay(pos);//返回到一条从摄像机到屏幕指定点的射线

        RaycastHit[] hits;//存储被射线碰撞到的所有体
        hits = Physics.RaycastAll(ray);
        for (int i = 0; i < hits.Length; i++)
        {
            RaycastHit hit = hits[i];

            if (hit.collider.gameObject.GetComponent() != null)//根据被碰撞到的物体的颜色确认得分
            {
                Color c = hit.collider.gameObject.GetComponent().material.color;
                if (c == Color.yellow) score += 1;
                if (c == Color.red) score += 2;
                if (c == Color.black) score += 3;

                hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
            }

        }
    }

    public int GetScore()
    {
        return score;
    }

    public void Restart()
    {
        score = 0;
        round = 1;
        start = true;
    }
    public bool RoundStop()
    {
        if (round > 3)
        {
            start = false;
            return Manager.IsAllFinished();
        }
        else return false;
    }
    public int GetRound()
    {
        return round;
    }
}

基本的思想就是这样,剩下的便是一些 GUI 界面的设置和 MVC 的框架类了,比较简单了。

项目代码:项目链接

 

你可能感兴趣的:(3DUnity编程与设计_HW5)