Создать игру с гравитацией - шарик прыгает между полом и потолком
Условие
Создайте игру где шарик прыгает между полом и потолком.
Требования
- Шарик двигается по коридору (пол и потолок)
- В полу и потолке есть дыры (препятствия)
- Нажатие на экран меняет направление гравитации
- Задача игрока - не дать шарику провалиться в дыру
- Процедурная генерация уровня
- Увеличение сложности со временем
UI
- Кнопка паузы
- Подсчет очков
- Экран Game Over с возможностью рестарта
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Игра с гравитацией - Flappy Bird улучшенный
Это задача требует создания динамичной игры с физикой гравитации, процедурной генерацией препятствий и интерактивным управлением. Опису полную архитектуру решения.
Архитектура системы
Основные компоненты:
- BallController — управление движением и гравитацией шарика
- LevelGenerator — процедурная генерация уровня и препятствий
- ObstacleManager — управление дырами в полу и потолке
- GameManager — логика игры и состояния
- 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 — временное замедление или расширение шарика
- Звуковые эффекты — при смене гравитации и столкновении
- Лидербоард — сохранение лучших результатов
- Различные режимы — бесконечный, с ограничением по времени, по ошибкам
- Визуальные эффекты — частицы при смене гравитации
Эта архитектура обеспечивает плавный геймплей, хорошую производительность и возможность легко добавлять новые функции.