LibGDX poruszanie postacią i losowanie tekstury

0

https://github.com/libgdx/libgdx/wiki/Extending-the-simple-game

Zrobiłem grę wdg tego tutoriala i mam dwa pytania:

  1. Czy jest sposób, by poruszać wiadrem w ten sposób by po jednorazowym kliknięciu cały czas poruszało się w jedną stronę (aż do wyznaczonej granicy), a przy kolejnym zmieniało kierunek?
  2. W jaki sposób zrobić by np generowane było kilka rodzajów kropli ( mam kilka tekstur dla kropli i żeby przed zespawnowanie kropli losowana była jej tekstura)?
    Z góry dziękuję za pomoc
2
  1. tak, użyj zmiennych typu boolean.
  2. ładujesz na początku wszystkie tekstury kropli do tablicy tekstur, dla każdego tworzonego obiektu kropli przypisujesz losową teksturę z tablicy tekstur.
0

Chodzi o TextureAtlas?

2

Nieważne, w jaki sposób przechowujesz tekstury. Musisz mieć dostęp do jakiegoś identyfikatora (indeks tablicy, pozycja w atlasie), dzięki temu przy tworzeniu obiektu kropli możesz wskazać losową kroplę (losowy numer indeksu).

0

while(direction == true){
hero.y -= 400 * Gdx.graphics.getDeltaTime();
}
while(direction == false){
hero.y += 400 * Gdx.graphics.getDeltaTime();
}

popełniłem coś takiego odnoścnie poruszania się postaci, oczywiście wcześniej deklarując zmienną boolean no i przy próbie uruchomienia program się zawiesza(chodzi o nieskończoną pętlę?)

I przy okazji amm pytanie, dlaczego an tej pętli program się nie zawiesza?

Iterator<Rectangle> iter = enemies.iterator();
while(iter.hasNext()){
Rectangle enemy = iter.next();
enemy.x -= 400 * Gdx.graphics.getDeltaTime();
if(enemy.x + 154 < 0){
iter.remove();
carsGone++;
}
if(enemy.overlaps(hero)){
game.setScreen(new GameOverScreen(game));
dispose();
}

  	}
0

Nie wiem czy dobrze zauważyłem, ale wydaje mi się, że metoda render, którą mam w swojej klasie to jest jedna wielka pętla więc załatwiłem to w ten sposób:

if(Gdx.input.justTouched()){
direction = !direction;
}
if(direction == true){
hero.y -= 400 * Gdx.graphics.getDeltaTime();
}else{
hero.y += 400 * Gdx.graphics.getDeltaTime();
}

i śmiga tak jak chciałem, dzięki wielkie za podrzucenie pomysłu z tym boolean, jako mocno początkujący jeszcze długo mogłem an to nie wpaść :P

Jednak cały czas nurtuje mnie pytanie dlaczego program przy pętli nieskończonej zacina się, oraz dlaczego ta druga pętla z iteratorem nie powoduje tego?

2
while(direction == true){
            hero.y -= 400 * Gdx.graphics.getDeltaTime();
        }

Nigdzie w tej pętli nie masz zmiany zmiennej direction. Dlatego pętla wykonuje się w nieskończoność. W pętli aplikacji wystarczy dać if zamiast while.

0

Jak deklaruję taką macierz:

Array<Texture> enemiesTexture;

to jak mam do niej przypisać kolejne elementy? bo nie mam pojęcia jak to zrobić, a próbowałem już kilka metod :(

2

Użyj ArrayList.

ArrayList<texture> enemiesTexture;
// [...]
enemiesTexture = new ArrayList<texture>();
enemiesTexture.add(new texture(...));
// [...]
enemiesTexture.get(idx);
0

Okej, czy dobrze rozumiem:
najpierw deklarujemy macierz, potem ją tworzymy, potem przypisujemy obiekt i on automatycznie dostaje numerek? a w ostatnim wywołujemy konkretny numer i to jest miejsce na funkcję random?
A co w wypadku gdy mamy bardzo dużo elemntów? da się jakoś wyświetlić zawartość z numerkami, żeby się w tym połapać?

2

Nie wiem co kombinujesz, ale wszystko się da ;) Możesz nawet zrobić hashmapę, żeby identyfikować tekstury po stringach. Wszystko zależy od potrzeb.
Skoro tworzysz tablicę/listę z zamiarem losowania tekstur, to nie oczekuj od niej uporządkowania. Jakbyś jeszcze nie szukał, ArrayList ma metodę size(), która podaje Ci ilość elementów -> przydatne przy losowaniu indeksu dla get().

Nie musisz wszystkich tekstur pchać do jednej listy, możesz zrobić tyle list ile Ci potrzeba by zachować porządek. Jeśli chcesz, to po takiej liście możesz przejść pętlą foreach i wypisać dane, które Ci potrzeba (chyba, że o inne wyświetlenie Ci chodzi).

