当前位置: 首页 > news >正文

【Unity3D】夏日大作战Jumper~

博主前些日子和别的学院的同学共同制作了一款小游戏Jumper,现在把其开源出来,希望可以给在Unity初学道路上的同学一些帮助 :)

我们首先看一下游戏的最终截图,效果完成度不高,但是其中代码的基本逻辑是比较齐全的。

这里写图片描述

我们看到的这只小鸡就是我们的主角了!背景是一个大楼,右上角有一个温度计,会随着时间上升。我们要跳上各种挡板,尽可能地在那些窗户上安装空调,(否则同学们会暴动的!)。提示,右下角有一个药丸,吃了可以大幅度提升跳跃能力,左下角是一个敌人的模型,别被黏黏的蜗牛触碰到哦!

私以为运用Unity,最重要就是场景的设计和游戏主角的逻辑。只要这两点明确下来,那么附属的物品等基本上就是轻车熟路了。包括阅读别人的代码,如果想从一个脚本中分辨出其十多个变量的意义,那么无异于舍本逐末。只要理解了其最最基本的主角逻辑和场景逻辑,那么再在其基础上加上”药物“、”物品“、”属性“、”分数“这些东西,就会事半功倍。

即时是一个最简单的游戏,
让我们看一下主角的行为,Character对象最后那个最最核心的Update()
为了让代码更清晰易懂,我只保留了其中的一部分。

