LibGDX, sprite nie jest zgrany z body (Box2d)

0

Witam,

uczę się pisać gry w LibGDX. Tworzę sobie taką prostą platformówkę po której biega sobie jakiś stworek. Korzystam przy tym z Box2D. I podczas testu aplikacji na desktopie, moj Sprite idzie dokładnie równo z body które stworzyłem, nawet przy zmianie rozmiaru okna wszystko gra. Ale kiedy odpalam tą gierkę na telefonie to Sprite jest przesunięty zdecydowanie dalej od body i porusza się trochę szybciej, bo podczas biegu w lewo go dogania. Jak poradzić sobie z tym problemem? Może to wina telefonu, Samsung Galaxy GIO z Androidem w wersji 2.3.4.
Sprawdzam współrzędne Sprita i body i są równe. Nie wiem o co może chodzić.

0

Fajny Pikachu :)

Co do problemu... Czy w każdej rysowanej klatce ustawiasz pozycję sprite'a na pozycję body?
Bo wiesz, jeśli Box2D ma kontrolować twojego sprite, to chcąc go poruszyć poruszasz tylko body. Sprite zawsze ma przyjmować pozycję body. Nie powinieneś przemieszczać sprite'a.

BTW. jeśli chcesz zrobić platformówkę, to naprawdę nie powinieneś używać silnika fizycznego do poruszania postacią. Silniki fizyczne są nieobliczalne. Jeśli sam zaprogramujesz, to będziesz miał większą kontrolę nad tym co się dzieje na scenie.

0

Wygląda to na problem z kamerą albo viewportem, a dokładniej z ich konfiguracją. Jeśli masz debugowanie włączone dla box2d to ono korzysta z swojej kamery więc może być tak że fizyka widzi ekran jako 1024x1024 a np. twoja kamera jako 920x920 także im dalej od 0 tym te przesunięcie będzie bardziej widoczne. Dla przykładu jeśli twoje realne body będzie na pozycji 512 to debugRender box2d pokaże je na środku aktualnie rysowanego ekranu, ale dla kamery i sprita będzie to już za połową, bliżej prawej strony bo dla niego połową jest 460. Daje to przesunięcie równe 52px, ale dla lewego, dolnego rogu czyli 0 w układzie współrzędnych wszystko będzie zgrane bo tam skala tego problemu jest mała i rośnie proporcjonalnie do oddalania się od jego centrum.

0

Zrobiłem tak, na początku, przed wszystkim, pobiera mi szerokość i wysokość okna. I wtedy rysuje tego Pikachu w dobrym miejscu, ale wszystkie elementy planszy są poprzestawiane. Nie jest to dobre rozwiązanie

W metodzie gdzie wszystko jest renderowane, jak ustawiam

        batch.setProjectionMatrix(cam.combined); 

to w ogóle nie rysuje tego Sprita.

Poniżej cała metoda z klasy gameRender


        public void render() {
        Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
        b2d.render(gameWorld.getWorld(), cam.combined);
        batch.setProjectionMatrix(cam.combined);
        batch.begin();  
        gameWorld.getHero().getSprite().draw(batch);
        batch.end();
    }

ViewPort też jest ustawiony.

public class GameScreen implements Screen{

    private GameWorld gameWorld;
    private GameRender gameRender;
    private OrthographicCamera cam;
    private Viewport viewport;

    public GameScreen(TowerGame towerGame) {

        cam = new OrthographicCamera();
        cam.setToOrtho(true, TowerGame.GAME_WIDTH / TowerGame.PixPerMeter, TowerGame.GAME_HEIGHT / TowerGame.PixPerMeter);

        viewport = new FitViewport(TowerGame.GAME_WIDTH/TowerGame.PixPerMeter, TowerGame.GAME_HEIGHT/TowerGame.PixPerMeter,cam);
        gameWorld = new GameWorld(cam);
        gameRender = new GameRender(gameWorld,cam);
//      Gdx.input.setInputProcessor(new InputHandler(gameWorld.getHero()));
    }

    @Override
    public void show() {

    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        gameRender.update();
        gameRender.render();

    }

    @Override
    public void resize(int width , int height) {
        viewport.update((int)(width), (int)(height));
    }
0

Powiedz mi do czego ten viewport?
Dobra na początek zrób tak że:

 cam = new OrthographicCamera();
 cam.setToOrtho(true, TowerGame.GAME_WIDTH / TowerGame.PixPerMeter, TowerGame.GAME_HEIGHT / TowerGame.PixPerMeter);

zamień na

float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight(); 
cam  = new OrthographicCamera(w, h);

Pokaż metode update, bo myślę że problem leży gdzieś tam. Dodatkowo proponuje przyjąć konwencję w libGDX że metoda o nazwie render zawiera w sobie update i draw. Update - uaktualnia mechanikę / draw- rysuje.

0

Klasa GameRender:

public class GameRender {

    private GameWorld gameWorld;
    private Box2DDebugRenderer b2d;
    private OrthographicCamera cam;
    private SpriteBatch batch;

    public GameRender(GameWorld gameWorld, OrthographicCamera cam) {

        batch = new SpriteBatch();
        this.b2d = new Box2DDebugRenderer();
        this.gameWorld = gameWorld;
        this.cam = cam;
    }

    public void render() {
        update();
        Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);

        b2d.render(gameWorld.getWorld(), cam.combined);
        batch.begin();  
            gameWorld.getHero().getSprite().draw(batch);
        batch.end();
    }

    public void update(){
        gameWorld.getHero().update();
        gameWorld.update();
        cam.update();       
    }

} 

Klasa GameWorld, tutaj jest tworzony "świat" fizyka, obsługa klawiszy i ekranu:

public class GameWorld {

    private Hero hero;

    private World world;
    private OrthographicCamera cam;
    private Body bodyplatform; // ruchoma platforma
    private float velX = 0.5f; // Prędkość platformy

    public GameWorld(OrthographicCamera cam) {

        this.cam = cam;
        world = new World(new Vector2(0, 10f), true);

        hero = new Hero(400,150, 20,20 , world);

        createFloor();
    }

    private void InputHandler(){//obsloga klawiszy

        hero.prevRunningRight = hero.runningRight;
        if(Gdx.input.isKeyJustPressed(Keys.W) && (hero.getState() != HeroStates.JUMP || hero.getState() != HeroStates.FALL)){
            hero.jump();
            if(hero.getHeroBody().getLinearVelocity().y < 0){
                //pika();
            }

        }
        if(Gdx.input.isKeyPressed(Keys.A)){
            hero.moveLeft();
            hero.runningRight = false;
            hero.actualState = HeroStates.RUN;
        }
        if(Gdx.input.isKeyPressed(Keys.D)){
            hero.moveRight();
            hero.runningRight = true;
            hero.actualState = HeroStates.RUN;
        }

        if(Gdx.input.isTouched())
            if(Gdx.input.getX() > Gdx.graphics.getWidth()/2){
                hero.moveRight();
                hero.runningRight = true;
                hero.actualState = HeroStates.RUN;
            }
            else if(Gdx.input.getX() < Gdx.graphics.getWidth()/2){
                hero.moveLeft();
                hero.runningRight = false;
                hero.actualState = HeroStates.RUN;
            }
        if(Gdx.input.justTouched() && Gdx.input.getY() < Gdx.graphics.getHeight()/2){
            hero.jump();
            pika();
        }

        hero.update();
    }
    public void update() {

        InputHandler();

        if(bodyplatform.getPosition().x > 7)
            velX = -0.5f;
        else if(bodyplatform.getPosition().x < 5f)
            velX = 0.5f;
        bodyplatform.setLinearVelocity(velX, 0f); //platforma jezdzi tam i z powrotem

        world.step(1/60f, 6 , 2);

    }

    public Hero getHero(){
        return hero;
    }

    public World getWorld(){
        return world;
    }

    private void createFloor(){ // tutaj tworzone jest body platform podlogi i jakies piłeczki

        BodyDef bdef = new BodyDef();
        bdef.type = BodyType.StaticBody;
        bdef.position.set(new Vector2(0f, TowerGame.GAME_HEIGHT/ TowerGame.PixPerMeter));

        Body body = world.createBody(bdef);

        PolygonShape poly = new PolygonShape();

        poly.setAsBox(cam.viewportWidth, 10f / TowerGame.PixPerMeter);

        body.createFixture(poly, 0.5f);

        poly.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.StaticBody;
        bdef.position.set(new Vector2(1f,170f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        poly = new PolygonShape();
        poly.setAsBox(0.3f, 0.3f);
        body.createFixture(poly,0.1f);
        poly.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(1.8f,110f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        CircleShape circle = new CircleShape();
        circle.setRadius(0.1f);
        body.createFixture(circle,0.2f);
        circle.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(3.5f,110f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        circle = new CircleShape();
        circle.setRadius(0.1f);
        body.createFixture(circle,0.2f);
        circle.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(3.5f,100f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        circle = new CircleShape();
        circle.setRadius(0.1f);
        body.createFixture(circle,0.2f);
        circle.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(3.5f,90f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        circle = new CircleShape();
        circle.setRadius(0.1f);
        body.createFixture(circle,0.2f);
        circle.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(3.5f,80f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        circle = new CircleShape();
        circle.setRadius(0.1f);
        body.createFixture(circle,0.2f);
        circle.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.StaticBody;
        bdef.position.set(new Vector2(2f,300f / TowerGame.PixPerMeter));
        body = world.createBody(bdef);

        poly = new PolygonShape();
        poly.setAsBox(0.9f, 0.3f);
        FixtureDef fixdef = new FixtureDef();
        fixdef.friction = 0;
        fixdef.restitution = 0.4f;
        fixdef.density = 1f;
        fixdef.shape = poly;
        Fixture fix = body.createFixture(fixdef);
        poly.dispose();

        bdef = new BodyDef();
        bdef.type = BodyType.KinematicBody;
        bdef.position.set(new Vector2(5f,300f / TowerGame.PixPerMeter));
        bodyplatform = world.createBody(bdef);
        poly = new PolygonShape();
        poly.setAsBox(0.9f, 0.3f);  
        bodyplatform.createFixture(poly, 0.1f);
        poly.dispose();
    }
}

Klasa Hero, tutaj tworzony jest Pikachu, jego body i sprite:

public class Hero{

    public static enum HeroStates {JUMP, RUN, STAND, FALL};

    public HeroStates actualState;
    private HeroStates prevState;

    public static final float MAX_VELOCITY = 1.5f;
    private float x,y,width,height;

    private World world;
    private BodyDef bdef;
    private Body body;
    private FixtureDef fixdef;
    private Fixture fixture;
    private CircleShape circle;
    private Vector2 velocity,position;

    private TextureAtlas atlas;
    private Animation run;
    private TextureRegion stand;
    private TextureRegion jump;

    private Sprite sprite;

    private float timer;
    public boolean runningRight, prevRunningRight;

    public void dispose(){
        world.dispose();
        atlas.dispose();
    }
    public Hero(int posX, int posY, int width, int height, World world){

        this.world = world;
        this.x = posX;
        this.y = posY;
        this.width = width;
        this.height = height;
        actualState = HeroStates.STAND;
        prevState = HeroStates.STAND;

        this.atlas = new TextureAtlas("pika.pack"); 
        this.timer = 0.0f;
        this.runningRight = true;
        this.prevRunningRight = true;
        heroDef();
                createSprite();
    }

    public void jump(){
        if(getState() != HeroStates.JUMP){
            if(getState() != HeroStates.FALL)
                body.applyLinearImpulse(new Vector2(0,-8f), body.getLocalCenter(), true);
        }
    }

    public void moveLeft(){

        velocity = body.getLinearVelocity();
        if(velocity.x > -MAX_VELOCITY)
            body.applyLinearImpulse(new Vector2(-0.9f,0),  body.getLocalCenter(), true);
    }

    public void moveRight(){

        velocity = body.getLinearVelocity();
        if(velocity.x < MAX_VELOCITY)
            body.applyLinearImpulse(new Vector2(0.9f,0), body.getLocalCenter(), true);  
    }

    public Body getHeroBody(){
        return body;
    }

    public boolean isRunningRight(){
        return runningRight;
    }

    public TextureRegion getFrame(){

        TextureRegion region;
        actualState = getState();

        timer = actualState == prevState ? timer + Gdx.graphics.getDeltaTime() : 0;
        switch(getState()){
        case JUMP:
            region = jump;
            break;//*/
        case STAND:
            region = stand;
            break;
        case RUN:
            region = run.getKeyFrame(timer,true);
            break;//*/

        default:
            region = stand;
        }

          if((getHeroBody().getLinearVelocity().x < 0 || !runningRight) && !region.isFlipX()){
                region.flip(true, false);
                runningRight = false;
            }

            //if mario is running right and the texture isnt facing right... flip it.
            else if((getHeroBody().getLinearVelocity().x > 0 || runningRight) && region.isFlipX()){
                region.flip(true, false);
                runningRight = true;
            }

        return region;
    }

    public Sprite getSprite(){
        return sprite;
    }

    public float getX(){
        return getHeroBody().getPosition().x*TowerGame.PixPerMeter;
    }
    public float getY(){
        return getHeroBody().getPosition().y*TowerGame.PixPerMeter;
    }

    public HeroStates getState(){
        prevState = actualState;
        if(body.getLinearVelocity().y < 0 || (body.getLinearVelocity().y > 0  && prevState == HeroStates.JUMP))
            return HeroStates.JUMP;
        else if(body.getLinearVelocity().y > 0)
            return HeroStates.FALL;
        else if(body.getLinearVelocity().x != 0)
            return HeroStates.RUN;
        else
            return HeroStates.STAND;
    }

    public void update(){
        sprite.setRegion(getFrame());
        sprite.setPosition(getX()-getFrame().getRegionWidth()/2, TowerGame.GAME_HEIGHT - getY() - getFrame().getRegionHeight()/2+2);
    }
    private void heroDef(){ //definicja pikachu 

        bdef = new BodyDef();
        bdef.type = BodyType.DynamicBody;
        bdef.position.set(new Vector2(x / TowerGame.PixPerMeter,y / TowerGame.PixPerMeter));

        body = world.createBody(bdef);

        body.setLinearDamping(3);

        circle = new CircleShape();
        circle.setRadius(10 / TowerGame.PixPerMeter);

        fixdef = new FixtureDef();
        fixdef.shape = circle;

        fixture = body.createFixture(fixdef);

        circle.dispose();   
    }

    private void createSprite(){//wygląd pikachu
                Array<TextureRegion> frames = new Array<TextureRegion>();

                for(int i = 0; i < 3; i++)
                    frames.add(new TextureRegion(atlas.findRegion("run"), i*32,0,32,31));
                run = new Animation(0.2f, frames);
                frames.clear();

                stand = new TextureRegion(atlas.findRegion("stand"),32,0,32,31);
                jump = new TextureRegion(atlas.findRegion("jump"),0,0,32,31);

                sprite = new Sprite(jump);
                //sprite.setOrigin(jump.getRegionWidth()/2, jump.getRegionHeight()/2);
    }
}

importów nie kopiowałem, ale były to głównie biblioteki badlogic.

Zmieniłem tak jak mi poleciłeś. I pikachu biega jakby dobrze, bo nie widać platform itd, ale wskakuje gdzieś coś się dzieje. Ale wszystkie body są rysowane w prawym górnym rogu, jakby były odbite względem punktu jakiegoś. Na załączonym obrazku jest Sprite pokazany w akcji i po drugiej stronie ten okrąg to jego body. Body jest do góry nogami.

1

Powiem ci że nie mam czasu przeanalizować całego kodu ale... Zacznij od zlikwidowania scalara

*TowerGame.PixPerMeter

Spójrz że klasa Sprite dziedziczy po TextureRegion. Więc zamiast podmieniać do sprite użyj klase Sprite bezpośrednio

 private void createSprite(){//wygląd pikachu
                Array<Sprite> frames = new Array<Sprite>();

                for(int i = 0; i < 3; i++)
                    frames.add(atlas.createSprite("run")); //Tutaj zdefiniuj sobie te indexy jeśli są potrzebne. 
                run = new Animation(0.2f, frames);
                frames.clear();

                stand = new TextureRegion(atlas.createSprite("stand"),32,0,32,31);
                jump = new TextureRegion(atlas.createSprite("jump"),0,0,32,31);

                sprite = new Sprite(jump); //<-- Wywal to
                //sprite.setOrigin(jump.getRegionWidth()/2, jump.getRegionHeight()/2);
    }

i działaj bezpośrednio na obiektach Sprite(żadną optymalizacją się nie przejmuj na tą chwilę - jest to wręcz dla ciebie szkodliwe). W sumie do testów działaj tylko na jednym sprite. I w sumie poleciłbym przepisać tą grę od początku w ramach nauki. Tylko bez żadnych ficzerów typu animacja i scalowanie mapy. Zrób zwyczajna kamerę, zwyczajnego sprita pikaczu i zwyczajne box2d body. Następnie zrób tak by pikaczy podążał za body. Potem to zrefakoryzuj i zabierz się za animiację. Polecam użyć do tego osobnej klasy:)

0

Dzięki ci za pomoc :) Zacznę jeszcze raz od nowa.
Rozumiem że dla Pikachu mam stworzyć klasę która będzie dziedziczyć po Sprite?

2

Ogólnie nie stosuj za dużo dziedziczenia bo to jest ograniczające:) Zrób obiekt Pikachu i w niego wtłocz Sprite i Body. Potem w update nałóż na Sprite pozycje z Body i w draw narysuj sprite. Jak się już trochę podszkolisz to obczaj sobie systemy ECS np. Ashley albo Artemis.

0

Zrobiłem to :) działa jak należy :) Dzięki ci bardzo za pomoc, już nie miałem pojęcia o co chodzi, a problem był w tym skalowaniu jednak. Teraz jednak zastanawiam się czemu body które jest dynamicznym, gdy na nim stanę moim pikachu to on zachowuje się jakby skakał.

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