游戏中随机地图的实现
很多游戏都用到了随机地图,比如矮人要塞,CDDA,MineCraft,RimWorld。
随机地图带给游戏更多的趣味性,每一次新建游戏都有不同的体验。
一般游戏中生成随机地形都是使用柏林噪声,接下来我们就来看看怎么实现。
白噪声
首先我们先来使用一个白噪声函数试试生成一张完全随机的图片。
public float[][] GenerateWhiteNoise(int width,int height,int seed)
{
Random r = new Random(seed);
float[][]noise=new float[width][];
for (int i = 0; i < width; i++)
{
noise[i] = new float[height];
}
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
noise[i][j] = (float)r.NextDouble() % 1;
}
}
return noise;
}
private void mapPanel_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Bitmap bm = new Bitmap(500, 500);
float[][] noises = GenerateWhiteNoise(500, 500,6527);
for (int i = 0; i < 500; i++)
{
for (int j = 0; j < 500; j++)
{
int co = (int)(noises[i][j] * 256);
bm.SetPixel(i, j, Color.FromArgb(co, co, co));
}
}
Pen p = new Pen(Color.Black, 1);
g.DrawImage(bm,new Point(0,0));
}
完全随机的话,貌似并不能到到我们要的效果,接下来,我们就试试加一些平滑过渡。
柏林噪声
柏林噪声应用在很多游戏的地形生成上,也应用在图片的处理上。
float Interpolate(float x0, float x1, float alpha)
{
return x0 * (1 - alpha) + alpha * x1;
}
public float[][] GenerateSmoothNoise(float[][] baseNoise, int octave)
{
int width = baseNoise.Length;
int height = baseNoise[0].Length;
float[][] smoothNoise = new float[width][];
for (int i = 0; i < width; i++)
{
smoothNoise[i] = new float[height];
}
int samplePeriod = 1 << octave;
float sampleFrequency = 1.0f / samplePeriod;
for (int i = 0; i < width; i++)
{
//calculate the horizontal sampling indices
int sample_i0 = (i / samplePeriod) * samplePeriod;
int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
float horizontal_blend = (i - sample_i0) * sampleFrequency;
for (int j = 0; j < height; j++)
{
//calculate the vertical sampling indices
int sample_j0 = (j / samplePeriod) * samplePeriod;
int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
float vertical_blend = (j - sample_j0) * sampleFrequency;
//blend the top two corners
float top = Interpolate(baseNoise[sample_i0][sample_j0],
baseNoise[sample_i1][sample_j0], horizontal_blend);
//blend the bottom two corners
float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
baseNoise[sample_i1][sample_j1], horizontal_blend);
//final blend
smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
}
}
return smoothNoise;
}
public float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount)
{
int width = baseNoise.Length;
int height = baseNoise[0].Length;
float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing
float persistance = 0.5f;
//generate smooth noise
for (int i = 0; i <octaveCount; i++)
{
smoothNoise[i] = GenerateSmoothNoise(baseNoise, i);
}
float[][] perlinNoise = new float[width][];
for (int i = 0; i < width; i++)
{
perlinNoise[i] = new float[height];
}
float amplitude = 1.0f;
float totalAmplitude = 0.0f;
//blend noise together
for (int octave = octaveCount - 1; octave >= 0; octave--)
{
amplitude *= persistance;
totalAmplitude += amplitude;
for (int i = 0; i <width; i++)
{
for (int j = 0; j < height; j++)
{
perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
}
}
}
//normalisation
for (int i = 0; i <width; i++)
{
for (int j = 0; j <height; j++)
{
perlinNoise[i][j] /= totalAmplitude;
}
}
return perlinNoise;
}
int o=10;
private void mapPanel_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Bitmap bm = new Bitmap(500, 500);
float[][] noises = GenerateWhiteNoise(500, 500,6527);
float[][] smooth = GenerateSmoothNoise(noises, 5);
float[][] perlins = GeneratePerlinNoise(noises, o);
for (int i = 0; i < 500; i++)
{
for (int j = 0; j < 500; j++)
{
int co = (int)(perlins[i][j] * 256);
bm.SetPixel(i, j, Color.FromArgb(co, co, co));
}
}
Pen p = new Pen(Color.Black, 1);
g.DrawImage(bm,new Point(0,0));
}
可以看到 octaveCount为10的时候就有很好的烟雾效果,
不过我应用在生成随机地图的时候,取值为6可以获得最好的效果。
柏林噪声的应用
柏林噪声可以应用在图片中,获得类似烟雾,爆炸,
气体的效果,可以将两张图片用一定的比例合并在一张。
Color GetColor(Color gradientStart, Color gradientEnd, float t)
{
float u = 1 - t;
Color color = Color.FromArgb(
255,
(int)(gradientStart.R * u + gradientEnd.R * t),
(int)(gradientStart.G * u + gradientEnd.G * t),
(int)(gradientStart.B * u + gradientEnd.B * t));
return color;
}
Color[][] MapGradient(Color gradientStart, Color gradientEnd, float[][] perlinNoise)
{
int width = perlinNoise.Length;
int height = perlinNoise[0].Length;
Color[][] image = new Color[width][];
for (int i = 0; i < width; i++)
{
image[i] = new Color[height];
}
for (int i = 0; i <width; i++)
{
for (int j = 0; j <height; j++)
{
image[i][j] = GetColor(gradientStart, gradientEnd, perlinNoise[i][j]);
}
}
return image;
}
int o = 5;
private void mapPanel_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Bitmap bm = new Bitmap(500, 500);
float[][] noises = GenerateWhiteNoise(500, 500,6527);
float[][] smooth = GenerateSmoothNoise(noises, 5);
float[][] perlins = GeneratePerlinNoise(noises, o);
Color[][] map = MapGradient( Color.Red,ColorTranslator.FromHtml("#7FFFD4"), perlins);
for (int i = 0; i < 500; i++)
{
for (int j = 0; j < 500; j++)
{
int co = (int)(perlins[i][j] * 256);
//bm.SetPixel(i, j, Color.FromArgb(co, co, co));
bm.SetPixel(i, j, map[i][j]);
}
}
Pen p = new Pen(Color.Black, 1);
g.DrawImage(bm,new Point(0,0));
}
随机地图的生成
查阅了一些资料,不同的游戏有不同的生成方式。
有的是使用维诺图加上柏林函数来生成世界,有的是生成高度图,再生成降水,再经过腐蚀而生成一个接近真实世界的地貌。
这里的实例是一个最简单的实现方法,使用方块格,根据柏林噪声获取该格的高度(0.1~1),再根据其高度来设定其地形。
public Color GetColorByFloat(float noise)
{
noise = noise * 255;
if (noise > 240) return Color.White;
if (noise > 210) return ColorTranslator.FromHtml("#AAAAAA");
if (noise > 180) return ColorTranslator.FromHtml("#C5C1AA");
if (noise > 165) return ColorTranslator.FromHtml("#3CB371");
if (noise > 130) return ColorTranslator.FromHtml("#228B22");
if (noise > 70) return ColorTranslator.FromHtml("#71C671");
return Color.SkyBlue;
}
private void button2_Click(object sender, EventArgs e)
{
Random r = new Random();
float[][] whiteNoise = GenerateWhiteNoise(100, 100, r.Next());
float[][] perlinNoise = GeneratePerlinNoise(whiteNoise, 5);
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
SolidBrush sb = new SolidBrush(GetColorByFloat(perlinNoise[i][j]));
pbG.FillRectangle(sb, i * width, j * height, width , height );
}
}
pictureBox1.Invalidate();
}
生成的效果勉强可以,但是如果添加更多的地形,加入更多的处理,相信可以做出更加好看,更加接近真实的地图。如果你有好的建议,请留言告诉我。