Improving Conditions: A Property Editor

Been there, done that, and got the T-shirt. Think of it as an initiation rite. I wish there was an easy way for the Quest or Inventory Item to scream “I’m not in a Resources folder!!”

Actually, there may be… I might revisit this…

Well during code it’s scream because saying resource’s. Also i ve three ideas for the future one is based in that tutorial, the other is at RPG general , and the last one generic.

One is about a tutorial heavy based in editor coding. I mean improved conditions 96% is about editor creation, right now we have Just a small editor code tutorial. But at that point the editor useful is much more than visual design only.

Second marriage of action system RPG system ( i ve enrol the lesson at udemy but haven’t watch it yet) with the RPG system.

Third Json saving system mostly data base and using Json with multiple challenges. For example how each playthrough have a different save folder and saves be there, saving multiple different things from the same class ( for example i want 2-3 bools, 8 ints etc) or even save classes as whole etc. Maybe even moving into a little server based saving, like I load a game file from a server not from PC and saving become into server’s instead of your PC. That’s all.( about saving could be as part of RPG conversion from the already saving system into Json saving system)

@Brian_Trotter if you wish me I can create new post with that recommendations

Do you mean Shops and Ablities? Because it is directly married to the RPG course series. :slight_smile:

I actually have adapted the Json saving system to Unity’s cloud saving platform. I’m using it in my soon to be released game. I just need to write that tutorial. The Capture/Restore code doesn’t really change, but in SavingSystem there are some changes to how files are saved and code in there cache the data and only write it to the cloud when it’s well and truly stale.

Hi Brian,
Just popping by to say Thank you so much for this!

I hopped on this thread’s train somewhere around 22PM, It’s now 4AM here and I just reached part 4, Already learned SO MUCH, keeping the rest of it for tomorrow.

All throughout this project I keep coming back to find your amazing solutions,
I know I could probably cut some of this work short using ODIN (i still haven’t gotten to learning it),
figured it was worth to look for some golden nuggets here - and they were found in abundance!

Just Wow.
Again, Thank you!

OdinInspector is a fantastic tool, but I’ve always been a strong advocate for learning the Editor tools directly. I’m glad this has been helpful!

1 Like

I mean the 3d person combat (combat) marriage with rpg tutorial

I’m acually working on a quasi-tutorial on merging the Third Person Combat and Traversal course into the RPG course now. It’s not without it’s challenges

what is quasi-tutorial ? sorry for the noob question

It’s not videos, it’s not a fully written drawn out tutorial. Since users should have already taken both the full RPG series and the Third Person series, I’ll just be going over the changes needed to blend the two systems together.

Hey, I know I’m late to the party, but in case you want to know how I implement all of this using OdinInspector I attach my code below:

The rest of the code is based on Part 1 of this forum topic (credits to @Brian_Trotter):
(Also, I haven’t reached the “MinimumTrait” predicate in the course yet (specifically on RPG course part 4), so that is not currently handled)

Sorry if my code is kinda of messed up, still learning :smiley:

Condition.cs

[System.Serializable]
public class Predicate
{
	[SerializeField] private EPredicate predicate;
	[ShowIf("QuestParameters")] [SerializeField] [ValueDropdown("GetScriptableObject")]
	private string questParameter = "";
	[ShowIf("ObjectiveParameters")] [SerializeField] [ValueDropdown("GetObjectiveParameter")]
	private string objectiveParameter = "";
	[ShowIf("InventoryItemParameters")] [SerializeField] [ValueDropdown("GetScriptableObject")]
	private string inventoryItemParameter = "";
	[ShowIf("EquipableItemParameters")] [SerializeField] [ValueDropdown("GetScriptableObject")]
	private string equipableItemParameter = "";
	[ShowIf("StackParameters")] [SerializeField, Range(0, 99)]
	private int stackParameter;
	[ShowIf("LevelParameters")] [SerializeField, Range(0, 99)]
	private int testLevel;
	[SerializeField] private bool negate;

	private bool QuestParameters() => 
		predicate is EPredicate.HasQuest or EPredicate.CompletedQuest or EPredicate.CompletedObjective;
	private bool ObjectiveParameters() => 
		questParameter != null && predicate is EPredicate.CompletedObjective;
	private bool InventoryItemParameters() =>
		predicate is EPredicate.HasItem or EPredicate.HasItems;
	private bool EquipableItemParameters() =>
		predicate is EPredicate.HasItemEquipped;
	private bool StackParameters() => 
		predicate is EPredicate.HasItems;
	private bool LevelParameters() => 
		predicate is EPredicate.HasLevel;

	public bool Check(IEnumerable<IPredicateEvaluator> evaluators)
	{
		string[] parameters =
		{
			questParameter,
			objectiveParameter,
			inventoryItemParameter,
			stackParameter.ToString()
		};
		parameters = parameters.Where(p => p != null && p != "0" && p != "").ToArray();
		foreach (var evaluator in evaluators)
		{
			var result = evaluator.Evaluate(predicate, parameters);
			if (result is null) continue;
			if (result == negate) return false;
		}
		return true;
	}

