TyapLyapDev - Разработка игр | Движуха Сакутина
225 subscribers
127 photos
77 videos
13 files
26 links
Download Telegram
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
Media is too big
VIEW IN TELEGRAM
Все сцены в отдельном окне

Идея не нова: многие разработчики так делают, чтобы не блуждать по иерархии проекта в поисках сцены, на которую надо переключиться. Я тоже не люблю покидать папку скриптов или префабов без особой необходимости.

Поэтому мной подготовлен собственный ассет для этих целей. Он состоит из одного скрипта на 180 строк.

В окне есть 2 списка: "Избранное" - где все основные сцены, с которыми вы работаете и "Остальные сцены" - это обычно демки с различных ассетов.

При нажатии на сцену происходит переход. При наведении на папку - вы видите путь к сцене в проекте. При нажатии на папку вы подсвечиваете сцену в проекте.

Ассет прикреплю в первом комментарии к записи.

На этом пока всё, до новых встреч 🙂

Проголосуйте:
👍 - если полезно
👎 - если бесполезно
👍11
Переписка с другом, который мне как брат. Я действительно хорошо себя чувствую в кинологии и могу зашибать от 140к в месяц на дрессуре. В этом направлении так же нет потолка и я в этой профессии уже 18 лет.

Но! Я не из тех, кто ищет благоприятную нишу. Готов лбом пробивать стены, чтобы заниматься тем, что интересно в данный момент. Я искренне считаю, что нет смысла искать прибыльную профессию, которая не будет приносить вам кайф.

Лет 10 назад все думали, что технологии вытеснят все профессии, не требующие творческой и умственной деятельности. А теперь что? Нейронки пишут музыку, генерируют изображения, 3д модели, рассуждают и познают наш мир. И лишь дворник стабильно делает своё дело без страха (пока), что подметать перед домом будет робот.

Всё так стремительно меняется, что не очень понятно, как будут обстоять дела через 5 лет. Так что моё решение даже слегка обоснованно.

Хотите, я расскажу, как ИИ может заменить кинологов и дрессировщиков собак уже сегодня?
👍 - да
👎 - нет
👍10
Media is too big
VIEW IN TELEGRAM
Как заменить кинолога нейросетями

Есть такие собачьи видеоняни, они:
- передают видео и звук на смартфон;
- имеют колонки, чтобы говорить что-то собаке через смартфон;
- по нажатию на кнопку телефона выкидывают корм для поощрения собаки.

Всё. Этого достаточно, чтобы подрубить ИИ, который будет подавать сигнал о правильном действии собаки и подкреплять её решение лакомством.

Вот так уходишь на работу, а нейросетка развлекает собаку: хвалит за нахождение в лежаке, учит командам и трюкам. Это возможно уже сейчас, просто никто не реализовал (пока что).

На видео я при помощи щелчков кликера и лакомства приучаю собаку открывать клетку, заходить и закрываться внутри. Ничего принципиально не изменится, если вместо меня это будет делать нейросеть. И это хорошо! Это сделает жизнь собачников комфортнее, а сами собаки не будут скучать ежедневно по 8 часов одни дома.
2🤔1
Блин, тогда я сеньор 😁
👏2👍1
Джуниоров, мидлов и сеньёров не существует

У меня уже глаз дёргается.

Каждый раз на конференции если есть доклад как стать: “Художником\Программистом\Баристом”

Обязательно пол презентации закручивают в уши одно и тоже: “Ну есть джуны, они попроще, есть сеньёры, они покруче. Ваша зарплата зависит от вашего опыта и кто вы”

Так и хочется крикнуть (что я и делаю): “А если я феечка винкс блин, блум например, мне сколько денег заплатят?”

Это тааакааая абстрактная условность, что относится к этому серьёзно, как относится серьёзно к званиям в детской игре.

Вот собралась толпа шестилеток и играют в королевство. Кто-то король, кто-то маг придворный, кто-то холоп. Разыгрывают сценки.

Групповая галлюцинация да и только.

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

Скорее всего пропаду на какое-то время (или буду периодически пропадать). А затем вернусь с новостями и, надеюсь, с первым готовым проектом 🙂
🎉4🔥3
Media is too big
VIEW IN TELEGRAM
Реф, по которому буду пилить первый проект пыточной. Меня пощадили с задачей (у остальных всё ИМХО сложнее). Дизайн можно менять, но не сильно отходить от исходных механик.

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

Очевидно проект 2D. По сути, это гибрид двух игр: zuma-подобной и головоломки с авто на парковке, которые мешают друг другу выехать.

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

Такую игруху можно зафигачить быстро, но если красиво оформлять, подключать плагин, следить за чистотой кода, то для моего уровня разработки где-то месяца 1,5-2. И увеличим это время на всякий случай раз так в 5 )
🔥6😁1🤯1
Media is too big
VIEW IN TELEGRAM
Решил начать с самого вкусного в задании

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

Во-вторых, я подумываю вместо смайлов сделать фрукты. И название Angry Fruits очень подходит, и разнообразие будет вплоть до экзотических фруктов.

В-третьих, сделал пул, но пока он не выполняет свою функцию, т.к. в последовательности пока ещё нет повторений и сейчас нечем атаковать, чтобы вернуть в пул.

В-четвёртых, уже надо немного рефакторить. Я зачем-то сделал расширенную настройку по количеству линий врагов, боевому порядку, отступу друг от друга. А с другой стороны, пусть пока будет.
4👍4🔥3
Всем привет! Сейчас в пути, а значит по традиции без фото и видео.

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

Мне надо сделать врагов в 3d, перекрашивать материал, а поверх накладывать текстуру злых лиц. Так я впервые сделал свой кастомный шейдер.

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

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

Наверно это сложно читать, но на самом деле пока всё просто, было бы время это делать )
🔥1