Created: 15-07-2021
Last update: 15-07-2021
Back Previous Next

Beginners howto 10 - the ghosting test


When FKiSS3 was defined, a new action was added and implemented in PlayFKiSS: ghost(). This command did not make cels transparent or invisible, just unclickable. Any clicks on a visible ghosted cel are treated as if the cel is unmapped, and passed down to whatever cel or background lies underneath. This was a useful action, and I happily coded it as "ghost(celname)", assuming the way to reverse it was "unghost(celname)".

This was wrong. The action "unghost()" doesn't exist. The correct syntax, according to the FKiSS3 tandard, is "ghost(cel/object,0/1)" and if the number is left off, PlayFKiSS assumes the default, 0. So writing "ghost(celname)" is the same as writing "ghost(celname,0)". A "1" means the cel or object becomes ghosted, a "0" means it becomes unghosted. I had effectively unghosted that cel. And yet, as a result of "ghost(celname)", the cel was ghosted.

It turned out the writer of PlayFKiSS had gotten the same idea, and accidentally coded it that way. But the standard had already been implemented correctly in other FKiSS3-capable viewers - at that time, GnomeKiSS and !PlayKiSS. PlayFKiSS being for Windows and therefore the most often used viewer at the time, almost all sets that used ghost(), used it the wrong way round.

In order to work with with KiSS sets written for all viewers, UltraKiSS included, in its many compatibility options, "Invert PlayFKiSS ghost setting logic" under the Viewer tab, and made that part of the PlayFKiSS preset.


See tooltip for details.

Being in java and therefore cross-platform, as well as covering all FKiSS versions, UltraKiSS is the best bet for viewing KiSS sets at this time. If a set doesn't seem to do what it should, ticking this option or straight up choosing the PlayFKiSS preset is an easy solution. Still, I would like a set to work under any viewer. For that purpose, I use a ghosting test.

If a cel or object (ie. all cels in that object) is ghosted, the code under "press(cel/object)" will not be carried out. (What applies to press() also applies to drop() and fixdrop(), but where the other two depend on fix value and viewer, press() always works the same way.) So if "press(ghostedcel)" produces a reaction, that cel was not, in fact, ghosted. If I used "ghost(ghostedcel,1)" to ghost it, that means the viewer was, in fact, expecting "ghost(ghostedcel,0)", and I should do that right now:

;@EventHandler
;@initialize()
;@  ghost("a-thing.cel",1)

[begin(), version() etc.]

; This will happen either once or never.
;@press("a-thing.cel")
;@   ghost("a-thing.cel",0) ; Now it will no longer happen.

And that is the simplest version of the ghosting test. The player will experience this as a tiny mouseclick hiccup, if at all. But what if there are many ghosted cels and/or objects, that all need their own ghosting test? Then I settle the matter for once and for all, right from the start, with a big cel that covers the whole playfield, right on top of all other cels, and is ghosted under initialize(). It's transparent, so it doesn't hide the cels under it, but to interact with these cels, the user has to click on it. A click that, if it is ghosted, will pass through it, as though it isn't there. I will call it "ghostest.cel", and use it to reghost all other ghosted cels in one fell swoop:

;@EventHandler
;@initialize()
;@  ghost("ghostest.cel",1)
;@  ghost("a-thing.cel",1)
;@  ghost("b-thing.cel",1)
;@  ghost("c-thing.cel",1)
;@  ghost(#20,1) ; It also works on objects!

[begin(), version() etc.]

; This will happen either once or never.
;@press("ghostest.cel")
;@   unmap("ghostest.cel") ; Now it will no longer happen. For any of them.
;@   ghost("a-thing.cel",0) ghost("b-thing.cel",0) ghost("c-thing.cel",0)
;@   ghost(#20,0)

(If the filesize of ghostest.cel gets too large to justify using it, for instance because it is a CKiSS cel, I can make it a small cel and tile that cel across the playfield in a series of objects, then repeat the ghosting test code for each object. This is what I did in the FKiSS jigsaw puzzle how-i.)

If needed, this will definitely seem like a one-time mouseclick hiccup, as the player tries to drag something, and fails; after that, the ghost testing cel is unmapped, having served its purpose. (The hiccup can be explained to the user in an accompanying readme file, or by popping up a notice.) If not needed, ghostest.cel just continues to cover the set, without getting in the way. If you want ghostest.cel gone anyway, pick a cel that is guaranteed to be clicked on (like the base doll's underwear, because it's highly likely the player will try to pull it off) or, if your KiSS set is a game that is started by clicking on a Start button, that button. Unmap ghostest.cel when that cel is clicked.

The ghosting test above assumes that ghosted cels or objects are meant to stay that way forever. But especially when making a game, it may be that cels have to be repeatedly ghosted and unghosted. For instance, if the game has game pieces and a settings screen, you might want to ghost the pieces while changing something in the settings screen, then ghost the settings screen while moving the pieces. So that ghosting test should not just be about how to ghost once, but should save that knowledge for future reference. FKiSS3 has just the thing for this: variables.

FKiSS3 variables have a letter or a letter/number combination for a name. I could set variable G (to help me remember that this variable is used for ghosting) to 0 or 1, depending on what to takes to ghost a cel or object, and from that point on, write "ghost("a-thing.cel",G)". But I would then have to use a clumsy if-block or other construction each time I want to unghost "a-thing.cel", so I will also set variable U (the value needed for unghosting). The ghosting test will not only ghost cels that need it, but also adjust these two variables.

;@EventHandler
;@initialize()
;@  let(G,1) let(U,0) ; These are the correct values.
;@  ghost("ghostest.cel",G)
;@  ghost("a-thing.cel",G)
[ghost all that needs ghosting, using G]

[begin(), version() etc.]

; This will happen either once or never.
;@press("ghostest.cel")
;@   unmap("ghostest.cel") ; Now it will no longer happen.
;@   let(G,0) let(U,1) ; Reverse settings.
[now ghost all cels again, still using G]
;@   ghost("a-thing.cel",G)

[event that needs cel ghosted]
;@   ghost("a-thing.cel",G)

[event that needs cel UNghosted]
;@   ghost("a-thing.cel",U)

The code in this howto can be used as-is in FKiSS4 sets; there is nothing FKiSS4 contributes to this test, other than the ability to replace one-letter variables with variables with meaningful names, like "UnghostValue". For static cels, FKiSS4 can bypass the need for a ghosting test altogether by using the ghost tag "%g" in the object declaration line, like this:

#65.999  a-thing.cel   *0 :0                   ;%g ;This cel starts out ghosted

What FKiSS4 should technically be able to contribute is the ability to ghost a cel group. So instead putting a ghostest.cel on top of the playfield, I could add every to-be-ghosted cel to cel group Ghosted, ghost Ghosted the standard way under initialize, and test like this:

;@press(Ghosted) ; This should never happen.
;@   ghost(Ghosted,0) ; Now it will no longer happen.

But for some reason - compatibility, viewer bug? - UltraKiSS does not want to use ghost() with cel groups. So I don't do that, as it would be conterproductive to disregard a possible viewer bug when coding what is a workaround for a viewer bug in the first place.



Back Previous Next