{"id":1250,"date":"2025-11-13T00:17:49","date_gmt":"2025-11-13T03:17:49","guid":{"rendered":"https:\/\/urutaudev.com.br\/?p=1250"},"modified":"2025-11-13T21:27:50","modified_gmt":"2025-11-14T00:27:50","slug":"game-com-treejs","status":"publish","type":"post","link":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/","title":{"rendered":"Game Com TreeJs"},"content":{"rendered":"<h2 style=\"font-size: 18pt; font-family: arial, helvetica, sans-serif;\">Game Com TreeJs<\/h2>\n<p>&nbsp;<\/p>\n<figure id=\"attachment_1259\" aria-describedby=\"caption-attachment-1259\" style=\"width: 425px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1259 \" src=\"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980-300x161.png\" alt=\"Game Com TreeJs\" width=\"425\" height=\"228\" srcset=\"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980-300x161.png 300w, https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980-1024x548.png 1024w, https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980-768x411.png 768w, https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980-1536x823.png 1536w, https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png 1673w\" sizes=\"auto, (max-width: 425px) 100vw, 425px\" \/><figcaption id=\"caption-attachment-1259\" class=\"wp-caption-text\">Game Com TreeJs<\/figcaption><\/figure>\n<p>&nbsp;<\/p>\n<section>\n<h2><span style=\"font-family: arial, helvetica, sans-serif;\">Introdu\u00e7\u00e3o<\/span><\/h2>\n<p style=\"font-size: 14pt; font-family: arial, helvetica, sans-serif;\">Game com Three.js, explorando um pouco desta lib que ajuda na cria\u00e7\u00e3o de games e anima\u00e7\u00f5es usando o Javascript e que roda no navegador.<\/p>\n<p style=\"font-size: 14pt; font-family: arial, helvetica, sans-serif;\">Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!<br \/>\nCom a biblioteca Three.js podemos transformar o navegador em um verdadeiro palco para experi\u00eancias 3D incr\u00edveis.<br \/>\nUsar o Three.js para fazer Games \u00e9 s\u00f3 um exemplo divertido de como a web pode ganhar vida com luzes, sombras e efeitos interativos, tudo em tempo real.<\/p>\n<\/section>\n<p>&nbsp;<\/p>\n<p><span style=\"font-family: arial, helvetica, sans-serif;\"><strong><span style=\"font-size: 14pt;\">Voc\u00ea pode visualizar o jogo no link abaixo:<\/span><\/strong><\/span><\/p>\n<p><strong><span style=\"font-family: arial, helvetica, sans-serif; font-size: 12pt;\"><a href=\"https:\/\/urutaudev.com.br\/games\/game-treejs-site\">Game Exemplo com TreeJs<\/a>\u00a0<\/span><\/strong><\/p>\n<section>\n<h2><span style=\"font-family: arial, helvetica, sans-serif;\">Scripts utilizado (html):<\/span><\/h2>\n<div>\n<div>\n<div>\n<div><code>&lt;!DOCTYPE html&gt;<\/code><\/div>\n<div><code>&lt;html lang=\"pt-BR\"&gt;<\/code><\/div>\n<div><code>&lt;head&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 &lt;meta charset=\"UTF-8\"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 &lt;title&gt;Game Three Js - Testes&lt;\/title&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 &lt;link rel=\"stylesheet\" href=\"\/css\/main.css\"&gt;<\/code><\/div>\n<div><code>&lt;\/head&gt;<\/code><\/div>\n<div><code>&lt;body&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 &lt;script type=\"module\" src=\"main.js\"&gt;&lt;\/script&gt;<\/code><\/div>\n<div><code>&lt;\/body&gt;<\/code><\/div>\n<div><code>&lt;\/html&gt;<\/code><\/div>\n<\/div>\n<\/div>\n<pre><\/pre>\n<\/div>\n<\/section>\n<p>&nbsp;<\/p>\n<h2><span style=\"font-family: arial, helvetica, sans-serif;\">Scripts utilizado (Javascript):<\/span><\/h2>\n<div>\n<pre><code><\/code><\/pre>\n<div>\n<div><code>import * as THREE from \"three\";<\/code><\/div>\n<div><code>import { OrbitControls } from \"three\/addons\/controls\/OrbitControls.js\";<\/code><\/div>\n<div><code>import { FBXLoader } from \"three\/addons\/loaders\/FBXLoader.js\";<\/code><\/div>\n<div><code>import { GUI } from \"three\/addons\/libs\/lil-gui.module.min.js\";<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ CONFIGURA\u00c7\u00d5ES GLOBAIS<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>const CONFIG = {<\/code><\/div>\n<div><code>\u00a0 \/\/ Movimento<\/code><\/div>\n<div><code>\u00a0 MOVEMENT_SPEED: 200,<\/code><\/div>\n<div><code>\u00a0 ROTATION_SPEED: 8,<\/code><\/div>\n<div><code>\u00a0 RUN_SPEED_MULTIPLIER: 1.5,<\/code><\/div>\n<div><code>\u00a0 \/\/ F\u00edsica do pulo<\/code><\/div>\n<div><code>\u00a0 JUMP_VELOCITY: 70,<\/code><\/div>\n<div><code>\u00a0 GRAVITY: -180,<\/code><\/div>\n<div><code>\u00a0 MAX_FALL_SPEED: -300,<\/code><\/div>\n<div><code>\u00a0 GROUND_LEVEL: 0,<\/code><\/div>\n<div><code>\u00a0 COYOTE_TIME: 0.15,<\/code><\/div>\n<div><code>\u00a0 \/\/ Anima\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 ANIMATION_FADE_DURATION: 0.15,<\/code><\/div>\n<div><code>\u00a0 JUMP_UP_THRESHOLD: 10,<\/code><\/div>\n<div><code>\u00a0 JUMP_DOWN_THRESHOLD: -10,<\/code><\/div>\n<div><code>\u00a0 \/\/ C\u00e2mera<\/code><\/div>\n<div><code>\u00a0 CAMERA_FOV: 65,<\/code><\/div>\n<div><code>\u00a0 CAMERA_DISTANCE: 280,<\/code><\/div>\n<div><code>\u00a0 CAMERA_HEIGHT: 140,<\/code><\/div>\n<div><code>\u00a0 CAMERA_FOLLOW_SMOOTHNESS: 0.08,<\/code><\/div>\n<div><code>\u00a0 \/\/ Visual<\/code><\/div>\n<div><code>\u00a0 MODEL_SCALE: 0.3,<\/code><\/div>\n<div><code>\u00a0 SHADOW_MAP_SIZE: 2048,<\/code><\/div>\n<div><code>\u00a0 \/\/ Sistema de Combate com F\u00edsica<\/code><\/div>\n<div><code>\u00a0 ATTACK: {<\/code><\/div>\n<div><code>\u00a0 \u00a0 PUNCH: {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 COOLDOWN: 0.5,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 MIN_RANGE: 20, \/\/ Dist\u00e2ncia m\u00ednima<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 MAX_RANGE: 100, \/\/ Dist\u00e2ncia m\u00e1xima<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ANGLE: Math.PI \/ 3, \/\/ \u00c2ngulo do cone<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 FORCE: 250, \/\/ For\u00e7a do impulso<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 KNOCKBACK: 0.8, \/\/ Multiplicador de knockback<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 HIT_WINDOW: 0.25, \/\/ Quando o hit acontece<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 DURATION: 0.6,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 DAMAGE: 15,<\/code><\/div>\n<div><code>\u00a0 \u00a0 },<\/code><\/div>\n<div><code>\u00a0 \u00a0 KICK: {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 COOLDOWN: 0.8,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 MIN_RANGE: 30,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 MAX_RANGE: 140,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ANGLE: Math.PI \/ 2.5,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 FORCE: 450,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 KNOCKBACK: 1.5,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 HIT_WINDOW: 0.35,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 DURATION: 0.9,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 DAMAGE: 25,<\/code><\/div>\n<div><code>\u00a0 \u00a0 },<\/code><\/div>\n<div><code>\u00a0 },<\/code><\/div>\n<div><code>\u00a0 \/\/ F\u00edsica dos objetos<\/code><\/div>\n<div><code>\u00a0 PHYSICS: {<\/code><\/div>\n<div><code>\u00a0 \u00a0 FRICTION: 0.92, \/\/ Atrito do ch\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 AIR_RESISTANCE: 0.98, \/\/ Resist\u00eancia do ar<\/code><\/div>\n<div><code>\u00a0 \u00a0 BOUNCE: 0.3, \/\/ Qu\u00e3o el\u00e1stico \u00e9 o objeto<\/code><\/div>\n<div><code>\u00a0 \u00a0 ROTATION_DAMPING: 0.95, \/\/ Amortecimento da rota\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 MIN_VELOCITY: 0.01, \/\/ Velocidade m\u00ednima antes de parar<\/code><\/div>\n<div><code>\u00a0 },<\/code><\/div>\n<div><code>\u00a0 \/\/ Colis\u00e3o<\/code><\/div>\n<div><code>\u00a0 CHARACTER_RADIUS: 30, \/\/ Raio de colis\u00e3o do personagem<\/code><\/div>\n<div><code>\u00a0 OBJECT_RADIUS: 35, \/\/ Raio de colis\u00e3o dos cubos (metade da diagonal)<\/code><\/div>\n<div><code>};<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ M\u00d3DULO: OBJETO COM F\u00cdSICA<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>class PhysicsObject {<\/code><\/div>\n<div><code>\u00a0 constructor(mesh) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh = mesh;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.velocity = new THREE.Vector3(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.angularVelocity = new THREE.Vector3(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mass = 1.0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.isGrounded = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.health = 100;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.maxHealth = 100;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Cria barra de vida<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.createHealthBar();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createHealthBar() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const barWidth = 60;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const barHeight = 4;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const canvas = document.createElement(\"canvas\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 canvas.width = 128;<\/code><\/div>\n<div><code>\u00a0 \u00a0 canvas.height = 32;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const ctx = canvas.getContext(\"2d\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 const texture = new THREE.CanvasTexture(canvas);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const material = new THREE.SpriteMaterial({ map: texture });<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBar = new THREE.Sprite(material);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBar.scale.set(barWidth, barHeight, 1);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.add(this.healthBar);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBar.position.set(0, 40, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBarCanvas = canvas;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBarCtx = ctx;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateHealthBar();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 updateHealthBar() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const ctx = this.healthBarCtx;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const canvas = this.healthBarCanvas;<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.clearRect(0, 0, canvas.width, canvas.height);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Fundo<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.fillStyle = \"rgba(0, 0, 0, 0.5)\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.fillRect(0, 0, canvas.width, canvas.height);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Barra de vida<\/code><\/div>\n<div><code>\u00a0 \u00a0 const healthPercent = this.health \/ this.maxHealth;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const barWidth = canvas.width * healthPercent;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Cor baseada na vida<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (healthPercent &gt; 0.6) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ctx.fillStyle = \"#00ff00\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else if (healthPercent &gt; 0.3) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ctx.fillStyle = \"#ffff00\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ctx.fillStyle = \"#ff0000\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.fillRect(0, 0, barWidth, canvas.height);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Borda<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.strokeStyle = \"#ffffff\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.lineWidth = 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.healthBar.material.map.needsUpdate = true;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 applyForce(force, point) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica for\u00e7a linear<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.velocity.add(force.clone().multiplyScalar(1 \/ this.mass));<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica torque (rota\u00e7\u00e3o) baseado no ponto de impacto<\/code><\/div>\n<div><code>\u00a0 \u00a0 const centerToPoint = point.clone().sub(this.mesh.position);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const torque = new THREE.Vector3().crossVectors(centerToPoint, force);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.angularVelocity.add(torque.multiplyScalar(0.01 \/ this.mass));<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 takeDamage(amount) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.health = Math.max(0, this.health - amount);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateHealthBar();<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.health &lt;= 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.onDestroy();<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 onDestroy() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Efeito de destrui\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 const material = this.mesh.material;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const originalColor = material.color.clone();<\/code><\/div>\n<div><code>\u00a0 \u00a0 let opacity = 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const fadeOut = () =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 opacity -= 0.05;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 material.opacity = opacity;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 material.transparent = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (opacity &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 requestAnimationFrame(fadeOut);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.mesh.visible = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \/\/ Regenera ap\u00f3s 5 segundos<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 setTimeout(() =&gt; this.respawn(), 5000);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 fadeOut();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 respawn() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.health = this.maxHealth;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.velocity.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.angularVelocity.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.material.opacity = 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.material.transparent = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.visible = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.rotation.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateHealthBar();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 update(delta) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica gravidade<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.isGrounded) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.y += CONFIG.GRAVITY * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica velocidade<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.position.add(this.velocity.clone().multiplyScalar(delta));<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica rota\u00e7\u00e3o angular<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.rotation.x += this.angularVelocity.x * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.rotation.y += this.angularVelocity.y * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mesh.rotation.z += this.angularVelocity.z * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Colis\u00e3o com o ch\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 const groundY = 25; \/\/ Altura do centro do cubo<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.mesh.position.y &lt;= groundY) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.mesh.position.y = groundY;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Bounce<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (Math.abs(this.velocity.y) &gt; 5) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.velocity.y *= -CONFIG.PHYSICS.BOUNCE;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.velocity.y = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.isGrounded = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isGrounded = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplicar atrito<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.isGrounded) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.x *= CONFIG.PHYSICS.FRICTION;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.z *= CONFIG.PHYSICS.FRICTION;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.angularVelocity.multiplyScalar(CONFIG.PHYSICS.ROTATION_DAMPING);<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Resist\u00eancia do ar<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.x *= CONFIG.PHYSICS.AIR_RESISTANCE;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.z *= CONFIG.PHYSICS.AIR_RESISTANCE;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Para o objeto se estiver muito devagar<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.velocity.length() &lt; CONFIG.PHYSICS.MIN_VELOCITY) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.set(0, this.velocity.y, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.angularVelocity.length() &lt; 0.01) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.angularVelocity.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Mant\u00e9m a barra de vida sempre virada para a c\u00e2mera<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.healthBar) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.healthBar.quaternion.copy(this.mesh.quaternion);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>}<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ M\u00d3DULO: GERENCIADOR DE UI<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>class UIManager {<\/code><\/div>\n<div><code>\u00a0 constructor() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.loadingScreen = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.hitIndicator = null;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createLoadingScreen() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const loader = document.createElement(\"div\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 loader.id = \"loading-screen\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 loader.style.cssText = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 position: fixed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 top: 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 left: 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 width: 100%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 height: 100%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 display: flex;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 flex-direction: column;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 align-items: center;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 justify-content: center;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: white;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-family: Arial, sans-serif;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 z-index: 1000;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 transition: opacity 0.5s;<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 loader.innerHTML = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div style=\"text-align: center;\"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;h1 style=\"font-size: 3em; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\"&gt;\u2694\ufe0f&lt;\/h1&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;h2 style=\"margin: 20px 0;\"&gt;Sistema de Combate&lt;\/h2&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div id=\"loading-progress\" style=\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 width: 300px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 height: 8px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 background: rgba(255,255,255,0.2);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 border-radius: 10px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 overflow: hidden;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 margin: 20px 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &lt;div id=\"loading-bar\" style=\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 width: 0%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 height: 100%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 background: white;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 transition: width 0.3s;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 border-radius: 10px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \"&gt;&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;p id=\"loading-text\" style=\"font-size: 0.9em; opacity: 0.8;\"&gt;Preparando ambiente...&lt;\/p&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.body.appendChild(loader);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.loadingScreen = loader;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 updateLoadingProgress(percent, text) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const bar = document.getElementById(\"loading-bar\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 const textEl = document.getElementById(\"loading-text\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (bar) bar.style.width = `${percent}%`;<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (textEl) textEl.textContent = text;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 hideLoadingScreen() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.loadingScreen) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.loadingScreen.style.opacity = \"0\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 setTimeout(() =&gt; this.loadingScreen.remove(), 500);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createInstructions() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const instructions = document.createElement(\"div\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 instructions.style.cssText = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 position: fixed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 bottom: 20px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 left: 50%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 transform: translateX(-50%);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 background: rgba(0,0,0,0.8);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: white;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 padding: 20px 30px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 border-radius: 15px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-family: Arial, sans-serif;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-size: 14px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 text-align: center;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 pointer-events: none;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 z-index: 100;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 box-shadow: 0 4px 20px rgba(0,0,0,0.5);<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 instructions.innerHTML = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div style=\"margin-bottom: 10px; font-size: 18px; font-weight: bold;\"&gt;\u2694\ufe0f CONTROLES DE COMBATE&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 15px; text-align: left;\"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div&gt;&lt;strong&gt;WASD\/Setas:&lt;\/strong&gt; Mover&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div&gt;&lt;strong&gt;ESPA\u00c7O:&lt;\/strong&gt; Pular&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div&gt;&lt;strong&gt;SHIFT:&lt;\/strong&gt; Correr&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div&gt;&lt;strong&gt;Mouse:&lt;\/strong&gt; C\u00e2mera&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div style=\"color: #ffdd57;\"&gt;&lt;strong&gt;J:&lt;\/strong&gt; Soco \ud83d\udc4a&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 &lt;div style=\"color: #ff6b6b;\"&gt;&lt;strong&gt;K:&lt;\/strong&gt; Chute \ud83e\uddb5&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div style=\"margin-top: 15px; font-size: 12px; opacity: 0.7;\"&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \ud83d\udca1 Aproxime-se dos cubos para acert\u00e1-los!<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.body.appendChild(instructions);<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(() =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 instructions.style.transition = \"opacity 1s\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 instructions.style.opacity = \"0\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 setTimeout(() =&gt; instructions.remove(), 1000);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }, 15000);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createCombatFeedback() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const feedback = document.createElement(\"div\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 feedback.id = \"combat-feedback\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 feedback.style.cssText = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 position: fixed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 top: 50%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 left: 50%;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 transform: translate(-50%, -50%);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-family: 'Arial Black', sans-serif;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-size: 64px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-weight: bold;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: white;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 text-shadow:<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 3px 3px 0px rgba(0,0,0,0.8),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 -1px -1px 0px rgba(0,0,0,0.8),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 1px -1px 0px rgba(0,0,0,0.8),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 -1px 1px 0px rgba(0,0,0,0.8);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 pointer-events: none;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 z-index: 200;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 opacity: 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 transition: all 0.1s;<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.body.appendChild(feedback);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback = feedback;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createHitIndicator() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const indicator = document.createElement(\"div\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 indicator.id = \"hit-indicator\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 indicator.style.cssText = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 position: fixed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 top: 20px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 right: 20px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 background: rgba(0,0,0,0.8);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: white;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 padding: 15px 25px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 border-radius: 10px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-family: Arial, sans-serif;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 font-size: 16px;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 pointer-events: none;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 z-index: 150;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 display: none;<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.body.appendChild(indicator);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.hitIndicator = indicator;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 showCombatHit(type, damage, distance, combo) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.combatFeedback) this.createCombatFeedback();<\/code><\/div>\n<div><code>\u00a0 \u00a0 const messages = {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 punch: [\"\ud83d\udca5 POW!\", \"\ud83d\udc4a BAM!\", \"\ud83d\udd25 HIT!\"],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 kick: [\"\ud83d\udca5 BOOM!\", \"\ud83e\uddb5 CRASH!\", \"\u26a1 SMASH!\"],<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 const msg =<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 messages[type][Math.floor(Math.random() * messages[type].length)];<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.innerHTML = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ${msg}&lt;br&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;span style=\"font-size: 32px;\"&gt;${damage} DMG&lt;\/span&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ${<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 combo &gt; 1<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ? `&lt;br&gt;&lt;span style=\"font-size: 28px; color: #ffdd57;\"&gt;x${combo} COMBO!&lt;\/span&gt;`<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 : \"\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.color = type === \"kick\" ? \"#ff6b6b\" : \"#ffdd57\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.opacity = \"1\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.transform = \"translate(-50%, -50%) scale(1.2)\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(() =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatFeedback.style.opacity = \"0\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatFeedback.style.transform = \"translate(-50%, -50%) scale(0.8)\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 }, 600);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Mostra indicador de dist\u00e2ncia<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.showHitInfo(type, distance, combo);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 showCombatMiss(type, reason = \"\") {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.combatFeedback) this.createCombatFeedback();<\/code><\/div>\n<div><code>\u00a0 \u00a0 const messages = {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"too-far\": \"\ud83c\udfaf MUITO LONGE!\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"too-close\": \"\u26a0\ufe0f MUITO PERTO!\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"wrong-angle\": \"\u2194\ufe0f FORA DE ALCANCE!\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 default: type === \"punch\" ? \"\ud83d\udc4a ERROU...\" : \"\ud83e\uddb5 ERROU...\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.textContent = messages[reason] || messages[\"default\"];<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.color = \"#888888\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.fontSize = \"48px\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.opacity = \"0.7\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatFeedback.style.transform = \"translate(-50%, -50%) scale(1)\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(() =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatFeedback.style.opacity = \"0\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 }, 400);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 showHitInfo(type, distance, combo) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.hitIndicator) this.createHitIndicator();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.hitIndicator.innerHTML = `<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div style=\"font-weight: bold; margin-bottom: 5px;\"&gt;${<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 type === \"punch\" ? \"\ud83d\udc4a SOCO\" : \"\ud83e\uddb5 CHUTE\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 &lt;div&gt;Dist\u00e2ncia: ${Math.round(distance)}u&lt;\/div&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ${combo &gt; 1 ? `&lt;div style=\"color: #ffdd57;\"&gt;Combo: x${combo}&lt;\/div&gt;` : \"\"}<\/code><\/div>\n<div><code>\u00a0 \u00a0 `;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.hitIndicator.style.display = \"block\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(() =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.hitIndicator.style.display = \"none\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 }, 2000);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>}<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ M\u00d3DULO: SISTEMA DE COMBATE<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>class CombatSystem {<\/code><\/div>\n<div><code>\u00a0 constructor(model, scene, uiManager) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.model = model;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene = scene;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ui = uiManager;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.attackState = {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 punch: { canAttack: true, cooldownTimer: 0 },<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 kick: { canAttack: true, cooldownTimer: 0 },<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.physicsObjects = [];<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.comboCounter = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.lastAttackTime = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Debug visual<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.createDebugVisualization();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createDebugVisualization() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Cria um cone para visualizar o alcance do ataque (debug)<\/code><\/div>\n<div><code>\u00a0 \u00a0 const geometry = new THREE.ConeGeometry(1, 1, 16);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const material = new THREE.MeshBasicMaterial({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: 0x00ff00,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 transparent: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 opacity: 0.2,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 wireframe: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh = new THREE.Mesh(geometry, material);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.visible = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(this.debugMesh);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 addPhysicsObject(physicsObj) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.physicsObjects.push(physicsObj);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 tryAttack(type, isJumping) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.model || isJumping) return false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const state = this.attackState[type];<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!state || !state.canAttack) return false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const config = CONFIG.ATTACK[type.toUpperCase()];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Bloqueia novo ataque<\/code><\/div>\n<div><code>\u00a0 \u00a0 state.canAttack = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 state.cooldownTimer = config.COOLDOWN;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Atualiza combo<\/code><\/div>\n<div><code>\u00a0 \u00a0 const now = Date.now();<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (now - this.lastAttackTime &lt; 1500) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.comboCounter++;<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.comboCounter = 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.lastAttackTime = now;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Agenda o hit no momento apropriado da anima\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 () =&gt; this.performAttack(type, config),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 config.HIT_WINDOW * 1000<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 return true;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 performAttack(type, config) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.model) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Posi\u00e7\u00e3o e dire\u00e7\u00e3o do personagem<\/code><\/div>\n<div><code>\u00a0 \u00a0 const yaw = this.model.rotation.y;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const forward = new THREE.Vector3(Math.sin(yaw), 0, Math.cos(yaw));<\/code><\/div>\n<div><code>\u00a0 \u00a0 const origin = this.model.position.clone();<\/code><\/div>\n<div><code>\u00a0 \u00a0 origin.y += 50; \/\/ Altura aproximada do soco\/chute<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Detecta hits<\/code><\/div>\n<div><code>\u00a0 \u00a0 const result = this.detectHits(origin, forward, config);<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (result.hits.length &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 result.hits.forEach((hit) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.applyHitEffects(hit, config, type);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const firstHit = result.hits[0];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.showCombatHit(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 type,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 config.DAMAGE * this.comboCounter,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 firstHit.distance,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.comboCounter<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.showCombatMiss(type, result.reason);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Visualiza\u00e7\u00e3o debug (tempor\u00e1ria)<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.showDebugVisualization(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 origin,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 forward,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 config,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 result.hits.length &gt; 0<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 detectHits(origin, forward, config) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const hits = [];<\/code><\/div>\n<div><code>\u00a0 \u00a0 let closestDistance = Infinity;<\/code><\/div>\n<div><code>\u00a0 \u00a0 let reason = \"default\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.physicsObjects.forEach((physObj) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (!physObj.mesh.visible) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const toObj = physObj.mesh.position.clone().sub(origin);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const horizontal = new THREE.Vector3(toObj.x, 0, toObj.z);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const dist = horizontal.length();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Verifica dist\u00e2ncia m\u00ednima<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (dist &lt; config.MIN_RANGE) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (dist &lt; closestDistance) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 closestDistance = dist;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reason = \"too-close\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Verifica dist\u00e2ncia m\u00e1xima<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (dist &gt; config.MAX_RANGE) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (dist &lt; closestDistance) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 closestDistance = dist;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reason = \"too-far\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Verifica \u00e2ngulo<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const angle = Math.acos(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 Math.max(-1, Math.min(1, forward.dot(horizontal.normalize())))<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (angle &gt; config.ANGLE \/ 2) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (dist &lt; closestDistance) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 closestDistance = dist;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 reason = \"wrong-angle\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Hit v\u00e1lido!<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 hits.push({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 physicsObject: physObj,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 distance: dist,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 direction: horizontal.normalize(),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 angle: angle,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 impactPoint: physObj.mesh.position.clone(),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Ordena por dist\u00e2ncia (mais pr\u00f3ximo primeiro)<\/code><\/div>\n<div><code>\u00a0 \u00a0 hits.sort((a, b) =&gt; a.distance - b.distance);<\/code><\/div>\n<div><code>\u00a0 \u00a0 return { hits, reason };<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 applyHitEffects(hit, config, type) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const { physicsObject, distance, direction, impactPoint } = hit;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Calcula for\u00e7a baseada na dist\u00e2ncia (mais perto = mais for\u00e7a)<\/code><\/div>\n<div><code>\u00a0 \u00a0 const distanceFactor =<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 1 - (distance - config.MIN_RANGE) \/ (config.MAX_RANGE - config.MIN_RANGE);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const comboMultiplier = 1.0 + (this.comboCounter - 1) * 0.3;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const totalForce =<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 config.FORCE * config.KNOCKBACK * distanceFactor * comboMultiplier;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica for\u00e7a com componente vertical<\/code><\/div>\n<div><code>\u00a0 \u00a0 const forceVector = direction.clone().multiplyScalar(totalForce);<\/code><\/div>\n<div><code>\u00a0 \u00a0 forceVector.y = totalForce * 0.4; \/\/ Lan\u00e7a o objeto para cima<\/code><\/div>\n<div><code>\u00a0 \u00a0 physicsObject.applyForce(forceVector, impactPoint);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplica dano<\/code><\/div>\n<div><code>\u00a0 \u00a0 const damage = Math.round(config.DAMAGE * comboMultiplier);<\/code><\/div>\n<div><code>\u00a0 \u00a0 physicsObject.takeDamage(damage);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Efeito visual de impacto<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.createImpactEffect(impactPoint, type);<\/code><\/div>\n<div><code>\u00a0 \u00a0 console.log(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 `\ud83d\udca5 ${type.toUpperCase()} | Dist: ${Math.round(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 distance<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 )}u | For\u00e7a: ${Math.round(totalForce)} | DMG: ${damage} | Combo: x${<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.comboCounter<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }`<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createImpactEffect(position, type) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Cria part\u00edculas no ponto de impacto<\/code><\/div>\n<div><code>\u00a0 \u00a0 const particleCount = 15;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const geometry = new THREE.SphereGeometry(2, 4, 4);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const color = type === \"kick\" ? 0xff6b6b : 0xffdd57;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const material = new THREE.MeshBasicMaterial({ color: color });<\/code><\/div>\n<div><code>\u00a0 \u00a0 for (let i = 0; i &lt; particleCount; i++) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const particle = new THREE.Mesh(geometry, material.clone());<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 particle.position.copy(position);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const velocity = new THREE.Vector3(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (Math.random() - 0.5) * 100,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 Math.random() * 80 + 40,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (Math.random() - 0.5) * 100<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.scene.add(particle);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Anima part\u00edcula<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const startTime = Date.now();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const animate = () =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const elapsed = (Date.now() - startTime) \/ 1000;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (elapsed &lt; 0.5) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.position.add(velocity.clone().multiplyScalar(0.016));<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 velocity.y -= 300 * 0.016; \/\/ Gravidade<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.scale.multiplyScalar(0.95);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.material.opacity = 1 - elapsed * 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.material.transparent = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 requestAnimationFrame(animate);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.scene.remove(particle);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.geometry.dispose();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 particle.material.dispose();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 animate();<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 showDebugVisualization(origin, forward, config, hit) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.debugMesh) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Configura o cone de debug<\/code><\/div>\n<div><code>\u00a0 \u00a0 const radius = Math.tan(config.ANGLE \/ 2) * config.MAX_RANGE;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.scale.set(radius, config.MAX_RANGE, radius);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.position.copy(origin);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.rotation.x = Math.PI \/ 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.rotation.z = -Math.atan2(forward.x, forward.z);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.material.color.setHex(hit ? 0x00ff00 : 0xff0000);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.debugMesh.visible = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Esconde ap\u00f3s 200ms<\/code><\/div>\n<div><code>\u00a0 \u00a0 setTimeout(() =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (this.debugMesh) this.debugMesh.visible = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }, 200);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 update(delta) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Atualiza cooldowns<\/code><\/div>\n<div><code>\u00a0 \u00a0 Object.keys(this.attackState).forEach((type) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const state = this.attackState[type];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (state.cooldownTimer &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 state.cooldownTimer -= delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (state.cooldownTimer &lt;= 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 state.cooldownTimer = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 state.canAttack = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Reset combo<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (Date.now() - this.lastAttackTime &gt; 2000) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.comboCounter = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Atualiza f\u00edsica dos objetos<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.physicsObjects.forEach((physObj) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 physObj.update(delta);<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>}<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ M\u00d3DULO: GERENCIADOR DE ANIMA\u00c7\u00d5ES<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>class AnimationManager {<\/code><\/div>\n<div><code>\u00a0 constructor(mixer) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mixer = mixer;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.actions = {};<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.activeAction = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.currentActionName = \"\";<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 addAction(name, clip) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.actions[name] = this.mixer.clipAction(clip);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 play(name, loop = true) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.actions[name] || this.currentActionName === name) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const newAction = this.actions[name];<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (loop) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 newAction.loop = THREE.LoopRepeat;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 newAction.clampWhenFinished = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 newAction.loop = THREE.LoopOnce;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 newAction.clampWhenFinished = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.activeAction &amp;&amp; this.activeAction !== newAction) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.activeAction.fadeOut(CONFIG.ANIMATION_FADE_DURATION);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 newAction.reset().fadeIn(CONFIG.ANIMATION_FADE_DURATION).play();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.activeAction = newAction;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.currentActionName = name;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 findAndPlay(partialName, loop = true) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const name = Object.keys(this.actions).find((k) =&gt;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 k.toLowerCase().includes(partialName.toLowerCase())<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (name) this.play(name, loop);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 update(delta) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.mixer) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.mixer.update(delta);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 getActionNames() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 return Object.keys(this.actions);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 setSpeed(speed) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.mixer) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.mixer.timeScale = speed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>}<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ CONTROLADOR PRINCIPAL<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>class CharacterController {<\/code><\/div>\n<div><code>\u00a0 constructor() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ui = new UIManager();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ui.createLoadingScreen();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.initScene();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.initLights();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.initGround();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.initControls();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.initInput();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.loader = new FBXLoader();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.clock = new THREE.Clock();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Estado do personagem<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.model = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.mixer = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.animManager = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.combatSystem = null;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ F\u00edsica<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.velocity = new THREE.Vector3(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.isOnGround = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.isJumping = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.coyoteTimer = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.jumpPhase = \"none\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Input<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.keys = {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 forward: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 backward: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 left: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 right: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 jump: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 run: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ C\u00e2mera<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.cameraOffset = new THREE.Vector3(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 0,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.CAMERA_HEIGHT,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.CAMERA_DISTANCE<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Colis\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.collidableObjects = [];<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.gui = null;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 initScene() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene = new THREE.Scene();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.background = new THREE.Color(0x87ceeb);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.fog = new THREE.Fog(0x87ceeb, 600, 1200);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.camera = new THREE.PerspectiveCamera(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.CAMERA_FOV,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 window.innerWidth \/ window.innerHeight,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 0.1,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 2000<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.camera.position.set(0, CONFIG.CAMERA_HEIGHT, CONFIG.CAMERA_DISTANCE);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer = new THREE.WebGLRenderer({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 antialias: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 powerPreference: \"high-performance\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.setSize(window.innerWidth, window.innerHeight);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.shadowMap.enabled = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.outputColorSpace = THREE.SRGBColorSpace;<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.body.appendChild(this.renderer.domElement);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 initLights() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight = new THREE.DirectionalLight(0xffffff, 3);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight.position.set(150, 250, 100);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight.castShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const shadowCam = this.dirLight.shadow.camera;<\/code><\/div>\n<div><code>\u00a0 \u00a0 shadowCam.top = shadowCam.right = 400;<\/code><\/div>\n<div><code>\u00a0 \u00a0 shadowCam.bottom = shadowCam.left = -400;<\/code><\/div>\n<div><code>\u00a0 \u00a0 shadowCam.near = 0.1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 shadowCam.far = 600;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight.shadow.mapSize.set(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.SHADOW_MAP_SIZE,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.SHADOW_MAP_SIZE<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight.shadow.bias = -0.0001;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.dirLight.shadow.radius = 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(this.dirLight);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const ambient = new THREE.AmbientLight(0x666666, 2);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(ambient);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.5);<\/code><\/div>\n<div><code>\u00a0 \u00a0 hemiLight.position.set(0, 300, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(hemiLight);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 initGround() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const groundGeometry = new THREE.PlaneGeometry(2000, 2000);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const groundMaterial = new THREE.MeshStandardMaterial({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 color: 0x558855,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 roughness: 0.8,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 metalness: 0.2,<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ground = new THREE.Mesh(groundGeometry, groundMaterial);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ground.rotation.x = -Math.PI \/ 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ground.receiveShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(this.ground);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.gridHelper = new THREE.GridHelper(2000, 100, 0x888888, 0x444444);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.gridHelper.position.y = 0.1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.scene.add(this.gridHelper);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 createEnvironmentObjects() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const cubeGeometry = new THREE.BoxGeometry(50, 50, 50);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const positions = [<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [200, 25, 0],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [-200, 25, 0],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [0, 25, 200],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [0, 25, -200],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [200, 25, 200],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 [-200, 25, -200],<\/code><\/div>\n<div><code>\u00a0 \u00a0 ];<\/code><\/div>\n<div><code>\u00a0 \u00a0 positions.forEach((pos, i) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const material = new THREE.MeshStandardMaterial({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 color: new THREE.Color().setHSL(i \/ positions.length, 0.7, 0.5),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 roughness: 0.5,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 metalness: 0.3,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const cube = new THREE.Mesh(cubeGeometry, material);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 cube.position.set(...pos);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 cube.castShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 cube.receiveShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.scene.add(cube);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Adiciona f\u00edsica ao cubo<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const physicsObj = new PhysicsObject(cube);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatSystem.addPhysicsObject(physicsObj);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Adiciona \u00e0 lista de objetos colid\u00edveis<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.collidableObjects.push({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 mesh: cube,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 radius: CONFIG.OBJECT_RADIUS,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 initControls() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls = new OrbitControls(this.camera, this.renderer.domElement);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.enableDamping = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.dampingFactor = 0.05;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.target.set(0, 100, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.minDistance = 150;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.maxDistance = 600;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.maxPolarAngle = Math.PI \/ 2 - 0.05;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.enablePan = false;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 initInput() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.addEventListener(\"keydown\", (e) =&gt; this.onKeyDown(e));<\/code><\/div>\n<div><code>\u00a0 \u00a0 document.addEventListener(\"keyup\", (e) =&gt; this.onKeyUp(e));<\/code><\/div>\n<div><code>\u00a0 \u00a0 window.addEventListener(\"resize\", () =&gt; this.onResize());<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.ui.createInstructions();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 onKeyDown(e) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (e.repeat) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 switch (e.code) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyW\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowUp\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.forward = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyS\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowDown\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.backward = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyA\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowLeft\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.left = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyD\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowRight\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.right = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"Space\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 e.preventDefault();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.jump = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ShiftLeft\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ShiftRight\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.run = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyR\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.resetPosition();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyJ\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.attack(\"punch\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyK\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.attack(\"kick\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 onKeyUp(e) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 switch (e.code) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyW\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowUp\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.forward = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyS\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowDown\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.backward = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyA\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowLeft\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.left = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"KeyD\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ArrowRight\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.right = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"Space\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.jump = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ShiftLeft\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 case \"ShiftRight\":<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.keys.run = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 break;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 onResize() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.camera.aspect = window.innerWidth \/ window.innerHeight;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.camera.updateProjectionMatrix();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.setSize(window.innerWidth, window.innerHeight);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 attack(type) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.combatSystem) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const success = this.combatSystem.tryAttack(type, this.isJumping);<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (success) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(type, false);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 async loadFBX(filename) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 return new Promise((resolve, reject) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.loader.load(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 `.\/models\/${filename}`,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (obj) =&gt; resolve(obj),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (xhr) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const percent = (xhr.loaded \/ xhr.total) * 100;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 console.log(`\ud83d\udce6 ${filename}: ${percent.toFixed(0)}%`);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 },<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (err) =&gt; reject(err)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 async loadModelAndAnimations() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const animationFiles = [<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"Idle.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"Jumping.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"jumping-down.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"running.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"punch.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"kick.fbx\",<\/code><\/div>\n<div><code>\u00a0 \u00a0 ];<\/code><\/div>\n<div><code>\u00a0 \u00a0 try {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(10, \"Carregando modelo principal...\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const mainModel = await this.loadFBX(animationFiles[0]);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 mainModel.scale.setScalar(CONFIG.MODEL_SCALE);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 mainModel.traverse((child) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (child.isMesh) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 child.castShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 child.receiveShadow = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if (child.material) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 child.material.needsUpdate = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 child.material.side = THREE.FrontSide;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.scene.add(mainModel);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model = mainModel;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.mixer = new THREE.AnimationMixer(this.model);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager = new AnimationManager(this.mixer);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatSystem = new CombatSystem(this.model, this.scene, this.ui);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(30, \"Configurando anima\u00e7\u00f5es...\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (mainModel.animations?.length &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const clip = mainModel.animations[0];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const clipName = this.getClipName(animationFiles[0], clip, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.animManager.addAction(clipName, clip);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const progressStep = 50 \/ animationFiles.length;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 for (let i = 1; i &lt; animationFiles.length; i++) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const filename = animationFiles[i];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 30 + progressStep * i,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 `Carregando ${filename}...`<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const obj = await this.loadFBX(filename);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (obj.animations?.length &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const clip = obj.animations[0];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const clipName = this.getClipName(filename, clip, i);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.animManager.addAction(clipName, clip);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Cria objetos com f\u00edsica DEPOIS do combatSystem estar pronto<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.createEnvironmentObjects();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(90, \"Finalizando...\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.setupGUI();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(100, \"Pronto!\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 setTimeout(() =&gt; this.ui.hideLoadingScreen(), 500);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 console.log(\"\ud83c\udf89 Sistema de combate carregado!\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 } catch (err) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 console.error(\"\u274c Erro ao carregar assets:\", err);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.ui.updateLoadingProgress(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 0,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \"Erro ao carregar! Verifique o console.\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 getClipName(filename, clip, index) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const baseName = filename.replace(\".fbx\", \"\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 return `${baseName}_${clip.name || `Anim_${index}`}`;<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 updateJumpAnimation() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.model || !this.animManager) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const velocityY = this.velocity.y;<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (velocityY &gt; CONFIG.JUMP_UP_THRESHOLD &amp;&amp; this.jumpPhase !== \"rising\") {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.jumpPhase = \"rising\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"jumping\", false);<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else if (<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 velocityY &lt; CONFIG.JUMP_DOWN_THRESHOLD &amp;&amp;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.jumpPhase !== \"falling\"<\/code><\/div>\n<div><code>\u00a0 \u00a0 ) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.jumpPhase = \"falling\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"jumping-down\", false);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 tryJump() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 const canJump =<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 (this.isOnGround || this.coyoteTimer &gt; 0) &amp;&amp; !this.isJumping;<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (canJump) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.y = CONFIG.JUMP_VELOCITY;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isOnGround = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isJumping = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.jumpPhase = \"rising\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.coyoteTimer = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"jumping\", false);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 updateMovement(delta) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.model) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.isOnGround &amp;&amp; this.coyoteTimer &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.coyoteTimer -= delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplicar gravidade<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.velocity.y = Math.max(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.y + CONFIG.GRAVITY * delta,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 CONFIG.MAX_FALL_SPEED<\/code><\/div>\n<div><code>\u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Processar pulo<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.keys.jump &amp;&amp; !this.isJumping) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.tryJump();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.keys.jump = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Atualiza anima\u00e7\u00e3o de pulo<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateJumpAnimation();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Movimento horizontal<\/code><\/div>\n<div><code>\u00a0 \u00a0 const speedMultiplier = this.keys.run ? CONFIG.RUN_SPEED_MULTIPLIER : 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const moveSpeed = CONFIG.MOVEMENT_SPEED * speedMultiplier * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const moveDir = new THREE.Vector3(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 let isMoving = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.keys.forward) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 moveDir.z -= 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 isMoving = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.keys.backward) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 moveDir.z += 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 isMoving = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.keys.left) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 moveDir.x -= 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 isMoving = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.keys.right) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 moveDir.x += 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 isMoving = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (moveDir.lengthSq() &gt; 0) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 moveDir.normalize();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Rota\u00e7\u00e3o suave em dire\u00e7\u00e3o ao movimento<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const targetAngle = Math.atan2(moveDir.x, moveDir.z);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const currentAngle = this.model.rotation.y;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 let angleDiff = targetAngle - currentAngle;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Normaliza para o caminho mais curto<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 while (angleDiff &gt; Math.PI) angleDiff -= Math.PI * 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 while (angleDiff &lt; -Math.PI) angleDiff += Math.PI * 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Aplica rota\u00e7\u00e3o suave<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model.rotation.y += angleDiff * CONFIG.ROTATION_SPEED * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Calcula nova posi\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const moveDist = moveSpeed;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const newX = this.model.position.x + Math.sin(targetAngle) * moveDist;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const newZ = this.model.position.z + Math.cos(targetAngle) * moveDist;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Verifica colis\u00e3o antes de mover<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const newPos = new THREE.Vector3(newX, this.model.position.y, newZ);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (!this.checkCollision(newPos)) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.model.position.x = newX;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.model.position.z = newZ;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \/\/ Tenta deslizar ao longo da parede<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const slideX = new THREE.Vector3(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 newX,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.y,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.z<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const slideZ = new THREE.Vector3(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.x,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.y,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 newZ<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (!this.checkCollision(slideX)) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.x = newX;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 } else if (!this.checkCollision(slideZ)) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.model.position.z = newZ;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Trocar anima\u00e7\u00e3o (exceto durante pulo ou ataque)<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 !this.isJumping &amp;&amp;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatSystem.attackState.punch.canAttack &amp;&amp;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatSystem.attackState.kick.canAttack<\/code><\/div>\n<div><code>\u00a0 \u00a0 ) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (isMoving) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"running\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"idle\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Aplicar velocidade vertical<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.model.position.y += this.velocity.y * delta;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Colis\u00e3o com ch\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.model.position.y &lt;= CONFIG.GROUND_LEVEL) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model.position.y = CONFIG.GROUND_LEVEL;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.y = 0;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (this.isJumping) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.isJumping = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.jumpPhase = \"none\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isOnGround = true;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.coyoteTimer = CONFIG.COYOTE_TIME;<\/code><\/div>\n<div><code>\u00a0 \u00a0 } else {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (this.isOnGround) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.coyoteTimer = CONFIG.COYOTE_TIME;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isOnGround = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 checkCollision(newPosition) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Verifica colis\u00e3o com todos os objetos colid\u00edveis<\/code><\/div>\n<div><code>\u00a0 \u00a0 for (let i = 0; i &lt; this.collidableObjects.length; i++) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const obj = this.collidableObjects[i];<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Ignora objetos invis\u00edveis (destru\u00eddos)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (!obj.mesh.visible) continue;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const objPos = obj.mesh.position;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const distance = new THREE.Vector2(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 newPosition.x - objPos.x,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 newPosition.z - objPos.z<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 ).length();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const minDistance = CONFIG.CHARACTER_RADIUS + obj.radius;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 if (distance &lt; minDistance) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 return true; \/\/ Colis\u00e3o detectada<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 return false; \/\/ Sem colis\u00e3o<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 updateCamera() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (!this.model) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const targetPos = this.model.position.clone();<\/code><\/div>\n<div><code>\u00a0 \u00a0 const targetLookAt = targetPos.clone();<\/code><\/div>\n<div><code>\u00a0 \u00a0 targetLookAt.y += 100;<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.target.lerp(targetLookAt, CONFIG.CAMERA_FOLLOW_SMOOTHNESS);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 resetCamera() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.camera.position.set(0, CONFIG.CAMERA_HEIGHT, CONFIG.CAMERA_DISTANCE);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.target.set(0, 100, 0);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 resetPosition() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.model) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model.position.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model.rotation.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.velocity.set(0, 0, 0);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.isJumping = false;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.jumpPhase = \"none\";<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.findAndPlay(\"idle\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.resetCamera();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 setupGUI() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.gui = new GUI();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.gui.title(\"\u2694\ufe0f Controles de Combate\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 const animNames = this.animManager.getActionNames();<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (animNames.length === 0) return;<\/code><\/div>\n<div><code>\u00a0 \u00a0 const guiControls = {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 animation: animNames[0],<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 speed: 1.0,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 lightIntensity: 3,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 shadowsEnabled: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 showGrid: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 debugVisualization: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 showColliders: false,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 punchRange: CONFIG.ATTACK.PUNCH.MAX_RANGE,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 kickRange: CONFIG.ATTACK.KICK.MAX_RANGE,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 jump: () =&gt; this.tryJump(),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 punch: () =&gt; this.attack(\"punch\"),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 kick: () =&gt; this.attack(\"kick\"),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 resetCamera: () =&gt; this.resetCamera(),<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 resetPosition: () =&gt; this.resetPosition(),<\/code><\/div>\n<div><code>\u00a0 \u00a0 };<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Pasta de Anima\u00e7\u00f5es<\/code><\/div>\n<div><code>\u00a0 \u00a0 const animFolder = this.gui.addFolder(\"\ud83c\udfac Anima\u00e7\u00f5es\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 animFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"animation\", animNames)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Anima\u00e7\u00e3o\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((n) =&gt; this.animManager.play(n));<\/code><\/div>\n<div><code>\u00a0 \u00a0 animFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"speed\", 0.1, 3, 0.1)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Velocidade\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; this.animManager.setSpeed(v));<\/code><\/div>\n<div><code>\u00a0 \u00a0 animFolder.add(guiControls, \"jump\").name(\"\u2b06\ufe0f Pular\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Pasta de Combate<\/code><\/div>\n<div><code>\u00a0 \u00a0 const combatFolder = this.gui.addFolder(\"\u2694\ufe0f Combate\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder.add(guiControls, \"punch\").name(\"\ud83d\udc4a Soco (J)\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder.add(guiControls, \"kick\").name(\"\ud83e\uddb5 Chute (K)\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"punchRange\", 50, 200, 10)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Alcance Soco\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; (CONFIG.ATTACK.PUNCH.MAX_RANGE = v));<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"kickRange\", 80, 250, 10)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Alcance Chute\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; (CONFIG.ATTACK.KICK.MAX_RANGE = v));<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"debugVisualization\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Debug Visual\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 if (this.combatSystem.debugMesh) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 this.combatSystem.debugMesh.visible = v;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"showColliders\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Mostrar Colis\u00f5es\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; this.toggleColliderVisualization(v));<\/code><\/div>\n<div><code>\u00a0 \u00a0 combatFolder.open();<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Pasta de Visual<\/code><\/div>\n<div><code>\u00a0 \u00a0 const visualFolder = this.gui.addFolder(\"\ud83d\udca1 Visual\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 visualFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"lightIntensity\", 0, 10, 0.5)<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Intensidade da Luz\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; (this.dirLight.intensity = v));<\/code><\/div>\n<div><code>\u00a0 \u00a0 visualFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"shadowsEnabled\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Sombras\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.renderer.shadowMap.enabled = v;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.dirLight.castShadow = v;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 visualFolder<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .add(guiControls, \"showGrid\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .name(\"Mostrar Grade\")<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 .onChange((v) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.gridHelper.visible = v;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Pasta de Personagem<\/code><\/div>\n<div><code>\u00a0 \u00a0 const charFolder = this.gui.addFolder(\"\ud83c\udfae Personagem\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 charFolder.add(guiControls, \"resetCamera\").name(\"\ud83d\udd04 Resetar C\u00e2mera\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 charFolder.add(guiControls, \"resetPosition\").name(\"\ud83d\udd04 Resetar Posi\u00e7\u00e3o\");<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Inicia com a primeira anima\u00e7\u00e3o<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.animManager.play(animNames[0]);<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 toggleColliderVisualization(show) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \/\/ Remove visualiza\u00e7\u00f5es anteriores<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.colliderHelpers) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.colliderHelpers.forEach((helper) =&gt; this.scene.remove(helper));<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.colliderHelpers = [];<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (show) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Adiciona c\u00edrculo para o personagem<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const charGeometry = new THREE.CircleGeometry(<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 CONFIG.CHARACTER_RADIUS,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 32<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 );<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const charMaterial = new THREE.MeshBasicMaterial({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 color: 0x00ff00,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 transparent: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 opacity: 0.3,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 side: THREE.DoubleSide,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 const charHelper = new THREE.Mesh(charGeometry, charMaterial);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 charHelper.rotation.x = -Math.PI \/ 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 charHelper.position.y = 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.model.add(charHelper);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.colliderHelpers.push(charHelper);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \/\/ Adiciona c\u00edrculos para os objetos<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.collidableObjects.forEach((obj) =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const objGeometry = new THREE.CircleGeometry(obj.radius, 32);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const objMaterial = new THREE.MeshBasicMaterial({<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 color: 0xff0000,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 transparent: true,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 opacity: 0.3,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 side: THREE.DoubleSide,<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 const objHelper = new THREE.Mesh(objGeometry, objMaterial);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 objHelper.rotation.x = -Math.PI \/ 2;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 objHelper.position.y = 1;<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 obj.mesh.add(objHelper);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 this.colliderHelpers.push(objHelper);<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 });<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>\u00a0 animate = () =&gt; {<\/code><\/div>\n<div><code>\u00a0 \u00a0 requestAnimationFrame(this.animate);<\/code><\/div>\n<div><code>\u00a0 \u00a0 const delta = Math.min(this.clock.getDelta(), 0.1);<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.animManager) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.animManager.update(delta);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 if (this.combatSystem) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 this.combatSystem.update(delta);<\/code><\/div>\n<div><code>\u00a0 \u00a0 }<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateMovement(delta);<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.updateCamera();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.controls.update();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.renderer.render(this.scene, this.camera);<\/code><\/div>\n<div><code>\u00a0 };<\/code><\/div>\n<div><code>\u00a0 async init() {<\/code><\/div>\n<div><code>\u00a0 \u00a0 await this.loadModelAndAnimations();<\/code><\/div>\n<div><code>\u00a0 \u00a0 this.animate();<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>}<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>\/\/ EXECUTAR<\/code><\/div>\n<div><code>\/\/ ===================================<\/code><\/div>\n<div><code>const controller = new CharacterController();<\/code><\/div>\n<div><code>controller.init().catch((err) =&gt; {<\/code><\/div>\n<div><code>\u00a0 console.error(\"\u274c Erro :\", err);<\/code><\/div>\n<div><code>\u00a0 const loader = document.getElementById(\"loading-screen\");<\/code><\/div>\n<div><code>\u00a0 if (loader) {<\/code><\/div>\n<div><code>\u00a0 \u00a0 loader.querySelector(\"#loading-text\").textContent =<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \"Erro ao carregar! Verifique se os arquivos FBX est\u00e3o na pasta .\/models\/\";<\/code><\/div>\n<div><code>\u00a0 }<\/code><\/div>\n<div><code>});<\/code><\/div>\n<\/div>\n<pre><code>\r\n<\/code><\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p><span style=\"font-family: arial, helvetica, sans-serif;\"><strong><span style=\"font-size: 14pt;\">Voc\u00ea pode visualizar o jogo no link abaixo:<\/span><\/strong><\/span><\/p>\n<p><strong><span style=\"font-family: arial, helvetica, sans-serif; font-size: 12pt;\"><a href=\"https:\/\/urutaudev.com.br\/games\/game-treejs-site\">Game Exemplo com TreeJs<\/a>\u00a0<\/span><\/strong><\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"font-family: arial, helvetica, sans-serif;\"><strong><span style=\"font-size: 14pt;\">Deixo aqui um v\u00eddeo curto do Jogo:<\/span><\/strong><\/span><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p><iframe loading=\"lazy\" title=\"Game com TreeJs\" width=\"800\" height=\"450\" src=\"https:\/\/www.youtube.com\/embed\/NqzUqsIZq3I?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2><\/h2>\n<p>&nbsp;<\/p>\n<p><iframe loading=\"lazy\" title=\"Game com TreeJs  #webdevelopment #javascript #programming\" width=\"563\" height=\"1000\" src=\"https:\/\/www.youtube.com\/embed\/Dz7AeZahvIQ?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>&nbsp;<\/p>\n<h2><\/h2>\n<h2><span style=\"font-family: arial, helvetica, sans-serif;\">Conclus\u00e3o<\/span><\/h2>\n<p><span style=\"font-size: 14pt; font-family: arial, helvetica, sans-serif;\">O Three.js mostra que criar jogos ou anima\u00e7\u00f5es 3D com poucas linhas de c\u00f3digo \u00e9 mais simples e acess\u00edvel do que parece.<br \/>\nCom um pouco de criatividade e curiosidade, qualquer pessoa pode transformar o seu site ou blog em um mundo cheio de luzes, cores e movimento.<br \/>\n<\/span><\/p>\n<p>&nbsp;<\/p>\n<h3><span style=\"font-family: arial, helvetica, sans-serif; font-size: 14pt;\">C\u00f3digo fonte do jogo em Javascript:<\/span><\/h3>\n<p><span style=\"font-family: arial, helvetica, sans-serif; font-size: 12pt;\"><strong><a href=\"https:\/\/github.com\/JrDevCJ\/javascript-games\/tree\/main\/game-treejs-site\" target=\"_blank\" rel=\"noopener\">C\u00f3digo Fonte<\/a><\/strong><\/span><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Game Com TreeJs &nbsp; &nbsp; Introdu\u00e7\u00e3o Game com Three.js, explorando um pouco desta lib que ajuda na cria\u00e7\u00e3o de games<\/p>\n","protected":false},"author":1,"featured_media":1259,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_container_layout":"default_layout","colormag_page_sidebar_layout":"default_layout","footnotes":""},"categories":[1,101],"tags":[57,114,81,27,26,173,18],"class_list":["post-1250","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","category-games","tag-css-tutorial","tag-game","tag-games","tag-html","tag-javascript","tag-treejs","tag-tutorias"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Game Com TreeJs - UrutauDev<\/title>\n<meta name=\"description\" content=\"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Game Com TreeJs - UrutauDev\" \/>\n<meta property=\"og:description\" content=\"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/\" \/>\n<meta property=\"og:site_name\" content=\"UrutauDev\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-13T03:17:49+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-14T00:27:50+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1673\" \/>\n\t<meta property=\"og:image:height\" content=\"896\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"C. Junior\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"C. Junior\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/\"},\"author\":{\"name\":\"C. Junior\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#\\\/schema\\\/person\\\/7e321618d23a158d5a42d5cfbdd99dd6\"},\"headline\":\"Game Com TreeJs\",\"datePublished\":\"2025-11-13T03:17:49+00:00\",\"dateModified\":\"2025-11-14T00:27:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/\"},\"wordCount\":241,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/imagem_2025-11-13_004533980.png\",\"keywords\":[\"css tutorial\",\"game\",\"games\",\"html\",\"javascript\",\"treejs\",\"tutorias\"],\"articleSection\":[\"Blog\",\"Games\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/\",\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/\",\"name\":\"Game Com TreeJs - UrutauDev\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/imagem_2025-11-13_004533980.png\",\"datePublished\":\"2025-11-13T03:17:49+00:00\",\"dateModified\":\"2025-11-14T00:27:50+00:00\",\"description\":\"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#primaryimage\",\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/imagem_2025-11-13_004533980.png\",\"contentUrl\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2025\\\/11\\\/imagem_2025-11-13_004533980.png\",\"width\":1673,\"height\":896,\"caption\":\"Game Com TreeJs\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/2025\\\/11\\\/13\\\/game-com-treejs\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"In\u00edcio\",\"item\":\"https:\\\/\\\/urutaudev.com.br\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Game Com TreeJs\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#website\",\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/\",\"name\":\"UrutauDev\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/urutaudev.com.br\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#organization\",\"name\":\"UrutauDev\",\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2023\\\/08\\\/cropped-urutau-dev-logo.png\",\"contentUrl\":\"https:\\\/\\\/urutaudev.com.br\\\/wp-content\\\/uploads\\\/2023\\\/08\\\/cropped-urutau-dev-logo.png\",\"width\":100,\"height\":84,\"caption\":\"UrutauDev\"},\"image\":{\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/urutaudev.com.br\\\/#\\\/schema\\\/person\\\/7e321618d23a158d5a42d5cfbdd99dd6\",\"name\":\"C. Junior\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g\",\"caption\":\"C. Junior\"},\"sameAs\":[\"http:\\\/\\\/urutaudev.com.br\",\"instagram.com\\\/urutautec\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC8pDLb5GaYBAoWwQkIzrhRw\"],\"url\":\"https:\\\/\\\/urutaudev.com.br\\\/index.php\\\/author\\\/urutaudev-com-br\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Game Com TreeJs - UrutauDev","description":"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/","og_locale":"en_US","og_type":"article","og_title":"Game Com TreeJs - UrutauDev","og_description":"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!","og_url":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/","og_site_name":"UrutauDev","article_published_time":"2025-11-13T03:17:49+00:00","article_modified_time":"2025-11-14T00:27:50+00:00","og_image":[{"width":1673,"height":896,"url":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png","type":"image\/png"}],"author":"C. Junior","twitter_card":"summary_large_image","twitter_misc":{"Written by":"C. Junior","Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#article","isPartOf":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/"},"author":{"name":"C. Junior","@id":"https:\/\/urutaudev.com.br\/#\/schema\/person\/7e321618d23a158d5a42d5cfbdd99dd6"},"headline":"Game Com TreeJs","datePublished":"2025-11-13T03:17:49+00:00","dateModified":"2025-11-14T00:27:50+00:00","mainEntityOfPage":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/"},"wordCount":241,"commentCount":0,"publisher":{"@id":"https:\/\/urutaudev.com.br\/#organization"},"image":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#primaryimage"},"thumbnailUrl":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png","keywords":["css tutorial","game","games","html","javascript","treejs","tutorias"],"articleSection":["Blog","Games"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/","url":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/","name":"Game Com TreeJs - UrutauDev","isPartOf":{"@id":"https:\/\/urutaudev.com.br\/#website"},"primaryImageOfPage":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#primaryimage"},"image":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#primaryimage"},"thumbnailUrl":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png","datePublished":"2025-11-13T03:17:49+00:00","dateModified":"2025-11-14T00:27:50+00:00","description":"Este post \u00e9 apenas um pequeno exemplo de um script de um game 3D usando apenas Javascript com o aucilixo da lib do Three.js!","breadcrumb":{"@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#primaryimage","url":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png","contentUrl":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2025\/11\/imagem_2025-11-13_004533980.png","width":1673,"height":896,"caption":"Game Com TreeJs"},{"@type":"BreadcrumbList","@id":"https:\/\/urutaudev.com.br\/index.php\/2025\/11\/13\/game-com-treejs\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"In\u00edcio","item":"https:\/\/urutaudev.com.br\/"},{"@type":"ListItem","position":2,"name":"Game Com TreeJs"}]},{"@type":"WebSite","@id":"https:\/\/urutaudev.com.br\/#website","url":"https:\/\/urutaudev.com.br\/","name":"UrutauDev","description":"","publisher":{"@id":"https:\/\/urutaudev.com.br\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/urutaudev.com.br\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/urutaudev.com.br\/#organization","name":"UrutauDev","url":"https:\/\/urutaudev.com.br\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/urutaudev.com.br\/#\/schema\/logo\/image\/","url":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2023\/08\/cropped-urutau-dev-logo.png","contentUrl":"https:\/\/urutaudev.com.br\/wp-content\/uploads\/2023\/08\/cropped-urutau-dev-logo.png","width":100,"height":84,"caption":"UrutauDev"},"image":{"@id":"https:\/\/urutaudev.com.br\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/urutaudev.com.br\/#\/schema\/person\/7e321618d23a158d5a42d5cfbdd99dd6","name":"C. Junior","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e7df4c63012e2f6991311d4037bdc57752b996058cf1396747a7ab74e8ac032e?s=96&d=mm&r=g","caption":"C. Junior"},"sameAs":["http:\/\/urutaudev.com.br","instagram.com\/urutautec","https:\/\/www.youtube.com\/channel\/UC8pDLb5GaYBAoWwQkIzrhRw"],"url":"https:\/\/urutaudev.com.br\/index.php\/author\/urutaudev-com-br\/"}]}},"_links":{"self":[{"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1250","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=1250"}],"version-history":[{"count":12,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1250\/revisions"}],"predecessor-version":[{"id":1263,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/posts\/1250\/revisions\/1263"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/media\/1259"}],"wp:attachment":[{"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=1250"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/categories?post=1250"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/urutaudev.com.br\/index.php\/wp-json\/wp\/v2\/tags?post=1250"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}