This commit is contained in:
Ruan Fernandes Guimaraes 2025-06-29 10:55:10 -03:00
commit 1f0daf82cb
15 changed files with 730 additions and 0 deletions

28
public/index.html Normal file
View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FreneticalPVP</title>
<script src="https://cdn.jsdelivr.net/npm/phaser@v3.90.0/dist/phaser.min.js"></script>
<script src="https://unpkg.com/colyseus.js@^0.16.0/dist/colyseus.js"></script>
</head>
<body>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
}
canvas {
display: block;
margin: 0 auto;
}
</style>
<script src="./js/main.js"></script>
</body>
</html>

344
public/js/main.js Normal file
View file

@ -0,0 +1,344 @@
const client = new Colyseus.Client('ws://localhost:3000');
let room;
let player;
const otherPlayers = new Map();
let scene;
const config = {
type: Phaser.AUTO,
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: '#1a1a1a',
physics: {
default: 'arcade',
arcade: { debug: false },
},
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
scene: {
preload,
create,
update,
},
};
const game = new Phaser.Game(config);
const MessageType = {
SERVER_CONNECT: 1,
SERVER_DISCONNECT: 2,
SERVER_MOVE: 3,
SERVER_CHAT: 4,
CLIENT_WELCOME: 1001,
CLIENT_PLAYER_JOINED: 1002,
CLIENT_PLAYER_LEFT: 1003,
CLIENT_UPDATE_PLAYER: 1004,
CLIENT_CHAT_MESSAGE: 1005,
CLIENT_ERROR: 1404,
CLIENT_PLAYERS_LIST: 1006, // Novo tipo para lista de jogadores
};
// Variáveis para movimento fluido
const keys = {};
let lastMovementTime = 0;
const MOVEMENT_INTERVAL = 16; // ~60fps
const SPEED = 500; // pixels por segundo
function preload() {
console.log('🎮 Preload phase started');
}
function create() {
scene = this;
console.log('🎯 Create phase started');
// Criar o player visual
player = createPlayerObject(this, 400, 300, 0x00ff00, 'Você');
// Configurar input contínuo
setupContinuousInput(this);
// Conectar ao servidor
console.log('🔗 Tentando conectar ao servidor...');
client
.joinOrCreate('world', { nickname: prompt('Nickname:') })
.then((r) => {
room = r;
console.log('✅ Conectado com sucesso:', room.sessionId);
// Configurar handlers de mensagem
setupMessageHandlers(scene);
})
.catch((error) => {
console.error('❌ Erro ao conectar:', error);
});
}
function createPlayerObject(scene, x, y, color, nickname) {
const playerContainer = scene.add.container(x, y);
// Corpo do jogador
const body = scene.add.rectangle(0, 0, 32, 32, color);
// Nome do jogador
const nameText = scene.add
.text(0, -25, nickname, {
fontSize: '12px',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 2,
})
.setOrigin(0.5);
// Texto de chat (inicialmente oculto)
const chatText = scene.add
.text(0, -50, '', {
fontSize: '10px',
fill: '#ffffff',
backgroundColor: '#000000aa',
padding: { x: 4, y: 2 },
})
.setOrigin(0.5)
.setVisible(false);
playerContainer.add([body, nameText, chatText]);
// Adicionar propriedades customizadas
playerContainer.body = body;
playerContainer.nameText = nameText;
playerContainer.chatText = chatText;
playerContainer.targetX = x;
playerContainer.targetY = y;
return playerContainer;
}
function setupContinuousInput(scene) {
// Configurar teclas
const cursors = scene.input.keyboard.createCursorKeys();
const wasd = scene.input.keyboard.addKeys('W,S,A,D');
// Armazenar referências das teclas
keys.up = cursors.up;
keys.down = cursors.down;
keys.left = cursors.left;
keys.right = cursors.right;
keys.w = wasd.W;
keys.s = wasd.S;
keys.a = wasd.A;
keys.d = wasd.D;
// Configurar tecla Enter para chat
scene.input.keyboard.on('keydown-ENTER', () => {
if (!room) return;
const msg = prompt('Mensagem:');
if (msg && msg.trim()) {
console.log('💬 Enviando mensagem:', msg);
room.send(MessageType.SERVER_CHAT, { message: msg.trim() });
}
});
}
function setupMessageHandlers(scene) {
// Welcome message
room.onMessage(MessageType.CLIENT_WELCOME, (data) => {
console.log('👋 Welcome:', data.message);
});
// Lista de jogadores online (novo)
room.onMessage(MessageType.CLIENT_PLAYERS_LIST, (players) => {
console.log('📋 Players list:', players);
players.forEach((playerData) => {
if (playerData.sessionId !== room.sessionId) {
createOtherPlayer(playerData);
}
});
});
// Player joined
room.onMessage(
MessageType.CLIENT_PLAYER_JOINED,
({ sessionId, options }) => {
console.log('🧍 Player joined:', sessionId, options);
if (sessionId === room.sessionId) return;
const newPlayer = createPlayerObject(
scene,
400,
300,
0xff0000,
options?.nickname || 'Player'
);
otherPlayers.set(sessionId, newPlayer);
}
);
// Player left
room.onMessage(MessageType.CLIENT_PLAYER_LEFT, ({ sessionId }) => {
console.log('👋 Player left:', sessionId);
const playerObj = otherPlayers.get(sessionId);
if (playerObj) {
playerObj.destroy();
otherPlayers.delete(sessionId);
}
});
// Player update - com interpolação suave
room.onMessage(
MessageType.CLIENT_UPDATE_PLAYER,
({ sessionId, position }) => {
if (sessionId === room.sessionId) {
return; // Não atualizar nosso próprio player
}
const playerObj = otherPlayers.get(sessionId);
if (playerObj) {
// Definir posição alvo para interpolação suave
playerObj.targetX = position.x;
playerObj.targetY = position.y;
}
}
);
// Chat message - exibir acima da cabeça
room.onMessage(
MessageType.CLIENT_CHAT_MESSAGE,
({ sessionId, message }) => {
console.log(`💬 [${sessionId}]:`, message);
let playerObj;
if (sessionId === room.sessionId) {
playerObj = player;
} else {
playerObj = otherPlayers.get(sessionId);
}
if (playerObj && playerObj.chatText) {
showChatMessage(playerObj, message);
}
}
);
// Error message
room.onMessage(MessageType.CLIENT_ERROR, ({ message }) => {
console.error('❌ Erro do servidor:', message);
});
// Connection events
room.onStateChange((state) => {
console.log('🔄 State changed:', state);
});
room.onError((code, message) => {
console.error('❌ Room error:', code, message);
});
room.onLeave((code) => {
console.log('👋 Left room with code:', code);
});
}
function createOtherPlayer(playerData) {
const newPlayer = createPlayerObject(
scene,
playerData.position.x,
playerData.position.y,
0xff0000,
playerData.nickname || 'Player'
);
otherPlayers.set(playerData.sessionId, newPlayer);
}
function showChatMessage(playerObj, message) {
if (!playerObj.chatText) return;
playerObj.chatText.setText(message);
playerObj.chatText.setVisible(true);
// Ocultar mensagem após 3 segundos
setTimeout(() => {
if (playerObj.chatText) {
playerObj.chatText.setVisible(false);
}
}, 3000);
}
function update(time, delta) {
// Movimento fluido baseado em input contínuo
if (room && player) {
handleContinuousMovement(delta);
}
// Interpolação suave para outros jogadores
interpolateOtherPlayers(delta);
}
function handleContinuousMovement(delta) {
const currentTime = Date.now();
// Verificar se é hora de enviar movimento
if (currentTime - lastMovementTime < MOVEMENT_INTERVAL) {
return;
}
let isMoving = false;
const movement = { x: 0, y: 0 };
const speed = (SPEED * delta) / 1000; // pixels por frame
// Verificar input
if (keys.up.isDown || keys.w.isDown) {
movement.y = -speed;
isMoving = true;
}
if (keys.down.isDown || keys.s.isDown) {
movement.y = speed;
isMoving = true;
}
if (keys.left.isDown || keys.a.isDown) {
movement.x = -speed;
isMoving = true;
}
if (keys.right.isDown || keys.d.isDown) {
movement.x = speed;
isMoving = true;
}
if (isMoving) {
// Atualizar posição local imediatamente
player.x += movement.x;
player.y += movement.y;
// Enviar para o servidor
room.send(MessageType.SERVER_MOVE, { x: player.x, y: player.y });
lastMovementTime = currentTime;
}
}
function interpolateOtherPlayers(delta) {
const lerpSpeed = 0.1; // Velocidade de interpolação (0-1)
otherPlayers.forEach((playerObj) => {
if (
playerObj.targetX !== undefined &&
playerObj.targetY !== undefined
) {
// Interpolação linear suave
const deltaX = playerObj.targetX - playerObj.x;
const deltaY = playerObj.targetY - playerObj.y;
if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
playerObj.x += deltaX * lerpSpeed;
playerObj.y += deltaY * lerpSpeed;
} else {
playerObj.x = playerObj.targetX;
playerObj.y = playerObj.targetY;
}
}
});
}