A Game loop of how the game should look like in easy difficulty.
A Game loop of how the game should look like in easy difficulty.
The design for WindowWiper was mostly based on the requirements made by SyncVR Fit. These requirements included but not limited to:
There must be a manager class in the game that directly inherits from ExerciseManager class.
There are certain UI elements that must be present in the game and must interact with the manager class described beforehand.
There must be a class WindoWiperDifficulty which inherits from the scriptable object class Difficulty.
All of which are explained more in the document below. Or if you click here.
Due to the above requirements there were limited choices to the design of WindowWiper but a couple of things were clear:
A Singleton Design Pattern (DP) should be used for the Manager class
The singleton DP was used for the Manager class mainly due to the previously mentioned requirements. However, this would allow me to use global variables, that would otherwise be inefficient to use, in order to control the main game loop of the game. For Example, the already existing BuildingManager script created for early prototypes of WindowWiper, could be reused and added to the WindowWiperManager derived class as a list. This would allow us to take advantage of the preexisting Coroutine Loop Game() by using a simple counter and finding the last BuildingManager in the list without having to sort through the entirety of the list.
A Prototype (Clone) DP will be used for the BuildingManager class.
The prototype DP is about having objects clone themselves, then the Factory Method (Virtual Constructor) DP is about using a Factory class to create the necessary objects needed. A mix of these two DP's was ideal given the BuildingManager class creates the BugCollider objects, and the BuildingManager itself will be cloned throughout the main game loop in the ExerciseManager class. Thus while the BuildingManager class will most likely not include a "Clone" method, it still holds all the necessary variables/gameobjects needed in order to clone itself. Then when it's spawned, the BuildingManager class will control the spawning of the abstract class BugColliders.
And finally a Template DP will be used for the Manager class and the BugCollider class.
The BugCollider Abstract class was created given early feedback consisted of adding multiple different types of bugs to the game, and given all of these bugs should still follow the basic mechanics of the game such as; moving forward, colliding with the window, and getting wiped, an abstract class was created in order to ensure all the bugs work in the same way and in different ways when intended. The Template Method DP would be ideal to be used in the design of the BugColliders since many of their functionalities could be split into individual methods and then can be overridden where needed
The tutorial system had to be able to be used in every game in the SyncVR Fit app, this posed a couple of unique challenges code-wise:
How to play a tutorial in every game with the minimum amount of code possible?
How to make a unique animation for every game with the least amount of resources?
How to play the desired animation in the desired game?
These designs were subsequently realized, realization can be found here.
Flowchart about the ExerciseManager Game method.
As explained in the document above, SyncVR uses a control class ExerciseManager that should be inherited by every game's Manager class. This prompted some research into the design of the ExerciseManager class which in turn lead to a solid solution for this question.Researching the ExerciseManager class a flowchart was created (as seen on the left) that detailed how the Game coroutine was started. It simply set an empty variable game = to StartCoroutine(Game()) which initialized the coroutine inside the StartExercise() method which is not possible by directly calling StartCoroutine inside a void method.
The method would be changed to call a new Coroutine GameLauncher() which in turn would call a StartTutorial Coroutine by using a "yield return". The reason it is important to use a coroutine to initialize the tutorial is that given the tutorial is played before the game, it would be too much information to the player if the tutorial is played at the same time as the game is starting. Thus the "yield return" is used since it pauses the code until the coroutine is completed. Thus allowing for a seamless transition between one coroutine into the other.
public class TutorialManager : MonoBehaviour
{
//assign in scene
[SerializeField]
private GameObject kyle;
[SerializeField]
private GameObject spawnPoint;
//auto assigned
private Animator animator;
private AnimationClip[] animClips;
//****** IMPORTANT STEPS TO FOLLOW FOR TUTORIAL MANAGER ******
//Add script to the SAME game object ExerciseManager script is attached to.
//Add reference to correct Kyle Tutorial Prefab found in _app/Prefabs/Tutorial/<exerciseName>Prefab.
//Add empty game object to scene in location you wish Kyle to spawn. Make sure the rotation of the game object marches the way you want Kyle to be rotated. MAKE SURE SCALE IS 1,1,1.
//Add reference to empty game object from previous step to the TutorialManager script as "spawnPoint".
public IEnumerator StartTutorial()
{
if (kyle != null && spawnPoint != null)
{
GameObject g = Instantiate(kyle);
g.transform.SetPositionAndRotation(spawnPoint.transform.position, spawnPoint.transform.rotation);
animator = g.GetComponentInChildren<Animator>();
animClips = animator.runtimeAnimatorController.animationClips;
animator.SetInteger("Controller", 1);
yield return new WaitForSecondsRealtime(animClips[0].length);
Destroy(g);
}
}
}
Sample Animator Override with custom animation below and template animator above
TutorialSaberSlash - Animation Page
The other challenge was how to create a wide array of unique animations, unique per team fit game.
The solution thought of was to create a template AnimatorController where the main state is "idle" and it changes state based on the parameter "controller". This parameter can be scene accessed in the tutorial animator script above.
Each animator override controller has a reference to it's own unique animation which subsequently is attached to it's respective Kyle prefab.
This way the original animators functionalities are re-used into every single animator needed for the Kyle, whilst not creating a new animator object but merely a reference to the original animator with a different name.
This question was pondered a bit longer than the previous given the amount of ways this could be done.
Initial ideas were:
To have a list of addressable animations which will be swapped by an if function that checks the scene name.
To use the unique codes' in the localization of the project to find each animation based off of it.
Finally the implementation decided was more arbitrary and required more "busy work" such as dragging and dropping references to the desired game object and/or animator. This proved to be a challenge as every game has different landscapes, derived manager class implementation, and mechanics. Thus each Kyle needed specific spawn position based on the scene, specific gameobjects inside the animation (eg: seagulls for Dodge, a building block for WindowWiper, etc) to resemble the game's mechanics.
The reason is that every game's implementation is different from the due to the nature of the ExerciseManager implementation. Thus the easiest way to ensure quality standards would be a manual implementation with references to the correct prefab/spawn point.