com.quadbits.gdxhelper.actors.AutomaticScrollingActor.java Source code

Java tutorial

Introduction

Here is the source code for com.quadbits.gdxhelper.actors.AutomaticScrollingActor.java

Source

/*
 * Copyright (c) 2015 Quadbits SLU
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.quadbits.gdxhelper.actors;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.Pool;
import com.quadbits.gdxhelper.screens.LWPScreen;
import com.quadbits.gdxhelper.utils.Recyclable;
import com.quadbits.gdxhelper.utils.ScrollTarget;

import javax.inject.Inject;

/**
 *
 */
public class AutomaticScrollingActor extends BaseActor implements Recyclable<AutomaticScrollingActor> {
    protected Pool<AutomaticScrollingActor> automaticScrollingActorPool;
    ScrollTarget scrollTarget;
    float scrollVelocityMillis;
    float scrollAccelFactor;
    State state;
    boolean paused;
    ScrollVariable scrollVariable;

    boolean debug;
    float debugAccelMillis;
    boolean debugBraking;
    float debugLineLengthMultiplier;

    @Inject
    LWPScreen screen;

    @Inject
    ShapeRenderer shapeRenderer;

    public static final float DEFAULT_SCROLL_ACCEL_FACTOR = 1;
    public static final float DEFAULT_DEBUG_LINE_LENGTH_MULTIPLIER = 1;

    /**
     * An enum for indicating the state of the actor
     */
    public static enum State {
        REST, BURST
    }

    /**
     * The scroll variable on which the actor will actuate: scrollX or scrollY
     */
    public static enum ScrollVariable {
        SCROLL_X, SCROLL_Y
    }

    @Inject
    public AutomaticScrollingActor() {
        super();
        this.reset();
    }

    @Override
    public void reset() {
        this.state = State.BURST;
        this.scrollVariable = ScrollVariable.SCROLL_Y;
        this.paused = false;
        this.scrollAccelFactor = DEFAULT_SCROLL_ACCEL_FACTOR;
        this.debugLineLengthMultiplier = DEFAULT_DEBUG_LINE_LENGTH_MULTIPLIER;
    }

    @Override
    public void free() {
        automaticScrollingActorPool.free(this);
    }

    @Override
    public void setPool(Pool<AutomaticScrollingActor> automaticScrollingActorPool) {
        this.automaticScrollingActorPool = automaticScrollingActorPool;
    }

    @Override
    public void act(float deltaTime) {
        super.act(deltaTime);

        // check parameters
        if (this.scrollTarget == null) {
            return;
        }

        // initialize debug variables
        this.debugAccelMillis = 0;
        this.debugBraking = false;

        // if paused, just return
        if (this.paused) {
            return;
        }

        // get current scroll value
        float currentScroll;
        if (this.scrollVariable == ScrollVariable.SCROLL_Y) {
            currentScroll = this.screen.getScrollY();
        } else {
            currentScroll = this.screen.getScrollX();
        }

        // get target scroll value
        float targetScroll = this.scrollTarget.getTargetScroll();
        if (this.debug) {
            Gdx.app.log("AutomaticScrollingActor", "scrollTarget = " + targetScroll);
        }

        // decide whether to interpolate or just simply advance scroll
        float newScroll;
        float minScrollVelocityMillis = this.scrollTarget.maxScrollVelocityMillis();
        float maxScrollDistance = minScrollVelocityMillis * 33;
        float scrollDiff = targetScroll - currentScroll;
        float scrollDiffAbs = Math.abs(scrollDiff);
        float scrollVelocityMillisAbs = Math.abs(this.scrollVelocityMillis);

        // set equal
        if (scrollDiffAbs <= maxScrollDistance && scrollVelocityMillisAbs <= minScrollVelocityMillis) {
            newScroll = targetScroll;
            this.scrollVelocityMillis = 0;
            this.state = State.REST;
        }

        // accelerate
        else {

            float scrollAccelMillis = minScrollVelocityMillis * this.scrollAccelFactor * Math.signum(scrollDiff);
            float scrollAccelMillisAbs = Math.abs(scrollAccelMillis);

            // Default behaviour is to accelerate; check for cases where we should stop accelerating
            // or directly break

            // If scroll velocity is 0, always accelerate; check other cases
            if (this.scrollVelocityMillis != 0) {
                // calculate required accel for arriving at the desired scroll value with velocity 0
                float targetAccelMillis = -(this.scrollVelocityMillis * this.scrollVelocityMillis)
                        / (2 * scrollDiff);
                float targetAccelMillisAbs = Math.abs(targetAccelMillis);
                float targetAccelSignum = Math.signum(targetAccelMillis);
                float scrollVelocitySignum = Math.signum(this.scrollVelocityMillis);

                // If target acceleration is in the same direction as the current velocity, we are
                // going the wrong way; continue accelerating. Otherwise, check for other cases
                if (scrollVelocitySignum != targetAccelSignum) {
                    // stop accelerating: target accel and scroll accel are approximately equal
                    // (their difference is less than 10% of scroll accel)
                    if (targetAccelMillisAbs >= scrollAccelMillisAbs * 0.9f
                            && targetAccelMillisAbs <= scrollAccelMillisAbs * 1.1f) {
                        scrollAccelMillis = 0;
                    }

                    // brake: set the necessary acceleration to arrive at velocity 0
                    else if (targetAccelMillisAbs > scrollAccelMillisAbs * 1.1f) {
                        scrollAccelMillis = targetAccelMillis;
                        this.debugBraking = true;
                    }
                }
            }

            // save scroll accel for debug purposes
            this.debugAccelMillis = scrollAccelMillis;

            // update scroll velocity according to scroll acceleration
            this.scrollVelocityMillis += scrollAccelMillis * deltaTime * 1000;

            // update new scroll according to scroll velocity
            newScroll = currentScroll + this.scrollVelocityMillis * deltaTime * 1000;

            // do not overscroll
            if (newScroll > 1) {
                newScroll = 1;
                this.scrollVelocityMillis = 0;
            }

            if (newScroll < 0) {
                newScroll = 0;
                this.scrollVelocityMillis = 0;
            }

            this.state = State.BURST;
        }

        // change scroll according to alpha

        // scrollY
        if (this.scrollVariable == ScrollVariable.SCROLL_Y) {
            this.screen.setScrollY(newScroll);
        }

        // scrollX
        else {
            this.screen.setScrollX(newScroll);
        }
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);

