打飞碟游戏

今天我们做一个简单的飞碟游戏

有了上一章点击地面出现攻击目标而引出的单例模式,这一次的游戏可以很好设计出来。

这个游戏中的主要角色有

  • 飞碟:最基本的要素,就是一个ganmeobject

  • 游戏场景:作为游戏美化

  • 导演:控制全局,场景转换(本游戏中还没有体现它的作用,因为只有一个场景。。。)

  • 场记:加载资源,给飞碟工厂、记分员、场次控制员下达命令,协调工作

  • 飞碟工厂:用于飞碟的制造和回收,以及发射

  • 飞碟加工厂:要发射的飞碟要根据场记的要求调整颜色、大小

  • 记分员:统计打下来的飞碟,根据难度,给玩家计算相应分数

  • 场次控制员:游戏分3个Round,玩家选择以后场次控制员要做相关调控

他们之间的关系可以用下图表示(不表示导演)
这里写图片描述

实现单例模式我是通过GetInstance来实现的,代码几乎都是

if (instance == null)
    instance = new 类名;
return instance;

这样编程的好处是分工合理,每个人各司其职,比如我们发现飞碟制造有问题,可以直接找飞碟工厂,计分有问题,可以直接找记分员,这样的思路相比于把所有代码都混在一个类里面,要好理解、更清晰得多

下面讲一下游戏规则
游戏难度分为三种难度,玩家选择后游戏开始,飞碟会随机从玩家的左边或右边飞出,颜色随机,大小、速度则严格按照选择的游戏难度进行复杂的概率分布得到的(好吧就是随机数把比例改变一下)每一局一共有20个飞碟,难度不同,飞碟飞出的时间间隔不同,难度三会同时多个飞碟一起飞出。玩家需要通过鼠标点击飞碟把它打下来(一开始想做一个瞄准镜,还搞了一把枪,但是枪在鼠标移出游戏界面后就会变乱,选的准星图片背景不能变透明很难看就放弃了,果断用光标吧。。。Low就Low了)玩家的枪一共有三发子弹,按空格就是换子弹

游戏规则弄清楚了,那么我说一下每个类

ISceneController

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

public interface ISceneController {

    void LoadResources();
    void Pause();
    void Resume();

}

SSDirector

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

public class SSDirector : System.Object {

    private static SSDirector _instance;

    public ISceneController currentSceneController { get; set; }

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

    public int getFPS()
    {
        return Application.targetFrameRate;
    }

    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }

}

RoundController

这个类很好理解,由于游戏只需要一个Round,所以有一个静态成员变量Round用于记录当前是哪个Round。用了OnGUI来给玩家提供难度以及重新开始的选项

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

public class RoundController : MonoBehaviour {
    private static RoundController instance;
    private static int Round;

    public static RoundController GetInstance()
    {
        if (instance == null)
            instance = new RoundController();
        return instance;
    }

    private void Start()
    {
        Init();
    }

    public void Init()
    {
        Round = 0;
    }

    public static int GetRound()
    {
        return Round;
    }

    void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.red;
        style.fontSize = 30;
        if (GUI.Button(new Rect(0, 410, 60, 30), "Round 1"))
        {
            Round = 1;
        } else if (GUI.Button(new Rect(0, 440, 60, 30), "Round 2"))
        {
            Round = 2;
        } else if (GUI.Button(new Rect(0, 470, 60, 30), "Round 3"))
        {
            Round = 3;
        } else if (GUI.Button(new Rect(0, 500, 60, 30), "Reset"))
        {
            Round = 4;
        }
        if (Round < 4 && Round != 0)
        {
            GUI.Label(new Rect(0, 0, 60, 30), "Round " + Round.ToString(), style);
        } else
        {
            GUI.Label(new Rect(0, 0, 60, 30), "Select a round to start", style);
        }
    }

}

ScoreRecorder

