Functional game engine
Hi @whitetigle and @MangelMaxime! Let's start a new issue as the previous one went off-topic long ago ;)
I've a bit of experience with game engines in the past (before I learnt functional programming) so here are my thoughts:
- We must do as @whitetigle says and start by creating a comparison with a pure JS sample like http://www.goodboydigital.com/pixijs/bunnymark_v3/ so we can always compare how well our engine performs against raw JS.
- @MangelMaxime's adaptation of Fable architecture is a good start but there's room for improvement. I guess we can still remove some steps while keeping an API similar to the one for fable-virtualdom.
- The idea of
renderwith something like Canvas is different from a GUI framework like DOM (or Windows Forms or whatever). In the latter,rendermeans updating the GUI component hierarchy to make some changes. This is expensive and it doesn't need to be done for every frame (actually the concept of frame is foreign to GUI frameworks), but virtualdom allows us to update the state without worrying too much about the actual DOM. On the other hand, with Canvas you draw raw pixel and everything must be rendered every frame (the only optimisation is to skip rendering of elements that are completely hidden). - Optimisation in games usually have to do with the
updatemethod, as it often involves expensive physic calculations. We need to receive the time elapsed (the argument in the callback to animation frame), first to do calculations by time and not by time frame (e.g. so bunnies rotate at the same speed even if the frame rate drops) and second to skip calculations that are not needed at that point.
So we need to take the classic game loop and try to make it more functional:
let gameLoop(dt) =
update(dt)
render()
window.requestAnimationFrame(gameLoop)
I have a couple of concerns at the moment:
- Events: we could take the events happening in every frame and put them in a buffer that is passed to the
updatefunction. Any ideas? - State: when dealing with thousand of sprites on screen I guess it's going to be difficult to keep the state functional and immutable. We'll probably have to use a mutable state, but we could try to do it in an elegant way.
Nice idea for starting this issue.
(the only optimisation is to skip rendering of elements that are completely hidden).
In fact, if I remember well this is possible for the user to cache others drawing element in another canvas for example and draw in the "visible" canvas using the other as a source. It's useful when dealing with tiles or tilesheets for example.
Optimisation in games usually have to do with the update method, as it often involves expensive physic calculations. We need to receive the time elapsed (the argument in the callback to animation frame), first to do calculations by time and not by time frame (e.g. so bunnies rotate at the same speed even if the frame rate drops) and second to skip calculations that are not needed at that point.
That's true and this is really easy to add the time elapsed. Current internal trigger is this function:
/// Function used to delay the Draw action (60fps based)
let schedule(msg) = scheduler.Post(PingIn((fun _ -> inbox.Post(msg))))
We should simply make the "Internal Actions" time based. For example (need to think if all need the time based or no)
type AppMessage<'TMessage> =
| AddSubscriber of string*Subscriber<'TMessage, 'TMessage>
| RemoveSubscriber of string
| Message of 'TMessage * float
| Tick of float
| Draw of float
Events: we could take the events happening in every frame and put them in a buffer that is passed to the update function. Any ideas?
For the moment, the events are supported the same way as for virtual-dom.
I mean each time we have an event occuring in the "view", it's push an Action to the App Actions buffer. https://github.com/fable-compiler/fable-graphics/blob/feature/fable-architecture/samples/pixi/fable-architecture/fable-architecture.fsx#L81
State...
True that having the state functional and immutable can be hard. But perhaps we could later the user use mutable model or mutable properties of the model. Moreover, I actually succeeded in using Aether with Fable. And this could help a lot to manipulate the data. (Still less performant than full mutable states) but still more elegant.
I think, we should make a compromise for a first time about performance and elegance of the architecture.
In fact, it's seems for me that we can enforce good architecture and good practice with a Framework Engine. Like using Aether, Immutable and pure functional approaches but at some point we should probably have an Advance topics to solve performance issue for example.
Using lenses would be really cool! Do you have some sample code? Could you link to it?
I will try to push something this evening or tomorrow.
where should we put the lenses samples? Consider it a new helper? Or simply but putting in a repo on github.com like one of mine?
I would create some cool examples with Pixi and lenses, see people feedback and then create an independent lenses helper if needed.
Pinging @andrea who already has experience on game engines with F# and can give us very useful advice 😸
Just some extra food for thoughts:
- pixi is usually good at optimizing rendering. Only very specific issues take a lot of render time (Graphics which are obviously slow, some unoptimized or heavy WebGL shaders, specific issues like masking + texturing + transformations at the same time, huge bitmaps/spritesheets although I think v4 did addresses some of these issues.), So on most cases, the render calls stay fast.
- even more rendering speed = shaders Since we can code our own shaders, there's a lot of things that can be even more optimized using shaders. So on the pixi side we have room for improvements.
- update loop & threads: if we've got a lot of things to do, here that's a problem. Now I wonder about using web workers. For instance regarding physics, I wonder if we could not try to make our world modifications using them. Same for vdom (I saw some react vdom using web workers). Same for logic. Moreover Web workers seem to be well supported . So that in the end we would just have render calls in the main JS thread for pixi rendering and dom rendering since they have to occur there (if I understand well). I absolutely have no xp regarding web workers, but maybe we could check?
- extra amount of FPS: usually even with thousands of particles, on modern platforms (desktop, high end smartphones and tablets) we should'nt have much problems to target 60FPS. 3 years ago, ok it was a bit sloooow. But nowadays, it's usually ok. I have tested on many devices since 2013 and things have gotten much better! But at the time it was already cool to have 30-40FPS on a nexus 7.
So if we consider that we want to aim for 60FPS, it means that we may have some horsepower available. So it would be interesting to check how much duty/time each task consumes and see if we could take this into account.
That's it for now :)
About web workers, I've seen samples of them being used with some physic JS libs. It's not straightforward because you've to sync the background calculations with the frame rendering and code for web workers have some limitations: they've to be in a different file and they need to reload all dependencies (including a module loader if needed). We could give them a try but maybe it's better to start without them and then moving some operations to the background as we run into performance issues.
Hi! I just found some interesting topic regarding UI architecture. . Some more food for thought.
[EDIT]: interesting article on game loop
So I am porting my current tools and projects to fable/pixi and of course while porting I'm wondering how I could make things way better using fable and fable architecture. So here are some thoughts.
1 - Behaviours.
I have played a few times this year with Construct2 to build some projects and they have what they call behaviours: basically a way to transform sprites following a few rules.
For instance if you take the car behaviour, you will have some friction when you move. A bullet behaviour will constantly incrase speed. We could have a simple particle behaviour: it would appear and then alpha would go down to 0. etc...
So I was wondering how it would be cool to have all these behaviours that we could just add to a sprite. Of course if we we using a physics engine it would just be a matter of chosing the right parameters for our bodies. In all cases to have a kind of library of Transform Behaviors would reduce dramatically the work and allow for fast debugging.
2 - UI layout management
Every project ends up the same: I need a basic positioning system to position elements without spending too much time. In Pixi there's no CSS so it's just a matter of changing x and y coordinates and add the sprites to the stage in the correct order (container, parent-child, etc..)
Since using Fable compilation + watch is fast. That's great. So I can imagine doing this by hand like I would with CSS. Now it would be great to have a flexbox like system to allow for fast positioning:
A - Position; place elements easily and move them on screen without any compilation to allow for very fast iterations. Allow for predefined positioning (centered. So outside config file would be needed.
B - Snapping: position elements relative to a grid
C - Align: stacked all up, all down, all centered, left, right, etc...
D - Spacing: take n elements and align them in the same space with the same distance between each.
Other similar ideas:
E - Groups: do transforms on a set of elements (=Container)
F - PinTo: transform element relative to another.
3 - Screens
I would call a screen, a context where I would put elements and that would hold them until I dispose it. It could also be called a View. For instance, in XCode we would create a storyboard to navigate from a view to another.
And as a matter of fact, there's some transition render happening between each screen. A fade, a simple hide or more complex transitions like sliding the whole screen and make the next one appear in a smooth move.
So basically in my projects I have screens. Each screen defines its rules and its own update. When I change screen, I launch the transition process and load the new screen and dispose the previous one.
In a screen I have a top container that will hold all my graphical elements (sub containers or sprites) and setup/init + update + dispose functions. I have found this approach very easy to use and understand so far. It has been used in many other frameworks. But it's clearly not modern. So maybe we could make it absolutely pretty and even more powerful using fable-architecture?
4. No DOM. 100% Pixi. Ramblings 😉
A while ago I was thinking about using Dom or VDom for ingame UI. After thinking about both dom and no dom approach, there's something that keeps coming back: the poor implementation of CSS everywhere and the hard souvenir of how much time I lost using flexbox a few months ago on safari iOS (flexbox just for fun).
In a perfect world, I would be very happy to design everything using CSS. In the real world, CSS has proven a constant pain in my back. Every year I say to myself: things get better. Every year I stumble on some tricky behavior. I curse CSS but remember that it works most of the time. 98% of the time. And that's the 2% that literally swallow hours.
So in a game environment, and for UI I think using DOM + CSS would prove a bad move after all. I think it would lead to a more than wanted complex architecture. After I wthink we should assume Pixi engine is stable on every platform and do the same thing everywhere. What do you think?
Ok that's pretty much it for today. Thanks for reading!
More links for fun:
- https://github.com/PixelsCommander/HTML-GL
- https://github.com/liabru/matter-js
Hello :)
Good job on all your research :).
1 - Behaviours.
For the point 1, this is something needed to simplify game development but behavior are really something coming from POO world. We should try to find something more functional I think. Because I played for some time with Class and F# and it's work great that can feel a little strange by moment.
Here is a code I was playing with to test some GUI library over Pixi.
Rectangle sample Button inherited rectangle
2 - UI layout management
I already tried to write some GUI library. I first tried Immediate Mode and Second Time Component mode which is the code I show on point 1.
I would definitely go for IMGUI because this could be more functionnal and also it's don't enforce the user to make the GUI retain the informations. He can use the storage he wants :)
The reference in term of IMGUI is I think this project: https://github.com/ocornut/imgui It's in C++ ok... But if you look at the samples it's kind of powerfull and the code is simple.
I am working a port of IMGUI for Fable. Still under development and I am trying to look at how we can use Pixi as Backend. Because Pixi support the Text rendering and it's the hard part :) (Everyone can draw a rectangle).
3 - Screens
Screens are good enough but old has you say. We can try to modernize it like Godot do.
In fact in Godot there some Scenes (aka screens here) and this Scenes contains some Nodes. And in fact, everything is a Node (sprites, collisions box, etc.) and even Scenes can be a Node.
For example, you can make a scene "player" which contain the sprite, spriteanim, collision box, the scripts, etc. And you can import it in others scenes. And so if you change the Player scripts it reflected everywhere. It's some how different from the traditional game engine but I don't really explain it well. I would advise you to do some research if you can.
4 - No DOM. 100% Pixi. Ramblings :wink:
I hate CSS and that why I am working on a GUI library. Really this language is a pain to play with and I think we could have better result by saying ok we only have a canvas. So our GUI part (draw, event, etc.) can only be used in this context ("pixel based").
I am not really giving you solutions except perhaps on point number 2 where my GUI will only say I am based on pixed otherwise I am agnostic to your implementation. So a good idea could be to share our time between the different aspects of the project ?
Finally, I think if we can abstract the pixi part which is somehow POO based. We could try to make something 100% functional. Because Pixi is already doing a great with performance for Rendering. There is one other place where performance are needed which is the Physics part and probably here POO or Prototype are better. But otherwise we could stay functional and have the benefit of using F# and not JavaScript :)
Ps: Hope to make some sense :)
Thanks @MangelMaxime for these anwsers! The information you shared has proven very interesting.
I like the IMGUI approach; It's seems indeed very convenient to use. Now for a game UI, I still need to make my mind around it. For instance for a life bar à la Street fighter.
I read the samples you provided (Button and Rectangle), thought about all the stuff you've shared ☕ and did a few experiments before coming back to this idea of behaviors which in the end would look like to me like function composition.
So I made a first try at it using an OO approach:
open System
open Fable.Core
open System.Collections.Generic
open Fable.Core.JsInterop
open Fable.Import.PIXI
open Fable.Import.PIXI.extras
open Fable.Import.Browser
open Fable.Import.JS
let options = [
Antialias true
BackgroundColor ( float 0x7A325D )
]
let renderer = WebGLRenderer( 800., 600., options)
let gameDiv = document.getElementById("game")
gameDiv.appendChild( renderer.view )
// create the root of the scene graph
let mutable stage = new Container()
stage.interactive <- true
type Easing = float -> float -> float -> float -> float
// So this is our Behavior contract
[<AbstractClass>]
type Behavior() =
abstract Update : Sprite -> unit
// this is a sample of behavior: a simple fade
type Fade(easeFunction, duration) =
inherit Behavior()
let _easeFunction : Easing = easeFunction
let _duration : float = duration
let _start = DateTime.Now
override self.Update(s:Sprite) =
if s.alpha > 0. then
let newT = DateTime.Now
let elapsed = newT - _start
let result = _easeFunction (float elapsed.TotalMilliseconds) 0. 1. _duration
s.alpha <- 1. - result
if s.alpha < 0. then
s.alpha <- 0.
// another behavior
type Blink(freq) =
inherit Behavior()
let _freq : float = freq
let mutable _start = DateTime.Now
override self.Update(s:Sprite) =
let newT = DateTime.Now
let elapsed = newT - _start
if elapsed.TotalMilliseconds > _freq then
s.visible <- not s.visible
_start <- DateTime.Now
// a last one for fun
type MoveBehavior(speed) =
inherit Behavior()
let _speed : Point = speed
override self.Update(s:Sprite) =
s.position <- Point( s.position.x + _speed.x, s.position.y + _speed.y )
// this is our "smart" sprite that uses behaviors
type ESprite(t:Texture) =
inherit Sprite(t)
let _behaviors = List<Behavior>()
member self.Behave(b:Behavior) =
_behaviors.Add(b)
// the "magic" is happening here
member self.Update() =
_behaviors |> Seq.iter( fun b ->
b.Update(self)
)
let nodes = ResizeArray<ESprite>()
let rec animate (dt:float)=
// now our behaviors are plugged, the animate loop remains easy to read
nodes |> Seq.iter( fun n -> n.Update() )
renderer.render(stage)
window.requestAnimationFrame(FrameRequestCallback(animate)) |> ignore
// Now let's try!
let g = Graphics()
g.beginFill(float 0xFFFFFF)
g.drawCircle(0.,0.,10.)
g.endFill()
let r = U2.Case2 renderer
let t = g.generateTexture(r,Globals.SCALE_MODES.LINEAR,1.0)
let easeLinear (t:float) (b:float) (c:float) (d:float) : float =
c * t / d + b
for i in 0..1000 do
let dot = ESprite(t)
dot.anchor.x <- 0.5
dot.anchor.y <- 0.5
dot.position <- Point(Math.random() * renderer.width,renderer.height * Math.random())
stage.addChild(dot) |> ignore
// add blink
dot.Behave( Blink(500. * Math.random() + 200.) )
// add move
let dirX = if Math.random() <= 0.5 then 1. else -1.
let dirY = if Math.random() <= 0.5 then 1. else -1.
dot.Behave( MoveBehavior(Point(Math.random() * 0.5 * dirX,Math.random() * 0.5 * dirY)) )
// add fade
dot.Behave( Fade(easeLinear, Math.random() * 5000. + 1000.) )
nodes.Add(dot)
animate 0.
So there's and Eprite which is a Sprite with a list of behaviors. A behavior is a class with an Update(sprite) method. The sprite is the updated through the internals of a behaviour. No direct manipulations and no sprite?whatever mutable var. Everything is secured in a Behavior. So it's quite easy to create new behaviors and plug them.
I have not plugged this to fable architecture but it would work great since it's on another level. We now also have the possibility to add behaviors while playing.
Now I think this could be done in an even more functional way. What do you think?
@alfonsogarciacaro
Optimisation in games usually have to do with the update method, as it often involves expensive physic calculations
Found some interesting information: Time corrected Verlet integration
matter.js use of Verlet Integration
And a great post on reddit
Thanks for this sample. The code looks really nice.
Life bar case
For the life bar, you quoted yes it's can be a bit harder because the base library will not provide a "progess bar" with 3 layers. But you will be able to customize the color and have the right behavior.
Because it's should still be easier to prototype and use the UI than if you need to make Retained UI Mode (like the one from the browser which retained the state of your data).
General case
After a lot of research (it's been more than 1 year that I am prototyping UI and searching stuff on it^^). Both IMGUI and RUIM are good and the performance of both are similar like for every program it's depend on what you want etc.
I think RUIM are easier to develop because we can easily wrap Pixi like I did with the sample shared. And still get a good final user experience :). If you look at the Demo folder it's more or less clean :) (still need to be refined because I was just experimenting the internal structure not the usage of the UI).
IMGUI can be really good to work with etc. But I still have some hard time to wrote it with pixi because I need to understand how to use pixi to draw generic stuff like Rectangle, Button, Text, etc. And also manage the position etc.
I agree with you about RUIM although now you told me about IMGUI, my mind is running wild 😈 I need to think about it!
Ok here is my second take (still done in standard architecture).
I added some basic cleanup system on Behavior and ESprite with the possibiliy to call an OnComplete callback.
- when completed, Behaviors set themselves to Dispose. ESprite then take rid of them but tries to call the OnComplete before
(unit ->unit) option. - Same for Esprite: some behaviors can set them to Dispose then they just remove themselves.
Although the callback system works and is super easy to understand, I'm not very happy with it since it could lead to some nasty chain of callbacks
Are there interesting options to search or should I leave it this way?
Now using fable-architecture, I was thinking about:
- using callbacks for direct issues without side effects on our model (spaceship collides -> generate explosion -> play sounds -> quake effect, etc...)
- using post for anything that would change our model: basically generate new updated state
For a scene, I was thinking about having one playing scene in our model that would change when calling some ChangeScene( newScene, transition ) action. something like that.
For instance we would have an Intro scene, a main menu scene, a game scene, etc... each one holding it's own tree of DisplayObjects.
ChangeScene would launch the transition effect, dispose the old scene and call the next scene. So a scene would also host some basic scheme routing to tell what scene would come next.
What do you think?
[EDIT] forgot to mention that the current sample take 100 balls and launch the 50 last against the 50 first. Each one then disappear on collision. It was to test interactions using behaviors and see if they would fit well.
[Just for fun]
🎨 Same algorithm but a little bit more arty ;)

