Unity3D学习(5)之工厂回收利用的3D版飞碟游戏

2023-11-15

       这一次我们来做的任务是3D版鼠标点击鼠标的游戏。我们先来看一下游戏需求。
案例研究:“鼠标打飞碟”游戏设计 游戏需求:
    1. 分多个 round , 飞碟数量每个 round 都是 n 个
    2. 每个 round 的飞碟的色彩,大小;发射位置,速度, 角度,每次发射飞碟数量不一样。
    3. 鼠标击中得分,得分按色彩、大小、速度不同计算,规则自己定
先考虑一下游戏的对象要有飞碟,而且可能会需要很多个飞碟,再加上分多个round,这样游戏的总飞碟数需要很多,首先不断地去new GameObject需要消耗比较多内存,还有可能让游戏卡顿的情况。所以这时候我们需要建立一个游戏工厂由它负责去回收,产生飞碟。游戏工厂的好处在于要减少对象的创建与销毁,而且屏蔽创建与销毁的逻辑,使程序易于扩展,并且分离出场记的任务,使游戏架构更加清晰明了。
       要实现回收和利用我们可以使用两个链表used,free分别表示使用中的飞碟和空闲的飞碟。然后在Diskfactory里面实现两个函数getDisk,freeDisk。我们看下伪代码先。
getDisk(ruler) //伪代码 
IF (free list has disk) 
THEN    
  a_disk = remove one from list 
ELSE    
  a_disk = clone from Prefabs 
ENDIF 
Set DiskData of a_disk with the ruler
Add a_disk to used list 
Return a_disk  

FreeDisk(disk) //伪代码
Find disk in used list 
IF (not found) 
THEN THROW exception 
Move disk from used to free list 
接下来就可以来具体用C#语言实现
public GameObject getDisk(int round)
    {
        if (sceneControler.num == 31 && sceneControler.Score >= round * 20)
        //每轮总共发射30个,如果得分达到一定要求进入下一轮,否则GameOver
        {
            sceneControler.round++;
            sceneControler.num = 0;
        } 
        else if(sceneControler.num == 31 && sceneControler.Score < round * 20)
        {
            sceneControler.game = 2;//游戏结束
        }

        GameObject newDisk;
        if (free.Count == 0)
        {
            newDisk = GameObject.Instantiate(Resources.Load("prefabs/Disk")) as GameObject;
            Debug.Log("111");
        }
        else
        {
            newDisk = free[0];
            free.Remove(free[0]);
        }
        switch (round)
        //根据轮数制定飞碟的颜色和大小
        {
            case 1:
                newDisk.transform.localScale = new Vector3(DiskData.round1.size, DiskData.round1.size, DiskData.round1.size);
                newDisk.GetComponent<Renderer>().material.color = DiskData.round1.color;
                break;
            case 2:
                newDisk.transform.localScale = new Vector3(DiskData.round2.size, DiskData.round2.size, DiskData.round2.size);
                newDisk.GetComponent<Renderer>().material.color = DiskData.round2.color;
                break;
            case 3:
                newDisk.transform.localScale = new Vector3(DiskData.round3.size, DiskData.round3.size, DiskData.round3.size);
                newDisk.GetComponent<Renderer>().material.color = DiskData.round3.color;
                break;
        }
        used.Add(newDisk);
        return newDisk;
    }
public void freeDisk(GameObject disk1)
    {
        for (int i = 0; i < used.Count; i++)
        {
            if (used[i] == disk1)
            {
                used.Remove(disk1);
                disk1.SetActive(true);//被鼠标击中的disk设置为false,所以这里全部激活一遍
                free.Add(disk1);
            }
        }
        return;
    }
接着实现工厂定义,这里请原谅我的年幼无知,将DiskFactory定义成System.Object,这样的弊端很大,所有的Scene都能访问到它,下一篇博客我会就此改进的。 害羞 害羞
public class DiskFactory : System.Object
{
    private static DiskFactory _instance;
    public SceneController sceneControler { get; set; }
    public List<GameObject> used;
    public List<GameObject> free;
    // Use this for initialization

