TyapLyapDev - Разработка игр | Движуха Сакутина
225 subscribers
127 photos
77 videos
13 files
26 links
Download Telegram
TyapLyapDev - Разработка игр | Движуха Сакутина
Photo
Уже сутки дерусь с пользовательским редактором юнити, чтобы сделать эту систему ещё удобнее, настраиваемой в специальном окне. Думаю, это станет полноценной утилитой, чтобы легко настраивать и не лезть в код. Сначала меня пугала работа в редакторе. Сейчас тоже пугает, но меньше, т.к. я начал раскидывать логику по ответственностям и дебажить, расширять, модифицировать становится проще.

Такой вот совет: если перед вами страшный код, раскидайте его по скриптам и работайте с кусочками.
🔥2
Media is too big
VIEW IN TELEGRAM
Показыватель иконок в иерархии сцены 2.0! Теперь достаточно открыть окно, добавить элемент в список и выбрать там иконку и компонент, который висит на объектах для отрисовки. Чем это удобно? Ну, вы можете подсветить в иерархии сцены все объекты, на которые вашим коллегам по разработке надо обратить особое внимание. Я выделял объекты с дорожными модельками для человека, который собирал уровень. Оказалось очень даже удобно. Не тестил на разных версиях юньки, поэтому пишите, если найдёте баги 🙂
🔥4
TyapLyapDev - Разработка игр | Движуха Сакутина
Video
Какое было решение для снижения нагрузки: каждый кубик - объект, который внутри себя содержит триггер-коллайдер в виде сферы, чисто для плавности и саму модельку куба, которая выключена. Позиция триггера никогда не меняется, а вот позиция модельки при деактивации (и на старте) присваивает случайное сферическое отклонение и случайную дистанцию. При активации моделька получает ссылку на цель (игрока) появляется по направлению от цели на эту самую рандомную дистанцию, плюс рандомное отклонение и вращение. Чем меньше дистанция до цели, тем ближе моделька к нулевым координатам позиции и вращению.

Теперь игрок. У него есть детектор в виде сферы, который активирует кубы при входе в триггер и деактивирует при выходе. Физика настроена так, что детектор видит только кубы, а кубы детектор. Все остальные слои игнорируются.

Чтобы ручками не составлять лабиринты из кубиков, я сделал их генерацию с заполнением по всему объёму коллайдеров стен. Компонент принимает размер кубов для каждой стены, зазор между кубами и цветовую схему. После заполнения кубами у стены удаляется рендерер, отрисовывается гизмос в сцене и остаётся только коллайдер, по которому двигается коллайдер игрока.

В целом я постарался в оптимизацию, поэтому всё работает без лагов, сколько бы кубов на сцене не находилось.
🔥41
Сделай первую игру с нуля и получи грант на 1 000 000 рублей
Привет! Я веду этот канал как участник творческой мастерской Романа Сакутина. Наша цель создавать творческие игры и радовать игроков.
Становись разработчиком игр вместе со мной здесь - https://ijunior.ru/workshop-gamedev?utm_source=telegram_workshops&utm_medium=pinned_post&utm_campaign=tyaplyapdev
😁4
TyapLyapDev - Разработка игр | Движуха Сакутина pinned «Сделай первую игру с нуля и получи грант на 1 000 000 рублей Привет! Я веду этот канал как участник творческой мастерской Романа Сакутина. Наша цель создавать творческие игры и радовать игроков. Становись разработчиком игр вместе со мной здесь - https:/…»
Media is too big
VIEW IN TELEGRAM
Языковая локализация игры.

Вам уже доводилось переводить игры на другие языки? Я уже с этим несколько раз сталкивалася, поэтому просто сделал для себя заготовку.

Самый простой, понятный и эффективный способ - накидывать на объекты с текстовыми компонентами кастомный скрипт, в котором есть несколько полей для записи. Вся логика уложилась в 3 скрипта, в которых суммарно 132 строчки кода - это уже с удобным переключением.

Ассет с демосценой прикреплю в комментариях (при импорте так же подтяните TMP). Ещё я в комменты запульну сообщения с кодом чтобы вы могли посмотреть, а кто-то возможно найдёт косяки.