有一个静态成员变量Score用于记录当前分数,与RoundController一样,都只需要一个实例,所以用了单例模式GetInstance。Record函数用于统计分数,根据飞碟的大小来统计

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

public class ScoreRecorder : MonoBehaviour {

    private static ScoreRecorder instance;
    private static int Score;

    public static ScoreRecorder GetInstance()
    {
        if (instance == null)
            instance = new ScoreRecorder();
        return instance;
    }

    public static int GetScore()
    {
        return Score;
    }

    private void Start()
    {
        Score = 0;
    }

    public void Reset()
    {
        Score = 0;
    }

    public void Record(GameObject disk)
    {
        if (disk.GetComponent<Transform>().localScale == new Vector3(1, 0.1f, 1))
            Score += 3;
        else if (disk.GetComponent<Transform>().localScale == new Vector3(2, 0.1f, 2))
            Score += 2;
        else
            Score += 1;
        Debug.Log(Score.ToString());
    }

    void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.yellow;
        style.fontSize = 30;
        GUI.Label(new Rect(800, 0, 60, 30), "Score: " + Score.ToString(), style);
    }

}

DiskData

是飞碟的加工厂,不同于生产厂,这里记录了飞碟的color、size。在我的认知中,List中存的应该是对象本身,所以我觉得DiskData不是作为记录飞碟数据的存在(可能是我认知有缺陷)而是实实在在的飞碟本身,所以包含一个游戏对象disk,可以说,在我的设计中,DiskData才是真正飞碟的制造商

关键方法:

  • DiskData:从预设中克隆一个飞碟,把它放到地图中玩家看不到的地方

  • build:根据设定的颜色、大小对飞碟进行改造,并决定它是放在玩家右边还是玩家左边

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

public class DiskData : MonoBehaviour {
    private string color;
    private int size;
    public GameObject disk;
    Texture img;
    public DiskData()
    {
        disk = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
        SetColor("red");
        SetSize(1);
        disk.transform.position = Vector3.zero;
        disk.transform.Rotate(new Vector3(-25, 0, 0));
    }



    public void SetColor(string col)
    {
        color = col;
    }

    public void SetSize(int n)
    {
        size = n;
    }



    public void build(int Pos = 0)
    {
        if (color == "red")
        {
            img = (Texture)Resources.Load("red"); // 这是纹理
        } else if (color == "blue")
        {
            img = (Texture)Resources.Load("blue");
        } else if (color == "yellow")
        {
            img = (Texture)Resources.Load("yellow");
        }
        disk.GetComponent<Renderer>().material.mainTexture = img;
        if (size == 1)
            disk.GetComponent<Transform>().localScale = new Vector3(1, 0.1f, 1);
        else if (size == 2)
            disk.GetComponent<Transform>().localScale = new Vector3(2, 0.1f, 2);
        else if (size == 3)
            disk.GetComponent<Transform>().localScale = new Vector3(3, 0.1f, 3);

        if (Pos == 0)
        {
            System.Random pos = new System.Random();
            int where = pos.Next(0, 2);
            if (where == 0)
            {
                disk.transform.position = new Vector3(-3.5f, 0, 5f);
            }
            else
            {
                disk.transform.position = new Vector3(3.5f, 0, 5f);
            }
        } else
        {
            if (Pos == 1) // right
                disk.transform.position = new Vector3(3.5f, 0, 5f);
            else
                disk.transform.position = new Vector3(-3.5f, 0, 5f);
        }
    }


}

DiskFactory

飞碟工厂,场记发送指令,告诉DiskFactory要什么颜色、什么大小、什么速度的飞碟,DiskFactory得到指令后开始生产(交给它的DiskData小弟做完)然后完成发射。同时,当玩家打掉一个飞碟时,场记通知DiskFactory回收飞碟。

关键变量:

  • used:正在被用的集合,表示正在飞的飞碟

  • free:库存中的飞碟,即回收和生产后多余的飞碟,可以拿来使用

  • Speed :记录场记要求的速度,添加到Fly组件中