    public static DiskFactory getInstance()
    {
        if (_instance == null)
        {
            _instance = new DiskFactory();
            _instance.used = new List<GameObject>();
            _instance.free = new List<GameObject>();
        }
        return _instance;
    }

接下来我们考虑下飞碟的动作Emit,难点在于如何实现曲线运动。我们可以通过化直为曲的方式实现,每一帧计算弧线夹角去平移,这样看起来就是曲线了,先定义动作基类。
public enum SSActionEventType : int { Started, Competeted }

public interface ISSActionCallback
{
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null);
}

public class SSAction : ScriptableObject
{
    public bool enable = true;
    public bool destroy = false;

    public GameObject gameobject { get; set; }
    public Transform transform { get; set; }
    public ISSActionCallback callback { get; set; }

    protected SSAction() { }

    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}
然后定义曲线运动
public override void Update()
    {
        Vector3 targetPos = target.transform.position;

        //让始终它朝着目标  
        gameobject.transform.LookAt(targetPos);

        //计算弧线中的夹角  
        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.transform.position);
        gameobject.transform.Translate(Vector3.forward * Mathf.Min(speed * Time.deltaTime, currentDist));
        if (this.transform.position == target.transform.position)
        {
            DiskFactory.getInstance().freeDisk(gameobject);//到达终点就free
            Destroy(target);
            this.destroy = true;
            this.callback.SSActionEvent(this);
        }
    }
接下来完善Emit的定义
public class Emit2 : SSAction
{
    public SceneController sceneControler = (SceneController)SSDirector.getInstance().currentScenceController;
    public GameObject target;   //要到达的目标  
    public float speed;    //速度  
    private float distanceToTarget;   //两者之间的距离  
    float startX;
    float targetX;
    float targetY;

    public override void Start()
    {
        speed = 5 + sceneControler.round * 5;//使速度随着轮数变化
        startX = 6 - Random.value * 12;//使发射位置随机在(-6,6)
        if (Random.value > 0.5)
        {
            targetX = 32;
        }
        else
        {
            targetX = -32;
        }
        targetY = (Random.value * 25);//随机在(0,25)
        this.transform.position = new Vector3(startX, 0, 0);
        target = new GameObject();//创建终点
        target.transform.position = new Vector3(targetX, targetY, 30);
        //计算两者之间的距离  
        distanceToTarget = Vector3.Distance(this.transform.position, target.transform.position);
    }
    public static Emit2 GetSSAction()
    {
        Emit2 action = ScriptableObject.CreateInstance<Emit2>();
        return action;
    }
不要以为那些位置值是凭空出来的,都是用一个具体cube,不断微调它的位置让它恰好在摄像机屏幕最下方的完整的一行,终点也是如此,微调到Cube恰好出屏幕的一个位置,便于最快速回收,太远会飞很久然后需要跟多实例飞碟,所以说往往细节决定成败。FIGHTING!!!
      然后定义下动作管理器的基类和派生类。
public class SSActionManager : MonoBehaviour
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    private List<SSAction> waitingAdd = new List<SSAction>();
    private List<int> waitingDelete = new List<int>();

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    protected void Update()
    {
        foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
        waitingAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                waitingDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key]; actions.Remove(key); DestroyObject(ac);
        }
        waitingDelete.Clear();
    }

    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
    {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }
}

public class CCActionManager : SSActionManager, ISSActionCallback
{
    public SceneController sceneController;
    public DiskFactory diskFactory;
    public Emit2 EmitDisk;
    int count = 0;
    // Use this for initialization
    protected void Start()
    {
        sceneController = (SceneController)SSDirector.getInstance().currentScenceController;
        diskFactory = DiskFactory.getInstance();
        sceneController.actionManager = this;
    }

