Podwójne dziedziczenie w javie

0

Piszę grę na Androida i mam klasę Bullet która jest poruszającą sie kulką-pociskiem, chce teraz mieć klasę RotateBullet któa dziedziczy z Bullet i będzie to kulka która będzie się poruszała. Chcę też aby kulki były rozróżnialne na moje i przeciwnika, więc mam klasy HeroBullet i EnemyBullet(mają na przykład inne metody służące do usuwania). No i teraz jak chcę mieć rotującą się kulkę przeciwnika to przydało by się zrobić kulkę która ma dwa dziedziczenia, no ale w javie tak nie można, ale na pewno musi być jakiś trick którego nie znam. Z góry dzięki za pomoc.
package com.example.user.bulletfalls;

import android.content.Context;
import android.graphics.Point;
import android.widget.FrameLayout;

import com.example.user.bulletfalls.JsonClasses.Bullets.JsonBullet;

public abstract class Bullet extends ViewElement {
    boolean collisionAble;
    public Bullet(Context context,  int power, int speed, Point startingPoint, int width, int height, int randeringFrequency, int imageResource, FrameLayout frame,GameController controller,boolean collisionAble) {
        super(context, power, speed, startingPoint, width, height, randeringFrequency, imageResource,frame);
        this.controller=controller;
        this.collisionAble=collisionAble;

    }
    public Bullet(Context context, JsonBullet jsonBullet)
    {
        super(context,jsonBullet);
        this.collisionAble=jsonBullet.isCollisionAble();
    }


    public boolean isCollisionAble() {
        return collisionAble;
    }

    public void setCollisionAble(boolean collisionAble) {
        this.collisionAble = collisionAble;
    }

    abstract public void destroy();

    protected void move()
    {

        ((Game)this.getContext()).setX(this,(int)(getX()+speed));
        if(getX()+getWidth()>frame.getWidth()||getX()<0||getY()+getHeight()>frame.getHeight()||getY()<0)
        {
            destroy();

        }
    }





}
public class RotateBulletHero extends HeroBullet {
    int rotationSpeed;
    public RotateBulletHero(Context context, int power, int speed, Point startingPoint, int width, int height, int randeringFrequency, int imageResource, FrameLayout frame, GameController controller, boolean collisionAble,int rotationSpeed) {
        super(context, power, speed, startingPoint, width, height, randeringFrequency, imageResource, frame, controller, collisionAble);
        this.rotationSpeed=rotationSpeed;
    }

    public RotateBulletHero(Context context, JsonBullet bullet) {
        super(context, bullet);
    }

    @Override
    public void move()
    {
        ((Game)this.getContext()).setX(this,(int)(getX()+speed));

        if(getX()+getWidth()>frame.getWidth()||getX()<0||getY()+getHeight()>frame.getHeight()||getY()<0)
        {
            destroy();

        }
    }
}
public class HeroBullet extends Bullet {
    public HeroBullet(Context context, int power, int speed, Point startingPoint, int width, int height, int randeringFrequency, int imageResource, FrameLayout frame, GameController controller,boolean collisionAble) {
        super(context, power, speed, startingPoint, width, height, randeringFrequency, imageResource, frame, controller,collisionAble);
    }

    public HeroBullet(Context context, JsonBullet bullet) {
        super(context,bullet);
    }


    public void destroy()
    {


        controller.removeHeroBullet(this);
        ((Game)getContext()).removeObject(this);



    }

    @Override
    public ViewElement clone() {
        HeroBullet bullet= new HeroBullet(this.getContext(),this.power,this.speed,this.startingPoint,this.width,this.height,this.randeringFrequency,imageResources,this.frame,this.controller,this.collisionAble);
    return bullet;
        }
}
public class EnemyBullet extends Bullet {
    public EnemyBullet(Context context, int power, int speed, Point startingPoint, int width, int height, int randeringFrequency, int imageResource, FrameLayout frame, GameController controller,boolean collisionAble) {
        super(context, power, speed, startingPoint, width, height, randeringFrequency, imageResource, frame, controller,collisionAble);
        if(speed>0)
        {
            this.speed=speed*(-1);
        }
    }

    public EnemyBullet(Context context, JsonBullet bullet) {
        super(context,bullet);
    }




    @Override
    public void destroy() {
        ((Game)getContext()).removeObject(this);
        controller.removeEnemyBullet(this);

    }

    @Override
    public ViewElement clone() {
        EnemyBullet bullet= new EnemyBullet(this.getContext(),this.power,this.speed,this.startingPoint,this.width,this.height,this.randeringFrequency,imageResources,this.frame,this.controller,this.collisionAble);
        return bullet;
    }
}
3

Podstawowy trick jest jeden. Używaj kompozycji zamiast dziedziczenia. :)

0

To, że potrzebujesz to zrobić, znaczy że masz generalnie problem z architekturą. Pytanie co zrobisz jak będzie dwóch przeciwników. Klasę Enemy1Bullet i Enemy2Bullet? A jak będzie ich 5? A jak będą się spawnować w czasie życia aplikacji?

Nie powinno być klas EnemyBullet i HeroBullet. Trzymaj je w jakimś serwisie (a nawet w repo) i zarządzaj nimi jako obiektami. Determinuj ich zależność na podstawie przynależności do obiektu, np.player.getBullet().destroy()

0

Bullet jest osobnym obiektem tworzonym przez bohatera następnie nie jest już on powiązany z bohaterem, jego poruszaniem i kolizjami z innymi obiektami zajmuje się klas Controller która przechowuje wszystkie obiekty które biorą udział w grze. Klasy HeroBulelt i Enemy Bullet różnią się od siebie usuwaniem. Jedne i drugie są w różnych tablicach, jak sprawdzam czy strzeliliłem w przeciwnika to nie muszę sprawdzać kolizji ze wszystkimi kulkami a tylko z tablicą HeroBullets

0

@Zakręcony Samiec to nadal jest zupełnie bez sensu. W ogóle cały ten kod wygląda na mocno pokręcony, mimo że robi zupełnie trywialne rzeczy. Moja rada: poćwicz podstawy zanim zaczniesz pisać gry.

0

Dzięki za nic nie wnoszący, pesymistyczny komentarz, mam nadzieję, że ci ulżyło ;)

0

Tyle że nie jest to pesymistyczny komentarz tylko niestety fakt. Ale to nie jest nic złego, każdy w końcu zaczynał, nie ma sensu brać się za trudniejsze rzeczy jak się nie umie prostych. Mówimy to dla Twojego dobra (i dla tych z którymi może będziesz kiedyś pracować :D )

1

@Trzeźwy Kura komentarz nie jest pesymistyczny tylko realistyczny. To co napisałeś to jest dramat, wynikający z tego że po prostu masz mało doświadczenia. Zamiast pisać strzelankę to zacznij może od gry w karty albo od planszówki? (jak już musisz grę)

0

Skoro bardzo chcesz zrobić sobie krzywdę to trik się nazywa interfejsy i metody domyślne.

Sam tego chciałeś :P

0

Tak jak reszta pisze, twój kod cierpi na architekturę big bull of mud. Proponuję ci refaktoryzować go, wtedy nie będziesz potrzebował czegoś takiego jak multi-inheritance (jakby zwykłe dziedziczenie nie było dostatecznie złe)

  1. Proponuję ci, zrobić z Bulletów zwykłe pojosy (oczywiście z getterami i seterami):
class Bullet {
     boolean collisionable;
     Point position;
}

oraz

class RotateBullet extends Bullet{
     boolean collisionable;
     Point position;
     int rotationSpeed;
}

Zwykłe pojosy, które nie wiedzą o kontekście gry, nie decydują same o swoim cyklu życia

  1. Zrób serwis, który zarządza tymi pociskami. Napisałeś, że masz tablice (wat?!) tych pocisków i sprawdzasz kolizje między przeciwnikami.
class BulletService {
    GameContext gameContext;
    Map<PlayerId, List<Bullet>> bullets;

    void create(playerId){
        bullets.putIfAbsent(playerId, new ArrayList<>());
        bullets.get(playerId).add(new Bullet());
   }

   void move(){
        bullets.values().stream().flatMap(Collection::stream).forEach(bullet -> bullet.setPosition(newPosition));
        checkCollisions();
    }

    void checkCollisions(){
        //Some collision check procedure
        collidedBullets.forEach((playerId, bullet) -> {
            gameContext.remove(bullet);
            bullets.get(playerId).remove(bullet);
        });
    }
}

