这个作业属于哪个课程 | 软件工程2022年春-F班 |
---|---|
这个作业要求在哪里 | 软件工程实践总结&个人技术博客 |
这个作业的目标 | 课程回顾、个人技术总结 |
其他参考文献 | 《构建之法》 |
一、技术概述
1.1 技术使用场景
你不满足于别人提供的现成的地图,想要利用原有的素材来将你自己的想法实现吗?或者你不知道如何获取素材,不知道如何使用素材?在此发挥你天马行空的想象力,用你的鼠标和键盘来绘制自己的王国吧!
1.2 技术难点
随机资源生成算法的实现。(注:本人unity版本为2021.3.2f1c1,不同版本可能组件位置有所不同)
二、技术详述
2.1 素材导入
在项目中,点击左上角菜单栏的window,可以看到子选项中有一个Asset Store。
点击它我们可以来到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的必学操作之一,其难度并不高,主要依靠学者的细心和耐心。关于资源的随机生成本博客只是提供了一个简单的办法,实际上关于随机地图的生成有其他更深入的研究,包括限制资源生成范围、地形的生成,甚至可以做到地图全随机而不用自己先搭建出一个框架,涉及到了各种模型分析和数值计算。学无止境,不可止步于此。文章来源:https://www.toymoban.com/news/detail-401436.html
五、参考内容
1、如何在Tilemap上随机生成资源——bilibili
2、Unity2018教程2D入门——bilibili
3、Unity官方用户手册文章来源地址https://www.toymoban.com/news/detail-401436.html
到了这里,关于在Unity中简单地制作一张2D地图并随机生成资源的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!