В первой части этой стати мы с нейросетью Qwen пытались создать аналог Pong! в зимнем сеттинге (Снежинка вместо шарика и на фоне падает снег). И у нас получилась вполне рабочая и симпатичная игра. Посмотреть саму игру вы можете тут.
Не знаю, как долго она будет храниться на серверах, поэтому не могу ручаться за работоспособность ссылки для тех, кто увидит статью сильно позже её выхода.
Итак, с понгом 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>