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); }; }