Main principles of creating an "object"

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Main principles of creating an "object"
by on (#140830)
So, I think I'm at the point where I can ask this question. What I mean are what are the steps to creating an object like gnawty from DKC? There are many things I can think of, like actually programming the object (how it walks and attacks, and this of course includes collision detection,) looking to see if anything shares the objects palette, looking for an empty vram slot, keeping track of where it is in vram and updating the tiles, (you would have to make an animation engine) and actually making it through a metasprite making routine and other things. (this would probably go last.) I'd just like to know a good way to link everything together. I also kind of wonder how you would have multiple objects on screen at once and not interfere with the other objects. I mean like having an x and y position for every object and maybe a general purpose register for a timer and whatnot. I guess you could set it up like a sprite buffer or something like that, but how would you remember what registers go to what object? Grr...
Re: Main principles of creating an "object"
by on (#140838)
Let's say that in your game, each object has an "x_position" register, a "y_position" register, and a "metasprite_data_address" register. You can define each of them with an 8-bit address.

x_position = $00
y_position = $02
metasprite_data_address = $04

If you set the Direct Page to $0200 like this:

lda #$0200
tcd

Your object controlling code would work, except it will read and write from $0200, $0202 and $0204, instead of $00, $02 and $04. You can have multiple sets of identical registers by changing the Direct Page. You can do this:

lda #$0200
tcd
do "object A" stuff here
lda #$0280
tcd
do "object B" stuff here
lda #$0300
tcd
do "object C" stuff here
Re: Main principles of creating an "object"
by on (#140840)
Quote:
but how would you remember what registers go to what object?


I think what you're getting at is, how do you manage these objects?

With a list.
Then you stop caring about what position it is in the list, just that it is in there somewhere.


So you'd have a method to 1) clear the entire list, 2) add an object to the list (or signal an error if it's full, or silently overwrite the oldest entry, or the last slot, or whatever). And 3) a method to remove the object from the list (mark the item in the list as unused).

When it comes time to process the list, additional code would look at first of all what kind of thing is in this slot, with something like value 0 meaning skip and look immediately at the next slot as it's not used. or value 1 could be goon enemy, and value 2 could be bird enemy, value 3 is an explosion that animates for a specific series of frames, etc.

Based on that "kind of enemy" variable, call the code that processes it using a jump table with kind of enemy as the index (or more easily with a series of comparisons and conditional branches (if equal to that value branch here)), that code process the enemy logic, then have it return back so the next item in the list can be processed, until everything in the list has been looked at, then move on.

When calling the object handling code, you would need to pass it a memory address pointer (in a register, on the call stack, or in a specific fixed memory location, or another index) to the memory area in this list that the object handling code can modify and reference. So things like the X position, Y position, internal-state, name (if it matters), and anything else you can think of are referenced offset off of this memory pointer. (let's say internal state is a word at my_memory_pointer+6).



As to how long the object list is, or how many variables need to be in each slot in the list, that's totally dependent on the worst case of what the game design demands. Also it would be wise for debug code for complain that the list is too small, to catch the errors during play testing.
Re: Main principles of creating an "object"
by on (#140842)
I think I remember koitsu saying that the ca65 assembler always has the index register set to 0, so I won't have to worry about that now. I'm wondering if maybe you would need to have a table in ram that would store different things for each objects like:

Object 1:

x_position = $00
y_position = $02
metasprite_data_address = $04

Object 2:

x_position = $06
y_position = $07
metasprite_data_address = $08

Object 3:

x_position = $0A
y_position = $0C
metasprite_data_address = $0D

etc...

I guess whenever you spawn a new object by looking at a table when scrolling (which I think someone explained to me how, but I don't remember...) you would have an incrementing pointer so you don't just write to the first object every time. I was wondering how you would tell what each object was by its slot in the table, but I guess you could have an "object identification" byte, (or bytes) that would say what each object is, like if it's a Koopa or a Goomba or something like that. How many slots for objects do you think I should have? I'd say you could have 128 because this grantees you have at least the same number of objects as sprites, but this would be very circumstantial but if every object somehow only used 1 sprite, they could all still show.
Re: Main principles of creating an "object"
by on (#140844)
I don't write SNES code, most of the logic involved would be the same no matter the system, so I'll share some of the information I find important.

Like whicker said, you should maintain a list (or an array) of objects. How many bytes per object is up to you, and depends on how much state you want to keep track of. Each object has a type, a position, physics data, graphics pointers, counters, and so on, so be sure to pick a size that will comfortably hold all the data you need. Keep in mind that if only a couple of objects (such as bosses) are overly complex and need significantly more RAM, you can have it occupy more than one object slot, instead of making each slot huge and hardly use the space most of the time. Not such a big problem on the SNES, which has quite a bit of RAM, but still.

Whenever you need to load a new object, be it from a list of objects in ROM or a dynamic object (like an explosion), just look for an empty slot (scan the list looking for an invalid object type, which acts as a flag to indicate that the slot is free) and put it there. Set its type, position and anything else necessary to get the object started. Similarly, when the object is done, just have it overwrite its type with the "empty" flag and the slot is free again.

To process the objects, scan the object list every frame, and whenever you find a valid object type, use that to jump to the object's AI routine. A jump table would be the best option here. Accessing the chunk of RAM reserved for each slot can be done in different ways. On the NES, we can use indirect (a pointer indicates the beginning of the RAM chunk) or indexed (the X register indicates which slot is being used, and the attributes are interleaved) addressing, and I'm sure the SNES provides even more ways for you to do that. There's not a single right way to do it, so use what makes you more comfortable.

The important thing is that the AI routine always manipulates the chunk of RAM that belong the current object. This will allow your objects to work from whatever slot they're loaded in. You can also write functions that can be reused for several objects. This is useful for physics, metasprite rendering, and all sorts of tasks that are common for many objects. Just call these functions from within the object's AI code and they'll also work on the appropriate chunk of RAM.

About interactions between objects: most of them need to interact with the player, so it makes sense to put the player at a know position in the list (the first position is a good candidate), so you can have the other objects call functions like "TestCollisionWithPlayer" and these routines know where to find the player. Object management gets a little more complicated when more objects have to interact with each other. In this case, it helps to have separate lists of objects: one for projectiles, another for enemies, and so on, depending on which groups interact with which. This will allow enemies to call a "TestCollisionWithProjectile" routine that will scan only the list of projectiles looking for a hit.
Re: Main principles of creating an "object"
by on (#140845)
Espozo wrote:
How many slots for objects do you think I should have? I'd say you could have 128 because this grantees you have at least the same number of objects as sprites

I think 128 is overkill, and there's no way in hell you'd be able to update 128 objects with physics and collisions and keep the game running at 60fps. The exception would be a bullet hell shooter, but in most other cases you won't get anywhere near to 128 active objects. You'll be scanning this list quite frequently, so making it exceedingly long might still impact performance even if you're not using most of it most of the time.

You could build a couple of linked lists to help you manage the main object list (I do this in my NES projects). One of them lists empty slots and the other lists slots in use. This makes loading objects faster (no need to scan the list, just claim the first unused slot and remove if from the list of unused slots). Processing objects is also faster, because you only need to check the slots that are listed as used, instead of checking them all and looking at the object type.

If you do decide to have as many object slots as 128, I'd say it's essential that you have these linked lists to help you manage them.
Re: Main principles of creating an "object"
by on (#140847)
When you say "linked list" do you mean a list that that says what slots in the list are used and what aren't? Couldn't you just have it to where you check to see if the "object identification" byte in each slot is 0, meaning that the slot is empty and you can just jump to the next one? Also, I am planning on making a run and gun game (which I will never finish) and I want 128 objects for the worst possible scenario. I really think there could be something like 128 of something like explosions and the game wouldn't slow down too much, if at all, because all you would need to do is animate the explosion every couple of frames and build the metasprite. You wouldn't have to do collision detection or any real programming aside from knowing when to update vram and when to "kill" the object.

You know, about bosses taking up extra slots, I think DKC actually does something like this, because if you move Very Gnawty in a place he wasn't designed to be in, he plays his hopping animation, but he doesn't even move and he doesn't take damage either. I guess that the bytes that control his movement and health meter are being used for something else or other, so he is effectively messed up. Other bosses act in a similar manner.
Re: Main principles of creating an "object"
by on (#140849)
Espozo wrote:
When you say "linked list" do you mean a list that that says what slots in the list are used and what aren't?

Yes. A liked list is a list where each element points to the next. For example, a variable would indicate that slot 35 is the first free slot. Then, inside slot 35, you'd find the index of the next free slot, and so on, until the last free slot, that has an invalid index signaling its the last.

Quote:
Couldn't you just have it to where you check to see if the "object identification" byte in each slot is 0, meaning that the slot is empty and you can just jump to the next one?

Sure you could, but when you have 128 slots this might start to have an impact on the performance of the game. Each time you load a new object (which will happen as the player or the enemies fire their guns or the level scrolls), you'll have to scan the list looking for a free slot, and this could happen multiple times in the same frame. With a linked list you can just look at the variable that indicates the first free slot and you immediately know you can use the slot it indicates. To remove it from the list (since you'll be using it), all you have to do is read the index of the second free slot (which is stored somewhere in the slot you just claimed) and store it in the variable that indicates the first free slot. This means you're gonna make use of the first free slot and the second free slot becomes the first. This is really quick and you don't have to do any scanning.

Quote:
You wouldn't have to do collision detection or any real programming aside from knowing when to update vram and when to "kill" the object.

I guess that "dumb" objects like these could coexist in large numbers, as long as you don't animate them all at the same time. I still doubt you'll ever reach 128 though. =)
Re: Main principles of creating an "object"
by on (#140850)
If checking to see if a byte is 0 or not is such a big burden, how would you have it to where you go where depending on what the "identification" byte is? I doubt you would check a single byte 256 times just to find out where to go. I guess you could load the "identification" byte into y, and jump to a location specified by a table that is being indexed by y? Would this work, because it appears to make sense but I've never tried this before, so I don't have a clue.

Could you have something like this and have each name be a value that says where to jump to?

dw #nothing,#goomba,#koopa,etc...

Quote:
I guess that "dumb" objects like these could coexist in large numbers, as long as you don't animate them all at the same time. I still doubt you'll ever reach 128 though. =)

Well see... :twisted: (there's probably going to be plenty of overdraw problems, but that's a different story.)
Re: Main principles of creating an "object"
by on (#140851)
There is the instruction "jsr (abs,x)."

The 128 slots thing, I had trouble with that. I find dp slots more convenient than index slots, except that the direct page has to be in bank 0, and you're limited to 8kB.
Re: Main principles of creating an "object"
by on (#140853)
Espozo wrote:
I think I remember koitsu saying that the ca65 assembler always has the index register set to 0, so I won't have to worry about that now.

I'm not sure what you're referring to here. Index registers (X and Y) are set to whatever you set them to (via ldx/ldy). On power-on/reset the CPU sets them to 0. But none of that has to do with the assembler. Can you shed some light on this?
Re: Main principles of creating an "object"
by on (#140855)
Espozo wrote:
If checking to see if a byte is 0 or not is such a big burden

It normally isn't, but if you do it several hundred times per frame then it may start to become a problem.

Quote:
how would you have it to where you go where depending on what the "identification" byte is? I doubt you would check a single byte 256 times just to find out where to go. I guess you could load the "identification" byte into y, and jump to a location specified by a table that is being indexed by y?

That's the general idea behind a jump table, but I'm not sure about the 65816 specifics, since I'm a 6502 guy! =)

Quote:
dw #nothing,#goomba,#koopa,etc...

Sure, that's the idea. You don't need the # because these are not immediate values, but addresses. Also, you don't need to declare an address for the null/nothing object, you can start from the first valid object and read the table using JumpTable-2, y instead if JumpTable, y. Sounds like a stupid optimization, but why waste space you don't have to? =)

psycopathicteen wrote:
There is the instruction "jsr (abs,x)."

This is perfect for a jump table! Just use jsr (JumpTable-2, x) and you won't need to waste any bytes because of the null object.
Re: Main principles of creating an "object"
by on (#140856)
koitsu wrote:
I think I remember koitsu saying that the ca65 assembler always has the index register set to 0, so I won't have to worry about that now.I'm not sure what you're referring to here. Index registers (X and Y) are set to whatever you set them to (via ldx/ldy). On power-on/reset the CPU sets them to 0. But none of that has to do with the assembler. Can you shed some light on this?

Just forget what I said completely, as I don't have a clue as to what I'm talking about. :roll:

Quote:
There is the instruction "jsr (abs,x)."

Well, yeah. I thought that that was the only way you could do it. This is how you'd write it correct?

Code:
ldy  ObjectIdentificationRegister
jsr  (would a # go here?)ObjectIdentificationTable,y
Re: Main principles of creating an "object"
by on (#140857)
I think you mean direct page - ca65 is apparently 6502-oriented, and doesn't know that the zero page can be relocated on a 65816...
Re: Main principles of creating an "object"
by on (#140858)
yeah... You're right. :oops:
Re: Main principles of creating an "object"
by on (#140860)
Espozo wrote:
Quote:
There is the instruction "jsr (abs,x)."

Well, yeah. I thought that that was the only way you could do it. This is how you'd write it correct?

Code:
ldy  ObjectIdentificationRegister
jsr  (would a # go here?)ObjectIdentificationTable,y

No. Multiple things here:

1. Not all instructions/addressing modes support interchangeable X and Y index registers. Rephrased: you cannot always replace X with Y or Y with X in an instruction. The instruction above happens to be one of them.

2. The instruction psychopathicteen and tokumaru mentioned is jsr (address,x). The addressing mode is officially called "indexed indirect X". The parenthesis (()) are necessary (they indicate indirect addressing), and the only index register supported in this mode is the X index register, so you cannot use Y with it.

3. No, hash (#) would not go there. There is no JSR immediate addressing mode. Think about it: jsr #$1234. What do you think this would do? (Again: this addressing mode doesn't exist) Compare that to jsr $1234. Now think about this (this makes even less sense): jsr #$1234,y

If you'd like someone to explain to you the following concepts, just say so:

* Addressing mode: absolute indirect -- lda ($1234)
* Addressing mode: absolute indexed indirect X -- lda ($1234,x) -- a.k.a. "indirect pre-indexed"
* Addressing mode: absolute indirect indexed Y -- lda ($1234),y -- a.k.a. "indirect post-indexed"

There is no "absolute indexed indirect Y" (lda ($1234,y)) or "absolute indirect indexed X" (lda ($1234),x) addressing mode.

Yes, the order of the words "indexed" and "indirect" matter depending on what you're trying to say. Yes I know that's confusing. That's why some people use "pre-indexed" or "post-indexed".

I feel that right now in your 65816 training, indirect addressing would likely end up confusing you, especially when you're having difficulty knowing when/where to use #. For now, just know this: ever hear of "pointers" in C? Ever wonder "how" they work? Indirect addressing is how.

Here's the opcode chart for JSR and JMP. Be sure to note there is no indirect indexed Y addressing mode:

$4C -- JMP absolute -- jmp $1234 -- supported by 6502, 65c02, 65816
$6C -- JMP indirect -- jmp ($1234) -- supported by 6502, 65c02, 65816
$7C -- JMP absolute indexed indirect X -- jmp ($1234,x) -- supported by 65c02, 65816
$5C -- JMP absolute long -- jmp $801234 -- supported by 65816
$DC -- JMP absolute indirect long -- jmp [$1234] (where $1234 contains a 24-bit address) -- supported by 65816

$20 -- JSR absolute -- jsr $1234 -- supported by 6502, 65c02, 65816
$FC -- JSR absolute indexed indirect X -- jsr ($1234,x) -- supported by 65816
$22 -- JSR absolute long -- jsr $801234 -- supported by 65816

A common alias opcode for long addressing with JMP is jml, and for long addressing with JSR is jsl.
Re: Main principles of creating an "object"
by on (#140864)
I think indirect long means that it's an address pointing to a long address, not the other way around. I could be wrong though, knowing how confusing the addressing modes on the 65816 are.
Re: Main principles of creating an "object"
by on (#140880)
Using the equivalent of an array of structures on C will do the job?
Elements of the array are consecutive on memory, each element of the array has a fixed size.
The size of each element must be a Power of two so all the address calculations can be done with ASL and LSR

The "structure" will be something like this:

Code:
;object attributes
.ENUM $00
    OBJECT_X             DW
    OBJECT_Y             DW
    OBJECT_PALETTE DB
    ;etc...

    OBJECT_RTN        DW ; Index on a table that contains the addresses
                         ; of the subroutines for each type of object
                         ; One type can be a coin other an enemy , an special block , ect..
                         ; Each one behaves diferrent so each one has a diferent subroutine
.ENDE


You have an array of these structures and a list that indicates which ones are active and what elements of the array are "empty".
You can use each bit of the list as a binary flag to indicate if that element is "empty" or not.
If you want 128 objects you will need only 16 bytes for that list.
I think that checking the list will be relatively fast using ASL and BCS/BCC.
If you want to "create" a new object , set the bit to 1 and initialize the values at the array.
if you want to free the element just reset the bit on the table to zero.

For processing each element , set the DP register to the address of the array element and jump to his subroutine.
The subroutine of each element can use DP addressing mode so is faster and , because all the object atributes addresses
on the subroutines are relative to the DP register , they will be the same in all the objects , thats more speed and less trouble.

Sorry for the broken english :oops:

Quote:
Yes. A liked list is a list where each element points to the next. For example, a variable would indicate that slot 35 is the first free slot. Then, inside slot 35, you'd find the index of the next free slot, and so on, until the last free slot, that has an invalid index signaling its the last.


This is how malloc() , free() and similars are implemented on C , right?
At least in the explanation on K&R
Re: Main principles of creating an "object"
by on (#140884)
kp64 wrote:
a list that indicates which ones are active and what elements of the array are "empty".
You can use each bit of the list as a binary flag to indicate if that element is "empty" or not.
If you want 128 objects you will need only 16 bytes for that list.

That doesn't make any sense! This won't make the object management any faster, because scanning and manipulating a list of bits could be even slower than accessing the object slots directly (since you'd have to divide the slot index by 8 and use bitmasks to access the individual bits), where you could look at the object type in order to know whether the slot is occupied.

A linked list however will give you direct access to empty/occupied slots without any need gor scanning. This was just a suggestion though, it would probably be better to use the object types to identify the status of the slots, and only implement these other lists if the basic method proves itself slow.
Re: Main principles of creating an "object"
by on (#140887)
koitsu wrote:
Espozo wrote:
I think I remember koitsu saying that the ca65 assembler always has the index register set to 0, so I won't have to worry about that now.

I'm not sure what you're referring to here. Index registers (X and Y) are set to whatever you set them to (via ldx/ldy). On power-on/reset the CPU sets them to 0. But none of that has to do with the assembler. Can you shed some light on this?

I think Espozo might be remembering those last two topics where my LoROM project template came under discussion. ("Ho-ly-shit. Really?") In this post, you wrote:
Footnote: I'm kinda disgusted how ca65 has no real concept of 65816 direct page (only 6502 zero page). The best you can do is declare zero page in $0000-00ff and then the rest as BSS. I kept rolling my eyes over this. But hey, at least the assembler can generate sane listings. :-)


Having been originally designed for 6502, ca65 assumes that D=$0000 and uses direct page addressing mode for addresses in $000000-$0000FF. I still need to figure out reliable patterns to suppress this behavior in case someone wants to use D as a base pointer while still accessing global variables on real zero page.

koitsu wrote:
Yes, the order of the words "indexed" and "indirect" matter depending on what you're trying to say. Yes I know that's confusing. That's why some people use "pre-indexed" or "post-indexed".

And why I'm more likely to name indirect or indexed addressing modes by the abbreviation in the WDC datasheet's opcode matrix, such as "(d),y" or "(a,x)".
Re: Main principles of creating an "object"
by on (#140890)
And now for the platform-independent part of my reply:

Let me try to explain tokumaru's linked list method in different terms. There's a global variable for the index of the first empty slot. Each empty object has a type of "empty" and a state variable containing the index of the next empty slot. Because objects of type "empty" will never be used, you can even reuse one of the coordinate variables. The slots behave as a stack, meaning the slot of the last object to be destroyed will be the first slot to be reused.

When allocating an object:
  1. Look up the first empty slot.
  2. Set the new first empty slot to this new object's next empty slot.
  3. Construct the object in that slot.

When destroying an object:
  1. Set the object's type to empty.
  2. Set its next empty slot to the current first empty slot.
  3. Set the new first empty slot to the index of the destroyed object.

Keep in mind that because you essentially have pointers now, you'll have to respect these empty slots when sorting your objects for collision or display purposes.
Re: Main principles of creating an "object"
by on (#140903)
psycopathicteen wrote:
I think indirect long means that it's an address pointing to a long address, not the other way around. I could be wrong though, knowing how confusing the addressing modes on the 65816 are.

Ack, you're right -- I gave an incorrect example (and quite bad at that). For opcode $DC, the opcode+operand is 3 bytes (so the absolute address is 16-bit not 24-bit), but what the CPU loads indirectly from the absolute address is a 24-bit address. I'll fix my example. Thank you!
Re: Main principles of creating an "object"
by on (#140904)
tokumaru wrote:
kp64 wrote:
a list that indicates which ones are active and what elements of the array are "empty".
You can use each bit of the list as a binary flag to indicate if that element is "empty" or not.
If you want 128 objects you will need only 16 bytes for that list.

That doesn't make any sense! This won't make the object management any faster, because scanning and manipulating a list of bits could be even slower than accessing the object slots directly (since you'd have to divide the slot index by 8 and use bitmasks to access the individual bits), where you could look at the object type in order to know whether the slot is occupied.

It does make some sense, since if you're looking for a free slot, you'd essentially only have to scan the 16 byte table to find a byte that is not $FF (= at least one free slot, assuming 0 = free, 1 = taken), and then you could use e.g. a lookup table to translate that 8-bit value (together with the byte table index) into an object slot index. No need for bit operations. If you flip the meaning of the bits (i.e. 0 = taken, 1 = free), the loop could be somewhat faster as well (zero flag).

I still prefer using linked lists as you described, though.
Re: Main principles of creating an "object"
by on (#140908)
thefox wrote:
It does make some sense, since if you're looking for a free slot, you'd essentially only have to scan the 16 byte table to find a byte that is not $FF (= at least one free slot, assuming 0 = free, 1 = taken), and then you could use e.g. a lookup table to translate that 8-bit value (together with the byte table index) into an object slot index.

Interesting idea, I didn't consider this option. The linked list approach probably remains the most efficient option, so I too would rather stick with it.
Re: Main principles of creating an "object"
by on (#140916)
A bit off topic, but I tried to link an extra file to my main one in ca65, but it says "Error: 1 unresolved external(s) found" even though everything looks good to me.

In the main file: ( MetaspriteTest2)
Code:
; See ProcessOam.asm
.import process_oam

Latter in the main file: (this is what it's freaking out about)
Code:
  jsr process_oam


The file I am trying to add:
Code:
.setcpu "65816"
.smart

; See MetaspriteTest2.asm
.importzp SpriteCount
.import SpriteBuf1
.import SpriteBuf3

; Used by MetaspriteTest2.asm
.export process_oam

.segment "CODE"

;=====================================================================================

.proc process_oam
php
rep #$10
sep #$20

ldx SpriteCount
lda #$f0
clear_oam_loop:
cpx #$0200
beq finished_clearing_oam_loop
sta SpriteBuf1,x
inx #4
bra clear_oam_loop
finished_clearing_oam_loop:

ldy #$0000
sty SpriteCount

build_hioam_loop:
lda SpriteBuf3+14,y      ;; sprite 3, size bit
asl
ora SpriteBuf3+13,y      ;; sprite 3, 9th x bit
asl
ora SpriteBuf3+10,y      ;; sprite 2, size bit
asl
ora SpriteBuf3+9,y      ;; sprite 2, 9th x bit
asl
ora SpriteBuf3+6,y      ;; sprite 1, size bit
asl
ora SpriteBuf3+5,y      ;; sprite 1, 9th x bit
asl
ora SpriteBuf3+2,y      ;; sprite 0, size bit
asl
ora SpriteBuf3+1,y      ;; sprite 0, 9th x bit
sta SpriteBuf1,x
rep #$20
tya
clc
adc #$0010
tay
sep #$20
inx
cpx #$0220
bne build_hioam_loop

plp
rts

.endproc

This is all that I did. I'm not sure if I'm missing something.

Oh, a bit on topic, do you think that I'll need more than four bytes for objects, not including the x and y positions and the identification? I said I want to make a run and gun game, and the object's behaviors in those games generally aren't very complex.
Re: Main principles of creating an "object"
by on (#140917)
Did you update build.bat to include assembling of ProcessOam.asm? Probably not.

P.S. -- I have no idea what inx #4 is, but that isn't valid on any model of 65xxx CPU. I strongly do not recommend making extended macros with the same names as their opcodes (I pray ca65 disallows this), just to support variable arguments (e.g. inx #4 transparently getting turned into inx/inx/inx/inx). All that does is create confusion.
Re: Main principles of creating an "object"
by on (#140918)
koitsu wrote:
Did you update build.bat to include assembling of ProcessOam.asm? Probably not

You know me too well. :wink:

koitsu wrote:
P.S. -- I have no idea what inx #4 is, but that isn't valid on any model of 65xxx CPU. I strongly do not recommend making extended macros with the same names as their opcodes (I pray ca65 disallows this), just to support variable arguments (e.g. inx #4 transparently getting turned into inx/inx/inx/inx). All that does is create confusion.

I'm really not sure what I was even thinking. :roll:

Everything works now so thank you!
Re: Main principles of creating an "object"
by on (#140919)
Espozo wrote:
Oh, a bit on topic, do you think that I'll need more than four bytes for objects, not including the x and y positions and the identification? I said I want to make a run and gun game, and the object's behaviors in those games generally aren't very complex.

This is the freaking SNES, don't be so stingy. There are tons of things you're probably forgetting. For example, you'll definitely need a pointer to the animation the object is using, along with the index of the current frame and the timer to the next frame.

I just found how the object RAM is organized in Sonic the Hedgehog (Genesis): http://info.sonicretro.org/SCHG:Sonic_t ... ble_Format

This will probably give you an idea of the kinds of properties that might be interesting to keep track of.

Sonic uses 64 bytes per object. In my own NES project I designed everything around 28~32 bytes per object.
Re: Main principles of creating an "object"
by on (#140920)
I think that used to be my code. It was originally programmed in for xkas assembler, so that's why it has the "inx #4" thing.
Re: Main principles of creating an "object"
by on (#140922)
tokumaru wrote:
This is the freaking SNES, don't be so stingy. There are tons of things you're probably forgetting. For example, you'll definitely need a pointer to the animation the object is using, along with the index of the current frame and the timer to the next frame.

Wow. I really didn't think of about 90% of the things on that list. :oops: That Sonic the Hedgehog object ram setup you showed me looks pretty helpful. :)

psycopathicteen wrote:
I think that used to be my code. It was originally programmed in for xkas assembler, so that's why it has the "inx #4" thing.

I found it from here: http://wiki.superfamicom.org/snes/show/ ... ay+sprites but yeah, someone could have stolen it from you. :?
Re: Main principles of creating an "object"
by on (#140925)
I actually wrote that page on superfamicom.org, so yes, it was from me.
Re: Main principles of creating an "object"
by on (#140931)
I know I'm going to sound like a jerk, but like bazz said, I think you might want to consider changing the title to something a little more specific, like something along the lines of "how to effectively populate hioam" or at least "how to effectively manage sprites". When I had first saw the title, I thought it was just going to talk about creating a replica of oam in ram, writing a value or two and setting the sprites to display, just to kind of show you how to get started. I know it isn't exactly "advanced" but the title suggests that it is more of a "beginners" tutorial. (As in it's about as easy as creating a white screen.) Also, you go by the name Aaendi? Didn't you just say he copies your stuff though? Nothing escapes my eagle vision! :shock:
Re: Main principles of creating an "object"
by on (#140933)
Espozo wrote:
I'm really not sure what I was even thinking. :roll:

You were probably thinking of the adc #4 from something like txa/clc/adc #4/tax. Either that or inx/inx/inx/inx are fine to use. The quad-inx method is nominally better (uses 4 bytes, costs 2+2+2+2=8 cycles total), while txa/clc/adc #4/tax takes either 5 (A=8-bit) or 6 bytes (A=16-bit) and 2+2+2+2=8 cycles (A=8-bit) or 2+2+3+2=9 cycles (A=16-bit). I'm excluding decimal mode from any part of the calculation (only stating this to keep trolls at bay ;-) ).

The basic rule-of-thumb I learned with 65xxx is: if you need to increment/decrement an index register by more than 4, and can sacrifice the accumulator, then doing the addition/subtraction using the accumulator is better. Otherwise if you need to increment/decrement an index register by 1, 2, or 3, then just use the appropriate opcode that affects just the register.
Re: Main principles of creating an "object"
by on (#140934)
At first I was trying to trick you, but since I got bored waiting for a response, I just revealed the truth.

I know that my method of doing hioam is "tricky," but is there a "non-tricky" way that doesn't involve overcomplicated code?
Re: Main principles of creating an "object"
by on (#140943)
For what it's worth, I use psycopathicteen's high OAM method in my Super NES project template.
Re: Main principles of creating an "object"
by on (#140947)
tepples wrote:
For what it's worth, I use psycopathicteen's high OAM method in my Super NES project template.

Got some example code? I'm curious what the approach is (though I think I know what it might be).
Re: Main principles of creating an "object"
by on (#140952)
I know that on bass you can do this, to find the address of the last object:

define last_object( {slot_size} * {number_of_slots} + {first_object} )

...and you can tweak these numbers, so you have just enough for your game, but could you do something like this with WLA?
Re: Main principles of creating an "object"
by on (#140953)
psycopathicteen wrote:
At first I was trying to trick you, but since I got bored waiting for a response, I just revealed the truth.
Don't get sassy now. :wink:

Again, kind of unrelated, but I tried to implement the hioam code and it mostly works, but for some reason, whenever a sprite reaches the left and right side of the screen, it wraps around for one frame and then quickly corrects itself. I got rid of the oam clearing routine at the beginning (because the new code cleans it up every frame) and I replaced it with a code that instead puts a 1 on every 2nd byte in SpriteBuf 3 (oam+544). I think the problem resides in Metasprite 2, because I cleaned up something else that I already found.

The part of the code that might be a part of the problem is this (this is the only way I know how to do it, but it looks really bad).

Code:
  lda MetaspriteTable,x     ; 1st byte = sprite X position (value 0-255)
  clc
  adc MetaspriteXPosition
  and #$01FF
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
  and #$00FF
  sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y

The code that puts the x bit and size bit in SpriteBuf3 in SpriteBuf2 is located in the NMI code. (I put it in other places but it still didn't work)

Attachment:
MetaspriteDemoKoitsu.rar [259.82 KiB]
Downloaded 90 times
Re: Main principles of creating an "object"
by on (#140959)
It works if you put it right after the metasprite routine, but before the WAI.

Code:
InfiniteLoop:
  rep #$30                  ; A=16, X/Y=16
  sep #$20                  ; A=8
  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount
  ldy #0                    ; Offset into SpriteBuf1 (buffer used by DMA every frame)
  ldx #0                    ; Offset into MetaspriteTable
  jsr start_metasprite      ; jump to start_metasprite to build metasprites

  jsr process_oam

;
; Note from koitsu: wai can go back up at the top of you want, I had to move it
; here to allow the Y-button press stuff to do what I wanted.
;
skipmetaspriteupdate:
  wai              ; Wait for interrupts to finish (NMI/VBlank)
Re: Main principles of creating an "object"
by on (#140960)
Oh! I wasn't even aware there was a wai there. Thank You! (Lord knows I'm going to have more problems though. :roll: )

Quote:
Got some example code? I'm curious what the approach is (though I think I know what it might be).

Wait, what is this supposed to mean? How tepples implemented psychopathicteen's into his?
Re: Main principles of creating an "object"
by on (#140961)
Object-oriented assembly? That sounds like a complete mess, if anything...well, unless handled properly, of course. If done right then it sounds like the best thing ever.

(Well that turned over quickly :mrgreen: )
Re: Main principles of creating an "object"
by on (#140962)
nicklausw wrote:
Object-oriented assembly?

You mean the process of creating objects?

This is kind of a random thought, but it seems like the hardest part (to me) of making a game would be making the "game engine" which would be things like the collision detection code and the animation code among other things. It seems like you would just need to make things like level maps, program the behavior of the objects (which is no easy task) and create the graphics. (which is definatelly not an easy task) I've never made anything that serious, (that file is literally all I have done, aside form the collision detection code found in another file) so I'm really not the one to talk.
Re: Main principles of creating an "object"
by on (#140963)
nicklausw wrote:
Object-oriented assembly?

This is more like functions working on structures than actual OOP, but something like this is practically mandatory in games that have to deal with loading/unloading objects that have complex behaviors.
Re: Main principles of creating an "object"
by on (#140964)
Quote:
Quote:
Object-oriented assembly?

This is more like functions working on structures than actual OOP, but something like this is practically mandatory in games that have to deal with loading/unloading objects that have complex behaviors.

Like I said, this is working with the game after the "game engine" has been completed? Well, then in that case, it seems like just about any non Atari 2600 game uses "object-oriented assembly".
Re: Main principles of creating an "object"
by on (#140966)
The word "object" means something different in C++ lingo.
Re: Main principles of creating an "object"
by on (#140970)
When I said object-oriented, what I meant was something Game Maker style-like, which seemed to be what you're getting at. Not even entirely sure how C++'s object orientation works.
Re: Main principles of creating an "object"
by on (#141128)
For a very simple game all the objects can have the same number of sprites , and the same amout of data on RAM.
If the objects have diferent sizes , your game will need a defragmenter or something like that, right?
The game needs a subroutine that merges together small free blocks and produce bigger ones.
That means that the subroutine must copy and transfer blocks from one place to another to reallocate memory.
Given the fact that the game is going to create objects in small amounts each time , the fragmentation will not increase very quickly.
Running the defragmenter each frame when everything else is done and until the VBLANK occurs maybe will be enough.

nicklausw wrote:
Object-oriented assembly? That sounds like a complete mess, if anything...well, unless handled properly, of course. If done right then it sounds like the best thing ever.

(Well that turned over quickly :mrgreen: )


From the README file on the SuperRoadBlaster source code:

Quote:
This game is written purely in 65816-Assembler.
Apart from that, this game builds upon an abstraction layer that takes ideas
from object oriented programming intended to enhance flexibility.(...)

Some advantages of representing almost everything in the game(...) by objects:

-it's possible to generate any number of instances of objects whenever required
without having to micro-manage data access(...)

-objects presenting uniform interfaces can be grouped, selected and processed in a
generalized fashion.(...)
Re: Main principles of creating an "object"
by on (#141132)
The object system in Super Road Blaster is extremely impressive as a feat of WLA wrangling. It's not really built for optimum performance, though. For example, a shoot em up would hit the "within one frame" limit with rather few active objects. (Going from what I remember from reading the notes and source when it was released a couple of years ago.)

When I've been entertaining the thought of an action game engine for SNES, I've felt quite convinced that optimizing for anything but maximum load within one frame is a fool's errand. That is, you need to set a limit on the max amount of objects that can be processed – say 128 – and make sure you'll always be able to handle a scene with all objects active within your frame budget.

With that assumption it doesn't make sense with a linked list to save traversal time for the case when you're processing a small number of objects since that approach will always be slower when "operating at maximum capacity". So, the only suitable data structure is a flat array with a fixed byte count for each object, and the engine traverses the whole array with each update. An empty slot only needs an LDA and a conditional branch which is hardly wasteful.

I'm not sure any traditional data structure for keeping track of empty slots would be advantageous either, compared to say, an offset to the last "released" object. Possibly a circular FIFO stack could work though...
Re: Main principles of creating an "object"
by on (#141136)
Optiroc wrote:
I'm not sure any traditional data structure for keeping track of empty slots would be advantageous either, compared to say, an offset to the last "released" object. Possibly a circular FIFO stack could work though...

In this case, the linked list of free objects acts as a LIFO stack. There is one global offset to the most recently destroyed object, and this object points to the object destroyed before it. Thus you can allocate a new object in O(1), rather than taking O(n) time to scan all slots every time you allocate something.
Re: Main principles of creating an "object"
by on (#141156)
You know, this isn't very related, but I noticed that the metasprite code currently is designed to only look at one table. I thought I would write

Code:
  lda MetaspriteTable       ; Load number of Metasprites
  sta MetaspriteTableOffset ; ...and store it in MetaspriteCount

which would store the address of MetaspriteTable into the register MetaspriteTableOffset. I then did things like this in my metasprite code

Code:
  lda MetaspriteTableOffset+6,x
  sta SpriteBuf1+2,y        ; Palette/Character word

and tested it on BSNES, but the metasprite is messed up because everything is bunched up to the center of it. I assume the problem is that in the register, it is not treating the value like an offset, but just a plain value. How do I fix this? I thought I needed to use the brackets, but it became even more messed up and started to change palettes and start to move down and to the right, so that's clearly not the way to go.
Re: Main principles of creating an "object"
by on (#141169)
I'm not exactly sure, but I think you mean this:
Code:
lda #MetaspriteTable       ; Load number of Metasprites
sta MetaspriteTableOffset ; ...and store it in MetaspriteCount

#label = constant
label = address
(label) = address of address
[label] = address of long address
Re: Main principles of creating an "object"
by on (#141170)
Oh, I accidentally left the comments when I pasted a code and changed it. :oops: Anyway this is what I mean:

Code:
InfiniteLoop:

  wai              ; Wait for interrupts to finish (NMI/VBlank)
  rep #$30                  ; A=16, X/Y=16
  sep #$20                  ; A=8

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

  lda MetaspriteTable
  sta MetaspriteTableOffset ;This is where problems arise...

  ldy #0                    ; Offset into SpriteBuf1 (buffer used by DMA every frame)
  ldx #0                    ; Offset into MetaspriteTable
  jsr start_metasprite      ; jump to start_metasprite to build metasprites

Code:
.proc start_metasprite
  pha
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16
  ldy SpriteCount
  lda MetaspriteTableOffset

: cpy #$0200          ; sees if all 128 sprites are used up
  beq done
  lda MetaspriteCount       ; If MetaspriteCount is zero, then we're done.  Otherwise we have
  beq done                  ; metasprites to iterate over and populate for DMA (see VBlank)
  lda MetaspriteTableOffset,x     ; 1st byte = sprite X position (value 0-255)
  clc
  adc MetaspriteXPosition
  and #$01FF
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
  and #$00FF
  sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y
  lda MetaspriteTableOffset+2,x   ; 2nd byte = sprite Y position (value 0-255)
  clc
  adc MetaspriteYPosition
  and #$00FF
  sta SpriteBuf1+1,y        ; Store sprite Y position in SpriteBuf1+1,y
  lda MetaspriteTableOffset+4,x
  sta SpriteBuf3+2,y        ; Sprite size
  lda MetaspriteTableOffset+6,x
  sta SpriteBuf1+2,y        ; Palette/Character word
  txa
  clc
  adc #$0008
  tax
  iny
  iny
  iny
  iny
  dec MetaspriteCount       ; Decrement MetaspriteCount by 1
  bra :-                    ; Back to the loop...

done:
  sty SpriteCount       ; Says how many sprites have been made
  plp         ; Restore P (A/X/Y sizes)
  ply         ; Restore Y
  plx         ; Restore X
  pla         ; Restore A
  rts
.endproc
Re: Main principles of creating an "object"
by on (#141173)
It's as psychopathicteen says:

Code:
  lda MetaspriteTable
  sta MetaspriteTableOffset ;This is where problems arise...

The problem is misunderstanding what the code is actually doing. Pause for a moment, clear your mind, then think about it:

lda variablename translates into the accumulator being loaded with the data that is *at* the address specified by variablename. For example (just assume 16-bit accum), if variablename is $1F45, then you would be loading data from memory locations $1F45 and $1F46. If you wanted to change the memory at $1F45/1F46, you'd use sta variablename. The key point here is that you're accessing the data at a memory location. That's what your current (faulty) code is doing.

But what you're wanting is to load the address of the memory location itself -- not what's stored at the memory location.

Again using the above example, if you wanted to load the address of the memory location into the accumulator, then you'd use lda #$1F45. Note the syntax: #$1F45 vs. $1F45. # stands for "immediate", as in "a static value". You're already familiar with this notation.

So now that you understand those two concepts, you'll have a better idea of what you want to do, but you may be thinking: "okay I get it, but how do I make this work with a label/variable name?" And the answer is: lda variablename vs. lda #variablename.

The former would load the data that's at whatever memory location variablename is (e.g. $1F45 in the above example), while the latter would load the address of whatever variablename is itself.

The assembler knows during assemble-time what the address of variablename is -- because it's within the zeropage segment, within the BSS segment, etc... So quite literally, if the address was $1F95, lda #variablename and lda #$1F95 would be doing the exact same thing.

As before: if you aren't sure what's going on, take a look at the generated assembly listing (now that you're using ca65 this is do-able with ease). Immediate addressing uses a different opcode than absolute addressing (absolute reads the data at a memory location, immediate loads a static value).

So in summary, you want:

Code:
  lda #MetaspriteTable
  sta MetaspriteTableOffset

Or alternately (and this is what I strongly recommend, because it makes it extra clear what you're doing):

Code:
  lda #.LOWORD(MetaspriteTable)
  sta MetaspriteTableOffset

.LOWORD is a ca65 assembler directive that gets the lower 16-bits of whatever you give it. Reference: http://cc65.github.io/doc/ca65.html#.LOWORD

But there's another problem that you probably overlooked. Look very closely here (I've applied the above fix):

Code:
  rep #$30                  ; A=16, X/Y=16
  sep #$20                  ; A=8

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

  lda #.LOWORD(MetaspriteTable)
  sta MetaspriteTableOffset

Notice anything wrong? Hint: accumulator size. MetaspriteTable is going to be a 16-bit address, but your accumulator is 8-bit. That's not going to work correctly (assembler will probably throw a warning or error).

You should therefore do the 16-bit loading before switching to an 8-bit accumulator, i.e.:

Code:
  rep #$30                  ; A=16, X/Y=16

  lda #.LOWORD(MetaspriteTable)
  sta MetaspriteTableOffset

  sep #$20                  ; A=8

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

Honestly, I'd recommend just increasing the size of MetaspriteCount to 2 bytes (in fact, your start_metasprite routine treats it as a 16-bit value -- look at the accumulator size! -- that's actually a bug in your present code, because it's going to use the data from MetaspriteCount and whatever the next byte is (in the code I have, that's Joy1Data), hence bug) via MetaspriteCount .res 2 and get rid of the sep #$20 entirely.

P.S. -- Don't forget to fix your comments when changing code. For example, this comment is wrong compared to what the code does (code uses SpriteBuf3, comment mentions SpriteBuf1).

Code:
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y

P.P.S. -- If you choose to increase MetaspriteCount to a 16-bit value, don't forget to fix all the addresses in the comments to properly reflect offset/size. In other words:

Code:
FrameNum:        .res 2      ; $00: NMI/frame counter
MapX:            .res 2      ; $02: MapX
MapY:            .res 2      ; $04: MapY
XPosition:       .res 2      ; $06: XPosition
YPosition:       .res 2      ; $08: YPosition
MetaspriteCount: .res 1      ; $0a: temporary counter based on MetaspriteTableSize
Joy1Data:        .res 2      ; $0b: Current button state of joypad1, bit0=0 if it is a valid joypad
Joy2Data:        .res 2      ; $0d: same thing for all pads...
Joy1Press:       .res 2      ; $0f: Holds joypad1 keys that are pressed and have been pressed since clearing this mem
Joy2Press:       .res 2      ; $11: same thing for all pads...

Becomes:

Code:
FrameNum:        .res 2      ; $00: NMI/frame counter
MapX:            .res 2      ; $02: MapX
MapY:            .res 2      ; $04: MapY
XPosition:       .res 2      ; $06: XPosition
YPosition:       .res 2      ; $08: YPosition
MetaspriteCount: .res 2      ; $0a: temporary counter based on MetaspriteTableSize
Joy1Data:        .res 2      ; $0c: Current button state of joypad1, bit0=0 if it is a valid joypad
Joy2Data:        .res 2      ; $0e: same thing for all pads...
Joy1Press:       .res 2      ; $10: Holds joypad1 keys that are pressed and have been pressed since clearing this mem
Joy2Press:       .res 2      ; $12: same thing for all pads...
Re: Main principles of creating an "object"
by on (#141178)
I'm sorry, but I looked at what you did and I thought I applied the fixes, but I give up. (It's probably just something simple)

Attachment:
MetaspriteDemoKoitsu.rar [264.92 KiB]
Downloaded 78 times
Re: Main principles of creating an "object"
by on (#141181)
I came up with this idea today, and I want to know if this is a good idea or not.

I'm thinking that instead of hardcoding physics into object AI, maybe it would be a good idea to have a separate physics routine that gets run after every AI routine, and uses a register for "physics flags."

bit 0: apply gravity
bit 1: apply tile collision
bits 2-7: anything you want
Re: Main principles of creating an "object"
by on (#141184)
Espozo wrote:
I'm sorry, but I looked at what you did and I thought I applied the fixes, but I give up. (It's probably just something simple)

Again: just giving source code isn't enough, you need to describe what the issue is. I have no context. To me this looks fine (i.e. I don't know what's wrong).
Re: Main principles of creating an "object"
by on (#141186)
It's supposed to look something like this:

Attachment:
player2.png
player2.png [ 607 Bytes | Viewed 1397 times ]

It was working correctly before I was trying to tamper with it, but it didn't come together correctly. Look at Metasprite table and compare it to what is being displayed on the screen and you'll see what is wrong. The objects is made of 4 16x16's and kind of resembles the "T" Tetris shape on it's side. I appreciate you trying to help me for about the hundredth time.

Oh, and psychopathicteen, that seems like a good idea, but doesn't things like gravity normally depend on the object? A swaying leaf is probably going to fall slower than the character, unless you plan on making a byte for the "weight" or something. Same thing goes for momentum.
Re: Main principles of creating an "object"
by on (#141188)
Can I have the code that you were using that works, i.e. before you started tampering? I'll have a better idea what's wrong once I compare the differences.
Re: Main principles of creating an "object"
by on (#141189)
It's gone... :( I was stupid and I overwrote it, but I'll get to recreating it now.

Edit: Here it is:

Attachment:
MetaspriteDemoKoitsu.rar [264.51 KiB]
Downloaded 95 times
Re: Main principles of creating an "object"
by on (#141190)
One bug you definitely have relates to sprite DMA and the introduction of SpriteBuf3. Take a close look at SpriteBufSize -- it should be glaringly obvious what the problem there is (I don't even know how anything relating to SpriteBuf3 is even properly working because of this bug). But I don't want to tinker with that until I'm able to see code that actually works.
Re: Main principles of creating an "object"
by on (#141191)
Okay yeah, here's the diff (for those who can read it). I can clearly see what the problem here is -- it all stems from misunderstanding what it is you're doing with variables and addressing modes. You can't just rename variables and expect things to magically work, i.e.:

Code:
-  lda MetaspriteTable+6,x
+  lda MetaspriteTableOffset+6,x

This will not do what you think it does.

I'll give you a hint: remember the "very old code" in this routine, where it did things like lda $00,x and you had comments like "I don't understand what this does, why $00,x?" and so on? I'm crossing my fingers that given your changes and that comment + old code makes your brain go "OH!!! Oh oh oh, I get it now!"

The reason I changed your code (when moving from WLA DX to ca65) to get rid of that methodology is because it made absolutely no sense given that you only had one single table of data. Now that you plan on introducing more than one thing, it starts to make more sense.

Code:
D:\downloads>diff -ruN MetaspriteDemoKoitsu.working MetaspriteDemoKoitsu.broken
diff -ruN MetaspriteDemoKoitsu.working/Metasprite2.asm MetaspriteDemoKoitsu.broken/Metasprite2.asm
--- MetaspriteDemoKoitsu.working/Metasprite2.asm        2015-02-14 14:12:31.642723000 -0800
+++ MetaspriteDemoKoitsu.broken/Metasprite2.asm 2015-02-14 12:57:21.042469000 -0800
@@ -5,10 +5,10 @@
 .importzp MetaspriteXPosition
 .importzp MetaspriteYPosition
 .importzp MetaspriteCount
+.importzp MetaspriteTableOffset
 .importzp SpriteCount
 .import SpriteBuf1
 .import SpriteBuf3
-.import MetaspriteTable

 ; Used by MetaspriteTest2.asm
 .export start_metasprite
@@ -29,27 +29,27 @@
   php         ; Save P (A/X/Y sizes)
   rep #$30    ; A=16, X/Y=16
   ldy SpriteCount
-  lda MetaspriteTable
+  lda MetaspriteTableOffset

 : cpy #$0200               ; sees if all 128 sprites are used up
   beq done
   lda MetaspriteCount       ; If MetaspriteCount is zero, then we're done.  Otherwise we have
   beq done                  ; metasprites to iterate over and populate for DMA (see VBlank)
-  lda MetaspriteTable,x     ; 1st byte = sprite X position (value 0-255)
+  lda MetaspriteTableOffset,x     ; 1st byte = sprite X position (value 0-255)
   clc
   adc MetaspriteXPosition
   and #$01FF
   sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
   and #$00FF
   sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y
-  lda MetaspriteTable+2,x   ; 2nd byte = sprite Y position (value 0-255)
+  lda MetaspriteTableOffset+2,x   ; 2nd byte = sprite Y position (value 0-255)
   clc
   adc MetaspriteYPosition
   and #$00FF
   sta SpriteBuf1+1,y        ; Store sprite Y position in SpriteBuf1+1,y
-  lda MetaspriteTable+4,x
+  lda MetaspriteTableOffset+4,x
   sta SpriteBuf3+2,y        ; Sprite size
-  lda MetaspriteTable+6,x
+  lda MetaspriteTableOffset+6,x
   sta SpriteBuf1+2,y        ; Palette/Character word
   txa
   clc
diff -ruN MetaspriteDemoKoitsu.working/MetaspriteTest2.asm MetaspriteDemoKoitsu.broken/MetaspriteTest2.asm
--- MetaspriteDemoKoitsu.working/MetaspriteTest2.asm    2015-02-14 14:12:35.506366800 -0800
+++ MetaspriteDemoKoitsu.broken/MetaspriteTest2.asm     2015-02-14 13:02:59.634231700 -0800
@@ -19,8 +19,9 @@
 .exportzp MetaspriteXPosition
 .exportzp MetaspriteYPosition
 .exportzp MetaspriteCount
+.exportzp MetaspriteTableOffset
 .exportzp SpriteCount
-.export SpriteBuf1, SpriteBuf2, SpriteBuf3, MetaspriteTable
+.export SpriteBuf1, SpriteBuf2, SpriteBuf3

 ; Used by Header.asm
 .export VBlank, Main
@@ -41,6 +42,7 @@
 MetaspriteXPosition:       .res 2
 MetaspriteYPosition:       .res 2
 MetaspriteCount:           .res 2
+MetaspriteTableOffset:     .res 2
 SpriteCount:               .res 2
 Joy1Data:                  .res 2
 Joy2Data:                  .res 2
@@ -154,6 +156,10 @@

   wai              ; Wait for interrupts to finish (NMI/VBlank)
   rep #$30                  ; A=16, X/Y=16
+
+  lda #.LOWORD(MetaspriteTable)
+  sta MetaspriteTableOffset
+
   sep #$20                  ; A=8

   lda MetaspriteTableSize   ; Load number of Metasprites

P.S. -- That first lda in start_metasprite serves no purpose, or is a bug of some sort.
Re: Main principles of creating an "object"
by on (#141194)
Wait, is it because I need to write "ldx #MetaspriteTable" before I jump to the metasprite routine and offset an empty register like $00 by MetaspriteTable? I know I cannot currently write $00 because it is already used up. Maybe I can reserve a blank register. By the way, sorry if I seem really stupid, but what is wrong with SpriteBuf3?
Re: Main principles of creating an "object"
by on (#141200)
I finally solved the damned thing...

Code:
  wai              ; Wait for interrupts to finish (NMI/VBlank)
  rep #$30                  ; A=16, X/Y=16

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

  ldy #0                    ; Offset into SpriteBuf1 (buffer used by DMA every frame)
  ldx #MetaspriteTable      ; Offset into MetaspriteTable
  stx TempX
  jsr start_metasprite      ; jump to start_metasprite to build metasprites

  sep #$20                  ; A=8

Code:
.proc start_metasprite
  pha
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16
  ldy SpriteCount
  ldx TempX

: cpy #$0200          ; sees if all 128 sprites are used up
  beq done
  lda MetaspriteCount       ; If MetaspriteCount is zero, then we're done.  Otherwise we have
  beq done                  ; metasprites to iterate over and populate for DMA (see VBlank)
  lda Empty,x               ; 1st byte = sprite X position (value 0-255)
  clc
  adc MetaspriteXPosition
  and #$01FF
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
  and #$00FF
  sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y
  lda Empty+2,x   ; 2nd byte = sprite Y position (value 0-255)
  clc
  adc MetaspriteYPosition
  and #$00FF
  sta SpriteBuf1+1,y        ; Store sprite Y position in SpriteBuf1+1,y
  lda Empty+4,x
  sta SpriteBuf3+2,y        ; Sprite size
  lda Empty+6,x
  sta SpriteBuf1+2,y        ; Palette/Character word
  txa
  clc
  adc #$0008
  tax
  iny
  iny
  iny
  iny
  dec MetaspriteCount       ; Decrement MetaspriteCount by 1
  bra :-                    ; Back to the loop...

done:
  sty SpriteCount       ; Says how many sprites have been made
  plp         ; Restore P (A/X/Y sizes)
  ply         ; Restore Y
  plx         ; Restore X
  pla         ; Restore A
  rts
.endproc
Re: Main principles of creating an "object"
by on (#141201)
tepples wrote:
Optiroc wrote:
I'm not sure any traditional data structure for keeping track of empty slots would be advantageous either, compared to say, an offset to the last "released" object. Possibly a circular FIFO stack could work though...

In this case, the linked list of free objects acts as a LIFO stack. There is one global offset to the most recently destroyed object, and this object points to the object destroyed before it. Thus you can allocate a new object in O(1), rather than taking O(n) time to scan all slots every time you allocate something.

Oh, yes, a LIFO stack obviously. I've already forgot why I had a FIFO queue on my mind when I wrote that, but it was probably just sleep depraved absent mindness talking...

Btw, what game would be the most performant in the SNES library when it comes to amount of objects thrown around? Good old Space Megaforce?
Re: Main principles of creating an "object"
by on (#141202)
Optiroc wrote:
Btw, what game would be the most performant in the SNES library when it comes to amount of objects thrown around? Good old Space Megaforce?

Either that or Rendering Ranger R2 (I'm leaning toward the ladder, even if it looks like there is more because of how fast bullets move). I personally think R-type 3 looks the best out of all of them due to the artwork, but it doesn't throw as much at you.

A random nitpick, but I always found it bizarre how Contra 3 uses 8x8 and 16x16 sized sprites because it gets to be incredibly easy to run out of sprites because of the 32x32 explosions. Almost every boss is a BG layer when they really don't need to be. They also waste BG layer 3 on something the NES games just used sprites for, and the NES obviously had considerably worse sprite abilities.
Re: Main principles of creating an "object"
by on (#141203)
Espozo wrote:
I finally solved the damned thing...

Nice job! Consider me impressed, thumbs up! By the way, you don't need the TempX variable (you really don't), you can stick with MetaspriteTableOffset if you just revamp your routine a little bit. I.e.:

Code:
  lda #.LOWORD(MetaspriteTable)
  sta MetaspriteTableOffset

...

  ldy #0
  jsr start_metasprite

...

.proc start_metasprite
  pha
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16

  ldx MetaspriteTableOffset
  ldy SpriteCount

: cpy #$0200          ; sees if all 128 sprites are used up
  beq done
  lda MetaspriteCount       ; If MetaspriteCount is zero, then we're done.  Otherwise we have
  beq done                  ; metasprites to iterate over and populate for DMA (see VBlank)
  lda $0000,x               ; 1st byte = sprite X position (value 0-255)
  clc
  adc MetaspriteXPosition
  and #$01FF
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
  and #$00FF
  sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y
  lda $0000+2,x   ; 2nd byte = sprite Y position (value 0-255)
  clc
  adc MetaspriteYPosition
  and #$00FF
  sta SpriteBuf1+1,y        ; Store sprite Y position in SpriteBuf1+1,y
  lda $0000+4,x
  sta SpriteBuf3+2,y        ; Sprite size
  lda $0000+6,x
  sta SpriteBuf1+2,y        ; Palette/Character word
  txa
  clc
  adc #$0008
  tax
  iny
  iny
  iny
  iny
  dec MetaspriteCount       ; Decrement MetaspriteCount by 1
  bra :-                    ; Back to the loop...

...

The variable or equate you have called "Empty" is absolutely silly -- its name is awful and is misleading. Just use $0000. You can happily do things like $0000+6 (or $0006 or $6 if you want). If you're worried about not knowing what it does: that's what comments are for!

Now, about the sprite DMA bug. There is 1 bug and one accident-waiting-to-happen. First, look very closely at what SpriteBufSize (last line) is being calculated as:

Code:
.segment "BSS"
SpriteBuf1:    .res $200   ; Sprite buffer for OAM DMA, 512 ($200) bytes
SpriteBuf1Size = *-SpriteBuf1
SpriteBuf2:    .res $20    ; Sprite buffer for OAM DMA, 32  ($20) bytes
SpriteBuf2Size = *-SpriteBuf2
SpriteBuf3:    .res $200   ; Sprite buffer for OAM DMA, 512  ($200) bytes
SpriteBuf3Size = *-SpriteBuf3
SpriteBufSize  = SpriteBuf1Size+SpriteBuf2Size

This looks like an "innocent typo" until you actually look at the DMA routine inside VBlank:

Code:
  stz $4300                    ; CPU -> PPU, auto-increment, write once
  lda #$04
  sta $4301                    ; DMA will write to register $2104 (OAM data write)
  ldy #.LOWORD(SpriteBuf1)
  sty $4302                    ; source offset of SpriteBuf1
  lda #.BANKBYTE(SpriteBuf1)
  sta $4304                    ; bank address of SpriteBuf1
  ldy #SpriteBufSize
  sty $4305                    ; transfer SpriteBufSize bytes (pre-calculated number)
  lda #$01
  sta $420b                    ; start DMA transfer

One is a definite bug: your sprite DMA routine is only transferring $220 ($200+$20) bytes, not $420 ($200+$20+$200), thus is not DMA'ing the contents of SpriteBuf3 -- and the other is an accident waiting to happen: if you ever inserted a new sprite buffer in front of SpriteBuf1 in the BSS segment, the DMA routine would never use it. So, to solve both of these issues:

Code:
.segment "BSS"
SpriteBufStart:
SpriteBuf1:    .res $200   ; Sprite buffer for OAM DMA, 512 ($200) bytes
SpriteBuf2:    .res $20    ; Sprite buffer for OAM DMA, 32  ($20) bytes
SpriteBuf3:    .res $200   ; Sprite buffer for OAM DMA, 512  ($200) bytes
SpriteBufSize = *-SpriteBufStart

And then do this instead:

Code:
  stz $4300                    ; CPU -> PPU, auto-increment, write once
  lda #$04
  sta $4301                    ; DMA will write to register $2104 (OAM data write)
  ldy #.LOWORD(SpriteBufStart)
  sty $4302                    ; source offset of SpriteBufStart
  lda #.BANKBYTE(SpriteBufStart)
  sta $4304                    ; bank address of SpriteBufStart
  ldy #SpriteBufSize
  sty $4305                    ; transfer SpriteBufSize bytes (pre-calculated number)
  lda #$01
  sta $420b                    ; start DMA transfer

Finally: all your sprite stuff in the BSS segment might be a good place to try using ca65's .STRUCT and .SIZEOF() directives. If you'd like me to implement that (I think you might actually like the syntax), if you upload your current code I can implement it for you (it's not hard and should mostly make sense to you).
Re: Main principles of creating an "object"
by on (#141205)
Espozo wrote:
Optiroc wrote:
Btw, what game would be the most performant in the SNES library when it comes to amount of objects thrown around? Good old Space Megaforce?

Either that or Rendering Ranger R2 (I'm leaning toward the ladder, even if it looks like there is more because of how fast bullets move). I personally think R-type 3 looks the best out of all of them due to the artwork, but it doesn't throw as much at you.

A random nitpick, but I always found it bizarre how Contra 3 uses 8x8 and 16x16 sized sprites because it gets to be incredibly easy to run out of sprites because of the 32x32 explosions. Almost every boss is a BG layer when they really don't need to be. They also waste BG layer 3 on something the NES games just used sprites for, and the NES obviously had considerably worse sprite abilities.


One reason that contra or any other game would use small sprites is that it although it doesn't conserve entries in the sprite table, it conserves precious VRAM in cases where your sprites don't use a full 16x16, 32x32, or 64x64 perimeter.
Re: Main principles of creating an "object"
by on (#141206)
bazz wrote:
One reason that contra or any other game would use small sprites is that it although it doesn't conserve entries in the sprite table, it conserves precious VRAM in cases where your sprites don't use a full 16x16, 32x32, or 64x64 perimeter.

Oh, 64x64 is completely out of the question. I swear, the hardware designers at Nintendo must have really been smoking something when they added that option... You really don't even have a fraction of the amount of vram or sprite overdraw that that sprite size would require. It would be okay for some things that actually do take up that much space, but with only 2 sprite sizes at a time, the option for 64x64 sized sprites is just ludicrous. I always wondered if maybe, just maybe, The SNES was originally designed to be more off an "upper class, Neo Geo" type system and this was one of the things that they didn't get rid of... Probably not. :P
Re: Main principles of creating an "object"
by on (#141209)
I too think the 64px sprites are silly, especially given that there's not enough room for one to move off the top or bottom of the screen. But there's a lot that could have been solved if Nintendo would have dedicated an entire byte of high OAM to each sprite.
  • Sprites using all 4 sizes
  • Rectangular sprites
  • More than 512 sprite tiles
  • Select individual sprites for participation in color math
Nintendo eventually realized this by the GBA era, expanding OAM to 6 bytes per sprite.
Re: Main principles of creating an "object"
by on (#141212)
Oh, koitsu, I didn't even notice that you posted! Thank you for complementing me! I've gotten pretty far from I when first started with SNES dev. (which isn't very far, mind you! :P ) I like how you fixed my SpriteBuf3 routine too. :wink:

tepples wrote:
there's a lot that could have been solved if Nintendo would have dedicated an entire byte of high OAM to each sprite.
  • Sprites using all 4 sizes
  • Rectangular sprites
  • More than 512 sprite tiles
  • Select individual sprites for participation in color math

I wonder, would increasing oam size affect anything aside from oam being bigger? Like if more processor time is taken or something? I think making oam 5bytes for sprites and getting rid of hioam would have worked wonders. I always wondered how far Nintendo would have gone if they even just threw an extra $50 at the SNES when they made it, but according to this, $200 was "pushing it".

https://www.youtube.com/watch?v=MTzyz2TgGls (part 1:39 is just blasphemy, even compared to all the other sh*t they say in the video)

tepples wrote:
I too think the 64px sprites are silly, especially given that there's not enough room for one to move off the top or bottom of the screen.

The only "work around" to this I can think of is to check if the 64x64 sprite is out of bounds and to turn it into 2 32x32's but the fact that you would even have to do this is just ridiculous. (Not to mention it takes up time that could be spent on something else.)

tepples wrote:
Nintendo eventually realized this by the GBA era, expanding OAM to 6 bytes per sprite.

There's a lot of screw ups Nintendo realized when they made the GBA (I honestly kind of drool over 4 8bpp BG's and 5x overdraw) but a lot of fixes would have driven the price to "ludicrous levels". I don't think that increasing oam would have impacted the price much, but as koitsu always says, the past is the past.

Edit: Oh, koitsu, I looked over what you said more and when you said that I had an error in that I wasn't DMAing the contents of SpriteBuf3 to oam, this was actually intentional, because I transfer the contents of SpriteBuf3 to SpriteBuf2 which then gets DMAed to oam. I realized that I wrote:

Code:
SpriteBuf3:    .res $200   ; Sprite buffer for OAM DMA, 512  ($200) bytes

But this is because of copy and pasting. :oops: It should say:

Code:
SpriteBuf3:    .res $200   ; Sprite buffer for SpriteBuf2, 512  ($200) bytes
Re: Main principles of creating an "object"
by on (#141262)
Just thinking, with the way I have my vram searching routine, I figure that in each object "slot", I could have 32 bytes that give the character offset for each sprite in the object. This would allow you hold the tile number for 16 different sprites, and I doubt an object is going to consist of more than 16 sprites because I am using 16x16's and 32x32's. I already thought of having byte or word that says what the object is, but I am thinking about having an additional byte that says the number of what frame of animation the object is currently on. This would help because with objects like explosions that can potentially use a lot of vram, but often go off at the same time, with every explosion, you could check to see if there are any other explosions and if they are at the same frame. You would then dump the tile numbers from the pre existing explosion into the new one, so you don't have 2 of the exact same explosion in vram. I don't think that this would be very worthwhile for every object, but it could work here because there really isn't much going on with an explosion except animating it. (There isn't normally any collision detection or anything.)
Re: Main principles of creating an "object"
by on (#141265)
Maybe instead of looking through all the objects for another explosion of the same frame, it will be faster if every object would use a register as a switch between animating it's own metasprite, and using a metasprite from another object.
Re: Main principles of creating an "object"
by on (#141266)
Wouldn't that mean that it is the exact same object, coordinates and all? as I currently have it, my metasprite routine loads a sprites position from a table and gets the object's coordinates, which are added to the value from the table to get the final position of the sprite and you store it in the sprite buffer. I guess you could find the positions for the sprites prior to creating the metasprite?
Re: Main principles of creating an "object"
by on (#141272)
Don't copy the X and Y coordinates, just copy the metasprite data address. You don't need to copy the entire CHR-offset table, because you can just index from the parent sprite's CHR-offset table.

BTW, is every frame going to use a different metasprite, or just a different graphics address? The former is more v-ram efficient, but the latter is easier to work with.
Re: Main principles of creating an "object"
by on (#141278)
psycopathicteen wrote:
Don't copy the X and Y coordinates, just copy the metasprite data address. You don't need to copy the entire CHR-offset table, because you can just index from the parent sprite's CHR-offset table.

Okay, I get you now. :wink: I'll try to implement that sometime.

psycopathicteen wrote:
BTW, is every frame going to use a different metasprite, or just a different graphics address? The former is more v-ram efficient, but the latter is easier to work with.

Definitely the former. If I make a 64x64 explosion, I want it to start out as 1 32x32 sized sprite, then 1 32x32 and 5 16x16 sized sprites to create a 48x48 sized metasprite, and finally 4 32x32 sized sprites. Similarly, the character I want is going to have the legs be 32 pixels across for when their legs are furthest apart in their running animation, but when the legs cross over, they will be 16 pixels across.
Re: Main principles of creating an "object"
by on (#141294)
I always considered the 64x64 sprite size on the SNES as brag rights.


If you have access to all sprite sizes, and less segmented sprite layout, it'd be a little more useful. Not overly so, but still useful. PCE games use the 32x64 sprite size more often than you would think. Bonk 3, there are places in the game where both players can be 'giant' bonk; each one is 80x96 pixels in size (or slightly bigger depending on the frame). Each player reserved 8k of vram (for double buffering), so 16k total vram just for both players. 16k for tiles, and the remaining vram for tilemap, sat, and other game sprites. I guess that's the reason why 'big' Bonk on the SNES was smaller? Wait.. it's not even a 2 player game so that shouldn't mattered. Hmm, that shouldn't have busted the snes' vram layout then. Nevermind.
Re: Main principles of creating an "object"
by on (#141296)
tomaitheous wrote:
I always considered the 64x64 sprite size on the SNES as brag rights.

Only really on a paper that say things like

Sprites: 128 with 64x64 maximum.
Sprites per scan line: 32

(And yes, I have seen ones that bad before.)


Sometimes, I feel like a lot of things in the SNES are for bragging rights, like the "hi res" mode. It could have maybe been useful, if the sprites were too, but then you'd have to make the add bits that control the coordinates.


About poorly made lists, I've seen things like this before:

Resolution: 512x448
Background layers: 4 (with matrix transformations)
Tile color depth: 8bpp
Sprites: 128 with 64x64 maximum.
Sprites per scan line: 32

Forget the Neo Geo, this puts the GBA to shame!

(What kind of power would it take to have all that running at the same time? Way more than anyone in 1990 could afford, that's for sure. :roll: )
Re: Main principles of creating an "object"
by on (#141298)
Espozo wrote:
Sometimes, I feel like a lot of things in the SNES are for bragging rights, like the "hi res" mode. It could have maybe been useful

Hi-res was useful for text, especially trying to fit detailed kanji into a small box. Pseudo-hi-res was useful for three-part blending (1/2 sub, 1/4 main, 1/4 COLDATA). I'm not aware of any game other than RPM Racing that used actual hi-res graphics in-game.
Re: Main principles of creating an "object"
by on (#141301)
Yeah, the hires mode seems useless. To be honest, I rather have it not be applied to sprites. Imagine have a 272 pixel scanline limit for 512 pixel mode? The PCE has a 512 mode, and the pixel limit doesn't increase with it (it's got the pixel fetching speed for it, just not the buffer size for it). It's usefulness is pretty much for backgrounds art, like for an RPG (dithering?). But the SNES has tons of colors, which make low res look great for BG art anyway. I think one of the Kirby games make unique use of high res mode? For blending they normally couldn't do? If sprites were high res and same pixel buffer limit, they might not have used it.

Edit: Tepples ninja-answered that.

I'd trade quite a few features of the SNES to have double the sprite pixel scanline limit in their place. The SGX is the only console of that generation to have a really nice/healthy scanline pixel limit. Funny that it's not even a blip on the video game map for most gamers. Well, technically you could count the addon chips (and SegaCD), but I mean native to the video hardware.
Re: Main principles of creating an "object"
by on (#141302)
Okay, just like half of my posts, this isn't super related to the topic, but I tried adding an area in RAM 8192 bytes in size and is going to be the place where all the object related information is stored.

Code:
ObjectBuf:    .res $2000   ;I'm definatelly going to rename this

However, I tried to assemble it using ca64, but it tells me this:

Quote:
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Users\mrdre_000>cd C:\Users\mrdre_000\Desktop\MetaspriteDemoKoitsu

C:\Users\mrdre_000\Desktop\MetaspriteDemoKoitsu>build
Assembling... Header.asm
Assembling... 2input.asm
Assembling... LoadGraphics.asm
Assembling... Metasprite2.asm
Assembling... MetaspriteTest2.asm
Assembling... ProcessOam.asm
Linking...
ld65.exe: Warning: lorom256k.cfg(7): Memory area overflow in `BSS', segment `BSS
' (1568 bytes)
ld65.exe: Error: Cannot generate output due to memory area overflow

Finished linking. Please take note of any warnings/errors above.
Assembler or linker failed; see above output for details.

C:\Users\mrdre_000\Desktop\MetaspriteDemoKoitsu>

Memory overflow? I know for a fact that that fits in ram. Why is it even saying anything about lorom? It's an area in ram.

Quote:
I'd trade quite a few features of the SNES to have double the sprite pixel scanline limit in their place. The SGX is the only console of that generation to have a really nice/healthy scanline pixel limit. Funny that it's not even a blip on the video game map for most gamers. Well, technically you could count the addon chips (and SegaCD), but I mean native to the video hardware.


I wonder what you could trade. I would definitely get rid of BG 3 if you could in order to double the sprite per scan line limit. It's a bit suprising that Nintendo didn't copy all the enhancements from the Super Graphix to the SNES considering the SNES came out later, but given the Super Graphix's popularity, I guess they didn't think anyone would even notice. The Super Graphix actually seems a lot like the SNES in some respects, if not actually better. (Color depth is still a bit of a problem though.)
Re: Main principles of creating an "object"
by on (#141303)
My linker script is called "lorom" because it's designed for use with cartridge boards that implement LoROM (mapper $20). It defines BSS as the first 8192 bytes of WRAM, which are mirrored down to low memory. You need to put big tables in .segment "BSS7E" (56 KiB) or .segment "BSS7F" (64 KiB).
Re: Main principles of creating an "object"
by on (#141309)
Espozo wrote:

I wonder what you could trade. I would definitely get rid of BG 3 if you could in order to double the sprite per scan line limit.


Given what the SNES could do graphic wise, I'm pretty happy for what it can deliver. But for speculation's sake, I'd probably get ride of the interlaced mode. On the Genesis, it was recently revealed that the interlace mode took up a lot silicone real estate for the VDP, even to the point where they couldn't add more color cram for more colors on screen (cram is internal to the chip). So if it takes up similar on the SNES, then I say chuck it. That and mode 0. And a few other modes things I can't remember off the top of my head.


Quote:
It's a bit suprising that Nintendo didn't copy all the enhancements from the Super Graphix to the SNES considering the SNES came out later, but given the Super Graphix's popularity, I guess they didn't think anyone would even notice. The Super Graphix actually seems a lot like the SNES in some respects, if not actually better. (Color depth is still a bit of a problem though.)


I think Chris Covell showed that the SuperFami hardware was pretty much finalized early on. They were probably waiting on the most opportune time to release it. Which means mostly likely before the SGX was out.

Want to hear something crazy? The PCE and SGX both have DACs capable of displaying 32768 YUV colors. Like the NES, the composite video is actually generated digitally. The PCE has two sets of DACs; YUV and RGB (5bit DACs). The RGB ram is actually nothing more than a index into a digital conversion table where it outputs 15bit color. Imagine if you had direct access to that, or even just 'emphasis' bits. Idiots. I mean, the PCE/SGX has a special chip just to house color ram and build NTSC timings (takes a digital pixel bus from the video chip). Surely they had enough silicone/room to implement something a little more advantageous to handle those 'perdy DACs. They missed it a second time around with the SGX too. Hahaha.
Re: Main principles of creating an "object"
by on (#141310)
tomaitheous wrote:
I'd probably get ride of the interlaced mode. On the Genesis, it was recently revealed that the interlace mode took up a lot silicone real estate for the VDP, even to the point where they couldn't add more color cram for more colors on screen

When you say interlaced, do you mean this?:

Wikipedia wrote:
Interlaced video is a technique for doubling the perceived frame rate of a video display without consuming extra bandwidth. The interlaced signal contains two fields of a video frame captured at two different times. This enhances motion perception to the viewer, and reduces flicker by taking advantage of the phi phenomenon effect.

If anything could be done to increase the amount of colors on the Genesis, then I say do it. That's pretty much one of the main thing that made it look so much worse than the SNES in comparison and what made it not able to last as long. (DKC would have looked awful.)

tomaitheous wrote:
That and mode 0.

Another example of bragging rights. I don't see any reason in making two BG's look considerably worse just to add one more. You know, this is going to sound crazy, But I always thought it would be cool if someone made an demo of 4 NES games being played over each other on the SNES using mode 0. I know you don't necessarily have quadruple the number of resources for everything, (128 sprites instead of 256) but I'm sure you could work around it. (using 16x16 sized sprites instead of 4 8x8's)

tomaitheous wrote:
Want to hear something crazy? The PCE and SGX both have DACs capable of displaying 32768 YUV colors. Like the NES, the composite video is actually generated digitally. The PCE has two sets of DACs; YUV and RGB (5bit DACs). The RGB ram is actually nothing more than a index into a digital conversion table where it outputs 15bit color. Imagine if you had direct access to that, or even just 'emphasis' bits. Idiots. I mean, the PCE/SGX has a special chip just to house color ram and build NTSC timings (takes a digital pixel bus from the video chip). Surely they had enough silicone/room to implement something a little more advantageous to handle those 'perdy DACs. They missed it a second time around with the SGX too. Hahaha.

That's truly nonsensical. all they really would have needed to do is increase cg ram, or at least I think. How is cg ram stored in the PCE if each color is 9bits? Is it stored kind of like hioam in the SNES?

I know this is much farther ahead, but I really feel like Nintendo knew how to make really good 2D hardware when they made the GBA. I know it's far more advanced than the SNES (despite people saying the GBA is "as powerful, f not, slightly more powerful") but I feel like the GBA is far more well balanced and less nonsensical than the SNES from a technical perspective overall. Except resolution. That really could have been better and allowed for arcade perfect ports. is the screen the GBA's main reason for having the resolution it does, or is it the video hardware? I feel that it would have been really cool if Nintendo had basically put two GBA screens together, (no, I'm not talking about the DS :roll: ) and tilted it sideways for a 320x240 display which is exactly 2x the amount of pixels. The aspect ratio would than be 3:4 and this would allow things like Genesis games to have pixel perfect ports.
Re: Main principles of creating an "object"
by on (#141317)
Espozo wrote:
I feel like the GBA is far more well balanced and less nonsensical than the SNES from a technical perspective overall.


I don't do any SNES programming, but every time I take a peek at what you guys are doing here, it really makes me appreciate how easy it is to wrap my head around the GBA. I get the feeling that Nintendo kind of went out of their way to make programming on their handhelds a fair bit easier than on the consoles, probably because the handhelds appeared later than their comparable consoles (NES first, GBC second, SNES first, GBA second) so they had time to see what worked and what didn't for developers.

Espozo wrote:
Except resolution. That really could have been better and allowed for arcade perfect ports. is the screen the GBA's main reason for having the resolution it does, or is it the video hardware?


As I stated before (somewhere around here), the GBA was designed with late 90s mobile tech. Performance wise, they may not have been able to push that many pixels at a price people would be willing to pay, although later DS-level hardware could easily do so. Even if the GBA had enough VRAM for that resolution, producing LCD units at that resolution may have been cost prohibitive for Nintendo.

Espozo wrote:
I feel that it would have been really cool if Nintendo had basically put two GBA screens together, (no, I'm not talking about the DS :roll: ) and tilted it sideways for a 320x240 display which is exactly 2x the amount of pixels. The aspect ratio would than be 3:4 and this would allow things like Genesis games to have pixel perfect ports.


If it's any consolidation, the 3DS has a 320x240 screen on the bottom (400x240 up top). Unfortunately in our world of shiny tablets and fancy smartphones, that's rather tiny as for as modern portable gaming goes.
Re: Main principles of creating an "object"
by on (#141337)
tepples wrote:
My linker script is called "lorom" because it's designed for use with cartridge boards that implement LoROM (mapper $20). It defines BSS as the first 8192 bytes of WRAM, which are mirrored down to low memory. You need to put big tables in .segment "BSS7E" (56 KiB) or .segment "BSS7F" (64 KiB).

His code current code will not work correctly outside of a single bank. Well, more specifically, parts of it will, other parts won't. There is basically constant assumption that B=K most of the time. This is something I've intentionally been avoiding mentioning for weeks now because it's another humongous time sink.

Also, Tepples' explanation is both helpful and unhelpful at the same time. It doesn't explain the real reason for the assembler error (memory area overflow), so I'll explain that. It relates to the SNES memory model, which for mode 20 you should get familiar with anyway. Please see the below two pictures.

lorom256k.cfg is basically a definition of what that memory map is (or more specifically, parts of it -- because the assembler needs to know what addresses things are at so that it assembles things with the right addresses and banks, and so that your code can get that information and use it appropriately (like what's in LoadBlockToVRAM)). What you're interested in is the RAM part.

The SNES has a limited amount of RAM -- specifically about 128KBytes total (8+56+64). The general.png image does a better job explaining this, but here it is in text:

Bank $00, $0000-1FFF = RAM (8KBytes)
Banks $00-3F, $0000-1FFF = mirror of $00/0000-1FFF
Banks $80-BF, $0000-1FFF = mirror of $00/0000-1FFF

Bank $7E, $0000-1FFF = mirror of $00/0000-1FFF
Bank $7E, $2000-FFFF = RAM (56KBytes)
Bank $7F, $0000-FFFF = RAM (64KBytes)

The "mirrored regions" of RAM are to allow a program executing code within a non-$00 bank to still access RAM through absolute addressing as if the code were in bank $00. (I've honestly never understood why this was necessary, since direct page is always in bank $00 and can be used to accomplish the same thing, but whatever).

The 8KBytes of RAM from $0000-1FFF are (generally speaking) intended to hold RAM for direct page and the stack.

Now if you look in lorom256k.cfg this should start to make more sense. Note the size field too.

Code:
MEMORY {
  ZEROPAGE:   start =  $000000, size =  $0100;   # $0000-00ff -- zero page
                                                 # $0100-01ff -- stack
  BSS:        start =  $000200, size =  $1e00;   # $0200-1fff -- RAM
  BSS7E:      start =  $7e2000, size =  $e000;   # SNES work RAM, $7e2000-7effff
  BSS7F:      start =  $7f0000, size = $10000;   # SNES work RAM, $7f0000-$7ffff
  ROM0:       start =  $008000, size =  $8000, fill = yes;
  ROM1:       start =  $018000, size =  $8000, fill = yes;
  ROM2:       start =  $028000, size =  $8000, fill = yes;
  ROM3:       start =  $038000, size =  $8000, fill = yes;
  ROM4:       start =  $048000, size =  $8000, fill = yes;
  ROM5:       start =  $058000, size =  $8000, fill = yes;
  ROM6:       start =  $068000, size =  $8000, fill = yes;
  ROM7:       start =  $078000, size =  $8000, fill = yes;
}

The ZEROPAGE part is what I've complained about in the past -- that ca65 requires this segment and does not understand the concept of 65816 direct page. So that's why the first 256 bytes ($0000-00FF) are ZEROPAGE.

The stack isn't defined in this config (Tepples may have fixed this in his newer LoROM template example, I didn't look), but it's technically between $0100-01FF (look in your code, specifically look for a txs statement).

That leaves $0200-1FFF, which is simply labelled "BSS" (this is a ca65 naming scheme thing).

So now you can see why you can't just simply increase the size of something in the BSS segment in your code and have it magically work. Sure, you could go change lorom256k.cfg to have a larger segment, but you would end up in a world of hurt -- you would probably end up writing code that would stomp over the stack and direct page (your variable you're already using in the ZEROPAGE segment), and this would make a giant mess. (Your variables could/would lose their contents, and your stack would be trashed so that any of your stack push/pull ops and/or JSRs could/would freak out).

So basically, the assembler error is telling you "you're trying to do .res $2000 but the BSS segment is only $1e00 bytes in size" (or more appropriately, if you had other .res statements in BSS, "you want more memory than is left/available in BSS"). And I just explained why you can't simply change lorom256k.cfg + increase size on BSS.

That leaves the remaining two other BSS segments: BSS7E and BSS7F. These are what correlate with RAM in $7E/2000-FFFF and $7F/0000-FFFF (hence their names). These are the general-purpose RAM areas that get used for a multitude of things, so that's where something 8KBytes in size should go.

But as I mentioned above, you'll need to make sure your code properly uses a different bank, either through long addressing or some other means (e.g. php/phb/sep #$20/lda #$7e/pha/plb/rep #$30 + whatever code populates some part of bank $7e + plb/plp).

Edit: there are also registers $2180 and $2181-2183 which can let you access the RAM in banks $7E/7F; these are most commonly used for general DMA to/from RAM (gut feeling is you'll probably be needing this). Just remember that the range accessed there starts at $7E0000, not $7E2000, i.e. you can easily overwrite your own direct page/stack area if you forget this fact.

Final note: it's up to you to keep track of what parts of banks $7E and $7F are used for what. The console will not do this for you. Documenting what goes where and what size is important. Use code comments, use something. You aren't going to be able to keep all of this in your head indefinitely.

(2018/08/29 Edit: attachments removed.)
Re: Main principles of creating an "object"
by on (#141352)
Not only did Nintendo get their act together when they made the GBA, I think the CPU market also got more well-balanced too. Look at the CPU market back then. You got some 8-bit, 16-bit and 32-bit CISC processors, some 32-bit RISC processors, and some really cheap 8-bit RISC processors. Do you notice anything missing here?
Re: Main principles of creating an "object"
by on (#141652)
Sorry if I'm bumping or whatever, but I think I got the object checker code done and I want to see if it makes any sense because I'm a bit mentally exhausted today from school. I want to have it to where you say "jmp start_object_identifier" once somewhere in the code and it goes through all 128 objects so you don't have to say "jmp start_object_identifier" 128 times. I am wondering, if you jmp 2 times and then jsr 2 times and don't use the stack, will it send you to where you originally did the first jump?

Code:
.proc start_object_identifier
  pha
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16

object_identifier_loop:
  cpy #8192            ;sees if all 128 objects have been identified (each object is 128 bytes)
  beq done_identifying_objects      ;if so, quit searching
  lda ObjectTable,y         ;load the object identification byte of the object we're currently on
  tax               ;put the result in x so we can offset the object identification table to see what the object is
  jsr (ObjectIdentificationTable,x)   ;jump to the code that corresponds with the object
  pha               ;init stuff...
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16
  lda ObjectIdentifierCounter      ;says how many objects have been identified
  adc #$0040            ;add 64 to look at the next object
  sta ObjectIdentifierCounter      ;store the result for the next time we go through the loop
  tay               ;transfer the accumulator to y for "cpy #8192" and "lda ObjectTable,y"
  bra object_identifier_loop      ;jump back to look at the next object

done_identifying_objects:
  stz ObjectIdentifierCounter      ;make it so we start at object zero next frame
  plp         ; Restore P (A/X/Y sizes)
  ply         ; Restore Y
  plx         ; Restore X
  pla         ; Restore A
  rts

.endproc
Re: Main principles of creating an "object"
by on (#141663)
Here are a few things that look wrong to me:

1 - Y is undefined the first time the loop runs. Maybe you're missing an ldy ObjectIdentifierCounter before the loop starts, but I really don't see why this variable should be global, so I think you should just set Y to 0. You can back it up to ObjectIdentifierCounter before you call the object's handler, but the object must have access to this value so that it knows which slot to work with.

2 - You're not ignoring the object if the slot is empty. If lda ObjectTable, y returns 0, you should proceed to increment the counter, as opposed to calling the object's handler. Unless you have an empty handler for object 0, which is also a valid (but slightly slower) option.

3- Why are you pushing registers to the stack a second time, after calling the object's hendler (init stuff???)? I don't see any reason for that, and since there are no corresponding pulls, these values will be left on the stack (except for the last set), and the original set of registers you backed up at the beginning of the routine will not be restored.
Re: Main principles of creating an "object"
by on (#141665)
tokumaru wrote:
1 - Y is undefined the first time the loop runs. Maybe you're missing an ldy ObjectIdentifierCounter before the loop starts, but I really don't see why this variable should be global, so I think you should just set Y to 0. You can back it up to ObjectIdentifierCounter before you call the object's handler, but the object must have access to this value so that it knows which slot to work with.

Oh yeah, I forgot to have y be 0 at the beginning. I had an ldy ObjectIdentifierCounter at the beginning and got rid of it, but forgot to replace it with 0

tokumaru wrote:
2 - You're not ignoring the object if the slot is empty. If lda ObjectTable, y returns 0, you should proceed to increment the counter, as opposed to calling the object's handler. Unless you have an empty handler for object 0, which is also a valid (but slightly slower) option.

I had the empty handler option in mind, but I'll fix it.

tokumaru wrote:
3- Why are you pushing registers to the stack a second time, after calling the object's hendler (init stuff???)? I don't see any reason for that, and since there are no corresponding pulls, these values will be left on the stack (except for the last set), and the original set of registers you backed up at the beginning of the routine will not be restored.

The day I understand what
pha
phx
phy
php ; Save P (A/X/Y sizes)
means is the day I die. I thought that meant you pushed variables onto the stack, but why are you doing this? What does the second p in php even stand for?

Well, at least I got this mostly correct...

Edit: This should be good.

Code:
.proc start_object_identifier
  pha
  phx
  phy
  php         ; Save P (A/X/Y sizes)
  rep #$30    ; A=16, X/Y=16
  ldy #$0000

object_identifier_loop:
  cpy #8192            ;sees if all 128 objects have been identified (each object is 64 bytes)
  beq done_identifying_objects      ;if so, quit searching
  lda ObjectTable,y         ;load the object identification byte of the object we're currently on
  beq next_object
  tax               ;put the result in x so we can offset the object identification table to see what the object is
  jsr (ObjectIdentificationTable,x)   ;jump to the code that corresponds with the object
  rep #$30    ; A=16, X/Y=16
next_object
  lda ObjectIdentifierCounter      ;says how many objects have been identified
  adc #$0040            ;add 64 to look at the next object
  sta ObjectIdentifierCounter      ;store the result for the next time we go through the loop
  tay               ;transfer the accumulator to y for "cpy #8192" and "lda ObjectTable,y"
  bra object_identifier_loop      ;jump back to look at the next object

done_identifying_objects:
  stz ObjectIdentifierCounter      ;make it so we start at object zero next frame
  plp         ; Restore P (A/X/Y sizes)
  ply         ; Restore Y
  plx         ; Restore X
  pla         ; Restore A
  rts

.endproc
Re: Main principles of creating an "object"
by on (#141669)
As described previously somewhere in a thread by me: PHP pushes the P register onto the stack. The P register is what contains all the bits that control things like accumulator size, X/Y size, zero flag, negative flag, etc.. It's functionally the same thing as any of the other push operations, just that it's pushing P. PLP pulls whatever was most recently pushed onto the stack into the P register. The P register is 8 bits in size (always).

The intention behind doing PHP/PLP is so that you can manipulate the accum and X/Y register sizes "safely" (via REP/SEP). It's commonly used within subroutines where REP/SEP are used, so that A/X/Y sizes are restored once the routine is over with.

It's also important to understand that the order of operation matters as well: you would want PHA/PHX/PHY/PHP (note PHP comes last) at the start of the routine, not PHP/PHA/PHX/PHY. This matters because at the end of the routine, you would do the PLP/PLY/PLX/PLA -- notice how P gets restored first, ensuring that whatever the register sizes (of A, and X/Y) are restored *before* doing the pulls on Y, X, and A. If you did this in the wrong order, there's a very good possibility you could have pushed 16-bit accumulator or X/Y on the stack at routine entry, followed by pulling 8-bit accumulator or X/Y off stack on routine exit -- and that would result in an eventual stack overflow or underflow, and you'd be screwed.
Re: Main principles of creating an "object"
by on (#141671)
Espozo wrote:
I had the empty handler option in mind, but I'll fix it.

The empty handler is fine, I just wanted to make sure you weren't forgetting about the empty slots.

Quote:
The day I understand what
pha
phx
phy
php ; Save P (A/X/Y sizes)
means is the day I die.

You really shouldn't be using things you don't understand.

Quote:
I thought that meant you pushed variables onto the stack, but why are you doing this?

The main reason to do this is so you can restore all the flags as they were before an interrupt. By definition, an interrupt interrupts the code that is currently running, so you have to back up all the registers before modifying them and restore them before returning from the interrupt, so that the code that was interrupted can continue as if nothing happened.

I don't see many reasons to do this inside a routine you'll be calling yourself at predictable times, because you know for sure that the registers will be modified. So unless the code that calls this routine expects registers to be preserved, you probably don't need to back them up at all. And even if they do need to be preserved, it would make more sense for the calling code to back up only what's necessary before calling the routine, and restore them afterwards.

The 65816 has the added complication (when compared to the 6502) of register sizes. Like koitsu said, you might want to back up and restore those settings inside a routine that will be called from different places, since the registers might be configured differently.

Quote:
What does the second p in php even stand for?

"P" is the processor status register. It contains several flags, such as C, N and Z, among others.

Quote:
Edit: This should be good.

I'm no SNES expert but this should work, provided you initialize ObjectIdentifierCounter to 0 before calling this function for the first time. Like I said before, I find it a little weird that you're preparing this variable for the next frame, like you need to preserve its value outside the routine. You don't, it always starts as 0, so I'd seriously consider removing that stz ObjectIdentifierCounter and putting a sty ObjectIdentifierCounter right before calling the object handler.

The main advantage of doing this is that the routine will become more self contained, since it won't need any sort of external initialization. Also, with the variable properly sync'ed to the Y register, you'll be giving the object freedom to do whatever it wants with that register, because it can easily know its slot by reading ObjectIdentifierCounter back.

And there are a couple of optimizations you can make, like checking for the end of the loop at the bottom. You can use the branch in this decision to loop back (bne object_identifier_loop) and get rid of the bra.
Re: Main principles of creating an "object"
by on (#141673)
tokumaru wrote:
And there are a couple of optimizations you can make, like checking for the end of the loop at the bottom. You can use the branch in this decision to loop back (bne object_identifier_loop) and get rid of the bra.

I just fixed that now.

tokumaru wrote:
I don't see many reasons to do this inside a routine you'll be calling yourself at predictable times, because you know for sure that the registers will be modified. So unless the code that calls this routine expects registers to be preserved, you probably don't need to back them up at all. And even if they do need to be preserved, it would make more sense for the calling code to back up only what's necessary before calling the routine, and restore them afterwards.

In other words, get rid of
pha
phx
phy
php ; Save P (A/X/Y sizes)?

tokumaru wrote:
Like I said before, I find it a little weird that you're preparing this variable for the next frame, like you need to preserve its value outside the routine. You don't, it always starts as 0, so I'd seriously consider removing that stz ObjectIdentifierCounter and putting a sty ObjectIdentifierCounter right before calling the object handler.

??? I need to set ObjectIdentifierCounter to 0 every frame so that it doesn't leave the object table. Otherwise, it will look way farther ahead in ram and it will result in a mess. This code is the only place where ObjectIdentifierCounter will be bothered with. Are you saying you want me to set it to 0 before I start the routine instead of after?
Re: Main principles of creating an "object"
by on (#141691)
What data bank is this using? If the data bank is $00, then you only have 8kB of ram to work with and part of that is already taken up with "local variables" and stack. If the data bank is $7e, you can use the entire RAM. If you want to access the entire RAM, but want to stay in bank $00, you can switch index Y with index X, because you can index 24-bit addresses with X, but not with Y.
Re: Main principles of creating an "object"
by on (#141696)
psycopathicteen wrote:
What data bank is this using?

Bank $00.

psycopathicteen wrote:
If the data bank is $00, then you only have 8kB of ram to work with and part of that is already taken up with "local variables" and stack. If the data bank is $7e, you can use the entire RAM. If you want to access the entire RAM, but want to stay in bank $00, you can switch index Y with index X, because you can index 24-bit addresses with X, but not with Y.
\
What exactly? I'm already using x to say where to jump to, but I guess I could keep storing x and loading x to replace y if it comes down to it. I tried putting this: (I already know that the player 1 code does not take advantage of the fact that I'm using objects because I pretty much just cut and pasted it)

Code:
;===============================================================================
; start_object_identifier
;====================================================================================

.proc start_object_identifier
  rep #$30    ; A=16, X/Y=16
  ldy #$0000
  sty ObjectIdentifierCount

object_identifier_loop:
  lda ObjectTable,y         ;load the object identification byte of the object we're currently on
  beq next_object
  tax               ;put the result in x so we can offset the object identification table to see what the object is
  jsr (ObjectIdentificationTable-2,x)   ;jump to the code that corresponds with the object
  rep #$30    ; A=16, X/Y=16
next_object:
  lda ObjectIdentifierCount      ;says how many objects have been identified
  adc #$0040            ;add 64 to look at the next object
  sta ObjectIdentifierCount      ;store the result for the next time we go through the loop
  tay               ;transfer the accumulator to y for "cpy #8192" and "lda ObjectTable,y"
  cpy #8192            ;sees if all 128 objects have been identified (each object is 128 bytes)
  bne object_identifier_loop      ;if so, quit searching
  rts

.endproc

;====================================================================================
; ObjectIdentificationTable
;====================================================================================

ObjectIdentificationTable:
  .word player1

;====================================================================================
; Objects
;====================================================================================



.proc player1
  rep #$30                  ; A=16, X/Y=16

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

  ldx #MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectXPosition
  sta MetaspriteXPosition
  lda ObjectYPosition
  sta MetaspriteYPosition

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  sep #$20                  ; A=8


_up:
  lda Joy1Data+1
  and #$08
  beq _down

  lda ObjectYPosition
  sec
  sbc #$02
  sta ObjectYPosition

_down:
  lda Joy1Data+1
  and #$04
  beq _left

  lda ObjectYPosition
  clc
  adc #$02
  sta ObjectYPosition

_left:
  lda Joy1Data+1
  and #$02
  beq _right

  rep #$20
  dec MapX
  lda ObjectXPosition
  sec
  sbc #$02
  sta ObjectXPosition
  sep #$20

_right:
  lda Joy1Data+1
  and #$01
  beq _done

  rep #$20
  inc MapX
  lda ObjectXPosition
  clc
  adc #$02
  sta ObjectXPosition

_done:
  rts

.endproc

in the rest of my code, but the game bugs out and the screen flashes. It's even effecting my the rest of my code because it stops my oam clearing routine from ever happening. If you want to look at it, go right ahead...

Attachment:
MetaspriteDemoKoitsu.rar [287.16 KiB]
Downloaded 82 times

This is the original one that does not jump to the code and works.

Attachment:
MetaspriteDemoKoitsu Working.rar [291.03 KiB]
Downloaded 102 times
Re: Main principles of creating an "object"
by on (#141702)
Quote:
What exactly? I'm already using x to say where to jump to, but I guess I could keep storing x and loading x to replace y if it comes down to it. I tried putting this: (I already know that the player 1 code does not take advantage of the fact that I'm using objects because I pretty much just cut and pasted it)


Okay, that is a good point. I tried reducing your object table to 96 objects in order to fit into the bank $00, but WLA has some convoluted way of managing addresses.
Re: Main principles of creating an "object"
by on (#141703)
psycopathicteen wrote:
but WLA has some convoluted way of managing addresses.

I'm using ca65 now. To assemble the file, go to the command line and just type build. (I forgot to mention that. :oops: )
Re: Main principles of creating an "object"
by on (#141713)
I figured out how to get this to work.

1) Move object table to BSS segment.
2) Reduce object table to 6144 bytes (96 objects)
3) There needs to be a "clc" before you add 64 to the object pointer.
4) Player 1 should have an object routine number of 2, because addresses are 2 bytes.
5) Replace the jmp with a jsr, when jumping to the object routine.
Re: Main principles of creating an "object"
by on (#141717)
psycopathicteen wrote:
1) Move object table to BSS segment.

What is a "BSS segment"?

Edit: Never mind, I got you.

psycopathicteen wrote:
2) Reduce object table to 6144 bytes (96 objects)

Would it truly be impossible to look through 128 objects, because I would rather each object have 48 bytes instead of 64 and have 128 objects total. The table would be the exact same size as if you did it your way.

psycopathicteen wrote:
4) Player 1 should have an object routine number of 2, because addresses are 2 bytes.

??? If you are saying that I should have an empty space for the first objects in the "ObjectIdentificationTable", notice how I wrote "jsr (ObjectIdentificationTable-2,x)" instead of "jsr (ObjectIdentificationTable,x)".

psycopathicteen wrote:
5) Replace the jmp with a jsr, when jumping to the object routine.

That's probably what's jacking it up the most. I really didn't see how the glitch result I got was even related to the object code. I know this is weird, but I get jsr and jmp mixed up sometimes.

I greatly appreciate you taking the time to help me.
Re: Main principles of creating an "object"
by on (#141724)
Espozo wrote:
psycopathicteen wrote:
2) Reduce object table to 6144 bytes (96 objects)

Would it truly be impossible to look through 128 objects, because I would rather each object have 48 bytes instead of 64 and have 128 objects total. The table would be the exact same size as if you did it your way.

That sounds good. I also thought of a way around the memory limitation. Instead of using a pointer to a table of object routines, you can just store the jump address in the object table, and use jsr (ObjectTable,x) to jump to the object routine, that is pointed to by X. Then if you run out of memory for objects, you could have a second object table of the same size somewhere in the $7E2000-$7EFFFF range.

Espozo wrote:
psycopathicteen wrote:
4) Player 1 should have an object routine number of 2, because addresses are 2 bytes.

??? If you are saying that I should have an empty space for the first objects in the "ObjectIdentificationTable", notice how I wrote "jsr (ObjectIdentificationTable-2,x)" instead of "jsr (ObjectIdentificationTable,x)".

You're subtracting 2 from ObjectIdentificationTable.

Espozo wrote:
I greatly appreciate you taking the time to help me.

Thanks!
Re: Main principles of creating an "object"
by on (#141734)
An approach I started to use in my NES projects is to store the address of the object handler in the object slot, as opposed to storing the object IDs. This way you get to do some advanced state management with the objects. It kinda allows you to have "sub handlers", like a routine for walking, another for attacking, and so on. All you have to do is change the pointer in the object slot. To do this, I have made a routine that saves the current PC before returning from the handler, that objects only call when they enter a new state.

This eliminates the need to create codes for each state, and to decide where to jump to every time the handler starts.
Re: Main principles of creating an "object"
by on (#141737)
I got the object routine to work finally (I haven't started trying to increase the object table yet though) and it works fine for one object, but when you try to put two down, it uses the same registers as the first one, resulting in the objects adding twice and moving twice as fast. Is this wrong? (Look at ObjectOffset.) I had this working fine when I said ObjectTable,y, but I want to free up y. I think the problem is that it is looking at the address of where ObjectOffset is instead of the value it holds, but how do I fix this?

Code:
.segment "CODE"

;====================================================================================
; start_object_identifier
;====================================================================================

.proc start_object_identifier
  rep #$30    ; A=16, X/Y=16
  ldy #$0000
  sty ObjectIdentifierCount

object_identifier_loop:
  lda ObjectTable,y         ;load the object identification byte of the object we're currently on
  beq next_object
  sta ObjectOffset
  tax               ;put the result in x so we can offset the object identification table to see what the object is
  jsr (ObjectIdentificationTable-2,x)   ;jump to the code that corresponds with the object
  rep #$30    ; A=16, X/Y=16
next_object:
  lda ObjectIdentifierCount      ;says how many objects have been identified
  clc
  adc #$0030            ;add 48 to look at the next object
  sta ObjectIdentifierCount      ;store the result for the next time we go through the loop
  tay               ;transfer the accumulator to y for "cpy #8192" and "lda ObjectTable,y"
  cpy #6144            ;sees if all 128 objects have been identified (each object is 128 bytes)
  bne object_identifier_loop      ;if so, quit searching
  rts

.endproc

;====================================================================================
; ObjectIdentificationTable
;====================================================================================

ObjectIdentificationTable:
  .word player1

;====================================================================================
; Objects
;====================================================================================

.proc player1
  rep #$30                  ; A=16, X/Y=16

  lda MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount       ; ...and store it in MetaspriteCount

  ldx #MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectOffset+2
  sta MetaspriteXPosition
  lda ObjectOffset+4
  sta MetaspriteYPosition

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  sep #$20                  ; A=8

_up:
  lda Joy1Data+1
  and #$08
  beq _down

  lda ObjectOffset+4
  sec
  sbc #$02
  sta ObjectOffset+4

_down:
  lda Joy1Data+1
  and #$04
  beq _left

  lda ObjectOffset+4
  clc
  adc #$02
  sta ObjectOffset+4

_left:
  lda Joy1Data+1
  and #$02
  beq _right

  rep #$20
  dec MapX
  lda ObjectOffset+2
  sec
  sbc #$02
  sta ObjectOffset+2
  sep #$20

_right:
  lda Joy1Data+1
  and #$01
  beq _done

  rep #$20
  inc MapX
  lda ObjectOffset+2
  clc
  adc #$02
  sta ObjectOffset+2

_done:
  rts

.endproc
Re: Main principles of creating an "object"
by on (#141739)
The problem is you set aside 48 bytes of RAM for each instanciated object but you're not using them for anything besides the object ID. This will cause all instances of the same type of object to use the same global variables, while in fact you should be using the RAM you set aside for each instance.

The variables used by the objects should live inside the object slot. You can do this by assigning an offset to each property. For example, you can create a symbol "ObjectOffset" that equals 8 (i.e. the 9th byte within the object slot). Then you can access it like this:

Code:
lda ObjectTable+ObjectOffset, y

...assuming Y is pointing to the current object slot. I don't know if this is the same on the 65816, but on the 6502, the amount of operations you can perform using Y indexing is very limited... if this is the case you should consider using X instead of Y to point to the object slots.
For multi-byte properties, you can keep using "+1", "+2", and so on.
Re: Main principles of creating an "object"
by on (#141743)
Just adding to what I wrote above: Using a block of RAM for each object instance also means that each object handler must initialize the slots they're gonna use.

Each time an object is loaded, its handler should initialize all the properties to their default values, before the object can start "living". This is another reason why it might be interesting to remember each object's PC instead of its type: you won't have to skip the initialization every frame before doing what the object is supposed to be doing, since you can just save the PC after the initialization.
Re: Main principles of creating an "object"
by on (#141758)
Does Espozo know how to use direct page? I always used direct page for object slots, so I didn't have to keep juggling index registers.
Re: Main principles of creating an "object"
by on (#141761)
That's beyond my knowledge, so maybe if you show some sample code he'll prefer that.
Re: Main principles of creating an "object"
by on (#141772)
It looks really difficult to use the direct page in ca65, for anything other than local memory. In bass, you can do "define something($00)" and have be an object variable, and do "define something_else($0000)", and use it as a local variable. In ca65, it expects the direct page to always be at $0000-$00FF.

Maybe you can get around this problem by placing the stack at $0000-$00FF, and placing local memory at $0100-$01FF.
Re: Main principles of creating an "object"
by on (#141774)
Wait a minute... the assembler doesn't let you do something that the CPU is capable of? WTF?
Re: Main principles of creating an "object"
by on (#141780)
I looked around and according to this http://www.cc65.org/doc/ca65-6.html ca65 supports the ability to support labels, so you might be able to control what variables are accessed with DP, and what is accessed with absolute addressing. Espozo is using some weird automatic memory labeling feature that involves "memory segments."
Re: Main principles of creating an "object"
by on (#141784)
Issue has been discussed before. ca65 will generate opcodes using direct page opcodes, as long as the addresses referenced are within $0000-00FF (ZEROPAGE segment), i.e. lda $0000 will get turned into $A5 00, while lda $0100 would get turned into $AD 00 01 (there is no "direct page" version of this that is 3 bytes long; the zero page opcode on 6502/65c02 is the same opcode as on the 65816 for direct page). I believe it will use absolute addresses for locations outside of that range, which is also okay (review all the addressing modes on 65816 to see why). Things like lda ($00) (that's a 65c02/65816-ism) would get assembled into $B2 00, and lda ($00,x) would get assembled into $A1 00.

if you want to force absolute addressing, it's possible to do so. See said thread (read every post): viewtopic.php?t=4166

Regarding memory/segments and ca65 templates: I've already expressed my displeasure with ca65's lack of knowledge there (more specifically: that the ZEROPAGE segment is a requirement and is highly 6502-centric). But all of that can be worked around without too much effort.

The "memory segment" thing is a requirement for ca65 (see above, re: config template). That's just how it works.
Re: Main principles of creating an "object"
by on (#141787)
tokumaru wrote:
The problem is you set aside 48 bytes of RAM for each instanciated object but you're not using them for anything besides the object ID. This will cause all instances of the same type of object to use the same global variables, while in fact you should be using the RAM you set aside for each instance.

The variables used by the objects should live inside the object slot. You can do this by assigning an offset to each property. For example, you can create a symbol "ObjectOffset" that equals 8 (i.e. the 9th byte within the object slot). Then you can access it like this:

Code:
lda ObjectTable+ObjectOffset, y

...assuming Y is pointing to the current object slot. I don't know if this is the same on the 65816, but on the 6502, the amount of operations you can perform using Y indexing is very limited... if this is the case you should consider using X instead of Y to point to the object slots.
For multi-byte properties, you can keep using "+1", "+2", and so on.

Oh yeah, I was being stupid. :oops: I know this would be the solution, because I tested it and it works

Code:
  ldy ObjectOffset   ;This was originally ObjectIdentifierCounter, but I change the name
  lda ObjectTable+4,y

I was trying not to use x or y earlier, but I guess I have to.

koitsu wrote:
Regarding memory/segments and ca65 templates: I've already expressed my displeasure with ca65's lack of knowledge there (more specifically: that the ZEROPAGE segment is a requirement and is highly 6502-centric). But all of that can be worked around without too much effort.

At least ca65 isn't broken like WLA.
Re: Main principles of creating an "object"
by on (#142214)
Just want to say thank you guys for creating this thread. I build an object-handling engine in my game recently (then re-built it all to run on the direct page), and it's now working beautifully. I see how WLA is such a pain in the ass now though. If I try to do something like "LDA $0000+obj0XPosn, x" with x as the absolute object slot address, WLA takes the liberty of assuming I meant a one-byte operand because the result of the addition is below $0100, therefore I have to use LDA.w. Also get the same problem if I want to do a "LDA spr0XPosn, x" with a label like ".EQU spr0XPosn $0000", have to use the .w again or it defaults to direct page. It wasn't obvious to me what was so wrong about that at first just because I hadn't touched direct page yet and WLA is basically the only assembler I've really used. I get it now.

Anyways. What I have functions great, but I wonder if it could be better. With each object, I test every other object that is FURTHER DOWN THE LIST for collisions. It's the easiest way I know to test every pair with no duplication, but it starts to become exponentially more demanding once there are about 20-30 different objects on screen. It also means I have essentially duplicate code mirrored for say colliding a projectile with an enemy, and an enemy with a projectile (which I guess I could simply invert the indexes and call a common routine, but at the expense of even more processing...)

My only thoughts on improving this would be to have one or more separate lists of specific kinds of objects, say a separate enemy list. Or probably even better an idea, to simply keep the list sorted each frame. When spawning an object, scan down the list and compare the ID variable until you find one greater than the new object, and insert it there (fill next available slot and re-link the list around it). That way you only have to check each object against types further down the list and you can choose object numbers appropriately, hmm... Now that I think about it, I should probably do that one.

Anyways, I'm curious if there is any common advice about object collisions I should know about. I've read in various places about breaking down the map into regions, then only testing collisions within each region to reduce the number of comparisons, but am I correct in assuming this won't provide much benefit when we're limited to somewhere around 64 objects?

(If my questions have already been answered in this thread I'm sorry, I did read it and I've probably forgotten some things.)
Re: Main principles of creating an "object"
by on (#142217)
Almost every run'n'gun game I know of uses exactly 8 bullets.
Re: Main principles of creating an "object"
by on (#142221)
psycopathicteen wrote:
Almost every run'n'gun game I know of uses exactly 8 bullets.

8 bullets? Do you mean only 8 enemy bullets or 8 bullets total? I'm absolutely sure Contra III puts more than 8 bullets on screen, but I don't know if there are more than 8 enemy bullets or not. I currently just have a counter for how many frames the player can't shoot bullets, and when shooting in a strait line, I've never gotten more than 5 because the bullets fly off the screen. This way, I don't have to keep track of how many bullets there are and you won't have it to were you get about five bullets back to back and then 0 after if you are using a turbo controller. I haven't done anything with enemies yet, just the player.
Re: Main principles of creating an "object"
by on (#142223)
Honestly, I've found few things more frustrating in old games than having to wait for a missed bullet to disappear before you can fire another one... I am avoiding that at all costs.

That said my projectiles don't also usually despawn on impact, you can run and pick them back up. I've thought it would probably be smart to remove them from the object list when they become idle and stick them in a separate array of IDs and coordinates for pickups that just sit there until player contact.
Re: Main principles of creating an "object"
by on (#142230)
Khaz wrote:
With each object, I test every other object that is FURTHER DOWN THE LIST for collisions. It's the easiest way I know to test every pair with no duplication, but it starts to become exponentially more demanding once there are about 20-30 different objects on screen.

Avoiding duplicate tests is a good improvement, but it's still terribly wasteful to test for all possible collisions between all the objects. Do bullets collide with bullets in your game? Enemies with enemies? Enemies with items? I'm sure you could be avoiding some of these.

Quote:
My only thoughts on improving this would be to have one or more separate lists of specific kinds of objects, say a separate enemy list.

That would be my suggestion. You can still have a single list of objects, to keep all the spawning and common routines working normally without duplicated code, but you could have linked lists of specific object types. A variable indicates the slot of the first enemy. Inside that slot, a byte indicates the slot of the second enemy, and so on. That way, inside the AI code for any object, you can call a CollideWithEnemies routine, that will test for collisions exclusively against the objects in the list of enemies. One nice aspect of this is that the types of collisions aren't hardcoded, you can still have a special enemy that collides with enemies, because his AI will call CollideWithEnemies, while most enemies won't.

Quote:
Or probably even better an idea, to simply keep the list sorted each frame. When spawning an object, scan down the list and compare the ID variable until you find one greater than the new object, and insert it there (fill next available slot and re-link the list around it). That way you only have to check each object against types further down the list and you can choose object numbers appropriately, hmm... Now that I think about it, I should probably do that one.

You think that's simpler? I honestly don't, and it doesn't sound like it'd work. Consider this: two types of objects are not supposed to collide with each other, such as enemies and items, but you still have to put both in the list. No matter which one you pick to go first, it will collide with the other, because it's further down the list.

Quote:
Anyways, I'm curious if there is any common advice about object collisions I should know about. I've read in various places about breaking down the map into regions, then only testing collisions within each region to reduce the number of comparisons, but am I correct in assuming this won't provide much benefit when we're limited to somewhere around 64 objects?

Does your game have a scrolling map? Are you loading and unloading objects as the screen scrolls? If you are (and you should be!), you are kinda already breaking the map into regions. The only objects being tested are the ones that are in the region surrounding the camera. I don't think that subdividing that space further would bring any benefits.
Re: Main principles of creating an "object"
by on (#142239)
Quote:
That would be my suggestion. You can still have a single list of objects, to keep all the spawning and common routines working normally without duplicated code, but you could have linked lists of specific object types. A variable indicates the slot of the first enemy. Inside that slot, a byte indicates the slot of the second enemy, and so on. That way, inside the AI code for any object, you can call a CollideWithEnemies routine, that will test for collisions exclusively against the objects in the list of enemies. One nice aspect of this is that the types of collisions aren't hardcoded, you can still have a special enemy that collides with enemies, because his AI will call CollideWithEnemies, while most enemies won't.

Yeah, I see what you mean. That's good advice. You could have a sub-list for each type of object really efficiently that way...
Quote:
Does your game have a scrolling map? Are you loading and unloading objects as the screen scrolls? If you are (and you should be!), you are kinda already breaking the map into regions. The only objects being tested are the ones that are in the region surrounding the camera. I don't think that subdividing that space further would bring any benefits.

Actually, it has more of a metroid-style map system at the moment, each map being just a full 64x64 tilemap of 16x16s. I'm not entirely certain if it'll be staying that way, but I suppose now would be the time to make up my mind on that. It shouldn't be too difficult to process a full map that size, I wouldn't think...
Re: Main principles of creating an "object"
by on (#142244)
Khaz wrote:
You could have a sub-list for each type of object really efficiently that way...

Exactly.

If you index the types (i.e. the variables that indicate the first elements of the linked lists could be arranged as an array), you won't even have to duplicate the collision routine. That way you could load an index register with the index of the type you want to test collisions with (e.g. 0 = enemies, 1 = bullets, 2 = items, etc.) and jump to the collision routine. The routine would then use this index to loop up the index of the object slot that contains the first object of the type you selected, and would traverse the entire list testing for collisions between these objects and the one that called the routine.

Indexing the types should also prevent any code duplication for adding/removing objects to the sub-lists.

Quote:
Actually, it has more of a metroid-style map system at the moment, each map being just a full 64x64 tilemap of 16x16s. I'm not entirely certain if it'll be staying that way, but I suppose now would be the time to make up my mind on that. It shouldn't be too difficult to process a full map that size, I wouldn't think...

I see. Yeah, if the maps are this small I don't think you need complex object loading/unloading. Some sort of camera proximity test could help prevent unnecessary processing, but I'm not sure if that's necessary.

If I were to do a camera proximity test, I'd probably do it in my meta-sprite rendering routine. I mean, it already calculates the position of the object relative to the camera (so that the individual sprites are generated based on this coordinate), it would just be a matter of checking if the upper bytes of these coordinates are over a certain threshold. Using this flag I could make the decision of unload the object (in case of bullets) or not do any more processing on it (physics, collisions, etc.) this frame.
Re: Main principles of creating an "object"
by on (#142247)
The size of such a room is about 4 screens in each dimension... yeah, not really worth doing any distance management unless you have tons of objects (and even then at that point you should consider if some should be processed all the time). Probably better to reserve it only to when the sprites are drawn.

psycopathicteen wrote:
Almost every run'n'gun game I know of uses exactly 8 bullets.

Not a run'n'gun but relevant:

Image
Re: Main principles of creating an "object"
by on (#147333)
koitsu wrote:
Issue has been discussed before. ca65 will generate opcodes using direct page opcodes, as long as the addresses referenced are within $0000-00FF (ZEROPAGE segment), i.e. lda $0000 will get turned into $A5 00, while lda $0100 would get turned into $AD 00 01 (there is no "direct page" version of this that is 3 bytes long; the zero page opcode on 6502/65c02 is the same opcode as on the 65816 for direct page). I believe it will use absolute addresses for locations outside of that range, which is also okay (review all the addressing modes on 65816 to see why). Things like lda ($00) (that's a 65c02/65816-ism) would get assembled into $B2 00, and lda ($00,x) would get assembled into $A1 00.

I finally made the move to ca65 for SNES development, and was a bit weary of all talk I've heard about its rigid zeropage behaviour... But so far I haven't encountered a situation which couldn't be solved with the z: prefix:

Code:
lda     z:<LABEL     ;if DP is page aligned and LABEL is within that page
lda     z:LABEL-dp   ;here I use "dp" as a special variable which is updated in a "set dpage" macro

My biggest issue was to solve the 2nd case since I couldn't find anyway to .redef a normal .define:d variable. I solved it by using some macro magic found here. Anyone know of a cleaner way to redefine variables? That solution seems a bit fragile to me (mostly because I'm not fully sure what's actually going on).