Snowpainters
Overview
-
Role: Programmer
-
Genre: 3D Racing
-
Engine: Unreal 4
-
Team Size: 40 People
-
Development Time: 3 months
High Concept
Snowpainters is a “console-style arcade kart racer” for the PC in which the penguin characters slide around, bringing color to a plain arctic world while competing against other people and AI opponents with driving skills and color pickups.
Responsibilities
-
Implemented AI peiguns which can complete a lap and use all game mechanics like pickups, speed boosts and shortcuts.
-
Designed and implemented the dynamic spline system which allows AIs to move freely in the track.
-
Implemented and Designed multiple AI personalities with unique and distinct AI behaviors.
-
Implemented the dynamic difficulty balancing system which adjusts AIs' difficulty level dynamic during the game.
-
Collaborated with level designers to determine possible shortcuts and pickup placements .
The Penguins
In Snowpainters, players control the penguins as the karts against each other and AIs. There are three different penguin types, and each type has different settings. Since the penguins that AIs control are the same as players' penguins, it is hard to implement AIs which are competitive and smart. Also, in the game, there is a unique feature called paint trails which penguins can get speed up when sliding on the trails. One big challenge when implementing AI behaviors is how to let the AI penguins follow the trails. In this section, I will talk about all those challenges I had during the development stage and the ways I solved them.
Dynamic Spline
In Snowpainters​, AIs follow a pre-defined spline in each map to complete a lap, and they generate their own spline on their current positions. The pre-defined main spline on the middle of each track guides AIs whether moving to the left, moving to the right, or moving straight forward. AIs calculate the distance between their current positions and the main spline point and generates a shorter spline with fewer spline points that has the same transform as the main spline. This algorithm avoids all AIs trying to move along the same spline at the same time causing chaos, and it allows AIs to target different objects such as pickups and paint trails while they still move along the track without hitting the track boundaries or moving weirdly.
​
Below is a screenshot of AI dynamic splines. Each AI penguin has its own spline that will update after passing each spline point or changing its current target object. To avoid initializing the whole spline each time, the dynamic spline only has limited spline points which allow the penguins to move forward while saving a lot of calculating time.
To provide a smooth and continuous movement, AI calculates its steering value along its spline every frame. In each frame, AI finds one point along the spline based on its current speed. If AI moves faster, it will find a point father, and if AI moves slower, it finds a point closer to its current position. Then, it calculates the angle between the position and its forward vector to get the steering direction and value. By doing this calculation, AI can immediately adjust its turning angle for any turns and any speeds. The code below shows the steering value calculation process.
The blueprint below shows part of the dynamic spline generation process. When generating each AI's own spline, it calculates the distance between its current position and nearest spline point on the main spline, and copy six forward spline points by adding the distance to them. It will also connect the last spline point on its previous spline to the newly generated spline. This small part gives AI a more natural movement when switching between splines.
Behavior Tree and Game Mechanics
The AI behaviors in the early stages were based on a simple behavior tree, and the AI team was having a hard time debugging and adding new behaviors. Therefore, I restructured the AI behavior tree to have more specific behaviors such as "UpdateSplineBasedOnSnowInfo" and other nodes instead of a general action node "UpdateSpline". The current behavior tree structure is able to gather all information AI collects in that frame and decides what will be the best decision based on the information. Also, by using the new structure, I can add and delete any specific AI action node without editing the whole behavior tree and changing the AI pawn.
The images below illustrate the old behavior tree and the new behavior tree. The old behavior tree just has a simple selector of "UsePickups" that contains all the AI behaviors of using pickups and targeting pickups. The new behavior tree splits all the conditions separately that allows us to debug each specific condition and change the AI behaviors without touching other functions.
The image on the right side is the special structure that I created for the AI pawn. The structure contains all information that the AI can get from the game in each frame. At the beginning of each frame, AI behavior trees will first call “UpdateAllInformaciton” function to update AI information. Then, the behavior tree will use the information to determine what would be the best decision for AIs to act in that frame. This approach allows behavior trees to access information effectively without collecting the same information multiple times, which saves a lot of performance and helps me debug.
In the new AI behavior tree, once it collects information from the current game state, it will use the information to determine AI's next action. Therefore, I split the original simple "UseMechanic" action node into two different large selector nodes: "TargetInteactableActors" and "UseRNGAndSpeedBoost". The image below is the first selector node that uses AI perception information and current pickup information to determine if there is a game actor that it needs to target. If there is a new target, it will set the target for AI, and another part of the behavior tree will change AI's dynamic spline to drive AI to that target. Since in Snowpainters, the main mechanic is paint trail, which allows the same color penguins to get speed boosts on paint trails, this part allows AIs to make smarter decisions and shows that AIs have the ability to follow the paint trails just like human players.
The second selector gives AIs the ability to use random pickups and speed boosts. I also set up special conditions for each pickup based on its ability and using condition. For example, the Snowpile works like banana peel in Mario Cart 8, so I implemented AI hearing and AI dropping a Snowpile if it hears any other penguins behind.
Personalities
One of the challenges we had is how we can implement different AIs with different behaviors, like different personalities. After the new AI behavior tree structure was completed, I was able to create and define AI personalities by adjusting the behavior tree structures. The image below shows the four personalities in Snowpainters, and each personality has a unique behavior tree and distinct in-game behaviors.
​
The personality mechanic gives the game more dynamic gameplay and replayable elements. In each game, the game instance will randomly pick AIs from the list, and each AI is tied to its own personality, which allows each game have different AI personalities. Also, since each personality has distinct behaviors, I designed some personalities that are harder to beat while some other personalities that are always in last places.
Dynamic Difficulties
Since in Snowpainters, there is no difficulty option for players to choose, how to balance AI difficulty became a serious problem after we entered the beta phase. To solve this problem, I created a dynamic difficulty system that adjusts real-time game difficulty by calculating players' places and positions and also each AI's current personality. The image below shows the five different difficulties in Snowpainters.
Once difficulty levels were defined, the problem became how we could apply those difficulties to AIs and what each difficulty would affect AI behaviors. The left image is the structure of the AI dynamic difficulty actor. The actor will apply the different enhancements to help AIs catch up with players. At the beginning of each frame, each personality behavior tree will first collect in-game information and then adjust current difficulty based on its place and players' places.
​
Since each personality behaves differently in the game, it causes some personalities can always get higher places while some other personalities always get last places. This was another problem I needed to solve when applying the dynamic AI difficulties. Hence, I added a value called "Difficulty Offset" that varies in each personality. For example, victorious AIs always get higher places, so it increases its difficulty much slower comparing other personalities and decreases difficulty much faster.
​
The dynamic difficulty system provides a balanced but still competitive game experience in which players can still have competitive games while casual players can still enjoy cute penguins and interesting paint trails.
Post Mortem
What Went Well
-
The new structure is done and works pretty well
-
Get all tasks done before asset lock
-
Feel ok about the one-person team
-
Since now AI is using the new structure that I am more familiar with so I could find & fix more knowing bugs
-
Great communication with myself
-
Everything that went well before continued going well
What Went Wrong
-
Restructuring behavior tree was too late, which caused many tasks to be delayed and canceled
-
Poor communication between the level design team and the gameplay team
What Needs Work
-
Get more feedback from LDs/Artists
-
Should check Jira more often
-
More playtesting to adjust AI difficulty and stats
-
Establish a process to gain feedback about AIs when others are doing playtesting