个人技术总结——在Unity中简单地制作一张2D地图并随机生成资源

221900108-侯沛延 学生 2022-06-26 22:00:02
这个作业属于哪个课程软件工程2022年春-F班
这个作业要求在哪里软件工程实践总结&个人技术博客
这个作业的目标课程回顾、个人技术总结
其他参考文献《构建之法》

目录

  • 一、技术概述
  • 1.1 技术使用场景
  • 1.2 技术难点
  • 二、技术详述
  • 2.1 素材导入
  • 2.2 编辑素材
  • 2.3 绘制地图
  • 2.4 关于图层
  • 2.5 简单的随机资源生成
  • 三、可能遇到的问题以及解决方案
  • 3.1 素材问题
  • 3.2 地图制作问题
  • 3.3 随机生成资源问题
  • 四、总结
  • 五、参考内容


一、技术概述

1.1 技术使用场景

  你不满足于别人提供的现成的地图,想要利用原有的素材来将你自己的想法实现吗?或者你不知道如何获取素材,不知道如何使用素材?在此发挥你天马行空的想象力,用你的鼠标和键盘来绘制自己的王国吧!

1.2 技术难点

  随机资源生成算法的实现。(注:本人unity版本为2021.3.2f1c1,不同版本可能组件位置有所不同)

二、技术详述

2.1 素材导入

  在项目中,点击左上角菜单栏的window,可以看到子选项中有一个Asset Store。

img

  点击它我们可以来到Unity资源商店寻找我们想要的资源,包括地图、角色、道具、材质等等,你几乎可以在这里找到所想要的东西。

在这里插入图片描述

  点击左上角的2D,并勾选上“环境”、“免费资源”(能不花钱就不花钱啊喂),我们来挑选一张心仪的地图,进入资源详情页后,点击“添加至我的资源”后选择“在unity中打开”即可。

在这里插入图片描述


在这里插入图片描述

  选择import,等待资源导入。

在这里插入图片描述


在这里插入图片描述

  可以看到左下角的项目文件已经把素材导入进来,现在我们可以开始创作了!

在这里插入图片描述

2.2 编辑素材

  首先我们更改一下素材的配置,主要要更改一下素材每单位的像素点。我们可以看到我们的Scene界面有很多网格,一个格子就是一个Unit,这个Pixels Per Unit就是指一个格子中的像素点,当这个值过大,会让每个单位格里面的东西变得很小,个人习惯将这个值调至16,当然你可以自由选择。Sprite Mode更改成Multiple,这会有利于我们后续对素材的切割和制作。更改后,记得点击Apply应用。

在这里插入图片描述

2.3 绘制地图

  绘制地图我们需要一个载体,右键左部空白位置,点击2D Object -> Tilemap ->Rectangular ,生成一个Tilemap,供我们在上面制作地图。

在这里插入图片描述

  我们还需要一个画板,就跟画家的调色板一样,他们会在上面放上各种颜料,而我们需要在上面放上我们的素材,我们的画板就是Tile Palette。点击左上角的window -> 2D -> Tile Paletee,这时候我们弹出了一个窗口,这个就是我们的画板,将素材(颜料)放在上面,我们才可以进行我们的创作。

在这里插入图片描述


在这里插入图片描述

  我们可以看到这个素材包里面有现成的帮我们“调制”好的颜料,不过呢有的素材包是没有的,为了能够掌握地图绘制的步骤,我们来重新“调制”一遍!点击画板中的Create New Palette,将新画板的名字重命名,然后点击Create,这时候会弹窗让我们选择一个保存位置,我们可以新建一个文件夹,随便命名一下(我这里命名为Map),选择该文件夹将该画板保存到这个位置。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

  不过现在你可以看到,我们的画板上没有任何的东西,我们现在需要将我们的素材切割好并导入,也就是调制我们的颜料。在素材详情页面点击Sprite Editor,进入素材编辑界面,点击Type更改切割类型,选择Grid By Cell Size,更改Pixel Size中x和y使之和我们之前定义的Pixels Per Unit数值一致,之后点击slice,最后记得点击右上角的Apply保存。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

  这时我们可以使用素材中每个单元格来进行地图的绘制了!将素材拖入我们的Tile Palette中,选择一个保存的文件夹(我们还是选择放在Map文件夹中),等待导入,之后我们就会发现,我们现在画板上就已经有了素材,而且它已经被划分成了一小格一小格的单位,这时候,我们便可以拿起我们的画笔,正式开始创作!

