LONG GIANT
Unity - C#
Systems, AI and Gameplay
Long Giant is an AR mobile game that was developed in a very short amount of time (4 days). Our team was made up of 4 Programmers, and we had that short time to define and produce every aspect of the game, from design to art and, of course, implementation. It was developed using Unity and scripting in C#.
It was the first time I worked within a team made up of programmers only, so it was a really great opportunity to dispatch work items in order to produce the game right on time. Each programmer was responsible for a pretty precise part of the game, allowing us to work in parallel and come up with a final uniform result.
MY WORK ON THE PROJECT
SYSTEMS PROGRAMMING
I was mainly responsible for the development of the various systems of the game, like the procedural generation of the game space, the game’s progression, the physical objects interactions, the enemies spawning system… I had to design and implement those different systems to make them work with all of the other features of the game. That allowed for a complete, systemic and procedural game experience.
AI PROGRAMMING
I was also responsible for the behaviors of the tiny enemies of the game, the Humans. I worked on their movement and attack behaviors, as well as the spawning system responsible for their apparition during the game. I exposed several parameters and set up an easy tweaking process, in order to allow for a quick balancing of the game’s difficulty.
CODE SAMPLE
-
World Generation, Goal and Progression : A system responsible for the procedural generation of the game’s world, considering the generated objects in order to set up the party’s goal.
-
Physical Objects and Collisions : An architecture and system able to handle physical objects with different actions (throwing, handling collisions and playing some actions…).
-
Enemies Spawning System : A system responsible for the spawning of the game’s enemies, evolving with the player’s progression to change balancing during the party.
WORLD GENERATION, GOAL AND PROGRESSION
The player’s goal is to complete a construction by filling it with specific amounts of different Resources. These Resources can be found by breaking the Constructions (houses, well…) scattered around the game’s space during the world's generation.
This system generates the world, processes the total quantity of each Resource contained inside of the generated Constructions, and sets up the player’s goal with them.
MAIN SCRIPTS
-
WorldGenerationManager : Holds references to the generation settings, generates the world and holds the total number of Resources of the map to generate the player’s goal.
-
GiantConstructionScript : Able to receive Resources and to check for goal’s progression each time a new Resource is received.
-
WorldElementPrefab : A prefab filled with Constructions, processing the exact amount of Resources the prefab contains.
PROCESS AND LOGIC
In Long Giant, the game’s world is generated depending on a center position and radius, set up during the AR initialization.
Once that data has been processed, the WorldGenerationManager uses the center and radius of the game area (as well as other parameters like spacing) to generate a list of suitable positions. Random WorldElementPrefabs are then selected (depending on random picking parameters) in order to instantiate them in the world on the generated positions. Each WorldElementPrefab holds the reference to its children Constructions, enabling the Manager to process the precise amount of each Resource that has been generated in the world.
After that, the party’s goal is generated using the “GenerateObjective” method, processing the total amount of each Resource and multiplying it by a weight (between 0 and 1) in order to give more importance to certain Resources. The generated goal is then passed as parameter on the GiantConstructionScript “GenerateNeededResourcesDictionary” method, which uses the generated goal to set up an UI above the player’s construction, displaying the party’s goal. From here, the game is ready to be run.
When a Resource is added to the GiantConstructionScript, its “AddResourceToConstruction” method is called. It checks if the Resource should be kept or ejected from the construction (for example, if all required Resources of that type have already been received). If it is kept, we check if all needed Resources have been gathered to complete the party’s goal. If they aren’t, the HumanSpawningManager spawn rate is updated to increase difficulty. If all resources have been gathered, “FinishConstruction” is called, which sets up the end of the game.
World generated with a small radius (Giant Construction in the middle)
World generated with a medium radius (Giant Construction in the middle)
World generated with a large radius and a very lower fill amount (Giant Construction in the middle)
World generated with a small radius (Giant Construction in the middle)
TOOL'S USABILITY
In order to easily bind a selected object with a weight value, I created a bunch of custom PropertyDrawers, displaying an object right next to its probability. This allowed for a pretty quick tweaking process in which we could modify the probability of each WorldElementPrefab, as well as the importance of each Resource in the party’s goal.
Regarding WorldElementPrefab, we needed them to get the reference to their children's Constructions in order to be able to process the precise quantity of each Resource within the prefab. A simple ContextMenu method was created, to assign the children automatically.
MAIN PROBLEMATICS
HANDLING PREFABS POSITIONING
(see diagrams below)
The main challenge of this procedural system was to generate the positions on which to place the different game prefabs. We wanted to generate them within a circle area, and to have them placed at approximately equal distance from each other.
I got inspired by hexagonal grids, and generated a method in the CirclePositionsGenerator. The idea is to move right X times and move up right Y times. This will give a single position that can be rotated 5 times of 60° from the center to get 6 unique positions. X and Y are then incremented until a limit is reached (radius, number of positions…) This is not a very rigourous mathematical approach, but it made the trick for this generation system.
GENERATE AN ADAPTATIVE PARTY'S GOAL
A more manageable problem was to generate a balanced party’s goal from the generated world. Depending on the radius of the game area (set up during the AR initialization), and on the picked WorldElementPrefabs, 10 of iron could have been generated as well as 100 of them.
In order to handle all those cases, we decided that a single Construction will always hold the same Resources in the same amounts (ex : A weapon rack will always hold 2 irons and 1 wood). This allowed us to easily process the Resources quantity of each Prefab, and therefore of the total world. We then added an importance weight to each Resource, requiring the player to gather greater proportions of some Resources, like iron, which is the rarest Resource. This pushes the player to search for precise Resources at some point of the game.
The main loop goes through each circle of the hexagon until it reaches a max distance
We search for every position on the current circle by changing the number of right and up-right moves
(Ex 1 : 2 rights moves and 1 up-right move)
The found positions are then rotated of 60°, five times, to complete the circle entirely
PHYSICAL SYSTEM AND COLLISIONS
The main feature of the game is to toy around with a bunch of objects, to pick them, to throw them at each others, to crush them with your giant hand… This basic principle required us to set up an architecture and system able to handle all those physical interactions, and play different actions depending on the collisions.
The main physical objects are the Constructions (houses and stuff generated at the beginning of the game), the Resources (spawned by Constructions when destroyed, and used to complete the player’s goal) and the Humans (enemies of the game). These objects can receive and / or deal damages, which will define what happens when two objects collide with each other (ex : a house colliding with another house will likely result in both houses being destroyed since both can receive and deal damages).
MAIN SCRIPTS
-
PhysicalObjectScript : This is the base class that any other Physical Object inherits from (Constructions, Resources, Humans). It contains all the required behavior to handle physical interactions (OnCollisionEnter, Throw, CheckForDestroy/Destroy…).
-
PhysicalObjectsCollisionsManager : A manager which is responsible for the management of collisions occurring between two Physical Objects.
PROCESS AND LOGIC
All Physical Objects of the game inherits from the PhysicalObjectScript base class, requiring them to hold a Rigidbody, and giving them access to common behaviors and methods than our objects need to implement. This also allows them to hold common variables, like the “objectDamagingMass” variable, defining if the object is rather heavy or not. This defines if it will more likely destroy objects it collides with, or destroys itself if it hits a static object.
All Physical Objects hold the “Throw” method, allowing them to receive a particular velocity. This method is used on many features of the game : Objects can be thrown from the player’s hand, Resources are expelled out of their Construction when it gets destroyed… This easily enables for more juicy objects' movements.
Regarding the collision management, each Physical Object uses the “OnCollisionEnter” callback, and checks the nature of the hit object. If the hit object is not a Physical Object, the colliding Physical Object check if it is destroyed under its own velocity. If the hit object is a Physical Object, the “AskForCollisionTreatment” method from the CollisionsManager is called in order to process the collision between two Physical Objects. All these queries between Physical Objects are processed during LateUpdate, to make sure that all of them have been updated before treating the collision.
When a Physical Object receives a collision, the “CheckForDestroy” method is called, checking if the object should be destroyed depending on the speed force it received. If force is high enough, the object is destroyed on the next update, just to make sure interactions between two Physical Objects are handled as they should. “DestroyPhysicalObject” is a virtual method, allowing for specific behaviors on destroy, like spawning Resources on Construction destroy.
MAIN PROBLEMATICS
AFFORDANT DESTROYING RULES
The biggest challenge of this system was to generate a tweakable system, defining when objects are going to be destroyed or not in an affordant way. For example, a house should be destroyed when it hits another house, but should not be destroyed if a small human is thrown at it.
When a Physical Object hits a static object, it receives its own damaging speed (its velocity multiplied by its own “objectDamagingMass”). But when a Physical Object hits another one, damaging speed is processed by calculating the relative velocity between objects, and multiplying it by the objectDamagingMass of the other object. This way, when a human (small mass) is thrown at a house (big mass), the human is destroyed and not the house.
LINK WITH THE CONTROLLER
Another tricky challeng was to link the Physical Objects with the hand controller, which needed me to work closely with the Controller Programmer. The player needed to be able to grab physical objects and throw them - We achieved that by disabling the Object’s physics while it is being held, and by calling the “Throw” method when the player wants to throw it.
But the hand was also able to destroy Physical Objects if the player is moving the hand fast enough against objects - We achieved that by putting a PhysicalObjectScript behavior on the hand's collider, enabling and disabling its ability to deal damages depending if the hand is holding an object or not.
ENEMIES SPAWNING SYSTEM
The Enemies Spawning System is responsible for the spawning of Humans during the game’s process, from different sources : frequency spawning, house destroying…
In order to generate an evolutive difficulty, the Spawning System is regularly observing certain parameters of the game, like the player’s goal’s completion. The spawning system also makes sure that a limit is set to the maximum number of Humans, stopping the frequency spawning when too many Humans are there.
MAIN SCRIPTS
-
HumanSpawningManager : Manager Responsible for the spawning of the Humans.
-
HumanScript : Script responsible for the AI behavior of the Humans. Inherits from “PhysicalObjectScript”.
-
PhysicalObjectConstructionScript : Physical Object that can be used as Human spawner, and that can spawn Humans when destroyed.
PROCESS AND LOGIC
When the world is generated, Constructions that are marked as spawners are added to the Spawning Manager. They will be used as spawning spots for the Humans, to make it feel like they are going out of their house to attack you.
When a first Construction gets destroyed, the spawning system starts to update its frequency system. When the frequency system completes a frequence, the “SpawnHumanOnRandomSpawner” method is called. This will get a random spawner (if one remains, else it will get a random location around the game area), and call the “SpawnRandomHuman” on that location. A random Human prefab will be picked (depending on probability parameters) thanks to the “GetRandomHumanPrefab” method, and then spawned on the right location.
Humans can also be expelled when Constructions get destroyed, which allows for a funnier interaction with game elements. Constructions can define a specific number of humans to spawn when the “DestroyPhysicalObject” method is called.
When the player’s goal completion changes, the “UpdateSpawnRate” is called, processing a new spawning frequency, depending on the completion coefficient. This is what makes the spawning system react to the player's progression and adapt difficulty. The closer the player gets from its goal, the more frantically Humans will spawn and try to ruin its construction.
LEARNING AND TAKEAWAYS
This project was the first one on which I wasn’t responsible for the most part of the development, since we were a team of 4 experienced programmers. Getting to work on a very precise part of the development, the game systems, was a really interesting experience for several reasons.
WORK WITH OTHER PROGRAMMERS
Because of the really short amount of time we got to develop the game, we had to find a development process able to get the game to work while being still a fun experience, including the AR constraints. We achieved that by giving responsibilities to each other on very specific tasks (Controller Programmer, AR Programmer, Integrator, Systems Programmer), which was very insightful about how things go when it comes to work as a team of several programmers.
SYSTEMS PROGRAMMING DISCOVERY
I didn’t really realize what working on Systems Programming was before I worked on Long Giant. And actually, It was really thrilling to work on systems, learning how to make them flexible, to allow tweaking and experimentation, while keeping them robust enough to avoid any bug. It made me realize that I might like Systems Programming as well as Gameplay Programming.
REVIEWS
Looking back at the projects scripts gave me the opportunity to realize bad practices I had while working on Long Giant. Now, I do my best to avoid those bad practices and work as clean as possible on projects.
TAKE ADVANTAGE OF INTERFACES
I kinda felt ashamed when I realized that the project is not using a single interface, which could have been a better way to handle Physical Objects for example. Working without Interfaces could have been a real problem on a more long-term production. Interfaces were more likely avoided to work with more concrete references and avoid losing any time. Now I make sure to use them when needed.
MAKE MORE READABLE CODE
Also, I realized that none of the code I wrote was commented. This was fine during the production since each programmer was working on its own task, but would again be a real problem on a more long-term production. Now, I make sure to add tooltips or comments on things that should be easily understandable for other members of the team.
SET UP POOLING
No pooling was set up within the project, and most of the objects are instantiated at runtime, which is a particularly bad idea when it comes to mobile development. I make sure to implement a real pooling system on productions when I have enough time to do so.