zoukankan      html  css  js  c++  java
  • [libgdx游戏开发教程]使用Libgdx进行游戏开发(8)-粒子系统

    没有美工的程序员,能够依赖的还有粒子系统。

    这一章我们将使用libGDX的粒子系统线性插值以及其他的方法来增加一些特效。

    你也可以使用自己编辑的粒子效果,比如这个粒子文件dust:http://files.cnblogs.com/mignet/particles.zip

    这个灰尘的特效用在兔子头在地面跑的时候,啪啪的一溜烟。

    线性插值可以让我们的摄像机在移动的时候更平滑。

    当然,之前提到的背景上的山要实现视差移动效果也要实现。

    白云会用随机的速度从右向左飘。

    GUI的部分也要增加些效果比如掉了命,得了分等。

    粒子系统通常用来模拟复杂的特效:比如fire, smoke, explosions等等.

    ParticleEffect简介:

    • start(): This starts the animation of the particle effect
    • reset(): This resets and restarts the animation of the particle effect
    • update(): This must be called to let the particle effect act in accordance to time
    • draw(): This renders the particle effect at its current position
    • allowCompletion(): This allows emitters to stop smoothly even if particle effects are set to play continuously
    • setDuration(): This sets the overall duration the particle effect will run
    • setPosition(): This sets the position to where it will be drawn
    • setFlip(): This sets horizontal and vertical flip modes
    • save(): This saves a particle effect with all its settings to a file
    • load(): This loads a particle effect with all its settings from a saved file
    • dispose(): This frees all resources allocated by the particle effect

    粒子效果通常需要一个粒子发射器ParticleEmitter:

    ParticleEffect effect = new ParticleEffect();
    ParticleEmitter emitter = new ParticleEmitter();
    effect.getEmitters().add(emitter);
    emitter.setAdditive(true);
    emitter.getDelay().setActive(true);
    emitter.getDelay().setLow(0.5f);
    // ... more code for emitter initialization ...

    当然,不建议在代码里初始化例子发射器。因为发射器有20多个属性,要是在代码里初始化会很杂乱并且不容易维护。我们使用Libgdx的编辑器来编辑想要的粒子。

    https://github.com/libgdx/libgdx/wiki/Particle-editor

    我们来调个灰尘特效吧:

    保存文件到CanyonBunny-android/assets/particles/dust.pfx

    虽然并没有规定粒子文件要用什么文件后缀,但是我们统一叫pfx。记得把图片https://github.com/libgdx/libgdx/blob/master/extensions/gdx-tools/assets/particle.png

    也保存到相同的文件夹。

    首先在兔子头BunnyHead里添加代码:

    public ParticleEffect dustParticles = new ParticleEffect();

    public void init () { ... // Power-ups hasFeatherPowerup = false; timeLeftFeatherPowerup = 0; // Particles dustParticles.load(Gdx.files.internal("particles/dust.pfx"), Gdx.files.internal("particles")); }
    @Override
    public void update (float deltaTime) {
        super.update(deltaTime);
        ...
        dustParticles.update(deltaTime);
    }
    @Override
    public void render (SpriteBatch batch) {
        TextureRegion reg = null;
        // Draw Particles
        dustParticles.draw(batch);
        // Apply Skin Color
        ...
    }

     让灰尘跟着兔子:

        protected void updateMotionY(float deltaTime) {
            switch (jumpState) {
            case GROUNDED:
                jumpState = JUMP_STATE.FALLING;
                if (velocity.x != 0) {
                    dustParticles.setPosition(position.x + dimension.x / 2,
                            position.y);
                    dustParticles.start();
                }
                break;
    。。。
        if (jumpState != JUMP_STATE.GROUNDED){
                dustParticles.allowCompletion();
                super.updateMotionY(deltaTime);
            }
        }
    }

    ok,跑起..

    接下来,让云飘起来:

    private Cloud spawnCloud() {
            Cloud cloud = new Cloud();
            cloud.dimension.set(dimension);
            // select random cloud image
            cloud.setRegion(regClouds.random());
            // position
            Vector2 pos = new Vector2();
            pos.x = length + 10; // position after end of level
            pos.y += 1.75; // base position
            // random additional position
            pos.y += MathUtils.random(0.0f, 0.2f)
                    * (MathUtils.randomBoolean() ? 1 : -1);
            cloud.position.set(pos);
            // speed
            Vector2 speed = new Vector2();
            speed.x += 0.5f; // base speed
            // random additional speed
            speed.x += MathUtils.random(0.0f, 0.75f);
            cloud.terminalVelocity.set(speed);
            speed.x *= -1; // move left
            cloud.velocity.set(speed);
            return cloud;
        }
    
        @Override
        public void update(float deltaTime) {
            for (int i = clouds.size - 1; i >= 0; i--) {
                Cloud cloud = clouds.get(i);
                cloud.update(deltaTime);
                if (cloud.position.x < -10) {
                    // cloud moved outside of world.
                    // destroy and spawn new cloud at end of level.
                    clouds.removeIndex(i);
                    clouds.add(spawnCloud());
                }
            }
        }

    线性插值,让摄像机平滑移动到跟随的目标(Libgdx已经实现了lerp)CameraHelper

        private final float FOLLOW_SPEED = 4.0f;
        
        public void update(float deltaTime) {
            if (!hasTarget())
                return;
            position.lerp(target.position, FOLLOW_SPEED * deltaTime);
            // Prevent camera from moving down too far
            position.y = Math.max(-1f, position.y);
        }

    让岩石浮在水面上Rocks:

        private final float FLOAT_CYCLE_TIME = 2.0f;
        private final float FLOAT_AMPLITUDE = 0.25f;
        private float floatCycleTimeLeft;
        private boolean floatingDownwards;
        private Vector2 floatTargetPosition;
    
        private void init() {
            dimension.set(1, 1.5f);
            regEdge = Assets.instance.rock.edge;
            regMiddle = Assets.instance.rock.middle;
            // Start length of this rock
            setLength(1);
    
            floatingDownwards = false;
            floatCycleTimeLeft = MathUtils.random(0, FLOAT_CYCLE_TIME / 2);
            floatTargetPosition = null;
        }
    
        @Override
        public void update(float deltaTime) {
            super.update(deltaTime);
            floatCycleTimeLeft -= deltaTime;
            if (floatTargetPosition == null)
                floatTargetPosition = new Vector2(position);
            if (floatCycleTimeLeft <= 0) {
                floatCycleTimeLeft = FLOAT_CYCLE_TIME;
                floatingDownwards = !floatingDownwards;
                floatTargetPosition.y += FLOAT_AMPLITUDE
                        * (floatingDownwards ? -1 : 1);
            }
            position.lerp(floatTargetPosition, deltaTime);
        }

    让山随着兔子视差Mountains:

    public void updateScrollPosition(Vector2 camPosition) {
            position.set(camPosition.x, position.y);
        }
    
        private void drawMountain(SpriteBatch batch, float offsetX, float offsetY,
                float tintColor, float parallaxSpeedX) {
            TextureRegion reg = null;
            batch.setColor(tintColor, tintColor, tintColor, 1);
            float xRel = dimension.x * offsetX;
            float yRel = dimension.y * offsetY;
            // mountains span the whole level
            int mountainLength = 0;
            mountainLength += MathUtils.ceil(length / (2 * dimension.x)
                    * (1 - parallaxSpeedX));
            mountainLength += MathUtils.ceil(0.5f + offsetX);
            for (int i = 0; i < mountainLength; i++) {
                // mountain left
                reg = regMountainLeft;
                batch.draw(reg.getTexture(), origin.x + xRel + position.x
                        * parallaxSpeedX, origin.y + yRel + position.y, origin.x,
                        origin.y, dimension.x, dimension.y, scale.x, scale.y,
                        rotation, reg.getRegionX(), reg.getRegionY(),
                        reg.getRegionWidth(), reg.getRegionHeight(), false, false);
                xRel += dimension.x;
                // mountain right
                reg = regMountainRight;
                batch.draw(reg.getTexture(), origin.x + xRel + position.x
                        * parallaxSpeedX, origin.y + yRel + position.y, origin.x,
                        origin.y, dimension.x, dimension.y, scale.x, scale.y,
                        rotation, reg.getRegionX(), reg.getRegionY(),
                        reg.getRegionWidth(), reg.getRegionHeight(), false, false);
                xRel += dimension.x;
            }
            // reset color to white
            batch.setColor(1, 1, 1, 1);
        }
    
        @Override
        public void render(SpriteBatch batch) {
            // 80% distant mountains (dark gray)
            drawMountain(batch, 0.5f, 0.5f, 0.5f, 0.8f);
            // 50% distant mountains (gray)
            drawMountain(batch, 0.25f, 0.25f, 0.7f, 0.5f);
            // 30% distant mountains (light gray)
            drawMountain(batch, 0.0f, 0.0f, 0.9f, 0.3f);
        }

    把这个加到worldcontroller里:

    public void update(float deltaTime) {
            handleDebugInput(deltaTime);
            if (isGameOver()) {
                timeLeftGameOverDelay -= deltaTime;
                if (timeLeftGameOverDelay < 0)
                    backToMenu();
            } else {
                handleInputGame(deltaTime);
            }
            level.update(deltaTime);
            testCollisions();
            cameraHelper.update(deltaTime);
            if (!isGameOver() && isPlayerInWater()) {
                lives--;
                if (isGameOver())
                    timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER;
                else
                    initLevel();
            }
            level.mountains.updateScrollPosition(cameraHelper.getPosition());
        }

    现在,增加GUI的特效。

    首先是掉了命:

    在worldcontroller里加public float livesVisual;

    private void init() {
            Gdx.input.setInputProcessor(this);
            cameraHelper = new CameraHelper();
            lives = Constants.LIVES_START;
            livesVisual = lives;
            timeLeftGameOverDelay = 0;
            initLevel();
        }
    
        public void update(float deltaTime) {
            handleDebugInput(deltaTime);
            if (isGameOver()) {
                timeLeftGameOverDelay -= deltaTime;
                if (timeLeftGameOverDelay < 0)
                    backToMenu();
            } else {
                handleInputGame(deltaTime);
            }
            level.update(deltaTime);
            testCollisions();
            cameraHelper.update(deltaTime);
            if (!isGameOver() && isPlayerInWater()) {
                lives--;
                if (isGameOver())
                    timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER;
                else
                    initLevel();
            }
            level.mountains.updateScrollPosition(cameraHelper.getPosition());
            if (livesVisual > lives)
                livesVisual = Math.max(lives, livesVisual - 1 * deltaTime);
        }

    同时,在WorldRenderer里相应的修改:

    private void renderGuiExtraLive(SpriteBatch batch) {
            float x = cameraGUI.viewportWidth - 50 - Constants.LIVES_START * 50;
            float y = -15;
            for (int i = 0; i < Constants.LIVES_START; i++) {
                if (worldController.lives <= i)
                    batch.setColor(0.5f, 0.5f, 0.5f, 0.5f);
                batch.draw(Assets.instance.bunny.head, x + i * 50, y, 50, 50, 120,
                        100, 0.35f, -0.35f, 0);
                batch.setColor(1, 1, 1, 1);
            }
            if (worldController.lives >= 0
                    && worldController.livesVisual > worldController.lives) {
                int i = worldController.lives;
                float alphaColor = Math.max(0, worldController.livesVisual
                        - worldController.lives - 0.5f);
                float alphaScale = 0.35f * (2 + worldController.lives - worldController.livesVisual) * 2;
                float alphaRotate = -45 * alphaColor;
                batch.setColor(1.0f, 0.7f, 0.7f, alphaColor);
                batch.draw(Assets.instance.bunny.head, x + i * 50, y, 50, 50, 120,
                        100, alphaScale, -alphaScale, alphaRotate);
                batch.setColor(1, 1, 1, 1);
            }
        }

    数字增涨效果:

    WorldController增加:public float scoreVisual;在initLevel中添加scoreVisual = score;

    在update的最后增加

    if (scoreVisual < score)
    scoreVisual = Math.min(score, scoreVisual+ 250 * deltaTime);

    在WorldRenderer里修改:

    private void renderGuiScore(SpriteBatch batch) {
            float x = -15;
            float y = -15;
            float offsetX = 50;
            float offsetY = 50;
            if (worldController.scoreVisual < worldController.score) {
                long shakeAlpha = System.currentTimeMillis() % 360;
                float shakeDist = 1.5f;
                offsetX += MathUtils.sinDeg(shakeAlpha * 2.2f) * shakeDist;
                offsetY += MathUtils.sinDeg(shakeAlpha * 2.9f) * shakeDist;
            }
            batch.draw(Assets.instance.goldCoin.goldCoin, x, y, offsetX, offsetY,
                    100, 100, 0.35f, -0.35f, 0);
            Assets.instance.fonts.defaultBig.draw(batch, ""
                    + (int) worldController.scoreVisual, x + 75, y + 37);
        }

    在下一章,我们将使用转场动画来平滑的过渡场景

  • 相关阅读:
    Oralce中备份,还原数据库
    Linux基础--目录了解以及安装后的优化
    PHP学习之旅——PHP环境搭建
    在虚拟机上安装Linux系统
    Hibernate 命名查询
    MyBatis入门案例
    MyBatis中关于别名typeAliases的设置
    SpingMvc中的异常处理
    无意之间发现的Servlet3.0新特性@WebServlet
    SpringMvc核心流程以及入门案例的搭建
  • 原文地址:https://www.cnblogs.com/mignet/p/libgdx_game_development_08.html
Copyright © 2011-2022 走看看