Wow, isn't that cool! 👏
Sorry, guys. I don't have proper internet connection these days and I cannot follow the conversation very thoroughly, but if there's a specific thing you need may help with or if you need a specific feature in Fable compiler just shout my name :)
BTW, have a look at the Uniqueness types. They should be very useful for games, though the PR won't be probably merged until 0.7.0 (end of next week maybe).
BTW, have a look at the Uniqueness types.
Sure I'll try to understand what it does 😉
So we've covered a lot of subjects with @MangelMaxime and I think we'll eventually come to something interesting.
Meanwhile I begin to get myself comfortable with f# so things are becoming much more interesting for me. yet, I spend a lot of time on https://fsharpforfunandprofit.com which is clearly cool and am happy to read top topics on Fable gitter from top people 👍
So it rocks! 🎸
Really nice work @whitetigle
I wish to have more time next week to be able to work on some IMGUI code. But it's really impressive to see how the code is expressive from your sample :) Great work once again
Hi! Thanks for your feedback! Happy you like it!
I've added some new behaviors and am trying to see how far it could be usable so I'm doing some arty experiments (old idea that comes back to life).
I've also rewritten a simple loader for pictures, json remote and local, sounds, shaders, etc... that works but can certainly be optimized and will certainly be simplified with pixi v4. It's more a working draft and certainly needs some functional boost but allows me to work with old projects. Basically it loads some asset list from a json file and then loads each asset and stores them in the appropriate Dictionary. Callback is then called when everything's been loaded.
// start our loading
let assetLoader = Loader(onLoadComplete, errorCallback=errorCallback, progressCallback = progressCallback)
assetLoader.LoadFromJson("loader.json")
I have to think about and build yet the concept of scene/node and bring the whole back into fable architecture to see how the whole merge together. Then I will have the required basis to work on the next projects with Fable.
Regarding IMGUI, I will try to throw some thinking and let you know so we can share ideas.
Last but not least, regarding the samples that I could add to fable: I wonder what would be the most appropriate. The Fable dots sample is quite complete but I wonder what would fable users really want regarding pixi. So maybe we could ask the community about some useful samples? Like a how would I do that using pixi and fable-architecture? Or give ideas and let them vote on Github? Something interactive? Do you have any idea? Or maybe it's too soon and we need to wait for a fable-architecture + pixi v4.x?
You tell me 😄
I think having a real case sample is generally best whatever the project.
So we could probably a simple platformer with:
- Start Menu
- X levels
- Start point
- End point
- Collectible items
- Pause
I think this should not be to complicated and could be a nice starter to new users :). If they want to compare the synthax etc. They already have the current samples.
So a platformer? Why not. Random monster generation based on count of last week remaining bug issues and coins based on commits? 😉
@alfonsogarciacaro what do you think?
Ahah :) How to motive developers ^^
Hi there! Sorry, I didn't have time to read the comments in depth until now. First of all, I must say I'm really excited by the work of you both @whitetigle and @MangelMaxime and the research (the html-gl and matter-js libraries look really interesting). It would be incredible awesome to have a simple platform game, and @whitetigle's idea of linking it to Github can be extremely funny 😆 This would serve as a workbench to later extract a general-purpose game engine from it.
I find @whitetigle idea of attaching behaviors to the sprites very appealing. I've tried to adapt your sample here. The new code is neither perfect nor purely functional (it still relies on mutations for performance), but it does try to be more F# idiomatic. For example, there's the obvious step of making the behaviors more functional by turning them from objects into functions. This should make compositions and abstractions easier.
Another thing I'm experimenting with is making behaviors return promises. This is clearly an overkill in this case as all the code in the sample is synchronous, but it could be a solution to the problem @whitetigle mentioned with multiple callbacks or if in the future we decide to use web workers. Thanks to F# computation expressions, we can deal with multiple promises as if they were synchronous. In any case, please note the code is still too simplistic: if one promise takes too long to be resolved that would halt the animation.
Unfortunately, I'm gonna be a bit busy the next days so I may be not able to check progress in much detail, but please contact me at any time for specific questions/requests and if you want me to review code so I can try to give advice when possible (you actually have much expertise than me with pixel-based GUIs :wink:).
Really looking forward to the development of Fable's game engine!
Hi ! First of all, thanks @alfonsogarciacaro for the amazing piece of code you shared. It has been literally enlightening for me. Especially bypassing OO way of thinking through function composition and closures.
So here are some new ideas I'm evolving around. Sorry it may look a little bit messy and far from complete but maybe it will light some sparks?
Interactions.
I started to ask myself a very simple question: what is an interaction?
Citing wikipedia: Interaction is a kind of action that occurs as two or more objects have an effect upon one another. The idea of a two-way effect is essential in the concept of interaction as opposed to a one-way causal effect
So I started with Human Machine Interactions. User presses a key. Something happens. Thus User touched something = something was touched.
Then I took other samples related to video games and tried to understand what is an interaction and what it does mean..
OneToOne
Context: a moving player grabs a rotating coin.
Let's cut this to:
A. Player has a moving behavior B. Player can interact with a coin C. A coin has a rotating behavior D. A Coin can interact with player.
Let's say that there is only one player and many coins. A possible meaning would then be: A Coin is an Entity waiting for an interaction with a Player. Let's generalize a bit: a coin is an entity which has some behavior and can interact with another entity which also have some behaviors called Player. Furthermore it is waiting for this one very special interaction with the Player. And this very special interaction is a simple collision.
So we've got entities that possesses behaviors. And can have interactions with other entities. But here fundamentally what we have is a one-to-one relationship between two entities. Nothing will ever happen if these two don't collide. And no other collision exists for one of these two.
Let's take another example: a player character touching a door to open it. It would lead to the same kind of interaction; the player has to collide with the door. Or we could say the door is waiting for the player to collide with it.
Another example: a glowing health kit is an entity with a glowing behavior waiting for a collision with another entity.
OneToMany
Context: the player fires a missile against hordes of foes.
From the missile point of view what we have is some kind of one-to-many interaction. Indeed the missile can touch any foe and it will have consequences.
So let's cut this: A. The homing missile is an entity B. it has a moving behavior C. A foe is an entity D. on collision something may happen to both entities or only the missile.
So the missile is kind of agnostic. The differences with the previous OneToOne samples are:
- the target is unknown until collision happens.
- the result of the collision between the entity called missile and any another is unknown. It means that it will be solved on collision. Here the context of both entities is important.
Last but not least, we can add one new information: one entity may be able to transform another entity on collision. This was also true with the health kit.
OneToNone = Particles
The last entity would be a particle which has no collision with any other entity and exists only as a visual candy .
OneToNone=
- Entity
- with Behavior
- without collision
For instance: non dangerous fire sparks, stars in the sky, explosion particles, smoke, etc...
An entity will die
Every entity has its own lifespan and has some known dying condition .
For instance, a particle will die after it's alpha value reaches zero. A foe or an hero will die when taking too much damage. A mechanism will "die" with the death of the level (for instance a door). A health kit will die after a short timespan. etc..
So we need a die check to be able to dispose entities.
First conclusion
So far we have
- entities
- that have behaviors = inner transforms
- whose only goal is to collide to generate events/consequences/effects
- that can be transformed on collision by other entities
- that die
Then we have
- OneToOne relationships: an entity is waiting for a collision with a designated entity
- OneToMany relationships: an entity can interact with any other entity.
On collision
So now we're left with a central question: on collision things happen. But what are such things and how do we manage them?
Sample 1: player shoots foe.
Let's refine this to
keypress event -> spawn bullet entity with moving behavior
even better
keypress event -> spawn bullet entity at position with time/frame based transforms
then update until collision happens. When collision happens, if relevant apply transforms. How can we know that it is relevant?
Case 1: bullet can touch quite any entity. On collision we try to apply transformations but it may fail. ( (Type checking? )
Case 2: bullet can only touch one defined subset of identical entities. Transformations to bullet and target are automatically applied on collision.
Case 3: bullet can only touch a defined subset of compatible entities. Transformations to bullet and target are applied on collision but effects are contextual to the entity.
Sample 2: player grabs some health kit WIP :wink: .
Random topics
Here are other things I'm thinking about.
Mixed interactions
These need both ingame collision and user interactions. For instance, to open a door, the player needs to make the collision between his ingame character and the door happen. Then when the character is inside the collision zone, the player must press the enter key to actually open the door. So we would need inner and outer interactions.
Scene
What is a scene?
Could it be the collisions of Entities that, when all dead, trigger an end? Then could this mean that some Entity may be empowered with some death power over all other entities?
For instance the door of the level that when colliding with the hero triggers a next level event and forces every entity to die?
What is sure is that there are some conditions that trigger the a scene's ending if such exists;
Time for more playing
Thanks for reading. I will continue to think, refine and list more interactions to try to have some complete system and see how the whole could work together.
What an amazing topics @whitetigle
This is really interesting to cut the interaction part like that. It's give me some idea (and some for presentation of the interaction). I will try to find some time tomorrow to share my mind :)
I think that if we could keep going by splitting interaction like you did. We could find some schema/architecture we want to follow. I will explain better my ideas tomorrow with some drawing etc. :)
Great @MangelMaxime, can't wait to know what you have in mind!
Here are my ideas. At first I was not wanting to use classical Entity management because I find it a little old but from you code and sample I want to go deeper with it. Sorry don't really have time to work on this project for the moment so I will only give ideas.
All the thing that @whitetigle said in is last long post are really nice and a clear approaches. We saw that we can simply relate on making every thing a behavior and it's depends the mix of these different behavior make the "Entity" specialized.
From my point of view, when reading, your post I was making some groups of behavior and here ares the ones I have in minds:
- Time based:
- Every X time do something
- In X time do once something
- Interaction:
- Pick up
- Collision
- Open an inventory
- Transformation
- Translate
- Rotate
- Scale
- General Event type
- The player died
- Inputs
- Mouse
- Click
- Move
- etc.
- Keyboard
- Press
- Release
- etc.
- Window
- Resize
- Focus
- Blur
- etc.
- Mouse
Here I have just tried to make some sort of organization around the behavior. In the samples with the dots, we are only manipulating the position of the dot (ok ok also, the color, size, etc. but it's not important^^). What happen if I want to have Health attach to my player Entity.
We could make use of inheritance to make Player inherit from ESprite for exemple but the past show that OOP concept are goods but not perfect. :)
So here is my proposition. Sorry I tried to make a Graph but couldn't find a good presentation. So it's will be textual. Hoping it will be clear enough.
We could have what we call an Entity, which have Component attach to it.
A Component has a list of behavior and also it's own reference to Attributes.
An Attribute is for example the X coordinate, or the number of Health.
I can say that an Engine is the entry point of a game. And that Engine contains a list of Entities which have different Behavior and Attributes.
Here we see that there is a lot of internal list because:
- Engine has a list of Entities
- Entity has a list of Component
- Component has a list of Behavior
- etc.
So to render one frame we will need to iterate over a lot of list. But we could optimize this by having several updates running in parallel. It's something common in game engine to have a Rendering loop and another for the Physics for example. But we could make it possible to have more for example, if the game got some IA probably we need a loop for it too etc.
So we could puts tags to help the loop to take place. A tag can be "Physics", "Display", "IA", etc.
Like so we can simplify the access to the different sub trees etc.
Only problem is how to make the different loop to synchronize at some points. This specific point make me think of the Railway Oriented Programming where you have different tracks and them as the last you have a merge function. But on this point I am not sure of the implementation :)
One more idea about the loops could be to have specialized loops for Particle system, Background, etc. The idea, is in the far end: If we have some heavy charge in the current scene we can probably decided to stop the particle or reduce it's updates or even better reduce the count of particles drawn etc.
And one last point, it's possible that all this loop are only updating internals value. And them only when the Frame is ready to be draw we should commit the current calculated value.
I tried to lecture myself several times and hope I have been clear enough to share my ideas :).
Sorry, I still need to go through your very interesting comments in depth, but just I quick note: collision detection is indeed one of the most demanding calculations in games. But if we're to use a physics lib like matter-js, they usually already have performant ways to detect collisions. I've to check yet about matter-js but in other libs there's usually a flag to specify if the entity is not moving or not participating in collisions to improve performance.
Thanks @MangelMaxime for all these ideas 👍 I too need to take time to review all this! Another interesting lib: sat collision detection
[EDIT while I'm at it]: simple camera primer no object culling in pixi