0% found this document useful (0 votes)
13 views9 pages

Template HTML Game Script - Metroidvania Game

The document outlines a web-based game titled 'FanTube: Chaos in YouTube City', featuring an HTML5 canvas for gameplay and a FastAPI backend for handling queries. The game includes various controls for player actions such as jumping, attacking, and using skills, along with a simple enemy AI and physics for movement. The code also includes UI elements and game mechanics, allowing for an interactive experience on both mobile and desktop platforms.

Uploaded by

fantubeyt19
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views9 pages

Template HTML Game Script - Metroidvania Game

The document outlines a web-based game titled 'FanTube: Chaos in YouTube City', featuring an HTML5 canvas for gameplay and a FastAPI backend for handling queries. The game includes various controls for player actions such as jumping, attacking, and using skills, along with a simple enemy AI and physics for movement. The code also includes UI elements and game mechanics, allowing for an interactive experience on both mobile and desktop platforms.

Uploaded by

fantubeyt19
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 9

<!

DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FanTube: Chaos in YouTube City</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>

<canvas id="gameCanvas"></canvas>

<div id="ui">
<div class="dir">
<button id="btn-left">◀</button>
<button id="btn-right">▶</button>
</div>

<div class="actions">
<button id="btn-jump">Pular</button>
<button id="btn-attack">Socar</button>
<button id="btn-dodge">Esquiva</button>
</div>

<div class="skills">
<button id="btn-skill1">Dash</button>
<button id="btn-skill2">Hand Likes</button>
<button id="btn-skill3">Dash Frenético</button>
<button id="btn-inventory">Inventário</button>
</div>
</div>

<script src="game.js"></script>
</body>
</html># arquivo: ai_api.py
from fastapi import FastAPI, Request
from pydantic import BaseModel
import random

app = FastAPI(title="MiniIA API 💡")

# Modelo de requisição
class Query(BaseModel):
pergunta: str

# Função "inteligente" simples


def gerar_resposta(texto: str) -> str:
respostas = [
"Hmmm, interessante 😏",
"Não tenho certeza, mas posso tentar! 🔥",
"Isso parece divertido! 🌟",
f"Você disse '{texto}', né? Que legal! 😜"
]
# Retorna uma resposta aleatória
return random.choice(respostas)

@app.post("/perguntar")
async def perguntar(query: Query):
resposta = gerar_resposta(query.pergunta)
return {"resposta": resposta}

# Roda com: uvicorn ai_api:app --reload/* Layout básico */


* { box-sizing: border-box; }
html,body { height:100%; margin:0; font-family: Inter, Arial, sans-serif; -webkit-
user-select:none; -ms-user-select:none; user-select:none; }
body { background: linear-gradient(180deg,#111020 0%, #1b1b2f 100%);
overflow:hidden; }

/* Canvas ocupa a tela */


#gameCanvas { display:block; width:100vw; height:100vh; background: linear-
gradient(180deg,#0b0b12 0%, #111 70%); }

/* UI mobile */
#ui {
position: absolute;
bottom: 12px;
left: 12px;
right: 12px;
display:flex;
justify-content:space-between;
align-items:center;
gap:8px;
pointer-events:none; /* para evitar bloquear o canvas; habilitamos nos buttons */
}

/* grupos */
#ui .dir, #ui .actions, #ui .skills {
display:flex; gap:8px;
pointer-events:auto;
}

/* botões */
#ui button {
min-width:64px;
padding:10px 12px;
border-radius:12px;
border:none;
background: linear-gradient(180deg,#ff66aa,#ff3388);
color:white;
font-weight:700;
font-size:14px;
box-shadow:0 6px 14px rgba(0,0,0,0.4);
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}

/* mais responsivo em telas pequenas */


@media (max-width:420px) {
#ui button { min-width:54px; padding:8px 10px; font-size:13px; }
}/* FanTube: Chaos in YouTube City
Versão web inicial (HTML5 Canvas)
Arquivo: game.js
- Modular, expansível, mobile-friendly
*/

/* ----- Setup canvas ----- */


const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();

/* ----- Utilidades ----- */


function clamp(v,a,b){ return Math.max(a,Math.min(b,v)); }

/* ----- Game state ----- */


const GAME = {
dt: 1/60,
gravity: 0.6,
speedModes: [1, 1.3, 1.9], // médio, rápido, hyper
speedIndex: 0,
slow: false,
map: null,
enemies: [],
bosses: [],
checkpoints: []
};

/* ----- Simple Tilemap / Rooms basic (metroidvania skeleton) ----- */


const MAP = {
width: 3000,
height: 900,
areas: ['YouTube City', 'Cavernas', 'Cyberspace', 'Espaço'],
camera: {x:0,y:0,w:canvas.width, h:canvas.height}
};
GAME.map = MAP;

/* ----- Player (FanTube) ----- */


const player = {
x: 150, y: 0,
w: 48, h: 64,
vx: 0, vy: 0,
speed: 4,
onGround: false,
facing: 1, // 1 right, -1 left
hp: 10,
maxHp: 10,
combo: {step:0, timer:0, cooldown:0.35},
dash: {cool:0, active:false},
abilities: {
dashDirected: true,
handLikes: false,
dashFrenzy: false,
stopTime: false,
hyperPunch: false,
speedClone: false
},
invincible: 0
};

