Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix scrolling of parallax layer background within game world #46

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ import korlibs.image.format.*
import korlibs.io.file.*
import korlibs.korge.fleks.utils.*
import kotlinx.serialization.*
import kotlinx.serialization.EncodeDefault.Mode.NEVER

import korlibs.korge.fleks.renderSystems.ParallaxRenderSystem

/** This function reads the parallax data from an Aseprite file.
* It reads the background, foreground and attached layers as well as the parallax plane from the Aseprite file.
*
* The [config] object contains the configuration for the parallax effect. It contains the name of the Aseprite file
* and the configuration for the layers and the parallax plane.
*
* The [format] parameter is used to specify the image format of the file. Currently only Aseprite is supported [ASE].
*
* The [atlas] parameter is used to pack the image data into an atlas. If it is not null the image data will be packed
* into the atlas. If it is null the image data will be stored separately.
*/
suspend fun VfsFile.readParallaxDataContainer(
config: ParallaxConfig,
format: ImageFormat = ASE,
Expand Down Expand Up @@ -89,11 +99,11 @@ suspend fun VfsFile.readParallaxDataContainer(
when (config.mode) {
ParallaxConfig.Mode.HORIZONTAL_PLANE -> {
(backgroundLayers?.height ?: foregroundLayers?.height ?: attachedLayersFront?.height
?: attachedLayersRear?.height ?: 0) - (config.parallaxPlane?.offset ?: 0)
?: attachedLayersRear?.height ?: 0) - config.offset
}
ParallaxConfig.Mode.VERTICAL_PLANE -> {
(backgroundLayers?.width ?: foregroundLayers?.width ?: attachedLayersFront?.width
?: attachedLayersRear?.height ?: 0) - (config.parallaxPlane?.offset ?: 0)
?: attachedLayersRear?.height ?: 0) - config.offset
}
ParallaxConfig.Mode.NO_PLANE -> 0 // not used without parallax plane setup
}
Expand All @@ -119,7 +129,8 @@ suspend fun VfsFile.readParallaxDataContainer(
}