Пишите замечания, задавайте вопросы, с радостью пообщаюсь 🙂
🔥4👍2
Зачем кешировать transform?

Кеширование трансформа в Unity (например, _transform = transform) делают для повышения производительности. Вот основные причины:

1. Избежание повторных вызовов геттера
Свойство transform в Unity не просто возвращает поле - оно вызывает внутренний метод GetComponent<Transform>(), который ищет компонент в GameObject. Это требует вычислительных затрат.

Кеширование сохраняет ссылку на трансформ один раз (например, в Awake()), избегая повторного поиска.

2. Сокращение накладных расходов
В циклах (например, Update()) частые обращения к transform без кеширования могут снижать FPS, особенно на мобильных устройствах или при большом количестве объектов.

Пример без кеширования:
void Update() 
{
// Каждый раз вызывается геттер transform
transform.Translate(1, 0, 0);
transform.Rotate(0, 1, 0);
}


Пример с кешированием:
private Transform _transform;

void Awake()
{
_transform = transform; // Сохраняем ссылку один раз
}

void Update()
{
_transform.Translate(1, 0, 0); // Работаем с кешированной ссылкой
_transform.Rotate(0, 1, 0);
}


Важно!
Современные версии Unity (особенно с 2018+) оптимизировали геттер transform, и разница в производительности стала менее заметной. Однако для критичных к производительности проектов кеширование всё ещё рекомендуется.

Всегда кешируйте компоненты в Awake() или Start(), а не в Update(), чтобы избежать лишних операций.

Что ещё кешируют?
Другие компоненты: Rigidbody, Renderer, Canvas и т.д.
Ссылки на игровые объекты, если они используются часто.

Таким образом, кеширование — это простая и эффективная практика для оптимизации кода в Unity.
👍42🔥2
Хочу поделиться с вами своими соображениями по поводу комментариев в коде. Мне кажется это плохой практикой и вот почему:
1. Код желательно писать так, чтобы он сам говорил за себя. Для этого достаточно придумывать понятные названия для классов, полей и методов, а так же раскидывать логику по ответственностям.
2. Если в коде и комментариях одно и то же, то какой смысл в этих комментариях?!
3. Если вы изменили логику в коде, но не поправили пояснения в комментариях, то это скорее запутает, чем поможет.

Таким образом комментирование - скорее бесполезная штука и двойная работа.

Но не будем голословными. Для примера я качнул из ассет стора Starter Assets - ThirdPersonController .

Давайте посмотрим кусочки:

// cinemachine
private float _cinemachineTargetYaw;
private float _cinemachineTargetPitch;

// player
private float _speed;
private float _animationBlend;
private float _targetRotation = 0.0f;
private float _rotationVelocity;
private float _verticalVelocity;
private float _terminalVelocity = 53.0f;

// timeout deltatime
private float _jumpTimeoutDelta;
private float _fallTimeoutDelta;

// animation IDs
private int _animIDSpeed;
private int _animIDGrounded;
private int _animIDJump;
private int _animIDFreeFall;
private int _animIDMotionSpeed;


Вообще в этом классе 392 строки и он занимается сразу всем. Если раскидать по скриптам, то внезапно окажется, что поля с комментарием // cinemachine попадут в один класс, // player в другой класс и т.д. Это означает, что сама архитектура снимет с вас необходимость группировать поля таким способом. Но допустим, это прототип и не страшно, что здесь всего напихано. Смотрим ещё раз:

// cinemachine
private float _cinemachineTargetYaw;
private float _cinemachineTargetPitch;

В самом названии полей написано _cinemachine , так же и в остальных. Здесь можно поудалять комментарии, оставить лишь разделение групп пустыми строками и читаемость нисколько не пострадает, а даже улучшится.

Смотрим дальше:
private void Awake()
{
// get a reference to our main camera
if (_mainCamera == null)
{
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
}
}

В комментарии говорится "// получите ссылку на нашу основную камеру". А в коде что? А вот: если ссылка на Камеру равна null , получить ссылку по тегу "MainCamera" и присоить её _mainCamera

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

Дальше:
        private void Start()
{
...

// reset our timeouts on start
_jumpTimeoutDelta = JumpTimeout;
_fallTimeoutDelta = FallTimeout;
}


