← Назад к вопросам

Создать игру с гравитацией - шарик прыгает между полом и потолком

1.0 Junior🔥 161 комментариев
#C# и ООП#UI#Unity Core#Физика и математика

Условие

Создайте игру где шарик прыгает между полом и потолком.

Требования

  1. Шарик двигается по коридору (пол и потолок)
  2. В полу и потолке есть дыры (препятствия)
  3. Нажатие на экран меняет направление гравитации
  4. Задача игрока - не дать шарику провалиться в дыру
  5. Процедурная генерация уровня
  6. Увеличение сложности со временем

UI

  • Кнопка паузы
  • Подсчет очков
  • Экран Game Over с возможностью рестарта

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Игра с гравитацией - Flappy Bird улучшенный

Это задача требует создания динамичной игры с физикой гравитации, процедурной генерацией препятствий и интерактивным управлением. Опису полную архитектуру решения.

Архитектура системы

Основные компоненты:

  1. BallController — управление движением и гравитацией шарика
  2. LevelGenerator — процедурная генерация уровня и препятствий
  3. ObstacleManager — управление дырами в полу и потолке
  4. GameManager — логика игры и состояния
  5. UIManager — интерфейс и счёт

BallController - Физика и управление

public class BallController : MonoBehaviour
{
    private Rigidbody2D rb;
    private float gravity = -9.81f;
    private float jumpForce = 10f;
    private float maxSpeed = 20f;
    private bool gravityUp = false;
    
    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        rb.gravityScale = 1f;
    }
    
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            ReverseGravity();
        }
        
        // Ограничение максимальной скорости
        if (rb.velocity.magnitude > maxSpeed)
        {
            rb.velocity = rb.velocity.normalized * maxSpeed;
        }
    }
    
    public void ReverseGravity()
    {
        gravityUp = !gravityUp;
        rb.gravityScale = gravityUp ? -1f : 1f;
        
        // Импульс при смене гравитации
        float forceDirection = gravityUp ? 1f : -1f;
        rb.velocity = new Vector2(rb.velocity.x, 0);
        rb.AddForce(Vector2.up * jumpForce * forceDirection, ForceMode2D.Impulse);
    }
    
    public bool IsGravityUp => gravityUp;
    
    public void Die()
    {
        rb.velocity = Vector2.zero;
        rb.isKinematic = true;
    }
}

LevelGenerator - Процедурная генерация

public class LevelGenerator : MonoBehaviour
{
    [SerializeField] private GameObject obstaclePrefab;
    [SerializeField] private Transform levelContainer;
    [SerializeField] private float levelWidth = 10f;
    [SerializeField] private float holeDistance = 3f;
    [SerializeField] private float minHoleSize = 1.5f;
    [SerializeField] private float maxHoleSize = 3f;
    
    private float currentZ = 0f;
    private float difficultyMultiplier = 1f;
    private Queue<GameObject> obstaclePool = new Queue<GameObject>();
    
    public void Initialize()
    {
        currentZ = 0f;
        difficultyMultiplier = 1f;
        
        // Создаём начальные препятствия
        for (int i = 0; i < 5; i++)
        {
            GenerateObstacle();
        }
    }
    
    public void GenerateObstacle()
    {
        currentZ += holeDistance;
        
        // Рассчитываем размер дыры в зависимости от сложности
        float holeSize = Mathf.Lerp(minHoleSize, maxHoleSize, 
            Mathf.Min(difficultyMultiplier / 5f, 1f));
        
        // Случайная позиция дыры
        float holePosition = Random.Range(holeSize / 2, levelWidth - holeSize / 2);
        
        // Создаём верхнее препятствие (с дырой)
        GameObject topObstacle = CreateObstaclePart(
            new Vector3(0, 3.5f, currentZ),
            holePosition - holeSize / 2,
            true
        );
        
        // Создаём нижнее препятствие (с дырой)
        GameObject bottomObstacle = CreateObstaclePart(
            new Vector3(0, -3.5f, currentZ),
            holePosition + holeSize / 2,
            false
        );
        
        // Увеличиваем сложность
        difficultyMultiplier += 0.1f;
    }
    
    private GameObject CreateObstaclePart(Vector3 position, float holePosition, bool isTop)
    {
        GameObject obstacle = Instantiate(obstaclePrefab, position, Quaternion.identity, levelContainer);
        
        ObstaclePart part = obstacle.GetComponent<ObstaclePart>();
        part.Initialize(holePosition, isTop, levelWidth);
        
        return obstacle;
    }
}

ObstacleManager - Управление препятствиями

public class ObstaclePart : MonoBehaviour
{
    private BoxCollider2D leftCollider;
    private BoxCollider2D rightCollider;
    private bool isTop;
    
