<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no" />
<title>Моя игровая платформа — BETA</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
body {
background: #0b1020;
color: #fff;
overflow: hidden;
}
button {
border: 0;
color: white;
cursor: pointer;
font-weight: bold;
}
.screen {
width: 100vw;
height: 100vh;
display: none;
}
.screen.active {
display: block;
}
/* ================= МЕНЮ ================= */
#home {
overflow-y: auto;
background: radial-gradient(circle at 80% 10%, #354b9b 0, #141d38 35%, #080b14 100%);
padding: 20px;
}
.header {
max-width: 1100px;
margin: auto;
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
}
.logo {
font-size: 25px;
font-weight: 900;
}
.logo span {
color: #62e6ff;
}
.header button {
background: #725cff;
padding: 12px 18px;
border-radius: 12px;
}
.hero {
max-width: 1100px;
margin: 45px auto 30px;
}
.hero h1 {
font-size: 42px;
margin-bottom: 10px;
}
.hero p {
color: #aeb8d0;
max-width: 650px;
line-height: 1.5;
}
.sectionTitle {
max-width: 1100px;
margin: 25px auto 15px;
font-size: 23px;
}
.games {
max-width: 1100px;
margin: auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px;
}
.card {
background: #151d31;
border: 1px solid #29344e;
border-radius: 18px;
overflow: hidden;
}
.preview {
height: 150px;
display: flex;
justify-content: center;
align-items: center;
font-size: 70px;
}
.p1 {
background: linear-gradient(135deg, #287cff, #743cff);
}
.p2 {
background: linear-gradient(135deg, #087f55, #173e31);
}
.cardContent {
padding: 17px;
}
.cardContent h3 {
font-size: 21px;
margin-bottom: 7px;
}
.cardContent p {
color: #9ca9c2;
min-height: 45px;
line-height: 1.4;
}
.playBtn,
.createBtn {
width: 100%;
margin-top: 14px;
padding: 13px;
border-radius: 11px;
background: #725cff;
}
.createLarge {
max-width: 1100px;
margin: 25px auto 50px;
background: #151d31;
border: 1px solid #29344e;
border-radius: 18px;
padding: 20px;
}
.createLarge button {
background: #12a875;
padding: 13px 20px;
border-radius: 11px;
margin-top: 12px;
}
/* ================= СОЗДАТЕЛЬ ================= */
#creator {
background: #101827;
}
.topbar {
height: 58px;
background: #171f32;
display: flex;
align-items: center;
gap: 12px;
padding: 8px;
position: relative;
z-index: 20;
}
.backBtn {
background: #303c54;
padding: 10px 14px;
border-radius: 9px;
}
.title {
font-weight: bold;
}
.creatorArea {
height: calc(100vh - 58px);
position: relative;
overflow: hidden;
}
#editor {
width: 100%;
height: 100%;
display: block;
background: #80cfff;
}
.panel {
position: absolute;
top: 15px;
left: 15px;
width: 205px;
padding: 14px;
border-radius: 15px;
background: rgba(13, 20, 34, 0.95);
border: 1px solid #35415b;
z-index: 5;
}
.panel h3 {
margin-bottom: 12px;
}
.tool {
width: 100%;
background: #29354c;
padding: 10px;
border-radius: 9px;
text-align: left;
margin-bottom: 7px;
}
.tool.selected {
background: #725cff;
}
#gameName {
width: 100%;
padding: 10px;
border: 1px solid #45516b;
background: #111827;
color: #fff;
border-radius: 8px;
margin-bottom: 10px;
}
.publishBtn {
width: 100%;
padding: 11px;
background: #12a875;
border-radius: 9px;
margin-top: 4px;
}
.hint {
position: absolute;
right: 15px;
top: 15px;
width: 210px;
background: rgba(13, 20, 34, 0.9);
padding: 12px;
border-radius: 12px;
color: #aeb9cd;
font-size: 13px;
line-height: 1.4;
}
/* ================= ИГРА ================= */
#game {
background: #75cfff;
}
#gameCanvas {
display: block;
width: 100%;
height: calc(100vh - 58px);
}
.mobileControls {
display: none;
position: absolute;
left: 0;
right: 0;
bottom: 20px;
justify-content: center;
gap: 12px;
z-index: 30;
}
.mobileControls button {
width: 62px;
height: 62px;
border-radius: 50%;
background: rgba(20, 30, 50, 0.85);
font-size: 23px;
}
.gameInfo {
position: absolute;
left: 15px;
top: 72px;
padding: 9px 13px;
border-radius: 10px;
background: rgba(10, 15, 25, 0.75);
}
/* ================= МОБИЛЬНЫЙ ================= */
@media (max-width: 650px) {
.hero h1 {
font-size: 30px;
}
.hero {
margin-top: 30px;
}
.panel {
width: 165px;
font-size: 12px;
}
.hint {
display: none;
}
.mobileControls {
display: flex;
}
}
</style>
</head>
<body>
<!-- =====================================================
ГЛАВНОЕ МЕНЮ
===================================================== -->
<section id="home" class="screen active">
<header class="header">
<div class="logo">🌐 <span>МОЙ</span> МИР ИГР</div>
<button onclick="openCreator()">+ Создать игру</button>
</header>
<div class="hero">
<h1>Играй и создавай</h1>
<p>
Это beta-версия моей собственной игровой платформы. Здесь будут находиться игры, которые
создают пользователи.
</p>
</div>
<div class="sectionTitle">🎮 Игры</div>
<div class="games">
<div class="card">
<div class="preview p1">🧱🏃</div>
<div class="cardContent">
<h3>Прыжок по небу</h3>
<p>Доберись до конца трассы, прыгая по платформам.</p>
<button class="playBtn" onclick="startGame('obby')">▶ Играть</button>
</div>
</div>
<div class="card">
<div class="preview p2">🌲💎</div>
<div class="cardContent">
<h3>Лесной путь</h3>
<p>Беги по лесу и собирай светящиеся кристаллы.</p>
<button class="playBtn" onclick="startGame('forest')">▶ Играть</button>
</div>
</div>
</div>
<div class="sectionTitle">🛠 Создай свою игру</div>
<div class="createLarge">
<h2>Твой собственный 3D-мир</h2>
<p style="color: #9ca9c2; margin-top: 8px">
Добавляй платформы, блоки, кристаллы, деревья и другие объекты.
</p>
<button onclick="openCreator()">🛠 Открыть 3D-конструктор</button>
</div>
</section>
<!-- =====================================================
СОЗДАТЕЛЬ
===================================================== -->
<section id="creator" class="screen">
<div class="topbar">
<button class="backBtn" onclick="showHome()">← Меню</button>
<div class="title">🛠 Создатель игры</div>
</div>
<div class="creatorArea">
<canvas id="editor"></canvas>
<div class="panel">
<h3>Создание</h3>
<input id="gameName" placeholder="Название игры" maxlength="30" />
<button class="tool selected" data-tool="platform">🟩 Платформа</button>
<button class="tool" data-tool="block">🧱 Блок</button>
<button class="tool" data-tool="crystal">💎 Кристалл</button>
<button class="tool" data-tool="tree">🌲 Дерево</button>
<button class="tool" data-tool="lava">🔥 Лава</button>
<button class="tool" data-tool="finish">🏁 Финиш</button>
<button class="publishBtn" onclick="publishGame()">🚀 Опубликовать</button>
</div>
<div class="hint">
<b>Как создавать?</b>
<br /><br />
1. Напиши название игры.
<br />
2. Выбери объект.
<br />
3. Нажми на карту.
<br />
4. Добавляй объекты.
<br />
5. Нажми «Опубликовать».
</div>
</div>
</section>
<!-- =====================================================
ИГРА
===================================================== -->
<section id="game" class="screen">
<div class="topbar">
<button class="backBtn" onclick="exitGame()">← Выйти</button>
<div class="title" id="gameTitle">Игра</div>
</div>
<canvas id="gameCanvas"></canvas>
<div class="gameInfo" id="gameInfo">⭐ 0</div>
<div class="mobileControls">
<button id="left">←</button>
<button id="jump">↑</button>
<button id="right">→</button>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/three@0.161.0/build/three.min.js"></script>
<script>
/* =====================================================
ЭКРАНЫ
===================================================== */
const home = document.getElementById("home");
const creator = document.getElementById("creator");
const game = document.getElementById("game");
function showScreen(screen) {
document.querySelectorAll(".screen").forEach((s) => {
s.classList.remove("active");
});
screen.classList.add("active");
}
function showHome() {
stopEditor();
showScreen(home);
}
function openCreator() {
showScreen(creator);
initEditor();
}
/* =====================================================
3D РЕДАКТОР
===================================================== */
let editorScene;
let editorCamera;
let editorRenderer;
let editorFrame;
let currentTool = "platform";
function initEditor() {
const canvas = document.getElementById("editor");
editorScene = new THREE.Scene();
editorScene.background = new THREE.Color(0x7ecfff);
editorCamera = new THREE.PerspectiveCamera(
60,
canvas.clientWidth / canvas.clientHeight,
0.1,
1000,
);
editorCamera.position.set(10, 11, 15);
editorCamera.lookAt(0, 0, 0);
editorRenderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
});
editorRenderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
editorRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const light = new THREE.HemisphereLight(0xffffff, 0x49634b, 2);
editorScene.add(light);
const sun = new THREE.DirectionalLight(0xffffff, 2);
sun.position.set(10, 20, 10);
editorScene.add(sun);
/* земля */
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(50, 50),
new THREE.MeshStandardMaterial({
color: 0x4c9850,
}),
);
ground.rotation.x = -Math.PI / 2;
ground.name = "GROUND";
editorScene.add(ground);
/* сетка */
const grid = new THREE.GridHelper(50, 25, 0xffffff, 0x7ca77c);
editorScene.add(grid);
/* стартовая платформа */
createObject("platform", 0, 0.5, 0);
editorLoop();
}
function createObject(type, x, y, z) {
let object;
if (type === "platform") {
object = new THREE.Mesh(
new THREE.BoxGeometry(5, 1, 5),
new THREE.MeshStandardMaterial({
color: 0x3b82f6,
}),
);
}
if (type === "block") {
object = new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2),
new THREE.MeshStandardMaterial({
color: 0xf97316,
}),
);
}
if (type === "crystal") {
object = new THREE.Mesh(
new THREE.OctahedronGeometry(0.8),
new THREE.MeshStandardMaterial({
color: 0x22d3ee,
emissive: 0x075985,
}),
);
}
if (type === "tree") {
object = new THREE.Group();
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.3, 0.4, 2),
new THREE.MeshStandardMaterial({
color: 0x75451f,
}),
);
trunk.position.y = 1;
const leaves = new THREE.Mesh(
new THREE.ConeGeometry(1.5, 3, 8),
new THREE.MeshStandardMaterial({
color: 0x16803c,
}),
);
leaves.position.y = 3;
object.add(trunk);
object.add(leaves);
}
if (type === "lava") {
object = new THREE.Mesh(
new THREE.BoxGeometry(3, 0.3, 3),
new THREE.MeshStandardMaterial({
color: 0xff3000,
emissive: 0x881100,
}),
);
}
if (type === "finish") {
object = new THREE.Mesh(
new THREE.BoxGeometry(2, 3, 0.3),
new THREE.MeshStandardMaterial({
color: 0xffd700,
}),
);
}
if (!object) return;
object.position.set(x, y, z);
editorScene.add(object);
}
function editorLoop() {
if (!creator.classList.contains("active")) return;
editorFrame = requestAnimationFrame(editorLoop);
editorRenderer.render(editorScene, editorCamera);
}
function stopEditor() {
if (editorFrame) {
cancelAnimationFrame(editorFrame);
editorFrame = null;
}
}
/* =====================================================
ВЫБОР ИНСТРУМЕНТА
===================================================== */
document.querySelectorAll(".tool").forEach((button) => {
button.addEventListener("click", () => {
document.querySelectorAll(".tool").forEach((b) => {
b.classList.remove("selected");
});
button.classList.add("selected");
currentTool = button.dataset.tool;
});
});
/* =====================================================
КЛИК ПО КАРТЕ
===================================================== */
document.getElementById("editor").addEventListener("click", (event) => {
if (!editorScene) return;
const canvas = document.getElementById("editor");
const rect = canvas.getBoundingClientRect();
const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1,
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, editorCamera);
const ground = editorScene.getObjectByName("GROUND");
const hits = raycaster.intersectObject(ground);
if (!hits.length) return;
const point = hits[0].point;
let y = 1;
if (currentTool === "platform") y = 0.5;
if (currentTool === "tree") y = 0;
if (currentTool === "lava") y = 0.15;
if (currentTool === "finish") y = 1.5;
createObject(currentTool, Math.round(point.x), y, Math.round(point.z));
});
/* =====================================================
ПУБЛИКАЦИЯ
===================================================== */
function publishGame() {
let name = document.getElementById("gameName").value.trim();
if (!name) {
name = "Моя новая игра";
}
const data = {
name: name,
created: Date.now(),
objects: editorScene ? editorScene.children.length : 0,
};
localStorage.setItem("my_beta_game", JSON.stringify(data));
alert(
"🎉 Игра сохранена!\n\n" +
"Название: " +
name +
"\n\nВ следующей версии она будет " +
"появляться прямо в каталоге.",
);
}
/* =====================================================
ИГРОВАЯ СЦЕНА
===================================================== */
let scene;
let camera;
let renderer;
let frame;
let player;
let velocityY = 0;
let movingLeft = false;
let movingRight = false;
let score = 0;
function startGame(type) {
showScreen(game);
document.getElementById("gameTitle").textContent =
type === "obby" ? "🧱 Прыжок по небу" : "🌲 Лесной путь";
score = 0;
document.getElementById("gameInfo").textContent = "⭐ 0";
initGame(type);
}
function initGame(type) {
const canvas = document.getElementById("gameCanvas");
scene = new THREE.Scene();
scene.background = type === "obby" ? new THREE.Color(0x78cfff) : new THREE.Color(0x183c2b);
camera = new THREE.PerspectiveCamera(
60,
canvas.clientWidth / canvas.clientHeight,
0.1,
1000,
);
renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
});
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const light = new THREE.HemisphereLight(0xffffff, 0x444444, 2);
scene.add(light);
createPlayer();
if (type === "obby") createObby();
else createForest();
gameLoop();
}
/* =====================================================
ИГРОК
===================================================== */
function createPlayer() {
player = new THREE.Mesh(
new THREE.BoxGeometry(1, 1.7, 1),
new THREE.MeshStandardMaterial({
color: 0xffd166,
}),
);
player.position.set(0, 1, 5);
scene.add(player);
}
/* =====================================================
ОББИ
===================================================== */
function createObby() {
const platforms = [
[0, 0, 5],
[0, 0, 0],
[3, 1, -5],
[-3, 2, -10],
[2, 3, -15],
[-2, 4, -20],
[2, 5, -25],
[0, 6, -30],
];
platforms.forEach((p) => {
const platform = new THREE.Mesh(
new THREE.BoxGeometry(5, 1, 5),
new THREE.MeshStandardMaterial({
color: 0x725cff,
}),
);
platform.position.set(p[0], p[1], p[2]);
scene.add(platform);
});
}
/* =====================================================
ЛЕС
===================================================== */
function createForest() {
const ground = new THREE.Mesh(
new THREE.BoxGeometry(14, 1, 100),
new THREE.MeshStandardMaterial({
color: 0x3e7d3b,
}),
);
ground.position.set(0, -0.5, -35);
scene.add(ground);
for (let z = 0; z > -90; z -= 8) {
createTree(-5, z);
createTree(5, z);
}
for (let z = -10; z > -85; z -= 13) {
const obstacle = new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2),
new THREE.MeshStandardMaterial({
color: 0xef4444,
}),
);
obstacle.position.set(
Math.random() > 0.5 ? 2 : -2,
1,
z,
);
scene.add(obstacle);
}
for (let z = -5; z > -90; z -= 15) {
const crystal = new THREE.Mesh(
new THREE.OctahedronGeometry(0.55),
new THREE.MeshStandardMaterial({
color: 0x22d3ee,
emissive: 0x075985,
}),
);
crystal.position.set(0, 1, z);
scene.add(crystal);
}
}
function createTree(x, z) {
const tree = new THREE.Mesh(
new THREE.ConeGeometry(1.4, 4, 8),
new THREE.MeshStandardMaterial({
color: 0x15803d,
}),
);
tree.position.set(x, 2, z);
scene.add(tree);
}
/* =====================================================
УПРАВЛЕНИЕ
===================================================== */
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowLeft" || e.key === "a") movingLeft = true;
if (e.key === "ArrowRight" || e.key === "d") movingRight = true;
if (e.key === "ArrowUp" || e.key === "w" || e.key === " ") jump();
});
document.addEventListener("keyup", (e) => {
if (e.key === "ArrowLeft" || e.key === "a") movingLeft = false;
if (e.key === "ArrowRight" || e.key === "d") movingRight = false;
});
function jump() {
if (player && player.position.y <= 1.05) {
velocityY = 0.25;
}
}
/* =====================================================
МОБИЛЬНЫЕ КНОПКИ
===================================================== */
const left = document.getElementById("left");
const right = document.getElementById("right");
const jumpButton = document.getElementById("jump");
left.addEventListener("touchstart", (e) => {
e.preventDefault();
movingLeft = true;
});
left.addEventListener("touchend", (e) => {
e.preventDefault();
movingLeft = false;
});
right.addEventListener("touchstart", (e) => {
e.preventDefault();
movingRight = true;
});
right.addEventListener("touchend", (e) => {
e.preventDefault();
movingRight = false;
});
jumpButton.addEventListener("touchstart", (e) => {
e.preventDefault();
jump();
});
/* =====================================================
ИГРОВОЙ ЦИКЛ
===================================================== */
function gameLoop() {
if (!game.classList.contains("active")) return;
frame = requestAnimationFrame(gameLoop);
if (!player) return;
if (movingLeft) player.position.x -= 0.12;
if (movingRight) player.position.x += 0.12;
velocityY -= 0.012;
player.position.y += velocityY;
if (player.position.y < 1) {
player.position.y = 1;
velocityY = 0;
}
/*
Автоматическое движение
вперёд
*/
player.position.z -= 0.035;
camera.position.x = player.position.x;
camera.position.y = player.position.y + 5;
camera.position.z = player.position.z + 12;
camera.lookAt(player.position.x, player.position.y, player.position.z - 6);
renderer.render(scene, camera);
score = Math.max(score, Math.floor(Math.abs(player.position.z - 5)));
document.getElementById("gameInfo").textContent = "⭐ " + score;
}
/* =====================================================
ВЫХОД
===================================================== */
function exitGame() {
if (frame) cancelAnimationFrame(frame);
frame = null;
scene = null;
player = null;
showScreen(home);
}
</script>
</body>
</html>