Готова ли Игра?
253 subscribers
122 photos
20 videos
9 files
31 links
Научу делать игры ПРАВИЛЬНО!

Для связи со мной: @ArtemiZ_GD
Download Telegram
Vector3Extensions.cs
331 B
Полезная находка!

Не всегда, когда сравниваешь расстояния, нужно знать конкретное значение. Иногда достаточно информации, какой вектор длиннее.

Для этого очень хорошо подойдет расширения методов для Vector3.

Чтобы его использовать, просто добавляете файл в проект игры и пишете:
transform.position.IsEnoughClose()
или
transform.position.SqrDistance()

#Фишки
Помните, я писал про полезное расширение для Vector3?

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

    public static Vector3 WithX(this Vector3 vector, float x) 
{
return new Vector3(x, vector.y, vector.z);
}

public static Vector3 WithY(this Vector3 vector, float y)
{
return new Vector3(vector.x, y, vector.z);
}

public static Vector3 WithZ(this Vector3 vector, float z)
{
return new Vector3(vector.x, vector.y, z);
}


вот такие три метода помогут решить проблему

просто пишем:

    transform.position = transform.position.WithZ(0); // Зануление координаты Z


#Фишки
Автоматический синглтон

Я обычно не использую синглтоны в проектах, так как они от части рушат структуру, но если вы их часто пишете, можно добавить в проект вот такой вот класс:
using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;

public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
GameObject go = new GameObject(typeof(T).Name);
_instance = go.AddComponent<T>();
}
}
return _instance;
}
}

protected virtual void Awake()
{
if (_instance == null)
{
_instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}

Вы просто пишите:
public class SceneLoader : Singleton<SceneLoader> 
{
// Ваш код
}

Затем:
Singleton<SceneLoader>.Instance.LoadNextLevel();

и экономите кучу времени)

#Фишки
Было ли такое, что вы хотите во время симуляции в Unity узнать значение поля в классе?

Есть несколько вариантов это сделать:
1. С помощью Debug.Log();
2. С помощью перехода в Debug режим инспектора

Но эти варианты не всегда могут быть удобными

Поэтому я создал для вас кастомный Атрибут!

using UnityEditor;
using UnityEngine;

public class ReadOnlyAttribute : PropertyAttribute { }

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label);
GUI.enabled = true;
}
}
#endif


Добавляете файл в проект и пишете:

    [SerializeField, ReadOnly] private bool _isGrounded;


Если поле публичное (что я категорически не советую делать), SerializeField можно не писать

Update: Исправил баг с компеляцией

#Фишки
Если вы используете однотипные объекты, постоянно их создавая и уничтожая, лучше всего применять пул объектов для них.

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

Скидываю вам самое простое и быстрое решение:

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool<T> where T : Component
{
private readonly T _prefab;
private readonly Queue<T> _objects = new();

public ObjectPool(T prefab, int initialSize)
{
_prefab = prefab;

for (int i = 0; i < initialSize; i++)
{
CreateObject();
}
}

public T Get()
{
if (_objects.Count == 0)
{
CreateObject();
}

T obj = _objects.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}

public void Return(T obj)
{
obj.gameObject.SetActive(false);
_objects.Enqueue(obj);
}

private void CreateObject()
{
T newObject = Object.Instantiate(_prefab);
newObject.gameObject.SetActive(false);
_objects.Enqueue(newObject);
}
}


Пишите в комментах, какие фишки хотите видеть в следующих постах❤️

#Фишки
Please open Telegram to view this post
VIEW IN TELEGRAM
👾 Навигация 👾

🔤Теги

#Фишки - Посты с полезной информацией про Unity. Расширение, оптимизация или просто совет

#ЖизньArtemiZ - Посты с личной информацией. О том, как я стал программистом и как провожу свое время

#Спойлер - Посты - тизеры к моим текущим проектам. У меня есть как рабочие проекты, так и личные. Рано или поздно я их выпущу:)

#Мнение - рандомный пост с моим личным мнением на ту или иную тему. Скорее всего связанную с Unity

❗️Полезные статьи

Путеводитель для начинающего разработчика игр на Unity

Нейросети в разработке игр на Unity

Ссылки на полезную инфу

Порядок расположения элементов в классе

⭐️ Может тебя заинтересовать

Как я докатился до жизни такой - Пост о том, как я стал программистом
Please open Telegram to view this post
VIEW IN TELEGRAM
ShowIfAttribute.cs
5.1 KB
Атрибут условного отображения

У меня очень часто случается потребность скрыть или показать поле в зависимости от значений другого. Например, если _hasSecondaryFire == true я отображаю поле _secondaryFireCooldown и наоборот

Делаю подгон для вас!
Реализация данного атрибута с поддержкой пяти типов данных

