From 274862e2530b7651fcd19922bfef7ad8b756beff Mon Sep 17 00:00:00 2001 From: xhwanlan <53598943+xhwanlan@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:49:01 +0800 Subject: [PATCH 1/7] fix: typos --- en/manual/nuget/create-packages.md | 4 ++-- en/manual/troubleshooting/logging.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/en/manual/nuget/create-packages.md b/en/manual/nuget/create-packages.md index 4e6ec4a69..8643b8e53 100644 --- a/en/manual/nuget/create-packages.md +++ b/en/manual/nuget/create-packages.md @@ -10,8 +10,8 @@ First of all, after saving all your changes, open your project with Visual Studi ![Open project in Visual Studio](../game-studio/media/open-project-in-visual-studio.png) A few things to look out for: -* Delete unecessary assets (i.e. GameSettings, etc...) -* Delete unecessary `PackageReference` +* Delete unnecessary assets (i.e. GameSettings, etc...) +* Delete unnecessary `PackageReference` ## Optional: Setup Package properties diff --git a/en/manual/troubleshooting/logging.md b/en/manual/troubleshooting/logging.md index a5dffa62c..d335eca46 100644 --- a/en/manual/troubleshooting/logging.md +++ b/en/manual/troubleshooting/logging.md @@ -91,7 +91,7 @@ This creates a file in the Debug folder of your project (eg *MyGame\MyGame\Bin\W ## Example script -The following script checks that the texture `MyTexture` is loaded. When the texture loads, the log displays a debug message (`Log.Error`). If it doesn't load, the log records an error message (`Log.Debug`). +The following script checks that the texture `MyTexture` is loaded. When the texture loads, the log displays a debug message (`Log.Debug`). If it doesn't load, the log records an error message (`Log.Error`). ```cs using System.Linq; From 8da7463ce83f0160dfa15c50f22054770e99d0fe Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 22 Jan 2025 00:02:30 +0100 Subject: [PATCH 2/7] feat: Best Practices --- en/manual/scripts/best-practice.md | 187 +++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 en/manual/scripts/best-practice.md diff --git a/en/manual/scripts/best-practice.md b/en/manual/scripts/best-practice.md new file mode 100644 index 000000000..a677c0388 --- /dev/null +++ b/en/manual/scripts/best-practice.md @@ -0,0 +1,187 @@ +# TODO: ADD EXAMPLES OF HOW TO AVOID ISSUES I AM RAISING + +# Best Practices + +Tips to build a robust and maintainable CodeBase for your Stride Project + +## Don't rush big systems + +Before starting on larger systems, make sure it would integrate with the rest of the existing systems, would it play well with saving and reloading, with the multiplayer architecture, when the game is paused, would it leak into other scenes ... + +Having this in mind ensures you avoid bugs and rewrites down the line because you hadn't thought of the above. + +## Implement custom assets + +Some of the systems you will build make far more sense as assets rather than entities, consider making them an asset when any of the following is true: +- It survives between multiple scenes +- It is read only +- It is not part of the definition of an entity, doesn't exist within your game world +- It should be editable within the editor, by a designer + +Examples of such are: +- Player Input Configuration, defining actions in the source, assigning buttons in editor, saving and loading to disk when initializing the game +- Balance settings, tweaking constants and formulas from the editor to improve iteration when testing your game +- Mission/quest, referencing quests inside of components to unlock spots in game when they are completed, giving the ability for your designer to set those up +- Loot tables, having a list of `UrlReference` with a probability of drop to easily re-use across multiple mobs +- As an all-purpose robust 'key' type, see ## Strings as keys, testing strings for equality + +### Do not mutate Assets +To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state doesn't leak through them when loading a new session, game mode, or whatever else. +For example, let's say you have an Axe Asset which has a list of modifier, you save the game, progress for a bit then add a modifier, but end up reloading ot the previous save, the modifier will carry over to that previous game state. + +### Scene quirk +The default scene the game spawns for you is the instance stored in the content manager, when running the game you mutate that very instance, meaning that if you want to retrieve the scene in its initial state, you must force the content manager to unload it, and then reload it. +This makes it a bit counterintuitive when you just want to re-spawn the current scene to roll back your changes. + +## Statics, singletons and other global-like writes +We strongly advise you to make sure that the entirety of your game's state is implemented as instance properties on components inside the root scene's hierarchy. Avoid static properties and static objects. + +This is essential to reduce bugs that come in when implementing all encompassing systems, like the saving system, or the multiplayer layer. Those systems can then be implemented with the expectation that everything they may care about is within the root scene, they can replace this root scene and expect the game state to be completely reset, they can serialize or monitor those entities for changes, etc. +If you do not follow this, part of your game's state will leak between play sessions, creating issues that are really hard to reproduce. + +Some systems were this wouldn't make sense, in that case see #Singletons +- The functionality and variables saved by such systems persist for the duration of the program, or across all sessions + - Saving, loading systems, meta-progression trackers, like achievements and roguelite progression +- The system is read-only + - Multiplayer server browser or matchmaking back-end. Once you are connected it's a different story though, now you must hold a bunch of states that are only valid to this session, it should not leak to the rest of the program, and so is best left as a component on an entity in the scene. + +Singletons can be entirely replaced by using your game's `ServiceRegistry` which can be accessed from any `ScriptComponent`; +```cs +public class MySingleton : IService +{ + public static IService NewInstance(IServiceRegistry r) => new MySingleton(); + + public void DoStuff(){} +} + +// You may now access this singleton from any of your components: +public override void Update() +{ + Services.GetOrCreate().DoStuff(); +} + +// If you need this singleton to live inside the scene, you just have to add a couple of calls +public class MySingleton : SyncScript, IService +{ + public override void Start() + { + var instance = Services.GetService(); + if (instance != this) + Entity.Remove(this); // Makes sure we remove this instance if another exists already + else if (instance is null) + Services.AddService(this); // Register this as a service if it was added through the scene instead of through Services.GetOrCreate() + } + + public override void Cancel() + { + base.Cancel(); + Services.RemoveService(this); // Remove this one from the services when this component is removed from the scene + } + + public override void Update() { } + + public static IService NewInstance(IServiceRegistry r) + { + var instance = new MySingleton(); + r.GetService().SceneInstance.RootScene.Entities.Add(new Entity{ instance }); + return instance; + } +} +``` + +## Strings as Keys/Identifiers +This is a very popular anti-pattern, strings that are used as keys or identifiers shows up all over the place, here's a short example describing such an usage: +The quest you're implementing requires the player to gather 10 bear arses, your check for that is to loop through the list of items the player has and check that the item's name match "bear arses". + +Here's a couple reasons why this is a bad idea: +- Hard to maintain; if your item ends up changing names because bears are banned in Freedonia, your checks will silently fail +- Fragile, your string isn't checked against anything, typos - "bear ass" wouldn't work, careful with leading or trailing whitespaces ... +- Obtuse, designers not aware of how your system work may not understand what they should input there +- No uniqueness, if your system expects those to be unique, i.e.: you are looking for a very specific "bear arse", but you multiple different bear arses +- Hard to monitor, strings are too ubiquitous, hard to quickly retrieve all instances/usages of those to build a database for different purposes, like translations, validations ... + +All of this will inevitably lead to bugs, or additional work to avoid them, time you could definitely use to take care of other, more fun parts of your game. + +There are a couple ways to avoid this, one of the more robust ones is to rely on assets themselves; SEE # CUSTOM ASSETS +```cs +public class Item { } + +// Now in your component ... +public Item ItemToCheck; +public int AmountRequired = 10; + +public bool HasTheItem() +{ + if (Inventory.TryGetAmount(ItemToCheck, out var amount) && amount >= AmountRequired) + return true; + return false; +} + +``` +Assets are unique, they have an identity since they are reference types, they are +This is too fragile and obtuse, we can use an asset for this instead. Create a new asset type, it doesn't need any property. Use that asset's type as input for your custom condition and your unique trigger. +- It doesn't break if we end up changing the name. + - Particularly useful for persistence as we could now track them between versions and check if we introduce a breaking change to the player's progression save +- We can see straight from the editor where it is used through the reference window when selecting the asset. + - Meaning that it is now even more straightforward to remove it from the game if you end up not needing it, go through each reference and replace it appropriately +- From a documentation standpoint, we don't need to keep a document going over each of them, we can just look at the directory were they are stored in the editor. + + +## Avoid writing extension methods for access shortcuts + +`static Entity GetFirstChild(this Entity Entity) => Entity.Transform.Children[0].Entity` + +It's a double-edged: +- Make sure that accessing what you are hiding is never error prone, even more so if the name of the method does not make that obvious. You may be reducing the time wasted from typing, but you could very well increase the time you would take to debug it when it does error out. +- It may very well be a slippery slope to introduce even more shortcuts to other properties or methods of the object you are presenting +- You might be reducing the skill floor required for users not accustomed to the API, but you're also hindering their growth as they now rely on this extension method, and likely are not aware of were the rest of the depth that that object provides +- It might imply to the reader that your shortcut is different to the + + +This is a slippery slope, as once you start creating this shortcut, others may see this extension and request other shortcuts + + +## static EventKeys + +Do not use them, they don't generate useful stacktraces when exceptions are thrown, making debugging harder than necessary. They do not provide direct mapping between callers and callees, +Their lifetime is very complex to reason about, i.e.: we recommend making them static, but messages only make sense in the context of a scene, not across the whole process. So, if for example, some system signals the key, the whole scene unloads and reloads, the signal can still be received by systems that just came into this new scene. + +## Object definition, ownership, a hierarchical idea + +`.Get()` must be used only for conditional logic, if the caller expects it to exist, it should be part of some definition + +Yes: +``` +EntityA { components for this item } +``` +No: +``` +EntityA + - EntityA { components for this item } + - ColGroup0_EntityA { some other components also related to this item } +``` + +Destroying an object +The above idea is helpful as it is now very obvious how your object should be destroyed when you need to do so, pick the entity your 'definition' component is on and simply remove it from the scene + +One caveat that you may still fall into with this idea, is if you have another component that must be notified of when this entity is destroyed. +This can be resolved in two ways +- If that component requires this other component, you should consider merging them, strong dependencies like so imply that you should design those in some other way +- This object should be the only one aware of the former's existence, that way, you ensure that the codepath must go through that object to remove the former object + + + + +# Definition + + +# Destroying + +Destroying or removing an entity from the world should always be as simple as `Entity.Scene = null`, or `MyComponent.Entity.Scene = null`. +Rely on overriding your script's `Cancel` method to clean things you might have set up. + +Upholding this idea makes managing your objects' very straightforward, anyone would intuitively rely on scene removal for any entity or component to stop them existing/affecting the game. And with how we laid out how objects are defined through ECS, we intuitively know which entity we must remove for a component to behave + + +## Class imply identity, struct imply data + From f18eeeb75bd1292aaceae763e3d12a6f20593a8c Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 22 Jan 2025 01:06:44 +0100 Subject: [PATCH 3/7] Update index and toc --- en/manual/scripts/index.md | 3 ++- en/manual/toc.yml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/en/manual/scripts/index.md b/en/manual/scripts/index.md index 181485e8d..7958cb167 100644 --- a/en/manual/scripts/index.md +++ b/en/manual/scripts/index.md @@ -41,4 +41,5 @@ You can still use standard C# classes in Stride, but these aren't called scripts * [Preprocessor variables](preprocessor-variables.md) * [Create a model from code](create-a-model-from-code.md) * [Create Gizmos for your components](gizmos.md) -* [Create Custom Assets](custom-assets.md) \ No newline at end of file +* [Create Custom Assets](custom-assets.md) +* [Best Practice](best-practice.md) \ No newline at end of file diff --git a/en/manual/toc.yml b/en/manual/toc.yml index 4abc8d15b..9ec03c93c 100644 --- a/en/manual/toc.yml +++ b/en/manual/toc.yml @@ -540,6 +540,8 @@ items: href: scripts/gizmos.md - name: Create Custom Assets href: scripts/custom-assets.md + - name: Best Practice + href: scripts/best-practice.md - name: Sprites href: sprites/index.md From d80f4ab3b6f2edb034a8c1d5108199a2bd61f5a4 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 22 Jan 2025 01:07:13 +0100 Subject: [PATCH 4/7] Small fixes + extension methods --- en/manual/scripts/best-practice.md | 67 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/en/manual/scripts/best-practice.md b/en/manual/scripts/best-practice.md index a677c0388..54426d562 100644 --- a/en/manual/scripts/best-practice.md +++ b/en/manual/scripts/best-practice.md @@ -23,10 +23,10 @@ Examples of such are: - Balance settings, tweaking constants and formulas from the editor to improve iteration when testing your game - Mission/quest, referencing quests inside of components to unlock spots in game when they are completed, giving the ability for your designer to set those up - Loot tables, having a list of `UrlReference` with a probability of drop to easily re-use across multiple mobs -- As an all-purpose robust 'key' type, see ## Strings as keys, testing strings for equality +- As an all-purpose robust 'key' or 'identifier' type, see [this section](#strings-as-keys-or-identifiers) ### Do not mutate Assets -To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state doesn't leak through them when loading a new session, game mode, or whatever else. +To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state does not leak through them when loading a new session, game mode, or whatever else. For example, let's say you have an Axe Asset which has a list of modifier, you save the game, progress for a bit then add a modifier, but end up reloading ot the previous save, the modifier will carry over to that previous game state. ### Scene quirk @@ -89,20 +89,20 @@ public class MySingleton : SyncScript, IService } ``` -## Strings as Keys/Identifiers -This is a very popular anti-pattern, strings that are used as keys or identifiers shows up all over the place, here's a short example describing such an usage: -The quest you're implementing requires the player to gather 10 bear arses, your check for that is to loop through the list of items the player has and check that the item's name match "bear arses". +## Strings as Keys or Identifiers +This is a very popular anti-pattern, strings that are used as keys or identifiers shows up all over the place, here's a short example describing such a usage: +The quest you're implementing requires the player to gather 10 bear arses, your check for that is to loop through the list of items the player has and check that the item's name match `bear arses`. -Here's a couple reasons why this is a bad idea: +Here's a couple of reasons why this is a bad idea: - Hard to maintain; if your item ends up changing names because bears are banned in Freedonia, your checks will silently fail -- Fragile, your string isn't checked against anything, typos - "bear ass" wouldn't work, careful with leading or trailing whitespaces ... -- Obtuse, designers not aware of how your system work may not understand what they should input there -- No uniqueness, if your system expects those to be unique, i.e.: you are looking for a very specific "bear arse", but you multiple different bear arses -- Hard to monitor, strings are too ubiquitous, hard to quickly retrieve all instances/usages of those to build a database for different purposes, like translations, validations ... +- Fragile; your string isn't checked against anything, typos - `bear ass` wouldn't work, careful with leading or trailing whitespaces ... +- Obtuse; designers not aware of how your system work may not understand what they should input there +- Non-explicit uniqueness requirement, if your system expects those to be unique, i.e.: you are looking for a very specific "bear arse"; but you could have multiple different items all named `bear arse` +- Hard to keep track of; strings are too ubiquitous, hard to quickly retrieve all instances/usages of those to build a database for different purposes, like translations, validations ... -All of this will inevitably lead to bugs, or additional work to avoid them, time you could definitely use to take care of other, more fun parts of your game. +All of this will inevitably lead to bugs, or additional work to avoid them - time you could definitely use to take care of other, more fun parts of your game. -There are a couple ways to avoid this, one of the more robust ones is to rely on assets themselves; SEE # CUSTOM ASSETS +There are a couple ways to avoid this, one of the more robust ones is to rely on assets themselves; see [custom assets](custom-assets.md) ```cs public class Item { } @@ -112,34 +112,33 @@ public int AmountRequired = 10; public bool HasTheItem() { - if (Inventory.TryGetAmount(ItemToCheck, out var amount) && amount >= AmountRequired) - return true; - return false; + if (Inventory.TryGetAmount(ItemToCheck, out var amount) && amount >= AmountRequired) + return true; + return false; } - ``` -Assets are unique, they have an identity since they are reference types, they are -This is too fragile and obtuse, we can use an asset for this instead. Create a new asset type, it doesn't need any property. Use that asset's type as input for your custom condition and your unique trigger. -- It doesn't break if we end up changing the name. - - Particularly useful for persistence as we could now track them between versions and check if we introduce a breaking change to the player's progression save -- We can see straight from the editor where it is used through the reference window when selecting the asset. - - Meaning that it is now even more straightforward to remove it from the game if you end up not needing it, go through each reference and replace it appropriately -- From a documentation standpoint, we don't need to keep a document going over each of them, we can just look at the directory were they are stored in the editor. - +- Robust; you can change its name and its path, as long as you do not delete it or change its internal id all components referencing it will keep that reference. +- Easy to use and understand; If a component requires a specific asset you don't have an infinite amount of possibilities, you can either set it to an existing one or create a new one. It's far more foolproof too now that typos are out of the equation. +- Easy to keep track of; each type has a unique extension which you can search for in your file explorer, they exist on disk, and so can be organized into the same directory. + - The editor's reference window lists all assets that use the selection, this greatly helps when you need to swap the identifier for another or remove it altogether, just go through all the assets that refer to it. + - you won't need to keep a document going over each identifier you might have in game, one just has to look at the directory were they are stored in the editor. +- Easy to extend; your identifier can now be more than just that, you can attach properties to it, perhaps a description to keep more information about this key. ## Avoid writing extension methods for access shortcuts -`static Entity GetFirstChild(this Entity Entity) => Entity.Transform.Children[0].Entity` - -It's a double-edged: -- Make sure that accessing what you are hiding is never error prone, even more so if the name of the method does not make that obvious. You may be reducing the time wasted from typing, but you could very well increase the time you would take to debug it when it does error out. -- It may very well be a slippery slope to introduce even more shortcuts to other properties or methods of the object you are presenting -- You might be reducing the skill floor required for users not accustomed to the API, but you're also hindering their growth as they now rely on this extension method, and likely are not aware of were the rest of the depth that that object provides -- It might imply to the reader that your shortcut is different to the - - -This is a slippery slope, as once you start creating this shortcut, others may see this extension and request other shortcuts +This is specifically referring to methods of this kind: +``` +static Entity GetFirstChild(this Entity Entity) => Entity.Transform.Children[0].Entity; +// Or +static void AddAsFirstChild(this Entity Entity, Entity entity) => Entity.Transform.Children.Insert(0, entity); +``` +It's a double-edged sword: +- You are reducing the skill floor required for users not accustomed to the API, but you're also hindering their growth as they now rely on your shortcut instead of seeking it for themselves, making them aware of concepts and objects neighboring that one, giving them a clearer view of how all the objects fit together. What if they need to access all children, from this extension method they would not be aware that the transform component stores them, that he could just access it directly for that. +- Make sure that accessing what you are hiding is never error-prone, even more so if the name of the method does not make that obvious. You may be reducing the time wasted from typing, but you could very well increase the time you would take to debug it when it does create issues. +- It may very well be a slippery slope to introduce even more shortcuts to other properties or methods of the object you are presenting, how about creating an extension for the second child of the entity, the third ... +- Polluting intellisense; in most cases this is a non-issue, but collection types are a prime example of this. Discoverability for extension methods through intellisense is nigh-on-impossible, there are just far too many extension methods introduced by linq. +- It might imply to the user that your shortcut is somehow different compared to accessing it without the shortcut. ## static EventKeys From 18189a8de1bee954f8900dce4937aedca7d6be7c Mon Sep 17 00:00:00 2001 From: Eideren Date: Sun, 26 Jan 2025 23:40:34 +0100 Subject: [PATCH 5/7] progress --- en/manual/scripts/best-practice.md | 101 ++++++++++++++++++----------- 1 file changed, 64 insertions(+), 37 deletions(-) diff --git a/en/manual/scripts/best-practice.md b/en/manual/scripts/best-practice.md index 54426d562..c1884016a 100644 --- a/en/manual/scripts/best-practice.md +++ b/en/manual/scripts/best-practice.md @@ -1,4 +1,5 @@ # TODO: ADD EXAMPLES OF HOW TO AVOID ISSUES I AM RAISING +# TODO: DO NOT FIGHT ECS, WORK WITH IT, IF YOU REALLY WANT TO DO WITHOUT, MAKE SURE THAT YOUR OTHER SYSTEM IS FINISHED BEFORE WRITING ANY LOGIC FOR IT # Best Practices @@ -8,45 +9,44 @@ Tips to build a robust and maintainable CodeBase for your Stride Project Before starting on larger systems, make sure it would integrate with the rest of the existing systems, would it play well with saving and reloading, with the multiplayer architecture, when the game is paused, would it leak into other scenes ... -Having this in mind ensures you avoid bugs and rewrites down the line because you hadn't thought of the above. +Having this in mind ensures you won't write yourself into a corner, creating a system that needs to be patched up to work properly within your project, increasing your project's technical debt. -## Implement custom assets +A trap you may fall into is to then over design your systems, supporting features that your game will never need, convincing yourself that you could re-use that system for another game, that you could share or sell it. +The vast majority of systems in games are purpose built for that game, your next game will require other features you could not support, let alone predict. You will also acquire a significant amount of experience working on this one, seeing issues it had and wanting to rewrite a better one. -Some of the systems you will build make far more sense as assets rather than entities, consider making them an asset when any of the following is true: -- It survives between multiple scenes -- It is read only -- It is not part of the definition of an entity, doesn't exist within your game world -- It should be editable within the editor, by a designer +### Figuring out your system's lifetime -Examples of such are: -- Player Input Configuration, defining actions in the source, assigning buttons in editor, saving and loading to disk when initializing the game -- Balance settings, tweaking constants and formulas from the editor to improve iteration when testing your game -- Mission/quest, referencing quests inside of components to unlock spots in game when they are completed, giving the ability for your designer to set those up -- Loot tables, having a list of `UrlReference` with a probability of drop to easily re-use across multiple mobs -- As an all-purpose robust 'key' or 'identifier' type, see [this section](#strings-as-keys-or-identifiers) +What is the scope of the system you're writing; +- Should the same instance be used throughout the entire lifetime of the application ? +- Only while playing a game session ? +- Only for the duration of the currently loaded game session ? +- Within a single map ? +- For a specific game mode ? +- ... -### Do not mutate Assets -To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state does not leak through them when loading a new session, game mode, or whatever else. -For example, let's say you have an Axe Asset which has a list of modifier, you save the game, progress for a bit then add a modifier, but end up reloading ot the previous save, the modifier will carry over to that previous game state. +This will set some expectation as to where the system you're building should reside, how it interacts with other systems, and when it should be started and disposed ... -### Scene quirk -The default scene the game spawns for you is the instance stored in the content manager, when running the game you mutate that very instance, meaning that if you want to retrieve the scene in its initial state, you must force the content manager to unload it, and then reload it. -This makes it a bit counterintuitive when you just want to re-spawn the current scene to roll back your changes. +Some entry and exit points to manage your systems' lifetime: +- On demand by getting and setting it in the Services +- System as a [singleton script](#Statics-singletons-and-other-global-like-writes) with the `Start()` and `Cancel()` +- Through your game's `BeginRun()` and `EndRun()` +- As a component processor when associated component types are added to the scene ## Statics, singletons and other global-like writes We strongly advise you to make sure that the entirety of your game's state is implemented as instance properties on components inside the root scene's hierarchy. Avoid static properties and static objects. -This is essential to reduce bugs that come in when implementing all encompassing systems, like the saving system, or the multiplayer layer. Those systems can then be implemented with the expectation that everything they may care about is within the root scene, they can replace this root scene and expect the game state to be completely reset, they can serialize or monitor those entities for changes, etc. -If you do not follow this, part of your game's state will leak between play sessions, creating issues that are really hard to reproduce. +This is essential to reduce bugs that come in when implementing systems that manage the game's state, like the saving system, or the multiplayer layer. +Those systems can then be implemented with the expectation that everything they may care about is within the root scene, they can replace this root scene and expect the game state to be completely reset, they can serialize or monitor those entities for changes, etc. without the risk of your game's state leaking between play sessions and creating issues that are really hard to reproduce. -Some systems were this wouldn't make sense, in that case see #Singletons +Some systems may not make sense as part of the scene when: - The functionality and variables saved by such systems persist for the duration of the program, or across all sessions - - Saving, loading systems, meta-progression trackers, like achievements and roguelite progression + - Saving, loading systems, meta-progression trackers or achievements - The system is read-only - - Multiplayer server browser or matchmaking back-end. Once you are connected it's a different story though, now you must hold a bunch of states that are only valid to this session, it should not leak to the rest of the program, and so is best left as a component on an entity in the scene. + - Multiplayer server browser or matchmaking back-end. Once connected to a session it's a different story though, now you must hold a bunch of states that are only valid to this session, it should not leak to the rest of the program, and so is best left as a component on an entity in the scene. -Singletons can be entirely replaced by using your game's `ServiceRegistry` which can be accessed from any `ScriptComponent`; +Those restrictions do not prevent you from using the singleton pattern, you can use the `ServiceRegistry` which can be accessed from any `ScriptComponent`; ```cs +// Here's a basic singleton class public class MySingleton : IService { public static IService NewInstance(IServiceRegistry r) => new MySingleton(); @@ -54,13 +54,13 @@ public class MySingleton : IService public void DoStuff(){} } -// You may now access this singleton from any of your components: +// You can create and get this singleton from any of your components: public override void Update() { Services.GetOrCreate().DoStuff(); } -// If you need this singleton to live inside the scene, you just have to add a couple of calls +// Here's a singleton implementation when you need its lifetime to be limited to the scene's: public class MySingleton : SyncScript, IService { public override void Start() @@ -89,6 +89,29 @@ public class MySingleton : SyncScript, IService } ``` +## Implement custom assets + +Some of the systems you will build make far more sense as assets rather than entities, consider making them an asset when any of the following is true: +- It survives between multiple scenes +- It is read only +- It is not part of the definition of an entity, doesn't exist within your game world +- It should be editable within the editor, by a designer + +Examples of such are: +- Player Input Configuration, defining actions in the source, assigning buttons in editor, saving and loading to disk when initializing the game +- Balance settings, tweaking constants and formulas from the editor to improve iteration when testing your game +- Mission/quest, referencing quests inside of components to unlock spots in game when they are completed, giving the ability for your designer to set those up +- Loot tables, having a list of `UrlReference` with a probability of drop to easily re-use across multiple mobs +- As an all-purpose robust 'key' or 'identifier' type, see [this section](#strings-as-keys-or-identifiers) + +### Do not mutate Assets +To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state does not leak through them when loading a new session, game mode, or whatever else. +For example, let's say you have an Axe Asset which has a list of modifier, you save the game, progress for a bit then add a modifier, but end up reloading ot the previous save, the modifier will carry over to that previous game state. + +### Scene quirks +The default scene the game spawns for you is the instance stored in the content manager, when running the game you mutate that very instance, meaning that if you want to retrieve the scene in its initial state, you must force the content manager to unload it, and then reload it. +This makes it a bit counterintuitive when you just want to re-spawn the current scene to roll back your changes. + ## Strings as Keys or Identifiers This is a very popular anti-pattern, strings that are used as keys or identifiers shows up all over the place, here's a short example describing such a usage: The quest you're implementing requires the player to gather 10 bear arses, your check for that is to loop through the list of items the player has and check that the item's name match `bear arses`. @@ -107,7 +130,7 @@ There are a couple ways to avoid this, one of the more robust ones is to rely on public class Item { } // Now in your component ... -public Item ItemToCheck; +public Item ItemToCheck; // You would assign this reference in the editor public int AmountRequired = 10; public bool HasTheItem() @@ -117,11 +140,11 @@ public bool HasTheItem() return false; } ``` -- Robust; you can change its name and its path, as long as you do not delete it or change its internal id all components referencing it will keep that reference. -- Easy to use and understand; If a component requires a specific asset you don't have an infinite amount of possibilities, you can either set it to an existing one or create a new one. It's far more foolproof too now that typos are out of the equation. +- Robust; you can change its name and its path, as long as you do not delete it or change its internal id, all components referencing it will keep that reference. +- Easy to use and understand; If a component requires a specific asset, you don't have an infinite amount of possibilities, you can either set it to an existing one or create a new one. It's far more foolproof too now that typos are out of the equation. - Easy to keep track of; each type has a unique extension which you can search for in your file explorer, they exist on disk, and so can be organized into the same directory. - - The editor's reference window lists all assets that use the selection, this greatly helps when you need to swap the identifier for another or remove it altogether, just go through all the assets that refer to it. - - you won't need to keep a document going over each identifier you might have in game, one just has to look at the directory were they are stored in the editor. + - The editor's reference window lists all assets that use the selection, this greatly helps when you need to swap the identifier for another or remove it altogether, just go through all the assets that refer to it. + - you won't need to keep a document going over each identifier you might have in game, one just has to look at the directory were they are stored in the editor. - Easy to extend; your identifier can now be more than just that, you can attach properties to it, perhaps a description to keep more information about this key. ## Avoid writing extension methods for access shortcuts @@ -134,16 +157,20 @@ static void AddAsFirstChild(this Entity Entity, Entity entity) => Entity.Transfo ``` It's a double-edged sword: -- You are reducing the skill floor required for users not accustomed to the API, but you're also hindering their growth as they now rely on your shortcut instead of seeking it for themselves, making them aware of concepts and objects neighboring that one, giving them a clearer view of how all the objects fit together. What if they need to access all children, from this extension method they would not be aware that the transform component stores them, that he could just access it directly for that. +- You are reducing the skill floor required for users not accustomed to the API, but you're also hindering their growth as they now rely on your shortcut instead of discovering the API for themselves, making them aware of concepts and objects neighboring that one, giving them a clearer view of how all the objects fit together. What if they need to access all children, from this extension method they would not be aware that the transform component stores them, that they could access it directly for that. - Make sure that accessing what you are hiding is never error-prone, even more so if the name of the method does not make that obvious. You may be reducing the time wasted from typing, but you could very well increase the time you would take to debug it when it does create issues. - It may very well be a slippery slope to introduce even more shortcuts to other properties or methods of the object you are presenting, how about creating an extension for the second child of the entity, the third ... - Polluting intellisense; in most cases this is a non-issue, but collection types are a prime example of this. Discoverability for extension methods through intellisense is nigh-on-impossible, there are just far too many extension methods introduced by linq. -- It might imply to the user that your shortcut is somehow different compared to accessing it without the shortcut. +- It might imply to the user that your shortcut is somehow different from the source. + +## Avoid EventKeys, async and other systems with large levels of indirection when mutating the game state +Event keys and async methods carry a lot of implicit complexity as they may not complete when signaled/called. When the async resumed/the event key is received, the game may not be in a state where the logic you run is still valid. Some entities might have been removed from the scene, the inventory might no longer hold the item, the player character may be incapacitated ... + +This quirk also means that their execution are not part of their callers' stack, making debugging issues with them far harder to figure out. -## static EventKeys +Their lifetime is also far harder to reason about as EventKeys will carry the signal even if the scene was replaced in the meantime, while async will continue running when running outside your AsyncScript's `Execute()` -Do not use them, they don't generate useful stacktraces when exceptions are thrown, making debugging harder than necessary. They do not provide direct mapping between callers and callees, -Their lifetime is very complex to reason about, i.e.: we recommend making them static, but messages only make sense in the context of a scene, not across the whole process. So, if for example, some system signals the key, the whole scene unloads and reloads, the signal can still be received by systems that just came into this new scene. +Prefer using c# events instead of EventKeys, and restructure your async into synchronous calls if you can avoid introducing any latency, otherwise, try to avoid touching the game state if at all possible. ## Object definition, ownership, a hierarchical idea From 8c7ce93f61700522c463b91846bda1ead9f5b785 Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 30 Jan 2025 00:27:25 +0100 Subject: [PATCH 6/7] Finish and add links from other related pages --- en/manual/scripts/best-practice.md | 93 ++++++++++++------- en/manual/scripts/create-a-script.md | 1 + en/manual/scripts/custom-assets.md | 7 +- en/manual/scripts/events.md | 4 + .../stride-for-godot-developers/index.md | 7 +- .../stride-for-unity-developers/index.md | 4 + 6 files changed, 78 insertions(+), 38 deletions(-) diff --git a/en/manual/scripts/best-practice.md b/en/manual/scripts/best-practice.md index c1884016a..c3115d37a 100644 --- a/en/manual/scripts/best-practice.md +++ b/en/manual/scripts/best-practice.md @@ -147,6 +147,25 @@ public bool HasTheItem() - you won't need to keep a document going over each identifier you might have in game, one just has to look at the directory were they are stored in the editor. - Easy to extend; your identifier can now be more than just that, you can attach properties to it, perhaps a description to keep more information about this key. +## Avoid EventKeys, async and other systems with large levels of indirection when mutating the game state +Event keys and async methods carry a lot of implicit complexity as they may not complete when signaled/called. When the async resumed/the event key is received, the game may not be in a state where the logic you run is still valid. Some entities might have been removed from the scene, the inventory might no longer hold the item, the player character may be incapacitated ... + +This quirk also means that their execution are not part of their callers' stack, making debugging issues with them far harder to figure out. + +Their lifetime is also far harder to reason about as EventKeys will carry the signal even if the scene was replaced in the meantime, while async will continue running when running outside your AsyncScript's `Execute()` + +Alternatives to EventKeys +- C# events, although this requires the receivers to have a direct reference to the sender +- Components with an interface bound to a [Flexible processors](../engine/entity-component-system/flexible-processing.md). Add the processor to the service registry, call some method which goes through and call each one of the components implementing the interface of that processor + +Alternatives to async +- Restructure your async into a synchronous one ... obviously ! +- If you can't avoid using async ... +- Don't touch the game state, just take some input, spit out an output that gets read by a `SyncScript` +- Ensure you always leave the game state in a valid state before awaiting, and after awaiting check that it is still in a state were continuing the async method makes sense. I.e.: are we suddenly back on the main menu ?! + +You may notice that those two last ones could require a ton of additional logic to support properly, this is an indication that your logic should be rethought - you're writing yourself into a corner + ## Avoid writing extension methods for access shortcuts This is specifically referring to methods of this kind: @@ -163,51 +182,53 @@ It's a double-edged sword: - Polluting intellisense; in most cases this is a non-issue, but collection types are a prime example of this. Discoverability for extension methods through intellisense is nigh-on-impossible, there are just far too many extension methods introduced by linq. - It might imply to the user that your shortcut is somehow different from the source. -## Avoid EventKeys, async and other systems with large levels of indirection when mutating the game state -Event keys and async methods carry a lot of implicit complexity as they may not complete when signaled/called. When the async resumed/the event key is received, the game may not be in a state where the logic you run is still valid. Some entities might have been removed from the scene, the inventory might no longer hold the item, the player character may be incapacitated ... +## Entity and components' lifetime -This quirk also means that their execution are not part of their callers' stack, making debugging issues with them far harder to figure out. +One unexpected quirk of Stride is that components and entities are expected to survive across any number of removal and re-insertion into the scene. Those objects are never truly 'destroyed', they are treated like any other c# object, they either exist or are out of scope. -Their lifetime is also far harder to reason about as EventKeys will carry the signal even if the scene was replaced in the meantime, while async will continue running when running outside your AsyncScript's `Execute()` +Make sure that your components adhere to this rule by rolling back any effects introduced in `Start()` through `Cancel()` +This quirk provides a couple of nice benefits, a major one is that you can temporarily remove components, entities and even scenes from your game and re-introduce them whenever you need without any loss of data or complex serialization steps. -Prefer using c# events instead of EventKeys, and restructure your async into synchronous calls if you can avoid introducing any latency, otherwise, try to avoid touching the game state if at all possible. +This also means that you should avoid writing any custom 'destroy' function to ensure that any part of the engine at any time can simply remove the entity from the scene and rely on your implementation of `Cancel()` to take care of anything that should occur when 'destroyed'. -## Object definition, ownership, a hierarchical idea +### Usage of Get -`.Get()` must be used only for conditional logic, if the caller expects it to exist, it should be part of some definition +When using `Get` ask yourself whether the function would fail to operate if that call were to return null, if that is the case, then your function is dependent on that component existing on that entity. +This is a hard dependency, you should do everything you can to notify the rest of your codebase and designers using the editor that it this component is a requirement to avoid wasting time debugging issues related to it. -Yes: -``` -EntityA { components for this item } -``` -No: -``` -EntityA - - EntityA { components for this item } - - ColGroup0_EntityA { some other components also related to this item } +There are a couple of ways to do so, here we simply add the component directly as a parameter to the function: +```cs +// From +public void MyFunction(Entity entity) +{ + entity.Get().DoSomething(); +} +// To +public void MyFunction(MyComponent component) +{ + component.DoSomething(); +} ``` +And here we add this component as a property to set in the editor: +```cs +// From +public void MyFunction() +{ + Entity.Get().DoSomething(); +} -Destroying an object -The above idea is helpful as it is now very obvious how your object should be destroyed when you need to do so, pick the entity your 'definition' component is on and simply remove it from the scene - -One caveat that you may still fall into with this idea, is if you have another component that must be notified of when this entity is destroyed. -This can be resolved in two ways -- If that component requires this other component, you should consider merging them, strong dependencies like so imply that you should design those in some other way -- This object should be the only one aware of the former's existence, that way, you ensure that the codepath must go through that object to remove the former object - - - - -# Definition - - -# Destroying - -Destroying or removing an entity from the world should always be as simple as `Entity.Scene = null`, or `MyComponent.Entity.Scene = null`. -Rely on overriding your script's `Cancel` method to clean things you might have set up. +// To -Upholding this idea makes managing your objects' very straightforward, anyone would intuitively rely on scene removal for any entity or component to stop them existing/affecting the game. And with how we laid out how objects are defined through ECS, we intuitively know which entity we must remove for a component to behave +// The 'required' keyword will generate a warning on build when the value is not set in the editor +public required MyComponent MyRequiredComponent { get; set; } +public void MyFunction() +{ + MyRequiredComponent.DoSomething(); +} +``` -## Class imply identity, struct imply data +A trap you may fall into after reading this is to write defensively, checking if it is null and returning in such cases even if the rest of the logic expects some sort of change. +This will more often than not force you to write far more boilerplate logic than you would have if you ensured you had a valid one in the first place. +One thing you may also consider is whether to simply merge the dependant object together, if either one of the objects are used only for the other's purpose, it may make far more sense to simply merge them instead of having two different components. \ No newline at end of file diff --git a/en/manual/scripts/create-a-script.md b/en/manual/scripts/create-a-script.md index eb9707b2f..5aa47de12 100644 --- a/en/manual/scripts/create-a-script.md +++ b/en/manual/scripts/create-a-script.md @@ -127,6 +127,7 @@ You can see the script in the **Asset View**. ## See also +* [Best Practice](best-practice.md) * [Types of script](types-of-script.md) * [Use a script](use-a-script.md) * [Public properties and fields](public-properties-and-fields.md) diff --git a/en/manual/scripts/custom-assets.md b/en/manual/scripts/custom-assets.md index 1700713af..243b386bc 100644 --- a/en/manual/scripts/custom-assets.md +++ b/en/manual/scripts/custom-assets.md @@ -176,4 +176,9 @@ RootAssets: [] And you're finally done, have fun ! -![Result](media/template-result.png) \ No newline at end of file +![Result](media/template-result.png) + + +## See also + +* [Best Practice](best-practice.md) \ No newline at end of file diff --git a/en/manual/scripts/events.md b/en/manual/scripts/events.md index 6a412a94e..d2c4fc522 100644 --- a/en/manual/scripts/events.md +++ b/en/manual/scripts/events.md @@ -1,5 +1,8 @@ # Events +>[!Note] +>Events are not recommended anymore, see [Best Practice](best-practice.md) + Intermediate Programmer @@ -54,6 +57,7 @@ string asyncData = await gameOverListenerWithData.ReceiveAsync(); ## See also +* [Best Practice](best-practice.md) * [Types of script](types-of-script.md) * [Create a script](create-a-script.md) * [Use a script](use-a-script.md) diff --git a/en/manual/stride-for-godot-developers/index.md b/en/manual/stride-for-godot-developers/index.md index a6710e5b1..0491116b7 100644 --- a/en/manual/stride-for-godot-developers/index.md +++ b/en/manual/stride-for-godot-developers/index.md @@ -507,4 +507,9 @@ System.Diagnostics.Debug.WriteLine("hello"); ``` >[!Note] -> To print debug messages, you have to run the game from Visual Studio, not Game Studio. There's no way to print to the Game Studio output window. \ No newline at end of file +> To print debug messages, you have to run the game from Visual Studio, not Game Studio. There's no way to print to the Game Studio output window. + + +## See also + +* [Best Practice](../scripts/best-practice.md) \ No newline at end of file diff --git a/en/manual/stride-for-unity-developers/index.md b/en/manual/stride-for-unity-developers/index.md index 632dfc9cc..652a83463 100644 --- a/en/manual/stride-for-unity-developers/index.md +++ b/en/manual/stride-for-unity-developers/index.md @@ -817,3 +817,7 @@ public float MyProperty { get; init; } --- Unity® is a trademark of Unity Technologies. + +## See also + +* [Best Practice](../scripts/best-practice.md) \ No newline at end of file From ec2a4ca5e02457dd76341b03a5c643cb2c50f8d2 Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 30 Jan 2025 01:00:43 +0100 Subject: [PATCH 7/7] Formatting --- en/manual/scripts/best-practice.md | 53 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/en/manual/scripts/best-practice.md b/en/manual/scripts/best-practice.md index c3115d37a..a33708c0e 100644 --- a/en/manual/scripts/best-practice.md +++ b/en/manual/scripts/best-practice.md @@ -1,20 +1,17 @@ -# TODO: ADD EXAMPLES OF HOW TO AVOID ISSUES I AM RAISING -# TODO: DO NOT FIGHT ECS, WORK WITH IT, IF YOU REALLY WANT TO DO WITHOUT, MAKE SURE THAT YOUR OTHER SYSTEM IS FINISHED BEFORE WRITING ANY LOGIC FOR IT - # Best Practices -Tips to build a robust and maintainable CodeBase for your Stride Project +Tips to build a robust and maintainable CodeBase for your Stride project -## Don't rush big systems +## Think Twice, Implement Once -Before starting on larger systems, make sure it would integrate with the rest of the existing systems, would it play well with saving and reloading, with the multiplayer architecture, when the game is paused, would it leak into other scenes ... +Before starting on major systems, make sure it would integrate with the rest of the existing systems; how would it behave when saving and reloading, with the multiplayer architecture, when the game is paused, would it leak into other scenes or game modes ... -Having this in mind ensures you won't write yourself into a corner, creating a system that needs to be patched up to work properly within your project, increasing your project's technical debt. +Having this in mind ensures you won't write yourself into a corner, creating a system that would need to be patched up when you could have figured it out earlier with a bit of planning, best to avoid introducing any technical debt if you can help it. A trap you may fall into is to then over design your systems, supporting features that your game will never need, convincing yourself that you could re-use that system for another game, that you could share or sell it. The vast majority of systems in games are purpose built for that game, your next game will require other features you could not support, let alone predict. You will also acquire a significant amount of experience working on this one, seeing issues it had and wanting to rewrite a better one. -### Figuring out your system's lifetime +### Figuring Out Your System's Lifetime What is the scope of the system you're writing; - Should the same instance be used throughout the entire lifetime of the application ? @@ -32,7 +29,7 @@ Some entry and exit points to manage your systems' lifetime: - Through your game's `BeginRun()` and `EndRun()` - As a component processor when associated component types are added to the scene -## Statics, singletons and other global-like writes +## Statics, Singletons and Other Globals We strongly advise you to make sure that the entirety of your game's state is implemented as instance properties on components inside the root scene's hierarchy. Avoid static properties and static objects. This is essential to reduce bugs that come in when implementing systems that manage the game's state, like the saving system, or the multiplayer layer. @@ -44,7 +41,16 @@ Some systems may not make sense as part of the scene when: - The system is read-only - Multiplayer server browser or matchmaking back-end. Once connected to a session it's a different story though, now you must hold a bunch of states that are only valid to this session, it should not leak to the rest of the program, and so is best left as a component on an entity in the scene. -Those restrictions do not prevent you from using the singleton pattern, you can use the `ServiceRegistry` which can be accessed from any `ScriptComponent`; +Those restrictions do not prevent you from using the singleton pattern, you can use the `ServiceRegistry` which can be accessed from any `ScriptComponent` + +