请添加图片描述


请添加图片描述

  让我们用流程图总结一下简单地图制作的过程!

在这里插入图片描述

2.4 关于图层

  图层问题是一个需要长篇幅来叙述的功能,你可以利用图层决定角色、水域、草地等元素的显示优先级以及碰撞等等一些细节的体现,本博客止于地图的简单创建,接下来随机生成物体的内容需要你对诸如Tilemap的创建、预制体的创建等内容有所了解,如果想对这些部分进行学习,可以查看本博客的参考内容中的教程。

2.5 简单的随机资源生成

  我们首先创建一张地图,将可生成资源的地块划分到一个图层,剩下的划分到另一个图层。例如我这里将不可生成资源的地块划分到了unrandomground,可生成资源的地块在randomground中。我们还需要准备一些预制体,将这些预制体作为生成的资源。

在这里插入图片描述

  新建一个脚本,开始编写代码。思路如下流程图所示:

在这里插入图片描述

  首先做好前期准备,保存我们需要随机的资源:

  //需要随机的资源
    public List<GameObject> resourceGrass = new List<GameObject>(); // 草
    public List<GameObject> resourceGravel = new List<GameObject>(); // 石头
    public List<GameObject> resourceTree = new List<GameObject>(); // 树木、灌木
    public List<GameObject> resourceWoodenConstruction = new List<GameObject>(); // 木制结构
    public List<GameObject> resourceLithicalConstruction = new List<GameObject>(); // 石制结构
    

    private Tilemap tilemap;
    private List<Vector3> randomGroundTileWorldPos = new List<Vector3>(); // 获取可随机的地块
    private bool[] randomGroundTileHasEmptySlot; // 判断地块是否已占用,需要初始化

    private int randomGroundTileCount; // 随机地块数量
    private int resourcesGrassCount; // 草 数量
    private int resourcesGravelCount; // 碎石 数量
    private int resourcesTreeCount; // 树木、灌木
    private int resourcesWoodConstructionCount; // 木制结构数量
    private int resourcesLithicalConstructionCount; // 石制结构数量

  初始化函数,由于我们可以将脚本挂接在randomground下,因此保存所有地块就相当于保存了所有可随机地块:

 void InitializeTileMap() // 保存地图的所有地块
    {
        tilemap = GetComponent<Tilemap>();
        Vector3Int tmOrg = tilemap.origin;
        Vector3Int tmSz = tilemap.size;

        for (int x = tmOrg.x; x < tmSz.x; x++)
        {
            for (int y = tmOrg.y; y < tmSz.y; y++)
            {
                if (tilemap.GetTile(new Vector3Int(x, y, 0)) != null)
                {
                    Vector3 cellToWorldPos = tilemap.GetCellCenterWorld(new Vector3Int(x, y, 0));
                    randomGroundTileWorldPos.Add(cellToWorldPos);
                }
            }
        }
    }

  接着就开始生成资源,这里举例生成草的函数:

  void GenerateGrass(System.Random rm) // 生成草
    {
        int aRandomTile;
        for (int i = 0; i < 150; i++) 
        {
            while(!randomGroundTileHasEmptySlot[aRandomTile])
            {
                  aRandomTile= rm.Next(0, randomGroundTileCount);
            }
            Vector3 spawnPos = randomGroundTileWorldPos[aRandomTile];
            int aRandomRes = rm.Next(0,resourcesGrassCount);
               GameObject spawnRes = resourceGrass[aRandomRes];
            Instantiate(spawnRes, spawnPos, Quaternion.identity);
            randomGroundTileHasEmptySlot[aRandomTile] = false;
        }
    }

  编写完代码后,将脚本挂在randomground下,将需要生成的资源以预制体的形式拖入,点击运行,就可以得到我们想要的效果