/* Spawn example enemies */


function spawnEnemy(x,y,type='runner'){
const e = {
id: 'e'+Math.random().toString(36).slice(2,7),
x,y,w:40,h:40,
vx: (Math.random()>0.5?1:-1)*1,
type, hp:3, alive:true,
aiTimer:0
};
GAME.enemies.push(e);
}

/* Spawn some enemies across the map */


for(let i=0;i<8;i++){
spawnEnemy(400 + i*200, canvas.height-110, i%2? 'flyer':'runner');
}

/* ----- Controls ----- */


const keys =
{left:false,right:false,jump:false,attack:false,dodge:false,skill1:false,skill2:fal
se,skill3:false};
function bindBtn(id, onStart, onEnd){
const el = document.getElementById(id);
if(!el) return;
el.addEventListener('touchstart', e=>{ e.preventDefault(); onStart(); },
{passive:false});
el.addEventListener('mousedown', e=>{ e.preventDefault(); onStart(); });
el.addEventListener('touchend', e=>{ e.preventDefault(); onEnd(); },
{passive:false});
el.addEventListener('mouseup', e=>{ e.preventDefault(); onEnd(); });
}
/* movement left/right */
bindBtn('btn-left', ()=>keys.left=true, ()=>keys.left=false);
bindBtn('btn-right', ()=>keys.right=true, ()=>keys.right=false);
bindBtn('btn-jump', ()=>{ if(player.onGround) player.vy=-14; });
bindBtn('btn-attack', ()=>attackCombo());
bindBtn('btn-dodge', ()=>dodge());
bindBtn('btn-skill1', ()=>useDash());
bindBtn('btn-skill2', ()=>useHandLikes());
bindBtn('btn-skill3', ()=>useDashFrenzy());
bindBtn('btn-inventory', ()=>openInventory());

/* Keyboard support for desktop testing */


window.addEventListener('keydown', e=>{
if(e.key==='ArrowLeft') keys.left=true;
if(e.key==='ArrowRight') keys.right=true;
if(e.key==='ArrowUp') if(player.onGround) player.vy=-14;
if(e.key==='z') attackCombo();
if(e.key==='x') dodge();
if(e.key==='c') useDash();
if(e.key==='v') useHandLikes();
if(e.key==='b') useDashFrenzy();
});
window.addEventListener('keyup', e=>{
if(e.key==='ArrowLeft') keys.left=false;
if(e.key==='ArrowRight') keys.right=false;
});

/* ----- Game mechanics ----- */

/* Attack combo */
function attackCombo(){
if(player.combo.timer > 0) {
// continue combo
player.combo.step = (player.combo.step + 1) % 4;
} else {
player.combo.step = 0;
}
player.combo.timer = player.combo.cooldown;
// hitbox check on enemies
const range = 60;
GAME.enemies.forEach(e=>{
if(!e.alive) return;
const dx = (e.x + e.w/2) - (player.x + player.w/2);
const dy = Math.abs((e.y + e.h/2) - (player.y + player.h/2));
if(Math.abs(dx) < range && dy < 50){
e.hp -= 1 + (player.abilities.handLikes? 2:0); // handlikes stronger
e.vx = (dx>0)?2:-2;
if(e.hp <= 0) e.alive = false;
}
});
}

/* Dodge - timing can counter */


function dodge(){
player.invincible = 18; // frames
// if timed exactly, would trigger counter (not fully implemented)
// simple visual feedback:
playEffect('dodge', player.x, player.y);
}

/* Dash Directed */
function useDash(){
if(player.dash.cool > 0) return;
player.dash.cool = 90; // frames until reuse
player.dash.active = true;
GAME.slow = true;
// directional aiming: if user presses left/right quickly while dash active,
we'll use that
setTimeout(()=>{ /* placeholder for ending slow */ }, 200);
// implement immediate burst in update()
}

/* Hand Likes */
function useHandLikes(){
if(!player.abilities.handLikes) { /* locked: unlock later */
playEffect('locked'); return; }
// special heavy combo
player.combo.step = 3;
playEffect('handlikes', player.x, player.y);
}

/* Dash Frenzy */
function useDashFrenzy(){
if(!player.abilities.dashFrenzy) { playEffect('locked'); return; }
// set small loop of dashes that circle nearest enemy
playEffect('dashfrenzy', player.x, player.y);
}

/* Parar o tempo placeholder (unlock later) */


function stopTime(){
if(!player.abilities.stopTime) { playEffect('locked'); return; }
// freeze enemies for short time
GAME.enemies.forEach(e=> e.frozen = 45 );
GAME.slow = true;
setTimeout(()=>{ GAME.slow = false; }, 800);
}

/* Effects (simple) */
const effects = [];
function playEffect(name,x=0,y=0){
effects.push({name,x,y,t:30});
}

/* ----- Physics + update ----- */


