Создание игры на движке Unity с целью заработка

XX Международный конкурс научно-исследовательских и творческих работ учащихся
Старт в науке

Создание игры на движке Unity с целью заработка

Орлов С.В. 1
1МБОУ СОШ М. И. Калинина
Лексина Е.В. 1
1МБОУ СОШ М.И.Калинина
Автор работы награжден дипломом победителя I степени
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

Введение

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

В 1960-х годах компьютеры стали более доступными, и игры начали распространяться среди более широкого круга людей. В этот период появились такие знаковые игры, как Spacewar! (1962) и Pong (1972).

В 1970-х годах игровая индустрия начала стремительно развиваться. В этот период появились первые домашние игровые консоли, такие как Atari 2600 (1977) и NintendoEntertainmentSystem (1983).

В 1980-х годах игровая индустрия столкнулась с кризисом, вызванным переизбытком низкокачественных игр. Однако, в конце десятилетия индустрия начала восстанавливаться, и в этот период появились такие хиты, как SuperMarioBros. (1985) и TheLegendofZelda (1986).

В 1990-х годах игровая индустрия продолжила расти и развиваться. В этот период появились первые трёхмерные игры, такие как Doom (1993) и Myst (1993).

В 2000-х годах игровая индустрия стала одной из самых прибыльных отраслей в мире. В этот период появились такие массовые многопользовательские онлайн-игры (MMO), как WorldofWarcraft (2004) и LeagueofLegends (2009).

В 2010-х годах игровая индустрия продолжала развиваться в различных направлениях. В этот период появились такие новые жанры, как мобильные игры и игры в виртуальной реальности.

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

Игровая индустрия является одной из самых быстрорастущих отраслей экономики в мире. В 2022 году мировой рынок видеоигр достиг объема в 180,34 миллиарда долларов США. То есть развитие этой отрасли очень важно для экономики страны.

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

Актуальность данной темы заключается в следующем:

  • Развитие творческого мышления и креативности. Создание игры требует от человека творческого подхода и умения решать нестандартные задачи.

  • Повышение самооценки и уверенности в себе. Успешное завершение проекта по созданию игры может стать большим достижением для человека и повысить его самооценку.

  • Приобретение новых навыков и знаний. В процессе работы над проектом человек получит новые знания в области программирования, дизайна и графики.

  • Создание игр важно для культурного наследия человечества, также как книги, фильмы и т.д

  • Поднятия экономики страны.

Глава 1. Изучение c# + Unity

1.1 Почему именно C# и Unity

В мире существует большое количество языков программирования, и движков. Каждый программист, когда, начинает хоть немного, заниматься программированием выбирает, для себя сферу, которую он будет учить, в зависимости от сферы меняется и язык и движок. Люди, которые выбирают создание игр, чаще всего выбирают между Unity + c# и UnrealEngine + c++. Я пробовал и то и другое, и выбрал для себя c#.

 

1.2 Что такое движок?

Движок — в программировании — ядро компьютерной программы для реализации конкретной прикладной задачи, чтобы отличить ее от наполнения и внешнего вида конкретной программы. 

 

1.3 План изучения

Первые 2 месяца

  • Изучение основ C#. Это включает в себя изучение синтаксиса языка, типов данных, конструкций управления, функций и методов.для этого следующие будут использоваться следующие ресурсы:

    • Книги: "C# для начинающих" Майкла Брандта, "C#. Полное руководство" Кента Беккера

    • Онлайн-курсы: Учитывая, что я хочу учится на бесплатных материалах, курсы мне не нужны, но мог бы использовать: "C# для начинающих" на Udemy, "C#. Основы" на Coursera

    • Среды разработки: VisualStudio

  • Изучите основы Unity. Это включает в себя изучение интерфейса Unity, работы с объектами, компонентов и скриптов. Вы можете использовать для этого следующие ресурсы:

    • Книги: "Unity. Полное руководство" Кента Беккера, "Unity. Основы" на PacktPublishing

    • Онлайн-курсы: Учитывая, что я хочу учится на бесплатных материалах, курсы мне не нужны, но мог бы использовать:"Unity для начинающих" на Udemy, "Unity. Основы" на Coursera

    • Документация Unity

Следующие 4 месяца

  • Практика в создании простых игр. Это поможет закрепить полученные знания и навыки.

  • Изучить более продвинутые темы C# и Unity. Это включает в себя изучение таких тем, как:

    • Объектно-ориентированное программирование

    • Работа с графикой и звуком

    • Интернет-технологии

    • Игровой дизайн

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

Последние 2 месяца

  • Сосредоточиться на создании своей собственной игры. Применить полученные знания и навыки в реальной работе.

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

1.4 Начало изучения

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

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