在这里插入图片描述


在这里插入图片描述

  不过我们发现,当物体较多时,生成的速率会变慢,这是因为我们用一个数组去存储地块的生成资源情况会使得在地块稀少时寻找可利用地块耗时增加,我们可以改进一下,将生成过资源的地块直接从我们保存地块的列表之中移出,代码如下:

void GenerateTree(System.Random rm) // 生成树木、灌木
    {
        for (int i = 0; i < 100; i++) // 生成树木和灌木
        {
            randomGroundTileCount = randomGroundTileWorldPos.Count; // 随机地块数量
            int aRandomTile = rm.Next(0, randomGroundTileCount);

            Vector3 spawnPos = randomGroundTileWorldPos[aRandomTile];

            int aRandomRes = rm.Next(0, resourcesTreeCount);
            GameObject spawnRes = resourceTree[aRandomRes];
            Instantiate(spawnRes, spawnPos, Quaternion.identity);

            randomGroundTileWorldPos.Remove(spawnPos);
        }
    }

  可以发现效率较上一种方法高的多。

三、可能遇到的问题以及解决方案

3.1 素材问题

Q:Unity商店里有我很喜欢的素材,但是卖的很贵怎么办?
A:
  如果只是新手练手使用,可以由tb或者pdd等渠道获取,但如果要用于商业,必须由本人在版权方所提供的渠道购买(Unity商店),否则会有侵权的问题。不过Unity商店提供的素材经常有大力度的打折,你可以收藏下你喜欢的素材等打折时购买。这里推荐一个B站Up主Lee哥的游戏开发加油站,他经常发一些优质且有很大折扣的Unity素材,可供参考。

Q:我在切割素材时,有些按Grid By Cell Size类型切割并不能得到我想要的效果?
A:
  可以尝试着选择其他类型的切割方式,或者是调整一下每个单元格的像素量,素材质量有高有低,有时还需要自己微调。

3.2 地图制作问题

Q:我明明给地图某些部分添加了碰撞体积,但是子弹能穿过去?
A:
  地图的碰撞体积一般由如下图三部分构成,选择Composite Collider 2D中的Geometry Type(指定复合碰撞体生成的几何体的类型),将其设为Polygons(设置符合碰撞体,以便为由凸多边形组成的合并碰撞体集合体生成封闭轮廓),然后在子弹的碰撞体积中,设置Rigidbody 2D的Collision Detection为Continuous(连续检测,防止对象穿过所有静态碰撞体)

在这里插入图片描述


在这里插入图片描述

  可参考Unity-Rigidbody组件Unity官方文档-GeometryType

3.3 随机生成资源问题

Q:生成的物体位置与我设想的不一致?
   正常情况下树木如果在它脚下这块石板上生成,应该会是如下图所示,但实际会往下偏移

在这里插入图片描述

A: 这是预制体的锚点问题,在sprite Editor中将对应物体的锚点往下移动即可。

请添加图片描述

Q:生成的物体图层有问题,例如树木后面的物体应该是被遮蔽的,事实却不是如此?
A:
  点击Edit选择ProjectSettings,在Graphics中选择Transparency Sort Mode,将其置为Custom Axis,此时在同一图层上,物体会根据所处的Y值排序,即Y值越高,显示优先级越靠后,便解决了该问题

在这里插入图片描述


在这里插入图片描述

四、总结

  Unity的地图制作是新手入门Unity的必学操作之一,其难度并不高,主要依靠学者的细心和耐心。关于资源的随机生成本博客只是提供了一个简单的办法,实际上关于随机地图的生成有其他更深入的研究,包括限制资源生成范围、地形的生成,甚至可以做到地图全随机而不用自己先搭建出一个框架,涉及到了各种模型分析和数值计算。学无止境,不可止步于此。

五、参考内容

1、如何在Tilemap上随机生成资源——bilibili
2、Unity2018教程2D入门——bilibili
3、Unity官方用户手册

