Sündmuse käivitamine¶
Selleks et mängus objektidega suhelda, peab mängija tegema teatud toiminguid. Näiteks ukse avamiseks peab mängija tegema vähemalt kaks tegevust:
Viima tegelase ukse juurde.
Vajutama klahvi.
Seejärel uks avaneb, eeldusel et ei ole täiendavaid tingimusi, nagu näiteks võtme olemasolu. Seega, et sellist interaktsiooni mängu lisada, tuleb meil: tuvastada mängija kokkupuude sihtobjektiga (või sisenemine kindlasse tsooni), teavitada mängijat, et ta saab toimingu sooritada (näiteks kiri "Vajuta 'E', et uks avada"). Ja pärast nupu vajutamist avada uks. Järgmistes alapeatükkides selgitatakse üksikasjalikult, kuidas seda teha, koos näitega. Koodi saab vaadata siit.
Kuidas teha?¶
Kõigepealt lisame abstraktse klassi "Entity", millel on esialgu ainult kolm atribuuti: olendi tekstuur, asukoht maailmas ning ristkülik bounds, mis näitab ruumi, mida olend hõivab. Teisisõnu hitBox
package ee.taltech.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
public abstract class Entity {
private Texture texture;
private Vector2 position;
protected Rectangle bounds;
public Entity(float x, float y, String texturePath, float width, float height) {
Texture texture = new Texture(texturePath);
// Resize texture to passed width and height
texture.getTextureData().prepare();
Pixmap original = texture.getTextureData().consumePixmap();
Pixmap resized = new Pixmap((int) width, (int) height, original.getFormat());
resized.drawPixmap(original, 0, 0, original.getWidth(), original.getHeight(),
0, 0, (int) width, (int) height);
this.texture = new Texture(resized);
this.texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
original.dispose();
resized.dispose();
texture.dispose();
this.position = new Vector2(x, y);
this.bounds = new Rectangle(x, y, width, height);
}
public void render(SpriteBatch batch) {
batch.draw(texture, position.x, position.y, texture.getWidth(), texture.getHeight());
}
public void dispose() {
texture.dispose();
}
public Vector2 getPosition() {
return position;
}
public Rectangle getBounds() {
return bounds;
}
}
Nüüd loome kaks klassi – "Player" ja "Door", mis pärivad "Entity". Mängijale lisame kohe lihtsa liikumise vasakule ja paremale, ning uksele täiendava parameetri isOpened.
package ee.taltech.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class Player extends Entity {
private float speed = 200f;
public Player(float x, float y, String texturePath, float width, float height) {
super(x, y, texturePath, width, height);
}
@Override
public void render(SpriteBatch batch) {
super.render(batch);
}
public void update() {
handleInput();
}
private void handleInput() {
if (Gdx.input.isKeyPressed(Input.Keys.LEFT) || Gdx.input.isKeyPressed(Input.Keys.A)) {
getPosition().x -= speed * Gdx.graphics.getDeltaTime();
}
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT) || Gdx.input.isKeyPressed(Input.Keys.D)) {
getPosition().x += speed * Gdx.graphics.getDeltaTime();
}
bounds.setX(getPosition().x);
}
}
package ee.taltech.entities;
public class Door extends Entity {
private boolean isOpen = false;
public Door(float x, float y, String texturePath, float width, float height) {
super(x, y, texturePath, width, height);
}
public boolean isOpen() {
return isOpen;
}
public void setOpen(boolean open) {
isOpen = open;
}
}
Nüüd tuleb lisada nende kahe objekti kokkupõrke kontroll. Selleks teeme mängu põhitsüklis järgmist – uuendame mängija positsiooni, seejärel saame getterite abil mängija ja ukse hitBox-id ning kasutame meetodit "overlaps", mis kontrollib, kas objektid põrkuvad kokku.
@Override
public void render() {
ScreenUtils.clear(0.15f, 0.15f, 0.2f, 1f);
batch.begin();
player.update();
batch.draw(image, 140, 210);
if (player.getBounds().overlaps(door.getBounds())) {
System.out.println("Overlapping!!!");
}
door.render(batch);
player.render(batch);
batch.end();
}
Ja nüüd töö kontroll:
Kõik töötab suurepäraselt! Ainus asi, mis silma jääb, on see, et mängija ja ukse hitBox-id põrkuvad kokku palju varem, kui see tegelikult visuaalselt tundub. Selle parandamiseks tuleb hitBox-ide mõõtmeid kohandada ning lisaks lisada nende ümber joon, et visuaalselt määrata kokkupõrke piirid. Selleks muudame koodi meetodis "render" klassis "Entity".
Selleks loome meetodi "debugRender" klassis "Entity". Esiteks deklareerime klassi alguses atribuudi shapeRenderer sobiva tüübi kujul. Meetodis "debugRender" määrame, et joonistame punased jooned kui äärise, määrame hitBox-i praeguse suuruse ja asukoha ning lõpetame joonistamise. Tähtis märkus on see, et äärise joonistame eraldi meetodis, sest ShapeRendererit saab kasutada alles pärast seda, kui spriteBatch.end() on lõpetatud. Kui seda ei arvestata, võivad tekkida visuaalsed vead.
...
private final ShapeRenderer shapeRenderer = new ShapeRenderer();
public void debugRender() {
Gdx.gl.glLineWidth(4);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(Color.RED);
shapeRenderer.rect(bounds.x, bounds.y, bounds.width, bounds.height);
shapeRenderer.end();
}
public void dispose() {
texture.dispose();
shapeRenderer.dispose();
}
NB! Ära unusta ressursse vabastada.
public void dispose() {
texture.dispose();
shapeRenderer.dispose();
}
Ja põhitsüklis:
...
door.render(batch);
player.render(batch);
batch.end();
door.debugRender();
player.debugRender();
}
Nüüd saame kontrollida, mis välja tuli:
Nüüd saab hitBox-i mõõtmeid veidi kohandada.
Jäänud on lisada kiri „Vajuta 'E', et uks avada“ ning töödelda 'E' klahvi vajutust.
Selleks loome põhiklassis kaks objekti – "GlyphLayout", mis sisaldab kuvatavat teksti, ja "BitmapFont", mis joonistab meie teksti ekraanile. Teksti joonistamisel on x-koordinaat vasak äär ja y-koordinaat teksti alusjoon (tähtede alumine piir).
X-koordinaadi määramiseks kasutame järgmist valemit: jagame ekraani laiuse kahega ja lahutame poole teksti pikkusest – nii saame teksti täpselt ekraani keskele.
Y-koordinaadi puhul lisame ainult väikese vahe ekraani ülemisest servast.
NB! Ärge unustage meetodit "dispose()"
private BitmapFont usageLabel;
private final String usageText = "Vajuta 'E', et uks avada";
private GlyphLayout layout;
@Override
public void create() {
...
usageLabel = new BitmapFont();
layout = new GlyphLayout();
layout.setText(usageLabel, usageText);
}
@Override
public void render() {
...
if (player.getBounds().overlaps(door.getBounds())) {
usageLabel.draw(batch, usageText, (Gdx.graphics.getWidth() - layout.width) / 2, 20);
}
...
}
@Override
public void dispose() {
...
usageLabel.dispose();
}
Test:
Nüüd kuulame mängu põhitsüklis "E" klahvi vajutust ning kui vajutuse hetkel mängija hitBox kattub ukse hitBox-iga (ja uks on suletud), avame ukse.
@Override
public void render() {
...
if (player.getBounds().overlaps(door.getBounds()) && !door.isOpen()) {
usageLabel.draw(batch, usageText, (Gdx.graphics.getWidth() - layout.width) / 2, 20);
if (Gdx.input.isKeyPressed(Input.Keys.E)) {
door.setOpen(true);
}
}
...
}
Ja lõpuks vahetame suletud ukse tekstuuri lahtise vastu. Selleks hoiame klassis "Door" kahte tekstuuri – suletud ja avatud ust ning kirjutame üle klassist "Entity" päritud meetodid "render" ja "dispose", et kuvada vastavalt ukse olekule õiget tekstuuri.
private Texture textureOpened;
public Door(float x, float y, String texturePathOpened, String texturePathClosed, float width, float height, float collisionWidth, float collisionHeight) {
super(x, y, texturePathClosed, width, height, collisionWidth, collisionHeight);
Texture texture = new Texture(texturePathOpened);
// Resize texture to passed width and height
texture.getTextureData().prepare();
Pixmap original = texture.getTextureData().consumePixmap();
Pixmap resized = new Pixmap((int) width, (int) height, original.getFormat());
resized.drawPixmap(original, 0, 0, original.getWidth(), original.getHeight(),
0, 0, (int) width, (int) height);
this.textureOpened = new Texture(resized);
this.textureOpened.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
}
@Override
public void dispose() {
super.dispose();
textureOpened.dispose();
}
@Override
public void render(SpriteBatch batch) {
if (isOpen) {
batch.draw(textureOpened, position.x, position.y, textureOpened.getWidth(), textureOpened.getHeight());
} else {
super.render(batch); // render default texture
}
}
Ja nüüd näeb lõplik tulemus välja järgmine: