Stabilize battle unit hitboxes
This commit is contained in:
BIN
docs/battle-unit-hitbox-local-command-menu.png
Normal file
BIN
docs/battle-unit-hitbox-local-command-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/battle-unit-hitbox-local-selection.png
Normal file
BIN
docs/battle-unit-hitbox-local-selection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/battle-unit-hitbox-local-target-chosen.png
Normal file
BIN
docs/battle-unit-hitbox-local-target-chosen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/battle-unit-hitbox-prod-command-menu.png
Normal file
BIN
docs/battle-unit-hitbox-prod-command-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/battle-unit-hitbox-prod-selection.png
Normal file
BIN
docs/battle-unit-hitbox-prod-selection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/battle-unit-hitbox-prod-target-chosen.png
Normal file
BIN
docs/battle-unit-hitbox-prod-target-chosen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
40
docs/battle-unit-hitbox-report.md
Normal file
40
docs/battle-unit-hitbox-report.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Battle Unit Hitbox Verification
|
||||
|
||||
Date: 2026-07-03
|
||||
|
||||
## Change
|
||||
|
||||
- Unit sprites no longer receive pointer input from the full enlarged sprite rectangle.
|
||||
- Each unit now owns an invisible tile-sized hit zone centered on its tactical tile.
|
||||
- The hit zone follows unit movement, visibility, retreat state, and map visibility.
|
||||
- Attack/support target markers stay above unit hit zones, so target selection keeps priority during targeting.
|
||||
|
||||
## Local Verification
|
||||
|
||||
- Command: `pnpm build`
|
||||
- URL: `http://127.0.0.1:4193/heros_web/?debug&debugBattleSetup=attack-preview&v=unit-hitbox-local`
|
||||
- Adjacent unit selection: clicked Liu Bei, Guan Yu, and Zhang Fei by tile center; final selection was Zhang Fei.
|
||||
- Command menu: clicked Zhang Fei tile center again; Zhang Fei command menu opened.
|
||||
- Targeting: selected Guan Yu attack, clicked the adjacent Yellow Turban target, and opened combat prediction.
|
||||
- Console errors: 0
|
||||
|
||||
Screenshots:
|
||||
|
||||
- `docs/battle-unit-hitbox-local-selection.png`
|
||||
- `docs/battle-unit-hitbox-local-command-menu.png`
|
||||
- `docs/battle-unit-hitbox-local-target-chosen.png`
|
||||
|
||||
## Deployed Verification
|
||||
|
||||
- Command: `pnpm deploy:nas`
|
||||
- URL: `https://comtropy.synology.me/heros_web/?debug&debugBattleSetup=attack-preview&v=unit-hitbox-20260703`
|
||||
- Adjacent unit selection: clicked Liu Bei, Guan Yu, and Zhang Fei by tile center; final selection was Zhang Fei.
|
||||
- Command menu: clicked Zhang Fei tile center again; Zhang Fei command menu opened.
|
||||
- Targeting: selected Guan Yu attack, clicked the adjacent Yellow Turban target, and opened combat prediction.
|
||||
- Console errors: 0
|
||||
|
||||
Screenshots:
|
||||
|
||||
- `docs/battle-unit-hitbox-prod-selection.png`
|
||||
- `docs/battle-unit-hitbox-prod-command-menu.png`
|
||||
- `docs/battle-unit-hitbox-prod-target-chosen.png`
|
||||
@@ -1085,6 +1085,7 @@ type MiniMapLayout = {
|
||||
|
||||
type UnitView = {
|
||||
sprite: Phaser.GameObjects.Sprite;
|
||||
hitZone: Phaser.GameObjects.Zone;
|
||||
label: Phaser.GameObjects.Text;
|
||||
textureBase: string;
|
||||
direction: UnitDirection;
|
||||
@@ -3446,8 +3447,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
if (this.mapMask) {
|
||||
sprite.setMask(this.mapMask);
|
||||
}
|
||||
sprite.setInteractive({ useHandCursor: unit.faction === 'ally' });
|
||||
sprite.on('pointerdown', () => this.selectUnit(unit));
|
||||
|
||||
const hitZone = this.add.zone(centerX, centerY, this.layout.tileSize, this.layout.tileSize);
|
||||
hitZone.setName(`unit-hit-${unit.id}`);
|
||||
hitZone.setDepth(10.8);
|
||||
hitZone.setInteractive({ useHandCursor: true });
|
||||
hitZone.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.leftButtonDown()) {
|
||||
this.selectUnit(unit);
|
||||
}
|
||||
});
|
||||
|
||||
const label = this.add.text(sprite.x, sprite.y + this.layout.tileSize * 0.58, unit.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
@@ -3464,6 +3473,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
const view = {
|
||||
sprite,
|
||||
hitZone,
|
||||
label,
|
||||
textureBase,
|
||||
direction: 'south' as UnitDirection,
|
||||
@@ -3897,6 +3907,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
view.label.setPosition(view.baseX, view.baseY + this.layout.tileSize * 0.52);
|
||||
const visible = unit.hp > 0 && this.isTileVisible(unit.x, unit.y);
|
||||
view.sprite.setVisible(visible);
|
||||
this.setUnitHitZoneEnabled(view, visible);
|
||||
view.label.setVisible(visible);
|
||||
this.syncUnitMotion(unit, view);
|
||||
this.applyUnitLegibilityStyle(unit, view);
|
||||
@@ -3921,6 +3932,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
view.sprite.clearTint();
|
||||
view.sprite.setAlpha(1);
|
||||
view.sprite.setVisible(visible);
|
||||
this.setUnitHitZoneEnabled(view, visible);
|
||||
|
||||
view.label.setVisible(visible);
|
||||
view.label.setAlpha(1);
|
||||
@@ -9495,9 +9507,6 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.setUnitBasePosition(view, this.tileCenterX(unit.x), this.tileCenterY(unit.y));
|
||||
this.resetUnitSpritePose(view);
|
||||
view.label.setPosition(view.baseX, view.baseY + this.layout.tileSize * 0.52);
|
||||
if (unit.hp > 0) {
|
||||
view.sprite.setInteractive({ useHandCursor: true });
|
||||
}
|
||||
this.applyUnitLegibilityStyle(unit, view);
|
||||
}
|
||||
|
||||
@@ -11990,14 +11999,14 @@ export class BattleScene extends Phaser.Scene {
|
||||
const direction = this.directionFromDelta(x - fromX, y - fromY);
|
||||
const movementDistance = this.movementTileDistanceFrom(fromX, fromY, x, y);
|
||||
const movementDuration = this.movementDuration(duration, movementDistance);
|
||||
this.tweens.killTweensOf([view.sprite, view.label]);
|
||||
this.tweens.killTweensOf([view.sprite, view.hitZone, view.label]);
|
||||
this.setUnitBasePosition(view, startX, startY);
|
||||
this.resetUnitSpritePose(view);
|
||||
view.label.setPosition(startX, startY + this.layout.tileSize * 0.52);
|
||||
this.playMovementSound(unit, movementDuration, movementDistance);
|
||||
this.playUnitWalk(view, direction);
|
||||
this.tweens.add({
|
||||
targets: view.sprite,
|
||||
targets: [view.sprite, view.hitZone],
|
||||
x: targetX,
|
||||
y: targetY,
|
||||
duration: movementDuration,
|
||||
@@ -12616,14 +12625,14 @@ export class BattleScene extends Phaser.Scene {
|
||||
const direction = this.directionFromDelta(x - fromX, y - fromY);
|
||||
const movementDistance = this.movementTileDistanceFrom(fromX, fromY, x, y);
|
||||
const movementDuration = this.movementDuration(duration, movementDistance);
|
||||
this.tweens.killTweensOf([view.sprite, view.label]);
|
||||
this.tweens.killTweensOf([view.sprite, view.hitZone, view.label]);
|
||||
this.setUnitBasePosition(view, startX, startY);
|
||||
this.resetUnitSpritePose(view);
|
||||
view.label.setPosition(startX, startY + this.layout.tileSize * 0.52);
|
||||
this.playMovementSound(unit, movementDuration, movementDistance);
|
||||
this.playUnitWalk(view, direction);
|
||||
this.tweens.add({
|
||||
targets: view.sprite,
|
||||
targets: [view.sprite, view.hitZone],
|
||||
x: targetX,
|
||||
y: targetY,
|
||||
duration: movementDuration,
|
||||
@@ -12826,6 +12835,15 @@ export class BattleScene extends Phaser.Scene {
|
||||
view.baseX = x;
|
||||
view.baseY = y;
|
||||
view.sprite.setPosition(x, y);
|
||||
view.hitZone.setPosition(x, y);
|
||||
view.hitZone.setSize(this.layout.tileSize, this.layout.tileSize);
|
||||
}
|
||||
|
||||
private setUnitHitZoneEnabled(view: UnitView, enabled: boolean) {
|
||||
view.hitZone.setVisible(enabled);
|
||||
if (view.hitZone.input) {
|
||||
view.hitZone.input.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private resetUnitSpritePose(view: UnitView) {
|
||||
@@ -13101,6 +13119,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
view.sprite.clearTint();
|
||||
view.sprite.setAlpha(0);
|
||||
view.sprite.setVisible(false);
|
||||
this.setUnitHitZoneEnabled(view, false);
|
||||
view.label.setAlpha(0);
|
||||
view.label.setVisible(false);
|
||||
view.label.setColor('#b6b6b6');
|
||||
|
||||
Reference in New Issue
Block a user