关键方法:

  • GetDisk:生产飞碟的函数,四个参数分别代表颜色、大小、速度、以及位置。执行该函数,工厂首先会判断是否有库存,有直接拿来用,没有的话判断当前是否已经生产了20个(工厂也要钱运作,不能浪费),已经有20个则告诉场记没钱了不干了,没有则默默生产,并把生产的添加到used中,对其进行相关改造(build),然后给它岸上飞翔的翅膀(Fly组件)

  • FreeDisk:把飞碟的翅膀拔掉(删除组件),并对其进行回收,放在玩家看不见的地方等候使用

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

public class DiskFactory : MonoBehaviour {
    private static DiskFactory instance; 
    public static List<DiskData> used = new List<DiskData>();
    public static List<DiskData> free = new List<DiskData>();
    public static float Speed;
    public static DiskFactory GetInstance()
    {
        if (instance == null)
            instance = new DiskFactory();
        return instance;
    }

    public void Init()
    {
        while (used.Count != 0)
        {
            FreeDisk(used[0].disk);
        }
    }

    public void GetDisk(string color, int size, float speed, int pos = 0)
    {
        DiskData t;
        if (free.Count ==0)
        {
            if (used.Count == 20)
            {
                Debug.LogError("There are too many disks!");
                return;
            }
            t = new DiskData();
        } else
        {
            t = free[0];
            free.Remove(t);
        }
        Speed = speed;
        t.SetColor(color);
        t.SetSize(size);
        t.build(pos);
        used.Add(t);
        if (t.disk.GetComponent<Fly>() != null)
            t.disk.GetComponent<Fly>().life = 1;
        else
        t.disk.AddComponent<Fly>();
    }

    public void FreeDisk(GameObject Disk)
    {
        Disk.GetComponent<Fly>().life = 0;
        free.Add(used[0]);
        used.RemoveAt(0);
        Disk.transform.position = Vector3.zero;
        print("free: " + free.Count);
        print("used: " + used.Count);
    }
}

这里添加了一个Fly组件,所以我还要说一下Fly组件。
由于游戏创建和销毁的成本很高,组件也是一样,所以我在这个组件中加入了一个变量life,要用的时候设为1,不用的时候设为0,这样子可以节省一大部分的资源

Fly

给飞碟添加飞行动作,包含飞碟的速度以及终点位置。
Mathf函数与随机函数结合动态地给飞碟一个相对合理的终点,增加游戏可玩性(好吧,本身这游戏就很无聊)。飞碟的运动包括朝终点的飞行以及自身的旋转,当飞碟到达终点以后,被工厂回收

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


public class Fly : DiskFactory{
    private Vector3 dir;
    float time;
    float x;
    float y;
    public float speed;
    public int life;
    // Use this for initialization
    void Start () {
        life = 1;
        speed = Speed;
        System.Random pos = new System.Random();
        float z = 50;
        y = pos.Next(7, 15);
        if (Mathf.Min(transform.position.x, 0) == transform.position.x)
        {            
            x = pos.Next(2, 10);

        } else
        {
            x = pos.Next(-11, -2);
        }


        dir = new Vector3(x, y, z);
    }

    // Update is called once per frame
    void Update () {
        if (life == 1)
        {
            time += Time.deltaTime;
            if (transform.position == dir)
            {
                FreeDisk(transform.gameObject);
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position, dir, Time.deltaTime * 15);
                transform.Rotate(Vector3.up * 500 * Time.deltaTime);
            }
        }
    }
}

FirstController

作为场记,有着协调各部门工作的任务,所以肯定有飞碟工厂、场景控制员、记分员这几个变量。主要功能是加载资源、接受这三个部门发来的消息并进行处理、同时给他们下达命令。还要时时监察游戏进行情况,包括当前子弹数、飞碟剩余数,有一个名叫cam的游戏对象,是用于放入摄像机从而进行撞击检测的,以及一个time记录时间确定是否需要飞碟。

