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

Beginners howto 9 - tiling cels


What is tiling? In this context, it is placing copies of a square or rectangular image adjacent to each oher to fill a space, like tiles on a wall. A suitable image for tiling is one of which all edges blend together seamlessly, so that when it is put next to itself, it seems one whole picture, not two pictures side by side. People who remember Windows 95 when it first came out, may also remember its tiled desktop backgrounds. Harddisks were small then, and using a small square to build up a desktop background picture saved space.

When KiSS was first defined, people still used MS-DOS and Windows 3, harddisks were even smaller and space mattered even more. I don't think any sets from that period used tiling, because they were mainly of dressup dolls which have no use for tiled backgrounds, and also because using a small cel for tiling, while saving space on disk, filled up RAM, which was also quite limited then.

Nowadays computers have more megabytes of RAM than they used to have in harddisk space, and harddisks are both cheap and full of disk space, but one constraint has remained: KiSS set filesize. Small as cels and other set files used to be, they were made even smaller by packing them on a compressed file, for easier distribution and shorter download times. My rule of thumb at that time was "don't make a set larger than 1,44Mb", this being the maximum size of a floppy disk. Floppy disks have been replaced by CD-ROMs and USB drives of soaring sizes, so I may be childish for still trying to keep the filesize down; but most files are distributed through downloading these days, my wireless internet connection isn't the best, and the smaller the file, the less chance there is that its download will abort halfway through.

Another reason to tile cels is: because I can, and it's fun to see what KiSS can do. Besides, if I use a tile for a background and want to change the background, I only need to edit a small graphic. But the reasoning behind the example below comes out of a jigsaw puzzle how-i, quoted:

Creating a ghost testing cel is easy; it's a solid white rectangle of 580x480 pixels that I create in GIMP by choosing to make a new file, filling in the height and width, and exporting the result as "ghostest.cel". This cel is a whopping 1Mb, so I decide to use the tiling principle and divide the total size by 4, remaking ghostest.cel as a rectangle of 145x120 pixels instead, to cover the playfield as sixteen additional objects. [...] As with all new objects, I add the ghost test object coordinates to the coordinates block by hand; since the ghost-testing objects are all the same size, their coordinates are easy to calculate.

The first lesson here is that the total playfield size must be a multiple of the tile size, so that the tiles fit in the playfield; I can't have the outermost row or column of tiles stick out past the edge. If the tiles are transparent, I can fit them inside the playfield by letting their edges overlap, but if I did that with visible background tiles, it would ruin the seamless background effect. The second lesson is that cel tiling is done in the object declaration block and the object coordinates block; there is no FKiSS code involved.

In practice, I put the tile cel in as many object declaration lines as there are tiles, with a different object number for each line, and then arrange the object coordinates in rows as follows: the first row has Y coordinate 0, and the tiles' X coordinates are 0, the tile's width, twice the tile's width, three times the tile's width and so on, until the last tile in the row touches the playfield's edge. The second row's Y coordinate is the tile height, and the X coordinates are a copy of the first row's X coordinates. The third row's Y coordinate is double the tile height, the fourth row's Y coordinate is triple the tile height andsoforth. Lifting the example straight out of the jigsaw puzzle how-I, these are the sixteen object declaration lines for the (almost transparent) cels and their hand-calculated object coordinates:

[header stuff]

#133.999 ghostest.cel   *0 :0                   ;%t254
#134.999 ghostest.cel   *0 :0                   ;%t254
#135.999 ghostest.cel   *0 :0                   ;%t254
#136.999 ghostest.cel   *0 :0                   ;%t254
#137.999 ghostest.cel   *0 :0                   ;%t254
#138.999 ghostest.cel   *0 :0                   ;%t254
#139.999 ghostest.cel   *0 :0                   ;%t254
#140.999 ghostest.cel   *0 :0                   ;%t254
#141.999 ghostest.cel   *0 :0                   ;%t254
#142.999 ghostest.cel   *0 :0                   ;%t254
#143.999 ghostest.cel   *0 :0                   ;%t254
#144.999 ghostest.cel   *0 :0                   ;%t254
#145.999 ghostest.cel   *0 :0                   ;%t254
#146.999 ghostest.cel   *0 :0                   ;%t254
#147.999 ghostest.cel   *0 :0                   ;%t254
#148.999 ghostest.cel   *0 :0                   ;%t254

[rest of cels]

[fkiss code block]

; Set 0
$0 [coordinates for objects 0 to 132]
 0,0 145,0 290,0 435,0
 0,120 145,120 290,120 435,120
 0,240 145,240 290,240 435,240
 0,360 145,360 290,360 435,360

