Cloaking device
The next problem I want to address is hiding the enemy ships when they're on undiscovered tiles, and obviously showing them as soon as they enter a discovered one.
This can be easily done with the Actor's SetActorHiddenInGame() method, but I want to try and make the effect a little nicer, by playing an animation from totally transparent to totally opaque while the ship is moving.
But first things first, let's use visibility to make it visible as soon as it starts to move from an undiscovered tile to a discovered one:
Since all enemies start from outside the grid, their initial visibility will be set to "hidden" by default:
Remember that the method sets the Actor to Hidden, so passing a true parameter means we want it to be invisible, and passing false will show it.
Then I created a new int variable in enemyClass.h, and I called it changeTransparency; it can take 3 values:
- 0: the ship doesn't need to change its transparency.
- 1: the ship is currently invisible and must become visible.
- -1: the ship is currently visible and must become invisible.
Now, where to set this variable? Looking at my existing code, I decided that the best place to compute this in the Logic() method; it's convenient because it's used only for Enemies, and towards the end it already performs a map->findTileIndexes() call. I will use the boolean variable bHidden to know if the ship is currently visible or not; this variable is inherited from the AActor class, so we don't need to create it. Here's the code I added to the Logic() method:
What I'm saying here is:
1) Is the ship hidden and my target tile discovered? Then activate the transition hidden -> visible.
2) Is the ship visible and my target tile undiscovered? Then activate the transition visible-> hidden.
3) None of the above? Don't do nothing.
Now that the changeTransparency variable is properly set, let's use it. I want the transition to start when the ship starts moving, and not when rotating, so I'm putting the call to the new function here, inside the Tick() method of the enemyClass:
It's clear that if changeTransparency is equal to 0 I won't do nothing, else I call the new method SetEnemyVisibility(). Let's see its body:
It's very simple, we check the value of the checkTransparency global variable, and set the HiddenInGame accordingly. If you run the game now, you'll not see the enemies ships; but as soon as they enter a discovered tile they'll turn visible, just before starting to move.
What we covered here is the case when an enemy ships moves into a tile; but what if, during our turn, we discover a tile where an enemy is already present? We need to take care of that case too.
The simplest way to do it is, inside the enemies' Tick() method, to check whether it's hidden or not; in that case, if it's on a discovered cell, and finally make it visible:
Ok, all the logic is in place. Now, the difficult part: it's not visually appealing to see a ship suddenly appear or disappear; a gradual opacity change would be much better. Unfortuantly it's not as simple as "set Actor opacity to 0.5", we need to do some extra work on materials.
The first aspect to consider is that Opaque materal, that is the standard material we've been using for our ships until now, is not suitable to accept opacity changes. Unreal gives us a material called Transulcent that accepts opacity values, but it's good for glass or semi-transparent objects and would give render problems if used for our meshes. The only solution is to use a Masked material. Masked materials have a property called Opacity mask, in which every pixel must be either 0 (hidden) or 1 (visible). No intermediate values.
So we can't simply pass a float value, but we need to filter it through a DitherTemporalAA box; this will create a dotted pattern moving over time, based on the alpha passed as input.
Now, the second problem. We can have multiple enemies of the same type onscreen, but if we change the opacity mask of the material, all the ships will become visible or invisible at the same time, and we don't want this. What we need to use here are Dynamic Instanced Materials.
A Dynamic Instanced Material is a local copy of a material, based on a Parametrized Material; all the Parameters created in the main material can be changed locally in the instance, without affecting the other instances.
So, let's create our parametrized material first: take the standard enemy1mat material, change its Blend Mode to Masked and search for the box DitherTemporalAA, connecting its output to the Opacity Mask pin of the material.
Next, search for "Scalar Parameter"; name it "Opacity" and connect it to the Alpha pin of the DitherTemporalAA:
Opacity will be the parameter we can change from code to make the ship more or less visible. But before that, we have to assign an Instance of this material to our ship. In the EnemyClass.h file let's add a new UPROPERTY called EnemyMaterial:
It's a pointer to a Dynamic Instance of a material; please note that I marked it as BlueprintReadWrite, because I want to assign it a value from Blueprint.
After compiling, let's move to the MyEnemyClassBP editor.
Here I want to chain something to the BeginPlay event. Search for "Create Dynamic Material Instance (StaticMesh)". This will ask for an index of the static mesh of our Actor (we have only one at the moment, so it's just 0), and create a Dynamic Instance based on the material assigned to the Static Mesh. After creating the instance, let's put its reference inside the EnemyMaterial variable:
Back in C++, what I want is to have the alpha parameter equal to 0.0 at the beginning of the movement, and equal to 1.0 at the end of it. (Reverse the numbers in case of the disappearing ship). The function I'm using for movement, inside the move() method, is FMath::InterpEaseInOut(). It interpolates between the positions A and B, applying an ease at the beginnin and at the end of the movement. It accepts four parameters:
- FVector A: the starting point of the movement
- FVector B: the end point of the movement
- Float Alpha: the current position (0.0 - 1.0) inside the curve
- Float Exp: The degree of the curve
Here's the function diagram:
When alpha is 0.0, the Actor is in position A; when alpha is 1.0 it's in position B; with the ease, the movement looks smoother than a simple linear interpolation, with a slight acceleration / deceleration effect near the 2 ends of the curve. With this function, you don't need to set a speed for your ship; You just have to specify how long it takes for the alpha to go from 0.0 to 1.0.
Using this function gives us a welcome side-effect: we already have the alpha variable to plug into our Opacity parameter! So I will use the global variable where I store the alpha (I called it movingAlpha) inside the SetEnemyVisibility() method:
Here I use MovingAlpha to set the Scalar Parameter "Opacity" when I have to change from hidden to visible, and 1.0 - MovingAlpha (reverse) to go from visible to hidden. I also moved the SetActorHiddenInGame() call at the end of the transition, because I want the ship to still be visible while disappearing.
Here's the result of today's work:
It wasn't a simpe task, but we learned a lot about how materials work, and how to apply different effects to objects sharing the same base material. For any doubt or further explanation, fell free to comment!
And here is a video showing the progress we made on the game until now:
Thanks fro reading and game you next time!
Here's the result of today's work:
It wasn't a simpe task, but we learned a lot about how materials work, and how to apply different effects to objects sharing the same base material. For any doubt or further explanation, fell free to comment!
And here is a video showing the progress we made on the game until now:
Commenti
Posta un commento