Example 5: Using Perlin simplex-noise to generate an infinite tiled map.

vb.net Code:
  1. Public Class Game1
  2.     Inherits Microsoft.Xna.Framework.Game
  3.  
  4.     Private WithEvents graphics As GraphicsDeviceManager
  5.     Private WithEvents spriteBatch As SpriteBatch
  6.  
  7.     Private mymap As TiledMap
  8.  
  9.     Public Sub New()
  10.         graphics = New GraphicsDeviceManager(Me)
  11.         Content.RootDirectory = "Content"
  12.     End Sub
  13.  
  14.     Protected Overrides Sub Initialize()
  15.         mymap = New TiledMap(Me, 0.04F)
  16.         Components.Add(mymap)
  17.         IsMouseVisible = True
  18.         Window.AllowUserResizing = True
  19.         Window.Title = "Example 5 (A tiled map using Perlin simplex-noise)" 'Modified title.
  20.         MyBase.Initialize()
  21.     End Sub
  22.  
  23.     Protected Overrides Sub LoadContent()
  24.         spriteBatch = New SpriteBatch(GraphicsDevice)
  25.         MyBase.LoadContent()
  26.     End Sub
  27.  
  28.     Protected Overrides Sub UnloadContent()
  29.         MyBase.UnloadContent()
  30.     End Sub
  31.  
  32.     Protected Overrides Sub Update(ByVal gameTime As GameTime)
  33.         If Keyboard.GetState.IsKeyDown(Keys.Escape) Then Me.Exit()
  34.  
  35.         MyBase.Update(gameTime)
  36.     End Sub
  37.  
  38.     Protected Overrides Sub Draw(ByVal gameTime As GameTime)
  39.         GraphicsDevice.Clear(Color.Black)
  40.  
  41.         MyBase.Draw(gameTime)
  42.     End Sub
  43.  
  44. End Class

A new class called TiledMap (create this class in the main project and copy/paste the code):
vb.net Code:
  1. Public Class TiledMap
  2.  
  3.     Inherits DrawableGameComponent
  4.  
  5.     Private sprites As SpriteBatch
  6.     Private tile_texture As Texture2D
  7.     Private mappos_x, mappos_y, pixel_x, pixel_y As Integer
  8.     Private _rect As Rectangle = New Rectangle(0, 0, 64, 64)
  9.     Private _scale As Single
  10.     Private _oldmstate As MouseState
  11.  
  12.     Public Sub New(myproject As Game, scale As Single)
  13.         MyBase.New(myproject)
  14.         _scale = scale
  15.     End Sub
  16.  
  17.     Public Overrides Sub Initialize()
  18.         MyBase.Initialize()
  19.     End Sub
  20.  
  21.     Protected Overrides Sub LoadContent()
  22.         MyBase.LoadContent()
  23.         sprites = New SpriteBatch(GraphicsDevice)
  24.         tile_texture = Game.Content.Load(Of Texture2D)("SmallTextures")
  25.     End Sub
  26.  
  27.     Protected Overrides Sub UnloadContent()
  28.         MyBase.UnloadContent()
  29.     End Sub
  30.  
  31.     Public Overrides Sub Update(ByVal gameTime As GameTime)
  32.         Dim mstate As MouseState = Mouse.GetState
  33.  
  34.         If mstate.LeftButton = ButtonState.Pressed AndAlso Game.IsActive Then
  35.             If _oldmstate.LeftButton = ButtonState.Pressed Then
  36.  
  37.                 Dim deltax As Integer = _oldmstate.X - mstate.X
  38.                 Dim deltay As Integer = _oldmstate.Y - mstate.Y
  39.  
  40.                 mappos_x += deltax \ 64
  41.                 mappos_y += deltay \ 64
  42.                 pixel_x -= deltax Mod 64
  43.                 pixel_y -= deltay Mod 64
  44.  
  45.                 While pixel_x < 0
  46.                     pixel_x += 64
  47.                     mappos_x += 1
  48.                 End While
  49.  
  50.                 While pixel_x >= 64
  51.                     pixel_x -= 64
  52.                     mappos_x -= 1
  53.                 End While
  54.  
  55.                 While pixel_y < 0
  56.                     pixel_y += 64
  57.                     mappos_y += 1
  58.                 End While
  59.  
  60.                 While pixel_y >= 64
  61.                     pixel_y -= 64
  62.                     mappos_y -= 1
  63.                 End While
  64.  
  65.             End If
  66.         End If
  67.         _oldmstate = mstate
  68.  
  69.         MyBase.Update(gameTime)
  70.     End Sub
  71.  
  72.     Public Overrides Sub Draw(ByVal gameTime As GameTime)
  73.         Dim w As Integer = GraphicsDevice.Viewport.Width
  74.         Dim h As Integer = GraphicsDevice.Viewport.Height
  75.         Dim wtile As Integer = w \ 64 + 1
  76.         Dim htile As Integer = h \ 64 + 1
  77.         Dim loc, mappos As Vector2
  78.         Dim perlin As Single
  79.  
  80.         sprites.Begin()
  81.         For i As Integer = 0 To wtile
  82.             loc.X = pixel_x + 64.0F * (i - 1)
  83.             mappos.X = (mappos_x + i) * _scale
  84.             For j As Integer = 0 To htile
  85.                 loc.Y = pixel_y + 64.0F * (j - 1)
  86.                 mappos.Y = (mappos_y + j) * _scale
  87.  
  88.                 perlin = PerlinNoise.PerlinSimplex2D(mappos)
  89.  
  90.                 If perlin < 0.0F Then 'water
  91.                     _rect.X = 0
  92.                 ElseIf perlin < 0.6F Then 'grass
  93.                     _rect.X = 64
  94.                 ElseIf perlin < 0.8F Then 'forest
  95.                     _rect.X = 128
  96.                 ElseIf perlin < 0.9F Then 'desert
  97.                     _rect.X = 192
  98.                 Else 'mountain
  99.                     _rect.X = 256
  100.                 End If
  101.  
  102.                 sprites.Draw(tile_texture, loc, _rect, Color.White)
  103.             Next
  104.         Next
  105.         sprites.End()
  106.  
  107.         MyBase.Draw(gameTime)
  108.     End Sub
  109.  
  110. End Class

