Player Movement - Input + Network Messages

Started by
16 comments, last by Chumble 10 years, 8 months ago

Preface: I am making a 2D networked game where each player controls a single character. Primary goal is getting more comfortable with Java, but I'm having fun making it too.

This topic has two parts. The first deals strictly with Java and implementing the key listeners to move a player.

The second I would like to pretty much detach from Java completely and deal strictly with pseudo-code or simply documentation.

1) Player Input in Java

Currently, my client GUI has a JPanel with addKeyListener(new ClientInput(this));

My ClientInput extends KeyAdapter and has functions for keyPressed, keyReleased, and keyTyped.

First problem is with continuous input. Just like when typing on here, if you hold a letter down, it is subject to your input delay. Press and hold "f" and you see it types one "f", you get a delay, then a steady stream of "f"s. At first I thought it was because I was using keyPressed instead of keyTyped, but it seems Typed does the same thing. How can I deal with this issue? I'm assuming I'll need to use something other than KeyAdapter.

2) Networked Movement Architecture

Preface note: I am using a TCP connection.

I have started everything with the most basic and straightforward implementation. As of now, this is how my client/server function:

a) Client presses "up" - sends "move up" message to server

b) Server moves the player up, sends "move up" message to ALL clients

c) All clients move the player up

As I said, this is the most "Brute force" method I could come up with. That was the idea - start simple, optimize later (as I've been told on several occasions). Well, I had 2 friends connect and we all started running around .. took about 5 seconds before all the clients crashed. It seems the server sent a null network message to everyone for some reason and everyone crashed. I am certain this is related to the high volume of messages as when we all re-connected and moved slowly, it worked. But moving steadily faster over time.. null message again. The server stays up and running, which was weird.

Firstly, does it make sense that 3 players connected to a central server, sending about 30 messages per second and the server with a thread for each client responding with 90 messages per second would cause problems? I know it seems like a lot but I kinda would have thought it could handle much more.

Secondly, I think it's time to look into a couple optimizations. I think if I can figure part 1 out, I can at least slow the player input down a little bit and lessen the number of messages sent. After reading through some other posts, I've gathered that this is a popular architecture:

a) Client presses "up"

b) Client performs client-side collision check. If it fails, end here. Else..

c) Client moves up (1). Sends "Moving up" message to server

d) Server gets "Moving up" message. Sets a "moving" flag to indicate this player is moving up.

e) ??? Update Thread ??? on server - every X ms, check for moving flags, update positions, broadcast updated coordinates to all clients

f) Clients get message from update thread. Update coordinates to what is broadcast.

g) Client releases "up"

h) Client stops locally, sends "Stopped" message to server

i) Server gets "Stopped" message. Clears movement flag.

This would involve adding a new thread to my server, the Update thread from step "e". I guess all it would do is store the "old" value of player positions and check the movement flags for each player (every X ms). If it is set, update the position of the player and send a message to all clients letting them know the player had moved.

(1)* I realize in this step (c), I would need to implement some prediction. Depending on the current latency, this may cause the player to stutter along as the server constantly corrects the player's position. I may end up needing to force a delay in here to make sure the client doesn't update the position too quickly.

This adds three levels of optimization. The first is the client doesn't even bother sending a move message if client-side collision fails. I realize there is a chance for error here - if the client has bad information, the client could fail collision even if there's really nothing there.

The second is limiting the number of messages sent to the server. The client will only send a message to indicate they've started moving (in a direction) or stopped moving (in a direction). The client will probably be moving a lot and updating their direction constantly, but probably not 30 times a second, thus limiting the number of messages sent to the server.

The third is capping the number of messages sent to the client. The update thread will only broadcast a message every X ms instead of on every single client move message. While this might cause a slight delay, I don't think it will affect my game too badly.

Anyone have any thoughts on this? Again, please keep anything pertaining to point 2 as pseudo-code or just steps. Not only does it make it easier for me to read, but it allows me to figure out the syntax myself (and allows non-Java readers to make use of it too). If there's nothing wrong with this.. great! Hopefully this can serve as a resource for others with the same issue. If there is though, please don't hesitate to point it out.

Thanks,

Jeremy

Advertisement

Just going to take a crack at answering #1...

The way I got around this before is by keeping track of a boolean that is set to true on keyPressed, and false on keyReleased. In your "keyPressed" event handler, just set the variable to true, and the keyReleased, set it to false.

Then, in your game's update loop, you simply check whether that key is down and if it is take the appropriate action.

When I implemented it, if I recall correctly, I had a 256 element boolean array, and used the Keycode to directly insert into the array.

For network data you do not send all the input message to all clients you send position updates and interpolate between the previous and current one to make a prediction for future movement. TCP connections have a high overhead as all messages need to arrive and processed in order, miss one and the server has to resend it, UDP is a better solution. In the case of UDP if you get a message that has a older id than the latest you receive you ignore it instead.

Your second outline is more along the lines of how XBL and PSN games work.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

For network data you do not send all the input message to all clients you send position updates and interpolate between the previous and current one to make a prediction for future movement. TCP connections have a high overhead as all messages need to arrive and processed in order, miss one and the server has to resend it, UDP is a better solution. In the case of UDP if you get a message that has a older id than the latest you receive you ignore it instead.

Your second outline is more along the lines of how XBL and PSN games work.

Sorry, I guess I wasn't clear. I am not sending input messages to the clients, I am sending updated coordinates.