Я учился в 10 классе, и мне нужно было совмещать обучение программированию с учёбой. Это было непросто, но я старался найти время для обоих занятий. В итоге мне удалось уложиться в полгода. Конечно, при должной мотивации и усердии можно освоить основы программирования и быстрее.

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

Глава 2. Создание игры

2.1 С чего начать?

Определение жанра и концепции игры

При выборе жанра и концепции игры важно учитывать следующие факторы:

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

  • Ваши навыки и опыт. Если вы только начинаете свой путь в разработке игр, лучше выбрать жанр, который не требует слишком большого количества навыков и ресурсов.

  • Потенциальная аудитория. Подумайте, кому может понравиться ваша игра.

Исследование рынка

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

  • Сайты и форумы о видеоиграх.

  • Статистика продаж игр.

  • Обзоры игр.

  • Самостоятельно просмотреть площадки с играми, особенно, на которые будет выпускаться игра

Нужно создать ГДД (полноценная техническая документация игры) документ, в него входит:

  • Название игры: Полное название игры, включая подзаголовок, если есть.

  • Жанр: Тип игры, к которому она относится.

  • Целевая аудитория: Кто является целевой аудиторией игры?

  • Сеттинг:Атмосфера игры

  • Игровой процесс: Как игрок будет взаимодействовать с игрой?

  • Сюжет: Что будет происходить в игре?

  • Персонажи: Кто будет играть в игре?

  • Локации: Где будет происходить действие игры?

  • Другие элементы: Другие важные элементы игры, такие как графика, звук, музыка и т.д.

Геймплей

  • Цели игрока: Что игрок должен достичь в игре?

  • Основные механики: Какие основные игровые механики будут использоваться в игре?

  • Системы игры: Какие системы будут использоваться в игре?

  • Динамика игры: Как будет развиваться игра от начала до конца?

Сюжет

  • Основная история: Основная сюжетная линия игры.

  • Дополнительные сюжетные линии: Дополнительные сюжетные линии, которые могут быть в игре.

  • Персонажи: Характеристики и мотивы персонажей.

  • Локации: Описание локаций, в которых будет происходить действие игры.

Сеттинг

  • Графика: Общий стиль графики игры.

  • Анимация: Использование анимации в игре.

  • Звук: Музыка, звуковые эффекты и озвучка.

Технологии

  • Игровой движок: Какой игровой движок будет использоваться для разработки игры.

  • Другие технологии: Другие технологии, которые будут использоваться в игре.

Продукт-менеджмент

  • Цели и задачи проекта: Что нужно достичь в рамках проекта?

  • Бюджет и сроки: Бюджет и сроки разработки игры.

  • Команда проекта: Кто будет участвовать в разработке игры?

Проверка и тестирование

  • Требования к тестированию: Что нужно протестировать в игре?

  • Методики тестирования: Как будет проводиться тестирование игры?

  • Планы тестирования: Когда и как будет проводиться тестирование игры.

Маркетинг и продвижение

  • Цели маркетинга: Что нужно достичь в рамках маркетинговой кампании?

  • Целевая аудитория маркетинга: На кого будет направлена маркетинговая кампания?

  • Средства маркетинга: Какие средства будут использоваться в маркетинговой кампании?

После того как мы разобрались, можно начинать разработку игры

2.2 Начало разработки

Перед началом работы необходимо установить и настроить Unity. Поскольку проект планируется выкладывать на веб-площадки, Unity нужно подготовить для работы в веб-среде. Для этого необходимо выполнить следующие действия:

Установка Unity

  • скачиваем и устанавливаем UnityHub.

  • ИдёмвGet Unity - Download Archive - UnityиищемверсиюLTS 2022.3.4f1, качаем её через зелёную кнопку UnityHub. Не используйте версии новей, это вредит рабочему процессу.

  • В Unity обязательно устанавливать модуль WebGLBuildSupport.

Настройка проекта

  • Обязательно используйте Built-in рендер пайплайн. Не используйте URP и HDRP.

  • Заходим в Edit ->ProjectSettings и там обязательно включаем DecompressionFallback (приходится из-за того что наши веб платформы не дают тонких настроек).
    А так же:

  • CompressionFormat: Brotli (значительно сокращает размер).

  • Включить NameFilesAsHashes (для избежания проблем с кешированием).

  • Выключить DataCaching (браузеры и так всё кешируют).

  • Там же включаем RunInBackground (для воркэраунда бага с зависанием).

  • В настройках билда:

    • Меняем сжатие на ASTC.
      (Это значительно сократит количество потребляемой памяти и время загрузки на телефонах, а также улучшает качество сжатых текстур).

    • "CodeOptimization" и "IL2CPPCodeGeneration" оставитьстандартные: "Speed" и "Fasterruntime".Если поставить CodeOptimization на "Size", то первые 10 секунд на мобилках будет очень сильно лагать.

  • Поставьтекастомный шаблон страницы с игрой. Он автоматически скалируется под размер окна браузера. В репозитории содержится краткийReadme по установке.
    Темплейт можно (а иногда даже нужно) модифицировать под свою игру. Например только через модификацию темплейта можновключить сглаживание.

Установка SDK и других интеграций

Обязательно накатите WebUtility, там содержатся воркэраунды для багов Unity.

Обычно интеграции устанавливаются с помощью Window ->PackageManager.

Вот визуальный пример того как это делается.

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

Ознакомиться, как работать с интеграциями можно с помощью семпла.
Импортируются семплы вот так.

Вот и закончилась самая сложная часть, теперь самое время приступить к разработке, которая обещает быть более увлекательной и творческой.

В первую очередь я создаю на сцене черновой набросок персонажа. Для этого я создаю объект Cube в Иерархии и перетаскиваю его на сцену. Затем я настраиваю размер и положение объекта.

После этого я создаю черновые наброски врагов и земли. Для этого я использую аналогичные методы. В результате у меня получается сцена с основными объектами игры, которые удобны для тестирования скриптов.

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

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

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

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

Вообще по-хорошему весь этот код нужно декомпозировать. И не советовал бы брать пример с написания мною кода.

Я создам три класса, Player, PlayerMover и Point, в скрипте Player будут находиться такие поля и методы как:

    private float _health = 3;

    private float _currentHealth;

    privateAudioSource _audioSourse;

    privateint _money = 0;

    privatebool _isDied = false;

    public event UnityAction<float>OnHealthChanged;

    private void Awake()

    {

        _money = PlayerPrefs.GetInt("Money");

        _audioSourse =     GetComponent<AudioSource>();

        _currentHealth = _health;

       OnHealthChanged?.Invoke(_health);

    }

Пояснение:думаю все поля здесь понятны, и стоит пояснить только событие OnHealthChanged. Оно отвечает за изменение здоровья, я его использую в методе Start, чтобы инициализировать здоровье в начале игры,далее это событие будет использоваться при любом изменение здоровья.

в классе PlayerMover будут такие поля и методы:

