В первой части этой стати мы с нейросетью Qwen пытались создать аналог Pong! в зимнем сеттинге (Снежинка вместо шарика и на фоне падает снег). И у нас получилась вполне рабочая и симпатичная игра. Посмотреть саму игру вы можете тут:
https://chat.qwen.ai/s/deploy/386f16fb-096d-4fe2-b706-a8c72374825c
Не знаю, как долго она будет храниться на серверах, поэтому не могу ручаться за работоспособность ссылки для тех, кто увидит статью сильно позже её выхода.
Итак, с понгом Qwen справился, настало время для более сложных испытаний. Посмотрим, как он справится с платформером. Буду давать ему все тот же «Снежный» сеттинг. Поэтому прыгать мы будем по льдам. Я написал о том, что я хочу ИИ и получил такой ответ:
Qwen:
✅ Пример: что будет в игре
🎄 Снежный персонаж прыгает по льдинкам
🏆 Счёт: сколько льдинок прошёл
🔁 Рестарт при падении
Я: Хорошо, давай.
Код первой итерации платформера составил 335 строчек. Игра выглядела так:

Что ж, для прототипа неплохо, но это был даже далеко не MVP. Например, яйцо прыгало только вверх и не управлялось вправо-влево. Да и при чем тут вообще яйцо?
Давайте попробуем заменить это яйцо на что-нибудь более тематическое, например, снеговика. А дальше будем разбираться с механиками игры.

Снеговик получился забавный, но мы тут за тем, чтобы оценить возможности кодинга, а не дизайна, поэтому пусть будет так. Разве что, по какой-то причине, его модель находится не на платформе, а над ней. Исправим.
Далее я попытался добавить автоматическую генерацию платформ для бесконечной игры.
Тут возникла первая серьезная проблема: платформы генерировались слишком далеко от персонажа (он просто не доставал до них прыжком) и слишком близко друг к другу. После нескольких попыток я решил отключить автоматическую генерацию и бесконечную игру.

Была еще одна проблема: с каждым прыжком нашего персонажа камера опускалась вниз вместо того, чтобы следовать за ним наверх. В итоге уже на 5-6 прыжке мы теряли снеговика из виду. Эту проблему тоже решили короткой перепиской с Qwen и получили более-менее играбельный платформер. Правда, уровень состоял всего из 7 платформ и при прохождении уровня ничего не происходило. Снеговик просто гордо стоял на вершине. Хотя по промпту игра должна была перевести игрока на новый уровень.

Я добавил флаг на последнюю платформу и при достижении флага должен был запуститься экран победы. Но ничего не произошло. Снеговик все так же грустно стоял на вершине, но уже рядом с флагом.

Пусть и не сразу, но нам удалось решить вопрос с окончанием уровня. После того, как персонаж наступал на последнюю платформу стало выходить окно с поздравлением и предложением начать второй уровень:

На втором уровне игрока должно было ждать усложнение: некоторые платформы должны двигаться. Никаких проблем с движущимися платформами не возникло, персонаж взаимодействовал с ними аналогично статичным. Единственной проблемой второго уровня оказалось то, что он бы последним. Поэтому, после его прохождения выходило окно с поздравлением игрока и предложением пройти второй уровень снова. Давайте попробуем добавить третий уровень и, например, окно победы в игре.

Далее мы с Qwen создали третий уровень, где все платформы двигались. Сначала это вызвало проблему: первая платформа, с которой стартовал снеговик тоже двигалась и почти сразу сбрасывала игрока. Эта проблема была быстро решена – первую и последнюю платформу я сделал статичной, а остальные двигались. Дальше, после прохождения финального уровня открывалось окно, в котором игроку сообщалось о его победе: все уровни были пройдены, а игра закончена.

