libgdx - 在 libgdx 中正确实现平移和缩放
问题描述
我几乎完成了我的第一个 libgdx 2d 游戏,只是缺少了这个。目前我有我的 main GameScreen
,其中包含 3 个阶段(按此顺序呈现):
- 游戏舞台
包含游戏演员、地图等。基本上是游戏本身。
- 信息舞台
有关游戏的所有控件和信息,即所谓的“hud”。
- 对话阶段
用于显示对话框,以便它们位于所有内容之上。
我正在使用OrthographicCamera
显示整个地图和 FitViewport 所需大小的单个视口和视口,以确保在调整大小或比率发生变化时一切正常。
现在我想进行更改,以便InfoStage
保持DialogStage
几乎这样 -fixed
在屏幕上并允许GameStage
缩放和平移移动。
首先,这对视口和相机意味着什么?这是否意味着我将拥有相机+视口对InfoStage
和DialogStage
不同的相机+视口对GameStage
?
其次也是最重要的——如何在这个阶段实现缩放和平移?我见过一些类似的问题,但实现对我来说并没有真正起作用,我也不喜欢它们。感觉可以有一个简单的分离类来做到这一点 - 可能GestureListener
或EventListener
/InputProcessor
通过Camera
/Viewport
自动完成所有工作。我发现CameraInputController做了类似的事情,所以我想这可能有点领先。有没有人实现过这个组件?如果没有,你会建议做什么?
解决方案
到目前为止,我制作了GestureListener
,它处理zoom
和pan
,确保您永远不会缩小太多并且永远不会平移出地图,工作非常顺利,但可能无法涵盖所有可能性,例如 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)
}
}
推荐阅读
- macos - Bowtie2 错误:“不存在或不是 Bowtie 2 索引”
- php - 如何让工作人员在 PHP - Heroku 中持续运行?
- python-3.x - 如何在 apache2 上运行 FastAPI?
- android - 显示警报对话框的 Activity 方法的 Android Mockito 测试
- node.js - 已解决 - 条带克隆客户并在关联账户上创建直接费用
- reactjs - 'ReferenceError: cptable is not defined' 运行 Jest 测试时,生成 excel 工作表的实际代码工作正常
- mysql - MySQL - 具有空字段的 MIN 和 MAX 值
- python-3.x - python3:在没有超级用户权限的情况下附加到自定义 /var/log/mylog
- google-sheets - 如何使用 VLOOKUP 将多个匹配的列返回到 1 列
- javascript - 如何使用 amCharts 将标签放在分层条形图中最高条的顶部?