	private IEnumerable<ValueDropdownItem> GetObjectiveParameter()
	{
		var quest = Quest.GetByID(questParameter);
		if (quest)
		{
			foreach (var objective in quest.GetObjectives())
			{
				yield return new ValueDropdownItem(objective.description, objective.reference);
			}
		}
		else
		{
			yield return new ValueDropdownItem();
		}
	}

	private IEnumerable<ValueDropdownItem> GetScriptableObject()
	{
		switch (predicate)
		{
			case EPredicate.HasQuest or EPredicate.CompletedQuest or EPredicate.CompletedObjective:
				return GetScriptableObjectNamesInFolder("Quest");
			case EPredicate.HasItem or EPredicate.HasItems:
				return GetScriptableObjectNamesInFolder("InventoryItem");
			case EPredicate.HasItemEquipped:
				return GetScriptableObjectNamesInFolder("EquipableItem");
		}
		return GetScriptableObjectNamesInFolder("ScriptableObject");
	}

	private IEnumerable<ValueDropdownItem> GetScriptableObjectNamesInFolder(string assetType)
	{
		return UnityEditor.AssetDatabase
			.FindAssets($"t:{assetType}", new[] { "Assets/Game" })
			.Select(UnityEditor.AssetDatabase.GUIDToAssetPath).Select(GetValueDropdownItem);
		ValueDropdownItem GetValueDropdownItem(string assetPath)
		{
			var scriptableObject = UnityEditor.AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
			return assetType == "ScriptableObject"
				? new ValueDropdownItem(assetPath, scriptableObject)
				: new ValueDropdownItem(
					$"{scriptableObject.name} ({scriptableObject.GetType().Name})",
					((ScriptableObjectWithGuid)scriptableObject).ItemID
				);
		}

ScriptableObjectWithGuid.cs

public abstract class ScriptableObjectWithGuid : ScriptableObject
{
	public abstract string ItemID { get;}
}

Quest.cs

[CreateAssetMenu(menuName = "Quest", fileName = "Quest", order = 0)]
public class Quest : ScriptableObjectWithGuid, ISerializationCallbackReceiver
{
	// ...
	[Tooltip("Auto-generated UUID for saving/loading. Clear this field if you want to generate a new one.")]
	[SerializeField] [ReadOnly] private string itemID;
	// ...
	public override string ItemID => itemID;
	// ...

	public static Quest GetByID(string itemID)
	{
		return Resources.LoadAll<Quest>("").FirstOrDefault(quest => quest.itemID == itemID);
	}
	
	public void OnBeforeSerialize()
	{
		// Generate and save a new UUID if this is blank.
		if (string.IsNullOrWhiteSpace(itemID)) itemID = Guid.NewGuid().ToString();
	}
	public void OnAfterDeserialize()
	{
		// Require by the ISerializationCallbackReceiver but we don't need
		// to do anything with it.
	}
}

InventoryItem.cs

public abstract class InventoryItem : ScriptableObjectWithGuid, ISerializationCallbackReceiver
{
	// ...
	[Tooltip("Auto-generated UUID for saving/loading. Clear this field if you want to generate a new one.")]
	[SerializeField] [ReadOnly]
	private string itemID;
	// ...
	public override string ItemID => itemID;
	// ...
}

Result

2023-12-29_21-17-46

1 Like

Hmmm I have OdinInspector (it was in part of a bundle), but I’ve always tended to avoid using it. Good job making this work with Odin!

1 Like

Brain, Thanks for this. It is a very good improvement to the conditions inspector. Easy to follow and well written.

I’m surprised you’re just seeing this. This is one of the best editor improvements I’ve had in a while :slight_smile: - I wish we had more enum implementations over magic strings like this one to stop us from going nuts because of misspellings

I’m not just seeing this. I have a list of things to do in my project and am taking them one at a time. Yesterday was the time for this one. Now I am starting the merge tutorial.

Ooh nice. It’ll be well worth it. Enjoy the run!

I am enjoying it, but not at a run. It’s more of a long, slow, march.

fair deal my friend :slight_smile:

This is the way! Often the way of learning is similar to the energy shields in Dune… if you attack (study) too fast, no damage (learning) happens. A slow attack (study) gets through (and you learn).

2 Likes

I’m enjoying it to be honest, and I got a little too excited after I saw “Projectiles” being the next topic to address :smiley: (I hope banking and its long-term performance are on that list too (last thing I remember, it used to significantly slow down the game’s performance after a short while of fast withdrawal and deposits). It’s an incredibly good concept to let go of to be honest)

Brian, you are a champ! I had trouble with the predicates that I wasn’t able to solve. Think it got messed up somewhere when mixing ANDs and ORs, but using the interface you created solved it all. Now everything is as it should be. Thank you so much for saving me without even trying!

Privacy & Terms