[SerializeField] private float _speed;

 [SerializeField] private Camera _camera;

    private Transform[] _points;

    privatebool _isStop = false;

    private Transform _target;

    private Animator _animator;

    privateint _currentPoint = -1;

    private Player _player;

 private void Start()

    {

        _animator = GetComponent<Animator>();

        _player = GetComponent<Player>();

        _points = new Transform[_path.transform.childCount];

        for (int i = 0; i < _path.transform.childCount; i++)

        {

            _points[i] = _path.transform.GetChild(i);

        }

        TakePointPosition();

    }

    private void Update()

    {

        if (_isStop == false)

        {

            transform.position = Vector3.MoveTowards(transform.position, _target.position, _speed * Time.deltaTime);

        

            if(transform.position == _target.position)

            {

                Stop();

                _camera.transform.rotation = Quaternion.identity;

                AttackPlayer();

            }

            if(_points[_currentPoint].GetComponent<Point>().CountEnemy == 0 &&transform.position == _target.position)

            {

                Continue();

            }

        }

public void TakePointPosition()

    {

        if (TryMoving())

        {

            _currentPoint++;    

            _target = _points[_currentPoint];

        }

        else if(_isWin == false && _player.IsDied == false)

        {

            _game.WinLevel();

            _isWin = true;

        }

    }

    privateboolTryMoving()

    {

        if (_currentPoint + 1 < _points.Length)

            return true;

        else

            return false;

    }

public void DeleteEnemy()

    {

        _points[_currentPoint].GetComponent<Point>().Clear();

    }

    private void Stop()

    {

        AudioManager.instance.Stop("Run");

        _isStop = true;

    }

    public void Continue()

    {

        AudioManager.instance.Play("Run");

        TakePointPosition();

        _isStop = false;

    }

Класс PlayerMoverпредставляет из себя класс, который реализует движение игрока.

Пояснение:поле _points  - это массив пути игрока, он задаётся в начале игры. target - это цель к, которой следует игрок, она берётся из массива _points.

В методе Update проверяется булевая переменная _isStop, если она равна false, то персонаж идёт. Если персонаж дошёл, то проходит следующая проверка, и срабатывается метод Stop, персонаж останавливается. Активируется метод AttackPlayer, враги начинают атаковать.

В целом остальные методы интуитивно понятны.

Класс Pointпредставляетизсебя:

    [SerializeField] private EnemyController[] _enemyControllers;

    [SerializeField] private Sniper[] _snipers;

    [SerializeField] private PlayerController _playerController;

    [SerializeField] private bool _isTutorial;

    privateint _countEnemy;

    publicPlayerControllerPlayerController => _playerController;

    publicintCountEnemy => _countEnemy;

    private void Start()

    {

        foreach(var item in _enemyControllers)

        {

            _countEnemy++;

        }

        foreach(var item in _snipers)

        {

            _countEnemy++;

        }

    }

    public void LetEnemiesAttack()

    {

        if (_isTutorial == false)

        {

            foreach (var item in _enemyControllers)

            {

                item.Move();

                item.GetComponent<Animator>().SetBool("Run", true);

            }

            foreach (var item in _snipers)

            {

                if(item.gameObject.active == true)

                item.Attack();

            }

        }

    }

    public void Clear()

    {

        foreach (var item in _enemyControllers)

        {

            item.gameObject.SetActive(false);

        }

        foreach (var item in _snipers)

        {

            if (item.gameObject.active == true)

                item.gameObject.SetActive(false);

        }

        _countEnemy = 0;

        _playerController.Continue();

    }

    public void DieEnemy()

    {

        _countEnemy--;

        if (_countEnemy<= 0 && _playerController.transform.position == transform.position)

            _playerController.Continue();

    }

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

я создам такие классы: Enemy, EnemyMover

В Enemy напишу:

 [SerializeField] privatePoint _point; - точка к, которой привязан враг

    privateParticleSystem _dieEffect;

    private float _health = 100f;    

    private float _currentHealth;

    private float _damage = 1f;

    private Player _player;

    private Animator _animator;

    privatebool _isDied = false;

    privateEnemyMover _enemyMover; 

    publicboolIsDied => _isDied;

    public float Damage => _damage;

    public Player Player => _player;

    public event UnityAction<float,float>HealthChanged;

 private void Start()

    {

        _animator = GetComponent<Animator>();

        _enemyMover = GetComponent<_enemyMover>();

        _dieEffect = GetComponentInChildren<ParticleSystem>();

        _player = _point.PlayerController.GetComponent<Player>();

        _currentHealth = _health;

        if (_point == null)

            gameObject.SetActive(false);

    }

    public void TakeDamage(float damage, string hitPoint)

    {

        _currentHealth -= damage;

        HealthChanged?.Invoke(_currentHealth, _health);

        AudioManager.instance.Play("HitEnemy");

        if (_currentHealth<= 0)  

                Die();

        

    }

    public void Die()

    {

        if (_isDied == false)

        {

            _point.DieEnemy();

            if (gameObject.TryGetComponent<Sniper>(out Sniper sniper))

            {

                sniper.TurnOffImage();

                sniper.Stop();

            }

            _enemyMover.Stop();

            _animator.SetTrigger("Die");

            if (_dieEffect != null)

                _dieEffect.Play();

            _isDied = true;

         

            Invoke("Destroing", 4f);

        }

    }

    private void Destroing()

    {

        gameObject.SetActive(false);

    }

}

АвEnemyMover:

    [SerializeField] private float _speed;

    [SerializeField] private Player _player;

    [SerializeField] private Transform _path; - этодляврагов, которыебудутвыбегатьиз-заугла

    [SerializeField] private Point _endPoint;

    privateint _currentPoint = 0;

    privatebool _isMoving = false;

    privatebool _isCoroutineAttackRunning = false;

    private Enemy _enemy;

    privateCoroutine _attackCoroutine;

    private Transform[] _points;

    privateWaitForSeconds _waitAttack = new WaitForSeconds(1f);

    private Animator _animator;

    private Sniper _sniper;

    private void Start()

    {

        _sniper = GetComponent<Sniper>();

        _animator = GetComponent<Animator>();

        _enemy = GetComponent<Enemy>();

        if (_path != null)

        {

            _points = new Transform[_path.transform.childCount];

            for (int i = 0; i < _path.transform.childCount; i++)

            {

                _points[i] = _path.transform.GetChild(i);

            }

        }

    }

    private void Update()

    {

        if (_isMoving&& _path != null && _endPoint == null)

        {

            if (_currentPoint< _points.Length)

            {

                Vector3 lookDirection = _points[_currentPoint].position - transform.position;

                transform.rotation = Quaternion.LookRotation(lookDirection, Vector3.up);

                transform.position = Vector3.MoveTowards(transform.position, _points[_currentPoint].position, _speed * Time.deltaTime);

                if (transform.position == _points[_currentPoint].position)

                    _currentPoint++;

            }

            else if (_isCoroutineAttackRunning == false)

            {

                _attackCoroutine = StartCoroutine(DelayBeforeAttack());

                _animator.SetTrigger("Attack");

            }

        }

        if (_isMoving&& _path == null && _endPoint != null)

        {

            float distance = Vector3.Distance(transform.position, _endPoint.transform.position);

            Vector3 lookDirection = _endPoint.transform.position - transform.position;

            transform.rotation = Quaternion.LookRotation(lookDirection, Vector3.up);

Этот Код отвечает за поворот врага в сторону point

            if (distance > 1f)

            {

                transform.position = Vector3.MoveTowards(transform.position, _endPoint.transform.position, _speed * Time.deltaTime);

            }

            else if (_isCoroutineAttackRunning == false)

            {

                _attackCoroutine = StartCoroutine(DelayBeforeAttack());

                _animator.SetTrigger("Attack");

            }

        }

        if (_sniper == null && _path == null && _endPoint == null)

        {

            _enemy.Die();

        }

    }

    privateIEnumeratorDelayBeforeAttack()

    {

        while (true)

        {

            yield return _waitAttack;

            if (_isCoroutineAttackRunning == false && _enemy.IsDied == false)

            {

                _player.TakeDamage(_enemy.Damage);

                _enemy.Die();

                _isCoroutineAttackRunning = true;

                StopAttackCoroutine();

            }

            else

            {

                StopAttackCoroutine();

            }

        }

    }

    public void Move()

    {

        if (_enemy.IsDied == false)

            _isMoving = true;

    }

    public void Stop()

    {

        _isMoving = false;

    }

    private void StopAttackCoroutine()

    {

        StopCoroutine(_attackCoroutine);

    }

}