Yeah, I know UDP is generally reccomended .. I went through lots and lots of "TCP vs UDP" threads before sticking with TCP...Basically because TCP was easier. I'm starting with easy stuff, getting comfortable with it, then once I understand its limitations, I'm moving on to more complicated optimizations.

Just going to take a crack at answering #1...

The way I got around this before is by keeping track of a boolean that is set to true on keyPressed, and false on keyReleased. In your "keyPressed" event handler, just set the variable to true, and the keyReleased, set it to false.

Then, in your game's update loop, you simply check whether that key is down and if it is take the appropriate action.

When I implemented it, if I recall correctly, I had a 256 element boolean array, and used the Keycode to directly insert into the array.

Hmm.. yeah, that could work, I'll give it a shot. Thanks!

For the input part, check this out:

http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-keyboard-and-mouse-r2439

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

Hmm.. yeah, that could work, I'll give it a shot. Thanks!

I can't speak with JPanel experience but language agnostic generality is that the OS sends keydown events with that delay.

Depending on what you're doing that can either be good or bad, for something like a GUI you'd want to forward the keypress events to your gui and basically treat it normally. For something like character movement you usually want to create a keystate class and have a class repesenting each key and it's state, then update that based on OS events.

I.e. I press letter w down then set the keydown bool for w to true, then I keep it that way until a key up event is received from the listener, then you reverse it. Using that methodology you can just poll your keystate class to see if a key is still down at the moment you check it.

Essentially a long winded explanation of what neonic said.

On the networking issue, I agree with NightCreature that UDP is a better protocol, but that has no real bearing on finding a "best solution" solution to your problem. It might throw up some roadblocks along the way, but that's all.

As far as how the client-server model should work, the most efficient way that I've seen to handle and dispatch client events to a server is to track each player object's states, depending on what type of game you're making this might only be a movement vector, and transmit those to the server only when they change. So, to break this down a little further:

(Assume only one player is actually playing and everyone else is watching, just for ease of typing this out)

*Player presses the left arrow key, movement vector is now (-5, 0).

*Vector (-5,0) is pushed to the server, stored player vector is updated.

*Player position is updated and transmitted to all connected clients.

*Player continues press the left arrow key, movement vector remains (-5,0)

*No data is transmitted to server

*Server uses stored player vector to update position and transmits to all connected clients.

*Player releases left arrow key, movement vector is now (0,0)

... and so on.

Every now and then, you'll also need to do a full state check for each connected client, ensuring that the server's calculated position for that player is correct, since any dropped packets could result in a miscalculation.

And as for your first question, neonic's solution is absolutely the way to go. My input manager classes always have a private boolean array with 256 indexes. It uses minimal memory and means that you don't need any custom handlers for each key.

Thanks everyone for the replies!

@Satharis, I accidentally pressed the down arrow on your comment instead of the up arrow and it doesn't seem to let me change it :(


*Player presses the left arrow key, movement vector is now (-5, 0).
*Vector (-5,0) is pushed to the server, stored player vector is updated.

Maybe I'm over-thinking it but I think I'd rather just say "Move + <Direction>" rather than pass in an actual value. This way I think it helps prevent cheating maybe?


*Player continues press the left arrow key, movement vector remains (-5,0)
*No data is transmitted to server
*Server uses stored player vector to update position and transmits to all connected clients.

If I do a check server side and confirm that the player did not change direction/speed, do I even need to send the updated position to the clients? If the clients all know a player is moving, I can use some client-side prediction to show their position.. I guess that could get out of sync pretty quickly. I wonder how often I would need to update the clients with the position?


Maybe I'm over-thinking it but I think I'd rather just say "Move + " rather than pass in an actual value. This way I think it helps prevent cheating maybe?

This is a good idea. You should still pass data to the server only when there's a delta, but you're right, this should make it harder to cheat.


If I do a check server side and confirm that the player did not change direction/speed, do I even need to send the updated position to the clients? If the clients all know a player is moving, I can use some client-side prediction to show their position.. I guess that could get out of sync pretty quickly. I wonder how often I would need to update the clients with the position?

You'll want to do the server side check on a very limited basis. The example that I gave was probably a little too limited, but when there are multiple players at once, each client will send any input changes to the server while the server is busy doing all of the physics calculations and pushing updated positional "snapshots" of the scene at set intervals of a few milliseconds. The snapshots should contain all information related to position for each non-static object so that the clients are synced to the server's version of the game. The server side check is to ensure that the server's version is accurate and doesn't need to be done nearly as often, once every one to two seconds should keep any interpolation error correction from being very noticeable.


Maybe I'm over-thinking it but I think I'd rather just say "Move + " rather than pass in an actual value. This way I think it helps prevent cheating maybe?

Is your player, and all other animate objects in the game only going to move at one speed? Will you not have a speed buff, or a slow debuff for the players? Keep it simple, and use the actual velocities the object is moving at (bullets match this too).

Yes, your clients should also interpolate the other players positions every frame based on the last known information (position and velocity), but your server should still send a full game state every "so often" (how often can be determine based on how frenetic your game will be, how many players there are, how much traffic there already will be, etc.).

This also means you should have time-stamped messages, so clients know, at "this point in time" player X was at Y location, moving at this Z velocity, so you can make sure the player is at the correct state at your clients time.

Good luck, and have fun!

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

This topic is closed to new replies.

Advertisement