        if (this.debug) {
            if (this.debugAccelMillis == 0) {
                return;
            }

            Color lineColor;
            if (this.debugBraking) {
                lineColor = Color.RED;
            } else {
                lineColor = Color.GREEN;
            }

            float lineLength = this.debugAccelMillis * this.debugLineLengthMultiplier;

            batch.end();

            this.shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
            this.shapeRenderer.setColor(lineColor);
            float cx = Gdx.graphics.getWidth() / 2;
            float cy = Gdx.graphics.getHeight() / 2;
            this.shapeRenderer.line(cx, cy, cx, cy + lineLength);
            this.shapeRenderer.end();

            batch.begin();
        }
    }

    @Override
    public long getMaxSleepTime() {
        if (this.paused) {
            return Long.MAX_VALUE;
        }

        if (this.state == State.BURST) {
            return 0;
        }

        return this.scrollTarget.getMaxSleepTime();
    }

    public boolean isPaused() {
        return this.paused;
    }

    public void pause() {
        this.paused = true;
    }

    public void resume() {
        this.paused = false;

        if (this.screen.isFlingEnabled()) {
            float scrollVelocitySeconds;
            if (this.scrollVariable == ScrollVariable.SCROLL_Y) {
                scrollVelocitySeconds = this.screen.getFlingVelocityY();
            } else {
                scrollVelocitySeconds = this.screen.getFlingVelocityX();
            }
            this.scrollVelocityMillis = scrollVelocitySeconds * 1000;
        }
    }

    public ScrollTarget getScrollTarget() {
        return scrollTarget;
    }

    public void setScrollTarget(ScrollTarget scrollTarget) {
        this.scrollTarget = scrollTarget;
    }

    public ScrollVariable getScrollVariable() {
        return scrollVariable;
    }

    public void setScrollVariable(ScrollVariable scrollVariable) {
        this.scrollVariable = scrollVariable;
    }

    public float getScrollAccelFactor() {
        return scrollAccelFactor;
    }

    public void setScrollAccelFactor(float scrollAccelFactor) {
        this.scrollAccelFactor = scrollAccelFactor;
    }

    public boolean isDebug() {
        return debug;
    }

    @Override
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public float getDebugLineLengthMultiplier() {
        return debugLineLengthMultiplier;
    }

    public void setDebugLineLengthMultiplier(float debugLineLengthMultiplier) {
        this.debugLineLengthMultiplier = debugLineLengthMultiplier;
    }
}