Author Topic: YATE - Yet Another Tilemap Engine  (Read 7641 times)

0 Members and 1 Guest are viewing this topic.

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
YATE - Yet Another Tilemap Engine
« on: May 27, 2019, 09:58:09 pm »
Hi all! At Eeems' suggestion, I am posting this work-in-progress code :P

I've been working on-and-off on my tilemap engine since my Pokemon Amber attempt. The current rewrite is slower, but a bit easier to work with on the my end. It also allows the surrounding map to still be animated if I need to pull up a menu or dialog! I've also added in map compression using modified code from my LZD project, as well as event tiles!

In this version, I mess around with OS RAM to make larger chunks of contiguous non-user RAM, so it might cause crashes.

As of now, all you can do is walk around the map and enter/exit map editing mode. Since maps are currently stored in the app, map editing is not permanent. If you leave the map and come back, all changes are reverted.

If you want to edit the map:
Code: [Select]
Press [mode] to enter map editing mode
Use [+] and [-] to scroll through the available tiles for the map
Use [*] to toggle walkability. You'll see corner guards if the tile can't be walked on.
Use [Enter] to set a tile.
Use [stat] to exit map editing

When you hover over a tile that can't be walked over, a solid border will be drawn.
When you walk over an event tile, a dotted border will be drawn.


If you want to *actually* edit or create maps, you'll have to do that on your computer and recompile the app. You'll need spasm (with app signing enabled!) and Python with numpy. The map structure is:
Code: [Select]
.db height,width
 .db start_y,start_x   ;The player will be drawn at y+5 and x+6 !
 .db number_of_tiles
 .dw tile_index_0
 .dw tile_index_1
 ...

;map data
 .db blah
 .db blah
 .db blah
...

;Event code
 .db 0
The map data is actually stored transposed. So the tiles you see across the top row are actually stored in the left column of bytes! This makes it a headache to code.

You can use up to 64 different tiles in a map.

Each byte in the map has the index into the tileset stored in the lower six bits. Setting  the top bit means it can't be walked on, and setting bit 6 means walking into it triggers an event.

When events are triggered, the map's event handler is parsed an executed. It is a very basic scripting language that currently handles addition, subtraction, =, <, &, warping, and conditional execution. Scripts end with a null byte (0x00) and are stored similarly to how they'd be parsed in RPN format, currently with the exception of conditional execution (since the condition is checked before executing). 'x' and 'y' can be used to read the player's current coordinates, 'i' evaluates a condition (like an If statement) and if it is true, it executes the next command, otherwise it skips. 'w' is used to warp and requires bytes in the form of `map_id,start_y-5,start_x-6`. Finally, 8-bit integers are prefixed by a `.db 1`. For a convoluted example, here is the event handler for inside the "house" :
Code: [Select]
;if (y == 3 or y == 4) and x == 7:
;    warp(0,2-5,5-6)
.db "y",_num,3,"-",_num,2,"<x",_num,7,"=&iw",0,2-5,5-6
.db 0


Once you've made your map, you'll need to compress it. First you'll compile it to a binary, then you need to compress that data and convert the compressed data back to assembly. For example:
Code: [Select]
spasm maps/map9.z80 maps/map9.bin
python zlz.py maps/map9.bin maps/map9_comp.z80 map9

And finally, you need to add the map to the mapLUT found near the bottom of main.z80, just add the label name (in the above case, `.dw map9`) and include the compressed version of the file at the bottom.



The system is largely interrupt-based. A custom interrupt updates the current keypress, updates the tile animation, updates the LCD, and draws the tilemap. All you have to do is set the appropriate flag:
Code: [Select]
rpgflags  = 33
updateLCD = 0   ;Tells the interrupts to update the LCD
drawmap   = 1   ;Tells the interrupts to redraw the map
animate   = 2   ;Tells the interrupts to animate tiles
noupdate  = 3   ;Overrides the interrupt's LCD update and tilemap redraw
When a tile's animation counter runs out, it automatically sets the drawmap flag. When a map is redrawn, it automatically set the updateLCD flag and resets the drawmap flag (no need to keep doing it). And if the noupdate flag is set, then interrupt-based map drawing and LCD updating is disabled (but these can still be used manually).

