在libGDX中是否有JavaFX的PathTransition等价物?

时间:2016-11-29 01:51:15

标签: java javafx libgdx

我们正处于从JavaFX过渡到libGDX的过程中。想知道libGDX中是否有等价的PathTransition

PathTransition的特别之处在于它在遍历Path时会相应地旋转Node。关于PathTransition的糟糕之处在于你需要自己计算速度,也就是说你只提供完成遍历所需的时间,但你需要弄清楚路径的长度以便你可以设置适当的持续时间

希望libGDX有类似或更好的改进版本的PathTransition。

2 个答案:

答案 0 :(得分:1)

根据您发布的示例,我认为libGDX中没有 direct 等效项,但我们可以制作一个。

libGDX附带一个名为Scene2d的场景图,它允许您将动作应用于Actors或Actors组。内置了一系列操作(MoveTo,RotateBy),您可以相互并行或顺序执行它们。您还可以应用Interpolation类进行补间。

libGDX还带有Path接口,但从1.9.4开始没有PathAction,但这并不意味着你不能构建类似下面的那个:

package tech.otter.gdxsandbox.demos;

import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.CatmullRomSpline;
import com.badlogic.gdx.math.Path;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class PathDemo extends Demo {
    private ShapeRenderer sr;
    private Stage stage;

    public PathDemo() {
        super("Action Demo");
        stage = new Stage();
    }

    @Override
    public void show() {
        sr = new ShapeRenderer();
        Actor ourCircle = new SimpleCircleActor(sr, 10f);
        stage.addActor(ourCircle);

        ourCircle.addAction(new PathAction(5f, 50f, 50f, 100f, 60f, 300f, 200f, 100f, 400f));
    }

    @Override
    public void render(float delta) {
        sr.setAutoShapeType(true);
        sr.begin();
        sr.set(ShapeRenderer.ShapeType.Filled);
        stage.act(delta);
        stage.draw();
        sr.end();
    }

    @Override
    public void dispose() {
        stage.dispose();
        sr.dispose();
    }

    private class SimpleCircleActor extends Actor {
        private ShapeRenderer renderer;
        private float radius;

        public SimpleCircleActor(ShapeRenderer renderer, float radius) {
            this.renderer = renderer;
            this.radius = radius;
        }

        /**
         * Assumes something outside is setting the ShapeRenderer type/begin/end.
         * Don't do this at home, kids.
         */
        @Override
        public void draw(Batch batch, float delta) {
            renderer.circle(this.getX(), this.getY(), this.radius);
        }
    }

    private class PathAction extends Action {
        private Path<Vector2> path;
        private float current;
        private float duration;
        public PathAction(float duration, float... coordinates) {
            this.duration = duration;
            this.current = 0;

            Vector2[] dataSet = new Vector2[coordinates.length / 2];
            for(int i = 0; i < dataSet.length; i++) {
                dataSet[i] = new Vector2(coordinates[2*i], coordinates[2*i+1]);
            }
            this.path = new CatmullRomSpline<Vector2>(dataSet, true);
        }
        @Override
        public boolean act(float delta) {
            if(duration == current) return true; // The action has already completed.

            if(current + delta >= duration) {
                current = duration;
            } else {
                current += delta;
            }

            Vector2 out = new Vector2(); // Get our position on the path.
            this.path.valueAt(out, current / duration);
            this.actor.setPosition(out.x, out.y);

            return duration == current; // Return whether we are now complete.
        }
    }
}

答案 1 :(得分:0)

所以我开始工作了。下面的类创建了一个Action实例,确切地说是SequenceAction。它创建的Action将通过用户提供的点遍历Actor。它会考虑速度,持续时间,偏移Actor在路径上的位置,旋转Actor,甚至在离开和进入屏幕时缩进Actor

import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.actions.SequenceAction;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author jmart
 */
public class PathActionMaker {

    private final float[][] points;
    private float speed = 0;
    private final float rotationSpeed = 600;
    private float[] screenSize;

    /**
     *
     * @param points - all the path points that must be traversed
     * @param duration - how long it should take to traverse the path points
     * @param screenSize - the size of the screen
     */
    public PathActionMaker(float[][] points, float duration, float[] screenSize) {
        this.points = points;
        this.speed = getSpeed(points, duration);
        this.screenSize = screenSize;
    }

