![image](https://habrastorage.org/webt/qp/i3/-7/qpi3-7igilxfsvtapy0zshpmfqm.jpeg)
В пятом издании изложены последние версии всех современных функций C# на примерах из игрового движка Unity, а также добавлена новая глава о промежуточных типах коллекций. Вы начнете с основ программирования и языка C#, узнаете основные концепции программирования на С#, включая переменные, классы и объектно-ориентированное программирование. Освоив программирование на C#, переключитесь непосредственно на разработку игр на Unity и узнаете, как написать сценарий простой игры на C#. На протяжении всей книги описываются лучшие практики программирования, которые помогут вам вывести свои навыки Unity и C# на новый уровень. В результате вы сможете использовать язык C # для создания собственных реальных проектов игр на Unity.
Работа с классами, структурами и ООП
Разумеется, цель книги заключается не в том, чтобы ваша голова взорвалась от переизбытка информации. Тем не менее темы, которые мы рассмотрим в этой главе, переведут вас из комнаты для новичков в мир объектно-ориентированного программирования (ООП). До этого момента мы полагались исключительно на предопределенные типы переменных, которые являются частью языка C#, а именно встроенные строки, списки и словари. Эти типы являются классами, вследствие чего мы можем создавать их и использовать их свойства через точечную нотацию. Однако у использования встроенных типов есть один вопиющий недостаток — невозможность отклониться от схем, определенных языком C#.
Создание классов дает вам свободу определять и настраивать свои шаблоны для проектирования программы, собирать информацию и управлять действиями, характерными для вашей игры или приложения. По сути, пользовательские классы и ООП — это ключи от царства программирования. Без них уникальных программ было бы мало.
В этой главе мы получим практический опыт создания классов с нуля и поговорим о внутреннем устройстве переменных, конструкторов и методов класса. Кроме того, мы познакомимся с различиями между объектами ссылочного типа и типа значения, а также с тем, чем эти концепции полезны в Unity. По мере продвижения по главе мы подробно рассмотрим следующие темы:
- определение классов;
- объявление структур;
- объявление и использование структур;
- общие сведения о ссылочных типах и типах значения;
- основы ООП;
- применение ООП в Unity.
Определение класса
В главе 2 мы кратко поговорили о том, что классы являются схемами объектов, и я упомянул, что их можно рассматривать как пользовательские типы переменных.
Мы также узнали, что сценарий LearningCurve — это класс, но особый, который Unity позволяет прикреплять к объектам на сцене. Главное, что нужно помнить о классах, — это то, что они относятся к ссылочным типам, то есть когда они назначаются или передаются другой переменной, создается ссылка на исходный объект, а не на новую копию. К этому мы еще вернемся после обсуждения структур, но прежде вам нужно будет понять основы создания классов.
Базовый синтаксис
На мгновение забудем, как классы и сценарии работают в Unity, и сосредоточимся на том, как создавать и использовать их в C#.
Если вы помните схему, которую мы набросали ранее, то классы создаются с помощью ключевого слова class, как показано ниже:
accessModifier class UniqueName
{
Переменные
Конструкторы
Методы
}
Любые переменные или методы, объявленные внутри класса, принадлежат ему, и обращаться к ним можно через уникальное имя данного класса.
Чтобы все примеры в этой главе хорошо вплетались в канву, заданную нами ранее, мы создадим и будем далее развивать простой класс Character, который часто встречается в играх. Вдобавок я перестану приводить скриншоты с кодом, чтобы вы привыкли читать и интерпретировать код, так сказать, «вживую».
Но первое, что нам понадобится, — собственный класс, поэтому создадим его.
Время действовать. Создаем класс персонажа
Нам понадобится класс, на котором мы будем практиковаться, чтобы понять, как все устроено. А раз так, то создадим новый сценарий C# и начнем с нуля.
1. Щелкните правой кнопкой мыши на папке Scripts, выберите команду Create, а затем C# Script.
2. Назовите новый сценарий Character, откройте его в Visual Studio и удалите весь автоматически сгенерированный код после строки using UnityEngine.
3. Объявите public class с именем Character, после имени добавьте пару фигурных скобок, а затем сохраните файл. Код вашего класса должен выглядеть следующим образом:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character
{
}
Теперь Character — шаблон публичного класса. Это значит, что любой класс в проекте может обращаться к нему для создания персонажей. Однако это всего лишь своего рода инструкция. А для создания персонажа потребуется еще один шаг. Данный этап называется созданием экземпляра, и следующий подраздел посвящен именно ему.
Создание экземпляра объекта класса
Создание экземпляра — это создание нового объекта из заранее известного набора инструкций. Новый объект называется экземпляром. Если классы — это чертежи, то экземпляры — это дома, построенные по данному чертежу. Каждый новый экземпляр Character — отдельный объект класса, точно так же, как два дома, построенные по одним и тем же инструкциям, являются двумя разными домами. Происходящее с одним из них не влияет на другой.
В предыдущей главе мы создали списки и словари, которые являются классами, с помощью их типов и ключевого слова new. То же самое можно делать и с пользовательскими классами, такими как Character, что мы и рассмотрим дальше.
Время действовать. Создаем новый персонаж
Мы объявили класс Character как публичный, а это значит, что экземпляр Character может быть создан в любом другом классе. Поскольку у нас уже есть сценарий LearningCurve, объявим новый персонаж в методе Start().
Объявите новую переменную типа Character в методе Start() сценария LearningCurve:
Character hero = new Character();
Пошагово посмотрим, что здесь к чему.
- Мы задали тип переменной Character; это значит, что переменная является экземпляром данного класса.
- Имя переменной — hero, и она создается с использованием ключевого слова new, за которым следуют имя класса Character и две круглые скобки. Здесь в памяти программы создается фактический экземпляр, даже если класс сейчас пуст.
Мы можем использовать переменную hero, как и любой другой объект, с которым работали до сих пор. Когда у класса Character будут собственные переменные и методы, мы сможем получить к ним доступ из переменной hero с помощью точечной нотации.
Для создания нового персонажа можно также использовать предполагаемое объявление:
var hero = new Character();
Однако наш класс персонажа не очень-то полезен без полей класса, с которыми можно было бы работать. В следующих нескольких подразделах мы добавим поля классов и многое другое.
Добавление полей класса
Добавление переменных или полей в пользовательский класс ничем не отличается от того, что мы уже делали в сценарии LearningCurve. Мы задействуем уже известные концепции, включая модификаторы доступа, область действия переменных и присвоение значений. Однако любые переменные, принадлежащие классу, создаются с экземпляром класса, а это значит, что если им не присвоены значения, то они будут по умолчанию равны нулю (zero), или null. В целом выбор начальных значений сводится к тому, какая информация будет храниться в переменной:
- если некая переменная должна иметь одно и то же начальное значение при создании экземпляра класса, то можно задать начальное значение сразу;
- если переменную необходимо настраивать по-своему в каждом экземпляре класса, то оставьте ее значение неназначенным и используйте конструктор класса (о них мы поговорим позже).
Каждому классу персонажа потребуется несколько основных полей. Далее нам предстоит добавить их.
Время действовать. Конкретизируем детали персонажа
Добавим две переменные, в которых будем хранить имя персонажа и изначальное количество очков опыта.
1. Добавим две публичные переменные внутри фигурных скобок класса Character — строковую переменную для имени и целочисленную переменную для очков опыта.
2. Оставьте значение name пустым, а очкам опыта задайте значение 0, чтобы каждый персонаж стартовал с начала:
public class Character
{
public string name;
public int exp = 0;
}
3. Добавьте в сценарий LearningCurve вывод в консоль сразу после инициализации экземпляра Character. Выведите переменные name и exp нового персонажа, используя точечную нотацию:
Character hero = new Character();
Debug.LogFormat("Hero: {0} - {1} EXP", hero.name, hero.exp);
Когда инициализируется объект hero, его параметру name присваивается нулевое значение, которое в консоли отображается как пустое место, а exp равно 0. Обратите внимание: нам не нужно было прикреплять сценарий Character к каким-либо объектам GameObject в сцене. Мы просто сослались на него в LearningCurve, а все остальное сделал Unity. Теперь в консоли будет выводиться информация о нашем герое игры следующим образом (рис. 5.1).
![image](https://habrastorage.org/webt/ac/ed/cz/acedczrf5u9h_zpq4tebyb5uzae.png)
На данный момент наш класс работает, но с пустыми значениями пользы от него пока немного. Попробуем исправить это с помощью так называемого конструктора класса.
Использование конструкторов
Конструкторы классов — это специальные методы, которые запускаются автоматически при создании экземпляра класса, что аналогично запуску метода Start в сценарии LearningCurve. Конструкторы строят класс в соответствии с его шаблоном:
- если конструктор не указан, то C# создает конструктор по умолчанию. Он устанавливает для всех переменных значения по умолчанию, соответствующие их типам: числовые значения равны нулю, логические значения — false, а ссылочные типы (классы) — null;
- пользовательские конструкторы можно определять с помощью параметров, как и любой другой метод, и они призваны задавать значения переменных класса при инициализации;
- у класса может быть несколько конструкторов.
Конструкторы — обычные методы, но с некоторыми отличиями. Например, они должны быть публичными, не иметь возвращаемого типа, а имя метода всегда является именем класса. В качестве примера добавим базовый конструктор без параметров к классу Character и установим в поле name значение, отличное от null.
Добавьте этот новый код непосредственно под переменные класса, как показано ниже:
public string name;
public int exp = 0;
public Character()
{
name = "Not assigned";
}
Запустите проект в Unity, и вы увидите экземпляр hero, созданный через новый конструктор. Параметр name будет иметь значение Not assigned вместо null (рис. 5.2).
![image](https://habrastorage.org/webt/bq/ot/i5/bqoti57npc4k30xorlvwc7fb_jo.png)
Уже лучше, но нам требуется бо'льшая гибкость конструктора класса. Это значит, нам нужна возможность передавать ему значения, чтобы их можно было использовать в качестве начальных значений для экземпляра класса. Так и поступим.
Время действовать. Определяем начальные свойства
Теперь поведение класса Character больше похоже на поведение настоящего объекта, но мы можем еще улучшить его, добавив второй конструктор, который принимает имя при инициализации и устанавливает его в поле имени.
1. Добавьте в Character еще один конструктор, который принимает параметр типа string с именем name.
2. Назначьте параметр переменной name класса с помощью ключевого слова this. Это называется перегрузкой конструктора:
public Character(string name)
{
this.name = name;
}
Для удобства у конструкторов часто бывают параметры, имена которых совпадают с именем переменной класса. В этих случаях поможет ключевое слово this, указывающее, какая переменная принадлежит классу. В приведенном здесь примере this.name указывает на переменную name данного класса, а name — это параметр. Если не использовать ключевое слово this, то компилятор выдаст предупреждение, поскольку для него эти имена одинаковы.
3. Создайте в сценарии LearningCurve новый экземпляр класса Character с именем heroine. Примените пользовательский конструктор, чтобы передать имя при его инициализации и вывести его в консоль:
Character heroine = new Character("Agatha");
Debug.LogFormat("Hero: {0} - {1} EXP", heroine.name, heroine.exp);
Когда у класса есть несколько конструкторов или у метода несколько вариантов, Visual Studio покажет набор стрелок во всплывающем окне автозаполнения, которое можно прокручивать с помощью кнопок со стрелками (рис. 5.3).
![image](https://habrastorage.org/webt/rb/wd/h2/rbwdh2wvizpk5kvgxdshhdgut4g.png)
Теперь мы можем выбирать при инициализации нового класса Character между базовым и расширенным конструктором. Сам класс Character теперь гораздо более гибок и позволяет настраивать разные экземпляры для разных ситуаций (рис. 5.4).
![image](https://habrastorage.org/webt/7k/7u/8g/7k7u8gtmugfiii3k5ghjgpecocq.png)
Теперь начинается настоящая работа. Нашему классу нужны методы, чтобы они могли делать нечто полезное, кроме хранения переменных. Ваша следующая задача — применить это на практике.
Объявление методов класса
Добавление методов в пользовательские классы ничем не отличается от добавления их в LearningCurve. Однако это прекрасная возможность поговорить о главном правиле хорошего программирования — Don’t Repeat Yourself (DRY), эталонном принципе качественного кода. По сути, если вы вдруг осознали, что пишете одну и ту же строку или строки снова и снова, то пришло время переосмыслить и реорганизовать ваш код. Обычно это приводит к созданию нового метода, в котором хранится повторяющийся код, что упрощает его изменение и дальнейшее использование в другом месте.
В программировании это называется абстрагированием метода или функции.
У нас уже есть изрядное количество повторяющегося кода, поэтому посмотрим, получится ли нам повысить удобочитаемость и эффективность наших сценариев.
Время действовать. Выводим данные персонажа
Наши сообщения в консоли — прекрасная возможность абстрагироваться от кода непосредственно в классе Character.
1. Добавьте в класс Character новый общедоступный метод с возвращаемым типом void с названием PrintStatsInfo.
2. Скопируйте и вставьте вывод в консоль из класса LearningCurve в тело метода.
3. Измените переменные на name и exp, поскольку теперь на них можно ссылаться напрямую из класса:
public void PrintStatsInfo()
{
Debug.LogFormat("Hero: {0} - {1} EXP", name, exp);
}
4. Замените вывод в консоль персонажа, который мы добавили ранее в метод LearningCurve, поместив на его место вызов метода PrintStatsInfo, и нажмите кнопку Play:
Character hero = new Character();
hero.PrintStatsInfo();
Character heroine = new Character("Agatha");
heroine.PrintStatsInfo();
Теперь, когда у класса Character есть метод, любой экземпляр может свободно обращаться к нему через точечную нотацию. Поскольку hero и heroine являются отдельными объектами, PrintStatsInfo выводит их соответствующие значения name и exp в консоли.
Это лучше, чем писать вывод в консоль напрямую в LearningCurve. Всегда полезно группировать функциональные возможности в класс и управлять действиями с помощью методов. Это делает код более удобочитаемым, поскольку наши объекты Character отдают команду при выводе сообщений в консоль вместо повторения кода.
Весь класс Character приведен на рис. 5.5.
![image](https://habrastorage.org/webt/d3/8z/vz/d38zvzsumrhutoc8wckp-62r0lu.png)
Мы обсудили классы и теперь возьмемся за их более легкий объект-собрат — структуры!
Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 25% по купону — Unity
Комментарии (8)
OlegZH
21.12.2021 22:36Статья немного странная: начинается как обзор книги, по перетекает в описание отдельного аспекта — введение в ООП.
Лично мне, для своих предполагаемых разработок, было бы интересно узнать, можно ли вести протокол действий пользователя с отсчётами времени, чтобы задать пользователю определённую игровую ситуацию, и измерять его реакцию на происходящие события.
FAT
Купил бумажное издание, но в придачу мне выдали ещё и PDF, и ЕPUB. Так и должно быть?
Stasxuz
Поделись пожалуйста пдф
MTyrz
Я сам далеко не безгрешен, но такой коммент: прямо в блоге издательства, прямо под анонсом книги и объявлением скидки, даже мне кажется некоторым… э-э… излишеством.
ph_piter Автор
Подскажите, цена на электронную книгу адекватна ?
MTyrz
Я вам отвечу уклончиво: я довольно давно не в России, и нынешние порядки российских цен для меня уже непривычны и незнакомы, выпал из контекста.
Сравнил цены на Литресе — да вроде бы похожи…
Stasxuz
Почему?
ph_piter Автор
Да, об этом написано под бумажной версией книги. Пока бумага к вам едет, вы уже можете читать онлайн.