MistSonata
Moderator
DISCLAIMER: This is a work in progress, and since I don't know everything about how NESmaker's engine updates tiles, I don't even know if this idea would even work. But if it does eventually work, it'll be fucking amazing.
I've been studying the path system for a little while, trying to understand how it works, and looking at the LoadMetaNametableWithPaths.asm file, it's actually kind of hard to comprehend. It's not laid out the way I thought it would be (or the way I thought it would in my mind), so I thought I'd sort of lay out how I'd write them if I were more experienced with 6502.
So, what the current handlePath script does is check the 8 metatiles around it to see if they match the path tile it's comparing to, and if it does match with the current path (stored in temp) it writes that into one of 8 variables prepared specifically (I think?) for the handlePath code.
My version would use one dedicated variable, and would store whether the neighboring tiles are paths (or should be considered paths, in the case of screen edges or "ignore path" tiles) and store them as bits, starting with bit 0 at the top center and going around clockwise from there.
I've found that, for each quadrant (NE, SE, SW, NW), they really only need to read 3 of those 8 bits. So once you've set all the bits, you can focus on one quadrant and only read the 3 bits it needs and determine which tile it should load.
Now, as I plotted this out I noticed that the current path system has it so that each quadrant has 5 possible states that are split up between 8 different scenarios (shown above). And while I was thinking of how you would program it so that it reads both 000 and 010 as "exterior corner piece", I thought "why not use all 8 scenarios? It'd probably be easier".
So what if, for each path, we had NESmaker generate a table like this:
With each slot being populated by a user-defined offset in the tool that picks which tile in the tileset to put in that quadrant for that specific situation.
This means that you could make a path with 32 unique tiles, but if you want to make it fewer than that, you can. You can make it behave exactly like the current paths do now, or you could make it so that you have two unique tiles for the left and top edges, but have the bottom and right edges use the same tile. You could have it so that instead of using an exterior corner piece in situation 010, you could have it connect diagonally to the path (the scenario for the SW quadrant in the path diagonal would also be 010). You could have it so that instead of using a straight edge in scenarios 110 and 011 you could have it start curving toward the direction the path is going. You could (I think?) have paths share tiles with each other while still behaving as though they're separate paths. You could even have all the offsets on the table point to the same tile so that it's just repeating the same tile over and over (not sure why you would, but you could is the point I'm making).
So I started trying to replicate the handlePath code, but do it so that it used the method described above.
The code here is incomplete and I'm not even sure how parts of it work, but I'd love to hear feedback!
TLR - If my idea works, paths could be much more customizable with up to 32 unique tiles per path (the amount of tiles dedicated to paths would still be limited), opening up a wide range of versatility and the possibility for creative applications!
I've been studying the path system for a little while, trying to understand how it works, and looking at the LoadMetaNametableWithPaths.asm file, it's actually kind of hard to comprehend. It's not laid out the way I thought it would be (or the way I thought it would in my mind), so I thought I'd sort of lay out how I'd write them if I were more experienced with 6502.
So, what the current handlePath script does is check the 8 metatiles around it to see if they match the path tile it's comparing to, and if it does match with the current path (stored in temp) it writes that into one of 8 variables prepared specifically (I think?) for the handlePath code.
My version would use one dedicated variable, and would store whether the neighboring tiles are paths (or should be considered paths, in the case of screen edges or "ignore path" tiles) and store them as bits, starting with bit 0 at the top center and going around clockwise from there.
I've found that, for each quadrant (NE, SE, SW, NW), they really only need to read 3 of those 8 bits. So once you've set all the bits, you can focus on one quadrant and only read the 3 bits it needs and determine which tile it should load.
Now, as I plotted this out I noticed that the current path system has it so that each quadrant has 5 possible states that are split up between 8 different scenarios (shown above). And while I was thinking of how you would program it so that it reads both 000 and 010 as "exterior corner piece", I thought "why not use all 8 scenarios? It'd probably be easier".
So what if, for each path, we had NESmaker generate a table like this:
Code:
+---+---+---+---+---+---+---+---+---+
| |000|001|010|011|100|101|110|111|
+---+---+---+---+---+---+---+---+---+
|NE:| | | | | | | | |
+---+---+---+---+---+---+---+---+---+
|SE:| | | | | | | | |
+---+---+---+---+---+---+---+---+---+
|SW:| | | | | | | | |
+---+---+---+---+---+---+---+---+---+
|NW:| | | | | | | | |
+---+---+---+---+---+---+---+---+---+
This means that you could make a path with 32 unique tiles, but if you want to make it fewer than that, you can. You can make it behave exactly like the current paths do now, or you could make it so that you have two unique tiles for the left and top edges, but have the bottom and right edges use the same tile. You could have it so that instead of using an exterior corner piece in situation 010, you could have it connect diagonally to the path (the scenario for the SW quadrant in the path diagonal would also be 010). You could have it so that instead of using a straight edge in scenarios 110 and 011 you could have it start curving toward the direction the path is going. You could (I think?) have paths share tiles with each other while still behaving as though they're separate paths. You could even have all the offsets on the table point to the same tile so that it's just repeating the same tile over and over (not sure why you would, but you could is the point I'm making).
So I started trying to replicate the handlePath code, but do it so that it used the method described above.
Code:
handlePath:
LDA #$00
STA Path_Neighbors ;; This the variable that we're going to store the surrounding paths in.
;; Bit 0 at the top center, and going clockwise around the tile from there.
;; 1= Same Path in this slot, 0= Not the same path in this slot. Pretty simple, yo.
;; +---+---+---+
;; | 7 | 0 | 1 |
;; +---+---+---+
;; | 6 | | 2 |
;; +---+---+---+
;; | 5 | 4 | 3 |
;; +---+---+---+
TYA
STA nt_index_hold ;; nt_index_hold is how we can now restore y if we have to
;; corrupt it.
;; checkTopEdgePath:
CPY #$10
BNE checkLeftEdgePath
LDA #%10000011
STA Path_Neighbors ;; If the path is along the top row, set the top bits to 1 so that the path connects to the next screen
checkLeftEdgePath:
TYA
AND #%00001111
BNE checkRightEdgePath
LDA Path_Neighbors
ORA #%11100000
STA Path_Neighbors
CMP #%11100011
BEQ checkCRPathNeighbor ;; If both top and left sides are on the edge, there's no sense checking the bottom or right edges or re-checking the top row
checkRightEdgePath:
TYA
AND #%00001111
BNE checkBottomEdgePath ;; (btw I'm only assuming this and the left edge code checks the edges, I actually have no idea how this works)
LDA Path_Neighbors
ORA #%00001110
STA Path_Neighbors
CMP #%10001111
BEQ checkBCPathNeighbor ;; If both top and right sides are on the edge, there's no sense checking the bottom edge or the top and right tiles
checkBottomEdgePath:
LDA nt_index_hold
CMP #$E0
BCC checkTLPathNeighbor
LDA Path_Neighbors
ORA #%00111000
STA Path_Neighbors ;; Now that edges are taken care of, we can fill in the blanks
checkTLPathNeighbor: ;bit 7 - Checking this first so that we go from top to bottom (and make it easier to skip if top edge was set)
;; Check the Top Left (bit 7) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%10000000
STA Path_Neighbors
checkTCPathNeighbor: ;bit 0
;; Check the Top Center (bit 0) metatile for path. If it's the same, set that bit to 1.
BNE checkTRPathNeighbor
LDA Path_Neighbors
ORA #%00000001
STA Path_Neighbors
checkTRPathNeighbor: ;bit 1
;; Check the Top Right (bit 1) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%00000010
STA Path_Neighbors
checkCRPathNeighbor: ;bit 2
;; Check the Center Right (bit 2) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%00000100
STA Path_Neighbors
checkBRPathNeighbor: ;bit 3
;; Check the Bottom Right (bit 3) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%00001000
STA Path_Neighbors
AND #%01110000
CMP #%01110000
BEQ skipNeighborCheck ;This'll save a little time if it triggered the bottom edge check
checkBCPathNeighbor: ;bit 4
;; Check the Bottom Center (bit 4) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%00010000
STA Path_Neighbors
checkBLPathNeighbor: ;bit 5
;; Check the Bottom Left(bit 5 metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%00100000
STA Path_Neighbors
checkCLPathNeighbor: ;bit 6
;; Check the Center Left (bit 6) metatile for path. If it's the same, set that bit to 1.
LDA Path_Neighbors
ORA #%01000000
STA Path_Neighbors
skipNeighborCheck:
;; ======= Once we have our Path_Neighbors bits loaded, we can get the values for each quadrant and grab their offsets from a table =======
;; First we need to grab bits 0, 1, and 2 for the NE quadrant for the table offset (0-7) and then load the chr offset stored there into that quadrant.
;; LSR once, and if the carry is set, write 1 into bit 7 (this is important, as the NW quadrant needs the original value in bit 0) then LSR again, then do the same as above for the SE quadrant
;; LSR twice and record bits 0, 1, and 2 for the SW quadrant
;; Do the same as above for the NW quadrant (the original bit 0 should now be in bit 2)
;; You should now have all your quadrants set! :D
;; End handlePath
The code here is incomplete and I'm not even sure how parts of it work, but I'd love to hear feedback!
TLR - If my idea works, paths could be much more customizable with up to 32 unique tiles per path (the amount of tiles dedicated to paths would still be limited), opening up a wide range of versatility and the possibility for creative applications!