mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-08-27 21:19:59 -04:00
4b2cd90320
Only show in developer changelog
487 lines
12 KiB
TypeScript
487 lines
12 KiB
TypeScript
namespace gdjs {
|
|
/*
|
|
GDevelop - Skeleton Object Extension
|
|
Copyright (c) 2017-2018 Franco Maciel (francomaciel10@gmail.com)
|
|
This project is released under the MIT License.
|
|
*/
|
|
|
|
/**
|
|
* The SkeletonRuntimeObject imports and displays skeletal animations files.
|
|
* @ignore
|
|
*/
|
|
export class SkeletonRuntimeObject extends gdjs.RuntimeObject {
|
|
rootArmature: any;
|
|
animationPlaying: boolean = true;
|
|
animationSmooth: boolean = true;
|
|
timeScale: float = 1.0;
|
|
scaleX: number = 1.0;
|
|
scaleY: number = 1.0;
|
|
hitboxSlot: any = null;
|
|
manager: any;
|
|
x: any;
|
|
y: any;
|
|
angle: float;
|
|
|
|
constructor(runtimeScene, objectData) {
|
|
super(runtimeScene, objectData);
|
|
this.rootArmature = new gdjs.sk.Armature(this);
|
|
this.rootArmature.getRenderer().putInScene(this, runtimeScene);
|
|
this.rootArmature.setAsRoot();
|
|
this.manager = gdjs.SkeletonObjectsManager.getManager(runtimeScene);
|
|
this.getSkeletonData(runtimeScene, objectData);
|
|
|
|
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
|
this.onCreated();
|
|
}
|
|
|
|
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
|
if (initialInstanceData.customSize) {
|
|
this.setWidth(initialInstanceData.width);
|
|
this.setHeight(initialInstanceData.height);
|
|
}
|
|
}
|
|
|
|
getSkeletonData(runtimeScene, objectData) {
|
|
const skeletonData = this.manager.getSkeleton(
|
|
runtimeScene,
|
|
this.name,
|
|
objectData
|
|
);
|
|
if (skeletonData.armatures.length > 0) {
|
|
this.rootArmature.loadData(
|
|
skeletonData.armatures[skeletonData.rootArmature],
|
|
skeletonData,
|
|
objectData.debugPolygons
|
|
);
|
|
this.rootArmature.resetState();
|
|
}
|
|
}
|
|
|
|
// RuntimeObject overwrites
|
|
setX(x): void {
|
|
this.x = x;
|
|
this.rootArmature.setX(x);
|
|
}
|
|
|
|
setY(y): void {
|
|
this.y = y;
|
|
this.rootArmature.setY(y);
|
|
}
|
|
|
|
setAngle(angle): void {
|
|
this.angle = angle;
|
|
this.rootArmature.setRot(angle);
|
|
}
|
|
|
|
getRendererObject() {
|
|
return this.rootArmature.getRendererObject();
|
|
}
|
|
|
|
getHitBoxes() {
|
|
if (this.hitboxSlot) {
|
|
return this.hitboxSlot.getPolygons();
|
|
}
|
|
return [this.rootArmature.getAABB()];
|
|
}
|
|
|
|
stepBehaviorsPreEvents(runtimeScene) {
|
|
const delta = this.getElapsedTime(runtimeScene) / 1000.0;
|
|
if (this.animationPlaying) {
|
|
this.rootArmature.updateAnimation(delta * this.timeScale);
|
|
}
|
|
super.stepBehaviorsPreEvents(runtimeScene);
|
|
}
|
|
|
|
update(runtimeScene): void {
|
|
this.rootArmature.update();
|
|
}
|
|
|
|
getDrawableX(): float {
|
|
return (
|
|
this.getX() -
|
|
this.rootArmature.shared.aabb[0][0] * Math.abs(this.scaleX)
|
|
);
|
|
}
|
|
|
|
getDrawableY(): float {
|
|
return (
|
|
this.getY() -
|
|
this.rootArmature.shared.aabb[0][1] * Math.abs(this.scaleY)
|
|
);
|
|
}
|
|
|
|
// Object instructions
|
|
getScaleX(): float {
|
|
return this.scaleX;
|
|
}
|
|
|
|
setScaleX(scaleX): void {
|
|
this.scaleX = scaleX;
|
|
this.rootArmature.setSx(scaleX);
|
|
}
|
|
|
|
getScaleY(): float {
|
|
return this.scaleY;
|
|
}
|
|
|
|
setScaleY(scaleY): void {
|
|
this.scaleY = scaleY;
|
|
this.rootArmature.setSy(scaleY);
|
|
}
|
|
|
|
getWidth(): float {
|
|
return this.rootArmature.getDefaultWidth() * Math.abs(this.scaleX);
|
|
}
|
|
|
|
setWidth(width): void {
|
|
if (width < 0) {
|
|
width = 0;
|
|
}
|
|
this.setScaleX(width / this.rootArmature.getDefaultWidth());
|
|
}
|
|
|
|
getHeight(): float {
|
|
return this.rootArmature.getDefaultHeight() * Math.abs(this.scaleY);
|
|
}
|
|
|
|
setHeight(height): void {
|
|
if (height < 0) {
|
|
height = 0;
|
|
}
|
|
this.setScaleY(height / this.rootArmature.getDefaultHeight());
|
|
}
|
|
|
|
setDefaultHitbox(slotPath): void {
|
|
if (slotPath === '') {
|
|
this.hitboxSlot = null;
|
|
return;
|
|
}
|
|
const slot = this.getSlot(slotPath);
|
|
if (slot) {
|
|
this.hitboxSlot = slot;
|
|
}
|
|
}
|
|
|
|
// Animation instructions
|
|
isAnimationPaused(): boolean {
|
|
return !this.animationPlaying;
|
|
}
|
|
|
|
setAnimationPaused(paused): void {
|
|
this.animationPlaying = !paused;
|
|
}
|
|
|
|
isAnimationFinished(): boolean {
|
|
return this.rootArmature.isAnimationFinished();
|
|
}
|
|
|
|
getAnimationTime(): float {
|
|
return this.rootArmature.getAnimationTime();
|
|
}
|
|
|
|
setAnimationTime(time): void {
|
|
this.rootArmature.setAnimationTime(time);
|
|
}
|
|
|
|
getAnimationTimeLength() {
|
|
return this.rootArmature.getAnimationTimeLength();
|
|
}
|
|
|
|
getAnimationFrame() {
|
|
return this.rootArmature.getAnimationFrame();
|
|
}
|
|
|
|
setAnimationFrame(frame): void {
|
|
this.rootArmature.setAnimationFrame(frame);
|
|
}
|
|
|
|
getAnimationFrameLength() {
|
|
return this.rootArmature.getAnimationFrameLength();
|
|
}
|
|
|
|
getAnimationIndex() {
|
|
return this.rootArmature.getAnimationIndex();
|
|
}
|
|
|
|
setAnimationIndex(newAnimation, blendTime = 0, loops = -1): void {
|
|
this.rootArmature.setAnimationIndex(newAnimation, blendTime, loops);
|
|
}
|
|
|
|
getAnimationName(): string {
|
|
return this.rootArmature.getAnimationName();
|
|
}
|
|
|
|
setAnimationName(newAnimation, blendTime = 0, loops = -1): void {
|
|
this.rootArmature.setAnimationName(newAnimation, blendTime, loops);
|
|
}
|
|
|
|
isAnimationSmooth(): boolean {
|
|
return this.animationSmooth;
|
|
}
|
|
|
|
setAnimationSmooth(smooth): void {
|
|
this.animationSmooth = smooth;
|
|
}
|
|
|
|
getAnimationTimeScale(): float {
|
|
return this.timeScale;
|
|
}
|
|
|
|
setAnimationTimeScale(timeScale): void {
|
|
if (timeScale < 0) {
|
|
timeScale = 0;
|
|
}
|
|
|
|
// Support negative timeScale (backward animation) ?
|
|
this.timeScale = timeScale;
|
|
}
|
|
|
|
resetAnimation(): void {
|
|
this.rootArmature.resetAnimation();
|
|
}
|
|
|
|
// Bone instructions
|
|
getBoneX(bonePath): float {
|
|
const bone = this.getBone(bonePath);
|
|
return bone ? bone.getGlobalX() : 0;
|
|
}
|
|
|
|
setBoneX(bonePath, x): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.setGlobalX(x);
|
|
bone.update();
|
|
}
|
|
}
|
|
|
|
getBoneY(bonePath): float {
|
|
const bone = this.getBone(bonePath);
|
|
return bone ? bone.getGlobalY() : 0;
|
|
}
|
|
|
|
setBoneY(bonePath, y): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.setGlobalY(y);
|
|
bone.update();
|
|
}
|
|
}
|
|
|
|
getBoneAngle(bonePath): float {
|
|
const bone = this.getBone(bonePath);
|
|
return bone ? bone.getGlobalRot() : 0;
|
|
}
|
|
|
|
setBoneAngle(bonePath, angle): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.setGlobalRot(angle);
|
|
bone.update();
|
|
}
|
|
}
|
|
|
|
getBoneScaleX(bonePath): float {
|
|
const bone = this.getBone(bonePath);
|
|
return bone ? bone.getSx() : 0;
|
|
}
|
|
|
|
setBoneScaleX(bonePath, scaleX): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.setSx(scaleX);
|
|
bone.update();
|
|
}
|
|
}
|
|
|
|
getBoneScaleY(bonePath): float {
|
|
const bone = this.getBone(bonePath);
|
|
return bone ? bone.getSy() : 0;
|
|
}
|
|
|
|
setBoneScaleY(bonePath, scaleY): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.setSy(scaleY);
|
|
bone.update();
|
|
}
|
|
}
|
|
|
|
resetBone(bonePath): void {
|
|
const bone = this.getBone(bonePath);
|
|
if (bone) {
|
|
bone.resetState();
|
|
}
|
|
}
|
|
|
|
// Slot instructions
|
|
setSlotColor(slotPath, color): void {
|
|
const slot = this.getSlot(slotPath);
|
|
if (slot) {
|
|
const rgb = color.split(';');
|
|
if (rgb.length < 3) {
|
|
return;
|
|
}
|
|
slot.setColor(...rgb);
|
|
}
|
|
}
|
|
|
|
isSlotVisible(slotPath): boolean {
|
|
const slot = this.getSlot(slotPath);
|
|
return slot ? slot.getVisible() : false;
|
|
}
|
|
|
|
setSlotVisible(slotPath, visible): void {
|
|
const slot = this.getSlot(slotPath);
|
|
if (slot) {
|
|
slot.setVisible(visible);
|
|
}
|
|
}
|
|
|
|
getSlotZOrder(slotPath): float {
|
|
const slot = this.getSlot(slotPath);
|
|
return slot ? slot.getZ() : 0;
|
|
}
|
|
|
|
setSlotZOrder(slotPath, z): void {
|
|
const slot = this.getSlot(slotPath);
|
|
if (slot) {
|
|
slot.setZ(z);
|
|
}
|
|
}
|
|
|
|
isPointInsideSlot(slotPath, x, y): boolean {
|
|
const hitBoxes = this.getPolygons(slotPath);
|
|
if (!hitBoxes) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < this.hitBoxes.length; ++i) {
|
|
if (gdjs.Polygon.isPointInside(hitBoxes[i], x, y)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Extension instructions
|
|
raycastSlot(slotPath, x, y, angle, dist, closest) {
|
|
let result = gdjs.Polygon.raycastTestStatics.result;
|
|
result.collision = false;
|
|
const endX = x + dist * Math.cos((angle * Math.PI) / 180.0);
|
|
const endY = y + dist * Math.sin((angle * Math.PI) / 180.0);
|
|
let testSqDist = closest ? dist * dist : 0;
|
|
const hitBoxes = this.getPolygons(slotPath);
|
|
if (!hitBoxes) {
|
|
return result;
|
|
}
|
|
for (let i = 0; i < hitBoxes.length; i++) {
|
|
const res = gdjs.Polygon.raycastTest(hitBoxes[i], x, y, endX, endY);
|
|
if (res.collision) {
|
|
if (closest && res.closeSqDist < testSqDist) {
|
|
testSqDist = res.closeSqDist;
|
|
result = res;
|
|
} else {
|
|
if (
|
|
!closest &&
|
|
res.farSqDist > testSqDist &&
|
|
res.farSqDist <= dist * dist
|
|
) {
|
|
testSqDist = res.farSqDist;
|
|
result = res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Warning!, assuming gdjs.evtTools.object.twoListsTest calls the predicate
|
|
// respecting the given objects lists paramenters order
|
|
static slotObjectCollisionTest(skl, obj, slotPath) {
|
|
const hitBoxes1 = skl.getPolygons(slotPath);
|
|
if (!hitBoxes1) {
|
|
return false;
|
|
}
|
|
const hitBoxes2 = obj.getHitBoxes();
|
|
for (let k = 0, lenBoxes1 = hitBoxes1.length; k < lenBoxes1; ++k) {
|
|
for (let l = 0, lenBoxes2 = hitBoxes2.length; l < lenBoxes2; ++l) {
|
|
if (
|
|
gdjs.Polygon.collisionTest(hitBoxes1[k], hitBoxes2[l], false)
|
|
.collision
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static slotSlotCollisionTest(skl1, skl2, slotPaths) {
|
|
const hitBoxes1 = skl1.getPolygons(slotPaths[0]);
|
|
const hitBoxes2 = skl2.getPolygons(slotPaths[1]);
|
|
if (!hitBoxes1 || !hitBoxes2) {
|
|
return false;
|
|
}
|
|
for (let k = 0, lenBoxes1 = hitBoxes1.length; k < lenBoxes1; ++k) {
|
|
for (let l = 0, lenBoxes2 = hitBoxes2.length; l < lenBoxes2; ++l) {
|
|
if (
|
|
gdjs.Polygon.collisionTest(hitBoxes1[k], hitBoxes2[l], false)
|
|
.collision
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Helpers
|
|
getSlot(slotPath) {
|
|
const slotArray = slotPath.split('/');
|
|
let currentSlot = null;
|
|
if (slotArray[0] in this.rootArmature.slotsMap) {
|
|
currentSlot = this.rootArmature.slotsMap[slotArray[0]];
|
|
for (let i = 1; i < slotArray.length; i++) {
|
|
if (
|
|
currentSlot.type === gdjs.sk.SLOT_ARMATURE &&
|
|
slotArray[i] in currentSlot.childArmature.slotsMap
|
|
) {
|
|
currentSlot = currentSlot.childArmature.slotsMap[slotArray[i]];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
return currentSlot;
|
|
}
|
|
|
|
getBone(bonePath) {
|
|
const slotArray = bonePath.split('/');
|
|
const boneName = slotArray.pop();
|
|
if (slotArray.length === 0 && boneName in this.rootArmature.bonesMap) {
|
|
return this.rootArmature.bonesMap[boneName];
|
|
}
|
|
const slot = this.getSlot(slotArray.join('/'));
|
|
if (
|
|
slot &&
|
|
slot.type === gdjs.sk.SLOT_ARMATURE &&
|
|
boneName in slot.childArmature.bonesMap
|
|
) {
|
|
return slot.childArmature.bonesMap[boneName];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getPolygons(slotPath) {
|
|
if (slotPath === '') {
|
|
return this.rootArmature.getHitBoxes();
|
|
}
|
|
const slot = this.getSlot(slotPath);
|
|
if (slot) {
|
|
return slot.getPolygons();
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
gdjs.registerObject('SkeletonObject::Skeleton', gdjs.SkeletonRuntimeObject);
|
|
}
|