I said that no FKiSS code was involved, by which I mean nothing in the code block; FKiSS4 adds tags, which are technically FKiSS, but are used in the object declaration block, and can make life a lot easier. In the FKiSS4 version, all object declaration lines now have the same object number (#0) as well as the same cel name. (They also have the same cel group name, "!GhostTest", which, for this howto, you may ignore.) What used to be their coordinates in the object coordinate block are now the numbers behind the %x and %y offset tags (if there is no tag, the offset is 0), and their single object coordinate is "0,0". Lifting another example from the jigsaw puzzle how-I:

[header stuff of FKiSS4 cnf]

#0.999 ghostest.cel     *0 :0                   ;%t254 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y120 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y240 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x145 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x290 %y360 !GhostTest
#0.999 ghostest.cel     *0 :0                   ;%t254 %x435 %y360 !GhostTest

[rest of cels]

[fkiss code block]

; Set 0
$0 0,0

If I already explained how to tile cels in the jigsaw puzzle how-I, why make this separate howto? Because, roughly two decades ago, I tiled cels before, using a command supported only by UltraKiSS: clone(). I made a demo set called "wallpap.zip" to show off the viewer's on-the-fly tiling ability. Quoting from that set's readme file:

The sole purpose of this set is to demonstrate the abilities of UltraKiSS,
and possibly serve as example to artists who want to use these effects in other
UltraKiSS sets. It is NOT standard (F)KiSS and will not work in standard
viewers.

What does this set do:

Using UltraKiSS's ability to clone and destroy objects, it takes a chosen
tile (or the three chosen tiles) and makes enough copies to fill the whole
playfield (rather like the BACKGROUND tag in HTML).

On page 1, it simply pastes 120 copies of the same tile.
On page 2, it makes a random background of three tiles.
On page 3, it makes a background of three tiles in a chosen pattern.
On page 4, it pastes 120 copies of a tile linked to a palette with ten internal
palettes; and as all copies inherit the palette, changing the palette group
recolours the whole background.

I'll explain only the code for copying one single tile, else it wouldn't be a beginner tutorial any more. (Besides, when I examine that old code, my head hurts. How did I ever do this?)

The old documentation has gone a bit missing, so I don't know the exact syntax for clone(), but I cloned a filename (a GIF, but it could have been a cel) by sticking together a name and a number, so the copy of "File" would be "File1". I did this in a loop that raises the value of a variable by 1 for every iteration, so I ended up with cloned objects File1 to File120. Then I calculated coordinates for these cloned objects, positioned them at these coordinates, and mapped them. When I needed to get rid of them, I used the same filename, variable and loop with destroy() to unmap and get rid of them.

A bit of fiddling reveals that the right syntax is:

clone(string variable, object number)

where the variable contains the string that is the new object's name, and the object number belongs to an existing object being cloned. The clone has the same cel name (or names, in the case of an object with more cels, I presume) as the original, and inherits its palette(s) and lock value. In terms of layering, the clone lies below the original.

To make one clone, I first put a string in a variable. UltraKiSS allows let() to accept a string:

;@  let(ObjVarName,"WallTile")

Then, clone() uses not so much the variable itself as the content of the variable, indicated by putting a dollar sign before the variable name, with an object number to create a clone called "WallTile":

;@  clone($ObjVarName,#0)

As a refinement, I can use a variable with an integer value as an object number for the second argument. FKiSS4 already allows this to some extent, but in UltraKiSS I can insert the variable name as if it were the object number, again by putting "$" in front of the variable name:

;@  let(ObjNo,0)
;@  clone($ObjVarName,#$ObjNo) ; Note: hash, dollar sign, variable name.

What I want to clone is a square tile of 100x100 pixels, to cover the background of a 500x400 playfield. That means 20 tiles in all: the original, and 19 clones. I'm not going to put a string in a variable and use clone() on it nineteen times. There has to be a faster way.

And there is. There are two. First, the FKiSS4 loop command repeat():

repeat(labelno,times,incremented variable)

For those who are failiar with FKiSS3, repeat() is like gosub(), but whereas gosub() calls a label once, repeat calls the label (first argument) a number of times (second argument) while putting the number of each time in a variable (third argument), so in the first iteration of the loop, the variable equals 1, in the second it equals 2, andsoforth. That would be handy for when I want to create WallTile1 to WallTile19. But the repeat() statement and the label are two different bits of code.

The for-loop, available in UltraKiSS, is both these things rolled into one:

for(incremented variable,start value,end value) [code goes here] next(incremented variable)

Using Counter as the incremented variable, I can use a for-loop to clone object #0 to objects WallTile1 to WallTile19 with the help of concat, which is like let(), but for joining strings together, where any integer value is automatically converted to a string:

; Do 19 times, raising the value of Counter for each loop
;@  for(Counter,1,19)
; Put string "WallTile" plus number in ObjVarName
;@    concat(ObjVarName,"WallTile",Counter)
; Make clone with string in variable
;@    clone($ObjVarName,#0)
;@  next(Counter)

There are now 19 copies of the walltile cel clustered in the top left corner under the original, and I can't see them. Because I forgot to map them. No matter, I can do that in the loop:

;@  for(Counter,1,19)
;@    concat(ObjVarName,"WallTile",Counter)
;@    clone($ObjVarName,#0)
; Make it appear!
;@    map($ObjVarName)
;@  next(Counter)

Being made on-the-fly, clones have no place in the object cordinates block. To place them, I must either drag them or move them where I want with FKiSS code. Since I want them to wallpaper the background, I might as well use move commands inside the for-loop.

The tiles will be laid out in four rows of five blocks, except the top row, which starts at the second block, because the original object occupies the first position. So if Counter is between 1 and 4, the Y coordinate is 0; when Counter is between 5 and 9, the Y coordinate is 100; and it goes up by 100 every 5 iterations. The best way to apply an if-elseif-block to this - and UltraKiSS, like FKiSS4, supports elseif conditions - is to start at the top, ie. if Counter is greater than 14 then set a variable to 400, else if Counter is greater than 9 (but, implicitly, not greater than 14) then set that variable to 300, etc. The X variable, on the other hand, starts at 100 and goes up by 100 every iteration until it reaches 400, the horizontal position of the fifth block in the row, then it starts at 0 again. I will use integer variables XPos and YPos for this calculation, where XPos must be set above the for-loop:

;@  let(XPos,100) ; start with the second position in the row
;@  for(Counter,1,19)
;@    concat(ObjVarName,"WallTile",Counter)
;@    clone($ObjVarName,#0)
;@    map($ObjVarName)
; Calculate YPos
;@    ifgreaterthan(Counter,14) ; fourth row, 15-19
;@      let(YPos,300)
;@    elseifgreaterthan(Counter,9) ; third row, 10-14
;@      let(YPos,200)
;@    elseifgreaterthan(Counter,4) ; second row, 5-9
;@      let(YPos,100)
;@    else() ; first row, 1-4
;@      let(YPos,0)
;@    endif()
; Use XPos, set value for next round
;@    moveto($ObjVarName,XPos,YPos)
;@    ifequal(XPos,400)
;@      let(XPos,0) ; back to beginning
;@    else()
;@      add(XPos,XPos,100)
;@    endif()
;@  next(Counter)

Why does this loop not do anything? Because it's all actions. For it to be carried out, it needs to go under an event. As my example simply provides static wallpaper, begin() would be a suitable event; loading the set will tile its background.

But what if I want to destroy and recreate those copies? I could make button cels saying "Create" and "Destroy", but there is a far lazier way. The object is only cloned on the page that is active when the for-loop is carried out, which, if it happens under begin(), is page 0. If I want tiling on every page, I should put the for-loop under set() instead. And since there are ten set()s, I should put the code under a label and have each set() event call that label. And! Since I don't want to clone unnecessarily, I should keep track in ten variables, one per set, whether that set is already tiled. Like this:

;@set(1)
;@  ifequal(Page1Tiled,0) ; set 1 not tiled yet
;@    let(Page1Tiled,1) ; it will be now
;@    goto(0) ; so tile it!
;@  endif()
[repeat for all set()s]

;@label(0)
[for-loop example above]

(There is probably an easier way to code tiling for every set() event using string concatenation or substitution, but this is a beginner tutorial, thankyewverymuch. Yes, the tiling code may seem very technical, but as UltraKiSS-specific code goes, this is beginner stuff.)

Destroying the clones on any page will happen when clicking object #0, which is any tile at all, because every last one of those clones answers to "#0". They're basically ambiguous cels in the same object, which is why they should be referred to by their variable names.

I can use the same for-loop as when cloning the objects, except for the positioning code. Before destroying each object, I have to unmap it. After the for-loop, I reset the "tiled" variable for the page I'm on. (Again, for the more advanced UltraKiSS programmer, there is a shorter way to do this with string substitution or somesuch.)

;@press(#0)
;@  for(Counter,1,19)
;@    concat(ObjVarName,"WallTile",Counter)
;@    unmap($ObjVarName)
;@    destroy($ObjVarName)
;@  next(Counter)
; set "tiled" variable to 0 for the set you're in
;@  letset(S)
;@  ifequal(S,0)
;@    let(Page0Tiled,0)
;@  elseifequal(S,1)
;@    let(Page1Tiled,0)
;@  elseifequal(S,2)
;@    let(Page2Tiled,0)
;@  elseifequal(S,3)
;@    let(Page3Tiled,0)
;@  elseifequal(S,4)
;@    let(Page4Tiled,0)
;@  elseifequal(S,5)
;@    let(Page5Tiled,0)
;@  elseifequal(S,6)
;@    let(Page6Tiled,0)
;@  elseifequal(S,7)
;@    let(Page7Tiled,0)
;@  elseifequal(S,8)
;@    let(Page8Tiled,0)
;@  elseifequal(S,9)
;@    let(Page9Tiled,0)
;@  endif()

If I turn to a page, it fills up with tiles. I click on any tile, and the clones vanish, leaving only the original. I go to another page and back, the page fills up again. And that is how you tile in UltraKiSS.

The code isn't perfect. There's a lot of repetition, and after I destroy a page of tiles, I can click on the remaining original to destroy them again, even though they're already gone. I should add a safeguard against that, but I didn't want to complicate the code too much for this tutorial.

Click on the image to download the demo set (only for UltraKiSS!!). demo set



Back Previous Next