newlll / index.html
pijou's picture
Add 2 files
762708f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raccoon Rumble</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.min.js"></script>
<style>
@font-face {
font-family: 'PressStart2P';
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
}
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #111;
font-family: 'PressStart2P', cursive;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#game-canvas {
display: block;
margin: 0 auto;
background-color: #000;
}
.health-bar {
height: 30px;
transition: width 0.3s ease;
}
.combo-text {
position: absolute;
color: #ff0;
font-size: 24px;
text-shadow: 0 0 10px #f80;
animation: pop 0.5s ease-out;
}
@keyframes pop {
0% { transform: scale(0.5); opacity: 0; }
80% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.menu-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
color: white;
}
.menu-title {
font-size: 48px;
color: #ff9500;
margin-bottom: 40px;
text-shadow: 0 0 10px #ff0;
}
.menu-button {
background-color: #ff9500;
color: #000;
border: none;
padding: 15px 30px;
margin: 10px;
font-size: 20px;
cursor: pointer;
border-radius: 5px;
transition: all 0.3s;
font-family: 'PressStart2P', cursive;
}
.menu-button:hover {
background-color: #ff0;
transform: scale(1.05);
}
.controls-display {
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
margin-top: 30px;
max-width: 600px;
}
.control-item {
display: flex;
align-items: center;
margin: 10px 0;
}
.key {
background-color: #333;
color: white;
padding: 5px 10px;
margin-right: 15px;
border-radius: 5px;
min-width: 40px;
text-align: center;
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-canvas"></div>
<!-- Main Menu -->
<div id="main-menu" class="menu-screen">
<h1 class="menu-title">RACCOON RUMBLE</h1>
<button id="start-button" class="menu-button">START GAME</button>
<button id="controls-button" class="menu-button">CONTROLS</button>
<div class="mt-8 text-center">
<p>Player 1: WASD + JKL</p>
<p>Player 2: Arrow Keys + Numpad 1-3</p>
<p>Or connect a gamepad!</p>
</div>
</div>
<!-- Controls Screen -->
<div id="controls-screen" class="menu-screen" style="display: none;">
<h1 class="menu-title">CONTROLS</h1>
<div class="controls-display">
<h2 class="text-xl mb-4 text-center">Player 1 (Keyboard)</h2>
<div class="control-item">
<div class="key">W</div>
<span>Jump</span>
</div>
<div class="control-item">
<div class="key">A</div>
<span>Move Left</span>
</div>
<div class="control-item">
<div class="key">D</div>
<span>Move Right</span>
</div>
<div class="control-item">
<div class="key">J</div>
<span>Light Attack</span>
</div>
<div class="control-item">
<div class="key">K</div>
<span>Heavy Attack</span>
</div>
<div class="control-item">
<div class="key">L</div>
<span>Special Attack</span>
</div>
<h2 class="text-xl mt-8 mb-4 text-center">Player 2 (Keyboard)</h2>
<div class="control-item">
<div class="key"></div>
<span>Jump</span>
</div>
<div class="control-item">
<div class="key"></div>
<span>Move Left</span>
</div>
<div class="control-item">
<div class="key"></div>
<span>Move Right</span>
</div>
<div class="control-item">
<div class="key">Num 1</div>
<span>Light Attack</span>
</div>
<div class="control-item">
<div class="key">Num 2</div>
<span>Heavy Attack</span>
</div>
<div class="control-item">
<div class="key">Num 3</div>
<span>Special Attack</span>
</div>
<p class="mt-8 text-center">Gamepads: Use left stick/d-pad to move and face buttons for attacks</p>
</div>
<button id="back-button" class="menu-button mt-8">BACK TO MENU</button>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="menu-screen" style="display: none;">
<h1 id="winner-text" class="menu-title">PLAYER 1 WINS!</h1>
<button id="rematch-button" class="menu-button">REMATCH</button>
<button id="menu-button" class="menu-button">MAIN MENU</button>
</div>
</div>
<script>
// Game configuration
const config = {
type: Phaser.AUTO,
width: 1024,
height: 576,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 800 },
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
},
parent: 'game-container',
canvas: document.getElementById('game-canvas')
};
// Game variables
let game = new Phaser.Game(config);
let player1, player2;
let platforms;
let cursors, p2Cursors;
let gamepads = [];
let comboCount = 0;
let lastHitTime = 0;
let comboTexts = [];
let background;
let hitSound, jumpSound, specialSound, backgroundMusic;
let gameState = 'menu'; // menu, playing, gameover
// Raccoon character class
class Raccoon extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y, isPlayer1) {
super(scene, x, y, 'raccoon');
this.isPlayer1 = isPlayer1;
this.health = 100;
this.maxHealth = 100;
this.isAttacking = false;
this.isBlocking = false;
this.isJumping = false;
this.isSpecial = false;
this.comboCount = 0;
this.lastHitTime = 0;
this.facingRight = isPlayer1;
this.specialMeter = 0;
this.maxSpecialMeter = 100;
this.invulnerable = false;
scene.add.existing(this);
scene.physics.add.existing(this);
this.setCollideWorldBounds(true);
this.setBounce(0.2);
this.setDragX(400);
this.setSize(60, 90);
this.setOffset(35, 30);
// Create animations
this.createAnimations();
// Set up attack hitboxes
this.attackHitbox = scene.add.zone(0, 0, 80, 80);
scene.physics.add.existing(this.attackHitbox);
this.attackHitbox.body.enable = false;
// Health bar
this.healthBar = scene.add.rectangle(
isPlayer1 ? 100 : config.width - 100,
50,
this.health * 2,
20,
isPlayer1 ? 0x00ff00 : 0xff0000
);
this.healthBar.setOrigin(isPlayer1 ? 0 : 1, 0.5);
// Special meter
this.specialMeterBar = scene.add.rectangle(
isPlayer1 ? 100 : config.width - 100,
80,
0,
10,
0x00aaff
);
this.specialMeterBar.setOrigin(isPlayer1 ? 0 : 1, 0.5);
// Player indicator
this.playerIndicator = scene.add.text(
isPlayer1 ? 20 : config.width - 20,
50,
isPlayer1 ? 'P1' : 'P2',
{
fontSize: '24px',
fill: isPlayer1 ? '#00ff00' : '#ff0000',
fontFamily: 'PressStart2P'
}
);
this.playerIndicator.setOrigin(isPlayer1 ? 0 : 1, 0.5);
}
createAnimations() {
const anims = this.scene.anims;
// Idle animation
anims.create({
key: `idle-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 0, end: 3 }),
frameRate: 8,
repeat: -1
});
// Walk animation
anims.create({
key: `walk-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 4, end: 7 }),
frameRate: 12,
repeat: -1
});
// Jump animation
anims.create({
key: `jump-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 8, end: 11 }),
frameRate: 8,
repeat: 0
});
// Light attack animation
anims.create({
key: `light-attack-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 12, end: 15 }),
frameRate: 12,
repeat: 0
});
// Heavy attack animation
anims.create({
key: `heavy-attack-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 16, end: 19 }),
frameRate: 10,
repeat: 0
});
// Special attack animation
anims.create({
key: `special-attack-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 20, end: 23 }),
frameRate: 8,
repeat: 0
});
// Hit animation
anims.create({
key: `hit-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 24, end: 27 }),
frameRate: 10,
repeat: 0
});
// Block animation
anims.create({
key: `block-${this.isPlayer1 ? 'p1' : 'p2'}`,
frames: anims.generateFrameNumbers('raccoon', { start: 28, end: 31 }),
frameRate: 8,
repeat: 0
});
}
update(cursors, gamepad) {
if (this.health <= 0) return;
// Update health bar
this.healthBar.width = (this.health / this.maxHealth) * 200;
// Update special meter
this.specialMeterBar.width = (this.specialMeter / this.maxSpecialMeter) * 200;
// Handle movement
if (!this.isAttacking && !this.isBlocking) {
let moveX = 0;
// Keyboard controls
if (cursors) {
if (cursors.left.isDown) {
moveX = -1;
this.facingRight = false;
} else if (cursors.right.isDown) {
moveX = 1;
this.facingRight = true;
}
if (cursors.up.isDown && this.body.onFloor()) {
this.jump();
}
}
// Gamepad controls
if (gamepad) {
if (gamepad.axes[0] < -0.5 || gamepad.buttons[14].pressed) {
moveX = -1;
this.facingRight = false;
} else if (gamepad.axes[0] > 0.5 || gamepad.buttons[15].pressed) {
moveX = 1;
this.facingRight = true;
}
if ((gamepad.buttons[0].pressed || gamepad.buttons[12].pressed) && this.body.onFloor()) {
this.jump();
}
}
this.setVelocityX(moveX * 200);
// Play animations based on state
if (this.body.onFloor()) {
if (moveX !== 0) {
this.play(`walk-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
} else {
this.play(`idle-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
}
}
}
// Flip sprite based on facing direction
this.flipX = !this.facingRight;
// Position attack hitbox
this.positionAttackHitbox();
// Handle attacks
if (!this.isAttacking && !this.isBlocking) {
// Keyboard attacks
if (cursors) {
if (cursors.lightAttack.isDown) {
this.lightAttack();
} else if (cursors.heavyAttack.isDown) {
this.heavyAttack();
} else if (cursors.specialAttack.isDown && this.specialMeter >= this.maxSpecialMeter) {
this.specialAttack();
}
}
// Gamepad attacks
if (gamepad) {
if (gamepad.buttons[2].pressed) {
this.lightAttack();
} else if (gamepad.buttons[1].pressed) {
this.heavyAttack();
} else if (gamepad.buttons[3].pressed && this.specialMeter >= this.maxSpecialMeter) {
this.specialAttack();
}
}
}
// Handle blocking
if (!this.isAttacking) {
let isBlocking = false;
// Keyboard blocking
if (cursors && cursors.down.isDown && !this.body.onFloor()) {
isBlocking = true;
}
// Gamepad blocking
if (gamepad && (gamepad.buttons[8].pressed || gamepad.buttons[9].pressed)) {
isBlocking = true;
}
if (isBlocking && !this.isBlocking) {
this.block();
} else if (!isBlocking && this.isBlocking) {
this.releaseBlock();
}
}
// Update special meter (gain over time)
if (this.specialMeter < this.maxSpecialMeter) {
this.specialMeter += 0.1;
if (this.specialMeter > this.maxSpecialMeter) {
this.specialMeter = this.maxSpecialMeter;
}
}
}
positionAttackHitbox() {
if (!this.attackHitbox) return;
const offsetX = this.facingRight ? 50 : -50;
this.attackHitbox.x = this.x + offsetX;
this.attackHitbox.y = this.y - 20;
}
jump() {
if (this.body.onFloor()) {
this.setVelocityY(-500);
this.play(`jump-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
this.isJumping = true;
jumpSound.play();
this.scene.time.delayedCall(1000, () => {
this.isJumping = false;
});
}
}
lightAttack() {
this.isAttacking = true;
this.play(`light-attack-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
hitSound.play();
// Enable hitbox
this.attackHitbox.body.enable = true;
this.attackHitbox.body.setSize(80, 80);
// Check for hits
this.scene.physics.overlap(this.attackHitbox, this.isPlayer1 ? player2 : player1, this.handleHit.bind(this, 10, 'light'));
// Disable hitbox after animation
this.scene.time.delayedCall(300, () => {
this.attackHitbox.body.enable = false;
this.isAttacking = false;
});
}
heavyAttack() {
this.isAttacking = true;
this.play(`heavy-attack-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
hitSound.play({ volume: 0.8 });
// Enable hitbox
this.attackHitbox.body.enable = true;
this.attackHitbox.body.setSize(100, 100);
// Check for hits
this.scene.physics.overlap(this.attackHitbox, this.isPlayer1 ? player2 : player1, this.handleHit.bind(this, 20, 'heavy'));
// Disable hitbox after animation
this.scene.time.delayedCall(400, () => {
this.attackHitbox.body.enable = false;
this.isAttacking = false;
});
}
specialAttack() {
this.isAttacking = true;
this.isSpecial = true;
this.play(`special-attack-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
specialSound.play();
// Reset special meter
this.specialMeter = 0;
// Enable hitbox (larger for special)
this.attackHitbox.body.enable = true;
this.attackHitbox.body.setSize(120, 120);
// Check for hits
this.scene.physics.overlap(this.attackHitbox, this.isPlayer1 ? player2 : player1, this.handleHit.bind(this, 30, 'special'));
// Disable hitbox after animation
this.scene.time.delayedCall(600, () => {
this.attackHitbox.body.enable = false;
this.isAttacking = false;
this.isSpecial = false;
});
}
block() {
this.isBlocking = true;
this.setVelocityX(0);
this.play(`block-${this.isPlayer1 ? 'p1' : 'p2'}`, true);
}
releaseBlock() {
this.isBlocking = false;
}
handleHit(damage, attackType, attacker, defender) {
if (defender.invulnerable) return;
// Check if defender is blocking
if (defender.isBlocking) {
// Reduce damage when blocking
damage *= 0.3;
// Play block animation
defender.play(`block-${defender.isPlayer1 ? 'p1' : 'p2'}`, true);
// Small knockback
const knockback = this.facingRight ? -100 : 100;
defender.setVelocityX(knockback);
// Make defender briefly invulnerable
defender.invulnerable = true;
this.scene.time.delayedCall(500, () => {
defender.invulnerable = false;
});
} else {
// Play hit animation
defender.play(`hit-${defender.isPlayer1 ? 'p1' : 'p2'}`, true);
// Apply knockback based on attack type
let knockback = 0;
if (attackType === 'light') knockback = this.facingRight ? 150 : -150;
else if (attackType === 'heavy') knockback = this.facingRight ? 250 : -250;
else if (attackType === 'special') knockback = this.facingRight ? 400 : -400;
defender.setVelocityX(knockback);
defender.setVelocityY(-100);
// Make defender briefly invulnerable
defender.invulnerable = true;
this.scene.time.delayedCall(800, () => {
defender.invulnerable = false;
});
}
// Apply damage
defender.health -= damage;
if (defender.health < 0) defender.health = 0;
// Add to attacker's special meter
this.specialMeter += damage;
if (this.specialMeter > this.maxSpecialMeter) {
this.specialMeter = this.maxSpecialMeter;
}
// Combo system
const now = this.scene.time.now;
if (now - this.lastHitTime < 1000) {
this.comboCount++;
// Show combo text
const comboText = this.scene.add.text(
defender.x,
defender.y - 100,
`COMBO x${this.comboCount + 1}!`,
{
fontSize: '32px',
fill: '#ff0',
fontFamily: 'PressStart2P',
stroke: '#000',
strokeThickness: 4
}
);
comboText.setOrigin(0.5);
// Add to combo texts array for cleanup
comboTexts.push(comboText);
// Animate combo text
this.scene.tweens.add({
targets: comboText,
y: comboText.y - 50,
alpha: 0,
duration: 1000,
onComplete: () => {
comboText.destroy();
comboTexts = comboTexts.filter(t => t !== comboText);
}
});
} else {
this.comboCount = 0;
}
this.lastHitTime = now;
// Check for KO
if (defender.health <= 0) {
this.scene.gameOver(this.isPlayer1 ? 'Player 1' : 'Player 2');
}
}
}
// Preload assets
function preload() {
// Load raccoon sprite sheet
this.load.spritesheet('raccoon', 'https://i.imgur.com/JQlY5zX.png', {
frameWidth: 128,
frameHeight: 128
});
// Load backgrounds
this.load.image('background1', 'https://i.imgur.com/XH7QZ0a.png');
this.load.image('background2', 'https://i.imgur.com/Y9vBQ3c.png');
// Load platforms
this.load.image('platform', 'https://i.imgur.com/8Z7WQkK.png');
// Load sounds
this.load.audio('hit', 'https://assets.codepen.io/21542/hit.mp3');
this.load.audio('jump', 'https://assets.codepen.io/21542/jump.mp3');
this.load.audio('special', 'https://assets.codepen.io/21542/special.mp3');
this.load.audio('backgroundMusic', 'https://assets.codepen.io/21542/fight-music.mp3');
}
// Create game objects
function create() {
// Randomly select background
const bgKey = Phaser.Math.Between(0, 1) === 0 ? 'background1' : 'background2';
background = this.add.image(config.width / 2, config.height / 2, bgKey);
background.setDisplaySize(config.width, config.height);
// Create platforms
platforms = this.physics.add.staticGroup();
// Main platform
platforms.create(config.width / 2, config.height - 50, 'platform')
.setScale(10, 1)
.refreshBody();
// Side platforms
platforms.create(200, 400, 'platform').setScale(3, 1).refreshBody();
platforms.create(config.width - 200, 400, 'platform').setScale(3, 1).refreshBody();
// Create players
player1 = new Raccoon(this, 300, 300, true);
player2 = new Raccoon(this, config.width - 300, 300, false);
// Set up physics collisions
this.physics.add.collider(player1, platforms);
this.physics.add.collider(player2, platforms);
// Set up keyboard controls
cursors = this.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.W,
left: Phaser.Input.Keyboard.KeyCodes.A,
right: Phaser.Input.Keyboard.KeyCodes.D,
down: Phaser.Input.Keyboard.KeyCodes.S,
lightAttack: Phaser.Input.Keyboard.KeyCodes.J,
heavyAttack: Phaser.Input.Keyboard.KeyCodes.K,
specialAttack: Phaser.Input.Keyboard.KeyCodes.L
});
p2Cursors = this.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.UP,
left: Phaser.Input.Keyboard.KeyCodes.LEFT,
right: Phaser.Input.Keyboard.KeyCodes.RIGHT,
down: Phaser.Input.Keyboard.KeyCodes.DOWN,
lightAttack: Phaser.Input.Keyboard.KeyCodes.NUMPAD_ONE,
heavyAttack: Phaser.Input.Keyboard.KeyCodes.NUMPAD_TWO,
specialAttack: Phaser.Input.Keyboard.KeyCodes.NUMPAD_THREE
});
// Set up gamepad controls
this.input.gamepad.on('connected', (pad) => {
console.log('Gamepad connected');
gamepads[pad.index] = pad;
});
// Initialize sounds
hitSound = this.sound.add('hit');
jumpSound = this.sound.add('jump');
specialSound = this.sound.add('special');
backgroundMusic = this.sound.add('backgroundMusic');
// Set up UI events
document.getElementById('start-button').addEventListener('click', () => {
this.startGame();
});
document.getElementById('controls-button').addEventListener('click', () => {
document.getElementById('main-menu').style.display = 'none';
document.getElementById('controls-screen').style.display = 'flex';
});
document.getElementById('back-button').addEventListener('click', () => {
document.getElementById('controls-screen').style.display = 'none';
document.getElementById('main-menu').style.display = 'flex';
});
document.getElementById('rematch-button').addEventListener('click', () => {
this.startGame();
});
document.getElementById('menu-button').addEventListener('click', () => {
this.showMainMenu();
});
// Start in menu state
this.showMainMenu();
}
// Update game state
function update() {
if (gameState !== 'playing') return;
// Update gamepads
const gamepads = navigator.getGamepads();
// Update players
player1.update(cursors, gamepads[0]);
player2.update(p2Cursors, gamepads[1]);
// Clean up old combo texts
comboTexts.forEach(text => {
if (text.active) {
text.y -= 1;
}
});
}
// Scene methods
function startGame() {
// Reset game state
gameState = 'playing';
// Hide menus
document.getElementById('main-menu').style.display = 'none';
document.getElementById('controls-screen').style.display = 'none';
document.getElementById('game-over-screen').style.display = 'none';
// Reset players
player1.health = player1.maxHealth;
player2.health = player2.maxHealth;
player1.specialMeter = 0;
player2.specialMeter = 0;
player1.setPosition(300, 300);
player2.setPosition(config.width - 300, 300);
player1.setVelocity(0, 0);
player2.setVelocity(0, 0);
player1.isAttacking = false;
player2.isAttacking = false;
player1.isBlocking = false;
player2.isBlocking = false;
// Play background music
backgroundMusic.play({
loop: true,
volume: 0.5
});
}
function gameOver(winner) {
gameState = 'gameover';
// Stop background music
backgroundMusic.stop();
// Show game over screen
document.getElementById('game-over-screen').style.display = 'flex';
document.getElementById('winner-text').textContent = `${winner} WINS!`;
}
function showMainMenu() {
gameState = 'menu';
// Stop background music
backgroundMusic.stop();
// Show main menu
document.getElementById('main-menu').style.display = 'flex';
document.getElementById('controls-screen').style.display = 'none';
document.getElementById('game-over-screen').style.display = 'none';
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=pijou/newlll" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>