A (Game) State of Affairs

Until recently, the ‘game’ wasn’t much of a game. There really wasn’t a beginning to the level while playing through. Once the level loaded, Suzy’s AI would immediately begin to run and off she would go. But, it didn’t matter how many of the fruit objects she picked up: one, three, ten…all. It didn’t make any difference. There was no true ending to the level. That is where the GameMode comes in.

The name “GameMode” is a bit misleading, as you may not expect the game mode to handle the state of the game. But according to the documentation here, the game mode keeps track of the current levels progress according to the rules of that particular game. In the case of Capuchin Capers, the rules are very simple. When the level starts, you are in a pre-game state, and are able to run around the level to get a feel for it’s size and shape. While in the pre-game state, you can’t see any of the fruit objects or the game UI (which could reveal the number of fruit objects if visible). The game proper doesn’t start until you enter the grass hut located near your beginning position. Once you enter the hut, the game state changes to the in-game state.

The in-game state is where you are competing with Suzy to find the most fruit objects. The UI is visible and Suzy’s AI, as well as the Director’s AI, is enabled and she will immediately begin searching for fruit objects. The game mode will remain in this state until one of two things happens. If Suzy finds half of the fruit objects (rounded down) she wins the competition. Or, if the player finds more than half of the fruit objects, they win. As you can see, Suzy wins all ties according to these simple rules. Once one of these two things occurs, the game will transition to the post-game state.

In the post-game state, the end of the level is executed, starting by taking the player’s camera and translating it high above the player’s head. From this bird’s-eye-view, the camera’s position will move to a location high above the hut, looking down towards it. From that known location, we can start the actual cinematic to end the level. These steps to this point are necessary, because we can’t know where the player is on the island when they or Suzy wins the match. We need a way to get the camera to a position that we can always guarantee, regardless of where the player was on the island at the end. Once the camera is in this “safe” location, we trigger the level sequence. The level sequence’s camera starts at the exact same location as the “safe” location, so the cinematics’ start is seamless. From there, we play the cinematic for the player as a reward for playing through the level.

In all of this, there are a lot of moving parts, even in a game as simple as Capuchin Capers. So, how is all of this done in the game mode? A state machine. The implementation of the state machine is actually fairly simple compared to it’s impact. Warning: C++ incoming. There is a base class named CCStateBase with the following declaration:

class CAPUCHINCAPERS_API CCStateBase
{
public:
	CCStateBase();
	virtual ~CCStateBase();

	/** Called when the state is first entered. */
	virtual void Enter();

	/** Called each tick for this state. */
	virtual void Update(float Delta);

	/** Called before the state is exited. */
	virtual void Exit();

	void SetParent(ACapuchinCapersGameMode* ParentParam);
	ACapuchinCapersGameMode* GetParent();

protected:
	ACapuchinCapersGameMode* Parent;
};

Each of the states mentioned above (pre-game, in-game, and post-game) all derive from this class. The derived classes define what each of their methods should actually do. Obviously, the Enter() method for the in-game state would be completely different than that of the post-game state. This is really nice to work with, because it compartmentalizes the functionality of each state and makes it easier to visualize how everything flows. From the above code, it is easy to see that the game mode is the owner of the state machine and is set as “Parent” in each of the state machines objects.

In the game mode each of the three game states are created as objects, with a pointer to the base class CCStateBase being declared as well. That pointer, which is named “CurrentState”, is where all the magic happens. By using polymorphism we can smoothly change between states, with all the functionality that we have built into our state machine, and the game mode object never needs to be aware of the current state of the state machine. This is done, primarily, by the SetNextState() method:

void ACapuchinCapersGameMode::SetNextState(ECCGameState NextState)
{
	// If this is the first call to this method,
	// CurrentState will be nullptr. Otherwise,
	// we need to exit the current state before
	// entering the next.
	if (CurrentState)
	{
		CurrentState->Exit();
	}

	switch (NextState)
	{
	case ECCGameState::IngameState:
		CurrentState = &IngameState;
		break;

	case ECCGameState::PostgameState:
		CurrentState = &PostgameState;
		break;

	default:
	case ECCGameState::PregameState:
		CurrentState = &PregameState;
		break;
	}

	if (CurrentState)
	{
		CurrentState->Enter();

		// Broadcast the state change to any
		// listeners of this delegate.
		OnGameStateChanged.Broadcast(NextState);
	}
	else
	{
		UE_LOG(LogTemp,
			Warning,
			TEXT("CurrentState is NULL."));
	}
}

For a mechanism that controls the entirety of the game’s rules, this state machine is incredibly simple. There is quite a bit that isn’t being shown here, of course. This article isn’t meant to be a tutorial on how to implement a state machine. For that, you would definitely be better served to find a good book or tutorial aimed specifically at that. I can recommend “Game Development Patterns and Best Practices” by John P. Doran and Matt Casanova, as that is where I learned this design pattern.

In closing I have to say that the structure of the Unreal Engine is complex, and what every part is supposed to be doing isn’t always clear when you are just starting out. I never would have thought for a moment that the state machine that controls the game would go in the game mode, while all of the data such as player scores, would go in the AGameStateBase derived object. This can all be a bit confusing when first learning it, but once you understand what everything is doing it gets more clear and easier to grasp. Happy developing!

