与游戏世界交互

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

游戏内容要求:

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

动作管理器

动作管理器可以在上一次作业的基础上进行修改。

  • ISSActionCallback为动作接口
  • SSAction为动作父类,规定所有Action的属性和方法
  • DiskFlyAction继承自SSAction,规定了一个Disk的动作,动作即飞碟的移动,由两个参数决定:受力角度和受力大小。构造函数初始化初始速度,Update函数模拟物体的坐标移动过程,当disk在画面之外(通过坐标判定),就callback通知动作做完。
        public override void Update()
        {
            time += Time.fixedDeltaTime;

            gravity_vector.y = 0;  //gravity * time;   

            transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
            current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
            transform.eulerAngles = current_angle;

            //动作做完
            if (this.transform.position.y < -10 || this.transform.position.y > 10)
            {
                this.destroy = true;
                this.callback.SSActionEvent(this);
            }
        }
  • SequenceAction函数不用改动,由于Disk是直线运动也用不到。
  • SSActionManager函数。动作管理器,管理Action List中的每个Action(不用关心是单一Action还是连续Action)。
  • FirstActionManager。这个函数封装了Action的相关类的操作,使得FirstController可以简单地调用FirstActionManager中的函数。
    public class FirstActionManager : SSActionManager
    {

        public DiskFlyAction fly;                            //飞碟飞行的动作
        public FirstController scene_controller;             //当前场景的场景控制器

        protected void Start()
        {
            scene_controller = (FirstController)Director.GetInstance().currentSceneController;
            scene_controller.actionManager = this;
        }

        //飞碟飞行
        public void diskFly(GameObject disk, float angle, float power)
        {
            fly = DiskFlyAction.GetSSAction(angle, power); //disk.GetComponent<Disk>().direction, angle, power);
            this.RunAction(disk, fly, this);
        }
    }

飞碟

Disk类只简单记录飞碟的属性,它会作为一个组件添加到具体的GameObject上

    public class Disk : MonoBehaviour
    {
        public float size;
        public Color color;
        //move
        public float angle;
        public float power;
    }

飞碟工厂

负责飞碟的生产和回收。管理两个List,usingDisks存放正在使用的Disk,freeDisks管理使用了过后回收的Disk。

回收:从usingDisks中去掉并加到freeDisks中。

        public void freeDisk(GameObject usedDisk)
        {
           if(usedDisk != null)
           {
                usedDisk.SetActive(false);
                usingDisks.Remove(usedDisk);
                freeDisks.Add(usedDisk);
           }
        }

生产:
如果freeDisks中有飞碟,则不用再实例化新的Disk,直接从中取即可;否则需要再实例化一个新的Disk,实例化之后还需要命名,给飞碟添加Disk、Rigidbody组件,但是工厂不负责设置这些组件的属性,工厂只负责把Disk生产出来。

        public GameObject getDisk(int round)
        {
            GameObject newDisk = null;
            if (freeDisks.Count > 0)
            {
                newDisk = freeDisks[0].gameObject;
                freeDisks.Remove(freeDisks[0]);
            }
            else
            {
                newDisk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
                newDisk.AddComponent<Disk>();
                newDisk.name = nameIndex.ToString();
                nameIndex++;
            }
            return newDisk;
        }

工厂是场景单实例的

    public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    {
        protected static T instance;

        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = (T)FindObjectOfType(typeof(T));
                    if (instance == null)
                    {
                        Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none.");
                    }
                }
                return instance;
            }
        }
    }

规则管理器

Ruler管理游戏规则,它与游戏轮数Round紧密相关。

Ruler根据Round设置Disk的属性,基本规则是Round越大,飞碟速度越快、大小越小,而对于颜色、出现位置、角度这些属性,与Round无关。

    public class Ruler
    {
        public int[] score;

        public Ruler()
        {
            score = new int[10];
        }

        public void setDiskProperty(GameObject disk, int round)
        {
            disk.transform.position = this.setRandomInitPos();
            disk.GetComponent<Renderer>().material.color = setRandomColor();
            disk.transform.localScale = setScale(round);
            disk.GetComponent<Disk>().angle = setRandomAngle();
            disk.GetComponent<Disk>().power = setPower(round);
        }

        public Vector3 setRandomInitPos()
        {
            float x = Random.Range(-10f, 10f);
            float y = Random.Range(-1f, 5f);
            float z = Random.Range(-3f, 3f);
            return new Vector3(x, y, z);
        }

        public Vector4 setRandomColor()
        {
            int r = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
            int g = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
            int b = Random.Range(0f, 1f) > 0.5 ? 255 : 0;
            return new Vector4(r, g, b, 1);
        }

        public Vector3 setScale(int round)
        {
            float x = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
            float y = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
            float z = Random.Range((float)(1 - 0.1 * round), (float)(2 - 0.1 * round));
            return new Vector3(x, y, z);
        }

        public float setRandomAngle()
        {
            return Random.Range(-360f, 360f);
        }

        public float setPower(int round)
        {
            if (round == 1)
            {
                return 2;
            }
            return 3 * round;
        }
	}

