我的罗里吧嗦的,根据小朋友的要求,边听边写边输入的提示词:
请生成一段完整的在网页中用html5和javascript代码模拟“我的世界”中游戏场景的互动画面,要求提供若干人物选项可以选择,请自行选择需要使用哪些库或框架来实现该功能,但希望能够保存之后,用浏览器打开就能够使用。希望能够实现若干个人物的选择,其中可供选择的人物要具有动作演示的效果,请自己选一些实现动画效果的JavaScript库,可以直接在线使用,无须其它依赖和安装,此功能可以支持两个人同时操作。请提供完整的可以直接在浏览器中打开就能够简单实现的代码
结果打开之后是这样的,真能操控:
代码也是毫不吝啬,一口气输出这么多:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minecraft-Style 3D Game</title>
<link href="https://fastly.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://fastly.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.0.0/css/all.min.css" rel="stylesheet">
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Minecraft', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #333;
}
@font-face {
font-family: 'Minecraft';
src: url('https://fastly.jsdelivr.net/npm/minecraft-font@1.0.0/assets/fonts/minecraft.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
}
#render-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.interface {
position: absolute;
z-index: 10;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
}
.character-selection {
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid #555;
padding: 15px;
border-radius: 8px;
width: 90%;
max-width: 800px;
}
.controls {
bottom: 20px;
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid #555;
padding: 10px;
border-radius: 8px;
width: 250px;
}
#player1-controls {
left: 20px;
}
#player2-controls {
right: 20px;
}
.status-bar {
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid #555;
padding: 10px;
border-radius: 8px;
width: 90%;
max-width: 500px;
text-align: center;
}
.character-option {
display: inline-block;
margin: 5px;
cursor: pointer;
transition: all 0.3s;
vertical-align: top;
text-align: center;
}
.character-option img {
width: 60px;
height: 120px;
object-fit: cover;
border: 3px solid transparent;
border-radius: 4px;
filter: brightness(0.7);
transition: all 0.3s;
}
.character-option:hover img {
border-color: #aaa;
filter: brightness(1);
}
.character-option.selected img {
border-color: #5cf;
filter: brightness(1);
}
.key {
display: inline-block;
background-color: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.4);
padding: 5px 8px;
margin: 2px;
border-radius: 4px;
font-weight: bold;
}
.loading-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #333;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
color: white;
font-size: 24px;
}
.progress-bar {
width: 300px;
height: 20px;
background-color: #222;
border: 2px solid #555;
margin-top: 20px;
border-radius: 10px;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #5cf;
width: 0%;
transition: width 0.3s;
}
.dirt-block {
display: inline-block;
width: 30px;
height: 30px;
background-image: url('https://images.unsplash.com/photo-1607513746994-51f730a44832?q=80&w=30&auto=format&fit=crop');
background-size: cover;
margin: 0 5px;
border-radius: 2px;
vertical-align: middle;
}
@media (max-width: 768px) {
.character-selection {
padding: 10px;
}
.character-option img {
width: 40px;
height: 80px;
}
.controls {
width: 150px;
font-size: 12px;
}
.status-bar {
font-size: 12px;
}
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div id="f2305d93-bcef-4e15-b7ce-a63c8d491b52" class="loading-screen">
<div id="d40cd4d8-4c0e-48e4-a4d0-05bbfd6fa7db" class="dirt-block"></div>
<span id="916207e2-1aa4-49bc-b83c-c3eece7a44c2">Loading Minecraft-Style Game...</span>
<div id="b83699d9-78e5-4708-b801-716a85eef33f" class="progress-bar">
<div id="39d52c16-9215-4ebf-a6a0-25c67b5a8de1" class="progress"></div>
</div>
</div>
<!-- Game Container -->
<div id="a24df7c4-8bb6-4ac3-9e3b-91aa2a63136f" class="game-container">
<!-- Canvas for Three.js rendering -->
<canvas id="render-canvas"></canvas>
<!-- Character Selection UI -->
<div id="6c1abfc1-2331-40ab-a1e5-5eeb687d8092" class="interface character-selection">
<div id="cf8ae90c-cf05-40b4-af8a-faf0b60c47cb" class="grid grid-cols-2 md:grid-cols-4 gap-2">
<div id="b05e2f3c-5dbe-444f-9fa0-0e9a1e637919" class="text-center col-span-2 md:col-span-4 mb-2">
<h2 id="c815cc5e-5ff2-46f5-9342-c4118cb0ecd7" class="text-xl font-bold">Select Characters</h2>
<div id="8268f04e-2325-4c9e-aa64-44bb6acb32c0" class="mt-1">
<span id="1cff1ae5-0e54-4f3f-bf0b-ab87bce5408f" class="text-blue-300">Player 1:</span>
<span id="player1-character">Steve</span> |
<span id="5f21f725-c5c0-4034-bb01-a7f56da3f128" class="text-green-300">Player 2:</span>
<span id="player2-character">Alex</span>
</div>
</div>
<div id="character-options"></div>
</div>
</div>
<!-- Player 1 Controls -->
<div id="player1-controls" class="interface controls">
<h3 id="0376da1d-f2a0-4b95-907e-936476a841a1" class="text-blue-300 font-bold">Player 1 Controls:</h3>
<div id="d2bc2fb3-0502-4f5d-86b3-0f2a5f028a3a">
<div id="a0294bc3-bcc8-4f28-b001-e9fc0ea2f56a" class="mb-1"><span id="cd96b41c-00d3-4309-b805-f2befdda40c9" class="key">W</span> Forward</div>
<div id="369be69d-d537-4bc3-a11e-2411ca8ee159" class="mb-1">
<span id="9ae1c1c2-6f71-4575-8dd1-dc0fccd922e3" class="key">A</span>
<span id="d7053019-0309-433e-be51-603a9a10cb23" class="key">S</span>
<span id="1f78a456-bc7e-4bb2-9c52-7efc4c2c65c4" class="key">D</span> Move
</div>
<div id="27312d5e-377e-4469-9802-7c3b846846f3" class="mb-1"><span id="0c01c5d2-b55a-4e2c-b21b-bdd32152b755" class="key">Space</span> Jump</div>
<div id="15cfdb30-6696-4cfd-b61c-6ac80e730ebe"><span id="6a42c7fc-dc00-442c-9478-8c5d295bd8b5" class="key">E</span> Action</div>
</div>
</div>
<!-- Player 2 Controls -->
<div id="player2-controls" class="interface controls">
<h3 id="140c646d-11b9-4ea8-82ad-bf915bd8b8f0" class="text-green-300 font-bold">Player 2 Controls:</h3>
<div id="9dba0abc-4a53-4299-92b3-2c6319cfe33d">
<div id="0c620c17-2910-430a-b0c5-428c2e86fbc8" class="mb-1"><span id="80c340f8-c8d9-41d0-9e71-0d14ea07e90e" class="key">↑</span> Forward</div>
<div id="007bccc6-d4fb-4658-98cf-6b5c88d491be" class="mb-1">
<span id="086ae8b2-c512-4ad3-8a35-4d60e8a441ae" class="key">←</span>
<span id="e4222974-a876-490d-aa4b-c2f4188ac164" class="key">↓</span>
<span id="0a94ad7f-9ef5-440e-a144-a875a790ffb0" class="key">→</span> Move
</div>
<div id="fb07362d-4363-40d3-b520-3b56722fbea1" class="mb-1"><span id="9a900166-a068-4c4c-b1a6-59bcc0304d38" class="key">Enter</span> Jump</div>
<div id="bac976e5-fa44-462a-902e-bf729516482e"><span id="d6c0bf9a-6007-4f5f-a530-a9b64c6f6c11" class="key">/</span> Action</div>
</div>
</div>
<!-- Status Bar -->
<div id="de6f85ed-466b-4bd7-9e51-5b778908622d" class="interface status-bar">
<div id="game-status">Use WASD+Space and Arrow+Enter to move around and explore the world!</div>
</div>
</div>
<!-- Scripts -->
<script src="https://fastly.jsdelivr.net/npm/three@0.142.0/build/three.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/three@0.142.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://fastly.jsdelivr.net/npm/@tweenjs/tween.js@18.6.4/dist/tween.umd.js"></script>
<script>
// Main game class
class MinecraftStyleGame {
constructor() {
// Core properties
this.scene = null;
this.camera = null;
this.renderer = null;
this.clock = new THREE.Clock();
// Game state
this.players = [
{ id: 1, character: 'Steve', model: null, position: new THREE.Vector3(0, 1, 0), velocity: new THREE.Vector3(), isJumping: false, onGround: true },
{ id: 2, character: 'Alex', model: null, position: new THREE.Vector3(3, 1, 0), velocity: new THREE.Vector3(), isJumping: false, onGround: true }
];
this.keys = {
player1: {
forward: false,
backward: false,
left: false,
right: false,
jump: false,
action: false
},
player2: {
forward: false,
backward: false,
left: false,
right: false,
jump: false,
action: false
}
};
// Game world parameters
this.worldSize = 20;
this.blockSize = 1;
this.gravity = 0.02;
this.jumpForce = 0.2;
this.moveSpeed = 0.1;
// Character options
this.characterOptions = [
{ name: 'Steve', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/steve.png', playerID: 1 },
{ name: 'Alex', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/alex.png', playerID: 2 },
{ name: 'Zombie', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/zombie.png', playerID: null },
{ name: 'Skeleton', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/skeleton.png', playerID: null },
{ name: 'Creeper', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/creeper.png', playerID: null },
{ name: 'Enderman', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/enderman.png', playerID: null },
{ name: 'Villager', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/villager.png', playerID: null },
{ name: 'Pig', texture: 'https://minecraft-heads.com/media/com_minecraft/skins/original/pig.png', playerID: null }
];
// Resources to load
this.textures = {
dirt: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1607513746994-51f730a44832?q=80&w=200&auto=format&fit=crop'),
grass: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1626269555171-153a1bca85f7?q=80&w=200&auto=format&fit=crop'),
stone: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1588371758051-e93a5ce1b5f4?q=80&w=200&auto=format&fit=crop'),
wood: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1599751449128-eb7249c3d6b1?q=80&w=200&auto=format&fit=crop'),
leaves: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1570822153940-2b3b795f54cf?q=80&w=200&auto=format&fit=crop')
};
// Loading progress
this.resourcesLoaded = 0;
this.totalResources = Object.keys(this.textures).length + this.characterOptions.length;
// Initialize the game
this.init();
}
init() {
// Initialize Three.js scene
this.initScene();
// Handle loading
this.loadResources();
// UI setup
this.setupCharacterSelection();
// Setup event listeners
this.setupEventListeners();
// Start animation loop
this.animate();
}
initScene() {
// Create scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Create camera
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(10, 10, 10);
this.camera.lookAt(0, 0, 0);
// Create renderer
this.renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('render-canvas'),
antialias: true
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
// Add light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
this.scene.add(directionalLight);
// Add controls for debugging
// this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
loadResources() {
// Track texture loading
const loadingManager = new THREE.LoadingManager();
loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
this.resourcesLoaded++;
this.updateLoadingProgress();
};
loadingManager.onLoad = () => {
// All textures are loaded
this.createWorld();
setTimeout(() => {
document.getElementById('f2305d93-bcef-4e15-b7ce-a63c8d491b52').style.display = 'none';
}, 500);
};
// Create texture loader with the loading manager
const textureLoader = new THREE.TextureLoader(loadingManager);
// Load all textures
Object.keys(this.textures).forEach(key => {
const url = this.textures[key].source?.data?.src;
if (url) {
this.textures[key] = textureLoader.load(url);
this.textures[key].wrapS = THREE.RepeatWrapping;
this.textures[key].wrapT = THREE.RepeatWrapping;
}
});
// Load character textures
this.characterOptions.forEach(char => {
textureLoader.load(char.texture, () => {
this.resourcesLoaded++;
this.updateLoadingProgress();
});
});
}
updateLoadingProgress() {
const progress = Math.min((this.resourcesLoaded / this.totalResources) * 100, 100);
document.getElementById('39d52c16-9215-4ebf-a6a0-25c67b5a8de1').style.width = `${progress}%`;
}
createWorld() {
// Create ground
this.createTerrain();
// Create various structures
this.createStructures();
// Create player characters
this.createPlayerCharacters();
}
createTerrain() {
// Create base terrain
const groundGeometry = new THREE.BoxGeometry(this.worldSize, 1, this.worldSize);
const groundMaterial = new THREE.MeshStandardMaterial({ map: this.textures.grass });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.position.y = -0.5;
ground.receiveShadow = true;
this.scene.add(ground);
// Create random terrain variations
for (let x = -this.worldSize/2; x < this.worldSize/2; x += this.blockSize) {
for (let z = -this.worldSize/2; z < this.worldSize/2; z += this.blockSize) {
// Skip the center area for player spawn
if (Math.abs(x) < 4 && Math.abs(z) < 4) continue;
if (Math.random() < 0.1) {
const height = Math.floor(Math.random() * 3) + 1;
for (let y = 0; y < height; y++) {
const material = y === height - 1 ?
new THREE.MeshStandardMaterial({ map: this.textures.grass }) :
new THREE.MeshStandardMaterial({ map: this.textures.dirt });
this.createBlock(x, y, z, material);
}
}
// Create trees
if (Math.random() < 0.02) {
this.createTree(x, 0, z);
}
}
}
}
createBlock(x, y, z, material) {
const geometry = new THREE.BoxGeometry(this.blockSize, this.blockSize, this.blockSize);
const block = new THREE.Mesh(geometry, material);
block.position.set(x, y, z);
block.castShadow = true;
block.receiveShadow = true;
this.scene.add(block);
return block;
}
createTree(x, y, z) {
// Tree trunk
const trunkHeight = 4 + Math.floor(Math.random() * 3);
const woodMaterial = new THREE.MeshStandardMaterial({ map: this.textures.wood });
for (let i = 0; i < trunkHeight; i++) {
this.createBlock(x, y + i, z, woodMaterial);
}
// Tree leaves
const leavesMaterial = new THREE.MeshStandardMaterial({
map: this.textures.leaves,
transparent: true,
alphaTest: 0.8
});
const leavesSize = 3;
for (let lx = -leavesSize; lx <= leavesSize; lx++) {
for (let ly = 0; ly <= leavesSize + 1; ly++) {
for (let lz = -leavesSize; lz <= leavesSize; lz++) {
// Create spherical-ish shape for leaves
if (lx*lx + (ly-1)*(ly-1) + lz*lz <= leavesSize*leavesSize) {
// Don't place leaves where trunk is
if (!(lx === 0 && lz === 0 && ly < leavesSize)) {
this.createBlock(
x + lx,
y + trunkHeight - 1 + ly,
z + lz,
leavesMaterial
);
}
}
}
}
}
}
createStructures() {
// Create a small house
const housePosX = 8;
const housePosZ = 8;
const houseSizeX = 5;
const houseSizeZ = 4;
const wallHeight = 3;
const stoneMaterial = new THREE.MeshStandardMaterial({ map: this.textures.stone });
const woodMaterial = new THREE.MeshStandardMaterial({ map: this.textures.wood });
// Floor
for (let x = 0; x < houseSizeX; x++) {
for (let z = 0; z < houseSizeZ; z++) {
this.createBlock(housePosX + x, 0, housePosZ + z, stoneMaterial);
}
}
// Walls
for (let x = 0; x < houseSizeX; x++) {
for (let y = 1; y <= wallHeight; y++) {
// Front and back walls
if (!(x === 2 && y < 3)) { // Door in front wall
this.createBlock(housePosX + x, y, housePosZ, woodMaterial); // Front wall
}
this.createBlock(housePosX + x, y, housePosZ + houseSizeZ - 1, woodMaterial); // Back wall
}
}
for (let z = 0; z < houseSizeZ; z++) {
for (let y = 1; y <= wallHeight; y++) {
// Side walls
this.createBlock(housePosX, y, housePosZ + z, woodMaterial); // Left wall
this.createBlock(housePosX + houseSizeX - 1, y, housePosZ + z, woodMaterial); // Right wall
}
}
// Roof
for (let x = -1; x <= houseSizeX; x++) {
for (let z = -1; z <= houseSizeZ; z++) {
this.createBlock(housePosX + x, wallHeight + 1, housePosZ + z, stoneMaterial);
}
}
// Create a small mining area
const minePosX = -8;
const minePosZ = -8;
// Create pit
for (let x = 0; x < 4; x++) {
for (let z = 0; z < 4; z++) {
// Remove blocks to create a pit
// but leave some stone blocks at the bottom
if (!(x === 1 && z === 1) && !(x === 2 && z === 2)) {
this.createBlock(minePosX + x, -3, minePosZ + z, stoneMaterial);
}
}
}
// Add some stone around the pit
for (let i = 0; i < 10; i++) {
const rx = minePosX + Math.floor(Math.random() * 6) - 1;
const rz = minePosZ + Math.floor(Math.random() * 6) - 1;
const ry = Math.floor(Math.random() * 2);
this.createBlock(rx, ry, rz, stoneMaterial);
}
}
createPlayerCharacters() {
// We'll create simple block player models for now
// In a real implementation, you would load Minecraft character models
// Player 1
this.createPlayerModel(this.players[0]);
// Player 2
this.createPlayerModel(this.players[1]);
}
createPlayerModel(player) {
// Get character info from options
const characterInfo = this.characterOptions.find(c => c.name === player.character);
if (!characterInfo) return;
// Create character group
const playerModel = new THREE.Group();
// Load character texture
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(characterInfo.texture);
// Create body parts with correct proportions
// Head
const headGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
const headMaterial = new THREE.MeshStandardMaterial({ map: texture });
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 1.4;
head.castShadow = true;
playerModel.add(head);
// Body
const bodyGeometry = new THREE.BoxGeometry(0.6, 1.2, 0.4);
const bodyMaterial = new THREE.MeshStandardMaterial({
map: texture,
color: player.id === 1 ? 0x3090ff : 0x60d060
});
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 0.6;
body.castShadow = true;
playerModel.add(body);
// Arms
const armGeometry = new THREE.BoxGeometry(0.3, 1.0, 0.3);
const armMaterial = new THREE.MeshStandardMaterial({
map: texture,
color: player.id === 1 ? 0x2080dd : 0x50c050
});
const leftArm = new THREE.Mesh(armGeometry, armMaterial);
leftArm.position.set(-0.45, 0.5, 0);
leftArm.castShadow = true;
playerModel.add(leftArm);
const rightArm = new THREE.Mesh(armGeometry, armMaterial);
rightArm.position.set(0.45, 0.5, 0);
rightArm.castShadow = true;
playerModel.add(rightArm);
// Legs
const legGeometry = new THREE.BoxGeometry(0.3, 1.0, 0.3);
const legMaterial = new THREE.MeshStandardMaterial({
map: texture,
color: 0x3060b0
});
const leftLeg = new THREE.Mesh(legGeometry, legMaterial);
leftLeg.position.set(-0.2, -0.5, 0);
leftLeg.castShadow = true;
playerModel.add(leftLeg);
const rightLeg = new THREE.Mesh(legGeometry, legMaterial);
rightLeg.position.set(0.2, -0.5, 0);
rightLeg.castShadow = true;
playerModel.add(rightLeg);
// Position the player model
playerModel.position.copy(player.position);
// Store limbs for animation
playerModel.userData = {
head,
leftArm,
rightArm,
leftLeg,
rightLeg,
animationTime: 0
};
// Add to scene and assign to player
this.scene.add(playerModel);
player.model = playerModel;
}
setupCharacterSelection() {
const container = document.getElementById('character-options');
this.characterOptions.forEach((char, index) => {
const option = document.createElement('div');
option.className = 'character-option';
if (char.playerID === 1) option.classList.add('selected');
const img = document.createElement('img');
img.src = char.texture;
img.alt = char.name;
const name = document.createElement('div');
name.textContent = char.name;
option.appendChild(img);
option.appendChild(name);
option.addEventListener('click', () => {
// Show character selection dialog
this.selectCharacter(char, index);
});
container.appendChild(option);
});
}
selectCharacter(character, index) {
// Create a simple modal for character selection
const selection = prompt(`Assign ${character.name} to:\n1: Player 1\n2: Player 2\n0: Cancel`);
if (selection === '1' || selection === '2') {
const playerIndex = parseInt(selection) - 1;
const player = this.players[playerIndex];
// Update selectedPlayerID on old character
const oldCharOption = this.characterOptions.find(c => c.name === player.character);
if (oldCharOption) oldCharOption.playerID = null;
// Update player character
player.character = character.name;
character.playerID = parseInt(selection);
// Update UI
document.getElementById(`player${selection}-character`).textContent = character.name;
// Remove old player model and create a new one
if (player.model) {
this.scene.remove(player.model);
}
this.createPlayerModel(player);
// Update character selection UI
const options = document.querySelectorAll('.character-option');
options.forEach((opt, idx) => {
if (this.characterOptions[idx].playerID === 1 || this.characterOptions[idx].playerID === 2) {
opt.classList.add('selected');
} else {
opt.classList.remove('selected');
}
});
}
}
setupEventListeners() {
// Keyboard controls
document.addEventListener('keydown', (event) => this.handleKeyDown(event));
document.addEventListener('keyup', (event) => this.handleKeyUp(event));
// Window resize
window.addEventListener('resize', () => this.handleResize());
}
handleKeyDown(event) {
// Player 1 controls (WASD + Space)
switch(event.code) {
case 'KeyW': this.keys.player1.forward = true; break;
case 'KeyS': this.keys.player1.backward = true; break;
case 'KeyA': this.keys.player1.left = true; break;
case 'KeyD': this.keys.player1.right = true; break;
case 'Space':
event.preventDefault();
this.keys.player1.jump = true;
break;
case 'KeyE': this.keys.player1.action = true; break;
}
// Player 2 controls (Arrow keys + Enter)
switch(event.code) {
case 'ArrowUp': this.keys.player2.forward = true; break;
case 'ArrowDown': this.keys.player2.backward = true; break;
case 'ArrowLeft': this.keys.player2.left = true; break;
case 'ArrowRight': this.keys.player2.right = true; break;
case 'Enter': this.keys.player2.jump = true; break;
case 'Slash': this.keys.player2.action = true; break;
}
}
handleKeyUp(event) {
// Player 1 controls
switch(event.code) {
case 'KeyW': this.keys.player1.forward = false; break;
case 'KeyS': this.keys.player1.backward = false; break;
case 'KeyA': this.keys.player1.left = false; break;
case 'KeyD': this.keys.player1.right = false; break;
case 'Space': this.keys.player1.jump = false; break;
case 'KeyE': this.keys.player1.action = false; break;
}
// Player 2 controls
switch(event.code) {
case 'ArrowUp': this.keys.player2.forward = false; break;
case 'ArrowDown': this.keys.player2.backward = false; break;
case 'ArrowLeft': this.keys.player2.left = false; break;
case 'ArrowRight': this.keys.player2.right = false; break;
case 'Enter': this.keys.player2.jump = false; break;
case 'Slash': this.keys.player2.action = false; break;
}
}
handleResize() {
const width = window.innerWidth;
const height = window.innerHeight;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
}
updatePlayer(player, controls, deltaTime) {
if (!player.model) return;
// Movement direction
const moveDir = new THREE.Vector3(0, 0, 0);
if (controls.forward) moveDir.z -= 1;
if (controls.backward) moveDir.z += 1;
if (controls.left) moveDir.x -= 1;
if (controls.right) moveDir.x += 1;
// Normalize movement direction
if (moveDir.length() > 0) {
moveDir.normalize();
// Calculate movement
const movement = moveDir.multiplyScalar(this.moveSpeed * deltaTime * 60);
// Update position
player.position.x += movement.x;
player.position.z += movement.z;
// Update animation state
player.model.userData.animationTime += deltaTime * 10;
// Animate legs and arms while walking
this.animateWalking(player.model, player.model.userData.animationTime);
// Face direction of movement
if (movement.x !== 0 || movement.z !== 0) {
player.model.rotation.y = Math.atan2(movement.x, movement.z);
}
} else {
// Reset animation when not moving
this.resetAnimation(player.model);
}
// Jumping
if (controls.jump && player.onGround) {
player.velocity.y = this.jumpForce;
player.isJumping = true;
player.onGround = false;
// Update game status
document.getElementById('game-status').textContent = `Player ${player.id} jumped!`;
}
// Apply gravity
player.velocity.y -= this.gravity;
player.position.y += player.velocity.y;
// Ground collision detection
if (player.position.y <= 1) {
player.position.y = 1;
player.velocity.y = 0;
player.onGround = true;
player.isJumping = false;
}
// World boundaries
const boundaryLimit = this.worldSize / 2 - 1;
player.position.x = Math.max(-boundaryLimit, Math.min(boundaryLimit, player.position.x));
player.position.z = Math.max(-boundaryLimit, Math.min(boundaryLimit, player.position.z));
// Update model position
player.model.position.copy(player.position);
// Perform action if requested
if (controls.action) {
this.performAction(player);
}
}
performAction(player) {
// Simple action feedback
document.getElementById('game-status').textContent = `Player ${player.id} is performing an action!`;
// Animate action (e.g. arm swing)
if (player.model) {
const rightArm = player.model.userData.rightArm;
// Create a simple animation for the arm swing
const startRotation = { x: 0 };
const endRotation = { x: -Math.PI / 2 };
// Animate swing down
const swingDown = new TWEEN.Tween(startRotation)
.to(endRotation, 200)
.easing(TWEEN.Easing.Quadratic.Out)
.onUpdate(() => {
rightArm.rotation.x = startRotation.x;
});
// Animate swing back
const swingBack = new TWEEN.Tween(endRotation)
.to({ x: 0 }, 200)
.easing(TWEEN.Easing.Quadratic.In)
.onUpdate(() => {
rightArm.rotation.x = endRotation.x;
});
// Chain the animations
swingDown.chain(swingBack);
swingDown.start();
}
}
animateWalking(model, time) {
if (!model || !model.userData) return;
const { leftLeg, rightLeg, leftArm, rightArm } = model.userData;
// Animate legs
if (leftLeg && rightLeg) {
leftLeg.rotation.x = Math.sin(time) * 0.5;
rightLeg.rotation.x = Math.sin(time + Math.PI) * 0.5;
}
// Animate arms
if (leftArm && rightArm) {
leftArm.rotation.x = Math.sin(time + Math.PI) * 0.5;
rightArm.rotation.x = Math.sin(time) * 0.5;
}
}
resetAnimation(model) {
if (!model || !model.userData) return;
const { leftLeg, rightLeg, leftArm, rightArm } = model.userData;
// Reset all rotations
if (leftLeg) leftLeg.rotation.set(0, 0, 0);
if (rightLeg) rightLeg.rotation.set(0, 0, 0);
if (leftArm) leftArm.rotation.set(0, 0, 0);
if (rightArm) rightArm.rotation.set(0, 0, 0);
}
updateCamera() {
// Calculate center position between players
const center = new THREE.Vector3();
this.players.forEach(player => {
center.add(player.position);
});
center.divideScalar(this.players.length);
// Calculate distance between players
let maxDistance = 0;
this.players.forEach(player => {
const distance = player.position.distanceTo(center);
maxDistance = Math.max(maxDistance, distance);
});
// Adjust camera position based on player positions
const cameraDistance = Math.max(10, maxDistance * 3);
const cameraHeight = Math.max(6, maxDistance * 1.5);
// Smoothly move camera to new position
const targetCameraPos = new THREE.Vector3(
center.x + cameraDistance * Math.sin(Date.now() * 0.0001),
cameraHeight,
center.z + cameraDistance * Math.cos(Date.now() * 0.0001)
);
this.camera.position.lerp(targetCameraPos, 0.02);
this.camera.lookAt(center);
}
animate() {
requestAnimationFrame(() => this.animate());
const deltaTime = this.clock.getDelta();
// Update players
this.updatePlayer(this.players[0], this.keys.player1, deltaTime);
this.updatePlayer(this.players[1], this.keys.player2, deltaTime);
// Update camera to follow players
this.updateCamera();
// Update animations
TWEEN.update();
// Render scene
this.renderer.render(this.scene, this.camera);
}
}
// Initialize the game when page is loaded
window.addEventListener('load', () => {
const game = new MinecraftStyleGame();
});
</script>
</body>
</html>