image Привет, Хаброжители! Изучение C# через разработку игр на Unity — популярный способ ускоренного освоения мощного и универсального языка программирования, используемого для решения прикладных задач в широком спектре предметных областей. Эта книга дает вам возможность с нуля изучить программирование на C# без зубодробительных терминов и непонятной логики программирования, причем процесс изучения сопровождается созданием простой игры на Unity.

В пятом издании изложены последние версии всех современных функций 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

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

Использование конструкторов

Конструкторы классов — это специальные методы, которые запускаются автоматически при создании экземпляра класса, что аналогично запуску метода 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

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

Время действовать. Определяем начальные свойства

Теперь поведение класса 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

Теперь мы можем выбирать при инициализации нового класса Character между базовым и расширенным конструктором. Сам класс Character теперь гораздо более гибок и позволяет настраивать разные экземпляры для разных ситуаций (рис. 5.4).

image

Теперь начинается настоящая работа. Нашему классу нужны методы, чтобы они могли делать нечто полезное, кроме хранения переменных. Ваша следующая задача — применить это на практике.

Объявление методов класса

Добавление методов в пользовательские классы ничем не отличается от добавления их в 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

Мы обсудили классы и теперь возьмемся за их более легкий объект-собрат — структуры!

Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Unity

Комментарии (8)


  1. FAT
    21.12.2021 19:49

    Купил бумажное издание, но в придачу мне выдали ещё и PDF, и ЕPUB. Так и должно быть?


    1. Stasxuz
      21.12.2021 22:40
      -2

      Поделись пожалуйста пдф


      1. MTyrz
        21.12.2021 22:48
        +3

        Я сам далеко не безгрешен, но такой коммент: прямо в блоге издательства, прямо под анонсом книги и объявлением скидки, даже мне кажется некоторым… э-э… излишеством.


        1. ph_piter Автор
          22.12.2021 12:22

          Подскажите, цена на электронную книгу адекватна ?


          1. MTyrz
            22.12.2021 17:24

            Я вам отвечу уклончиво: я довольно давно не в России, и нынешние порядки российских цен для меня уже непривычны и незнакомы, выпал из контекста.
            Сравнил цены на Литресе — да вроде бы похожи…


        1. Stasxuz
          23.12.2021 13:22
          -2

          Почему?


    1. ph_piter Автор
      21.12.2021 22:43

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


  1. OlegZH
    21.12.2021 22:36

    Статья немного странная: начинается как обзор книги, по перетекает в описание отдельного аспекта — введение в ООП.

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