Last active
November 12, 2020 19:11
-
-
Save acanthite1855/185de1ca81e86fcb4ebdafa600ddbd11 to your computer and use it in GitHub Desktop.
A simple CameraController for LibGDX from one of my old projects. Allows smooth zooming, translation, rotation and setting padding for OrthographicCamera. All the same!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import com.badlogic.gdx.graphics.OrthographicCamera | |
| import com.badlogic.gdx.math.Interpolation | |
| import com.badlogic.gdx.math.MathUtils | |
| import com.badlogic.gdx.math.Vector2 | |
| import com.badlogic.gdx.math.Vector3 | |
| import kotlin.math.min | |
| class CameraController( | |
| var camera: OrthographicCamera, | |
| worldWidth: Int, | |
| worldHeight: Int | |
| ) { | |
| private var targetZoom: Float | |
| private var currentRotation = 0f | |
| private var targetRotation = 0f | |
| private val targetPosition = Vector3() | |
| var scrollSpeed = ScrollSpeed.NORMAL | |
| val worldSize = Vector2() | |
| var clampPosition = true | |
| var clampZoom = true | |
| var smoothSrolling = true | |
| var interpolation = Interpolation.exp10Out | |
| var paddingTop = 0f | |
| var paddingLeft = 0f | |
| var paddingBottom = 0f | |
| var paddingRight = 0f | |
| val rotation get() = currentRotation | |
| val zoom get() = camera.zoom | |
| val position get() = camera.position | |
| init { | |
| worldSize.set(worldWidth.toFloat(), worldHeight.toFloat()) | |
| targetZoom = camera.zoom | |
| targetPosition.set(camera.position) | |
| } | |
| companion object { | |
| private const val ROUNDING_ERROR = 0.01f | |
| } | |
| fun setPadding(paddingX: Float, paddingY: Float) { | |
| setPadding(paddingY, paddingX, paddingY, paddingX) | |
| } | |
| fun setPadding(paddingTop: Float, paddingLeft: Float, paddingBottom: Float, paddingRight: Float) { | |
| this.paddingTop = paddingTop | |
| this.paddingLeft = paddingLeft | |
| this.paddingBottom = paddingBottom | |
| this.paddingRight = paddingRight | |
| } | |
| fun update(deltaTime: Float) { | |
| zoomCamera(deltaTime) | |
| translateCamera(deltaTime) | |
| rotateCamera(deltaTime) | |
| camera.update() | |
| } | |
| private fun zoomCamera(deltaTime: Float) { | |
| if (!MathUtils.isEqual(camera.zoom, targetZoom)) { | |
| if (smoothSrolling) { | |
| camera.zoom = applyInterpolation(camera.zoom, targetZoom, deltaTime) | |
| return | |
| } | |
| } | |
| camera.zoom = targetZoom | |
| } | |
| private fun translateCamera(deltaTime: Float) { | |
| if (!camera.position.epsilonEquals(targetPosition, ROUNDING_ERROR)) { | |
| if (smoothSrolling) { | |
| camera.position.x = applyInterpolation(camera.position.x, targetPosition.x, deltaTime) | |
| camera.position.y = applyInterpolation(camera.position.y, targetPosition.y, deltaTime) | |
| camera.position.z = 0f | |
| return | |
| } | |
| } | |
| camera.position.set(targetPosition) | |
| } | |
| private fun rotateCamera(deltaTime: Float) { | |
| if (!MathUtils.isEqual(currentRotation, targetRotation)) { | |
| if (smoothSrolling) { | |
| setCurrentRotation(applyInterpolation(currentRotation, targetRotation, deltaTime)) | |
| return | |
| } | |
| } | |
| setCurrentRotation(targetRotation) | |
| } | |
| private fun applyInterpolation(start: Float, end: Float, delta: Float): Float { | |
| return interpolation.apply(start, end, scrollSpeed.alpha(delta)) | |
| } | |
| private fun setCurrentRotation(angle: Float) { | |
| // The API of OrthographicCamera does not provide getter for | |
| // rotation angle, and we the previous angle from the camera | |
| // object. So we just store it a private field | |
| val previousAngle = currentRotation | |
| currentRotation = angle | |
| camera.rotate(angle - previousAngle) | |
| } | |
| fun addZoom(value: Float) { | |
| zoomTo(camera.zoom + value) | |
| } | |
| fun multiplyZoom(scale: Float) { | |
| targetZoom *= scale | |
| clampTargetZoomIfNeeded() | |
| } | |
| fun zoomTo(value: Float) { | |
| targetZoom = value | |
| clampTargetZoomIfNeeded() | |
| } | |
| private fun clampTargetZoomIfNeeded() { | |
| if (clampZoom) { | |
| targetZoom = MathUtils.clamp(targetZoom, 0.1f, maxZoomValue()) | |
| } | |
| } | |
| fun maxZoomValue(): Float { | |
| val paddingX = (paddingLeft + paddingRight) | |
| val paddingY = (paddingTop + paddingBottom) | |
| val xZoom = (worldSize.x + paddingX) / camera.viewportWidth | |
| val yZoom = (worldSize.y + paddingY) / camera.viewportHeight | |
| return min(xZoom, yZoom) | |
| } | |
| fun zoomToFitViewport() { | |
| zoomTo(maxZoomValue()) | |
| } | |
| fun resetZoom() { | |
| zoomTo(1f) | |
| } | |
| fun center() { | |
| targetPosition.set(worldSize, 0f).scl(0.5f) | |
| clampTargetPositionIfNeeded() | |
| } | |
| fun moveTo(position: Vector2) { | |
| moveTo(position.x, position.y) | |
| } | |
| fun moveTo(x: Float, y: Float) { | |
| targetPosition[x, y] = 0f | |
| clampTargetPositionIfNeeded() | |
| } | |
| fun translate(delta: Vector2) { | |
| translate(delta.x, delta.y) | |
| } | |
| fun translate(deltaX: Float, deltaY: Float) { | |
| targetPosition.add(deltaX, deltaY, 0f) | |
| clampTargetPositionIfNeeded() | |
| } | |
| private fun clampTargetPositionIfNeeded() { | |
| if (clampPosition) { | |
| targetPosition.x = MathUtils.clamp(targetPosition.x, 0f, worldSize.x) | |
| targetPosition.y = MathUtils.clamp(targetPosition.y, 0f, worldSize.y) | |
| targetPosition.z = 0f | |
| } | |
| } | |
| fun resetRotation() { | |
| rotate(0f) | |
| } | |
| fun addRotation(angle: Float) { | |
| rotate(currentRotation + angle) | |
| } | |
| fun rotate(angle: Float) { | |
| targetRotation = angle % 360f | |
| } | |
| fun resize(width: Int, height: Int) { | |
| camera.viewportWidth = width.toFloat() | |
| camera.viewportHeight = height.toFloat() | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment