AnnTenna AnnTenna

Зарелизили перевод на английский язык нашей фан-игры!

guniball guniball

Запустили альфу Wild Terra 2! PvE и PvP, рыбалка, питомцы, 115 рецептов крафта и строительства, 27 навыков и море фана!

DuCats Games DuCats Games

Dreaman, да, в рамках нашей игры такое не впихнешь) У нас и так там есть треш и угар - мы его просто не показывали, но кто играл в первую текстовую игру - тот поймет о чем я.

Dreaman Dreaman

DuCats Games, хе хе )) Точно. Классно придумал :)
Это надо отдельную игру делать - "Невероятные приключения Ленина" :))

DuCats Games DuCats Games

Dreaman, Dreaman, ну да)) Это ржачно было бы)) Суперспособности в стиле призыва броневика и блеска лысины, ослепляющего врагов)

Dreaman Dreaman

DuCats Games,

Хех )) Всё, теперь уже фантазия разыгралась. Было бы круто, если бы в качестве главного героя был Ленин с мечом. По крайней мере, чтобы его можно было выбирать в качестве альтернативного персонажа ...

DuCats Games DuCats Games

Dreaman, ага)) Про Ленина в игре будет прямо что надо))

Dreaman Dreaman

DuCats Games,

Аа! ) А я смотрю, трейлеры то маленько отличаются. Вот я и подумал, что это реально новый серьёзный трейлер и вы решили именно такую музыку использовать на полном серьёзе ))

Rfdshir Rfdshir

Jusper, KO3bMA, alexprey, Dreaman, пасиб! Да, это покадровая съемка пластилиновых моделей.

DuCats Games DuCats Games

Dreaman, ну да, это и есть в качестве прикола же)) А в прошлый субботник выкладывал нормальный трейлер с нормальной музыкой) А это так - поугарать.

Jusper Jusper

KO3bMA, покадровая съёмка, вспомнил!

Dreaman Dreaman

kirsakshlil,

Интересно выглядит. Теперь игра стала похожа на всеми любимый Doom :)

Dreaman Dreaman

DuCats Games,

Всё хорошо и красиво, но музыка такая конечно совершенно не подходит. Разве что только в качестве прикола ;)

alexprey alexprey

kirsakshlil, внезапная смена локаций)

Dreaman Dreaman

TheDarkestRed,

Классный паучок! Не хотел бы я встретить такого в лесу ))

Dreaman Dreaman

Игра конечно очень странно выглядит и в то же время очень оригинально и интересно :)

Dreaman Dreaman

KO3bMA,

Так это ж всё из пластилина сделано. И анимировано всё также как анимируют пластилиновые мультики - покадрово всё снимают, меняя положение пластилиновых фигурок. Ну как-то так )

alexprey alexprey

Ого, круто то какая!

kirsakshlil kirsakshlil

Герою предстоит побывать во множестве разных локаций, одной из которой является "Выжженные земли". Это остатки древнего королевства на неизвестной планете, которая когда-то давно была объектом оккупации и экспериментов машин...

...
Jusper Jusper

KO3bMA, больше похоже на технологию анимирование через фотографии (забыл как называется)

Логотип проекта Unity

Паттерны. "Strategy". Unity

Здравствуйте уважаемые читатели данной статьи. Сегодня я решил поговорить о ОО-проектировании в сфере геймдева, используя движок Unity. Сразу скажу, что данная статься является объективным видением использования паттернов и в особенности, их реализация. Кто-то может говорить о том, что в моих далее приведенных примерах лучше использовать тот или иной паттерн и возможно вы будете правы, но моя задача как минимум поверхностно пройтись по этой достаточно сложной теме. Я рассчитываю сделать небольшой цикл статей про разные паттерны и их примерное использование, чтобы архитектура вашего проекта стала гибкой и расширяемой.
Также хочу отметить, что данный цикл будет требовать определенных знаний в области ОО-программирования и базового ознакомления с понятием "паттерн".
Вроде всё оговорил, можем начинать!

Паттерн "Стратегия". Его использование.

Подробнее с самим паттерном можно ознакомится здесь: https://metanit.com/sharp/patterns/3.1.php