function update(dt){
// dt fixed small step
// movement
const speedMul = GAME.speedModes[GAME.speedIndex] * (GAME.slow ? 0.45 : 1);
let vx = 0;
if(keys.left) { vx = -player.speed * speedMul; player.facing = -1; }
if(keys.right) { vx = player.speed * speedMul; player.facing = 1; }
player.vx = vx;

// dash active quick burst


if(player.dash.active){
player.vx = 18 * player.facing;
player.dash.active = false;
// short invulnerability
player.invincible = 20;
setTimeout(()=>{ GAME.slow = false; }, 150);
}

// apply vx/vy
player.x += player.vx;
player.vy += GAME.gravity;
player.y += player.vy;

// ground collision (simple)


const groundY = canvas.height - 80;
if(player.y + player.h >= groundY){
player.y = groundY - player.h;
player.vy = 0;
player.onGround = true;
} else player.onGround = false;

// combo timer tick


if(player.combo.timer > 0) player.combo.timer -= dt;

// dash cooldown tick


if(player.dash.cool > 0) player.dash.cool -= 1;

// invincible tick
if(player.invincible > 0) player.invincible -= 1;

// update enemies AI
for(let e of GAME.enemies){
if(!e.alive) continue;
if(e.frozen && e.frozen>0){ e.frozen -= 1; continue; }
e.aiTimer += dt;
// simple horizontal patrol
e.x += e.vx * (1 + (Math.sin(e.aiTimer*2)*0.2));
// gravity
if(e.y + e.h < groundY){ e.y += 2; } else e.y = groundY - e.h;
// collision with player
if(rectsOverlap(e, player) && player.invincible <= 0){
player.hp -= 1;
player.invincible = 40;
// knockback
player.vx = (player.x < e.x) ? -6 : 6;
}
}

// remove dead enemies (simple)


GAME.enemies = GAME.enemies.filter(e=> e.alive);

// keep camera following player


MAP.camera.x = clamp(player.x - canvas.width/2 + player.w/2, 0, MAP.width -
canvas.width);
MAP.camera.y = 0;
}

/* rectangle overlap helper */


function rectsOverlap(a,b){
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}

/* ----- Render ----- */


function render(){
// clear
ctx.clearRect(0,0,canvas.width,canvas.height);

// background (parallax simple)


ctx.fillStyle = '#071226';
ctx.fillRect(0,0,canvas.width,canvas.height);

// ground
ctx.fillStyle = '#222';
ctx.fillRect(0, canvas.height-80, canvas.width, 80);

// draw map bounds indicator


ctx.fillStyle = 'rgba(255,255,255,0.03)';
ctx.fillRect(-MAP.camera.x, 0, MAP.width, MAP.height);

// draw enemies
for(let e of GAME.enemies){
const ex = e.x - MAP.camera.x;
ctx.fillStyle = e.type==='flyer' ? '#ffdd55' : '#ff6666';
ctx.fillRect(ex, e.y, e.w, e.h);
// HP bar
ctx.fillStyle = '#222';
ctx.fillRect(ex, e.y-8, e.w, 6);
ctx.fillStyle = '#0f0';
ctx.fillRect(ex, e.y-8, e.w * clamp(e.hp/3,0,1), 6);
}

// player
const px = player.x - MAP.camera.x;
ctx.save();
// flash when invincible
if(player.invincible > 0 && Math.floor(player.invincible/6)%2===0)
ctx.globalAlpha = 0.4;
ctx.fillStyle = '#00ffcc';
ctx.fillRect(px, player.y, player.w, player.h);
ctx.restore();

// effects
for(let i=effects.length-1;i>=0;i--){
const ef = effects[i];
ef.t--;
if(ef.t<=0) effects.splice(i,1);
else{
const ex = ef.x - MAP.camera.x;
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.fillRect(ex-8, ef.y-8, 16, 16);
}
}

// HUD: HP
ctx.fillStyle = 'white';
ctx.font = '18px Arial';
ctx.fillText(`HP: ${player.hp}/${player.maxHp}`, 18, 28);
ctx.fillText(`Area: ${MAP.areas[0]}`, 18, 52);
}

/* ----- Inventory and UI features ----- */


let inventoryOpen = false;
function openInventory(){
inventoryOpen = !inventoryOpen;
if(inventoryOpen) {
GAME.slow = true;
alert('Inventário aberto (exemplo). Aqui você poderia equipar habilidades.
Fecha pra continuar.');
GAME.slow = false;
inventoryOpen = false;
}
}

/* ----- Game loop ----- */


let last = performance.now();
function loop(ts){
const delta = (ts - last) / 1000;
last = ts;
// we step fixed dt for stability
const step = 1/60;
let accumulator = delta;
while(accumulator > 0){
update(step);
accumulator -= step;
}
render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

/* ----- Helper: spawn more enemies dynamically for demo ----- */


setInterval(()=>{
const x = clamp(player.x + canvas.width * (0.6 + Math.random()*0.6), 200,
MAP.width-200);
spawnEnemy(x, canvas.height - 120, Math.random()>0.6? 'flyer':'runner');
}, 4000);

/* ----- End of game.js ----- */

You might also like