Unity 오브젝트 부서지는 효과
Nvidia Blast - News & General Discussion - Unity Discussions 해당 효과는 위의 글에서 bocs라는 분이 nvidia blast를 unity에서 쉽게 쓸 수 있도록만든 외부 라이브러리를 사용했습니다. 구현 일단 위의 패키지를
gamecoke.tistory.com
이번 글은 위 글에 있는 Blast 패키지와 FractureTool.cs 스크립트가 필요합니다.
또한, UI Toolkit이 필요한데 이건 아마 2021.3 이상 유니티 버전에 기본으로 깔려있을겁니다.
Play Mode에서 실시간으로 만드는 것보다
미리 만들어 놓고 설정하는 게 더 편할 것 같아서 Edit Mode도 만들었습니다.
구현
이 파일은 UI Toolkit으로 만든, 위 움짤에서 보이는 Fracture Generater 에디터의 uxml들입니다.
해당 파일을 Import 해서 아무대나 푸신 다음
FractureInEditMode.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
public class FractureInEditMode : EditorWindow
{
private const int WINDOW_PRIORITY = 0;
private UnsignedIntegerField breakForceField;
private FloatField densityField;
private SliderInt siteCountSlider;
private Button generateButton;
private List<FractureData> fractureDataList;
private VisualTreeAsset visualTree, dataVisualTree;
private ListView fracturableObjectListView;
[MenuItem("Tools/Fracture Editor", priority = WINDOW_PRIORITY)]
public static void ShowWindow()
{
GetWindow<FractureInEditMode>("Fracture Editor");
}
private void Awake()
{
fractureDataList = new List<FractureData>();
visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("!! FractureEditor.uxml의 위치 !!");
dataVisualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("!! FractureData.uxml의 위치 !!");
}
private void CreateGUI()
{
visualTree.CloneTree(rootVisualElement);
fracturableObjectListView = rootVisualElement.Q<ListView>("fracturableObjectListView");
AddListViewItem(fracturableObjectListView, dataVisualTree);
breakForceField = rootVisualElement.Q<UnsignedIntegerField>("breakForceField");
densityField = rootVisualElement.Q<FloatField>("densityField");
siteCountSlider = rootVisualElement.Q<SliderInt>("siteCountSlider");
generateButton = rootVisualElement.Q<Button>("generateButton");
generateButton.clicked += GenerateFracture;
}
private void AddListViewItem(ListView fracturableObjectListView, VisualTreeAsset dataVisualTree)
{
fracturableObjectListView.makeItem = dataVisualTree.CloneTree;
fracturableObjectListView.bindItem = BindItem;
fracturableObjectListView.itemsSource = fractureDataList;
fracturableObjectListView.RefreshItems();
}
private void BindItem(VisualElement element, int idx)
{
fractureDataList[idx] = fractureDataList[idx] ?? CreateInstance<FractureData>();
var data = fractureDataList[idx];
var fracturableObjectField = element.Q<ObjectField>("fracturableObject");
var insideMaterialField = element.Q<ObjectField>("insideMaterial");
var outsideMaterialField = element.Q<ObjectField>("outsideMaterial");
var haveIndividualSettings = element.Q<Toggle>("haveIndividualSettings");
var individualSettingsWnd = element.Q<VisualElement>("individualSettings");
fracturableObjectField.RegisterValueChangedCallback((obj) => data.fracturableObject = obj.newValue as GameObject);
insideMaterialField.RegisterValueChangedCallback((obj) => data.insideMaterial = obj.newValue as Material);
outsideMaterialField.RegisterValueChangedCallback((obj) => data.outsideMaterial = obj.newValue as Material);
haveIndividualSettings.RegisterValueChangedCallback((evt) =>
{
data.haveIndividualSettings = evt.newValue;
ActiveIndividualSettingsWindow(evt, individualSettingsWnd, idx);
});
fracturableObjectField.value = data.fracturableObject;
insideMaterialField.value = data.insideMaterial;
outsideMaterialField.value = data.outsideMaterial;
haveIndividualSettings.value = data.haveIndividualSettings;
}
private void ActiveIndividualSettingsWindow(ChangeEvent<bool> evt, VisualElement individualSettingsWnd, int idx)
{
if (evt.newValue == true)
{
var data = fractureDataList[idx];
var individualBreakForceField = individualSettingsWnd.Q<UnsignedIntegerField>("breakForceField");
var individualDensityField = individualSettingsWnd.Q<FloatField>("densityField");
var individualSiteCountSlider = individualSettingsWnd.Q<SliderInt>("siteCountSlider");
individualBreakForceField.RegisterValueChangedCallback((value) => data.breakForce = (int)value.newValue);
individualDensityField.RegisterValueChangedCallback((value) => data.density = value.newValue);
individualSiteCountSlider.RegisterValueChangedCallback((value) => data.siteCount = value.newValue);
individualBreakForceField.value = (uint)data.breakForce;
individualDensityField.value = data.density;
individualSiteCountSlider.value = data.siteCount;
}
individualSettingsWnd.style.display = evt.newValue ? DisplayStyle.Flex : DisplayStyle.None;
}
private void GenerateFracture()
{
for (int i = 0; i < fractureDataList.Count; i++)
{
var data = fractureDataList[i];
if (data.fracturableObject == null) continue;
// 공용 속성을 사용할 경우
if (!data.haveIndividualSettings)
{
data.breakForce = (int) breakForceField.value;
data.density = densityField.value;
data.siteCount = siteCountSlider.value;
}
data.fracturableObject.TryGetComponent(out MeshRenderer meshRenderer);
data.fracturableObject.TryGetComponent(out Collider collider);
meshRenderer.enabled = false;
collider.enabled = false;
var meshes = FractureTool.CreateFractureMeshes(data.fracturableObject, data, data.fracturableObject.GetComponent<MeshFilter>().sharedMesh);
FractureTool.CreateFractureGameObjects(data.fracturableObject, data, meshes);
}
}
private void OnDestroy()
{
fractureDataList = null;
}
}
위 스크립트를 만들면 되는데
여기서 주의할 점은 Awake 안에 "!! ~ !! 위치" 라고 적힌 곳에
FractureEditor.uxml, FractureData.uxml 경로를 설정해 줘야합니다.
예를 들어, Assets/Scripts/FractureEditor.xml 이 있으면 이 경로를 그대로 적어주면 됩니다.
사용법
사용법은 Play Mode에서 생성했던 방식과 다릅니다.
우선 Tools에 Fracture Editor를 클릭합니다.
그러면 Fractures가 접혀있을 수도 있기 때문에 펼칩니다.
그리고 오른쪽 중간에 + 버튼을 누르면 아래 Item이 추가됩니다.
여기서 조각날 GameObject에 조각내고 싶은 오브젝트를 넣으면 되는데
주의할 점은 오브젝트 MeshFilter에 Mesh가 존재해야 합니다.
또한, Play Mode에선 인스턴스화 된 Mesh를 사용했지만
Edit Mode에선 shared Mesh를 사용합니다.
안쪽 Material과 바깥쪽 Material은 넣어도 되고 안 넣어도 됩니다.
안 넣으면 안쪽 Material은 Standard Material이 들어가고
바깥쪽 Material은 조각날 GameObject의 Material이 들어갑니다.
개별 속성을 체크하게 되면 전체 속성에 영향을 안 받고 오브젝트 마다 설정을 다르게 할 수 있습니다.
여기서
Density는 밀도
Break Force는 Joint가 부서질 힘
Site Count는 조각 개수입니다.
대충 설정해서 Generate를 눌러주면 '조각날 GameObject' 자식에 Chunks가 추가됩니다.
여기서 알아둬야 할 점은
생성 후 '조각날 GameObject'의 Mesh Renderer와 Trigger는 비활성화 상태가 됩니다.
이렇게 생성하면 최종적으로
이런 식으로 분리가 되고
Play Mode와 마찬가지로 rigidbody와 trigger를 가지는 물체로 부딪히면
joint가 풀려서 부서지는 효과를 볼 수 있습니다.
'유니티 > Script' 카테고리의 다른 글
Unity Play Mode에서 오브젝트 부서지는 효과 만들기 (0) | 2025.02.01 |
---|---|
폴더를 클릭하면 Inspector 바뀌는 거 방지 (0) | 2025.01.01 |