    public void Initialize(float holePosition, bool isTopObstacle, float levelWidth)
    {
        isTop = isTopObstacle;
        
        // Создаём коллайдеры слева и справа от дыры
        CreateCollider(0, holePosition / 2, holePosition, isTop);
        CreateCollider(1, (levelWidth - holePosition) / 2, holePosition, isTop);
    }
    
    private void CreateCollider(int side, float width, float holePos, bool isTop)
    {
        GameObject colliderObj = new GameObject($"{(isTop ? "Top" : "Bottom")}Collider{side}");
        colliderObj.transform.SetParent(transform);
        colliderObj.transform.localPosition = Vector3.zero;
        
        BoxCollider2D collider = colliderObj.AddComponent<BoxCollider2D>();
        
        if (side == 0)
        {
            // Левая часть
            collider.size = new Vector2(holePos, 0.5f);
            collider.offset = new Vector2(holePos / 2, 0);
        }
        else
        {
            // Правая часть
            float rightPos = holePos + 0.5f;
            collider.size = new Vector2(5f - rightPos, 0.5f);
            collider.offset = new Vector2(rightPos, 0);
        }
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            GameManager.Instance.GameOver();
        }
    }
}

GameManager - Основная логика игры

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    
    [SerializeField] private BallController ballController;
    [SerializeField] private LevelGenerator levelGenerator;
    [SerializeField] private UIManager uiManager;
    
    private int score = 0;
    private bool isGameOver = false;
    private bool isPaused = false;
    private float timeElapsed = 0f;
    
    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
    }
    
    private void Start()
    {
        levelGenerator.Initialize();
        uiManager.UpdateScore(0);
    }
    
    private void Update()
    {
        if (isGameOver || isPaused)
            return;
        
        timeElapsed += Time.deltaTime;
        score = (int)(timeElapsed * 10); // 10 очков за каждую секунду
        uiManager.UpdateScore(score);
        
        // Генерируем новые препятствия когда шарик движется вперёд
        if (ballController.transform.position.z > levelGenerator.CurrentZ - 5f)
        {
            levelGenerator.GenerateObstacle();
        }
    }
    
    public void GameOver()
    {
        isGameOver = true;
        ballController.Die();
        uiManager.ShowGameOver(score);
    }
    
    public void TogglePause()
    {
        isPaused = !isPaused;
        Time.timeScale = isPaused ? 0f : 1f;
        uiManager.ShowPausePanel(isPaused);
    }
    
    public void Restart()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

UIManager - Интерфейс

public class UIManager : MonoBehaviour
{
    [SerializeField] private Text scoreText;
    [SerializeField] private Button pauseButton;
    [SerializeField] private Canvas gameOverPanel;
    [SerializeField] private Canvas pausePanel;
    [SerializeField] private Text gameOverScore;
    [SerializeField] private Button restartButton;
    
    private void Start()
    {
        pauseButton.onClick.AddListener(() => GameManager.Instance.TogglePause());
        restartButton.onClick.AddListener(() => GameManager.Instance.Restart());
        
        gameOverPanel.enabled = false;
        pausePanel.enabled = false;
    }
    
    public void UpdateScore(int score)
    {
        scoreText.text = $"Счёт: {score}";
    }
    
    public void ShowGameOver(int finalScore)
    {
        gameOverPanel.enabled = true;
        gameOverScore.text = $"Финальный счёт: {finalScore}";
    }
    
    public void ShowPausePanel(bool show)
    {
        pausePanel.enabled = show;
    }
}

Камера и следование

public class CameraFollower : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private float smoothSpeed = 0.3f;
    [SerializeField] private Vector3 offset = new Vector3(0, 0, -5);
    
    private void LateUpdate()
    {
        Vector3 desiredPosition = target.position + offset;
        Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed);
        
        transform.position = new Vector3(0, smoothedPosition.y, smoothedPosition.z);
    }
}

Ключевые особенности

1. Физика гравитации — используется Rigidbody2D с переменной gravityScale

2. Процедурная генерация — препятствия создаются автоматически при движении

3. Динамическая сложность — размер дыр уменьшается со временем

4. Оптимизация коллайдеров — две части для каждого препятствия вместо одного с отверстием

5. Система очков — увеличивается в зависимости от времени игры

6. Синхронизация камеры — плавное следование за шариком

Дополнительные возможности

  • Power-ups — временное замедление или расширение шарика
  • Звуковые эффекты — при смене гравитации и столкновении
  • Лидербоард — сохранение лучших результатов
  • Различные режимы — бесконечный, с ограничением по времени, по ошибкам
  • Визуальные эффекты — частицы при смене гравитации

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

Создать игру с гравитацией - шарик прыгает между полом и потолком | PrepBro