A... i kod, który napisałem dobrze rozumiesz ;)

0

rand1 = new Random();
enemyTexture = enemiesTexture.get(rand1.nextInt(enemyTextureNumber));

Zrobiłem, co poleciłeś, mam macierz,nawet losuje texturę tylko jest problem, z tym gdzie mam tę komendę wstawić, żeby losowało oddzielną texturę dla kazego obiektu z macierzy Rectangle zawierającej obiekty enemy i się trzymało jej potem, bo do tej pory wstawiłem to raz w metodę render i wyglądało to tak, że każda kropla była dokładnie taka sama tylko na bieżąco losowało jej texturę i ją rysowało więc wyglądało to jak dyskoteka. Potem wstawiłem te dwie linijki do konstruktora całej klasy, to losowało jedną texutrę i do końca gry każda kropla wyglądała tak samo. Jeszcze inaczej wstawiłe do metody spawnEnemy() co zaowocowało tym, że za znowu wszystkie krople miały tę samą teksturę, tylko, że zmieniała się ona z każdym wykonaniem metody, tzn co około 1 sek dla każdej kropli pozsotając taka sama.

2

W jaki sposób rysujesz wrogów?

Każdy obiekt enemy (klasa) powinien mieć pola takie jak tekstura i pozycja. Także potem rysując wszystko w pętli będziesz podawał te dane. Nie możesz używać wprost przykładu ze strony libgdx ;) Musisz sobie wroga opakować w klasę. I wszystkich wrogów pakuj do listy, podobnie jak tekstury.

Czyli przy rysowaniu kropel nie będziesz pisał:

   for(Rectangle raindrop: raindrops) {
      batch.draw(dropImage, raindrop.x, raindrop.y);
   }

Tylko:

   for(MyEnemy enemy: enemies) {
      batch.draw(enemy.image, enemy.rect.x, enemy.rect.y);
   }

W metodzie SpawnRaindrop() będziesz losował teksturę i przypisywał ją do pola image obiektu enemy. podobnie z pozycją, zamiast do raindrop.x..., będziesz ją zapisywał do randrop.rect.x... Tylko musisz sobie napisać prostą klasę "MyEnemy" grupującą te pola. Pamiętaj, że raindrop.rect musisz też utworzyć przed użyciem, (raindrop.rect = new Rectangle();)

Poza tym obiekt Random wystarczy Ci utworzyć raz na cały żywot aplikacji. Możesz się odwoływać do niego przez pole statyczne, albo singleton.

0

A mógłbyś mi podpowiedzieć jak powinien wyglądać kontruktor takiej klasy? Bo nie jestem pewien co powinien zawierać. Jedynie teksturę i rectangle, czy jeszcze coś więcej?

2

Wedle uznania.

Na stronie libgdx masz przykład:

   private void spawnRaindrop() {
      Rectangle raindrop = new Rectangle();
      raindrop.x = MathUtils.random(0, 800-64);
      raindrop.y = 480;
      raindrop.width = 64;
      raindrop.height = 64;
      raindrops.add(raindrop);
      lastDropTime = TimeUtils.nanoTime();
   }

Więc teraz jak będziesz miał klasę MyEnemy, to będzie wyglądało tak:

   private void spawnRaindrop() {
      MyEnemy raindrop = new MyEnemy();
      raindrop.rect.x = MathUtils.random(0, 800-64);
      raindrop.rect.y = 480;
      raindrop.rect.width = 64;
      raindrop.rect.height = 64;
      raindrop.tex = enemiesTexture.get(rand1.nextInt(enemiesTexture.size()));
      raindrops.add(raindrop);
      lastDropTime = TimeUtils.nanoTime();
   }

Więc najlepiej w konstruktorze klasy MyEnemy nie przyjmuj żadnych argumentów, tylko stwórz obiekt Rectangle();, czyli rect = new Rectangle();, dzięki temu chociaż trochę się oddalisz od "zewnętrznego zarządzania obiektami klasy" ;)

Rect oraz tex będą polami publicznymi.

0

Świetnie, wszystko działa, wielkie dzięki! Ale teraz nasunęło mi się jeszcze kilka pytań odnośnie całego mechanizmu:
1.Moja klasa wygląda tak:
rect i tex to są pola tej klasy tak? dlaczego tworzymy tylko Rectangle w tej klasie, a Texture już nie?


public class Enemy {
	
	public Rectangle rect;
	public Texture tex;
	
	public Enemy(){
		rect = new Rectangle();
		
	}

}
 
  1. Do czego służy macierz (?) Iterator i cała związana z tym pętla while? Czy mógłbyś mi opowiedzieć co tam się dzieje w środku, po kolei?