A new class called PerlinNoise (create this class in the main project and copy/paste the code):
vb.net Code:
  1. Public Class PerlinNoise
  2.  
  3.     'Created on the basis on this excellent article explaining simplex noise:
  4.     'Stefan Gustavson, Linköping University, Sweden ([email protected]), 2005-03-22
  5.  
  6.     Private Shared gradients() As Vector2 = _
  7.             {New Vector2(1.0F, 1.0F), New Vector2(-1.0F, 1.0F), New Vector2(1.0F, -1.0F), New Vector2(-1.0F, -1.0F), _
  8.              New Vector2(1.0F, 0.0F), New Vector2(-1.0F, 0.0F), New Vector2(1.0F, 0.0F), New Vector2(-1.0F, 0.0F), _
  9.              New Vector2(0.0F, 1.0F), New Vector2(0.0F, 1.0F), New Vector2(0.0F, -1.0F), New Vector2(0.0F, -1.0F)}
  10.  
  11.     Private Shared bytehash() As Byte = _
  12.         {&H97, &HA0, &H89, &H5B, &H5A, &HF, &H83, &HD, &HC9, &H5F, &H60, &H35, &HC2, &HE9, &H7, &HE1, _
  13.          &H8C, &H24, &H67, &H1E, &H45, &H8E, &H8, &H63, &H25, &HF0, &H15, &HA, &H17, &HBE, &H6, &H94, _
  14.          &HF7, &H78, &HEA, &H4B, &H0, &H1A, &HC5, &H3E, &H5E, &HFC, &HDB, &HCB, &H75, &H23, &HB, &H20, _
  15.          &H39, &HB1, &H21, &H58, &HED, &H95, &H38, &H57, &HAE, &H14, &H7D, &H88, &HAB, &HA8, &H44, &HAF, _
  16.          &H4A, &HA5, &H47, &H86, &H8B, &H30, &H1B, &HA6, &H4D, &H92, &H9E, &HE7, &H53, &H6F, &HE5, &H7A, _
  17.          &H3C, &HD3, &H85, &HE6, &HDC, &H69, &H5C, &H29, &H37, &H2E, &HF5, &H28, &HF4, &H66, &H8F, &H36, _
  18.          &H41, &H19, &H3F, &HA1, &H1, &HD8, &H50, &H49, &HD1, &H4C, &H84, &HBB, &HD0, &H59, &H12, &HA9, _
  19.          &HC8, &HC4, &H87, &H82, &H74, &HBC, &H9F, &H56, &HA4, &H64, &H6D, &HC6, &HAD, &HBA, &H3, &H40, _
  20.          &H34, &HD9, &HE2, &HFA, &H7C, &H7B, &H5, &HCA, &H26, &H93, &H76, &H7E, &HFF, &H52, &H55, &HD4, _
  21.          &HCF, &HCE, &H3B, &HE3, &H2F, &H10, &H3A, &H11, &HB6, &HBD, &H1C, &H2A, &HDF, &HB7, &HAA, &HD5, _
  22.          &H77, &HF8, &H98, &H2, &H2C, &H9A, &HA3, &H46, &HDD, &H99, &H65, &H9B, &HA7, &H2B, &HAC, &H9, _
  23.          &H81, &H16, &H27, &HFD, &H13, &H62, &H6C, &H6E, &H4F, &H71, &HE0, &HE8, &HB2, &HB9, &H70, &H68, _
  24.          &HDA, &HF6, &H61, &HE4, &HFB, &H22, &HF2, &HC1, &HEE, &HD2, &H90, &HC, &HBF, &HB3, &HA2, &HF1, _
  25.          &H51, &H33, &H91, &HEB, &HF9, &HE, &HEF, &H6B, &H31, &HC0, &HD6, &H1F, &HB5, &HC7, &H6A, &H9D, _
  26.          &HB8, &H54, &HCC, &HB0, &H73, &H79, &H32, &H2D, &H7F, &H4, &H96, &HFE, &H8A, &HEC, &HCD, &H5D, _
  27.          &HDE, &H72, &H43, &H1D, &H18, &H48, &HF3, &H8D, &H80, &HC3, &H4E, &H42, &HD7, &H3D, &H9C, &HB4, _
  28.          &H97, &HA0, &H89, &H5B, &H5A, &HF, &H83, &HD, &HC9, &H5F, &H60, &H35, &HC2, &HE9, &H7, &HE1, _
  29.          &H8C, &H24, &H67, &H1E, &H45, &H8E, &H8, &H63, &H25, &HF0, &H15, &HA, &H17, &HBE, &H6, &H94, _
  30.          &HF7, &H78, &HEA, &H4B, &H0, &H1A, &HC5, &H3E, &H5E, &HFC, &HDB, &HCB, &H75, &H23, &HB, &H20, _
  31.          &H39, &HB1, &H21, &H58, &HED, &H95, &H38, &H57, &HAE, &H14, &H7D, &H88, &HAB, &HA8, &H44, &HAF, _
  32.          &H4A, &HA5, &H47, &H86, &H8B, &H30, &H1B, &HA6, &H4D, &H92, &H9E, &HE7, &H53, &H6F, &HE5, &H7A, _
  33.          &H3C, &HD3, &H85, &HE6, &HDC, &H69, &H5C, &H29, &H37, &H2E, &HF5, &H28, &HF4, &H66, &H8F, &H36, _
  34.          &H41, &H19, &H3F, &HA1, &H1, &HD8, &H50, &H49, &HD1, &H4C, &H84, &HBB, &HD0, &H59, &H12, &HA9, _
  35.          &HC8, &HC4, &H87, &H82, &H74, &HBC, &H9F, &H56, &HA4, &H64, &H6D, &HC6, &HAD, &HBA, &H3, &H40, _
  36.          &H34, &HD9, &HE2, &HFA, &H7C, &H7B, &H5, &HCA, &H26, &H93, &H76, &H7E, &HFF, &H52, &H55, &HD4, _
  37.          &HCF, &HCE, &H3B, &HE3, &H2F, &H10, &H3A, &H11, &HB6, &HBD, &H1C, &H2A, &HDF, &HB7, &HAA, &HD5, _
  38.          &H77, &HF8, &H98, &H2, &H2C, &H9A, &HA3, &H46, &HDD, &H99, &H65, &H9B, &HA7, &H2B, &HAC, &H9, _
  39.          &H81, &H16, &H27, &HFD, &H13, &H62, &H6C, &H6E, &H4F, &H71, &HE0, &HE8, &HB2, &HB9, &H70, &H68, _
  40.          &HDA, &HF6, &H61, &HE4, &HFB, &H22, &HF2, &HC1, &HEE, &HD2, &H90, &HC, &HBF, &HB3, &HA2, &HF1, _
  41.          &H51, &H33, &H91, &HEB, &HF9, &HE, &HEF, &H6B, &H31, &HC0, &HD6, &H1F, &HB5, &HC7, &H6A, &H9D, _
  42.          &HB8, &H54, &HCC, &HB0, &H73, &H79, &H32, &H2D, &H7F, &H4, &H96, &HFE, &H8A, &HEC, &HCD, &H5D, _
  43.          &HDE, &H72, &H43, &H1D, &H18, &H48, &HF3, &H8D, &H80, &HC3, &H4E, &H42, &HD7, &H3D, &H9C, &HB4}
  44.  
  45.     Public Shared Function PerlinSimplex2D(loc As Vector2) As Single
  46.  
  47.         Static F As Single = Convert.ToSingle(Math.Sqrt(0.75)) - 0.5F
  48.         Static G As Single = 0.5F - Convert.ToSingle(Math.Sqrt(1.0 / 12.0))
  49.  
  50.         Dim s As Single = (loc.X + loc.Y) * F
  51.         Dim i As Integer = fastfloor(loc.X + s)
  52.         Dim j As Integer = fastfloor(loc.Y + s)
  53.         Dim t As Single = (i + j) * G
  54.         Dim v0 As New Vector2(loc.X - i + t, loc.Y - j + t)
  55.         Dim v1 As New Vector2(v0.X + G, v0.Y + G)
  56.         Dim v2 As New Vector2(v1.X + G - 1.0F, v1.Y + G - 1.0F)
  57.         Dim ii As Integer = i And &HFF
  58.         Dim jj As Integer = j And &HFF
  59.         Dim gi0 As Integer = bytehash(ii + bytehash(jj)) Mod 12
  60.         Dim gi1 As Integer
  61.         Dim gi2 As Integer = bytehash(ii + 1 + bytehash(jj + 1)) Mod 12
  62.  
  63.         If v0.X > v0.Y Then
  64.             v1.X -= 1.0F
  65.             gi1 = bytehash(ii + 1 + bytehash(jj)) Mod 12
  66.         Else
  67.             v1.Y -= 1.0F
  68.             gi1 = bytehash(ii + bytehash(jj + 1)) Mod 12
  69.         End If
  70.  
  71.         Dim t0 As Single = 0.5F - v0.LengthSquared
  72.         Dim t1 As Single = 0.5F - v1.LengthSquared
  73.         Dim t2 As Single = 0.5F - v2.LengthSquared
  74.         Dim d0, d1, d2 As Single
  75.  
  76.         If t0 < 0.0F Then
  77.             d0 = 0.0F
  78.         Else
  79.             t0 *= t0
  80.             d0 = t0 * t0 * Vector2.Dot(gradients(gi0), v0)
  81.         End If
  82.  
  83.         If t1 < 0.0F Then
  84.             d1 = 0.0F
  85.         Else
  86.             t1 *= t1
  87.             d1 = t1 * t1 * Vector2.Dot(gradients(gi1), v1)
  88.         End If
  89.  
  90.         If t2 < 0.0F Then
  91.             d2 = 0.0F
  92.         Else
  93.             t2 *= t2
  94.             d2 = t2 * t2 * Vector2.Dot(gradients(gi2), v2)
  95.         End If
  96.  
  97.         Return 70.0F * (d0 + d1 + d2)
  98.  
  99.     End Function
  100.  
  101.     Public Shared Function Generate(dimension As Integer, scale As Single, offset As Vector2, c1 As Color, c2 As Color) As Color()
  102.  
  103.         Dim v As New Vector2
  104.         Dim k As Integer = 0
  105.         Dim rval(dimension * dimension - 1) As Color
  106.  
  107.         For i As Integer = 0 To dimension - 1
  108.             v.X = i * scale + offset.X
  109.             v.Y = offset.Y
  110.             For j As Integer = 0 To dimension - 1
  111.                 rval(k) = Color.Lerp(c1, c2, (PerlinSimplex2D(v) + 1.0F) / 2.0F)
  112.                 v.Y += scale
  113.                 k += 1
  114.             Next
  115.         Next
  116.  
  117.         Return rval
  118.  
  119.     End Function
  120.  
  121.     Private Shared Function fastfloor(val As Single) As Integer
  122.  
  123.         Return Convert.ToInt32(If(val > 0, Int(val), Int(val) - 1))
  124.  
  125.     End Function
  126.  
  127. End Class