Стрельба будет реализована с помощью Physics.Raycast. Raycast представляет из себя невидимый луч, который выходит из определённой точки, и при соприкосновении с каким-нибудьколлайдером, даёт об этом знать.

Почему именно Raycast. Потому что это очень нетребовательный для системы процесс, так как моя игра на мобильные устройства нужно учитывать всё.

Для оружия я создал абстрактный класс Weapon:

[SerializeField] protected float Damage;

    [SerializeField] protected ParticleSystemShootEffect;

    [SerializeField] protected Camera MainCamera;

    [SerializeField] private ShootButton _shootButton;

    [SerializeField] protected Game Game;

    [SerializeField] protected TMP_TextMaxBullets;

    [SerializeField] protected TMP_TextCurrentBullets;

    [SerializeField] protected intMaxBulletCount;

    [SerializeField] private string _name;

    protectedCoroutineShootCoroutine;

    protectedCoroutineReloadCoroutine;

    protected float RealodingDelay;

    protected float ShootingDelay;

    protectedintCountBulletSpent;

    protected float Range = 13f;

    protected Animator Animator;

    protectedboolIsReloading = false;

    protectedboolIsShooting = false;

    protectedWaitForSecondsDelayReload = new WaitForSeconds(1.41f);

    privateint _improvementCount;

    public string NameGun => _name;

    publicShootButtonShootButton => _shootButton;

    private void Start()

    {

        Animator = GetComponent<Animator>();

        _improvementCount = PlayerPrefs.GetInt("ImprovementCount" + _name);

        float damage = Damage;

        if (_improvementCount == 1)

        {

            damage = damage * 0.3f;

            Damage += damage;

        }

        if (_improvementCount == 2)

        {

            Damage = damage * 0.5f;

            Damage += damage;

        }

        else if (_improvementCount>= 3)

        {

            Damage += damage;

        }

    }

    private void Awake()

    {

        CountBulletSpent = MaxBulletCount;

    }

    public abstract void Shoot();

    publicabstractvoidReload();

