arena/src/game/fighterFactory.js

145 lines
4.1 KiB
JavaScript

import Phaser from "phaser";
import {
FIGHTER_FRAME_HEIGHT,
FIGHTER_FRAME_WIDTH,
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, { faceLeft, name, skin, team, teamIndex, x, y }) {
const fighter = scene.physics.add.sprite(x, y, fighterSheetKey(skin, "idle"), 0);
fighter.setScale(FIGHTER_SCALE);
fighter.setDepth(2);
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.selectionOutline = scene.add
.sprite(x, y, fighterOutlineSheetKeyFromSheetKey(fighterSheetKey(skin, "idle")), 0)
.setDisplaySize(FIGHTER_FRAME_WIDTH * FIGHTER_SCALE, FIGHTER_FRAME_HEIGHT * FIGHTER_SCALE)
.setDepth(1.9)
.setVisible(false);
fighter.nameLabel = scene.add
.text(x, y, name, {
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.team = team;
fighter.teamIndex = teamIndex;
fighter.baseScaleX = FIGHTER_SCALE;
fighter.baseScaleY = FIGHTER_SCALE;
fighter.isSelected = false;
fighter.killCount = 0;
fighter.killRewardMultiplier = 1;
fighter.maxHp = FIGHTER_MAX_HP;
fighter.hp = fighter.maxHp;
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 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)));
syncSelectionOutline(fighter);
}
function syncSelectionOutline(fighter) {
const outline = fighter.selectionOutline;
if (!outline) {
return;
}
const isVisible = Boolean(fighter.isSelected && !fighter.isDead);
outline.setVisible(isVisible);
if (!isVisible) {
return;
}
const outlineTextureKey = fighterOutlineSheetKeyFromSheetKey(fighter.texture.key);
if (fighter.scene.textures.exists(outlineTextureKey)) {
outline.setTexture(outlineTextureKey, fighter.frame.name);
}
outline.setPosition(fighter.x, fighter.y);
outline.setScale(fighter.scaleX, fighter.scaleY);
outline.setFlipX(fighter.flipX);
outline.setDepth(fighter.depth - 0.1);
}
function attachHudCleanup(fighter) {
const originalDestroy = fighter.destroy.bind(fighter);
fighter.destroy = (...args) => {
fighter.selectionOutline.destroy();
fighter.nameLabel.destroy();
fighter.healthBack.destroy();
fighter.healthBar.destroy();
originalDestroy(...args);
};
}