Разрушаемость в Unity ч.2

Ранее я писал пост про разрушаемость в Unity. В результате я получил отличные замечания и решил довести механику разрушений до ума. Ссылка на репозиторий проекта из видео в конце поста.

Демонстрационный проект

Основной комментарий, который убедил меня дорабатывать логику:

"Сразу бросилось в глаза, что верхняя часть, которая находится выше объекта разлетается на осколки, ниже объекта остаётся нетронутой, как будто материал не идентичный"

Найди отличия

Второй момент, который я хотел проработать это оптимизация.

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

Добавляем реализм

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

Для решения проблемы я написал функцию, которая объединяет все связанные фрагменты в одного родителя.

Каждый из маленьких кусочков при этом лишается собственной физике, но сам родитель получает Rigidbody с массой равной сумме фрагментов и BoxCollider, вмещающий в себя ровно все кусочки.

И уже с этим родителем мы поступаем как с обычным фрагментом, это мы уже умеем.

private void ActivateFragmentsGroup(HashSet<Fragment> fragmentsComponent) { if (fragmentsComponent == null || fragmentsComponent.Count == 0) return; GameObject groupRoot = new GameObject("FragmentGroup"); groupRoot.transform.parent = destroyedObject.transform; groupRoot.transform.position = GetCenterPosition(fragmentsComponent); float totalGroupMass = 0f; foreach (var fragGO in fragmentsComponent) { totalGroupMass += fragGO.GetMass(); fragGO.NullifyFragment(); fragGO.RemovePhysicsComponents(); RemoveFragmentFromGraph(fragGO); fragGO.transform.SetParent(groupRoot.transform, true); } var groupFragment = groupRoot.AddComponent<Fragment>(); groupFragment.Initiate(groupRoot.transform, this); groupFragment.SetMass(Mathf.Max(0.01f, totalGroupMass)); FitGroupBoxCollider(groupRoot, fragmentsComponent); groupFragment.ActivateFragment(); groupFragment.AddForceAtPosition(lastImpulseForce, lastImpulsePosition); }

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

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

Для этого используется функция SplitGraphComponent. В которой случайным образом выбираются вершины по количеству необходимых кусков и из каждой мы обходим граф в ширину получая из одной компоненты связности N различных подграфов.

List<HashSet<Fragment>> SplitGraphComponent(HashSet<Fragment> compToSplit, int splitCount) { var result = new List<HashSet<Fragment>>(); if (compToSplit == null || compToSplit.Count == 0 || splitCount <= 1) { result.Add(new HashSet<Fragment>(compToSplit)); return result; } splitCount = Mathf.Min(splitCount, compToSplit.Count); List<Fragment> sources = new List<Fragment>(compToSplit); ShuffleFragmentsList(sources); sources = sources.GetRange(0, splitCount); var owner = new Dictionary<Fragment, int>(); var queue = new Queue<Fragment>(); for (int i = 0; i < splitCount; i++) { result.Add(new HashSet<Fragment>()); owner[sources[i]] = i; result[i].Add(sources[i]); queue.Enqueue(sources[i]); } while (queue.Count > 0) { var cur = queue.Dequeue(); int curOwner = owner[cur]; foreach (var neighbor in _fragmentGraph[cur]) { if (!compToSplit.Contains(neighbor)) continue; if (owner.ContainsKey(neighbor)) continue; owner[neighbor] = curOwner; result[curOwner].Add(neighbor); queue.Enqueue(neighbor); } } return result; }

Оптимизируем

Тут всё намного прозаичнее. Выключаем то, что уже не нужно.

Для каждого фрагмента, который высвобождается из конструкции мы сразу отключаем триггер SphereCollider.

А через 2 секунды после высвобождения все куски, которые остановились мы лишаем физики, остаётся только меш в том месте, где остановил своё движение его Rigidbody.

Сам меш также удаляется со сцены по истечении определенного времени.

private void Update() { if (shouldDestroy && !enqueuedForDestruction & !shouldFreeze) { Destroy(gameObject); enqueuedForDestruction = true; } } private void FixedUpdate() { if (rigidBody == null) { shouldFreeze = false; return; } if (shouldFreeze && rigidBody.linearVelocity.magnitude < 0.01) { shouldFreeze = false; RemovePhysicsComponents(); } } public void RemovePhysicsComponents() { Destroy(rigidBody); Destroy(boxCollider); } public void NullifyFragment() { activated = true; Destroy(sphereCollider); }

Вот, как это происходит наглядно:

В slow motion всё становится круче

Спасибо за внимание! Как и обещал, ссылка репозиторий и телеграм.

29
3
2
1
1
9 комментариев