本章主要讲解场景过渡效果的使用。这里将用到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,增加了转场效果之后,游戏是不是漂亮了许多?

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

[libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡的更多相关文章

  1. [libGDX游戏开发教程]使用libGDX进行游戏开发(12)-Action动画

    前文章节列表:  使用libGDX进行游戏开发(11)-高级编程技巧   使用libGDX进行游戏开发(10)-音乐音效不求人,程序员也可以DIY   使用libGDX进行游戏开发(9)-场景过渡   ...

  2. [libGDX游戏开发教程]使用libGDX进行游戏开发(1)-游戏设计

    声明:<使用Libgdx进行游戏开发>是一个系列,文章的原文是<Learning Libgdx Game Development>,大家请周知.后续的文章连接在这里 使用Lib ...

  3. 使用Html5+C#+微信 开发移动端游戏详细教程: (四)游戏中层的概念与设计

    众所周知,网站的前端页面结构一般是由div组成,父div包涵子div,子div包涵各种标签和项, 同理,游戏中我们也将若干游戏模块拆分成层,在后续的代码维护和游戏程序逻辑中将更加清晰和便于控制. We ...

  4. 微信小程序开发教程 #043 - 在小程序开发中使用 npm

    本文介绍了如何在微信小程序开发中使用 npm 中包的功能,大大提高微信小程序的开发效率,同时也是微信小程序系列教程的视频版更新. 微信小程序在发布之初没有对 npm 的支持功能,这也是目前很多前端开发 ...

  5. PythonWeb开发教程(一),开发之前需要准备什么

    什么是web开发呢,其实就是开发一个网站了.那开发网站需要用到哪些知识呢 1.python基础,因为用python开发的,所以python指定要会,最起码你也得会条件判断,循环,函数,类这些知识: 2 ...

  6. [libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧 Box2d和Shader

    高级编程技巧只是相对的,其实主要是讲物理模拟和着色器程序的使用. 本章主要讲解利用Box2D并用它来实现萝卜雨,然后是使用单色着色器shader让画面呈现单色状态:http://files.cnblo ...

  7. [libgdx游戏开发教程]使用Libgdx进行游戏开发(10)-音乐和音效

    本章音效文件都来自于公共许可: http://files.cnblogs.com/mignet/sounds.zip 在游戏中,播放背景音乐和音效是基本的功能. Libgdx提供了跨平台的声音播放功能 ...

  8. [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游戏框架搭建

    让我们抛开理论开始code吧. 入口类CanyonBunnyMain的代码: package com.packtpub.libgdx.canyonbunny; import com.badlogic. ...

  9. [libgdx游戏开发教程]使用Libgdx进行游戏开发(7)-屏幕布局的最佳实践

    管理多个屏幕 我们的菜单屏有2个按钮,一个play一个option.option里就是一些开关的设置,比如音乐音效等.这些设置将会保存到Preferences中. 多屏幕切换是游戏的基本机制,Libg ...

随机推荐

  1. Aspose.Pdf合并PDF文件

    使用Aspose.Pdf类库,有很多种方法可以合并PDF文件,这里简单介绍小生见到的几种: Doucment.Pages.Add PdfFileEditor.Append PdfFileEditor. ...

  2. IO调度

    互联网公司不关注真实的文件系统,他们关注VFS层,关注block层,关注IO的管控. queue->make_request_fn ( blk_queue_bio ),其中blk_queue_b ...

  3. 【loj6177】「美团 CodeM 初赛 Round B」送外卖2 Floyd+状压dp

    题目描述 一张$n$个点$m$条边的有向图,通过每条边需要消耗时间,初始为$0$时刻,可以在某个点停留.有$q$个任务,每个任务要求在$l_i$或以后时刻到$s_i$接受任务,并在$r_i$或以前时刻 ...

  4. 【算法】分块——教主的魔法&不勤劳的图书管理员

    由不勤劳的图书管理员带入了分块的坑,深深地被其暴力与优雅所征服.分块的实质就是将暴力块状封装起来,一整块的部分就一整块处理,零碎的部分就怎么暴力怎么来.因为分块大小的原因,限制了零碎部分数据的数量级, ...

  5. [SCOI2010]序列操作 线段树

    ---题面--- 题解: 在考场上打的这道题,出人意料的很快就打完了?! 直接用线段树,维护几个东西: 1,lazy标记 : 表示区间赋值 2,mark标记:表示区间翻转 3,l1:前缀最长连续的1的 ...

  6. BZOJ1115 [POI2009]石子游戏Kam 【博弈论——阶梯游戏】

    题目 有N堆石子,除了第一堆外,每堆石子个数都不少于前一堆的石子个数.两人轮流操作每次操作可以从一堆石子中移走任意多石子,但是要保证操作后仍然满足初始时的条件谁没有石子可移时输掉游戏.问先手是否必胜. ...

  7. CF893F Subtree Minimum Query 解题报告

    CF893F Subtree Minimum Query 输入输出格式 输入格式: The first line contains two integers \(n\) and \(r\) ( \(1 ...

  8. Kruskal算法及其类似原理的应用——【BZOJ 3654】tree&&【BZOJ 3624】[Apio2008]免费道路

    首先让我们来介绍Krukal算法,他是一种用来求解最小生成树问题的算法,首先把边按边权排序,然后贪心得从最小开始往大里取,只要那个边的两端点暂时还没有在一个联通块里,我们就把他相连,只要这个图里存在最 ...

  9. bzoj1814 Ural 1519 Formula 1(插头dp模板题)

    1814: Ural 1519 Formula 1 Time Limit: 1 Sec  Memory Limit: 64 MBSubmit: 924  Solved: 351[Submit][Sta ...

  10. python爬取七星彩的开奖历史记录

    1.因为人不可能一直无休止的学习,偶尔也想做点儿别的,昨天无聊就想写写Python,当然我承认我上班后基本都是在学工作方面的事情,在这个岗位我也呆了三年多了,还是那句话问我什么会不会我会给你说我啥都会 ...