Banner
Views: 854,256,984
Time:
19 users online:  Anorakun, Aram, autisticsceptile1993, Ayami, brickblock369, Charizard746, DoodleMyNoodle, ft029, GbreezeSunset, JupiHornet, katun24, Kignak, LadiesMan217, LocoFuzzyPants, MORC, SimFan96, TypeII_Mound, UltiMario, westslasher2 - Guests: 59 - Bots: 56 Users: 47,143 (2,478 active)
Latest: rawros
Tip: Ask the author for permission before ripping graphics from their hack.Not logged in.
ObjecTool 0.4 (making custom objects)
Forum Index - SMW Hacking - SMW Hacking Help - Tutorials - ObjecTool 0.4 (making custom objects)
Pages: « 1 »
Hi, this is imamelia, and I'm here to teach you about a useful but horribly underused patch...ObjecTool. Yes, it's called ObjecTOOL even though it's a patch, and it's ObjecTool, not Object Tool. Were I the first to make it, I would have chosen the latter name, but I didn't make the original ObjecTool, 1024 (aka 0x400) did. But I digress. So, what exactly is an object? In technical terms, the "object" layer of the SNES is actually what we know as the sprite layer, and it has nothing to do with the objects we place in Lunar Magic. But we're not interested in that right now. In terms of foreground tiles in a level in Super Mario World (or Yoshi's Island, etc.), an object is a piece of code that runs once during level load. The vast majority of objects create Map16 tiles, although they don't have to. For example, screen exits in SMW are actually set by means of a certain extended object, and most Yoshi's Island hackers are probably familiar with the special objects that disable screen-scrolling in certain directions. This tutorial, though, will focus almost entirely on objects used to place Map16 tiles into levels.

First of all, to understand and work with this properly, you'll need ObjecTool, which can be found here. You'll also need some knowledge of ASM, preferably at least intermediate level. If you've coded any sprites or made significant edits to existing ones, you shouldn't have too much trouble coding objects. Objects can be very simple or very complex, but unlike sprites or patches, most of them follow very specific rules and patterns. For the most part, if you can understand the code of one object, you can understand a lot of them. If you care to look at the code for SMW's objects, it is all in bank 0D, and many objects have entries in the ROM map for their locations.

So, let's start making some objects, shall we? If you open the .zip folder of that patch, there should be six files: custobjcode.asm, custobjptrs1.asm, custobjptrs2.asm, custobjptrs3.asm, objectool04.asm, and the readme. You don't need to worry about custobjptrs1/2/3.asm; you will not need to modify them, but place them in the same folder as xkas and the main patch, objectool04.asm. (You could put them all in a subfolder, but then you'd need to mess with the incsrc paths.) You won't need to modify the main patch either, except possibly the freespace setting. The one you DO need to modify is custobjcode.asm, which is where all the code for custom objects will go. Currently, there is no code in there except example subroutines. (Feel free to examine the example subroutines; most of them have descriptions for what they do.) Now, where should we start...how about with something easy? Find the label "CustObj98:". This is where the code for EXTENDED object 98 will go. (Okay, so perhaps I should have made some distinction between extended objects and normal objects, but you shouldn't usually get confused since normal objects don't go any higher than 3F, while extended objects go from 00-FF, but the first custom extended object slot is 98...if you don't count slots 02-0F.) Now type this underneath the label:

CustObj98:

LDY $57
LDA #$30
STA [$6B],y
LDA #$01
STA [$6E],y
RTS

If you use that code, then extended object 98 will create a single cement block. What exactly does each line of code do, though? Well, let's break it down:

LDY $57

$57 holds the position of the object within a single subscreen. The lower 4 bits are the X position, and the higher 4 bits are the Y position. For example, if the object is at X=3 and Y=7 on any subscreen, $57 will be equal to 73. Most objects immediately load the value of $57 into Y, because...well, let's look at the next two lines:

LDA #$30
STA [$6B],y

A cement block is tile 130, which is why the 30 is here; it's the low byte of the Map16 tile we want to create. We put this value in [$6B],y. [$6B] is a 24-bit pointer that points to the RAM address containing the low byte of the current Map16 tile. But how do we know what the "current" Map16 tile is, exactly? Before the object code is run, [$6B] will point to the tile at the top-left of the current subscreen, i.e. position X=0/Y=0. Some object codes modify the pointer, although this one does not. And remember what I said about $57? To get the real "current" Map16 tile, we need to add an index to the pointer: [$6B],y. Assuming that Y is equal to the value of $57, this will reference the Map16 tile that is at the object's position. If we want to change which tile goes here, we store a value to this pointer. But what about the next two lines?

LDA #$01
STA [$6E],y

This is pretty much the same thing as the previous two lines, except that [$6E],y points to the high byte of the current Map16 tile instead. Then we simply terminate the object code:

RTS

You must always end object codes with RTS, never RTL. At least, if you're using this patch. What, then, would the code look like if we commented it? Perhaps something like this:

CustObj98: ;

LDY $57 ; load the XY position of the object into Y as an index
LDA #$30 ; cement block = tile 130; low byte is 30
STA [$6B],y ; set the low byte of the Map16 tile
LDA #$01 ; cement block = tile 130; high byte is 01
STA [$6E],y ; set the high byte of the Map16 tile
RTS ; end object code

Alternatively, if you want the entire tile number to be visible at once rather than being split up like that, you could use 16-bit mode:

LDY $57 ; load the XY position of the object into Y as an index
REP #$20 ; set A to 16-bit mode
LDA #$0130 ; cement block = tile 130
SEP #$20 ; A back into 8-bit mode
STA [$6B],y ; set the low byte of the Map16 tile
XBA ; switch the lower byte of A with the upper byte
STA [$6E],y ; set the high byte of the Map16 tile
RTS ; end object code

That is a little longer, though. (Two opcodes and four bytes longer.) It's really up to you which you want to do. You can change the Map16 number to whatever you want, as long as it's on a Map16 page that actually contains foreground tile data. For instance, you could change the low byte to 2B and the high byte to 00 to make it spawn a coin instead. Or if you wanted the object to spawn a custom block on page 5, you could set the high byte to 05.

Now, just placing one tile in the level doesn't do much good, except maybe for saving a couple bytes. (Extended objects take up only 3 bytes in the ROM, while direct Map16 tiles take up at least 5, so making extended objects for custom blocks that you know will always appear by themselves would save a small amount of space.) What objects, especially extended ones, are very useful for doing is creating different Map16 tiles depending on a certain condition. The switch blocks in the original SMW do this, for example. Let's make an object that will create a solid cement block only if the level it is placed in has been passed. If the level is not passed, it won't do anything at all.

LDX $13BF
LDA $1EA2,x
BPL .Return
LDY $57
LDA #$30
STA [$6B],y
LDA #$01
STA [$6E],y
.Return
RTS

What does THIS code do? Well, $13BF is the current overworld level number, and $1EA2 is a 96-byte table (one byte per overworld level) that holds some settings for each overworld level. Because this is a table, we can't just load $1EA2 directly; we have to make sure we index it by the overworld level. That's what the LDX $13BF is for. This way, if we're in overworld level 20, for example, the LDA $1EA2,x will read from the 0x20th byte of the table (starting at 0), $1EC2, which holds the settings for overworld level 20. To determine whether or not the level has been passed, we check bit 7. The format of $1EA2 is this:

Bit 0: Enable walking right from this level on the overworld.
Bit 1: Enable walking left from this level on the overworld.
Bit 2: Enable walking down from this level on the overworld.
Bit 3: Enable walking up from this level on the overworld.
Bit 4: Bring up a save prompt when the level is beaten.
Bit 5: If the level has already been passed, it cannot be reentered.
Bit 6: The midway point has been activated.
Bit 7: The level has been passed.

Of course, this isn't terribly important to coding objects, but since we're checking bit 7, we can use BPL/BMI. If that "BPL .Return" branches, then bit 7 is clear -> the level has not yet been passed -> we don't create the cement block tile. If the BPL does not branch, then bit 7 is set -> the level has been passed -> we run the code to create the cement block tile. Now let's modify the code slightly...what if we want the block to create tile 25 if the level is not passed? This is not the same thing as not doing anything at all; here, the tile under the object will be blank even if there was a non-blank tile there before (such as part of a bush or platform). This is one way to do it:

LDY $57
LDX $13BF
LDA $1EA2,x
BPL .Blank
LDA #$30
STA [$6B],y
LDA #$01
STA [$6E],y
RTS
.Blank
LDA #$25
STA [$6B],y
LDA #$00
STA [$6E],y
RTS

A possible alternative would be:

LDY $57
LDX $13BF
LDA $1EA2,x
BPL .Blank
LDA #$01
XBA
LDA #$30
BRA .StoreTile
.Blank
LDA #$00
XBA
LDA #$25
.StoreTile
STA [$6B],y
XBA
STA [$6E],y
RTS

Still another:

LowByte:
db $25,$30
HighByte:
db $00,$01

LDY $57
LDX $13BF
LDA $1EA2,x
ROL
ROL
AND #$01
TAX
LDA LowByte,x
STA [$6B],y
LDA HighByte,x
STA [$6E],y
RTS

If you want the tile to be blank when the level has been passed instead, just change the BPL to BMI or switch the 01 and 30 with the 00 and 25. You can even make an object with more than two possibilities for the tiles. A code for that might look something like this:

LDY $57
LDX #$00
LDA $1F2E
CMP #$0A
BCC .SetTile
INX
CMP #$14
BCC .SetTile
INX
CMP #$1E
BCC .SetTile
INX
CMP #$28
BCC .SetTile
INX
CMP #$32
BCC .SetTile
INX
.SetTile
LDY $57
LDA.w .LowBytes,x
STA [$6B],y
LDA.w .HighBytes,x
STA [$6E],y
RTS

.LowBytes
db $01,$02,$00,$30,$11,$1F
.HighBytes
db $01,$01,$00,$01,$01,$00

There is no significance to the low and high byte tables; I just picked numbers arbitrarily. But this extended object, rather than creating one of two possible tiles, will create one of six possible tiles depending on how many events have been activated. ($1F2E holds how many events have been activated.) If the player has activated fewer than 10 (decimal) events, the first tile will be used. If the player has activated 10-19 events, the second tile will be used. 20-29 events -> third tile. 30-39 -> fourth tile, 40-49 -> fifth tile, and if the player has activated 50 or more events, the sixth tile will be used. (And yes, we could make the tables each 13 bytes long and go in increments of 10 all the way up to 120 events, the maximum possible.) The way the code works is we start X at zero, keep checking for successively higher numbers, and increment X once every time we find that the number of events is greater than that number, and then when we finally reach the tile-storing code, the value of X is used to index the tile number tables. If the player had activated 37 events, for example, the code would find the number to be greater than 0x0A (10), increment X, find the number to be greater than 0x14 (20), increment X, find the number to be greater than 0x1E (30), increment X, find the number to be less than 0x28 (40), and then branch to the tile-setting code with X = 04.

And that's how you make a variable extended object! This knowledge alone is enough to be useful (among other things, you can make some snazzy switch blocks), but let's go further. So far, we've only worked with extended objects that create a single tile...how about making a larger one? The code for making an object that creates two cement blocks, one below the other, would be this:

LDY $57
LDA #$30
STA [$6B],y
LDA #$01
STA [$6E],y
JSR ShiftObjDown
LDA #$30
STA [$6B],y
LDA #$01
STA [$6E],y
RTS

This will make an extended object that creates a cement block at its position and a cement block directly below the first one. If we wanted the second block to be one tile to the right instead of one tile down, we'd replace "JSR ShiftObjDown" with "JSR ShiftObjRight". If we wanted the second block to be one tile above, we would use "JSR ShiftObjUp". And if we wanted the second block to be one tile to the left of the first one...you guessed it, JSR ShiftObjLeft. See, you couldn't do any of these things in the old ObjecTool, because it didn't come with any subroutines. In the old ObjecTool, to shift the tile to the right, you'd increment the tile index once, which could be done with a simple INY. To shift the tile down, you'd add 0x10 to Y (TYA, CLC, ADC #$10, then TAY). These methods will both work in the new ObjecTool as well, and they are actually quicker than using the subroutine (especially shifting the tile right), but unfortunately, they don't work properly with screen or subscreen boundaries. The position-shifting subroutines allow objects to go across screen boundaries and subscreen boundaries with no trouble at all, while simply changing the value of Y, if the object happens to straddle a screen or subscreen boundary, will cause the tiles not to go to the next subscreen, but rather end up on the other side of the current subscreen.

Now, check out this code:

LDY $57
LDA $1422
CMP #$05
BCS .EnableExit
LDA #$33
STA [$6B],y
LDA #$01
STA [$6E],y
JSR ShiftObjRight
LDA #$34
STA [$6B],y
LDA #$01
STA [$6E],y
BRA .BottomOfPipe
.EnableExit
LDA #$37
STA [$6B],y
LDA #$01
STA [$6E],y
JSR ShiftObjRight
LDA #$38
STA [$6B],y
LDA #$01
STA [$6E],y
.BottomOfPipe
JSR ShiftObjDown
LDA #$36
STA [$6B],y
LDA #$01
STA [$6E],y
JSR ShiftObjLeft
LDA #$35
STA [$6B],y
LDA #$01
STA [$6E],y
RTS

It looks long, but it's really just the exact same four lines repeated 6 times with a few intervening commands. This extended object is a 2-tile-high pipe...but if the player has collected all 5 Yoshi coins, then the pipe will be exit-enabled. First, we make sure that Y has the index to the Map16 pointers, and then we check the number of Yoshi coins ($1422). If it is less than 5, we use tiles 133 and 134 for the top of the pipe, but if it is 5 or more, we use tiles 137 and 138 instead. So if the BCS does not branch, then we set the current tile number to 0133, shift the base position right one tile, and set the next tile to 0134. If the BCS does branch, then we set the current tile number to 0137, shift the base position right one tile, and set the next tile to 0138. The bottom of the pipe will use the same two tiles whether the branch is taken or not, so we run that code either way. We just need to make sure to shift the object position down a tile first. (Also, the reason we are shifting left on the second layer instead of right is because the tile position was one tile to the right after creating the top of the pipe. If we shifted right instead of left, the bottom two tiles of the pipe would be one tile farther right than they should be. That's also why the code sets tile 136 first instead of doing 135 followed by 136.)

That's about all you need to know about extended objects! With this amount of information, you can make lots and lots of different extended objects. If you get stuck on one, you can always look at SMW's original object codes. All 256 extended objects (00-FF), even ones that were never used in the original SMW, have pointers. There is a table of 3-byte pointers to all the extended object codes that starts at $0DA10F, and the pointers are in the order of the extended object numbers. But wait! There's more. I've covered extended objects, but...ObjecTool 0.4, unlike the original (0.3), allows you to insert custom normal objects as well. (At least, assuming you're using Lunar Magic 1.80 or higher..extended objects should work with versions before that, but normal objects will most definitely not.) What's the difference between a normal object and an extended object? Extended objects always have a fixed size. (In fact, extended objects in SMW and YI are actually different forms of normal object 00; the bits that would normally be used to indicate the object size are interpreted as the extended object number instead.) Normal objects, conversely, can be resized along at least one axis. Normal objects in SMW include water (both animated and non-animated), pipes, standard ledges and edges, vines, and coins. As you can imagine, normal objects are more flexible, both figuratively and literally.

How exactly would we make a normal object, though, as opposed to an extended one? Well, first of all, we will be putting the code in the same .asm file, custobjcode.asm, but this time, we're using a different set of pointers. All custom normal objects have the same object number when you place them in Lunar Magic: 2D. Objects 22-2D were unused in the original SMW, and FuSoYa used some of them for features of Lunar Magic. Direct Map16 tiles, for example, are one of these new objects. He chose object 2D as the object number to use for custom ones. The note above the pointers even says so:

;; This is where you put the code for NORMAL object 2D.

But we can't just call all of our custom objects "object 2D", can we? I'd just call them "custom object XX", where XX is the custom object number. When FuSoYa reserved object 2D for custom objects, he did another thing to it: He made it 5 bytes long instead of 3 bytes. Objects in SMW, both normal and extended, are normally 3 bytes long, but many extra and custom ones are more. So yes, this does mean that while custom extended objects use no more space than original SMW extended objects, custom objects made with object 2D take up 66% more space in the ROM than original SMW objects do...but so do direct Map16 tiles. But what am I blabbering for? Let's see some action. Custom normal objects will go under one of these pointers:

CustObj2Dx00:
CustObj2Dx01:
CustObj2Dx02:
CustObj2Dx03:
CustObj2Dx04:
...

For simplicity's sake, I'll just use custom object 00 for my examples. It really doesn't matter which numbers you use, but it only makes sense to go in order. One simple normal object we can make is this:

LDA #$29
STA $0C
LDA #$00
STA $0D
LDX #$01
JMP SingleTileObj

This object will create an array of invisible P-switch-activated question-mark blocks. There is an object for this in SMW, extended object 16, but, well, it's an extended object. If we want a lot of these in one place, we'll be saving space by using a custom object. The big thing here is that since the object is made up of only one Map16 tile, we can use one of my subroutines. The "SingleTileObj" subroutine will create an object made up of only one Map16 tile that is as wide and as high as it is set to be in Lunar Magic. (Even though the graphics for object 2D are not correct in the editor, it does allow you to resize it, and I designed the patch so that the bits used for size in custom objects would be exactly the same as they are for non-custom ones.) This subroutine takes two pieces of input:

1) $0C-$0D should contain the Map16 tile number.
2) If we want to utilize item memory, X should be 00. If not, X can be anything but 00.

The first thing is pretty self-explanatory. We're creating Map16 tile 29, so store #$29 to $0C and #$00 to $0D. This works for any Map16 number, even custom blocks and the like. The second thing, on the other hand...well, you know how in the original SMW, if you put some coins in a level normally, go to a different sublevel, and then come back to the first sublevel, the coins will be gone, but if you place the coins using Direct Map16 Access instead, the coins will reappear? That's because Direct Map16 tiles do not take item memory into account, while object 05—which is what all coins in the original SMW were—does. Item memory tells the object, "Hey, some of these tiles have already been collected, so skip over them." We can use item memory in custom objects as well, but this one doesn't really need it, so we LDX #$01 before calling the subroutine. If we wanted the object to use item memory, we would put LDX #$00 instead. Note that if you don't want the subroutine to take item memory into account, X doesn't have to be 01; it could be anything, as long as it's not 00. If you want to LDX #$FF instead, or LDX #$42, or LDX #$27, go right ahead. Unless I modify that subroutine later to use more values of X, it doesn't really matter what number you use. All you need to do is set up those two variables (three of you count $0C and $0D separately) and jump to that subroutine, and voila—an object.

How about another one, this time with some variability? You know how the yellow and green switch blocks in SMW were extended objects, so if you wanted several of them in a row, you had to place several instances of the object? Let's create a yellow switch block that doesn't have that limitation.

LDA #$6B
STA $0C
LDX $1F28
LDA SwitchTileHighBytes,x
STA $0D
LDX #$01
JMP SingleTileObj

.SwitchTileHighBytes
db $00,$01

This isn't much different from the previous object. The switch block flags are $1F27, $1F28, $1F29, and $1F2A, and they are in the following order: green, yellow, blue, red. If the corresponding switch has been pressed, its flag is 01; if not, its flag is 00. Which tile numbers are the yellow switch blocks (one filled in, the other not)? Coincidentally, they are exactly one page apart: the outlined block is tile 6B, and the filled-in block is tile 16B. This means we don't even need to mess with the low byte; we can just set the high byte to 00 or 01 depending on whether the flag is set or not. In fact, this code can be made even shorter:

LDA #$6B
STA $0C
LDA $1F28
STA $0D
LDX #$01
JMP SingleTileObj

If you wanted to do the same thing with a custom block, you might make both the high and low bytes into a table. Either way, objects that use only one Map16 tile are easy...but let's up the ante. How would we make an object with two tiles in a checkerboard pattern? We couldn't use the SingleTileObj subroutine because, well, it's no longer a single tile. What we can do, however, is adapt it. This subroutine will create a checkerboard pattern of tiles 48 and 112, the always-turning turn block and on/off switch:

LDY $57 ;
LDA $59 ;
AND #$0F ; get the object's Y size
STA $00 ;
STA $02 ;
LDA $59 ;
LSR #4 ; get the object's X size
STA $01 ;

JSR BackUpPtrs ;

.StartObjLoop0 ;

LDA $02 ;
EOR $01 ;
AND #$01 ;
TAX ;

LDA .LowBytes,x ; set the tile number
STA [$6B],y ; this routine originally used a table for the low byte
LDA .HighBytes,x ; and checked the index to determine whether to set
STA [$6E],y ; the high byte to 00 or 01, but I have circumvented that

JSR ShiftObjRight ; shift the tile position right one tile

DEC $02 ; if there are more tiles to draw on this line...
BPL .StartObjLoop0 ; loop the routine

JSR RestorePtrs ; if not, restore the base tile position...
JSR ShiftObjDown ; and move on to the next line

LDA $00 ;
STA $02 ; also reset the X position counter
DEC $01 ; decrement the Y position (line) counter
BMI .EndObjLoop0 ; if still positive...
JMP .StartObjLoop0 ; loop the routine

.EndObjLoop0 ;
RTS ;

.LowBytes
db $48,$12
.HighBytes
db $00,$01

Notice the differences? For one, we can get rid of the item memory part, since we're not using it for this object. For another, take a look at this:

LDA $02 ;
EOR $01 ;
AND #$01 ;
TAX ;

$02 is the object's width and $01 is its height, so this basically means "if the X and Y coordinates of a tile are both even or both odd, X = 0; if one is odd and the other is even, X = 1", which gives us our checkerboard pattern. But I'm getting ahead of myself...let's figure out just what this code does exactly. First up is this part:

LDY $57 ;
LDA $59 ;
AND #$0F ; get the object's Y size
STA $00 ;
STA $02 ;
LDA $59 ;
LSR #4 ; get the object's X size
STA $01 ;

JSR BackUpPtrs ;

LDY $57 you already know. RAM address $59 contains the object's size. In SMW, for most objects, the upper 4 bits are used for Y size, and the lower 4 bits are X size. This code first takes the X size, clearing out the other 4 bits, and stores it to both $00 and $02. (Why it uses both will come up later.) Then it takes the Y size and divides it by 0x10, effectively reducing it to a number from 00-0F just like the X size, and stores that to $01. As for "JSR BackUpPtrs"...well, that simply backs up the values of $6B, $6C, $6E, and $6F in scratch RAM, because they will be modified later. Next comes this:

.StartObjLoop0 ;

LDA $02 ;
EOR $01 ;
AND #$01 ;
TAX ;

LDA .LowBytes,x ; set the tile number
STA [$6B],y ; this routine originally used a table for the low byte
LDA .HighBytes,x ; and checked the index to determine whether to set
STA [$6E],y ; the high byte to 00 or 01, but I have circumvented that

This part starts a loop and sets the tile number. I already explained the first half, which makes X equal either 00 or 01 depending on which tile is about to be used (the turn block or the on/off switch). The last four lines simply store the low and high bytes to the object data pointers. Now for the next short section:

JSR ShiftObjRight ; shift the tile position right one tile

DEC $02 ; if there are more tiles to draw on this line...
BPL .StartObjLoop0 ; loop the routine

The comments pretty much explain everything here. ShiftObjRight is a subroutine we've already encountered. Then we decrement $02, which holds the counter for the number of tiles left to draw on the current line. If $02 is still positive, then we go back to the start of the loop and run the whole tile-storing code again. (Remember that we shift the tile position right one tile every time.) Finally, we have this:

JSR RestorePtrs ; if not, restore the base tile position...
JSR ShiftObjDown ; and move on to the next line

LDA $00 ;
STA $02 ; also reset the X position counter
DEC $01 ; decrement the Y position (line) counter
BMI .EndObjLoop0 ; if still positive...
JMP .StartObjLoop0 ; loop the routine

.EndObjLoop0 ;
RTS ;

Remember when we backed up the values of the object data pointers? The "RestorePtrs" subroutine, as you can imagine, restores the values of those. "ShiftObjDown" is a subroutine that we've already seen. The next two lines are important: LDA $00, then STA $02. Why did we store the object's X size to both $00 and $02? Because we need to decrement $02 to determine how many times to create a tile without moving to the next line, but we also need to keep track of the object's X size so we know how many tiles to create on each successive line. We don't need to do that with the Y size, because look at the next three lines. This decrements $01, which holds the Y size, and if the result is positive, we go back to the start of the loop again to create the next line of tiles. We don't need to keep track of the Y size, because once $01 drops below zero, there is no more code to run. The object is finished.

This code can be used as a template for all kinds of objects. $00 holds the X size, $01 holds the Y size and current line of tiles being created, and $02 holds the current tile being created on a particular line (starting at the X size and counting backward), and you can use these three variables for quite a large variety of stuff. Want to make a ledge with solid tiles on the top? Check $01 and use a different tile number if the line count is 0. Want to make a vertical column with edge tiles on both sides? Check $02 and use different tiles if the line count is 0 or maximum (i.e. equal to $00). Or what if you want to make an object that is always a fixed number of tiles high but can be extended many tiles along one axis, like object 21 in the original SMW? Simply store a specific number to $01, and store $59 in its entirety to $00/$02 rather than splitting it up.

Speaking of objects not always using 4 bits for height and 4 bits for width, how about those vertical pipes? Lunar Magic says "0F _Y Vertical pipe, end on top... [new line] ...with exit enabled.", et cetera. But none of them except the first have an object number before them...are the vertical pipes all object 0F? The answer is yes. The upper 4 bits of $59 are still used for object Y size, but the lower 4 bits are not used for object X size. They are instead used within the object code to determine which pipe the current object is. The horizontal pipes do the same thing, except that the lower 4 bits of $59 are used for object X size and the upper 4 are used within the object code to determine which pipe it is. But now take a look at $0DAB3E, the code for all the different slope objects (object 12):

CODE_0DAB3E: A5 59 LDA $59
CODE_0DAB40: 29 0F AND.B #$0F
CODE_0DAB42: C9 0A CMP.B #$0A
CODE_0DAB44: 30 06 BMI CODE_0DAB4C
ADDR_0DAB46: 38 SEC
ADDR_0DAB47: E9 0A SBC.B #$0A
ADDR_0DAB49: 4C 42 AB JMP.W CODE_0DAB42

CODE_0DAB4C: 22 FA 86 00 JSL.L ExecutePtrLong

PtrsLong0DAB50: 6E AB 0D .dw CODE_0DAB6E .db :$CODE_0DAB6E
21 AC 0D .dw CODE_0DAC21 .db :$CODE_0DAC21
92 AC 0D .dw CODE_0DAC92 .db :$CODE_0DAC92
44 AD 0D .dw CODE_0DAD44 .db :$CODE_0DAD44
A3 AD 0D .dw CODE_0DADA3 .db :$CODE_0DADA3
EB AD 0D .dw CODE_0DADEB .db :$CODE_0DADEB
6D AE 0D .dw CODE_0DAE6D .db :$CODE_0DAE6D
FC AE 0D .dw CODE_0DAEFC .db :$CODE_0DAEFC
61 AF 0D .dw CODE_0DAF61 .db :$CODE_0DAF61
EA AF 0D .dw CODE_0DAFEA .db :$CODE_0DAFEA

Here is something new...the lower 4 bits of the object size are being used as a pointer index. (Since there are only 10 different slope objects, if the bits are equal to 0A or above, the code just converts them into a smaller number.) You can easily do this in a custom object code, too:

LDA $59
AND #$0F
JSL $8086DF

dw .Type0
dw .Type1
dw .Type2
dw .Type3
dw .Type4
dw .Type5
dw .Type6
dw .Type7
dw .Type8
dw .Type9
dw .TypeA
dw .TypeB
dw .TypeC
dw .TypeD
dw .TypeE
dw .TypeF

Under all the .TypeX labels, you just put the code for each object type. (The word "type" is often used to describe forms of the same object when the object does not have both X and Y size bits.) For instance, if you were making a custom object for the red line platforms in Yoshi's Island and one for the wooden pipe platforms from the same game, you wouldn't need the height bits, since both of those objects will only ever be stretched horizontally. You can instead use them as an object type, so that, for example, custom object 12/type 0 is the red line platform, and object 12/type 1 is the wooden pipe. Of course, if ".TypeX" starts to get confusing, it's easy enough to change the labels to something descriptive, like:

dw YIRedLinePlatform
dw YIWoodenPipe
dw YICloudPlatform
dw SMB3WoodenPlatform

Note, however, that Lunar Magic will still act as if the other 4 bits were height bits instead of type, and the same is true for objects that use the width bits as type. The YI wooden pipe here will show up in Lunar Magic as an object that is 2 tiles high, even though it is never more than 1 tile high in game. Just another reason why FuSoYa should make tooltips for custom objects, I guess. (If enough people would use them, then he might make them...you never know!)

There isn't much left I need to mention about coding objects, really. One thing is item memory. Nine times out of ten, you don't even need to call the item memory subroutine directly, since objects that use it usually consist of only one Map16 tile (such as a coin), and you can just use the SingleTileObj routine. But what if you want to use item memory for an object that uses more than one Map16 tile? Don't worry, it's not hard to do. The subroutine GetItemMemoryBit will give you a boolean output that will indicate whether the item memory flag that corresponds with a particular column of tiles on a certain subscreen is set or not. Yes, I did say "column"...unfortunately, the way SMW's item memory routine works, objects that are within the same subscreen and at the same X position will share the same item memory flag. To use this subroutine, however, you simply have to call it and then check the value of $0F. If $0F is equal to zero after calling the item memory subroutine, then that item memory bit is not set. If $0F is not equal to zero, then the item memory bit is set. So you could do something like this:

JSR GetItemMemoryBit
LDA $0F
BNE .SkipTile
; tile-storing code
.SkipTile
DEC $02
BPL .StartObjLoop

There is one thing left that I must bring up: tileset-specific objects. In the original SMW, for some reason, all objects were treated as if they were tileset-specific. The main object-loading routine at $0DA40F jumps to different loading routines for objects 01-3F depending on the tileset, even though most of them are not tileset-specific. But we are better than that, aren't we? It is fully possible to make a tileset-specific custom object. During object routines, $1931 holds the current tileset number, and it can go from 0-E (technically 0-F, but tileset F is unused and does not work correctly; there are only 15, not 16, pointers for objects in the original SMW anyway). So the code for a tileset-specific custom object would go something like this:

LDA $1931
JSL $8086DF

dw .Tileset0
dw .Tileset1
dw .Tileset2
dw .Tileset3
dw .Tileset4
dw .Tileset5
dw .Tileset6
dw .Tileset7
dw .Tileset8
dw .Tileset9
dw .TilesetA
dw .TilesetB
dw .TilesetC
dw .TilesetD
dw .TilesetE

You could theoretically make tileset-specific extended objects just as easily, although I don't think there are any original ones that are.

That's all I have to teach. As I said in my introduction, ObjecTool (even 1024's original version) is quite a useful patch, but it seems underused. Very few people make custom objects, even out of those who do ASM. I hope this tutorial will teach more people how to make custom objects and how to use ObjecTool. If anyone has any suggestions for the tutorial, feel free to mention them. Are there confusing parts? Did I leave out things? Is there other object-related information I could provide? Speak up if you want.
I guess this is useful....maybe. IT'S A WALL OF TEXT!