// reset our timeouts on start - // сбросьте наши тайм-ауты при запуске. А что мешает сделать метод:
private void ResetTimeouts()
{
_jumpTimeoutDelta = JumpTimeout;
_fallTimeoutDelta = FallTimeout;
}


Во-первых не придётся писать, что сбрасываем при старте - логично же, что раз метод вызывается в старте, то там идёт сброс таймаутов. Во-вторых, можно переиспользовать этот метод в других частях кода.

Дальше просто поскидываю такие кусочки кода. Посмотрите, как это комично выглядит:
// set sphere position, with offset
Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset,
transform.position.z);

// clamp our rotations so our values are limited 360 degrees
_cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
_cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);

// Cinemachine will follow this target
CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, _cinemachineTargetYaw, 0.0f);

// set target speed based on move speed, sprint speed and if sprint is pressed
float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

// reset the fall timeout timer
_fallTimeoutDelta = FallTimeout;
3👍1🔥1
Forwarded from Soulshift | ALPHA
🔥 Привет, герои Emberfall!

Рады сообщить, что регистрация и авторизация в игре работают на 100%! Теперь вы можете:

- Создать аккаунт: нажмите SIGN UP, придумайте уникальный Nickname и пароль
- Войти в игру: нажмите LOGIN или отметьте «Remember me» для быстрого доступа
- Восстановить доступ: нажмите Forgot Password? и получите ссылку для сброса пароля

Готовьтесь к новому приключению: собирайте отряды героев, прокачивайте умения и побеждайте в эпических сражениях!

Подписывайтесь на канал, чтобы первыми узнать дату закрытого бета-тестирования и получить эксклюзивные бонусы.

Вперед, навстречу легендам Emberfall! ⚔️
🔥21👍1
Предположим мы меняем позицию и вращение объекта, например:
private void Update()
{
_transform.position = _position;
_transform.rotation = _quaternion;
}


IDE -шка выдаст вот такое сообщение: "Assigning position and rotation sequentially could be optimized." ("Можно оптимизировать последовательное назначение положения и поворота").

Послушаемся и применим рекомендации, получится следующее:
private void Update()
{
_transform.SetPositionAndRotation(_position, _quaternion);
}

Такое объединение оправданно, т.к. SetPositionAndRotation() обновляет позицию и вращение за один вызов. Движок вычисляет мировые преобразования только один раз. А вот раздельные присваивания (position и rotation) приводят к двум отдельным операциям, каждая из которых вызывает пересчёт матриц преобразования, коллизий и других зависимых компонентов.

В этом случае без предъяв. Но когда вы пишите собственные методы, избегайте "и" в их названии. Если в голову приходит назвать через "And", то скорее всего это 2 разных метода и логику надо на них разделить. Например, CalculateAndShoot() - тут одна часть вообще математическая, которую иногда даже нет необходимости хранить конкретно в этом классе, а вторая отвечает за стрельбу. Или, например, PlaySoundAndAnimate() - сомнительное название. А скорее всего они вообще окажутся в разных классах и встретятся лишь в каком-нибудь методе типа OnPressJump(), который подписан на событие _input.JumpPressed += OnPressJump.

Такая вот инфа.
Интересно ли вам углубиться в код-стайл?
🔥 - да
💩 - нет
🔥10
🥳

Ещё 4 модуля впереди
🔥7
🚀 Не просто кодстайл, а философия читаемого кода в Unity

Привет, коллеги! 👋 Сегодня хочу показать вам не просто список правил, а целостный подход к написанию кода на C# в Unity, который делает его предсказуемым, читаемым и быстрым. Тут не всё, смотрите прикреплённый в комментариях файл (вообще я его составлял для того, чтобы закидывать первым же сообщением в нейросеть и на дальнейшие вопросы получать ответы уже в понятной для меня стилистике).

Вот несколько ключевых принципов, которые я использую:

1. 🤫 Инкапсуляция
// Вместо:
public float Speed

// Пишем:
[SerializeField] private float _speed;

Почему так? Это защита от хаоса: Никто извне не сможет изменить скорость как захочет.
Явность намерений: Поле публично только для инспектора, а не для других скриптов.
Контроль: Вся логика изменения значения находится в одном месте.