Oczywiście wszystko to pseudokod.
3. Nie używaj clone'a chyba, że w androidzie jest to z jakiegoś powodu promowane
4. Staraj się nie używać rzutowanie jeśli to możliwe
5. Proponuję ci używać słowa-klucza controller w kontekście "web controllera" (mapowanie restów itp) chyba, że w androdzie jest inna konwencja
6. "collisionable", a nie "collisionAble"

0

@Trzeźwy Kura: Może na początek da się zrobić np. BulletSpecification i produkować Bullet w oparciu o specyfikacje? Taki spec mógłby zawierać "parametry bojowe", bitmapy/animacje. Alternatywnie wzorzec Builder i produkować Bullet o dowolnej specyfikacji. Czyli miałbyś wspomnianą kompozycję zamiast dziedziczenia.

Taki spec powinien zredukować Ci ilość parametrów w konstruktorze, a logika biznesowa w Bullet mogłaby korzystać z tej specyfikacji.

Dodatkowo, wydaje mi się, że mieszasz różne rzeczy w tym Bulllet, łamiąc tym samym zasadę SRP. Dlaczego obiekt modelu ma mieć dostęp do kontrolera? :-)
Nie wydaje CI się, że to kontroler powinien reagować na naciśnięcie guzika, aktualizować model i zlecać jego renderowanie?

0

Jak ci już napisano - nie modeluj zachowań za pomocą hierarchii dziedziczenia. Problem, na który się natknąłeś, najlepiej pokazuje, dlaczego to jest ślepy zaułek. Wielodziedziczenie, nawet gdyby było możliwe, to ucieczka z deszczu pod rynnę.

Szczególne rodzaje zachowania - np. wykrywania kolizji, przesuwania pocisku, usunięcia go itd. - powinny być wymodelowane jako osobne obiekty, i "wstrzyknięte" do klasy Bullet bądź do kontrolera, czy w każdym razie jakiejś klasy, która zajmuje się zarządzaniem i przeliczaniem całej sceny.

Poczytaj o wzorcach projektowych, np.:

https://pl.wikipedia.org/wiki/Strategia_(wzorzec_projektowy)
https://pl.wikibooks.org/wiki/Kody_źródłowe/Strategia_(wzorzec_projektowy)#Java

https://pl.wikipedia.org/wiki/Odwiedzaj%C4%85cy

https://pl.wikipedia.org/wiki/Obserwator_(wzorzec_projektowy)
https://pl.wikibooks.org/wiki/Kody_źródłowe/Obserwator_(wzorzec_projektowy)

To są sposoby na poskromienie pokusy wielokrotnego dziedziczenia.

Forum ewidentnie nie radzi sobie z linkami na WikiBooks, ale są one w artykułach wikipedyjnych. Generalnie i tak lepiej sięgnąć do angielskojęzycznych wersji tych materiałów.

0

W końcu ktoś się zlitował i napisał wartościowy komentarz, wielkie dzięki!!! Jak skończę gierkę to wstawię linka, żeby niedowiarki nauczyły się pisać komentarze.

0

Poczytałem trochę o tych wzorcach i jednak dalej nie rozumiem. Jedno czego nie napisałem to że ViewElement dziedziczy po ImageView, czyli ostatecznie Bullet jest reprezentacją logiczną kulki a jednocześnie graficzną. Aktywność na której jest gra odpala grę przez stworzenie controllera, controller przechowuje bohatera, listę przeciwników, i dwie listy kulek. Odpala timer, co określony cykl controller dodaje nowego wroga, wywołuje na każdym obiekcie metodę move, następnie na postaciach wywołuje shoot(), postać tworzy nową kulkę, następnie controller sprawdza kolizje, usuwa kulki i postacie (wywołując na nich destroy()) które nie mają życia i czeka na kolejny cykl timera.

  1. Jak mam używać interfejsów zamiast dziedziczenia skoro ja potrzebuję nowe atrybuty
  2. Z tym herobullet to była racja, że powinienem użyć interface,( ostatecznie zrezygnowałem z rozróżniania kulek na moje i przeciwnika w tym elemencie)
  3. Z tymi pojosami które ktoś napisał czy to ma jakiś sens w tym przypadku, kulki swoją drogą a późnij tworzenie ImageView zupełnie osobno, nie wiem dla mnie to wydaje się nie naturalne ale może tak się robi proszę o radę
    4.Clone robię po to, że w mojej klasie Hero np. mam referencję do kulki którą strzela bohater, potem klonuję ja i wypuszczam, trochę to dziwne więc jak ktoś ma lepszy pomysł to tutaj najbardziej proszę o radę
  4. W kontrolerze mam kulki i czy controller powinien je przesuwać czy tylko wywoływać klasę move()?
  5. Zgadzam się z tym że kulki nie powinny mieć kontrollera zrobiłem to bo tak było łatwiej, ale wiedziałem że to jest bzdura faktycznie trzeba to poprawić
  6. Nie rozumiem do końca o co chodzi z tą specyfikacją, jakby można było rozwinąć temat albo wysłać jakiś link do materiałów to by było super
0
Trzeźwy Kura napisał(a):

...

  1. Nie rozumiem do końca o co chodzi z tą specyfikacją, jakby można było rozwinąć temat albo wysłać jakiś link do materiałów to by było super

Jeśli chodzi o specyfikacje, to jak dla mnie taka kulka ma charakterystyki:

  • masa
  • prędkość lotu
  • uszkodzenia
  • akcje związane z kolizjami (spowolnij bohatera, unieruchom bohatera, rykoszet od przeszkody)
  • trajektoria lotu (linia prosta, sinusoida, ... np. z malejącą amplitudą na potrzeby rzutu granatem odbijającym się od ziemi)
  • wygląd (bitmapa? link do bitmapy? )
  • min. skill bohatera żeby mógł używać tak niebezpiecznej kulki
  • itp.

Czyli różne kulki mają różną specyfikację i można taki spec różnie implementować, np.

Opcja 1) konstruktor z zamkniętą listą parametrów, które mogą się przewinąć w spec. Gdzieś tam przy inicjalizacji gry tworzysz instancje swoich specyfikacji.

BulletSpec { 
	BulletSpec(int mass, int speed, int damage, ...) //
	...
}

BulletSpec specialBulllet = new BulletSpec(3,10,20,...);

Opcja 2) Jak masz dużo parametrów, to konstruktor może wyglądać koszmarnie. Wówczas możesz użyć buildera i tworzyć specyfikacje za pomocą konstrukcji jak niżej:

	BulletSpec specialBullet =  BulletSpecBuilder
									.createBulletSpec()
									.setSpeed(3)
									.setMass(12)
									.setDamage(15)
									.addAction(new SlowDownHeroAction())
									.addAction(new TickleHeroAction())
									.setMinLevel(3)
									.setFlighPath(RANDOM_FLIGH_PATH)
									.build() 

Pewnie można mieć inne strategie implementacji takich specyfikacji. Istotą jest jednak, nie to jak się tworzy speca, tylko jak jest wykorzystywany.
Jak tworzysz kulkę, to podajesz jej specyfikację zgodnie z którą ma wyglądać/zachowywać się, zaś w logice wykorzystujesz dane ze specyfikacji.

class Bullet { 
	
	BulletSpec spec; 			// tu mamy charakterystyki kulki , czyli **korzystamy z kompozycji**
	
	Position currentPosition; 	// bieżąca pozycja, ustawiana po wystrzeleniu kulki
	int currentTick; 			// zliczamy ticki na potrzeby aktualizacji położenia kulki 
	int power;                                // modeluje wytracanie energii z upływem czasu
	
	Bullet(BulletSpec spec) {
		...
		this.spec = spec; 
		...
	}
	
	CollisionResult collideWith(GameObject target) {
		// pseudokod - do przemyślenia jakie akcje chcesz wykonywać i jakich potrzebują argumentów
		...
		spec.getActions().forEach(executeAction(target));
		...
	}
	
	Bullet launch(Postion heroPosition) {
		...
		currentTick = 0;
		currentPosition = heroPosition;
		...
	}
	
	Position move() {
		// wywoływane co tick , korzysta ze speca, żeby określić nową pozycje zgodnie z torem lotu wynikającym ze speca 
		...
		currentPosition = calculateNewPosition()  // aktualizuje pozycje zgodnie z trajektorią wynikająca ze speca, bądź jeśli power<0, to zmienia trajektorię na swobodny spadek
		power -= spec.getCombustionRatio();
                ... 
                // inna radosna twórczość związana z ruchem
		...
	}
	
}
0

Superowo, wielkie dzięki ;)

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