3
\$\begingroup\$

I have been working on a 2d game that is similar to billiards/pool. I've got the basic game down and now I'm working on some visual effects. One of the things I would like to do is have have numbers pop up on the screen where a scoring event occurs and then have that number "swoosh up" to the main score at the top of the screen. To accomplish this I have been trying to overlay a Stage onto my main game area so that I can add score text and then let the LibGDX Scene2d take over. I am adding the text as an actor to the stage and then assigning some actions to it to achieve the desired effect. The problem I'm having is that when I add the object I need to convert from world to stage coordinates. This is because I want to place the score popup where two balls touch, which is in world coordinates. I have tried to use the "project" method of the stage's viewport (it is a FitViewport) but the coordinates never translate correctly.

Now that I've explained what I'm trying to do, I was hoping that someone would know how I could pull this off. I'm sure it isn't difficult and that I'm missing some small detail.

Here is the relevant code: This is the class that will be used to overlay a stage onto the main game screen.

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.viewport.Viewport;

public class UIOverlay {

    private Skin skin = new Skin(   Gdx.files.internal("skins/gamePlaySkin.json"));
    private Stage stage = new Stage();

    public void init (Viewport viewport) {

        stage.setViewport(viewport);
        stage.getViewport().update(GameWorld.WIDTH, GameWorld.HEIGHT, true);
    }

    public void render () {

        stage.act();
        stage.draw();
    }

    public Stage getStage () {

        return this.stage;
    }

    public void addScorePopup (int score, Vector2 worldPos) {

        Viewport vp = stage.getViewport();
        Vector2 screenPos = new Vector2(worldPos.x, worldPos.y);
        vp.project(screenPos);

        // Some sample vectors after projection:
        // worldPos.x = 0.88585174, worldPos.y = 1.6051532
        // screenPos.x = 0.885849, screenPos.y = 1022.39484
        // Another Set:
        // worldPos.x = 0.63776636, worldPos.y = 0.8991218
        // screenPos.x = 0.63777924, screenPos.y = 1023.1009

        Label popLabel = new Label(String.valueOf(score), skin, "popupScore");
        stage.addActor(popLabel);
        popLabel.setPosition(screenPos.x, screenPos.y);

        float time = 0.5f;
        popLabel.addAction(Actions.delay(1000, Actions.sequence(Actions.moveTo(0.0f, 0.0f, time, Interpolation.sineIn), Actions.alpha(0, 0.1f))));
    }
}

This is where I set up the main game viewport:

public static void init(){
    // GameWorld.WIDTH and HEIGHT are 1280 and 1024
    m_camera = new OrthographicCamera();
    m_camera.setToOrtho(true, GameWorld.WIDTH, GameWorld.HEIGHT);
    m_viewport = new FitViewport(GameWorld.WIDTH, GameWorld.HEIGHT, m_camera);
}

When creating the UIOverlay I pass the game's m_viewport into UIOverlay.init(). I have also tried creating a new FitViewport in UIOverlay with the same dimensions as the game's viewport, but have the same behavior.

\$\endgroup\$
4
\$\begingroup\$

You need to project from the worldspace to screen space using the correct camera, not the project method on the Vector2. Then unproject back into the camera of the Stage (but in your case that is set to the screen so it should just work).

I think you want something like this;

public void addScorePopup (int score, Vector2 worldPos, Camera gameCamera) {
    Vector3 screenCoords = gameGamera.project(new Vector3(worldPos.x, worldPos.y, 0));
    // screenCoordsA is now the position on screen of the collision
    // Now you need to take that into the viewport of the Stage
    Vector3 stageCoord = stage.getCamera().unproject(screenCoords);

    popLabel.setPosition(stageCoord.x, stageCoord.y);

    // etc, etc...
}

It might seem weird to unproject but it means you can use any viewport for the Stage, it won't be bound to the dimensions of the screen which is convenient if your doing cross-platform things.

Here is a full, working example of a Scene2D Actor following a green ball (whose coordinates are updated in world space), and another Actor animating from the balls starting position to (0, 0);

Actor following world space entity

    package com.bornander.androidstudiosandbox;

    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.actions.Actions;
    import com.badlogic.gdx.scenes.scene2d.ui.Label;
    import com.badlogic.gdx.scenes.scene2d.ui.Skin;
    import com.badlogic.gdx.utils.viewport.FitViewport;

    public class MyAndroidStudioSandboxGame extends ApplicationAdapter {

        private OrthographicCamera m_camera;
        private FitViewport m_viewport;
        private Stage m_stage;
        private Label m_follow;
        private Label m_goToZero;
        private ShapeRenderer m_shapeRenderer;
        private Vector2 m_worldPosition = new Vector2();

        @Override
        public void create () {
            m_camera = new OrthographicCamera();
            m_camera.setToOrtho(true, 1280, 1024);
            m_camera.position.setZero();
            m_viewport = new FitViewport(1280, 1024, m_camera);

            m_stage = new Stage();
            m_stage.getViewport().update(1280, 1024, true);

            m_camera.update();
            m_shapeRenderer = new ShapeRenderer();

            Skin skin = new Skin(Gdx.files.internal("uiskin.json"));

            m_follow = new Label("Follow", skin.get(Label.LabelStyle.class));
            m_goToZero = new Label("Go to Zero", skin.get(Label.LabelStyle.class));
            m_stage.addActor(m_follow);
            m_stage.addActor(m_goToZero);

            Vector2 p = getStagePositionOfWorldPosition();
            m_goToZero.setPosition(p.x, p.y);
            m_goToZero.addAction(Actions.moveTo(0, 0, 5.0f));
        }

        private Vector2 getStagePositionOfWorldPosition() {
            Vector3 screenPosition = m_camera.project(new Vector3(m_worldPosition.x, m_worldPosition.y, 0));
            Vector3 stagePosition = m_stage.getCamera().unproject(screenPosition);
            return new Vector2(stagePosition.x, stagePosition.y);
        }

        @Override
        public void render () {
            Gdx.gl.glClearColor(0, 0, 0, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

            m_worldPosition.x += 64.0f * Gdx.graphics.getDeltaTime();

            m_camera.update();

            Vector2 followPosition = getStagePositionOfWorldPosition();
            m_follow.setPosition(followPosition.x, followPosition.y);
            m_stage.act(Gdx.graphics.getDeltaTime());

            m_shapeRenderer.setProjectionMatrix(m_camera.combined);
            m_shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
            m_shapeRenderer.setColor(Color.GREEN);
            m_shapeRenderer.circle(m_worldPosition.x, m_worldPosition.y, 32);
            m_shapeRenderer.end();

            m_stage.draw();
        }
    }
\$\endgroup\$
  • \$\begingroup\$ I tried that and, unfortunately, I'm getting similar results to what I was originally getting. Thanks for trying :) \$\endgroup\$ – nybblesAndBits Jan 11 '16 at 16:04
  • \$\begingroup\$ Are you sure you got it right? I have updated my answer with the full source code for an example where a Stage2D actor is follwing an entity based on its world space coordinates on screen. \$\endgroup\$ – bornander Jan 11 '16 at 20:53
  • 1
    \$\begingroup\$ I must have something wrong with how my viewport and/or camera is configured. I'm marking your solution as the answer since it most certainly works. I'll post my solution as another answer when I figure out the weirdness in my project. Thank you very much for the effort you put into this :) \$\endgroup\$ – nybblesAndBits Jan 12 '16 at 14:46
  • \$\begingroup\$ @nybblesAndBits Glad I could help. \$\endgroup\$ – bornander Jan 12 '16 at 16:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.