GraphQL Client For Unity
Hey there! Today, I want to share my experience developing a GraphQL client for Unity. This client was part of a project born during my internship at Turku Game Lab in Finland, but you know, sometimes there's not enough time for everything and this project sit on my laptop for some time, while I still want to implement said project, I decided to break it down a little bit to make sure everything works correctly once I start integrating all the features together.
How It All Started
Picture this: I'm sitting in a meeting with my tutor, fresh off completing a Unity course, and we're brainstorming ways to showcase what I've learned during my vocational training. The catch? Most trainees at the lab came from game development or graphics backgrounds, while I was the odd one out with my multiplatform development experience.
That's when it hit me – why not leverage what makes me different? While I might not know about 3D modeling, shader programming or the intricacies of game mechanics implementation, I did have a solid grasp on databases, I also know about REST APIs, and client-server architectures. So, I thought, "Why not bring these skills into the game dev world?"
The Idea: A Networked Quiz Game
The idea it's not new by any means, around that time, I was in Finland and to spend time with my friends and classmates from Spain, we played Jackbox and some broswer games. Inspired by that, I decided to create a prototype for a networked quiz game. And for this, GraphQL seemed like the perfect fit. Why? Well, imagine being able to craft flexible and reusable questions based on a rich, relational dataset. Think questions like "Name Pokémon you can find in Route X" or "What's the hidden ability of Pokémon Y?" – With GraphQL such operations will only require a single request and managing that data would be much easier.
But that was just one piece of the puzzle. The other part? Creating a server to handle lobby and room management, allowing players to compete with their friends. It was ambitious, sure, but it felt like the perfect way to show off both my programming experience and my newfound game development skills.
The GraphQL Client
So, what exactly did I build? At its core, it's a GraphQL client tailored for Unity. Here's what it can do:
- Execute GraphQL queries and mutations
- Handle responses
- Cache results for best practices and better performance
- Integrate smoothly with Unity's MonoBehaviour system
So what makes it interesting? A Query Builder that lets you construct GraphQL queries programmatically. Including input arguments and filters, it wasn't easy, but more on that later.
The Tech Stack
For this project, I dived into:
- Unity (of course!)
- C#
- GraphQL (even if it was new tech for me)
- NSubstitute (for mocking in our tests – yeah, we'll get to that too)
Diving into Development
Alright, let's review some code and check of how this GraphQL client came to life. Fair warning: this is not intended to be a full release, but it works. In the repo there's a demo scene that showcases it, here's a little preview of it:
And here's part of the setup:
As you can see only few pokemon names were required to make it work and you can easily change it to use a range of numbers for example.
Setting the Stage
First things first, I had to decide how to structure this package. I knew I wanted something that would play nice with Unity's MonoBehaviour system, but I also needed it to be flexible enough to handle complex GraphQL operations. So, I settled on a core GraphQLUnityClient
class that would do the heavy lifting, with a GraphQLClientBehaviour
MonoBehaviour wrapper for easy integration into Unity scenes.
The Query Builder
Now, let's talk about the Query Builder. This thing... it was like trying to solve a Rubik's cube for the first time. The idea was simple: create a way to build GraphQL queries programmatically. But you know String tokenization is tricky and as much as I'd like to get better with Regex, I wasn't having much success.
Here's a bit of what it can do:
var query = new QueryBuilder()
.Operation("GetPokemon")
.Variable("name", "Pikachu", "String!")
.BeginObject("pokemon(name: $name)")
.Field("id")
.Field("name")
.BeginObject("abilities")
.Field("name")
.EndObject()
.EndObject()
.Build();
Looks nice right? But getting there was a journey. I spent days wrestling with string manipulation, trying to figure out how to nest objects properly, handle variables, and make sure everything was formatted correctly for GraphQL.
I'll be honest, towards the end, I had to call in some help – yep, I leaned on some AI assistance to help smooth out the edges. But hey, knowing when to ask for help is a skill too, isn't it?
Testing: To Mock or Not to Mock
Now, onto testing. This is where things got interesting. After the unit tests, I was all set to create a local server to test against. But when I saw the requirements for deploying a local instance of the PokeAPI (our test GraphQL endpoint), I quickly realized that imight be overkill for our needs.
So, Plan B: mocking. This is when I found NSubstitute, a mocking library for .NET. Now, I'll admit, I'm not usually the biggest fan of mocking tests. There's times where you end coding too much abstractions and tests for the sake of testing without knowing what the real requierements and edge cases would be. But in this case, I think it was the right call.
Setting up NSubstitute in Unity was... an adventure. Unity's testing framework isn't exactly known for playing nice with third-party libraries. But after some visits to forums, some tinkering (and maybe a few choice words that I won't write here), I got it working.
Here's a snippet of what our tests ended up looking like:
[Test]
public void SendQuery_SuccessfulQuery_ReturnsCorrectResponse()
{
// Arrange
var mockWebRequest = Substitute.For<IWebRequest>();
mockWebRequest.SendWebRequestAsync().Returns(Task.FromResult("{\"data\":{\"pokemon\":{\"name\":\"Pikachu\"}}}"));
var client = new GraphQLUnityClient("https://example.com/graphql", null, mockWebRequestFactory);
// Act
var response = await client.SendQueryAsync<PokemonResponse>(query);
// Assert
Assert.AreEqual("Pikachu", response.Data.Pokemon.Name);
}
It's not perfect, but it allowed us to test our client's behavior without needing a full GraphQL server up and running.
Challenges and Unity Quirks
Alright, so we've covered the basics of how we built this GraphQL client. Now, let's dive into some of the issues that almost made me throw this project and go for something, anything simpler.
Challenge #1: The Query Builder
I mentioned earlier that the Query Builder was a bit out of my league, but let me elaborate. The main issue wasn't just stringing together some fields - it was making sure the builder could handle the complexity of GraphQL queries. We're talking nested objects, variables, custom scalars, the works.
Here's a snippet that gave me a couple of headaches:
public QueryBuilder BeginObject(string name)
{
if (_indent == 0)
{
if (_variableDefinitions.Count > 0)
{
_query.Append($"({string.Join(", ", _variableDefinitions)}) ");
}
_query.AppendLine("{");
}
else
{
if (!_isFirstField)
{
_query.AppendLine();
}
}
_query.AppendLine($"{new string(' ', _indent * 2)}{name} {{");
_indent++;
_isFirstField = true;
hasObject = true;
return this;
}
Looks simple enough, right? But making sure this worked with all the other methods, keeping track of indentation, and handling edge cases... let's just say I was made humble, I thought I was more capable than I actually am, after all I was the one yelling "Regex" everytime we had to talk about string validation in class, but this isn't even Regex.
Challenge #2: Making Unity and Async Play Together
Now, here's where things got interesting. GraphQL operations are inherently asynchronous, which is no big deal in a standard C# environment. But Unity? Well, Unity has its own ideas about threading and async operations.
The main issue was that Unity's networking system, UnityWebRequest, doesn't natively support the async/await pattern that makes asynchronous programming so much cleaner in modern C#. But I wasn't about to let that stop me.
Instead of falling back to coroutines (which, let's be honest, can get messy real quick with complex operations), I decided to extend Unity's WebRequest to allow for async/await usage. Here's what that looked like:
using UnityEngine.Networking;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace GraphQL.Unity
{
public static class UnityWebRequestExtensions
{
public static TaskAwaiter GetAwaiter(this UnityWebRequestAsyncOperation asyncOp)
{
var tcs = new TaskCompletionSource<object>();
asyncOp.completed += obj => { tcs.SetResult(null); };
return ((Task)tcs.Task).GetAwaiter();
}
}
}
This little snippet allow us to use async/await with Unity's WebRequest, making our GraphQL operations much cleaner and easier to manage. Here's how we can use it:
var getRequest = UnityWebRequest.Get("http://www.example.com/graphql");
await getRequest.SendWebRequest();
var result = getRequest.downloadHandler.text;
By implementing this extension, we got the best of both worlds: Unity's robust networking system and the clean, easy-to-read async/await pattern from modern C#. It allowed us to perform async operations without blocking the main thread or resorting to callback hell.
I'll admit, I can't take full credit for this idea. I found a similar approach in a GitHub gist and adapted it for our needs.
Unity-Specific Considerations
One thing that really stood out was how different Unity development is from standard C# development. In a regular C# environment, you might create a singleton or static class for your GraphQL client. But in Unity? MonoBehaviours are your bread and butter.
We had to wrap our entire client in a MonoBehaviour:
public class GraphQLClientBehaviour : MonoBehaviour
{
[SerializeField] private string graphQLUrl = "https://api.example.com/graphql";
[SerializeField] private string authToken;
private GraphQLUnityClient _client;
public GraphQLUnityClient Client
{
get
{
if (_client == null)
{
_client = new GraphQLUnityClient(graphQLUrl, authToken, new UnityWebRequestFactory());
}
return _client;
}
}
// ... other methods ...
}
This allowed us to easily attach the client to game objects and configure it through the Unity Inspector. It's a different approach to application architecture, but it fits well with Unity's component-based system.
Looking Ahead: What's Next for Our GraphQL Client?
Now, don't get me wrong - I felt accomplished when the demo scene that uses the client finally worked. But I can't call this a full release. Here are a few things on my to-do list:
- Caching the GraphQL Schema: Right now, we're flying blind when it comes to the structure of the GraphQL API we're querying. By caching the schema, we could provide better error checking and even auto-completion in the Query Builder.
- Separating Filters in the Query Builder: Currently, our filters are tightly coupled with the query fields. I'd like to separate these out, making the Query Builder even more flexible.
- Enhanced Unity Editor Integration: I've seen other community projects with this feature and it was ideal, so I'm looking forward to implement it for our client.
- Performance Optimizations: While our client performs well for most use-cases, there's always room for optimization. Particularly, I'd like to look into more efficient ways of handling large datasets.
Lessons Learned
- Working in New Enviroments: When I started this project, I felt a bit like a fish out of water. My C# knowledge isn't particularly strong, even less when we talk about Unity, but being able to make use of what I learned during my vocational training prove to me that I might have a chance as a videogame dev.
- The Power of Flexibility: When I resumed the original project, I remembered the importance of reusable code. I kept thinking about how the QueryBuilder could be used not just for our quiz game idea, but for all sorts of game mechanics. Need to fetch a player's inventory? GraphQL query. Want to update a character's stats? GraphQL mutation. This might be a game changer in some cases.
- Testing in Game Dev: Before this project, I didn't had many chances to perform tests, so it was good to finally have the chance to do so. But boy, there's so much you can do - dealing with MonoBehaviours, handling async operations, mocking Unity-specific classes - I can only imagine how could that look in games that take years to be developed. It gave me a new appreciation for the importance of testable code, even (especially!) in game development. And it taught me that sometimes, you need to get creative with your testing approaches. Mocking might not be ideal, but when it's that or no tests at all? Mock away.
Wrapping Up
So, what started as a way to showcase my skills turned into something else. Hopefully, I'll have the time to keep working on it. But who knows, I might move it to Godot if I end moving my quiz project to that engine. In any case, I see this project as a step in a good direction, as It gave me the chance recontextualize what I learned in a game development enviroment.