Home - IAMColumbia/gp2portfoliogame-JSchoppe GitHub Wiki
Welcome to the Wiki for the Brute Drive gameplay prototype. This wiki explains the choices made and the systems implemented. For those new to the project interested in getting it working you can read the QuickStart Guide.
This section outlines the initial proposal for this project. Each milestone is estimated to be about 3 weeks in duration. With a total project duration of about 2 months.
Development Value + Risks
Value:
- Demonstrates FSM using state pattern
- Demonstrates strategy pattern with Car/AI controller
- Demonstrates object pool, observer pattern, with AI manager
- Implements Google Maps API
- Reactive generation algorithm, geometry and graph analysis
- Implements physics system using forces + torques
Risks:
- Android input may be a time sink
- Car physics parameter tuning is a lot of design work
- Flocking behavior will be hard to manage with steering
- Potential unforeseen edge cases given strange map data
- Requires an upfront investment to create mock assets
Speculative UML
While this does not cover the entirety of all necessary scripts, I speculate the following UML will represent a majority of the work done in code. One of the key components will be the vehicle controller. This will not use the wheel colliders provided by Unity, and will instead simulate wheel and collision forces relative to 2D space. Initially damage will be applied without location context based on collision speed. Upon incurring some amount of damage the vehicles will destruct into a collection of free falling rigidbodies.
The player's car controller will take input from the device. Multiple controller configurations will need to be setup for testing and gameplay on the android device. Virtual touch controls will need to be created to support the android platform.
The AI will derive from the same base controller class as the player. They will implement a finite state machine that reacts to the current environment and other actors. The states shown here are tentative and more states might be required to ensure proper flocking behaviour.
One of the largest challenges will be meaningfully processing the google maps data into a stage. The data provides movement graphs for the road segments that can be used to find potential paths. Parameters will have to be added to ensure the generation algorithm can account for varied environments and outliers. The generator should be able to reliably partition off routes on the map such that the player cannot escape the level bounds. The AI manager and stage sequence scripts show the basic way the gameplay will be sequenced.
Milestones
This milestone will feature modeling of the driver controller for the player and AI, such that vehicles can interact with each other and the world in a very basic way. An algorithm will be developed to randomly choose a non-intersecting route through the recieved graph from google maps. Basic modifications will be made to the map to add colliders around the chosen route.
In this milestone the gameplay loop will be more fleshed out with basic vehicle damage implemented. Wrecks will be given a lightened weight so that they can be pushed out of the way by the player. Player will be able to complete a run from start to finish (on a prechosen map), and will also be able to fail a run if their vehicle receives too much damage.
Player will be able to choose the coordinates, or use current coordinates, to generate a stage. Generation should be tested against numerous different street layout archetypes to locate generation and behavioral bugs. AI will be fine tuned to be more reactive and predictive of the player's actions.
Fix lingering bugs and improve the gameplay polish. Clean up technical debt as time permits.
This sections contains the current perspective looking back on the project, documenting the experimentation in my design choices, and how well I think they worked out.
In this project I attempted to make the code as reusable as possible by minimizing dependencies wherever possible. While this worked for some things; basic AI state structure, and objective modeling, it was less elegant for other systems like the vehicles, and especially collision. Having spent tons of development time on just scaffolding and making interfaces to abstract implementations, I think it is worth questioning whether the tradeoff is worth it. In the long term there is a strong argument; but it largely slowed down my capacity to prototype and iterate on gameplay. In the future I think I should adjust my approach to be more top-down to serve a prototype, while keeping in mind how the organization of the code can be re-organized into a bottom-up abstraction in the future. I would say my approach was over-engineered on this project.
I would estimate that about 70% of the code here can be reused in a meaningful way; I can say this with some confidence since going into this some implementations were already reused and iterated on from the past. I think the level of abstraction is decent, but I am still interested to think about other ways these abstraction layers can be formed. It is clear that this approach is very intimidating looking in from the outside; which poses problems for the engagement of programmers on the team.
I used dependency injection as a means to draw a clear dividing line between my code and engine code. I think this ended up working fairly well; and there are some great arguments for code cleanliness under the interface segregation principle. I will continue to use this pattern for systems of objects that communicate with each other; as it helps create meaningful abstraction layers.
In the AI state machine, and in my abstracted state machine, state pattern is used to isolate states into their own classes. I'm not sure if I've found the best approach on this yet. I really do like the fact that you can have fields that are locally scoped to that state; and this becomes even more powerful if the classes are inner classes inside a FSM implementation. One challenge I still face is the fact that reusing state implementation in multiple state machines is tricky. If I developed more AI types for the game I think I would explore interface segregation as a means to inject different FSMs into state constructors.
Although not implemented, the AI manager was meant to pull its agent from a pool (since FSMs are bulky). I abstracted the object pool implementation into multiple layers and think it is decently reusable. The trickiest part is figuring out where the responsibility should go for notifying the object pool of an objects retirement to the pool. Arguably the notifying implementation should be injected, so that it is not required/defining a subclass. I think this one is pretty straight forward though; from a research standpoint it probably more viable to look into DOTS rather than micro-optimize an object pool for Unity.
Singleton pattern is used in this game to retrieve services from Unity, notably the tick service is retrieved from a singleton for both the Update and FixedUpdate game loops; this is done via event bindings on a singleton instance. I like this approach as a relatively simple and clean way to pry away MonoBehaviour from objects that need to tick. This can also be thought of as flyweight pattern, since ticking all of these objects through the service is consolidated down to own dependency on a bulky MonoBehaviour object. Distancing from MonoBehaviour has large positive implications for reusability and code legibility.
In the abstraction for the touch input system you can see some use of the observer pattern; when new touch coordinates enter focus, the touch controls are notified from a central subject/manager class. This is used as an abstraction layer to wrap the retrieval of touch information, and the processing of that information geometrically. I think this pattern worked pretty well in this case. It worked well in the context of this project.
- Some of the prototype classes implemented, such as the AI cruiser manager and maps generation scripts, have yet to be abstracted into reusability layers. These scripts pose portability difficulties for the codebase.
- Collision algorithms are not thoroughly tested/profiled. It is unclear whether they are overengineered or need more variables to control their precision.
- AI state machine is roughly implemented. Needs more well designed states and AI-AI communication to be engaging to play against.
- Stage generation is not well tested for edge cases. Maps have been found that generate through buildings, or with barriers placed in awkward places to do the graph structure of the map. More edge cases need to be isolated and filtered out if the game is to be tested at scale.
- Some classes lacking full documentation and containing fields that do not need to be public.
- There is a lot more specific small stuff, but I have tried my best to mark any and all instances of debt with TODO.
- The objectives system could be populated with more objectives to facilitate different modes of gameplay.
- Vehicle control, rendering, and physics can all be extended or modified since they are injected into the vehicle class.
- The AI state pattern is flexible in nature. In my case I use enums since I my FSMs are not dynamic, but since the base FSM class can take any type as its key, strings could be used to make fully dynamic state machines to facilitate more complicated AI.
- Maps generation is notably NOT extensible. To make this extensible I would have to expose a lot more axes of data about the processed map information. Currently it is rigid to simple route generation.