Iterator<Enemy> iter = enemyRaindrops.iterator();
		while(iter.hasNext()){
			Enemy enemy = iter.next();
			enemy.rect.x -= 400 * Gdx.graphics.getDeltaTime();
			if(enemy.rect.x + 154 < hero.x){
				iter.remove();
				raindropsGone++;
			}
 
  1. Po co umieszczamy tutaj enemiesTexture.size()? Wcześniej wrzucałem w to miejsce wygenerowany losowo numer by wybrać obiekt z macierzy o określonym numerze, czy to było źle?
 raindrop.tex = enemiesTexture.get(rand1.nextInt(enemiesTexture.size()));
 
  1. W którym miejscu obiekty, które już przeleciały cały ekran i znajdują się poza nim są usuwane?
1
sodiumnitrat napisał(a):

Świetnie, wszystko działa, wielkie dzięki! Ale teraz nasunęło mi się jeszcze kilka pytań odnośnie całego mechanizmu:
1.Moja klasa wygląda tak:
rect i tex to są pola tej klasy tak? dlaczego tworzymy tylko Rectangle w tej klasie, a Texture już nie?


public class Enemy {
	
	public Rectangle rect;
	public Texture tex;
	
	public Enemy(){
		rect = new Rectangle();
		
	}

}
 

Dlatego tworzysz tylko Rectangle, bo nie tworzysz go w innym miejscu. Instancję tekstury masz już utworzoną w ArrayList z teksturami (tam było new), więc tylko ją przypisujesz do swojego obiektu typu Enemy. Równie dobrze możesz usunąć ten konstruktor i tworzyć rectangle w metodzie SpawnEnemy. Wszystko można zrealizować na różne sposoby ;)

sodiumnitrat napisał(a):
  1. Do czego służy macierz (?) Iterator i cała związana z tym pętla while? Czy mógłbyś mi opowiedzieć co tam się dzieje w środku, po kolei?
Iterator<Enemy> iter = enemyRaindrops.iterator();
		while(iter.hasNext()){
			Enemy enemy = iter.next();
			enemy.rect.x -= 400 * Gdx.graphics.getDeltaTime();
			if(enemy.rect.x + 154 < hero.x){
				iter.remove();
				raindropsGone++;
			}
 

Właściwie to jest to samo co pętla foreach z tą różnicą, że można łatwo usuwać elementy podczas przechodzenia przez pętlę. Tutaj usuwani są Twoi wrogowie, kiedy przekroczą wskazaną pozycję.

sodiumnitrat napisał(a):
  1. Po co umieszczamy tutaj enemiesTexture.size()? Wcześniej wrzucałem w to miejsce wygenerowany losowo numer by wybrać obiekt z macierzy o określonym numerze, czy to było źle?
 raindrop.tex = enemiesTexture.get(rand1.nextInt(enemiesTexture.size()));
 

Tak, to było źle. Random.nextInt(liczba) zwraca Ci losową liczbę w przedziale <0;liczba). Podstawienie losowej liczby zamiast rozmiaru listy może Ci wyjechać poza zakres listy.

sodiumnitrat napisał(a):
  1. W którym miejscu obiekty, które już przeleciały cały ekran i znajdują się poza nim są usuwane?

Wydaje mi się, że odpowiedzialny jest za to kod w pytaniu nr 2.

0

Jest jakiś sposób, żeby odnieść się do konkretnego obiektu danej klasy widocznego na ekranie? jak np mamy w tej tabeli 3x obiekt enemy bo tak zadziałała metoda, że zespawnowała nam 3 takie obiekty to jak wybrać konkretny?

1

Zależy na jakiej zasadzie chcesz wybierać obiekty. Zadaj sobie pytanie: czym one się od siebie różnią?

  • pozycją? -> możesz iterować po wszystkich obiektach i sprawdzać czy pozycja obiektu mieści się w podanym przez Ciebie zakresie (np. podczas kliknięcia myszy, albo w każdej klatce sprawdzasz czy pozycja x czy y wroga osiągnęła wyznaczoną przez Ciebie pozycję).
  • teksturą? -> możesz iterować po wszystkich obiektach i porównywać pole tex, z wybraną teksturą.

Potem odnosisz się do obiektu, który spełnia określone przez Ciebie kryteria.

0

okej względem pozycji będzie super. a czy da się usunąć juz konkretny wybrany obiekt?

1

Tak.

Właściwie wszystko masz już napisane. Sam podałeś kawałek kodu:

