Now that we have our basic example of a test and our test class ready to go, let's examine some of the things that we're able to do with GameDriver before we devise a more complex test scenario. For instance, we have inputs; GameDriver can provide all of the inputs that a user can, and more. We can input raw keystrokes, button presses, axis presses, touch, tap, etc., and you can see some examples of these actions here. For a key press, we can provide one or more keys that work together, either in sequence or in parallel. For button presses, we support both the path to an object using the input system package or using a mapping such as 'Fire1' or 'horizontal' or 'vertical' to provide those inputs. We also support touch and tap, which are used for mobile and consoles. We can execute most commands like dragging from point A to point B along a certain path, or moving the mouse pointer to an object, which sometimes triggers actions on its own. We can also perform clicks using either coordinates or object names. Internally, GameDriver will find that object by its name or path, and we'll delve more into how that pathing works in just a moment. We can also perform double clicks on objects and double clicks with key presses. You can combine inputs between the keyboard and mouse, which leads to more complex behaviors. Take the scroll wheel, for example. We can work with object properties directly. Don't worry too much about the structure of these commands just yet. We'll discuss that structure in a moment under the Hierarchy Path examples. However, note that you can get the value of a property or set the value of a property using this structure, the syntax that you see in orange, to identify the precise object, component, and property that we want to change. Whether it's an object color, an integer, or a string value, we can query those objects using our Hierarchy Path query language, or set the property of that object using one of these two commands.
We also have additional commands for working with object properties such as position and rotation. You can get the object position using a Vector2 or Vector3 and perform some coordinate conversion if necessary. Sometimes there are multiple cameras in a scene and we need to convert the coordinates of where it's rendered on one camera to where it's being rendered in our viewport. We can also get the distance between objects. This could be the distance between two objects by name. It tells us the distance between them so that we can perform some action at the appropriate time. We can also rotate objects directly, using our own interface. For example, in a game, there might be no way for me to take an object and rotate it 90 degrees, but GameDriver provides that level of access. This can be useful if we're running a test that requires the starting point, as I mentioned, where the object is rotated in a certain way. It could be an enemy, for example, that's facing away from us so that I can perform the test of sneaking up behind the enemy and then striking it. We can also wait for object values, which is to wait for a specific object property value to occur. This will poll the object for a set amount of time while we're waiting for an event to occur, often because we performed some other input. We can also call in-game methods, whether that method is private or public. The developer does not need to give us access to those methods; that's all provided through the GameDriver agent. That method can be attached to any object in the scene as long as that object inherits the model behavior, at least in Unity. In Unreal, all methods that are exposed to blueprints are accessible. So in this case, what we're seeing is we're finding a method attached to the object 'canvas' with a component of 'HipProjectManager'. We're going to run the 'loadDetails' method, which takes an argument such as the path to a customer's file, and then we're going to check that it indeed loaded.
We also have the 'returnType' call method when we're expecting a value back from a method. That's when there's an event in a game that occurs when a method is executed, it performs some calculation, and we're going to get the value back from that. We can capture all of this from outside of the game.
There are additional utility functions provided by GameDriver, such as capturing screenshots, the connect method, which we discussed a bit about, enabling or disabling hooks (which is how we provide our inputs such as the keyboard, mouse, gamepad, or XR), an object cache (the caching allows us to query objects without repeatedly querying the scene, which can be useful in a very large scene with tens of thousands of objects), flushing that object lookup cache so that we're not using stale values, getting the scene name of the currently loaded scene, getting the current or last frames per second as we're constantly polling the frames per second value to understand how well the game is performing during our replay, getting an object list (an entire list of all of the objects in the scene so that we can work with those in a sort of offline manner to understand maybe how many objects were present, what names were provided, or if there's a certain path to those objects that we need to perform some parsing on, without having to go back and forth to the game for each one of those requests), 'getConnected GameDetails' (which returns the operating system, editor version, executable path, etc.), and launching and loading a scene.
We can also perform more complex behaviors like using NavAgents, or navigation agents within the game's scene itself, to move an object to a point. If you have a NavAgent attached to an object, that means there's a NavMesh in place that allows us to use the game's built-in AI pathing mechanism to get from point A to point B. We can also RayCast, which means that we're returning the objects or the specific information of an object in a given position. GameDriver also supports collision detection and we can register an object for collision detection, perform a set of actions, and then check whether or not that collision occurred. This is useful for finding boundaries or holes within a scene. We can, of course, wait - wait for an object or wait for empty input.