Загрузка данных
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const CONFIG = {
MAP_HALF_SIZE: 28,
PLAYER_SPEED: 0.18,
PLAYER_ROT_SPEED: 0.05,
CAMERA_HEIGHT_OFFSET: 12,
CAMERA_Z_OFFSET: -12,
COIN_COUNT: 5,
COIN_SCORE_VALUE: 10,
WIN_SCORE: 50,
ENEMY_ORBIT_SPEED: 0.02,
GROUND_SIZE: 60,
};
const ENEMIES_CONFIG = [
{
name: "enemy1",
color: new BABYLON.Color3(1, 0.2, 0.2),
startX: 10,
startZ: 10,
orbitRadius: 12,
orbitSpeedX: 1.0,
orbitSpeedZ: 1.0,
rotSpeed: 0.03,
},
{
name: "enemy2",
color: new BABYLON.Color3(1, 0.5, 0.1),
startX: -10,
startZ: -10,
orbitRadius: 15,
orbitSpeedX: 0.7,
orbitSpeedZ: 1.2,
rotSpeed: 0.02,
},
];
const COINS_CONFIG = Array.from({ length: CONFIG.COIN_COUNT }, (_, i) => ({
x: -12 + i * 6,
z: 12 - i * 3,
}));
const gameState = {
hp: 100,
score: 0,
seconds: 0,
orbitTime: 0,
isGameOver: false,
useFollowCamera: false,
keys: {},
};
function createColoredBox(name, size, color, scene) {
const mesh = BABYLON.MeshBuilder.CreateBox(name, { size }, scene);
const mat = new BABYLON.StandardMaterial(name + "_mat", scene);
mat.diffuseColor = color;
mesh.material = mat;
return mesh;
}
function createColoredSphere(name, diameter, color, scene) {
const mesh = BABYLON.MeshBuilder.CreateSphere(name, { diameter }, scene);
const mat = new BABYLON.StandardMaterial(name + "_mat", scene);
mat.diffuseColor = color;
mesh.material = mat;
return mesh;
}
function createColoredCylinder(name, diameter, height, color, scene) {
const mesh = BABYLON.MeshBuilder.CreateCylinder(name, { diameter, height }, scene);
const mat = new BABYLON.StandardMaterial(name + "_mat", scene);
mat.diffuseColor = color;
mesh.material = mat;
return mesh;
}
function createGround(scene) {
const ground = BABYLON.MeshBuilder.CreateGround(
"ground",
{ width: CONFIG.GROUND_SIZE, height: CONFIG.GROUND_SIZE },
scene
);
const mat = new BABYLON.StandardMaterial("groundMat", scene);
mat.diffuseColor = new BABYLON.Color3(0.3, 0.7, 0.3);
ground.material = mat;
return ground;
}
function createPlayer(scene) {
const player = createColoredBox(
"player",
2,
new BABYLON.Color3(0.2, 0.4, 1),
scene
);
player.position.y = 1;
return player;
}
function createEnemies(scene) {
return ENEMIES_CONFIG.map((cfg) => {
const enemy = createColoredSphere(cfg.name, 2, cfg.color, scene);
enemy.position.set(cfg.startX, 1, cfg.startZ);
return enemy;
});
}
function createCoins(scene) {
return COINS_CONFIG.map((cfg, i) => {
const coin = createColoredCylinder(
"coin" + i,
1,
0.3,
new BABYLON.Color3(1, 0.85, 0.1),
scene
);
coin.position.set(cfg.x, 1, cfg.z);
return coin;
});
}
function createCameras(scene) {
const orbitCamera = new BABYLON.ArcRotateCamera(
"orbitCam",
Math.PI / 2,
1.1,
30,
BABYLON.Vector3.Zero(),
scene
);
orbitCamera.attachControl(canvas, true);
const followCamera = new BABYLON.FreeCamera(
"followCam",
new BABYLON.Vector3(0, 15, -20),
scene
);
followCamera.setTarget(BABYLON.Vector3.Zero());
return { orbitCamera, followCamera };
}
function createLights(scene) {
const hemi = new BABYLON.HemisphericLight(
"hemiLight",
new BABYLON.Vector3(0, 1, 0),
scene
);
hemi.intensity = 0.95;
const dir = new BABYLON.DirectionalLight(
"dirLight",
new BABYLON.Vector3(-1, -2, -1),
scene
);
dir.position = new BABYLON.Vector3(20, 30, 20);
}
function createUILabel({ top, left = "-40%", color = "white", initialText }) {
const block = new BABYLON.GUI.TextBlock();
block.text = initialText;
block.color = color;
block.fontSize = 24;
block.top = top;
block.left = left;
block.width = "200px";
block.height = "40px";
return block;
}
function createUI(scene) {
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui", true, scene);
const hpText = createUILabel({ top: "-45%", initialText: `HP: ${gameState.hp}` });
const scoreText = createUILabel({ top: "-40%", initialText: `Score: ${gameState.score}` });
const timerText = createUILabel({ top: "-35%", initialText: "Time: 0" });
const infoText = new BABYLON.GUI.TextBlock();
infoText.text = "WASD — move | C — switch camera";
infoText.color = "yellow";
infoText.fontSize = 22;
infoText.top = "45%";
infoText.left = "0%";
infoText.width = "500px";
infoText.height = "40px";
[hpText, scoreText, timerText, infoText].forEach((c) => ui.addControl(c));
return { hpText, scoreText, timerText, infoText };
}
function setupInput(cameras, uiLabels) {
const { orbitCamera, followCamera } = cameras;
const { infoText } = uiLabels;
window.addEventListener("keydown", (ev) => {
const key = ev.key.toLowerCase();
gameState.keys[key] = true;
if (key === "c") {
gameState.useFollowCamera = !gameState.useFollowCamera;
if (gameState.useFollowCamera) {
scene.activeCamera = followCamera;
followCamera.attachControl(canvas, true);
infoText.text = "Camera: follow";
} else {
scene.activeCamera = orbitCamera;
orbitCamera.attachControl(canvas, true);
infoText.text = "Camera: orbit";
}
}
});
window.addEventListener("keyup", (ev) => {
gameState.keys[ev.key.toLowerCase()] = false;
});
}
function startTimer(timerText) {
setInterval(() => {
if (!gameState.isGameOver) {
gameState.seconds++;
timerText.text = `Time: ${gameState.seconds}`;
}
}, 1000);
}
function updatePlayer(player) {
const { keys } = gameState;
if (keys["w"]) player.position.z += CONFIG.PLAYER_SPEED;
if (keys["s"]) player.position.z -= CONFIG.PLAYER_SPEED;
if (keys["a"]) player.rotation.y -= CONFIG.PLAYER_ROT_SPEED;
if (keys["d"]) player.rotation.y += CONFIG.PLAYER_ROT_SPEED;
const limit = CONFIG.MAP_HALF_SIZE;
player.position.x = Math.max(-limit, Math.min(limit, player.position.x));
player.position.z = Math.max(-limit, Math.min(limit, player.position.z));
}
function updateEnemies(enemies) {
gameState.orbitTime += CONFIG.ENEMY_ORBIT_SPEED;
const t = gameState.orbitTime;
enemies.forEach((enemy, idx) => {
const cfg = ENEMIES_CONFIG[idx];
enemy.position.x = Math.sin(t * cfg.orbitSpeedX) * cfg.orbitRadius;
enemy.position.z = Math.cos(t * cfg.orbitSpeedZ) * cfg.orbitRadius;
enemy.rotation.y += cfg.rotSpeed;
});
}
function updateFollowCamera(followCamera, player) {
if (!gameState.useFollowCamera) return;
followCamera.position.x = player.position.x;
followCamera.position.y = player.position.y + CONFIG.CAMERA_HEIGHT_OFFSET;
followCamera.position.z = player.position.z + CONFIG.CAMERA_Z_OFFSET;
followCamera.setTarget(player.position);
}
function updateCoinCollection(player, coins, uiLabels) {
coins.forEach((coin) => {
if (!coin.isVisible) return;
if (!player.intersectsMesh(coin, false)) return;
coin.isVisible = false;
gameState.score += CONFIG.COIN_SCORE_VALUE;
uiLabels.scoreText.text = `Score: ${gameState.score}`;
});
}
function applyEnemyDamage(player, enemies, uiLabels) {
enemies.forEach((enemy) => {
if (!player.intersectsMesh(enemy, false)) return;
gameState.hp -= 1;
uiLabels.hpText.text = `HP: ${gameState.hp}`;
});
}
function checkEndConditions(uiLabels) {
if (gameState.hp <= 0) {
gameState.isGameOver = true;
uiLabels.infoText.text = "Game over. Reload page.";
return;
}
if (gameState.score >= CONFIG.WIN_SCORE) {
gameState.isGameOver = true;
uiLabels.infoText.text = "You win! Reload page.";
}
}
function setupGameLoop(scene, player, enemies, coins, cameras, uiLabels) {
scene.onBeforeRenderObservable.add(() => {
if (gameState.isGameOver) return;
updatePlayer(player);
updateEnemies(enemies);
updateFollowCamera(cameras.followCamera, player);
updateCoinCollection(player, coins, uiLabels);
applyEnemyDamage(player, enemies, uiLabels);
checkEndConditions(uiLabels);
});
}
function createScene() {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.75, 0.88, 1, 1);
const cameras = createCameras(scene);
createLights(scene);
createGround(scene);
const player = createPlayer(scene);
const enemies = createEnemies(scene);
const coins = createCoins(scene);
const uiLabels = createUI(scene);
setupInput(cameras, uiLabels);
startTimer(uiLabels.timerText);
setupGameLoop(scene, player, enemies, coins, cameras, uiLabels);
return scene;
}
const scene = createScene();
engine.runRenderLoop(() => scene.render());
window.addEventListener("resize", () => engine.resize());