Iterator<Enemy> iter = enemyRaindrops.iterator();
        while(iter.hasNext()){
            Enemy enemy = iter.next();
            enemy.rect.x -= 400 * Gdx.graphics.getDeltaTime();
            if(enemy.rect.x + 154 < hero.x){
                iter.remove();
                raindropsGone++;
            }

W ifie sprawdzasz, czy enemy spełnia określone warunki, po czym go usuwasz. Przed usunięciem/zamiast usunięcia możesz w tym samym ifie wykonywać operacje na obiekcie enemy.

0

No tak, dzięki wielkie!
Teraz mam kolejny proble, ponieważ chciałem tekstury zastąpić animacjami. No i wszystko fajnie, bo udało mi się to jakoś wykombinować, że pojawia mi się losowa animacja, tylko jest ten problem, że następne generowane animacje są dokładnie te same. Z racji tego, że są potrzebuję chyba kolejnych wcieleń Animacji by zrobić to o co mi chodzi to znowu trzeba będzie pokombinować z pętlą for i macierzami? Coś już próbowałem skleić, tylko jakbyś mi mógł powiedzieć w jakiej kolejności mam co wywoływać? Kod na animację wygląda tak:

w kontruktorze klasy głównej:
TextureRegion[][] tmp3 =TextureRegion.split(enemy2.tex, 128, 128);
		blueFrames = new TextureRegion[12];
		int index3 = 0;
		for( int i3 = 0; i3<3; i3++){
			for( int j3 = 0; j3<4; j3++){
				blueFrames[index3++] = tmp3[i3][j3];
			}
		}
		
		blueAnimation = new Animation(0.1f, blueFrames);

w metodzie render:
currentBlueFrame = blueAnimation.getKeyFrame(stateTime, true);

i tutaj oczywiście dostajemy "klatkę", którą następnie podajemy do narysowania
 
0

Napisz sobie klasę do animacji, a w niej ładowanie, rysowanie. Wtedy zamiast pola tex w klasie Enemy będziesz mógł zrealizować animacje.

0

To znaczy klasę, która po prostu będzie obsługiwała te wszystkie animacje czy tworzyła oddzielne animacje? Co znaczy że zamiast pola tex będę realizował animacje? Nie do końca rozumiem

1

Całą obsługę animacji wsadź do odpowiedniej klasy. Ładowanie wszystkich klatek animacji z plików, wyświetlanie animacji, zatrzymywanie, interwał między klatkami.

Chcesz, żeby każdy wróg miał przypisaną losową animację? Zamiast przypisywać im tekstury przypisuj im animacje w ten sam sposób co tekstury. Dobrze byłoby, żeby klasa animacji ładowała obrazki w taki sposób, żeby nie ładowała wielokrotnie tej samej tekstury. Także dobrze by było przy dodawaniu animacji do obiektu wroga tworzyć nowe instancje klasy animation (bo inaczej wiele wrogów może mieć przypisaną tą samą instancję animacji i będą sobie nawzajem krzaczyć).

0

Przeniosłem wszystkie tekstury animacji, macierz i cały ten proces dzielenia textury do klasy enemy:

	enemyTexture = enemiesTexture.get(rand.nextInt(11));
	
		TextureRegion[][] tmp =TextureRegion.split(enemyTexture, 128, 128);
		enemyTextureRegion = new TextureRegion[12];
		int index = 0;
		for( int i = 0; i<3; i++){
			for( int j = 0; j<4; j++){
				enemyTextureRegion[index++] = tmp[i][j];
			}
		}
		
		rect = new Rectangle();
		anim = new Animation(0.1f, enemyTextureRegion);
 

ale wywala mi błąd
at com.lefthandfreegames.game.Enemy.<init>(Enemy.java:50)
i chodzi o tę linijkę z losowaniem, jak wpiszę konkretny numerek z tabelki to działa, a jak już chcę wylosować to nie, dlaczego?

0

Albo wyjeżdżasz poza zakres listy, albo brakuje Ci referencji do rand.

0

Męczę się z tym i męczę i nie wychodzi cały czas. Czy mogę zrobić coś takiego, że załaduję wszystkie animacje w klasie game screen, wrzucę je w macierz, dodam klasie enemy pole animacja i potem przy tworzeniu nowego obiektu enemy będę podawał losowy obiekt z tej macierzy? ma to szansę zadziałać?

0

Potwierdzam, to działa, jednak wymyśliłem to sam więc zapewne coś jest zrobione niezgodnie z przyjętymi normami :P Czy mój sposób jest dobry, albo przynajmniej nie naganny?

0

Co jakiś czas wywala z takim błędem:

Exception in thread "LWJGL Application" java.lang.NullPointerException
at com.badlogic.gdx.graphics.g2d.SpriteBatch.draw(SpriteBatch.java:586)
at com.lefthandfreegames.game.GameScreen.render(GameScreen.java:350)
at com.badlogic.gdx.Game.render(Game.java:46)

o co chodzi? rozumiem, że czegoś nie może narysować, ale co konkretnie?

1 użytkowników online, w tym zalogowanych: 0, gości: 1