同时规定相邻两个Disk的出现间隔,round越大间隔越短

        public float setInterval(int round)
        {
            return (float)(2 - 0.2 * round);
        }

另外,Ruler还负责判断游戏是否可以进入下一轮/失败,round越大,要求进入下一轮的分数也越大。一轮10个trial,最高10分。

        public int getTargetThisRound(int round)
        {
            if (round != -1)
            {
                return 5 + round > 10 ? 10 : 5 + round;
            }
            return 0;
        }

        public bool enterNextRound(int round)
        {
            if (round != -1 && this.score[round - 1] >= (5 + round > 10 ? 10 : 5 + round))
            {
                return true;
            }
            return false;
        }
    

界面

接收用户的输入

        private void Update()
        {
            if (Input.GetButtonDown("Fire1"))
            {
                //Debug.Log("Fire1");
                Vector3 pos = Input.mousePosition;
                action.hit(pos);
            }
        }

显示Round和Score、输赢信息。

        void OnGUI()
        {
            GUI.skin.label.font = blue_font;
            GUI.Label(new Rect(Screen.width / 2 - 50, 20, 180, 50), "     Hit UFO     ");
            GUI.Label(new Rect(Screen.width / 2 - 30, 50, 180, 50), "score: " + score.ToString());
            GUI.Label(new Rect(Screen.width / 2 + 60, 50, 180, 50), "goal: " + targetThisRound.ToString());
            if (round != -1)
            {
                GUI.Label(new Rect(Screen.width / 2 - 120, 50, 100, 50), "Round: " + round.ToString());
            }
            else if (round == -1)
            {
                GUI.Label(new Rect(Screen.width / 2 - 120, 50, 100, 50), "You Lose!");
            }

            if (GUI.Button(new Rect(Screen.width / 2 - 40, 240, 70, 30), "Restart"))
            {
                action.restart();
            }
        }

Controller

这个文件不是一个功能明确的类,里面放了所有的接口函数和导演类,都和之前的一样,其中玩家与游戏交互的接口包含玩家的两个动作:

    public interface UserAction
    {
        void hit(Vector3 pos);
        void restart();
    }

FirstController

加载资源并控制整个游戏的流程。

加载资源

    void Awake()
    {
        Director director = Director.GetInstance();
        director.currentSceneController = this;
        director.currentSceneController.loadResources();

        userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
        this.gameObject.AddComponent<DiskFactory>();
        actionManager = gameObject.AddComponent<FirstActionManager>() as FirstActionManager;
    }

    public void loadResources()
    {
        ruler = new Ruler();
    }

在Update函数中,设定每隔一段时间就抛出一个disk,每个round总共抛出10个Disk;同时判断是否到达晋级下一轮的条件或是否失败。

	void Update () {
        if (ruler.enterNextRound(round))
        {
            round++;
            trial = 0;
            getDisksForNextRound();
            userGUI.score = this.score = 0;
            userGUI.targetThisRound = ruler.getTargetThisRound(round);
        }
        else if (!ruler.enterNextRound(round) && trial == 11)
        {
            round = -1;
        }

        if (this.round >= 1)
        {
            if (interval > ruler.setInterval(round))
            {
                if(trial < 10)
                {
                    throwDisk();
                    interval = 0;
                    trial++;
                }
                else if (trial == 10)
                {
                    trial++;
                }
            }
            else
            {
                interval += Time.deltaTime;
            }
        }

        userGUI.round = this.round;
	}

每一轮的开始,FirstController会从diskFactory中一次性拿够10个Disk放入queue中。

    public void getDisksForNextRound()
    {
        DiskFactory diskFactory = Singleton<DiskFactory>.Instance;
        int numDisk = 10;
        for (int i = 0; i < numDisk; i++)
        {
            GameObject disk = diskFactory.getDisk(round);
            disksQueue.Enqueue(disk);
        }
    }

然后抛出时,从queue中拿出一个,通过ruler为其设定好星官属性,然后将其交给动作管理器抛出。

    public void throwDisk()
    {
        if (disksQueue.Count != 0)
        {
            GameObject disk = disksQueue.Dequeue();
            ruler.setDiskProperty(disk, round);
            disk.SetActive(true);
            actionManager.diskFly(disk, disk.GetComponent<Disk>().angle, disk.GetComponent<Disk>().power);
        }
    }

github传送门

演示视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值