Layout by algorithmshark. Additional help by mockingod.

Linkable Links: Youtube Channel - If any of you want me to do hack videos, I will. I cannot guarantee sound, however.

Shadow of A Hero Progress:


Not doing much.
Ah, another fantastic tutorial I've been anxiously awaiting for some time. I've looked at ObjecTool briefly in the past whilst having a few ideas for custom objects, but have never really understood how to properly use it. This will help immensely indeed.


@Hu
Sheesh, as much as I've perused through the tutorials forum, I can't believe I've never seen that thread - makes me feel like an idiot, especially since I was looking through some other stuff on the third page just the other day.
Originally posted by Milk
Ah, another fantastic tutorial I've been anxiously awaiting for some time. I've looked at ObjecTool briefly in the past whilst having a few ideas for custom objects, but have never really understood how to properly use it. This will help immensely indeed.

:/ (That's a link)

imamelia's is likely better (Haven't read it yet, he's just better at explaining things than I), but it's not like it hasn't been around...
Woo!

Hmmm. Thanks for the tutorial!

Now I can use this in my team hack :D

Everyone should go to #radbusiness, my epic irc channel, RIGHT NOW!
Layout by the super-mega-awesome Counterfeit! THANKS!
Taking ASM requests! PM me if ya want something, but don't ask for sprites.
Also please install Dropbox It'll help both you and me! :3


Fixed the download link, since the updated version of the patch was accepted.

Would anyone rather I put the tutorial in a .txt file in Dropbox and linked it rather than having the entire thing in a post?
Actually, that would be a decent idea. But either way, I don't really care. This is still an awesome tutorial :D

Everyone should go to #radbusiness, my epic irc channel, RIGHT NOW!
Layout by the super-mega-awesome Counterfeit! THANKS!
Taking ASM requests! PM me if ya want something, but don't ask for sprites.
Also please install Dropbox It'll help both you and me! :3


Tilesets up to E...
Is tileset F used for anything?

--------------------
Let's milk Sunny Milk. Then she'll have enough money to fund Sunny Milk Real Estate.
Everypony's digging with a shovel
No, tileset F is unused (both object and sprite tilesets, in fact). I would have included it, but as far as I know, all the lists in SMW that are indexed by tileset only have 15 sets of data rather than 16, which would probably cause tileset F to glitch up. Now that I think about it, though, it seems like we or FuSoYa could use tileset F for something...maybe that should have been the original Super GFX Bypass indicator.
I'm curious-- is there any type of file for displaying custom object names in Lunar Magic (like .msc for music, .ssc for sprites, and .dsc for blocks)?

–=–=–=–=–=–=–
Alyssa's Unlikely Trap - Zeldara's Glitch City
No, there isn't, and I definitely wish there were. If you want one, though, say something here. The more people bring it up, the more likely it is to happen.
Any idea how to insert custom objects into Lunar Magic? It didn't say in this tutorial.

--------------------
100% Orange Juice Playthrough:
https://www.youtube.com/playlist?list=PLf1kPWkjcurtA3xPP3TybfqSiEn1AcX2A

VLDC9 Playthrough:
https://www.youtube.com/playlist?list=PLf1kPWkjcurtiP5de_-e6q0hSVrY37RB-.


Custom extended objects should be inserted as normal object 0, with the custom object number in the "Size/Type/Ext" box. Custom normal objects should be inserted as normal object 2D, with the custom object number and any extra settings in the "Extension" box.
I'm curious. Why couldn't you make this as a patch? I'll try learning this.

--------------------
My Mode 0 guide.

My Discord server. It has a lot of archived ASM stuff, so check that out!
I've got a suggestion to make. If you put a big bush object overlapping another big bush some of the tiles become different ones how would you do that?#tb{:?}
You can not only place but also load the Map16 tile at the current position. In case of the bush, if tile at the current position is part of the bush (or not empty altogether), you'll use the filled tile else the empty tile.

--------------------
Okay, my layout looks ugly.
Pages: « 1 »
Forum Index - SMW Hacking - SMW Hacking Help - Tutorials - ObjecTool 0.4 (making custom objects)

The purpose of this site is not to distribute copyrighted material, but to honor one of our favourite games.

Copyright © 2005 - 2020 - SMW Central
Legal Information - Privacy Policy - Link To Us


Menu

Follow Us On

  • YouTube
  • Twitch
  • Twitter

Affiliates

  • Super Mario Bros. X Community
  • ROMhacking.net
  • Mario Fan Games Galaxy