/**
* This class contains all data which is needed by the [ParallaxDataView] to display the parallax view on the screen.
* This class contains all data which is needed by [ParallaxRenderSystem] to render parallax data from an Aseprite file.
*
* It stores the [ParallaxConfig] and all [ImageData] objects for the background, foreground and attached Layers. The
* parallax plane is a sliced Aseprite image and therefore consists of a [ImageDataContainer] object.
*
Expand All @@ -137,9 +148,12 @@ data class ParallaxDataContainer(
)
/**
* This is the main parallax configuration.
*
* The [aseName] is the name of the aseprite file which is used for reading the image data.
* (Currently it is not used. It will be used when reading the config from YAML/JSON file.)
*
* [offset] is the amount of pixels from the top of the image where the upper part of the parallax plane starts.
*
* The parallax [mode] has to be one of the following enum values:
* - [NO_PLANE]
* This type is used to set up a parallax background which will scroll repeatedly in X and Y direction. For this
Expand All @@ -161,6 +175,7 @@ data class ParallaxDataContainer(
@Serializable @SerialName("ParallaxConfig")
data class ParallaxConfig(
val aseName: String,
val offset: Int = 0,
val mode: Mode = Mode.HORIZONTAL_PLANE,
val backgroundLayers: ArrayList<ParallaxLayerConfig>? = null,
val parallaxPlane: ParallaxPlaneConfig? = null,
Expand All @@ -178,7 +193,6 @@ data class ParallaxConfig(
* The top part is the upper half of the Aseprite image. The bottom part is the bottom half of the image. This is used
* to simulate a central vanishing point in the resulting parallax effect.
*
* [offset] is the amount of pixels from the top of the image where the upper part of the parallax plane starts.
* [name] has to be set to the name of the layer in the Aseprite which contains the image for the sliced stripes
* of the parallax plane.
* [speedFactor] is the factor for scrolling the parallax plane relative to the game play field (which usually contains the
Expand All @@ -191,7 +205,6 @@ data class ParallaxConfig(
*/
@Serializable @SerialName("ParallaxPlaneConfig")
data class ParallaxPlaneConfig(
val offset: Int = 0,
val name: String,
val speedFactor: Float = 1f,
val selfSpeed: Float = 0f,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ data class ParallaxComponent(
parallaxDataContainer.foregroundLayers?.height ?:
parallaxDataContainer.parallaxPlane?.default?.height ?: throw Error("ParallaxComponent: Parallax image data has no height!")
).toFloat()
val parallaxLayerHeight: Float = imageHeight - (parallaxDataContainer.config.parallaxPlane?.offset ?: 0)
this.system<CameraSystem>().parallaxHeight = parallaxLayerHeight
val parallaxLayerHeight: Float = imageHeight
this.system<CameraSystem>().parallaxHeight = parallaxLayerHeight - parallaxDataContainer.config.offset.toFloat()
this.system<CameraSystem>().parallaxOffset = parallaxDataContainer.config.offset.toFloat()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ data class MainCameraConfig(
// Camera has position within the game world
// Offset can be used to "shake" the camera on explosions etc.
it += PositionComponent()

// TODO: Move view port size to injectable object - it is dependent on the target device and game config might be
// moved to another device with different screen size
// Camera has a size which is the view port of the game
it += SizeIntComponent(
width = viewPortWith, // SizeIntComponent is used to store the view port size as integer values
Expand All @@ -35,9 +38,6 @@ data class MainCameraConfig(

// Camera has a tag to make it easily accessible for other systems and entity configurations
it += MainCameraTag

// TODO: Add bounds of level world (really here?)

}
return entity
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ class CameraSystem(
private val worldToPixelRatioInv = 1f / worldToPixelRatio
private val factor = 0.05f

private val assetStore: AssetStore = inject("AssetStore")
private val assetStore: AssetStore = inject<AssetStore>("AssetStore")

private val worldHeight: Float = assetStore.getWorldHeight()
private val worldWidth: Float = assetStore.getWorldWidth()

// These properties need to be set by the onAdd hook function of the ParallaxComponent
var parallaxHeight: Float = 0f
var parallaxOffset: Float = 0f

override fun onTickEntity(entity: Entity) {

Expand Down Expand Up @@ -65,20 +67,19 @@ class CameraSystem(
val position = parallaxEntity[PositionComponent]
val viewPortHeight = camera[SizeIntComponent].height

// Convert pixel distance of camera movement in the level to velocity for parallax layers
// Convert pixel distance of camera movement in the level to horizontal velocity for parallax layers
val distanceInWorldUnits = cameraDistX * worldToPixelRatioInv // (distance in pixel) / (world to pixel ratio)
motion.velocityX = -distanceInWorldUnits / deltaTime // world units per delta-time

// Calculate the ratio of the camera position in the world to the world height
// Camera position is in world coordinates
val ratio = cameraPosition.y / worldHeight // range: [0...1]
val ratio = (cameraPosition.y - viewPortHalf.height) / (worldHeight - viewPortHeight) // range: [0...1]

// TODO: Get vertical parallax offset from parallax config
val parallaxOffset = -36f
// Get the global position of the parallax layer in screen coordinates
val parallaxVerticalMax = viewPortHeight - parallaxHeight - parallaxOffset
val parallaxVerticalPosition = ratio * parallaxVerticalMax + parallaxOffset
val parallaxVerticalLength = viewPortHeight - parallaxHeight
val parallaxVerticalPosition = ratio * parallaxVerticalLength

position.y = parallaxVerticalPosition
position.y = parallaxVerticalPosition - parallaxOffset
// println("parallax y: $position.y")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ParallaxSystem(
val (configName, backgroundLayers, parallaxPlane, foregroundLayers) = entity[ParallaxComponent]
val (_, _, velocityX, velocityY) = entity[MotionComponent]
val parallaxDataContainer = assetStore.getBackground(configName)
val offset = parallaxDataContainer.config.offset

// Update local positions for each layer which is configured to be moving (speedFactor not null or zero)
backgroundLayers.fastForEachWithIndex { index, layer ->
Expand All @@ -34,18 +35,17 @@ class ParallaxSystem(
val plane = parallaxDataContainer.config.parallaxPlane!!
val parallaxMode = parallaxDataContainer.config.mode
val texture = parallaxDataContainer.attachedLayersRear!!.defaultAnimation.firstFrame.layerData[index]
val globalOffset = plane.offset
val attachTextureOffset =
if (plane.attachedLayersRear!![index].attachBottomRight) texture.height else 0
if (parallaxMode == ParallaxConfig.Mode.HORIZONTAL_PLANE) {
// Get correct speed factor for parallax plane lines from FloatArray
val lineIndex = texture.targetY - globalOffset + attachTextureOffset
val lineIndex = texture.targetY - offset + attachTextureOffset
val speedFactor = plane.parallaxPlaneSpeedFactors[lineIndex]
// Update position of parallax position
parallaxPlane.attachedLayersRearPositions[index] = (speedFactor * velocityX * worldToPixelRatio) * deltaTime + position
} else if (parallaxMode == ParallaxConfig.Mode.VERTICAL_PLANE) {
// Get correct speed factor for parallax plane lines from FloatArray
val lineIndex = texture.targetX - globalOffset + attachTextureOffset
val lineIndex = texture.targetX - offset + attachTextureOffset
val speedFactor = plane.parallaxPlaneSpeedFactors[lineIndex]
// Update position of parallax position
parallaxPlane.attachedLayersRearPositions[index] = (speedFactor * velocityY * worldToPixelRatio) * deltaTime + position
Expand All @@ -55,7 +55,6 @@ class ParallaxSystem(
parallaxPlane.linePositions.fastForEachWithIndex { index, position ->
val plane = parallaxDataContainer.config.parallaxPlane!!
val parallaxMode = parallaxDataContainer.config.mode
val offset = plane.offset
if (parallaxMode == ParallaxConfig.Mode.HORIZONTAL_PLANE) {
// Get correct speed factor for parallax plane lines from FloatArray
val lineIndex = parallaxDataContainer.parallaxPlane!!.imageDatas[index].defaultAnimation.firstFrame.targetY - offset
Expand All @@ -75,17 +74,16 @@ class ParallaxSystem(
val plane = parallaxDataContainer.config.parallaxPlane!!
val parallaxMode = parallaxDataContainer.config.mode
val texture = parallaxDataContainer.attachedLayersFront!!.defaultAnimation.firstFrame.layerData[index]
val globalOffset = plane.offset
val attachTextureOffset = if (plane.attachedLayersFront!![index].attachBottomRight) texture.height else 0
if (parallaxMode == ParallaxConfig.Mode.HORIZONTAL_PLANE) {
// Get correct speed factor for parallax plane lines from FloatArray
val lineIndex = texture.targetY - globalOffset + attachTextureOffset
val lineIndex = texture.targetY - offset + attachTextureOffset
val speedFactor = plane.parallaxPlaneSpeedFactors[lineIndex]
// Update position of parallax position
parallaxPlane.attachedLayersFrontPositions[index] = (speedFactor * velocityX * worldToPixelRatio) * deltaTime + position
} else if (parallaxMode == ParallaxConfig.Mode.VERTICAL_PLANE) {
// Get correct speed factor for parallax plane lines from FloatArray
val lineIndex = texture.targetX - globalOffset + attachTextureOffset
val lineIndex = texture.targetX - offset + attachTextureOffset
val speedFactor = plane.parallaxPlaneSpeedFactors[lineIndex]
// Update position of parallax position
parallaxPlane.attachedLayersFrontPositions[index] = (speedFactor * velocityY * worldToPixelRatio) * deltaTime + position
Expand Down