Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Introduksjon

Excerpt

Formålet til denne leksjonen er å lære hvordan en kan styre en figur rundt på skjermen og reagere på at den treffer andre figurer. I første del brukes piltastene for å flytte figuren rundt og i andre del så sjekkes det for overlapp med andre figurer. I Kontinuerlig bevegelse-leksjonen (uferdig) så kombineres dette med kontinuerlige bevegelser (objekter med fart), som er typisk for plattformspill.

Steg 1: Sette opp Java-prosjekt, og lage app-mappe og app-klasse

Start med å sette opp et Java-prosjekt, kalt figurstyring, som beskrevet i Hello world-leksjonen. Lag også en java-pakke (mappe for Java-kodefiler) med samme navn og en Java-klasse ved navn FigurstyringAppFigurstyringApp-klassen skal ha følgende innhold:

Code Block
languagejava
package figurstyring;

// her legger du inn passende import-setninger
 
public class FigurstyringApp extends Application {
	public void start(Stage primaryStage) throws IOException {
		FXMLLoader fxmlLoader = new FXMLLoader(FigurstyringApp.class.getResource("FigurstyringApp.fxml"));
		// setter controlleren, som styrer appen
		fxmlLoader.setController(new FigurstyringController());
		Pane pane = fxmlLoader.load();
		Scene scene = new Scene(pane);
		// sender alle tastetrykk som kommer til vinduet videre til panelet
		scene.setOnKeyPressed(keyEvent -> { pane.getOnKeyPressed().handle(keyEvent);});
		primaryStage.setScene(scene);
		primaryStage.show();
	}

	public static void main(String[] args) {
		launch(FigurstyringApp.class, args);
	}
}

Denne koden er omtrent som i FxmlLogoApp fra FXML-logo-leksjonen. Det er tre viktige forskjeller:

  1. FXML-fila som lastes inn heter FigurstyringApp.fxml. Det betyr at du må bruke dette navnet i neste trinn.
  2. Vi setter en såkalt Controller, som er en Java-klasse som kobles sammen med FXML-fila og har kode for å reagere på museklikk og tastetrykk. Kort sagt: Det er Controller-klassen som gir liv til appen!
  3. Vi sørger for at alle tastetrykk sendes til hovedpanelet i appen.

Steg 2: Lage FXML-fil og Controller-klasse

Nå er turen kommet til FXML-fila og den tilhørende Controller-klassen. Lag en FXML-fil med navnet som brukes i koden over (se etter getResource(...)). Fremgangsmåte er beskrevet i FXML-logo-leksjonen. Hint: Bruk New > Other og finn New FXML Document-veiviseren.

Først skal vi lage noen ulike typer figurer, hvor én skal være den du skal styre (tenk på det som spilleren), mens de andre foreløpig skal være i ro.

Du kan f.eks. velg en PacMan-aktig figur som spilleren, slik:

Etterpå lager du FigurstyringController-klassen (høyreklikk på figurstyring-pakka og velg New > Class). Det er lurt å ha denne klassen fremme, mens du jobber med FXML-fila i SceneBuilder.

Hvis du har gjort alt riktig så langt, så skal du kunne kjøre appen og få opp figurene i et vindu: Høyre-klikk på FigurstyringApp-klassen og velg Run As > Java Application og se om det fungerer.

 

Steg 3: Reagere på tastetrykk

Nå skal vi få spiller-figuren til å bevege seg når du trykker piltastene. Først må du ordne det slik at FigurstyringController-objektet får beskjed når tastene trykkes. Dette kan du gjøre i SceneBuilder, slik:

  • Klikk på bakgrunnen (altså panelet som inneholder figurene), slik at hovedpanelet velges i element-treet nede til venstre
  • Velg Code-seksjonen i panelet til høyre for tegnepanelet. Du finner den nederst i hjørnet.
  • Code-seksjonen er nokså lang, så du må rulle litt nedover før du finner egenskap-gruppa som heter Keyboard med egenskapen On Key Pressed.
  • Fyll inn keyPressed.

Det skal se ut omtrent som i figuren til høyre.

