Thursday, June 28, 2018

Flock of Dogs Online

So there's not much to show, literally, for progress on game. I'm just redoing everything with networking!

There's basically been three things to solve. How to connect players' computers through the internet, how to sync things between those computers during gameplay, and how to sync computers that join late.

I've opted to use the Photon Unity Networking (PUN) plug-in. There are a few reasons for this. (a) It's been around for several years and has a decent sized community and good documentation. (b) It provides servers (read: computers that are always on and waiting for people to access them), instead of relying on connecting players diretly to each other, because that fails a lot because of firewalls and/or routers, or so I've read. (c) Those servers are free for up to 10 concurrent users, which means it will be free while I'm still developing (this pricing scheme is common to other server providers). (d) When you do start having to pay, it's a per user basis, rather than per data. This is good for me, because each user will be passing around a lot of data (contrast this with turn based mobile apps, like Words With Friends, where each user only passes a very tiny amount of data every minute or so versus a persistant action game where you're constaly syncing positions, triggering animations and sound cues, syncing large game states.)

So PUN is fun. Following their tutorial is pretty easy and solves the first big problem of connecting players into the same room. The method is to register your game with a unique id with PUN's database. Then, in game, use PUN's API to call some connection methods that connects the local computer to PUN's server (or lobby). Then PUN recognizes your application's id and can then lump you together with other PUN connected computers matching your id. So if you and I both run Flock of Dogs, connect to PUN's server, then we can initiate a game together. Currently, that means one of us creates a game with a game name and other person types that game name in and connects to it. Then Flock of Dogs Online begins. Yayay.

Then comes the second big problem. Both players are in the same 'game', but all that means is that they're both running copies of the same application (Flock of Dogs) on their local computer and they also have a way of communicating with each other via the internet (!) which is facilitated by their connection to PUN's server. So a connected game rather is just two copies of the game running concurrently with behavior synced by passing data on the connection, which means I have to write code that says 'player 1 moved left a few inches' and 'player 2 just got on the blue dog' and 'the whale died' and send that in a message to the connected computers. Now, you want to be careful about how many/how frequently you send these messages over the connection because (a) it takes many milliseconds to send the data from your computer, to PUN, to the other players' computers. (b) I think there's still a cap on data / user from PUN's pricing scheme, so I might eventually hit that if I pay no attention.

So I read stuff about how networking works at a low level and that was interesting, but I'll just tell you the way that PUN implements stuff.

So the first way to send info using PUN that I learned was through Remote Procedure Call (RPC). You add a tag to a function in your code and when you call that function through using PUN's RPC calling method, it will alert the other connected computers to also run the matching method. So when you press the shoot button, instead of just having a normal bit of code for that you have an RPC tagged bit of code and then PUN knows to tell the other computers to have their copy of your player also shoot. So exacmples of PUN use are like 'player 2 just got on the blue dog' and 'the whale died'.

The second way to send info through Photon serialization stream. This is a function you implement on certain game objects that you want to send information about. You also have to define the ownership of the object as either locally owned or owned by another computer on the network. Then you define the function in two parts: if the object is owned locally (by you) or owned remotely (by another connected computer). In the first case, you send serialized data. That is, you send data that has to be serialized (or serializable) which means reduced to bytes. You can't just send your objects, like an instance of the dog class or an instance of the whale class. You have to break it down into integers, floats, or bools. Vector3s also work, because there are built in functions that automatically serialize Vector3s. So the use-case for this is for syncing the position of a flying dog or a flying whale. PUN will automatically call the OnPhotonSerialization () function many times a second. So you send the position of the dog you're in control over every fraction of a second. And in this case, since you care more about the current position of the dog on your friends' computers as it is on your computer more than you care that your friends see your dog travel through every position you did, you can tell PUN to send the data as fast as possible and don't wait to verify if they were received. That's something that RPCs do by default, which is to resend the call, if it never gets acknowledged by the remote computer. The message not getting through is a possibility every time you send something over the internet. If you turn on 'unreliable' in your Photon serialization settings for that gameobject, then, yes, it won't be reliable, but it will be faster. And if you're sending the position of your dog many times per second, then hopefully your friend will regularly receive the data that is only a fraction of second old, instead of having to wait for any resends sometimes.

So anyway, that explains the sending part of the OnPhotonSerialization() function. On gameobjects where you're not the owner, Photon will the OnPhotonSerialization() function object with the data that was sent (e.g. a Vector3 that represents the position of this dog, as sent by its owner a fraction of a second ago). So inside the function you check if you're not the owner. If you're not the owner, then you read the data passed in and cast it as a Vector2 and set your dog's position to that spot. Cool.

Now, there's details I'm leaving out, but I'm getting antsy. But generally speaking, RPCs and serialization streams is how I solve the moment-to-moment synchronization of gameplay.

Now, how to handle joining a game late? Once you're in the game, sure, every moment that Photon streams you data you'll update your dogs's and yoru whale's position. Every time an RPC is called to let you know that your buddy in Nebraska fired a bullet or did a flip or landed his dog then you'll know. But how do you know how many hearts that dog has when you join? How do you what upgrades the whale has gotten? How do you know how much water or health or solar energy the whale has stored up? Or what items in its belly? Or if a particular player has any harpoons? Essentially, how do you know the state of the game?

