Изучив основные типы данных, методы и условия мы уже можем сделать игру, основанную на простых алгоритмах. Но придётся весь код писать в одной программе, выражаясь по "сиШарповски" в одном классе. Эта программа (класс) будет разрастаться семимильными шагами и очень скоро в этой "простыне" станет сложно что-либо найти, легко потерять и проще забить. :)
Классы в C#
Вместо того, чтобы пытаться запихнуть всё в одну программу (класс), будет гораздо удобнее разделить логику и код по разным классам (программам), в зависимости от того, к какому объекту они относятся.
Например, класс Game вполне может заниматься основными переменными игры, типа очки и номер уровня, а также методами "Начать новую игру" и "Пауза", но вот код для управления персонажем логично было бы вынести в отдельный класс под названием Player или PlayerController.
Классы могут работать параллельно и независимо, а могут и учитывать данные друг друга. Например, если в классе Game переменная isPause станет true, то Player не должен иметь возможность управлять персонажем, пока пауза не будет выключена.
Для того, чтобы один класс мог что-то узнать из другого класса нам понадобится ссылка на него!
Значимые и ссылочные типы данных
Ссылка вроде бы такое простое понятие для всех, кто пользуется интернетом, но в контексте программирования оно затрагивает очень интересное явление - ссылочные типы данных. Типы данных бывают двух видов:
- Значимые: int, float, bool и другие
- Ссылочные: class, string, object и другие
Отличаются они в основном тем, как эти данные копируются и сравниваются. Например, уже знакомый нам тип string является ссылочным и при сравнении двух строковых переменных, будут сравниваться не их значения, а ссылки (на один ли объект они указывают). А вот int является значимым типом и при сравнении двух переменных типа int будут сравниваться их значения.
В случае копирования значимых типов, значение будет просто копироваться в другую переменную. Но при копировании ссылочных типов мы передадим ссылку на копируемую переменную.
Ссылки между классами
Если мы хотим, чтобы один класс имел возможность работать с полями и методами другого класса, то мы делаем в первом классе ссылку на второй. При работе с Unity, мы просто создаём публичное поле с названием нужного класса и указываем название класса в качестве типа переменной. Размещаем скрипты с этими классами на объектах сцены в Unity и у нас два варианта эту ссылку получить:
1. В коде получить ссылку на объект с помощью метода FindObjectOfType<Game>()
2. В редакторе Unity в окне Inspector можно просто перетащить объект с нужным скриптом (содержащим этот класс) в поле скрипта висящего на другом объекте.
Теперь мы можем из класса Lesson обратится к классу Game и получить доступ к его полям и методам, если конечно они не против и имеют идентификатор доступа public.
Идентификаторы доступа к полям и методам
Когда мы объявляем поля и методы в нашем классе, то перед типом данных мы можем написать идентификатор доступа public, что даст возможность другим классам получить доступ к ним. По умолчанию, если мы ничего не напишем, то идентификатор доступа будет private. Можем и вручную private написать для наглядности. Это сделает поля и методы недоступными извне. В таком случае только сам класс, в котором они объявлены, жадно и ревностно будет ими пользоваться.
Зачем такие сложности и зачем запрещать доступ? Все ради безопасности! Чтобы посторонний класс, например написанный другим человеком, ничего в нашей игре не поломал. Вдруг этот посторонний класс сделает так, что аптечка будет добавлять не 100 здоровья, а минус 999 и главный герой будет все время умирать, пытаясь вылечиться. Ну, а мы будем искать проблему, которую возможно даже и не мы создали.
Видов идентификаторов доступа существует больше, но на первых порах public и private нам вполне хватит.
Работа с большим количеством данных
Если в нашей игре переменных будет не очень много, то мы даже возможно не сойдём с ума, изменяя каждую по отдельности. :) Но что если в игре будет много уровней, много врагов, много предметов и нужно будет как-то с ними всеми работать?
Это получается придётся для каждого параметра каждого объекта, которых например 100, завести переменную и в коде к каждой из них обращаться. Наш класс, по объёму повторяющегося кода, стремительно начнёт догонять произведение "Война и мир". Да и трудоемкость написания такого когда будет
просто колоссальная! А хороший программист - это ленивый программист и главный босс ленивых программистов придумал массивы данных и списки.
Списки и массивы для работы с данными
Данные одного типа мы можем хранить в одном массиве и обращаться к каждому элементу этого массива по номеру. Например, в нашей игре много уровней и мы хотим куда-то сохранить данные о том, какие уровни пройдены, а какие нет. Вместо того, чтобы создавать 100 переменных типа bool, мы можем создать один массив данных типа bool со 100 элементами.
Давайте создадим массив на 100 элементов, в котором будет храниться информация какие уровни доступны для выбора, а какие нет. Массив объявляется просто указанием типа данных и добавление квадратных скобок [].
Но массив мало просто объявить. Он изначально пустой и не содержит ни одного элемента. Нужно его инициализировать, задав ему значение в виде нового экземпляра массива данных типа bool, указав в скобках количество элементов. По умолчанию все элементы примут значение false, 0 или null в зависимости от типа. В нашем случае будет false.
Теперь мы можем, указывая в скобках номер элемента, задавать ему значение true или false. Главное помнить, что нумерация в массивах идёт от нуля, а значит, нам доступны элементы с 0 до 99, т.к. мы задали общее количество 100.
Предположим, что метод OpenLevel() открывает нам первый уровень и вызывает метод UpdateLevels(), который делает иконки уровней активными.
Открывая один уровень, мы могли бы сразу его сделать активным и мудрить бы не пришлось. Но что если мы загрузили сохраненную игру и у нас весь массив openedLevels заполнился сохраненными значениями, которые нужно все обработать (у тех уровней, где значение true, сделать иконки активными). Тут нам на помощь придут циклы!
Циклы для работы с массивами
Обработать весь массив нам поможет цикл for, который может перебрать все 100 элементов, сделать для каждого проверку и выполнить действия в случае значения true.
У массива есть параметр Length (длина массива) равный количеству его элементов, который мы будем использовать в качестве аргумента условия цикла for. Цикл будет перебирать числа типа int от 0 и до Length, прибавляя каждый раз по 1 к своей внутренней переменной i. Для каждого значения i мы сделаем проверку в теле цикла - имеет ли элемент массива под этим номером значение true. Если условие истинно, то выполним включение иконки соответствующего уровня.
Списки и их отличие от массивов
По сути, список является более удобной версией массива данных и его разновидностью. Список объявляется точно также, как переменная внутри нашей программы, с помощью указания типа списка List<> и указания в треугольных скобках типа данных, которые в нём будут храниться.
Изначально список пуст и не содержит ни одного элемента. Но мы можем при объявлении, также как и массив, его инициализировать, задав нужное количество элементов. В списках вместо переменной Length используется переменная Count равная количеству элементов в списке.
Как видно, это практически тоже самое. Но список в отличии от массива может менять свою длину при добавлении и удалении элементов. Например, мы можем в списке хранить количество предметов в сумке у игрока. Их может быть 20, а может и не быть вовсе. Мы сразу узнаем об этом по значению Count.
Используя список, мы можем не задавать количество уровней по умолчанию, а получать его из другого класса, который, например, будет управлять их переключением.
Предположим, что уровни в процессе разработки игры будут добавляться. Тогда мы можем с помощью цикла создать список с актуальным количеством элементов и при необходимости даже менять это количество в процессе игры. Например, можно добавить новый элемент в список со значение false столько раз сколько нам нужно, с помощью метода Add().
Есть ещё очень удобная разновидность цикла for под название foreach, которая перебирает по очереди все элементы коллекции (коллекции - это общее название для массивов и списков). Она удобна, если нам не важен номер элемента в списке для действий над ним.
Метод ShowAllPlayers() просто выведет имена всех игроков, которые хранятся в списке playersName. А вот метод ShowTop10Players() выведет, только первые 10 имён игроков с порядковым номером, если конечно они вообще есть в списке.
Есть и другие циклы, но поначалу нам пригодятся только эти. Теперь мы можем завести в игре список любимых напитков и дополнять его прямо в процессе бурных отмечаний в честь победы над боссом А в случае, если напиток на утро оставит тяжелый след от своего употребления, то может быть немедленно исключен из списка, как не оправдавший доверия. :)
Спасибо, что дочитали до конца! :) Если статья была для вас полезна и вы не отказались бы почитать продолжение, то подписывайтесь на канал, а чтобы смотивировать меня выпустить его как можно скорее можно поставить лайк или даже оставить комментарий! :)
Вот вам еще первая часть по C#:
И подборка статей по разработке игры на Unity: