Jusper Jusper

Новые NPC в Slash Polygon.
А также новый тип оружия со своими анимациями - катана.

alexprey alexprey

TinyMiracleGame, динамичненько, понравилось различное взаимодействие с окружением

TinyMiracleGame TinyMiracleGame

Сделал трейлер для своей новой игры на мобилки "Robot Battle". Скоро появится в Google Play! (через некоторое время будет и в App Store)

IDALGAME IDALGAME

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

Razz Razz

Чавкающих тентаклей вам

Razz Razz

IDALGAME, блин, а круто смотрится

Jusper, да я как то всё в процессе бесконечного перепиливания. Следуя отзывам перепилил в "червя", потом ещё гифок накидаю

doctormanhattan doctormanhattan

Всем привет!
Сегодня мы хотели бы рассказать об одном из важных элементов игрового дизайна нашей игры Exoplanet: First Contact. В играх такого типа порой бывает трудно понять, где найти вещи, необходимые для успешного прохождения...

...
Jusper Jusper

Razz,

Новая экранизация слова "обрыгло"

win20082007 win20082007

Приветствую друзья! На этой неделе наконец то приступил к моделированию, риггингу и анимированию первой стадии развития главного героя моей игры. Интересное хоть и не простое занятие. Следить за разработкой и прочитать больше информации по проекту можно в моей группе...

...
TheDarkestRed TheDarkestRed

Обновлённые меню и интерфейс 🎮 👨‍🎨 🀄 👨‍💻
https://vk.com/the_darkestred

Jusper Jusper

Ой модельки красивые. Прямо слов нет. Стильно.
Очень здорово смотрятся.

Dreaman Dreaman

alexprey,

Согласен! И стреляет она круто. Враги классно разлетаютcя :)

Dreaman Dreaman

Ну это круто, ребят! Правда посмотрел пока только кусочек. Теперь надо найти время, чтобы посмотреть полностью )

GoloGames GoloGames

Jusper, вот тут описание всех ребят со ссылочками https://vk.com/gologamesgroup?w=wall-88091546_17669

Jusper Jusper

GoloGames,

Да это ж я так. Прикольно послушать всех было.

GoloGames GoloGames

Jusper, интервью записывались за месяц с лишним до Нового Года

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