Open dlight opened 4 years ago
Game::update
receives an immutable Window
by design.
The idea here is that update
should stay pure and have no side-effects other than changing game state. Ideally, your update logic should be completely decoupled from your rendering strategy. This makes your game way easier to test and lets you change your renderer freely (you could get a headless mode for free, even).
The current design in Coffee gets us halfway there. You can't draw or talk to the GPU in update
, but you can still rely on graphical assets. This is definitely something to improve, either we go all the way and we split game state entirely from assets, or we simply allow side-effects on update
. I personally think an opinionated approach is worth exploring in this case. I am open to any suggestions!
The current Task::run
should not be public (I forgot to make it private :sweat_smile:), as it is only meant to be used by loading screens. In master
it has been replaced by a method that takes a Gpu
instead of a Window
. If you want to load resources at runtime I recommend you to use methods that do not return a Task
for now. We will explore async asset loading once async/await reaches stable.
For your particular use case, I would probably generate and upload the game assets during draw
instead. Is there any reason why this may not work?
In any case, thank you for sharing your use case! I will keep it in mind!
For your particular use case, I would probably generate and upload the game assets during
draw
instead. Is there any reason why this may not work?
I don't know yet, I'm just beginning with this (amazing) crate!
But here's my guess: my map will not be static and actually depend on game logic, so its generation should be done on update
(the map is also not used only for display but also for physics etc). If I move its generation to graphics
, I will probably need to do there a lot of game logic (and I will lose the fixed timestep of update
)
However, map generation can be done completely on the CPU. On update
, I can generate an image::RgbImage
(from image
crate) and store it on my game state, and then load it on the next draw
(that's what I referred as "ugly"). But once it's generated I would like to push to the GPU as soon as possible, to hide the latency. So, should I really block my rendering on draw
to wait the CPU upload the buffer to the GPU? This might be slow. (btw I actually don't know whether this will matter in practice).
And indeed, I would be happy with async/await support for asset loading (in my head it should work like this: I start uploading to the GPU asynchronously on update
, and draw
will start using the new asset as soon as it is ready).
The idea here is that update should stay pure and have no side-effects other than changing game state. Ideally, your update logic should be completely decoupled from your rendering strategy. This makes your game way easier to test and lets you change your renderer freely (you could get a headless mode for free, even).
But, more philosophically.. I'm thinking about this. What I really wanted to do is to eventually move some game logic to the gpu by writing custom shaders. But yeah, this would make it harder to test (I don't know any CI with GPU support).
In master it has been replaced by a method that takes a
Gpu
instead of aWindow
Oh yes I'm actually using Task::using_gpu
(I moved to master because I wanted to use the new coffee::ui::Image
)
If you want to load resources at runtime I recommend you to use methods that do not return a
Task
for now.
And what method could do this?
You mean something like graphics::Image::from_image(window.gpu(), myimage)
? This will immediately block until it uploads the image to the GPU, right?
However, map generation can be done completely on the CPU. On update, I can generate an image::RgbImage (from image crate) and store it on my game state, and then load it on the next draw (that's what I referred as "ugly").
Will you procedurally generate every pixel of the map? This sounds very expensive. Normally, you generate a map as a bunch of game entities or terrain.
I have a prototype game that does procedural generation and lazy chunk loading. In update
, when the player moves to an unexplored area, I procedurally generate the tiles of a new chunk of my map (grass, water, sand, etc.) and its entities (trees, ores, enemies, etc.). I do not represent all this using pixels, but an actual data structure that makes sense for my update logic (most likely arrays and vectors of enums).
When it comes to rendering, all I do is simply query the chunks of my map that are visible, assign sprites to each of the terrain tiles and entities, and draw them all at once using a Batch
. Then, if you want to keep the static part of your map loaded in the GPU so rendering is faster you could use a Canvas
(terrain tends to be pretty static, for instance).
You mean something like
graphics::Image::from_image(window.gpu(), myimage)
? This will immediately block until it uploads the image to the GPU, right?
Yes. Keep in mind that Task::run
will also block immediately, there is no async support for it yet! Loading screens have access to the engine internals and are able to draw between each different Task
, but there is no actual loading happening in another thread.
My current plan is to first generate a map skeleton using petgraph, then lay it out on a lower resolution image, then do a bunch of stuff on top of it like maze generation (each pixel would roughly correspond to what a tile would be in size), then scale it to a higher resolution image and create finer detail (similar to this - the previous steps of the pipelines would provide rough shapes for it to work).
Yes, this is supposed to be expensive - that's why it's fun! I'm inspired by some stunning stuff produced by contemporary demoscene (which is surprisingly fast even on modest hardware). If this project grows I might need to make heavy use of rayon and simd on the CPU side, and move a lot of stuff to shaders on the GPU side, and at this point it might outgrow coffee (but knowing me, I'm much more likely to abandon the project much before this, so I'm fine, 😅)
That looks great! Be aware that Coffee does not support custom shaders (see #57), so it may not be a good fit!
In any case, I still think you should be able to keep a logic representation of the masks, and then map to actual colors (images) in draw
when your map changes. You can split your map into multiple logical units or chunks and use a Canvas
, so you do not have to redraw all of it.
This is what I personally do to implement my minimap, where I draw a LOT of individual pixels. You could also distribute the CPU load in multiple frames with an animation.
I note that
Game
has the following methods:But
load::Task::run
can only be called if I have&mut Window
, so I can only call it oninteract
, not onupdate
. Is this by design? Is the rationale on that written somewhere?I suppose that, since
Game::update
can potentially be called more times thanGame::interact
, it might not allow callingTask::run
because it would be expensive. However, what if I'm procedurally generating GPU assets onGame::update
and want to send it to the GPU ASAP? Should I store it somewhere and wait for the nextGame::interact
? (this looks ugly)