前言
最近在开发一款跑酷类小游戏,考虑到地编很麻烦,决定写一段随机生成地图的程序。由于项目时间紧张,本文内容比较简单,不适用于复杂的跑酷地图生成,如有错误,请斧正。
地图设计
一、设计地图样式
选取了类似地铁跑酷的地图(当然比它要简单得多),分为三条跑道,玩家通过切换跑道、跳跃、滑铲等方式避开障碍物。可将跑道看作一个3行n列的网格,每个障碍物占一格空间。
二、障碍物类型
1、柱型障碍物。即玩家不可跳跃或滑铲躲避的障碍物,只能通过切换跑到躲开。
2、低矮型障碍物。即玩家可以跳过或切换跑道躲避的障碍物。
3、悬挂型障碍物。即玩家可以滑铲或切换跑道躲避的障碍物。
4、可消除型障碍物。即玩家可通过某个操作使其消除的障碍物,同时也可以切换跑道躲避。
三、添加生成限制
为了简化地图生成方法,并保证不出现死路,为障碍物的生成添加两个限制:
1、每种类型的障碍物在每一列至多生成一个。
2、在每一行(即每条跑道)中每个障碍物的前后两格内不能生成障碍物。
实现过程
一、总体思路
首先将地图3行n列的网格储存为一个字符类型的二维数组,用不同符号表示不同的障碍物类型以及通路。
定义四个障碍物类实现同一个接口,并在类中提供生成本类障碍物的方法。
再定义一个地图生成类,然后依次调用每类障碍物中的方法,生成每种障碍物,例如先生成地图上所有的柱型障碍物,再生成所有的低矮型障碍物,以此类推生成四种类型的障碍物。
最后遍历该数组,在场景中绘制出完整的地图。
二、程序设计
UML类图如下:
首先定义一个障碍物接口,包含四个方法:创建障碍物(createBarrier)、间隔数增加(intervalIncrease)、重置间隔数(intervalReset)、是否可以放置障碍物(isCanBeBarrier)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface Barrier
{
public char[,] createBarrier(char[,] map, int mapLength);
public int intervalIncrease(int interval);
public void intervalReset();
public bool isCanBeBarrier();
}
其次分别定义四种障碍物类实现该接口。此处以柱型障碍物为例。使用三个参数控制随机生成,分别为:最小生成间隔(minInterval)、最大生成间隔(maxInterval)、生成概率(rate)。并定义两个属性:当前间隔数(currInterval)、代表字符(barrierChar)。
重写接口中的方法:
先判断是否满足三个参数所控制的条件,当当前间隔数大于最小生成间隔且随机数落在概率区间时,表示可以生成障碍物,当当前间隔数大于最大间隔数时不论概率,都可以生成障碍物。代码如下:
public bool isCanBeBarrier()
{
System.Random random = new System.Random();
//当该位置与上一放置位置的间隔大于等于最小间隔时,按照概率生成障碍物
if (random.Next(1, 101) <= rate && currInterval >= minInterval || currInterval >= maxInterval)
return true;
else
return false;
}
基于上面的函数返回值,再加上上文提到的两个限制条件,即可得到障碍物生成函数,代码如下:
public char[,] createBarrier(char[,] map, int mapLength)
{
//每行生成一个该类型的障碍
for (int i = 0; i < mapLength; i++)
{
System.Random random = new System.Random();
if (isCanBeBarrier())
{
int idx = random.Next(0, 3);
//当该位置是通路且前面两格内没有障碍物时则可放置障碍物
if (map[idx, i] == 'o' && map[idx, i - 1] == 'o' && map[idx, i - 2] == 'o' && map[idx, i + 1] == 'o' && map[idx, i + 2] == 'o')
{
//在数组中生成一个障碍物
map[idx, i] = barrierChar;
//重置间隔
intervalReset();
}
}
else
{
currInterval = intervalReduce(currInterval);
}
}
//返回生成后的地图
return map;
}
剩余两个方法的重写较为简单,这里就不多做赘述。
然后定义生成地图类,该类中定义了一个静态变量map,是一个储存地图的二位数组。定义一个Barrier类型长度为四的数组barriers,分别用四种障碍物的构造函数实例化,这样做的好处是方便对其统一操作。
生成地图时只需要遍历barriers数组,分别调用其中的createBarrier(map, mapLength)方法,代码如下:
public void createMap(int mapLength)
{
for(int i = 0; i < 4; i++)
{
map = barriers[i].createBarrier(map, mapLength);
}
}
当然别忘了将地图全部初始化为通路,可将数组全部元素初始化成“o”。
最后定义地图绘制类绘制地图。
获取每种障碍物的预制体,遍历map数组并将预制体实例化。代码如下:
public void drawMap()
{
//实例化地图
for(int i = 0; i < mapLength; i++)
{
for(int j = 0; j < 3; j++)
{
Vector3 position = new Vector3(j - 1, 0, i);
switch (MapCreater.map[j, i])
{
case 'o':
//通路
break;
case '#':
//柱形障碍
Instantiate(barrier_high, position, barrier_high.transform.rotation);
break;
case '_':
//低矮型障碍
Instantiate(barrier_down, position, barrier_down.transform.rotation);
break;
case '^':
//悬挂型障碍
Instantiate(barrier_up, position, barrier_up.transform.rotation);
break;
case '!':
//活动型障碍
Instantiate(barrier_active, position, barrier_active.transform.rotation);
break;
default:
Debug.LogError("地图错误!!!");
break;
}
}
}
}
该方法只负责生成障碍物,别忘了生成地面哦。
上述内容只介绍了每个类的主要方法,还有例如初始化等简单的方法没有过多赘述,诸位可自行探索。
最终效果
仅作为演示用,所以没有添加美术素材,有点抽象请见谅。
障碍物基本均匀分布,密度和频率可由最小间隔数、最大间隔数、概率三个参数决定,实例中每个障碍物的参数均相同,可改变参数使其不同来增加随机性。
小结
本文中的每种障碍物的生成方法完全相同,为增加其多样性,可为每种障碍物“量身定制”这些方法。同时本文的障碍物均占地一格,如果想生成占一个跑道的多格或者多个跑道的障碍物,可在对应障碍物类中createBarrier(char[,] map, int mapLength)方法的基础上修改,希望本文可以给你一些灵感。