CodeOfGame
год и 5 месяцев назад
Есть один недостаток. Симуляция применяется ко всем физическим объектам на сцене. В таком случае, лучшее решение либо собрать се объекты в массив, которые содержат Rigidbody и включить кинематику, и после симуляции снова отключать
год и 5 месяцев назад
Разбрасываем объекты в Unity с физикой
Что это за инструмент?
Вручную раскидывать камни, ящики, мусор и другие объекты по сцене — скучно и долго. Мы сделали Editor Tool, который делает это автоматически: спавнит, разбрасывает и даёт физике всё красиво разложить.
Как работает?
Открываем наше окно управления Tools/Разброс объектов
Добавляем префабы в специальное окно Drag&Drop.
Задаем параметры: высоту спавна, дистанцию между объектами.
Зажимаем Ctrl + ЛКМ в сцене — объект появляется с разбросом.
Запускаем симуляцию, ждем, пока физика всё разложит.
Сохраняем результат или откатываем, если не понравилось.
Где применять?
Этот инструмент полезен для расстановки предметов в сцене:
Камни в окружении.
Ящики, бочки, мусорные кучи в сцене.
Обломки после разрушений.
Перейдём к базовому скрипту PhysicsPlacement.cs, его не требуется устанавливать на объекты, контролируемый скрипт будет делать сам для тех объектов, которые вы будете раскидывать
И сам супер контролируемй скрипт Editor/PhysicsPlacementWindow.cs - не забудьте, что скрипт должен быть в папке Editor
Заключение
Система помогает быстро и естественно раскидать объекты по сцене, используя физику. Экономит время, избавляет от рутины и делает расстановку деталей более реалистичной. Какие ещё фишки можно добавить?
Разбрасываем объекты в Unity с физикой
Что это за инструмент?
Вручную раскидывать камни, ящики, мусор и другие объекты по сцене — скучно и долго. Мы сделали Editor Tool, который делает это автоматически: спавнит, разбрасывает и даёт физике всё красиво разложить.
Как работает?
Открываем наше окно управления Tools/Разброс объектов
Добавляем префабы в специальное окно Drag&Drop.
Задаем параметры: высоту спавна, дистанцию между объектами.
Зажимаем Ctrl + ЛКМ в сцене — объект появляется с разбросом.
Запускаем симуляцию, ждем, пока физика всё разложит.
Сохраняем результат или откатываем, если не понравилось.
Где применять?
Этот инструмент полезен для расстановки предметов в сцене:
Камни в окружении.
Ящики, бочки, мусорные кучи в сцене.
Обломки после разрушений.
Перейдём к базовому скрипту PhysicsPlacement.cs, его не требуется устанавливать на объекты, контролируемый скрипт будет делать сам для тех объектов, которые вы будете раскидывать
using UnityEngine;
[ExecuteInEditMode]
public class PhysicsPlacement : MonoBehaviour
{
[Tooltip("Удалять Rigidbody после симуляции?")]
public bool RemoveRigidbodyAfterSimulation = true;
[Tooltip("Удалять этот компонент после симуляции?")]
public bool RemoveThisComponentAfterSimulation = true;
}
И сам супер контролируемй скрипт Editor/PhysicsPlacementWindow.cs - не забудьте, что скрипт должен быть в папке Editor
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class PhysicsPlacementWindow : EditorWindow
{
private bool isSimulating = false;
private List<Vector3> originalPositions = new List<Vector3>();
private List<Quaternion> originalRotations = new List<Quaternion>();
private List<Rigidbody> rigidbodies = new List<Rigidbody>();
private float simulationTime = 0f;
private const float TimeStep = 0.02f;
// Настройки спавна
private List<GameObject> prefabs = new List<GameObject>();
private float baseSpawnHeight = 10f;
private float heightVariation = 1.5f;
private float minDistance = 1f;
private float maxDistance = 2f;
private const string PREFS_KEY = "PhysicsPlacement_SavedPrefabs";
[MenuItem("Tools/Разброс объектов")]
public static void ShowWindow()
{
GetWindow<PhysicsPlacementWindow>("Physics Placement");
}
private void OnEnable()
{
LoadPrefabs();
SceneView.duringSceneGui += OnSceneGUI;
}
private void OnDisable()
{
SavePrefabs();
SceneView.duringSceneGui -= OnSceneGUI;
}
private void OnGUI()
{
GUILayout.Label("Физическая установка позиций", EditorStyles.boldLabel);
// Настройки спавна
baseSpawnHeight = EditorGUILayout.FloatField("Спавн по высоте", baseSpawnHeight);
heightVariation = EditorGUILayout.FloatField("Разброс по высоте", heightVariation);
minDistance = EditorGUILayout.FloatField("Мин. дистанция", minDistance);
maxDistance = EditorGUILayout.FloatField("Макс. дистанция", maxDistance);
// Drag & Drop для префабов
GUILayout.Label("Префабы:", EditorStyles.boldLabel);
Rect dropArea = GUILayoutUtility.GetRect(0, 50, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "Drag & Drop для префабов", EditorStyles.helpBox);
// Обработка Drag & Drop
if (dropArea.Contains(Event.current.mousePosition))
{
if (Event.current.type == EventType.DragUpdated)
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (Event.current.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (var obj in DragAndDrop.objectReferences)
if (obj is GameObject prefab && !prefabs.Contains(prefab))
prefabs.Add(prefab);
}
}
int columns = Mathf.FloorToInt(EditorGUIUtility.currentViewWidth / 70);
Rect gridRect = EditorGUILayout.GetControlRect(false, 70 * Mathf.CeilToInt(prefabs.Count / (float)columns));
for (int i = 0; i < prefabs.Count; i++)
{
int row = i / columns;
int col = i % columns;
Rect tileRect = new Rect(
gridRect.x + col * 70,
gridRect.y + row * 70,
64,
64
);
// Отрисовка превью префаба
Texture preview = AssetPreview.GetAssetPreview(prefabs[i]) ?? AssetPreview.GetMiniThumbnail(prefabs[i]);
if (preview != null)
{
GUI.DrawTexture(tileRect, preview, ScaleMode.ScaleToFit);
}
// Крестик для удаления
Rect closeButtonRect = new Rect(
tileRect.x + tileRect.width - 20,
tileRect.y,
20,
16
);
if (GUI.Button(closeButtonRect, "×", EditorStyles.miniButton))
{
prefabs.RemoveAt(i);
GUI.changed = true;
Repaint();
return; // Выходим, чтобы избежать ошибок после удаления
}
}
// Кнопки симуляции (работают для всех объектов с PhysicsPlacement)
if (!isSimulating)
{
if (GUILayout.Button("Запуск симуляции"))
StartSimulation();
}
else
{
GUILayout.Label($"Время симуляции: {simulationTime:F2}s");
if (GUILayout.Button("Сохранить позиции"))
StopSimulation(true);
if (GUILayout.Button("Отменить"))
StopSimulation(false);
}
}
private void OnSceneGUI(SceneView sceneView)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && Event.current.control)
{
if (prefabs.Count == 0) return;
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
GameObject prefab = prefabs[Random.Range(0, prefabs.Count)];
// Рассчитываем случайную высоту в пределах вариации
float randomHeightVariation = Random.Range(-heightVariation, heightVariation);
float finalSpawnHeight = baseSpawnHeight + randomHeightVariation;
Vector3 spawnPos = hit.point + Random.insideUnitSphere * Random.Range(minDistance, maxDistance);
spawnPos.y = hit.point.y + finalSpawnHeight; // Используем рассчитанную высоту
GameObject newObj = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
newObj.transform.position = spawnPos;
newObj.AddComponent<PhysicsPlacement>();
Undo.RegisterCreatedObjectUndo(newObj, "Spawn Prefab");
}
Event.current.Use();
}
}
private void StartSimulation()
{
isSimulating = true;
simulationTime = 0f;
originalPositions.Clear();
originalRotations.Clear();
rigidbodies.Clear();
// Находим ВСЕ объекты с PhysicsPlacement в сцене
var allObjects = FindObjectsOfType<PhysicsPlacement>();
foreach (var placement in allObjects)
{
GameObject obj = placement.gameObject;
originalPositions.Add(obj.transform.position);
originalRotations.Add(obj.transform.rotation);
Rigidbody rb = obj.GetComponent<Rigidbody>();
if (rb == null) rb = obj.AddComponent<Rigidbody>();
if (obj.GetComponent<Collider>() == null)
{
if (obj.GetComponent<MeshFilter>() != null)
obj.AddComponent<MeshCollider>();
else
obj.AddComponent<BoxCollider>();
}
rigidbodies.Add(rb);
}
Physics.simulationMode = SimulationMode.Script;
EditorApplication.update += SimulatePhysicsStep;
}
private void StopSimulation(bool applyPositions)
{
isSimulating = false;
EditorApplication.update -= SimulatePhysicsStep;
Physics.simulationMode = SimulationMode.FixedUpdate;
for (int i = 0; i < rigidbodies.Count; i++)
{
if (rigidbodies[i] == null) continue;
var placement = rigidbodies[i].GetComponent<PhysicsPlacement>();
if (placement == null) continue;
if (applyPositions)
{
rigidbodies[i].isKinematic = true;
if (placement.RemoveRigidbodyAfterSimulation)
DestroyImmediate(rigidbodies[i]);
if (placement.RemoveThisComponentAfterSimulation)
DestroyImmediate(placement);
}
else
{
rigidbodies[i].transform.position = originalPositions[i];
rigidbodies[i].transform.rotation = originalRotations[i];
DestroyImmediate(rigidbodies[i]);
}
}
}
private void SimulatePhysicsStep()
{
if (!isSimulating) return;
simulationTime += TimeStep;
Physics.Simulate(TimeStep);
SceneView.RepaintAll();
}
private void SavePrefabs()
{
// Сохраняем только GUID префабов
var guids = prefabs
.Where(p => p != null)
.Select(p => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(p)))
.ToArray();
EditorPrefs.SetString(PREFS_KEY, string.Join(",", guids));
}
private void LoadPrefabs()
{
prefabs.Clear();
string savedGuids = EditorPrefs.GetString(PREFS_KEY, "");
if (!string.IsNullOrEmpty(savedGuids))
{
foreach (string guid in savedGuids.Split(','))
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path))
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
prefabs.Add(prefab);
}
}
}
}
}
Заключение
Система помогает быстро и естественно раскидать объекты по сцене, используя физику. Экономит время, избавляет от рутины и делает расстановку деталей более реалистичной. Какие ещё фишки можно добавить?