Если надо будет получить значение, то будем передавать через свойство, а изменять через метод с валидацией, например:
public float Speed => _speed;

public void SetSpeed(float value)
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), "Скорость не может быть отрицательной");

_speed = value;
}


2. 📖 Код как техническая документация
Я избавляюсь от комментариев-костылей. Для этого вместо, например, void Proc() пишу полностью void ProcessPlayerInput().
Не "dir", а "direction", не "rb", а "rigidbody" и т.д. Вообще не стоит стремиться сокращать, буквы в коде абсолютно бесплатные )
Имя метода должно однозначно говорить о его цели, а код говорить сам за себя.

Но если ваш класс называется Health, то не надо называть поле _healthValue. Нет необходимости повторять имя класса в полях и методах. Это избыточно. Например, я обращаюсь к классу извне и получаю значение: "float переменная = _health.HealthValue;" - хуже, чем "float переменная = _health.Value;"

3. ⚡️Микро-оптимизации на уровне привычки
Обратите внимание на порядок операций:
// Медленнее (умножение вектора на скаляр)
transform.position += transform.forward * _speed * Time.deltaTime;

// Быстрее (умножение скаляров между собой)
transform.position += _speed * Time.deltaTime * transform.forward;

Современное железо разницы особо не почувствует, но Visual Studio скорее всего предложит вам поменять операнды местами. Поэтому я включаю это правило в запрос нейронкам.

4. 🧠 Психология именования
Методы — это глаголы: CalculatePath(), TryGetComponent(), ShowInfo().
bool-переменные — это вопрос: isGrounded, canAttack, hasTarget.
Коллекции — во множественном числе: List<Enemy> _enemies.

А ещё следует избегать неоднозначных названий, типа Check - т.к. неясно - возвращает этот метод значение или просто проверяет внутри себя и сохраняет значение в поле. Методы, возвращающие булево значение, можно называть так же с Is, Can, Has. Возвращающие методы с Get только предоставляют инфу, а с Give – отдают данные из метода, с удалением этих данных в классе.

5. 🧼 Чистый и безопасный C#
Явная типизация: List<Vector3> points вместо var points. Читаемость важнее, чем удобство записи. Я хочу видеть тип значения, понимать, с чем работаю без дополнительных манипуляций со всплывающими подсказками.
Главный принцип: Код должен читаться как интересная техническая книга, а не как древние руны, которые нужно расшифровывать.

А что вы думаете? Какое ваше самое главное правило в кодстайле, без которого вы не можете работать? Я вообще предлагаю прям поболтать на эту тему, т.к. она куда обширнее, чем можно было бы рассказать одной публикацией. Буду ждать вас в комментариях! 👇
👍62🔥1
В беседе одного из чатов подняли вопрос, а насколько это тяжёлая операция - создание объекта и какое преимущество у пула.

Вот, я написал скриптик, чтобы проверить на практике.

В первом случае инстантировал и дестроил 5000 объектов. Во втором инстантировал внутри пула при необходимости и возвращал в пул.

В случае с пулом получилось почти в 6 раз эффективнее. Создавал примитив - куб.

Но я думаю, что пулы - оптимизация. Пока проект небольшой и если нет фризов, то оптимизировать нет необходимости. Может игра вообще получится неиграбельной и проект отправится на свалку, тогда зачем заранее париться за оптимизацию.
👍2
Для тех, кто забыл, спешу напомнить )
🔥10
This media is not supported in your browser
VIEW IN TELEGRAM
Кто играл в MiSide, наверно заметил, что там кнопка выхода слегка убегает от курсора.

Мне было интересно реализовать что-то подобное. Здесь две кнопки: одна храбрая - тянется к курсору, а вторая трусливая - пытается отталкиваться.

Я скину пакет в комментариях к этой записи, но учитывайте 3 момента:

1. Я позволил себе наговнокодить, т.к. знал, что менторам на проверку скидывать не придётся.

2. Это выдернуто прям из проекта и поэтому добавил заглушки, где раньше была связь с другими классами

3. TextMeshPro - эта штука в консоль выбрасывает ошибки. Их можно проигнорить, запустить демо-сцену и в PlayMode всё начнёт работать, но в идеале удалить объекты с компонентами TextMeshPro в сцене и создать заново.
👍3🔥3