feat: camera follow player

This commit is contained in:
tiennm99 committed 2025-04-27 23:18:18 +07:00
1 parent 00f4415236
commit 386c74e6f9
6 files changed
+156 -9

No files matched your search

+6
View File
@@ -152,6 +152,12 @@ export class LevelManager {
// Vẽ lại lưới
this.scene.grid.render();
// Setup camera to follow player after level is loaded
// Add a small delay to ensure everything is properly initialized
this.scene.time.delayedCall(100, () => {
this.scene.setupCamera();
});
return true;
}
+16
View File
@@ -30,6 +30,14 @@ export class Guard {
this.color // Màu sắc
);
// Add guard sprite to world container if it exists
if (this.scene.worldContainer) {
this.scene.worldContainer.add(this.sprite);
}
// Set depth to ensure guard is visible above grid but below player
this.sprite.setDepth(5);
// Đảm bảo hình tròn nằm chính giữa ô
// Circle đã tự động đặt origin ở (0.5, 0.5)
}
@@ -96,6 +104,14 @@ export class RotatingGuard extends Guard {
0xFFFFFF // Màu trắng
);
// Add direction indicator to world container if it exists
if (this.scene.worldContainer) {
this.scene.worldContainer.add(this.directionIndicator);
}
// Set depth to ensure direction indicator is visible above guard
this.directionIndicator.setDepth(6);
this.updateSpriteRotation();
}
+11
View File
@@ -6,6 +6,17 @@ export class LightingSystem {
this.grid = grid;
// Create graphics object for rendering light effects
this.graphics = this.scene.add.graphics();
// Add lighting graphics to world container if it exists
if (this.scene.worldContainer) {
this.scene.worldContainer.add(this.graphics);
}
// Position the lighting graphics at the same position as the grid
if (this.scene.gridOffsetX !== undefined && this.scene.gridOffsetY !== undefined) {
this.graphics.x = this.scene.gridOffsetX;
this.graphics.y = this.scene.gridOffsetY;
}
}
// Xóa tất cả ánh sáng trên lưới
+10
View File
@@ -21,6 +21,14 @@ export class Player {
this.grid.cellSize / 3,
0x000000
);
// Add player sprite to world container if it exists
if (this.scene.worldContainer) {
this.scene.worldContainer.add(this.sprite);
}
// Set depth to ensure player is visible above other elements
this.sprite.setDepth(10);
}
// Di chuyển đến vị trí mới
@@ -92,6 +100,8 @@ export class Player {
// Cập nhật vị trí sprite
update() {
const { x, y } = this.scene.gridToScreen(this.row, this.col);
// Update position
this.sprite.x = x;
this.sprite.y = y;
}
+109 -9
View File
@@ -26,6 +26,7 @@ export class Game extends Phaser.Scene {
this.inputCooldown = 0;
this.isPaused = false;
this.isFinalLevel = false;
this.cameraFollowsPlayer = true; // Enable camera following player
}
init(data) {
@@ -35,10 +36,17 @@ export class Game extends Phaser.Scene {
}
create() {
// Calculate grid position to center it
// Get screen dimensions
const width = this.cameras.main.width;
const height = this.cameras.main.height;
// Create a container for the game world
this.worldContainer = this.add.container(0, 0);
// Create a camera for the game world
this.gameCamera = this.cameras.main;
// Calculate grid position to center it
this.gridOffsetX = (width - (this.gridSize * this.cellSize)) / 2;
this.gridOffsetY = (height - (this.gridSize * this.cellSize)) / 2;
@@ -66,7 +74,7 @@ export class Game extends Phaser.Scene {
// Setup input
this.setupInput();
// Create UI
// Create UI (fixed to camera, not in world container)
this.createUI();
// Create mobile controls if needed
@@ -74,6 +82,51 @@ export class Game extends Phaser.Scene {
// Load level
this.levelManager.loadLevel(this.currentLevel);
// Setup camera to follow player after level is loaded
this.setupCamera();
}
setupCamera() {
// Make sure player exists
if (!this.player || !this.player.sprite) {
console.log("Cannot setup camera: player or player sprite is null");
return;
}
// Get the game dimensions
const width = this.cameras.main.width;
const height = this.cameras.main.height;
// Reset the main camera
this.cameras.main.resetFX();
this.cameras.main.stopFollow();
// Set the camera bounds to the entire world
const worldWidth = Math.max(width, this.gridSize * this.cellSize + this.gridOffsetX * 2);
const worldHeight = Math.max(height, this.gridSize * this.cellSize + this.gridOffsetY * 2);
// Set world bounds
this.physics.world.setBounds(0, 0, worldWidth, worldHeight);
// Set camera bounds
this.cameras.main.setBounds(0, 0, worldWidth, worldHeight);
// Start following the player
this.cameras.main.startFollow(
this.player.sprite, // Target to follow
true, // Round pixels
0.1, // Lerp X (smoothing)
0.1 // Lerp Y (smoothing)
);
// No deadzone - camera always centers on player
this.cameras.main.setDeadzone(0, 0);
// Set follow offset to center the player
this.cameras.main.followOffset.set(0, 0);
console.log("Camera setup complete - following player");
}
update() {
@@ -86,6 +139,18 @@ export class Game extends Phaser.Scene {
if (this.isFinalLevel) {
this.checkFinalLevelCondition();
}
// Make sure player exists
if (this.player && this.player.sprite) {
// Update player position
this.player.update();
// Check if camera is not following the player
if (!this.cameras.main.following) {
console.log("Camera not following - restarting follow");
this.setupCamera();
}
}
}
}
@@ -96,6 +161,9 @@ export class Game extends Phaser.Scene {
this.grid.graphics.x = this.gridOffsetX;
this.grid.graphics.y = this.gridOffsetY;
// Add grid graphics to world container
this.worldContainer.add(this.grid.graphics);
// Render the grid
this.grid.render();
@@ -186,11 +254,16 @@ export class Game extends Phaser.Scene {
// Create UI elements
const width = this.cameras.main.width;
// Create a UI container that will be fixed to the camera
this.uiContainer = this.add.container(0, 0);
this.uiContainer.setScrollFactor(0); // Fix to camera (don't move with world)
// Lives display
this.livesText = this.add.text(20, 20, `${getText('lives')}${this.livesRemaining}`, {
font: '18px Arial',
fill: '#ffffff'
});
this.livesText.setScrollFactor(0); // Fix to camera
// Level display
this.levelText = this.add.text(width - 20, 20, `${getText('level')}${this.currentLevel}`, {
@@ -198,16 +271,19 @@ export class Game extends Phaser.Scene {
fill: '#ffffff'
});
this.levelText.setOrigin(1, 0);
this.levelText.setScrollFactor(0); // Fix to camera
// Pause button
this.pauseButton = this.add.rectangle(width - 20, 60, 100, 30, 0x444444);
this.pauseButton.setOrigin(1, 0);
this.pauseButton.setScrollFactor(0); // Fix to camera
this.pauseText = this.add.text(width - 70, 75, getText('pause'), {
font: '16px Arial',
fill: '#ffffff'
});
this.pauseText.setOrigin(0.5, 0.5);
this.pauseText.setScrollFactor(0); // Fix to camera
this.pauseButton.setInteractive({ useHandCursor: true })
.on('pointerdown', () => {
@@ -217,12 +293,14 @@ export class Game extends Phaser.Scene {
// Return to Main Menu button
this.returnButton = this.add.rectangle(width - 20, 100, 100, 30, 0x444444);
this.returnButton.setOrigin(1, 0);
this.returnButton.setScrollFactor(0); // Fix to camera
this.returnText = this.add.text(width - 70, 115, getText('menu'), {
font: '16px Arial',
fill: '#ffffff'
});
this.returnText.setOrigin(0.5, 0.5);
this.returnText.setScrollFactor(0); // Fix to camera
this.returnButton.setInteractive({ useHandCursor: true })
.on('pointerover', () => this.returnButton.fillColor = 0x666666)
@@ -231,6 +309,16 @@ export class Game extends Phaser.Scene {
this.scene.start('MainMenu');
});
// Add all UI elements to the UI container
this.uiContainer.add([
this.livesText,
this.levelText,
this.pauseButton,
this.pauseText,
this.returnButton,
this.returnText
]);
// Create pause menu (initially hidden)
this.createPauseMenu();
}
@@ -241,6 +329,7 @@ export class Game extends Phaser.Scene {
// Create container for pause menu
this.pauseMenu = this.add.container(width / 2, height / 2);
this.pauseMenu.setScrollFactor(0); // Fix to camera (don't move with world)
// Background
const bg = this.add.rectangle(0, 0, 300, 250, 0x000000, 0.8);
@@ -248,7 +337,8 @@ export class Game extends Phaser.Scene {
// Title
const title = this.add.text(0, -100, getText('paused'), {
font: 'bold 32px Arial',
fill: '#ffffff'
fill: '#ffffff',
wordWrap: { width: 280 } // Enable text wrapping
});
title.setOrigin(0.5, 0.5);
@@ -256,7 +346,8 @@ export class Game extends Phaser.Scene {
const resumeButton = this.add.rectangle(0, -40, 200, 40, 0x444444);
const resumeText = this.add.text(0, -40, getText('resume'), {
font: '20px Arial',
fill: '#ffffff'
fill: '#ffffff',
wordWrap: { width: 180 } // Enable text wrapping
});
resumeText.setOrigin(0.5, 0.5);
@@ -264,7 +355,8 @@ export class Game extends Phaser.Scene {
const restartButton = this.add.rectangle(0, 20, 200, 40, 0x444444);
const restartText = this.add.text(0, 20, getText('restartLevel'), {
font: '20px Arial',
fill: '#ffffff'
fill: '#ffffff',
wordWrap: { width: 180 } // Enable text wrapping
});
restartText.setOrigin(0.5, 0.5);
@@ -272,7 +364,8 @@ export class Game extends Phaser.Scene {
const menuButton = this.add.rectangle(0, 80, 200, 40, 0x444444);
const menuText = this.add.text(0, 80, getText('mainMenu'), {
font: '20px Arial',
fill: '#ffffff'
fill: '#ffffff',
wordWrap: { width: 180 } // Enable text wrapping
});
menuText.setOrigin(0.5, 0.5);
@@ -319,6 +412,8 @@ export class Game extends Phaser.Scene {
// Container for mobile controls
this.mobileControls = this.add.container(width / 2, height - 100);
this.mobileControls.setScrollFactor(0); // Fix to camera (don't move with world)
this.mobileControls.setDepth(1000); // Ensure controls are above other elements
// Create directional buttons
const buttonSize = 60;
@@ -424,6 +519,7 @@ export class Game extends Phaser.Scene {
// Tạo container cho popup
this.detectionPopup = this.add.container(width / 2, height / 2);
this.detectionPopup.setDepth(100);
this.detectionPopup.setScrollFactor(0); // Fix to camera (don't move with world)
this.detectionPopup.popup = 'detection'; // Đánh dấu container
// Background
@@ -433,7 +529,8 @@ export class Game extends Phaser.Scene {
// Thông báo
const title = this.add.text(0, -60, getText('detected'), {
font: 'bold 28px Arial',
fill: '#FF0000'
fill: '#FF0000',
wordWrap: { width: 280 } // Enable text wrapping
});
title.setOrigin(0.5, 0.5);
title.popup = 'detection'; // Đánh dấu phần tử thuộc về popup
@@ -444,7 +541,8 @@ export class Game extends Phaser.Scene {
const restartText = this.add.text(0, 20, getText('playAgain'), {
font: '20px Arial',
fill: '#ffffff'
fill: '#ffffff',
wordWrap: { width: 180 } // Enable text wrapping
});
restartText.setOrigin(0.5, 0.5);
restartText.popup = 'detection'; // Đánh dấu phần tử thuộc về popup
@@ -568,10 +666,12 @@ export class Game extends Phaser.Scene {
font: 'bold 20px Arial',
fill: '#FF0000',
backgroundColor: '#000000',
padding: { x: 10, y: 5 }
padding: { x: 10, y: 5 },
wordWrap: { width: width * 0.8 } // Enable text wrapping with 80% of screen width
});
this.finalLevelMessage.setOrigin(0.5, 0.5);
this.finalLevelMessage.setDepth(100);
this.finalLevelMessage.setScrollFactor(0); // Fix to camera (don't move with world)
}
// Bật sáng toàn bộ các ô
+4
View File
@@ -14,6 +14,10 @@ const config = {
height: 600,
parent: 'app',
backgroundColor: '#333333',
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: [Boot, Preloader, MainMenu, LevelSelect, Game, GameOver, Settings, Guide],
physics: {
default: 'arcade',