There are three graphics buffers, one of which is quite non-standard. They are all vertically aligned instead of horizontally aligned (the OS' format is horizontal). That means the second byte corresponds to the 8-pixel chunk below the first byte, as opposed to adjacent to it.
Code: [Select]
gbuf     : This is a 1120-byte buffer! It is 80 pixels tall and 112 pixels wide. Only the center part is shown. This is where the tiemap is drawn.
maskbuf : A 768 byte buffer. During LCD updates, the data here is ANDed on top of the gbuf data.
topbuf  : A 768 byte buffer. During LCD updates, the data here is ORed on top of the previous data.

The latter two buffers are used for overworld graphics, like the player sprite, or in the future, dialog, menus, and NPCs, etc.

Offline Jonson26

  • LV4 Regular (Next: 200)
  • ****
  • Posts: 118
  • Rating: +1/-0
  • Follow cat! Do what cat! Into tree! Now!
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #1 on: May 28, 2019, 05:26:11 am »
Nice! How portable is this code? (I suppose, it's not really possible to convert it easily to 68k?)

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #2 on: May 28, 2019, 08:16:32 am »
You'd basically have to write it from scratch, but the general idea isn't too complicated to port.

Offline TIfanx1999

  • ಠ_ಠ ( ͡° ͜ʖ ͡°)
  • CoT Emeritus
  • LV13 Extreme Addict (Next: 9001)
  • *
  • Posts: 6173
  • Rating: +191/-9
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #3 on: May 28, 2019, 10:15:54 am »
I saw the pictures of this that you posted in the chat yesterday. Looks nice! ^^ It's cool to see you working on a tilemap engine again. :)

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #4 on: May 29, 2019, 06:45:47 pm »
Updates!

I now have saving working. Currently the save file is allotted 350 bytes. Whether or not it is archived, it gets copied to a temporary file in RAM. When you exit, the new data is automatically written back to the main save file. In the future, I want this to be a toggleable feature.

I also added in dialog boxes! This was tough to code. Due to the strange screen buffer format, I had to write text and rectangle routines from scratch.

I extended the abilities of the scripting language. Now it can initiate dialog boxes, read or write flags and tiles, and a few other things, probably.

I have now added in two more kinds of events-- one for when the user selects a tile (presses [Enter] to pick up an item or talk to someone, for example), and another for when the map is being loaded. In the example screenshot that I'll post below, map 0 checks two flags to determine if it should erase the flower tile upon loading. map 1 will warp the player out of that map until they've completed the task of bringing a flower.

I've included some defines to facilitate writing scripts in "parse.inc". You can view the (uncompressed) map files for example usage. For example, the map1 event code:
Code: [Select]
;events
.dw onload-$-1
.dw onselect-$-1
;if (y == 4 or y == 5) and x == 7:
;    warp(0,2,5)
.db "y"
num(4)
.db "-"
num(2)
.db "<x"
num(7)
.db "=&i"
warp(0,2,5)
.db 0

onload:
  ifnflag(0)
  jump(getout)
  ;else if flag 0 and flag 1 are set, need to do fancy stuff
  ifnflag(1)
  .db 0
  resflag(1)
  dialog(d_1)
  .db 0
getout:
  setflag(1)
  dialog(d_0)
  warp(0,2,5)
  .db 0

onselect:
  .db 0

d_0:
  .db "Get out stinky child!",c_nl
  .db "Don't come back",c_nl
  .db "without a floral",c_nl
  .db "aroma!",0

d_1:
  .db "Oh! What a lovely",c_nl
  .db "aroma! Please come in!",0

Finally, I added a tool to convert from Pixelscape's tilemap format to the format used here. Just run conv.py, passing the input file name on the commandline.

Here is the screenshot:

For some reason, Chrome renders the last dialog of the gif at the wrong speed.

Offline Eeems

  • Mr. Dictator
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 6266
  • Rating: +318/-36
  • little oof
    • View Profile
    • Eeems
Re: YATE - Yet Another Tilemap Engine
« Reply #5 on: May 30, 2019, 09:58:46 am »
I would be interested to compare your tilemapper engine to the one that was being developed for ESoR to see which performs better and also to see which uses less space with the same size map.
/e

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #6 on: June 03, 2019, 12:34:45 am »
Just a minor update: I'm working on making a tilemap editor in Python. It isn't great, but it will hopefully make development easier. I figured that I'd upload my progress here in case someone wants to modify it.

You need to have a "tiles.png", but otherwise you can run tilemap.py to start editing the map and tiles. This program requires pygame and uses Python 2 (not 3 :( ).

Layout:
The left-side 800x800 part of the screen is the tilemap.
The middle 512x800 is the tileset. (actually 512x512 is used)
The right-side is a tile editor

Controls:
Click on a tile and that loads it as the current tile to draw with or edit. Click or click-and-drag on the map area to draw the tile, or on the tile editing area to toggle the pixel. Press the 's' key to save, or the 'l' to load a new map (prompt is in the commandline!).




Offline Jonson26

  • LV4 Regular (Next: 200)
  • ****
  • Posts: 118
  • Rating: +1/-0
  • Follow cat! Do what cat! Into tree! Now!
    • View Profile
Re: YATE - Yet Another Tilemap Engine
« Reply #7 on: June 06, 2019, 04:56:15 pm »
Nice tilemap! Can you also make a png of the inside of the house? (I think i've seen a glympse on one of your gif's.)