changed visual movement speed to be slower and made diagonal movement with arrow keys work
This commit is contained in:
@@ -130,11 +130,12 @@ export const GAME_CONFIG = {
|
||||
horizontal: 54,
|
||||
vertical: 55,
|
||||
turning: 56
|
||||
}
|
||||
},
|
||||
moveDuration: 62 // Visual duration for movement in ms
|
||||
},
|
||||
|
||||
ui: {
|
||||
// ... rest of content ...
|
||||
// ... rest of content ...
|
||||
minimapPanelWidth: 340,
|
||||
minimapPanelHeight: 220,
|
||||
minimapPadding: 20,
|
||||
|
||||
@@ -80,12 +80,12 @@ export class GameInput extends Phaser.Events.EventEmitter {
|
||||
const isShiftDrag = p.isDown && p.event.shiftKey;
|
||||
|
||||
if (isRightDrag || isMiddleDrag || isShiftDrag) {
|
||||
const { x, y } = p.position;
|
||||
const { x: prevX, y: prevY } = p.prevPosition;
|
||||
const { x, y } = p.position;
|
||||
const { x: prevX, y: prevY } = p.prevPosition;
|
||||
|
||||
const dx = (x - prevX); // Zoom factor needs to be handled by receiver or passed here
|
||||
const dy = (y - prevY);
|
||||
this.emit("pan", dx, dy);
|
||||
const dx = (x - prevX); // Zoom factor needs to be handled by receiver or passed here
|
||||
const dy = (y - prevY);
|
||||
this.emit("pan", dx, dy);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -107,11 +107,17 @@ export class GameInput extends Phaser.Events.EventEmitter {
|
||||
if (down) dy += 1;
|
||||
|
||||
const anyJustDown = Phaser.Input.Keyboard.JustDown(this.cursors.left!) ||
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.right!) ||
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.up!) ||
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.down!);
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.right!) ||
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.up!) ||
|
||||
Phaser.Input.Keyboard.JustDown(this.cursors.down!);
|
||||
|
||||
return { dx, dy, anyJustDown };
|
||||
return {
|
||||
dx, dy, anyJustDown,
|
||||
isLeft: !!left,
|
||||
isRight: !!right,
|
||||
isUp: !!up,
|
||||
isDown: !!down
|
||||
};
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
|
||||
@@ -49,8 +49,8 @@ export function findPathAStar(
|
||||
return true;
|
||||
};
|
||||
|
||||
// Use rot-js A* pathfinding with 4-directional topology
|
||||
const astar = new ROT.Path.AStar(end.x, end.y, passableCallback, { topology: 4 });
|
||||
// Use rot-js A* pathfinding with 8-directional topology
|
||||
const astar = new ROT.Path.AStar(end.x, end.y, passableCallback, { topology: 8 });
|
||||
|
||||
const path: Vec2[] = [];
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ export class DungeonRenderer {
|
||||
computeFov() {
|
||||
const player = this.entityAccessor.getPlayer();
|
||||
if (player && player.category === "combatant") {
|
||||
this.fovManager.compute(this.world, player.pos);
|
||||
this.fovManager.compute(this.world, player.pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,9 +152,9 @@ export class DungeonRenderer {
|
||||
}
|
||||
|
||||
updateTile(x: number, y: number) {
|
||||
if (!this.map || !this.world) return;
|
||||
const t = this.world.tiles[idx(this.world, x, y)];
|
||||
this.map.putTileAt(t, x, y);
|
||||
if (!this.map || !this.world) return;
|
||||
const t = this.world.tiles[idx(this.world, x, y)];
|
||||
this.map.putTileAt(t, x, y);
|
||||
}
|
||||
|
||||
get seenArray() {
|
||||
@@ -243,14 +243,14 @@ export class DungeonRenderer {
|
||||
const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
if (this.playerSprite.x !== tx || this.playerSprite.y !== ty) {
|
||||
this.scene.tweens.add({
|
||||
targets: this.playerSprite,
|
||||
x: tx,
|
||||
y: ty,
|
||||
duration: 120,
|
||||
ease: 'Quad.easeOut',
|
||||
overwrite: true
|
||||
});
|
||||
this.scene.tweens.add({
|
||||
targets: this.playerSprite,
|
||||
x: tx,
|
||||
y: ty,
|
||||
duration: GAME_CONFIG.rendering.moveDuration,
|
||||
ease: 'Quad.easeOut',
|
||||
overwrite: true
|
||||
});
|
||||
}
|
||||
this.playerSprite.setVisible(true);
|
||||
}
|
||||
@@ -274,22 +274,22 @@ export class DungeonRenderer {
|
||||
this.enemySprites.set(a.id, sprite);
|
||||
sprite.setVisible(true);
|
||||
} else {
|
||||
if (!sprite.visible) {
|
||||
// If it was hidden, snap to new position immediately
|
||||
this.scene.tweens.killTweensOf(sprite);
|
||||
sprite.setPosition(tx, ty);
|
||||
sprite.setVisible(true);
|
||||
} else if (sprite.x !== tx || sprite.y !== ty) {
|
||||
// Only tween if it was already visible and moved
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
x: tx,
|
||||
y: ty,
|
||||
duration: 120,
|
||||
ease: 'Quad.easeOut',
|
||||
overwrite: true
|
||||
});
|
||||
}
|
||||
if (!sprite.visible) {
|
||||
// If it was hidden, snap to new position immediately
|
||||
this.scene.tweens.killTweensOf(sprite);
|
||||
sprite.setPosition(tx, ty);
|
||||
sprite.setVisible(true);
|
||||
} else if (sprite.x !== tx || sprite.y !== ty) {
|
||||
// Only tween if it was already visible and moved
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
x: tx,
|
||||
y: ty,
|
||||
duration: GAME_CONFIG.rendering.moveDuration,
|
||||
ease: 'Quad.easeOut',
|
||||
overwrite: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else if (a.category === "collectible") {
|
||||
@@ -308,23 +308,23 @@ export class DungeonRenderer {
|
||||
orb.setVisible(true);
|
||||
}
|
||||
} else if (a.category === "item_drop") {
|
||||
if (!isVis) continue;
|
||||
if (!isVis) continue;
|
||||
|
||||
activeItemIds.add(a.id);
|
||||
let itemContainer = this.itemSprites.get(a.id);
|
||||
if (!itemContainer) {
|
||||
// Use ItemSpriteFactory to create sprite with optional glow
|
||||
itemContainer = ItemSpriteFactory.createItemSprite(this.scene, a.item, 0, 0, 1);
|
||||
itemContainer.setDepth(40);
|
||||
this.itemSprites.set(a.id, itemContainer);
|
||||
}
|
||||
const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
itemContainer.setPosition(tx, ty);
|
||||
itemContainer.setVisible(true);
|
||||
activeItemIds.add(a.id);
|
||||
let itemContainer = this.itemSprites.get(a.id);
|
||||
if (!itemContainer) {
|
||||
// Use ItemSpriteFactory to create sprite with optional glow
|
||||
itemContainer = ItemSpriteFactory.createItemSprite(this.scene, a.item, 0, 0, 1);
|
||||
itemContainer.setDepth(40);
|
||||
this.itemSprites.set(a.id, itemContainer);
|
||||
}
|
||||
const tx = a.pos.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const ty = a.pos.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
itemContainer.setPosition(tx, ty);
|
||||
itemContainer.setVisible(true);
|
||||
|
||||
// bobbing effect on the container
|
||||
itemContainer.y += Math.sin(this.scene.time.now / 300) * 2;
|
||||
// bobbing effect on the container
|
||||
itemContainer.y += Math.sin(this.scene.time.now / 300) * 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,13 +350,13 @@ export class DungeonRenderer {
|
||||
}
|
||||
|
||||
for (const [id, item] of this.itemSprites.entries()) {
|
||||
if (!activeItemIds.has(id)) {
|
||||
item.setVisible(false);
|
||||
if (!this.entityAccessor.hasActor(id)) {
|
||||
item.destroy();
|
||||
this.itemSprites.delete(id);
|
||||
}
|
||||
if (!activeItemIds.has(id)) {
|
||||
item.setVisible(false);
|
||||
if (!this.entityAccessor.hasActor(id)) {
|
||||
item.destroy();
|
||||
this.itemSprites.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.minimapRenderer.render(this.world, seen, visible, this.entityAccessor);
|
||||
@@ -405,44 +405,44 @@ export class DungeonRenderer {
|
||||
}
|
||||
|
||||
showProjectile(from: Vec2, to: Vec2, itemId: string, onComplete: () => void) {
|
||||
// World coords
|
||||
const startX = from.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const startY = from.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endX = to.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endY = to.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
// World coords
|
||||
const startX = from.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const startY = from.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endX = to.x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const endY = to.y * TILE_SIZE + TILE_SIZE / 2;
|
||||
|
||||
// Create sprite
|
||||
// Look up sprite index from config
|
||||
const itemConfig = ALL_TEMPLATES[itemId as keyof typeof ALL_TEMPLATES];
|
||||
const texture = itemConfig?.textureKey ?? "items";
|
||||
const frame = itemConfig?.spriteIndex ?? 0;
|
||||
// Create sprite
|
||||
// Look up sprite index from config
|
||||
const itemConfig = ALL_TEMPLATES[itemId as keyof typeof ALL_TEMPLATES];
|
||||
const texture = itemConfig?.textureKey ?? "items";
|
||||
const frame = itemConfig?.spriteIndex ?? 0;
|
||||
|
||||
// Use 'items' spritesheet
|
||||
const sprite = this.scene.add.sprite(startX, startY, texture, frame);
|
||||
sprite.setDepth(2000);
|
||||
// Use 'items' spritesheet
|
||||
const sprite = this.scene.add.sprite(startX, startY, texture, frame);
|
||||
sprite.setDepth(2000);
|
||||
|
||||
// Rotate?
|
||||
const angle = Phaser.Math.Angle.Between(startX, startY, endX, endY);
|
||||
sprite.setRotation(angle + Math.PI / 4); // Adjust for sprite orientation (diagonal usually)
|
||||
// Rotate?
|
||||
const angle = Phaser.Math.Angle.Between(startX, startY, endX, endY);
|
||||
sprite.setRotation(angle + Math.PI / 4); // Adjust for sprite orientation (diagonal usually)
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(startX, startY, endX, endY);
|
||||
const duration = dist * 2; // speed
|
||||
const dist = Phaser.Math.Distance.Between(startX, startY, endX, endY);
|
||||
const duration = dist * 2; // speed
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
x: endX,
|
||||
y: endY,
|
||||
rotation: sprite.rotation + 4 * Math.PI, // Spin effect
|
||||
duration: duration,
|
||||
ease: 'Linear',
|
||||
onComplete: () => {
|
||||
sprite.destroy();
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
x: endX,
|
||||
y: endY,
|
||||
rotation: sprite.rotation + 4 * Math.PI, // Spin effect
|
||||
duration: duration,
|
||||
ease: 'Linear',
|
||||
onComplete: () => {
|
||||
sprite.destroy();
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shakeCamera() {
|
||||
this.scene.cameras.main.shake(100, 0.01);
|
||||
this.scene.cameras.main.shake(100, 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export class GameScene extends Phaser.Scene {
|
||||
|
||||
public playerPath: Vec2[] = [];
|
||||
public awaitingPlayer = false;
|
||||
private lastMoveTime = 0;
|
||||
|
||||
// Sub-systems
|
||||
public dungeonRenderer!: DungeonRenderer;
|
||||
@@ -138,7 +139,7 @@ export class GameScene extends Phaser.Scene {
|
||||
const dx = next.x - player.pos.x;
|
||||
const dy = next.y - player.pos.y;
|
||||
|
||||
if (Math.abs(dx) + Math.abs(dy) !== 1) {
|
||||
if (Math.max(Math.abs(dx), Math.abs(dy)) !== 1) {
|
||||
this.playerPath = [];
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +157,12 @@ export class GameScene extends Phaser.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.time.now - this.lastMoveTime < GAME_CONFIG.rendering.moveDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.commitPlayerAction({ type: "move", dx, dy });
|
||||
this.lastMoveTime = this.time.now;
|
||||
this.playerPath.shift();
|
||||
return;
|
||||
}
|
||||
@@ -164,8 +170,16 @@ export class GameScene extends Phaser.Scene {
|
||||
let action: Action | null = this.playerInputHandler.handleCursorMovement();
|
||||
|
||||
if (action) {
|
||||
if (action.type === "move" && this.time.now - this.lastMoveTime < GAME_CONFIG.rendering.moveDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playerPath = [];
|
||||
this.commitPlayerAction(action);
|
||||
|
||||
if (action.type === "move") {
|
||||
this.lastMoveTime = this.time.now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ import type { Action, RangedWeaponItem } from "../../core/types";
|
||||
export class PlayerInputHandler {
|
||||
private scene: GameScene;
|
||||
|
||||
// Input Chording state
|
||||
private pendingDx: number = 0;
|
||||
private pendingDy: number = 0;
|
||||
private moveChordTime: number = 0;
|
||||
private readonly CHORD_WINDOW = 40; // ms to wait for diagonal chord
|
||||
|
||||
constructor(scene: GameScene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
@@ -170,10 +176,28 @@ export class PlayerInputHandler {
|
||||
}
|
||||
|
||||
public handleCursorMovement(): Action | null {
|
||||
const { dx, dy, anyJustDown } = this.scene.gameInput.getCursorState();
|
||||
const { dx, dy, anyJustDown } = this.scene.gameInput.getCursorState() as any;
|
||||
const now = this.scene.time.now;
|
||||
|
||||
if (anyJustDown) {
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
// Start or update chord
|
||||
if (this.moveChordTime === 0) {
|
||||
this.moveChordTime = now + this.CHORD_WINDOW;
|
||||
}
|
||||
if (dx !== 0) this.pendingDx = dx;
|
||||
if (dy !== 0) this.pendingDy = dy;
|
||||
}
|
||||
|
||||
if (this.moveChordTime > 0 && now >= this.moveChordTime) {
|
||||
const finalDx = this.pendingDx;
|
||||
const finalDy = this.pendingDy;
|
||||
|
||||
// Reset state
|
||||
this.moveChordTime = 0;
|
||||
this.pendingDx = 0;
|
||||
this.pendingDy = 0;
|
||||
|
||||
if (finalDx !== 0 || finalDy !== 0) {
|
||||
if (this.scene.targetingSystem.isActive) {
|
||||
this.scene.targetingSystem.cancel();
|
||||
this.scene.emitUIUpdate();
|
||||
@@ -181,17 +205,15 @@ export class PlayerInputHandler {
|
||||
const player = this.scene.entityAccessor.getPlayer();
|
||||
if (!player) return null;
|
||||
|
||||
const targetX = player.pos.x + dx;
|
||||
const targetY = player.pos.y + dy;
|
||||
const targetX = player.pos.x + finalDx;
|
||||
const targetY = player.pos.y + finalDy;
|
||||
|
||||
const enemy = this.scene.entityAccessor.findEnemyAt(targetX, targetY);
|
||||
|
||||
if (enemy) {
|
||||
return { type: "attack", targetId: enemy.id };
|
||||
} else {
|
||||
if (Math.abs(dx) + Math.abs(dy) === 1) {
|
||||
return { type: "move", dx, dy };
|
||||
}
|
||||
return { type: "move", dx: finalDx, dy: finalDy };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user