Network Game Problems

Okay, I only wanted to post this as a last resort... but I can't figure anything else out.

I'm trying to create a Multiplayer RTS (Real Time Strategy) game, at the moment it's just Pong. Yes, it's my first ever multiplayer game, however, it's not anywhere near my first game. Anyway, I'm using aBufferedReader to read input lines from the server/client and aPrintWriter to send lines to the server/client. On the BufferedReader, thereadLine() function is good for a turn-based game since it breaks the code (pauses, basicly) until a line has been recieved. However, how can I use it for an RTS? I tried using this:

if (in.ready()) String input = in.readLine();

This removes lag but causes an even bigger problem... the game is out of sync (synchronization).

What can I do to fix this problem?

If you need to take a look at my code, you can download the source-code:

http://www.jamisongames.info/MultiPong/MultiPong.java

Also, I've searched the web for three days and haven't found anything on Multiplayer RTS game development in Java with TCP Sockets. I've even searched this forum... but I can't find a single thing.

Thank you,

Jamison

[1264 byte] By [Jamisona] at [2007-10-2 15:07:53]
# 1

you might want to look into non-blocking IO (the kind that doesnt make your code wait for it). you can find these classes in the [url=http://java.sun.com/j2se/1.4.2/docs/api/java/nio/channels/package-summary.html]java.nio.channels[/url] package.

also, here's an RPG (not RTS) that uses old-school sockets if you're interested: http://laminos.newdawnsoftware.com/index.php?page=downloads

it opens up a local server and people can connect to that. it's not multiplayer, though..

Woogleya at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 2
Thank you very much Woogley. I'll certanly check out non-blocking sockets. They sure look like they need like 50% more code than a regular socket... more work.
Jamisona at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 3
I don't understand... It seems like non-blocking sockets just simplifys multiple clients on a server application. Except, to me it just seems so much more complicated and stupid.
Jamisona at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 4
it is more work/code, but, when you get it all straightened out, it really is worth the effort. sometimes better things aren't the easier things
Woogleya at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 5

Okay, I've changed my code around and it's now using non-blocking sockets... but it's still not in sync and sometimes the collisions don't work on the client's end.

Please take a look at my source-code and tell me if im doing something wrong.

http://www.jamisongames.info/MultiPong/MultiPong.java

Oh and by the way, thanks for directing me to the java.nio.channels package, it's easier reading/writing bytes then strings (like with tradional sockets).

Jamisona at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 6

> I don't understand... It seems like non-blocking

> sockets just simplifys multiple clients on a server

> application. Except, to me it just seems so much more

> complicated and stupid.

IMO, nio is still missing some infrastructure, like asynchornous I/O. Fortunately, this is being JCPed. Unfortunately, it looks like it'll be java7 or later.

> Okay, I've changed my code around and it's now using

> non-blocking sockets... but it's still not in sync

> and sometimes the collisions don't work on the

> client's end.

>

> Please take a look at my source-code and tell me if

> im doing something wrong.

> http://www.jamisongames.info/MultiPong/MultiPong.java

I've checked out the link above, and it isn't using nio classes. Also, out-of-sync is a pretty general description. Usually it's better to say "I expected it to do X but it did Y".

DaanSa at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 7
Oh yeah, because I posted a topic somewhere else and I wanted to show them the code I had when I started with blocking sockets. I should've uploaded a differant filename.
Jamisona at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 8
Well, I can't tell you what's wrong with your non-blocking code without seeing it.BTW, I believe it's considered good practice to post relevant snippets inline instead of linking to source files.
DaanSa at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 9

Sorry, but the code was over 500 lines of code. I just didn't think it would be relevant to post that much code. But if you want me to, than here is my non-blocking code for my game:

import java.io.*;

import java.net.*;

import java.awt.*;

import java.applet.*;

import java.nio.channels.*;

import java.nio.ByteBuffer;

public class MultiPong extends Applet implements Runnable {

Thread t;

int width;

int height;

int fps = 20;

Graphics screen;

Image backBuffer;

boolean host = false;

boolean looking = true;

// Fonts

Font font_big = new Font("Helvetica", Font.BOLD, 24);

Font font_plain = new Font("Helvetica", Font.PLAIN, 12);

// Host Button, Position

int hostButtonX = 0;

int hostButtonY = 0;

int hostButtonW = 166;

int hostButtonH = 35;

// Sockets

SocketChannel client;

ServerSocketChannel server;

int port = 4789;

String error = "";

String player_name = "";

int totalGames = 0;

int roomX = 10;

int roomY = 10;

int roomW = 0;

int roomH = 20;

String gameIPs[] = new String[100];

// Actual game information

int angle = 0;

int speed = 8;

int ballW = 10;

int ballH = 10;

double ballX = 0;

double ballY = 0;

int paddle1X = 0;

int paddle1Y = 0;

int paddle2X = 0;

int paddle2Y = 0;

int paddleW = 14;

int paddleH = 60;

int paddle1Diry = 0;

int paddle2Diry = 0;

int paddleSpeed = 4;

double rad = Math.PI/180;

int score1 = 0;

int score2 = 0;

String message = "";

String lastMessage = "";

boolean sendMsg = false;

// ByteBuffer for reading/writing

ByteBuffer bb;

public boolean mouseDown(Event e, int x, int y) {

if (looking) {

// Host Game

boolean collX = ((x >= hostButtonX) && (x <= hostButtonX+hostButtonW));

boolean collY = ((y >= hostButtonY) && (y <= hostButtonY+hostButtonH));

if (collX && collY) HostGame();

// Join Game

for (int i=0; i<totalGames; i++) {

int roomy = roomY+(22*i);

collX = ((x >= roomX) && (x <= roomX+roomW));

collY = ((y >= roomy) && (y <= roomy+roomH));

if (collX && collY) JoinGame(i);

}

}

return true;

}

public boolean keyDown(Event e, int key) {

if (looking) {

if (key == 8 && player_name.length() > 0) {

repaint();

player_name = player_name.substring(0, player_name.length()-1);

}

else if (player_name.length() < 50) {

if ( (key >= ((int)'a') && key <= ((int)'z')) || (key >= ((int)'A') && key <= ((int)'Z')) || key == ((int)'-') || key == ((int)'_')){

player_name += (char)key;

repaint();

}

}

}

else {

if (client != null) {

if (key == 8 && message.length() > 0) {

message = message.substring(0, message.length()-1);

}

else if (key == e.ENTER && message.length() > 0) {

sendMsg = true;

}

else {

if ( (key >= (int)'a' && key <= (int)'z') || (key >= (int)'A' && key <= (int)'Z') || (key >= (int)'1' && key <= (int)'9') || key == (int)' ' || key == (int)'-' || key == (int)'_' ) message += (char)key;

}

// Move server paddle

if (key == e.UP) {

if (host) {

paddle1Diry = -1;

}

else {

paddle2Diry = -1;

}

}

// Move client paddle

if (key == e.DOWN) {

if (host) {

paddle1Diry = 1;

}

else {

paddle2Diry = 1;

}

}

}

}

return true;

}

public boolean keyUp(Event e, int key) {

if (!looking && client != null) {

if (key == e.UP) {

if (host) {

paddle1Diry = 0;

}

else {

paddle2Diry = 0;

}

}

if (key == e.DOWN) {

if (host) {

paddle1Diry = 0;

}

else {

paddle2Diry = 0;

}

}

}

return true;

}

public void init() {

t = new Thread(this);

t.start();

width = size().width;

height = size().height;

hostButtonY = height-36;

roomW = width-21;

backBuffer = createImage(width, height);

screen = backBuffer.getGraphics();

}

public void run() {

while (true) {

// Waiting for an opponent

if (client == null && server != null && host && !looking) {

try {

if (server.socket().isBound()) {

client = server.accept();

}

if (client != null) {

client.configureBlocking(false);

client.socket().setTcpNoDelay(false);

System.out.println("A client has connected.");

}

} catch(IOException e){}

}

// Server

else if (client != null && host && !looking) {

try {

// Write

bb = ByteBuffer.allocate(20);

bb.putInt(0, paddle1Diry);

bb.putDouble(4, ballX);

bb.putDouble(12, ballY);

client.write(bb);

// Read

bb = ByteBuffer.allocate(4);

client.read(bb);

paddle2Diry = bb.getInt(0);

} catch(IOException e) {}

}

// Client

else if (client != null && !looking) {

try {

// Write

bb = ByteBuffer.allocate(4);

bb.putInt(0, paddle2Diry);

client.write(bb);

// Read

bb = ByteBuffer.allocate(20);

client.read(bb);

paddle1Diry = bb.getInt(0);

ballX = bb.getDouble(4);

ballY = bb.getDouble(12);

} catch(IOException e){}

}

// Refresh and sleep

try {

repaint();

t.sleep(1000/fps);

} catch(InterruptedException e){}

}

}

public void update(Graphics g) {

paint(g);

}

public void paint(Graphics g) {

screen.setColor(Color.black);

screen.fillRect(0, 0, width, height);

if (looking) Lobby();

else {

if (error != "") {

screen.setColor(Color.white);

screen.drawString(error, 10, 15);

}

else {

PlayGame();

}

}

g.drawImage(backBuffer, 0, 0, this);

}

public void stop() {

t.stop();

try {

/*if (out != null) {

out.close();

}

if (in != null) {

in.close();

}*/

if (client != null) {

client.close();

}

if (server != null) {

server.close();

String gameLine = EndGame();

if (gameLine == "CANT_DELETE") {

error = "Unable to delete game session.";

}

}

System.out.println("All open streams have been closed.");

} catch(IOException e){}

}

public void SetupGame() {

// Initialize some variables

angle = 180;

ballX = (width/2)-(ballW/2);

ballY = (height/2)-(ballH/2);

paddle1X = 10;

paddle1Y = (height/2)-(paddleH/2);

paddle2X = (width-paddleW)-10;

paddle2Y = (height/2)-(paddleH/2);

looking = false;

}

public void JoinGame(int id) {

if (player_name.length() > 0) {

try {

client = SocketChannel.open(new InetSocketAddress(gameIPs[id], port));

client.configureBlocking(false);

client.socket().setTcpNoDelay(false);

SetupGame();

System.out.println("Joined game session "+(id+1)+".");

} catch(IOException e) {

error = "Unable to join game session "+(id+1)+".";

System.out.println(error);

System.out.println(e);

}

repaint();

}

}

public String UpdateGame() {

String gameLine = "";

try {

String encodedName = URLEncoder.encode(player_name);

URL url = new URL(getCodeBase(), "updateGame.php?host="+player_name);

URLConnection conn = url.openConnection();

BufferedReader url_in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

gameLine = url_in.readLine();

url_in.close();

} catch(IOException e){}

return gameLine;

}

public String EndGame() {

String gameLine = "";

try {

URL url = new URL(getCodeBase(), "endGame.php");

URLConnection conn = url.openConnection();

BufferedReader url_in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

gameLine = url_in.readLine();

url_in.close();

} catch(IOException e){}

return gameLine;

}

public void HostGame() {

if (player_name.length() > 0) {

host = true;

looking = false;

repaint();

// Create the server

try {

String gameLine = UpdateGame();

if (gameLine.equals("OKAY")) {

server = ServerSocketChannel.open();

server.configureBlocking(false);

server.socket().bind(new InetSocketAddress(port));

SetupGame();

System.out.println("Hosting game on port "+port+" was successful.");

}

else if (gameLine.equals("CANT_CREATE")) {

error = "Unable to create game session. Please try again later.";

}

else if (gameLine.equals("CANT_UPDATE")) {

error = "Unable to update game session. Please try again later.";

} else {

error = "An unknown error has occurred. Please try again later.";

}

} catch(IOException e) {

error = "Unable to host game. Port "+port+" may already be in use.";

}

}

}

public void drawGameRoom(String line, int y) {

screen.setColor(new Color(0, 80, 0));

screen.fillRect(10, 10+(22*(y-1)), width-21, 20);

screen.setColor(Color.green);

screen.drawRect(10, 10+(22*(y-1)), width-21, 20);

// Format the line

int s1 = line.indexOf("+");

int s2 = line.indexOf("+", s1);

String host = line.substring(0, s1);

String serverIP = line.substring(s2+1);

gameIPs[y-1] = serverIP;

int strY = 25+(22*(y-1));

screen.setColor(Color.white);

screen.drawString("Host: "+host+"", 15, strY);

String join = "Join this game now.";

FontMetrics fm = screen.getFontMetrics(screen.getFont());

int strWidth = fm.stringWidth(join);

screen.drawString(join, (width-15)-strWidth, strY);

}

public void Lobby() {

screen.setColor(Color.white);

try {

URL url = new URL(getCodeBase(), "getGames.php");

URLConnection conn = url.openConnection();

BufferedReader url_in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

String gameLine = url_in.readLine();

if (gameLine.equals("NO ROWS")) {

screen.drawString("No game sessions where found, but you can host your own game.", 10, 15);

}

else if (gameLine.equals("OKAY")) {

int i = 1;

totalGames = 0;

while((gameLine = url_in.readLine()) != null) {

drawGameRoom(gameLine, i++);

totalGames++;

}

} else {

screen.drawString("An unknown error occurred. Please try again later.", 10, 15);

}

url_in.close();

screen.setColor(new Color(0, 80, 0));

screen.fillRect(hostButtonX, hostButtonY, hostButtonW, hostButtonH);

screen.setColor(Color.green);

screen.drawRect(hostButtonX, hostButtonY, hostButtonW, hostButtonH);

screen.setFont(font_big);

screen.setColor(Color.white);

screen.drawString("Host a Game", 10, height-10);

screen.setFont(font_plain);

screen.drawString("Player Name: "+player_name, hostButtonX+hostButtonW+10, hostButtonY+(hostButtonH/2)+4);

} catch(IOException e){}

}

public void MoveGame() {

// Move the ball

double dx = speed*Math.cos(angle*rad);

double dy = speed*Math.sin(angle*rad);

ballX += dx;

ballY += dy;

// Bounce against the top edge

if (ballY <= 0) {

ballY = 0;

angle *= -1;

}

// Bounce against the bottom edge

if (ballY >= (height-ballH)) {

ballY = height-ballH;

angle *= -1;

}

// Score 1 for the client

if (ballX <= -ballW) {

score2++;

ResetBall(0);

}

// Score 1 for the server

if (ballX >= width) {

score1++;

ResetBall(180);

}

// Bounce against the server's paddle

boolean collX = ((ballX+ballW >= paddle1X) && (ballX <= paddle1X+paddleW));

boolean collY = ((ballY+ballH >= paddle1Y) && (ballY <= paddle1Y+paddleH));

if (collX && collY) {

double cangle = ((ballY+(ballH/2))-(paddle1Y+(paddleH/2)))*1.2;

angle = 0 + (int)cangle;

}

// Bounce against the client's paddle

collX = ((ballX+ballW >= paddle2X) && (ballX <= paddle2X+paddleW));

collY = ((ballY+ballH >= paddle2Y) && (ballY <= paddle2Y+paddleH));

if (collX && collY) {

double cangle = ((ballY+(ballH/2))-(paddle2Y+(paddleH/2)))*1.2;

angle = 180 - (int)cangle;

}

// Move the paddles

paddle1Y += paddleSpeed*paddle1Diry;

paddle2Y += paddleSpeed*paddle2Diry;

if (paddle1Y < 0) paddle1Y = 0;

if (paddle1Y > height-paddleH) paddle1Y = height-paddleH;

if (paddle2Y < 0) paddle2Y = 0;

if (paddle2Y > height-paddleH) paddle2Y = height-paddleH;

}

public void DrawGame() {

// Draw the game

String scoreString = score1 + " - " + score2;

FontMetrics fm = screen.getFontMetrics();

int strW = fm.stringWidth(scoreString);

screen.drawString(scoreString, (width/2)-(strW/2), 15);

screen.fillOval((int)ballX, (int)ballY, ballW, ballH);

screen.fillRect(paddle1X, paddle1Y, paddleW, paddleH);

screen.fillRect(paddle2X, paddle2Y, paddleW, paddleH);

screen.drawString(lastMessage, 5, height-6);

screen.drawString("Message: "+message, 5, height-21);

}

public void ResetBall(int newAngle) {

angle = newAngle;

ballX = (width/2)-(ballW/2);

ballY = (height/2)-(ballH/2);

}

public void PlayGame() {

screen.setColor(Color.white);

if (host) {

if (client == null) {

UpdateGame();

screen.drawString("Waiting for an opponent to join...", 10, 15);

}

else {

// Server

MoveGame();

DrawGame();

}

}

else {

// Client

MoveGame();

DrawGame();

}

}

}

559 Total Lines of Code

As you may notice, I've changed a few things aound (like temporarily removed the chat system) and made the paddles move smoothly by keyboard events. Hoping it would effect the out-of-sync problem but of course, it made no differance.

I think one problem is that I'm using Thread.sleep() to limit the frames, and well this works good on my slow PC, if I were to play it on a fast PC, it would go at light-speed.

I hope you guys can still help me out. I'd really appreciate it.

Oh and I've looked all over the net for tutorials on a Multiplayer Real Time Strategy game in Java, but I cannot find a single thing. Only tutorials on turn-based games, which the logic doesn't apply to RTS games.

Thank you,

Jamison

Jamisona at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...
# 10

*cough* I said relevant snippets *cough*

It would really help if you could come up with a smaller example which replicates the problem you are experiencing. Also, you still haven't described exactly what you mean by out-of-sync. An "I expected it to do this, but it did that" type of explanation usually works wonders.

I don't have time to dig deep into your code, but the way you're using Thread.sleep() definitely seems bogus. For one thing, it doesn't account for execution time.

DaanSa at 2007-7-13 14:00:46 > top of Java-index,Other Topics,Java Game Development...