Все в школе изучали на биологии классы, отряды, семейства... Даже если Вы не помните что это такое, Вам должно быть понятно значение слова "Млекопитающие". Это животные, которые выкармливают детенышей молоком. Это их общая черта, которая отличает их от других классов. Можно вести класс:
второе что их отличает - они живородящие. Для этих двух отличий мы вводим две переменные, которое у всех млекопитающих true. У иных существ эти переменные false.
Классы делятся на отряды. Наследование!
У класса млекопитающих есть 20 отрядов. Всем этим отрядам присущи две указанные черты млекопитающих, но у каждого отряда есть свои какие-то отличительные черты. Например хищники. Их отличительная особенность - они охотятся на других животных и питаются их мясом. В отличие от них насекомоядные понятно питаются насекомыми. Большинство грызунов питается травами, плодами и семенами растений, корнями и даже древесиной. Соответственно можно перечислить возможные типы пищи:
Code
enum Food { Meat; Vegetable; ... }
И отличать отряды по этому признаку. Однако не нужно забывать, что все они не прекращают быть млекопитающими. Поэтому у них должны быть упомянутые выше две переменные, отличающие млекопитающих. Можно, конечно ввести кучу классов по числу отрядов и у них всех ввести те же самые переменные, что у класса "Млекопитающие". Однако выгоднее будет использовать уже написанный код. Для этого вводится понятие "наследование". Класс, который наследуется от другого класса включает в себя автоматом все переменные и методы этого класса. "Наследует" их. Т.о. классы "Хищники" и грызуны будут:
Code
class Predator : Mlekopitajuchie { Food meal = Food.Meat; } class Rodent : Mlekopitajuchie { Food meal = Food.Vegetable; }
Наследование записывается так ": имя класса от которого наследуемся". Как видим мы не указали переменные mleko и liveRod, однако мы спокойно можем их использовать и у классов Predator и Rodent - они их унаследовали от Mlekopitajuchie! Аналогично можно определить все остальные классы млекопитающих. Совершенно аналогично все и относительно функций. У млекопитающих можно ввести функцию "Кормление". В которой, например, прибавлять энергию детенышу. Все млекопитающие,- и хищники и грызуны будут иметь эту функцию!
Виртуальные функции. Полиморфизм!
Все млекопитающие питаются. Соответственно в класс можно ввести функцию Eat. Все млекопитающие в детстве питаются молоком. Поэтому в этой функции проверяем есть ли поблизости молоко. Если есть уменьшаем количество молока и прибавляем энергию млекопитающему. Это общее поведение для всех млекопитающих.
Однако у каждого отряда в пищевом поведении взрослой особи есть свои особенности. Поэтому в классе "Хищники" нужно изменить функцию Eat. В данном случае проверяем возраст особи и если он больше определенного значения вместо поиска молока начинаем искать мясо. Если же меньше этого значения = ищем молоко.
здесь видим новую запись - "base." - таким образом вызываются функции из класса от которого наследовались.
Спросите - а где виртуальные функции? Дело в том, что очень часто нам не важно какого конкретно отряда животное. Поэтому вместо параметра функции типа Predator можно передать Mlekopitajuchie. Все Predator являются также и Mlekopitajuchie. Поэтому даже если в функцию будет передано значение типа Predator - ошибки не будет. Однако если мы вдруг в такой функции вызовем функцию Eat, то будет вызвана функция из класса Mlekopitajuchie, хотя у нас на самом деле Predator. Хотелось бы, чтобы при этом вызывалась соответствующая переданному значению функция. Вот тут приходят на помощь виртуальные функции. Переопределим функции так: "Млекопитающие"
Здесь в Млекопитающие - функция определена как виртуальная, а в Хищники - как переопределенная. Если теперь мы введем функцию:
Code
void Proba(Mlekopitajuchie animal) { Eat(); }
То внутри ее будет вызвана Eat, соответствующая тому, что мы передадим в качестве параметра. Если Хищник - то будет искать мясо, если Грызун, то растения. Ощущаете удобство?
Сорри, если несколько сумбурно - пишу на работе, второпях. Дергают и отвлекают.
Шарп не поддерживает множественное наследование. Т.е. нельзя напрямую сделать "Динотабурет". Однако есть обходной маневр. В шарпе есть такая штука - "Интерфейс". Класс может реализовывать много разных интерфейсов. Наиболее просто понять что такое интерфейс можно взглянув на какой-нибудь универсальный блок питания. Обычно у таких блоков шнур на конце которого несколько разных разъемов. Один можно воткнуть в мобильник, другой в какой-нибудь плейер. Эти разъемы и есть интерфейсы. Интерфейс записывается так:
Код
public interface IName { bool MyFunc(float par); }
Т.е. подобно классу. Только у функций в нем нет тела (операторов, которые что-то делают). Также у них не может быть модификаторов доступа (public). Тело функции Вы записываете в классе, который реализовывает этот интерфейс. Потому и говорят "реализовывает", а не наследует. У наследуемого класса функции уже что-то делают, у интерфейса только определена их сигнатура (т.е. имя, входные параметры и что возвращает). А что делать определяется в классе, реализующем интерфейс. С одной стороны такая структура уменьшает повторное использование кода. Реализацию, даже если она одинаковая приходится писать во всех классах, реализующих интерфейс. С другой стороны это все же позволяет имитировать множественное наследование. Например у нас есть класс и интерфейс
Код
class Dino { public virtual void Eat(float){} } interface ITaburet { bool SitDown(); }
И есть классы наследующие от Dino и реализующий интерфейс табурета
Код
class DiplodokTab : Dino, ITaburet { public bool SitDown(){return true;} }
class TiranosaurTab : Dino, ITaburet { public bool SitDown(){return false;} }
Можно как в первом топике использовать все преимущества наследования и переопределить метод Eat. Можно с другой стороны где-то написать функцию, которая получает флаг - можно ли на нем сидеть так:
Код
public bool Sit(ITaburet myDino) { return myDino.SitDown(); }
Почему можно? Потому что любой класс, который реализует интерфейс ITaburet обязан иметь функцию SitDown! Тогда если мы в нее передадим экземпляр класса диплодок - она вернет "да", а если тиранозавр - вернет "нет". PS: Как обычно слегка сумбурно, т.к. на ходу пишу.
Продолжаем создание игры. Курсор выбора меню, менеджер игры. Константы. Загрузка уровней. Сохранение, восстановление данных с помощью PlayerPrefs. Операторы минус-равно, плюс-равно... Свойства класса - геттер/сеттер. Запуск аудио. Action - передача метода в качестве параметра.
Урок 12. Коротенький урок, в котором я описываю вкраце как настроить проект для использования с системой контроля версий. На битбакет создал репозиторий по Battle City. Ссылку ищите на моем сайте http://devunity.tk