public async Task<Weapon> Create(WeaponSpawnConfig config)
{
Debug.Log($"Started spawning weapon: {config.baseWeaponConfig.WeaponType}");
Weapon res = (Weapon)Activator.CreateInstance(config.baseWeaponConfig.WeaponType, args: config.baseWeaponConfig);
Debug.Log($"Created instance of weapon type {config.baseWeaponConfig.WeaponType}: {res == null}");
var weaponModel = Addressables.LoadAssetAsync<GameObject>(config.baseWeaponConfig.ModelPath);
await weaponModel.Task;
res.InstantiatedModel = GameObject.Instantiate(weaponModel.Result, config.spawnPosition, config.spawnRotation, config.weaponParent);
Debug.Log($"Spawned weapon: {res.WeaponConfig.WeaponType}");
return res;
}
When I run it, the first Debug.Log gets shown in the console normally. But the second one just does not appear. There is no error or something, the game runs completely normally, except the rest of the function is not executed, so the weapons do not appear.
Agree, this Activator.CreateInstance reeks of bad design in this place. Especially if the created instance derives from UnityEngine.Object - thatâs an absolute no-go! Even if itâs a regular C# System.Object class you wouldnât want to use Activator to create it based on its type => see Factory pattern.
Also note that you can do:
var weaponModel = await AddressablesâŚ
Itâs not a MonoBehaviour.
Can you elaborate why is it a bad design pattern. I have different implementations of base class Weapon(). Creating a separate factory for each implementation while those factories maintain practically the same functionality would be a bad design, no?
The problem with Activator.CreateInstance is that it lets you create any type. It is too powerful for this purpose.
It will create types you did not anticipate, or types that do not exist, or types of the same name but they happen to be in another assembly. It also bypasses type safety, the WeaponType property is probably of type âTypeâ and therefore you could also assign a System.Object or string type or even ânullâ and then that code would fail because it cannot be cast to (Weapon). It may even be a security risk, injecting any type here could run code that isnât yours.
It is definitely bad to just cast to (Weapon) here (type cast exception) rather than using the âasâ operator (res will be null if itâs not the correct type).
In fact I think this is whatâs happening here. The first log appears, the second doesnât, therefore the CreateInstance line or the Debug.Log somehow failed. More than likely, you have an exception thrown here but perhaps you have the âerrorâ messages filter turned off in the console?
You should set a breakpoint on that line and just debug to see whatâs being fed into it and if there is any exception thrown and if not, that the resulting instance actually is the expected type.
Oh and it may not throw an exception as expected either due to async/await or perhaps due to the calling code catching and ignoring the exception.
Probably because the part that barfed was handled on another thread. Unity runs a loading thread that is responsible for calling all the constructors for Components and probably almost anything in the UnityEngine.Object namespace.
Besides, once you start breaking Unityâs rules and calling new (which Activator of course does) on engine objects, who knows what is happening inside the software?
Unity supports and relies heavily upon dependency injection. This is done via Prefabs or Scenes containing all the information necessary to create (eg, inject) all the necessary types on demand.
Yes you can do it all in code but then guess what? You get to maintain all that code!
Instead, make Prefabs out of all the âthingsâ you contemplate making and just call Instantiate() to make them.
As I said before I donât use activator on MonoBehaviours
IMO, you never build your architecture around loading prefabs. Unity doesnât even have a proper serializer, so you need to build some gimmicks to make it possible to set needed params in editor which just adds more work, not mentioning the fact that your IDE and version control will not help you track all the changes that were done to those prefabs by someone else which increases work for your designers.
And itâs also bad for performance because you pile up a bunch of unused data instead of discarding it like you do.
You are probably right about unity not logging errors that happen not on the main thread, because I had different types of errors happening on the âasync threadsâ and none of them were logged (and they donât stop the game either, at least in editor). I wonder if it leads to some kind of security vulnerability if it also doesnât crush the game when itâs built
How does my approach limit the non-coders? I just need to create a proper serialization tool for them to work in (which you do need to do anyway because unity canât even serialize simple stuff like dictionaries.
I wonder what your team have produced with that approach (itâs not a joke, can you leave a link? I donât think you are talking about games that are on your appstore). For example Iâm really interested to hear how do you run unit-tests. I can create thousands of config files with variable parameters to be fed into automatic testing with a click of a button, and all of them will practically take no space; but I donât see how you do it with prefabs.
I donât have a lot of experience working as a dev, but Iâve never met or watched or read a person who would recommend sacrificing SOLID (prefabs = strong dependency) for some illusory convenience that you would get anyway just by writing a simple editor tool that you will probably need anyway.
+The question with version control still stands, do all of your 4-12 contributors just remember what there was before and what changes they made? It seems super unreliable.
And how do you introduce knew team members? You need to tell them the whole structure of the project when they are assigned to it? IMO itâs much easier if you again have a special tool for creating design configs that looks approximately the same across all your companyâs projects.
I really canât see a single point of convenience nor usefulness in that approach. Both from technical and HR points of view.
Sorry for the rant, maybe Iâm just missing something in your point of view and Iâm trying to figure it out.
Unityâs serialisation is limited because it needs to be fast. If youâve ever overused another serialiser, like the Odin Serialiser part of Odin Inspector, youâll realise the amount of overhead it introduces.
And I feel like youâre forgetting about scriptable objects. You can easily use scriptable objects as a factory for plain C# objects if thatâs what you need.
Well, maybe. But I had never in my life seen anyone who used Odin and then was like: âNah, thatâs too slow, Imma go back to native untiyâ. If you used Odin once you are basically hooked on it, so I really wonder why unity doesnât just make it into the base kit.
You are right about scriptable objects, I also use them. But I think using them as a factory breaks single responsibility, because you are using the same class both as a data snapshot and a factory. In this project I use scriptable objects that can output a config class that I feed into a factory (which in itself is just a C# class, not a monobehaviour). Thus later if this project scales I can replace scriptable objects with a serializer tool fit specifically for this projectâs needs.
You can get interfaces fully working with serialized fields and the Inspector, so making full use of prefabs does not have to mean you canât also follow all the SOLID principles simultaneously. Itâs a pity it is not better supported out of the gate, but it is what it is
Components can also be designed to be unit-testing friendly. All you need is to add a mechanism for injecting dependencies as easily in code as it is to do with the Inspector, and after that unit-testing is no longer problematic at all (well, except for coroutines at least).
That sounds a lot like you might be trying to reinvent the wheel, instead of using all the powerful tools that Unity already offers, that have been polished and improved over several years by many professional developers, and battle-tested by thousands of game studios. Thereâs also a huge ecosystem of extensions built on top of the pre-existing tools in Unity, most of which I guess would be incompatible with your custom solution.
Almost all Unity developers that would join your team will also be intimately familiar with the prefab-based workflow in Unity. In many cases you basically just have to point out the project folder where the prefabs are located to a developer, and theyâll be able to figure out all the rest based on all their pre-existing knowledge and the intuitive visual Inspector. An animator can find the Animator component, and from there the referenced AnimatorController, and go work on the animations. An artist can find the Renderer / Image components, and from there the referenced art assets, and go make changes to them.
If you have a custom solution, then you will likely have to train every person that joins from scratch how to use it. If you have to open some custom editor window to edit things, people are very likely to forget how they can open that window - especially once a project has grown to the size where there are dozens of custom windows. If you just used prefabs, then every artist would know how to search for a prefab by their name in the Project window.
Iâve seen it several times over the years that when somebody tries to fight the Unity way of doing things too much, building complicated custom systems to replace the built-in ones, they more often than not eventually realize itâs not actually worth all the effort, and ditch the systems they built. There is another design principle in addition to the SOLID ones, that reminds us not to overdo it: KISS.
You can for sure use version control to track changes made to prefabs. The commit message can be used describe all the changes made to an asset, just like with code changes. If you use text serialization for prefabs, then you can even see the exact changes made to the YAML listed - not the most human readable format to be sure, but itâs usable in a pinch.
The only big downside in my book is that when two team members accidentally modify the same prefab at the same time, merging their changes together can be way more difficult than with code based changes. For this reason I think that doing everything with assets can also be problematic. There are some things that are better done with assets, and some things that are better done in code.
My games are just that, hobby / learning. Iâm talking about my day job where we operate games at commercial scale.
We tried unit tests but they didnât catch any bugs.
The bugs we get arenât findable by unit tests. The bugs we deal with are more like:
âIf user was watching a Vungle ad video and gets a phone call on iOS 17, when the app regains focus all button touch rectangles are offset to the right and down by 1/2 an inch. There are no exceptions thrown and nothing in the logs.â
Unit tests, for all the gallons of digital ink spilled on Medium about it, are essentially useless when the target is a complex UI driven and interleaved with a gaggle of different third-party libraries operating in real time.
So basically I need to do even more work to make it work? Why would I want that?
I think handing out a paper that says: to make a weapon config file go to Window â ConfigCreator â Weapons is easier then waiting for everyone to get familiar with the project structure.
Letâs get this straight, Iâm not saying that prefabs are some garbage and you never should use them. Iâm just opposing the point that Kurt made about creating a separate prefab for each âinstanceâ of some object in your project. In my opinion you should use prefabs for âstaticâ objects, for example: you have a player object that will always have components like Player.cs Locomotion.cs AttackHandler.cs, MeshRenderer, etc. And you make this into a prefab. But at the same time you create player configs, which contain information like what model the âplayer objectâ uses, how much health does it have, what attacks does it use and so on. And the best way to store this configs is json (during development to make it readable) and binary for deployment. That way you save space on hard drive, configs file are faster to load at runtime (bcs they are small) and you donât store excessive in RAM, because you discard the config files after getting everything you need (while it doesnât work that way with prefabs).
I wish I could find a job offer where they would ask for strong knowledge of KISS and not SOLID :(:(
You know you can have exactly that workflow with scriptable objects through the universally known create-asset menu?
Have to agree with the comments above. Try avoiding reinventing the wheel. Unity is already complex enough (as will any proper game engine be)
This is literally what SOs have been designed for. Blobs of data that live in asset context (as opposed to scene context).
I highly doubt your jsons are really saving any significant RAM or space.
As for prefab variants, those are useful when you need to change things more massively, especially swap components. Or just for faster workflow when the instances are not brought onto the scene via code but through drag and drop since then you donât need to manually assign the SO.
It will depend on the usecase whether SOs or prefab variants are doing the job better but those are two viable solutions already built into the engineâŚ
And also they rely on unity serializer which, as we concluded before, is not the best thing in the world.
And also they are in YAML reading which (for version control) is a form of torture according to Geneva convention, which we also established earlier.
SOs are a viable concept to get your prototype up and running, but if you use them in production (unless your game doesnât require any form of balancing after itâs released) ur mad.
YAML is only really a problem if you end up having to merge.
Thereâs a Unity YAML-merger plugin you can try but itâs not great either.
Ideally you keep the YAML bits small enough that merging is rarely necessary. This means if you have a humungous scene and longer-lived branches, you may need to do a bit of extra work to make your life better:
NEVER put manager objects in scenes (just have them load dynamically on demand)
use multiple additive scenes
use ScriptableObjects to break apart config data in your scene: rather than a huge list of random config on some random script or prefab deep in your scene, break it into areas of concern, put those into SOs, drag the SOs in, and then when you have to change them, change the SO instance, not the scene using it. This has the side benefit of keeping your YAML hierarchy much flatter and hence more-mergeable.
We configure everything from SOs. Itâs a dream compared to the alternatives. It works better than every other non-Unity dependency injection mechanism because they are a first class citizen in Unity. I almost never write custom editors for SOs either, except in cases where a custom editor can really add a lot of value to the editing process.
On some of our games we have an interface, I think called IAsyncInitializable that lets us drag all these SOs into an ordered list and have a generic âstander-upperâ loader ScriptableObject that also expresses whether it is ready for business yet.
Well I guess that you make different kind of games than I. Iâm making a prototype and I already run into a problem that scriptable objects donât have multiediting for inherent types (meaning that if you have SO A, and SOs (B: A) and (C: A) you canât edit them all at the same time).