controllerface / bvge

Personal game dev experiments
0 stars 0 forks source link

Define Inventory System #70

Closed controllerface closed 3 months ago

controllerface commented 4 months ago

I'm at the point now where I need the character to start being able to pick up items from the world. Before that can happen, I need an inventory system of some kind. I am not completely sure which way i want to go in terms of storage, basically I am either going to use some off-the-shelf embedded DB, or roll my own in a file format.

For this specific task, I am inclined not to try to implement it myself. There's just way too many options with significant feature sets that makes it really hard to justify. At the moment I am on the fence between something like SQLite or falling back to Xodus DB which I know and like.

I think for the level format, I am definitely leaning toward Xodus, so using it also for inventory is tempting just so there's only one DB type. That said, after messing around with SQLite a bit, it's very small and straightforward, with a lot of support, since it's just plain old SQL. The hesitation I have though, is that I may want to leverage the nice linking features of Xodus for some inventory actions. For example, two items in different inventory slots may be linked in some way, like dropping one item also drops another linked item, or similar things for usage of materials, like ammo.

Whatever the choice is, the API will need to be clean but allow for a really large number of different items, with differing types. I play to encode everything as enums, and mapping thee to Xodus keys should be just as easy as SQL columns.

controllerface commented 4 months ago

Oh, another thing I am really going to need is some way to render the inventory on the screen, in other words and actual HUD layer for the window. I can get away with console logging for debugging etc. but I will need something that visually represents the stored objects. I will want a separate renderer that either queries or passively is updated with the inventory contents.

controllerface commented 3 months ago

I have finally meandered into a place where I have "collected" object data transferred out of the GPU and available CPU side. As such, it's time to at least wire up some kind of inventory system for the player.

My original intent was to use the ECS system for this, so for example the player would have a component added for the type of object collected, or incremented if they already had some of that object. However, despite a lot of really good and sensible arguments for and ECS design, I find myself at a bit of a crossroads with how I personally see it working for my game.

When I read about CS and why it's so good, there's API/usability arguments that are definitely solid. However, no general purpose API is going to better than a a purpose built API, say for an inventory directly (as opposed to implementing it on top of an ECS paradigm). The other major positive point for them is cache locality improvements, which in my case (since the current ECS is implemented in Java code) is not really applicable. I don't directly control memory layout so I can't count on that benefit. Further, I already have several things one might use an ECS for, namely some characteristics of objects, stored in the GPU buffers, where I do have control of memory but am already storing things in memory tightly packed, so I'm probably already as optimized as I can be for cache access.

Developing this project "organically" has led me to the conclusion that ECS designs are nice, but may not always be the best approach. I am not throwing out the Java-side one I have now, I do still think there's potential for it to be very useful, but I have found that for a lot of things that might be "components", every single object needs to have that component. If pretty much all objects will need to have some component, what sense does it make to make that component something you "add" to the object? why not simply have it be part of the object's structure directly? I didn't really have any good argument and so I've implemented a lot of stuff exactly that way and found performance is quite good so far, even with 30K-40k+ objects being simulated.

So I think what I'm getting at is that I feel like an ECS system might be better suited for cases where lots of objects will have a lots of optional things that may be attached/detached over the course of the game. Right now, I don't see this use case in my own project, at least not of the scale that makes me feel like an ECS design is required. I may change my perspective as time goes on though, so I am not just abandoning it.

All this is to say that for the inventory system, I think for this first crack at it, I'm just going to implement a boring old class that allows me to add/remove/query, etc. for the players inventory. At the moment, this will restrict access to the inventory data to the CPU side code. But I think that is OK for now.

controllerface commented 3 months ago

Took a minor detour to streamline some kernel stuff. After having to add another buffer object to hold the "substance type" for an entity, I got a little annoyed with how many places needed to change to make that work. As such, now all create_ prefixed kernels (which as their name suggests, are called to create new objects) are generated from the Java side based on the layout defined in the kernel classes. It's a small improvement, but I'll take anything when it comes to this particular issue. I may start generating more kernels as I go, there's a several that are just too complex to be easily generated, but when the actual code in the kernel is easily predictable, I think I'm going to lean this way going forward.