+ +

+
+
+ ```cs // Here's a basic singleton class public class MySingleton : IService @@ -88,10 +94,12 @@ public class MySingleton : SyncScript, IService } } ``` +
+
-## Implement custom assets +## Implement Custom Assets -Some of the systems you will build make far more sense as assets rather than entities, consider making them an asset when any of the following is true: +Some of the systems you will build make far more sense as [assets](custom-assets.md) rather than entities, consider making them an asset when any of the following is true: - It survives between multiple scenes - It is read only - It is not part of the definition of an entity, doesn't exist within your game world @@ -104,11 +112,11 @@ Examples of such are: - Loot tables, having a list of `UrlReference` with a probability of drop to easily re-use across multiple mobs - As an all-purpose robust 'key' or 'identifier' type, see [this section](#strings-as-keys-or-identifiers) -### Do not mutate Assets +### Do not Mutate Assets To that point, make sure to only mutate assets when it makes sense to do so. Remember that a single asset may be referenced by hundreds of components and systems, those may not expect them to change at runtime. Adhering strictly to this idea also ensures that your game's state does not leak through them when loading a new session, game mode, or whatever else. For example, let's say you have an Axe Asset which has a list of modifier, you save the game, progress for a bit then add a modifier, but end up reloading ot the previous save, the modifier will carry over to that previous game state. -### Scene quirks +### Scene Quirks The default scene the game spawns for you is the instance stored in the content manager, when running the game you mutate that very instance, meaning that if you want to retrieve the scene in its initial state, you must force the content manager to unload it, and then reload it. This makes it a bit counterintuitive when you just want to re-spawn the current scene to roll back your changes. @@ -147,8 +155,8 @@ public bool HasTheItem() - you won't need to keep a document going over each identifier you might have in game, one just has to look at the directory were they are stored in the editor. - Easy to extend; your identifier can now be more than just that, you can attach properties to it, perhaps a description to keep more information about this key. -## Avoid EventKeys, async and other systems with large levels of indirection when mutating the game state -Event keys and async methods carry a lot of implicit complexity as they may not complete when signaled/called. When the async resumed/the event key is received, the game may not be in a state where the logic you run is still valid. Some entities might have been removed from the scene, the inventory might no longer hold the item, the player character may be incapacitated ... +## Avoid Patterns with High Levels of Indirection +Particularly when mutating the game state, [Event Keys](events.md) and async methods carry a lot of implicit complexity as they may not complete when signaled/called. When the async resumed/the event key is received, the game may not be in a state where the logic you run is still valid. Some entities might have been removed from the scene, the inventory might no longer hold the item, the player character may be incapacitated ... This quirk also means that their execution are not part of their callers' stack, making debugging issues with them far harder to figure out. @@ -166,7 +174,7 @@ Alternatives to async You may notice that those two last ones could require a ton of additional logic to support properly, this is an indication that your logic should be rethought - you're writing yourself into a corner -## Avoid writing extension methods for access shortcuts +## Avoid Writing Shortcut Extension Methods This is specifically referring to methods of this kind: ``` @@ -182,7 +190,7 @@ It's a double-edged sword: - Polluting intellisense; in most cases this is a non-issue, but collection types are a prime example of this. Discoverability for extension methods through intellisense is nigh-on-impossible, there are just far too many extension methods introduced by linq. - It might imply to the user that your shortcut is somehow different from the source. -## Entity and components' lifetime +## Entity and Components' Lifetime One unexpected quirk of Stride is that components and entities are expected to survive across any number of removal and re-insertion into the scene. Those objects are never truly 'destroyed', they are treated like any other c# object, they either exist or are out of scope. @@ -231,4 +239,11 @@ public void MyFunction() A trap you may fall into after reading this is to write defensively, checking if it is null and returning in such cases even if the rest of the logic expects some sort of change. This will more often than not force you to write far more boilerplate logic than you would have if you ensured you had a valid one in the first place. -One thing you may also consider is whether to simply merge the dependant object together, if either one of the objects are used only for the other's purpose, it may make far more sense to simply merge them instead of having two different components. \ No newline at end of file +One thing you may also consider is whether to simply merge the dependant object together, if either one of the objects are used only for the other's purpose, it may make far more sense to simply merge them instead of having two different components. + + +## See also + +* [Scheduling and priorities](scheduling-and-priorities.md) +* [Flexible processors](../engine/entity-component-system/flexible-processing.md) +* [Custom Assets](custom-assets.md) \ No newline at end of file