Stage ja Actorite süsteem¶
LibGDX-i Scene2D on raamistik 2D graafika, kasutajaliidese (UI), menüüde, HUD-i ja hierarhiliste mänguobjektide loomiseks. See võimaldab lihtsamalt hallata sisendit, paigutust, animatsioone ja komponentide organiseerimist võrreldes käsitsi SpriteBatch-iga töötamisega.
Scene2D võimaldab luua stseeni, kus objektid on organiseeritud hierarhias ning nende uuendamine ja joonistamine toimub tsentraalselt.
Scene2D põhikomponendid¶
Scene2D koosneb mitmest põhilisest ehitusblokist:
Stage– kogu stseeni konteiner, mis haldab Actor'eidActor– kõige baasilisem element stseenisGroup– Actor, mis võib sisaldada teisi Actor'eidWidget– kasutajaliidese komponent (Label, Button jne)Viewport– määrab kuidas stseen ekraanile sobitatakseTable– paigutuselement UI komponentide joondamiseks
Kõik Scene2D elemendid põhinevad lõpuks Actor klassil.
Miks Scene2D-d kasutada?¶
Scene2D eelised võrreldes käsitsi renderdamisega:
Sisend (klikid, puudutused, hover) on lihtne Actor'itele Listener'ite abil lisada
Animatsioonid tehakse
ActionssüsteemigaPaigutus on mugav
Table,HorizontalGroupjaVerticalGroupabilStagehaldab Actor'ite uuendamist ja joonistamistHierarhiline struktuur muudab suuremad UI süsteemid lihtsamini hallatavaks
Important
Kõik Scene2D komponendid (Stage, Actor, Table, Label, Skin jne) töötavad ainult kliendipoolses rakenduses. Serveris toimub ainult mänguloogika, füüsika ja seisundi sünkroniseerimine.
Klient saab serverilt andmed (näiteks ServerConnection kaudu) ning uuendab nende põhjal kasutajaliidest ja visuaalseid elemente.
Stage¶
Stage on Scene2D keskne konteiner. Ta hoiab kõiki selle külge lisatud Actor-eid ning vastutab nende uuendamise, joonistamise ja sisendi jagamise eest.
Stage'i peamised ülesanded:
hallata Actor'ite hierarhiat
uuendada Actor'eid meetodiga
act(delta)joonistada Actor'id meetodiga
draw()edastada sisendsündmusi õigetele komponentidele
kasutada
Viewport-i, et stseen sobituks ekraaniga
Ühele Screen-ile võib panna mitu Stage'i, kuid enamasti piisab ühest hästi organiseeritud Stage'ist koos Group-idega.
Stage'i eelised:
Actor'id asendavad paljusid Sprite'e
automaatne hierarhia ja paigutus (Layout liides)
lihtsad animatsioonid Actions'itega
sisend hõlpsasti:
Gdx.input.setInputProcessor(stage)+ Listener'id
Stage uuendamine ja joonistamine¶
Scene2D kasutamisel on kaks kõige olulisemat meetodit:
stage.act(delta)stage.draw()
Need täidavad erinevaid rolle.
stage.act(delta):
uuendab animatsioone
töötleb
Actionssüsteemiuuendab objektide liikumist
uuendab ajast sõltuvat loogikat
stage.draw():
joonistab Actor'id
kuvab visuaalse tulemuse
Lihtne reegel:
act(delta)= uuenda olekutdraw()= kuva olek
Warning
Kui stage.act(delta) ei kutsuta, siis animatsioonid ja Actions ei tööta ning UI ei uuene korrektselt.
Kui stage.draw() ei kutsuta, siis UI ei kuvata ekraanile.
Actorile InputListener'i lisamise näide¶
actor.setBounds(0, 0, texture.getWidth(), texture.getHeight());
actor.addListener(new InputListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
System.out.println("Down");
return true;
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
System.out.println("Up");
}
});
Mängu tüüpiline kihiline struktuur¶
Enamikus 2D-mängudes on kolm peamist kihti:
Taust
Mänguobjektid
HUD / UI
Kihiline renderdamine (alt üles):
[ Kiht 3: HUD / UI ] <-- Stage (Kõige peal)
[ Kiht 2: Maailm ] <-- Mänguobjektid
[ Kiht 1: Taust ] <-- Staatiline foon
Joonistamise järjekord:
Taust -> Maailm -> HUD
Note
LibGDX joonistab objekte selles järjekorras, kuidas neid render() meetodis kutsutakse.
Selleks, et UI ei jääks teiste objektide taha, peab see olema renderdamise lõpus.
Kihilise struktuuri loomiseks kasutatakse üldiselt kahte lähenemisviisi:
Üks Stage + mitu
Group-i (soovitatav)Eraldi Stage'id (kui Viewport'id on väga erinevad)
Soovitus jõudluseks: Üks Stage + Group'id on tavaliselt parem valik.
Camera ja Viewport Stage'iga¶
// Näide: maailm + UI koos
public void create() {
worldViewport = new FitViewport(1280, 720);
uiViewport = new FitViewport(1280, 720); // või ScreenViewport HUD-i jaoks
batch = new SpriteBatch();
stage = new Stage(uiViewport, batch);
}
public void render(float delta) {
// Maailma render
worldViewport.apply();
batch.setProjectionMatrix(worldViewport.getCamera().combined);
batch.begin();
// ... joonista mängumaailm
batch.end();
// UI / HUD
stage.getViewport().apply();
stage.act(delta); // animatsioonid ja UI uuendamine
stage.draw(); // UI joonistamine
}
public void resize(int width, int height) {
worldViewport.update(width, height, true);
stage.getViewport().update(width, height, true);
}
public void dispose() {
stage.dispose();
batch.dispose(); // kui ei jaga mujal
}
Actor ja selle hierarhia¶
Actor on kõige baasiliseim üksus Stage'il.
Peamised omadused:
positsioon
suurus
skale
rotatsioon
värv ja alpha
z-index
vanem-laps suhe
Listener'id
Lihtsustatud hierarhia:
Actor
├── Widget
│ ├── Label
│ ├── Image
│ ├── Button → TextButton, ImageButton, CheckBox
│ ├── TextField, TextArea
│ ├── ProgressBar, Slider
│ └── Touchpad
│
└── Group / WidgetGroup
├── Table
├── Container
├── Window, Dialog
├── ScrollPane
├── Stack
├── HorizontalGroup
└── VerticalGroup
Projektistruktuur¶
Kuna Scene2D on kliendipoolne tehnoloogia, asuvad need klassid ainult core moodulis.
Soovitatav struktuur:
core/src/main/java/ee/taltech/examplegame/
├── screen/
│ ├── TitleScreen.java
│ ├── GameScreen.java
│ └── overlay/
│ └── Hud.java
│
├── ui/
│ ├── widgets/
│ └── factories/
│
├── actors/
│ ├── PlayerActor.java
│ └── BulletActor.java
│
└── util/
└── SkinManager.java
Hea praktika:
Screen haldab Stage'i
Actor on visuaalne komponent
mänguloogika peaks olema eraldi klassides
Näide meie mängust¶
Kogu kood on leitav:
TitleScreen: https://github.com/Rig14/example-game/blob/main/core/src/main/java/ee/taltech/examplegame/screen/TitleScreen.java
public class TitleScreen extends ScreenAdapter {
private final Stage stage;
public TitleScreen(Game game) {
stage = new Stage();
// NB! important line - will make the stage listen for user input
// For example when this is not set no hover or click events will be triggered
Gdx.input.setInputProcessor(stage);
// menu buttons
var startButton = getButton(20, "Start", () -> {
// send a message to the server that the player wants to join the game
ServerConnection.getInstance().getClient().sendTCP(new GameJoinMessage());
game.setScreen(new GameScreen(game));
});
var exitButton = getButton(20, "Exit", () -> Gdx.app.exit());
// positioning the buttons. you can think of the following as a table (or flexbox) in HTML
var table = new Table();
table.setFillParent(true);
table.add(startButton).padBottom(20);
table.row();
table.add(exitButton);
stage.addActor(table);
}
/**
* Renders the TitleScreen, clearing it and drawing the buttons.
*
* @param delta time since last frame.
*/
@Override
public void render(float delta) {
super.render(delta);
// clear the screen
Gdx.gl.glClearColor(192 / 255f, 192 / 255f, 192 / 255f, 1); // to get that win 95 look
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// draw the buttons
stage.act(delta); // updates UI logic and animations
stage.draw(); // renders UI elements
}
@Override
public void resize(int width, int height) {
super.resize(width, height);
stage.getViewport().update(width, height, true);
}
}
/**
* Info overlay that contains information about: player names, lives, game status, game time.
*
* @param spriteBatch used for rendering all visual elements. Using the same spritebatch as the Arena (players,
* bullets etc.) helps with scaling and resizing
*/
public Hud(SpriteBatch spriteBatch) {
localPLayerId = ServerConnection.getInstance().getClient().getID();
// The viewport with current hardcoded width and height works decently with most screen/window sizes
// There's no need for additional font re-scaling when adjusting the window size
Viewport viewport = new FitViewport(640, 480, new OrthographicCamera());
// Create a stage to render the HUD content
stage = new Stage(viewport, spriteBatch);
// Create a table to display fields such as lives count
Table table = createHudTable();
table.setDebug(false); // true - outline all table cells, labels with a red line (makes table non-transparent)
stage.addActor(table);
}
/**
* Table manages where different fields such as player names and lives are displayed on the screen
*/
private Table createHudTable() {
// For simple tables, using an empty placeholder label is usually the easiest solution to adjust alignment
Label emptyLabel = createLabel("", Color.WHITE, LABEL_SIZE);
Table table = new Table();
table.setFillParent(true); // Fill whole screen
table.top(); // Align the table's content to the top
table.padTop(TABLE_PADDING_TOP);
// First row: player names and time
table.add(localPlayerNameLabel);
table.add(timeLabel);
table.add(remotePlayerNameLabel);
table.row().expandX(); // make the row fill the entire width of the screen
// Second row: player lives
table.add(localPlayerLivesLabel);
table.add(emptyLabel); // empty label as a placeholder for alignment
table.add(remotePlayerLivesLabel);
table.row().expandX();
// Third row: game status message
table.add(gameStatusLabel)
.colspan(3) // Make the label span across all 3 table columns
.padTop(GAME_STATUS_LABEL_PADDING_TOP)
.align(Align.center); // Center-align the label within the table cell
table.row();
return table;
}
Parimad praktikad ja jõudlus¶
Kasuta
Skin-i nuppude ja label'ite jaoksTable.setDebug(true)aitab paigutuse silumiselEelista Group'e mitme Stage asemel
Alati kutsu
dispose()Kasuta InputMultiplexer-it mitme sisendi jaoks
Gdx.input.setInputProcessor(new InputMultiplexer(stage, playerInput));
Scene2D: https://libgdx.com/wiki/graphics/2d/scene2d/scene2d
Table: https://libgdx.com/wiki/graphics/2d/scene2d/table
Viewport: https://libgdx.com/wiki/graphics/2d/scene2d/viewports
StackExchange: https://gamedev.stackexchange.com/questions/128381/may-i-put-a-clicklistener-to-the-stage-or-better-if-every-actor-group-has-its-ow
GameDevDoc: https://gamedevdoc.pages.taltech.ee/menu/menu.html#ekraani-loomine