HaberDashers is an arcade kart racing game for 1-4 players inspired by The Borrowers by Mary Norton and Studio Ghibli’s The Secret World of Arrietty. The game takes place in a house much like your own – except that in this house, tiny people live in the walls and come out to play whenever the humans have gone. The theme was selected for its ability to highlight each of the student disciplines of programming, art assets, and track/level design. The game was completed over the course of 16 weeks of three-hour classes, beginning with two prototypes before moving into full production.
While the project centered around the creation of an arcade kart racer, providing individuals with experience working in Unreal Engine 4 on a racing game, its ultimate goal was to provide an environment in which team members could learn how to cooperate and coordinate with a team of 50+ people. Our move to virtual halfway through the semester also created an opportunity to strengthen communication skills, task planning, and remote working skills.
I was responsible for a progress tracking system, so that all karts can know where they are in terms of the track. This progress knowledge could then be used in not only AI decisions and drivings, but also karts ranking, rubber banding, etc.
After discussion and research, I, along with two other AI programmers, built up a customized coordinates SplineCoords for calculating progress, and utilized blueprint spline as SplineManager to translate between world position in Cartesian coordinates. We also divided the track into five lanes internally and built up a track information query system inside SplineManager for AIs.
And some of the interesting problems we faced and the solutions I finally adopted are listed below.
What info should SplineCoords include? How to translate back and forth between two coordinates?
SplineCoords includes SplineID, SplineIndex, InputKey and Offset. SplineID refers to the unique ID each spline would be given on game starts (equals to their index in splines array), SplineIndex refers to a single blueprint spline's index (position) for a track, InputKey refers to the "position" on a spline that Unreal uses internally, and Offset refers to the displacement from a point on the spline to the exact point in the Cartesian coordinates. Splines could be given the same SplineIndex, so that short cuts could work properly for the game.
So from a point in Cartesian coordinates, the most direct way is to for loop all the splines, find the nearest, and fill out the SplineCoords info to return. But to improve performance, we did a specific progress tracking for AI: AI remembering its progress and spline and being offered spline choices when approaching a shortcut.
From SplineCoords, Cartesian world coordinates could be directly calculated from Get Location at Distance Along Spline. Also, the track width is utilized to clamp Offset, so that the returned position is always on track.
2. How to get track information (forward, right direction and width) from environment?
Tracks are not always flat in XY space and of the same width in HaberDashers and track layouts are constantly changing for a long time. The forward direction could be directly obtained from Get Direction at Distance Along Spline. So it is necessary to retrieve the right direction at the start of a game, while track width could be referenced in level design documents.
Therefore, I used data table in UE4 to record track width data, and ray cast at the start of a game to obtain direction information of the track. For every 1/10 segment between two spline points, ray cast once towards the ground, cross product to get the right direction. Translate ray cast hit actor's name into data table name to retrieve the track width.
I and the other two AI programmers came up with a customized AI architecture for HaberDashers.
AI senses the world by AIPerception and SplineManager, while AIPerception senses for pickups, hazards, and other karts, and SplineManager offers info on track (turns, width, direction).
AI makes decisions of future destinations based on a decision-maker and several advisors. The AI senses are fed into advisors separately: pickups into interest advisor, hazards (moving trains, static trap, etc.) into hazard advisor, karts into kart advisor. All advisors give their advice on which lane to drive next to the decision-maker. Decision-maker chooses the best lane and asks the MoveAlongSpline advisor to calculate the destination position in SplineCoords.
AI drives the kart based on the Steering component, which calculates the actual speed and angle to turn and feeds these into a kart.
AI also has other advisors to use items, use coins, choose the spline to go when nearing shortcut.
And some of the interesting problems we faced and the solutions we adopted are listed below.
How to custom the execution order of multiple components? And performance consideration.
We were encountering bugs related to the wrong execution order of components, like AI drives to the wrong place because of outdated advice. So we started to use Add Tick Prerequisite Component to force the dependency relation to be right.
But we soon got warnings from Unreal Profilers that AI computation became a concern, because every AI did all processing every game tick. However, simply change tick interval time was not satisfying: all AI karts were less responsive and sharp turns couldn't be handled anymore.
Finally, I mitigated the performance issue while guaranteeing AI responsiveness by updating only one AI's decision on the destination in one frame in turn (also choose spline), while the Steering component ticks every frame with AI controller, and use items/coins advisors ticks every several seconds.
The game was released on Steam and well-received
Everyone played their part in QA
Communication both inside AI team and outside
Component-based implementation
Consider performance early
A high level of architecture in the code helped on the back end of the project
Flash hacks, discovering the fun & mechanics
Features' "done" criteria not so clear
Virtual brings some problems (delayed feedback and cross-team discussions)
Inaccurate task estimates
Taking notes and keeping backlog alive
Avoid hard-coding variables and comment properly
Consensus decision making
Give team members more agency in creating Conditions of Satisfactions
Iterate on pipelines and communication just as implementation of features