关键方法:

  • LoadResources:加载我做的游戏场景(好大好大,毕竟100多M的游戏)

  • Init :初始化相关数据,包括场景控制器、工厂、记分员的初始化,以及子弹数、剩余飞盘数的初始化

  • GetColor、GetSize、GetSpeed:通过随机变量,以当前难道为依据,得到需要生产的飞碟的相关数据

  • Update:
    1.累加当前游戏进行时间
    2.判断是否需要飞碟,如果需要,获取飞碟生产的相关参数,通知飞碟工厂生产飞碟
    3.判断是否按了Reset,如果按了则游戏重置,等候玩家选择难度重新开始
    4.控制子弹的减少(鼠标点击)以及装弹(空格)
    5.检测是否击中飞碟,如果击中,通知飞碟工厂回收

值得一提的是判断飞碟是否被点击,如果单纯用if(Physics.Raycast(ray, out hit))是不行的,因为我在场景中加入了别的元素,当他们被点击时,由于他们不能被回收,所以会报错,我发现克隆后的飞碟名字叫“disk(Clone)”,所以可以这样子

if (Physics.Raycast(ray, out hit))
            {
                if (!hit.collider.gameObject.tag.Contains("Finish"))
                {
                    if (hit.collider.gameObject.name == "disk(Clone)")
                    {
                        MyFactroy.FreeDisk(hit.transform.gameObject);
                        Recorder.Record(hit.transform.gameObject);
                    }
                }
            }

贴代码

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

