- PVSM.RU - https://www.pvsm.ru -
Черная пятница, черная пятница… надоело. Объявляю свой личный Белый понедельник — за пару ночей написал небольшую игру и выкладываю ее код на всеобщее пользование, со скидкой 90%. Зачем мне это надо? Ну я вижу следующие плюсы — тот самый открытый код для поиска работы (да да, сейчас я нахожусь в активном поиске), почитать в комментариях о своих косяках, наконец то сменить статус на Хабре.
Идея пришла внезапно, и пока она не улетела решил записать а потом и воплотить ее. Представьте холодную, темную камеру в некой тюрьме. В ней сидит, неизвестно как и неизвестно кем, прикованный волшебник, которого каждую ночь мучает всякая нечисть. Из последних сил он создает небольшой файрбол и вдыхает в него подобие жизни.
Познакомьтесь, это Кальцифер, ваш аватар в этой игре.

Именно им вы будете уничтожать вся гадость, которая ползет в сторону нашего закованного бедняги.
Ссыль на код [1]
Начнем с GameManager. Он всему голова, именно в нем меняется состоянии игры — Initialization->GameLoop->Win или Lose. Одинок и един, ибо синглтон. Так как игра не сетевая, простая, и без сложных переходов, то было принято решение использовать этот паттерн. Здесь же идет обработка попаданий по игроку (см. ниже Известные проблемы [2]), учет хитпоинтов и проверка на выигрышпроигрыш. Был бы GodObject [3], да слишком мало у нас классов, поэтому знает не всё обо всех. На этапе инициализации, создается пул объектов отображающих анимацию урона по игроку и смерти врагов. Для отслеживания состояния хитпоинтов, можно подписаться на UpdateHpWizardDelegate или UpdateHpCalciferDelegate. В нашем случае это делает GUIManager для отображения текущего хп на экране.
К этому времени SpawnManager уже составил список точек спавна врагов

a WaveManager загрузил порядок волн создания врагов. Волны можно настроить двумя способами: прописать в коде игры или загрузить с Json файла. Для редактирования этого Json написан кастомный editor:GameDataEditor

Можно прописать точный номер спавна или указать что можно создаться на любом свободном.
Создание волн сделанно с помощью хитрой корутины:
private IEnumerator SpawnWaves()
{
yield return new WaitForSeconds(firstWaveDelay);
while (currentWave < waves.Count)
{
if (!CheckFinishedCurrentWave())
{
var step = waves[currentWave].GetCurrentWaveStep();
if (step != null)
{
yield return new WaitForSeconds(step.delay);
var spawners = step.spawners;
if (spawners != null)
{
foreach (var spawn in spawners)
{
SpawnPoint spawnPoint;
if (spawn.index == Consts.INDEX_RANDOM_SPAWN_POINT)
{
spawnPoint = SpawnManager.S_Instance.GetRandomEmptySpawnPoint();
}
else
{
spawnPoint = SpawnManager.S_Instance.GetSpawnPointByIndex(spawn.index);
}
if (spawnPoint != null)
{
SpawnManager.S_Instance.SpawnEnemy(spawnPoint, spawn.name);
}
else
{
throw new Exception("Not empty spawn point");
}
}
}
Debug.Log(step.text);
}
waves[currentWave].currentStep++;
}
else
{
SelectNextWave();
}
}
}
Вначале проходит firstWaveDelay секунд до начала запуска 1 первой волны. После этого в цикле прогоняют все волны по очереди, вставляя нужную задержку step.delay между шагами волны. Почему в корутине а не например в Update? Да собственно можно и так и эдак, просто тут более наглядно, видно где задержка ( yield return new WaitForSeconds) и не надо городить лишние циклы и проверки.
Давай те глянем что же представляют из себя SpawnPoint. Это MonoBehavior c 2 компонентами: SpawnPoint и CircleCollider2D. В первом, с помощью второго, определяется занят ли спавн каким то врагом. OnDrawGizmos отображает в редакторе Unity расположение спавнов.
void OnDrawGizmos()
{
if (m_IsDirty)
{
Gizmos.color = Color.red;
}
else
{
Gizmos.color = Color.green;
}
Gizmos.DrawSphere(transform.position, 0.3f);
}

Все враги происходят от базового класса BasicEnemy в котором есть несколько виртуальных методов:
Их можно переопределить в потомках, для различной реакции на эти события. Например PoisonEnemy ранит при прикосновении Кальцифера, в отличии от остальных врагов.
public override void ContactCalcifer()
{
GameManager.S_Instance.DamageCalcifer(damage);
base.ContactCalcifer();
}
Кстати, врагов и многие другие объекты (много и часто создаваемых на сцене) мы не удаляем с помощью Destroy(this), а отправляем обратно в пул объектов [4] — ObjectPool.Recycle(this). Таким макаром мы неплохо экономим на создании объектов, которое как известно достаточно затратное дело.
Так например анимации заканчиваются вызовом SelfDestroy(), который и возвращает объект анимации обратно в пул.

Движутся же враги с помощью силы пафоса компонента BasicEnemyMoving. В нем нас интересуют два метода: OnEnable()и Move(). OnEnable () вызывается после вытаскивания врага с пула и нужен для поворота врага (если необходимо) в сторону цели.
public void OnEnable()
{
if (obj!=null&&obj.NeedRotateForDirection)
{
Vector3 moveDirection = transform.position - GameManager.S_Instance.wizardTransform.position;
if (moveDirection != Vector3.zero)
{
float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg-90;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
}
Move() же является виртуальным методом, который и движет врага к цели. Его можно переопределить в потомках и сделать особенное движение (с рывками, синусоидальное и т.п.)
public virtual void Move()
{
transform.position = Vector3.MoveTowards(transform.position, SpawnManager.S_Instance.TEMP_GOAL.position, speed * Time.deltaTime);
}
На этом как бы всё.
Анимация огонька взята с powstudios.com [5]
За спрайт волшебника отдельное спасибо милой Kori Tyan [6]

Автор: Леонид
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/269711
Ссылки в тексте:
[1] Ссыль на код: https://github.com/DemiLix/1NightGame
[2] Известные проблемы: #GameManager
[3] GodObject: https://ru.wikipedia.org/wiki/%D0%91%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82
[4] пул объектов: https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D1%8B%D0%B9_%D0%BF%D1%83%D0%BB
[5] powstudios.com: http://powstudios.com/content/fire-animation-pack-1
[6] Kori Tyan : https://vk.com/corey_tyan
[7] Источник: https://habrahabr.ru/post/343728/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.