首页 > 解决方案 > 在 libgdx 中正确实现平移和缩放

问题描述

我几乎完成了我的第一个 libgdx 2d 游戏,只是缺少了这个。目前我有我的 main GameScreen,其中包含 3 个阶段(按此顺序呈现):

包含游戏演员、地图等。基本上是游戏本身。

有关游戏的所有控件和信息,即所谓的“hud”。

用于显示对话框,以便它们位于所有内容之上。

我正在使用OrthographicCamera显示整个地图和 FitViewport 所需大小的单个视口和视口,以确保在调整大小或比率发生变化时一切正常。

现在我想进行更改,以便InfoStage保持DialogStage几乎这样 -fixed在屏幕上并允许GameStage缩放和平移移动。

首先,这对视口和相机意味着什么?这是否意味着我将拥有相机+视口对InfoStageDialogStage不同的相机+视口对GameStage

其次也是最重要的——如何在这个阶段实现缩放和平移?我见过一些类似的问题,但实现对我来说并没有真正起作用,我也不喜欢它们。感觉可以有一个简单的分离类来做到这一点 - 可能GestureListenerEventListener/InputProcessor通过Camera/Viewport自动完成所有工作。我发现CameraInputController做了类似的事情,所以我想这可能有点领先。有没有人实现过这个组件?如果没有,你会建议做什么?

标签: libgdxorthographic

解决方案


到目前为止,我制作了GestureListener,它处理zoompan,确保您永远不会缩小太多并且永远不会平移出地图,工作非常顺利,但可能无法涵盖所有​​可能性,例如 y 轴旋转。如果您可以fitBounds在调整大小后单独调用,以便世界剪辑以备不时之需。我会尝试更新课程,因为我会变得更干净、更灵活。很确定这种scale()方法必须在引擎中有内置的替代方案。我添加的另一件事是ScrollInputListener,它可以监听鼠标滚动并执行缩放。

只需将这个类添加到GestureDetector并添加到您的输入多路复用器中。我正在使用它FillViewport

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.input.GestureDetector
import com.badlogic.gdx.utils.viewport.Viewport
class ScrollInputListener(
        val camera: OrthographicCamera,
        val panGestureListener: PanGestureListener
) : InputAdapter() {


    fun amountToZoom(amount: Int): Float {
        return (1.0f + amount.toFloat() / 10f)
    }

    override fun scrolled(amount: Int): Boolean {
        val newZoom = camera.zoom * amountToZoom(amount)
        return panGestureListener.doZoomIfAllowed(newZoom)
    }
}


class CameraMovement(val x: Float, val y: Float) {
    private fun shouldMove(): Boolean {
        return  x!= 0f || y != 0f
    }

    fun move(camera: OrthographicCamera, scale: Float): Boolean {
        if (shouldMove()) {
            camera.translate(-x / scale, y / scale)
            return true
        }
        return false
    }
}

val Viewport.targetRatio: Float
    get() {
        return Gdx.graphics.height.toFloat() / this.worldHeight.toFloat()
    }

val Viewport.sourceRatio: Float
    get() {
        return Gdx.graphics.width.toFloat() / this.worldWidth.toFloat()
    }

fun Viewport.scale(): Float {
    return if (targetRatio < sourceRatio) Gdx.graphics.width.toFloat() / this.worldWidth.toFloat() else Gdx.graphics.height.toFloat() / this.worldHeight.toFloat()
}

class PanGestureListener(
        val camera: OrthographicCamera,
        val viewport: Viewport,
        val minWorldRatio: Double = 0.5
) : GestureDetector.GestureAdapter() {

    override fun pan(x: Float, y: Float, deltaX: Float, deltaY: Float): Boolean {
        val scale = viewport.scale()
        return fitBounds(scale, deltaX, deltaY).move(camera, scale)
    }

    fun fitBounds(scale: Float = viewport.scale(), deltaX: Float = 0f, deltaY: Float = 0f): CameraMovement {
        val mapLeft = 0
        val mapRight = viewport.worldWidth / camera.zoom
        val mapTop = 0
        val mapBottom = viewport.worldHeight / camera.zoom

        val cameraHalfWidth = if (viewport.targetRatio < viewport.sourceRatio) {
            camera.viewportWidth / 2f
        } else {
            camera.viewportWidth / 2f * (viewport.sourceRatio / viewport.targetRatio)
        }
        val cameraHalfHeight = if (viewport.targetRatio < viewport.sourceRatio) {
            camera.viewportHeight / 2f * (viewport.targetRatio / viewport.sourceRatio)
        } else {
            camera.viewportHeight / 2f
        }
        var xMovement = deltaX
        var yMovement = deltaY

        if ((camera.position.x - deltaX / scale) / camera.zoom - cameraHalfWidth < mapLeft) {
            camera.position.x = (mapLeft + cameraHalfWidth) * camera.zoom
            xMovement = 0f
        }
        if ((camera.position.x - deltaX / scale) / camera.zoom + cameraHalfWidth > mapRight) {
            camera.position.x = (mapRight - cameraHalfWidth) * camera.zoom
            xMovement = 0f
        }

        if ((camera.position.y + deltaY / scale) / camera.zoom - cameraHalfHeight < mapTop) {
            camera.position.y = (mapTop + cameraHalfHeight) * camera.zoom
            yMovement = 0f
        }
        if ((camera.position.y + deltaY / scale) / camera.zoom + cameraHalfHeight > mapBottom) {
            camera.position.y = (mapBottom - cameraHalfHeight) * camera.zoom
            yMovement = 0f
        }
        return CameraMovement(xMovement, yMovement)
    }

    var initialZoom = camera.zoom

    internal fun checkZoom(newZoom: Float, scale: Float = viewport.scale()): Boolean {
        if (
                viewport.worldWidth * scale * newZoom > Gdx.graphics.width &&
                viewport.worldHeight * scale * newZoom > Gdx.graphics.height
        ) {
            return false
        }
        if (
                viewport.worldWidth * scale * newZoom < Gdx.graphics.width * minWorldRatio &&
                viewport.worldHeight * scale * newZoom < Gdx.graphics.height * minWorldRatio
        ) {
            return false
        }
        return true
    }

    override fun zoom(initialDistance: Float, distance: Float): Boolean {
        val newZoom = initialZoom * initialDistance / distance
        val scale = viewport.scale()
        return doZoomIfAllowed(newZoom, scale)
    }

    fun doZoomIfAllowed(newZoom: Float, scale: Float = viewport.scale()): Boolean {
        if (checkZoom(newZoom = newZoom, scale = scale)) {
            camera.zoom = newZoom
            fitBounds(scale = scale)
            return true
        }
        return false
    }

    override fun touchDown(x: Float, y: Float, pointer: Int, button: Int): Boolean {
        initialZoom = camera.zoom
        return super.touchDown(x, y, pointer, button)
    }
}

推荐阅读