mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
moving platform wip
This commit is contained in:
@@ -32,6 +32,7 @@ SetSol
|
|||||||
SetTurb
|
SetTurb
|
||||||
SetTime
|
SetTime
|
||||||
SetTimeDelta
|
SetTimeDelta
|
||||||
|
SpawnMovingPlatform
|
||||||
SpawnPhysTestBall
|
SpawnPhysTestBall
|
||||||
Teleport
|
Teleport
|
||||||
ToggleNoClip
|
ToggleNoClip
|
||||||
|
|||||||
|
@@ -1,40 +1,192 @@
|
|||||||
package net.torvald.terrarum.gameactors
|
package net.torvald.terrarum.gameactors
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||||
|
import net.torvald.terrarum.INGAME
|
||||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||||
import net.torvald.terrarum.gameactors.ActorID
|
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
||||||
import net.torvald.terrarum.gameactors.ActorWithBody
|
import org.dyn4j.geometry.Vector2
|
||||||
import net.torvald.terrarum.gameactors.PhysProperties
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Base class for autonomously moving platforms that carry other actors standing on them.
|
||||||
|
*
|
||||||
|
* Subclasses must set [platformVelocity] before calling `super.updateImpl(delta)`.
|
||||||
|
*
|
||||||
* Created by minjaesong on 2022-02-28.
|
* Created by minjaesong on 2022-02-28.
|
||||||
*/
|
*/
|
||||||
open class ActorMovingPlatform() : ActorWithBody() {
|
open class ActorMovingPlatform() : ActorWithBody() {
|
||||||
|
|
||||||
protected var tilewiseWidth = 3
|
protected var tilewiseWidth = 3
|
||||||
@Transient protected val actorsRiding = ArrayList<ActorID>() // saving actorID due to serialisation issues
|
|
||||||
|
constructor(newTilewiseWidth: Int) : this() {
|
||||||
|
this.tilewiseWidth = newTilewiseWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Actors currently riding this platform, stored by ActorID for serialisation. */
|
||||||
|
@Transient protected val actorsRiding = ArrayList<ActorID>()
|
||||||
|
|
||||||
|
/** Velocity the platform intends to move this tick. Subclasses set this before calling super.updateImpl(). */
|
||||||
|
@Transient protected val platformVelocity = Vector2(0.0, 0.0)
|
||||||
|
|
||||||
|
/** Actual displacement applied this tick (after clampHitbox). */
|
||||||
|
@Transient private val appliedVelocity = Vector2(0.0, 0.0)
|
||||||
|
|
||||||
|
/** Tolerance in pixels for "feet on top of platform" detection. */
|
||||||
|
@Transient private val MOUNT_TOLERANCE_Y = 2.0
|
||||||
|
|
||||||
|
/** Minimum combined Y velocity to count as "jumping up" (prevents mount while jumping). */
|
||||||
|
@Transient private val JUMP_THRESHOLD_Y = -0.5
|
||||||
|
|
||||||
|
@Transient private var platformTexture: Texture? = null
|
||||||
|
@Transient private var platformTextureRegion: TextureRegion? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
physProp = PhysProperties.PHYSICS_OBJECT()
|
physProp = PhysProperties.MOBILE_OBJECT()
|
||||||
|
collisionType = COLLISION_KINEMATIC
|
||||||
|
|
||||||
setHitboxDimension(TILE_SIZE * tilewiseWidth, TILE_SIZE, 0, 0)
|
setHitboxDimension(TILE_SIZE * tilewiseWidth, TILE_SIZE, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ensureTexture() {
|
||||||
|
if (platformTexture == null) {
|
||||||
|
val w = TILE_SIZE * tilewiseWidth
|
||||||
|
val h = TILE_SIZE
|
||||||
|
val pixmap = Pixmap(w, h, Pixmap.Format.RGBA8888)
|
||||||
|
// grey-blue colour
|
||||||
|
pixmap.setColor(Color(0.45f, 0.55f, 0.65f, 1f))
|
||||||
|
pixmap.fill()
|
||||||
|
// slightly darker border
|
||||||
|
pixmap.setColor(Color(0.35f, 0.45f, 0.55f, 1f))
|
||||||
|
pixmap.drawRectangle(0, 0, w, h)
|
||||||
|
platformTexture = Texture(pixmap)
|
||||||
|
platformTextureRegion = TextureRegion(platformTexture)
|
||||||
|
pixmap.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateImpl(delta: Float) {
|
override fun updateImpl(delta: Float) {
|
||||||
TODO("Not yet implemented")
|
// Snapshot position before movement
|
||||||
|
val oldX = hitbox.startX
|
||||||
|
val oldY = hitbox.startY
|
||||||
|
|
||||||
|
// Set externalV to our platform velocity so super translates the hitbox
|
||||||
|
externalV.set(platformVelocity.x, platformVelocity.y)
|
||||||
|
|
||||||
|
// super.updateImpl handles:
|
||||||
|
// - sprite updates
|
||||||
|
// - hitbox.translate(externalV) (since usePhysics=false -> isNoCollideWorld=true)
|
||||||
|
// - clampHitbox
|
||||||
|
// - tilewise hitbox cache updates
|
||||||
|
// - position vector updates
|
||||||
|
super.updateImpl(delta)
|
||||||
|
|
||||||
|
// Compute actual displacement (clampHitbox may have wrapped coordinates)
|
||||||
|
appliedVelocity.set(hitbox.startX - oldX, hitbox.startY - oldY)
|
||||||
|
|
||||||
|
// --- Mount detection and rider management ---
|
||||||
|
|
||||||
|
val ridersToRemove = ArrayList<ActorID>()
|
||||||
|
|
||||||
|
// Build set of actors currently on top of this platform
|
||||||
|
val newRiders = ArrayList<ActorWithBody>()
|
||||||
|
|
||||||
|
// Check all active actors + actorNowPlaying
|
||||||
|
val candidates = ArrayList<ActorWithBody>()
|
||||||
|
INGAME.actorContainerActive.forEach {
|
||||||
|
if (it is ActorWithBody && it !== this && it !is ActorMovingPlatform) {
|
||||||
|
candidates.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INGAME.actorNowPlaying?.let { candidates.add(it) }
|
||||||
|
|
||||||
|
for (actor in candidates) {
|
||||||
|
val feetY = actor.hitbox.endY
|
||||||
|
val platTop = hitbox.startY
|
||||||
|
|
||||||
|
// Check vertical proximity: feet within tolerance of platform top
|
||||||
|
val verticallyAligned = Math.abs(feetY - platTop) <= MOUNT_TOLERANCE_Y
|
||||||
|
|
||||||
|
// Check horizontal overlap
|
||||||
|
val horizontalOverlap = actor.hitbox.endX > hitbox.startX && actor.hitbox.startX < hitbox.endX
|
||||||
|
|
||||||
|
// Check not jumping upward
|
||||||
|
val combinedVelY = actor.externalV.y + (actor.controllerV?.y ?: 0.0)
|
||||||
|
val notJumping = combinedVelY >= JUMP_THRESHOLD_Y
|
||||||
|
|
||||||
|
if (verticallyAligned && horizontalOverlap && notJumping) {
|
||||||
|
if (!actorsRiding.contains(actor.referenceID)) {
|
||||||
|
mount(actor)
|
||||||
|
}
|
||||||
|
newRiders.add(actor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismount actors that are no longer on top
|
||||||
|
val newRiderIds = newRiders.map { it.referenceID }.toSet()
|
||||||
|
for (riderId in actorsRiding.toList()) {
|
||||||
|
if (riderId !in newRiderIds) {
|
||||||
|
val rider = INGAME.getActorByID(riderId)
|
||||||
|
if (rider is ActorWithBody) {
|
||||||
|
dismount(rider)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ridersToRemove.add(riderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ridersToRemove.forEach { actorsRiding.remove(it) }
|
||||||
|
|
||||||
|
// Move riders and suppress their gravity
|
||||||
|
for (rider in newRiders) {
|
||||||
|
// Translate rider by platform's actual displacement
|
||||||
|
rider.hitbox.translate(appliedVelocity)
|
||||||
|
|
||||||
|
// Snap rider's feet to platform top
|
||||||
|
rider.hitbox.setPositionY(hitbox.startY - rider.hitbox.height)
|
||||||
|
|
||||||
|
// Suppress gravity for this tick
|
||||||
|
rider.walledBottom = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the actor its externalV controlled by this platform
|
* Add an actor to the rider list.
|
||||||
*/
|
*/
|
||||||
fun mount(actor: ActorWithBody) {
|
fun mount(actor: ActorWithBody) {
|
||||||
|
if (!actorsRiding.contains(actor.referenceID)) {
|
||||||
actorsRiding.add(actor.referenceID)
|
actorsRiding.add(actor.referenceID)
|
||||||
|
actor.platformsRiding.add(this.referenceID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the actor its externalV no longer controlled by this platform
|
* Remove an actor from the rider list and apply dismount impulse.
|
||||||
*/
|
*/
|
||||||
fun dismount(actor: ActorWithBody) {
|
fun dismount(actor: ActorWithBody) {
|
||||||
actorsRiding.remove(actor.referenceID)
|
actorsRiding.remove(actor.referenceID)
|
||||||
|
actor.platformsRiding.remove(this.referenceID)
|
||||||
|
|
||||||
|
// Conservation of momentum: add platform velocity as impulse
|
||||||
|
actor.externalV.x += platformVelocity.x
|
||||||
|
actor.externalV.y += platformVelocity.y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun drawBody(frameDelta: Float, batch: SpriteBatch) {
|
||||||
|
if (isVisible) {
|
||||||
|
ensureTexture()
|
||||||
|
platformTextureRegion?.let {
|
||||||
|
drawTextureInGoodPosition(frameDelta, it, batch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
platformTexture?.dispose()
|
||||||
|
platformTexture = null
|
||||||
|
platformTextureRegion = null
|
||||||
|
super.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,7 @@ open class ActorWithBody : Actor {
|
|||||||
*
|
*
|
||||||
* Also see [net.torvald.terrarum.modulebasegame.gameactors.ActorMovingPlatform.actorsRiding]
|
* Also see [net.torvald.terrarum.modulebasegame.gameactors.ActorMovingPlatform.actorsRiding]
|
||||||
*/
|
*/
|
||||||
@Transient protected val platformsRiding = ArrayList<ActorID>()
|
@Transient internal val platformsRiding = ArrayList<ActorID>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gravitational Constant G. Load from gameworld.
|
* Gravitational Constant G. Load from gameworld.
|
||||||
|
|||||||
@@ -1480,8 +1480,16 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
actorNowPlaying?.update(delta)*/
|
actorNowPlaying?.update(delta)*/
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Pass 1: update moving platforms first so riders get displaced before their own update
|
||||||
actorContainerActive.forEach {
|
actorContainerActive.forEach {
|
||||||
if (it != actorNowPlaying) {
|
if (it is ActorMovingPlatform && it != actorNowPlaying) {
|
||||||
|
it.update(delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2: update all non-platform actors with existing callbacks
|
||||||
|
actorContainerActive.forEach {
|
||||||
|
if (it !is ActorMovingPlatform && it != actorNowPlaying) {
|
||||||
it.update(delta)
|
it.update(delta)
|
||||||
|
|
||||||
if (it is Pocketed) {
|
if (it is Pocketed) {
|
||||||
@@ -1515,6 +1523,8 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 3: update player
|
||||||
actorNowPlaying?.update(delta)
|
actorNowPlaying?.update(delta)
|
||||||
//AmmoMeterProxy(player, uiVitalItem.UI as UIVitalMetre)
|
//AmmoMeterProxy(player, uiVitalItem.UI as UIVitalMetre)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package net.torvald.terrarum.modulebasegame.console
|
||||||
|
|
||||||
|
import net.torvald.terrarum.INGAME
|
||||||
|
import net.torvald.terrarum.Terrarum
|
||||||
|
import net.torvald.terrarum.console.ConsoleAlias
|
||||||
|
import net.torvald.terrarum.console.ConsoleCommand
|
||||||
|
import net.torvald.terrarum.console.Echo
|
||||||
|
import net.torvald.terrarum.modulebasegame.gameactors.ActorTestPlatform
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2026-02-08.
|
||||||
|
*/
|
||||||
|
@ConsoleAlias("spawnplatform")
|
||||||
|
internal object SpawnMovingPlatform : ConsoleCommand {
|
||||||
|
override fun execute(args: Array<String>) {
|
||||||
|
val mouseX = Terrarum.mouseX
|
||||||
|
val mouseY = Terrarum.mouseY
|
||||||
|
|
||||||
|
val platform = ActorTestPlatform()
|
||||||
|
// setPosition places bottom-centre at the given point; offset Y so the platform is centred at cursor
|
||||||
|
platform.setPosition(mouseX, mouseY + platform.hitbox.height / 2.0)
|
||||||
|
|
||||||
|
INGAME.queueActorAddition(platform)
|
||||||
|
|
||||||
|
Echo("Spawned ActorTestPlatform at (${"%.1f".format(mouseX)}, ${"%.1f".format(mouseY)})")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun printUsage() {
|
||||||
|
Echo("usage: spawnplatform")
|
||||||
|
Echo("Spawns a test moving platform centred at the mouse cursor.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,77 @@
|
|||||||
package net.torvald.terrarum.modulebasegame.gameactors
|
package net.torvald.terrarum.modulebasegame.gameactors
|
||||||
|
|
||||||
import net.torvald.terrarum.gameactors.ActorMovingPlatform
|
import net.torvald.terrarum.gameactors.ActorMovingPlatform
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Test platform that randomly selects a movement pattern on spawn.
|
||||||
|
*
|
||||||
|
* Patterns:
|
||||||
|
* - 0: Horizontal pingpong (sine-eased)
|
||||||
|
* - 1: Vertical pingpong (sine-eased)
|
||||||
|
* - 2: Clockwise circular (constant speed)
|
||||||
|
* - 3: Counter-clockwise circular (constant speed)
|
||||||
|
*
|
||||||
* Created by minjaesong on 2022-03-02.
|
* Created by minjaesong on 2022-03-02.
|
||||||
*/
|
*/
|
||||||
class ActorTestPlatform : ActorMovingPlatform() {
|
class ActorTestPlatform : ActorMovingPlatform(8) {
|
||||||
|
|
||||||
|
/** Movement pattern index (0-3). */
|
||||||
|
private val pattern: Int = (0..3).random()
|
||||||
|
|
||||||
|
/** Speed in pixels per tick (2.0 to 4.0). */
|
||||||
|
private val speed: Double = 2.0 + Math.random() * 2.0
|
||||||
|
|
||||||
|
/** Current phase angle in radians. */
|
||||||
|
private var phase: Double = 0.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase step per tick.
|
||||||
|
*
|
||||||
|
* For pingpong: peak speed = amplitude * phaseStep = speed
|
||||||
|
* period = 128 ticks (~2s), so phaseStep = 2*PI/128
|
||||||
|
* amplitude = speed / phaseStep
|
||||||
|
*
|
||||||
|
* For circular: speed = radius * phaseStep
|
||||||
|
* using same phaseStep, radius = speed / phaseStep
|
||||||
|
*/
|
||||||
|
@Transient private val PERIOD_TICKS = 128.0
|
||||||
|
@Transient private val phaseStep: Double = 2.0 * Math.PI / PERIOD_TICKS
|
||||||
|
|
||||||
|
/** Amplitude for pingpong patterns, radius for circular patterns. */
|
||||||
|
@Transient private val amplitude: Double = speed / phaseStep
|
||||||
|
|
||||||
|
override fun updateImpl(delta: Float) {
|
||||||
|
val oldPhase = phase
|
||||||
|
phase += phaseStep
|
||||||
|
|
||||||
|
when (pattern) {
|
||||||
|
0 -> {
|
||||||
|
// Horizontal pingpong: position = A * sin(phase)
|
||||||
|
// Velocity = finite difference to prevent float drift
|
||||||
|
val dx = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
|
platformVelocity.set(dx, 0.0)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
// Vertical pingpong: position = A * sin(phase)
|
||||||
|
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
|
platformVelocity.set(0.0, dy)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
// Clockwise circular: position on circle (cos, sin)
|
||||||
|
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
||||||
|
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
|
platformVelocity.set(dx, dy)
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
// Counter-clockwise circular: negate Y component
|
||||||
|
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
||||||
|
val dy = -(amplitude * (sin(phase) - sin(oldPhase)))
|
||||||
|
platformVelocity.set(dx, dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.updateImpl(delta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user