void Update()
    {
        //Vector3 dir = Vector3.zero;
        //dir.x = -Input.acceleration.x * 2;
        //dir *= Time.deltaTime;

        if (Input.GetKey(KeyCode.LeftArrow))
        {
            if (!left) {/*主角转向*/
                transform.rotation = new Quaternion(0, 180, 0, 1);
                left = true;
            }
            /*向左移动*/
            transform.Translate(Vector3.right * -Time.deltaTime * moveSpeed, Space.World);

        }

        if (Input.GetKey(KeyCode.RightArrow))
        {
            /*和向左移动的逻辑类似*/
            if (left) {
                transform.rotation = new Quaternion(0, 0, 0, 1);
                left = false;
            }
            transform.Translate(Vector3.right * Time.deltaTime * moveSpeed, Space.World);
        }
        /*跳跃*/
        if (Input.GetKey(KeyCode.LeftControl))
        {
            Jump();
        }
        /*安装空调*/
        if (Input.GetKey(KeyCode.LeftShift))
        {
            OnWorking();
        }


有些人喜欢将类似于Jump和OnWorking()这样的函数直接在Update()中完成,我觉得这样做非常不妥当。类中函数调用的开销非常低,而且更有利于以后的重组、修改。而且整体逻辑看起来一目了然。

另一个比较重要的和主角有关的逻辑是死亡判定,我将这一部分写入到了另一个对象Score当中(它保有一个Character的引用)。因为主角的死亡原因可能不光是因为”跳到钉子上面“,还有可能是因为”超过了时间“等这样的全局因素影响。

 void Update()
    {
        if (player.transform.position.y > lastY)
        {
            score = (int)(player.transform.position.y * 200);
            lastY = player.transform.position.y;
        }

        if (!isDead)
        {
            gameObject.GetComponent<GUIText>().text = "Score: " + score;
        }



        if (isDead && !isSet)
        {
            audioLose.Play();
            int highscore = PlayerPrefs.GetInt("highscore");
            if (highscore < score)
            {
                PlayerPrefs.SetInt("highscore", score);
            }
            GetComponent<GUIText>().anchor = TextAnchor.MiddleCenter;
            transform.position = new Vector3(0.5f, 0.5f, 0);
            gameObject.GetComponent<GUIText>().text = "You lost\nYour Score: " + score + "\nYour Highscore: " + highscore;
            isSet = true;
            player.GetComponent<Collider>().enabled = false;
        }
    }

另一大要素场景的逻辑稍显复杂一些。我们除了背景固定外,各种障碍、跳板的位置是随机的。而且种类很多,有普通板(x),破碎的板子(b),带钉子的板子(s),需要安装空调的窗户(m)。
我写了一个类来专门负责它们的安装工作,它将从一个txt(类似于地图)中读取几个预设,然后返回一个ArrayList来存储类似于‘x’‘x’‘x’‘b’的字符。

private ArrayList readPattern (string path)
        {
                TextAsset txt = (TextAsset)Resources.Load (path, typeof(TextAsset));
                string content = txt.text;
                ArrayList patternList = new ArrayList ();

                string[] lines = content.Split ("\n" [0]);
                char[,] singlePattern = new char[,] {
                {'x','x','x','x','x'},
                {'x','x','x','x','x'},
                {'x','x','x','x','x'}
            };
                int lineCounter = 0; 
                foreach (string line in lines) {
                        if (lineCounter < 3) {
                                for (int i = 0; i < 5; i++) {
                                        singlePattern [lineCounter, i] = line.ToCharArray () [i];
                                }
                                lineCounter++;
                        } else if (lineCounter == 3) {
                                patternList.Add (singlePattern);
                                singlePattern = new char[,] {
                        {'x','x','x','x','x'},
                        {'x','x','x','x','x'},
                        {'x','x','x','x','x'}
                    };
                                lineCounter = 0;
                        }

                }
                return patternList;
        }

我们已经得到了随机的地图,下一步是有方法来调用他。
并且在合适的位置和合适的时机(在离主角足够近时)安装上这些挡板。

void Spawn ()
        {
            if (player.GetComponent<Rigidbody>().transform.position.y > 42) {
                return;    
            }
                float staticLastSpawnY = lastSpawnY;
                while (j <= staticLastSpawnY) {
                        char[,] tempPattern = (char[,])easySpawnPattern [Random.Range (0, easySpawnPattern.Count)];
                        if (player.GetComponent<Rigidbody>().transform.position.y < 50) {
                                tempPattern = (char[,])easySpawnPattern [Random.Range (0, easySpawnPattern.Count)];
                        }
                        if (player.GetComponent<Rigidbody>().transform.position.y > 50 && player.GetComponent<Rigidbody>().transform.position.y < 100) {
                                tempPattern = (char[,])mediumSpawnPattern [Random.Range (0, mediumSpawnPattern.Count)];
                        }
                        if (player.GetComponent<Rigidbody>().transform.position.y > 100) {
                                tempPattern = (char[,])hardSpawnPattern [Random.Range (0, hardSpawnPattern.Count)];
                        }

                        for (int k = 2; k >= 0; k--) {
                                j += 5f;
                                for (int i = 0; i < 5; i++) {
                                        if (tempPattern [k, i].ToString () == "-") {
                                            Instantiate(Resources.Load("Normals"), new Vector3(getWorldCoordinates(i), Random.Range(0, 0.25f) + j, 0), Quaternion.identity);
                                        }
                                        if (tempPattern [k, i].ToString () == "s") {
                                                Instantiate (Resources.Load ("Spikes"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j, 0), Quaternion.identity);
                                        }
                                        if (tempPattern [k,i].ToString () == "m") {
                                            //这里需要注意的是,由于这个坐标物体没有正确的原点。所以在z轴上进行了偏移
                                                Instantiate (Resources.Load ("Missions"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j,-0.3f), Quaternion.identity);
                                        }
                                        if (tempPattern [k,i].ToString () == "b") {
                                                Instantiate (Resources.Load ("Brokens"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j, 0), Quaternion.identity);
                                        }

                                }
                        }
                        Clouds (j, getWorldCoordinates (Random.Range (0, 5)) + Random.Range (-1, 1));
                        Award (j, getWorldCoordinates (Random.Range (0, 5)) + Random.Range (-1, 1));

                }
                lastSpawnY += 10;
        }

这个源代码中还有几个部分,例如【板子的种类】【药物】【主角属性的变化】【计时操作】,这几个部分我们先将他们和C#文件一一对应起来。再大段粘贴代码的话读者已经很难看下去了~

【板子的种类】
共有MissionTile、SpikeTile、BrokenTile它们共同继承于Tile(普通板)。MissionTile向Score”汇报“任务完成。(如果把这一工作交给了主角Character来做,明显是打破了单一职责原则)
【药物】
包括了Capsule,这个游戏目前只有一种药物。但是药物的效果并没有在里面体现。只说明了这是”哪一种“药物和 OnTriggerEnter()用来触发人物吃药的效果。我之所以这样设计是因为直接让药物这样的类去操纵人物的属性不符合里氏替换原则。(简单地说是请面对接口!)

【主角属性的变化】【计时操作】
主角属性的变化一般都是有时间限制的,我看到过一些写法,是在主角Character中完成这一计时。但是完全有更好的办法就是协程:
(我不得不再粘点代码上来。。)

 IEnumerator AffectTimer()
    {
        yield return new WaitForSeconds(Constants.CAPSULE_TIME);
        //todo 修改人物的属性到基本状体
        Debug.Log("药效完毕");
        //恢复正常状态
        GoBackNormal();
        yield return null;
    }

这就先告一段落了,我上一次使用Unity写的一个空调模拟系统这里也一并贴出来占一下空【请忽略下图
这里写图片描述

版权声明:本文为博主原创文章,转载请标明出处。

转载于:https://www.cnblogs.com/fridge/p/4861911.html

相关文章:

  • Play Famework Use JBoss Netty
  • Linux系统资源监控命令
  • 如何在Visual Studio 2010中新建CUDA 4.0项目
  • 深入理解JavaScript系列(11):执行上下文(Execution Contexts)
  • 采用curl库
  • centos 5.7_64位下FastDFS_client的配置和PHP测试
  • C#尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
  • 设置开机等待时间
  • Sharepoint 2013 左右quot;SPChangequot;一个简短的引论
  • 哄骗JQuery直接调用asp.net后台办法
  • DOM Document节点类型详解
  • C 工具库5:first fit pool
  • 使用eclipse 开发android应用没有代码提示
  • 写得蛮好的linux学习笔记
  • JavaWeb学习总结(三)——Tomcat服务器学习和使用(二)
  • ----------
  • canvas 五子棋游戏
  • classpath对获取配置文件的影响
  • ComponentOne 2017 V2版本正式发布
  • javascript 总结(常用工具类的封装)
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • TCP拥塞控制
  • use Google search engine
  • WePY 在小程序性能调优上做出的探究
  • 从零搭建Koa2 Server
  • 基于 Babel 的 npm 包最小化设置
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 通过git安装npm私有模块
  • 通过npm或yarn自动生成vue组件
  • 一道闭包题引发的思考
  • 源码安装memcached和php memcache扩展
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • ( 10 )MySQL中的外键
  • (31)对象的克隆
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (vue)页面文件上传获取:action地址
  • (十)T检验-第一部分
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转)创业家杂志:UCWEB天使第一步
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .net 使用ajax控件后如何调用前端脚本
  • .net打印*三角形
  • .NET基础篇——反射的奥妙
  • .stream().map与.stream().flatMap的使用
  • @Bean注解详解
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析