Necromantion : A rough Sketch of Mechanisms.
Rough Sketch of How Things Work
I wont be going into the exact details of how things work, mostly because it has been about 2 years since I stopped working on this particular game, and have mostly forgotten how things were supposed to work.
As I have mentioned already in some previous blog, there are several GameEngines, with a specific task. The most important one by far is the GameEngine. The engines work alongwith the GameLoop.
What happens is that after the gameloop is started, it pings all the engines to update themselves and render their new forms to the GameWindow. I will only go into how the gameEngine is updated.
Firstly, there are two modes in which the GameEngine(henceforth referred to as GE) can be in :
Map mode or inventory mode. Basically, in the map mode, the map is shown and the player can move about, attack enemies, collect and place items in the backpack. The inventory mode is basically the backpack. Here you can do stuff to the items in the backpack.
Backpack and Collectables.
Items that can be put into the backpack are all implementing the Collectable interface.
Now this part is a bit complicated. There can be several classes of items, for example, potions, food, weapons, holographs, shield etc. Each such class of items can have several different types of items in them as well, as well as special items. Each such class of item will have some functionality inherent to them. So what we need is a framework, so that each such class of item can be represented, and when selected, the appropriate usage menu can be opened. Also, we need it to be generic enough, so that it can be easily extended. Note that ideally, we should only store all the items in the backpack as a list of Collectables. But this makes the added functionality difficult to get. Here, I made specific interfaces for each such class of items, and then used reflection to get the type of actions appropriate for an object. There is also another difficulty : Some items act on other items. For that, if you chose to use an item, for example a holograph that upgrades some item or weapon, the UI will now only highlight the items on which this item (for now, the upgrade-holograph) can be used. Which is super cool for me, but again, kindof hard to implement. What to do? Well, make more interfaces !
Each such item, whose purpose is to be used on others, has a SelectionCriteria. Calling getSelectionCriteria() on that object returns the interface that the objects which this current object can be used upon has to possess. For our case, that upgrade-holograph gives a Upgradable.class when the getSelectionCriteria() on that holograph's object is called. Now, we iterate over all the items in the backpack, and all those that are an instance of Upgradable are selected as our reduced list --- list of items on which our holograph can be used. Thus, the underlying backpack class is very simple to use, but the UI is the hard part here.
I am not sure if this is a good solution to the problem, but it is extendable atleast. It worked for me.
MAP
This part is easier to describe. We excapsulate each level as a Map. There can be may maps, and the player can traverse from one map to another by using stairs. All this is managed by a .. well.. a MapManager which stores all the maps. Maps are randomly generated. (I used a engine for this random procedural map generation due to lack of understanding of such algorithms at that point of time). Maps contain all the GameObjects, and the GE basically calls the render() and tick() methods on the current map, which then asks all its gameObjects to render() and tick(). In the tick() method, all the GameObjects update their state, if any updation is required. Note that this is not called frequently as this is a turnbased game. Only where a new Action has been done by the user, this function is called. This is not the case for the render() method though, which has to be called several times for smooth animations.
This brings us to the Camera class. Well, obviously at each point, we only render a small portion of the map. I did this by putting the player at the centre of the screen. The Camera class takes care of all this information. It also can zoom in and out. Now during the rendering, we ask the camera if we are in the screen area. If so, only then do we render ourselves. This saves a lot of GPU processing. (I am also using bufferstrategy and such things but will not go into the technical details here BECAUSE I FORGOT ABOUT THEM :( :( :( ). The Camera class also scales stuff by an appropriate amount using some predeclared constants and also the zoom parameter.
We also have some shadow effects for the field of vision. There are several modes. First, everything is unseen, so everything is dark. Then, there are parts which are seen. these have some transparency, but you cannot see if some entities are on them currently. Lastly, the cells in your current field of vision have full visibility. This field of vision is determined by the Bresenham Line Algorithm.
Some advanced lightmap can be used, but I just made some simple transparency-gradient sprites so that the fog effect can be shown, by overlaying these on top of the rendering of the objects themselves.
Actions
Actions are basically user actions. I encapsulated each user action into these Action classes for better object-orientedness and some other usefulness that I dont really remember now, but if I recall correctly, had something to do with the fact that multiple actions together are kindof impossible and so they can be avoided using this. Also, it helps to ascertain when to call the tick() method on the objects. Plus, they can be passed around as objects -- pretty helpful.
EDIT: The main purpose of this was to keep the application running on a single thread. If I updated information of the characters/Game environment on the keyinput/mouseinput thread, it could have portentially caused some wierdness as is common for non-thread-safe application. But since I keep the effect of the input thread localised to the KeyInput/MouseInput class, and use proper concurrency, storing the input commands as so called "Actions" become useful. Thus, later when the Game thread checks the class for updates, it gets the latest Action which is stored.
EDIT: The main purpose of this was to keep the application running on a single thread. If I updated information of the characters/Game environment on the keyinput/mouseinput thread, it could have portentially caused some wierdness as is common for non-thread-safe application. But since I keep the effect of the input thread localised to the KeyInput/MouseInput class, and use proper concurrency, storing the input commands as so called "Actions" become useful. Thus, later when the Game thread checks the class for updates, it gets the latest Action which is stored.
Animations
Animations are also hard for turnbased games, because after each turn, each entity can do some action which have their own animations, but since these animations are sequential, the user can register more Actions inbetween. Furthermore, there can also be certain animations that can go on simultaneously, while some that are exclusive in nature -- only 1 exclusive animation can run at a time. I had some animation framework to deal with this, with queueing subsequent animations. Each animation is ultimately played by the specific entitity/object itself, and can only play it when they can get a lock on the animation. Ofcourse, this lock system is only logical, and not hardbound, but since I was the only person working in this program, it sufficed for me. The object trying to play a exclusive animation had to register its animation and then wait for the lock to be free. When the lock was released, it was automatically given the lock, as a instance of that object was actually registered instead of the animation. This was necessary because if the objects were to try and acquire the lock, they might do so without registering, or suppose that if A registered, and B registered, and when the lock was free, due to the ordering in the GameObject list in the Map object, the tick() of B was called before A, and so it got the lock before A even though A had registered first. To prevent all this, the Object wanting to run a exclusive animation had to give a reference to its own object while registering.
Again, I am not sure if this is a good implementation, but being the lone programmer, it worked for me.
This is more or less it. The crux of the whole how-it-works.
Obviously there were many more features, like implementing pop-up notification blocks, implementing mouse control, and other game-specific stuff, but those are quite easy to get, once the core structure has been setup. Hopefully this will be helpful for amateurs trying to write games that are actually playable.
Wow so insightful
ReplyDeletethanks for this useful article
ReplyDeleteHTML Editor Kya Hai?
Javascript Ko HTML Mai Kaise Insert Karte Hai?
HTML or CSS Se Simple Slider Kaise Banaye?
Anchor Tag Kya Hota Hai?
HTML Me Emojis Kaise Banaye?
HTML Se Blinking Text Effect Kaise Banaye ?
Dropdown Navigation Menu kaise Banaye