alright, so I’ll preface this by apologizing if this is one of the more commonly asked questions, because I’m still sort of figuring all this out, and the documentation is a bit dense, referring to definitions that reference themselves relative to each other ad infinitum. What is the best practices/most performant way to spawn an entity without a prefab?
I’ve seen about 20 or so tutorials and suggestions, and they all use prefabs- but they all just say “do this because it uses the editor.” if they even care to explain it… but I’ve never been in love with the editor, and I’ve never thought having to go to a new section of a window only accessible from object in a game(and deleted with said object) to declare which model I want to use was better than just telling the code “use pickup_speedIncrease.obj” and naming my assets in an organized, planned manner. To me, having scripts that run just by being in the game’s code somewhere is ideal.
so, I’m asking here, because it seems to be some sort of taboo not to use prefabs, and if I’m gonna break some unspoken rule I want to hear from you guys why it is. Simply put, I want the model an entity uses to be part of a component, so that multiple different looking objects can be the same archetype and manipulated at once- and I don’t want to have to make over a hundred slightly different prefabs for ECS. that seems… counter-productive, and exactly what ECS is here to solve.
Fastest? Via an archetype which can be created with EntityManager.CreateArchetype
Then it’s based on how much you want to instantiate in a single frame.
You can either use EntityManager or EntityCommandBuffer CreateEntity with the Archetype or create them batched with EntityManager, also with the archetype parameter and a count.
You should create the archetype once upfront because it allocates. From there you’re free to use it in jobs. You can add an EntityCommandBuffer into the mix to reduce main thread sync points.
public class SpawnSystem : SystemBase
{
private EntityArchetype archetype;
private EntityCommandBufferSystem ecbs;
protected override void OnCreate()
{
archetype = EntityManager.CreateArchetype(typeof(ComponentA), typeof(ComponentB), typeof(ComponentC));
ecbs = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
var ecb = ecbs.CreateCommandBuffer();
ecb.CreateEntity(archetype);
ecbs.AddJobHandleForProducer(Dependency); // technically unnecessary here but required if you add a job
}
}
There are overloads on CreateEntity to create multiple copies at the same time, which you should use if it applies. Unlike prefabs, all of these components are initialized to their default values, so you’ll need to set any initial values on your own. You could instead create a prefab via code and set default values on the prefab entity. Make sure it includes the Prefab component so it is excluded from queries.
public class SpawnSystem : SystemBase
{
private Entity prefab;
protected override void OnCreate()
{
prefab = EntityManager.CreateEntity(typeof(ComponentA), typeof(ComponentB), typeof(ComponentC), typeof(Prefab));
EntityManager.SetComponentData(prefab, new ComponentA { ... });
EntityManager.SetComponentData(prefab, new ComponentB { ... });
EntityManager.SetComponentData(prefab, new ComponentC { ... });
}
protected override void OnUpdate()
{
EntityManager.Instantiate(prefab);
}
}
heh. the amount will be fantastic. think dozens of thousands, potentially. (if the players set the options in a really funny self-destructive way meant to explode their computer). but now that you say it, it may be more performant/stable to use the roughly even counts of the different models (asteroids), and spawn them on different frames, with a brief delay, so the load is spread out over maybe a second when a new chunk is loaded (and loaded outside view distance, regardless. space is big.)
alright, so, combining these two replies, what I’m hearing is: create an entity archetype for each potential asteroid shape(of which there are many) and type (of which there are only a few) and copy them when needed.
but I think I mis-worded my question. I knew you could do it (though your code is really neat and better than mine) but I wanted to know if prefabs are as fast as doing it without, or if any metric for the speeds of various methods has been studied at all yet!
I’m not around for a day or two, so despite not being able to test things- while I have some attention, I’d also like to ask: how would you handle having many different models for otherwise identical entities? I assume you can set the mesh and texture as components the same as world transforms and rotation. that sounds like something they’d have built in.
but let’s say I have 36 models of asteroid, seeing as it’s a big part of the game, and then they come with 10 different materials you can mine from them. 360 prefabs is an absolute pain to manually include. is there some way to have them base their model on, like, an integer in one of their components?
or is this one of those cases where I need to instantiate the prefab, then move it and set the model after, preferably with both parallelization and entity command buffers?
I haven’t noticed a difference between instantiating archetypes and instantiating prefabs. Hypothetically…instantiating the archetype would probably be faster because doesn’t need to copy any of the prefab component data. In practice you’ll probably need to modify the default values from the archetype, which also takes time. Either way, you’ll want to optimize your archetypes for chunk count, concurrency, etc. That’s very project specific though.
I would make one asteroid prefab (or archetype) and randomize the mesh when you create it. Something like:
var meshes = GetSingleton<AsteroidMeshDatabase>().MeshArray;
for (var i = 0; i < 1000; i++)
{
var entity = ecb.CreateEntity(asteroidArchetype);
var random = Random.CreateFromIndex(i);
var mesh = meshes[random.NextInt(0, meshes.Length)]
ecb.SetComponent(entity, new SomeMeshComponent { MeshReference = mesh });
}
AsteroidMeshDatabase would be something you create during conversion, where MeshArray is probably implemented with a blob asset. I’m not familiar with the Hybrid Renderer components, but you should be able to swap out the mesh pointer pretty easily. You could also have some component like AsteroidMeshIndex and swap out the mesh in a later system, or even call directly into the render pipeline API and make batch draw calls.
i made some demo with very many asteroids, removing thousands of entities en instantiating new ones was faster then changing them so i guess its pretty performant as is. After all its just data copied into arrays pre-setup by archetypes, changing translations and meshes was much more expensive then just deleting the entities and instantiating new ones from prototypes.
I used 4 distinct meshes and could morph those into a lot of different looking asteroids with some simple random scaling, all textures went into 1 generated megatexture so only 1 material for all asteroids and 4 distinct meshes, the srp batcher likes it and all is generated from 4 lines of configuration specifying mesh, material and scaling limits for a randomizer.
this is just 4 meshes and ecs has no trouble instantiating 10’s of thousands of objects in some frames on a i9400 + 1080
EntityManager.SetSharedComponentData(e, new RenderMesh()
{
mesh = AsteroidSystem.AsteroidMeshes[imesh],
material = AsteroidSystem.AsteroidTexture,
castShadows = UnityEngine.Rendering.ShadowCastingMode.On,
receiveShadows = lod.behind == 0
});
If you are a solo developer it may work. But generally hard coding paths to assets in code is a bad idea. Imagine an artis wants to change some model or material. Now he or she has to dig thru code and change strings.
This is fantastic!.. however, my lead dev wants it to be multiplayer, so I can’t have it be random, only pseudo-random. I’m using a system which is basically a jittered dots algorithm so the asteroids cannot physically touch on spawn, but I need two computers to be able to agree on a chunk’s contents without communication, which only happens when one is interacted with.
with how yours is done, though, I gather I should be able to have one that uses the asteroid’s own (spawnID) component variable as the noise seed!
It would be awkward if your buddy flew through a torus asteroid when you saw a space station.
and I don’t mean paths to assets, just asset names. like that, they just put the file they want in the right folder, with the right filename, and it just works!
I would think on a larger team there would be some sort of asset naming/organization convention. I just really dislike work being stored in an inaccessible way where a program is shuffling things around behind the scenes and you just need to trust it. I hate storing values in a way that can just… disappear if you change something seemingly unrelated.
(I.E, the values on a single object’s script in an editor get nuked if you need to put it in again, unless the defaults in the code are correct. but then you’re already relying on hard coded stuff anyway! and if you have a procedural workflow, a large amount of these assets simply can’t have those options changed in the editor because they don’t exist till runtime!)…
TL;DR, hand placed assets and making a game connected to a map/world isn’t my style. I’m still getting used to unity, and the whole “scene” and “game world” thing. UI is a black box for me, still. I thought for the longest time start screens weren’t a game world, but… just some rendered assets with an engine behind it, ready to initialize a game world-
but this is terribly off topic now. just know that I’m allergic to relying on a saved UI workspace instead of a file to hold critical references, variable, and values. I just can’t trust nothing will change after 5 people touch it and send it back, but text. text is safe. you don’t accidentally mess it up without getting an error.