In any case... there is another detour I think I will have to take now too, which is into text rendering. The player inventory object actually now exists, and you can indeed collect broken objects, and there's a counter for each individual object that increases when you do so. However, there's nothing indicating that this is the case. So it would seem that it's finally time to actually render some data that says what items the player actually has.

I do have a ticket created already for this, but I'd like to reserve that one as a task to really refine the GUI a bit and make it look more "production ready". For this current task, something really simple, like just some text overlayed on the screen that flows down from the left corner, or something like that, is all I'm aiming for right now. I am hoping to use Harfbuzz and FreeType to do this as those are the two main approaches most tutorials seems to use. I did a very quick and dirty attempt already with these and unfortunately didn't get far, but I will try again and hopefully connect the dots.

controllerface commented 3 months ago

I now have a very basic ability to render text, not even to screen just yet, but into bitmaps on disk. After getting this in place, I am starting to consider perhaps doing a 180 here and not going full on into the UI aspects of the inventory just yet. Earlier, I thought perhaps the best approach would be to just get anything rendering on the screen, just so there's some indication of the player's inventory contents. However, I think even getting to that point is going to require a pretty hefty amount of exploratory coding. I do have a pretty solid understanding of what I need to do to build the UI, but still a little hazy on the how. But i know enough to know it's non-trivial, and as such I probably will get derailed if I keep going.

SO... instead. I'm gonna go back to my original plan of emitting inventory data as events from the editor class, which is already there and will not take nearly as much work to get going and as a bonus, it is work I already know how to do. Because the inventory is effectively going to be a collection of EnumMap<> s it should be very efficient and hopefully, I can figure out a way to "mirror" the contents of the map in the editor in an equally efficient way that allows for a view of inventory contents that stays updated but doesn't require a ton of events. I will probably use a JSON object for this one, since it will probably be a little more convenient that way.

controllerface commented 3 months ago

Just had a quite thought... it occurs to me that EnumMaps are not thread safe, and at the moment I have the inventory collection code tied to the world unload thread. This will complicate things a bit, so i will need to add a dedicated InventorySystem class that handles inventory I/O. Otherwise, the inventory will be prone to data races, which would be really bad 😄

EDIT: on second thought, I will just wrap the enum maps in synchronized wrappers. I will still make the inventory system though, I want it to be separate from the world unloader

controllerface commented 3 months ago

Took a small detour to clean up some buffer stuff that had been bugging me for a while, but I now have the core memory class in a much better state. It had honestly gotten a bit out of hand with a LOT of code to wade through, so I am happy to have things segmented a little more sensibly now. I may take another detour a later time to do something similar in the physics simulation class, which I was about to do now, but after taking a look it's not quite a bad and since nothing external is using the buffers in the class (unlike the core memory class) it's not nearly as cumbersome, just a bit high on the lines of code in the file, which isn't a great argument to refactor in and of itself.

So, now will get back to actually putting the inventory data into the editor class so it can be observed without using sout calls.

controllerface commented 3 months ago

Last night I got the Editor "inventory mirror" up and running. The UI is super ugly but it works, and now the different items you collect are reflected with names and counts in the editor UI. Just like other events, everything is behind checks for Editor.ACTIVE so there should be no impact when the editor is not active. If with it being active, I don't notice any slowdown or anything, and the thread that writes events is completely disconnected from the game so it really should never interfere, so far that has proven to be the case.

Right now, there is an inventory class and a separate service that uses the class, which I am hoping makes it easy to share just that inventory object and use it for new systems and logic that gets added later, like things that actually use inventory items (crafting, block placing). I think I am happy with there this is at the moment, and I may close this ticket or merge and put it into the backlog for further refinement later. I don't yet have all the pieces in place to do things like liquid collection, and I think there's a number of game systems I need to implement before I can really add much more to what is there now.

Will stew on it and decide tonight