vendredi 17 février 2023

Separating UI and data in gaming, what design pattern should I use?

So I come from a background of web development, and working with primarily react. In this style of development people tend to use design patterns that separate the data layer from the UI layer.

The normal design goes: prepare data and state -> render the UI deterministically and declaratively from the data and state. The state can trigger an update to the data, which will then trigger a rerender of the UI.

This is nice because your data code only has to deal with the data, and so code about data fetching or transformations like filtering doesn't have any references to the UI at all.

I'm currently building something in Pixi.JS, which is a JS library for building graphics and games. The "take the data and then pass it to code that renders it" strategy is not an option. Pixi.JS is more imperative in design.

Let me demonstrate with an example.

Imagine you're showing on screen a list of bullet points (the UI) which is generated from a list of strings (the data).

In React you just have a component that returns a list of bullet points in JSX from the list of strings. If a bullet point is removed from the list of strings, the component rerenders with the bullet point taken out. Simple.

In Pixi.JS, the bullet points are all their own sprite objects that you create. When a bullet point is removed you have to explicitly and imperatively tell Pixi.JS to remove the sprite from the scene. Data transformations therefore usually involve performing some manual imperative changes to the UI.

This then ends up meaning that you start smooshing your data and UI code together. You make a method removeBulletPoint() that changes the data and the UI at the same time. This leads to more closely coupled code, and makes it harder to follow the single responsibility principle. It makes it harder to test because you can't test the data code isolated from the UI code.

I've read a lot that your data code shouldn't know about your UI code, but with this library that seems impossible. What design patterns should I apply to manage all this complexity?

Aucun commentaire:

Enregistrer un commentaire