Данный паттерн рассчитан для добавления новых поведений объектам, имеющим один базовый класс, либо интерфейс.
Например, у нас есть объект "Enemy" и объект "Player". Наша задача - добавить новые виды врагов и NPC в нашу небольшую игру. Предположим, мы уже имеем базовую реализацию двух ранее написанных классов, но мы хотим и в перспективе добавлять всё новые типы врагов и NPC, а реализовывать отдельные классы для каждого типа - вещь затратная, как в плане времени, так в плане и "кодинга".
Если мыслить более абстрактно, мы можем выделить для класса "Enemy" , "Player" и "NPC" один абстрактный класс, который будет иметь общие хар-ки и методы. Назовём этот класс "Human". Я рассматриваю задачу на конкретных примерах и поэтому названия буду давать соответствующие.
Что мы имеем? Предположим такую реализацию класса "Human":

using UnityEngine;
using Game.Behaviours;

namespace Game.Entity
{
    public abstract class Human : MonoBehaviour
    {
        protected readonly byte maxHealth = 100;
        protected string humanName;
        protected byte currentHealth;  
    }
}

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

Примечание: В целом, набор параметров можно было бы хранить двумя другими способами. 1. Хранить внутри инкапсулированной структуры . 2. Хранить внутри ScriptableObject.

Теперь перейдём к реализации класса "Player":

using UnityEngine;
using Game.Behaviours;

namespace Game.Entity
{
    public class Player : Human
    {
        [System.Serializable]
        public struct PlayerInfo
        {
            public Vector3 position;

            public PlayerInfo(Vector3 position)
            {
                this.position = position;
            }
        }
        [SerializeField]
        private Vector3 direction;
        private PlayerInfo playerInfo;
        

        private void Start()
        {
        	playerInfo = new PlayerInfo(transform.position);
        }

        private void Update()
        {
        	if (Input.GetKey(KeyCode.Space)) Movement();
        }
        
        private void Movement()
        {
  		//Здесь мы будем двигать нашего игрока (для примера)
        }

    }
}