The Path Most Followed

Well, I have just spent the better part of the last week working on a custom EQS generator that creates a cone-shaped graph of points and then uses the A* algorithm to plot a path through the graph. Having virtually no experience in this field of programming I had no right to hope that I could produce anything of any value, but thanks to Red Blob Games excellent articles on this I was able to implement this in Unreal.

To say that this was challenging (for me, at least) is an understatement of massive proportions. While I am an experienced C++ programmer, this field of programming is very demanding. I am very happy with the results that I was able to achieve from studying the code examples and explanations on Red Blob Games site. If you have any interest in how pathfinding works in games or other applications, you owe it to yourself to read every word on their site.

My implementation uses a cone as the shape of the graph to generate the initial points for the graph. Next comes the line traces to find all of the blocking volumes that may have points generating within them. These need to be marked as ‘wall’ points for the A* algorithm to plot a path around them. There is also terrain costing that can be implemented, so that path generation can take the type of terrain into account while plotting through the graph. While this hasn’t been implemented in my generator yet, there is a cost function that is already being called. It just returns a value of 1 for every point in the graph, but later a data table can be added to allow for different terrain types to cost different amounts. This will require some rewriting of the code because at the moment the generator isn’t doing any line traces to the landscape underneath each graph point to find the material at that location. This approach may not be possible for a variety if reasons, especially considering Unreal’s landscape material architecture, but even if that is the case, there is always a way to do something if your really determined.

Having a generator that will create paths that avoid blocking volumes is crucial, because I had written another EQS generator that would create its points along a straight line from Suzy to the target. It is crude, but effective in some instances…except where it sends Suzy running through the ocean. Obviously, this isn’t ideal. In the image for this post, you can see the EQS generator has created a short path for Suzy to follow. This image doesn’t show off what the generator can really do, but does show it in action. Just as an aside, the image also shows off the new female mannequin that Epic has given the community. The hair groom was a quick job in Blender to test out export settings to finally get a groom out of Blender. The tutorial is by Marvel Master and does what it claims to.

It has taken a lot of work to create this EQS generator, and while it isn’t perfect, it works very well and allows Suzy to run from one side of the test Island all the way to the other. I don’t know how far it is in-game, but it has to be at least a kilometer and is likely more. This generator isn’t just a great asset to this project, but it is now a permanent tool in our toolkit, and I have no doubt that the time spent creating it will be paid back ten-fold in the future. Hard work pays off.

Coming to Our Senses

It has been quite a while since the last post here, and that is because work has been moving forward on Suzy’s AI as well as some ancillary code development for the AI Perception system as well as the Environment Query System (or EQS for short). Also, as can be seen in the image for this post, a test level was constructed to better represent the conditions the AI will need to operate it. This gave me a much better idea of how this AI will perform “in the wild“, as some like to say.

Suzy’s AI has come a long way, and it is now close to being implemented to the point that the AI outline describes. Whether or not it will be sufficient to make the game challenging enough is yet to be seen, but I am encouraged by the progress. With a much better understanding of all of the moving pieces in the Behavior Tree/Blackboard approach to AI design, I have been able to build up a reasonably intelligent AI that will wander looking for fruit to pick up. But, once the AI reaches a predefined level of frustration, it will seek out the player to follow them in the hopes of stealing a piece of fruit that the player may lead it to.

To help Suzy find fruit easier, and make the AI more challenging for the player, a new sense had to be created for UE4’s AI Perception system: Smell. With a sense of smell, the AI doesn’t have to actually see a piece of fruit to find it. This sense of smell respects not only the direction of the wind, but also its intensity. By taking the wind vector used in the newer atmosphere system’s material and converting that into a material parameter collection, the wind’s values can be piped into the perception system. In this way, the player will get a visual cue as to how, or why, the AI can sense them even when they remain unseen by the AI. It isn’t perfect by any means, but I feel that it is a great addition.

Finally, in this game Suzy is using a NavMesh Invoker to create a dynamic navigation mesh around her everywhere she goes. This is much better than trying to create a huge navmesh that encompasses the entire level. At best, that would be very time consuming during development due to the need to rebuild a huge navmesh whenever objects are moved in the level. At worst, the navmesh may be too big to generate at all, which would require an entirely new set of systems brought into the project (such as level streaming).

With a navmesh invoker, we can eliminate these issues. But, and you knew that ‘But’ was coming, navmesh invokers present their own sets of issues. The largest issue is that the AI can’t be given a target location to move to if that location is outside of it’s generated navmesh. For example, if the AI’s navmesh has a radius of 3000 units (the default) and you were to specify a location that is 4500 units away, that ‘move-to’ command would simply fail. The location is unreachable to the AI because it can’t build a path from where it is to where you are directing it to go. A solution that is still being developed is to use the EQS to generate a set of vectors that will be passed as an array from the AI’s current location to a target location. This will require multiple ‘move-to’ commands to go from the start to the end of the path, but hopefully, it will mitigate if not eliminate this problem.

There is an issue with the fact that the EQS is generating a straight line from point A to point B, and no tests can be used to score a better path. But, given the alternative, I feel that this is a good start of not a great solution.