...全文
334 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
Unity 对接网狐服务器 ——波波 网狐的棋牌服务器非常稳定,代码质量高,最重要的是开源。阅 读网狐的代码,自我感觉技术提高不少,真心感谢网狐! 网狐的 PC 客户端的代码,和服务器代码同出一辙,优秀稳定, 功能齐全。唯独移动平台的客户端的研发稍微有点落后。前几年,网 狐为 Android和 iPhone两个移动平台分别开发了大厅, 弊端显而易见, 一个游戏就会同时配有三个不同平台的客户端,也就是说,需要使用 三种编程语言来实现客户端才能满足市场的需求。 这三种语言分别是 C++,java,object-C。开发难度之大,可想而知。 2016 年 2 月,网狐终于推出了 cocos2d 的客户端,而在网狐推出 cocos 客户端之前,不少公司已经做到使用 cocos 客户端来对接网狐 的服务器了。然而,使用 unity 对接网狐服务器这个解决方案,网狐 迟迟还没有给出。下面简单介绍一下 unity 对接网狐服务器的解决方 案。 想对接成功,需要看懂网狐的服务器代码。网狐的代码其实并不 难, 工作经验超过一年的 C++程序员应该都可以看懂的。 网狐的 6603 版和经典版服务器, 上层的功能较多, 想理清代码逻辑, 需要点时间。 建议从网狐 6601 版服务器看起。 只需要看懂下面 9 个项目工程就 OK。 编译代码的先后顺序:公共服务,网络服务,列表服务,内核引 擎,游戏服务,心服务器,登录服务器,服务装载器。 共享组件是客户端和服务端公用的工程。其公共服务项目会被 内核引擎使用到,编译顺序需要优先;网络服务会被登录服务器和游 戏服务使用到,编译顺序需要优先。 编译整个解决方案后,会生成三个可执行文件和多个 DLL。三个 可 执 行 文 件 分 别 是 心 服 务 器 CenterServer.exe , 登 录 服 务 器 LogonServer.exe,服务装载器 ServiceLoader.exe。优先启动心服务 器,再接着启动登录服务器和服务器装载器。 下面说说这三者之间的关系: 心服务器:是登录服务器和所有游戏服务器的服务器,也就是 说, 在服务器层面上, 登录服务器和游戏服务器的角色是一个客户端。 心服务器启动后,各个游戏服务器再启动。这个过程,游戏 服务器要向心服务器进行房间注册。然后,启动登录服务器。登录 服务器会定时地向心服务器请求游戏房间的列表。 登录服务器:对应的客户端的大厅,大厅登录后,除了会获得当 前玩家的个人信息,还会获得游戏列表等多种信息。 游戏服务器:对应着具体的游戏客户端。由服务器装载器启动, 也就是由服务器装载器来启动各个游戏服务器项目编译出来的 DLL。 上面说法,仅仅针对网狐棋牌 6601 版本,事实上,网狐 6603 版 本在服务器结构设计上已经做了较大幅度的改动。但能看懂 6601 代 码的,也会看得懂 6603 代码,因为服务器的内核几乎没有变化,主 体架构不变。 下面开始介绍 unity 客户端。为了缩短开发示例时间,就采用 .net 的 WinForm 来做界面。代码可以直接移植到 unity。不解释。示例是 五子棋游戏。 在介绍示例前,先说说网狐的加解密方法和数据传输的协议。这 两个是难点,你能看得懂网狐的 C++版加解密方法和数据传输的协 议,并且能用其他一门语言翻译出来,就说明你的编程功底已经很不 错的,这篇漏文就不用看了,谢谢。 网狐加解密方法,是自研的。过程环环相扣,加密后的数据分析 难度大,解密难度也大。因为上一次发送的数据会作为下一次加密数 据的密钥。而上一次解密出来的数据又会作为下一次机密数据的密 钥。 就连第一次的加密密钥都是随机产生的。 应该不少公司采用 MD5 加密方法来加密数据。MD5 加密的数据是不可逆的,代价也很大。 一是 MD5 准确来说是签名,发送的数据被拦截后,整个数据包的内 容都可以分析,甚至可以换掉签名。二是,看看 MD5 的 C++实现, 就明白一次 MD5 加密会耗费多少服务器的 CPU。客户端的数据源源 不断,服务器要耗费多少时间在 MD5 加密验证上? 采用映射表和异或方法两层加密。映射方法,翻译简单。异或加 密方法,相对较难。 我用两个类来分别实现这两种加密方法。 这个过程其实并不简单。 具体看我的代码,不一一说。 接着是数据传输的协议,必须和服务器一样,才能传递正确的数 据。 难点在发送数据时,怎么组装一个命令的,并且为这个命令加密。 不一一说,具体看代码。 接下来,是大厅的介绍。 大厅要实现的功能不少,主要有注册登录获取个人信息,获取游 戏列表。其他功能,实现起来相对简单。 对应的是客户端工程的这部分代码。 再接着,是房间的功能介绍。主要包括:系统消息,公聊消息, 私聊消息,消息推送,银行操作,鲜花道具等
故事简介丑小鸭生来就很丑,谁都不喜欢它,从小被其他鸭子欺负。它无奈离开了妈妈,拿上一把猎枪,独自流浪,风餐露宿。每当遇到各种怪物而子弹不够用时,丑小鸭只能通过自己的血肉之躯踩死怪物。路上只能靠水果和蔬菜维持体力,无聊时也能抬头数星星。翻山越岭,逢水架桥,勇闯空栈道和独木桥,踩过蹦床,躲过电锯,钻过加农炮,坐过火箭,穿过枪林弹雨,在极度艰苦的条件下大战终极BOSS。最终,丑小鸭占领了一座豪华城堡,里面住着它心仪的白富美(其实也是个丑小鸭),它在夜色降临之前,轻轻关上门,打开灯,结束了流浪生涯,此时天空绽放绚烂的烟花,拉开幸福生活的序幕......这个故事告诉我们:只要你肯奋斗,我命由我不由天 时长课程分为上下两部,共64节课(21.1小时)其,上部29节课(8.5小时),下部35节课(12.6小时)课程特色对初学者友好,初次遇到新技术会详细讲解全程直播,坚决不在直播外偷偷修改展示所有细节,手把手教学游戏元素完整丰富,共3张地图18个关卡代码和文档开源,github托管地址 https://github.com/sailings/DuckAdventure完善的售后支持涵盖实战常用的知识点Physics,刚体,碰撞,弹簧体Mecanim,动画状态机,动画融合,动画层Animation,动画编辑与录制单例模式协程Dotween粒子特效射线检测键盘和移动端输入Cinemachine相机跟随,Confiner扩展UGUI常见控件,HUD屏幕自适应地图与关卡解锁关卡滑动背景滚动子弹轨迹计算音效管理场景编辑数据及上下文存储大纲丑小鸭历险记——趣味玩转unity2d游戏开发(上)  1.游戏简介及演示2.怎样画一匹骏马3.千里之行始于足下4.修复连续跳跃以及Jump动画融合和播放5.匍匐前进6.星星碰撞以及游戏管理增加积分7.吃水果蔬菜、游戏结束、制作水果蔬菜预设8.相机跟随、口水怪动画9.踩死怪物10.喷火怪11.钢管怪12.飞翔的小鸟怪13.食人鱼14.从天而降怪15.落水逻辑16.空栈道和独木桥17.蹦床和电锯18.加农炮19.强力磁铁20.坐着火箭旅行21.漫天飞舞的电锯22.枪林弹雨23.丑小鸭的反击24.定点保存25.Boss动画、移动、释放怪物、生命值管理26.Boss无敌以及特效27.Boss血条以及坠机冒烟28.Boss射击以及子弹轨道计算29.梦幻城堡

103

社区成员

发帖
与我相关
我的任务
社区描述
福州大学-计算机与大数据学院-傅明建
软件工程 高校
社区管理员
  • Mingjian_Fu
  • Lyu-
  • Wake_lie
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

本次作业截止时间为2022-02-18 23:59:59,请未完成的同学抓紧时间,加入社区后的同学要按照“学号-姓名”的格式修改社区昵称。

试试用AI创作助手写篇文章吧