Уже от этого класса будут наследоваться классы оружия, приведу пример написания одного класса PPSH.

 [SerializeField] private Player _player;

    privateWaitForSeconds _delayShoot = new WaitForSeconds(0.1f);

    private void Update()

    {

        if (Input.GetButton("Fire1") &&Game.IsMobile == false &&IsShooting == false &&IsReloading == false)

        {

            IsShooting = true;

            StartCoroutine(ShootDelay());

        }

        if (ShootButton.IsHold&&Game.IsMobile&&IsShooting == false &&IsReloading == false)

        {

            IsShooting = true;

            StartCoroutine(ShootDelay());

        }

        if (Input.GetKeyDown(KeyCode.R) &&IsReloading == false)

        {

            Reload();

        }

        CurrentBullets.text = CountBulletSpent.ToString();

        MaxBullets.text = MaxBulletCount.ToString();

    }

    private void OnEnable()

    {

        IsShooting = false;

        IsReloading = false;

    }

    public override void Reload()

    {

        if (gameObject.activeSelf&&IsReloading == false)

        {

            IsReloading = true;

            ReloadCoroutine = StartCoroutine(ReloadDelay());

        }

    }

    public override void Shoot()

    {

        CountBulletSpent--;

        if (CountBulletSpent>= 0)

        {

            RaycastHit hit;

            if (Physics.Raycast(MainCamera.transform.position, MainCamera.transform.forward, out hit, Range))

            {

                Enemy enemy = hit.transform.GetComponent<Enemy>();

                Explosion explosion = hit.transform.GetComponent<Explosion>();

                Head head = hit.transform.GetComponent<Head>();

                if (head != null)

                    enemy = head.GetComponentInParent<Enemy>();

                if (enemy != null && head == null &&enemy.IsDied == false)

                {

                    enemy.TakeDamage(Damage, "body");

                    _player.Aim.DOColor(Color.red, 0.05f);

                    _player.Aim.DOColor(Color.black, 0.4f);

                }

                if (head != null &&enemy.IsDied == false)

                {

                    head.GetComponentInParent<Enemy>().TakeDamage(Damage, "head");

                    _player.Aim.DOColor(Color.red, 0.05f);

                    _player.Aim.DOColor(Color.black, 0.4f);

                }

                if (explosion != null)

                {

                    explosion.ExplodeDelay();

                }

            }

        }

        else if (CountBulletSpent< 0)

        {

            Reload();

        }

        IsShooting = false;

    }

    privateIEnumeratorShootDelay()

    {

        yield return _delayShoot;

        IsShooting = false;

        AudioManager.instance.Play("PpshShoot");

        Animator.SetTrigger("shoot");

        ShootEffect.Play();

        Shoot();

    }

    privateIEnumeratorReloadDelay()

    {

        Animator.SetTrigger("reload");

        AudioManager.instance.Play("ReloadPpsh");

        CountBulletSpent = MaxBulletCount;

        yield return DelayReload;

        IsReloading = false;

    }

Пояснения: 

  • Метод Update не содержит ничего необычного, просто проверки на нажатие клавиши, и обновление патрон.

  • А вот метод Shoot уже поинтереснее, здесь как раз вся логика стрельбы, при попадании луча в объекты с компонентами Head, Explosion, Enemy. он их сохраняет в переменную. Дальше проверка на попадание по этим объектам, и действия, который нужно с ними сделать.

Можно приступать к созданию HUD (Head-UpDisplay).

