zoukankan      html  css  js  c++  java
  • [libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡

    本章主要讲解场景过渡效果的使用。这里将用到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 buffer
    Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // solid black
    Gdx.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 result
    Texture 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中使用这个特效:

    @Override
    public 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,增加了转场效果之后,游戏是不是漂亮了许多?

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

  • 相关阅读:
    Docker镜像仓库的搭建--> Harbor篇
    K8S入门系列之扩展组件 --> Dashboard (五)
    K8S入门系列之必备扩展组件--> coredns(四)
    K8S入门系列之集群二进制部署--> node篇(三)
    K8S入门系列之集群二进制部署--> master篇(二)
    K8S入门系列之集群yum安装-->初试篇(一)
    zabbix 4.2 的安装和设置(mysql57----centos7)
    SVN--服务端安装和设置---centos7
    Docker(二) Dockerfile 使用介绍
    二叉搜索树JavaScript实现
  • 原文地址:https://www.cnblogs.com/mignet/p/libgdx_game_development_09.html
Copyright © 2011-2022 走看看