Building a Game on Convex
General discussion about building multiplayer games on Convex.
53 Replies
hey! very possible to just use convex for everything. there are quite a few games built on convex.
Hey Jamie! Very possible, perhaps I don't need socket.io
Does convex support multiplayer capabilities?
yep
Such as rooms?
Welp, i'm glad I asked for help
well, you'd have to model this yourself in a table called
rooms
or whateverOh I see
if you want a higher-level game-specific capabilties, perhaps other frameworks have more "built-in"
then yeah, the need is to keep some sanity/consistency about their model vs. your data in convex
Gotcha, will start re-doing my logic to only use Convex. I just found this: https://docs.convex.dev/api/modules/server
Module: server | Convex Developer Hub
Utilities for implementing server-side Convex query and mutation functions.
I can probably re-do it easily following the docs
here's a game built on convex: https://www.convex.dev/ai-town , as is this: https://fast5.live/, and there were a bunch from a hackathon @Web Dev Cody hosted late last year
AI Town
A virtual town where AI characters live, chat and socialize
Wow this looks fantastic
thanks a bunch! I'll go through the convex docs to learn how it's done with this platform
Will let you know if I run into any hiccups
Hey @jamwt I wanted to double check to make sure I am following best practices. I created a room table and it contains a capacity field that contains the number of players in the room, I wish to do a query and get the first record found where capacity is 1, as that means that there is a player that is waiting for another player to join the room. The following is my query so far, but I don't believe it's correct. I also read that indexing is more efficient than using filters: https://docs.convex.dev/auth/database-auth
Storing Users in the Convex Database | Convex Developer Hub
You might want to have a centralized place that stores information about the
hey! yes, this would return exactly one room with a capacity equal to 1, if one exists. otherwise it would return []
a more idiomatic way would be to use the
.first()
method:
which will return the Doc<"rooms"> | null
so you'll either get one room or null rather than an array that never has more than 1 item in itPerfect, thanks! I do get one issue where capacity is not recognized, but I think it's just a typescript issue
ah, yes. the syntax is not quite right there. See https://docs.convex.dev/database/reading-data#equality-conditions
Reading Data | Convex Developer Hub
Query and
which demonstrates using a
q
argument to express an equalityError went away, I made the changes, as per the docs ^^ Thanks again! 😎
no problem. and if you want really fast turnaround on questions as you're learning, sometimes is fastest to use the bot we've built in the docs. just click this icon here (the yellow highlighted one):
and you'll get a chat session with a bot that can take on a lot of standard questions for using convex
Oh wow, that's super useful! Will def give it a try for next questions, that's awesome
There is an outdated repo for fast5 that does a lot of these queries: https://github.com/get-convex/fast5/tree/main/convex e.g. createOrJoinRandom
Thanks Ian! Will check out tonight when back from work, to update you, I got stuck yesterday evening that I plan on trying to find the solution for today.
Essentially, I have two new fields in my room table called player1Ready and player2Ready, it represents a boolean. It sets it to true when a player joins the room. When both players join the room, the fields are updated correctly, however on the client side player2Ready is still false.
The game will start when both these fields are true.
I believe I would have to requery for the room that player2 joined to get the updated data, I have the ID dynamically already and I made a query function that takes in an ID to return back that room, but I'm having trouble seeing where I can call it, as I cannot call it in any functions in my react component.
I think the solution would be for me to learn http with convex (routers, etc.) But I'm not sure if I have the right approach, if that is correct, my next step is to go through the docs regarding that
Back home, I'm posting my code below so far
This part is causing an issue:
``
calling it in an async function in my react component
Hey @Jonny Boi, have you gone through our tutorial? It helps to understand how apps should be modeled using automatically reactive queries and mutations.
If you do need to fetch a query (most of the time, you don't), you can use
const client = useConvex()
and await client.query(...)
, see https://docs.convex.dev/client/react#one-off-queriesConvex React | Convex Developer Hub
Convex React is the client library enabling your React application to interact
Thanks Michal! Will check out, much appreciated 🙂
I think I found a more clear way to indicate the issue, but going more-over docs to see if can find solution
after the query updateRoomCapacity, waitingRoomQuery is still setting/console.logging the old values
Which is why I thought I had to re-fetch it, but I think I had the wrong terminology, since convex already supports real time updates
also, when you set the second of player1Ready / player2Ready, you can do all of the work there, rather than doing it from the client side. Convex mutations are transactional so you won't have a data race where neither think they're the first one to get set
Thanks Ian for the tip, yeah I'm only setting it in the backend for player1Ready and player2Ready. When a room is created, I set player1Ready to true, since a room will only be created if it was not able to find an existing room with capacity of 1.
If a room of capacity 1 is found, it will set player2Ready to true in the backend
since now the room has 2 players to start a game
But even though player2Ready is set to true in the backend, console.log(waitingRoomQuery.player2Ready); still prints the previous value of 1
that's why I thought originally that I may have to re-query it
but the more I think about it, I realize that since convex is real time, the moment that capacity becomes two, it no longer becomes a valid result in the query I am doing, so I'm working on finding a different approach
you can have a query on the game that the current user is in, which will appear once a user is added to a game
Hi Ian, yup that's what I've been doing, but having the same issue
When a player joins a room with no capacity of 1, it will create a new room for the player, as no other players are waiting
Then when the next player joins a room, it will see that there exists a room with capacity 1
So it updates that room with capacity 2, since that player joined the room, as well as sets player2Ready to true and updates the status of the room
These updates are done in the backend, but the client triggers these updates accordingly
However, the issue is that even though the data updated correctly in the database
it still returns the old values when I do a console.log
@Jonny Boi where are you doing the console.log? in your app?
I'm doing it right after the mutation to the waitingRoomQuery. Example:
I wrote another query to actually fetch when both player1Ready states are true (set in backend) and re-working some of the code to see if can get it to work
@sujayakar what's the semantic here regarding read-your-writes on a subscription?
the guarantee is that the mutation's promise will resolve after the queries have rerendered with the new data
if i'm understanding correctly, the code looks like
in this case the
Component
will rerender before the updateRoomCapacity
promise returns -- that's our "read your own writes" guarantee. But since waitingRoomQuery
is a variable from the first render, captured by the useEffect
, waitingRoomQuery.player1Ready
will be printed as false
am i misunderstanding the code flow?Ah that makes sense
I have it setup like the following:
Where joinRoom is triggered from a button click:
I tried in a useEffect as well, but had same behavior
yeah this is just how javascript works
variables don't change out from under you
Do you have a recommendation on how I can get around this?
For example, I'm not sure if I can get the updated value from updateRoomCapacity in a more efficient manner and use that in the console.log example
a few options:
1. the mutation can return a value, so you can do
waitingRoomQuery = await updateRoomCapacity(...)
2. you can refactor to trigger the console.log (or whatever) when the component rerenders with the new query value
it depends what you want to doThanks Lee! I made the changes to my mutation to re-fetch the document id in order to return it and it's working now 🙂
thank you so much everyone!
Hey @lee Another question for you
Does the concept of listening for event changes exist in convex?
Context: I'm making progress with the multiplayer, however, when the second player joins the room, the game starts successfully
but for the first player that joined the room, nothing is updated for them, so trying to think of a way for player 1 to be notified when player 2 joined the room
"listening for event changes" sounds like the same thing as "subscribing to a reactive
useQuery
" to meAs I've been trying with my queries, but no luck so far
player 1 should have a query for "other player in the room with me"
Yup, but I can't seem to get it to update for player 1, even if player 1 is subscribed to a reactive useQuery
Roger, going to re-work to get a query more so like that
if the subscription isn't triggering when it should be, could you share the query and the mutation that should rerun it?
I have a query that gets game's in the ready state after a player joined the room, but your suggestion sounds more optimal
That's the query that gets a game that is ready
but as I copied the code I realize my mistake
enum GameStatus {
WaitingForPlayers = 'Waiting for players',
InProgress = 'In progress',
Completed = 'Completed',
}
that's the enum ^^
I put WaitingForPlayers instead of InProgress
going to correct and see if works
Nevermind same issue after update
will share mutation too
This is the query that gets a room with capacity 1, meaning a player is waiting for another player
and this is the mutation that updates the capacity to 2, it is triggered when a second player enters a room and getWaitingRoom returns a value
I pass in the ID of the getWaitingRoom result in order to update it
using
&&
in a query filter doesn't work. you can use q.and
. or you can use a filter like described in https://stack.convex.dev/complex-filters-in-convexUsing TypeScript to Write Complex Query Filters
There’s a new Convex helper to perform generic TypeScript filters, with the same performance as built-in Convex filters, and unlimited potential.
ahh gotcha, I made the correction
Thanks!
Same issue though, going to try your suggestion and make a players table
This is a good challenge, still working on it, but learning lots, even if don't finish, will still make presentation video and submit 💪