(Notice this is essentially the same question as loading a save game file, and I've never implemented game saves).

So this is where using PUN's RaiseEvent() function comes to the rescue and it's fantastic. This is most similar to low level networking and using it feels satisfying in a way I rarely feel when I program. The way RaiseEvent works is that when you call it and pass it some info, it createse and sends an 'event' over the network. And you define some special functions that lie in wait or 'listen' for network events. The low level part is that the info you pass in an event is usually in the form of an array of bytes. Which means that the listener function will receive just that array of bytes and not much else. Maybe the network identity of the sender and an event code (in the form an byte). And this is cool. This feels like big boy networking. I'm like only one step away from having to parse actual 1s and 0s. Anyway. I'm using the RaiseEvent() to send arrays of bytes representing the state of different game objects.

Now, some game objects, you don't need to sync, because once they're in the game, they don't do anything. That would be for all the static background objects. Those will spawn the same on any computer, whether they join early or late. But for everything else. Dogs, players, whales, enemies, destructible/interactable environment, enemies, NPCs, all those are subject to change. So I defined a GetState() function on each of them which returns a byte array. I have to custom define
the byte array for each of those objects. For instance, for a dog, the bytes in the byte array from byte[6] to byte[17], which is 16 bytes, is where I store 3 floats, (4 bytes per float). Those floats are the red, green, blue valus of the dog's color. I made the ReadState() function which takes in a byte array and it knows to read byte[6] through byte[17] and then convert them back into floats and then uses those floats as the RGB of a new color and sets the dog's color to that color. So after I defined a GetState() and a ReadState() for all those kinda of game objects I mentioned above, the entire game state can converted into a byte array and then read back. So, I'm in the process of doing that. But I went ahead and set up the state synchronization of some stuff using the RaiseEvents.

That involves when a new computer joins the game, it asks the computer who created the game to give them the state of the game to the best of their ability. I've set this up to initiate sending a sequence of RaiseEvent calls that the newly joined computer waits to hear about. This sequence is just the game creator going through game object by game object, calling their GetState() function, then taking that byte array and raising an event and sending that byte array. The new computer listening for an event, taking the  byte array, checking the first couple bytes to see if it's the state of a dog, or a whale, or an enemy, and then based on those first few bytes, it will know what to expect in the rest of the byte array.

And voila. Anyway. Today's World Cup games are well over and therefore this blog post is too.



Tuesday, June 12, 2018

Party On

After PAX, I worked on the opening of Flock of Dogs some more in anticipation of submitting to BFIG. Then I made a big decision. I'm going to attempt to add online multiplayer to Flock of Dogs. Now, I blogged about this like a year ago and decided not to do it then. I was warned that it's hard, takes a long time, and that since this is my first game, it's inadvisable (which, if you ask me, is kinda circular).

The reasons in favor of doing it have become clearer. A quick story.

When you want to fix bugs in your game, oftentimes, the first step is to figure out the conditions necessary to reproduce the bug. Once you've got reproducibility, you should have a pretty clear idea of where in your code the error is hiding.

Now, while this is unsurprising for any of my hardcore fans who've played endless hours of Flock of Dogs, at PAX, many bugs in my game were encountered. Since like I'm at a point where I want my game's demo to be playable (a) without my assistanc or presence and (b) to not crash and (c) to not suck, I want to smash bugs like never before! So. Time to reproduce. And, some might say this could have been forseen, but simulating the input of, say, 6 players, simultaneously, was too hard for little old me. I had anticipated this being a fine opportunity of having friends over, to relieve my great isolation, and, in truth, I've had a very good time when friends and family have demo'd my game. But I have fewer friends in Nashville (and now Long Beach) than in Boston. And the friends I've left behind (or the friends that left me)....I realized that (a) asking them to playtest without me my unfinished co-op game that crashes and glitches all the time, figure out how to recreate that behavior, and report back, would be tough, (b) proposing that they invite their friends over, have enough controllers for their friends, provide chips and salsa and drinks, to playtest my unfinished co-op game that crashes and glitches, figure out how to recreate that behavior, and report back to me, would be hard to organize and (c) asking them to do this like weekly or something....lol.

I like have 3 friends that even play video games. Like..that's partly why I'm making this game is to get my friends to play video games with me. And yeah, the dream was to have all my friends over and we all play together, but I'm a grown up now. And being grown up means that you go on Facaebook and realize all your once best friends live in Boston, Beverly, New York, Coeur D'Alene, Denver, Willits, San Francisco, LA, Long Beach, Las Vegas, Dallas, Nashville, Oxford.

So let's break it down.

PROS:
- provides means of more effective playtesting
- perhaps only way to regularly get 2+ people playing
- only way I'll ever be able to play Flock of Dogs with my little church buddies I grew up with, Ian, Daniel, and Joey, or my old roommates Jon, Johnny, and Jonathan, or my soccer buddies, Kevin, Bedig, Fithian, Lou, Sam, Monty, Oak, Brownie, and Matt T., or with Dave, Salem, and Matt B., or with Bill, Ted, Sarah, Sophie, and Matt S., all my other, leser friends ahahahaha.
- INCREASES THE POTENTIAL MARKET FOR FLOCK OF DOGS BY LIKE A MILLION
- much easier to build an online community
- would develop a marketable skill, you know, if I have to get a job some day

CONS:
- supposed to be really hard
- supposed to take a long time

So I'm giving it the old college try.

(Oh, I don't know what I meant by a quick story except that at PAX there were bugs and then I couldn't recreate them by myself after.)