    // Update is called once per frame
    protected new void Update()
    {
        if (sceneController.round <= 3 && sceneController.game == 1)
        {
            count++;
            if (count == 60)//实现60帧发射一个飞碟
            {
                EmitDisk = Emit2.GetSSAction();
                this.RunAction(diskFactory.getDisk(sceneController.round), EmitDisk, this);
                sceneController.num++;//记录发射数量
                print(sceneController.num);
                count = 0;//满60帧置零实现循环
            }
            base.Update();
        }
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {
        //
    }
}

然后实现用户接口实现功能显示细节,开始游戏,重新开始游戏。
public interface IUserAction
{
    void StartGame();//开始游戏
    void ShowDetail();//显示细节,介绍游戏
    void ReStart();//重新开始游戏
}

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    // Use this for initialization
    void Start()
    {
        action = SSDirector.getInstance().currentScenceController as IUserAction;
    }
    void OnGUI()
    {
        GUIStyle fontstyle1 = new GUIStyle();
        fontstyle1.fontSize = 50;
        fontstyle1.normal.textColor = new Color(255, 255, 255);
        if (GUI.RepeatButton(new Rect(0, 0, 120, 40), "Shooting Disk"))
        {
            action.ShowDetail();
        }
        if (GUI.Button(new Rect(0, 60, 120, 40), "STARTGAME"))
        {
            action.StartGame();
        }
        if (GUI.Button(new Rect(0, 120, 120, 40), "RESTART"))
        {
            action.ReStart();
        }
    }
    // Update is called once per frame
    void Update()
    {
        //
    }
}

最后实现下导演和场记就大功告成。
public interface ISceneController
{
    void LoadResources();
    //void Pause ();
    //void Resume ();
}

public class SSDirector : System.Object
{
    private static SSDirector _instance;

    public ISceneController currentScenceController { get; set; }
    public bool running { 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;
    }
}

做之前我们想一想,点击完飞碟直接设置active为false,就看不见了是不是有点不过瘾,少了点爆炸效果呢,如何做到呢,稍稍利用下PaticleSystem就行了。具体设置如下,我就不具体介绍了自己查查资料就知道了
               
还有如果只看到点击,没看到分数和轮数是不是有点遗憾,试用下Unity3D的GUI建立Text出来就行啦
具体属性可以在检视面板设置,自己去尝试尝试就知道啦 吐舌头
写出Updae如下
 // Update is called once per frame
    void Update()
    {
        ScoreText.text = "Score:" + Score.ToString();
        RoundText.text = "Round:" + round.ToString();
        if (Input.GetMouseButtonDown(0) && game == 1)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.transform.tag == "Disk")
                {
                    explosion.transform.position = hit.collider.gameObject.transform.position;
                    explosion.GetComponent<Renderer>().material = hit.collider.gameObject.GetComponent<Renderer>().material;
                    explosion.GetComponent<ParticleSystem>().Play();
                    hit.collider.gameObject.SetActive(false);
                    print("Hit!!!");
                    Score += round;
                }
            }
        }
        if(game == 2)
        {
            GameOver();
        }
    }
然后想着如果点击游戏开始直接开始了是不是很糟糕,玩家都没时间准备的,那就来个倒计时吧。如何实现呢?通过协程实现就行啦,什么是协程呢?请看博客 协程.
public IEnumerator waitForOneSecond()
    {
        while (CoolTimes >= 0 && game == 3)
        {
            GameText.text = CoolTimes.ToString();
            print("还剩" + CoolTimes);
            yield return new WaitForSeconds(1);
            CoolTimes--;
        }
        GameText.text = "";
        game = 1;//游戏开始
    }

我们只要在StartGame中调用即可
public void StartGame()
    {
        num = 0;
        if (game == 0)
        {
            game = 3;//进入倒计时状态
            StartCoroutine(waitForOneSecond());
        }
    }
接下来实现下其他User接口函数和其他定义即可
public void ReStart()
    {
        SceneManager.LoadScene("task1");
        game = 0;
    }
    public void ShowDetail()
    {
        GUI.Label(new Rect(220, 50, 350, 250), "Use your mouse click disk, you will get 1 point for green Disk,2 for yellow Disk,3 for red Disk,you should get 20 points to pass round1,40 to pass round2,60 to pass round3.There are three round.Good Luck!!!");
    }

public class SceneController : MonoBehaviour, ISceneController, IUserAction
{
    public SSActionManager actionManager { get; set; }
    public int round = 0;//轮数
    public float Score = 0;//分数
    public Text ScoreText;//分数文本
    public Text RoundText;//轮数文本
    public Text GameText;//倒计时文本
    public Text FinalText;//结束文本
    public int game = 0;//记录游戏进行情况
    public int num = 0;//每轮的飞碟数量
    GameObject disk;
    GameObject explosion;
    public int CoolTimes = 3; //准备时间
    // Use this for initialization
    void Awake()
    //创建导演实例并载入资源
    {
        SSDirector director = SSDirector.getInstance();
        DiskFactory DF = DiskFactory.getInstance();
        DF.sceneControler = this;
        director.setFPS(60);
        director.currentScenceController = this;
        director.currentScenceController.LoadResources();
    }
    void Start()
    {
        round = 1;
    }
    public void LoadResources()
    {
        explosion = Instantiate(Resources.Load("prefabs/Explosion"), new Vector3(-40, 0, 0), Quaternion.identity) as GameObject;
        Instantiate(Resources.Load("prefabs/Light"));
    }
public void GameOver()
    {
        FinalText.text = "Game Over!!!";
    }
最后上个类图和成果图帮大家理理关系
(告诫大家一个事,是我的亲身经历,本来我周四晚上已经做完了,但是为了选飞碟模型上网去下载了模型,想套用但是发现它不能变颜色,所以我删掉它但是不小心把整个文件夹都删了,我还以为在回收站能找回,ctrl+z能撤回,我想太多了,百度一下,答案都是删除文件夹就没有,我能怎么办,我也很绝望,打了一天的代码重打一次。 哭 哭 哭)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity3D学习(5)之工厂回收利用的3D版飞碟游戏 的相关文章

随机推荐

  • MOS驱动自举电容和限流电阻的选取

    自举电容选取 最近做逆变时出现了异常 使用2104驱动MOS管 蓝色为滤波后双端带载时出现的波形 一端带载时没有问题 放大波形后发现输出波形在占空比满值时垮掉 产生严重的震荡 可以看到波形顶部斜向下 我们可以推断是驱动自举电容值偏小 当占空
  • ARMv8-A 地址翻译技术之MMU的前世今生

    MMU的重要性不言而喻 支撑操作系统之上的各种复杂应用 但在正式讲MMU之前 我们先说说MMU的发展史 因为ARMv8 A的MMU相当复杂 直接切入正题 会显得比较枯燥 废话不多说 咱们马上开始 一 前言 关于虚拟内存系统的演变史 MMU在
  • 计划 060703

    ESOE项目暂时作为一个自娱型项目 每日投入30分钟 近期按计划完成以下工作 1 完成计划 ok 2 完成对ESOE项目的介绍 ok 060704 3 在blog发布已有的 ESOE Specification v0 1 doc 英文版 o
  • 什么是.NET架构

    什么是 NET架构 NET架构主要分为3部分 FCL Framework Class Library CTS Common Type System 其中包括Common Language Specification CLR Common L
  • 教你自制一款简单的助听器

    助听器实质上是一种低频放大器 可用耳机进行放音 当使用者用上耳机后 可提高老年者的听觉 同时可对青少年的学习和记忆能带来方便 一 工作原理 本电路由话筒 前置低放 功率放大电路和耳机等部分组成 原理电路图见图1 其印刷板电路图见图2 驻极体
  • c++面对对象基础知识

    一 类的定义格式 class calss name private data member declarations public member functions 二 构造函数 1 在程序声明对象时 将自动调用构造函数 2 c 提供两种构
  • 腾讯2017暑期实习生笔试题题解

    7个月没有刷题了 现在真的是菜到爆炸 所以来牛客水一水编程题 一 构造回文 题意 给定一个字符串s 你可以从中删除一些字符 使得剩下的串是一个回文串 如何删除才能使得回文串最长呢 输出需要删除的字符个数 输入描述 输入数据有多组 每组包含一
  • 获取配置文件中的属性

