174 lines
4.9 KiB
JavaScript
174 lines
4.9 KiB
JavaScript
import Phaser from "phaser";
|
|
import {
|
|
FIGHTER_FRAME_HEIGHT,
|
|
FIGHTER_FRAME_WIDTH,
|
|
FIGHTER_DEPTH,
|
|
FIGHTER_HITBOX_HEIGHT,
|
|
FIGHTER_HITBOX_OFFSET_X,
|
|
FIGHTER_HITBOX_OFFSET_Y,
|
|
FIGHTER_HITBOX_WIDTH,
|
|
FIGHTER_MAX_HP,
|
|
FIGHTER_SCALE,
|
|
} from "../constants.js";
|
|
import {
|
|
fighterAnimationKey,
|
|
fighterOutlineSheetKeyFromSheetKey,
|
|
fighterSheetKey,
|
|
} from "./fighterAssets.js";
|
|
|
|
const NAME_LABEL_BOTTOM_GAP = 14;
|
|
|
|
export function createFighter(
|
|
scene,
|
|
{ canSplitOnDeath = true, faceLeft, hp, maxHp, name, skin, team, teamIndex, x, y },
|
|
) {
|
|
const fighter = scene.physics.add.sprite(x, y, fighterSheetKey(skin, "idle"), 0);
|
|
const teamColor = Phaser.Display.Color.HexStringToColor(team.color).color;
|
|
const displayName = name || team.label;
|
|
const resolvedMaxHp = Math.max(1, Math.round(maxHp ?? skin.stats?.maxHp ?? FIGHTER_MAX_HP));
|
|
const resolvedHp = Math.min(
|
|
resolvedMaxHp,
|
|
Math.max(1, Math.round(hp ?? resolvedMaxHp)),
|
|
);
|
|
|
|
fighter.setScale(FIGHTER_SCALE);
|
|
fighter.setName(displayName);
|
|
fighter.setDepth(FIGHTER_DEPTH);
|
|
fighter.setAlpha(1);
|
|
fighter.setCollideWorldBounds(true);
|
|
fighter.setFlipX(faceLeft);
|
|
fighter.body.setSize(FIGHTER_HITBOX_WIDTH, FIGHTER_HITBOX_HEIGHT);
|
|
fighter.body.setOffset(FIGHTER_HITBOX_OFFSET_X, FIGHTER_HITBOX_OFFSET_Y);
|
|
fighter.setInteractive(
|
|
new Phaser.Geom.Rectangle(
|
|
FIGHTER_HITBOX_OFFSET_X,
|
|
FIGHTER_HITBOX_OFFSET_Y,
|
|
FIGHTER_HITBOX_WIDTH,
|
|
FIGHTER_HITBOX_HEIGHT,
|
|
),
|
|
Phaser.Geom.Rectangle.Contains,
|
|
);
|
|
fighter.input.cursor = "pointer";
|
|
|
|
fighter.teamMarker = scene.add
|
|
.sprite(x, y, fighterOutlineSheetKeyFromSheetKey(fighterSheetKey(skin, "idle")), 0)
|
|
.setDisplaySize(FIGHTER_FRAME_WIDTH * FIGHTER_SCALE, FIGHTER_FRAME_HEIGHT * FIGHTER_SCALE)
|
|
.setTint(teamColor)
|
|
.setAlpha(0.8)
|
|
.setDepth(1.9)
|
|
.setVisible(true);
|
|
|
|
fighter.nameLabel = scene.add
|
|
.text(x, y, displayName, {
|
|
color: "#fff2c2",
|
|
fontFamily: "Inter, Pretendard, sans-serif",
|
|
fontSize: "18px",
|
|
fontStyle: "700",
|
|
stroke: team.color,
|
|
strokeThickness: 4,
|
|
})
|
|
.setOrigin(0.5, 0)
|
|
.setDepth(4);
|
|
fighter.healthBack = scene.add
|
|
.rectangle(x, y - 44, 72, 8, 0x17180e, 0.92)
|
|
.setDepth(4);
|
|
fighter.healthBar = scene.add
|
|
.rectangle(x - 34, y - 44, 68, 4, 0xd95f3f, 1)
|
|
.setOrigin(0, 0.5)
|
|
.setDepth(5);
|
|
|
|
fighter.skin = skin;
|
|
fighter.fighterName = displayName;
|
|
fighter.team = team;
|
|
fighter.teamIndex = teamIndex;
|
|
fighter.baseScaleX = FIGHTER_SCALE;
|
|
fighter.baseScaleY = FIGHTER_SCALE;
|
|
fighter.canSplitOnDeath = canSplitOnDeath;
|
|
fighter.isSelected = false;
|
|
fighter.killCount = 0;
|
|
fighter.killRewardMultiplier = 1;
|
|
fighter.maxHp = resolvedMaxHp;
|
|
fighter.hp = resolvedHp;
|
|
fighter.nextAttackAt = 0;
|
|
fighter.isLocked = false;
|
|
fighter.isDead = false;
|
|
fighter.play(fighterAnimationKey(skin, "walk"));
|
|
|
|
fighter.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (animation) => {
|
|
if (fighter.isDead) {
|
|
return;
|
|
}
|
|
|
|
if (animation.key.includes("-attack") || animation.key.endsWith("-hurt-anim")) {
|
|
fighter.isLocked = false;
|
|
}
|
|
});
|
|
|
|
attachHudCleanup(fighter);
|
|
syncFighterHud(fighter);
|
|
|
|
return fighter;
|
|
}
|
|
|
|
export function syncFighterHud(fighter) {
|
|
const isVisible = Boolean(fighter.active && !fighter.isDead);
|
|
|
|
fighter.nameLabel.setVisible(isVisible);
|
|
fighter.healthBack.setVisible(isVisible);
|
|
fighter.healthBar.setVisible(isVisible);
|
|
syncTeamMarker(fighter);
|
|
|
|
if (!isVisible || !fighter.body) {
|
|
return;
|
|
}
|
|
|
|
const scaleRatio = Math.max(1, Math.abs(fighter.scaleY) / FIGHTER_SCALE);
|
|
const healthOffset = 44 * scaleRatio;
|
|
const hitbox = fighter.body;
|
|
const nameX = hitbox.x + hitbox.width / 2;
|
|
const nameY = hitbox.y + hitbox.height + NAME_LABEL_BOTTOM_GAP;
|
|
|
|
fighter.nameLabel.setPosition(nameX, nameY);
|
|
fighter.healthBack.setPosition(fighter.x, fighter.y - healthOffset);
|
|
fighter.healthBar.setPosition(fighter.x - 34, fighter.y - healthOffset);
|
|
fighter.healthBar.width = Math.max(0, 68 * (fighter.hp / (fighter.maxHp ?? FIGHTER_MAX_HP)));
|
|
}
|
|
|
|
function syncTeamMarker(fighter) {
|
|
const marker = fighter.teamMarker;
|
|
|
|
if (!marker) {
|
|
return;
|
|
}
|
|
|
|
const isVisible = Boolean(fighter.active && !fighter.isDead);
|
|
marker.setVisible(isVisible);
|
|
|
|
if (!isVisible) {
|
|
return;
|
|
}
|
|
|
|
const outlineTextureKey = fighterOutlineSheetKeyFromSheetKey(fighter.texture.key);
|
|
|
|
if (fighter.scene.textures.exists(outlineTextureKey)) {
|
|
marker.setTexture(outlineTextureKey, fighter.frame.name);
|
|
}
|
|
|
|
marker.setPosition(fighter.x, fighter.y);
|
|
marker.setScale(fighter.scaleX, fighter.scaleY);
|
|
marker.setFlipX(fighter.flipX);
|
|
marker.setDepth(fighter.depth - 0.1);
|
|
}
|
|
|
|
function attachHudCleanup(fighter) {
|
|
const originalDestroy = fighter.destroy.bind(fighter);
|
|
|
|
fighter.destroy = (...args) => {
|
|
fighter.teamMarker.destroy();
|
|
fighter.nameLabel.destroy();
|
|
fighter.healthBack.destroy();
|
|
fighter.healthBar.destroy();
|
|
originalDestroy(...args);
|
|
};
|
|
}
|