Actor.actions.moveTo overshooting with when speed is high? #2944
-
Hi, I'm just playing around with Excalibur.js. I have to say it's been amazing so far. The docs are great and the APIs are very intuitive. One thing I've noticed, is when I use I'm running the following code in a
CleanShot.2024-02-29.at.23.23.42.mp4If I go frame-by-frame, you can see how the sprite is too far along for one frame: CleanShot.2024-02-29.at.23.26.38.mp4How can I prevent this from happening? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Thanks for the kind words! 🥰 I'm pretty sure I know why this is happening. The actions system sets the velocity of the actor to move it forward, but the simulation is discrete so each frame the actor will move by velocity pixels/second, the action system then checks if the actor has traversed far enough then snaps to the target destination causing the overshoot. The reason Excalibur uses the motion simulation and not exact positioning is so that it plays nice with the impulse based collision system (but perhaps we should make that configurable or re-evaluate that decision?). Here are the guts of the moveTo implementation public update(_delta: number): void {
if (!this._started) {
this._started = true;
this._start = new Vector(this._tx.pos.x, this._tx.pos.y);
this._distance = this._start.distance(this._end);
this._dir = this._end.sub(this._start).normalize();
}
const m = this._dir.scale(this._speed);
this._motion.vel = vec(m.x, m.y);
if (this.isComplete(this.entity)) {
this._tx.pos = vec(this._end.x, this._end.y);
this._motion.vel = vec(0, 0);
}
} One way to work around this overshoot is to set the exact position of the actor using an easing function and not rely on motion simulation (this is can be totally fine, especially if collisions aren't a concern. And depending on what you need out collisions those might be totally find as well). Example with a coroutine (I'd recommend this for simplicity) import { Actor, Color, EasingFunctions, Engine, Vector, clamp, coroutine, vec } from "excalibur";
import { Resources } from "./resources";
export class Player extends Actor {
constructor() {
super({
pos: vec(150, 150),
width: 100,
height: 100
});
}
private _engine: Engine;
onInitialize(engine: Engine) {
this._engine = engine;
this.graphics.add(Resources.Sword.toSprite());
}
moveTo(destination: Vector, duration: number): Promise<void> {
const actor = this;
return coroutine(this._engine, function * () {
let currentTime = 0;
let start = actor.pos.clone();
while(currentTime < duration) {
const elapsed = yield;
currentTime += elapsed;
actor.pos.x = EasingFunctions.Linear(currentTime, start.x, destination.x, duration);
actor.pos.y = EasingFunctions.Linear(currentTime, start.y, destination.y, duration);
}
});
}
} Example without a coroutine import { Actor, Color, EasingFunctions, Engine, clamp, vec } from "excalibur";
import { Resources } from "./resources";
export class Player extends Actor {
constructor() {
super({
pos: vec(150, 150),
width: 100,
height: 100
});
}
onInitialize() {
this.graphics.add(Resources.Sword.toSprite());
}
start = this.pos.clone();
end = vec(500, 500);
duration = 2 * 1000; // 2 seconds
currentTime = 0;
override onPostUpdate(engine: Engine, elapsed: number) {
this.currentTime = clamp(this.currentTime + elapsed, 0, this.duration);
this.pos.x = EasingFunctions.Linear(this.currentTime, this.start.x, this.end.x, this.duration);
this.pos.y = EasingFunctions.Linear(this.currentTime, this.start.y, this.end.y, this.duration);
}
} This way you can move REALLY FAST no overshoot fastmove.mp4 |
Beta Was this translation helpful? Give feedback.
Hi @maximilianschmitt
Thanks for the kind words! 🥰
I'm pretty sure I know why this is happening. The actions system sets the velocity of the actor to move it forward, but the simulation is discrete so each frame the actor will move by velocity pixels/second, the action system then checks if the actor has traversed far enough then snaps to the target destination causing the overshoot. The reason Excalibur uses the motion simulation and not exact positioning is so that it plays nice with the impulse based collision system (but perhaps we should make that configurable or re-evaluate that decision?). Here are the guts of the moveTo implementation