Second commit

This commit is contained in:
ThomasCharester committed 2026-09-20 15:26:03 +03:00
commit 36840f3ad4
999 files changed
+61364

No files matched your search

@@ -0,0 +1,62 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Brush Entities")]
public class BrushEntitiesPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Brush Entities are brushes (models/shapes) in the map, which you want to apply a",
"MonoBehaviour script to. For example, a moving platform whose shape you design in",
"your TrenchBroom. Or, a trigger box which kills players on entry (see below).");
Image("T_Manual_BrushEntity");
Text("The following properties are available:");
PropertyDescription("name",
"The name of the entity in TrenchBroom.",
"If omitted, we convert your MonoBehaviour name according to your Naming Convention in settings."
);
PropertyDescription("category",
"The menu category to show the entity under, for example 'solid'.",
$"If omitted, we use the default prefix, '{FgdConsts.BRUSH_PREFIX}'."
);
PropertyDescription("type",
"Which brush type to use, which defines how the brush appears and how its collision",
"functions."
);
Foldout("Available Brush Types", () =>
{
PropertyDescription("Solid",
"A Solid brush with a texture.",
"Example: a moving platform.");
PropertyDescription("Liquid",
"A brush with a texture, but with a trigger collision instead of a solid one.",
"Example: a volume of water/lava, which damages players when entering.");
PropertyDescription("Trigger",
"A completely invisible brush which has a trigger collision.",
"Example: a volume which triggers an event when players walk into the area.");
PropertyDescription("Invisible",
"A completely invisible brush which has solid collision.",
"Example: an invisible wall to stop players going out of bounds.");
});
H1("Brush Entity Wizard");
Text(
"Tremble can guide you through creating the correct attribute code for your entity,",
"to help you get up and running quickly."
);
ActionBar_SingleAction("Build Brush Entity", () => GoToPage(typeof(BrushBuilderPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 468269cf35e449789a6062b8bcf8066f
timeCreated: 1723201757
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/BrushEntitiesPage.cs
uploadId: 939664
@@ -0,0 +1,94 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Diagnostics.Eventing.Reader;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public abstract class BuilderPageBase<TDataClass> : ManualPageBase
where TDataClass : ScriptableObject
{
private TDataClass m_Sample;
private SerializedObject m_SampleSO;
private TrembleSyncSettings m_SyncSettings;
protected virtual bool HasInitialQuestions { get; } = false;
private bool m_DoneInitialQuestions = false;
protected TDataClass Sample => m_Sample;
protected SerializedObject SampleSO => m_SampleSO;
protected TrembleSyncSettings SyncSettings => m_SyncSettings;
protected override void OnInit()
{
m_Sample = ScriptableObject.CreateInstance<TDataClass>();
m_SampleSO = new(m_Sample);
m_SyncSettings = TrembleSyncSettings.Get();
}
protected sealed override void OnGUI()
{
if (HasInitialQuestions && !m_DoneInitialQuestions)
{
OnInitialQuestionsGUI();
return;
}
OnPrePropertiesGUI();
m_SampleSO.Update();
{
OnPropertiesGUI();
}
m_SampleSO.ApplyModifiedPropertiesWithoutUndo();
OnPostPropertiesGUI();
}
protected virtual void OnInitialQuestionsGUI() { }
protected virtual void OnPrePropertiesGUI() { }
protected virtual void OnPropertiesGUI() { }
protected virtual void OnPostPropertiesGUI() { }
protected void MarkInitialQuestionsDone() => m_DoneInitialQuestions = true;
protected bool RenderProperty(string propName, string title, string description, Type objectReferenceType = null, Action extraContent = null)
{
SerializedProperty prop = m_SampleSO.FindProperty(propName);
uint hash = prop.GetHashOfContent();
GUILayout.BeginVertical(EditorStyles.helpBox);
{
if (prop.propertyType == SerializedPropertyType.ObjectReference)
{
GUILayout.BeginHorizontal();
{
EditorGUILayout.PrefixLabel(new GUIContent(title));
prop.objectReferenceValue = EditorGUILayout.ObjectField(prop.objectReferenceValue, objectReferenceType ?? typeof(GameObject), allowSceneObjects: false);
}
GUILayout.EndVertical();
m_SampleSO.ApplyModifiedProperties();
}
else
{
EditorGUILayout.PropertyField(prop, new GUIContent(title));
}
GUILayout.Label(description, ManualStyles.Styles.Text);
extraContent?.Invoke();
}
GUILayout.EndVertical();
GUILayout.Space(5f);
return hash != prop.GetHashOfContent();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 41ce3b3d48ed40cbbf56dd2e637d5e9e
timeCreated: 1723225850
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/BuilderPageBase.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f14b29b54b344b7b0aa40f206a78b41
timeCreated: 1723226299
@@ -0,0 +1,158 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Brush Entity Builder", showInTree: false)]
public class BrushBuilderPage : BuilderPageBase<BrushBuilderPage.SampleData>
{
public class SampleData : ScriptableObject
{
[SerializeField] internal string m_UnityName = "MyCoolBrush";
[SerializeField] internal string m_MapName = "";
[SerializeField] internal string m_Category = "";
[SerializeField] internal BrushType m_Type = BrushType.Solid;
}
protected override void OnPropertiesGUI()
{
RenderProperty(nameof(SampleData.m_UnityName),
title: "MonoBehaviour Name",
description: "The name of the main MonoBehaviour script for your brush entity.");
RenderProperty(nameof(SampleData.m_MapName),
title: "TrenchBroom name",
description: "(optional) The entity name to use in TrenchBroom. Leave this blank to use the default.");
RenderProperty(nameof(SampleData.m_Category),
title: "Category Name",
description: "(optional) The category/group name to use in TrenchBroom. Leave this blank to use the default.");
RenderProperty(nameof(SampleData.m_Type),
title: "Brush Type",
description: "The brush type to use. Defaults to 'Solid'.",
extraContent: () =>
{
Foldout("Available Brush Types", () =>
{
PropertyDescription("Solid",
"A Solid brush with a texture.",
"Example: a moving platform.");
PropertyDescription("Liquid",
"A brush with a texture, but with a trigger collision instead of a solid one.",
"Example: a volume of water/lava, which damages players when entering.");
PropertyDescription("Trigger",
"A completely invisible brush which has a trigger collision.",
"Example: a volume which triggers an event when players walk into the area.");
PropertyDescription("Invisible",
"A completely invisible brush which has solid collision.",
"Example: an invisible wall to stop players going out of bounds.");
});
});
}
protected override void OnPostPropertiesGUI()
{
List<string> attributeParts = new(16);
if (!Sample.m_MapName.IsNullOrEmpty())
{
attributeParts.Add($"\"{Sample.m_MapName}\"");
}
if (!Sample.m_Category.IsNullOrEmpty())
{
attributeParts.Add($"category: \"{Sample.m_Category}\"");
}
if (Sample.m_Type != BrushType.Solid)
{
attributeParts.Add($"type: {nameof(BrushType)}.{Sample.m_Type}");
}
string attrName = FormatAttributeName(typeof(BrushEntityAttribute));
string attributeLine = attributeParts.Count == 0
? $"[{attrName}]"
: $"[{attrName}({String.Join(", ", attributeParts)})]";
string unityName = Sample.m_UnityName.IsNullOrEmpty()
? "<error>"
: Sample.m_UnityName;
string catName = Sample.m_Category.IsNullOrEmpty() ? FgdConsts.BRUSH_PREFIX : Sample.m_Category;
string entName = Sample.m_MapName.IsNullOrEmpty() ? Sample.m_UnityName.ToNamingConvention(SyncSettings.TypeNamingConvention) : Sample.m_MapName;
HorizontalLine();
GUILayout.BeginHorizontal();
{
GUILayout.BeginVertical();
{
H3("Create New Entity");
string code =
"// Generated by Tremble v1.7.0\n" +
$"// Brush entity '{catName}_{entName}'\n" +
"\n" +
"using UnityEngine;\n" +
$"using {nameof(TinyGoose)}.{nameof(Tremble)};\n" +
"\n" +
attributeLine + "\n" +
$"public class {unityName} : MonoBehaviour\n" +
"{\n" +
" // Your cool class here ;)\n" +
"}";
CodeWithHeaderNoCopy($"Full source for {unityName}.cs", code);
ActionBar_SingleAction("Save to new MonoBehaviour class", () =>
{
string assetsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Assets");
string filename = EditorUtility.SaveFilePanel($"Choose where to save {unityName}", assetsFolder, $"{unityName}.cs", "cs");
if (!filename.IsNullOrEmpty())
{
File.WriteAllText(filename, code);
EditorCompileUtil.CompileThen(typeof(BrushBuilderPage), nameof(OnPostCompile));
}
});
}
GUILayout.EndVertical();
GUILayout.Label("-or-", EditorStyles.centeredGreyMiniLabel, GUILayout.ExpandHeight(true));
GUILayout.BeginVertical();
{
H3("Add to existing MonoBehaviour");
CodeWithHeaderNoCopy("Attribute line only (for existing MonoBehaviour)", attributeLine);
ActionBar_SingleAction("Copy to clipboard", () => EditorGUIUtility.systemCopyBuffer = attributeLine);
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
}
private static void OnPostCompile()
{
string message = TrenchBroomUtil.IsTrenchBroomRunning
? "Be sure to press F6 in TrenchBroom to reload entity definitions, so that your new entity appears!"
: "All done!";
EditorUtility.DisplayDialog("MonoBehaviour created!", message, "Great!");
ManualWindow mw = EditorWindow.GetWindow<ManualWindow>();
if (mw)
{
mw.GoToPage(typeof(BrushEntitiesPage));
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a19bc95fc7d74231b15345de8d617a86
timeCreated: 1723368005
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/Builders/BrushBuilderPage.cs
uploadId: 939664
@@ -0,0 +1,163 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Point Entity Builder", showInTree: false)]
public class PointBuilderPage : BuilderPageBase<PointBuilderPage.SampleData>
{
public class SampleData : ScriptableObject
{
[SerializeField] internal string m_UnityName = "MyCoolPoint";
[SerializeField] internal string m_MapName = "";
[SerializeField] internal string m_Category = "";
[SerializeField] internal Color m_Colour = Color.white;
[SerializeField] internal float m_Size = 0.25f;
[SerializeField] internal Texture2D m_Sprite;
}
protected override void OnPropertiesGUI()
{
RenderProperty(nameof(SampleData.m_UnityName),
title: "MonoBehaviour Name",
description: "The name of the main MonoBehaviour script of your point entity.");
RenderProperty(nameof(SampleData.m_MapName),
title: "TrenchBroom name",
description: "(optional) The entity name to use in TrenchBroom. Leave this blank to use the default.");
RenderProperty(nameof(SampleData.m_Category),
title: "Category Name",
description: "(optional) The category/group name to use in TrenchBroom. Leave this blank to use the default.");
RenderProperty(nameof(SampleData.m_Colour),
title: "Preview Colour",
description: "The colour of preview cube to represent this Point entity in TrenchBroom.");
RenderProperty(nameof(SampleData.m_Size),
title: "Preview Size",
description: "The size of preview cube to represent this Point entity in TrenchBroom.");
RenderProperty(nameof(SampleData.m_Sprite),
title: "Preview Sprite",
description: "(optional) A sprite to use to represent this Point entity in TrenchBroom.",
objectReferenceType: typeof(Texture2D));
}
protected override void OnPostPropertiesGUI()
{
List<string> attributeParts = new(16);
if (!Sample.m_MapName.IsNullOrEmpty())
{
attributeParts.Add($"\"{Sample.m_MapName}\"");
}
if (!Sample.m_Category.IsNullOrEmpty())
{
attributeParts.Add($"category: \"{Sample.m_Category}\"");
}
if (Sample.m_Sprite)
{
attributeParts.Add($"sprite: \"{Sample.m_Sprite.name}\"");
}
else
{
if (Sample.m_Colour != Color.white)
{
attributeParts.Add($"colour: \"{Sample.m_Colour.ToStringInvariant()}\"");
}
if (Sample.m_Size != 0.25f)
{
attributeParts.Add($"size: {Sample.m_Size}");
}
}
string attrName = FormatAttributeName(typeof(PointEntityAttribute));
string attributeLine = attributeParts.Count == 0
? $"[{attrName}]"
: $"[{attrName}({String.Join(", ", attributeParts)})]";
string unityName = Sample.m_UnityName.IsNullOrEmpty()
? "<error>"
: Sample.m_UnityName;
string catName = Sample.m_Category.IsNullOrEmpty() ? FgdConsts.POINT_PREFIX : Sample.m_Category;
string entName = Sample.m_MapName.IsNullOrEmpty() ? Sample.m_UnityName.ToNamingConvention(SyncSettings.TypeNamingConvention) : Sample.m_MapName;
HorizontalLine();
GUILayout.BeginHorizontal();
{
GUILayout.BeginVertical();
{
H3("Create New Entity");
string code =
"// Generated by Tremble v1.7.0\n" +
$"// Point entity '{catName}_{entName}'\n" +
"\n" +
"using UnityEngine;\n" +
$"using {nameof(TinyGoose)}.{nameof(Tremble)};\n" +
"\n" +
attributeLine + "\n" +
$"public class {unityName} : MonoBehaviour\n" +
"{\n" +
" // Your cool class here ;)\n" +
"}";
CodeWithHeaderNoCopy($"Full source for {unityName}.cs", code);
ActionBar_SingleAction("Save to new MonoBehaviour class", () =>
{
string assetsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Assets");
string filename = EditorUtility.SaveFilePanel($"Choose where to save {unityName}", assetsFolder, $"{unityName}.cs", "cs");
if (!filename.IsNullOrEmpty())
{
File.WriteAllText(filename, code);
EditorCompileUtil.CompileThen(typeof(PointBuilderPage), nameof(OnPostCompile));
}
});
}
GUILayout.EndVertical();
GUILayout.Label("-or-", EditorStyles.centeredGreyMiniLabel, GUILayout.ExpandHeight(true));
GUILayout.BeginVertical();
{
H3("Add to existing MonoBehaviour");
CodeWithHeaderNoCopy("Attribute line only (for existing MonoBehaviour)", attributeLine);
ActionBar_SingleAction("Copy to clipboard", () => EditorGUIUtility.systemCopyBuffer = attributeLine);
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
}
private static void OnPostCompile()
{
string message = TrenchBroomUtil.IsTrenchBroomRunning
? "Be sure to press F6 in TrenchBroom to reload entity definitions, so that your new entity appears!"
: "All done!";
EditorUtility.DisplayDialog("MonoBehaviour created!", message, "Great!");
ManualWindow mw = EditorWindow.GetWindow<ManualWindow>();
if (mw)
{
mw.GoToPage(typeof(PointEntitiesPage));
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d7c600b3e6a34d8a86cdaab529cef411
timeCreated: 1723368625
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/Builders/PointBuilderPage.cs
uploadId: 939664
@@ -0,0 +1,592 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Prefab Entity Builder", showInTree: false)]
public class PrefabBuilderPage : BuilderPageBase<PrefabBuilderPage.SampleData>
{
private static readonly Dictionary<Type, string> s_ScriptFileLookup = new();
protected override bool HasInitialQuestions => true;
private bool m_MonoBehaviourListDirty = false;
private readonly List<Type> m_CandidateTypes = new();
private readonly TrembleFieldConverterCollection m_FieldConverterCollection = new();
private Type m_UnityScriptType;
private string m_NewUnityScriptName;
private Action m_ResolutionAction;
private bool m_IsOnlyAddingSpawnablePrefab = false;
private bool m_IsAlreadyTrembleSpawnable = false;
private bool m_IsAlreadyPrefabEntity = false;
private bool m_IsMap = false;
private bool IsNewScript => !m_NewUnityScriptName.IsNullOrEmpty();
private string ScriptName => m_UnityScriptType?.Name ?? m_NewUnityScriptName;
public class SampleData : ScriptableObject
{
[SerializeField] internal GameObject m_BasePrefab;
[SerializeField] internal bool m_WantsData;
[SerializeField] internal string m_Category = "";
[SerializeField] internal GameObject[] m_ExcludedPrefabs = Array.Empty<GameObject>();
[SerializeField] internal GameObject[] m_IncludedPrefabs = Array.Empty<GameObject>();
[SerializeField] internal bool m_OnlyVariants = false;
}
protected override void OnInitialQuestionsGUI()
{
Text("Please select a prefab that you want to use in Tremble!");
m_MonoBehaviourListDirty = RenderProperty(nameof(SampleData.m_BasePrefab),
title: "Target Prefab",
description: "The prefab to use");
RenderProperty(nameof(SampleData.m_WantsData),
title: "I want to add data",
description: "Whether to allow setting fields on the prefab from TrenchBroom. e.g. "+
"variables such as Health, Colour, etc?");
if (Sample.m_BasePrefab)
{
if (m_MonoBehaviourListDirty)
{
Type[] monoBehaviours = Sample.m_BasePrefab
.GetComponents<MonoBehaviour>()
.Select(m => m.GetType())
.ToArray();
m_CandidateTypes.Clear();
m_CandidateTypes.AddRange(monoBehaviours
.Where(t => t != typeof(TrembleSpawnablePrefab)));
m_IsAlreadyTrembleSpawnable = monoBehaviours.Contains(typeof(TrembleSpawnablePrefab));
m_IsMap = monoBehaviours.Contains(typeof(MapDocument));
m_IsAlreadyPrefabEntity = !m_IsAlreadyTrembleSpawnable && monoBehaviours.Any(t => t.HasCustomAttribute<PrefabEntityAttribute>());
}
if (m_IsMap)
{
Text("Ha! No, that's a map prefab. We better not.");
}
else if (m_IsAlreadyPrefabEntity)
{
Text("Well, that's already spawnable in TrenchBroom. Try another prefab!");
}
else if (m_CandidateTypes.Count == 0)
{
if (m_IsAlreadyTrembleSpawnable)
{
Callout("Note, this prefab already has a TrembleSpawnablePrefab component",
"allowing it to be spawned from TrenchBroom - but without field support.",
"Converting this to a PrefabEntity will remove the TrembleSpawnablePrefab component!");
}
if (m_IsAlreadyTrembleSpawnable)
{
if (Sample.m_WantsData)
{
Resolution("Create a new MonoBehaviour to use - this allows",
"you to add fields which can be set in TrenchBroom",
"- in order to be able to set data (i.e. Health, Type, etc).");
ActionBar_SingleAction("Create New MonoBehaviour", () =>
{
m_NewUnityScriptName = GetScriptNameFromPrefab(Sample.m_BasePrefab);
if (Sample.m_BasePrefab.TryGetComponent(out TrembleSpawnablePrefab tsp))
{
Sample.m_Category = tsp.Category;
Sample.m_OnlyVariants = tsp.OnlyVariants;
}
SetResolution(Resolve_CreateNewClassAndAdd);
});
}
else
{
Text("Well, that's already spawnable in TrenchBroom. Tick the box above",
"to add data using a [PrefabEntity].");
}
}
else
{
if (Sample.m_WantsData)
{
Resolution("Create a new MonoBehaviour to use - this allows",
"you to add fields which can be set in TrenchBroom",
"- in order to be able to set data (i.e. Health, Type, etc).");
ActionBar_SingleAction("Create New MonoBehaviour", () =>
{
m_NewUnityScriptName = GetScriptNameFromPrefab(Sample.m_BasePrefab);
if (Sample.m_BasePrefab.TryGetComponent(out TrembleSpawnablePrefab tsp))
{
Sample.m_Category = tsp.Category;
Sample.m_OnlyVariants = tsp.OnlyVariants;
}
SetResolution(Resolve_CreateNewClassAndAdd);
});
}
else
{
Resolution("Add a 'TrembleSpawnablePrefab' component - this",
"only allows you to spawn this prefab without being able to",
"set any data on it.");
ActionBar_SingleAction("Add TrembleSpawnablePrefab component", () =>
{
m_IsOnlyAddingSpawnablePrefab = true;
SetResolution(Resolve_AddTrembleSpawnablePrefab);
});
}
}
}
else if (m_IsAlreadyTrembleSpawnable)
{
// Already spawnable, but no candidates for [PrefabEntity] - make a new code class
if (Sample.m_WantsData)
{
Resolution("Upgrade to a [PrefabEntity] - this allows",
"you to add fields which can be set in TrenchBroom",
"- in order to be able to set data (i.e. Health, Type, etc).");
foreach (Type type in m_CandidateTypes)
{
Bullet(type.Name);
ActionBar_SingleAction($"Add [PrefabEntity] to {type.Name}", () =>
{
m_UnityScriptType = type;
if (Sample.m_BasePrefab.TryGetComponent(out TrembleSpawnablePrefab tsp))
{
Sample.m_Category = tsp.Category;
Sample.m_OnlyVariants = tsp.OnlyVariants;
}
SetResolution(Resolve_AddPrefabEntityAttribute);
});
}
}
else
{
Text("This is already a TrembleSpawnablePrefab. Unless you want to add data,",
"you don't need to do anything!");
}
}
else
{
if (Sample.m_WantsData)
{
if (m_CandidateTypes.Count == 1)
{
Type type = m_CandidateTypes[0];
Resolution($"You can add a [PrefabEntity] attribute to {type.Name} on this",
"prefab. This will enable setting the following fields:");
Indent(() =>
{
foreach (string fieldName in GetFieldNames(type))
{
Bullet(fieldName);
}
});
ActionBar_SingleAction($"Add [PrefabEntity]", () =>
{
m_UnityScriptType = type;
SetResolution(Resolve_AddPrefabEntityAttribute);
});
}
else
{
Resolution("This prefab has MonoBehaviours which can be set to be a",
"[PrefabEntity]. Select which one to add this attribute to.");
foreach (Type type in m_CandidateTypes)
{
string[] fieldNames = GetFieldNames(type);
string fields = (fieldNames is { Length: > 0 }) ? String.Join(", ", fieldNames) : "no fields";
Bullet($"{type.Name} ({fields})");
ActionBar_SingleAction($"Add [PrefabEntity] to {type.Name}", () =>
{
m_UnityScriptType = type;
SetResolution(Resolve_AddPrefabEntityAttribute);
});
}
}
}
else
{
Resolution("Simply add a TrembleSpawnablePrefab component to it, which will",
"allow you to spawn the prefab in TrenchBroom, but not allow you to set any",
"data on it.");
ActionBar_SingleAction("Add TrembleSpawnablePrefab component", () =>
{
m_IsOnlyAddingSpawnablePrefab = true;
SetResolution(Resolve_AddTrembleSpawnablePrefab);
});
}
}
}
}
private string[] GetFieldNames(Type type)
{
List<Type> soTypes= new();
FgdClass dummyClass = new(FgdClassType.Point, type.Name, type.Name);
TrembleSync.AddExposedFieldsToEntity(type, dummyClass, m_FieldConverterCollection, soTypes);
// Gather spawnflags
NamingConvention spawnFlagNaming = TrembleSyncSettings.Get().SpawnFlagNamingConvention;
IEnumerable<string> spawnFlagNames = dummyClass.AllSpawnFlags
.Select(f =>
{
string name = f.FieldName.ToNamingConvention(spawnFlagNaming);
if (spawnFlagNaming == NamingConvention.HumanFriendly)
{
name += "?";
}
return $"{name} (Boolean)";
});
return dummyClass.AllFields
.Select(f => $"{f.FieldName} ({f.FriendlyTypeName})")
.Concat(spawnFlagNames)
.ToArray();
}
protected override void OnPropertiesGUI()
{
H2("Optional Extras");
RenderProperty(nameof(SampleData.m_Category),
title: "Category Name",
description: "(optional) The category/group name to use in TrenchBroom. Leave this blank to use the default.");
Foldout("(optional) Include/Exclude specific Prefabs?", () =>
{
if (!m_IsOnlyAddingSpawnablePrefab)
{
Text("By default, any other prefabs containing your MonoBehaviour will be exported.");
Text("Instead, you can choose to explicitly whitelist prefab(s) here.");
Text("Or, you can choose to blacklist certain prefab(s) here.");
RenderProperty(nameof(SampleData.m_IncludedPrefabs),
title: "Included Prefabs",
description: $"A whitelist of the ONLY prefab(s) to include, even though others may include your {ScriptName} MonoBehaviour?");
RenderProperty(nameof(SampleData.m_ExcludedPrefabs),
title: "Excluded Prefabs",
description: $"A blacklist of any prefab(s) to ignore, even though they include your {ScriptName} MonoBehaviour?");
}
RenderProperty(nameof(SampleData.m_OnlyVariants),
title: "Only Prefab Variants?",
description: "Whether to only export VARIANTS of the prefab(s) found?");
});
}
protected override void OnPostPropertiesGUI()
{
string catName = Sample.m_Category.IsNullOrEmpty() ? FgdConsts.PREFAB_PREFIX : Sample.m_Category;
//string entName = Sample.m_UnityName.ToNamingConvention(SyncSettings.TypeNamingConvention);
string fullEntName = $"{catName}_{Sample.m_BasePrefab.name}";
if (m_IsOnlyAddingSpawnablePrefab)
{
H2("Tremble Spawnable Prefab Details");
PropertyDescription("Prefab Name", labelWidth: 250f, Sample.m_BasePrefab.name);
PropertyDescription("Exported TrenchBroom Name", labelWidth: 250f, fullEntName);
}
else
{
H2("C# Code");
Text($"This is the code that will be used for {Sample.m_BasePrefab.name} if you click Apply below. You don't need to copy",
"it manually (unless you want to!)");
CodeWithHeader($"C# Script: {ScriptName}.cs (for TrenchBroom Prefab entity '{fullEntName}')", GenerateFullCodeSnippet());
}
Space();
GUI.color = Color.green;
LargeButton("Apply & Finish!", Resolve);
GUI.color = Color.white;
}
private void Resolution(params string[] resolution)
{
HorizontalLine();
H1("Action to take");
Text(resolution);
}
private string GetAttributeLine()
{
List<string> attributeParts = new(16);
if (!Sample.m_Category.IsNullOrEmpty())
{
attributeParts.Add($"category: \"{Sample.m_Category}\"");
}
if (TryFormatPrefabNames("excludePrefab", Sample.m_ExcludedPrefabs, out string excludePart))
{
attributeParts.Add(excludePart);
}
if (TryFormatPrefabNames("includePrefab", Sample.m_IncludedPrefabs, out string includePart))
{
attributeParts.Add(includePart);
}
if (Sample.m_OnlyVariants)
{
attributeParts.Add("onlyVariants: true");
}
string attrName = FormatAttributeName(typeof(PrefabEntityAttribute));
return attributeParts.Count == 0
? $"[{attrName}]"
: $"[{attrName}({String.Join(", ", attributeParts)})]";
}
private string GenerateFullCodeSnippet()
{
return
"using UnityEngine;\n" +
$"using {nameof(TinyGoose)}.{nameof(Tremble)};\n" +
"\n" +
GetAttributeLine() + "\n" +
$"public class {ScriptName} : MonoBehaviour\n" +
"{\n" +
(IsNewScript ? $" // Your new {ScriptName} class contents ;)" : $" // (your existing {ScriptName} code)") + "\n" +
"}";
}
private bool TryFormatPrefabNames(string propertyName, GameObject[] prefabs, out string formatted)
{
GameObject[] filteredPrefabs = prefabs.Where(p => p).ToArray();
switch (filteredPrefabs.Length)
{
case 1:
formatted = $"{propertyName}: \"{filteredPrefabs[0].name}\"";
return true;
case > 1:
{
string prefabList = String.Join("\", \"", filteredPrefabs.Select(p => p.name));
formatted = $"{propertyName}s: \"{prefabList}\"";
return true;
}
}
formatted = default;
return false;
}
private void SetResolution(Action resolutionAction)
{
m_ResolutionAction = resolutionAction;
MarkInitialQuestionsDone();
}
private void Resolve()
{
m_ResolutionAction.Invoke();
GoToPage(typeof(PrefabEntitiesPage));
}
private void Resolve_AddTrembleSpawnablePrefab()
{
Sample.m_BasePrefab.AddComponent<TrembleSpawnablePrefab>().Modify(so =>
{
so.FindBackedProperty(nameof(TrembleSpawnablePrefab.OnlyVariants)).boolValue = Sample.m_OnlyVariants;
so.FindBackedProperty(nameof(TrembleSpawnablePrefab.Category)).stringValue = Sample.m_Category;
});
EditorUtility.SetDirty(Sample.m_BasePrefab);
AssetDatabase.SaveAssets();
EditorApplication.delayCall += TrembleEditorAPI.SyncToTrenchBroom;
ShowCompleteDialog(
title: "TrembleSpawnablePrefab component added!",
message: $"{Sample.m_BasePrefab.name} now has a Tremble Spawnable Prefab component and should be available from TrenchBroom.");
}
private void Resolve_CreateNewClassAndAdd()
{
// Write code and compile
string assetsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Assets");
string scriptFilePath = EditorUtility.SaveFilePanel($"Choose where to save {ScriptName}", assetsFolder, $"{ScriptName}.cs", "cs");
if (scriptFilePath.IsNullOrEmpty())
return;
File.WriteAllText(scriptFilePath, GenerateFullCodeSnippet());
EditorCompileUtil.CompileThen(typeof(PrefabBuilderPage), nameof(OnPostCompile_AddNewScript),
ScriptName, AssetDatabase.GetAssetPath(Sample.m_BasePrefab));
}
private void Resolve_AddPrefabEntityAttribute()
{
string scriptFileName = GetScriptFilename(m_UnityScriptType);
if (scriptFileName.IsNullOrEmpty())
{
EditorUtility.DisplayDialog(
title: "Uh-oh!",
message: $"Tremble could not find the source file for {ScriptName}.",
"Oh no!");
return;
}
bool prefabEntityWasAdded = false;
bool usingWasAdded = false;
string[] lines = File.ReadAllLines(scriptFileName);
List<string> outLines = new(lines.Length+2);
foreach (string line in lines)
{
if (!usingWasAdded && !line.ContainsInvariant("using", caseSensitive: true))
{
outLines.Add("using TinyGoose.Tremble;");
usingWasAdded = true;
}
if (line.ContainsInvariant($"class {ScriptName}", caseSensitive: true))
{
prefabEntityWasAdded = true;
outLines.Add($"\t{GetAttributeLine()}");
}
outLines.Add(line);
}
if (!prefabEntityWasAdded)
{
EditorUtility.DisplayDialog(
title: "Uh-oh!",
message: $"Tremble could not find the class {ScriptName} in the source file {Path.GetFileName(scriptFileName)}.",
"Oh no!");
return;
}
File.WriteAllLines(scriptFileName, outLines);
EditorCompileUtil.CompileThen(typeof(PrefabBuilderPage), nameof(OnPostCompile_AddAttribute), ScriptName);
}
private static string GetScriptNameFromPrefab(GameObject prefab)
{
string newName = prefab.name;
if (newName.StartsWithInvariant("P_", caseSensitive: true))
return newName.Substring(2);
StringBuilder finalName = new(newName.Length);
foreach (char c in newName)
{
if (c is '-')
{
// Swap characters
finalName.Append('_');
}
else if (c is ' ')
{
// Omit characters
}
else
{
// Allowed characters
finalName.Append(c);
}
}
return finalName.ToString();
}
private static string GetScriptFilename(Type monoBehaviourType)
{
if (s_ScriptFileLookup.TryGetValue(monoBehaviourType, out string existingPath))
return existingPath;
foreach (string path in AssetDatabase.GetAllAssetPaths())
{
// Check if the file name matches exactly (without the full path)
if (Path.GetFileNameWithoutExtension(path).EqualsInvariant(monoBehaviourType.Name))
{
s_ScriptFileLookup[monoBehaviourType] = path;
return path;
}
}
return null;
}
private static void ShowCompleteDialog(string title, string message)
{
if (TrenchBroomUtil.IsTrenchBroomRunning)
{
message += "\n\n Note: Press F6 inside TrenchBroom to refresh Entity Definitions for it to appear!";
}
EditorUtility.DisplayDialog(title, message, "Great!");
}
private static void OnPostCompile_AddAttribute(string scriptName)
{
TrembleEditorAPI.SyncToTrenchBroom();
ShowCompleteDialog(
title: "[PrefabEntity] attribute added!",
message: $"{scriptName} now has a [PrefabEntity] attribute and should be available from TrenchBroom.");
}
private static void OnPostCompile_AddNewScript(string scriptName, string prefabPath)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Type newComponentType = Unsupported.GetTypeFromFullName(scriptName);
prefab.AddComponent(newComponentType);
foreach (TrembleSpawnablePrefab tsp in prefab.GetComponents<TrembleSpawnablePrefab>())
{
Component.DestroyImmediate(tsp, true);
}
EditorUtility.SetDirty(prefab);
AssetDatabase.SaveAssets();
// Now sync
EditorApplication.delayCall += TrembleEditorAPI.SyncToTrenchBroom;
// And tell the user!
ShowCompleteDialog(
title: "New Prefab Entity Created!",
message: $"{scriptName}.cs was created and attached to {prefab.name}, and should be available from TrenchBroom.");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 76e837b79b44474bb99e6e62f09d2af6
timeCreated: 1723206660
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/Builders/PrefabBuilderPage.cs
uploadId: 939664
@@ -0,0 +1,49 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Overview")]
public class EntitiesOverviewPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Entities are used to place objects (for example prefabs, trigger volumes, etc.)",
"into your map to make them more interactive."
);
Text("Tremble supports 3 kinds of Entities:");
Space();
PropertyDescription("Prefab Entities",
"These are 1:1 representations of your Unity prefabs, which can be dropped into a map.",
$"You can either use a '{nameof(TrembleSpawnablePrefab)}' component in your prefab to allow it to",
"be simply spawned from your map, or you can mark your scripts up with",
$"'[{FormatAttributeName(typeof(PrefabEntityAttribute))}]' to synchronise properties and more!");
ActionBar(() =>
{
Action("More about Spawnable Prefabs", () => GoToPage(typeof(SpawnablePrefabEntitiesPage)));
Action("More about Prefab Entities", () => GoToPage(typeof(PrefabEntitiesPage)));
});
Space();
PropertyDescription("Brush Entities",
"These are brushes (shapes/meshes) in the map, which you want to apply a MonoBehaviour script to.",
"These can be Solids - for example, a moving platform whose shape you design in TrenchBroom,",
"Triggers - for example an area which triggers an event, and more!");
ActionBar_SingleAction("More about Brush Entities", () => GoToPage(typeof(BrushEntitiesPage)));
Space();
PropertyDescription("Point Entities",
"These are points in space which contain data - for example a target location for spawning enemies,",
"or a goal location for a player to go to.");
ActionBar_SingleAction("More about Point Entities", () => GoToPage(typeof(PointEntitiesPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 62b4b35600af4819baf0389a9252be03
timeCreated: 1723278285
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/EntitiesOverviewPage.cs
uploadId: 939664
@@ -0,0 +1,51 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Point Entities")]
public class PointEntitiesPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Point Entities are points/positions in space in the map, which you want to apply a",
"MonoBehaviour script to. They do not have a visual representation (mesh).",
"For example, a target position that an enemy should move to. Or, a position in space",
"in which to spawn the player (see below).");
Image("T_Manual_PointEntity");
Text("The following properties are available:");
PropertyDescription("name",
"The name of the entity in TrenchBroom.",
"If omitted, we convert your MonoBehaviour name according to your Naming Convention in settings."
);
PropertyDescription("category",
"The menu category to show the entity under, for example 'solid'.",
$"If omitted, we use the default prefix, '{FgdConsts.BRUSH_PREFIX}'."
);
PropertyDescription("colour, size",
"Colour and Size define how to display this Point entity's preview cube in TrenchBroom."
);
PropertyDescription("sprite",
"(optional) A sprite to show, instead of a coloured cube for this Point entity in TrenchBroom.",
"This should be the name of a texture in your project, e.g. 'T_MyTexture'."
);
H1("Point Entity Wizard");
Text(
"Tremble can guide you through creating the correct attribute code for your entity,",
"to help you get up and running quickly."
);
ActionBar_SingleAction("Build Point Entity", () => GoToPage(typeof(PointBuilderPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d1a1a3ae162244f1819732c6cb04f4a8
timeCreated: 1723201769
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/PointEntitiesPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5dbfda69803f4f39ac23255094985859
timeCreated: 1723206454
@@ -0,0 +1,54 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Prefabs/Prefab Entities")]
public class PrefabEntitiesPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Prefab Entities are 1:1 representations of your Unity prefabs, which can be dropped into a map.",
$"All you have to do is mark up your scripts with '[{FormatAttributeName(typeof(PrefabEntityAttribute))}]' to synchronise properties and more!");
Image("T_Manual_PrefabEntity");
Text("The following properties are available:");
PropertyDescription("category",
"The menu category to show the entity under, for example 'player'.",
$"If omitted, we use the default prefix, '{FgdConsts.PREFAB_PREFIX}'."
);
PropertyDescription("excludePrefab(s)",
"Exclude prefabs with the given name. For example, if your",
"script appears on multiple prefabs 'P_Coin' and 'P_CoinSpecial', and you only",
" want to show 'P_Coin' in TrenchBroom, you can pass 'P_CoinSpecial' in here."
);
PropertyDescription("includePrefab(s)",
"Only include prefabs with the given name. For example, if your",
"script appears on multiple prefabs 'P_Coin' and 'P_CoinSpecial', and you only",
" want to show 'P_Coin' in TrenchBroom, you can pass 'P_Coin' in here."
);
PropertyDescription("onlyVariants",
"Only include variants of this prefab, not the base prefab itself.",
"For example, imagine your game has a 'P_Sign' prefab, which is an empty signpost,",
"but also has Prefab Variants 'P_Sign_Info' and 'P_Sign_QuestionMark' - ticking",
"this box will export those variants but not the base 'P_Sign'."
);
H1("Prefab Entity Wizard");
Text(
"Tremble can guide you through creating the correct attribute code for your entity,",
"to help you get up and running quickly."
);
ActionBar_SingleAction("Build Prefab Entity", () => GoToPage(typeof(PrefabBuilderPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fdcea6d257154068a23f70d1bdb72b0a
timeCreated: 1723201724
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/Prefabs/PrefabEntitiesPage.cs
uploadId: 939664
@@ -0,0 +1,40 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Entities/Prefabs/Spawnable Prefabs")]
public class SpawnablePrefabEntitiesPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Spawnable Prefabs are a quick way to add a single script to ANY prefab in editor, to",
"allow your prefab to be instantiated from TrenchBroom without any code changes.",
"Note that this method does not allow you to set variables on those prefabs!"
);
Image("T_Manual_SpawnablePrefab");
Text(
$"Add a '{nameof(TrembleSpawnablePrefab)}' component to your prefab. This gives you the following",
"options:"
);
PropertyDescription("Category",
"You can supply the 'Category' of prefab here, too - the menu category to",
"show the entity under, for example 'player'. If omitted, we use the prefix defined in",
"the settings (default: 'p')"
);
PropertyDescription("Only Variants",
"Only Variants: Tremble will ignore the base prefab and only export Prefab Variants of",
"the prefab. See the above explanation about 'onlyVariants' for more information."
);
ActionBar_SingleAction("Build Prefab Entity", () => GoToPage(typeof(PrefabBuilderPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7a0a012f06324a7e962feefd29e85628
timeCreated: 1723206473
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Entities/Prefabs/SpawnablePrefabsPage.cs
uploadId: 939664