mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
platform wip3
This commit is contained in:
@@ -5,63 +5,28 @@ import com.badlogic.gdx.graphics.Pixmap
|
|||||||
import com.badlogic.gdx.graphics.Texture
|
import com.badlogic.gdx.graphics.Texture
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
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.blockproperties.Block
|
|
||||||
import net.torvald.terrarum.gameitems.ItemID
|
|
||||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
|
||||||
import org.dyn4j.geometry.Vector2
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for autonomously moving platforms that carry other actors standing on them.
|
* A horizontal moving platform that carries actors standing on top of it.
|
||||||
*
|
*
|
||||||
* Subclasses must set [platformVelocity] before calling `super.updateImpl(delta)`.
|
* Subclasses must set [contraptionVelocity] before calling `super.updateImpl(delta)`.
|
||||||
*
|
|
||||||
* TODO: in the future this must be generalised as a PhysContraption
|
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2022-02-28.
|
* Created by minjaesong on 2022-02-28.
|
||||||
*/
|
*/
|
||||||
open class ActorMovingPlatform() : ActorWithBody() {
|
open class ActorMovingPlatform() : PhysContraption() {
|
||||||
|
|
||||||
protected var tilewiseWidth = 3 // default fallback value when no args were given
|
protected var tilewiseWidth = 3 // default fallback value when no args were given
|
||||||
|
|
||||||
constructor(newTilewiseWidth: Int) : this() {
|
constructor(newTilewiseWidth: Int) : this() {
|
||||||
this.tilewiseWidth = newTilewiseWidth
|
this.tilewiseWidth = newTilewiseWidth
|
||||||
|
|
||||||
physProp = PhysProperties.MOBILE_OBJECT()
|
|
||||||
collisionType = COLLISION_KINEMATIC
|
|
||||||
|
|
||||||
setHitboxDimension(TILE_SIZE * newTilewiseWidth, TILE_SIZE, 0, 0)
|
setHitboxDimension(TILE_SIZE * newTilewiseWidth, TILE_SIZE, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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 above platform top for "feet on top" detection (pixels). */
|
|
||||||
@Transient private val MOUNT_TOLERANCE_ABOVE = (TILE_SIZE / 2).toDouble()//2.0
|
|
||||||
|
|
||||||
/** Tolerance below platform top — how far feet can sink before dismount (pixels). */
|
|
||||||
@Transient private val MOUNT_TOLERANCE_BELOW = (TILE_SIZE / 2).toDouble()
|
|
||||||
|
|
||||||
/** Minimum combined Y velocity to count as "jumping up" (prevents mount while jumping). */
|
|
||||||
@Transient private val JUMP_THRESHOLD_Y = -0.5
|
|
||||||
|
|
||||||
/** Block whose friction this platform impersonates. Riders use this for feet friction. */
|
|
||||||
var surfaceBlock: ItemID = Block.STONE
|
|
||||||
|
|
||||||
@Transient private var platformTexture: Texture? = null
|
@Transient private var platformTexture: Texture? = null
|
||||||
@Transient private var platformTextureRegion: TextureRegion? = null
|
@Transient private var platformTextureRegion: TextureRegion? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
physProp = PhysProperties.MOBILE_OBJECT()
|
|
||||||
collisionType = COLLISION_KINEMATIC
|
|
||||||
|
|
||||||
setHitboxDimension(TILE_SIZE * tilewiseWidth, TILE_SIZE, 0, 0)
|
setHitboxDimension(TILE_SIZE * tilewiseWidth, TILE_SIZE, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,123 +47,26 @@ open class ActorMovingPlatform() : ActorWithBody() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateImpl(delta: Float) {
|
override fun isActorOnTop(actor: ActorWithBody): Boolean {
|
||||||
// Snapshot position before movement
|
val feetY = actor.hitbox.endY
|
||||||
val oldX = hitbox.startX
|
val headY = actor.hitbox.startY
|
||||||
val oldY = hitbox.startY
|
val platTop = hitbox.startY
|
||||||
|
|
||||||
// Set externalV to our platform velocity so super translates the hitbox
|
// Feet are near platform top: slightly above or sunk partway in
|
||||||
externalV.set(platformVelocity.x, platformVelocity.y)
|
val feetNearPlatTop = feetY >= platTop - MOUNT_TOLERANCE_ABOVE &&
|
||||||
|
feetY <= platTop + MOUNT_TOLERANCE_BELOW
|
||||||
|
|
||||||
// super.updateImpl handles:
|
// Actor's head must be above platform top (prevents mounting from below)
|
||||||
// - sprite updates
|
val comingFromAbove = headY < platTop
|
||||||
// - 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)
|
// Check horizontal overlap
|
||||||
appliedVelocity.set(hitbox.startX - oldX, hitbox.startY - oldY)
|
val horizontalOverlap = actor.hitbox.endX > hitbox.startX && actor.hitbox.startX < hitbox.endX
|
||||||
|
|
||||||
// --- Step 1: Move existing riders BEFORE mount detection ---
|
// Check not jumping upward
|
||||||
// This keeps riders aligned with the platform's new position so the
|
val combinedVelY = actor.externalV.y + (actor.controllerV?.y ?: 0.0)
|
||||||
// mount check doesn't fail when the platform is moving fast.
|
val notJumping = combinedVelY >= JUMP_THRESHOLD_Y
|
||||||
for (riderId in actorsRiding.toList()) {
|
|
||||||
val rider = INGAME.getActorByID(riderId) as? ActorWithBody
|
|
||||||
if (rider != null) {
|
|
||||||
rider.hitbox.translate(appliedVelocity)
|
|
||||||
rider.hitbox.setPositionY(hitbox.startY - rider.hitbox.height)
|
|
||||||
if (rider.externalV.y > 0.0) {
|
|
||||||
rider.externalV.y = 0.0
|
|
||||||
}
|
|
||||||
rider.walledBottom = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Step 2: Mount detection (riders are now at correct positions) ---
|
return feetNearPlatTop && comingFromAbove && horizontalOverlap && notJumping
|
||||||
|
|
||||||
val ridersToRemove = ArrayList<ActorID>()
|
|
||||||
val currentRiders = 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 headY = actor.hitbox.startY
|
|
||||||
val platTop = hitbox.startY
|
|
||||||
|
|
||||||
// Feet are near platform top: slightly above or sunk partway in
|
|
||||||
val feetNearPlatTop = feetY >= platTop - MOUNT_TOLERANCE_ABOVE &&
|
|
||||||
feetY <= platTop + MOUNT_TOLERANCE_BELOW
|
|
||||||
|
|
||||||
// Actor's head must be above platform top (prevents mounting from below)
|
|
||||||
val comingFromAbove = headY < platTop
|
|
||||||
|
|
||||||
// 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 (feetNearPlatTop && comingFromAbove && horizontalOverlap && notJumping) {
|
|
||||||
if (!actorsRiding.contains(actor.referenceID)) {
|
|
||||||
// New rider — mount and snap
|
|
||||||
mount(actor)
|
|
||||||
actor.hitbox.setPositionY(hitbox.startY - actor.hitbox.height)
|
|
||||||
if (actor.externalV.y > 0.0) {
|
|
||||||
actor.externalV.y = 0.0
|
|
||||||
}
|
|
||||||
actor.walledBottom = true
|
|
||||||
}
|
|
||||||
currentRiders.add(actor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Step 3: Dismount actors no longer on top ---
|
|
||||||
val currentRiderIds = currentRiders.map { it.referenceID }.toSet()
|
|
||||||
for (riderId in actorsRiding.toList()) {
|
|
||||||
if (riderId !in currentRiderIds) {
|
|
||||||
val rider = INGAME.getActorByID(riderId)
|
|
||||||
if (rider is ActorWithBody) {
|
|
||||||
dismount(rider)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ridersToRemove.add(riderId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ridersToRemove.forEach { actorsRiding.remove(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an actor to the rider list.
|
|
||||||
*/
|
|
||||||
fun mount(actor: ActorWithBody) {
|
|
||||||
if (!actorsRiding.contains(actor.referenceID)) {
|
|
||||||
actorsRiding.add(actor.referenceID)
|
|
||||||
actor.platformsRiding.add(this.referenceID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an actor from the rider list and apply dismount impulse.
|
|
||||||
*/
|
|
||||||
fun dismount(actor: ActorWithBody) {
|
|
||||||
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) {
|
override fun drawBody(frameDelta: Float, batch: SpriteBatch) {
|
||||||
|
|||||||
@@ -328,9 +328,9 @@ open class ActorWithBody : Actor {
|
|||||||
var isPickedUp = false
|
var isPickedUp = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redundant entry for ActorMovingPlatform.actorsRiding. This field must be modified by the platforms!
|
* Redundant entry for PhysContraption.actorsRiding. This field must be modified by the contraptions!
|
||||||
*
|
*
|
||||||
* Also see [net.torvald.terrarum.modulebasegame.gameactors.ActorMovingPlatform.actorsRiding]
|
* Also see [PhysContraption.actorsRiding]
|
||||||
*/
|
*/
|
||||||
@Transient internal val platformsRiding = ArrayList<ActorID>()
|
@Transient internal val platformsRiding = ArrayList<ActorID>()
|
||||||
|
|
||||||
@@ -604,14 +604,7 @@ open class ActorWithBody : Actor {
|
|||||||
* If and only if:
|
* If and only if:
|
||||||
* This body is NON-STATIC and the other body is STATIC
|
* This body is NON-STATIC and the other body is STATIC
|
||||||
*/
|
*/
|
||||||
if (platformsRiding.isNotEmpty()) {
|
if (!isNoCollideWorld) {
|
||||||
// Riding a platform: skip displaceHitbox entirely.
|
|
||||||
// The CCD/collision solver doesn't know about platforms and
|
|
||||||
// will corrupt the rider's position (bounce, stair-step, etc.).
|
|
||||||
// Only apply horizontal movement; the platform owns Y.
|
|
||||||
hitbox.translate(vecSum.x, 0.0)
|
|
||||||
}
|
|
||||||
else if (!isNoCollideWorld) {
|
|
||||||
val (collisionStatus, collisionDamage) = displaceHitbox(true)
|
val (collisionStatus, collisionDamage) = displaceHitbox(true)
|
||||||
|
|
||||||
|
|
||||||
@@ -640,6 +633,21 @@ open class ActorWithBody : Actor {
|
|||||||
hitbox.translate(vecSum)
|
hitbox.translate(vecSum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-snap to platform after displaceHitbox: terrain collision may
|
||||||
|
// have shifted Y (stair-stepping, two-side resolution, etc.) but the
|
||||||
|
// platform, not terrain, owns the rider's vertical position.
|
||||||
|
// Only snap if it wouldn't push the rider into a ceiling.
|
||||||
|
if (platformsRiding.isNotEmpty()) {
|
||||||
|
val platform = INGAME.getActorByID(platformsRiding[0])
|
||||||
|
if (platform is ActorWithBody) {
|
||||||
|
val preSnapY = hitbox.startY
|
||||||
|
hitbox.setPositionY(platform.hitbox.startY - hitbox.height)
|
||||||
|
if (isWalled(hitbox, COLLIDING_TOP)) {
|
||||||
|
hitbox.setPositionY(preSnapY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
// Codes that modifies velocity (after hitbox displacement) //
|
// Codes that modifies velocity (after hitbox displacement) //
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
@@ -1696,7 +1704,7 @@ open class ActorWithBody : Actor {
|
|||||||
// When riding a platform, use the platform's surface block friction
|
// When riding a platform, use the platform's surface block friction
|
||||||
if (platformsRiding.isNotEmpty()) {
|
if (platformsRiding.isNotEmpty()) {
|
||||||
val platform = INGAME.getActorByID(platformsRiding[0])
|
val platform = INGAME.getActorByID(platformsRiding[0])
|
||||||
if (platform is ActorMovingPlatform) {
|
if (platform is PhysContraption) {
|
||||||
return getTileFriction(platform.surfaceBlock)
|
return getTileFriction(platform.surfaceBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/net/torvald/terrarum/gameactors/PhysContraption.kt
Normal file
177
src/net/torvald/terrarum/gameactors/PhysContraption.kt
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package net.torvald.terrarum.gameactors
|
||||||
|
|
||||||
|
import net.torvald.terrarum.INGAME
|
||||||
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||||
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
|
||||||
|
import net.torvald.terrarum.abs
|
||||||
|
import net.torvald.terrarum.blockproperties.Block
|
||||||
|
import net.torvald.terrarum.gameitems.ItemID
|
||||||
|
import net.torvald.terrarum.sqrt
|
||||||
|
import org.dyn4j.geometry.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for autonomously moving contraptions that carry other actors.
|
||||||
|
*
|
||||||
|
* Handles rider management (mount/dismount), momentum conservation, and
|
||||||
|
* velocity-driven hitbox translation. Subclasses provide geometry-specific
|
||||||
|
* mount detection via [isActorOnTop] and set [contraptionVelocity] before
|
||||||
|
* calling `super.updateImpl(delta)`.
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2026-02-08.
|
||||||
|
*/
|
||||||
|
abstract class PhysContraption() : ActorWithBody() {
|
||||||
|
|
||||||
|
/** Actors currently riding this contraption, stored by ActorID for serialisation. */
|
||||||
|
protected val actorsRiding = ArrayList<ActorID>()
|
||||||
|
|
||||||
|
/** Velocity the contraption intends to move this tick. Subclasses set this before calling super.updateImpl(). */
|
||||||
|
protected val contraptionVelocity = Vector2(0.0, 0.0)
|
||||||
|
|
||||||
|
/** Actual displacement applied this tick (after clampHitbox). */
|
||||||
|
private val appliedVelocity = Vector2(0.0, 0.0)
|
||||||
|
|
||||||
|
/** Tolerance above contraption top for "feet on top" detection (pixels). */
|
||||||
|
@Transient protected open val MOUNT_TOLERANCE_ABOVE: Double = INGAME.world.gravitation.y.abs().sqrt()
|
||||||
|
|
||||||
|
/** Tolerance below contraption top — how far feet can sink before dismount (pixels). */
|
||||||
|
@Transient protected open val MOUNT_TOLERANCE_BELOW: Double = TILE_SIZED
|
||||||
|
|
||||||
|
/** Minimum combined Y velocity to count as "jumping up" (prevents mount while jumping). */
|
||||||
|
@Transient protected open val JUMP_THRESHOLD_Y: Double = -0.5
|
||||||
|
|
||||||
|
/** Block whose friction this contraption impersonates. Riders use this for feet friction. */
|
||||||
|
var surfaceBlock: ItemID = Block.STONE
|
||||||
|
|
||||||
|
init {
|
||||||
|
physProp = PhysProperties.MOBILE_OBJECT()
|
||||||
|
collisionType = COLLISION_KINEMATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateImpl(delta: Float) {
|
||||||
|
// Snapshot position before movement
|
||||||
|
val oldX = hitbox.startX
|
||||||
|
val oldY = hitbox.startY
|
||||||
|
|
||||||
|
// Set externalV to our contraption velocity so super translates the hitbox
|
||||||
|
externalV.set(contraptionVelocity.x, contraptionVelocity.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)
|
||||||
|
|
||||||
|
// --- Step 1: Move existing riders BEFORE mount detection ---
|
||||||
|
// This keeps riders aligned with the contraption's new position so the
|
||||||
|
// mount check doesn't fail when the contraption is moving fast.
|
||||||
|
for (riderId in actorsRiding.toList()) {
|
||||||
|
val rider = INGAME.getActorByID(riderId) as? ActorWithBody ?: continue
|
||||||
|
|
||||||
|
val oldRiderX = rider.hitbox.startX
|
||||||
|
val oldRiderY = rider.hitbox.startY
|
||||||
|
|
||||||
|
// Apply horizontal displacement, then check for wall collision
|
||||||
|
rider.hitbox.translatePosX(appliedVelocity.x)
|
||||||
|
if (!rider.isNoCollideWorld && rider.isWalled(rider.hitbox, COLLIDING_LR)) {
|
||||||
|
rider.hitbox.setPositionX(oldRiderX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap to contraption surface (sets Y), then check for ceiling collision
|
||||||
|
snapRiderToSurface(rider)
|
||||||
|
if (!rider.isNoCollideWorld && rider.isWalled(rider.hitbox, COLLIDING_TOP)) {
|
||||||
|
rider.hitbox.setPositionY(oldRiderY)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rider.externalV.y > 0.0) {
|
||||||
|
rider.externalV.y = 0.0
|
||||||
|
}
|
||||||
|
rider.walledBottom = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 2: Mount detection (riders are now at correct positions) ---
|
||||||
|
|
||||||
|
val ridersToRemove = ArrayList<ActorID>()
|
||||||
|
val currentRiders = ArrayList<ActorWithBody>()
|
||||||
|
|
||||||
|
// Check all active actors + actorNowPlaying
|
||||||
|
val candidates = ArrayList<ActorWithBody>()
|
||||||
|
INGAME.actorContainerActive.forEach {
|
||||||
|
if (it is ActorWithBody && it !== this && it !is PhysContraption) {
|
||||||
|
candidates.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INGAME.actorNowPlaying?.let { candidates.add(it) }
|
||||||
|
|
||||||
|
for (actor in candidates) {
|
||||||
|
if (isActorOnTop(actor)) {
|
||||||
|
if (!actorsRiding.contains(actor.referenceID)) {
|
||||||
|
// New rider — mount and snap
|
||||||
|
mount(actor)
|
||||||
|
snapRiderToSurface(actor)
|
||||||
|
if (actor.externalV.y > 0.0) {
|
||||||
|
actor.externalV.y = 0.0
|
||||||
|
}
|
||||||
|
actor.walledBottom = true
|
||||||
|
}
|
||||||
|
currentRiders.add(actor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 3: Dismount actors no longer on top ---
|
||||||
|
val currentRiderIds = currentRiders.map { it.referenceID }.toSet()
|
||||||
|
for (riderId in actorsRiding.toList()) {
|
||||||
|
if (riderId !in currentRiderIds) {
|
||||||
|
val rider = INGAME.getActorByID(riderId)
|
||||||
|
if (rider is ActorWithBody) {
|
||||||
|
dismount(rider)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ridersToRemove.add(riderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ridersToRemove.forEach { actorsRiding.remove(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geometry check: is this actor positioned on top of the contraption such that
|
||||||
|
* it should be considered a rider? Subclasses override for different geometries.
|
||||||
|
*/
|
||||||
|
abstract fun isActorOnTop(actor: ActorWithBody): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snap a rider's vertical position to this contraption's surface.
|
||||||
|
* Default implementation places the rider on top (feet at contraption top).
|
||||||
|
* Override for contraptions that carry riders differently.
|
||||||
|
*/
|
||||||
|
open fun snapRiderToSurface(rider: ActorWithBody) {
|
||||||
|
rider.hitbox.setPositionY(hitbox.startY - rider.hitbox.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an actor to the rider list.
|
||||||
|
*/
|
||||||
|
fun mount(actor: ActorWithBody) {
|
||||||
|
if (!actorsRiding.contains(actor.referenceID)) {
|
||||||
|
actorsRiding.add(actor.referenceID)
|
||||||
|
actor.platformsRiding.add(this.referenceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an actor from the rider list and apply dismount impulse.
|
||||||
|
*/
|
||||||
|
fun dismount(actor: ActorWithBody) {
|
||||||
|
actorsRiding.remove(actor.referenceID)
|
||||||
|
actor.platformsRiding.remove(this.referenceID)
|
||||||
|
|
||||||
|
// Conservation of momentum: add contraption velocity as impulse
|
||||||
|
actor.externalV.x += contraptionVelocity.x
|
||||||
|
actor.externalV.y += contraptionVelocity.y
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1480,16 +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
|
// Pass 1: update contraptions first so riders get displaced before their own update
|
||||||
actorContainerActive.forEach {
|
actorContainerActive.forEach {
|
||||||
if (it is ActorMovingPlatform && it != actorNowPlaying) {
|
if (it is PhysContraption && it != actorNowPlaying) {
|
||||||
it.update(delta)
|
it.update(delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: update all non-platform actors with existing callbacks
|
// Pass 2: update all non-contraption actors with existing callbacks
|
||||||
actorContainerActive.forEach {
|
actorContainerActive.forEach {
|
||||||
if (it !is ActorMovingPlatform && it != actorNowPlaying) {
|
if (it !is PhysContraption && it != actorNowPlaying) {
|
||||||
it.update(delta)
|
it.update(delta)
|
||||||
|
|
||||||
if (it is Pocketed) {
|
if (it is Pocketed) {
|
||||||
|
|||||||
@@ -51,24 +51,24 @@ class ActorTestPlatform : ActorMovingPlatform(8) {
|
|||||||
// Horizontal pingpong: position = A * sin(phase)
|
// Horizontal pingpong: position = A * sin(phase)
|
||||||
// Velocity = finite difference to prevent float drift
|
// Velocity = finite difference to prevent float drift
|
||||||
val dx = amplitude * (sin(phase) - sin(oldPhase))
|
val dx = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
platformVelocity.set(dx, 0.0)
|
contraptionVelocity.set(dx, 0.0)
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
// Vertical pingpong: position = A * sin(phase)
|
// Vertical pingpong: position = A * sin(phase)
|
||||||
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
platformVelocity.set(0.0, dy)
|
contraptionVelocity.set(0.0, dy)
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
// Clockwise circular: position on circle (cos, sin)
|
// Clockwise circular: position on circle (cos, sin)
|
||||||
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
||||||
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
val dy = amplitude * (sin(phase) - sin(oldPhase))
|
||||||
platformVelocity.set(dx, dy)
|
contraptionVelocity.set(dx, dy)
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
// Counter-clockwise circular: negate Y component
|
// Counter-clockwise circular: negate Y component
|
||||||
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
val dx = amplitude * (cos(phase) - cos(oldPhase))
|
||||||
val dy = -(amplitude * (sin(phase) - sin(oldPhase)))
|
val dy = -(amplitude * (sin(phase) - sin(oldPhase)))
|
||||||
platformVelocity.set(dx, dy)
|
contraptionVelocity.set(dx, dy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user