alexprey alexprey

Добавил скриншоты на главную, позже загружу дневники разработки игры, где можно будет посмотреть с чего все начиналось :)

alexprey alexprey

EfReeZe, о да, тогда было знатное время и игра казалась сделано круто :D До сих пор иногда играю на телефоне от скуки)

EfReeZe EfReeZe

Это был наш далёкий 2014...

Jusper Jusper

Запись трансляции на YouTube

Jusper Jusper

Случайно сделали битву сварщиков
https://devtribe.ru/p/slash-polygon

Jusper Jusper

Внезапно на сайте потерялся 15-й выпуск. Перевыложили.

Jusper Jusper

Я вам еще не закончил писать обратную связь по боевке, есть много вещей которые немного смутили, но они поправимы. Завтра, если перестанут дергать - чиркану. Спасибо за демку!

Jusper Jusper

ruggaraxe,

Да, в этом плане все ок. Логично, что графен на старой машине, если не упарываться, не взлетит. Но я рад, что это было не 5 фпс, как даже в некоторых АА (типа Pillars of Eternity в некоторых схватках...

Jusper Jusper

ruggaraxe,

Подкреплю ее к публикации.

ruggaraxe ruggaraxe

Jusper, вот ссылка на анкету (я затупил со ссылкой с топике, сорри)
https://docs.google.com/forms/d/e/1FAIpQLSd_Wn53lJFrnfGpWI2IX...

ruggaraxe ruggaraxe

Jusper, честно говоря, да на 800х600 даже не проверяли... :) сорри. Ориентировались на FullHD и выше. Хотя над интерфейсом конечно же надо еще хорошенько поработать.
Тултипы постараемся сделать обязательно к следующей версии...

GenElCon GenElCon

Jusper,

Наверное. В прошлом они сделали Endless Legend - посмотри и сразу станет ясно в какую сторону они работают.

Jusper Jusper

GenElCon,

Я не очень понял по трейлеру геймплей. Это что-то типа цивы? Или это RTS?

GenElCon GenElCon

Humankind от разработчиков Endless Legends (и Space, но тут важно именно Legends).
А также согревающие сердца олдов трейлеры Port Royal 4 и Knights of Honor.

Jusper Jusper

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

Jusper Jusper

Первое, оно же самое тяжелое - UI. Я конечно, понимаю, что 800x600 совсем уже не в моде (завтра проверю на нормальной широформатной машине). Заблюренный текст я еще прочитать могу, но вот конкретно размер его крайне мал...

...
Jusper Jusper

ruggaraxe, я поиграл на старом маке 2012 года (Macbook Pro, Intel HD 4000), рад что с учетом довольно нагруженной по свету и теням картинке игруля не лагает как последняя сволочь (лагает конечно, но очень терпимо...

Логотип проекта 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 (Текущая страница)
Справка