Skip to content
Snippets Groups Projects
Commit 7a47b613 authored by Sander Østrem Fagernes's avatar Sander Østrem Fagernes
Browse files

Resolve "Connect FindGameController to backend"

parent 26336913
No related branches found
No related tags found
1 merge request!34Resolve "Connect FindGameController to backend"
Showing
with 448 additions and 57 deletions
...@@ -18,6 +18,8 @@ app.use((req, res, next) => { ...@@ -18,6 +18,8 @@ app.use((req, res, next) => {
next(); next();
}); });
app.use(express.json());
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.send('Hello, World!'); res.send('Hello, World!');
}); });
...@@ -123,26 +125,43 @@ app.get('/highscores', async (req, res) => { ...@@ -123,26 +125,43 @@ app.get('/highscores', async (req, res) => {
}); });
// join a lobby with specific id // join a lobby with specific id
app.post('/lobby/:id', async (req, res) => { app.post('/lobby/create', async (req, res) => {
if (gameHandler.getLobbyById(req.params.id)) { // if lobby doesn't exist, create a new lobby
const lobby = gameHandler.getLobbyById(req.params.id); const lobby = gameHandler.createLobby();
lobby.addUser(req.body.userId);
res.status(201).send({ lobbyId: lobby.getId() });
});
if (!lobby) { // Inform client if requested lobby is full or not
res.status(404).send('Lobby not found'); app.get('/lobby/:id/status', async (req, res) => {
return; const lobby = gameHandler.getLobbyById(req.params.id);
}
lobby.addUser(req.body.userId); if (!lobby) {
gameHandler.createGame(lobby); // start game since 2 players are in lobby res.status(404).send('Lobby not found');
res.status(200).send(lobby.getId()); return;
}
if (lobby.isFull()) {
res.status(200).send({ isFull: true });
} else { } else {
// if lobby doesn't exist, create a new lobby res.status(200).send({ isFull: false });
const lobby = gameHandler.createLobby();
lobby.addUser(req.body.userId);
res.status(201).send(lobby.getId());
} }
}); });
// join a lobby with specific id
app.post('/lobby/:id/join', async (req, res) => {
const lobby = gameHandler.getLobbyById(req.params.id);
if (!lobby) {
res.status(404).send('Lobby not found');
return;
}
lobby.addUser(req.body.userId);
gameHandler.createGame(lobby); // start game since 2 players are in lobby
res.status(200).send({ lobbyId: lobby.getId() });
});
// leave a lobby with specific id // leave a lobby with specific id
app.post('/lobby/:id/leave', async (req, res) => { app.post('/lobby/:id/leave', async (req, res) => {
const lobby = gameHandler.getLobbyById(req.params.id); const lobby = gameHandler.getLobbyById(req.params.id);
...@@ -151,7 +170,7 @@ app.post('/lobby/:id/leave', async (req, res) => { ...@@ -151,7 +170,7 @@ app.post('/lobby/:id/leave', async (req, res) => {
if (lobby?.getUsers().length === 0) { if (lobby?.getUsers().length === 0) {
gameHandler.removeLobby(lobby); gameHandler.removeLobby(lobby);
} }
res.status(200).send(lobby?.getId()); res.status(200).send({ lobbyId: lobby?.getId() });
}); });
// Polling endpoint to check if its your turn (sends username) // Polling endpoint to check if its your turn (sends username)
......
...@@ -6,5 +6,6 @@ export interface ILobby { ...@@ -6,5 +6,6 @@ export interface ILobby {
getUsers(): User[]; getUsers(): User[];
id: string; id: string;
getId(): string; getId(): string;
isFull(): boolean;
// maybe add a relation to a game? // maybe add a relation to a game?
} }
...@@ -44,4 +44,8 @@ export class Lobby implements ILobby { ...@@ -44,4 +44,8 @@ export class Lobby implements ILobby {
getUsers() { getUsers() {
return this.users; return this.users;
} }
isFull() {
return this.game != undefined;
}
} }
...@@ -67,13 +67,20 @@ logo ...@@ -67,13 +67,20 @@ logo
orig: 134, 40 orig: 134, 40
offset: 0, 0 offset: 0, 0
index: -1 index: -1
transparent-white-box semi-transparent-white-box
rotate: false rotate: false
xy: 921, 1009 xy: 921, 1009
size: 1, 1 size: 1, 1
orig: 1, 1 orig: 1, 1
offset: 0, 0 offset: 0, 0
index: -1 index: -1
transparent-white-box
rotate: false
xy: 892, 973
size: 1, 1
orig: 1, 1
offset: 0, 0
index: -1
typing-cursor typing-cursor
rotate: false rotate: false
xy: 2, 2 xy: 2, 2
......
frontend/assets/menu-textures.png

51.1 KiB | W: | H:

frontend/assets/menu-textures.png

51.1 KiB | W: | H:

frontend/assets/menu-textures.png
frontend/assets/menu-textures.png
frontend/assets/menu-textures.png
frontend/assets/menu-textures.png
  • 2-up
  • Swipe
  • Onion skin
package com.game.tankwars;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.Net.HttpResponse;
/**
* Handles request and response from the HTTP request using
* a passed callback function that treats request success, failure and cancellation.
* Implements the retry tactic by resending the request on failure
* a few times with increasing backoff time between resends.
*/
public class HTTPRequestHandler implements Net.HttpResponseListener {
public static final String PROTOCOL = "http";
public static final String HOST = "localhost";
public static final int PORT = 3000;
private final Callback callback;
private final Net.HttpRequest request;
private int attempts = 0;
private final int MAX_ATTEMPTS = 3;
private final int BACKOFF_TIME = 300;
public HTTPRequestHandler(Callback callback, Net.HttpRequest request) {
this.callback = callback;
this.request = request;
}
/**
* Send the HTTP request
*/
public void sendRequest() {
Gdx.net.sendHttpRequest(request, this);
}
/**
* Request was successful and response received. Passes response body
* to callback.
*
* @param httpResponse The {@link HttpResponse} with the HTTP response values.
*/
public void handleHttpResponse(HttpResponse httpResponse) {
callback.onResult(httpResponse.getResultAsString());
}
/**
* Request failed. Request will be retried until the attempts
* have been exhausted with an increasing backoff time between each retry.
*
* @param t If the HTTP request failed because an Exception, t encapsulates it to give more information.
*/
public void failed(Throwable t) {
if (attempts < MAX_ATTEMPTS) {
attempts++;
try {
Thread.sleep((long) attempts * BACKOFF_TIME);
sendRequest();
} catch(InterruptedException e) {
System.err.println(e.getMessage());
}
} else {
callback.onFailed(t);
}
}
/**
* Request was cancelled
*/
public void cancelled() {
System.out.println("Request cancelled");
}
}
\ No newline at end of file
...@@ -19,6 +19,10 @@ public class ReceiverHandler implements Net.HttpResponseListener { ...@@ -19,6 +19,10 @@ public class ReceiverHandler implements Net.HttpResponseListener {
private final Callback callback; private final Callback callback;
public static final int MAX_RETRIES = 5; public static final int MAX_RETRIES = 5;
public static final String PROTOCOL = "http";
public static final String HOST = "localhost";
public static final int PORT = 3000;
// Constructor to initialize the Callback instance // Constructor to initialize the Callback instance
public ReceiverHandler(Callback callback) { public ReceiverHandler(Callback callback) {
this.callback = callback; this.callback = callback;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package com.game.tankwars; package com.game.tankwars;
import com.badlogic.gdx.Game; import com.badlogic.gdx.Game;
import com.game.tankwars.view.FindGameScreen;
import com.game.tankwars.view.LoginScreen; import com.game.tankwars.view.LoginScreen;
public class TankWarsGame extends Game { public class TankWarsGame extends Game {
......
package com.game.tankwars.controller; package com.game.tankwars.controller;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.net.HttpRequestBuilder;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Button; import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextField; import com.badlogic.gdx.scenes.scene2d.ui.TextField;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.SerializationException;
import com.game.tankwars.Callback;
import com.game.tankwars.ConfigReader;
import com.game.tankwars.HTTPRequestHandler;
import com.game.tankwars.ResourceManager; import com.game.tankwars.ResourceManager;
import com.game.tankwars.TankWarsGame; import com.game.tankwars.TankWarsGame;
import com.game.tankwars.model.CurrentUser;
import com.game.tankwars.model.LobbyId;
import com.game.tankwars.model.LobbyStatus;
import com.game.tankwars.view.FindGameScreen;
import com.game.tankwars.view.GameScreen; import com.game.tankwars.view.GameScreen;
import com.game.tankwars.view.MainMenuScreen; import com.game.tankwars.view.MainMenuScreen;
public class FindGameController { public class FindGameController {
private final TankWarsGame tankWarsGame; private final TankWarsGame tankWarsGame;
private final FindGameScreen screen;
private final Stage stage; private final Stage stage;
private final TextField gamePinField; private final TextField gamePinField;
private final TextButton joinLobbyButton, createLobbyButton; private final TextButton joinLobbyButton, createLobbyButton, cancelButton;
private final Button backButton; private final Button backButton;
private final Label gamePinWaitingLabel;
private EventListener backButtonInputListener, gamePinFieldInputListener,
gamePinFieldClickListener, joinLobbyButtonInputListener,
createLobbyButtonInputListener, cancelButtonInputListener;
private String lobbyId = null;
private final Runnable gameScreenTransition;
/** /**
* TODO: Ensure that user is logged in -> e.g. auth method in Auth/Utils class
* Sets the event listeners of the buttons and the text field of the FindGameScreen, * Sets the event listeners of the buttons and the text field of the FindGameScreen,
* and allows for transitioning to MainMenuScreen and to GameScreen. * and allows for transitioning to MainMenuScreen and to GameScreen. Handles communication
* with server to create, join and leave a lobbies.
*/ */
public FindGameController(final TankWarsGame tankWarsGame, public FindGameController(final TankWarsGame tankWarsGame, FindGameScreen screen,
TextField gamePinField, TextButton joinLobbyButton, TextField gamePinField, TextButton joinLobbyButton,
TextButton createLobbyButton, Button backButton, final Stage stage) { TextButton createLobbyButton, Button backButton,
TextButton cancelButton, Label gamePinWaitingLabel,
final Stage stage) {
this.tankWarsGame = tankWarsGame; this.tankWarsGame = tankWarsGame;
this.screen = screen;
this.gamePinField = gamePinField; this.gamePinField = gamePinField;
this.joinLobbyButton = joinLobbyButton; this.joinLobbyButton = joinLobbyButton;
this.createLobbyButton = createLobbyButton; this.createLobbyButton = createLobbyButton;
this.backButton = backButton; this.backButton = backButton;
this.cancelButton = cancelButton;
this.gamePinWaitingLabel = gamePinWaitingLabel;
this.stage = stage; this.stage = stage;
setEventListeners(); // This Runnable will be passed to the "render" thread
// using Gdx.app.postRunnable() from the threads of HTTP requests
gameScreenTransition = new Runnable() {
@Override
public void run() {
ResourceManager.getInstance().clear();
tankWarsGame.setScreen(new GameScreen(tankWarsGame));
}
};
defineEventListeners();
setMainListeners();
} }
public void setEventListeners() { private void defineEventListeners() {
/* /*
* Transitions back to MainMenuScreen * Transition back to MainMenuScreen
*/ */
backButton.addListener(new InputListener() { backButtonInputListener = new InputListener() {
@Override @Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
tankWarsGame.setScreen(new MainMenuScreen(tankWarsGame)); tankWarsGame.setScreen(new MainMenuScreen(tankWarsGame));
return true; return true;
} }
}); };
/* /*
* Filters text field input: * Remove keyboard and reset camera position when "Enter" button is pressed.
* Max 4 characters long and only digits * Enable the joinLobbyButton when gamePinField contains at least one character.
*/ */
gamePinField.setTextFieldFilter(new TextField.TextFieldFilter() { gamePinFieldInputListener = new InputListener() {
@Override
public boolean acceptChar(TextField textField, char c) {
return textField.getText().length() < 4 && Character.isDigit(c);
}
});
/*
* Enables the joinLobbyButton when the gamePinField contains 4 digits,
* and disables it otherwise
*/
gamePinField.addListener(new InputListener() {
@Override @Override
public boolean keyTyped(InputEvent event, char character) { public boolean keyTyped(InputEvent event, char character) {
super.keyTyped(event, character); super.keyTyped(event, character);
...@@ -77,50 +110,236 @@ public class FindGameController { ...@@ -77,50 +110,236 @@ public class FindGameController {
stage.getViewport().apply(); stage.getViewport().apply();
} }
joinLobbyButton.setDisabled(gamePinField.getText().length() != 4); joinLobbyButton.setDisabled(gamePinField.getText().length() == 0);
return true; return true;
} }
}); };
/* /*
* Move camera down when text field is clicked * Move camera down when text field is clicked
* to make the field appear above the keyboard. * to make the field appear above the keyboard.
*/ */
gamePinField.addListener(new ClickListener() { gamePinFieldClickListener = new ClickListener() {
@Override @Override
public void clicked(InputEvent event, float x, float y) { public void clicked(InputEvent event, float x, float y) {
super.clicked(event, x, y); super.clicked(event, x, y);
stage.getViewport().setScreenY((int) (2 * stage.getHeight() / 3)); stage.getViewport().setScreenY((int) (2 * stage.getHeight() / 3));
stage.getViewport().apply(); stage.getViewport().apply();
} }
}); };
/* /*
* Disables input listener when the button is disabled. * Send HTTP request to join lobby with game pin specified in text field.
* TODO: Join a lobby by sending a request to the backend * Avoid sending request if joinLobbyButton is disabled.
*/ */
joinLobbyButton.addListener(new InputListener() { joinLobbyButtonInputListener = new InputListener() {
@Override @Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
if (joinLobbyButton.isDisabled()) return true; if (joinLobbyButton.isDisabled()) return true;
System.out.println("Game pin: " + gamePinField.getText() + " - yet to be implemented"); joinLobby();
return true; return true;
} }
}); };
/* /*
* TODO: Create a lobby by sending request to backend - Transition to waiting screen? * Create new lobby and display waiting window.
* Poll server for lobby status to know when to transition to game screen.
*/ */
createLobbyButton.addListener(new InputListener() { createLobbyButtonInputListener = new InputListener() {
@Override @Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
ResourceManager.getInstance().clear(); createLobby();
tankWarsGame.setScreen(new GameScreen(tankWarsGame));
return true; return true;
} }
}); };
/*
* Exit from waiting window and render normal FindGameScreen
*/
cancelButtonInputListener = new InputListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
exitLobby();
return true;
}
};
}
public void setMainListeners() {
backButton.addListener(backButtonInputListener);
gamePinField.addListener(gamePinFieldInputListener);
gamePinField.addListener(gamePinFieldClickListener);
joinLobbyButton.addListener(joinLobbyButtonInputListener);
createLobbyButton.addListener(createLobbyButtonInputListener);
}
public void removeMainListeners() {
backButton.removeListener(backButtonInputListener);
gamePinField.removeListener(gamePinFieldInputListener);
gamePinField.removeListener(gamePinFieldClickListener);
joinLobbyButton.removeListener(joinLobbyButtonInputListener);
createLobbyButton.removeListener(createLobbyButtonInputListener);
}
public void setWaitingWindowListeners() {
cancelButton.addListener(cancelButtonInputListener);
}
public void removeWaitingWindowListeners() {
cancelButton.removeListener(cancelButtonInputListener);
} }
/**
* Send HTTP request to create a new lobby.
* On success, a waiting window is shown, and polling for
* the lobby status is initiated.
*/
private void createLobby() {
new HTTPRequestHandler(new Callback() {
@Override
public void onResult(String result) {
try {
LobbyId lobbyIdClass = new Json().fromJson(LobbyId.class, result);
lobbyId = lobbyIdClass.getLobbyId();
removeMainListeners();
setWaitingWindowListeners();
gamePinWaitingLabel.setText("Game pin: " + lobbyId);
screen.showWaitingWindow();
checkLobbyStatus();
} catch (SerializationException e) {
System.err.println("Invalid HTTP response on create lobby");
}
}
@Override
public void onFailed(Throwable t) {
System.err.println("Create lobby request failed:\n" + t);
}
}, new HttpRequestBuilder()
.newRequest()
.url(String.format("%s/lobby/create", ConfigReader.getProperty("backend.url")))
.method(Net.HttpMethods.POST)
.header("Content-Type", "application/json")
.content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id))
.build())
.sendRequest();
}
/**
* Send HTTP request while waiting for lobby to fill,
* polling for the lobby's fill status with a set backoff period between requests.
* Transition to game screen when lobby is full.
*/
private void checkLobbyStatus() {
new HTTPRequestHandler(new Callback() {
@Override
public void onResult(String result) {
try {
LobbyStatus lobbyStatus = new Json().fromJson(LobbyStatus.class, result);
if (lobbyStatus.isFull()) {
Gdx.app.postRunnable(gameScreenTransition);
} else if (lobbyId != null) {
System.out.println("Awaiting opponent...");
try {
Thread.sleep(1500);
if (lobbyId != null) checkLobbyStatus();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
exitLobby();
}
}
} catch (SerializationException e) {
System.err.println("Invalid HTTP response on check lobby status");
exitLobby();
}
}
@Override
public void onFailed(Throwable t) {
System.err.println("Check lobby status request failed:\n" + t);
exitLobby();
}
}, new HttpRequestBuilder()
.newRequest()
.url(String.format("%s/lobby/%s/status",
ConfigReader.getProperty("backend.url"), lobbyId))
.method(Net.HttpMethods.GET)
.build())
.sendRequest();
}
/**
* Send HTTP request to exit the joined lobby while the lobby
* is not yet full. Hide the waiting window.
*/
private void exitLobby() {
new HTTPRequestHandler(new Callback() {
@Override
public void onResult(String result) {
removeWaitingWindowListeners();
setMainListeners();
lobbyId = null;
screen.hideWaitingWindow();
}
@Override
public void onFailed(Throwable t) {
System.err.println("Exit lobby request failed:\n" + t);
removeWaitingWindowListeners();
setMainListeners();
lobbyId = null;
screen.hideWaitingWindow();
}
}, new HttpRequestBuilder()
.newRequest()
.url(String.format("%s/lobby/%s/leave",
ConfigReader.getProperty("backend.url"), lobbyId))
.method(Net.HttpMethods.POST)
.header("Content-Type", "application/json")
.content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id))
.build())
.sendRequest();
}
/**
* Send HTTP request to join a lobby using the game pin in the text field.
* On success, transition to game screen.
*/
private void joinLobby() {
new HTTPRequestHandler(new Callback() {
@Override
public void onResult(String result) {
try {
new Json().fromJson(LobbyId.class, result);
Gdx.app.postRunnable(gameScreenTransition);
} catch (SerializationException e) {
System.out.println(result);
}
}
@Override
public void onFailed(Throwable t) {
System.err.println("Join lobby request failed:\n" + t);
}
}, new HttpRequestBuilder()
.newRequest()
.url(String.format("%s/lobby/%s/join",
ConfigReader.getProperty("backend.url"), gamePinField.getText()))
.method(Net.HttpMethods.POST)
.header("Content-Type", "application/json")
.content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id))
.build())
.sendRequest();
}
} }
package com.game.tankwars.model;
public class LobbyId {
private String lobbyId;
public String getLobbyId() {
return lobbyId;
}
public void setLobbyId(String lobbyId) {
this.lobbyId = lobbyId;
}
}
package com.game.tankwars.model;
public class LobbyStatus {
private boolean isFull;
public boolean isFull() {
return isFull;
}
public void setFull(boolean full) {
isFull = full;
}
}
...@@ -28,6 +28,8 @@ public class FindGameScreen implements Screen { ...@@ -28,6 +28,8 @@ public class FindGameScreen implements Screen {
private final TankWarsGame tankWarsGame; private final TankWarsGame tankWarsGame;
private Stage stage; private Stage stage;
private Group layoutGroup;
private Table windowTable;
public FindGameScreen(final TankWarsGame tankWarsGame) { public FindGameScreen(final TankWarsGame tankWarsGame) {
this.tankWarsGame = tankWarsGame; this.tankWarsGame = tankWarsGame;
...@@ -65,7 +67,7 @@ public class FindGameScreen implements Screen { ...@@ -65,7 +67,7 @@ public class FindGameScreen implements Screen {
float rw = stage.getWidth() - lw; float rw = stage.getWidth() - lw;
Table rootTable = new Table(); Table rootTable = new Table();
rootTable.setFillParent(true); rootTable.setBounds(0, 0, stage.getWidth(), stage.getHeight());
Group leftGroup = new Group(); Group leftGroup = new Group();
leftGroup.setSize(lw, stage.getHeight()); leftGroup.setSize(lw, stage.getHeight());
...@@ -96,9 +98,42 @@ public class FindGameScreen implements Screen { ...@@ -96,9 +98,42 @@ public class FindGameScreen implements Screen {
rootTable.add(leftGroup).width(lw).height(stage.getHeight()); rootTable.add(leftGroup).width(lw).height(stage.getHeight());
rootTable.add(rightTable).expandX().height(stage.getHeight()); rootTable.add(rightTable).expandX().height(stage.getHeight());
stage.addActor(rootTable);
new FindGameController(tankWarsGame, gamePinField, joinLobbyButton, createLobbyButton, backButton, stage); //--- Awaiting opponent window
windowTable = new Table();
float ww = 3 * stage.getWidth() / 5f;
float wh = 3 * stage.getHeight() / 5f;
windowTable.setBounds(stage.getWidth() / 2f - ww / 2f, stage.getHeight() / 2f - wh / 2f, ww, wh);
Drawable windowBackground = skin.getDrawable("semi-transparent-white-box");
Label waitingLabel = new Label("Awaiting opponent...", skin.get("default", Label.LabelStyle.class));
Label gamePinWaitingLabel = new Label("Game pin: ---", skin.get("default", Label.LabelStyle.class));
TextButton cancelButton = new TextButton("Cancel", skin.get("default", TextButton.TextButtonStyle.class));
windowTable.background(windowBackground);
windowTable.row().expand();
windowTable.add(waitingLabel);
windowTable.row().expand();
windowTable.add(gamePinWaitingLabel);
windowTable.row().expand();
windowTable.add(cancelButton).width(2 * ww / 3f).height(30);
layoutGroup = new Group();
layoutGroup.addActor(rootTable);
stage.addActor(layoutGroup);
new FindGameController(tankWarsGame, this, gamePinField,
joinLobbyButton, createLobbyButton,
backButton, cancelButton, gamePinWaitingLabel, stage);
}
public void showWaitingWindow() { layoutGroup.addActor(windowTable); }
public void hideWaitingWindow() {
layoutGroup.removeActor(windowTable);
} }
@Override @Override
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment