I am a freshman student taking up Computer Science and as a project, we are making a game. I know a lot about java, so please don’t treat me as a noob.
I have finished almost all the parts of my game except the networking, we were required to implement native Java networking on our game. I have successfully done it but the lag is so severe, that it can only accept a single client.
My Multiplayer Client application looks like this.
init -> make 1 connection write thread and 1 connection read thread -> init nodes and objects
update -> connection read thread reads the current state of all characters from the sent output by the server -> update character and dummy characters representing the characters on the server -> accept input -> send input to server
The Server application looks like this
init -> get connections and start a write and read thread for every connection if the connections > 1
update->gets all input from write thread -> simulate -> output all data simulated(current state)
Here is my code if you need it:
GameServerApp
[java]
public class GameServerApp extends SimpleApplication{
GameServer gameServer;
BulletAppState bulletAppState;
@Override
public void simpleInitApp() {
gameServer = new GameServer(this);
System.out.println(gameServer.getIp());
}
@Override
public void simpleUpdate(float tpf){
gameServer.update();
}
public static void main(String[] args){
GameServerApp app = new GameServerApp();
AppSettings newSettings = new AppSettings(true);
newSettings.setFrameRate(30);
app.setSettings(newSettings);
//debug
app.start();
app.setPauseOnLostFocus(false);
//app.start(JmeContext.Type.Headless);
}
@Override
public void destroy(){
super.destroy();
gameServer.stop();
}
public void initPhysics(){
bulletAppState = new BulletAppState();
getStateManager().attach(bulletAppState);
}
public void initStage(){
Spatial stage = assetManager.loadModel("Models/Stage.j3o");
rootNode.attachChild(stage);
bulletAppState.getPhysicsSpace().addAll(stage);
bulletAppState.getPhysicsSpace().setGravity(Vector3f.UNIT_Y.mult(-240f));
}
public BulletAppState getBulletAppState(){
return bulletAppState;
}
}
[/java]
GameServer
[java]
public class GameServer {
GameServerApp gameServerApp;
ConnectionAcceptorThread cat;
ArrayList wConnections = new ArrayList();
ArrayList rConnections = new ArrayList();
ArrayList sockets = new ArrayList();
ArrayList avatars = new ArrayList();
ArrayList avatarNodes = new ArrayList();
ServerSocket ss;
boolean preGame, onGame;
String ip;
public GameServer(GameServerApp gameServerApp){
this(gameServerApp,2048);
}
public GameServer(GameServerApp gameServerApp, int portNumber){
this.gameServerApp = gameServerApp;
try {
ss = new ServerSocket(portNumber);
ip = InetAddress.getLocalHost().getHostAddress();
preGame = true;
} catch (IOException ex) {
Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void update(){
if(preGame){
preGameUpdate();
}
if(onGame){
gamePreTickUpdate();
gameTickUpdate();
}
}
public boolean startGame(){
if(sockets.size() >= 1){
gameServerApp.initPhysics();
gameServerApp.initStage();
for(int i = 0 ; i < sockets.size(); i++){
wConnections.add(new ConnectionWriteThread(sockets.get(i)));
wConnections.get(i).start();
wConnections.get(i).send(i+"s " + sockets.size());
rConnections.add(new ConnectionReadThread(sockets.get(i)));
rConnections.get(i).start();
avatars.add(new AvatarStatus());
avatarNodes.add(new AvatarServerNode("Player" + (i+1),gameServerApp,avatars.get(i)));
gameServerApp.getBulletAppState().getPhysicsSpace().add(avatarNodes.get(i).getNode());
gameServerApp.getRootNode().attachChild(avatarNodes.get(i).getNode());
}
preGame = false;
onGame = true;
cat.destroy();
cat = null;
return true;
}
return false;
}
public void preGameUpdate(){
if(cat == null){
cat = new ConnectionAcceptorThread(ss);
cat.start();
}
if(!cat.getSockets().isEmpty()){
sockets.add(cat.getSockets().remove(0));
}
//debug
if(!sockets.isEmpty()){
startGame();
}
}
private void gamePreTickUpdate() {
for(int i = 0; i < rConnections.size() ; i++){
ConnectionReadThread ct = rConnections.get(i);
System.out.println("Player "+ i + " size: " +ct.getInputs().size());
while(!ct.getInputs().isEmpty()){
String temp = ct.getInputs().remove(0);
if(temp.equals("") ){
continue;
} else {
if(temp.charAt(0) == 'p'){
String[] tempArr = temp.split(" ");
avatars.get(i).setPosition(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3]));
}else if(temp.charAt(0) == 'w'){
String[] tempArr = temp.split(" ");
avatars.get(i).setWalkDir(new Vector3f(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3])));
}else if(temp.charAt(0) == 'v'){
String[] tempArr = temp.split(" ");
avatars.get(i).setViewDir(new Vector3f(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3])));
}else if(temp.charAt(0) == 'a'){
avatars.get(i).setCurrAnim((temp.split(" "))[1]);
}
}
}
}
}
private void gameTickUpdate(){
for(int i = 0; i < wConnections.size(); i++){
for(int j = 0; j < avatars.size(); j++){
if(i!=j){
Vector3f tempWalkDir = avatars.get(j).getWalkDir();
Vector3f tempViewDir = avatars.get(j).getViewDir();
String anim = avatars.get(j).getCurrAnim();
wConnections.get(i).send(j + "w " + tempWalkDir.getX() + " " + tempWalkDir.getY() + " " + tempWalkDir.getZ() );
wConnections.get(i).send(j + "v " + tempViewDir.getX() + " " + tempViewDir.getY() + " " + tempViewDir.getZ() );
wConnections.get(i).send(j + "a " + anim);
}
}
}
}
public ArrayList getAvatars(){
return avatars;
}
public String getIp(){
return ip;
}
public boolean isCatAlive(){
if(cat == null) return false;
return true;
}
public void stop(){
for(ConnectionReadThread crt : rConnections){
crt.destroy();
}
for(ConnectionWriteThread cwt : wConnections){
cwt.destroy();
}
}
}
[/java]
MPlayerGameAppState
[java]
public class MPlayerGameAppState extends AbstractAppState {
static GameApp gameApp;
Node rootNode;
Node guiNode;
CameraNode camNode;
AssetManager assetManager;
AudioRenderer audioRenderer;
InputManager inputManager;
Renderer renderer;
ViewPort viewPort;
Camera cam;
BulletAppState bulletAppState;
Node avatar;
Spatial avatar_body;
BetterCharacterControl avatar_bcc;
AvatarClientControl avatar_ctrl;
FilterPostProcessor fpp;
DepthOfFieldFilter dofFilter;
ConnectionReadThread rConnection;
ConnectionWriteThread wConnection;
AvatarStatus[] avatars;
ArrayList avatarNodes = new ArrayList();
int avatarSize;
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
gameApp = (GameApp)app;
gameApp.setupConn("127.0.0.1", 2048);
connect();
initGame();
initPhysics();
initAvatar();
initStage();
// setupFilters();
//TODO: initialize your AppState, e.g. attach spatials to rootNode
//this is called on the OpenGL thread after the AppState has been attached
}
@Override
public void update(float tpf) {
gamePreTickUpdate();
rootNode.updateLogicalState(tpf);
rootNode.updateGeometricState();
guiNode.updateLogicalState(tpf);
guiNode.updateGeometricState();
//TODO: implement behavior during runtime
}
@Override
public void cleanup() {
super.cleanup();
rConnection.destroy();
wConnection.destroy();
rootNode.detachChildNamed("Avatar");
//TODO: clean up what you initialized in the initialize method,
//e.g. remove all spatials from rootNode
//this is called on the OpenGL thread after the AppState has been detached
}
public void initGame(){
rootNode = gameApp.getRootNode();
guiNode = gameApp.getGuiNode();
assetManager = gameApp.getAssetManager();
audioRenderer = gameApp.getAudioRenderer();
inputManager = gameApp.getInputManager();
viewPort = gameApp.getViewPort();
renderer = gameApp.getRenderer();
cam = gameApp.getCamera();
}
public void initPhysics(){
bulletAppState = new BulletAppState();
gameApp.getStateManager().attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
}
public void initAvatar(){
avatar = new Node("Avatar");
avatar_body = assetManager.loadModel("Models/Char.j3o");
TangentBinormalGenerator.generate(avatar_body);
avatar_bcc = new BetterCharacterControl(2f, 8f, 2f);
avatar.setLocalTranslation(new Vector3f(0f,-.5f,0f));
avatar_ctrl = new AvatarClientControl(null, inputManager, cam, avatar_body, rConnection,
wConnection);
avatar_ctrl.setSpeed(20f);
avatar.addControl(avatar_bcc);
avatar.addControl(avatar_ctrl);
avatar.attachChild(avatar_body);
bulletAppState.getPhysicsSpace().add(avatar);
rootNode.attachChild(avatar);
}
public void initStage(){
Spatial stage = assetManager.loadModel("Models/Stage.j3o");
rootNode.attachChild(stage);
bulletAppState.getPhysicsSpace().addAll(stage);
bulletAppState.getPhysicsSpace().setGravity(Vector3f.UNIT_Y.mult(-240f));
}
private void setupFilters() {
fpp = new FilterPostProcessor(assetManager);
if (renderer.getCaps().contains(Caps.GLSL120)){
CartoonEdgeProcessor cartoonEdgeProcessor = new CartoonEdgeProcessor();
viewPort.addProcessor(cartoonEdgeProcessor);
CartoonEdgeFilter cartoonEdgeFilter = new CartoonEdgeFilter();
fpp.addFilter(cartoonEdgeFilter);
MotionBlurFilter motionBlurFilter = new MotionBlurFilter();
fpp.addFilter(motionBlurFilter);
dofFilter = new DepthOfFieldFilter();
dofFilter.setFocusDistance(0);
dofFilter.setFocusRange(40);
dofFilter.setBlurScale(1.4f);
fpp.addFilter(dofFilter);
}
viewPort.addProcessor(fpp);
}
private void connect() {
try {
Socket s = new Socket(gameApp.getHost(),gameApp.getHostPort());
rConnection = new ConnectionReadThread(s);
wConnection = new ConnectionWriteThread(s);
rConnection.start();
} catch (UnknownHostException ex) {
Logger.getLogger(MPlayerGameAppState.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MPlayerGameAppState.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void gamePreTickUpdate() {
while(!rConnection.getInputs().isEmpty()){
String temp = rConnection.getInputs().remove(0);
if(temp.equals("") || temp.equals("null") || temp == null){
continue;
} else {
int i = Integer.parseInt(temp.charAt(0)+ "");
if(temp.charAt(1) == 'p'){
String[] tempArr = temp.split(" ");
avatars[i].setPosition(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3]));
}else if(temp.charAt(1) == 'w'){
String[] tempArr = temp.split(" ");
avatars[i].setWalkDir(new Vector3f(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3])));
}else if(temp.charAt(1) == 'v'){
String[] tempArr = temp.split(" ");
avatars[i].setViewDir(new Vector3f(Float.parseFloat(tempArr[1]),
Float.parseFloat(tempArr[2]),
Float.parseFloat(tempArr[3])));
}else if(temp.charAt(1) == 'a'){
avatars[i].setCurrAnim((temp.split(" "))[1]);
}else if(temp.charAt(1) == 's'){
avatarSize = Integer.parseInt(temp.split(" ")[1]);
int counter = 0;
avatars = new AvatarStatus[avatarSize];
for(int j = 0; j < avatarSize; j++){
if(i!=j){
avatars[j] = new AvatarStatus();
avatarNodes.add(new AvatarClientNode("Player" + (j+1),gameApp,avatars[j]));
bulletAppState.getPhysicsSpace().add(avatarNodes.get(counter).getNode());
rootNode.attachChild(avatarNodes.get(counter).getNode());
counter++;
}
}
wConnection.clear();
wConnection.start();
}
}
}
}
}
[/java]
here is my read and write threads:
ConnectionWriteThread
[java]
public class ConnectionWriteThread extends Thread {
Socket s;
private boolean stop, send;
PrintWriter out;
ArrayList outputCache = new ArrayList();
public ConnectionWriteThread(Socket s){
this.s = s;
try {
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(this.s.getOutputStream(),"UTF-8")),true);
} catch (IOException ex) {
Logger.getLogger(ConnectionWriteThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void run() {
while(true){
if(stop) break;
if(send){
while(!outputCache.isEmpty()){
out.println(outputCache.remove(0));
if(outputCache.isEmpty())send = false;
}
}
}
}
public void destroy(){
out.close();
stop = true;
}
public void send(String s){
outputCache.add(s);
send = true;
}
public void clear(){
outputCache.clear();
}
}
[/java]
ConnectionReadThread
[java]
public class ConnectionReadThread extends Thread {
Socket s;
private boolean stop, send;
BufferedReader in;
ArrayList inputCache = new ArrayList();
public ConnectionReadThread(Socket s){
this.s = s;
try {
in = new BufferedReader(new InputStreamReader(new BufferedInputStream(this.s.getInputStream())));
} catch (IOException ex) {
Logger.getLogger(ConnectionReadThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void run() {
while(true){
if(stop) break;
try {
inputCache.add(in.readLine());
} catch (IOException ex) {
Logger.getLogger(ConnectionReadThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void destroy(){
try {
in.close();
} catch (IOException ex) {
Logger.getLogger(ConnectionReadThread.class.getName()).log(Level.SEVERE, null, ex);
}
stop = true;
}
public ArrayList getInputs(){
return inputCache;
}
}
[/java]
Please drop any comments regarding my code.