Геймдев будущего? Разрабатываем игры через нейросети. Часть 1

Ни для кого уже не секрет, что нейросети сегодня используются при разработке игр: создание концептов, арта, написание историй. А что насчет кодинга? Могут ли нейросети кодить вместо разработчиков и создавать полноценные игры? За ответом на этот вопрос я обратился к одной из лучших по мнению многих обзорщиков нейросетей для разработки – Qwen. Кстати, эта нейросеть бесплатна и без проблем работает в РФ.

Давайте начнем с простого. База для любого разработчика игр – аналог Pong! Но с небольшим усложнением. Мне захотелось, чтобы вместо шарика в игре летала снежинка. Я вбил самый простой промпт:

Напиши игру типа Pong! Вместо шарика сделай снежинку

В результате в течение 1 минуты нейросеть написала 475 строк кода и выдала мне саммари:

Это полноценная HTML-игра в стиле Pong с снежинкой! Основные особенности:

  • Вместо шарика используется анимированная снежинка с вращением
  • Стильный темный интерфейс с градиентами и свечением
  • Анимированный снег в фоне
  • Реалистичная физика отскоков
  • Подсчет очков до 5
  • Эффекты при отскоке (вращение снежинки)
  • Адаптивный дизайн

Управление:

  • Игрок 1: W и S
  • Игрок 2: Стрелки вверх и вниз

🎉 Особенности дизайна:

  • Градиентный фон с зимней тематикой
  • Анимированные снежинки в фоне
  • Свечение элементов
  • Плавные переходы и анимации
  • Экран окончания игры с кнопкой перезапуска

Игра полностью автономна и работает в любом современном браузере!

Так выглядела самая первая версия игры
Так выглядела самая первая версия игры

То есть, Qwen в действительности сделала даже больше, чем я просил. Нейронка добавила в игру фон со стильными снежинками и добавила в игру полноценный PVP режим. Хотя, режима PVE в игре не было. Справедливости ради, в этом моя вина – я подразумевал его наличие у себя в голове, но не написал об этом в промпте. Что ж, давайте исправим:

Я: Можешь добавить в игру PVE режим?

Qwen: Конечно! Вот обновлённая версия игры Snow Pong с режимом PVE (игрок против компьютера). Я добавил:

  • Кнопку выбора режима: PVP или PVE
  • В режиме PVE правая ракетка управляется ИИ
  • ИИ отслеживает положение снежинки с небольшой задержкой для реалистичности
  • Сохраняется вся графика и анимации

Далее ещё минута, ещё 544 строчки кода и перевод мной новая версия игры: теперь я могу выбрать режим игры: PVP или PVE. Выбираем игру против компьютера и… понимаем, что это слишком легко. Qwen пожалел нас и сделал ракетку ИИ-противника очень медленной. С такой скоростью он отражал только один удар из пяти. Хорошо, что вместе с этим он предложил:

Qwen: Хочешь добавить уровни сложности (лёгкий/средний/сложный ИИ)?

Я: Да, давай добавим уровни сложности

Новое минутное ожидание и еще 536 строчек кода и передо мной новая игра с уровнями сложности. И вместе с этим, новое предложение от ИИ:

Qwen: Хочешь добавить звуки или счётчик очков за сессию? 😊

Я: Да, давай добавим звуки и счетчик очков за сессию, а так же переведем управление игрока в PVE режиме на стрелочки

Управление на W/S не всегда было удобно, так как эти символы не работали в верхнем регистре или в русской раскладке. Нейросеть в переписке со мной предлагала исправить эти проблемы, но я решил не зацикливаться на этом и просто перевести управление на стрелочки.

Итак, еще пару минут (по мере усложнения кода увеличивается и время ожидания) и 614 строчек кода спустя нас ждет новая версия игры. Новая версия оказалась проблемной – игра крашилась сразу же после запуска. После пары сообщений Qwen понял в чем была причина краша и исправил его. Но появилась новая проблема – теперь в PVE режиме ракетка игрока (на стрелочках) не двигалась. После недолгой переписки с ИИ мы решили проблему, на это ушло около пяти минут. Очень понравилась его решительность:

Я: Слушай, стрелочки все еще не работают

Qwen: Спасибо, что держишь меня в курсе! Давай раз и навсегда решим проблему с управлением стрелочками

Раз и навсегда решить проблему со стрелочками не удалось, поэтому я предложил альтернативу – управление второго игрока на клавишах O и L. Таким образом в PVP режиме игрок 1 управлял на WS, а игрок 2 на OL, а в PVE режиме игрок управлял на WS.

До идеальной игры осталась последняя деталь: боты в PVE режиме были слишком слабы – даже сильный бот не создавал никаких проблем. Я попросил его повысить уровень сложности и увеличить скорость передвижения ракетки у ботов – и все сработало. Теперь чтобы победить сложного бота надо постараться. Игра полностью готова и любой желающий может в неё поиграть:

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

<i>Я добавил меню игры</i>
Я добавил меню игры
<i>И окно победы/поражения</i>
И окно победы/поражения
<i>А так стал выглядеть сам геймплей</i>
А так стал выглядеть сам геймплей

Что я получил в итоге?

Полноценный симулятор Pong! со снежинкой вместо мячика. На фоне падал снег, добавляя игре атмосферы. В игре присутствовал счетчик очков. Удары ракетки по мячику и победа озвучивались. В игре было два режима: PVP и PVE и три уровня сложности для PVE режима. Режим и сложность выбирались в главном меню игры. Нейросеть отлично запоминает что мы с ней делали и по моей просьбе легко достает нужную мне версию игры. Итоговый код игры составил 701 строчку. Его я приложу ниже. Отмечу также, что мне не пришлось применить никаких знаний программирования для создания этой игры. Все что я делал – это ставил задачи нейросети.

Что я потратил?

Примерно полтора часа времени на диалог с ИИ с учетом времени ожидания ответа. Нейросеть бесплатна и ничего от меня не потребовала.

Получается, что нейросети уже сейчас могут успешно генерировать простые игры типа Pong!, даже с небольшими усложнениями. Но, что будет, если попросить их создать что-то более сложное? В следующий раз мы с Qwen попробуем создать что-то посложнее понга. Например, платформер. И я обязательно напишу об этом.
Код игры:

<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Snow Pong</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Arial', sans-serif; } body { background: linear-gradient(135deg, #1e3c72, #2a5298); display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; } .game-container { position: relative; width: 800px; height: 600px; background: rgba(10, 20, 40, 0.8); border-radius: 20px; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); overflow: hidden; border: 2px solid rgba(255, 255, 255, 0.2); } .game-title { text-align: center; color: white; padding: 20px; font-size: 28px; font-weight: bold; text-shadow: 0 0 10px rgba(135, 206, 250, 0.8); } .score-board { display: flex; justify-content: space-around; background: rgba(0, 0, 0, 0.3); padding: 15px; color: white; font-size: 24px; font-weight: bold; } .score { text-shadow: 0 0 5px rgba(135, 206, 250, 0.8); } .session-score { font-size: 16px; color: rgba(255, 255, 255, 0.7); margin-top: 5px; text-align: center; } .paddle { position: absolute; width: 15px; height: 100px; background: linear-gradient(to right, #4facfe, #00f2fe); border-radius: 10px; box-shadow: 0 0 15px rgba(79, 172, 254, 0.6); } .left-paddle { left: 30px; } .right-paddle { right: 30px; } /* --- ДВОЙНОЙ КРЕСТ — СНЕЖИНКА (настоящая!) --- */ .snowflake { position: absolute; width: 6px; height: 6px; background: white; border-radius: 50%; transform: translate(-50%, -50%); box-shadow: /* Горизонтальные лучи (← и →) */ -15px 0 0 rgba(255, 255, 255, 0.9), 15px 0 0 rgba(255, 255, 255, 0.9), /* Вертикальные лучи (↑ и ↓) */ 0 -15px 0 rgba(255, 255, 255, 0.9), 0 15px 0 rgba(255, 255, 255, 0.9), /* Диагональ ↖ и ↘ */ -11px -11px 0 rgba(255, 255, 255, 0.8), 11px 11px 0 rgba(255, 255, 255, 0.8), /* Диагональ ↗ и ↙ */ 11px -11px 0 rgba(255, 255, 255, 0.8), -11px 11px 0 rgba(255, 255, 255, 0.8); animation: rotate 8s linear infinite; filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8)); } @keyframes rotate { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } .center-line { position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: rgba(255, 255, 255, 0.2); transform: translateX(-50%); } .center-line::before { content: ''; position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; background: rgba(255, 255, 255, 0.2); border-radius: 50%; transform: translate(-50%, -50%); } .controls { text-align: center; color: rgba(255, 255, 255, 0.7); margin-top: 10px; font-size: 14px; } .game-over { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.5s; } .game-over.active { opacity: 1; pointer-events: all; } .game-over h2 { color: white; font-size: 48px; margin-bottom: 20px; text-shadow: 0 0 20px rgba(135, 206, 250, 0.8); } .game-over p { color: #4facfe; font-size: 24px; margin-bottom: 30px; } .restart-btn { background: linear-gradient(45deg, #4facfe, #00f2fe); color: white; border: none; padding: 15px 30px; font-size: 18px; border-radius: 25px; cursor: pointer; box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4); transition: all 0.3s; } .restart-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(79, 172, 254, 0.6); } .sound-toggle { position: absolute; top: 10px; right: 10px; background: transparent; border: none; color: rgba(255, 255, 255, 0.7); font-size: 20px; cursor: pointer; z-index: 5; } .snow-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .snow { position: absolute; background: white; border-radius: 50%; opacity: 0.8; pointer-events: none; } .start-screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 20; color: white; text-align: center; padding: 20px; } .start-screen h2 { font-size: 36px; margin-bottom: 20px; text-shadow: 0 0 10px rgba(135, 206, 250, 0.8); } .start-option { margin: 15px 0; font-size: 18px; color: rgba(255, 255, 255, 0.9); } .mode-btn, .difficulty-btn { display: inline-block; padding: 8px 16px; margin: 0 10px; border: 2px solid rgba(255, 255, 255, 0.3); background: transparent; color: white; border-radius: 20px; cursor: pointer; font-size: 14px; transition: all 0.3s; } .mode-btn.active, .difficulty-btn.active { background: rgba(79, 172, 254, 0.4); border-color: #4facfe; box-shadow: 0 0 10px rgba(79, 172, 254, 0.5); } .mode-btn:hover, .difficulty-btn:hover { background: rgba(255, 255, 255, 0.1); } .start-btn { margin-top: 30px; padding: 15px 40px; background: linear-gradient(45deg, #4facfe, #00f2fe); color: white; border: none; border-radius: 30px; font-size: 18px; cursor: pointer; box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4); transition: all 0.3s; } .start-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(79, 172, 254, 0.6); } .focus-hint { margin-top: 20px; padding: 10px 20px; background: rgba(255, 255, 255, 0.1); border-radius: 10px; color: #4facfe; font-size: 16px; cursor: pointer; border: 1px solid rgba(255, 255, 255, 0.3); } .debug-info { position: absolute; top: 10px; left: 10px; color: #00ff00; font-size: 12px; z-index: 10; background: rgba(0, 0, 0, 0.7); padding: 5px; border-radius: 5px; } </style> </head> <body> <div class="game-container"> <div class="game-title">❄️ Snow Pong ❄️</div> <button class="sound-toggle" id="sound-btn">🔊</button> <div class="score-board"> <div class="score" id="left-score">0</div> <div class="score" id="right-score">0</div> </div> <div class="session-score"> Всего голов за сессию: <span id="total-goals">0</span> </div> <div class="center-line"></div> <div class="paddle left-paddle" id="left-paddle"></div> <div class="paddle right-paddle" id="right-paddle"></div> <div class="snowflake" id="snowflake"></div> <div class="snow-effect" id="snow-effect"></div> <div class="game-over" id="game-over"> <h2>Игра окончена!</h2> <p id="winner-text">Победитель: Игрок 1</p> <button class="restart-btn" id="restart-btn">Новая игра</button> </div> <div class="controls"> <div id="control-hint">Управление: W/S</div> </div> <div class="start-screen" id="start-screen"> <h2>Добро пожаловать в Snow Pong! ❄️</h2> <div class="start-option"> <strong>Режим игры:</strong><br> <button class="mode-btn active" data-mode="pvp">PVP</button> <button class="mode-btn" data-mode="pve">PVE</button> </div> <div class="start-option" id="difficulty-section"> <strong>Сложность (для PVE):</strong><br> <button class="difficulty-btn active" data-level="easy">Лёгкий</button> <button class="difficulty-btn" data-level="medium">Средний</button> <button class="difficulty-btn" data-level="hard">Сложный</button> </div> <button class="start-btn" id="start-game-btn">Начать игру</button> <div class="focus-hint" id="focus-btn">👉 Кликните сюда для фокуса</div> </div> <div class="debug-info" id="debug">Последняя клавиша: —</div> </div> <script> // Элементы const gameContainer = document.querySelector('.game-container'); const leftPaddle = document.getElementById('left-paddle'); const rightPaddle = document.getElementById('right-paddle'); const snowflake = document.getElementById('snowflake'); const leftScoreElement = document.getElementById('left-score'); const rightScoreElement = document.getElementById('right-score'); const totalGoalsElement = document.getElementById('total-goals'); const gameOverScreen = document.getElementById('game-over'); const winnerText = document.getElementById('winner-text'); const restartBtn = document.getElementById('restart-btn'); const snowEffect = document.getElementById('snow-effect'); const soundBtn = document.getElementById('sound-btn'); const controlHint = document.getElementById('control-hint'); const startScreen = document.getElementById('start-screen'); const debug = document.getElementById('debug'); // Настройки let gameMode = 'pvp'; let difficulty = 'medium'; let soundEnabled = true; let audioContext = null; // Параметры const gameWidth = 800; const gameHeight = 600; const paddleHeight = 100; const paddleWidth = 15; const snowflakeSize = 30; const paddleSpeed = 8; const maxScore = 5; // Игровые переменные let leftPaddleY = gameHeight / 2 - paddleHeight / 2; let rightPaddleY = gameHeight / 2 - paddleHeight / 2; let snowflakeX = gameWidth / 2; let snowflakeY = gameHeight / 2; let snowflakeVelX = 5; let snowflakeVelY = 3; let leftScore = 0; let rightScore = 0; let totalGoals = 0; let gameActive = false; // Управление const keys = { w: false, s: false, o: false, l: false }; // --- НАСТРОЙКИ СЛОЖНОСТИ --- const difficultySettings = { easy: { speed: 0.1, error: 30, multiplier: 1 }, medium: { speed: 0.15, error: 10, multiplier: 2 }, hard: { speed: 0.2, error: 2, multiplier: 3 } }; // --- УПРОЩЁННЫЙ ИИ (без застревания) --- function moveAiPaddle() { if (gameMode !== 'pve') return; const settings = difficultySettings[difficulty]; const rightPaddleCenter = rightPaddleY + paddleHeight / 2; let targetY = snowflakeY; const error = difficulty === 'hard' ? 0 : (Math.random() * settings.error - settings.error / 2); const finalTarget = targetY + error; const diff = finalTarget - rightPaddleCenter; const threshold = difficulty === 'hard' ? 2 : 5; if (Math.abs(diff) > threshold) { const moveSpeed = paddleSpeed * settings.speed * settings.multiplier; if (diff > 0) { rightPaddleY += moveSpeed; } else { rightPaddleY -= moveSpeed; } } if (rightPaddleY < 0) rightPaddleY = 0; if (rightPaddleY > gameHeight - paddleHeight) rightPaddleY = gameHeight - paddleHeight; } // --- АУДИО --- function initAudio() { if (audioContext) return; try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.warn('Audio not available'); } } function playSound(freq, dur = 0.1) { if (!soundEnabled || !audioContext) return; const osc = audioContext.createOscillator(); const gain = audioContext.createGain(); osc.connect(gain); gain.connect(audioContext.destination); osc.frequency.value = freq; osc.type = 'square'; gain.gain.setValueAtTime(0.1, audioContext.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + dur); osc.start(); osc.stop(audioContext.currentTime + dur); } function playBounce() { playSound(300); } function playScore() { playSound(150, 0.2); } function playWin() { playSound(800, 0.3); } soundBtn.addEventListener('click', () => { soundEnabled = !soundEnabled; soundBtn.textContent = soundEnabled ? '🔊' : '🔇'; }); // --- СНЕГ --- function createSnowflakes() { for (let i = 0; i < 20; i++) { const snow = document.createElement('div'); snow.classList.add('snow'); const size = Math.random() * 6 + 2; snow.style.width = `${size}px`; snow.style.height = `${size}px`; 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() * 2 + 1; snowEffect.appendChild(snow); } } function animateSnowflakes() { document.querySelectorAll('.snow').forEach(snow => { let top = parseFloat(snow.style.top) + parseFloat(snow.dataset.speed); if (top > 100) top = -5; snow.style.top = `${top}%`; }); } // --- ВЫБОР НА СТАРТЕ --- const modeButtons = document.querySelectorAll('.mode-btn'); const difficultyButtons = document.querySelectorAll('.difficulty-btn'); const difficultySection = document.getElementById('difficulty-section'); const startGameBtn = document.getElementById('start-game-btn'); const focusBtn = document.getElementById('focus-btn'); modeButtons.forEach(btn => { btn.addEventListener('click', () => { modeButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); gameMode = btn.dataset.mode; difficultySection.style.display = gameMode === 'pve' ? 'block' : 'none'; updateControlHint(); }); }); difficultyButtons.forEach(btn => { btn.addEventListener('click', () => { difficultyButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); difficulty = btn.dataset.level; }); }); // --- ФОКУС --- function setFocus() { document.body.focus(); debug.textContent = 'Фокус установлен'; setTimeout(() => debug.textContent = 'Последняя клавиша: —', 1000); } focusBtn.addEventListener('click', setFocus); startGameBtn.addEventListener('click', setFocus); // --- УПРАВЛЕНИЕ --- document.addEventListener('keydown', (e) => { const key = e.key.toLowerCase(); debug.textContent = `Key down: ${e.key}`; if (keys.hasOwnProperty(key)) { keys[key] = true; e.preventDefault(); } }); document.addEventListener('keyup', (e) => { const key = e.key.toLowerCase(); if (keys.hasOwnProperty(key)) { keys[key] = false; } }); // --- ИГРОВАЯ ЛОГИКА --- function updateControlHint() { controlHint.textContent = gameMode === 'pvp' ? 'Игрок 1: W/S | Игрок 2: O/L' : 'Вы играете за левую ракетку. Управление: W/S'; } function checkCollision(el1, el2) { const r1 = el1.getBoundingClientRect(); const r2 = el2.getBoundingClientRect(); return !(r1.right < r2.left || r1.left > r2.right || r1.bottom < r2.top || r1.top > r2.bottom); } function resetSnowflake() { snowflakeX = gameWidth / 2; snowflakeY = gameHeight / 2; snowflakeVelX = 5 * (Math.random() > 0.5 ? 1 : -1); snowflakeVelY = 3 * (Math.random() > 0.5 ? 1 : -1); updateSnowflakePosition(); } function updateSnowflakePosition() { snowflake.style.left = `${snowflakeX}px`; snowflake.style.top = `${snowflakeY}px`; } function updatePaddlePositions() { leftPaddle.style.top = `${leftPaddleY}px`; rightPaddle.style.top = `${rightPaddleY}px`; } function resetGame() { leftScore = 0; rightScore = 0; leftScoreElement.textContent = '0'; rightScoreElement.textContent = '0'; leftPaddleY = gameHeight / 2 - paddleHeight / 2; rightPaddleY = gameHeight / 2 - paddleHeight / 2; resetSnowflake(); updatePaddlePositions(); } // --- ИГРОВОЙ ЦИКЛ --- function gameLoop() { if (gameActive) { // Управление if (gameMode === 'pvp') { if (keys.w) leftPaddleY = Math.max(0, leftPaddleY - paddleSpeed); if (keys.s) leftPaddleY = Math.min(gameHeight - paddleHeight, leftPaddleY + paddleSpeed); if (keys.o) rightPaddleY = Math.max(0, rightPaddleY - paddleSpeed); if (keys.l) rightPaddleY = Math.min(gameHeight - paddleHeight, rightPaddleY + paddleSpeed); } else if (gameMode === 'pve') { if (keys.w) leftPaddleY = Math.max(0, leftPaddleY - paddleSpeed); if (keys.s) leftPaddleY = Math.min(gameHeight - paddleHeight, leftPaddleY + paddleSpeed); moveAiPaddle(); } // Движение снежинки snowflakeX += snowflakeVelX; snowflakeY += snowflakeVelY; updateSnowflakePosition(); // Отскок от стен if (snowflakeY <= 0 || snowflakeY >= gameHeight - 6) { snowflakeVelY = -snowflakeVelY; playBounce(); } // Столкновение if (checkCollision(snowflake, leftPaddle) || checkCollision(snowflake, rightPaddle)) { snowflakeVelX = -snowflakeVelX * 1.05; if (checkCollision(snowflake, rightPaddle)) { snowflakeVelX *= 1.03; } playBounce(); } // Гол if (snowflakeX < 0) { rightScore++; totalGoals++; playScore(); resetSnowflake(); } else if (snowflakeX > gameWidth) { leftScore++; totalGoals++; playScore(); resetSnowflake(); } // Победа if (leftScore >= maxScore || rightScore >= maxScore) { gameActive = false; playWin(); gameOverScreen.classList.add('active'); winnerText.textContent = leftScore >= maxScore ? (gameMode === 'pve' ? '🎉 Вы победили!' : 'Победитель: Игрок 1!') : (gameMode === 'pve' ? '🤖 Компьютер победил!' : 'Победитель: Игрок 2!'); } // Обновление leftScoreElement.textContent = leftScore; rightScoreElement.textContent = rightScore; totalGoalsElement.textContent = totalGoals; updatePaddlePositions(); animateSnowflakes(); } requestAnimationFrame(gameLoop); } // --- ЗАПУСК ИГРЫ --- startGameBtn.addEventListener('click', () => { startScreen.style.display = 'none'; initAudio(); setFocus(); gameActive = true; updateControlHint(); requestAnimationFrame(gameLoop); }); restartBtn.addEventListener('click', () => { resetGame(); gameOverScreen.classList.remove('active'); gameActive = true; setFocus(); }); // --- ИНИЦИАЛИЗАЦИЯ --- window.onload = () => { createSnowflakes(); difficultySection.style.display = 'none'; updateControlHint(); document.body.tabIndex = 0; document.body.focus(); }; </script> </body> </html>
2
1
1
1
19 комментариев