March 24, 2019, 12:01:06 PM

Author Topic: Maze Raider - The aMAZEing code a game comp entry  (Read 4051 times)

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #30 on: September 26, 2018, 09:04:25 AM »
@ freed instances
https://www.reddit.com/r/godot/comments/8hp3ok/use_call_deferredfree_instead_of_queue_free/
https://github.com/godotengine/godot/issues/4306
...

In essence Godot encourages you to remove an object from the "tree" by using "obj.queue_free()". It waits until the "obj" is idle (not processing anything) and then it removes it from the tree/root...
You could call ".free()" to instantly free stuff - but in almost any case you will end up with a crash (as you remove it eg. inbetween of a physics-update or so).

Now assume "obj" is stored in
var arr1 = [obj]
var arr2 = [obj]
(and in the tree ... via an add_child(obj) from within a node/script)

Now you somewhere call "obj.queue_free()". It does not matter how long you wait now (/how many cycles). The "reference" in arr1[0] and arr2[0] won't evaluate to "null" ("if arr1[0] == null" will fail!). But in the same time you cannot access properties in arr1[0] and arr2[0]. The "Object" there is now a "previously freed instance".

How to circumvent? RIGHT ASIDE when "queue_free()"ing the object, you need to take care of removing the object's references manually (arr1[0] = null, arr2[0] = null). THIS is tedious.
I meanwhile added a "Delete()" method to some of my objects which removes the manually added stuff - but as you cannot ensure that all added nodes have this method, you need to check for it each time - or else just call "queue_free()". Also it does add dependencies (which is not a problem in this language as it throws everything into one bowl and mixes it up ... "include"-way instead of "import"). In the end you have code accessing stuff from multiple files which access the other files too - refactoring hell upcoming!


They have some kind of "weakref" class which you could use instead. Means instead of "arr1 = [obj]" you should add a "arr1 = [ weakref(obj)]" and when accessing the object you need to check it ("if arr1[0].get_ref(): arr1[0].get_ref().doSomeOperation()").


So why is this confusing?
There are some "free"-methods which only take care of the "default storage way" (adding it to a node and retrieve them from there - including performance costly for-loop-lookups). Tutorials always say you should use "[queue_]free()".
So ok, I may forget to remove a reference from within an array or list. I would assume that the object stays alive in that list (as it is still referenced) but no - you get this "previously freed instance" error. AND you do not have a way to check the object _in_advanced_ if it was safe to access it (aka "still valid instance"). With 3.1 (not released yet) they added an "is_valid_instance()" method to do that - after people begged for months!

THIS is what annoys me the most: to tinker around on how to remove/manage stuff. For now I need to "centralize" functions on where stuff is removed - and act accordingly to remove all references which might somewhere exist (especially for faster lookups ... just imagine you need to lookup for projectiles on a 1000*1000 tile grid...).


As much as I love the visual effect editing (including its annoyancies) the dynamically typed language thing and the "previously freed instance" stuff is driving me crazy and _really_ slows down development pace.


bye
Ron

Offline Matty

  • Hero Member
  • *****
  • Posts: 610
    • MattiesGames
Re: [ WIP ] Competition entry: Maze Raider
« Reply #31 on: September 26, 2018, 01:33:48 PM »
Can you allocate space for everything in the beginning and simply reuse objects over and over simply changing their properties and only free ghings wgen the level is completely ended.

Like a pool that reuses elements so memory remains roughly constant because nothing is destroyed and instead has a property that says whether an object is in use or not and if not it can be reused for something else of the same type.

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #32 on: September 26, 2018, 02:43:49 PM »
You would need to take care of resetting everything ... you obviously miss stuff here and there and then you run into bugs.

Also: if you collect a coin - then there is no need to keep it in the "pool" and to iterate over it again and again. Remember: if you put it into a new list ("availableList") without removing it from the other lists ("usedList") you end up either processing it everytime ("if dead then continue") - maybe even Godot processes it (physics) - maybe one could "pause it".

For such easy things like "coins" or 10 monsters - this is a big thing to code instead of having some simple "== null" thing.


bye
Ron

Offline Steve Elliott

  • Hero Member
  • *****
  • Posts: 1564
Re: [ WIP ] Competition entry: Maze Raider
« Reply #33 on: September 26, 2018, 06:57:34 PM »
This game has a nice feel to it...But you always seem to get caught-up in technical problems...Maybe you over-think the procedures?
Windows 10, 64-bit, 16Gb RAM, CPU Intel i5, 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)