(Boolean, Enum, Integer, Float, String)

и шести типов сравнений

(Equals, NotEquals, GreaterThan, LessThan, GreaterOrEquals, LessOrEquals)

А так же поддержка множественного условия! (я нигде не видел подобной реализации, так что считайте эксклюзив)

(And, Or)

Как использовать:

    public class Character : MonoBehaviour
{
[SerializeField] private int _level = 1;
[SerializeField] private float _health = 100f;
[SerializeField] private bool _isAlive = true;
[SerializeField] private CharacterClass _characterClass;

// Простое сравнение на равенство
[ShowIf(nameof(_isAlive), true)]
[SerializeField] private float _healthRegenRate;

// Использование оператора сравнения
[ShowIf(nameof(_health), CompareOperator.LessThan, 50f)]
[SerializeField] private bool _isLowHealth;

// Сложное условие с AND
[ShowIf(
nameof(_level), CompareOperator.GreaterOrEquals, 10,
nameof(_health), CompareOperator.GreaterThan, 30f
)]
[SerializeField] private bool _canUseSpecialAbility;

// Сложное условие с OR
[ShowIf(
nameof(_health), CompareOperator.LessThan, 20f,
Condition.Or,
nameof(_isAlive), false
)]
[SerializeField] private float _respawnTimer;

// Комбинация условий
[ShowIf(
nameof(_level), CompareOperator.GreaterOrEquals, 20,
nameof(_characterClass), CharacterClass.Mage,
Condition.Or,
nameof(_health), CompareOperator.LessThan, 30f
)]
[SerializeField] private bool _showEmergencyTeleport;
}


public enum CharacterClass
{
Warrior,
Mage,
Rogue
}


CompareOperator.Equals и Condition.And можно не указывать и пропускать, но советую так не делать, для большей читабельности

#Фишки
Please open Telegram to view this post
VIEW IN TELEGRAM
Таймер

Вчера была достаточно сложная фишка, поэтому понижаем градус)

Удобный и простой класс для создания таймера и позможности подписаться на него (а так же поставить на паузу, и получить оставшееся и максимальное время работы)

Кто-то скажет: есть же Invoke(). Вот только, в него можно передать метод только по имени (или через nameof) и нельзя остановить. В общем, он менее удобный

using UnityEngine;

public class Timer
{
public event System.Action TimerComplete;

public float Duration { get; private set; }
public float RemainingTime { get; private set; }
public bool IsRunning { get; private set; }

public Timer(float duration)
{
Duration = duration;
RemainingTime = duration;
}

public void Restart()
{
IsRunning = true;
RemainingTime = Duration;
}

public void Start()
{
IsRunning = true;
}

public void Stop()
{
IsRunning = false;
}

public void Update()
{
if (IsRunning == false) return;

RemainingTime -= Time.deltaTime;

if (RemainingTime <= 0)
{
IsRunning = false;
RemainingTime = Duration;
TimerComplete?.Invoke();
}
}
}


Пример использования:

var timer = new Timer(5f);
timer.OnTimerComplete += () => Debug.Log("Время вышло!");
timer.Start();


#Фишки
Please open Telegram to view this post
VIEW IN TELEGRAM
Атрибут MinMaxRange

Создает слайдер с двумя ползунками для определения минимального и максимального значения в инспекторе. Идеально подходит для настройки различных диапазонов (урон, время спавна, скорость и т.д.).

В коде ты сможешь взять рандомное значение в заданном диапазоне

using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
[MinMaxRange(1f, 10f)]
[SerializeField] private MinMaxRange _spawnInterval;

void Start()
{
// Получаем случайное значение из диапазона
float nextSpawnTime = _spawnInterval.RandomValue;
Debug.Log($"Следующий спавн через: {nextSpawnTime} секунд");
}
}


#Фишки
🚀 Как правильно использовать Update() и не убить производительность

Привет, геймдев! Сегодня разберём самую частую ошибку джунов - неправильное использование Update().

⚡️ Почему это важно?
Update() вызывается КАЖДЫЙ КАДР для КАЖДОГО активного скрипта. На 100 объектах при 60 FPS это уже 6000 вызовов в секунду!

🛠 Три золотых правила оптимизации Update():

1. Кэшируйте компоненты:// Плохо 
void Update() {
GetComponent<Rigidbody>().velocity = Vector3.up;
}

// Хорошо
Rigidbody rb;
void Start() {
rb = GetComponent<Rigidbody>();
}
void Update() {
rb.velocity = Vector3.up;
}


2. Избегайте Find и GetComponent в Update:// Очень плохо

void Update() {
GameObject.Find("Player").transform.position;
}


3. Используйте правильные события:
- FixedUpdate() - для физики
- LateUpdate() - для камеры
- Update() - для инпута и геймплея