Для отображения информации о состоянии игры и игрового процесса я создаю объект Canvas в иерархии и переименовываю его в HUD. В этом объекте будут размещаться все элементы HUD, такие как индикатор здоровья игрока, индикаторы оружия, и другие элементы.

  • Создаю объект типа TextMeshPro для отоброжения очков набранных за уровень.

  • Создаю объект HealthBar с компонентами HorizontalLayotGroup и скриптом HealthBar, который будет написан мною позже

  • Добавляю прицел, представляет из себя обычный image

  • Начинаю добавлять черновые наброски мобильного управления(ShootButton, TouchController, ReloadButton, ZoomButton, HideButton, ChooseWeaponButton

2.3 Середина разработки

Создание уровня

Сам уровень представляет из себяTerrain с объектами на нем.

Terrain — это инструмент Unity, который позволяет создавать и редактировать трехмерные ландшафты. Он позволяет создавать различные типы ландшафтов, от простых холмов и долин до сложных гор и пещер.

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

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

Далее я добавляю в мир объекты, такие как деревья, здания и другие элементы. Пользуясь бесплатными моделями, которые я скачал из различных источников. Выбираю модели, которые соответствуют стилю игры, и расставляю их по уровню.

Настройка баланса

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

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

Это очень долгий процесс, который нужно просто подгонять, и долго тестировать. 

Балансом я занимался весь процесс создания игры.

В него входит:

  • Расстановка врагов на уровне

  • Баланс оружия, чтобы оно не было слишком сильным, или слишком слабым

  • Баланс экономики, это очень важный аспект, так как заработок в игре, заставляет игрока задерживаться в ней на долго. Если деньги легко зарабатываются, игрок всё купит и просто уйдёт, из-за того, что пропала цель игры. А если слишком сложно, то уйдёт из-за недостежимости цели

Создание главного меню

В меню должно находиться:

  • Кнопка для выбора уровней

  • Магазин с оружием

  • Настройки(звука, чувствительности и выбора языка)

  • Лидерборд(обязательное условия перед публикацией на яндекс игры)

  • отображение денег и очков

  • выбранное оружие

Вся логика меню и окон в меню, у меня реализована на компоненте CanvasGroup. 

То есть у меня на отдельных окнах, которые открываются висит CanvasGroup. 

CanvasGroup - это компонент Unity, который используется для управления состоянием группы UI-элементов. Он позволяет изменять такие свойства, как прозрачность, видимость и возможность взаимодействия с UI-элементами.

Для того, чтобы такая система работа, я создал абстрактный класс Screen, от которого потом наследовались сами окна.

Самабстрактныйскрипт Screen:

usingUnityEngine;

    public abstract class Screen : MonoBehaviour

    {

        [SerializeField] protected CanvasGroupCanvasGroup;

        public abstract void Open();

        publicabstractvoidClose();

    }

Один из скриптов, который наследуется от скрин:

usingUnityEngine;

public class ShopScreen : Screen

{

    private void Start()

    {

        CanvasGroup.alpha = 0f;

        CanvasGroup.blocksRaycasts = false;

    }

    public override void Open()

    {

        CanvasGroup.blocksRaycasts = true;

        CanvasGroup.alpha = 1f;

        AudioManager.instance.Play("Click");

    }

    public override void Close()

    {

        CanvasGroup.alpha = 0f;

        CanvasGroup.blocksRaycasts = false;

    }

}

Далее пишу скрипты для магазина, уровней и настроек, ничего там интересного.

А вот скрипты для смены языка, это уже интересно.

Для реализации смены языка было создано 2 скрипта LanguageManager и LanguageText.

p.s вообще с точки зрения написание чистого кода, именовать классы с называние Manager, Controller является плохим тоном, поэтому еще раз скажу, что мой код не стоит использовать как учебное пособие, он не из самых лучших.

public class LanguageManager : MonoBehaviour

{

    publicint Language;

    privateYandexGame _yandexGame;

    private void GetData()

    {

        string language = YandexGame.EnvironmentData.language;

        if (PlayerPrefs.GetInt("languageSelected") == 0 &&YandexGame.SDKEnabled)

        {

            if (language == "ru" || language == "be" || language == "kk" || language == "uk" || language == "uz")

                RussianLanguage();

            else if (language == "tr")

                TurkishLanguage();

            else

                EnglishLanguage();

        }

    }

    private void Awake()

    {

        _yandexGame = GetComponent<YandexGame>();

        if (PlayerPrefs.GetInt("languageSelected") != 1)

        {

            GetData();

            PlayerPrefs.SetInt("languageSelected", 1);

        }

        else

        {

            Language = PlayerPrefs.GetInt("language", Language);

        }

    }

}

Что такое GetData? Это метод для автоматического определения языка с помощью SDK от Яндекса. Подключение этого SDK обязательна, как минимум для подключение рекламы.

В методе Awakeпроверяется был ли выбран уже язык, потому что в настройках дополнительно можно сменить язык. И это проверка сделана для того, чтобы запоминать смену языка игроком.

ИтеперьклассLanguageText

usingUnityEngine;

usingTMPro;

public class LanguageText : MonoBehaviour

{

    publicint Language;

    public string[] _text;

    privateTMP_Text _textLine;

    void Start()

    {

        Language = PlayerPrefs.GetInt("language", Language);

        _textLine = GetComponent<TMP_Text>();

        _textLine.text = "" + _text[Language];

    }

}

Этот скрипт накидывается на объект с Компонентом TextMeshPro, в нём указывается перевод для всех языков


Создание контента

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

Мною было добавлено:

  • Игровой мир состоит из 10 уровней, разделенных на две локации. Первые пять уровней представляют собой горную местность, покрытую густыми лесами и зелеными долинами. На них часто идет дождь, создавая особую атмосферу. Следующие пять уровней представляют собой пустыню с разрушенными зданиями.

  • В игру было множество оружия. При создании оружия я старался придерживаться исторического контекста, поэтому в игре нет оружия, которое не существовало во время Второй мировой войны. Однако, поскольку в эпоху холодной войны было относительно мало видов вооружения, мне пришлось немного нарушить это правило, чтобы сделать игру более интересной.

  • Добавил в игру снайпера, который также привязан к точке.его нужно снять с помощью снайперской винтовки, либо уклониться от его атаки, спрятавшись в укрытие.

2.4 Конечная часть разработки

Дело осталось за малым 

Встраивание рекламы в игру было одним из ключевых моментов в разработке. Для этого в документе ГДД было предусмотрено специальное поле "монетизация". Благодаря этому я смог заранее продумать, как будет реализована реклама в игре.

Для воспроизведения рекламы я использовал SDK Яндекса. Чтобы не тратить время на лишние телодвижения, я скачал бесплатный плагин для Unity, который упрощает взаимодействие с SDK.

В игру были добавлены следующие типы рекламы:

  • Между-сценовая реклама: реклама проигрывается при переходе между сценами.

  • Reward-реклама для воскрешения игрока после смерти: реклама появляется с 20% шансом, когда игрок умирает. При просмотре рекламы игрок может воскреснуть и продолжить игру.

  • Reward-реклама на получение денюшек в главном меню: реклама появляется в главном меню. При просмотре рекламы игрок получает виртуальные деньги, которые можно использовать для покупки внутриигровых предметов.

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

Правила

Помимо всего, очень важным пунктом является - соблюдение правил площадки. У яндекс игр есть свои очень суровые правила, которые нужно соблюдать, чтобы пройти модерацию, и добиться релиза игры. 

Все пункты правил написаны у них в документации, я её изучил, и подогнал всё под правила

Тестирование

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

И это всё?

Неужели, но я добрался до релиза, и готов выкладывать игру на яндекс игры.

Ага, а не тут то было. Теперь нужно заполнить информацию об игре на яндекс играх.

Это не оказалось непреодолимой задачей, но пришлось запариться над скриншотами, монтажом видео и создании иконки.

Ну теперь то точно всё?

Ну да, теперь всё получается.

Глава 3. Релиз

После того как мы убедились в этом:

  • Игра играбельная, ничего не нарушает игровой процесс, ничего не крашиться.

  • В игре хорошо настроен баланс

  • Минимально количество багов(желательно их отсутствие)

  • все правила площадки соблюдены

  • реклама корректно работает

Мы можем отдавать игру на модерацию.

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

Я быстро всё поправил, и на следующий день отправил игру снова на модерацию. В этот раз мне её приняли и игра была опубликована.

Глава 4. Заработок

Допущенные ошибки

Заработок с игры идёт мне с подключенной рекламы. Вообще игра у меня не была нацелена прямо на заработок, подобные игры сложно монетизировать полноценно, но можно. У меня не совсем это получилось, но, тем не менее денег я получил. И получил достаточно много игроков, для первого проекта особенно. 

В первые два дня игра принесла мне около 500 рублей. 

Далее произошёл кое какой казус, из-за которого этот доход обнулился и просела монетизация(не наступайте на грабли на которые наступил я, не вводите свои реквизиты в рекламную сеть яндекса(РСЯ) если вам не исполнилось 18-ти лет, вам просто заблокируют аккаунт): ).