    spring boot的工程启动的时候 内部文件默认是加载classpath路径或者classpath config目录下的application properties文件的 当然也可以指定加载其它的配置文件 如何获取配置文件中的属性呢 实
  • VSCODE如何汉化成中文

    VSCODE默认是以英文显示的 对于不习惯用英文的朋友可以将VSCODE汉化成中文 小编来说下如何汉化吧 工具 原料 VSCODE 方法 步骤 1 VSCODE默认情况下是英文的 点击左侧菜单栏最底下的四方形按钮打开扩展程序界面 在输入框内
  • 微信小程序之Image那些事

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 使用场景 二 使用方式 1 动态读取image大小 2 动态设置style 3 动态赋值 总结 前言 小程序中 Image使用频率是非常高的 不同场景下
  • 深度学习移动端在线训练 --- 基于MNN的端侧Finetune实现

    在决定使用MNN实现在线训练之前 也比较了TNN NCNN 发现目前各大端侧推理引擎的训练框架都不成熟 半斤八两的状态 可能都把精力放在推理和op支持上 但是端侧训练的需求真的少么 fine tune在端侧应用难道不是刚需 端侧推理的实现相
  • 爬虫日常-selenium登录12306,绕过验证

    文章目录 前言 代码设计 前言 hello兄弟们 这里是无聊的网友 愉快的周末过去了 欢迎回到学习频道 书接上文 我们说到了再用selenium登录12306时遇到了滑块验证的问题 当前的网站几乎每家都会在登录模块添加一个认证 来规避各种爬
  • maven依赖冲突以及解决方法

    什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包 有多个不同的版本 因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的 每个显式声明的类包都会依赖于一些其它的隐式类包 这些隐式的类包会被maven间接引入进来
  • 【技术经验分享】计算机毕业设计hadoop+spark知识图谱医生推荐系统 门诊人数预测 医疗数据可视化 医疗大数据 医疗数据分析 医生爬虫 大数据毕业设计 大数据毕设

    开发技术 springboot vue js element ui spark hadoop lstm情感分析模型 KNN CNN卷积神经 线性回归 协同过滤算法 用户 物品 MLP神经网络 SVD深度学习模型 echarts python
  • CRM IFD部署更换证书 - adfs证书更换

    更换证书 导入证书 更换IIS证书 更换ADFS证书 设置服务通信证书 添加令牌签名证书和令牌解密证书 更新证书指纹 更新配置 更新CRM配置 更新ADFS信赖方元数据 好家伙 证书又到期了 前面写了CRM网站的证书的更换比较简单 这次呢大
  • 【Transformer】20、SOFT: Softmax-free Transformer with Linear Complexity

    文章目录 一 背景 二 方法 2 1 Softmax free self attention formulation 2 2 通过矩阵分解来实现低秩规范化 三 效果 本文收录于 NeurIPS 2021 论文链接 https arxiv o
  • 使用spring mvc内部集成的jackson将对象转成json格式字符串

    如果是spring boot pom xml里面已经导入了下面这个mvc环境起步依赖也可以用 下面是例子
  • 深度学习入门之如何制作npz、npy文件

    一 需求 论文 EyeTracking for everyone 中提出了iTracker神经网络 并构建了一个叫GazeCapture的数据库 将其部分数据集下载后 可以看到文件的层次结构如下图所示 其中 整个数据集的后缀名是npz 内部
  • 暑假补卷5——进程信号

    信号入门 板书 1 生活角度的信号 你在网上买了很多件商品 再等待不同商品快递的到来 但即便快递没有到来 你也知道快递来临时 你该怎么处理快递 也就是你能 识别快递 当快递员到了你楼下 你也收到快递到来的通知 但是你正在打游戏 需5min之
  • Unity3D学习(5)之工厂回收利用的3D版飞碟游戏

    这一次我们来做的任务是3D版鼠标点击鼠标的游戏 我们先来看一下游戏需求 案例研究 鼠标打飞碟 游戏设计 游戏需求 1 分多个 round 飞碟数量每个 round 都是 n 个 2 每个 round 的飞碟的色彩 大小 发射位置 速度 角度