In Part 5: Mapstates and Map Setup I looked at the reading of the Console.txt files, and setting up camera switching logic and the post process volume and material.
In this post, I will look at setting up the Skybox and the actors that I'll be using to spawn the map tiles.
I will also briefly cover spawning the first rounds tiles.
To set up the Skybox, the first thing I needed was a properly formatted cube map texture. To generate this texture there are various tools you can use, but since I wanted a starry space scene I used a tool called Spacescape by Alex Peterson.
I won't be going in depth on using Spacescape (maybe a separate tutorial) but the process is pretty straightforward. Open one of the presets, or create your star field. Then select File then Export.
Export the texture with the following settings:
You should, from this point be able to import the DDS cube map straight into Unreal Engine. I had some technical issues importing the cube map, so I had to go through a few extra steps.
First, open the DDS file in Photoshop / GIMP. You should see six layer in the layers panel.
I had to make sure the visibility is turned ON for all six layers, like so:
The next step was to add an Alpha Channel on each layer, like this:
The screenshots above are from GIMP 2.10 for Mac but the steps should be similar for Photoshop. With the visibility and alpha setup for all six layers, I could go ahead and export the file as a Radiance RGBE file (with extension .HDR).
This .HDR file can be directly imported into Unreal Engine.
After importing the .HDR file, I had to adjust some settings. Double clicking the texture opens the asset editor window, and in the right hand details panel, I changed the compression settings to UserInterface(RGBA) as in this image:
This should remove any blurriness resulting from the import process and assigned compression. The second set of settings that needed to be changed is in the Level of Detail section of the settings panel. I changed the Mip Gen Settings to NoMipmaps, and I changed the Texture Group to Skybox, as in the image below:
Having the texture setup correctly, I proceeded to create a standard material, using the default Material Domain settings pictured below:
The complete node-graph for the Skybox material is pretty straightforward, even though it doesn't look like it:
Fairly straight forward. I started by setting a zero Reflection Vector, since we clearly do not want reflections coming of our Skybox. The third node is a TextureSampleParameterCube node, that will take in the Skybox texture that I created by importing the .HDR file in the previous step.
Connecting the UV pin from the TextureSampleParameterCube node to the emissive input of the final node would be enough to setup the Skybox material, however I would like some more control over the overall look and feel of the Skybox.
To that end, I created a Material Parameter Collection, which is a simple built-in data collection object. In this collection I setup a few elements and their default values:
Using this material parameter collection in the material node graph, is as simple as adding a Collection Parameter node, then selecting the collection and element for the node as shown here:
With that setup, back in the material node graph, I used the three parameters in the collection to control the main aspects of the Skybox appearance.
The first parameter, Power is a float value, that is directly multiplied with the Skybox texture that I imported, to boost the colour output of the texture.
This multiplied value is used as the secondary input of a Linear Interpolate node, with first input of zero. The second parameter in the collection, Scene Brightness, is used as the alpha input in the Linear Interpolate node. What the Linear Interpolate (LERP) does, is blend between first and second input, based on the alpha value input.
In this case, it allows me to used the scene brightness value to blend the whole scene between zero (black) and the full texture colour (that is also multiplied by the Power parameter). I can therefore control very finely, the overall 'presence' of the Skybox texture.The last control point is the 'Star Brightness'. The texture's red channel is fed into a 3PointLevels node, which is similar to the Histogram filter in image editing software. Some constants are fed into the other inputs to tweak the levels based on my specific texture, to isolate the stars from the rest of the texture.
Unreal Engine has a nifty feature, where you can right click any node and select Start previewing this node. The material preview window will then show you the effect of the current node on the texture, and this feature let me isolate the stars in the texture as finely as I wanted. The preview looks like this:
With the bright spots isolated to my liking, I proceeded to multiply the result with the final Star Brightness parameter. The result of this parameter multiplication is then combined with the output of the first LERP node, and this combination is used as the second input in another LERP node. The output of the first LERP is used as the input of the second LERP, and the multiplication of the 3Levels result, is used as the input of the alpha channel of the second LERP. This setup allows me to set the Star Brightness parameter to amplify the brightest parts of the Skybox texture.
The output of the second LERP is set as the material emissive colour. Only the emissive colour is used, as this material is setup as Unlit and won't take any lighting information from the scene, but will instead use the Skybox texture to emit its own light. And that's it.
The last step of making the Skybox is to create the Skybox actor. In the content browser, we add a new Blueprint Class and select Actor as the parent type. The blueprint setup looks like this:
In part 1 of the node graph, I've added a Static Mesh component to the DefaultSceneRoot and named it Skybox. For the construction script (part 2), I've created a reference to the Skybox static mesh, and a material type parameter. Additionally, I created three float type parameters. The first node takes the material set to the material parameter and sets the material of the static mesh. The other three input parameters are set to the material parameter collection I created to use in the Skybox material shown earlier.
Part 3 of the graph shows the defaults set for the static mesh (Skybox) and the material. The material is the Skybox material that I created earlier and the static mesh is SM_SkySphere. This is a static mesh that is provided as part of the included engine content.
And done! I now have a Skybox actor that I can drag into the level, and position at (0, 0, 0). The result is this:
To spawn the level, I also created a set of five actors. Three different rocks, to represent the Dirt tiles, a flat(-ish) piece of ground to represent the Air tile, and a simple model to represent the Health pickup.
Al the models were made in Blender 2.8, the rocks being sculpted and the health item just being a simple box model. The process of creating the models won't be covered here, but I will would considering making a separate tutorial if there's any interest.
After importing the FBX models into Unreal Engine, I needed to turn them into Actor blueprints to be able to spawn them in the level. First I created a set of new blueprint classes, three for dirt tiles, one for air tiles and one for the health item. For the dirt blocks, air tiles and health item, I opened the blueprint and added a Static Mesh component to the default scene root. I then and assigned the relevant imported .FBX model to the static mesh component. I also set the mobility of the actor to Stationary. This is to save some computational power, but also to free me from having to bake the lightning (which I can't do as I spawn the actors during runtime.) The settings look like this:
I also built two very simple materials for the dirt and air tiles.
Very simple opaque surface materials, setup as default lit. For the health pickup, I added a red glow to the material.
Again, a very simple material. To get the glow effect, I created a Strength parameter and multiplied it with the glow colour. This is then output to the emissive value of the material.
For the Deep Space tiles I made a translucent material with a time-based texture panner to create a ripple effect. At the moment, the low opacity makes it almost impossible to see if this works, so your mileage may vary..
I won't go into the materials here, but I may do a separate tutorial on material experiments in the future.
The final important setting on the actors, is the custom depth stencil value. This value is used in the post process material to include or exclude objects from the toon shading effect. For all the actors I setup a custom depth value, except for the health item. The health item had to be excluded from the toon shader, since it uses an emissive material that is incompatible with the toon shader. The screen shot shows the custom depth turned on, but I may turn it off later, depending on the effect this has on the lighting of the overall scene.
And that's it for the actors.
For spawning the map at level start, my approach was to run through all the rounds and spawn the relevant tile actor at the correct place, then add it to a collection to keep tracked of the tiles already spawned.
On subsequent rounds, I don't re-spawn the tile actors, but instead manage the transition fo the tile type - for instance, when a player digs a dirt tile, it will transition to an air tile.
My initial approach was to run through one round every second, so I had to set up a timing function for this. This work happens in the level blueprint. To open the level blueprint, open the level in the content browser, then select the Open Level Blueprint option from the Blueprint menu:
The first part, spawning the initial tiles, ended up looking like this:
In the blueprint event graph, there are a few standard events. The first thing I did, is hook into the EventBeginPlay event, which fires when the visualizer starts.
I used a built in function to get an instance of the PlayerController and dragged in a reference to the top-down camera actor (Camera 3). The next node in the sequence sets the view target to the top down camera. I then used the built in function GetAllActorsOfClass to get a reference to the GM_EC2019 game mode blueprint, where I stored the parsed Console.txt results. I then got the MapStatesArray from the game mode blueprint.
Since EventBeginPlay fires at the same time as the UI widgets are created, I check the length of the MapStatesArray to determined if there are any parsed results yet. Once I've determined that the MapStatesArray contains results, I set the results to a variable and initiate a timer to fire my TimedTrigger function once every second.
The TimedTrigger function keeps an integer variable Tick to keep track of the round being processed. Every time the function runs I use the tick to get the map states for the specific round, and iterate over every tile in the map state using a for each node. Once each tile has been processed, the for each node calls a completion handler, at which point I increment the Tick variable.
In the for each loop body, I take the current tile and extract the x and y positions, the unique key and the terrain type value. Using the x and y coordinates, I multiply those by the tile size (200 units) and then use the results to feed the built in MakeVector node to generate the position of each tile. I also check Tile Actor Ref Array to see if the tile key exists (indicating the tile has already spawned). The result of this check is used as input to a Branch node.
The branch node output indicates whether a tile at the relevant position has been spawned yet. If this is not the case, a call is made to the SpawnActorOfClass node, and the position vector of the tile is used as input. By default I spawn a normal tile actor. The TerrainType for the cell is used as input to s Switch node that determines the type of tile. In the event that it is a DeepSpace or an Air tile, I simply set the appropriate material created for the actors on the Tile Actor static mesh component.
In the case of the PowerUp tile (the health item) I use the same location vector and spawn a HealthItemActor. (I am aware that the already spawned tile actor is being discarded, so this is a good area for a refactor.)
In the case of a Dirt tile, I created a RandomStream variable and used the RandomIntegerFromStream (with a maximum value of 2). I then sent this value to a Switch node again, and for each of the three possible outcomes, I spawn the relevant of the three Dirt tile actors. This is simply for some variation across the map and has no impact on the game.
After spawning any of the Dirt actors, I assigned them the relevant material, and then for all the spawned actors, I added their keys to the Tile Actor Ref Array to prevent them spawning again in subsequent sections.
So finally I can show you the initial map spawned from the match logs.
Here it is, from the three different camera angles:
That's it. Log files read, first round map state rendered.
The next step would be to take the tiles for the rest of the rounds, and determine if there are transitions between types, and then updating the map.
After that, I will look at adding the worms and managing players actions in each round.
As always, question or comments here would be appreciate, or feel free to contact me the Contact Page.