MacOS Mojave, 64-bit, 8Gb RAM, CPU Intel i5, 2.3 Ghz, Intel Iris Plus Graphics 640 1536 MB.

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #34 on: September 26, 2018, 07:12:12 PM »
I might overcomplicate stuff yes.
But if you do not know what things "Godot" offers you are enforced to check the internet - and if nobody asked stuff with the terms you use (or nobody asked at all) then you are stuck until you found a solution, bypassed the problem or skip a certain functionality.

The "previously freed" thing is ... annoying at least. And it does not take hours to fix the particular issues - but to find the reason for it and how to avoid it in your game.

I think my experience is similar to Qube's Unity adventure (ok, with less progress than what he achieves ;-)). Once you understood a certain part in Godot/Unity you can reuse it later on (or more propably: avoid it next time and just use the pre-crafted and ready-to-use solution inbuilt in the software package).

And yes, I am aware that dynamically typed languages allow certain stuff without hassle - but with the costs of issues I experienced with Godot for now. Just am not sure if I can accept it.


bye
Ron

Offline Steve Elliott

  • Hero Member
  • *****
  • Posts: 1564
Re: [ WIP ] Competition entry: Maze Raider
« Reply #35 on: September 26, 2018, 07:22:08 PM »
Quote
The "previously freed" thing is ... annoying at least. And it does not take hours to fix the particular issues - but to find the reason for it and how to avoid it in your game.

I think my experience is similar to Qube's Unity adventure (ok, with less progress than what he achieves ;-)).

lol, I think engine designers speed off...Then suddenly realise their initial approach was maybe not the best logical approach - so it causes headaches for the users.
Windows 10, 64-bit, 16Gb RAM, CPU Intel i5, 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)

MacOS Mojave, 64-bit, 8Gb RAM, CPU Intel i5, 2.3 Ghz, Intel Iris Plus Graphics 640 1536 MB.

Offline Qube

  • Administrator
  • Hero Member
  • *****
  • Posts: 1616
Re: [ WIP ] Competition entry: Maze Raider
« Reply #36 on: September 26, 2018, 07:47:11 PM »
Quote
You could call ".free()" to instantly free stuff - but in almost any case you will end up with a crash (as you remove it eg. inbetween of a physics-update or so).
Wow! that sounds like they've not finished designing that side yet. You should be able to free an object at anytime without issue. In Unity you just Instantiate a prefab and Destroy() it when you need to. Some recommend doing a pooling system to create / destroy objects but I'm not going to overcomplicate things for my first outing.
Until the next time...

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #37 on: September 26, 2018, 08:10:14 PM »
Yes, when you use "free()" then it does that instantly - if you use "queue_free()" it waits until the object is no longer used (but this also means it is eg. available in other function calls of the same "logic loop").
So instead of deleting items, you need to set some variables (eg. "isDead = true") so stuff could handle it accordingly. In my case I eg. mark items as no longer "collideable" (instantly) so other entities do not collide with it too.

I really want to "like" Godot but hmmpf, I am too used to BlitzMax to adopt to the Godot-way-of-doing. Adding stuff here and there is fun. Fighting these odd things is what annoys and takes hours and kills the fun you just experienced the half of an hour at the begin of your coding session. So I had an "previously freed" error the last 10 tries in the game - somewhere in between, but for now I cannot replicate it (as it happened in an area which _should_ get cleaned when removing an entity).
Am happy that we are in a developer community, so we are used to odd segfaults, crashes and so on - so I _should_ move on and take care of it later (will be hard to ignore it for now ;-)).



bye
Ron

Offline Qube

  • Administrator
  • Hero Member
  • *****
  • Posts: 1616
Re: [ WIP ] Competition entry: Maze Raider
« Reply #38 on: September 27, 2018, 01:33:57 AM »
Quote
Yes, when you use "free()" then it does that instantly - if you use "queue_free()" it waits until the object is no longer used (but this also means it is eg. available in other function calls of the same "logic loop").
Then why not limit queue_free() to objects that don't have any code interaction?, like most particle effects ( explosions, smoke, fire etc ). Other objects like bullets, enemies, missiles and the like would always need to be instantly freed on contact ( after collision logic ).

I don't think you need to track for a safe time to remove an object for example :

Fire bullet > bullet hits something > free() > do fancy particle explosion > queue_free() - As you're not attaching any logic to an explosion then you don't need to track when it's done it's stuff, but if you do need to go a step further and do stuff like explosion ratio burns then surely it's a piece of cake to know if an instance of an object is still active?