В итоге нам удалось сделать платформер за пару часов. Его можно было бы масштабировать и улучшать, но мы получили достаточное качество и продолжительность игры, чтобы понять: нейросети с этим справились. Итоговый результат – готовая игра. В нее вы можете поиграть по ссылке: https://chat.qwen.ai/s/deploy/69dea8a8-a8e2-434a-805b-c963a62ad593
Что нам не удалось?
Не удалось решить проблему с прилипанием персонажа к платформе. Если персонаж встаёт на движущуюся платформу, то он падает после того, как она выезжает из-под него. Также по какой-то причине игра ускоряется с каждым следующим уровнем. Видимо, это происходит из-за того, что ИИ считает, что ускорение механик усложняет игру. Попытки исправить это ни к чему не привели. Автоматическая генерация уровней тоже вышла неудачной.
Что нам удалось?
Мы создали платформер из трех уровней в снежном сеттинге. Игровым персонажем у нас выступил снеговик, а на фоне шел снег. Сами платформы были похожи на льдины. Каждый последующий уровень был сложнее предыдущего, а по прохождении игры вы получали поздравления и предложение начать игру сначала. Основные проблемы присутствовали на этапе создания первого уровня и ключевых механик, далее игра масштабировалась достаточно быстро. Итоговый код игры составил ровно 600 строчек.
На создание игры было потрачено около двух часов времени и больше ничего. Сама нейросеть абсолютно бесплатна.
Итак, готовы ли нейросети заменить разработчиков? Пока на этот вопрос сложно ответить, для этого нужно устроить ей по-настоящему сложный вызов. Как насчет того, чтобы сделать "Героев меча и магии 3"? Попробуем в третьей части этой стати.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Снежный прыжок</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(to bottom, #1e3c72, #2a5298);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: Arial, sans-serif;
}
#game-container {
position: relative;
width: 400px;
height: 600px;
background: linear-gradient(to top, #000428, #004e92);
border: 4px solid #4facfe;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 0 20px rgba(79, 172, 254, 0.5);
}
#score {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
z-index: 5;
}
#best-score {
position: absolute;
top: 60px;
left: 20px;
color: #ffd700;
font-size: 18px;
font-weight: bold;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
z-index: 5;
}
#hint {
position: absolute;
top: 90px;
left: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
z-index: 5;
}
#game-over, .level-complete {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 30;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s;
}
#game-over.active, .level-complete.active {
opacity: 1;
pointer-events: all;
}
#restart-btn, #next-level-btn, #restart-all-btn {
margin-top: 20px;
padding: 12px 24px;
background: #4facfe;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 18px;
}
#next-level-btn {
background: #00f2fe;
box-shadow: 0 0 10px rgba(0, 242, 254, 0.5);
}
.snow-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.snow {
position: absolute;
width: 6px;
height: 6px;
background: white;
border-radius: 50%;
opacity: 0.8;
}
#player {
position: absolute;
width: 40px;
height: 50px;
transform: translate(-50%, -50%);
transition: transform 0.1s;
z-index: 10;
}
.head {
position: absolute;
width: 24px;
height: 24px;
background: white;
border-radius: 50%;
top: 0;
left: 8px;
box-shadow: 0 0 5px rgba(255, 255, 255, 0.6);
}
.body {
position: absolute;
width: 30px;
height: 30px;
background: white;
border-radius: 50%;
bottom: 0;
left: 5px;
box-shadow: 0 0 5px rgba(255, 255, 255, 0.6);
}
.eye {
position: absolute;
width: 5px;
height: 5px;
background: #333;
border-radius: 50%;
top: 12px;
}
.eye.left {
left: 12px;
}
.eye.right {
left: 19px;
}
.mouth {
position: absolute;
width: 10px;
height: 3px;
background: #333;
border-radius: 2px;
top: 18px;
left: 15px;
}
.arm {
position: absolute;
width: 6px;
height: 18px;
background: #ddd;
border-radius: 3px;
top: 12px;
}
.arm.left {
left: 2px;
transform: rotate(20deg);
}
.arm.right {
right: 2px;
transform: rotate(-20deg);
}
#player.jump .arm.left {
animation: wave-left 0.5s infinite alternate;
}
#player.jump .arm.right {
animation: wave-right 0.5s infinite alternate;
}
@keyframes wave-left {
0% { transform: rotate(20deg); }
100% { transform: rotate(40deg); }
}
@keyframes wave-right {
0% { transform: rotate(-20deg); }
100% { transform: rotate(-40deg); }
}
#player.squish {
transform: translate(-50%, -50%) scaleY(0.8);
}
#player.fall {
animation: blink 0.3s infinite;
}
@keyframes blink {
0%, 80% { opacity: 1; }
90%, 100% { opacity: 0.5; }
}
.platform {
position: absolute;
width: 100px;
height: 15px;
background: linear-gradient(to bottom, #a0e7ff, #d0f0ff);
border-radius: 10px;
box-shadow: 0 0 8px rgba(255, 255, 255, 0.4);
opacity: 1;
transition: opacity 0.3s;
}
.flag {
position: absolute;
width: 12px;
height: 20px;
background: #ff4757;
border-radius: 2px 0 0 2px;
}
.flag::after {
content: '';
position: absolute;
top: 0;
right: -8px;
width: 10px;
height: 10px;
background: #00f;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
</style>
</head>
<body>
<div id="game-container">
<div id="score">Счёт: 0</div>
<div id="best-score">Рекорд: 0</div>
<div id="hint">Подсказка: Доберись до флага!</div>
<div id="player">
<div class="head"></div>
<div class="body"></div>
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
<div class="arm left"></div>
<div class="arm right"></div>
</div>
<div class="snow-effect" id="snow-effect"></div>
<div id="game-over">
<h2>Вы упали!</h2>
<p>Счёт: <span id="final-score">0</span></p>
<button id="restart-btn">Начать сначала</button>
</div>
<div class="level-complete" id="level-complete">
<h2>Уровень пройден! 🎉</h2>
<button id="next-level-btn">Следующий уровень</button>
</div>
<div class="level-complete" id="final-win">
<h2>🎉 Победа! Все уровни пройдены!</h2>
<button id="restart-all-btn">Начать сначала</button>
</div>
</div>
<script>
const player = document.getElementById('player');
const gameContainer = document.getElementById('game-container');
const scoreElement = document.getElementById('score');
const bestScoreElement = document.getElementById('best-score');
const gameOverScreen = document.getElementById('game-over');
const levelCompleteScreen = document.getElementById('level-complete');
const finalWinScreen = document.getElementById('final-win');
const finalScoreElement = document.getElementById('final-score');
const restartBtn = document.getElementById('restart-btn');
const nextLevelBtn = document.getElementById('next-level-btn');
const restartAllBtn = document.getElementById('restart-all-btn');
const snowEffect = document.getElementById('snow-effect');
const gameWidth = 400;
const gameHeight = 600;
const playerWidth = 40;
const playerHeight = 50;
const platformWidth = 100;
const platformHeight = 15;
const playerSpeed = 6;
let playerX = gameWidth / 2 - playerWidth / 2;
let playerY = 550 - playerHeight / 2;
let velocityY = 0;
let gravity = 0.6;
let jumpPower = -13.5;
let isJumping = false;
let cameraY = 0;
let score = 0;
let platforms = [];
let gameActive = true;
let bestScore = 0;
let currentLevel = 1;
let levelFinished = false;
const keys = { w: false, a: false, d: false };
function createSnowflakes() {
for (let i = 0; i < 30; i++) {
const snow = document.createElement('div');
snow.classList.add('snow');
snow.style.left = `${Math.random() * 100}%`;
snow.style.top = `${Math.random() * 100}%`;
snow.style.opacity = Math.random() * 0.7 + 0.3;
snow.dataset.speed = Math.random() * 1.5 + 0.5;
snowEffect.appendChild(snow);
}
}
function animateSnowflakes() {
const snows = document.querySelectorAll('.snow');
if (!snows.length) return;
snows.forEach(snow => {
let top = parseFloat(snow.style.top) + parseFloat(snow.dataset.speed);
if (top > 100) top = -5;
snow.style.top = `${top}%`;
});
}
function createLevel1() {
platforms = [
{ x: 150, y: 550, touched: false, moving: false },
{ x: 250, y: 480, touched: false, moving: false },
{ x: 100, y: 410, touched: false, moving: false },
{ x: 200, y: 340, touched: false, moving: false },
{ x: 300, y: 270, touched: false, moving: false },
{ x: 120, y: 200, touched: false, moving: false },
{ x: 220, y: 130, touched: false, moving: false },
{ x: 150, y: 60, touched: false, moving: false }
];
}
function createLevel2() {
platforms = [
{ x: 180, y: 550, touched: false, moving: false },
{ x: 280, y: 480, touched: false, moving: true, direction: 1, speed: 1 },
{ x: 80, y: 410, touched: false, moving: true, direction: -1, speed: 1.2 },
{ x: 200, y: 340, touched: false, moving: false },
{ x: 300, y: 270, touched: false, moving: true, direction: 1, speed: 1.5 },
{ x: 100, y: 200, touched: false, moving: true, direction: -1, speed: 1 },
{ x: 220, y: 130, touched: false, moving: false },
{ x: 150, y: 60, touched: false, moving: false }
];
}
// --- ИСПРАВЛЕНИЕ: стартовая платформа — статичная ---
function createLevel3() {
platforms = [
{ x: 180, y: 550, touched: false, moving: false }, // ✅ Статичная
{ x: 200, y: 480, touched: false, moving: true, direction: -1, speed: 2.2 },
{ x: 180, y: 410, touched: false, moving: true, direction: 1, speed: 2.5 },
{ x: 200, y: 340, touched: false, moving: true, direction: -1, speed: 2 },
{ x: 180, y: 270, touched: false, moving: true, direction: 1, speed: 2.3 },
{ x: 200, y: 200, touched: false, moving: true, direction: -1, speed: 2.1 },
{ x: 180, y: 130, touched: false, moving: false },
{ x: 150, y: 60, touched: false, moving: false }
];
}
function renderPlatforms() {
document.querySelectorAll('.platform').forEach(p => p.remove());
document.querySelectorAll('.flag').forEach(f => f.remove());
platforms.forEach((p, index) => {
const platform = document.createElement('div');
platform.classList.add('platform');
platform.style.left = `${p.x}px`;
platform.style.top = `${p.y - cameraY}px`;
platform.dataset.y = p.y;
gameContainer.appendChild(platform);
if (index === platforms.length - 1) {
const flag = document.createElement('div');
flag.classList.add('flag');
flag.style.left = `${p.x + platformWidth - 20}px`;
flag.style.top = `${p.y - cameraY - 15}px`;
gameContainer.appendChild(flag);
}
});
}
function updateMovingPlatforms() {
platforms.forEach(p => {
if (p.moving) {
p.x += p.direction * p.speed;
if (p.x <= 50 || p.x >= gameWidth - platformWidth - 50) {
p.direction *= -1;
}
}
});
}
function jump() {
if (isJumping) return;
velocityY = jumpPower;
isJumping = true;
}
function checkCollision() {
const playerBottom = playerY + playerHeight / 2;
const playerCenterX = playerX + playerWidth / 2;
for (let p of platforms) {
if (
playerBottom >= p.y &&
playerBottom <= p.y + 10 &&
playerCenterX >= p.x &&
playerCenterX <= p.x + platformWidth &&
velocityY > 0
) {
isJumping = false;
velocityY = 0;
playerY = p.y - playerHeight / 2;
if (!p.touched) {
p.touched = true;
score++;
scoreElement.textContent = `Счёт: ${score}`;
setTimeout(() => {
const el = document.querySelector(`.platform[data-y="${p.y}"]`);
if (el) el.style.opacity = '0';
}, 500);
}
return true;
}
}
return false;
}
function gameLoop() {
if (!gameActive) return;
if (keys.a) playerX = Math.max(0, playerX - playerSpeed);
if (keys.d) playerX = Math.min(gameWidth - playerWidth, playerX + playerSpeed);
if (keys.w && !isJumping) jump();
velocityY += gravity;
playerY += velocityY;
checkCollision();
updateMovingPlatforms();
const targetCameraY = Math.max(0, playerY - gameHeight * 0.4);
cameraY += (targetCameraY - cameraY) * 0.1;
if (playerY > gameHeight + 100) {
endGame();
}
// --- ПРОВЕРКА ПОБЕДЫ ---
const lastPlatform = platforms[platforms.length - 1];
const playerBottom = playerY + playerHeight / 2;
const playerCenterX = playerX + playerWidth / 2;
if (
!levelFinished &&
playerBottom >= lastPlatform.y - 10 &&
playerBottom <= lastPlatform.y + 20 &&
playerCenterX >= lastPlatform.x &&
playerCenterX <= lastPlatform.x + platformWidth
) {
console.log(`🎉 Уровень ${currentLevel} пройден!`);
levelFinished = true;
setTimeout(() => {
if (currentLevel < 3) {
levelCompleteScreen.classList.add('active');
} else {
finalWinScreen.classList.add('active');
}
}, 600);
}
player.classList.remove('jump', 'squish', 'fall');
if (velocityY < 0) player.classList.add('jump');
else if (velocityY > 5) player.classList.add('fall');
if (!isJumping) player.classList.add('squish');
player.style.left = `${playerX + playerWidth / 2}px`;
player.style.top = `${playerY - cameraY}px`;
renderPlatforms();
animateSnowflakes();
requestAnimationFrame(gameLoop);
}
function endGame() {
gameActive = false;
finalScoreElement.textContent = score;
gameOverScreen.classList.add('active');
}
function startLevel(level) {
if (level > 3) return;
playerX = gameWidth / 2 - playerWidth / 2;
playerY = 550 - playerHeight / 2;
velocityY = 0;
cameraY = 0;
isJumping = false;
gameActive = true;
levelFinished = false;
scoreElement.textContent = `Счёт: ${score}`;
gameOverScreen.classList.remove('active');
levelCompleteScreen.classList.remove('active');
finalWinScreen.classList.remove('active');
if (level === 1) createLevel1();
else if (level === 2) createLevel2();
else if (level === 3) createLevel3();
currentLevel = level;
requestAnimationFrame(gameLoop);
}
restartBtn.addEventListener('click', () => {
score = 0;
startLevel(1);
});
nextLevelBtn.addEventListener('click', () => {
startLevel(currentLevel + 1);
});
restartAllBtn.addEventListener('click', () => {
score = 0;
startLevel(1);
});
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (key === 'w') keys.w = true;
if (key === 'a') keys.a = true;
if (key === 'd') keys.d = true;
if (key === ' ') {
e.preventDefault();
jump();
}
});
document.addEventListener('keyup', (e) => {
const key = e.key.toLowerCase();
if (key === 'w') keys.w = false;
if (key === 'a') keys.a = false;
if (key === 'd') keys.d = false;
});
// Запуск
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
createSnowflakes();
startLevel(1);
});
} else {
createSnowflakes();
startLevel(1);
}
</script>
</body>
</html>