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,3 @@
fileFormatVersion: 2
guid: 4a10d864747e4870a69b944b49fb0f57
timeCreated: 1723448980
@@ -0,0 +1,90 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Advanced/Base Classes")]
public class BaseClassesPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Sometimes, you want to use a field (or a set of fields) across multiple",
"prefabs, point entities, and brush entities. For example, imagine you have a 'team' system,",
"whereby different entities might be on a player 'team' or the enemy 'team'. Entities that",
"you may wish to give a 'team' in this case could be the player prefab, some enemy prefabs,",
"and some doors which can only be interacted with by things on the same 'team'."
);
Text(
$"In this case, you can create a C# interface derived from {nameof(ITrembleBaseClass)}, which",
"those entities or prefabs implement, in order to attach fields to all of",
"them at once."
);
Text("Here's how this might look for our 'teams' example:");
Code(
$"using {nameof(TinyGoose)}.{nameof(Tremble)};",
"",
"// Simple enum to denote which team an entity is on (or for).",
"public enum TeamType",
"{",
" Neutral,",
" Players,",
" Enemies,",
"}",
"",
"// Inherit your interface from ITrembleBaseClass in order to expose it to",
"// Tremble.",
$"public interface ITeam : {nameof(ITrembleBaseClass)}",
"{",
" public TeamType Team => TeamType.Neutral; //you must provide a default!",
"}"
);
Text("Now, attach the interface to your entities, like this:");
Code(
$"using {nameof(TinyGoose)}.{nameof(Tremble)};",
"",
$"[{FormatAttributeName(typeof(PrefabEntityAttribute))}]",
"public class Door : MonoBehaviour, ITeam",
"{",
"",
"}"
);
Text(
"In order to read the data set from this base class, you will need to implement",
"OnImportFromMapEntity. You can then store this data in a variable, or do whatever",
"else you need with it."
);
Code(
$"using {nameof(TinyGoose)}.{nameof(Tremble)};",
"",
$"[{FormatAttributeName(typeof(PrefabEntityAttribute))}]",
$"public class Door : MonoBehaviour, ITeam, {nameof(IOnImportFromMapEntity)}",
"{",
" // [SerializeField] so that Unity stores the result",
" [SerializeField] private TeamType m_Team; ",
" ",
" // We must implement this method and pull the data out manually",
$" public void OnImportFromMapEntity({nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity)",
" {",
" // Interestingly, because this is named `m_Team`, and our",
" // interface has `Team`, this would have been set",
" // automagically anyway. But it's better to be explicit!",
" m_Team = entity.GetBaseClassData<TeamType>(this, nameof(ITeam.Team));",
" Debug.Log($\"This door is usable by the {m_Team} team!\");",
" }",
"}"
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c25e8e2e7f7a4efdbc9985c7232a2ed0
timeCreated: 1723449065
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/BaseClassesPage.cs
uploadId: 939664
@@ -0,0 +1,120 @@
//
// 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("Advanced/Custom Types")]
public class CustomTypesPage : ManualPageBase
{
protected override void OnGUI()
{
Text("Tremble also supports syncing custom data types from your maps.");
Text("For example, imagine you have an enemy that has a random amount of",
"health, chosen between two values that you want to set in a map. You could of course add",
"two fields, like this:"
);
CodeWithHeader("Individual fields sample",
"[SerializeField] private int m_MinHealth = 10;",
"[SerializeField] private int m_MaxHealth = 50;"
);
Text("However, you might prefer a single field of a custom type, something like this:");
CodeWithHeader("Custom type sample",
"[SerializeField] private IntInRange m_HealthRange = new(10, 50);");
Text("To do this, first you need a custom type of course (well, you might already have one!)",
"- in our example something like this:");
CodeWithHeader("IntInRange.cs",
"[Serializable]",
"public struct IntInRange",
"{",
" [SerializeField] public int Min;",
" [SerializeField] public int Max;",
"",
" public override string ToString() => $\"{Min}-{Max}\";",
"}");
Text("Now we have our class to store our data, we just need to tell Tremble how to read/write",
"this data from our map.");
Text("To do this, we create a new class extending TrembleFieldConverter,",
"and add the [TrembleFieldConverter] attribute to it.");
CodeWithHeader("IntInRangeFieldConverter.cs",
"[TrembleFieldConverter(typeof(IntInRange))]",
"public class IntInRangeFieldConverter : TrembleFieldConverter<IntInRange>",
"{",
" ...",
"}");
Text("Next, you need to implement the AddFieldToFgd method which tells TrenchBroom how to",
"display your data. This can be as a string, an integer, a list of values, and more.");
CodeWithHeader("AddFieldToFgd Method",
$"protected override void AddFieldToFgd({nameof(FgdClass)} entityClass, string fieldName, IntInRange defaultValue, MemberInfo target)",
"{",
" // Add a hint to the end of the description to show the format",
" string extraDescription = \" (format: \\\"2-4\\\")\";",
"",
" // Get the [Tooltip] attribute for this field, if it exists",
" target.GetCustomAttributes(out TooltipAttribute existingTooltip);",
" ",
" // Add a \"string\" field to the FGD class (it's not a float or boolean, for example)",
$" entityClass.AddField(new {nameof(FgdStringField)}",
" {",
" Name = fieldName,",
" DefaultValue = defaultValue.ToString(),",
" Description = (existingTooltip?.tooltip ?? $\"IntInRange {target.Name}\") + extraDescription",
" });",
"}");
Text("Finally, you need to implement the TryGetValueFromMap method which reads your custom data",
"from an entity in map. This is used when a map is parsed during import. You can read any data",
"from the 'entity' passed in, in any format. This example reads a string value, but you can",
"also read integers, floats, and others.");
CodeWithHeader("TryGetValueFromMap Method",
"protected override bool TryGetValueFromMap(BspEntity entity, string key, MapProcessorBase mapProcessor, MemberInfo target, out IntInRange value)",
"{",
" // Try to read string value for our field",
" if (!entity.TryGetString(key, out string rangeIntValue))",
" {",
" value = default;",
" return false; // we could not convert the data",
" }",
"",
" // Now parse our range from the string value in the map.",
" // !! NOTE !!: This sample code performs no bounds checking and is",
" thus very unsafe. See IntInRangeFieldConverter.cs",
" in the Tremble Sample for a much better implementation!",
"",
" value.Min = int.Parse(rangeIntValue.Split(\"-\")[0]);",
" value.Max = int.Parse(rangeIntValue.Split(\"-\")[1]);",
" return true; // we succesfully converted the data",
"",
"}");
Text("And that's it! Now Tremble will be able to tell TrenchBroom how to display your custom",
"type, and it will be able to import it from maps without issue.");
Text("Feel free to check out the real 'IntInRange.cs' and 'IntInRangeFieldConverter.cs' classes",
"in the Tremble Sample folder, or look in Tremble's FieldConverters folder to see all the",
"built-in field converters for things like Vector3s, strings, Materials, and more!");
}
private void Test(out MinMax mm)
{
mm.Min = 2;
mm.Max = 2;
}
}
struct MinMax
{
public int Min;
public int Max;
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 88bb368f84f84983aa5e46dd6b28b70e
timeCreated: 1725216144
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/CustomTypesPage.cs
uploadId: 939664
@@ -0,0 +1,97 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Advanced/Map Processors")]
public class MapProcessorPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
$"While {nameof(IOnImportFromMapEntity)} can cover a lot of bases for custom import logic,",
"sometimes you need to either:"
);
Bullet(
"look at the map as a whole - for example, to capture all",
"entities of a certain class into a list, and pass this to some system"
);
Bullet(
"or, you might want to use Editor-only functionality, which can be tricky",
"to write inside an OnImportFromMapEntity callback."
);
Text(
$"For this, you can create your own {nameof(MapProcessorBase)} subclass, and override the",
"functions you need."
);
Text(
"An example of this is provided in SampleMapProcessor.cs. The example finds all",
"the Sheep Spawners in the map, and logs their position. However in a real game",
"you might, for example, take the positions of these and write them into a",
"ScriptableObject which could be later used by a minimap system."
);
Text(
"Map Processors can either be set per-Map or in the Tremble Pipeline settings to affect every",
"map in your project.");
Number("To set a per-Map processor, click your map file in the",
"Project tab, and ensure your map processor class(es) are set, like this:"
);
Image("T_Manual_MapProcessor");
Number("To set Map processors globally for your project, open the Pipeline settings.");
ActionBar_SingleAction("Open Pipeline Settings", () =>
{
const string TREMBLE_PIPELINE_PATH = "Project/Tremble/4 Pipeline";
SettingsService.OpenProjectSettings(TREMBLE_PIPELINE_PATH);
});
H1("Sample Map Processor");
Foldout("Show Code", () =>
{
Code(
$"using {nameof(TinyGoose)}.{nameof(Tremble)};",
"",
$"public class MyCoolMapProcessor : {nameof(MapProcessorBase)}",
"{",
$" public override void OnProcessingStarted({nameof(TrembleMapImportSettings)} context, GameObject root, {nameof(MapBsp)} mapBsp)",
" {",
" Debug.Log($\"Map processing started for {mapBsp.MapName}\");",
" }",
$" public override void OnProcessingCompleted({nameof(TrembleMapImportSettings)} context, GameObject root, {nameof(MapBsp)} mapBsp)",
" {",
" Debug.Log($\"Map processing completed for {mapBsp.MapName} - all entities spawned\");",
" }",
" ",
$" public override void ProcessWorldSpawnProperties({nameof(TrembleMapImportSettings)} context, {nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity, GameObject rootGameObject)",
" {",
" Debug.Log($\"Worldspawn created for {mapBsp.MapName} - map-wide properties can be read from 'entity'\");",
" }",
" ",
$" public override void ProcessPrefabEntity({nameof(TrembleMapImportSettings)} context, {nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity, GameObject prefab)",
" {",
" Debug.Log(\"Found a prefab!\");",
" }",
$" public override void ProcessBrushEntity({nameof(TrembleMapImportSettings)} context, {nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity, GameObject brush)",
" {",
" Debug.Log(\"Found a brush!\");",
" }",
$" public override void ProcessPointEntity({nameof(TrembleMapImportSettings)} context, {nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity, GameObject point)",
" {",
" Debug.Log(\"Found a point!\");",
" }",
"}");
});
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1a71f6a787fc471cbaabca9141e578fe
timeCreated: 1723449126
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/MapProcessorPage.cs
uploadId: 939664
@@ -0,0 +1,58 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Advanced/Custom Logic with IOnImportFromMapEntity")]
public class OnImportFromMapEntityPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Sometimes, the automated import which takes entity properties from TrenchBroom cannot",
"convert the entity fully, or you need to add custom processing logic to translate the",
"data afterwards."
);
Text(
"You can see an example of this in SheepSpawner.cs in the sample project.",
"In this case, we are importing a Point Entity with various properties,",
"but at runtime we set up a reference to a default prefab to spawn, if not set in TrenchBroom.",
$"So, by using this interface and handling OnImportFromMapEntity({nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity),",
"we can wire this up before the GameObject is saved into the imported map."
);
Text(
"You might also want to add extra components to a Brush entity - for example,",
"adding a Rigidbody:"
);
H1("Code sample");
Code(
$"[{FormatAttributeName(typeof(BrushEntityAttribute))}(\"rigidbody\", category: \"misc\")]",
$"public class RigidbodyEntity : MonoBehaviour, {nameof(IOnImportFromMapEntity)}",
"{",
" // Customisable mass for the rigidbody - but 1kg by default",
" [SerializeField] private float m_Mass = 1f;",
" ",
" // Called after importing this entity into the map.",
$" public void OnImportFromMapEntity({nameof(MapBsp)} mapBsp, {nameof(BspEntity)} entity)",
" {",
" // Turn all colliders for this mesh convex (required for Rigidbodies)",
" foreach (MeshCollider meshCollider in gameObject.GetComponentsInChildren<MeshCollider>())",
" {",
" meshCollider.convex = true;",
" }",
" ",
" // Finally add the rigidbody to the entity, and set its mass.",
" Rigidbody rb = gameObject.AddComponent<Rigidbody>();",
" rb.mass = m_Mass;",
" }",
"}");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1e143c387f9646d18f3ac2ddbc84093d
timeCreated: 1723449090
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/OnImportFromMapEntityPage.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.
//
using System;
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Advanced/Pipeline")]
public class PipelinePage : ManualPageBase
{
protected override void OnGUI()
{
Text("When Tremble imports a map, it performs a number of processing steps on it.");
Text(
"From the Pipeline settings, you can choose which steps to perform, and which",
"custom map processors (if any) you want to run. You can speed up import",
"by disabling any steps which your game does not use."
);
Text("The settings looks like this:");
Image("T_Manual_Pipeline");
Callout(
"Note that each step shows how long it took to run, the last time",
"a map was imported."
);
ActionBar_SingleAction("Open Pipeline Settings", () =>
{
const string TREMBLE_PIPELINE_PATH = "Project/Tremble/4 Pipeline";
SettingsService.OpenProjectSettings(TREMBLE_PIPELINE_PATH);
});
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4185f010c3e840a9bff636ae5998e587
timeCreated: 1728887902
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/PipelinePage.cs
uploadId: 939664
@@ -0,0 +1,73 @@
//
// 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("Advanced/Runtime Querying")]
public class RuntimeQueryingPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"At runtime, Tremble does not perform any logic and has zero overhead by design.",
"Almost all Tremble functionality only runs in the Editor. However, you may need to query",
"a loaded map to find certain objects at runtime."
);
Text(
"Of course, since entities are just MonoBehaviours, you can use Unity's functionality",
"(such as gameObject.GetComponentsInChildren<T>()) to find all objects of a given type."
);
Text(
"However, if you want to find a specific instance of an entity in the map,",
"you can do the following:"
);
Number(
"Ensure the entity you want to find is marked with a unique",
$"{TrembleSyncSettings.Get().IdentityPropertyName} property in TrenchBroom. You can also have multiple",
$"objects with the same {TrembleSyncSettings.Get().IdentityPropertyName}, but when you query the map",
"you will then get a list of entities back."
);
Indent(() =>
{
Bullet(
"For example, in the worldspawn entity you could have a start_spawn field",
"which names a spawn point to start from. We'll set it to 'my_cool_spawnpoint'.",
$"Then, you give your spawn point entity in the world a {TrembleSyncSettings.Get().IdentityPropertyName}"
,"field, with the value 'my_cool_spawnpoint'."
);
});
Number(
$"Now, given a reference to the {nameof(MapDocument)} for your map (which is added to the root",
$"GameObject), you can query for your {TrembleSyncSettings.Get().IdentityPropertyName} with the Q() method."
);
Code(
"// Get the map document - assumes one map in the scene.",
"// You might store a reference somewhere to this instead.",
"",
$"{nameof(MapDocument)} map = GameObject.FindAnyObjectByType<{nameof(MapDocument)}>();",
"",
$"// 1. Find a single entity with this {TrembleSyncSettings.Get().IdentityPropertyName}",
"if (map.Q(\"my_cool_spawnpoint\", out SpawnPoint spawnPoint))",
"{",
" spawnPoint.DoAThing();",
"}",
"",
$"// 2. Find many entities with this {TrembleSyncSettings.Get().IdentityPropertyName}",
"if (map.Q(\"my_cool_spawnpoint\", out SpawnPoint[] spawnPoints))",
"{",
" foreach (SpawnPoint spawnPoint in spawnPoints)",
" {",
" spawnPoint.DoAThing();",
" }",
"}"
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7d23c1b332fc42a98456960d6ee4e7de
timeCreated: 1723448990
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Advanced/RuntimeQueryingPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cbb8db30e0c04bceaa04fcd01ce6d5c1
timeCreated: 1723137362
@@ -0,0 +1,49 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Basics/Create Map", "Create your first map")]
public class CreateMapPage : ManualPageBase
{
protected override void OnGUI()
{
Number(
"In the project window, right-click and select 'Create' > 'Tremble Map'. Give your map a name,",
"then hit enter."
);
ActionBar_SingleAction("Create new map called 'Test' in Assets", () =>
{
MapFileAsset mapFileAsset = MapFileAsset.CreateInstance<MapFileAsset>();
AssetDatabase.CreateAsset(mapFileAsset, "Assets/MAP_Test.asset");
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
});
Number(
"TrenchBroom will open the new map for you to edit, with your project's materials",
"available to use."
);
Number(
"Since you have not yet marked any Unity scripts as entities, you will not see any",
"in TrenchBroom yet and can only create worldspawn brushes for now. See",
"the 'Entities' section to get started!"
);
ActionBar_SingleAction("Read about Entities", () => GoToPage(typeof(EntitiesOverviewPage)));
Number("When you're ready, save the map and return to Unity.");
Number("You should see a prompt - click 'Yep!'. Your new map is added to your current Unity scene.");
Image("T_Manual_CreateMap", width: 300f);
Callout("If the prompt doesn't show, or you want to add it to another scene, manually drag your",
"new map file into the scene.");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 43b737fff371496b807982db7eb1e9ad
timeCreated: 1723137369
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Basics/CreateMapPage.cs
uploadId: 939664
@@ -0,0 +1,105 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Basics/Live Update", "Using Live Update")]
public class LiveUpdatePage : ManualPageBase
{
private static readonly string[] REFRESH_MODES = { "Disabled", "Enabled", "Enabled Outside Playmode" };
protected override void OnGUI()
{
Experimental();
int autoRefreshMode = EditorPrefs.GetInt("kAutoRefreshMode");
if (autoRefreshMode != 1)
{
Callout($"Your project uses Auto Refresh Mode: {REFRESH_MODES[autoRefreshMode]}. This",
$"will mean that Live Update will not function. Change it to {REFRESH_MODES[1]} to",
"use Live Update!");
ActionBar_SingleAction("Enable Auto Refresh during playmode", () => EditorPrefs.SetInt("kAutoRefreshMode", 1));
}
Text(
"Tremble supports Live Editing - the act of editing a map while in Unity Editor's playmode.",
"All you need to do is save changes to your map file while the game is running in editor playmode.",
"The map is imported as normal, and then Tremble will try to 'reinstance' the map in memory."
);
Foldout("Wait, what's reinstancing?", () =>
{
Text("What this means is, Tremble will:");
Bullet("Destroy all worldspawn geometry, and copy the new geometry from the new map");
Bullet("For entities:");
Indent(() =>
{
Bullet(
"If an entity is not present in the running game, spawn it (note: this may mean, for example,",
"that if you kill an enemy and then save the map, the enemy may get respawned)."
);
Bullet(
"If an entity is present in the running game, but not in the map, it is removed",
"(this does not work in all cases!)"
);
Bullet(
"If any entity properties have changed, and are simple properties",
"(floats, ints, Vector3s), these are updated."
);
});
});
H1("Handling Live Update logic");
{
H2("General Usage");
Text(
"If you need to re-run some logic in an entity after it is LiveUpdated, you can implement",
$"the {nameof(IOnLiveUpdate)} interface, and override the OnLiveUpdate() method to perform any custom",
"logic."
);
Text(
"For example, finding the list of entities of a certain type (as these may have",
"changed as a result of the map changing)."
);
Image("T_Manual_LiveUpdate1");
H2("Simpler Usage");
Text(
"If this logic is identical to what would usually happen in your Start or Awake methods,",
$"you can instead implement the {nameof(IOnLiveUpdate_Simple)} interface."
);
Text(
"This will signal to Tremble that after a LiveUpdate occurs, it should call OnDestroy,",
"followed by Awake and Start (in that order) on your entity. That way, as long as your",
"Awake/Start methods set your entity up, and OnDestroy removes any state, it will 'just work'!"
);
Image("T_Manual_LiveUpdate2");
}
H2("Warnings!");
{
Text(
"If LiveUpdate goes wrong or breaks, your game may be left in a broken state.",
"To recover from this, you'll need to stop playmode, and start it again."
);
Callout(
"Known bug: Sometimes, after LiveUpdate has failed, the map is left in a broken state",
"outside of playmode too. This is a rare bug that I've only seen a handful of times.",
"Of course, if this occurs, simply re-import your map again after exiting playmode to fix it."
);
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5da16a3f20f7452f92a7f6ca768bb67b
timeCreated: 1723449031
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Basics/LiveUpdatePage.cs
uploadId: 939664
@@ -0,0 +1,27 @@
//
// 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("Basics/Settings", "Tremble's Settings")]
public class SettingsPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Tremble's default settings are designed to be suitable for most users, and most games.",
"If you're a bit more advanced, or just want to tweak various things - click ''Tools'' > ",
"''Tremble'' > ''Open Settings...'' in the Unity menu."
);
Text(
"Each tab shows settings for a different aspect of Tremble.",
"Help text is provided for each option."
);
ActionBar_SingleAction("Open Settings for me", TrembleEditorAPI.OpenSettings);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 00222940e88a45a3ac88a8fa87192328
timeCreated: 1723137185
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Basics/SettingsPage.cs
uploadId: 939664
@@ -0,0 +1,71 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Basics/Sync Toolbar")]
public class SyncOverlayPage : ManualPageBase
{
private bool m_ShowAlreadyHere;
protected override void OnGUI()
{
Text(
"By default, Tremble adds a toolbar to your scene view to quickly sync settings to",
"TrenchBroom, or to (re)import your map."
);
Text(
"You can reopen it with \"Tools\" > \"Tremble\" > \"Show Sync Toolbar\" (Unity 2022+)",
"if you lose it, or by right-clicking on your scene tab and clicking \"Overlay Menu\"",
"(or hitting [`] on your keyboard) and selecting \"Tremble Sync\".");
#if UNITY_2022_1_OR_NEWER
ActionBar_SingleAction("Open the Sync Toolbar for me", () => SceneView.AddOverlayToActiveView(new TrembleSyncOverlay()));
#endif
H1("Toolbar Buttons");
Text("The sync toolbar looks like this:");
ToolbarButtonDescription("T_TB_Import", "Re-import map(s) in current scene from TrenchBroom");
ToolbarButtonDescription("T_TB_Sync", "Sync Unity prefabs and materials to TrenchBroom");
ToolbarButtonDescription("T_TB_Config", "Open Tremble's Settings");
ToolbarButtonDescription("T_TB_Manual", "Open the Tremble Manual (this!)");
ActionBar(() =>
{
Action("Import", TrembleEditorAPI.ReimportCurrentMap);
Action("Sync", TrembleEditorAPI.SyncToTrenchBroom);
Action("Settings", TrembleEditorAPI.OpenSettings);
Action("Manual", () => m_ShowAlreadyHere = true);
});
if (m_ShowAlreadyHere)
{
Text("You silly! You're already in the manual!");
}
}
private void ToolbarButtonDescription(string textureName, string description)
{
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
{
Image(textureName, 30f, centred: false);
GUILayout.BeginVertical();
{
GUILayout.Space(10f * ManualStyles.Scale);
GUILayout.Label(description, ManualStyles.Styles.Text);
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
SmallSpace();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b7dbe33a0d0446e381e8d0316f066317
timeCreated: 1723117770
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Basics/SyncOverlayPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9d822fe9fde64471bed45f0c6381719e
timeCreated: 1726388519
@@ -0,0 +1,181 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Debug/Sanity Check")]
public class DebugSanityCheckPage : ManualPageBase
{
private PrefabNameLookup m_PrefabNameLookup;
private MapTypeLookup m_MapTypeLookup;
private readonly Dictionary<Type, MonoScript> m_TypeToMonoScript = new();
private int m_SelectedTabIdx = 0;
private readonly string[] m_TabNames = new[] { "Basics", "Entities" };
protected override void OnInit()
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
m_PrefabNameLookup = new(syncSettings);
m_MapTypeLookup = new(syncSettings);
}
protected override void OnGUI()
{
Tabs(ref m_SelectedTabIdx, m_TabNames);
switch (m_SelectedTabIdx)
{
case 0:
OnBasicsGUI();
break;
case 1:
OnEntitiesGUI();
break;
}
}
private void OnBasicsGUI()
{
Text("This page will verify that Tremble is installed and working correctly!");
// TB?
bool trenchBroomInstalled = !TrenchBroomUtil.TrenchBroomPath.IsNullOrEmpty();
CheckListItem("TrenchBroom installed and detected?", trenchBroomInstalled);
if (!trenchBroomInstalled)
{
Callout("If TrenchBroom is installed, but not detected here, open it",
"and Tremble should detect its location!");
}
// Installed game into TB?
bool gameFolderExists = Directory.Exists(TrenchBroomUtil.GetGameFolder());
CheckListItem("TrenchBroom game folder exists?", gameFolderExists, "Sync to create it!", TrembleEditorAPI.SyncToTrenchBroom);
bool gameInstalledInTBPrefs = TrenchBroomUtil.DoesEnginePathExistInTrenchBroomPrefs();
CheckListItem("TrenchBroom knows game location?", gameInstalledInTBPrefs, "Sync to create it!", TrembleEditorAPI.SyncToTrenchBroom);
// BaseQ3 folder
bool baseQ3FolderExists = Directory.Exists(TrembleConsts.BASEQ3_PATH);
CheckListItem("BaseQ3 folder exists in Library?", baseQ3FolderExists, "Sync to create it!", TrembleEditorAPI.SyncToTrenchBroom);
// DLL loaded?
bool dllIsLoaded = Q3Map2Dll.IsLoaded;
CheckListItem("Q3Map2 DLL loaded successfully?", dllIsLoaded);
}
private void OnEntitiesGUI()
{
Foldout("Prefab Entities", () =>
{
Text("The following are the prefabs that Tremble knows about:");
EditorGUI.BeginDisabledGroup(true);
{
foreach (string prefabPath in m_PrefabNameLookup.AllPrefabPaths)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
m_PrefabNameLookup.TryGetMapNameFromPrefabPath(prefabPath, out string mapName);
GUILayout.BeginHorizontal();
{
EditorGUILayout.PrefixLabel(mapName);
EditorGUILayout.ObjectField(prefab, typeof(GameObject), false);
}
GUILayout.EndHorizontal();
}
}
EditorGUI.EndDisabledGroup();
});
Foldout("Point Entities", () =>
{
Text("The following are the [PointEntity] MonoBehaviours that Tremble knows about:");
bool wasAnyPresent = false;
foreach (Type type in m_MapTypeLookup.AllTypes)
{
if (!m_MapTypeLookup.TryGetMapNameFromClass(type, out string mapName))
{
Text($"Invalid: '{type.Name}' - no map name known!");
continue;
}
if (!m_MapTypeLookup.TryGetEntityTypeFromMapName(mapName, out EntityType entityType) || entityType != EntityType.Point)
continue;
DisabledTypeField(type, mapName);
wasAnyPresent = true;
}
if (!wasAnyPresent)
{
Text("There are none (which might be fine)!");
}
});
Foldout("Brush Entities", () =>
{
Text("The following are the [BrushEntity] MonoBehaviours that Tremble knows about:");
bool wasAnyPresent = false;
foreach (Type type in m_MapTypeLookup.AllTypes)
{
if (!m_MapTypeLookup.TryGetMapNameFromClass(type, out string mapName))
{
Text($"Invalid: '{type.Name}' - no map name known!");
continue;
}
if (!m_MapTypeLookup.TryGetEntityTypeFromMapName(mapName, out EntityType entityType) || entityType != EntityType.Brush)
continue;
DisabledTypeField(type, mapName);
wasAnyPresent = true;
}
if (!wasAnyPresent)
{
Text("There are none (which might be fine)!");
}
});
}
private void DisabledTypeField(Type type, string label)
{
EditorGUI.BeginDisabledGroup(true);
{
if (!m_TypeToMonoScript.TryGetValue(type, out MonoScript monoScript))
{
GameObject go = new("Tester");
MonoBehaviour mb = (MonoBehaviour)go.AddComponent(type);
monoScript = MonoScript.FromMonoBehaviour(mb);
m_TypeToMonoScript[type] = monoScript;
GameObject.DestroyImmediate(go);
}
GUILayout.BeginHorizontal();
{
EditorGUILayout.PrefixLabel(label);
EditorGUILayout.ObjectField(monoScript, typeof(MonoScript), false);
}
GUILayout.EndHorizontal();
}
EditorGUI.EndDisabledGroup();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a7c8885112604038b963d644ebbb4ead
timeCreated: 1726483054
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Debug/DebugSanityCheckPage.cs
uploadId: 939664
@@ -0,0 +1,68 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Debug/Entity Explorer")]
public class DebugShowEntitiesPage : ManualPageBase
{
private FgdWriter m_FgdWriter;
private string[] m_ClassList;
private int m_SelectedClassIdx = 0;
protected override void OnInit()
{
m_FgdWriter = TrembleSync.GatherEntities();
m_ClassList = m_FgdWriter.BaseClasses
.Select(bc => $"@BaseClass {bc.Name}")
.Concat(m_FgdWriter.Classes.Select(c => $"@{c.Type}Class {c.Name}"))
.Prepend("(select a class...)")
.ToArray();
}
protected override void OnGUI()
{
m_SelectedClassIdx = EditorGUILayout.Popup(m_SelectedClassIdx, m_ClassList);
int selectedClassIdx = m_SelectedClassIdx - 1;
if (selectedClassIdx < 0)
{
Text("Select a class above to see its entity definition!");
return;
}
FgdClass entityClass = (selectedClassIdx < m_FgdWriter.BaseClasses.Count)
? m_FgdWriter.BaseClasses[selectedClassIdx]
: m_FgdWriter.Classes[selectedClassIdx - m_FgdWriter.BaseClasses.Count];
MemoryStream memoryStream = new();
// Write to memory
using StreamWriter writer = new(memoryStream, Encoding.ASCII);
entityClass.WriteFgd(writer, TrembleSyncSettings.Get().ImportScale);
writer.Flush();
// Rewind and read
memoryStream.Position = 0;
using StreamReader reader = new(memoryStream, Encoding.ASCII);
string snippet = reader.ReadToEnd().Replace("\t", " ");
CodeWithHeader($"[{entityClass.Type}] {entityClass.Name}", snippet);
Text("Above is how the entity is passed to TrenchBroom. If you're familiar with FGD files,",
"this will look very familiar! If not, you basically are looking to make sure the name",
"is what you would expect, and any fields that you wish to edit on entities of that",
"class are present.");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2dc49cc1fab340b8a6d97e6fbff9fe66
timeCreated: 1726387089
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Debug/DebugShowEntitiesPage.cs
uploadId: 939664
@@ -0,0 +1,112 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Debug/Material Explorer")]
public class DebugShowMaterialsPage : ManualPageBase
{
private readonly Dictionary<string, List<Texture>> m_Textures = new();
protected override void OnInit()
{
int texSize = TrembleSyncSettings.Get().MaterialExportSize.ToIntSize();
string texturesPath = TrembleConsts.BASE_MATERIALS_PATH;
foreach (string dir in Directory.GetDirectories(texturesPath))
{
string[] pngFiles = Directory.GetFiles(dir, "*.png");
if (pngFiles.Length == 0)
continue;
List<Texture> textures = new(pngFiles.Length);
foreach (string textureFile in pngFiles)
{
byte[] textureBytes = File.ReadAllBytes(textureFile);
Texture2D texture = new(texSize, texSize);
texture.name = Path.GetFileNameWithoutExtension(textureFile);
texture.LoadImage(textureBytes);
textures.Add(texture);
}
m_Textures[Path.GetFileName(dir)] = textures;
}
}
protected override void OnDeInit()
{
foreach (List<Texture> textures in m_Textures.Values)
{
foreach (Texture texture in textures)
{
Texture.DestroyImmediate(texture);
}
}
m_Textures.Clear();
}
protected override void OnGUI()
{
float thumbnailSize = 64f * ManualStyles.Scale;
float labelSize = thumbnailSize + 40f * ManualStyles.Scale;
int numWide = Mathf.Max(1, Mathf.FloorToInt(PageWidth / labelSize) - 2);
foreach (string textureFolder in m_Textures.Keys)
{
H1(textureFolder);
{
int cell = 0;
GUILayout.BeginHorizontal();
{
foreach (Texture texture in m_Textures[textureFolder])
{
GUILayout.BeginVertical();
{
GUILayout.BeginHorizontal(GUILayout.Width(labelSize));
{
GUILayout.Space((labelSize - thumbnailSize) / 2f);
GUIContent content = new(texture, texture.name);
GUILayout.Box(content, GUILayout.Width(thumbnailSize), GUILayout.Height(thumbnailSize));
}
GUILayout.EndHorizontal();
GUILayout.Label(texture.name, ManualStyles.Styles.CenteredGreyMiniLabel, GUILayout.Width(labelSize));
}
GUILayout.EndVertical();
// New row?
if (++cell >= numWide)
{
cell = 0;
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(1f);
GUILayout.BeginHorizontal();
}
}
if (cell <= numWide)
{
GUILayout.FlexibleSpace();
}
}
GUILayout.EndHorizontal();
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d1d737f97a4f45c193f15ac0551af98e
timeCreated: 1726390246
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Debug/DebugShowMaterialsPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: efd666aa9a5c4579a55f666673349d69
timeCreated: 1723201715
@@ -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
@@ -0,0 +1,37 @@
//
// 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("Map Repair")]
public class MapRepairOverviewPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Sometimes, after renaming entities or materials, your map might import in a broken state,",
"missing materials or functionality. In this case, you can right click the map in Unity,",
"and select 'Repair Map' (or, go to Tools > Tremble > Repair Map)."
);
Image("T_Manual_MapRepair");
ActionBar_SingleAction("Open Map Repair", MapRepairWindow.OpenMapRepair);
Callout("Note, that you should ensure TrenchBroom does not have your map open while it is repaired.");
H2("Other uses for map repair");
Bullet(
"Swap materials around, or reassign entities in the map wholesale - which can",
"be far quicker than manually editing them."
);
Bullet(
"Import maps from Quake®, or another game using .map files"
);
ActionBar_SingleAction("Show me how to import maps from other games", () => GoToPage(typeof(MapRepairForeignPage)));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 235e96de3d244c26bdf4c5c2e8b833dc
timeCreated: 1723448743
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/MapRepairPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 966bf55c023a42aa9b1ee89f32290315
timeCreated: 1724656265
@@ -0,0 +1,138 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Materials/Material Groups")]
public class MaterialGroupsPage : ManualPageBase
{
private TrembleSyncSettings m_SyncSettings;
private MaterialGroup m_DefaultMaterialGroup;
private Material[] m_UnexposedMaterials;
protected override void OnInit()
{
m_SyncSettings = TrembleSyncSettings.Get();
Material[] allMaterials = AssetDatabase
.FindAssets("t:material", new[] { "Assets" })
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<Material>)
.ToArray();
m_DefaultMaterialGroup = MaterialGroup.CreateDefault();
m_UnexposedMaterials = allMaterials
.Where(m => !MaterialGroupUtil.IsMaterialPartOfAnyMaterialGroup(m))
.ToArray();
}
protected override void OnGUI()
{
Text(
"By default, Tremble will export all of the materials in your project into TrenchBroom.",
"If your project has a lot of materials though, this can take some time,",
"and there may be materials that you never want to use in TrenchBroom at all."
);
Text(
"By using material groups, you can decide which materials to export to TrenchBroom,",
"and even organise them into groups!"
);
H1("Assigning material groups");
Text("There are 2 ways to set up material groups:");
Bullet(
"Click an individual material (or a group of materials) in Unity's Project window,",
"and select a group from the dropdown at the top of the Inspector view."
);
Image("T_Manual_MaterialGroups");
Bullet(
"or, go into Tremble's settings (see Basics > Tremble's Settings), click 'Materials',",
"and add one or more groups. Now you can assign materials to each group."
);
H1("How is my project set up?");
if (m_SyncSettings.MaterialGroups is { Length: > 0 })
{
Text(
$"Your project is using material groups, and you have {m_SyncSettings.MaterialGroups.Length}",
"of them:"
);
foreach (MaterialGroup matGroup in m_SyncSettings.MaterialGroups)
{
Indent(() =>
{
Foldout($"{matGroup.Name}, {matGroup.Materials.Length} material(s)", () =>
{
foreach (Material mat in matGroup.Materials)
{
GUILayout.BeginHorizontal();
{
Bullet(mat.name);
Button($"select {mat.name}", width: 200f, action: () => Selection.activeObject = mat);
}
GUILayout.EndHorizontal();
}
});
});
}
if (m_UnexposedMaterials.Length == 0)
{
Text("...and that's all your materials!");
}
else
{
Text($"There are {m_UnexposedMaterials.Length} materials that are not exposed to TrenchBroom:");
Indent(() =>
{
Foldout($"Unavailable material(s)", () =>
{
foreach (Material mat in m_UnexposedMaterials)
{
GUILayout.BeginHorizontal();
{
Bullet(mat.name);
Button($"select {mat.name}", width: 200f, action: () => Selection.activeObject = mat);
}
GUILayout.EndHorizontal();
}
Text("To make a Material available for use, select it and use the Inspector to add it",
"to a Material Group.");
});
});
}
}
else
{
Text(
$"Your project is not using material groups, and you have {m_DefaultMaterialGroup.Materials.Length}",
"compatible material(s) in your project:"
);
Indent(() =>
{
Foldout("Show materials", () =>
{
foreach (Material material in m_DefaultMaterialGroup.Materials)
{
Bullet(material.name);
}
});
});
}
string trembleMaterialsPath = "Project/Tremble/3 Materials";
ActionBar_SingleAction("Edit Material Groups", () => SettingsService.OpenProjectSettings(trembleMaterialsPath));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fcdaf93099d84ea28783d12928ca5055
timeCreated: 1723448806
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Materials/MaterialGroupsPage.cs
uploadId: 939664
@@ -0,0 +1,71 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Materials/Render Settings")]
public class MaterialRenderDataPage : ManualPageBase
{
private TrembleSyncSettings m_SyncSettings;
private MaterialGroup m_DefaultMaterialGroup;
private Material[] m_UnexposedMaterials;
protected override void OnInit()
{
m_SyncSettings = TrembleSyncSettings.Get();
}
protected override void OnGUI()
{
Text(
"When Tremble exports materials, it can convert your materials in a few different ways.",
"You can choose a material's render settings by finding that material in the editor,",
"and changing the following settings:"
);
Image("T_Manual_RenderSettings");
H1("Render Mode");
Bold("Dynamic Mode (\"MainTex\")");
Bullet(
"Uses the main texture of the material as the texture exported to Trenchbroom.",
"This will work fine for most materials. Directly copies the texture to the export folder",
"if the resolution is unmodified."
);
Bullet(
"If you're using a custom shader, make sure your material either has a texture named _MainTex,",
"or a texture marked as Main Texture. This can be done through ShaderLab markup with [MainTexture]",
"for code shaders, or by marking the texture as a Main Texture in Shader Graph shaders.");
Bullet(
"If the renderer fails to find a matching main texture, it will try a few other texture names",
"like _Color, _BaseColor, or other misspellings of _MainTex (just in case).");
Bullet(
"If that also fails, the renderer will switch to Fixed Size Mode."
);
Bold("Fixed-Size Mode (\"Legacy\")");
Bullet(
"Renders the material using a camera system. ",
"This is useful for procedural materials, or any other material that doesn't have",
"a main texture parameter."
);
Bullet(
"Will be automatically used if MainTex mode fails."
);
H1("Resolution");
Text(
"You can use these settings to set the resolution of a texture.",
"This is useful for textures that don't have the same resolution on the X and Y axis.",
"Note that the resolution affects UV mapping - in Trenchbroom, one pixel is one unit by default."
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b87f0fb523a94356827025cd020dd8d3
timeCreated: 1752489026
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Materials/MaterialRenderDataPage.cs
uploadId: 939664
@@ -0,0 +1,57 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Materials/About")]
public class MaterialsAboutPage : ManualPageBase
{
protected override void OnGUI()
{
Text(
"Tremble uses a novel approach compared to other map import tools.",
"Many map import tools will allow users to create their own Textures folder for use",
"with TrenchBroom, and will try to create materials based on those on import."
);
Text(
"This results in materials in your project which use the built-in 'Standard'",
"(or URP/HDRP 'Lit') shader."
);
Text(
"Tremble does NOT work in this way. Tremble takes your existing Unity materials,",
"and generates a Textures folder for you with an accurate representation of each",
"Unity material as a texture."
);
H1("Why?!");
Text(
"Well, we believe that users don't want to spend time curating textures for",
"TrenchBroom when ultimately what they care about is the Material looking good",
"in Unity (where your players will see it). It's the same reason Tremble does",
"not support Quake®'s lightmapping tech. Your game will use Unity's lighting,",
"so this is where you should apply your scene's lighting setup."
);
H1("But really, what benefits does it give me?");
Bullet("Your materials and shaders appear exactly how you set them up in Unity, in your game!");
Bullet(
"You are not restricted to using Standard/Lit shaders for your map materials! You",
"can use custom shaders/Shader Graphs, including Unlit, bumpmapped, or entirely HLSL-driven",
"materials."
);
Bullet(
"There's no need to worry about how TrenchBroom handles textures - Tremble",
"keeps everything in sync automagically!"
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0eedd4cad37c4fdca5801d345f060473
timeCreated: 1724656293
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Materials/MaterialsAboutPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9168ecc737624c8899cf1cb3474b0b9f
timeCreated: 1723449331
@@ -0,0 +1,43 @@
//
// 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("Import from Other Games/Texture FGDs from other games")]
public class ImportFgdPage : ManualPageBase
{
protected override void OnGUI()
{
Text("When importing maps from other games, Tremble might encounter entities which it does not recognise.");
Text("For example, if you import a map from Quake®, it will contain (among others) an",
"'info_player_start' entity to define where the player should spawn.");
Text("If you import the map and use Map Repair, you can either:");
Number("Map an existing MonoBehaviour from your game to this unknown entity, or");
Number("Have Tremble generate a new MonoBehaviour, by making assumptions based on what's in the map.");
Text("However, this can be problematic. If the map doesn't contain data for a given field,",
"Tremble will have no knowledge of it and won't generate complete C# script for it.");
Text("Tremble will never generate anything invalid, but you may be missing functionality.");
Text("This is where importing an FGD file can help!");
Foldout("What's an FGD file?", () =>
{
Text("A Forge Game Data file. It contains a list of all entities in a game, and what",
"fields they support.");
Text("It can also contain helpful tooltips and hints as to how each field is used.");
});
H1("Importing an FGD file");
Text("To import an FGD file, simply drag it into your project.");
Text("Tremble will extract the FGD classes into a folder of the same name.");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e85c2df6351a493aaeea88ae4c9fdae4
timeCreated: 1724661376
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/OtherGames/ImportFgdPage.cs
uploadId: 939664
@@ -0,0 +1,28 @@
//
// 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("Import from Other Games/Texture WADs from other games")]
public class ImportWadPage : ManualPageBase
{
protected override void OnGUI()
{
Text("Unfortunately, you cannot supply a WAD file in your TrenchBroom map files for Tremble to use.");
Text("However, Tremble does support extracting WAD files into your Unity project, which in turn",
"makes them available to TrenchBroom.");
Callout("Note: Only WAD2 (Quake) format WAD files are supported at the moment.",
"WAD3 (Valve) are not supported (yet?)");
Text("To get started, simply drop a WAD file into your Unity project.");
Text("This process can take a while for a large WAD file - up to a minute or two.",
"It only needs to be done once.");
Image("T_Manual_WadImport2");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 418c0e37fc314aa489c2459b11529dbf
timeCreated: 1723449354
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/OtherGames/ImportWadPage.cs
uploadId: 939664
@@ -0,0 +1,30 @@
//
// 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("Import from Other Games/FGD Comparison", title: "The difference made by importing an FGD file", showInTree: false)]
public class MapRepairFgdComparisonPage : ManualPageBase
{
protected override void OnGUI()
{
Text("With an FGD file imported, you'll see that Tremble has the proper names and",
"descriptions of all the fields of the map classes - rather than just the ones",
"that happened to be set in the map.");
Text("It's recommended to import the FGD whenever importing 'foreign' maps to make",
"them easier to work with.");
CustomPropertyDescription("No FGD File", () =>
{
Image("T_Manual_MapRepair_NoFGD", width: 700f, centred: false);
});
CustomPropertyDescription("With FGD File", () =>
{
Image("T_Manual_MapRepair_WithFGD", width: 700f, centred: false);
});
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 937a21eaecb54e83a8e4180905259474
timeCreated: 1723549012
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/OtherGames/MapRepairFgdComparisonPage.cs
uploadId: 939664
@@ -0,0 +1,82 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Import from Other Games/Maps from other games")]
public class MapRepairForeignPage : ManualPageBase
{
private static readonly string[] TAB_NAMES = new[]
{
"Materials/Textures", "Entities"
};
private int m_SelectedTabIdx;
protected override void OnGUI()
{
Image("T_Manual_MapRepair_Required", width: 220f);
Text("You can also use Map Repair to patch up what Tremble terms 'foreign maps' (maps",
"from other games) to work with your Tremble/Unity project. This also allows you to",
"import/export maps between your own projects, or even import maps from other games",
"(such as Quake®!).");
Text("For any unrecognised entities in maps, Map Repair will generate new MonoBehaviour classes",
"to match them. This saves time creating C# classes for brand new entities you've",
"added to the map.");
Text("These generated classes will allow Tremble to import these new entities and will",
"bring any values from the map into Unity - but of course, it won't provide any game",
"logic such as movement or AI - you'd have to code that yourself.");
H2("Available Tabs");
{
switch (Tabs(ref m_SelectedTabIdx, TAB_NAMES, width: 420f))
{
case 0: // Textures
GUILayout.BeginHorizontal();
{
Image("T_Manual_MapRepair_Textures", width: 400f);
GUILayout.BeginVertical();
{
Text("This tab shows a list of all of the map's textures, and Tremble’s best guess",
"at which project Materials match those textures.");
Bullet("You can then change which material each texture maps to manually, if you desire.");
Bullet("You can click the small '!' button to apply a given material to all missing materials",
"(for example, if you want to apply a single material to all unknown materials in a map)");
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
break;
case 1: // Entities
GUILayout.BeginHorizontal();
{
Image("T_Manual_MapRepair_Entities", width: 400f);
GUILayout.BeginVertical();
{
Text("This tab shows a list of all the map's point and brush entities, and Tremble’s best guess",
"at which MonoBehaviour classes match those entities.");
Bullet("You can then change which class each entity maps to manually, if you desire.");
Bullet("Leave an unknown entity class as 'Generate new class' to ask Tremble to generate",
"a new MonoBehaviour class that matches the entity. This may need manual tweaking",
"afterwards, but will be valid and able to parse the map supplied.");
Bullet("At the bottom of this tab, you are able to designate where new classes",
"are generated and an optional namespace to use for them.");
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
break;
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e510aaa0a33f4bceb10e55cb90bbedd1
timeCreated: 1723448767
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/OtherGames/MapRepairForeignPage.cs
uploadId: 939664
@@ -0,0 +1,125 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Overview", isHomePage: true)]
public class OverviewPage : ManualPageBase
{
private bool m_GotLatestVersions = false;
protected override void OnInit()
{
VersionCheck.FetchLatestVersionsInBackground(() =>
{
m_GotLatestVersions = true;
Repaint();
});
}
protected override void OnGUI()
{
GUILayout.BeginHorizontal();
{
Image("T_TrembleLogo", width: 200f, centred: false);
GUILayout.BeginVertical();
{
H1("Welcome to the Tremble manual!");
Text(
"Please click an item on the left to navigate. There are also buttons in the top",
"toolbar for navigating back/forwards, and returning here (Home)."
);
PropertyDescription("Your version of Tremble", labelWidth: 200f, $"Version {TrembleConsts.VERSION_STRING}");
if (!m_GotLatestVersions)
{
GUI.color = Color.yellow;
PropertyDescription("Latest version of Tremble", "... checking if there's a newer version ...");
GUI.color = Color.white;
}
else
{
if (VersionCheck.IsNewerVersionAvailable)
{
GUILayout.BeginVertical(EditorStyles.helpBox);
{
PropertyDescription("Latest version of Tremble", labelWidth: 200f, $"There's a newer version available, v{VersionCheck.NewestAvailableVersion.Version}",
$"({VersionCheck.NumVersionsBehind} versions behind)!");
ActionBar_SingleAction("Upgrade in Package Manager!", () => EditorApplication.ExecuteMenuItem("Window/Package Manager"));
}
GUILayout.EndVertical();
}
else
{
PropertyDescription("Latest version of Tremble", labelWidth: 200f, $"Version {VersionCheck.CurrentInstalledVersion.Version} - you're already on the latest and greatest!");
}
}
if (TrenchBroomUtil.TrenchBroomPath == null)
{
PropertyDescription("TrenchBroom", labelWidth: 200f, "(unknown location!)");
GUI.color = Color.yellow;
Text(
"Is TrenchBroom installed? Tremble is unsure where to find it. Try",
"opening TrenchBroom now - I'll keep checking for it :)"
);
GUI.color = Color.white;
}
else
{
string path = TrenchBroomUtil.TrenchBroomPath;
#if UNITY_EDITOR_OSX
// Under macOS, remove /Contents/MacOS/TrenchBroom
int suffix = path.IndexOfInvariant("/Contents", caseSensitive: true);
if (suffix != -1)
{
path = path.Substring(0, suffix);
}
#endif
string running = TrenchBroomUtil.IsTrenchBroomRunning ? " (running)" : "";
PropertyDescription("TrenchBroom", labelWidth: 200f, $"TrenchBroom was discovered at: {path}{running}");
}
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
LargeSpace();
if (TrenchBroomUtil.TrenchBroomPath == null)
{
ActionBar_SingleAction("Download TrenchBroom", () => { Application.OpenURL("https://github.com/TrenchBroom/TrenchBroom/releases");});
}
H2("Getting Started");
ActionBar(() =>
{
Action("Create your first map", () => GoToPage(typeof(CreateMapPage)));
Action("Set Tremble up for User Generated Content", () => GoToPage(typeof(UgcSetupPage)));
Action("Get support!", () => GoToPage(typeof(TroubleshootingGetHelpPage)));
});
H2("Quick Create Entity");
ActionBar(() =>
{
Action("Create a POINT entity", () => GoToPage(typeof(PointEntitiesPage)));
Action("Create a BRUSH entity", () => GoToPage(typeof(BrushEntitiesPage)));
Action("Create a PREFAB entity", () => GoToPage(typeof(PrefabEntitiesPage)));
});
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7901a6be0c0b40a6bae863067e7c1470
timeCreated: 1722961223
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/OverviewPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 273698197de74f00b452be8efe988040
timeCreated: 1723448826
@@ -0,0 +1,50 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Troubleshooting/'dylib can't be opened' (macOS only)")]
public class TroubleshootingDLLPage : ManualPageBase
{
protected override void OnGUI()
{
Image("T_Manual_Trouble_DLL1", width: 200f);
Text(
"This can sometimes occur because Q3Map2, the converter which transforms your .map",
"file into something Tremble can understand, is not currently signed.",
"(this may change in future!)"
);
Text(
"It's a warning from macOS that you may not wish to trust the Q3Map2 binary."
);
Foldout("Why should I trust it?", () =>
{
Bullet("Q3Map2 is distributed with Tremble from the Asset Store and is safe to use");
Bullet("You can verify the source code OR build your own Q3Map2 library, if you prefer");
ActionBar_SingleAction("Verify Q3Map2 source on Github", () => Application.OpenURL("https://github.com/tinygooseuk/q3map2/tree/libq3map2"));
});
H1("To fix the error, do the following:");
Number("Click 'Done' to close the 'can't be opened' dialogue");
Number("Open 'System Settings' on your Mac, and scroll down to 'Privacy & Security' in the left sidebar");
Number("Scroll down to 'Security', and click 'Allow Anyway'");
Image("T_Manual_Trouble_DLL2");
Number("You may be prompted to enter your password or use Touch ID. Do so");
Number("Close System Settings and return to Unity");
Number("Try to import the map again. You will see the same error, but this time there will be an 'Open' button. Click 'Open'");
Text("This should now work!");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4dcb872d73a04f568fdea7a559b18319
timeCreated: 1723448843
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Troubleshooting/TroubleshootingDLLPage.cs
uploadId: 939664
@@ -0,0 +1,32 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Troubleshooting/Get Help & Support")]
public class TroubleshootingGetHelpPage : ManualPageBase
{
protected override void OnGUI()
{
H1("Discord");
Text(
"If you need help, please first join us on our Discord server:",
"https://discord.gg/aUTmYxbVHZ."
);
ActionBar_SingleAction("Open Discord", () => Application.OpenURL("https://discord.gg/aUTmYxbVHZ"));
H1("Email Support");
Text(
"For further assistance, or if you do not have access to Discord, do",
"not hesitate to contact us at: hello@tinygoose.com."
);
ActionBar_SingleAction("Send Email", () => Application.OpenURL("mailto:hello@tinygoose.com"));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ce80159b60eb429c822dbbab4c39d44b
timeCreated: 1723448946
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Troubleshooting/TroubleshootingGetHelpPage.cs
uploadId: 939664
@@ -0,0 +1,56 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Troubleshooting/Too dark or too bright materials")]
public class TroubleshootingMaterialBrightnessPage : ManualPageBase
{
protected override void OnGUI()
{
Image("T_Manual_Trouble_MaterialBrightness");
Text("Some users experience issues with materials exporting too bright or too dark when",
"viewed in TrenchBroom. This can be due to the way Tremble captures Unity Materials to",
"texture PNGs - which uses specific lighting, but can sometimes take on the environment",
"from your scene.");
Text("Please note that this does NOT affect how your materials will appear in Unity/your game!");
Text("As a workaround, you can adjust how Tremble lights your materials in Tremble's Settings,",
"under Advanced/Materials.");
Callout("When changing these settings or force-resyncing below, note that you should",
"Refresh Texture Collections (F5) and Refresh Entity Definitions (F6) in TrenchBroom",
"to update to the latest data.");
Image("T_Manual_Trouble_MaterialBrightnessSettings");
ActionBar_SingleAction("Open in Settings", ()
=> SettingsService.OpenProjectSettings("Project/Tremble/4 Advanced/3 Materials"));
Text("As a last resort, you can also try force-syncing your project to TrenchBroom. You can do this",
"by clearing the cache and then re-syncing, from Tools > Tremble > Sync Project to TrenchBroom (Clean, Slow)",
"- or by pressing the button below.");
ActionBar_SingleAction("Force-resync for me", ()=>
{
TrembleEditorAPI.InvalidateMaterialAndPrefabCache(silent: true);
TrembleEditorAPI.SyncToTrenchBroom();
if (TrenchBroomUtil.IsTrenchBroomRunning)
{
EditorUtility.DisplayDialog(
title: "TrenchBroom is running",
message: "You may need to refresh Textures and Entities in TrenchBroom (press F5, then F6 in TrenchBroom)!",
"Oh, okay!");
}
});
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ef8cfc4d6b7b43baa6078bbb722dca8f
timeCreated: 1723448882
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Troubleshooting/TroubleshootingMaterialBrightnessPage.cs
uploadId: 939664
@@ -0,0 +1,57 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Troubleshooting/Missing Geometry")]
public class TroubleshootingMissingGeoPage : ManualPageBase
{
private const float IMAGE_WIDTH = 300f;
protected override void OnGUI()
{
Text("If you create an area in a map that is completely enclosed (known in the mapping",
"community as being 'sealed'), you might find that Tremble culls the",
"inside of these areas entirely!");
GUILayout.BeginHorizontal();
{
Bold("TrenchBroom (before):", width: IMAGE_WIDTH);
Bold("Unity (before):", width: IMAGE_WIDTH);
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
{
Image("T_Manual_MissingGeo_TB1", width: IMAGE_WIDTH, centred: false);
Image("T_Manual_MissingGeo_Unity1", width: IMAGE_WIDTH, centred: false);
}
GUILayout.EndHorizontal();
Text("This is because Q3Map2 (the map compiler) determines that these areas can never",
"be reached, and thus is unnecessary geometry.");
Text("In order to keep this geometry around, you need to place a Point or Brush entity",
"(or a prefab) inside this area. Tremble will no longer cull the area, as it",
"determines that there are gameplay elements inside the area and thus it is important.");
GUILayout.BeginHorizontal();
{
Bold("TrenchBroom (after):", width: IMAGE_WIDTH);
Bold("Unity (after):", width: IMAGE_WIDTH);
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
{
Image("T_Manual_MissingGeo_TB2", width: IMAGE_WIDTH, centred: false);
Image("T_Manual_MissingGeo_Unity2", width: IMAGE_WIDTH, centred: false);
}
GUILayout.EndHorizontal();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2319b986770f4d8c9ddcbb9b6d37ba62
timeCreated: 1724754283
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Troubleshooting/TroubleshootingMissingGeoPage.cs
uploadId: 939664
@@ -0,0 +1,34 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("Troubleshooting/Pink materials in TrenchBroom")]
public class TroubleshootingPinkMaterialsPage : ManualPageBase
{
protected override void OnGUI()
{
Text("Pink materials can appear in either Unity or TrenchBroom, and they indicate that",
"a Material or Shader could not be found.");
Image("T_Manual_Trouble_PinkMaterials");
H1("For your own materials/textures not rendering");
Text("Try to run Map Repair on the map with pink materials. You will be able to swap the",
"missing materials for valid ones.");
ActionBar_SingleAction("Open Map Repair", MapRepairWindow.OpenMapRepair);
H1("For Tremble sample materials not rendering, under URP or HDRP");
Text("Tremble's sample project materials are built for the Built-In Render Pipeline.",
"This means, if you're using URP or HDRP, you should manually upgrade them to your pipeline.");
Text("There's a great tutorial for this by SpawnCampGames on YouTube:");
Image("T_Manual_Trouble_FixPinkMaterialsVideo");
ActionBar_SingleAction("Open Video", () => Application.OpenURL("https://www.youtube.com/watch?v=V_EGF1M3fgY"));
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 8a86dbcdd6714f008d781a4d19f04b82
timeCreated: 1723453626
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/Troubleshooting/TroubleshootingPinkMaterialsPage.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed2fb86e7fa34acc930cb86907b49812
timeCreated: 1723536800
@@ -0,0 +1,61 @@
//
// 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("User Generated Content/For Players")]
public class UgcPlayerHintsPage : ManualPageBase
{
protected override void OnGUI()
{
Experimental();
Text("This page gives a bit more detail on what players need, in order to import maps");
H2("How do players get the FGD file/textures/etc?");
Text(
"These are installed onto the user's computer when you call",
$"{nameof(TrembleRuntimeAPI)}.{nameof(TrembleRuntimeAPI.Initialise)},",
"so there is no need to distribute these yourself."
);
H2("Do players need a copy of my Unity project to import maps?");
Text(
"Absolutely not! Just a copy of your game with Tremble installed and configured."
);
H2("Do players need to purchase a licence for Tremble, on the Asset Store?");
Text(
"Again - absolutely not! Just a copy of your game with Tremble installed and configured."
);
H2("What if players want to use their own textures?");
Text(
"Generally this is discouraged, especially when distributing user-generated maps.",
"However - it is possible! Users should place their textures (PNG only!) into your game's",
"Library/baseq3/Textures folder (this will be created on first run). When Tremble",
"finds these unexpected textures referenced in a map, it will create Unity",
"materials on-the-fly to display them."
);
Text(
"Note, that you will need to distribute these textures with the maps, and place",
"them into the correct folder at runtime for this to work when sharing maps between",
"players."
);
H2("What about if these textures should be PBR materials?");
Text(
"Yup - you might want to change the metallic and smoothness parameters when",
"importing a user's custom texture. To do that, Tremble uses a naming convention."
);
PropertyDescription("s[0-100]", "Smoothness percent");
PropertyDescription("m[0-100]", "Metallic/metalness percent");
Text(
"For example, a texture which should be 60% smooth and 100% metal could be named: ",
"'MyCoolTexture_s60_m100.png'."
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 56855e58ea5a449082f0be2069093e76
timeCreated: 1723536946
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/UserGeneratedContent/UgcPlayerHintsPage.cs
uploadId: 939664
@@ -0,0 +1,100 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ManualPage("User Generated Content/Setup", title: "UGC Setup")]
public class UgcSetupPage : ManualPageBase
{
private TrembleSyncSettings m_SyncSettings;
private bool IsAddressablesInstalled =>
#if ADDRESSABLES_INSTALLED
true;
#else
false;
#endif
private bool m_IsInstalling;
protected override void OnInit()
{
m_SyncSettings = TrembleSyncSettings.Get();
}
protected override void OnGUI()
{
Experimental();
Text(
"Tremble now supports importing maps at runtime, which allows players to create their",
"own maps in TrenchBroom, and import it into your game themselves."
);
Text(
"You could use something like Unity's UGC package, or Steam Workshop to distribute",
"maps to players (which is up to you to implement). Tremble just needs the map file",
"to load on disk somewhere, and it will import it at runtime."
);
H1("Setup");
Text(
"There are a few things to set up first, before you can use this feature. This page",
"will help you do that."
);
CheckListItem("Install the Addressables package from Unity", m_IsInstalling || IsAddressablesInstalled, "Install", () =>
{
m_IsInstalling = true;
UnityEditor.PackageManager.Client.Add("com.unity.addressables");
});
CheckListItem("Enable the Tremble setting for UGC", m_SyncSettings.AllowStandaloneImport, "Enable", () =>
{
SerializedObject so = new(m_SyncSettings);
so.FindBackedProperty(nameof(m_SyncSettings.AllowStandaloneImport)).boolValue = true;
so.ApplyModifiedPropertiesWithoutUndo();
TrembleEditorAPI.SyncToTrenchBroom();
});
H1("Initialising the system");
{
Text(
"This step will prepare Tremble for loading maps at runtime.",
"Unfortunately, it can lock your game up for about 200ms - so the idea is to do",
"this during your game's startup code, to allow Tremble to initialise",
"without causing your game to hitch during gameplay."
);
Code("await TrembleRuntimeAPI.Initialise();");
}
H1("Loading a map at runtime");
{
Text("Loading a map at runtime couldn't be simpler - simply call the following function:");
Code(
$"{nameof(MapDocument)} map = await {nameof(TrembleRuntimeAPI)}.{nameof(TrembleRuntimeAPI.LoadMapAsync)}(\"C:\\somemap.map\");",
"",
"// Your map is now in the current scene, if loading succeeded.",
"// Check `map.Result` to see if it worked, and the `map.gameObject` is the root",
"// of your new map!",
"Debug.Log($\"Result: {map.Result}\");",
"Debug.Log($\"Loaded map: {map.BspName}\");"
);
H2("Notes");
{
Bullet("You can also provide additional arguments, such as subdivision parameters and smoothing angles.");
Bullet("Tremble respects Application.backgroundLoadingPriority, which you can use to signal how",
"much time you want Unity to dedicate per frame to async loading. Higher priorities will load",
"your map faster, but at the expense of dropping more frames during the load.");
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f5ecfa2702b44c4b9f03bbacef113cfc
timeCreated: 1723448499
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Manual/Pages/UserGeneratedContent/UgcSetupPage.cs
uploadId: 939664