public class FirstController : MonoBehaviour, ISceneController
{
    public GameObject cam;
    private DiskFactory MyFactroy;
    private RoundController Round;
    private ScoreRecorder Recorder;
    private int number;
    float time;
    private int now = 0;
    private int bullet;

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }

    public void LoadResources()
    {
        GameObject Environment = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Environment"), new Vector3(3.279565f, 0.5247455f, 9.926256f), Quaternion.identity);
        Environment.name = "Environment";
    }

    // Use this for initialization
    void Start () {
        Init();
    }

    void Init()
    {
        Round = RoundController.GetInstance();
        MyFactroy = DiskFactory.GetInstance();
        Recorder = ScoreRecorder.GetInstance();
        Debug.Log("start");
        time = 0;
        now = 0;
        number = 20;
        bullet = 3;
    }

    public string GetColor()
    {
        System.Random select = new System.Random();
        string color = "red";
        switch (select.Next(0, 3))
        {
            case 0:
                color = "red";
                break;
            case 1:
                color = "blue";
                break;
            case 2:
                color = "yellow";
                break;
        }
        return color;
    }

    private void Reset()
    {
        Init();
        MyFactroy.Init();
        Recorder.Reset();
    }

    private void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.normal.background = null;
        style.normal.textColor = Color.blue;
        style.fontSize = 30;
        GUI.Label(new Rect(400, 0, 60, 30), "剩余飞盘数: " + number.ToString(), style);
        GUI.Label(new Rect(400, 30, 60, 30), "Bullet: " + bullet.ToString(), style);
        if (number == 0)
        {
            style.normal.textColor = Color.black;
            GUI.Label(new Rect(300, 200, 100, 30), "Game over. Your Score is: " + ScoreRecorder.GetScore(), style);
        }
    }

    public int GetSize(int round)
    {
        int size = 3;
        System.Random select = new System.Random();
        if (round == 1)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                case 2:
                    size = 3;
                    break;
                case 3:
                case 4:
                    size = 2;
                    break;
                case 5:
                    size = 1;
                    break;
            }
        } else if (round == 2)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                    size = 3;
                    break;
                case 2:
                case 3:
                case 4:
                    size = 2;
                    break;
                case 5:
                    size = 1;
                    break;
            }
        } else if (round == 3)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                    size = 3;
                    break;
                case 1:
                case 2:
                    size = 2;
                    break;
                case 3:
                case 4:
                case 5:
                    size = 1;
                    break;
            }
        }
        return size;
    }

    float GetSpeed(int round)
    {
        int speed = 15;
        System.Random select = new System.Random();
        if (round == 1)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                case 2:
                    speed = 15;
                    break;
                case 3:
                case 4:
                    speed = 20;
                    break;
                case 5:
                    speed = 25;
                    break;
            }
        }
        else if (round == 2)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                case 1:
                    speed = 15;
                    break;
                case 2:
                case 3:
                case 4:
                    speed = 20;
                    break;
                case 5:
                    speed = 25;
                    break;
            }
        }
        else if (round == 3)
        {
            switch (select.Next(0, 6))
            {
                case 0:
                    speed = 15;
                    break;
                case 1:
                case 2:
                    speed = 20;
                    break;
                case 3:
                case 4:
                case 5:
                    speed = 25;
                    break;
            }
        }
        return speed;
    }
    // Update is called once per frame
    void Update ()
    {
        float speed = 15;
        if (RoundController.GetRound() == 4)
        {
            Reset();
        }
        if (number == 0)
        {
            return;
        }
        time += Time.deltaTime;
        System.Random select = new System.Random();
        string color = GetColor();
        int size = 3;
        if (now != RoundController.GetRound())
        {
            Reset();
            now = RoundController.GetRound();
        } 
        if (RoundController.GetRound() == 1)  // 难度一
        {

            if (time >= 6)
            {
                speed = GetSpeed(RoundController.GetRound());
                number--;
                time = 0;
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed);
            }
        } else if (RoundController.GetRound() == 2)
        {
            if (time >= 4)
            {
                speed = GetSpeed(RoundController.GetRound());
                number--;
                time = 0;
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed);
            }
        } else if (RoundController.GetRound() == 3)
        {
            if (time >= 3)
            {
                speed = GetSpeed(RoundController.GetRound());
                float speed2 = GetSpeed(RoundController.GetRound());
                number -= 2;
                time = 0;
                int size2 = GetSize(RoundController.GetRound());
                string color2 = GetColor();
                size = GetSize(RoundController.GetRound());
                MyFactroy.GetDisk(color, size, speed, 1);
                MyFactroy.GetDisk(color2, size2, speed2, 2);
            }
        }
        if (Input.GetAxis("Jump") > 0)
            bullet = 3; 

        if (Input.GetButtonDown("Fire1") && bullet > 0)
        {
            bullet--;
            Vector3 mp = Input.mousePosition;

            Camera ca = cam.GetComponent<Camera>();
            Ray ray = ca.ScreenPointToRay(mp);

            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                if (!hit.collider.gameObject.tag.Contains("Finish"))
                {
                    if (hit.collider.gameObject.name == "disk(Clone)")
                    {
                        MyFactroy.FreeDisk(hit.transform.gameObject);
                        Recorder.Record(hit.transform.gameObject);
                    }
                }
            }
        }
    }

    public void Pause()
    {
        throw new NotImplementedException();
    }

    public void Resume()
    {
        throw new NotImplementedException();
    }
}

所有类都构造完成了,我们需要把FirstController、RoundController和ScoreRecorder挂载到一个新建的空对象中,然后把Main Camera作为 FirstController中的对象传进去就完成了,为什么要挂载RoundController和ScoreRecorder?因为我在里面设置了GUI,我发现如果不挂载,仅仅作为FirstController的组成是不会触发GUI的,暂时还没找到解决办法。

然后我们可以根据之前学的场景美化,种点花花草草,挖点坑,造点山,可以做出挺不错的效果

这里写图片描述
我没开玩笑!真的挖了个坑!

最后的效果还是蛮好看的
这里写图片描述

感谢阅读!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值