Dette forteller JavaFX at når en tast trykkes (On Key Pressed) så skal metoden keyPressed i FigurstyringController-objektet kalles.

Husk å lagre før du går tilbake til Eclipse for å skrive inn keyPressed-metoden i FigurstyringController-klassen.

Hvis du åpner FXML-fila, så vil du se at følgende kode er lagt inn i tag-en for panelet (den heter AnchorPane, Pane e.l.): onKeyPressed="#keyPressed". Det kan f.eks. se slik ut:

Code Block
languagejavafx
<AnchorPane onKeyPressed="#keyPressed" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
	<children>
		<Circle fill="LIME" layoutX="612.0" layoutY="220.0" radius="85.0" stroke="BLACK" strokeType="INSIDE" />
		<Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="150.0" layoutX="449.0" layoutY="351.0" stroke="BLACK" strokeType="INSIDE" width="78.0" />
		<Ellipse fill="RED" layoutX="266.0" layoutY="135.0" radiusX="28.0" radiusY="75.0" stroke="BLACK" strokeType="INSIDE" />
      	<Arc fill="WHITE" layoutX="139.0" layoutY="332.0" length="270.0" radiusX="45.0" radiusY="45.0" startAngle="45.0" stroke="BLACK" strokeType="INSIDE" type="ROUND" />
	</children>
</AnchorPane>

Du kan jobbe direkte med FXML-koden i Eclipse, men det kan være litt vanskeligere å finne frem. Du må du passe på å få lagt inn onKeyPressed="#keyPressed" på rett sted selv!

Hvis du endrer FXML-koden i Eclipse og så går tilbake til SceneBuilder, så må du huske å laste koden inn på nytt, for å få inn endringene du gjorde i Eclipse. Velg File > Revert to Saved for å gjøre dette.

Koden onKeyPressed="#keyPressed" forteller som sagt JavaFX at metoden keyPressed i Controller-klassen skal kjøres hver gang brukeren trykker ned en tast på tastaturet. Det betyr at du må lage en slik metode i FigurstyringController-klassen. I første omgang kan du skrive inn følgende kode i FigurstyringController:

Code Block
@FXML
void keyPressed(KeyEvent keyEvent) {
	KeyCode keyCode = keyEvent.getCode();
	System.out.println("Du trykket " + keyCode);
}

Linja med @FXML forteller JavaFX at metoden er lagt inn for å stemme med FXML-kode av typen onKeyPressed="..." og gir JavaFX lov til å kalle den. Selve metoden tar i mot et objekt som bl.a. forteller hvilken tast som ble trykket. Taste-koden hentes ut i første linje av metoden og skrives ut i andre linje. Hvis du nå kjører appen og trykker noen taster, så skal du se at det skrives ut en beskjed i Console-panelet i Eclipse for hver tast som trykkes.

Steg 4: Flytte figur

Å reagere på tastetrykk er vel og bra, men målet er jo å flytte figuren, så her må det flere triks til!

Det første problemet som må løses er å gi FigurstyringController-objektet mulighet til å endre på spiller-figuren. For å få det til så må den gis et navn, både i FXML-fila og i FigurstyringController-klassen.

Med SceneBuilder, så gjør du som følger:

  • Velg spiller-figuren, enten i tegnepanelet eller i element-treet nede til venstre.
  • Velg så Code-seksjonen i egenskaper-panelet til høyre (samme som du gjorde over).
  • Finn fx:id-egenskapen øverst i Code-seksjonen i Identity-gruppa.
  • Fyll inn player, evt. et annet navn (bare vanlige bokstaver)

Det skal se ut omtrent som i figuren til høyre.

Som tidligere så kan du legge inn denne koden i FXML-fila i Eclipse, om du ønsker. Finn FXML-koden for spiller-figuren og legg inn fx:id="player". Det skal se omtrent slik ut:

Code Block
languagejavafx
<Arc fx:id="player" fill="WHITE" layoutX="139.0" layoutY="332.0" length="270.0" radiusX="45.0" radiusY="45.0" startAngle="45.0" stroke="BLACK" strokeType="INSIDE" type="ROUND" />