    /**
     *
     * @param speed - the speed at which the Actor should move traverse through
     * all the points
     * @param points - all the path points that must be traversed
     * @param screenSize - the size of the screen
     */
    public PathActionMaker(float speed, float[][] points, float[] screenSize) {
        this.points = points;
        this.speed = speed;
        this.screenSize = screenSize;
    }

    /**
     * The offsets are used to allow centering of an actor on the provided
     * points.
     *
     * @param xOffset
     * @param yOffset
     * @param offScreenIndent number of pixels to move the actor out of screen
     * when entering and leaving
     * @return
     */
    public SequenceAction getAction(float xOffset, float yOffset, float offScreenIndent) {
        float[] prev = null;
        float prevTheta = 0;
        int i = 0;
        SequenceAction seqAction = Actions.sequence();
        boolean firstRotation = true;
        float[][] fixedPoints = getOffSetFixedPoints(points, xOffset, yOffset, offScreenIndent);
        for (float[] point : fixedPoints) {
            float[] p = point;
            if (prev == null) {
                seqAction.addAction(Actions.moveTo(p[0], p[1]));
            } else {
                float dist = MathUtils.getDistance(prev[0], prev[1], p[0], p[1]);
                float time = dist / speed;
                float theta = MathUtils.angle(prev[0], prev[1], p[0], p[1]) + 90;
                if (theta > 360) {
                    theta -= 360;
                }
                float dTheta = Math.abs(prevTheta - theta);
                float rotateDuration = dTheta / rotationSpeed;
                // the first rotation should be immediate.
                if (firstRotation) {
                    firstRotation = false;
                    seqAction.addAction(Actions.parallel(
                            Actions.moveTo(p[0], p[1], time),
                            Actions.rotateTo(theta)));//...rotation duration is 0, aka immediate
                } else {
                    seqAction.addAction(Actions.parallel(
                            Actions.moveTo(p[0], p[1], time),
                            Actions.rotateTo(theta, rotateDuration)));
                }
                prevTheta = theta;
            }
            prev = p;
        }
        return seqAction;
    }

    public float[][] getOffSetFixedPoints(float[][] points, float xOffset, float yOffset, float offScreenIndent) {
        List<Float[]> list = new ArrayList<>();

        //apply offset indent at beginning of path
        float[] p1 = points[0];
        float[] p2 = points[1];
        float slope = (p1[1] - p2[1]) / (p1[0] - p2[0]);
        float c = p1[1] - slope * p1[0];

        if (p1[0] == 0) {
            float x = -offScreenIndent;
            float y = slope * x + c;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p1[0] == screenSize[0]) {
            float x = screenSize[0] + offScreenIndent;
            float y = slope * x + c;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p1[1] == 0) {
            float y = -offScreenIndent;
            float x = (y - c) / slope;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p1[1] == screenSize[1]) {
            float y = screenSize[1] + offScreenIndent;
            float x = (y - c) / slope;
            list.add(new Float[]{x - xOffset, y - yOffset});
        }

        //apply the remaining points
        for (float[] point : points) {
            float[] p = {point[0] - xOffset, point[1] - yOffset};
            addPointToList(p, list);
        }

        //apply offscreen indent at end of path
        float[] p4 = points[points.length - 1];
        float[] p3 = points[points.length - 2];
        slope = (p3[1] - p4[1]) / (p3[0] - p4[0]);
        c = p3[1] - slope * p3[0];

        if (p4[0] <= 0) {
            float x = -offScreenIndent;
            float y = slope * x + c;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p4[0] >= screenSize[0]) {
            float x = screenSize[0] + offScreenIndent;
            float y = slope * x + c;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p4[1] <= 0) {
            float y = -offScreenIndent;
            float x = (y - c) / slope;
            list.add(new Float[]{x - xOffset, y - yOffset});
        } else if (p4[1] >= screenSize[1]) {
            float y = screenSize[1] + offScreenIndent;
            float x = (y - c) / slope;
            list.add(new Float[]{x - xOffset, y - yOffset});
        }

        //convert list to float[][]
        float[][] rv = new float[list.size()][];
        int i = 0;
        for (Float[] p : list) {
            float[] j = new float[2];
            j[0] = p[0];
            j[1] = p[1];
            rv[i++] = j;
        }
        return rv;
    }

    private static void addPointToList(float[] point, List<Float[]> list) {
        list.add(new Float[]{point[0], point[1]});
    }

    private float getSpeed(float[][] points, float duration) {
        //speed = distance/time
        float distance = MathUtils.getDistance(points);
        return distance / duration;
    }

}