1 results found
Escreva um jogo 3D de carro em um único código HTML para qualquer Android com joystick.
Para criar um jogo 3D de carro em um único código HTML, otimizado para Android e com suporte a joystick, precisaremos usar uma biblioteca 3D leve como o Three.js (carregado via CDN para manter o código principal conciso) e o Gamepad API do JavaScript.
Este exemplo será *minimalista* para caber em um único arquivo e ser conciso. Ele terá:
* Um carro simples (um cubo com "rodas").
* Um plano como chão.
* Câmera seguindo o carro.
* Controles básicos de aceleração/freio e direção via teclado e gamepad.
**Conceitos Chave:**
1. **Three.js:** Biblioteca JavaScript para WebGL, simplificando a criação de gráficos 3D.
2. **Gamepad API:** Permite ler inputs de joysticks conectados ao dispositivo.
3. **Inline Scripting:** Todo o JS e CSS estarão dentro do HTML.
4. **CDN:** O Three.js será carregado de um CDN para evitar incluir o código completo da biblioteca no arquivo HTML, mantendo-o mais conciso.
---
```html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Jogo de Carro 3D (Android + Joystick)</title>
<style>
body { margin: 0; overflow: hidden; background-color: #222; }
canvas { display: block; }
#instructions {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: sans-serif;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
font-size: 14px;
}
#gamepad-status {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-family: sans-serif;
background: rgba(0,0,0,0.5);
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
}
</style>
</head>
<body>
<div id="instructions">
<h2>Controles:</h2>
<p><strong>Teclado:</strong></p>
<ul>
<li>'W' / Seta Cima: Acelerar</li>
<li>'S' / Seta Baixo: Frear / Ré</li>
<li>'A' / Seta Esquerda: Virar Esquerda</li>
<li>'D' / Seta Direita: Virar Direita</li>
</ul>
<p><strong>Joystick:</strong></p>
<ul>
<li>Analógico Esquerdo (Y): Acelerar / Frear</li>
<li>Analógico Esquerdo (X): Virar Esquerda / Direita</li>
<li>Ou Botões direcionais (D-Pad)</li>
</ul>
</div>
<div id="gamepad-status">Gamepad: Desconectado</div>
<!-- Carrega a biblioteca Three.js do CDN -->
<script type="module">
import * as THREE from 'https://unpkg.com/[email protected]/build/three.module.js';
// --- Configurações da Cena ---
let scene, camera, renderer;
let car;
let keys = { w: false, s: false, a: false, d: false };
let gamepadIndex = -1; // -1 significa nenhum gamepad conectado
let gamepadStatusDiv = document.getElementById('gamepad-status');
// --- Parâmetros do Carro ---
const maxSpeed = 0.2;
const acceleration = 0.005;
const braking = 0.008;
const friction = 0.003;
const steeringSpeed = 0.03;
let currentSpeed = 0;
function init() {
// Cena
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Céu azul
// Câmera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 15); // Posição inicial da câmera
// Renderizador
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // Habilita sombras
document.body.appendChild(renderer.domElement);
// Luz Ambiente
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// Luz Direcional (Simula o Sol)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -20;
directionalLight.shadow.camera.right = 20;
directionalLight.shadow.camera.top = 20;
directionalLight.shadow.camera.bottom = -20;
scene.add(directionalLight);
// Chão (Plano)
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x558B2F }); // Verde escuro
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2; // Gira para ficar horizontal
ground.receiveShadow = true; // Recebe sombras
scene.add(ground);
// Carro (Corpo principal)
const carBodyGeometry = new THREE.BoxGeometry(2, 0.8, 4); // Largura, Altura, Profundidade
const carBodyMaterial = new THREE.MeshStandardMaterial({ color: 0xFF0000 }); // Vermelho
car = new THREE.Mesh(carBodyGeometry, carBodyMaterial);
car.position.y = 0.4; // Eleva o carro do chão
car.castShadow = true;
scene.add(car);
// Rodas (Exemplo simplificado)
const wheelGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.5, 12);
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const wheels = [];
const wheelPositions = [
{ x: -1.0, z: 1.5 }, { x: 1.0, z: 1.5 }, // Frente
{ x: -1.0, z: -1.5 }, { x: 1.0, z: -1.5 } // Traseira
];
wheelPositions.forEach(pos => {
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheel.rotation.x = Math.PI / 2; // Gira para ficar como roda
wheel.position.set(pos.x, -0.2, pos.z); // Posição em relação ao corpo do carro
wheel.castShadow = true;
car.add(wheel); // Adiciona a roda como filho do carro
wheels.push(wheel);
});
// Event Listeners
window.addEventListener('resize', onWindowResize);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
window.addEventListener('gamepadconnected', onGamepadConnected);
window.addEventListener('gamepaddisconnected', onGamepadDisconnected);
animate();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onKeyDown(event) {
switch (event.code) {
case 'KeyW': case 'ArrowUp': keys.w = true; break;
case 'KeyS': case 'ArrowDown': keys.s = true; break;
case 'KeyA': case 'ArrowLeft': keys.a = true; break;
case 'KeyD': case 'ArrowRight': keys.d = true; break;
}
}
function onKeyUp(event) {
switch (event.code) {
case 'KeyW': case 'ArrowUp': keys.w = false; break;
case 'KeyS': case 'ArrowDown': keys.s = false; break;
case 'KeyA': case 'ArrowLeft': keys.a = false; break;
case 'KeyD': case 'ArrowRight': keys.d = false; break;
}
}
function onGamepadConnected(event) {
gamepadIndex = event.gamepad.index;
console.log(`Gamepad ${event.gamepad.id} conectado no índice ${gamepadIndex}`);
gamepadStatusDiv.textContent = `Gamepad: Conectado (${event.gamepad.id})`;
}
function onGamepadDisconnected(event) {
if (gamepadIndex === event.gamepad.index) {
gamepadIndex = -1;
}
console.log(`Gamepad ${event.gamepad.id} desconectado.`);
gamepadStatusDiv.textContent = 'Gamepad: Desconectado';
}
function handleGamepadInput() {
if (gamepadIndex === -1) return;
const gamepads = navigator.getGamepads();
const gamepad = gamepads[gamepadIndex];
if (!gamepad) {
gamepadIndex = -1; // Gamepad pode ter sido desconectado
gamepadStatusDiv.textContent = 'Gamepad: Desconectado';
return;
}
// --- Analógicos ---
// Eixo Y do analógico esquerdo para aceleração/freio
// Valor entre -1 (frente) e 1 (trás)
const throttleBrake = gamepad.axes[1];
// Eixo X do analógico esquerdo para direção
// Valor entre -1 (esquerda) e 1 (direita)
const steer = gamepad.axes[0];
// Zona morta para os analógicos
const deadZone = 0.15;
// Aceleração/Freio
if (throttleBrake < -deadZone) { // Frente
keys.w = true;
keys.s = false;
} else if (throttleBrake > deadZone) { // Trás
keys.s = true;
keys.w = false;
} else {
keys.w = false;
keys.s = false;
}
// Direção
if (steer < -deadZone) { // Esquerda
keys.a = true;
keys.d = false;
} else if (steer > deadZone) { // Direita
keys.d = true;
keys.a = false;
} else {
keys.a = false;
keys.d = false;
}
// --- D-Pad (Botões direcionais, se disponíveis) ---
// Assume layout de D-pad comum (ex: 12-15 para cima, baixo, esq, dir)
// Os índices dos botões podem variar entre gamepads.
const dPadUp = gamepad.buttons[12]?.pressed;
const dPadDown = gamepad.buttons[13]?.pressed;
const dPadLeft = gamepad.buttons[14]?.pressed;
const dPadRight = gamepad.buttons[15]?.pressed;
if (dPadUp) keys.w = true; else if (!throttleBrake) keys.w = false;
if (dPadDown) keys.s = true; else if (!throttleBrake) keys.s = false;
if (dPadLeft) keys.a = true; else if (!steer) keys.a = false;
if (dPadRight) keys.d = true; else if (!steer) keys.d = false;
}
function animate() {
requestAnimationFrame(animate);
handleGamepadInput(); // Checa o gamepad a cada frame
// --- Lógica de Movimento do Carro ---
if (keys.w) {
currentSpeed += acceleration;
} else if (keys.s) {
currentSpeed -= braking;
} else {
// Aplica atrito se não estiver acelerando nem freando
if (currentSpeed > 0) currentSpeed = Math.max(0, currentSpeed - friction);
if (currentSpeed < 0) currentSpeed = Math.min(0, currentSpeed + friction);
}
// Limita a velocidade
currentSpeed = Math.max(-maxSpeed / 2, Math.min(maxSpeed, currentSpeed));
// Aplica direção (só vira se o carro estiver em movimento)
if (Math.abs(currentSpeed) > 0.005) { // Checa se o carro está realmente se movendo
if (keys.a) {
car.rotation.y += steeringSpeed * (currentSpeed > 0 ? 1 : -1); // Inverte a direção se estiver de ré
}
if (keys.d) {
car.rotation.y -= steeringSpeed * (currentSpeed > 0 ? 1 : -1); // Inverte a direção se estiver de ré
}
}
// Move o carro na direção atual
car.position.x += Math.sin(car.rotation.y) * currentSpeed;
car.position.z += Math.cos(car.rotation.y) * currentSpeed;
// --- Lógica da Câmera (Seguindo o Carro) ---
const cameraOffset = new THREE.Vector3(0, 5, 10); // Offset da câmera em relação ao carro
const rotatedOffset = cameraOffset.clone().applyQuaternion(car.quaternion);
camera.position.copy(car.position).add(rotatedOffset);
camera.lookAt(car.position.clone().add(new THREE.Vector3(0, 1, 0))); // Olha um pouco acima do carro
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>
```
---
**Como usar (para Android com Joystick):**
1. **Copie e Salve:** Copie todo o código acima e salve-o em um arquivo com a extensão `.html` (ex: `car_game.html`).
2. **Transfira para o Android:** Coloque este arquivo HTML em seu dispositivo Android.
3. **Conecte o Joystick:** Conecte seu joystick via Bluetooth ou USB ao seu dispositivo Android. Certifique-se de que ele esteja pareado e funcionando corretamente no sistema.
4. **Abra no Navegador:** Use o navegador Chrome (ou outro navegador moderno com suporte a WebGL e Gamepad API) no seu Android para abrir o arquivo HTML. Você pode fazer isso navegando até o arquivo pelo gerenciador de arquivos e clicando nele.
5. **Interaja:** Quando o jogo carregar, o navegador detectará seu joystick automaticamente se ele já estiver conectado. Use os analógicos ou D-pad para controlar o carro.
**Observações Importantes:**
* **Performance:** Este é um jogo 3D básico. Em dispositivos Android mais antigos ou menos potentes, a performance pode variar.
* **Compatibilidade do Joystick:** A Gamepad API funciona bem, mas os *índices dos botões e eixos* podem variar ligeiramente entre diferentes modelos de joysticks (Xbox, PlayStation, genéricos). O código tenta usar um mapeamento comum para analógicos.
* **"Único Código HTML":** Para ser *realmente* um único arquivo, a biblioteca Three.js deveria ser incorporada diretamente no script, o que o tornaria enorme e menos conciso. A prática comum para "single HTML" é permitir o carregamento de bibliotecas externas via CDN, como feito aqui.
* **Toque na Tela:** Este exemplo **não** inclui controles na tela (botões virtuais de toque). O foco é no joystick. Adicionar controles de toque exigiria mais código JS e CSS, tornando-o menos conciso.
* **Física:** A física do carro é muito simplificada (baseada em velocidade e rotação diretas). Jogos de carro mais avançados usam motores de física dedicados (como Cannon.js ou Ammo.js), o que aumentaria bastante a complexidade e o tamanho do código.
* **"Conciso e Útil":** Este código atinge o equilíbrio entre ser funcional, demonstrar os conceitos solicitados e manter um tamanho razoável para ser apresentado como uma "solução única".
Related Questions
Quick Insights
Try Pro!
Get priority access to the latest AI models and unlimited searches.