Filip Smola

Categories

  • Open Desert

Tags

  • components
  • entity system
  • implementation

During the implementation of the Entity Component System, some things had to be changed from the design described in the previous post. In this post I will summarise these changes and the reasons for them. A lot of the reasons stem from my desire to keep the code simple and modular. As this article goes out, the changes will be merged from their feature branch into the master branch of that project repository.

Game Object

When implementing the game object, I have noticed that the handle is used much less than the design suggested. In the current state, it is only used in one log message. One problem I have noticed with the handle as it was designed, was when implementing the game object serialization. That originally saved the handle as well, but that would have been impractical. It would mean I would have to maintain a list of used handles to make sure a newly constructed game object didn’t get assigned a handle of a game object that was deserialized. This would make the game object creation and deserialization much more complex than it needs to be and therefore I decided to scrap that idea, at least for now. I might, at a later date, reintroduce it if I come up with an elegant solution, but for now the handle is not preserved through serialization and is not widely used.

Component

Component implementation has changed a lot from the design, mostly towards being less restrictive. Components are now classes that implement a fairly simple Component interface, and a lot of what was designed to be handled by the system is now left to the specific implementation of the component (access control, default values, field overriding, …).

Fields are no longer declared in the components file, as this would introduce a lot of extra work in processing, validating and using components and templates. Moreover the information on the fields is already contained in the associated class itself.

Templates now state what fields of the component they wish to override, that data gets passed to the component instance after its construction and the component itself decides what to do with it. Therefore the whole system allows more freedom to the component.

Components also utilise events even more that the design intended. During the implementation it became obvious just how much that helped keep all components separate. The best example would be, that none of the position, rotation, or scale components need to know about the existence of the world_matrix component, they just send out events through the game object whenever their values change.

Worker

The closest to a worker, as the design describes them, would be the rendering of game objects. That is currently handled by a SpriteRenderer class with a function that, given a projection matrix and a game object, renders that game object. The game objects are gathered from the world tree by a condition on having the sprite component and then passed to this function along with the camera’s projection matrix.

The sprite component contains all the data that the rendering requires except the projection and world matrices (provided by the camera and a separate component respectively).

Template

The main difference between the design and implementation of templates is that not all fields of a component need to be overridden in a template. If a field is not overridden, it will just keep the default value specified in the component implementation. This makes templates shorter, easier to write, and also increases their backward compatibility. When a new field is added, it is not necessary to change all the templates that use the component unless the default value is not suitable.

Files

As mentioned above, fields were dropped from the components files and the handle was dropped from the gameobjects files. For illustration, below are examples of files from the implementation.

#numbers.template
name: numbers
description: Sprite test (numbers from 0 to 7)
parent: none
components:
    - position:
        position: 800, 600
    - rotation
    - scale
    - world_matrix
    - sprite:
        atlas: textures/atlas.png
        dimensions: 64, 64
    - keyboard_sensitive
    - test_sprite_control:
        increment: left
        decrement: right
#main.components
- name: position
  description: Position
  class: net.pilif0.open_desert.components.PositionComponent
  required: none

- name: rotation
  description: Rotation
  class: net.pilif0.open_desert.components.RotationComponent
  required: none

- name: scale
  description: Scale
  class: net.pilif0.open_desert.components.ScaleComponent
  required: none
...
#root.gameobjects
- template: texture_test
  components:
    - position: {position: '238.738922, 242.719513'}
...