Dreaman Dreaman

Mariya, законченный вариант реально классно выглядит! Молодец :)

Mariya Mariya

Всем привет!
Сегодня хочу показать законченный вариант домика Сырны.

alexprey alexprey

StarPlosion: Битва с пиратами за планету

Wings' might Wings' might

Всем привет)
Добавил на этой неделе начальное окно, новую валюту и достижения:

alexprey alexprey

shadeborn, может быть они нашли способ как обойти полноценный запуск этих сервисов при запуске игры? Если так, то почему бы и не использовать единый клиент?) В любом случае, чем дальше все идет, там это все больше становится похоже на эту шутку

...
shadeborn shadeborn

Это всё круто, но...зачем? Ок есть Стим. Хорошо, теперь еще и ЕГС есть. Благо, комп не зашкварен Оригином...но у людей и он стоит. И для работы игр внутри этих сервисов нужны, собсно, эти сервисы. Так поверх этих сервисов нужно накатить еще один...

Devion Devion

согласен, есть кейсы которые без них вообще не делаются, не знать или избегать их определенно неправильно

Так точно, либо любое изменение в объекте на стороне редактора.

alexprey alexprey

Devion, только рад за дополнение, Спасибо)

хм, а вот это интересно, не пробовал никогда такой кейс.

Devion Devion

лёш, ты же не против если я дополню? )

скриптаблы, для понимания могут храниться на уровне ассета и на уровне сцены.

Mariya Mariya

Всем привет!
Начали работу над мебелью в домик Сырны, и первым сделали чайный столик с чайным сервизом. А так же продолжаем работу над анимациями.

Wings' might Wings' might

Всем привет)
За неделю в игру было добавлено меню настроек, переделана старая локация, добавлены новые враги и повышена производительности

alexprey alexprey

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

Dreaman Dreaman

Всем привет!
Для проекта "Mental State" разработано новое устройство, которое уже полностью функционирует внутри игрового мира. Оно носит название "Репульсивер". Совместно с очередными большими воротами это устройство образует новую головоломку...

...
Mariya Mariya

Всем привет!
На этой неделе мы научили Сырну летать!

Tartal Tartal

alexprey, кастомизации - создание внешности персонажа? Я всегда любил это, но не думаю, что это будет к месту в мясном шутере)
EfimovMax, да, есть немного)

Tartal Tartal

Jusper, точно не помню, уже как полгода точно) Я вроде в Дискорде немного обсуждал эту тему. Пока немножко попробовал движок - мне очень нравится. Ну, в конце концов, он идеально подходит под жанры, с которыми я хочу работать...

alexprey alexprey

Первая тема крутая очень!

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