A few weeks ago I used the Mapbox Unity SDK to create a Minecraft-inspired world. I got a lot of interest after that initial blog post, so I put together a step-by-step guide of how to create your own Minecraft-inspired world with the Mapbox Unity SDK.
Step 0: Pick your location
Before you get started, look up the latitude and longitude of the real world location you want to style with Minecraft-inspired voxels.
I chose to style Mont Blanc, the highest mountain in the Alps, located at 45.8326° N, 6.8652° E.
Step 1: Create your style
Open up Mapbox Studio to create your game world’s style. The colors you choose here will be used later on to determine which voxels to generate. Make sure you choose colors that are highly contrasting. If you’re new to Mapbox Studio, check out our guide for getting started.
For my Mont Blanc example, I started with an empty style and added layers. Giving distinct colors to grass
, wood
, water
, and more. Similar layers, such as glacier
and snow
, share the same style color (cyan, in my case). Don’t forget to set filters for sources such as Vector Terrain landcover
.
Step 2: Fetch your style data in Unity
Publish your style within Mapbox Studio and pass the style URL to the MapId
property of the RasterTile
.
if(!string.IsNullOrEmpty(_styleUrl)){_raster.MapId=_styleUrl;}
Then fetch your style as raster data.
voidStart(){_raster=newMap<RasterTile>(_fileSource);_raster.Subscribe(this);// Mont Blanc Latitude/Longitude. You can use our Geocoding API to obtain this.RequestWorldData(newGeoCoordinate(45.967,6.902));}voidRequestWorldData(GeoCoordinatecoordinates){varbounds=newGeoCoordinateBounds();bounds.Center=coordinates;_raster.SetGeoCoordinateBoundsZoom(bounds,_zoom);_elevation.SetGeoCoordinateBoundsZoom(bounds,_zoom);}publicvoidOnNext(RasterTiletile){if(tile.CurrentState==Tile.State.Loaded&&tile.Error==null){_rasterTexture=newTexture2D(2,2);_rasterTexture.LoadImage(tile.Data);}}
Step 3: Downsample the bitmap
Each pixel of the bitmap will generate one voxel, so downsample in order to generate fewer voxels for the tile that we’ve requested. Using real-world scale would create a dull voxel environment, so for more exaggeration, I recommend shrinking the texture by half.
TextureScale.Point(_rasterTexture,_tileWidthInVoxels,_tileWidthInVoxels);
Step 4: Map pixel colors to voxel types
The next step is to map each pixel’s color to a specific voxel type or unique prefab.
For Mont Blanc, I used various voxel types for each landuse defined in my style. You can see how I’ve mapped bright green to a Grass
voxel.
To map the voxels, I used a simple “nearest color” formula, which is very similar to the Pythagorean Theorem.
namespaceMapbox.Examples.Voxels{publicclassVoxelFetcher:MonoBehaviour{[SerializeField]VoxelColorMapper[]_voxels;publicGameObjectGetVoxelFromColor(Colorcolor){GameObjectmatchingVoxel=_voxels[0].Voxel;varminDistance=Mathf.Infinity;foreach(varvoxelin_voxels){vardistance=GetDistanceBetweenColors(voxel.Color,color);if(distance<minDistance){matchingVoxel=voxel.Voxel;minDistance=distance;}}returnmatchingVoxel;}publicstaticfloatGetDistanceBetweenColors(Colorcolor1,Colorcolor2){returnMathf.Sqrt(Mathf.Pow(color1.r-color2.r,2f)+Mathf.Pow(color1.g-color2.g,2f)+Mathf.Pow(color1.b-color2.b,2f));}}[Serializable]publicclassVoxelColorMapper{publicColorColor;publicGameObjectVoxel;}}
Step 5: Place voxels based on pixel location
Now you’ll need to place your voxels in Unity space based on the location of each pixel in the texture.
for(intx=0;x<_rasterTexture.width;x++){for(intz=0;z<_rasterTexture.height;z++){_voxels.Add(newVoxelData(){Position=newVector3(x,0,z),Prefab=_voxelFetcher.GetVoxelFromColor(color)});}}
Step 6: Add in global terrain data
To determine where to vertically position each voxel, you can use our global terrain layer, Terrain-RGB.
Use this code to extract absolute height from the color of a pixel.
publicstaticfloatGetAbsoluteHeightFromColor(Colorcolor){return(float)(-10000+((color.r*256*256*256+color.g*256*256+color.b*256)*0.1));}
You’ll also need to adjust the height based on meters per pixel for the specified zoom level.
voidAwake(){_tileScale=_tileWidthInVoxels/GetTileScaleInMeters((float)coordinates.Latitude,_zoom);}publicstaticfloatGetTileScaleInMeters(floatlatitude,intzoom){return40075000*Mathf.Cos(Mathf.Deg2Rad*latitude)/(Mathf.Pow(2f,zoom+8))*256;}voidExtrude(){varheight=(int)GetAbsoluteHeightFromColor(_elevationTexture.GetPixel(x,z));height=(int)(height*_tileScale);}
Step 7: Experiment
Play with zoom level, texture downsampling, and height multipliers to stylize your environment.
There’s a ton you can do with styling and the Mapbox Unity SDK. You could use landuse data in our vector tiles to inform organic biome generation, rather than mapping voxels to raster data. Waterways could be used to inform physics-based flow systems rather than spawning voxels. Additionally, you could use our building footprint data to create voxel buildings and villages. You could add gameplay elements by allowing players to create and destroy features.
We can’t wait to see what you come up with. Sign up to receive the Mapbox Unity SDK Beta and download my Mont Blanc voxel example for the full code.