feat(jogo): expediente dos mortos, jogo de zumbis em primeira pessoa
Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
This commit is contained in:
1 parent
c7d09d63d4
commit
67b32fcab3
118 files changed
+16730
No files matched your search
@@ -0,0 +1,169 @@
|
||||
"use client";
|
||||
|
||||
import { type PointerEvent, useEffect, useRef, useState } from "react";
|
||||
import { alavanca } from "@/lib/jogo/alavanca";
|
||||
import type { ArmaId } from "@/lib/jogo/tipos";
|
||||
import type { Controle } from "./contrato";
|
||||
import {
|
||||
TOQUE_ATIRAR,
|
||||
TOQUE_BOTAO,
|
||||
TOQUE_DIREITA,
|
||||
TOQUE_EMBAIXO,
|
||||
TOQUE_ESPACO,
|
||||
} from "./medidas";
|
||||
|
||||
const RAIO = 56;
|
||||
const MIRA = 0.005;
|
||||
const FACA: ArmaId = 2;
|
||||
|
||||
// right/bottom e tamanhos dos botões vêm de ./medidas, que o Hud também lê.
|
||||
|
||||
/** "tiro" é o dedo no botão Atirar: segura o tiro e também mira. */
|
||||
type Dedo = { papel: "mover" | "mirar" | "tiro"; x: number; y: number };
|
||||
type Direcional = { x: number; y: number; kx: number; ky: number };
|
||||
|
||||
const BOTAO =
|
||||
"pointer-events-auto flex min-w-14 flex-col items-center justify-center border bg-[color:var(--ink)]/30 px-3 font-[family-name:var(--font-mono)] text-[13px] leading-tight tracking-[0.02em] text-[color:var(--paper)] select-none";
|
||||
|
||||
export function Toque({
|
||||
controle,
|
||||
arma,
|
||||
municao,
|
||||
mirando,
|
||||
}: {
|
||||
controle: Controle;
|
||||
arma: ArmaId;
|
||||
municao: number;
|
||||
mirando: boolean;
|
||||
}) {
|
||||
const dedos = useRef(new Map<number, Dedo>());
|
||||
const apertado = useRef(false);
|
||||
const automatico = useRef(false);
|
||||
const [direcional, setDirecional] = useState<Direcional | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
automatico.current = mirando && arma !== FACA;
|
||||
controle.definirAtirar(apertado.current || automatico.current);
|
||||
}, [controle, mirando, arma]);
|
||||
|
||||
function aoApertar(e: PointerEvent<HTMLDivElement>) {
|
||||
const papel = (e.target as HTMLElement).closest<HTMLElement>("[data-papel]")
|
||||
?.dataset.papel;
|
||||
if (papel === "arma") return;
|
||||
const { clientX: x, clientY: y, pointerId } = e;
|
||||
if (papel === "atirar") {
|
||||
apertado.current = true;
|
||||
controle.definirAtirar(true);
|
||||
dedos.current.set(pointerId, { papel: "tiro", x, y });
|
||||
return;
|
||||
}
|
||||
const lado = x < window.innerWidth / 2 ? "mover" : "mirar";
|
||||
const ocupado = [...dedos.current.values()].some((d) => d.papel === lado);
|
||||
if (ocupado) return;
|
||||
dedos.current.set(pointerId, { papel: lado, x, y });
|
||||
if (lado === "mover") setDirecional({ x, y, kx: 0, ky: 0 });
|
||||
}
|
||||
|
||||
function aoMover(e: PointerEvent<HTMLDivElement>) {
|
||||
const dedo = dedos.current.get(e.pointerId);
|
||||
if (!dedo) return;
|
||||
const dx = e.clientX - dedo.x;
|
||||
const dy = e.clientY - dedo.y;
|
||||
if (dedo.papel !== "mover") {
|
||||
controle.somarOlhar(-dx * MIRA, -dy * MIRA);
|
||||
dedo.x = e.clientX;
|
||||
dedo.y = e.clientY;
|
||||
return;
|
||||
}
|
||||
const [direita, frente] = alavanca(dx, dy, RAIO);
|
||||
controle.definirMover(direita, frente);
|
||||
setDirecional({
|
||||
x: dedo.x,
|
||||
y: dedo.y,
|
||||
kx: direita * RAIO,
|
||||
ky: -frente * RAIO,
|
||||
});
|
||||
}
|
||||
|
||||
function aoSoltar(e: PointerEvent<HTMLDivElement>) {
|
||||
const dedo = dedos.current.get(e.pointerId);
|
||||
if (!dedo) return;
|
||||
dedos.current.delete(e.pointerId);
|
||||
if (dedo.papel === "tiro") {
|
||||
apertado.current = false;
|
||||
controle.definirAtirar(automatico.current);
|
||||
}
|
||||
if (dedo.papel === "mover") {
|
||||
controle.definirMover(0, 0);
|
||||
setDirecional(null);
|
||||
}
|
||||
}
|
||||
|
||||
const armas: { id: ArmaId; nome: string }[] = [
|
||||
{ id: 0, nome: "Pistola" },
|
||||
{ id: 1, nome: "Automática" },
|
||||
{ id: 2, nome: "Faca" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
role="application"
|
||||
aria-label="Controles de toque"
|
||||
className="absolute inset-0 select-none"
|
||||
style={{ touchAction: "none" }}
|
||||
onPointerDown={aoApertar}
|
||||
onPointerMove={aoMover}
|
||||
onPointerUp={aoSoltar}
|
||||
onPointerCancel={aoSoltar}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{direcional && (
|
||||
<div
|
||||
data-testid="direcional"
|
||||
className="pointer-events-none absolute size-28 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-[color:var(--slash)]/50"
|
||||
style={{ left: direcional.x, top: direcional.y }}
|
||||
>
|
||||
<div
|
||||
className="absolute top-1/2 left-1/2 size-12 rounded-full bg-[color:var(--slash)]/40"
|
||||
style={{
|
||||
transform: `translate(calc(-50% + ${direcional.kx}px), calc(-50% + ${direcional.ky}px))`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="pointer-events-none absolute flex flex-col items-end"
|
||||
style={{
|
||||
right: TOQUE_DIREITA,
|
||||
bottom: TOQUE_EMBAIXO,
|
||||
gap: TOQUE_ESPACO,
|
||||
}}
|
||||
>
|
||||
<div className="flex" style={{ gap: TOQUE_ESPACO }}>
|
||||
{armas.map(({ id, nome }) => (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
data-papel="arma"
|
||||
aria-pressed={arma === id}
|
||||
onClick={() => controle.definirArma(id)}
|
||||
style={{ minHeight: TOQUE_BOTAO }}
|
||||
className={`${BOTAO} ${arma === id ? "border-[color:var(--slash)] bg-[color:var(--slash)]/25" : "border-[color:var(--paper)]/40"}`}
|
||||
>
|
||||
{nome}
|
||||
{id === 1 && <span>{municao}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-papel="atirar"
|
||||
style={{ width: TOQUE_ATIRAR, height: TOQUE_ATIRAR }}
|
||||
className={`${BOTAO} border-[color:var(--slash)]/70 active:bg-[color:var(--slash)]/30`}
|
||||
>
|
||||
Atirar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user