Поясню некоторые моменты:

  1. Все изменяемые параметры игрока я храню внутри структуры (чтобы в будущем можно было бы, например, сохранять эти данные в JSON формате. (сохранение игровых данных, проще говоря).
  2. Для примера, я решил сделать передвижение игрока, поэтому добавил некоторые элементы, которые будут для нас важны в будущем.

А теперь реализация класса "Enemy":

using UnityEngine;

namespace Game.Entity
{

    public class Enemy : Human
    {

        private void Start()
        {
           
        }

        private void Update()
        {

        }
    }
}

Пока что ничего интересного, просто пустой класс. Теперь предположим, что мы хотим, чтобы наш игрок мог двигаться, но при этом, наши враги тоже могли. Для этого мы создадим... Интерфейс! Да-да, именно. А далее, мы создадим класс-поведение, которое будет наследовать данный интерфейс. А теперь, по-порядку.
Интерфейс:

public interface WalkBehaviour
    {      
        float SetMoveSpeed(float speed);
        void Move(Vector3 direction);
    }

Классический пример интерфейса для движения объекта. Далее, класс-поведение:

using UnityEngine;

namespace Game.Behaviours
{

    public class HumanWalkBehaviour :  WalkBehaviour
    {
        private Transform humanTransform;
        private float speed;

        public HumanWalkBehaviour(Transform humanTransform, float speed)
        {
            this.humanTransform = humanTransform;
            this.speed = speed;
        }
 
        public float SetMoveSpeed(float speed) => this.speed = speed;
       
        public void Move(Vector3 direction)
        {        	
            humanTransform.position += direction * speed;
            //Проверка. Как и подобает.
            Debug.LogFormat("Transform: {0}, Direction: {1}, Speed: {2}", humanTransform, direction, speed);           
        }

    }
}

Как видите, все необходимые данные инициализируются в конструкторе класса, а при этом, само поведение не будет занимать места среди компонентов игровых объектов, так как существует он только внутри классов, которыми он будет создан.
Теперь немного преобразуем класс "Human":

using UnityEngine;
using Game.Behaviours;

namespace Game.Entity
{

    public abstract class Human : MonoBehaviour
    {
        protected readonly byte maxHealth = 100;
        protected string humanName;
        protected byte currentHealth;
        private WalkBehaviour walkBehaviour;

        public void SetWalkBehaviour(WalkBehaviour behaviour) => walkBehaviour = behaviour;

        public void PerformWalkSetSpeed(float speed) => walkBehaviour.SetMoveSpeed(speed);

        public void PerformWalkMove(Vector3 direction) => walkBehaviour.Move(direction);       
        
    }
}

Если вы обратите внимание, то увидите, что благодаря методу "SetWalkBehaviour" вы имеете возможность добавить новое поведение, а конкретно поведение, реализуемое на базе интерфейса "WalkBehaviour". Достоинство этого способа заключается в том, что экзмепляр, который хранит у нас "WalkBehavoir" никак не привязан к классу "HumanWalkBehaviour". Что это значит? То, что вы можете делать несколько классов-поведений, которые связаны одной задачей, но при этом имеют разную реализацию. Конкретнее в примере далее.
Теперь преобразуем класс "Player":

using UnityEngine;
using Game.Behaviours;

namespace Game.Entity
{
    public class Player : Human
    {
        [System.Serializable]
        public struct PlayerInfo
        {
            public Vector3 position;

            public PlayerInfo(Vector3 position)
            {
                this.position = position;
            }
        }
        [SerializeField]
        private Vector3 direction;
        private PlayerInfo playerInfo;
        

        private void Start()
        {
            playerInfo = new PlayerInfo(transform.position);
            SetWalkBehaviour(new HumanWalkBehaviour(transform, .25f));
          
        }

        private void Update()
        {
            if (Input.GetKey(KeyCode.Space)) Movement();

        }
        
        private void Movement()
        {
            PerformWalkMove(direction);
           
        }

    }
}

Как вы можете видеть, класс "Player" создаёт новое для себя поведение, а также передаёт некоторые входные параметры, необходимые для данного поведения. Сам вызов поведения реализован в методе "PerformWalk" и вызывается внутри класса "Player". Если протестировать, игрок будет двигаться в том направлении, которое ему укажут (переменная 'direction' в окне "Inspector").
Таким образом, мы применили паттерн "Стратегия". Мы создали один общий класс для многих похожих друг на друга классов, которые имеют общие параметры, но при этом могут иметь или не иметь разные поведения.
Таким способом, вы можете добавлять совершенно разные поведения и устанавливать их для разных классов. Например для класса "Enemy" я создам новое поведение.
Вот новое поведение для класса "Enemy":
using UnityEngine;

namespace Game.Behaviours
{

    public class EnemyNotWalkBehaviour : WalkBehaviour
    {

        public void Move(Vector3 direction) => Debug.Log("This enemy can not walk! He can only kill!");

        public float SetMoveSpeed(float speed) => 0;
        
    }
}

И его использование в классе "Enemy":

using UnityEngine;
using Game.Behaviours;

namespace Game.Entity
{

    public class Enemy : Human
    {

        private void Start()
        {
            SetWalkBehaviour(new EnemyNotWalkBehaviour());
            PerformWalkMove(Vector3.zero);
        }
    }
}

Хм. Кажется, этот враг не может ходить... Верно, мы ведь избавили его от этой нелёгкой работы.
Как видите, я использую те же методы, что и в классе "Player", только теперь добавляю новое поведение. Однако, я могу добавить это поведение и игроку. Но стоит ли?

В следующей статье я хочу рассказать сразу о двух паттернах, а именно "Одиночка" и "Декоратор".

Заключение:
Конечно, может кто-то и скажет, что для такой задачи лучше мог подойти паттерн "состояние" с использованием конечных автоматов, но, как мне кажется, данный паттерн лучше использовать, например, для написания ИИ. А для общей картины, лучше подойдёт "Стратегия". Но это лишь моё мнение.
Кстати, если вдруг тут будет гуру-программист, у меня есть небольшая просьба в виде совета. Подробнее в ЛС, пожалуйста.

Смотрите также:


Комментарии



  • 1
  • 2 (Текущая страница)

Познавательно, спасибо.

  • 1
  • 2 (Текущая страница)
Справка