I've made a neat ct.js type for making animated legs for top-down games, though it surely fits other viewports as well, depending on its settings. It is a "leg" with just one joint, but it is enough to make neat animations for bipedal, quadrupedal, or simply monstrous creatures:
The idea breaks down into several points:
- A leg consists of a foot and a long leg body. The body is a sprite appended to the foot.
- An owning copy creates one or more of these legs and sets its options: position, animation speed, sensitivity.
- Each leg tracks the position of the owning copy and repositions itself with
ct.tween
when a set threshold is reached.
- The target position is calculated based on owner's size, rotation, and its speed. When a copy moves fast, it is logical to put legs in front of it to imitate proper leg motion: we step in front of us to propel us further.
- The foot stays in place until it needs to be relocated, and the body of the leg rotates and stretches to the owning copy to be snapped in a proper spot.
And here is the code of the leg:
On Create
this.maxDistance = this.maxDistance || 120;
this.shiftX = this.shiftX || 0;
this.shiftY = this.shiftY || 0;
this.snapTargetX = this.snapTargetX || 0;
this.snapTargetY = this.snapTargetY || 0;
this.snapLength = ct.u.pdc(0, 0, this.snapTargetX, this.snapTargetY);
this.snapDirection = ct.u.pdn(0, 0, this.snapTargetX, this.snapTargetY);
this.speedAffector = this.speedAffector || 1;
this.animationDuration = this.animationDuration || 150;
var legBody = new PIXI.Sprite(ct.res.getTexture(this.legTexture || 'SpiderLeg_Body', 0));
legBody.anchor.x = 0;
legBody.anchor.y = 0.5;
this.addChild(legBody);
this.legBody = legBody;
if (this.footTexture) {
this.tex = this.footTexture;
}
if (this.legFlip) {
this.legBody.scale.y = -1;
}
this.legBody.scale.y *= this.legScale || 1;
On Step
if (!this.owner || this.owner.kill) {
this.kill = true;
return;
}
let l = ct.u.pdc(0, 0, this.shiftX, this.shiftY) * this.owner.scale.x,
d = ct.u.pdn(0, 0, this.shiftX, this.shiftY) + this.owner.rotation;
let dx = ct.u.ldx(this.snapLength, this.snapDirection + this.owner.rotation) * this.owner.scale.x,
dy = ct.u.ldy(this.snapLength, this.snapDirection + this.owner.rotation) * this.owner.scale.x;
var targetX = ct.u.ldx(l, d) + this.owner.x + dx,
targetY = ct.u.ldy(l, d) + this.owner.y + dy;
var dist = ct.u.pdc(
this.x,
this.y,
targetX,
targetY
);
if (dist > this.maxDistance && !this.transitioning) {
this.transitioning = true;
ct.tween.add({
obj: this,
duration: this.animationDuration,
fields: {
x: targetX + ct.u.ldx(this.owner.speed * this.speedAffector, this.owner.direction),
y: targetY + ct.u.ldy(this.owner.speed * this.speedAffector, this.owner.direction),
rotation: this.feetLookAway ? d : this.owner.rotation
}
}).then(() => {
this.transitioning = false;
})
}
On Draw
let dx = ct.u.ldx(this.snapLength, this.snapDirection + this.owner.rotation) * this.owner.scale.x,
dy = ct.u.ldy(this.snapLength, this.snapDirection + this.owner.rotation) * this.owner.scale.x;
this.legBody.angle = -ct.u.pdn(this.x, this.y, this.owner.x + dx, this.owner.y + dy) + this.rotation;
this.legBody.scale.x = ct.u.pdc(
this.x,
this.y,
this.owner.x + dx,
this.owner.y + dy
) / 128;
Here 'SpiderLeg_Body'
is the default name for a leg's texture.
Now, how do you use it? You will create several of these copies for each type that needs the legs. For example, here is the fragment of the OnCreate event that creates playable character's legs:
this.leftLeg = ct.types.copy('Leg', this.x, this.y - 90, {
snapTargetX: 0,
snapTargetY: -60,
shiftX: 0,
shiftY: -10,
maxDistance: 120,
owner: this,
speedAffector: 15,
legTexture: 'HeroLeg',
footTexture: 'HeroFoot',
legFlip: true,
animationDuration: 200
});
this.rightLeg = ct.types.copy('Leg', this.x, this.y - 90, {
snapTargetX: 0,
snapTargetY: 60,
shiftX: 0,
shiftY: 10,
maxDistance: 120,
owner: this,
speedAffector: 15,
legTexture: 'HeroLeg',
footTexture: 'HeroFoot',
animationDuration: 200
});
And here are all the options explained:
owner
— the most important one. It is the copy to which the legs are attached. If it is destroyed, the legs destroy as well.
maxDistance
— the radius after which the leg should relocate. Can be seen as a step size.
shiftX
, shiftY
— the target position of the foot's resting spot, relative to the owner of the leg.
snapTargetX
, snapTargetY
— the starting point to which the leg snaps and from which the distance is measured.
speedAffector
— the larger this value, the more owner's speed affects how feet are positioned. Copies will try to position their feet in front of them based on their speed and this value.
animationDuration
— the duration (in milliseconds) at which a foot relocates.
footTexture
, legTexture
— ct.js' textures' names. It is expected that the second texture is 128px wide, but you can tweak it.
legScale
— the thickness of the leg.
legFlip
— whether the leg's texture should be flipped across its direction.
feetLookAway
— if set to true
, the feet will be positioned so that they point away from the body. Otherwise, the feet will be rotated to follow the owner's rotation.
Here're the textures I used for the hero: the hero itself, its leg (HeroLeg
) and a foot (HeroFoot
)
And I think that's it. Feel free to ask any questions here.
Happy coding!
Comigo