From the sounds of it ( quick doc read ) "queue_free()" is based on the idea that when an object become idle it is then deleted. To me that instantly results in a coding nightmare as queue_free() can never ever deal with every scenario, especially when you have logic attached to that object. No wonder it craps out.

Personally I'd only use queue_free() in the example above, i.e. when I don't need to know when something has ended or has attached game logic.

Quick question though... Why don't you know when you need to delete an object?
Until the next time...

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #39 on: September 27, 2018, 08:07:36 AM »
Yes I think so too about free() and queue_free().
It only get's tricky if I add objects to arrays/lists/containers and so holding additional references. If I forget to remove from such a container and then "free/queue_free" the object I will run into the "previously freed" issue once I iterate over this particular array/list/...
In BlitzMax I could "autorepair" such things by eg. have an "If bla.isDead then toRemove :+ [bla]" thing.

In Godot I cannot repair it - as I cannot see if an object is still valid, means I get a "handle" but do not know whether I should remove it or not ... and I cannot access it as it leads to the "previously freed instance" issue...

I am pretty sure that the free/qeue_free() stuff somehow works - I just sometimes use it as I am not intented to use it (eg. forgetting to remove a reference when freeing).



----------

Yesterdays evening ended with refactoring some code so that tiles have the possibility to be non-static. So a static ground tile becomes ex. a "trap" which has a script attached (based on a base unit script also creatures and collectibles share - so it has certain functionality inbuilt now) - this is needed as some elements do not need a ground (the blocker wall needs to go lower than the ground - including a "hole"). This also means I need to do "all [interactive] vs all [interactive]" checks to inform ex. about players entering an interactive ground tile ("activate trap"). Performancewise it could be better to have a list of "objects having changed grid position" and then in the objects' update-call check if there are entries in that "changed position list" - would save some function calls but is of course more cumbersome. As said the current system works and for now the performance culprit is surely the light+shadow system and not the simple game logic.

Ok, so current todo-plan is:
- add collectible "torch" ("dry") - or have one automatically (old bone + clothes are everywhere to find ;-))
- "refill" and activate torch at tiles with fire pillars
- burning torches allow to burn (certain?) enemies
- burning enemies can kill you
- wall blockers + switches (walk on the switch and the blocker wall moves up and blocks a path until you reactivate the switch/other switch/riddles?)
- spike traps: move over them in the wrong moment and... you're dead
- level end gate which opens once you collected "required" items (some special ultra-rare artefacts each adventurer wants...)
- burning enemies should move away from the player - but what happens if there is no space left to go - should it move towards the player then (and so kill the player? - increasing difficulty)


bye
Ron

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #40 on: September 27, 2018, 02:17:19 PM »
Ok, so ...  wanted to check out bitmap fonts. Seems there is some "hmm hmm" code in - Godot users should use "Dynamic Fonts" instead - but since Godot 3.x there are less options available (prior you could add gradients, outlines ...).
Means I created some bitmap font with "littera" - and somehow the glyphs jumped (baseline incorrect). Opened up my old XP VM and run "ShoeBox" in there (drop a PSD file containing your glyphs and it creates the font out of it... so individual styling is possible).
No jumping there, yes sir ... but ahem... somehow my drop shadows made the background brighter. Digged around what could be the reason. I converted my file to use premultiplied alpha:

Code: [Select]
$ convert fontHudSmall.png -background black -alpha Remove fontHudSmall.png -compose Copy_Opacity -composite fontHudSmall.png

and the image now looks way better.



Is this stated somewhere in the docs/issues? Hmm the only thing I found, was an 2016 (v2.x) report that the option for premultiplied alpha bitmap font images should be added (so the normal one is default) ... arghh.
Meanwhile I raised an issue and hopefully they resolve it - or show me the right way of doing a "HUD" over an 3D environment - seems this is not done so easily (I want to avoid to "render to texture" and set a quad somewhere over my game - I think a game design editor thingy should provide this already).

issue:
https://github.com/godotengine/godot/issues/22477

If this was a bug, then ... don't we talk about a version 3.x not 0.x? Shouldn't others use "HUDs" too - do they only achieve that via complex r2t? Why isn't there a proper tutorial about this (2d hud over 3d environment) - maybe I just lack the right terms to feed to youtube/google.


Edit:
Maybe I should also create another issue: if I render a colored rectangle (color is param of the draw command, no previous world.setcolor() thing!) then the upcoming world rendering is using the color of the rectangle. Need to render an empty but white text string to return to "white".