I will not explain how Perlin noise is implemented or how it works. There are plenty of pages on that already - for interested readers, the article referred at the top of the PerlinNoise class is an excellent place to start. For others simply accept that for any given 2d-vector Perlin noise will give a floating point number, so that the function is continuous everywhere yet produces semi-random values. And Perlin spent a long time working on his version(s) trying to get it as fast as possible; using the additional shared generate method to construct entire textures of Perlin-noise is indeed doable. Even with multiple calls per pixel (as is costumary to generate the most spectacular of effects). In our little example however, we will just call the plain simplex function since this example is not about generating beautiful and accurate landscapes but rather illustrating a method. To run the example, you will need to supply a tiled texture of size 320 x 64 pixels consisting of 5 tiles of water, grass, forest, desert and mountains respectively. The one I used is linked here Name:  SmallTextures.png
Views: 1145
Size:  1.5 KB, which is merely colored squares representing the areas (I'm not much of an artist).
Aside from the fact that Perlin-noise is used, nothing much is new in this example. The tiled texture is loaded and 64 by 64 bits of it is displayed at every location on the screen based on the Perlin-simplex of the location (or rather a scaled down version of the location). The update method in the TiledMap class illustrates how an 'old' state of the mouse is saved to determine if the left button has just been pressed. Then the location of the mousepointer is used to generate a small offset of pixels and an offset into the map. Feel free to adjust the levels in TiledMap.Draw to accomodate more forest or whatever - Perlin-noise gives values in the range -1 to 1, and it should be easy to allow for more area-types or various other features.