💡 Лайфхак: Для редких проверок используйте корутины:// Вместо проверки каждый кадр
IEnumerator CheckEverySecond() {
while (true) {
CheckSomething();
yield return new WaitForSeconds(1f);
}
}

🎯 Результат:
- Стабильный FPS
- Меньше нагрузка на CPU
- Счастливые игроки

#Фишки
🎮 Как оптимизировать физику и перестать терять FPS

Сегодня разберём, почему ваша игра может тормозить из-за физики и как это исправить.

🔥 Главная ошибка: использование слишком сложных коллайдеров

Вот что нужно знать:
// Скорость расчётов (от быстрого к медленному):
1. Sphere Collider
2. Box Collider
3. Capsule Collider
4. Mesh Collider (convex)
5. Mesh Collider (non-convex) 💀


⚡️ Золотое правило: используйте составные примитивы вместо Mesh Collider

// Плохо 
- Один сложный Mesh Collider для всего персонажа

// Хорошо
- Capsule Collider для тела
- Box Collider для оружия
- Sphere Collider для головы


🛠 Три быстрых способа оптимизировать физику:

1. Используйте слои столкновений:
// В Physics Settings настройте матрицу
// чтобы враги не проверяли столкновения друг с другом

[SerializeField] private LayerMask _enemyLayer;

private void OnCollisionEnter(Collision other)
{
if (other.gameObject.layer == _enemyLayer)
{
// Do something
}
}


2. Отключайте Rigidbody когда можно:
_rb.isKinematic = true; // для неподвижных объектов


3. Используйте физические материалы с правильными настройками трения:
// Для скользких поверхностей
_physicMaterial.friction = 0;
_physicMaterial.bounciness = 0;


💡 Профи-совет:
Включите Wireframe mode в Scene View (Shading mode -> Wireframe), выделите все объекты и проверьте, нет ли лишних коллайдеров.

🎯 Результат: Стабильный FPS даже при большом количестве объектов

#Фишки
🎮Чистый код в Unity для начинающих

Привет! Сегодня поговорим об основах архитектуры кода в Unity. Это то, что отличает начинающего разработчика от профессионала.

📝 Основные принципы чистого кода:

1. Правильное именование
//  Плохо
public class plController : MonoBehaviour
{
private float spd = 5f;
public void mv() { }
}

// Хорошо
public class PlayerController : MonoBehaviour
{
private float _moveSpeed = 5f;

public void Move() { }
}


2. Один скрипт - одна задача
//  Плохо
public class Player : MonoBehaviour
{
public void Move() { }
public void Attack() { }
public void UpdateUI() { }
public void SaveGame() { }
}

// Хорошо
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float _moveSpeed = 5f;

private Rigidbody2D _rb;

private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
}

public void Move(Vector2 direction)
{
_rb.velocity = direction * _moveSpeed;
}
}


3. Кэширование компонентов
//  Плохо
private void Update()
{
GetComponent<Rigidbody2D>().velocity = Vector2.right;
}

// Хорошо
private Rigidbody2D _rb;

private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
}

private void FixedUpdate()
{
_rb.velocity = Vector2.right;
}


💡 Полезные советы:

- Используйте [SerializeField] вместо public для инспектора
- Группируйте похожие переменные
- Добавляйте XML-комментарии к публичным методам
/// <summary>
/// Наносит урон игроку.
/// </summary>
/// <param name="damage">Количество урона.</param>
/// <returns>Оставшееся здоровье.</returns>
public float TakeDamage(float damage)
{
_currentHealth -= damage;
return _currentHealth;
}


🎯 Простой пример правильной структуры:
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private float _maxHealth = 100f;

private float _currentHealth;

public event System.Action<float> OnHealthChanged;

private void Awake()
{
_currentHealth = _maxHealth;
}

public void TakeDamage(float damage)
{
_currentHealth = Mathf.Max(0f, _currentHealth - damage);
OnHealthChanged?.Invoke(_currentHealth);
}

public void Heal(float amount)
{
_currentHealth = Mathf.Min(_maxHealth, _currentHealth + amount);
OnHealthChanged?.Invoke(_currentHealth);
}
}


📚 Что изучать дальше:
- SOLID принципы
- Паттерны проектирования
- Dependency Injection
- Событийно-ориентированная архитектура

⚡️ Совет: Начните применять эти принципы в маленьких проектах. Со временем чистый код войдет в привычку.

#Фишки
Процесс разработки игры + Git

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

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

Все достаточно просто: прежде чем делать, запишите все идеи в блокнот, назначте одну и сделайте ее до конца. Затем коммит и возвращаемся к выбору идеи.
Максимально тривиально, но очень эффективно:)

Возможно кто-то уже говорил про такой подход) но я для себя его открыл сам

#Фишки