Nå må du legge inn en variabel i FigurstyringController-klassen med det samme navnet! Når FXMLLoader-en laster inn FXML-koden (dette skjer i start-metoden i FigurstyringApp), så sørger den samtidig for at denne variabelen settes til riktig figur-objekt, slik at keyPressed-koden kan flytte rundt på det (eller gjøre andre endringer. Koden for player-variabelen er som følger og kan legges inn rett over keyPressed-metoden:

Code Block
languagejava
@FXML
Node player; 

@FXML brukes litt som over, for å si fra om at variabelen er lagt inn for å stemme med FXML-kode av typen fx:id="..." og gir JavaFX lov til å endre den.

Det siste som trengs er å legge inn kode som ut fra hva slags tast som trykkes flytter figuren i ønsket retning. Trikset er å ha en if som sjekker taste-koden mot taste-kodene for piltastene og bestemmer i hvilken retning figur-posisjonen skal endres:

Code Block
languagejava
@FXML
void keyPressed(KeyEvent keyEvent) {
	KeyCode keyCode = keyEvent.getCode();
	// variabler for retningen spiller-figuren skal flyttes i
	int dx = 0, dy = 0;
	// endre retningen tilsvarende piltasten
	if (keyCode == KeyCode.LEFT) {
		dx = -1;
	} else if (keyCode == KeyCode.RIGHT) {
		dx = 1;
	} else if (keyCode == KeyCode.UP) {
		dy = -1;
	} else if (keyCode == KeyCode.DOWN) {
		dy = 1;
	}
	// gjør selve endringen av posisjonen
	player.setLayoutX(player.getLayoutX() + dx);
	player.setLayoutY(player.getLayoutY() + dy);
}

Vi bruker her variablene dx og dy for å lagre hvor langt til venstre (dx < 0) eller høyre (dx > 0) og opp (dy < 0) eller ned (dy > 0) som spiller-figuren skal flyttes. Etter alle if-setningene, så skal dx og dy være justert til å passe med piltastene. Merk at hvis en trykker helt andre taster, så vil dx og dy begge være 0, og det betyr jo at spiller-figuren står stille!

Til slutt settes layoutX- og layoutY- egenskapene (med setLayoutX og setLayoutY) til verdien de har nå (getLayoutX() og getLayoutY()) pluss justeringen som er lagt inn i dx og dy.

Kjør koden og så hva som skjer! Sjekk spesielt hva som skjer om du flytter spiller-figuren inn i de andre figurene.

Steg 4: Kollisjonssjekk

Hvis du fikk kjørt koden og den virket som forventet, så oppdaget du sikkert at spiller-figuren kunne kjøres rett over (eller under avhengig av rekkefølgen i FXML-fila). I et virkelig spill er det viktig å sjekke om spilleren kolliderer med ulike typer figurer og sørge for riktig effekt. F.eks. skal en ikke kunne gå gjennom vegger, dersom en støtter borti farlige ting så skal spilleren kanskje dø og noen ting skal en kanskje kunne plukke opp. Så først må en kunne sjekke om spilleren kolliderer, så kan en etterpå bestemme reaksjonen ut fra hva slags figur spilleren kræsjet i.

JavaFX har innebygde metoder for å sjekke om en figur overlapper med en annen. Så hvis en for hver gang spilleren flytter, sjekker om den overlapper med en av de andre figurene, så er en på god vei. Her er liksom-kode for dette:

Code Block
for hver figur i panelet:
	hvis figur ikke er spiller-figuren:
		hvis figur overlapper med spiller-figuren så:
			skriv beskjed om kollisjon

Det er greit å få tak i spiller-figuren, den ligger jo i player-variablen, men hvordan får en tak i alle (de andre) figurene? Det går an å lage en fx:id og variabel for hver figur, slik vi gjorde med spiller-figuren. Men det er fryktelig mye jobb, hvis vi har mange figurer. Derfor gir vi heller navn til hovedpanelet, slik at vi kan gå gjennom alle figurene som ligger inni det! Legg derfor inn en fx:id i FXML-koden for hovedpanelet og lag en tilsvarende variabel i FigurstyringController-klassen. Her er koden jeg har laget for variabelen:

Code Block
languagejava
@FXML
AnchorPane room; // bruk room som fx:id i FXML-koden

Nå kan vi fylle inn ordentlig kode i keyPressed-metoden, istedenfor liksom-koden:

Code Block
@FXML
void keyPressed(KeyEvent keyEvent) {


	... som over ...

	Bounds playerBounds = player.getBoundsInParent();
	for (Node child : room.getChildrenUnmodifiable()) {
		if (child != player) {
			Bounds childBounds = child.getBoundsInParent();
			if (playerBounds.intersects(childBounds)) {
				System.out.println("Kræsj!!!");
			}
		}
	}
}

Her har vi en for-løkke som går gjennom alle figurene som ligger inn i hovedpanelet. Hvor hver runde i lista vil child-variablene være en ny figur.

Hvis denne er ulike spilleren (child != player) så sjekker vi om de overlapper (eng: intersects) og i tilfelle skriver vi ut en beskjed.

Kjør koden, styr spiller-figuren bort til de ulike figurene og se hva som skjer!

 

P.S. Egentlig så sjekker vi ikke om figurene overlapper, men om en tenkt firkant (Bounds) rundt dem overlapper. Dette er enklere å finne ut, men det betyr også at det blir nokså unøyaktig, spesielt når en har runde figurer som den PacMan-aktige spilleren, den røde ovalen og den grønne sirkelen. Det går an å lage smartere kode for dette, men det er litt vanskelig så vi venter med det til senere...

Steg 5: Ulike reaksjon avhengig av hva en kolliderer med

Det som nå gjenstår er å lage kode som gjør ulike ting avhengig av hvilken (type) figur som spiller-figuren kræsjer med. Trikset er å få lagt inn en merkelapp i hver figur om hva slags type figur det er.

Med SceneBuilder, så gjør du som følger:

  • Velg en av figurene, f.eks. den røde ovalen, enten i tegnepanelet eller i element-treet nede til venstre.
  • Velg så Properties-seksjonen i egenskaper-panelet til høyre.
  • Finn Style Class-egenskapen i JavaFX CSS-gruppa.
  • Fyll inn dangerShape, evt. et annet navn (bare vanlige bokstaver)

Det skal se ut omtrent som i figuren til høyre.

Gjør tilsvarende for de andre figurene, men velg andre navn, f.eks. wallShape og targetShape, så du får ulike typer figurer.

For å gjøre det litt mer spennende og krevende så kan du lage et rom med vegger (wallShape) som PacMan-figuren skal komme ut av. Den må unngå de røde ovalene (dangerShape) for å få premien (targetShape). Det er lurt å sette merkelapper på figurene først og så kopiere dem med Edit > Duplicate-funksjonen.

 

 

Koden som du skrev over må nå utvides slik at du skriver ut ulike beskjeder når du kolliderer med de ulike figurene. Bruk child.getStyleClass().contains("dangerShape") for å sjekke om figuren har dangerShape-merkelappen. Se om du klarer å legge inn if-setninger som sjekker for hver av de ulike merkelappene og skriver ut en egen beskjed for hver.

Til slutt får du en liten nøtte-oppgave: Det skal være umulig å gå inn i en vegg. Hvis merkelappen er wallShape så skal figuren altså ikke flytte seg likevel! Hint: Hvis spiller-figuren kræsjer i en vegg, så flytt spiller-figuren tilbake (altså like langt i motsatt retning).

Hva har du lært?

  • å få JavaFX til å kalle en metode automatisk når en tast trykkes ned
  • å flytte en figur avhengig av hvilken tast som trykkes
  • å klassifisere figurer med styleClass-attributtet
  • å sjekke om spiller kolliderer (overlapper) med andre figurer
  • å reagere på ulikt vis avhengig av hva slags type figur som spilleren treffer på

I leksjonen Kontinuerlig bevegelse-leksjonen (uferdig) vil du bygge videre på denne leksjonen og legge til kode slik at figurene beveger seg av seg selv!