←back to thread

95 points BSTRhino | 2 comments | | HN request time: 0.002s | source

For the past 3 years, I've been creating a new 2D game programming language where the multiplayer is completely automatic. The idea is that someone who doesn't even know what a "remote procedure call" is can make a multiplayer game by just setting `maxHumanPlayers=5` and it "just works". The trick is the whole game simulation, including all the concurrent threads, can be executed deterministically and snapshotted for rollback netcode.

Normally when coding multiplayer you have to worry about following "the rules of multiplayer" like avoiding non-determinism, or not modifying entities your client has no authority over, but all that is just way too hard for someone who just wants to get straight into making games. So my idea was that if we put multiplayer into the fabric of the programming language, below all of your code, we can make the entire language multiplayer-safe. In Easel the entire world is hermetically sealed - there is nothing you can do to break multiplayer, which means it suits someone who just wants to make games and not learn all about networking. I've had people make multiplayer games on their first day of coding with Easel because you basically cannot go wrong.

There were so many other interesting things that went into this project. It's written in Rust and compiled to WebAssembly because I think that the zero-download nature of the web is a better way of getting many people together into multiplayer games. The networking is done by relaying peer-to-peer connections through Cloudflare Calls, which means Cloudflare collates the messages and reduces the bandwidth requirements for the clients so games can have more players.

I also took inspiration from my experience React when creating this language, here's how you would make a ship change color from green to red as it loses health:

`with Health { ImageSprite(@ship.svg, color=(Health / MaxHealth).BlendHue(#ff6600, #66ff00)) }`

There is a lot of hidden magic that makes the code snippet above work - it creates a async coroutine that loops each time Health sends a signal, and the ImageSprite has an implicit ID assigned by the compiler so it knows which one to update each time around the loop. All of this lets you work at a higher level of abstraction and, in my opinion, make code that is easier to understand.

Speaking of async coroutines, my belief is that they don't get used enough in other game engines because their lifetimes are not tied to anything - you have this danger where they can outlive their entities and crash your game. In Easel each async task lives and dies with its entity, which is why we call them behaviors. Clear lifetime semantics makes it safe to use async tasks everywhere in Easel, which is why Easel games often consist of thousands of concurrently-executing behaviors. In my opinion, this untangles your code and makes it easier to understand.

That's just the beginning, there is even more to talk about, it has been a long journey these past 3 years, but I will stop there for now! I hope that, even for those people who don't care about the multiplayer capabilities of Easel, they just find it an interesting proposal of how a next-generation game programming language could work.

The Editor runs in your web browser and is free to play around with, so I would love to see more people try out making some games! Click the "Try it out" button to open the Sample Project and see if you can change the code to achieve the suggested tasks listed in the README.

1. chrisdirkis ◴[] No.44002058[source]
A few quick q's, since I'm working on a game with a hand-rolled rollback impl (we have state copying, so we can do some nice tricks):

- Is there anywhere we can follow you about the clock-sync trick? I'd definitely love to be notified - On the adaptive delay, are there gameplay or rollback engine implications to variable delays? Seems somewhat "unfair" for a player to be penalised for poor network conditions, but maybe it's better than them teleporting around everywhere.

Good luck with the project! I'll hopefully have a fiddle around with it soon :)

replies(1): >>44079985 #
2. BSTRhino ◴[] No.44079985[source]
Sorry for the slow reply!

I think maybe the Twitter might be the best place to follow for blog updates: https://x.com/MadeWithEasel - I will definitely be posting about every blog there once I get there.

On the adaptive delays: I have a multiplayer game which gets about 100 players a day and it has been interesting seeing how they have all reacted to various iterations of the netcode. The overarching thing I've learned is that latency is quite psychological, it's the difference between expectation and reality that matters. In other words, high latency doesn't necessary mean an unhappy player, if they are expecting the high latency.

First though, the amount of rollback is limited to what the player's device is able to handle (there's yet another algorithm I've made for collecting statistics and estimating this!) Some devices cannot handle any rollback at all and so unfortunately sometimes rollback netcode isn't solving anything for those players. I think these are the cases where the adaptive latency is more important.

We sometimes would have games where we have 3 people from the US playing happily together, and then 15 minutes later 1 person from South Korea would join, and the latency would jump up dramatically for everyone. The US players would feel the difference and become unhappy. The simplest way to explain what Easel does now is it places the server at the weighted-average midpoint between all the players. So in this case, you can imagine that the server started in the US, and the moved 1/4 of the way towards South Korea (since 1 out of 4 players are in South Korea). I have found this to be the most fair and the key thing is the players find it to be fair. It matches how they think the latency should be apportioned and so they are okay with it.

Recently though I added a feature which splits the world up into regions (it's more complicated than it sounds because the regions flex around the players a bit, see https://easel.games/docs/learn/multiplayer/regions). By default, you only play with players near your region, but you can switch into Roaming Mode and play with people all over the world. The trick here is, when the player chooses Roaming Mode, they are explicitly choosing high latency, which changes their expectations. When they get high latency, they expect it, and so they are happy. The funny thing is, the algorithm used to automatically assign them the same high latency in these situations but players didn't like it because they didn't have the choice.