bye
Ron

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #41 on: September 27, 2018, 04:16:07 PM »
My bad ... seems ShoeBox (http://renderhjs.net/shoebox/) has issues importing PSD files correctly. Exporting them to .png first and then dropping the resulting .png files into ShoeBox results in an bitmap font not having this issue.
So this time Steve was absolutely right... the computer bug sits in front of the computer ;-)

Next problem to tackle: how to scale a bitmap font properly (when resizing the window)? A DynamicFont handles it nicely but of course it does not have the option for drop shadows, gradients, outline strokes, ... it had such things in 2.x and for now it was delayed for v3.2 (stable is 3.0.6 for now).
Argh. Think I need to set the scale of the "HUD"-canvas layer according to "original/designed resolution" and "current resolution". Might look a bit ugly/blurred then. Hmpf.


bye
Ron

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #42 on: September 28, 2018, 09:00:58 AM »
Our today's annoyance comes from ... "_physics_update(delta)".

To ensure that something is updated at a fixed rate (read: amount of times in a fixed period of time), one should use "_physics_update". So ... what are your assumptions on what it does? I assumed that all objects (doing physics or setting to do so) are run at the very same "tick". Means a all get their "_physics_update()"-call right after each other.
NOPE ... they don't.

Code: [Select]
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
level physics
level physics ... done
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
player physics Player
level physics
level physics ... done
player physics Player
level physics
level physics ... done
player physics Player
player physics Player
level physics
level physics ... done
...

So some stuff is more often updated than others. Argh. This is not nice Godot! You tricked me again!
I used the physics update to do the usual
oldPos = pos
pos = new calculated position
thingy ...

This allowed me to check if an object changed its grid cell. But during dev I wondered why other objects "sometimes" recognized a change of other checked objects - and sometimes not. If you now know that some objects get updated more often than others it is clear that my approach no longer works (old position is already 7 times set to a newer position which might be within a new tile grid cell).
Back to the drawing table...

... first try will be a "signal relay" (units emit a signal about "having changed grid cell now", level registers for this signal [needs to be done for _EACH_ unit individually!], level relays this signal to other known units/tiles which are interested in it - i.e. the ones on the affected old/new tile grid cell).
The odd thing for me is the fact that a "one to unknown"-signal is not possible (kind of a broadcast). I would prefer a "emit_signal(no target, signalname, params)" which is only received by ones listening to "signalname". But this option is not available yet - and I would need to code it on my own.
... using the dirty Godot-everybody-knows-everything approach each unit could straight inform the level about changes (which then informs all other ones) - signals are just there for "decoupling" but I do not know if signal listeners get disconnected when deleting objects - do not want to create memory leaks.

Seems things become a bit more messy if you cannot "click/drag together" stuff in the visual editor.

bye
Ron

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #43 on: September 28, 2018, 10:33:41 AM »
Ok, so made it "dirty" and units/tiles just inform the level/grid if they changed tile position meanwhile (entering, leaving, ...).
So finally "switches" and "wall blockers" work as intented:



As you see it misses an effect if the wall goes up while a unit walks over it ... think the unit should get thrown out of the map ;-)

I really enjoy how the map lights up when the enemies engulf a bit. Think it is enough for now - job coding has to get done and upcoming weekend time is family time (except they are all asleep).


bye
Ron

Online Derron

  • Hero Member
  • *****
  • Posts: 1685
Re: [ WIP ] Competition entry: Maze Raider
« Reply #44 on: October 04, 2018, 02:58:39 PM »
Got "knockback" and "stun" of enemies working. To achieve this I needed to introduce a "last tile" (used, not "tile during last update"). This allows calculation of a movement vector - or simply checking if enemy comes from "left/right/..." to knockback the enemy in the right direction.
Knockback is used to allow burning enemies while also being able to get hit/killed by burning enemies.

Together with that I added a "Jump To Grid Tile" function (including some "up-in-the-air, move, fall-down" animation). Using functionality I used (in a different way) before feels nice - as you come to results fast and without googling around ;-)


Current issue of this lunch-breaks-coding-session: "TextureButtons" (parent: Control node) do not emit signals while they should. I am pretty sure (but surely failed somewhere) that I did similar to a tutorial about this thing. Button press-signals seem not to get emitted. Grr.
Nonetheless a simple screen transition code base was established (for now: fade to black, replace screen, fade from black to transparen/screen). Will add some nice "swish"-decoration elements (running/flying scarab ?!).

Think for now I will have to skip many game elements I "planned" and just create a basic game with all required elements.


bye
Ron