[libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡效果

来源:互联网 发布:ubuntu修改系统语言 编辑:程序博客网 时间:2024/06/08 20:14
本章主要讲解场景过渡效果的使用。这里将用到Render to Texture(RTT)技术。

Libgdx提供了一个类,实现了各种常见的插值算法,不仅适合过渡效果,也适合任意特定行为。

在本游戏里面,我们将实现3种转场效果:fade, slide和slice.

和前面提到的多场景管理一样,我们也需要这样的结构来统一管理转场特效:

首先创建接口ScreenTransition:

package com.packtpub.libgdx.canyonbunny.screens.transitions;import com.badlogic.gdx.graphics.Texture;import com.badlogic.gdx.graphics.g2d.SpriteBatch;public interface ScreenTransition {    public float getDuration();    public void render(SpriteBatch batch, Texture currScreen,            Texture nextScreen, float alpha);}

OpenGL有个叫做Framebuffer Objects(FBO)的特性,它允许在内存中把离屏画面渲染到纹理中。

要使用这一特性,实例化Libgdx的Framebuffer类即可,一般的用法是:

// ...Framebuffer fbo;fbo = new Framebuffer(Format.RGB888, width, height, false);fbo.begin(); // set render target to FBO's texture bufferGdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // solid blackGdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // clear FBO batch.draw(someTextureRegion, 0, 0); // draw (to FBO)fbo.end(); // revert render target back to normal// retrieve resultTexture fboTexture = fbo.getColorBufferTexture();// ...

其实这个特性是OpenGL ES 2.0模式下的,我们假设现在的硬件都支持了。哈哈。

为了让我们的Project支持OpenGL ES 2.0,需要做以下改动:

main方法:cfg.useGL20 = true;

把Android工程里的AndroidManifest.xml增加一行:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

还有MainActivity里修改cfg.useGL20 = true;

HTML5项目就不用修改了,因为GWT一直就是用的OpenGL ES 2.0模式在渲染。

接下来,我们添加一个新的类DirectedGame来管理场景使用过渡的情况(替换掉Libgdx的Game类):

package com.packtpub.libgdx.canyonbunny.screens;import com.badlogic.gdx.ApplicationListener;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.Pixmap.Format;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.graphics.glutils.FrameBuffer;import com.packtpub.libgdx.canyonbunny.screens.transitions.ScreenTransition;public abstract class DirectedGame implements ApplicationListener {    private boolean init;    private AbstractGameScreen currScreen;    private AbstractGameScreen nextScreen;    private FrameBuffer currFbo;    private FrameBuffer nextFbo;    private SpriteBatch batch;    private float t;    private ScreenTransition screenTransition;    public void setScreen(AbstractGameScreen screen) {        setScreen(screen, null);    }    public void setScreen(AbstractGameScreen screen,            ScreenTransition screenTransition) {        int w = Gdx.graphics.getWidth();        int h = Gdx.graphics.getHeight();        if (!init) {            currFbo = new FrameBuffer(Format.RGB888, w, h, false);            nextFbo = new FrameBuffer(Format.RGB888, w, h, false);            batch = new SpriteBatch();            init = true;        }        // start new transition        nextScreen = screen;        nextScreen.show(); // activate next screen        nextScreen.resize(w, h);        nextScreen.render(0); // let screen update() once        if (currScreen != null)            currScreen.pause();        nextScreen.pause();        Gdx.input.setInputProcessor(null); // disable input        this.screenTransition = screenTransition;        t = 0;    }}

重写ApplicationListener接口的方法:

@Override    public void render() {        // get delta time and ensure an upper limit of one 60th second        float deltaTime = Math.min(Gdx.graphics.getDeltaTime(), 1.0f / 60.0f);        if (nextScreen == null) {            // no ongoing transition            if (currScreen != null)                currScreen.render(deltaTime);        } else {            // ongoing transition            float duration = 0;            if (screenTransition != null)                duration = screenTransition.getDuration();            // update progress of ongoing transition            t = Math.min(t + deltaTime, duration);            if (screenTransition == null || t >= duration) {                // no transition effect set or transition has just finished                if (currScreen != null)                    currScreen.hide();                nextScreen.resume();                // enable input for next screen                Gdx.input.setInputProcessor(nextScreen.getInputProcessor());                // switch screens                currScreen = nextScreen;                nextScreen = null;                screenTransition = null;            } else {                // render screens to FBOs                currFbo.begin();                if (currScreen != null)                    currScreen.render(deltaTime);                currFbo.end();                nextFbo.begin();                nextScreen.render(deltaTime);                nextFbo.end();                // render transition effect to screen                float alpha = t / duration;                screenTransition.render(batch, currFbo.getColorBufferTexture(),                        nextFbo.getColorBufferTexture(), alpha);            }        }    }    @Override    public void resize(int width, int height) {        if (currScreen != null)            currScreen.resize(width, height);        if (nextScreen != null)            nextScreen.resize(width, height);    }    @Override    public void pause() {        if (currScreen != null)            currScreen.pause();    }    @Override    public void resume() {        if (currScreen != null)            currScreen.resume();    }    @Override    public void dispose() {        if (currScreen != null)            currScreen.hide();        if (nextScreen != null)            nextScreen.hide();        if (init) {            currFbo.dispose();            currScreen = null;            nextFbo.dispose();            nextScreen = null;            batch.dispose();            init = false;        }    }

最后,我们要把先前用的Game类的地方,替换成DirectedGame.

首先修改AbstractGameScreen:

    protected DirectedGame game;
public AbstractGameScreen(DirectedGame game) { this.game = game; } public abstract InputProcessor getInputProcessor();

然后修改CanyonBunnyMain然它继承DirectedGame。

还有MenuScreen的构造函数改成public MenuScreen (DirectedGame game),同时添加:

    @Override
    public void show() {        stage = new Stage();        rebuildStage();    }    @Override    public InputProcessor getInputProcessor() {        return stage;    }

同样,GameScreen相应修改构造函数参数类型Game为DirectedGame,还有添加:

    @Override    public InputProcessor getInputProcessor() {        return worldController;    }

最后,修改worldcontroller里的game为DirectedGame,移除init中的InputProcessor:

private void init () {cameraHelper = new CameraHelper();lives = Constants.LIVES_START;livesVisual = lives;timeLeftGameOverDelay = 0;initLevel();}

应用的代码已经修改完成了,但是我们的转场特效现在还是空的,接下来一步步实现它。

这里有必要普及一下理论知识,转场特效的核心就是插值算法,Libgdx已经实现了很多线性和非线性的插值算法,我们来看看这些算法图:

通俗的讲,这些图就相当于每一个供查询的表,用户提供一个阿尔法值(x轴),通过表就得到一个结果值(y轴)。

在其他的游戏引擎中,比如Cocos2D引擎,已经封装了很多转场效果供开发者调用,比如Cocos里的FadeIn和FadeOut,就是对应于fade图的阿尔法的取值范围0-0.5 和 0.5-1。

在Libgdx中,这些特效都是不固定的,开发者可以自由组合。通常用法是这样的:

float alpha = 0.25f;float interpolatedValue = Interpolation.elastic.apply(alpha);

这里的阿尔法除了可以理解为参数以外,还可以理解为是整个动作进行的百分比。

下面我们来创建fade, slide 和 slice效果。

fade就是当前场景从不透明到完全透明,同时新场景从透明到完全不透明。

创建类ScreenTransitionFade:

package com.packtpub.libgdx.canyonbunny.screens.transitions;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.GL10;import com.badlogic.gdx.graphics.Texture;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.math.Interpolation;public class ScreenTransitionFade implements ScreenTransition {    private static final ScreenTransitionFade instance = new ScreenTransitionFade();    private float duration;    public static ScreenTransitionFade init(float duration) {        instance.duration = duration;        return instance;    }    @Override    public float getDuration() {        return duration;    }    @Override    public void render(SpriteBatch batch, Texture currScreen,            Texture nextScreen, float alpha) {        float w = currScreen.getWidth();        float h = currScreen.getHeight();        alpha = Interpolation.fade.apply(alpha);        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);        batch.begin();        batch.setColor(1, 1, 1, 1);        batch.draw(currScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,                currScreen.getWidth(), currScreen.getHeight(), false, true);        batch.setColor(1, 1, 1, alpha);        batch.draw(nextScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,                nextScreen.getWidth(), nextScreen.getHeight(), false, true);        batch.end();    }}

现在,我们把play按钮的clicked代码修改一下,用上fade的效果:

ScreenTransition transition = ScreenTransitionFade.init(0.75f);game.setScreen(new GameScreen(game), transition);

创建类ScreenTransitionSlide:

package com.packtpub.libgdx.canyonbunny.screens.transitions;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.GL10;import com.badlogic.gdx.graphics.Texture;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.math.Interpolation;public class ScreenTransitionSlide implements ScreenTransition {    public static final int LEFT = 1;    public static final int RIGHT = 2;    public static final int UP = 3;    public static final int DOWN = 4;    private static final ScreenTransitionSlide instance = new ScreenTransitionSlide();    private float duration;    private int direction;    private boolean slideOut;    private Interpolation easing;    public static ScreenTransitionSlide init(float duration, int direction,            boolean slideOut, Interpolation easing) {        instance.duration = duration;        instance.direction = direction;        instance.slideOut = slideOut;        instance.easing = easing;        return instance;    }    @Override    public float getDuration() {        return duration;    }    @Override    public void render(SpriteBatch batch, Texture currScreen,            Texture nextScreen, float alpha) {        float w = currScreen.getWidth();        float h = currScreen.getHeight();        float x = 0;        float y = 0;        if (easing != null)            alpha = easing.apply(alpha);        // calculate position offset        switch (direction) {        case LEFT:            x = -w * alpha;            if (!slideOut)                x += w;            break;        case RIGHT:            x = w * alpha;            if (!slideOut)                x -= w;            break;        case UP:            y = h * alpha;            if (!slideOut)                y -= h;            break;        case DOWN:            y = -h * alpha;            if (!slideOut)                y += h;            break;        }        // drawing order depends on slide type ('in' or 'out')        Texture texBottom = slideOut ? nextScreen : currScreen;        Texture texTop = slideOut ? currScreen : nextScreen;        // finally, draw both screens        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);        batch.begin();        batch.draw(texBottom, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,                currScreen.getWidth(), currScreen.getHeight(), false, true);        batch.draw(texTop, x, y, 0, 0, w, h, 1, 1, 0, 0, 0,                nextScreen.getWidth(), nextScreen.getHeight(), false, true);        batch.end();    }}

然后在worldcontroller的backmenu里使用这个效果:

    private void backToMenu() {        // switch to menu screen        ScreenTransition transition = ScreenTransitionSlide.init(0.75f,                ScreenTransitionSlide.DOWN, false, Interpolation.bounceOut);        game.setScreen(new MenuScreen(game), transition);    }

创建类ScreenTransitionSlice:

package com.packtpub.libgdx.canyonbunny.screens.transitions;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.GL10;import com.badlogic.gdx.graphics.Texture;import com.badlogic.gdx.graphics.g2d.SpriteBatch;import com.badlogic.gdx.math.Interpolation;import com.badlogic.gdx.utils.Array;public class ScreenTransitionSlice implements ScreenTransition {    public static final int UP = 1;    public static final int DOWN = 2;    public static final int UP_DOWN = 3;    private static final ScreenTransitionSlice instance = new ScreenTransitionSlice();    private float duration;    private int direction;    private Interpolation easing;    private Array<Integer> sliceIndex = new Array<Integer>();    public static ScreenTransitionSlice init(float duration, int direction,            int numSlices, Interpolation easing) {        instance.duration = duration;        instance.direction = direction;        instance.easing = easing;        // create shuffled list of slice indices which determines        // the order of slice animation        instance.sliceIndex.clear();        for (int i = 0; i < numSlices; i++)            instance.sliceIndex.add(i);        instance.sliceIndex.shuffle();        return instance;    }    @Override    public float getDuration() {        return duration;    }    @Override    public void render(SpriteBatch batch, Texture currScreen,            Texture nextScreen, float alpha) {        float w = currScreen.getWidth();        float h = currScreen.getHeight();        float x = 0;        float y = 0;        int sliceWidth = (int) (w / sliceIndex.size);        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);        batch.begin();        batch.draw(currScreen, 0, 0, 0, 0, w, h, 1, 1, 0, 0, 0,                currScreen.getWidth(), currScreen.getHeight(), false, true);        if (easing != null)            alpha = easing.apply(alpha);        for (int i = 0; i < sliceIndex.size; i++) {            // current slice/column            x = i * sliceWidth;            // vertical displacement using randomized            // list of slice indices            float offsetY = h                    * (1 + sliceIndex.get(i) / (float) sliceIndex.size);            switch (direction) {            case UP:                y = -offsetY + offsetY * alpha;                break;            case DOWN:                y = offsetY - offsetY * alpha;                break;            case UP_DOWN:                if (i % 2 == 0) {                    y = -offsetY + offsetY * alpha;                } else {                    y = offsetY - offsetY * alpha;                }                break;            }            batch.draw(nextScreen, x, y, 0, 0, sliceWidth, h, 1, 1, 0, i                    * sliceWidth, 0, sliceWidth, nextScreen.getHeight(), false,                    true);        }        batch.end();    }}

在CanyonBunnyMain中使用这个特效:

@Overridepublic void create () {  // Set Libgdx log level  Gdx.app.setLogLevel(Application.LOG_DEBUG);  // Load assets  Assets.instance.init(new AssetManager());  // Start game at menu screen  ScreenTransition transition = ScreenTransitionSlice.init(2,  ScreenTransitionSlice.UP_DOWN, 10, Interpolation.pow5Out);  setScreen(new MenuScreen(this), transition);}

ok,增加了转场效果之后,游戏是不是漂亮了许多?

在下一章,我们将添加音乐和音效。


PS:欢迎各路游戏爱好者入群 426950359


 

0 0
原创粉丝点击