Смена аккаунта

После смены аккаунта, мне снова пошёл доход с игры, и сейчас он насчитывает 1200 рублей. Доход с рекламы очень не стабильный. Сегодня 160 рублей, завтра уже будет 30 в день.

Глава 5. Анкетирование

В современном мире игры являются неотъемлемой частью жизни детей. Они позволяют им развиваться, учиться новому и весело проводить время. Чтобы лучше понять, какие игры нравятся детям, я провел анкетирование среди детей возрастом от 7 до 17 лет. В анкете было всего три вопроса:

  • Какие игры вы любите играть?

  • Сколько времени в день вы проводите за играми?

  • С кем вы обычно играете?

В анкетировании приняли участие 100 детей из моей школы. Результаты анкетирования показали, что дети предпочитают следующие виды игр:

  • Казуальные мобильные игры. Мобильные игры удобны и доступны, поэтому они пользуются большой популярностью у детей. К популярным мобильным играм для детей относятся Standoff 2, Roblox и AmongUs.

  • Приключенческие игры. Эти игры позволяют детям исследовать новые миры и решать интересные загадки. Были названы Minecraft, Roblox.

  • Игры с открытым миром. Эти игры дают детям свободу действий и позволяют им исследовать мир по своему усмотрению. Чаще всего были названы такие игры: GrandTheftAuto V, RedDeadRedemption 2, и Minecraft.

  • Игры с элементами творчества. Эти игры позволяют детям проявить свою фантазию и творчество. К популярным играм с элементами творчества для детей относятся Roblox, Minecraft, и AnimalCrossing: NewHorizons.

  • Стратегические игры. Эти игры развивают логическое мышление и стратегическое планирование. К популярным стратегическим играм для детей относятся ClashRoyale, ClashofClans, CS2 и Цивилизация.

Что касается времени, которое дети проводят за играми, то оно варьируется от 1 до 5 часов в день. Большинство детей играют в игры с друзьями.

По результату анкетирования, наибольшим спросом пользуются казуальные стрелялки, поэтому было принято решение создания стрелялки.

Выводы

Вот так потратив 3 месяца на создание игры и 6 месяцев на обучение этому делу, параллельно учась в 10-м классе, я смог исполнить свою мечту, и при этом еще и заработать.

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

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

 Актуальная ссылка на игру: Игра. Можно зайти и поиграть

Литература

Книги

  1. Бонд Д. Г. Unity и C#. Геймдев от идеи до реализации

  2. Прайс. C# 7 и .NET Core.Кросс-платформенная разработка для профессионалов

Интернет ресурсы

  1. websketches.ru

  2. habrahabr.ru

  3. docs.unity3d.com

  4. pikabu.ru

  5. youtube.com

  6. https://youtu.be/w8rRhAup4kg?si=Hm59UtD0TaDdc1eL

  7. https://learn.microsoft.com/ru-ru/dotnet/csharp/

Просмотров работы: 48