Создать игру Space Invaders
Условие
Создайте игру типа Space Invaders на Unity.
Требования
- Три визуально различных типа врагов
- Враги могут стрелять, если перед ними нет другого врага
- UFO периодически пролетает сверху экрана для бонусных очков
- Враги движутся группой и меняют направление у края экрана
- Скорость группы врагов увеличивается по мере их уничтожения
- Игрок может двигаться влево-вправо и стрелять
- Подсчет очков и отображение рекорда
- Меню старт, пауза, game over
Бонус
- Укрытия которые разрушаются от попаданий
- Несколько жизней у игрока
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение Space Invaders
Архитектура игры
Space Invaders требует координации нескольких систем: управления группой врагов, стрельбы, столкновений, UI и score. Я использую паттерны, которые были в предыдущих задачах: Object Pool для пуль, Event System для событий, и модульную архитектуру.
1. Менеджер игры и состояния
using UnityEngine;
using System;
public class SpaceInvadersGameManager : Singleton<SpaceInvadersGameManager>
{
[Header("Game Settings")]
[SerializeField] private int initialLives = 3;
[SerializeField] private float gameSpeed = 1f;
[SerializeField] private int scorePerKill = 10;
[SerializeField] private int scoreUFO = 100;
[Header("Prefabs")]
[SerializeField] private Player playerPrefab;
[SerializeField] private EnemyGroup enemyGroupPrefab;
[SerializeField] private UFO ufoPrefab;
[SerializeField] private AudioClip gameOverSound;
// Состояние игры
public enum GameState { Menu, Playing, Paused, GameOver }
private GameState currentState = GameState.Menu;
private int currentScore = 0;
private int highScore = 0;
private int currentLives = 0;
private int enemiesKilled = 0;
// Объекты
private Player player;
private EnemyGroup enemyGroup;
private ObjectPool<Bullet> bulletPool;
// События
public static event Action<int> OnScoreChanged;
public static event Action<int> OnLivesChanged;
public static event Action<GameState> OnGameStateChanged;
public static event Action OnGameOver;
protected override void Awake()
{
base.Awake();
LoadHighScore();
}
void Start()
{
// Подписываемся на события
Player.OnPlayerDied += OnPlayerDied;
EnemyGroup.OnAllEnemiesDied += OnAllEnemiesDied;
}
/// <summary>Начать новую игру</summary>
public void StartGame()
{
currentScore = 0;
currentLives = initialLives;
enemiesKilled = 0;
gameSpeed = 1f;
ChangeGameState(GameState.Playing);
// Создаём игрока
player = Instantiate(playerPrefab);
// Создаём группу врагов
enemyGroup = Instantiate(enemyGroupPrefab);
// Создаём пул пуль
bulletPool = new ObjectPool<Bullet>(playerPrefab.GetComponent<Player>().bulletPrefab, transform);
bulletPool.Prewarm(20);
OnLivesChanged?.Invoke(currentLives);
OnScoreChanged?.Invoke(currentScore);
// Запускаем UFO
StartCoroutine(SpawnUFOPeriodically());
}
/// <summary>Пауза/Возобновление</summary>
public void TogglePause()
{
if (currentState == GameState.Playing)
{
ChangeGameState(GameState.Paused);
Time.timeScale = 0f;
}
else if (currentState == GameState.Paused)
{
ChangeGameState(GameState.Playing);
Time.timeScale = 1f;
}
}
/// <summary>Добавить очки</summary>
public void AddScore(int points)
{
currentScore += points;
OnScoreChanged?.Invoke(currentScore);
// Увеличиваем скорость по мере прогресса
gameSpeed = 1f + (enemiesKilled * 0.1f);
}
/// <summary>Когда игрок погиб</summary>
private void OnPlayerDied()
{
currentLives--;
OnLivesChanged?.Invoke(currentLives);
if (currentLives <= 0)
{
GameOver();
}
else
{
// Возрождаем игрока
Destroy(player.gameObject);
player = Instantiate(playerPrefab);
}
}
/// <summary>Все враги уничтожены</summary>
private void OnAllEnemiesDied()
{
enemiesKilled++;
gameSpeed = 1f + (enemiesKilled * 0.1f);
// Создаём новую волну врагов
Destroy(enemyGroup.gameObject);
enemyGroup = Instantiate(enemyGroupPrefab);
}
/// <summary>Конец игры</summary>
private void GameOver()
{
ChangeGameState(GameState.GameOver);
Time.timeScale = 0f;
// Сохраняем рекорд
if (currentScore > highScore)
{
highScore = currentScore;
SaveHighScore();
}
OnGameOver?.Invoke();
if (gameOverSound != null)
AudioManager.Instance?.PlaySFX(gameOverSound);
}
/// <summary>Смена состояния игры</summary>
private void ChangeGameState(GameState newState)
{
currentState = newState;
OnGameStateChanged?.Invoke(newState);
}
/// <summary>Спавн UFO с интервалом</summary>
private System.Collections.IEnumerator SpawnUFOPeriodically()
{
while (currentState != GameState.GameOver)
{
yield return new WaitForSeconds(Random.Range(15f, 30f));
if (currentState == GameState.Playing && enemyGroup != null)
{
UFO ufo = Instantiate(ufoPrefab);
ufo.SetReward(scoreUFO);
}
}
}
private void SaveHighScore() => PlayerPrefs.SetInt("HighScore", highScore);
private void LoadHighScore() => highScore = PlayerPrefs.GetInt("HighScore", 0);
// Getters
public int GetCurrentScore() => currentScore;
public int GetHighScore() => highScore;
public int GetLives() => currentLives;
public float GetGameSpeed() => gameSpeed;
public GameState GetGameState() => currentState;
public ObjectPool<Bullet> GetBulletPool() => bulletPool;
}
2. Система врагов
public class Enemy : MonoBehaviour
{
[Header("Settings")]
[SerializeField] protected float moveSpeed = 2f;
[SerializeField] protected int pointValue = 10;
[SerializeField] protected AudioClip deathSound;
[SerializeField] protected GameObject deathEffectPrefab;
[Header("Shooting")]
[SerializeField] protected float shootCooldown = 2f;
[SerializeField] protected Bullet bulletPrefab;
protected bool canShoot = true;
protected float shootTimer = 0f;
protected bool hasEnemyInFront = false;
protected Rigidbody2D rb;
public event Action OnDied;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
protected virtual void Update()
{
shootTimer += Time.deltaTime;
if (shootTimer >= shootCooldown)
{
shootTimer = 0f;
canShoot = true;
}
}
public virtual void Move(Vector3 direction)
{
rb.velocity = direction * moveSpeed * SpaceInvadersGameManager.Instance.GetGameSpeed();
}
public virtual void TryShoot()
{
if (canShoot && !hasEnemyInFront)
{
Shoot();
canShoot = false;
}
}
protected virtual void Shoot()
{
Bullet bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
bullet.SetDirection(Vector2.down);
}
public virtual void Die()
{
SpaceInvadersGameManager.Instance.AddScore(pointValue);
if (deathEffectPrefab != null)
Instantiate(deathEffectPrefab, transform.position, Quaternion.identity);
if (deathSound != null)
AudioManager.Instance?.PlaySFX(deathSound);
OnDied?.Invoke();
Destroy(gameObject);
}
public void SetHasEnemyInFront(bool has) => hasEnemyInFront = has;
}
/// <summary>Группа врагов (движутся вместе)</summary>
public class EnemyGroup : MonoBehaviour
{
[Header("Formation")]
[SerializeField] private int rowCount = 2;
[SerializeField] private int columnCount = 8;
[SerializeField] private float spacingX = 1.5f;
[SerializeField] private float spacingY = 1.5f;
[SerializeField] private Vector2 spawnPosition = Vector2.zero;
[Header("Prefabs")]
[SerializeField] private Enemy[] enemyTypePrefabs; // 3 типа врагов
[Header("Movement")]
[SerializeField] private float moveDistance = 0.5f;
[SerializeField] private float moveInterval = 0.75f;
private Enemy[,] formation;
private Vector2 direction = Vector2.right;
private float moveTimer = 0f;
private bool shouldDropDown = false;
private int totalEnemies = 0;
private int enemiesAlive = 0;
public static event Action OnAllEnemiesDied;
void Start()
{
CreateFormation();
}
void Update()
{
moveTimer += Time.deltaTime;
if (moveTimer >= moveInterval)
{
moveTimer = 0f;
MoveGroup();
TryShoot();
}
}
private void CreateFormation()
{
formation = new Enemy[rowCount, columnCount];
totalEnemies = rowCount * columnCount;
enemiesAlive = totalEnemies;
for (int row = 0; row < rowCount; row++)
{
for (int col = 0; col < columnCount; col++)
{
int enemyType = row % enemyTypePrefabs.Length;
Vector2 spawnPos = spawnPosition + new Vector2(
col * spacingX,
-row * spacingY
);
Enemy enemy = Instantiate(enemyTypePrefabs[enemyType], spawnPos, Quaternion.identity, transform);
enemy.OnDied += () => OnEnemyDied(row, col);
formation[row, col] = enemy;
}
}
}
private void MoveGroup()
{
// Проверяем границы
float leftmost = float.MaxValue;
float rightmost = float.MinValue;
for (int row = 0; row < rowCount; row++)
{
for (int col = 0; col < columnCount; col++)
{
if (formation[row, col] != null)
{
leftmost = Mathf.Min(leftmost, formation[row, col].transform.position.x);
rightmost = Mathf.Max(rightmost, formation[row, col].transform.position.x);
}
}
}
// Если достигли края, меняем направление и опускаемся
if ((direction == Vector2.right && rightmost >= 8f) ||
(direction == Vector2.left && leftmost <= -8f))
{
direction = -direction;
shouldDropDown = true;
}
// Двигаем группу
Vector2 moveVector = direction * moveDistance;
if (shouldDropDown)
{
moveVector.y = -0.5f;
shouldDropDown = false;
}
for (int row = 0; row < rowCount; row++)
{
for (int col = 0; col < columnCount; col++)
{
if (formation[row, col] != null)
{
formation[row, col].transform.position += (Vector3)moveVector;
}
}
}
}
private void TryShoot()
{
// Враги в нижнем ряду стреляют случайно
for (int col = 0; col < columnCount; col++)
{
for (int row = rowCount - 1; row >= 0; row--)
{
if (formation[row, col] != null)
{
// Проверяем есть ли враг впереди
bool hasEnemyInFront = row < rowCount - 1;
formation[row, col].SetHasEnemyInFront(hasEnemyInFront);
if (!hasEnemyInFront && Random.value < 0.1f)
{
formation[row, col].TryShoot();
}
break;
}
}
}
}
private void OnEnemyDied(int row, int col)
{
formation[row, col] = null;
enemiesAlive--;
if (enemiesAlive <= 0)
{
OnAllEnemiesDied?.Invoke();
}
}
}
3. Игрок и UFO
public class Player : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float moveSpeed = 8f;
[SerializeField] private float minX = -8f;
[SerializeField] private float maxX = 8f;
[Header("Shooting")]
[SerializeField] public Bullet bulletPrefab;
[SerializeField] private float shootCooldown = 0.3f;
[SerializeField] private AudioClip shootSound;
[Header("Settings")]
[SerializeField] private AudioClip deathSound;
private Rigidbody2D rb;
private float shootTimer = 0f;
private int lives = 3;
public static event Action OnPlayerDied;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
HandleMovement();
HandleShooting();
}
private void HandleMovement()
{
float input = Input.GetAxis("Horizontal");
rb.velocity = new Vector2(input * moveSpeed, rb.velocity.y);
// Ограничиваем границы
Vector3 pos = transform.position;
pos.x = Mathf.Clamp(pos.x, minX, maxX);
transform.position = pos;
}
private void HandleShooting()
{
shootTimer += Time.deltaTime;
if (Input.GetKey(KeyCode.Space) && shootTimer >= shootCooldown)
{
Shoot();
shootTimer = 0f;
}
}
private void Shoot()
{
Bullet bullet = Instantiate(bulletPrefab, transform.position + Vector3.up * 0.5f, Quaternion.identity);
bullet.SetDirection(Vector2.up);
if (shootSound != null)
AudioManager.Instance?.PlaySFX(shootSound);
}
public void Die()
{
if (deathSound != null)
AudioManager.Instance?.PlaySFX(deathSound);
OnPlayerDied?.Invoke();
Destroy(gameObject);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("EnemyBullet"))
{
Die();
}
}
}
public class UFO : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private AudioClip deathSound;
private int reward = 100;
void Start()
{
// Спауним слева или справа случайно
float startX = Random.value > 0.5f ? -10f : 10f;
transform.position = new Vector3(startX, 8f, 0);
GetComponent<Rigidbody2D>().velocity = new Vector2((startX > 0 ? -1 : 1) * moveSpeed, 0);
}
void Update()
{
// Удаляем если ушёл за границы
if (Mathf.Abs(transform.position.x) > 12f)
{
Destroy(gameObject);
}
}
public void Die()
{
SpaceInvadersGameManager.Instance.AddScore(reward);
if (deathSound != null)
AudioManager.Instance?.PlaySFX(deathSound);
Destroy(gameObject);
}
public void SetReward(int amount) => reward = amount;
}
public class Bullet : MonoBehaviour
{
[SerializeField] private float speed = 10f;
private Vector2 direction = Vector2.up;
private Rigidbody2D rb;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Start()
{
rb.velocity = direction * speed;
}
public void SetDirection(Vector2 dir) => direction = dir.normalized;
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy"))
{
Enemy enemy = other.GetComponent<Enemy>();
if (enemy != null)
enemy.Die();
Destroy(gameObject);
}
else if (other.CompareTag("UFO"))
{
UFO ufo = other.GetComponent<UFO>();
if (ufo != null)
ufo.Die();
Destroy(gameObject);
}
else if (other.CompareTag("Bunker"))
{
// Разрушение укрытия
Destroy(gameObject);
}
}
}
4. UI Manager
public class SpaceInvadersUI : MonoBehaviour
{
[SerializeField] private Text scoreText;
[SerializeField] private Text highScoreText;
[SerializeField] private Text livesText;
[SerializeField] private GameObject gameOverPanel;
[SerializeField] private GameObject pausePanel;
[SerializeField] private Button startButton;
[SerializeField] private Button pauseButton;
[SerializeField] private Button resumeButton;
[SerializeField] private Button restartButton;
void Start()
{
SpaceInvadersGameManager.OnScoreChanged += UpdateScore;
SpaceInvadersGameManager.OnLivesChanged += UpdateLives;
SpaceInvadersGameManager.OnGameStateChanged += OnGameStateChanged;
SpaceInvadersGameManager.OnGameOver += ShowGameOver;
startButton.onClick.AddListener(() => SpaceInvadersGameManager.Instance.StartGame());
pauseButton.onClick.AddListener(() => SpaceInvadersGameManager.Instance.TogglePause());
resumeButton.onClick.AddListener(() => SpaceInvadersGameManager.Instance.TogglePause());
restartButton.onClick.AddListener(() => {
Time.timeScale = 1f;
UnityEngine.SceneManagement.SceneManager.LoadScene(0);
});
gameOverPanel.SetActive(false);
pausePanel.SetActive(false);
}
private void UpdateScore(int score)
{
scoreText.text = $"Score: {score}";
}
private void UpdateLives(int lives)
{
livesText.text = $"Lives: {lives}";
}
private void OnGameStateChanged(SpaceInvadersGameManager.GameState state)
{
pausePanel.SetActive(state == SpaceInvadersGameManager.GameState.Paused);
}
private void ShowGameOver()
{
gameOverPanel.SetActive(true);
highScoreText.text = $"High Score: {SpaceInvadersGameManager.Instance.GetHighScore()}";
}
}
Ключевые компоненты
1. Система врагов — формация с координированным движением и стрельбой.
2. Object Pooling — пулинг пуль для оптимизации.
3. Scoring — динамический подсчёт очков с рекордом.
4. Game States — Menu, Playing, Paused, GameOver.
5. Wave System — повышение сложности по мере уничтожения волн.
6. UFO bonus — периодический бонус сверху.
Эта реализация обеспечивает полнофункциональную классическую игру Space Invaders с хорошей архитектурой и оптимизацией.