Skip to content

Prefabs

Marc Flerackers edited this page Jul 19, 2025 · 39 revisions

Status: In Progress


Component Serialization

Each component will need a method called serialize, which serializes the component into plain data, which can be converted to JSON

For example for pos, it would serialize the position, rotate the angle:

add([pos(0, 100), rotate(90), sprite("bean")]).serialize();

would serialize its components so you get

{
  components: {
    pos: { pos: { x: 0, y: 100 },
    angle: { angle: 90 },
    sprite: { sprite: "bean" },
  }
}

Note

Components serialized data should always be the most verbose possible pos: { pos: { x: 0, y: 100 } } instead of pos: { x: 0, y: 100 } makes easy later serialize future pos component data

Component Deserialization

We register a function that deserializes the component using its data:

registerFactory("pos", data => { return pos(data.pos); });
registerFactory("rotate", data => { return rotate(data.angle); });

so addPrefab(data) will read all fields, finds "pos", calls the factory method and passes data["pos"], thus obtaining the component and adding it to a list and finally returns add(list).

Object Serialization

When calling serialize on an object, it will serialize all its components, as well as its children

obj = add([pos(100, 100)]);
obj.add([pos(0, 100), rotate(90), sprite("bean")]);

obj.serialize()

Would give

{
  pos: {x: 100, y: 100},
  children: [
    {
      pos: {x: 0, y: 100},
      rotate: { angle: 90 },
      sprite: { sprite: "bean" }
    }
  ]
}

Children

Children are serialized and placed in a field called "children".

{
  components: {
    pos: { pos: { x: 0, y: 100 } },
    rotate: { angle: 90 },
    sprite: { sprite: "bean" },
  },
  children: [
    {
      components: {
        pos: { pos: { x: 0, y: 100 } },
        rotate: { angle: -90 },
        sprite: { sprite: "cat" }
      }
    }
  ]
}

Tags

Tags are serialized in a tag field.

{
  components: {
    pos: { pos: { x: 0, y: 100 } },
    rotate: { angle: 90 },
    sprite: { sprite: "bean" },
  },
  tags: ["bean"]
}

API

loadPrefab

loadPrefab(name, uri);

Loads an object hierarchy serialized and converted to json and converts it to javascript data in order to use it as deserialization data for a prefab.

addPrefab

addPrefab(name | data)
GameObj.addPrefab(name | data)

Adds a prefab by deserializing all the objects, and returns the new root object of the prefab.

addPrefab(name | data, Comp[])
GameObj.addPrefab(name | data, Comp[])

Advanced usage, in order to customize a prefab

addPrefab("hexagon", [pos(200, 200), color(BLUE)]);

Note

An object added as a prefab, will serialize as a prefab or modified prefab. It retains its prefab-ness. This means that if the prefab is modified, by repositioning it for example, only a reference to the prefab and the new position is stored when serializing. (We may need to add an option to "flatten" the object at creation if this behavior would not be desired).

{
  prefab: "hexagon",
  pos: { x: 0, y: 100 }
}

If a prefab has children nodes, for which some are modified, we may store these modifications as follows

{
  prefab: "hexagon",
  pos: { x: 0, y: 100 },
  children: [
    {}, // no change
    { color: { 255, 0, 255 } }
  ]
}

However, if the prefab would be modified to have a different amount of children, or the order changes, this wouldn't work. It may therefore be better to use named objects.

createPrefab

createPrefab(obj)

Serializes the object and its children and returns javascript data.

createPrefab(name, obj)

Serializes the object and its children, registers the prefab as asset and returns javascript data.

File format (.kaprefab)

{
  "info": {
    "name": "Hexagon",
    "creator": "Amy",
    "exporter": "KAPLAYGROUND"
  },
  "data": {
    "components": {
      "color"  : { "color": {"r": 255, "g": 255, "b": 255} },
      "anchor" : { "anchor": "center" },
      "polygon": { "pts": [] }
    },
    "tags": ["Goodbye", "Hello"],
    "children": [
      {
        "components": {
          "color"  : { "color": {"r": 255, "g": 255, "b": 255} },
          "anchor" : {"anchor": "center"},
          "sprite" : { "sprite": "kat" }
        },
        "tags": ["Goodbye", "Hello"]
      }
    ]
  }
}

File format (.ka????)

When a game or scene is saved, all assets are saved too.

{
  "info": {
    "name": "Hexagon game",
    "creator": "Amy",
    "exporter": "KAPLAYGROUND"
  },
  "assets": {
    "sprite": [
      { "name": "bean", "uri": "sprites/bean.png" },
      { "name": "kat", "uri": "sprites/kat.png" }
    ],
    "canvas": [
      { "name": "canvas", "width": 320, "height": 200 }
    ],
    "prefab" : [
      { "name": "hexagon", "uri": "prefabs/hexagon.kaprefab" }
    ]
  }
  "data": {
    "components": {
      "color"  : { "color": {"r": 255, "g": 255, "b": 255} },
      "anchor" : { "anchor": "center" },
      "polygon": { "pts": [] }
      "drawon" : { "canvas": "canvas" }
    },
    "tags": ["Goodbye", "Hello"],
    "children": [
      {
        "components": {
          "color"  : { "color": {"r": 255, "g": 255, "b": 255} },
          "anchor" : {"anchor": "center"},
          "sprite" : { "sprite": "kat" }
        },
        "tags": ["Goodbye", "Hello"]
      },
      {
        "prefab" : { "name": "hexagon" },
        "components": {
          "color"  : { "color": {"r": 255, "g": 255, "b": 255} },
          "anchor" : {"anchor": "center"},
        },
        "tags": ["Goodbye", "Hello"]
      }
    ]
  }
}

Events and custom code

In order to serialize custom behavior, you need to add a custom named component with a serialize method. For example, a state component's events can be initializes in the add event callback of a custom component.

function enemy() {
  id: "enemy",
  add() {
    const player = get("player");
    this.onStateEnter("attack", () => 
    {
      this.play("attackAnim", {
        onEnd: () => this.enterState("idle", rand(1, 3)),
      });
      checkHit(this, player);
    });
    this.onStateEnter("idle", (time) =>     
    {
      this.play("idleAnim");
      wait(time, () =>    
        this.enterState("move"));
    });
    this.onStateUpdate("move", () => {
      this.follow(player);
      if (this.pos.dist(player.pos) < 16) {
        this.enterState("attack");
      }
    });
  },
  serialize() {
    return {};
  }
}

registerFactory("enemy", data => { return enemy(); });

const enemyObj = add([
    pos(80, 100),
    sprite("robot"),
    state("idle", ["idle", "attack", "move"]),
    enemy()
]);

createPrefab("enemy", enemyObj);

TODO

  • References to textures (for uvquad, particles).
  • Event callbacks -> use custom components
  • Customizing prefab children from addPrefab. For example using
addPrefab("button", [pos(200, 50)], {"button_text": [text("Options")]})

to customize the text on child called button_text. Or

addPrefab("button", { children: { "0": [text("Options")] } });

by index.

  • If a file called enemy.js or enemy.ts in kaplayground is created, exporting a component method and factory, add it to the "add component" menu.
  • Scene behavior. Serialized object behavior can be customized using components, but what about global events? We could use object scoped global events, and use components on said object, but isn't there a better way?

Clone this wiki locally