Creating Prefabs Workflow

I’m trying to replicate the “prefab” workflow that you can create in Unity or Unreal. Right now I’m creating a grenade mesh object that has a physics impostor and will have more logic to create an explosion and affect nearby objects. What is the recommended workflow for something like this? Do I just create the grenade as a separate class then create an instance of that class every time I want to create a grenade in-game?

Initially I was looking at doing it all within a class to test out tossing a grenade but had some issues. I imported the grenade model and was able to create all the physics elements for it, but I wanted to save that as a template and hide/dispose the original grenade that was imported. I could import it dynamically everytime I want to create the grenade, but that doesn’t seem very efficient as I’d be importing the same mesh every time.

I also tried using clone and createInstance but that only works for meshes. I was hoping there was something similar built-in for creating an instance of an object, but I suppose that’s where creating a separate class would come in.

That said here are my questions regarding all of this:

  1. Is creating a class for each “prefab” the proper approach?

  2. Can I create an instance of the grenade (with logic included) without having to import the mesh every time?

Hey @jgonzosan!

My approach to the same problem is:

  1. create a static class, let’s call it AssetManager which will load all your assets in an ObjectAtlas which can be implemented as follows:
export class ObjectAtlas {
  private _objectAtlas = new Map<ObjectType, ISceneLoaderAsyncResult>()

  public get(objectType: ObjectType) {
    const loaded = this._objectAtlas.get(objectType)
    return loaded
  }

  public set(objectType: ObjectType, loaded: ISceneLoaderAsyncResult) {
    this._objectAtlas.set(objectType, loaded)
  }
}
  1. use an enum to easily identify your prefab
export enum ObjectType {
  Player,
  Grenade,
  Explosion,
  etc.
}
  1. example of AssetManager
export class AssetManager {
  public static ObjectAtlas = new ObjectAtlas()

  public async init() {
    this._createPrefabInAtlas(ObjectType.Grenade, URL_TO_GRENADE_ASSET)
  }

  private async _createPrefabInAtlas(objectType: ObjectType, filename: string) {
    const loaded = await this._loadAsset(filename)
    AssetManager.ObjectAtlas.set(objectType, loaded)
    return loaded
  }

  private async _loadAsset(filename: string) {
    const loaded = await this._loadModel(Config.assetsBaseUrl, filename)
    loaded.meshes[0].name = 'prefab-' + filename
    loaded.meshes[0].setEnabled(false) // disable the prefab
    return loaded
  }
  private async _loadModel(baseUrl: string, filename: string) {
    const loaded = await SceneLoader.ImportMeshAsync('', baseUrl, filename)
    return loaded
  }
}

Now you can acces you prefab from anywhere:

 const prefab = AssetManager.ObjectAtlas.get(ObjectType.Grenade)

Please note, that this will return the original prefab. You don’t want to modify itso you have to clone it before use, so the original stays untouched. Another thing here is that clones uses the same geometry. If you want to modify the geometry of your game object, you have to mesh.makeGeometryUnique() after you have cloned it.

  1. create your base GameObject (it can handle basic stuff needed for all your game objects). For example you can:
  public transform: TransformNode
  public mesh: Mesh

 constructor(
    public name: string,
    protected _prefabMesh: Mesh,
    protected _scene: Scene,
    protected _loaded?: ISceneLoaderAsyncResult,
    protected _objectInfo?: ObjectInfo
  ) {
   // create a parent node for the game object
    this.transform = new TransformNode(name, _scene)
    this.mesh = new Mesh('clone-' + this.name)
    this.mesh.isPickable = false

    if (_objectInfo) {
      this._position = new Vector3(_objectInfo.x, _objectInfo.y, _objectInfo.z)
      this._rotation = new Vector3(_objectInfo.rx, _objectInfo.ry, _objectInfo.rz)
      this._scaling = new Vector3(_objectInfo.sx, _objectInfo.sy, _objectInfo.sz)
    } else {
      this.position = new Vector3()
    }
    _prefabMesh.getChildMeshes().forEach(m => {
      if (m.subMeshes) {
        const instance = (m as Mesh).clone('clone-' + m.name, this.mesh)
        instance.isPickable = false
      }
    })
    this.mesh.parent = this.transform

    if (this.mesh) {
      this.mesh.setEnabled(true)
      this.mesh.getChildMeshes().forEach(m => {
        m.setEnabled(true)
      })
    }

    this._scene.onBeforeRenderObservable.add(() => {
      this.transform.position.x = this._position.x
      this.transform.position.y = this._position.y
      this.transform.position.z = this._position.z

      this.transform.rotation.x = this._rotation.x
      this.transform.rotation.y = this._rotation.y
      this.transform.rotation.z = this._rotation.z

      this.transform.scaling.x = this._scaling.x
      this.transform.scaling.y = this._scaling.y
      this.transform.scaling.z = this._scaling.z
    })
  }
  1. create your grenade object:
export class Grenade extends GameObject {
  private static _instanceCounter = 0

  static get InstanceCounter() {
    return Grenade._instanceCounter
  }

  public init() {
   // initialization after an instance was created
  }

  // add your own methods
  public explode() {
  }

  // you have many options here, for example you can use a static method to spawn your game objects
  public static Create(name: string, gameObjectInfo: GameObjectInfo, scene: Scene) {
    // this depends on you implementation, I use ISceneLoaderAsyncResult and always getting the __root__ mesh here
    const prefab = AssetManager.ObjectAtlas.get(objectInfo.type)?.meshes[0] as Mesh
    if (prefab) {
      const newGrenade = new Grenade(name + Grenade.InstanceCounter, prefab, scene, undefined, objectInfo)
      newGrenade.init()
      Grenade._instanceCounter++
      return newGrenade 
    }
    return null
  }
}

The GameObjectInfo could be:

export interface GameObjectInfo {
  x: number // position
  y: number
  z: number

  rx: number // rotation
  ry: number
  rz: number

  sx: number // scaling
  sy: number
  sz: number

  type: ObjectType
  // add whatever you need here
}
  1. you can then:
const gameObjectInfo = {
  x: 0,
  y:0,
  z:0,
  type: ObjectType.Grenade
...etc
}
const myGrenade = Grenade.Create("M67", gameObjectInfo, this._scene)

// so you can
myGrenade.position = new Vector3(x, y, z)

// or you can
myGrenade.explode()

...etc

The possibilities are endless. Choose the encapsulation of your properties and methods wisely and do not expose too many public properties and/or methods to avoid trouble finding which method is modifying your game objects.

Hopefully this will help you to make your own decision and implement your implementation. :vulcan_salute:

r.

3 Likes

Great, thank you. I think I have a few ways to approach this so I’ll just implement the one I like.

1 Like

@roland I love it !!!

1 Like