My sister made good sprites art for an image of 8x16 pixels... but later, after playing pacman on nes, i noiced that the ghost looks like it fills a space of 2 8x16 pixel sprites... and then thought maybe every nes-pixel equals a group of 4 cpu-pixels. Am I kindof right? I searched and found one of yall talking about a 16x16 title.. but normal sprites are 8x8... right?
ok.. in
this thread Bregalad helped me learn that a 16x16 pixel image is a meta-sprite. So are the ghosts, in nes pacman, meta-sprites?
unregistered wrote:
ok.. in
this thread Bregalad helped me learn that a 16x16 pixel image is a meta-sprite. So are the ghosts, in nes pacman, meta-sprites?
Yes, almost any game sprite-object is a metasprite due to the 8x8 or 8x16 limitation of the hardware sprites on the nes.
They can be either 8x8 or 8x16.
You can change the sprite size setting a certain bit in one of the PPU registers. ($2000)
EDIT: Oops! Beaten to the punch!
Thank you both for your answers!
I thought a regular nes sprite would be 16x16 because 1) Mario is 16x16, i think, and 2) In the 1.31 NESst when you try to apply a chosen
00 or 01 or 10 or 11 pallet to the nametable it only allows a space of 16x16 to be colored. And I know yall suggested lots of fixes and things so then: why does the nes not allow a smaller 8x8 tile to be a certian color when 8x8 is the size of a small sprite? Why is applying a pallet limited to a 16x16 space?
The reason for the background being in 16x16 squares is probably because the RAM probably cost more and they didn't really need it, since specific 8x8 tiles are probably too small to vary colors anyway with the background.
Then, are sprites allowed 8x8 pallet access? Thanks for your help.
It seems restrictive to limit the background to 16x16. But yes, maybe it's ok.
Did they improve pallet access for backgrounds on the snes hardware?
By "improve" i mean enable 8x8 pallet application access
Not sure, but I know they added a TON of crap to it. I think they have a 256 color mode for backgrounds? So tile pallets are basically useless with that many color options, if I am correct.
Sprites are different from backgrounds. For the background, palettes are applied to 16x16 pixel areas (except for a special video mode available through the MMC5 mapper, which allows palettes to be applied to individual tiles), but each sprite can use a different palette. If the PPU is configured to use 8x8 sprites, each 8x8 square can use a different palette, if it's configured to use 8x16, each 8x16 rectangle can use a different palette.
Usually, game objects are indeed represented by multiple hardware sprites, because most of them are larger than 8x8 or 8x16 pixels. Most games have some kind of meta-sprite system, with tables indicating how many hardware sprites are necessary to form a game sprite, how they are positioned, the palette each one uses, and so on. Only bullets and other small objects use a single hardware sprite.
3gengames wrote:
Not sure, but I know they added a TON of crap to it. I think they have a 256 color mode for backgrounds? So tile pallets are basically useless with that many color options, if I am correct.
A 256-color background means fewer background layers and fewer tiles that fit in video RAM.
tokumaru wrote:
Usually, game objects are indeed represented by multiple hardware sprites, because most of them are larger than 8x8 or 8x16 pixels. Most games have some kind of meta-sprite system, with tables indicating how many hardware sprites are necessary to form a game sprite, how they are positioned, the palette each one uses, and so on. Only bullets and other small objects use a single hardware sprite.
Thank you so much for your extra helpful reply!
What are the limits of the meta-sprite system? I remember reading about how Punchout!! required special hardware to draw/animate the humungus enemies. Like
what is the largest meta-sprite we could use without improving cart hardware? (Question is from my artist!)
Also... about scrolling; what are possible width and height/depth for scrolling... guess she is wondering how much of a level can we do? Mario 3 has the type of scrolling she is wanting.
unregistered wrote:
What are the limits of the meta-sprite system?
That depends on the game. There is no standard for that, and each game has its own implementation of a meta-sprite system designed according to its specs.
For example, some systems might require that all sprites are arranged in a grid and use the same palette, so that the meta-sprite definitions will use less ROM space (no need to store the coordinates and attributes of each sprite). In my game I have a very versatile system, where each sprite's position is relative to the object's coordinates and has its own attributes. As a result, the definitions are larger.
Quote:
I remember reading about how Punchout!! required special hardware to draw/animate the humungus enemies.
I'm not aware of that. The mapper used in that game doesn't have any features that would increase the sprite capacity of the system. What you can do sometimes is draw game objects using background tiles, if the background is flat enough. The ring is just a solid color, so it's possible that one of the fighters is actually drawn with background tiles rather than sprites, but I didn't check.
Quote:
Like what is the largest meta-sprite we could use without improving cart hardware? (Question is from my artist!)
You are the programmer, so you're the one that should have the answer!
Truth is that it's impossible for mappers to increase the amount of displayed sprites (at most they can provide you with a larger amount of tiles to pick from), because all the sprite logic and OAM is internal to the PPU, meaning that external hardware can't mess with it. So no matter the kind of system you code, you'll always be limited by the same hardware rules: 8 sprites per scanline, 64 sprites screen.
So the largest meta-sprite is either 64x64 pixels (in 8x8 mode) or 64x128 pixels (in 8x16 mode). I wouldn't make such a huge sprite though, because there wouldn't be any sprites left for other game objects.
Quote:
Also... about scrolling; what are possible width and height/depth for scrolling... guess she is wondering how much of a level can we do?
Again, this is up to the programmer. Levels can be as large as you want, it all comes down to the memory necessary to store the level maps. Depending on the type of compression you use, your maps will occupy more or less ROM space. If you implement a system where levels are generated randomly, you could have an endless level.
Each game's engine handles the scrolling differently. Some decompress whole levels to RAM (needs too much RAM, not recommended on the NES unless you have extra memory on the cart), some decompress the levels progressively so that only the visible area is in RAM, and some even read map data directly from the ROM, so they don't have to worry about wasting too much RAM, but the compression schemes used for this don't usually compress as well, so the levels need more ROM space.
Quote:
Mario 3 has the type of scrolling she is wanting.
You have to select the kind of compression that will work best with the kind of levels you want to make, also taking into consideration the decoding process (i.e. how you are going to read the map data, for drawing and for collision with the objects).
SMB3 has some limitations, like the height of the level, which is 2 NES screens tall (minus the status bar), and color glitches at the right of the screen because of the way it uses the name tables (horizontal mirroring). Status bars also affect the way you implement the scrolling if you plan on scrolling vertically (which I assume you do), so I need to know whether you want a status bar in your game and whether your levels can be taller than 2 screens before I suggest the optimal solution for your case.
tokumaru wrote:
Truth is that it's impossible for mappers to increase the amount of displayed sprites (at most they can provide you with a larger amount of tiles to pick from), because all the sprite logic and OAM is internal to the PPU
Unless you put an auxiliary PPU on the cartridge that renders sprites to background tiles. Super Game Boy, for example, does this.
Quote:
If you implement a system where levels are generated randomly, you could have an endless level.
This has been done even on the lowly Atari 2600: see River Raid.
Quote:
some even read map data directly from the ROM, so they don't have to worry about wasting too much RAM, but the compression schemes used for this don't usually compress as well, so the levels need more ROM space.
These include multilayer metatile engines like those of Mega Man series, Blaster Master, Sonic 2 for Genesis, and your Sonic clone, right?
tepples wrote:
Unless you put an auxiliary PPU on the cartridge that renders sprites to background tiles. Super Game Boy, for example, does this.
True, but the 4-colors per tile limitation severely reduces the possibilities. If the NES had a 4bpp mode, like the SMS for example, this would be a very interesting mapper feature. The SMS can't have this though, since the cart has no direct access to the video memory.
Quote:
This has been done even on the lowly Atari 2600: see River Raid.
Exactly. Also Pitfall, I think. Many Atari games had to fit in 4KB of ROM, so since there wasn't much space for level maps the solution was to generate them in real time. If you always start with the same seed, the same levels will be generated every time.
Quote:
These include multilayer metatile engines like those of Mega Man series, Blaster Master, Sonic 2 for Genesis, and your Sonic clone, right?
Yes, but Sonic on the Genesis actually uses the data in RAM, because it's compressed even further in the ROM.
tokumaru wrote:
True, but the 4-colors per tile limitation severely reduces the possibilities. If the NES had a 4bpp mode, like the SMS for example, this would be a very interesting mapper feature. The SMS can't have this though, since the cart has no direct access to the video memory.
At least, I think the Wide-boy (
link to Chris' page) sort of use this to draw the whole gameboy display with backgrounds (but then of course the gameboy only has 4 different shades in total, even after combining the sprites with the background plane, and so this works), and it even needs to change the tiles mid-screen to display a whole frame (I wonder why this is needed, as 160x144 occupies only 360 8x8 tiles and you can just change the tile page used for background midscreen when it uses up 256 of them, instead of rewriting the memory, and there're still enough space for stuff such as border graphics, etc.). That isn't much useful for FC/NES game making though, as the WB is just the gameboy hardware in whole.
Gilbert wrote:
(but then of course the gameboy only has 4 different shades in total, even after combining the sprites with the background plane, and so this works)
Exactly. That works fine for the GB, but NES games using only 4 colors isn't very impressive, so it kinda kills the point of having more sprites (or any other improvement you can think of).
Quote:
(I wonder why this is needed, as 160x144 occupies only 360 8x8 tiles and you can just change the tile page used for background midscreen when it uses up 256 of them, instead of rewriting the memory, and there're still enough space for stuff such as border graphics, etc.)
It's probably not rewriting anything, just bankswitching (i.e. it has enough memory for the whole GB screen, and is able to select which parts of it can be seen by the NES), which is not much different from switching pattern pages. Maybe they just didn't want the 6502 to do much.
Quote:
That isn't much useful for FC/NES game making though, as the WB is just the gameboy hardware in whole.
It proves we can build a mapper with its own PPU that feeds its output to the NES through the pattern/name/attribute tables. But I agree that it's not very useful, because of the 4 colors per tile limitation.
tokumaru wrote:
It's probably not rewriting anything, just bankswitching (i.e. it has enough memory for the whole GB screen, and is able to select which parts of it can be seen by the NES), which is not much different from switching pattern pages. Maybe they just didn't want the 6502 to do much.
I don't know, but as Chris wrote on the page (unless I misunderstood the wordings):
Quote:
You see, the Famicom doesn't have enough BG tiles in VRAM to display an entire 160x144 Gameboy screen, so the WideBoy hardware has to write to the same RAM with different graphics halfway down the screen.
and we know that his information are mostly trustable so that made me wonder about its implementation.
I'm not familiar with the cart port and such (too lazy to look this up from the wiki), but can the pins access the PPU ports directly? If it can then the PCB may be able to change the pattern tables itself and ignore the 6502 in the console. If it can't then yes, it's more likely to be bank-switching, as I suspect it's hard to rewrite that large amount of data given limited bankwidth in the middle of a single frame.
Gilbert wrote:
I don't know, but as Chris wrote on the page (unless I misunderstood the wordings):
Quote:
You see, the Famicom doesn't have enough BG tiles in VRAM to display an entire 160x144 Gameboy screen, so the WideBoy hardware has to write to the same RAM with different graphics halfway down the screen.
I see... Well, this might very well be the case if the WideBoy can write to the RAM at the same time as the NES PPU is reading from it. It would just be a matter of overwriting tiles that were already rendered by the NES. Few people have WideBoys, so we have to trust Chris.
Quote:
I'm not familiar with the cart port and such (too lazy to look this up from the wiki), but can the pins access the PPU ports directly? If it can then the PCB may be able to change the pattern tables itself and ignore the 6502 in the console.
What do you mean by "the pins"? What I know is that NES code can only access VRAM through the PPU ports because the CPU has no direct access to that bus, but a mapper can have access to both buses, so it surely can manipulate VRAM without having to go through the PPU.
tokumaru wrote:
What do you mean by "the pins"? What I know is that NES code can only access VRAM through the PPU ports because the CPU has no direct access to that bus, but a mapper can have access to both buses, so it surely can manipulate VRAM without having to go through the PPU.
I meant the pins on the
cartridge port, but yeah, I now see that you cannot access directly the PPU registers (in the case of switching pattern tables, the PPU port pointed to at $2000) by sending signals (whatever) via the connector pins, so we will need the 6502 to write to the register for this and so manipulating CHR-RAM contents or mapper bankswitching seems to be the simpler solution.
tokumaru wrote:
unregistered wrote:
What are the limits of the meta-sprite system?
That depends on the game. There is no standard for that, and each game has its own implementation of a meta-sprite system designed according to its specs.
For example, some systems might require that all sprites are arranged in a grid and use the same palette, so that the meta-sprite definitions will use less ROM space (no need to store the coordinates and attributes of each sprite). In my game I have a very versatile system, where each sprite's position is relative to the object's coordinates and has its own attributes. As a result, the definitions are larger.
Wonderful to think about, thanks!
tokumaru wrote:
unregistered wrote:
I remember reading about how Punchout!! required special hardware to draw/animate the humungus enemies.
I'm not aware of that. The mapper used in that game doesn't have any features that would increase the sprite capacity of the system. What you can do sometimes is draw game objects using background tiles, if the background is flat enough. The ring is just a solid color, so it's possible that one of the fighters is actually drawn with background tiles rather than sprites, but I didn't check.
I can't find where I read that.
Are the background tiles able to be collision detected?
tokumaru wrote:
unregistered wrote:
Like what is the largest meta-sprite we could use without improving cart hardware? (Question is from my artist!)
You are the programmer, so you're the one that should have the answer!
: )
tokumaru wrote:
unregistered wrote:
Also... about scrolling; what are possible width and height/depth for scrolling... guess she is wondering how much of a level can we do?
Again, this is up to the programmer. Levels can be as large as you want, it all comes down to the memory necessary to store the level maps. Depending on the type of compression you use, your maps will occupy more or less ROM space. If you implement a system where levels are generated randomly, you could have an endless level.
Sweet! Thank you for all (you are very good at teaching) of this information!
tokumaru wrote:
unregistered wrote:
Mario 3 has the type of scrolling she is wanting.
You have to select the kind of compression that will work best with the kind of levels you want to make, also taking into consideration the decoding process (i.e. how you are going to read the map data, for drawing and for collision with the objects).
SMB3 has some limitations, like the height of the level, which is 2 NES screens tall (minus the status bar), and color glitches at the right of the screen because of the way it uses the name tables (horizontal mirroring). Status bars also affect the way you implement the scrolling if you plan on scrolling vertically (which I assume you do), so I need to know whether you want a status bar in your game and whether your levels can be taller than 2 screens before I suggest the optimal solution for your case.
Status bar would only be 8 pixels high and would have one or two text stats, maybe it could be clear and at the top of the screen, like in Mario (is that a status bar?). We would like our levels to be 4 screens tall. "It's our first game, we're just experimenting." Thank you for your help!
unregistered wrote:
Are the background tiles able to be collision detected?
Not by hardware... but then again, neither are the sprites. Unlike the Atari 2600 for example, the NES doesn't have any sort of hardware features for collision detection (the sprite 0 hit is not meant for testing object collisions, it's meant for detecting the position of the beam for raster effects). All collisions between objects, no matter if sprite or background, are done in software, through comparisons of their coordinates in the game world.
Quote:
Status bar would only be 8 pixels high
Remember that some of the image is cropped by the television, so you shouldn't have important information too close to the screen edges. So even if your information only needs 1 row of tiles to be displayed, it should be 1 or 2 rows away from the edge of the picture.
Quote:
and would have one or two text stats, maybe it could be clear and at the top of the screen, like in Mario (is that a status bar?).
Yes, it's a status bar. It's not clear though, it just appears to be that way because it uses the same color as the sky for its background. There never is any background going behind it, because the status bar itself is drawn with background tiles, and the NES has only 1 background layer.
If you have little information to display, you could use sprites to display the status, like in Mega Man or Sonic The Hedgehog (not NES, I know, but it's a good example). It's simpler that way, because you don't have to worry about splitting the background, something that can be particularly hard if you are scrolling vertically. The disadvantage of using sprites is that since you can only show 8 of them side by side, you can't have a lot of information laid out horizontally (hence why Mega Man's energy bar is vertical).
Quote:
We would like our levels to be 4 screens tall. "It's our first game, we're just experimenting." Thank you for your help!
Then SMB3's solution is not very good for you. You can use either vertical or horizontal mirroring (it depends on whether you prefer to have scrolling artifacts at the top and bottom or left and right of the screen - notice how SMB3 has some color issues at the right side because it uses horizontal mirroring) if you decide to display the status with sprites, and single-screen mirroring (one name table for the gameplay and the other for the status bar) if you stick to a background status bar.
tokumaru wrote:
Quote:
Status bar would only be 8 pixels high
Remember that some of the image is cropped by the television, so you shouldn't have important information too close to the screen edges. So even if your information only needs 1 row of tiles to be displayed, it should be 1 or 2 rows away from the edge of the picture.
Top: SMB1. Bottom: SMB1 with its status bar collapsed to 8 pixels tall (not unlike the GBC port) and 24 pixels from the top of the picture.
Quote:
If you have little information to display, you could use sprites to display the status, like in Mega Man or Sonic The Hedgehog (not NES, I know, but it's a good example).
Other examples include Super Mario Bros. 2, Contra, Metroid, Ikari Warriors, and this shop of SMB1 with a heavily Ikari-inspired status bar:
Quote:
Then SMB3's solution is not very good for you. You can use either vertical or horizontal mirroring (it depends on whether you prefer to have scrolling artifacts at the top and bottom or left and right of the screen - notice how SMB3 has some color issues at the right side because it uses horizontal mirroring) if you decide to display the status with sprites
I seem to remember that one of the Double Dragon games uses another solution: horizontal mirroring, and whenever the camera starts to move into the status bar, move the status bar to the other nametable. With a semi-unrolled loop, it should be possible to push up to 4 rows of status bar to VRAM plus the corresponding attributes during one NTSC vblank.
I won't go into Crystalis's solution because discrete mappers and current CPLD mappers lack the needed scanline counter, and dpcm_split is an even more advanced trick. Nor do I feel ready to go into the diagonal mirroring (CIRAM A10 = PA10 xor PA11) that MMC5 and T*SROM theoretically support.
tepples wrote:
I seem to remember that one of the Double Dragon games uses another solution: horizontal mirroring, and whenever the camera starts to move into the status bar, move the status bar to the other nametable. With a semi-unrolled loop, it should be possible to push up to 4 rows of status bar to VRAM plus the corresponding attributes during one NTSC vblank.
Yes, this is a valid solution that we don't talk about much (I didn't even know it was used in Double Dragon). The fact that you'd have to give up the regular PPU updates for a frame just to reposition the status bar makes this option less interesting IMO, but you could probably make a more dynamic system that progressively draws one status bar as the vertical scrolling changes (i.e. the closer the screen gets to one status bar the more of the other is drawn).
Thank you tokumaru and tepples!
The scrolling is going to be a fun challenge for me; but, i have not chosen an assembler yet. From other threads i have learned that
dasm is rough to learn and that noone has used, from whayt i've read,
Ophis. Why dont yall use ophis? It sounds really good... except that to reserve a variable you use
Code:
.space variable 1
. Thats a 5 character word and it must be used a lot!
Its two counters, one for the
Code:
.data
sections and another for the
Code:
.text
sections, sound really helpful and interesting;
but, no-one uses Ophis; why? Which assembler is best to use??? I want to use the one that allows us to do the most. ....................................
What assembler did Nintendo use? Did they send the development studios that assembler? ............................
What assembler to you use? What do you like about it and what do you hate about it?
Either go with NESASM for simplicity, but I'd go with something better like CA65 or whatever it's called. Seems alot nicer. I am going to dump NESASM for something that allows for + and - tags after I get done with this project.
unregistered wrote:
Like what is the largest meta-sprite we could use without improving cart hardware?
64x128. That eats up all 64 sprite slots with each hardware sprite being 8x16 pixels in size.
As for which assembler to use, I recommend ASM6 because it just works.
^ Thank you 3gengames for answering!
Ophis allows for + and - tags... edit: and thank you Dwedit for mentioning ASM6... tokumaru like that one the best in 2009... will check it out, thanks!
It seems to me that the nes development community would be much easier to enter if we all used the same assembler...
Yeah, but I like ease of use. NESASM is exactly that when you get it set up. It's just a standalone utility. But I am hurting for some other assemblers features. I will look into it late, but it also seems like ASM6 is popular and pretty good. I'm sure if you pick a big name other then NESASM3(.0.1), It will be better. But the way you set up the program files/roms might be different. I don't know if any other assemblers offer this, but NESASM also can include other files. I use this to keep all subroutines, engine(s), etc in seperate program files and combine them all when I need them in the main one with one line of code.
If you want more help feel free to mail me, I'd be interested in learning a new assembler, too.
3gengames wrote:
But the way you set up the program files/roms might be different. I don't know if any other assemblers offer this, but NESASM also can include other files.
Pretty much every assembler has the equivalent of C #include. But including one code file from another is considered bad practice in both C compilers and ca65. In these environments, the common practice is to #include a
header file with definitions of constants and .global statements, compile each
translation unit (that is, source code file) separately to an object code file, and then
link them all together at the end.
3gengames wrote:
I don't know if any other assemblers offer this, but NESASM also can include other files.
All of them do (I only remember DASM not having .incbin when I used it, but it had been added since). With NESASM though you can't include any file larger than 8kB. My sound data, for one thing tends to be bigger than that.
unregistered wrote:
What assembler did Nintendo use?
I don't know if anyone here has this information, but I assure you that whatever it was, it was worse than the options we have today. These kinds of applications do not age well. And even if you found a copy of it and really wanted to use it for some reason, you'd probably have to emulate it, since Nintendo didn't use PCs for development (I'm not even sure if there is an emulator for whatever computer they used).
unregistered wrote:
and thank you Dwedit for mentioning ASM6... tokumaru like that one the best in 2009...
I still do! =) I usually recommend it because it's easy as hell to start using (meaning it's newbie-friendly enough), just like NESASM, but doesn't have the annoying bugs, limitations, and inconsistencies of NESASM.
Check
this thread for some discussion about assemblers. It has a poll where people voted for their favorite assemblers.
! have chosen asm6
2 quick questons. 1) In the Processor Status register could we store a 4 bit binary number for a short time in bits 6 through 3?
Well, "Assembly In One Step" shows the bits of the Processor Status register... the 6th bit is set when an overflow of an addition or subtraction occurs,
the 5th bit is never set/reset,
the 4th bit is "...set if an interrupt caused by a BRK [and] reset if caused by an external interupt...",
and the 3rd bit doesn't matter cause it is only changed by two instructions and it's a flag for decimal mode which is missing on the nes, i think. So, is bit 4 the most changed-at random-times bit 'cause it is affected by interrupts? I dont understand "interrupts" much; what are they (and does bit 2 of the Processor Status register kindof disable the random-change-ness of bit 4?)? I'm floored and so excited that we can probably access the Processor Status register with PLP+PHA and PHP+PLA!!!!(just reread the kim1-6502 Appendixes and noticed that it says "Push
Processor Status on Stack" and "Pull Accumulator from Stack" and PLP and PHA
AND "Assembly In One Step" calls the fourth register "Processor Status")!!!!!21 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2) Um.. i dont remember my second question ...sorry
(i'll respond to yalls previous posts, maybe later... good night. : )
Bits 5 and 4 do not exist inside the CPU. Bit 4 exists only in the byte pushed out to the stack. Bit 3 (decimal mode) exists even though it doesn't affect the adder, and it can be used to store one bit, but it's not practical. Bit 6 (overflow) will be wiped out after the next ADC, SBC, or BIT instruction.
The most reliable source of randomness on the NES is the time since power on, mixed up with some sort of hashing function.
tepples wrote:
Bits 5 and 4 do not exist inside the CPU. Bit 4 exists only in the byte pushed out to the stack. Bit 3 (decimal mode) exists even though it doesn't affect the adder, and it can be used to store one bit, but it's not practical. Bit 6 (overflow) will be wiped out after the next ADC, SBC, or BIT instruction.
The most reliable source of randomness on the NES is the time since power on, mixed up with some sort of hashing function.
woah ok, tepples, thanks for that info!
And how do you access the time since power on? That's interesting.
tokumaru wrote:
unregistered wrote:
wrote:
What assembler did Nintendo use?
I don't know if anyone here has this information, but I assure you that whatever it was, it was worse than the options we have today. These kinds of applications do not age well. And even if you found a copy of it and really wanted to use it for some reason, you'd probably have to emulate it, since Nintendo didn't use PCs for development (I'm not even sure if there is an emulator for whatever computer they used).
I didn't ever think about that Nintendo might not use PCs for development. The only other choice i can guess is maybe they used macs?
tokumaru wrote:
Check
this thread for some discussion about assemblers. It has a poll where people voted for their favorite assemblers.
Thank you.
3gengames wrote:
I don't know if any other assemblers offer this, but NESASM also can include other files.
ASM6 has INCLUDE and INCBIN.
There is more explanation of the entire ASM6 in README.TXT... it's a really good explanation.
3gengames wrote:
If you want more help feel free to mail me, I'd be interested in learning a new assembler, too.
You are so kind - thank you, but the included README.TXT help is really excellent! And there's a whole strong ASM6 user group here.
And, asking basic questions in this forum can also help others. Do you agree?
unregistered wrote:
I dont understand "interrupts" much; what are they
They interrupt (hence their name) the program in order to run some other piece of code. Important events generate interrupts because such events need to be handled right away. For example, if NMIs are enabled, this interrupt will fire when VBlank starts. The start of VBlank is an important event, so it makes sense to interrupt whatever the PPU is doing so that the full duration of VBlank can be used for VRAM updates. Scanline interrupts are also important, because they fire at the exact moment that a certain scanline is being rendered, so if you want to do some kind of raster effect at that location you have to immediately respond to the event.
When an interrupt occurs, the program counter and the status flags are pushed to the stack, so that when the interrupt handler is over the CPU can resume whatever it was doing, without even realizing it was interrupted. If you need to modify any registers inside the interrupt handlers, you have to back them up to the stack and restore them after returning, or else the code that was interrupted will most likely crash.
unregistered wrote:
tepples wrote:
Bits 5 and 4 do not exist inside the CPU. Bit 4 exists only in the byte pushed out to the stack. Bit 3 (decimal mode) exists even though it doesn't affect the adder, and it can be used to store one bit, but it's not practical. Bit 6 (overflow) will be wiped out after the next ADC, SBC, or BIT instruction.
The most reliable source of randomness on the NES is the time since power on, mixed up with some sort of hashing function.
woah ok, tepples, thanks for that info!
Had you tried searching wiki.nesdev.com?
Quote:
And how do you access the time since power on?
Make a variable, clear it to 0 at reset, and add 1 to it in your NMI handler. Then 60 times a second (or perhaps 50 times a second depending on the particular console's TV system), the variable's value will increase by 1.
Quote:
tokumaru wrote:
Nintendo didn't use PCs for development (I'm not even sure if there is an emulator for whatever computer they used).
I didn't ever think about that Nintendo might not use PCs for development. The only other choice i can guess is maybe they used macs?
In the 1980s, there were more platforms than just PC and Mac. I seem to remember that
Nintendo used Apple IIGS computers for first-gen Super NES development because native 65816 assemblers were available and programmers could run unit tests on the same architecture. But after that generation, the next generation devkit (Mirage) appears to have run on a Mac, and
this other article appears to agree.
Thank you tokumaru!
tepples wrote:
unregistered wrote:
tepples wrote:
Bits 5 and 4 do not exist inside the CPU. Bit 4 exists only in the byte pushed out to the stack. Bit 3 (decimal mode) exists even though it doesn't affect the adder, and it can be used to store one bit, but it's not practical. Bit 6 (overflow) will be wiped out after the next ADC, SBC, or BIT instruction.
The most reliable source of randomness on the NES is the time since power on, mixed up with some sort of hashing function.
woah ok, tepples, thanks for that info!
Had you tried searching wiki.nesdev.com?
No, not really, since it can be changed by anyone... a friend had a bad experience with the wikipedia. But, that one says not everyone can change it?!
Thank you for mentioning it!
Now I'm learning from this page
http://wiki.nesdev.com/w/index.php/PPU_OAMAlso, maybe yall could add
this... it's my sketch of the memory and it should print out on one page (uncheck the "Enable Shrink-to-Fit" box, i think) Hope it helps someone, yall can read it, and maybe use it on the wiki. It's old... and i misspelled Pallets "Paletts" sorry.
tepples wrote:
Quote:
And how do you access the time since power on?
Make a variable, clear it to 0 at reset, and add 1 to it in your NMI handler. Then 60 times a second (or perhaps 50 times a second depending on the particular console's TV system), the variable's value will increase by 1.
Thanks
, it's always fun to find out how to do nes programming for me.
tepples wrote:
Quote:
tokumaru wrote:
Nintendo didn't use PCs for development (I'm not even sure if there is an emulator for whatever computer they used).
I didn't ever think about that Nintendo might not use PCs for development. The only other choice i can guess is maybe they used macs?
In the 1980s, there were more platforms than just PC and Mac. I seem to remember that
Nintendo used Apple IIGS computers for first-gen Super NES development because native 65816 assemblers were available and programmers could run unit tests on the same architecture. But after that generation, the next generation devkit (Mirage) appears to have run on a Mac, and
this other article appears to agree.
WOW cool!
unregistered wrote:
[usually a wiki] can be changed by anyone... a friend had a bad experience with the wikipedia. But, that one says not everyone can change it?!
Thank you for mentioning it!
The trusted group is given to anyone with a couple dozen on-topic posts on the BBS who has PM'd me requesting access. But even if it were as open as it used to be, I still check recent changes daily to revert vandalism.
Quote:
Great minds think alike; someone else had the same idea. See
CPU memory map and
PPU memory map.
tepples wrote:
unregistered wrote:
[usually a wiki] can be changed by anyone... a friend had a bad experience with the wikipedia. But, that one says not everyone can change it?!
Thank you for mentioning it!
The trusted group is given to anyone with a couple dozen on-topic posts on the BBS who has PM'd me requesting access. But even if it were as open as it used to be, I still check recent changes daily to revert vandalism.
Quote:
Great minds think alike; someone else had the same idea. See
CPU memory map and
PPU memory map.
Ah ok, thanks tepples
hey, unregistered (OP), i am from houston.
http://www.reddit.com/r/nesdevhouston/
check it out. it's empty right now. join up! i'm trying to find out who all is into nesdev in houston and the surrounding areas. maybe we can get a meetup going?
edit: i realize this is a sort of strange first post. i have been lurking for a little bit, though.
^ yesyesyall, sorry, I'm uncomfortable to meet with you.
---------------------------------------------------------------------------
And so my question (edit: wait for it:)) to all of the asm6 user people: I'm trying to quote
tokumaru wrote:
It may seem strange if you are doing it in a hardcoded way (manually setting up all the sprites of a particular object), but usually programmers implement a sprite system. For each possible frame of a character there is a list of the necessary sprites to draw it. It's just a series of .db or .dw statements that represent things like "this frame needs 4 sprites; the first one is at coordinates (4, 5) and uses tile $78; the second is at coordinates (12, and uses tile $79 (...)". The coordinates are usually relative to the position of the object/character being drawn. This means that the sprite system can draw any frame of any character if you point it to the correct list. from
this thread. btw, thread starts at 0. Not 2.
I am worrying about not being able to set a variable inside the series of .db statements.... i want something like
Code:
aY .byte 10
aX .byte 20
.db aY, $80, $00, aX, aY, $81, $00, aX +1
Do you all think that will work? im getting excited now... thinking it may be possible!
That almost works. What you are looking for is the "EQU" assembler directive. From the ASM6 documentation:
Code:
EQU
For literal string replacement, similar to #define in C.
one EQU 1
plus EQU +
DB one plus one ;DB 1 + 1
So your example above becomes:
Code:
aY EQU 10
aX EQU 20
.db aY, $80, $00, aX, aY, $81, $00, aX +1
Remember, the .byte and .db directives emit a byte of data into the output ROM file. The EQU directive (and it's cousin, the = operator) create symbols within the assembler that you can use latter on.
qbradq wrote:
That almost works. What you are looking for is the "EQU" assembler directive. From the ASM6 documentation:
Code:
EQU
For literal string replacement, similar to #define in C.
one EQU 1
plus EQU +
DB one plus one ;DB 1 + 1
So your example above becomes:
Code:
aY EQU 10
aX EQU 20
.db aY, $80, $00, aX, aY, $81, $00, aX +1
Remember, the .byte and .db directives emit a byte of data into the output ROM file. The EQU directive (and it's cousin, the = operator) create symbols within the assembler that you can use latter on.
Thank you qbradq!
What are symbols?
Does that mean that symbols are variables that dont exist in the ROM?
Symbols are just "nicknames" for numbers. These numbers can represent memory locations (which is the case of labels and variables) or numeric constants (values you commonly use throughout the program).
They exist only in the source code to make programming easier (just imagine if you had to remember the addresses of everything!), they do not exist in the assembled program.
Thank you tokumaru!
That, symbols, si awesome!
edit: I've started with asm6... and the NES 101 tutorial by... Michael Martin. There is a file i made for the ... vblank part; and am trying to transfer all the vblank code to it. Started with just the three lines:
Code:
jsr scroll_screen
jsr update_sprite
jsr react_to_input
rti
anddd, it worked!!!
The ... links seem to work through different files! Amazing! And... don't remember what i was thinking, sorry
...[/code]
OH! I don't know if the code for the sound file thing should be copied over to the vblank part cause even though there is a link from the react_to_input code to it, it is still not vblank ppu code... is it? No, i dont think it is, because that's part of the CPU... it is also a pAPU pseudo-Audio Processing Unit! So even though it seems to .... not "link"... link to the Audio code it's not part of vblank, right?
unregistered wrote:
[color=#808080]^ yesyesyall, sorry, I'm uncomfortable to meet with you.
cool! totally understand!
unregistered wrote:
tokumaru wrote:
It may seem strange if you are doing it in a hardcoded way (manually setting up all the sprites of a particular object), but usually programmers implement a sprite system. For each possible frame of a character there is a list of the necessary sprites to draw it. It's just a series of .db or .dw statements that represent things like "this frame needs 4 sprites; the first one is at coordinates (4, 5) and uses tile $78; the second is at coordinates (12, and uses tile $79 (...)". The coordinates are usually relative to the position of the object/character being drawn. How do you make the coordinates relative to the position of the object/character being drawn?
Code:
aY .byte 10
aX .byte 20
.db aY, $80, $00, aX, aY, $81, $00, aX +1
would this work?
Code:
aY .equ 10
aX .equ 20
.db aY,$80,$00, aX, aY, $81, $00, aX +1
this would work but I don't understand how the coordinates would stay relative. Aren't aY and aX constants here?
unregistered wrote:
tokumaru wrote:
This means that the sprite system can draw any frame of any character if you point it to the correct list.
Really happy to have found this, thank you.
The positions you specify in the meta-sprite are relative to the object's origin. You make it relative to the object's position by adding the object's position to those values. If you game scrolls you will also need to subtract the scroll amounts.
There is no automatic way to make the coordinates relative. This is not something the NES or the assembler will do for you, you'll have to code the logic for this yourself.
Every frame, instead of blindly copying the meta-sprite definitions directly to OAM, you'll do some processing with that data. Since you have the position of the object somewhere in RAM, you'll have to add the relative sprite coordinates to the object coordinates in order to find the final sprite coordinates, which you'll then write to OAM. Be careful with sprites going off screen, as you probably don't want pats of your characters wrapping to the opposite side of the screen.
Thank you qbradq and tokumaru, that makes sense; I'm stumbling on one part... how do you, or what is the best way you can, write to OAM? Right now i have one meta-sprite to display and move around the screen. Am i susposed to use lda and sta to write all the sprites to $0200?
Each frame during v blank it sends all of $0200 - $02ff to OAM through
the OAM_DMA register. That's all I can think of... lda and sta to page 02.... is there a better method?
Nope. That's how most games (homebrew and commercial alike) do it.
Also, you do not usually allocate slots within the OAM page to specific objects. Rather you loop over all of your objects each frame and lay down thier OAM data in the OAM page. That way when you need to start cycling your sprites (and you will, trust me), you can just change your starting location in the OAM page each frame.
But we'll cover sprite cycling when you get there
unregistered wrote:
unregistered wrote:
tokumaru wrote:
This means that the sprite system can draw any frame of any character if you point it to the correct list.
How do you point it to the correct list?
So far I have thisCode:
;80 81
;90 91
;a0 a1
;b0 b1
.db aY, $80, $00, aX, aY, $81, $00, aX+8,
aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
There's something i'm missing.
It'd go a little something like this (untested):
Code:
hero_frame1:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
.db aY, $82, $00, aX, aY, $83, $00, aX+8
.db aY+8, $92, $00, aX, aY+8, $93, $00, aX+8
.db aY+16, $a2, $00, aX, aY+16, $a3, $00, aX+8
.db aY+24, $b2, $00, aX, aY+24, $b3, $00, aX+8
hero_frame3:
.db aY, $84, $00, aX, aY, $85, $00, aX+8
.db aY+8, $94, $00, aX, aY+8, $95, $00, aX+8
.db aY+16, $a4, $00, aX, aY+16, $a5, $00, aX+8
.db aY+24, $b4, $00, aX, aY+24, $b5, $00, aX+8
hero_frame4:
.db aY, $86, $00, aX, aY, $87, $00, aX+8
.db aY+8, $96, $00, aX, aY+8, $97, $00, aX+8
.db aY+16, $a6, $00, aX, aY+16, $a7, $00, aX+8
.db aY+24, $b6, $00, aX, aY+24, $b7, $00, aX+8
; Operator < produces the low byte of an address
sprite_layouts_lo:
.db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4
; Operator > produces the high byte of an address
sprite_layouts_hi:
.db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4
; Number of hardware sprites in each layout
sprite_layouts_count:
.db 8, 8, 8, 8
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo,x
sta t0
lda sprite_layouts_hi,x
sta t0+1
lda sprite_layouts_count,x
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0),y
iny
; etc.
dec t2
bne @loop
stx oamIndex
rts
some_other_method:
ldx #2
jsr draw_sprite
EDIT: Fixed a typo.
Yea, what Tepples said
I have added a
wiki document about using pointer tables. I wrote it prior to seeing Tepples's reply so it uses different code, but there is more explanation.
Dang Tepples, you code quick
tepple's example is right. First you have to define the sprite data for all possible animation frames. Then you make a list of pointers to each of the frames, in order to give each of them an index. Finally, you make a function/subroutine that will receive a meta-sprite index and an object index and will render the requested frame for the requested object.
You can use registers X and Y to pass the indexes as parameters to the sprite drawing function. This function should add the meta-sprite coordinates to the object's coordinates (this is why the function needs to receive an object index) in order to find the final sprite coordinates, and, if those coordinates are within screen limits, write them to the OAM buffer (usually $0200-$02FF).
tepples wrote:
It'd go a little something like this (untested):
Code:
hero_frame1:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
.db aY, $82, $00, aX, aY, $83, $00, aX+8
.db aY+8, $92, $00, aX, aY+8, $93, $00, aX+8
.db aY+16, $a2, $00, aX, aY+16, $a3, $00, aX+8
.db aY+24, $b2, $00, aX, aY+24, $b3, $00, aX+8
hero_frame3:
.db aY, $84, $00, aX, aY, $85, $00, aX+8
.db aY+8, $94, $00, aX, aY+8, $95, $00, aX+8
.db aY+16, $a4, $00, aX, aY+16, $a5, $00, aX+8
.db aY+24, $b4, $00, aX, aY+24, $b5, $00, aX+8
hero_frame4:
.db aY, $86, $00, aX, aY, $87, $00, aX+8
.db aY+8, $96, $00, aX, aY+8, $97, $00, aX+8
.db aY+16, $a6, $00, aX, aY+16, $a7, $00, aX+8
.db aY+24, $b6, $00, aX, aY+24, $b7, $00, aX+8
; Operator < produces the low byte of an address
sprite_layouts_lo:
.db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4
; Operator > produces the high byte of an address
sprite_layouts_hi:
.db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4
; Number of hardware sprites in each layout
sprite_layouts_count:
.db 8, 8, 8, 8
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo,x
sta t0
lda sprite_layouts_hi,x
sta t0+1
lda sprite_layouts_count,x
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0),y
iny
; etc.
dec t2
bne loop
stx oamIndex
rts
some_other_method:
ldx #2
jsr draw_sprite
Thank you tepples!
And thank you qbradq for your work... haven't really thought about it yet, sorry, tired. And thank you tokumaru! This is the second time, i think, you have explained step by step the first guy's help and that helps me so much too!
Tomorrow is going to be so much fun!
Good night.
tepples, this code rocks the house!! Thank you so much!
I get the error "daprg.asm(250): Branch out of range."
frome here:
Code:
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x
sta t0
lda sprite_layouts_hi, x
sta t0+1
lda sprite_layouts_count, x
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0), y
iny
;ect.
dec t2
bne loop ;########(LINE NUMBER 250)
stx oamIndex
rts
; Set initial value of dx
lda #$01
sta dx
rts
; Load palette into $3F00
load_palette:
So why is it out of range? The only help i could find was
in
this thread
koitsu wrote:
Branch instructions range from +127 to -128. It's just a signed 8-bit number.
I don't understand though.
The problem is my fault. I partially rewrote the code to reflect an apparent coding style of not using the .proc statement of ca65. So I changed loop: to @loop: but not bne loop to bne @loop. I have edited the sample code.
Ok, I didn't know if the local link needed an @ in
Code:
bne @loop
because with the @ asm6 builds the rom with out error; but, now, when running it, Nintendulator stops and says "Bad opcode, CPU locked" and VirtuaNES stops and says "Executed an undefined order." I thought this was brought on by adding the @ to loop up there... the asm6 README.TXT doesn't have an example of that code. So, thank you for helping me learn that it's proper to use an @ in that code.
It's ok tepples
I'm still lost. It confuses me that asm6 would assemble a "bad opcode"?
Most of the time, bad opcode means either A. you forgot the RTS at the end of a subroutine, B. you messed up a bankswitch, or C. you did RTS on a messed-up stack.
tepples wrote:
Most of the time, bad opcode means either A. you forgot the RTS at the end of a subroutine, B. you messed up a bankswitch, or C. you did RTS on a messed-up stack.
tepples, thanks!
I guess it was kind of A. I put your code in the middle of a subroutine. So it had the first part of the first subroutine missing an RTS at the end.... like
Code:
[subroutineA...[subroutineB...rts]...rts]
[/quote]
Code:
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x
sta t0
lda sprite_layouts_hi, x
sta t0+1
lda sprite_layouts_count, x
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0), y
iny
;ect. start
sta sprite+4;+oamIndex
inx
inc oamIndex
;end ect.
dec t2
bne @loop
stx oamIndex
rts
This is what I have so far.
My brain doesn't work right now, but I want to make progress.
My code always draws the sprite with just one tile; how do i increase the size? I used Code:
sta sprite+oamIndex
but that didnt help. And also, is that code possible... like for me, it is hard to understand why "sta sprite+aVariable" would work...? Found my assembly textbook from college and read the entire first chapter and going to finish the second chapter by today. It is really fun reading it!
Nope, that code isn't possible. You have to store a sprite next to/below in the next sprite address.
Thank you 3gengames!
I think I've found the problem... put tepples' code inside the code that runs before the
Code:
cli
.
Inside the NES 101 Tutorial the only code that runs after
cli is the endless loop transfering control to the vblank interrupt, i think. Where do you think tepples' code should go? I'm asking beccause my skill with rearanging others' code is very poor; guess i could keep try ing though.
The code tepples posted is a subroutine. You can place it wherever you want (of course not in the middle of some other code). Was this what you meant, or do you mean where to place the actual calls (jsr) to the routine?
Jaffe wrote:
The code tepples posted is a subroutine.
Yes, thank you; I agree.
Jaffe wrote:
You can place it wherever you want (of course not in the middle of some other code). Was this what you meant, or do you mean where to place the actual calls (jsr) to the routine?
Well, i think i ment both... My first problem was that I placed it in the middle of some other code. I've moved the
Code:
; Background data
bg:
.incbin "charSelect1.nam"
; Attribute table
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F
;sprite data
hero_frame1:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame3:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame4:
.db aY, $80, $00, aX, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
;Operator < produces the low byte of an address
sprite_layouts_lo:
.db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4
;Operator > produces the high byte of an address
sprite_layouts_hi:
.db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4
;Number of hardware sprites in each layout
sprite_layouts_count:
.db 8, 8, 8, 8
that part underneath the background and attribute data code at the end... that was obvious to me; after i did it.
but, it's ok to make a mistake
and then learn from that. : )
Jaffe wrote:
...or do you mean where to place the actual calls (jsr) to the routine?
After many ideas and hours of reading I have decided to place the
jsr in my vblank code because vblank is the period where you write to the sprite memory,
right?Thank you Jaffe for helping me!
This code has not worked for me, yet... now, it draws the first sprite in the correct spot on the screen; but only the first sprite. Back to my assembly book.
3
edit: forgot to post my codeCode:
draw_sprite:
...
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0), y
iny
;ect. start
sta sprite, x
inx
;end ect. THIS IS THE END OF MY CODE.
dec t2
bne @loop
stx oamIndex
rts
I have a bit of a hard time getting an overview of your code. Could you post it in its entirety?
The subroutine should not be called during vblank time, at least not before all PPU updates are done. You'll want the subroutine to store sprite data in a page of RAM, and let the contents of this page be copied into OAM by a DMA transfer during vblank (from reading this thread it seems you did this before?) That way you can use all of the non-vblank time to do complex calculations etc. to compute what objects are to be shown in the next frame, where they should be placed, etc. Then when vblank comes, you'll have the sprite data ready to be copied into OAM.
I assume your game has some kind of a main loop that just runs over and over? This is usually where you'd like to place your call to the routine, but that requires that you have some way of controlling how often the loop runs. You could also place it at the end of your vblank code, after you're sure that all code accessing the PPU has run. If you just want to show one object now as a test, you can just as well call it once before your code enters the main infinite loop.
Jaffe wrote:
I have a bit of a hard time getting an overview of your code. Could you post it in its entirety?
It's not really my code yet; it is really the code of Michael Martin in the NES 101 Tutorial and the code of tepples in his post on page 4, i think, of this thread. Code:
; Transfer control to the VBLANK routines.
loop: jmp loop
;80 81
;90 91
;a0 a1
;b0 b1
; .db aY, $80, $00, aX, aY, $81, $00, aX+8,
; aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
; aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
; aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x
sta t0
lda sprite_layouts_hi, x
sta t0+1
lda sprite_layouts_count, x
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0), y
iny
;ect. start
sta sprite, x
inx
;end ect.
dec t2
bne @loop
stx oamIndex
rts
Yes, I remember doing that from before in this thread. Right now, my file for vblank looks like:
Code:
jsr scroll_screen
jsr update_sprite
jsr react_to_input
rti
scroll_screen:
ldx #$00 ; Reset VRAM
stx $2006
stx $2006
ldx scroll ; Do we need to scroll at all?
beq no_scroll
dex
stx scroll
lda #$00
sta $2005 ; Write 0 for Horiz. Scroll value
stx $2005 ; Write the value of 'scroll' for Vert. Scroll value
no_scroll:
rts
update_sprite:
ldx #3
jsr draw_sprite
lda #>sprite
sta $4014 ;OAM_DMA register ; Jam page $200-$2FF into SPR-RAM
;takes 513 cycles.
react_to_input:
lda #$01 ; strobe joypad
sta $4016
lda #$00
sta $4016
lda $4016 ; Is the A button down?
and #1
beq @b
jsr low_c
@b: lda $4016 ;Is the B button down?
and #1
beq not_dn
jsr high_c
not_dn: rts ; Ignore left and right, we don't use 'em
low_c:
pha
lda #$84
sta $4000
lda #$AA
sta $4002
lda #$09
sta $4003
pla
rts
high_c:
pha
lda #$86
sta $4000 ;audio
lda #$69
sta $4002
lda #$08
sta $4003
pla
rts
Jaffe wrote:
That way you can use all of the non-vblank time to do complex calculations etc. to compute what objects are to be shown in the next frame, where they should be placed, etc. Then when vblank comes, you'll have the sprite data ready to be copied into OAM.
So the first thing in vblank should be the copying of page 2 to OAM? And then is it right to do all the updating of page 2 after vblank ends? At vblank we are suspossed to make our changes known to OAM first and writes to page 2 are for between Vblank times. That's what im learning right now, thank you.
tokumaru wrote:
The start of VBlank is an important event, so it makes sense to interrupt whatever the PPU is doing so that the full duration of VBlank can be used for VRAM updates.
What is VRAM?
Jaffe wrote:
If you just want to show one object now as a test, you can just as well call it once before your code enters the main infinite loop.
Ok, thanks!
I changed it, but have the poblem again... it's only drawing the first of the 8 sprite object/meta-sprite. It's in the right place though and it's the right sprite.
Yes, you can update the CPU's copy of OAM outside of vblank. In fact, you're supposed to.
VRAM is any RAM mapped into PPU $0000-$2FFF. It includes CIRAM, the memory inside the NES used for nametables, and CHR RAM, the memory inside the cartridge used for pattern tables. Some cartridges have CHR ROM instead of CHR RAM; for these, the only VRAM is CIRAM.
Are you using the exact code you pasted here? You know it's incomplete, right? The sprite drawing routine is not copying all the bytes it's supposed to from the meta-sprite definitions, and it's not adding the coordinates to the object's coordinates either. That routine was provided as an example, not as something you can copy & paste.
I advise you start writing your own code, or at least read all the code you use very carefully so that you know exactly what it's doing. Making Frankenstein programs (copying blocks of code from different sources) is a terrible way to learn, because you have no idea of what is actually going on in the program and are only hoping that it will somehow work.
tepples wrote:
Yes, you can update the CPU's copy of OAM outside of vblank. In fact, you're supposed to.
Thank you.
Would testing the input be ok for during vblank? No, testing the input hasn't anything to do with VRAM... right?
tepples wrote:
VRAM is any RAM mapped into PPU $0000-$2FFF. It includes CIRAM, the memory inside the NES used for nametables, and CHR RAM, the memory inside the cartridge used for pattern tables. Some cartridges have CHR ROM instead of CHR RAM; for these, the only VRAM is CIRAM.
Thanks!
tokumaru wrote:
Are you using the exact code you pasted here? You know it's incomplete, right? The sprite drawing routine is not copying all the bytes it's supposed to from the meta-sprite definitions, and it's not adding the coordinates to the object's coordinates either. That routine was provided as an example, not as something you can copy & paste.
I advise you start writing your own code, or at least read all the code you use very carefully so that you know exactly what it's doing. Making Frankenstein programs (copying blocks of code from different sources) is a terrible way to learn, because you have no idea of what is actually going on in the program and are only hoping that it will somehow work.
Yes ok, you are right on all points. Thank you for your help.
God really helped me with it and so it kind of works now!
The code... it's able to print out the entire meta-sprite now using tepples' code and my code! Now i'm trying to figure out how to move it with the control pad... and I think maybe calling
Code:
ldx #3
jsr draw_sprite
after each input-check would work... but how should I try this?
Here's code that appplies to this question.
Code:
lda #%00011110
sta $2001
;matthew's init
cli
;----------------------------END OF RESET----------------------------------
; Transfer control to the VBLANK routines.
ldx #3
jsr draw_sprite
loop: jmp loop
;80 81
;90 91
;a0 a1
;b0 b1
; .db aY, $80, $00, aX, aY, $81, $00, aX+8,
; aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
; aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
; aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
draw_sprite:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
Would
jsr draw_sprite be ok to include inside of the
loop: jmp loop?
Is that the only place I can put it?
(Because the rest of the file is subroutines and then data.) I'm missing knowlege about a way to organize my code with files.
I remember reading some post by tokumaru that's about organizing code with different files... will search for that tomorrow. Good night.
unregistered wrote:
Would jsr draw_sprite be ok to include inside of the loop: jmp loop?
It depends. There are 3 common ways to organize your frame logic in NES programs in order to make sure it will run 60 times per second (as it should):
1. Put the game logic after VRAM-related operations in your NMI. The NMI routine is automatically called at the start of VBlank, and once the VRAM updates are finished, you are free to do other kinds of operations. Since the NES runs at 60fps, there are 60 VBlanks in a second, so your game logic will also run at 60 Hz. Many commercial games do this, including SMB and Final Fantasy. There are some problems with this method though, which make lag frames (these happen when the frame logic takes longer than a frame to complete) difficult to manage. SMB suffers from this when there are too many enemies on screen (apparently the status bar "jumps" and the music slows down). Most tutorials appear to use this.
Code:
Loop:
jmp Loop
NMI:
;DO PPU UPDATES HERE;
;RUN THE GAME LOGIC HERE;
rti
2. Use the NMI just for signaling the start of VBlank, and both the frame logic and the VRAM-updating in the main thread (the "loop: jmp loop" part). This is very similar to the above, only the order of the updates is different. Here we update the frame logic first and then we wait for VBlank (by checking a flag which is modified by the NMI when VBlank starts) before performing all VRAM updates. This technique suffers from the same lag frame problems. AFAIK, no commercial games did it like this.
Code:
Loop:
;RUN THE GAME LOGIC HERE;
;WAIT FOR THE NMI;
;DO PPU UPDATES HERE;
jmp Loop
NMI:
;INDICATE THE START OF VBLANK;
rti
3. Separate your main thread from your NMI thread and run them in parallel. This is my favorite, because it allows for safe handling of lag frames. When the NMI fires, it can detect if the frame logic is complete or not, so it's able to detect lag frames and make sura that status bars, the music and other things still work even though the frame logic isn't finished. AFAIK, most commercial games use this.
Code:
Loop:
;RUN THE GAME LOGIC HERE;
;WAIT FOR THE PPU UPDATES TO COMPLETE;
jmp Loop
NMI:
;DO PPU UPDATES HERE;
rti
So, to answer your question more directly: If you just place the sprite-drawing call in the main loop without any other modifications, that routine will be called 100's of times in a frame, because there isn't any sort of timing control in there. In your case, the simplest fix would probably be to place that call right after the controller is checked and the player's coordinates are changed. Since it appears you are building this from a tutorial, it would be better to stick to the first solution I explained in this post. Just keep in mind that there are other options, and that you might want to take a look at them once your games become more complex and need more "robust" timing.
Quote:
I remember reading some post by tokumaru that's about organizing code with different files...
How you organize your files doesn't make any difference in the program... Separating files and using includes is something that's supposed to help you keep track of what's where and make your code more easily maintainable, but in the end the assemble still sees all the code as if it were in a single huge file. What matters for NES programs is the order in which things are processed, which doesn't necessarily match the order in which they are written.
tokumaru wrote:
2. Use the NMI just for signaling the start of VBlank, and both the frame logic and the VRAM-updating in the main thread (the "loop: jmp loop" part). [...] AFAIK, no commercial games did it like this.
Don't Squaresoft games such as Final Fantasy do it this way?
I'll have to see if Nintendulator and NESICIDE can measure /NMI-to-RTI time.
tepples wrote:
Don't Squaresoft games such as Final Fantasy do it this way?
I though Final Fantasy had everything in the NMI, but I'm not 100% sure about this.
tokumaru, thank you so much!
Everything you said makes sense, I think... It is very thorough.
I'm about to create a
Code:
NMI: jsr vblank
rti
and see what happens. I'm going to try your first solution.
Quick question: Is NMI susposed to replace vblank? I'm not sure what to do.... ...I cant call jsr vblank because that ends with an rti not a rts.
I think that the "vblank" label you talk about is the one I called "NMI" in my examples. It's just a name, you can call it whatever you want, as long as that's the label that gets called when an NMI happens (i.e. its address is in the NMI vector at the end of the ROM).
So you don't have to replace anything, just read my example as if I had writen "vblank" instead of "NMI".
tokumaru wrote:
...So you don't have to replace anything, just read my example as if I had writen "vblank" instead of "NMI".
*Big sigh of relief*
Thank you.
So my sister has asked me to work on music now. Well I have tried to pick a program to learn; found this site in the forum here...
http://www.nes-audio.com and i've downloaded PR8, but it doesnt work in Nintendulator, and Pulsar, it doesnt work either, and last Nijuu which does work, I think so. After much reading of its manual I learned that help hasnt been written yet for trying to include the song you create in an actual game. So Nijuu doesn't work either for me, i think.
Is there another program that helps you to make audio that you can include in a NES game? I would be content to read it's manual.
You should also check out the FamiTone library which can play a very large subset of the music and sound effects that Famitracker can create. Search the forum for it. I can't give you the link because I am on my phone.
3gengames wrote:
Famitracker.
Thank you!!
The one I downloaded doesnt have a help... it has all of the topics... but therre's no pages.
qbradq wrote:
You should also check out the FamiTone library which can play a very large subset of the music and sound effects that Famitracker can create. Search the forum for it.
Found it. Thanks!
Yeah, I had to look up a video how to use it online, but once you get going it's easy.
Have fun!
3gengames wrote:
Yeah, I had to look up a video how to use it online, but once you get going it's easy.
Have fun!
Thank you so much for the videos idea; found some great ones on youtube!
Thanks!
Ok, we have one complete song so far. Just finished reading famitone's readme.txt... going to read it again.
Right now, my goal is to get our song to play in Nintendulator. There is much to learn how to do and so do yall have any tips or recomendations to help with getting my song to play? What do I need to add codewise? Sure that I'll get that from the readme reread. Would including a sound file with the song and its codewise info be a good idea? That would help me to keep the game-code separate from the sound-code.
I think FamiTone comes with instructions for how to get your song data from FamiTracker into a source for it uses, and then how to link those songs up with the library.
You'll need to watch out for FamiTone's RAM usage though. This is part of the readme (I think), but FamiTone's RAM can be remapped by editing the code.
You will basically need to tell FamiTone to start it's RAM area at $0200, then start your own RAM variables above $0376 or something like that. The actual number again should be documented in the readme.
You might also want to ask FamiTone-specific questions in the
FamiTone Release Thread. There is a lot of good information there.
Good luck man! I am really impressed at how you are sticking to your project!
qbradq wrote:
I think FamiTone comes with instructions for how to get your song data from FamiTracker into a source for it uses, and then how to link those songs up with the library.
You'll need to watch out for FamiTone's RAM usage though. This is part of the readme (I think), but FamiTone's RAM can be remapped by editing the code.
You will basically need to tell FamiTone to start it's RAM area at $0200, then start your own RAM variables above $0376 or something like that. The actual number again should be documented in the readme.
Well, I cant tell FamiTone to start its RAM area at $0200 cause that is my sprite page. But, FamiTone's RAM does start on page $03. That's ok right? So I should start my RAM variables above $0476?
qbradq wrote:
You might also want to ask FamiTone-specific questions in the
FamiTone Release Thread. There is a lot of good information there.
Thanks! : )
qbradq wrote:
Good luck man! I am really impressed at how you are sticking to your project!
Thank you so much!
Honestly, God has really been helping me stay focused on making progress. I keep asking him to do this; need all the help I can get. (I'm slowly getting better at making progress, I think.
)
The $0476 thing is way off base. I can't download the package right now to look at it, but according to the last few posts in the release thread the included formats.txt file contains that information. The folks in the thread seem to think it needs 176 bytes of RAM.
Just to be safe I'd give FamiTone the entire third page, and start your RAM addresses at $0400. In most assemblers this can be accomplished by issuing an "ORG $0400" directive prior to your variables.
If you get to really hurting for RAM you can back that ORG address down. But this way if FamiTone is updated and you swap to a new version that uses more RAM you will still be safe.
You might also want to consider releasing your current ROM in the
Homebrew Forum for others to comment on. I know that always gives me a lot of insight and motivation. Plus I'd love to see what you're working on
unregistered wrote:
tokumaru wrote:
...So you don't have to replace anything, just read my example as if I had writen "vblank" instead of "NMI".
*Big sigh of relief*
Thank you.
waaaaaaait. Um, isn't "
NMI" more correct than "
vblank"? A
vblank is an
NMI... but aren't there other
NMI?? So, if that is a yes, then it would make more sense for it to be called "
NMI"... i guess, right? I'm almost to the point, because it is used in FamiTone, where I'm going to change it to
NMI from
vblank. Please help me.
edit: --------------------
a reply to you, qbradq, isnt wirtten
yet. Sorry.
NMI is an interrupt signal that begins to be asserted at the start of vertical blanking. NMI ends when vblank ends, but NMI also ends on $2002 read. In fact, turning $2000 bit 7 off and on during a single vblank (without reading $2002) causes multiple NMIs. An NMI handler can end before or after NMI ends. The behavior of $2003-$2007 depends on vblank state, disirregardless of NMI state.
It doesn't matter what you call it, as long as you do it consistently. From a technical perspective "NMI" is not the same as "VBlank". VBlank is a period when we are allowed to make changes to VRAM, and the NMI is an interrupt that fires when this period starts, so that we can be sure to make use of it.
VBlanks are the only source of NMIs on the NES, the IRQs are the ones that can be generated by different sources, such as the APU or mappers.
tepples wrote:
The behavior of $2003-$2007 depends on vblank state, disirregardless of NMI state.
Going to stick with vblank! Thanks tepples!
tokumaru wrote:
It doesn't matter what you call it, as long as you do it consistently. From a technical perspective "NMI" is not the same as "VBlank". VBlank is a period when we are allowed to make changes to VRAM, and the NMI is an interrupt that fires when this period starts, so that we can be sure to make use of it.
VBlanks are the only source of NMIs on the NES, the IRQs are the ones that can be generated by different sources, such as the APU or mappers.
Great dialogue!
Thanks so much tokumaru and tepples!
I'm not going to change it to NMI; I promise. : )
I'm trying to build my .nes file, but I recieve errors that say
Unknown label. and
Can't determine address. All of the errors are listed being in my FamiTone music file that was created for me by FamiTone with the -asm6 flag. All of the errors are found on lines that just have a @label (temporary label). There is only one regular label at the top and i tried to add regular label at the bottom, but it listed thta line as an error too. I'm so lost as to what to do now
... could you help me?
Time to move to NESASM3?
3gengames wrote:
Time to move to NESASM3?
hehe asm6 continues to rock.
So, God helped me make progress again!!!
But, my question should go to Shiru...
Have you seen
QASM? It's totally super awesome. It'll even clean your toilets!
Actually it is still likely to have bugs
I wouldn't recommend it for a newer developer due to that. You might hit a bug in the assembler and think you're doing something wrong.
qbradq wrote:
Have you seen
QASM? It's totally super awesome. It'll even clean your toilets!
Actually it is still likely to have bugs
I wouldn't recommend it for a newer developer due to that. You might hit a bug in the assembler and think you're doing something wrong.
Haha
, that's pretty good
(edit: i'm sorry for my failure at being honest here) : ) Thanks for sharing........ and a reply to you is still on its way.
edit: Spent some time learning about qasm from
qasm_dev.zip. It's exciting and fun!
I'm looking forward to it.
In asm6 when trying to understand
.enum and
.ende they use $200. What do the 3 digits mean?
README.TXT wrote:
Reassign PC and suppress assembly output. Useful for defining
variables in RAM.
ENUM $200
foo: db 0
foo2: db 0
ENDE
Does $200 = $0200 or $2000 or what else? Is this three digit implementation only in asm6? Is it only used with .enum? Could you use 4 digits? What important thing did I miss reading?
unregistered wrote:
Does $200 = $0200 or $2000 or what else?
$0200
Quote:
Is this three digit implementation only in asm6?
No.
Quote:
Is it only used with .enum?
No.
Quote:
Could you use 4 digits?
Yes.
Quote:
What important thing did I miss reading?
None, he was just skipping the leading zero. Sometimes people use only 3 digits when referencing RAM on the NES because the NES has only $800 bytes of it.
You completely misunderstood what enum does.
It temporarily changes the Current Address ($), and temporarily stops any bytes from being emitted in the output, but addresses continue to increase as bytes or instructions are defined.
So you do ENUM $200. You set the current address to $200, so your next label starts there. You have a .db 0, so the address goes up by 1, and the next label uses that address.
Later you do .ende, so it gets out of ENUM mode, and goes back to generating code as usual, at the previous address that was used before enum mode was turned on.
You use enum mode to define a bunch of labels in a block using space-filling syntax, instead of adding 1 to previously defined addresses in chains.
With enum mode:
.enum $200
label1: .db 0
label2: .db 0
label3: .dw 0
label4: .dsb 32
label5: .db 0
.ende
Without enum mode:
label1 = $200
label2 = label1 + 1
label3 = label2 + 1
label4 = label3 + 2
label5 = label4 + 32
BTW, .enum is great for declaring variables. You can have .enum blocks wherever you want, as they don't mess up the program counter. I use it, for example, to declare "local" variables, variables that are used only during parts of the program, so there's no need to waste RAM locations for them permanently.
After the global variables of each page, I put a marker label:
Code:
.enum $0300 ;variables in page 3
SomeArray .dsb 64
MyVariable .dsb 1
AnotherVariable .dsb 1
Pointer .dsb 2
LocalVariables3 ;this marks the starting point for local variables
.ende
And then, at the top of each module's file I do something like this:
Code:
.enum LocalVariables3 ;local variables in page 3
LocalVariable .dsb 1
AnotherOne .dsb 2
SmallArray .dsb 12
.ende
And I can continue to fill page 3 from where I left off. This is the safest and most organized way I found to reuse RAM in different parts of the program. You can safely rearrange/add/remove global variables and as long as the total doesn't extrapolate the memory you have free it will still work fine.
qasm_dev.zip is actually older than the current release right now
Grab qasm_v0_05.zip for the latest. If you want to learn about how an assembler is coded in the classic style have a look at qasm_v0_01.zip. That is a traditional two-pass assembler without expressions and macros.
tokumaru, thank you for answering all of my questions!
Makes sense.
Dwedit, wow, thank you! Now I have an extensive understanding of enum mode; and even know what it would be like to not have enum mode!
Which is really helpfull, to me!
tokumaru wrote:
I use it, for example, to declare "local" variables, variables that are used only during parts of the program, so there's no need to waste RAM locations for them permanently.
!!So that means we could declare a variable... and then, later on, since the previous variable was local... and we are in a different area of code, we could declare another variable in the same spot!? That would be incredible... like REALLY!
Just like local variables in C++? If I understood you correctly here, could you answer this next question? If all of the files combine in to one single .asm file then how does this work when there is just one file? The whole thing doesn't make sense to me... there's something I'm missing.
qbradq wrote:
If you want to learn about how an assembler is coded in the classic style have a look at qasm_v0_01.zip.
Maybe this could help me understand what im missing above. Cause the local variables in an asm program is what im missing AND your assembler probagbly deals with local variables. Thanks qbradq. : )
Local variables in C and C++ are allocated on the stack (a sliding window of RAM used just for locals and function parameters). On a 6502 you don't have such a thing.
In 6502 ASM a "local" variable is one that is only used by one particular subroutine. Assemblers that support scoping can make this easier by making access to labels from the outside harder. This
does not support recursion, so if your subroutine calls itself it will stomp on the local variables, unlike C and C++.
What Tokumaru was describing was something different. I will try to explain in more round terms:
Hypothetical Man wrote:
I have a routine that handles the title screen. It needs 500 bytes of RAM, which I allocate starting at $0300.
I have another routine that implements my game. It needs 1500 bytes of RAM. Because I know that the game and the title screen will never be running at the same time, I also start allocating RAM at $0300.
So now I have two routines that use the same memory locations for variables, but because they never run at the same time (and do not care what is in RAM when they start) they do not step on each other's toes.
I think this approach is fine when you are dealing with distinct modes of operation, like our title screen / game example. If your game is a series of mini games (like Pirates!), each mini-game might reuse the same area of RAM for variables, and then you might have a small protected segment to keep game state variables.
Personally I do not like doing this within a game mode. It's true, my sprite generator never runs at the same time as my object updates, but this type of data interleaving over-complicates your code. Only if I were
very desperate for RAM would this be an option for me.
unregistered wrote:
So that means we could declare a variable... and then, later on, since the previous variable was local... and we are in a different area of code, we could declare another variable in the same spot!?
That's the idea. Since not all variables must exist at all times, many variables can share memory locations, and the code I wrote before is the most organized way I can think of doing that.
Quote:
Just like local variables in C++?
Kinda... In the sample code I wrote they aren't exactly local, because technically you could still use them anywhere in the program (i.e. the labels are global). Of course you wouldn't want to use one module's variables in another module, because that would crash the program. You could probably use ASM6's local labels (they start with a period, I think) though, and they would be more like local variables. Something like this:
Code:
SomeFunction:
.enum LocalVariables3
.LocalVariable1 .dsb 1
.LocalVariable2 .dsb 1
.LocalPointer .dsb 2
.ende
;the variables declared above can only be seen here
;return
rts
Quote:
If all of the files combine in to one single .asm file then how does this work when there is just one file?
There's really no difference between 1 file vs. multiple files... When you include a file, it's the same as if you were copying and pasting its contents directly into the first file. The only reason to separate the source into multiple files and use includes is organization. Scrolling through a 20,000 line program looking for a piece of code is hell.
qbradq wrote:
Personally I do not like doing this within a game mode. It's true, my sprite generator never runs at the same time as my object updates, but this type of data interleaving over-complicates your code. Only if I were very desperate for RAM would this be an option for me.
I wouldn't be able to make my game without this. The main engine uses every last byte of RAM, so I can't afford to have memory allocated for a bunch of stuff that just isn't running during gameplay (title screen, menus, bonus stages, etc). The main systems, like the ones dealing with music, sprites, etc. use global variables, because those are used by all program modes. It's not a difficult thing to manage at all: whatever is used by multiple program modes is global.
qbradq and tokumaru, thanks so much for helping me through this!
I understand everything yall said now!
tokumaru, thank you also for telling us about your best way to make variables!
Shiru, in famitone's readme.txt, wrote:
Warning: don't forget that active DMC conflicts with $2002 and controllers polling, read docs to learn how to avoid it (generally don't use $2002 polling and poll controllers three times in a row, then use matching result).
What is active DMC? I know $2002 is in the first group of I/O registers... I guess it allows interaction with the controllers. In my code I'm using $4016 to read the controller... $4016 is from the second and last group of I/O registers. When I read the controller from $4016 is that called 'polling' too?
Any reading that has side effects may screw up, because DMC adds one extra read. Examples of reads with side effects include reading the controller or reading bytes from PPU Data. Reading PPU status is okay, since one extra read won't hurt anything.
Dwedit wrote:
Any reading that has side effects may screw up... Examples of reads with side effects include reading the controller
Yes, I agree, now (after reading some more about reading). : ) My song wont play for me.
And so I'm wondering if it has something to do with the way I've worked the callling code into the controller reading part. Here is what i've got (
the famitone code is between the ---\/-- and the ---^---):
Code:
react_to_input:
lda #$01 ; strobe joypad
sta $4016
lda #$00
sta $4016
lda $4016 ; Is the A button down?
and #1
beq @not_a
ldx aHasbeenpressed
bne @b
sta aHasbeenpressed
inc aFrame ;run only once per press.
jmp @b
@not_a: sta aHasbeenpressed
@b: lda $4016 ;Is the B button down?
and #1
beq @select
lda #0
sta aFrame
jsr low_c ;low_c is just small code that plays a note
;-----------------------\/------------------------------------------
@select lda $4016 ; Select does something(music no working)
and #1
beq @start
ldx <musicA_module ;also songA
ldy >musicA_module
jsr FamiToneMusicStart
@start lda $4016 ; Start does nothing
and #1
beq @up
jsr FamiToneMusicStop
;-----------------------^-------------------------------------------
@up lda $4016 ;Is Up down?
and #1
beq @down
dec oY
@down lda $4016 ;is Down down?
and #1
beq @left
inc oY
@left lda $4016 ;is Left down?
and #1
beq @right
dec oX
@right lda $4016 ;Is Right down?
and #1
beq not_dn
inc oX
not_dn: rts
Thank you for spending time reading that big amount of code. Sorry, though, I hope there is something wrong there.
Dwedit wrote:
Any reading that has side effects may screw up, because DMC adds one extra read.
DMC is
delta modulation channel. That is the fifth APU channel right? My song doesn't have anything in that channel... so that means there is one less read... and then the side effects don't happen?
First off, you need to read the controller and store it into a variable. Doing it right off of the read is a terrible way to make an engine. Do a LSR and ROL it into a variable, then use that variable to see what needs to happen.
Thanks for your suggestion!
Here's my code now... does nothing... maybe i need to separate the reading code from the controller check code??
Code:
react_to_input:
lda #$01 ; strobe joypad
sta $4016
lda #$00
sta $4016
;*****************Read Entire Controller*
lda #0
sta controller1
ldx #8
-read: lda $4016
ora controller1
lsr ;shift right one bit
ror ;rotate one bit right
sta controller1
dex
bne -read
;*******************************************
lda controller1 ; Is the A button down?
and #1
beq @not_a
ldx aHasbeenpressed
bne @b
sta aHasbeenpressed
inc aFrame ;run only once per press.
jmp @b
@not_a: sta aHasbeenpressed
@b: lda controller1 ;Is the B button down?
and #00000010b
beq @select
lda #0
sta aFrame
jsr low_c ;low_c is just small code that plays a note
;-----------------------\/------------------------------------------
@select lda controller1 ; Select does something(music no working)
and #00000100b
beq @start
ldx <musicA_module ;also songA
ldy >musicA_module
jsr FamiToneMusicStart
@start lda controller1 ; Start does nothing
and #00001000b
beq @up
jsr FamiToneMusicStop
;-----------------------^-------------------------------------------
@up lda controller1 ;Is Up down?
and #00010000b
beq @down
dec oY
@down lda controller1 ;is Down down?
and #00100000b
beq @left
inc oY
@left lda controller1 ;is Left down?
and #01000000b
beq @right
dec oX
@right lda controller1 ;Is Right down?
and #10000000b
beq not_dn
inc oX
not_dn: rts
That's not right. This is what you need:
.rsset $300
ControllerButtons: .rs 1
LDX #$01
STA $4016
DEX
STX $4016
LDX #$08
Loop:
LDA $4016
LSR A
ROL ControllerButtons
DEX
BNE Loop
RTS ;Controller value in the variable ControllerButtons.
Maybe look into more 6502. Seems you don't have a good enough understanding of it or the hardware.
3gengames, I relpaced my code with your code and put this right after it
Code:
jsr high_c
.if ControllerButtons != 0
.error "need help"
.endif
And while running my program it never ever stops and says need help no matther what I'm pressing on the controller. The controller works fine in other programs. It is continuealy reaching this code cause I can hear a high pitch C noise over and over.
In your code you've got
Code:
Loop:
LDA $4016
LSR A
ROL ControllerButtons
DEX
BNE Loop
That lsr a writes its answer back into the accumulator right? And rol ControllerButtons writes its answer back into ControllerButtons right? So how is ControllerButtons updated? (These questions are for yall too.)
"LDA $4016" puts the state of the current button into the accumulator; "LSR A" (or simply "LSR", depending on the assembler) shifts the bit out of the accumulator and into the carry flag; "ROL ControllerButtons" shifts the carry flag into ControllerButtons. Do this 8 times in a row and each bit of "ControllerButtons" will indicate the state of a buttom.
the .rsset and Controller variable is any piece of RAM you want to point it at, here's a commented and better version:
Code:
LDX #$01 ;X=1
STX $4016 ;Write high latch value to controller port.
DEX ;X=0
STX $4016 ;4016=0 now, can be read back.
LDX #$08 ;X=8
Loop: LDA $4016; Put the player 1 controller value into the accumulator. This will hold the value of ONE button.
LSR A ;Put DataLine1 [Controller] onto the Carry.
ROL ControllerButtons ;Shift ControllerButtons RAM one bit left with the carry. When done 8 times, will be updated with button statuses for each button. One bit will represent one button. The MSB will be first read, LSB last read. 1=Pressed, 0=Not pressed.
DEX ;X=X-1
BNE Loop;If X!=0 then loop.
RTS ;Return from subroutine. New controller status for Player 1 will be in the RAM byte ControllerButtons.
Does this make sense on how it works? Any qustions just ask. This program should work.
ETA: NINJA'D!
3gengames wrote:
[code]LSR $4016; Put the player 1 controller value into the CARRY bit.
Does this really work? I mean, LSR absolute is a read-modify-write instruction, so you're effectively writing something back to $4016... Doesn't this interfere with the reading process?
3gengames, thank you for helping me. Now your code makes sense to me!
(Please answer tokumaru's question to you at the bottom of page 8.)
tokumaru wrote:
"LDA $4016" puts the state of the current button into the accumulator; "LSR A" (or simply "LSR", depending on the assembler) shifts the bit out of the accumulator and into the carry flag; "ROL ControllerButtons" shifts the carry flag into ControllerButtons. Do this 8 times in a row and each bit of "ControllerButtons" will indicate the state of a buttom.
Thank you for explaining all of that! (glad I asked!) "LSR SomeMemory" will move/write SomeMemory's LSB into the Carry bit. (The operation of LSR is
Operation: 0 -> byte ->C) Will it write the bit shift to that memory location as well? Is ROL read-modify-write too? (The operation of ROL is
Operation: |-<- byte <- C <-|)
Yeah, it will ROL the memory. The MSB on the memory will go to the carry. The LSB will turn into the carry. After 8 loops, all bits [Which represent button presses] will be updated. Far easier to check then instead of hardcoding it in.
tokumaru wrote:
3gengames wrote:
[code]LSR $4016; Put the player 1 controller value into the CARRY bit.
Does this really work? I mean, LSR absolute is a read-modify-write instruction, so you're effectively writing something back to $4016... Doesn't this interfere with the reading process?
Good call-out Tokumaru. This is not something I would have done in my own code, but once you think about this should not interfere with the reading process.
The only significant bit of $4016 when writing is bit 0, which controls the logic level on both of the controller port's OUT pins (the Strobe signal). So your read-modify-write looks like this:
1. Read $4016, A=%00000001, C=Don't Care
2. Logic Shift Right, A=%00000000, C=1
3. Write %00000000 to $4016
This will just keep the Strobe signal low, which it should normally be. This will not even interfere with other controllers as the logic level simply does not change.
Even if you are using something that uses the D3 and D4 lines it is still not a problem. You're RMW cycle looks like this instead:
1. Read $4016, A=%00011001, C=Don't Care
2. Logic Shift Right, A=%00001100, C=1
3. Write %00001100 to $4016
In the above bit 0 still isn't set. Even though bits 2 and 3 are it does not matter because they don't do anything on a write.
When using this on $4017 to read the state of controller port 2 this entire discussion is moot. $4017 does nothing on a write.
All of that said, this is how the hardware works (it's just a 4021 shift register after all). Emulators may do something entirely different, especially when input movie playback is involved. Use with caution.
qbradq wrote:
$4017 does nothing on a write.
Except reconfigure the APU Frame Counter.
Besides, if you LSR $4016, how will you read the expansion controllers on a Famicom?
I wouldn't use $4016 like that. There's more than D3 and D4, there is also D1 and D2 that exist on the NES and FC expansion port. So you might not see a glitch yourself, but it's possible that someone could. Safer to just LDA $4016 then LSR A.
And a RMW instruction actually does 2 writes. It writes back the original value before writing back the changed value. Sounds strange, but that's what it does (6502 reads or writes on every cycle, even if it's basically garbage). So depending on the state of D0 and D1, I can see how an LSR $4016 could actually end up strobing the controller.
Wow, I've learned a lot today. Thanks guys!
Edit: I see what you're saying, sorry. Yeah, use the earlier I guess. I'll edit the old post too. You probably shouldn't use the shift then, I never did either, just thought it'd be optimal. I was just ignorant of the other bits. Whoops!
qbradq wrote:
You might also want to consider releasing your current ROM in the
Homebrew Forum for others to comment on. I know that always gives me a lot of insight and motivation. Plus I'd love to see what you're working on
Thank you qbradq!
That's very helpfull for me.
I don't think now would be a good time release the rom because I'm stuck... and my sister wouldn't let me do that. But, she might be ok with a video... maybe. She has completed a couple of the levels - the graphics are awesome, I think!
Once I can finally get the music to play and also get my controller to work again, and then I would be estatic to work on scrolling. After all of that, a video could be made, that's a fun and helpfull idea!
qbradq wrote:
Wow, I've learned a lot today. Thanks guys!
And thank you, qbradq, for continuing tokumaru's thought!
edit: GOT THE CONTROLLER working again!
It's almost working. I'm getting a bunch of beeps and beats and other high notes. And the screen is bumping up and down with a beat... kind of. It sometimes sounds like a music file is being played; rarely though. I found my init_sound part
Code:
init_sound:
; initialize sound hardware
lda #$01
sta $4015
lda #$00
sta $4001
lda #$40
sta $4017
rts
Are those the right inits? Why did he (the nes 101 tutorial guy ... Michael Martin) store #$40 into $4017...
and #$01 into $4015 and #$00 into $4001?
To disable or what? If you want to disable, clear all the appropriate bits in $4015.
3gengames wrote:
To disable or what? If you want to disable, clear all the appropriate bits in $4015.
I want to know what has been set up for me by those three lda+stas. Maybe a setting is wrong... there's something wrong... somewhere.
Well try to understand all the code you have since you're just blindly storing to registers, understand the registers [See the wiki:
http://wiki.nesdev.com/w/index.php/APU] and then try again. That's what I'd do. You can silence all channels by doing a $4015 store I think, but other than that I won't be of much help, I haven't touched sound yet either.
It's difficult.
Go. To. The. Wiki. It's the best resource on the site.
[You mean the wiki, not Wikipedia. Wikipedia is only one of several wikis; our wiki is another. --MOD]
That's the same page I'm on... in another tab. Yes it is very helpfull.
And I just undersand, now,
that writing #$40 to $4017 is setting the Mode to 1... it, "selects 5-step sequence."
And then after more reading I understand that "In this mode, the frame interupt flag is never set." Which, I guess, might mean
that setting timed events is not possible. ... and well thanks for the wiki recommendation (I remember tepples was the first to recommend the wiki to me) and, I agree with you, sound is difficult.
Ordinarily, games time their music engines off the vertical blanking NMI, not the APU Frame IRQ. I think only about two games actually use the APU Frame IRQ. As I understand it, the APU Frame IRQ isn't really for the NES as much as for other applications of a 2A03 CPU with no other source of periodic interrupts.
.............................................................this is what happens
I have set the controller so that
1.) Pressing a direction increments or decrements the oX or oY values that represent the x and y location of our meta-sprite.
2.) Pressing select "starts" my song.
3.) Pressing start "stops" my song.
4.) Pressing A simply displays the next meta-sprite.
5.) Pressing B simply returns our meta-sprite back to what it originally looked like.
So, when starting the game our meta-sprite begins in the upper left corner. Pressing left or right moves our sprite either left or right one pixel. Upon releasing that left or right press our meta-sprite returns back a pixel right or left to its origin. Pressing up or down works like "normal"; our meta-sprite ascends or descends until the up or down press is released. Most recently I pressed A twice and then pressed select and the screen began to gyrate around between two or three screens over and over again with the sound of beeps and other non musical stuff... then I pressed A and the screen changed its gyrate to a different pattern and the half-music/noise changed also... along with the screen...
And so I pressed A again and the noise-screen gyration changed again. Remember how our meta-sprite had problems moving left and right... well as the A button was pressed the sprite also returned to its origin and became unmoveable up and down and left and right beyond the one pixel reach from before.
As I think about this it seems that the problem is solveable and I hope that you have an idea-fix or helpfull thoughts to share with us.
edit: When the game starts our screen, from above, scrolls down into the screen area. It has like an upper floor part and that floor part is raised and lowered very randomly in its pattern pieced gyrations.
Is it good to set up the controller reading part with a system that checks each button-part to see if it has already been pressed and so then it should be ignored until it's released?
You don't need any complex logic for that. With just a few bitwise operations you can detect all kinds of keypresses. Read
this post and the ones after it.
I have the controller reading code a subroutine, then I put my movement and actions based off the computer in my main engine, I think that'd the standard way of doing things.
Sweet! Thank you tokumaru!
I can move our sprite reguarly now!!!
Thank you 3gengames for those two sentences, they helped me alot
once I finally spent time to understand it.
So far only half of my controller works... the controlpad part works; A B Select Start don't do anything. Do you have an idea of what coould be wrong?
Well, can't tell without the code...
Code:
lookatController:
lda ControllerButtons
sta lastControllerButtons
LDX #$01
STX $4016
DEX
STX $4016
LDX #$08
-Loop:
LDA $4016
LSR A
ROr ControllerButtons
DEX
BNE -Loop
lda lastControllerButtons
eor #$ff ;invert
and ControllerButtons ;AND result with the new state
sta pressedControllerButtons
RTS ;Controller value in the variable ControllerButtons.
...
react_to_input:
jsr lookatController
lda pressedControllerButtons ; Is the A button down?
and BUTTON_A ;#00000001b
beq @b
inc aFrame ;run only once per press.
@b: lda pressedControllerButtons ;Is the B button down?
and BUTTON_B ;#00000010b
beq @select
lda #0
sta aFrame
jsr low_c ;low_c is just small code that plays a note
@select lda pressedControllerButtons ; Select does something(music no working)
and BUTTON_SELECT ;#00000100b
beq @start
ldx <musicA_module ;also songA
ldy >musicA_module
jsr FamiToneMusicStart
@start lda pressedControllerButtons ; Start does nothing
and BUTTON_START ;#00001000b
beq @up
jsr FamiToneMusicStop
@up lda pressedControllerButtons ;Is Up down?
and BUTTON_UP ;#00010000b
beq @down
dec oY
@down lda pressedControllerButtons ;is Down down?
and BUTTON_DOWN ;#00100000b
beq @left
inc oY
@left lda pressedControllerButtons ;is Left down?
and BUTTON_LEFT ;#01000000b
beq @right
dec oX
@right lda pressedControllerButtons ;Is Right down?
and BUTTON_RIGHT ;#10000000b
beq not_dn
inc oX
not_dn: rts
Hmm, not sure what it's doing here and I don't know if I can find out. Is it doing anything at all? What isn't happening?
unregistered wrote:
So far only half of my controller works... the controlpad part works; A B Select Start don't do anything.
A & B aren't working at all, they are susposed to advance and reset the sprite frame. Select and start arent working at alll, they are susposed to start and stop a famitone song.
Are you ROLing them the right way in your routine? You ROR, I dunno if you ment to do a ROL instead.
Have you tried swapping around which button does what?
What are the definitions of BUTTON_A, BUTTON_B, BUTTON_SELECT, and BUTTON_START? They appear not to be present in this page of the discussion or on the previous page.
Have you made sure that A, B, Select, and Start are correctly mapped in the emulator on which you're testing, such as by playing someone else's homebrew game?
Have you tried getting on a PC running Windows and stepping through react_to_input using FCEUX or Nintendulator?
EDIT:
@3gengames: ROR is fine if you prefer little-endian bit order of the buttons in the byte, not unlike how the Game Boy Advance buttons are ordered.
tepples wrote:
Have you tried swapping around which button does what?
No... well, I did a long time ago, I remember.
tepples wrote:
What are the definitions of BUTTON_A, BUTTON_B, BUTTON_SELECT, and BUTTON_START? They appear not to be present in this page of the discussion or on the previous page.
next to each one of these i commented out the value (in the last code up there)
Code:
BUTTON_RIGHT .equ #10000000b
BUTTON_LEFT .equ #01000000b
BUTTON_DOWN .equ #00100000b
BUTTON_UP .equ #00010000b
BUTTON_START .equ #00001000b
BUTTON_SELECT .equ #00000100b
BUTTON_B .equ #00000010b
BUTTON_A .equ #00000001b
tepples wrote:
Have you made sure that A, B, Select, and Start are correctly mapped in the emulator on which you're testing, such as by playing someone else's homebrew game?
No, that is a good idea, thank you... now, yes they are working
tepples wrote:
Have you tried getting on a PC running Windows and stepping through react_to_input using FCEUX or Nintendulator?
I've never stepped through anything. Do you have any tips/suggestions? I'm going to use Nintendulator.
In Nintendulator 0.970, open your ROM and under Debug, choose Disassembly. In the Controls pane of the debugger window, click "Step" to stop your program. Click "Step" a few more times to execute an instruction at a time. In the Trace pane, scroll up and down to see instructions around the program counter. You can double-click an instruction to set a breakpoint. Once you've set a breakpoint on an instruction; the "Run" button in the will run the program until a breakpoint is executed. So if you put a breakpoint just before jsr react_to_input, you can run the program and then step through the subroutine.
tepples wrote:
In Nintendulator 0.970, open your ROM and under Debug, choose Disassembly. In the Controls pane of the debugger window, click "Step" to stop your program. Click "Step" a few more times to execute an instruction at a time. In the Trace pane, scroll up and down to see instructions around the program counter. You can double-click an instruction to set a breakpoint. Once you've set a breakpoint on an instruction; the "Run" button in the will run the program until a breakpoint is executed. So if you put a breakpoint just before jsr react_to_input, you can run the program and then step through the subroutine.
tepples, thanks for these instructions!
How can i find out what the line number address is just before
jsr react_to_input?
That depends on how your assembler outputs listing or map files.
What I usually do is put in a dummy instruction and set a breakpoint on that. In Nintendulator you can tell it to break when a certain address is read or written to.
I usually do a bit $0100 to read from the last byte of the stack, something that does not normally happen in my programs. I stick that instruction in wherever I want a break, then set the break point to Read $0100.
tepples wrote:
That depends on how your assembler outputs listing or map files.
WOAH WOW! LISTING FILES RULE!
Thank you!!
qbradq wrote:
What I usually do is put in a dummy instruction and set a breakpoint on that. In Nintendulator you can tell it to break when a certain address is read or written to.
I usually do a bit $0100 to read from the last byte of the stack, something that does not normally happen in my programs. I stick that instruction in wherever I want a break, then set the break point to Read $0100.
Thank you qbradq!
In NintendulatorDX you can also input the function name in the debug watch window (given that your assembler is capable of spitting out a compatible debug file) and set breakpoint on that. In NESICIDE you can simply click on the left hand margin to set a breakpoint (again, if you have the debug info).
BTW, it probably wouldn't be too hard to add debug file support to ASM6/NESASM.. the debug file format is pretty much self explanatory (and if there are problems, I can help). Any takers?
I could look into this, although
QASM is coming along nicely
Which one do you think would be more worth-while to start with?
qbradq wrote:
I could look into this, although
QASM is coming along nicely
Which one do you think would be more worth-while to start with?
Probably ASM6, but I don't know. Probably going to be an unthankful job for anybody who's not using the said assemblers themselves.
---
This question is to anyone. (Sorrry to interrupt yall's conversation.
)
Does the controller work during the step through? I can't tell, it's all much to learn, and I'm going to learn it.
edit: I'm using a regular nintendo controller with a nes-to-usb adapter that i bought
here.
Everything should work through step throughs.
unregistered wrote:
Does the controller work during the step through?
In theory, yes. In practice, some emulators might ignore button presses while the emulator's video output window (the one with the game in it) is not focused, causing buttons to read as not pressed or as stuck if you Run several frames in the debugger. This behavior of ignoring presses to an unfocused has little to do with whether you're using a keyboard, a PC game controller, an NES controller through a retrousb.com adapter, or a Nintendo 64 controller through an Adaptoid adapter (what I use), and more to do with how Windows handles input focus.
I have a rough time getting Nintendulator to do this. When I need to debug a program's input handling I usually add a small test to fake a conditional breakpoint.
Code:
lda control_state
beq skip_break
bit $0100
skip_break:
And I set a breakpoint for reads to $0100. That way the emulator breaks the next time I press a button, and I can single-step through my handling of it.
It's little tricks like this that need to be documented somewheres
The only fix for the focus problem I know is to focus the main window and then step using Shift+F2 or whatever the shortcut for step was. This way the controller keys can be held down simultaneously, altho it's a bit awkward.
Wouldn't the keys need to be held down at the start of the frame though? Or do we get the state of the keyboard the instant we strobe $4016 in Nintendulator?
I was trying to do frame advance mode, hold down the buttons I needed, use Space Bar to advance a frame, then step into my controller code. Never worked right for me.
Well, the controller correctly reads 8, 4, 2, or 1 for the first hex number and 0 for the last hex number. Something is not working, but it's all good cause God and I can figure it out!
Yall are so helpfull, thanks 3gengames, tepples, and qbradq.
I'lm going to try doing it your way now qbradq...
Oh, before that there's something else I wanted to share. tepples, I did rearange what the buttons do and it seemed to me, yesterday, that the code I added below BUTTON_UP didn't work for me then; it kindof did... it incremented the aFrame... but incorrectly. After two presses of the Up button it went through all our frames (which is definitly more than 2 frames).
The updownleftright pad must repeatedly be pressed over and over... like turbo?
edit: Thank you to thefox too!
Maybe your controller reading routine is a bit off? I dunno, I have a hard time groking all of the binary xor'ing stuff that's required. I haven't touched my controller library in ages
I've been scolded once before for posting incomplete controller polling routines, so I'll include my entire library. It's in
QASM syntax, so you'd have to change it a little bit to work with other assemblers, but it's very well tested and works reliably. As a bonus it calculates what was just pressed this frame and what was just released this frame as well.
Code:
; Control pad library, for player 1 only
.scope control
; Button bits
; abltudlr
; |||||||+- DPad Right
; ||||||+-- DPad Left
; |||||+--- DPad Down
; ||||+---- DPad Up
; |||+----- Start
; ||+------ Select
; |+------- B Button
; +-------- A Button
.equ abutton %10000000
.equ bbutton %01000000
.equ select %00100000
.equ start %00010000
.equ dpadup %00001000
.equ dpaddown %00000100
.equ dpadleft %00000010
.equ dpadright %00000001
; Control pad registers
.equ reg_pad1 $4016
.equ reg_pad2 $4017
; Globals
.segment zeropage
.res pressed 1 ; If a bit is set in this register the button was just pressed this frame
.res released 1 ; If a bit is set in this register the button was just released this frame
.res state 1 ; If a bit is set in this regsiter the button is currently down
.endsegment
; Updates the controller states
.proc update
; Locals
.segment ram
.res previous_controler_state 1 ; The value of state from the previous frame
.endsegment
; Cycle controller states
lda state
sta previous_controler_state
; Strobe the controler port
ldx #$09
stx reg_pad1
dex
stx reg_pad1
; Read control port data
loop:
lda reg_pad1
lsr ; bit0 -> Carry
rol state ; bit0 <- Carry
dex
bne loop
; Compute pressed
lda previous_controler_state
eor #$ff
and state
sta pressed
; Compute released
lda state
eor #$ff
and previous_controler_state
sta released
rts
.endproc
.endscope
qbradq wrote:
I have a rough time getting Nintendulator to do this. When I need to debug a program's input handling I usually add a small test to fake a conditional breakpoint.
Code:
lda control_state
beq skip_break
bit $0100
skip_break:
And I set a breakpoint for reads to $0100. That way the emulator breaks the next time I press a button, and I can single-step through my handling of it.
How does that work? It didnt for me. I understand now though how inserting
bit $0100 and setting a breakpoint for a read from $0100 is done, thanks
, but I dont see how these 4 lines of code here make the emulator break every time a button is pressed.
Am I susposed to figure that part out for myself?
Go to the debugger, and set a break point for every time it reads/write from/to that location in memory, it won't do anything with your game [besides possibly mess it up] unless you use the debugger. When it hits the read/write from that location, it'll stop, allowing you to step through your code there.
3gengames wrote:
Go to the debugger, and set a break point for every time it reads/write from/to that location in memory, it won't do anything with your game [besides possibly mess it up]...
It reads $0100 and then writes twice to the status register, right? That's pretty safe, to me.
unregistered wrote:
3gengames wrote:
Go to the debugger, and set a break point for every time it reads/write from/to that location in memory, it won't do anything with your game [besides possibly mess it up]...
It reads $0100 and then writes twice to the status register, right? That's pretty safe, to me.
I dunno, but you're stack is there, but you probably shouldn't ever use all of it to even $100, haha.
Did you get the debugging to work?
Heh, I actually use $FF as my breakpoint location. But that's because I use a good part of page 1 for variables, starting at $0100, since I won't ever need all 256 for the stack. If I'm not mistaken, I use only 32 bytes or so for the stack, the rest is all variables. ZP is also filled with variables, but until I reach the very last byte of it, $FF is a safe place for breakpoints.
3gengames wrote:
Did you get the debugging to work?
Yes, I feel I'm making progress.
But, it was too weird and so I changed back to the ROL and so now after pressing
select it does something... my meta-sprite returns to the left-hand side of the screen... and then, after that, the controls start working normaly. Each button only completes its ppurpose one time per press. So, it kindof works more now... but if i dont press
select, the directional pad does nothing.
edit: I changed it back to
ROL because I wanted to see if
select worked. With the
ROR I could only press directions on the directional pad. After changing it to
ROL I updated my BUTTON definitions:
Code:
BUTTON_RIGHT .equ #00000001b
BUTTON_LEFT .equ #00000010b
BUTTON_DOWN .equ #00000100b
BUTTON_UP .equ #00001000b
BUTTON_START .equ #00010000b
BUTTON_SELECT equ #00100000b
BUTTON_B .equ #01000000b
BUTTON_A .equ #10000000b
(They're in the wrong order but that doesn't matter
.) The
ROL started to work right... kindof,
Select,
Start,
B, and
A worked now. And now the other half doesn't work... It's the same problem on BOTH
ROL AND ROR!
Select doesn't play the famitone song, but it does fix whatever is wrong with my controller reading code. After pressing
select all the buttons do something.
Thank you qbradq for all your code you posted... I can't bring myself to copy it... yet because I learn by trying to fix by myself. : )
tokumaru wrote:
Heh, I actually use $FF as my breakpoint location. But that's because I use a good part of page 1 for variables, starting at $0100
Isn't the stack inverted? Is that why you say, "I use a good part of page 1 for variables, starting at $0100"?
He's making the RAM in "pages" of 256.
Page 0=$0000-$00FF
Page 1=$0100-$01FF
And so on. Page 1 is the stck area, it's hard wired. Phen you put something on it, it DE-INCREMENTS the stack. So when you load the stack with FF (LDX #$FF, TXS) it's at the highest point. Most people don't use all of the stack, so sometimes random variables go into the unused space, despite the stack being there. I don't put any there myself, but if I need to, they're going there. I doubt I'll need it though.
unregistered wrote:
Isn't the stack inverted? Is that why you say, "I use a good part of page 1 for variables, starting at $0100"?
I just meant that since I don't use the whole stack, I put variables in the unused space. So while the stack grows from $1FF down, I have variables from $100 up. As long as the variables don't touch the deepest level my stack goes, that's fine.
Just a minor point, but I would suggest to avoid doing .equ #00000000 and would do .equ 00000000 instead, then put the # in your code. I know it should work either way, but it's helpful to have your code be as clear as possible that immediate mode is being used (in addition to using all caps for defined constants, like you're doing already).
Memblers wrote:
Just a minor point, but I would suggest to avoid doing .equ #00000000 and would do .equ 00000000 instead, then put the # in your code. I know it should work either way, but it's helpful to have your code be as clear as possible that immediate mode is being used (in addition to using all caps for defined constants, like you're doing already).
I thought # ment immediate mode.
It does, but the immediate mode goes in an instruction, not in the equate statement. The instruction is "LDA #" or "AND #"; its operand is 0.
tepples wrote:
It does, but the immediate mode goes in an instruction, not in the equate statement. The instruction is "LDA #" or "AND #"; its operand is 0.
Ah!
Thank you tepples! Thank you Memblers!
My computer got changed or fixed... sorry for missing 3 weeks.
How do I enable the character to move like it used to when pressing a direction? It used to move until you released the dpad direction. Now it only moves 1 pixel per press because of the changes I've been instructed to make. It doesn't check the controller 60 times each second anymore. I don't get it.
You should use different kinds of button detection for different game events. For things like menu navigation and shooting for example, you'll want to detect when buttons are pressed, but for walking you'll actually want to react 60 times per second.
Most people just keep 2 button state bytes, one with the buttons that are currently pressed (regardless of their state in the previous frame), and one with the buttons that are pressed now but weren't last frame (i.e. new buttons). Both of them are useful in games.
Ah, thank you so much tokumaru! This seems possible now!
Thank you God for this understanding! He always helps.
this is crazy. Here is my code
Code:
lookatController:
lda currControllerButtons
sta lastControllerButtons
LDX #$01
STX $4016
DEX
STX $4016
bit $0100
LDX #$08
-Loop:
;LDA $4016
;LSR A
;ROl currControllerButtons
;DEX
pha
; Read next button state and mask off low 2 bits.
; Compare with $01, which will set carry flag if
; either or both bits are set.
lda $4016
and #$03
cmp #$01
; Now, rotate the carry flag into the top of A,
; land shift all the other buttons to the right
pla
rol a
dex
BNE -Loop
lda lastControllerButtons
eor #$ff ;invert
and currControllerButtons ;AND result with the new state
sta newControllerButtons
RTS ;Controller value in the variable currControllerButtons.
The wiki code is there in the -Loop part; I commented out the 4 lines and pasted the wiki code... just to see what would happen. It totally suprized me! My figure chose to start moving left and then went on a diagonally north-east path until I stopped nintendulator. Pressing buttons didn't help. I'm so tired... been up all night trying to complete the movement part. There must be some horrible mistake in that code... posting it here to see if yall could figure it out. I'm going to be away from my computer until next Tuesday. We're leaving this morning.. have to get ready. Thank you for reading all of this. Love yall.
God, please help me some more. Amen. : )
You are not saving the current button status anywhere after reading. Just put "sta currControllerButtons" right below "BNE -Loop".
OR replace the whole loop with the following:
Code:
-Loop:
lda $4016
and #$03
cmp #$01
rol currControllerButtons
dex
bne - Loop
I see absolutely no reason for you to be monkeying around with the stack.
I must remind you that copying and pasting code around just to "see if it works" is a terrible way to program. Things end up working by pure luck, meaning you don't learn anything (because you don't know
why they worked) and your programs aren't reliable at all, since different circumstances could easily break them.
EDIT: I just want to add that what I meant in my previous messages is that for some game events you should use
currControllerButtons (walking, jumping - if you have variable jump heights -, etc.), but for others you should use
newControllerButtons (menu navigation, shooting, etc.).
tokumaru wrote:
You are not saving the current button status anywhere after reading. Just put "sta currControllerButtons" right below "BNE -Loop".
OR replace the whole loop with the following:
Code:
-Loop:
lda $4016
and #$03
cmp #$01
rol currControllerButtons
dex
bne - Loop
I see absolutely no reason for you to be monkeying around with the stack.
I must remind you that copying and pasting code around just to "see if it works" is a terrible way to program. Things end up working by pure luck, meaning you don't learn anything (because you don't know
why they worked) and your programs aren't reliable at all, since different circumstances could easily break them.
EDIT: I just want to add that what I meant in my previous messages is that for some game events you should use
currControllerButtons (walking, jumping - if you have variable jump heights -, etc.), but for others you should use
newControllerButtons (menu navigation, shooting, etc.).
tokumaru, thank you for the information and thank you for the advice. : )
blessings,
unregistered (matthew)
Oh, I just noticed a mistake in my code... it's obviously $4016, not #4016.
tokumaru wrote:
Oh, I just noticed a mistake in my code... it's obviously $4016, not #4016.
It's ok...
Edited mine to have the correct code too.
Also, have a question:
In the Trace box on Nintendulator's Debugger screen... it has
LDA $01 = 0F. I understand that $01 means $0001. What does the
= 0F mean? I don't understand what that is for. : (
It just shows content of memory at the address. I.e. $01 contains $0f.
Shiru wrote:
It just shows content of memory at the address. I.e. $01 contains $0f.
How does it know the future content of everything? There are instructions with = signs all the way to the bottom of the Trace window. The instruction to execute next is at the top of the Trace window, right?
edit: Shiru, thanks for your answer!
It goes by what's currently there, not the future. Numbers shown in a disassembly view will change as the registers or memory changes. (or even RAM code modified)
The = X isn't part of the instruction, it's just the debugger helping you out by telling you what's at that memory address.
Ah!
Thank you very much Dwedit!
at myself... hahahaha - i'm dumb.
But God is not dumb and he is so loving!!
: )
ok, so this is what happened... Memblers wrote earlier, in this thread:
Memblers wrote:
Just a minor point, but I would suggest to avoid doing .equ #00000000 and would do .equ 00000000 instead, then put the # in your code. I know it should work either way, but it's helpful to have your code be as clear as possible that immediate mode is being used (in addition to using all caps for defined constants, like you're doing already).
and I didn't understand what he ment by ", then put the # in your code."
Today, just a little while, ago I read these wise words from Tepples
Quote:
Code:
load_font:
lda #font&255
sta addy
lda #font/256
sta addy+1
ldx #3
ldy #0
What do the first two lda's do? I don't understand the #font& and #font/ parts. Does a preceding # mean? Can that character be left out?
# means the following value is a number to put directly into A, not an address from which to load the value into A. Look at the "immediate" addressing mode.
And so in my head # started to make sense. I was running and stepping through my "game" with a break everytime there was a write to $0000 through $0002. That's where i placed the three variables
0) currControllerButtons
1) lastControllerButtons
2) newControllerButtons
that I had been watching and thinking about. For some reason the top line of the Memory box started with 0F 0F 00 and that didn't make sense. Why would it be thinking someone had pressed every direction all at the same time? And so I tried the break (same one as i mentioned earlier) to find out. And finally it all started making sense! Each line was missing a # and so that ment that it was an address from which to load the value into A!! So I quickly went to the top of my prg file to this code
Code:
BUTTON_RIGHT .equ 00000001b
BUTTON_LEFT .equ 00000010b
BUTTON_DOWN .equ 00000100b
BUTTON_UP .equ 00001000b
BUTTON_START .equ 00010000b
BUTTON_SELECT equ 00100000b
BUTTON_B .equ 01000000b
BUTTON_A .equ 10000000b
and I started to add a # in front of each binary number... but quit and undid that and quickly went to my vblank file to my code where it says
Code:
lda newControllerButtons ; Is the A button down?
and #BUTTON_A ;10000000b
beq @b
inc aFrame ;run only once per press.
@b: lda newControllerButtons ;Is the B button down?
and #BUTTON_B ;01000000b
and as you can see, I added a # right before each use of my constants.
"and Button_A" became
"and #Button_A".
It works better than it used to!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Memblers, I'm so sorry that I didn't write and ask you what you were trying to say! The question that remains unasked is dumb. I agree. : )
!!!!!!!!!!!!!!!!!!!!!!!! I pressed select and the music played until i pressed start and it stopped!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
I needed to add a # to
Code:
ldx #<musicA_module
ldy #>musicA_module
jsr FamiToneMusicStart
The music plays but there is something unsteady about it.
edit: Only the first square channel plays... I used the first 4 channels. I can't listen to the song because I lost that file...
But, I have the asm file...
I'm glad that fix helped, actually I'm surprised it changed the functionality because I thought EQU is supposed to be a simple text-replacement. I guess it ignored the # then, I didn't expect that.
unregistered wrote:
The music plays but there is something unsteady about it.
edit: Only the first square channel plays... I used the first 4 channels. I can't listen to the song because I lost that file...
But, I have the asm file...
I would check that the song was made with a version that the playback engine is compatible with (there had been a lot of Famitracker updates, so that's always a possible problem). Next I would check that the RAM locations used by the playback engine aren't being used by your program. Then last is probably not necessary, but I'd make sure the engine is setting the right bits in $4015, and doesn't depend on the user to set those (I'm pretty sure the engine would handle those, it really should).
Memblers wrote:
I'm glad that fix helped, actually I'm surprised it changed the functionality because I thought EQU is supposed to be a simple text-replacement.
The assignment operator (=, :=, or EQU depending on the assembler's dialect) creates a
symbol. The following lines are equivalent:*
Code:
label:
label = *
For text replacement, use #define in the C preprocessor or
.define in ca65 2.5 or later.
Quote:
I guess it ignored the # then, I didn't expect that.
I expected it to give an error message.
* ca65 actually supports two kinds of symbols: labels and nonlabels. ld65 treats these identically, but a debugger might treat them differently. To force a symbol to be a label, use := instead of = .
Memblers wrote:
I'm glad that fix helped
Thanks. Me too
Memblers wrote:
, actually I'm surprised it changed the functionality because I thought EQU is supposed to be a simple text-replacement. I guess it ignored the # then, I didn't expect that.
What ignored the # ?
I must be missing something.
Memblers wrote:
unregistered wrote:
The music plays but there is something unsteady about it.
edit: Only the first square channel plays... I used the first 4 channels. I can't listen to the song because I lost that file...
But, I have the asm file...
I would check that the song was made with a version that the playback engine is compatible with (there had been a lot of Famitracker updates, so that's always a possible problem).
think so... think it was made with
Famitracker 0.3.6. It was released in Jan 2011... i dont believe I've used Famitracker before January.
Memblers wrote:
Next I would check that the RAM locations used by the playback engine aren't being used by your program.
Thanks for having me check those!
They are ok now, I think. Well, this helped with some of the problem: the screen doesnt jump up and down very much anymore. The lady meta-sprite is still moved over to the left side of the screen... this always happens
after select is pressed - character moves over to left side of screen,
the screen scrolls down a little,
the screen slowly mildly jumps up and down,
our lady meta-sprite is trapped... she cant move anywhere else. Except when after pressing start and the music stops and normal game like state works... unless you press select again.
The song plays through the first part ok; it only sounds like 1 square channel is working. Then it's quiet for a while...
Memblers wrote:
Then last is probably not necessary, but I'd make sure the engine is setting the right bits in $4015, and doesn't depend on the user to set those (I'm pretty sure the engine would handle those, it really should).
$4015 starts out with a setting of
$0f. Then further down in the code it changes to
$1f. It says
;start DMC next to that.
Make sure your music pay respect to all the requirements of the player, you can't just take a random FTM and use it.
Shiru wrote:
Make sure your music pay respect to all the requirements of the player, you can't just take a random FTM and use it.
Yes, soI get the same problems after I changed it to play your
danger streets. The screen shakes up and down... maybe in beat with the music. Our lady meta-sprite is shoved over to the left of the screen where she stays trapped until I press
start. It sounds weird, the same way my song sounds weird. I'm going to try to make a mp3 recording of the sound so you can hear and maybe help me some more.
edit: What is a good recording software for me to use?
If you screen is shaking in beat with the music, you probably call music update before VRAM update. It should be vice versa, because you only can update VRAM in short period of time after NMI, and calling music first you waste part or all of that time.
Shiru wrote:
If you screen is shaking in beat with the music, you probably call music update before VRAM update. It should be vice versa, because you only can update VRAM in short period of time after NMI, and calling music first you waste part or all of that time.
Thank you for helping me.
Ah ok well,
jsr FamiToneUpdate is the last thing called in my vblank file. I remember being told to make it begin in the end of vblank. Sorry that it doesn't work yet. ...going to sleep good night. : )
unregistered wrote:
What is a good recording software for me to use?
The one built into the emulator, if you're playing on an emulator. The one that came with your video capture card, if you're playing on an NES.
Anyway, does jsr FamiToneUpdate happen before or after the writes to $2005 to set the scroll position?
jsr FamiToneUpdate happens after the writes to $2005 to set the scroll position.
Why play a guess game, why not to show whole code?
My sister has limited me; i'm not able to share the code. It's ok... it will somehow work.
edit: (right click & save target as:)
You can
listen to what
danger streets sounds like
playing inside our game on my computer.
And then
listen to what
danger streets sounds like
playing through Shiru's demo.nes on my computer. It's much more pronounced.
It sounds like you really want other people to play a guess game. Even I can only say: you have some problem. And acting like this you want to get a diagnosis by a photo. What so secret in your buggy code?
unregistered wrote:
My sister has limited me; i'm not able to share the code.
Where is she? 1983?
THE MUSIC JUST PLAYS.... no screen shaking and no trips to the left half of the screen!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Now I need to try to find out why it sounds weird...
Adding a call to FamiToneInit
makes it not weird sounding anymore!!!!!!!
Now to see if my song will work.
YYYYYYYYYYYEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHHH!!!!!!!!!!!!!!!
it works now
Thank you so much God for helping me!
And thank yall for helping me!
I go tooooo sleep now.good night.
---------
I'm going to try to build one of our levels now...4 screens tall by 6 screens wide.
The most helpful pages I've found are
the PPU registers wiki page and
the PPU power-up state.
Is most of the code for scrolling going to be like
tokumaru's code on this page? Is it a process of learning how to set the PPU registers at the right time? This sounds fun.
Two other questions.
1) I found
this post in the forum and printed it out. But maybe there is a problem... WedNESday says, "
the 15bit VRAM Address is made up of...five counters." I can only find four counters...
A 5th one listed is FineX but that one wasn't assigned any bits so it seems like, to me, that FineX isn't a part of
the 15bit VRAM Address.
2) I printed out this nice diagram too:
I re-drew the table about a year ago to use more standard characters. It displays properly using any fixed-width font. I hope this displays properly here in the forums, if not copy it into any text editor. Also I didn't check for any discrepancies between this version and the latest 2C02 doc, so be careful!
Code:
+---------------+-------------------------------------------------------+
|2000 | 1 0 4 |
|2005/1 | 76543 210 |
|2005/2 | 210 76543 |
|2006/1 | -54 3 2 10 |
|2006/2 | 765 43210 |
|NT read | 76543210 |
|AT read (4) | 10 |
+---------------+-------------------------------------------------------+
| |+---+ +-+ +-+ +-----+ +-----+ +---+ +-+ +--------+ +--+|
|PPU registers || FV| |V| |H| | VT| | HT| | FH| |S| | PAR| |AR||
| |+---+ +-+ +-+ +-----+ +-----+ +---+ +-+ +--------+ +--+|
|PPU counters || | | | | | | | | | |
| |+---+ +-+ +-+ +-----+ +-----+ |
+---------------+-------------------------------------------------------+
|2007 access | DC B A 98765 43210 |
|NT read (1) | B A 98765 43210 |
|AT read (1,2,4)| B A 543c 210b |
|PT read (3) | 210 C BA987654 |
+-----------------------------------------------------------------------+
It looks very helpful... but how does it help me; i dont understand it.
unregistered wrote:
Is most of the code for scrolling going to be like
tokumaru's code on this page?
That code is only for selecting which part of the name tables will be displayed on the screen, and only if you are doing it after the end of VBlank. Setting the scroll still inside of VBlank only requires one $2000 write and two $2005 writes, no tricks necessary.
A scrolling engine requires much more than setting the scroll though, the hard part is actually updating the correct parts of the name tables and attribute tables. I can assure you that programming a 4-way scrolling engine for levels taller than 2 screens will be pretty tough for a beginner.
tokumaru wrote:
unregistered wrote:
Is most of the code for scrolling going to be like
tokumaru's code on this page?
That code is only for selecting which part of the name tables will be displayed on the screen, and only if you are doing it after the end of VBlank. Setting the scroll still inside of VBlank only requires one $2000 write and two $2005 writes, no tricks necessary.
A scrolling engine requires much more than setting the scroll though, the hard part is actually updating the correct parts of the name tables and attribute tables. I can assure you that programming a 4-way scrolling engine for levels taller than 2 screens will be pretty tough for a beginner.
I've never programed an engine before... I must though so I will program my scrolling engine. My sister just told me that when she created the level in her head/on paper she assumed no vertical scrolling; it would be like it is in Zelda. Horizontal scrolling is needed though. : ) Something like that would be easier to program right? I need something to read that will help me to create a scrolling engine. Thank you for your reply tokumaru.
Any scrolling is not suitable project for a beginner, it adds another layer of complexity. Make something simpler first.
Agreed with Shiru, you can if you want, but I'd say get more experience before you try to make something that scrolls.
And yeah, and on top you won't have scrolling artifacts like SMB3, and horizontal scrolling is a lot easier than bidirectional scrolling
Horizontal scrolling is easier to implement than vertical, and infinitely easier than horizontal + vertical. If you design your drawing routines to work in columns from the start, implementing the horizontal scroll should be fairly simple.
I have to agree that any scrolling is kinda tough for absolute beginners (people who are new to the NES as well as game programming in general). If you have never programmed a game/engine for any platform before, it would be a good idea to practice with something simpler on the NES before messing with scrolling.
Quote:
Horizontal scrolling is easier to implement than vertical, and infinitely easier than horizontal + vertical. If you design your drawing routines to work in columns from the start, implementing the horizontal scroll should be fairly simple.
I can definitely confirm this. At first it seems weird, you'd think it would be 2 times more complex to implement horizontal and vertical, but unfortunately no.
Also vertical scrolling is harder because of the incomplete attribute row at the end of each NES nametable.
Bregalad wrote:
At first it seems weird, you'd think it would be 2 times more complex to implement horizontal and vertical, but unfortunately no.
Yeah, it's more than twice as hard because one type of scrolling interferes with the other, which ends up complicating both of them. Making sure that both types of scrolling play nice with each other is not such a trivial thing. It's obviously not impossible either, it's just something you have to put a lot of thought into.
Quote:
Also vertical scrolling is harder because of the incomplete attribute row at the end of each NES nametable.
That and because 30 (the number of tiles in a name table column) is not a power of 2. Attributes are definitely the worst part, though.
Thank you all for the honest and helpfull advice. Scrolling will be ok for me to attempt, I think, because there are two big thick books that remind me of programming with Torque, a 3D game engine. It was a hard and immensly difficult task, but God was there with me and he helped me figure it out.
We made an A and I got credit for an internship for that class!
My mom helped me remember the last part of that. I think I can do this scrolling... my sister helped me to find
this Nerdy Nights scrolling article. And it has made some sense so far... I've made it to the drawing new columns part. Will read this again after supper.
I didn't even know that Nerdy Nights went as far as scrolling... Well, if you can understand the basic concept and are feeling confident about implementing this, then go for it! =)
Drawing columns in an one-axis scroll is easiest part. The problem is with effective objects and collisions processing.
Shiru wrote:
The problem is with effective objects and collisions processing.
I disagree. As long as you use 16-bit coordinates for everything (meaning that all objects are inside a "room" larger than 256x256) it shouldn't be much different from single screen games.
The only real difference is that you can't directly use the objects' coordinates for their sprites, instead you have to subtract the camera's coordinates from them in order to find the coordinates of the sprites on the screen.
Seriously, scrolling in one axis only is dead simple, I just don't recommend it to newbies because sometimes it's hard for them to grasp the concept of the progressive name table updates (because all tutorials just dump the whole screen to VRAM at once and they don't really know how to do it any other way).
tokumaru wrote:
I just don't recommend it to newbies because sometimes it's hard for them to grasp the concept of the progressive name table updates
Would this GIF make the concept easier for them to grasp?
tokumaru, what about moving the objects and check collisions with them? I hope, you don't going to process all the objects on the level all the time? What about level size limitiation? With 12:4 you can have just 16 screens, it is not that much - it is about the same width as
one SMB level. What about storing the level? Without metatiles one level of SMB would take about 16K, and metatiles surely make collision more complex. It is not that easy, especially for someone who had the problems you can see above in the thread.
Shiru wrote:
tokumaru, what about moving the objects and check collisions with them?
It's the same as in single-screen games, only the "screen" is actually the level, and it can be much larger than 256x256. Instead of 8-bit comparisons you'll be making 16-bit ones, that's the only difference in movement and collisions.
Quote:
I hope, you don't going to process all the objects on the level all the time?
Ah, object spawning is a very different thing. Not very complex if done in only one axis though. It can be confusing for a newbie, that's for sure.
Quote:
What about level size limitiation? With 12:4 you can have just 16 screens, it is not that much
But then it's your fault for being stingy and not using 3 bytes for coordinates. Most operations (such as collisions and sprite drawing) can be done in 16-bits, and only objects that need sub-pixel movement have one (or two, if horizontal and vertical precision is needed) extra byte, used exclusively for physics.
Quote:
What about storing the level? Without metatiles one level of SMB would take about 16K, and metatiles surely make collision more complex.
Metatiles are not specific to scrolling games though. Without map compression, even non-scrolling games can't have a lot of screens.
Quote:
It is not that easy, especially for someone who had the problems you can see above in the thread.
Agreed. Even if the scrolling itself isn't such a big challenge, typical platformers make use of many other concepts that are not trivial for new programmers.
With single screen game you can just unpack level from metatiles or anything in RAM buffer as simple tile/collision map, and have simple collision code, that's what I meant.
Shiru wrote:
With single screen game you can just unpack level from metatiles or anything in RAM buffer as simple tile/collision map, and have simple collision code, that's what I meant.
I see. Well, in scrolling games you can use the same type of progressive updates you use for the name tables with the collision data, it's basically the same principle.
Personally, I prefer to access level data directly from ROM, using compression schemes that allow random access.
Let me try to classify all the important parts of a scrolling game, the ones that might differ from single-screen games.
First there's the scrolling engine itself, which handles the rendering of the background:
- A "camera" that hovers over the level and decides when it's necessary to draw new rows/columns;
- A method to extract the required rows/columns of tiles and attributes from the level map;
- A method to calculate the target address for the tiles/attributes in VRAM and a procedure to write them there during VBlank;
Then there's the game logic:
- Management of object/enemy (re)spawning based on a sorted list of entities;
- Collisions between objects, which can be done by directly comparing their coordinates (coordinates must have a number of bits compatible with the dimensions of the largest level);
- Collisions between objects and the background, which require access to the level's collision data (can be progressively decompressed or accessed directly from ROM);
And finally there's the sprites:
- Some objects are positioned with absolute coordinates, such as the HUD or anything else that's part of the screen rather than the level;
- Level objects/enemies must have their screen coordinates calculated by subtracting the camera's position from their own;
Quote:
It's the same as in single-screen games, only the "screen" is actually the level, and it can be much larger than 256x256. Instead of 8-bit comparisons you'll be making 16-bit ones, that's the only difference in movement and collisions.
Trust me, even if you're not scrolling, use 16-bits by all means !
I had to rewrite everything to convert from 8-bits to 16-bits because I wasn't able to handle sweet movement, and couldn't handle overflows properly.
I like to use the low 4-bits for sub-pixel movement, the mid 8-bits for pixels and the high 4-bits for screens. I only use screen 0, 1 and -1 (1 and -1 are overflow) in my game but you could go up to 16 screens in both directions with this system.
About object spawning I've never actually tried this but if I were to do it, I'd just do it so that the game engine computes the distance between the edge of the screen and the object, and if too high just don't call that object's AI routine, so that it freezes. It makes sense to me and it will automatically work again if the player enters that area again. I don't know why so many games does terrible things at enemies respawning (Ninja Gaiden, Mega Man, ....)
And when I say "computes the distance", I of course not mean the true pythagorian distance, but just use the min of X and Y distance.
Bregalad wrote:
if I were to do it, I'd just do it so that the game engine computes the distance between the edge of the screen and the object, and if too high just don't call that object's AI routine, so that it freezes.
Super Bat Puncher does exactly this, and it uses SNROM (which has PRG RAM).
Quote:
I don't know why so many games does terrible things at enemies respawning (Ninja Gaiden, Mega Man, ....)
RAM limits, I guess.
Bregalad wrote:
Trust me, even if you're not scrolling, use 16-bits by all means !
I second this. Even in single-screen games you may need to scroll in and out of the screen, and smooth acceleration and deceleration.
Quote:
I wasn't able to handle sweet movement
Did you mean "smooth"? I'm trying really hard to figure out what "sweet movement" could be... XD
Quote:
I'd just do it so that the game engine computes the distance between the edge of the screen and the object, and if too high just don't call that object's AI routine, so that it freezes.
Depending on the total number of objects, even just checking how far they are from the camera could be too slow. With 200 or so objects/enemies in a level you would spend several (like, around 50 I'm guessing) scanlines just comparing coordinates.
tepples wrote:
Quote:
I don't know why so many games does terrible things at enemies respawning (Ninja Gaiden, Mega Man, ....)
RAM limits, I guess.
It might be more difficult without the extra RAM, but definitely not impossible.
tokumaru wrote:
Well, if you can understand the basic concept and are feeling confident about implementing this, then go for it! =)
Thank you so much! The positivity is very good! It helped me to decide to try my best... then I read through the rest of you and Shiru talking and it made me realise that this will be an incredible journey.
tokumaru wrote:
Bregalad wrote:
Trust me, even if you're not scrolling, use 16-bits by all means !
I second this. Even in single-screen games you may need to scroll in and out of the screen, and smooth acceleration and deceleration.
Ok, thanks Bregalad and tokumaru. I remember hearing that from yall during my first nes game attempt... I think I was username "aaable" on yall's old forum. I dont remember much from then.
In my lst file I just found out that
$D0D1 skips down through a lot of famitone code. There are instructions on the right but the address never changes from
$D0D1 for quite a while. Why..what..how?
edit: Maybe it's because that part of the code isn't used or is disabled; i remember telling famitone to not include DPCM code. Maybe that is correct!
Code:
enum $000 ;asm6's way of declaring vars in ram
;a is for A register
;x is for X register
;y is for Y register
currControllerButtons .dsb 1,0
lastControllerButtons .dsb 1
newControllerButtons .dsb 1
read .db #31
oX .db #$8b ;($0004 is X)
oY .db #$8b ;($0005 is Y)
ok, I would like the X and Y coordinates to be equal to
#$8B. It never makes it to
#$8B.... the sprite just stays at
#$00... unless, I press up down left or right then it
incrementsORdecrements oXORoY by
1; the sprite then responds accordingly!
...Do I have to set oX and oY manually?
Why?
You can't make RAM anything on start up, you have to store it with your program there if you want it to start at a value.
Why? In asm6 there's .dsb
asm6's README.TXT wrote:
DSB, DSW (also DS.B/DS.W)
Define storage (bytes or words). The size argument may be followed
by a fill value (default filler is 0).
DSB 4 ;equivalent to DB 0,0,0,0
DSB 8,1 ;equivalent to DB 1,1,1,1,1,1,1,1
DSW 4,$ABCD ;equivalent to DW $ABCD,$ABCD,$ABCD,$ABCD
Why did they make this... changed my code to
oX .dsb 1,$8b and it still started the sprite at
0,
0.
Your program you put in the ROM isn't RAM.When the system boots up, it has garbage in it. Usually it's cleared on boot up, you have to store the variables you need in it at the spot you want them. Before your program starts, do a LDA #$NUMBER STA Label to put it there, there's no way around it, you have to.
Thank you 3gengames for your help.
...Why would loopy add a pointless way of declaring a storage fill value in his assembler?
dsb
unregistered wrote:
Why would loopy add a pointless way of declaring a storage fill value in his assembler?
dsb
Because .dsb can also be used in ROM, where you can actually fill values.
tokumaru wrote:
unregistered wrote:
Why would loopy add a pointless way of declaring a storage fill value in his assembler?
dsb
Because .dsb can also be used in R
OM, where you can actually fill values.
Ah, that's great tokumaru, thank you.
So, when using a mapper you could specify
RAM; right?
No, nothing in the world can put what you compile in RAM at startup except a Boot ROM which is wasteful and not going to happen because it's not even in the ROM for it to know what to put there.
If you want something to be in RAM when your program starts, copy it from ROM to RAM in the init code. This is exactly how the cc65 runtime library handles .segment "DATA".
tepples wrote:
If you want something to be in RAM when your program starts, copy it from ROM to RAM in the init code.
How would I do that?
No offense....but you know how computers work right? You should maybe read up more on programming and more 6502. This something really, really..REALLY basic.
It'd go something like this for a small chunk of data to copy to RAM:
Code:
;Somewhere in RAM:
MoveToRAM: .rs $100
;Somewhere in ROM, put:
SomeLabel: incbin "Data256bytes.bin"
LDX #$00
Loop:
LDA SomeLabel,X
STA MoveToRAM,X
DEX
BNE Loop
;Done.
It's the most straightforward a program can be.
3gengames wrote:
No offense....but you know how computers work right? You should maybe read up more on programming and more 6502. This something really, really..REALLY basic.
3gengames wrote:
It'd go something like this for a small chunk of data to copy to RAM:
Code:
;Somewhere in RAM:
MoveToRAM: .rs $100
;Somewhere in ROM, put:
SomeLabel: incbin "Data256bytes.bin"
LDX #$00
Loop:
LDA SomeLabel,X
STA MoveToRAM,X
DEX
BNE Loop
;Done.
It's the most straightforward a program can be.
3gengames, thank you for that explaination!
How can I choose
RAM instead of
ROM?
If you make the first byte a BRANCH to the part of the program you want, you can just so JMP Label and it'll run the code from RAM.
Okay. Here's a quick crash course.
So this is what an assembler does: It creates a sequence of bytes based on the instructions you give it.
Code:
reset:
sei
ldx #$00
becomes $78, $A2, $00. This is rom. If you opened your assembled rom in a hex editor and searched for this byte sequence you would find it. A guide like this:
http://www.obelisk.demon.co.uk/6502/reference.htmlTells you what byte each instruction is assembled as, but that is not that important.
The next thing to know, is a label doesn't actually take any rom. What takes up space are references to it. In the code above, reset takes no rom. References to it are actually stored with the instruction.
So this:
Code:
reset:
sei
ldx #$00
lda reset
would be assembled as
$78, $A2, $00, $AD, (low byte of reset), (high byte of reset).
All these bytes are actually part of your games binary (the .nes rom), so they are rom. To further bring this home, you can actually load an instruction's opcode. Remember how I said sei becomes $78?
So what do you think is stored in the accumulator when I lda reset in the code above? $78. lda reset+1? $A2.
When you directly load a number (lda #$00), the actual number (here it's 0) is stored in rom. $A9, $00 would be what is assembled. When you're referring to an address, you don't include the # part.
RAM is $0000-$07FF. Whenever you load a number from any of those locations, you're loading from RAM. These numbers can be changed. To put actual code in RAM is easy. You just look up the opcodes. Remember the byte stream of the code above?
$78, $A2, $00 was sei, ldx #$00
Code:
lda #$78
sta $00
lda #$A2
sta $01
lda #$00
sta $02
jmp $0000
This stores code into RAM, then jmps there and runs it. The NES CPU makes no distinction between RAM and ROM when it's running through instructions.
RAM can't be changed before the program is run. If you want a location in RAM to be a certain number, you need to set it to that number at startup. It's as easy as loading the number you want, and storing it there. If you want to load the number from rom, you can incbin a byte stream, and load each value then store it in RAM, but I just use immediate addressing. What I think is being suggested to you is something like this:
You want ram locations $00, $01, $02, $03 to contain $F3, $FD, $08, $23 respectively. So you create a binary file that contains the bytes $F3, $FD, $08, and $23.
Then you incbin that file after an address.
Code:
zeropageinitialvalues: incbin "zeropagebinary.bin"
Then you run code like what 3gengames wrote.
For reference, I'd just do this:
Code:
lda #$F3
sta $00
lda #$FD
sta $01
lda #$08
sta $02
lda #$23
sta $03
since it's easier to change a number, and you won't necessarily be storing numbers so sequentially.
If I were you, I would try to read and understand thing like the difference between lda #$00 and lda $00 before doing anything else. I might also try to completely rewrite whatever you've got, since if you didn't know this stuff, it has gotta be messy.
And also, I said use a branch because while you can only go about 127 bytes in either direction, the position doesn't matter wherever it is, while jumps will ALWAYS goto where it was compiled to go to weather it was moved or not.
unregistered, you appear to be very lost, and not clear enough about what you want.
RAM is where your variables (i.e. dynamic data) sit. Variables are values that will change as the program runs, such as the positions of objects, number of lives, things like that. RAM is always "empty" (it's not really empty, but since its contents are somewhat random we should think of it as empty, although the correct term is "uninitialized") when the NES first starts. Before using our variables, we must initialize them.
When you do this:
Code:
enum $0000
currControllerButtons .dsb 1
lastControllerButtons .dsb 1
You are just telling the assembler where your variables should be stored. currControllerButtons will be at memory location $0000 and lastControllerButtons will be at memory location $0001. This simply declares the variables but they will be uninitiated when the program starts, so you have to initialize them yourself in the code. Nobody is gonna do it for you. So in order to do what you want you have to first declare the variables:
Code:
enum $0000
;(...)
oX .dsb 1
oY .dsb 1
And then, before using those variables in the program you have to initialize them:
Code:
lda #$8b
sta oX
sta oY
Thank you everyone for helping.
tokumaru wrote:
unregistered, you appear to be very lost, and not clear enough about what you want.
RAM is where your variables (i.e. dynamic data) sit. Variables are values that will change as the program runs, such as the positions of objects, number of lives, things like that. RAM is always "empty" (it's not really empty, but since its contents are somewhat random we should think of it as empty, although the correct term is "uninitialized") when the NES first starts. Before using our variables, we must initialize them.
When you do this:
Code:
enum $0000
currControllerButtons .dsb 1
lastControllerButtons .dsb 1
You are just telling the assembler where your variables should be stored. currControllerButtons will be at memory location $0000 and lastControllerButtons will be at memory location $0001. This simply declares the variables but they will be uninitiated when the program starts, so you have to initialize them yourself in the code. Nobody is gonna do it for you. So in order to do what you want you have to first declare the variables:
Code:
enum $0000
;(...)
oX .dsb 1
oY .dsb 1
And then, before using those variables in the program you have to initialize them:
Code:
lda #$8b
sta oX
sta oY
I understand at least most of that, but for this code:
Quote:
oX .dsb 1,#$8b
How could one get that code to work successfully? Why doesn't
oX equal
#$8b? Those are the two questions that went through my head... I'm sorry for my muddiness tonight. My time sheet says I've worked over 10 hours on the "game" today... too long... time to sleep.
LDA #$NUMBER
STA Label
Wil make it equal what you need it to, you HAVE TO do that.
unregistered wrote:
Quote:
oX .dsb 1,#$8b
How could one get that code to work successfully?
Forget about this code, this is not how you do it! You simply can't declare the variable and give it a value at the same time, in assembly this is impossible (if someone mentions machines that load code into RAM only to confuse unregistered even more, I'll kill you). This has to be done in two steps: first declare the variable and later in the code you give it a value.
Quote:
Why doesn't oX equal #$8b?
Because RAM is uninitialized on start up, period. There's no way to change that. No matter what value you try to assign it, the variable will always contain an unpredictable value on start up, this is just how RAM works. You absolutely must use LDA / STA to assign values to your variables.
I'll do my take on this.
Imagine a big deal of equally-sized boxes, like at a post office.
That's RAM.
When the "world starts", they're all empty. Completely empty, with nothing in them*. All the boxes are numbered, of course, but these numbers... are not terribly helpful. What we need are some labels. So, we take out the dymo-tape...
...and start labeling! In ASM6, when you do .dsb and .dsw in the .enum range of $0000-$07FF, this is exactly what you're doing: labeling. That way, when you get your instructions, they can be written in the form "put this letter in the box for Mr. Howe" as opposed to "put this letter in the 53rd box." Note that you can reserve multiple boxes this way.
Of course, the act of putting a letter in to the box doesn't just happen. You have to do it yourself! The world starts and all the boxes are empty (or "uninitialiazed" as tokumaru pointed out).
As before, in your initiation code before any of the actual action happens, just load whatever value into the accumulator (LDA #letter) you want and then store it in the RAM (sta MR_HOWE).
Finally note that .dsb and .dsw are general purpose and can be used to flood the ROM (the part of the game that is "written in stone") with values. That's what you're trying to do
in RAM but this operation only works for ROM. In other words, that use of those directives only works on addresses $8000-$FFFF (or if you use more than one PRG bank and/or a mapper, whatever is applicable then).
* - not necessarily true; may have garbage values that are
not zero. Check standard init code. The samples you'll find have a place to reset all RAM to zero.
tokumaru wrote:
You simply can't declare the variable and give it a value at the same time, in assembly this is impossible (if someone mentions machines that load code into RAM only to confuse unregistered even more, I'll kill you).
Load and run addresses for segment ".DATA". Kill Ullrich von Bassewitz.
That's a very good analogy, booker. Hopefully this ought to get message across.
BTW, tepples, I knew you'd be the one to do it.
Ah! Yes, booker, great analogy! Thank you so much!
It really helped me recieve the entire message that yall were trying to give me! RAM is not ROM, it always starts uninitalized.
tokumaru, my friend, please don't kill tepples. : ) I didn't follow his link. : )
ahhh, the girl starts at (#$8b, #$8b) now!!!!!!!!!!!!!!!!!!! Tomorrow is going to be great! S cr oll in g!!!
Ok, so I'm at the point now where I'm tryiing to write the code. My first goal is to get the screen to scroll to the next nametable as our character makes her way across the floor she starts on. This is susposed to be an easy goal to achieve. So far:
1.) I've set the PPU to mirror the nametables vertically.
2.) Both nametables have been set up correctly.
3.) Our lady character moves forward and backward very easily because the sound really works now.
The next step to reach my goal is to check if the lady character is far enough over to the right of the screen (then scrollling starts). I'm going to use an iterative construct to acomplish that.
Is scroll always at the bottom left corner of the screen? If
scroll is incremented every frame the screen scrolls over to the left. If the screen can make it over to 255 most of all of the second nametable would be shown. But you have to switch which nametable is on the left to get the rest of the second nametable to appear.
The X,Y coordinate in the scroll register is that of the pixel at the top left corner of the screen, which does happen to be more or less equivalent to the bottom left corner but only in single-screen or vertical mirroring. To switch which nametable is at the top left corner, use bits 1 and 0 of PPUCTRL ($2000). One might think of these as the high bits of the scroll register.
I still don't understand why does the nametable on the left have to be switched for the right side of the second nametable to appear? I stopped my scroll at 255 and it is already showing most all of the second nametable. I'm so confused.
Thank you for reminding me it's the top left pixel!
That helps! And it's nice to know that I can think of it being the bottom left corner because I'm not horizontally mirroring.
To set the starting nametable, change bit 0 of the PPU control register at $2000. Clearing it to 0 will put nametables 0 and 2 on the left side of the screen with 1 and 3 to the right. Setting it to 1 will put 1 and 3 on the left, and 0 and 2 on the right.
tepples wrote:
To switch which nametable is at the top left corner, use bits 1 and 0 of PPUCTRL ($2000).
So setting bits 1 and 0 of PPUCTRL to
10 will put nametables 2 and 0 on the left and 3 and 1 to the right? And setting PPUCTRL to
xxxxxx11 will put nametables 3 and 1 on the left and 2 and 0 on the right?
When you're "camera" moves the screen, once you hit the nametable, how will you scroll to the right more? To do that, you must set the main nametable to be the one you're scrolled onto all the way, because to scroll across it more, it has to be the main table since you can't scroll anymore if you set the scroll to the max.
In my head the top left of the screen/camera can make it to 255. Then wouldn't almost all of NameTable 1 be shown?
Then how do you scroll to the right of that nametable? By setting it as the main one, resetting your scroll, and then scrolling right again. I wish I could make gif's, as it'd be way easier to show than tell.
3gengames wrote:
Then how do you scroll to the right of that nametable?
I dont want to scroll to the right of
Name
Table
1 right now.
3gengames wrote:
I wish I could make gif's, as it'd be way easier to show than tell.
oooh well I just used bunnyboy's graphic from
this page. Just copied it over from there to my web account server... You can make gif's. Try
the gimp!
Okay, as long as you don't need to scroll to the right of the 2nd nametable, it should be fine then.
Ah ha! Ok, thanks 3gengames!
unregistered wrote:
Ah ha! Ok, thanks 3gengames!
Yep, and I have failed at making that GIF, heh. And same goes for the bottom, as long as you don't need to scroll past there, you're fine.
Good luck.
3gengames, thank you.
---
For
sbc,
Code:
Operation: A - M - -C -> A
Note: -C = Borrow
I have
clc right before
sbc... but since
-C is the Borrow it seems, to me, that it is really
¬C. So my idea is to
sec instead. (I'm trying to clear the borrow.) What do yall think?
3gengames wrote:
Set subtract. Clear Add.
Rock (yes)!
Thanks!
Praise God!!
The screen scrolls right when our lady travels right close to the edge of the screen!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Thank you so much tepples and 3gengames and tokumaru and booker and Kasumi and Bregalad and Shiru!
I also have a question to ask: How do I make it so that the screen scrollls over the same speed as our lady travels? Right now she reaches the right side of the screen before she makes it to the end of
Name
Table
1.
Make a game object called the "camera" and fix the scroll position relative to it.
tepples wrote:
Make a game object called the "camera" and fix the scroll position relative to it.
Ok I created a byte called "
Camera". Now there's 2 variables that are similar to
Camera:
oX and
scroll. I should move both the screen and the character by
Camera, right? Should
Camera be 16bits so it can keep track of what nametable it is on? Here is
good camera discussion.
Your camera coordinates should be 16-bit. /since your game only scrolls horizontally, you only need a "CameraX" variable, because "CameraY" would always be 0.
You should make the camera follow the player using whatever logic you see fit. Some games move the camera by the same amount as the player every frame, others move it only if the player has gone past a certain point. No matter what rules you use, the camera must always follow the player.
The coordinates of the camera can be directly used to set the scroll, you don't need more variables for that. Another important thing is that the coordinates of the sprites must be calculated by subtracting the coordinates of the objects by the coordinates of the camera. With scrolling, object coordinates can no longer be used directly as sprite coordinates, because they are relative to the level and not the screen. That subtraction does the convertion from level coordinates to screen coordinates.
tokumaru wrote:
Your camera coordinates should be 16-bit. /since your game only scrolls horizontally, you only need a "CameraX" variable, because "CameraY" would always be 0.
You should make the camera follow the player using whatever logic you see fit. Some games move the camera by the same amount as the player every frame, others move it only if the player has gone past a certain point. No matter what rules you use, the camera must always follow the player.
The coordinates of the camera can be directly used to set the scroll, you don't need more variables for that. Another important thing is that the coordinates of the sprites must be calculated by subtracting the coordinates of the objects by the coordinates of the camera. With scrolling, object coordinates can no longer be used directly as sprite coordinates, because they are relative to the level and not the screen. That subtraction does the convertion from level coordinates to screen coordinates.
tokumaru, thank you!
here is code from
the scrolling tutorial
Code:
NTSwapCheck:
LDA scroll ; check if the scroll just wrapped from 255 to 0
BNE NTSwapCheckDone ;if not, skip this NameTable Swap
NTSwap:
LDA currNameTable ; load current nametable number (0 or 1)
EOR #$01 ; exclusive OR of bit 0 will flip that bit
STA currNameTable ; so if nametable was 0, now 1
; if nametable was 1, now 0
inc CameraX+1
NTSwapCheckDone:
LDA #$00
STA $2003
And I added the
inc CameraX+1. After starting our game and then waiting for a while
CameraX+1 becomes very incremented. That isn't good. If our character waits at the beginning of "the game" the scroll variable stays at 0. So each time it runs through this code it changes
currNameTable... that's not good either, right? I'm unsure of changing the code because there may be a reason that bunnyboy kept it like this.
Do you have a helpfull idea?
edit: And what if during the game one person stops traveling with scroll at exactly 0 and waits for a bit? It would mess up the same way, I think.
That code is terrible. The camera can only move 1 pixel at a time (otherwise you might miss the 0 that triggers the nametable switch), and the person who wrote it failed to realize that you don't need any sort of special checks to switch nametables: the lowest bit of CameraX+1 (i.e. the 9th bit of the overall coordinate) tells you which name table to display.
You can do something like this:
Code:
lda currNameTable
and #$fe ;clear the lowest bit
sta currNameTable
lda CameraX+1 ;get the high byte of the camera
and #$01 ;keep only the lowest bit
ora currNameTable ;combine with the other value
sta currNameTable ;this is what you'll write to $2000 when setting the scroll
And the low byte of CameraX (CameraX+0 or simply CameraX - I like the "+0" because it helps me identify multi-byte variables, even though in practice adding 0 is useless) is what you'll write to $2005 when setting the X scroll. There's no need for a "scroll" variable.
That way you can modify CameraX anyway you want, no need to change it pixel by pixel, which mean your character can move faster if you want.
tokumaru wrote:
Code:
lda currNameTable
and #$fe ;clear the lowest bit
sta currNameTable
Does
currNameTable ever have a value other than 0 or 1?
After this code is run
currNameTable is always 0 right?
tokumaru wrote:
Code:
lda CameraX+1 ;get the high byte of the camera
and #$01 ;keep only the lowest bit
ora currNameTable ;combine with the other value
sta currNameTable ;this is what you'll write to $2000 when setting the scroll
What do you mean
ora currNameTable to say? X OR False == X ?
unregistered wrote:
Does
currNameTable ever have a value other than 0 or 1?
Heh, I don't know, I haven't seen the rest of the code. Even if the name suggests it only specifies a nametable, I assumed it was the value that's written to $2000 (the lower 2 bits select the nametable, while the upper bits do other things).
Quote:
After this code is run currNameTable is always 0 right?
The purpose is to clear bit 0, but keep all the others.
tokumaru wrote:
Is the
ora currNameTable a waste? X OR False == X ?
(I'm sorry, this is me asking stupid questions.)
OR can be used to set bits. First I cleared the bit (AND 0 always results in 0) and then I OR'ed it with the nametable bit. 0 OR 0 = 0, while 0 OR 1 = 1, so I essentially transferred the bit from one variable (CameraX+1) to the other (currNameTable).
You can do this in many different ways, but the code I wrote is more "compatible" with the one you had before, so I thought it would be easier for you to understand. Honestly I'd rather just do this to set the scroll:
Code:
lda CameraX+1
and #$01 ;keep only the first bit
ora Cur2000Settings ;combine with the other PPU settings
sta $2000
lda CameraX+0
sta $2005 ;set the horizontal scroll
lda #$00
sta $2005 ;set the vertical scroll
This takes care of everything. You just have to put all your $2000 configuration in "Cur2000Settings" and keep the lowest bit clear.
I'm sorry for saying some of the code that you spent valuable time typing out for me was a waste. My sister helped me understand my mistake but I asked her too late. Ok. Thank you for your response tokumaru. Have a good night.
Maybe you should have your sister code the game then! XD
haha! Thanks, your code works excellent! I can increase the speed of the scrolling like you said!
Time for lunch...
(I've been working on learning and using this code for almost 3 hours.)
Also, maybe make it so that you have 2 variables for the screen nametabale and other stuff, so that when you do a $2000 and $2001 store, you don't have to worry about your code running out of VBlank.
3gengames wrote:
Also, maybe make it so that you have 2 variables for the screen nametabale and other stuff, so that when you do a $2000 and $2001 store, you don't have to worry about your code running out of VBlank.
I've never planned on doing a $2000 and $2001 store except the first one, but I read bunnyboys code and it says:
Code:
;;This is the PPU clean up section, so rendering the next frame starts properly.
LDA #%10010000 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
ORA nametable ; select correct nametable for bit 0
STA $2000
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
So I added the $2000 store that tokumaru suggested. Maybe future $2000 stores would be good to have.? Thanks 3gengames.
Writes to register $2000 are ignored for the first
30000 cycles!
Well my first $2000 store didn't matter.....
Yes, in your startup code you should have something like...
Setup stuff;
BIT $2002
-
BIT $2002
NPL -
Clear RAM/Stuff;
-
Bit $2002
BPL -
MainCode after
and then you need 2 variables in RAM, 1 for the $2000 write, and 1 for the $2001 write. After you wait the 2 frames with the BIT loops in the beginning, you can just write the statuses to RAM, and then turn on the NMI with a $2002 write outside of VBlank to start it, but at the end of your VBlank you can then write the writes from the 2 RAM spots to $2000 and $2001, because then other parts of your program that need to edit attributes of the screen like moving the nametable to another one or stuff, can do so without custom and bulky VBlank code to catch those and add them outside of the preprocessing. Make sense?
A bad example would be using the intensity bits to show the game's paused. [Bad idea to do something like that, but makes a good example.] If you don't use the method of storing $2000/$2001 write from RAM, then your program will have to set a flag and catch inside VBlank that a certain program needs to run and eat up precious cycles. But with the other method, you can over run your main game engine by making the pause turn on the intensity bits from the piece of RAM instead of trying to catch that it needs to happen in VBlank. Although the best way for this to seperate your NMI and your game code entirely and just do a INC FRAME/RTI, although there's still benefits from writing to $2000/$2001 inside VBlank from RAM though.
ok. : )
---
Before continueing scrolling beyond two screens... I think I should introduce our character to the level. Right now it doesn't rely on anything coded in the level. We want this lady to stand on the ground.... how should we do that? I have the tile codes for the ground parts/sprites.
Well, start making parts of the entire system. Stuff like background collision [Possibly worth using 64 bytes as a buffer to do calculations outside of vblank for the buffer] and stuff like that. Hit detection, object creation/deletion, etc.
How are your levels stored? Just like you read them in order to draw to the nametables, you have to read them to check for collisions. Each block in the level must have its own solidity information (you can use a table for that).
You'll have to check key points around the character and make decisions depending on the solidity of the blocks that surround her. For gravity, for example, you should always check the block right below the character's feet, and if it's not solid the character must move down (i.e. fall).
Here are some posts where I talked about background collision, see if you can get something useful from them:
http://nesdev.com/bbs/viewtopi ... =4617#4617
http://nesdev.com/bbs/viewtopi ... 0918#40918
http://nesdev.com/bbs/viewtopi ... 9897#59897
http://nesdev.com/bbs/viewtopi ... 0374#60374
I'm not gonna lie to you, this will be hard to get right. This is the part where your little NES program stops being a playground and starts becoming a game. At this point, everything you program needs to be more robust and integrated, everything must be a "system", rather than just random blocks of code with immediate results on the screen. This is serious business.
How good of practice is it to buffer a map with either 64 bytes [1 pit per tile] or 2 bits per tile and make it 128 bytes and use that to do all calculations out of vblank and such?
Considering that there are usually several objects that need collision tests against the background, the time taken to do this will easily be more than 20 scanlines, so that fact that this HAS to be done outside of VBlank is not even up for discussion.
Now, if you use only 1 bit per tile, you can only mark the tiles as either solid or empty. Forget about water, lava, blocks that hurt the player, and so on. This might be OK for some simple games, but definitely too limited for the majority of them. With 2 bits it gets a little better, but not much.
The ideal way to do it, in my opinion, is to use metatiles and associate all collision information to them, rather than to individual tiles. Also, instead of buffering whole screens of collision data, just read the metatiles from the level map (which may or may not be buffered) and then look for their collision information. I don't expect newbies to do this on their first try though.
unregistered (a few pages back) wrote:
Thank you all for the honest and helpfull advice. Scrolling will be ok for me to attempt, I think, because there are two big thick books that remind me of programming with Torque, a 3D game engine. It was a hard and immensly difficult task, but God was there with me and he helped me figure it out.
We made an A and I got credit for an internship for that class!
My
mom helped me remember the last part of that. I think I can do this scrolling... my
sister helped me to find
this Nerdy Nights scrolling article. And it has made some sense so far... I've made it to the drawing new columns part. Will read this again after supper.
unregistered wrote:
I'm sorry for saying some of the code that you spent valuable time typing out for me was a waste. My
sister helped me understand my mistake but I asked her too late. Ok. Thank you for your response tokumaru. Have a good night.
You've got a quite interesting family!
3gengames wrote:
Well, start making parts of the entire system. Stuff like background collision
Thank you 3gengames.
Found this thread; it was my starting place to learn about background collision.
http://nesdev.com/bbs/viewtopic.php?t=7579&highlight=background+collisiontokumaru wrote:
I'm not gonna lie to you, this will be hard to get right. This is the part where your little NES program stops being a playground and starts becoming a game. At this point, everything you program needs to be more robust and integrated, everything must be a "system", rather than just random blocks of code with immediate results on the screen. This is serious business.
This sounds like fun.
The posts you mentioned/linked are so great! Thank you tokumaru!
~J-@D!~ wrote:
You've got a quite interesting family!
Thank you!
I love them all so very much!
tokumaru wrote:
Considering that there are usually several objects that need collision tests against the background, the time taken to do this will easily be more than 20 scanlines, so that fact that this HAS to be done outside of VBlank is not even up for discussion.
Ok, so collision should be done outside of VBlank. Does this mean collision code should go in to the empty loop that "; Transfer
s control to the VBLANK routines"?
unregistered wrote:
Does this mean collision code should go in to the empty loop that "; Transfers control to the VBLANK routines"?
Not necessarily. If you currently have your whole engine running in the NMI (which I assume you do, since you have an empty loop), just be sure to run the collision code
after the VRAM updates (i.e. sprite DMA, name table updates, setting the scroll, etc.).
The NMI fires as soon as VBlank starts. Since VBlank is quite short, you can't waste time doing tasks that could be performed outside of VBlank, so you should rush to the video updates and get them done as soon as possible. THEN you do the other stuff, such as music, collision and all other game logic.
It does feel kinda "backwards", but since you are doing it all inside the NMI then there's no other way.
It's not so backwards if you think of it as preprocessing and thinking of it like you're doing next frame's work now.
That's why I said "kinda" backwards. What's more confusing to a newbie, IMO, is that the first time the NMI runs there are no updates to do, because the frame logic hasn't run yet. To work around that you have to implement a "FrameReady" or "FirstFrame" flag, to skip the first session of VRAM updates.
I just preprocess the first frame of my game myself, turn on the screen/nmi when it hits and is ready, and then hits the groun running, no variables needed. See, unregistered, there's many ways to go about everything. Youcan do different tests on mini programs and find what works best for you and the mini programs will really help you get a better understanding each one.
tokumaru wrote:
the first time the NMI runs there are no updates to do, because the frame logic hasn't run yet. To work around that you have to implement a "FrameReady" or "FirstFrame" flag, to skip the first session of VRAM updates.
You have to do that anyway in an NMI-driven game engine in case your game logic runs over 27 kcycles.
tokumaru wrote:
unregistered wrote:
Does this mean collision code should go in to the empty loop that "; Transfers control to the VBLANK routines"?
Not necessarily. If you currently have your whole engine running in the NMI (which I assume you do, since you have an empty loop)
What normally would go into the empty loop? Are there any downsides to having a whole engine running in the NMI?
unregistered wrote:
What normally would go into the empty loop?
There are 3 main ways to set up NES programs:
1. Game logic in the main loop, video and audio updates in the NMI:The whole game logic (i.e. movement, collisions, etc.) is in the main loop, and once it finishes the program waits for the NMI to fire. When it fires, it performs all the necessary video and audio updates, and then control is returned to the main loop, where the next frame will be processed.
2. Game logic and video and audio updates in the main loop, the NMI only sets a flag:The whole game logic is in the main loop, and once it finishes the program waits for VBlank. The NMI will simply set a flag to indicate that VBlank has started and then return, at which point the main loop will proceed to perform video and audio updates and loop back to process the next frame.
3. Video and audio updates and game logic in the NMI, the main loop is empty:Video and audio updates based on the previous frame are performed first, and then the game logic of the following frame is calculated. Absolutely nothing is done in the main loop. This is considered the most newbie-friendly method because the programmer doesn't have to worry about 2 "threads" running in parallel, and the NMI automatically firing 60 (or 50, if it's a PAL NES) times per second is a very convenient way to time the program without any effort (i.e. no need to wait for VBlank).
Quote:
Are there any downsides to having a whole engine running in the NMI?
Yes, if you have raster effects timed from the start of the frame (such as a status bar). Methods 2 an 3 suck for handling cases when the game logic takes too long (i.e. longer than a frame) to complete and you have such effects. Unpleasant visual glitches and music slowdown are the side effects of this (the program can even crash if sprite 0 hits are involved). Method 1 is the best to avoid these problems.
Simple games will hardly use too much CPU time unless they are very badly programmed, but complex games with scrolling, large number of active enemies, and so on, will certainly have a few sections with slowdown, and this is an issue for them.
tokumaru wrote:
unregistered wrote:
What normally would go into the empty loop?
There are 3 main ways to set up NES programs:
1. Game logic in the main loop, video and audio updates in the NMI:The whole game logic (i.e. movement, collisions, etc.) is in the main loop, and once it finishes the program waits for the NMI to fire. When it fires, it performs all the necessary video and audio updates, and then control is returned to the main loop, where the next frame will be processed.
2. Game logic and video and audio updates in the main loop, the NMI only sets a flag:The whole game logic is in the main loop, and once it finishes the program waits for VBlank. The NMI will simply set a flag to indicate that VBlank has started and then return, at which point the main loop will proceed to perform video and audio updates and loop back to process the next frame.
3. Video and audio updates and game logic in the NMI, the main loop is empty:Video and audio updates based on the previous frame are performed first, and then the game logic of the following frame is calculated. Absolutely nothing is done in the main loop. This is considered the most newbie-friendly method because the programmer doesn't have to worry about 2 "threads" running in parallel, and the NMI automatically firing 60 (or 50, if it's a PAL NES) times per second is a very convenient way to time the program without any effort (i.e. no need to wait for VBlank).
Quote:
Are there any downsides to having a whole engine running in the NMI?
Yes, if you have raster effects timed from the start of the frame (such as a status bar). Methods 2 an 3 suck for handling cases when the game logic takes too long (i.e. longer than a frame) to complete and you have such effects. Unpleasant visual glitches and music slowdown are the side effects of this (the program can even crash if sprite 0 hits are involved). Method 1 is the best to avoid these problems.
Simple games will hardly use too much CPU time unless they are very badly programmed, but complex games with scrolling, large number of active enemies, and so on, will certainly have a few sections with slowdown, and this is an issue for them.
tokumaru, wow, you could write an NES book!
Thank you so much for all of this info! I'm going to try Method 1.
Does Method 1 have an empty loop in it where it waits for NMI?
unregistered wrote:
tokumaru, wow, you could write an NES book!
Nah, I get things wrong more often than I'd like! =)
Quote:
Does Method 1 have an empty loop in it where it waits for NMI?
It often uses a flag to indicate that the frame calculations are done and the data necessary for VRAM updates is ready. The main loop will look something like this:
Code:
;initialize the flag to "false"
lda #$00
sta FrameReady
MainLoop:
;DO THE GAME LOGIC HERE
;indicate that the frame calculations are done
dec FrameReady
WaitForUpdates:
;wait for the flag to change
bit FrameReady
bmi WaitForUpdates
jmp MainLoop
And the NMI will be similar to this:
Code:
NMI:
;skip the video updates if the frame calculations aren't over yet
bit FrameReady
bpl SkipUpdates
;PERFORM VIDEO UPDATES HERE
;modify the flag
inc FrameReady
SkipUpdates:
;PERFORM TASKS THAT MUST BE PERFORMED EVEN
;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
;THE MUSIC OR DRAWING A STATUS BAR
;return from the NMI
rti
I'm using "FrameReady" as a boolean variable here ($00 = false, $ff = true), but you can implement this logic differently if you want.
tokumaru wrote:
unregistered wrote:
tokumaru, wow, you could write an NES book!
Nah, I get things wrong more often than I'd like! =)
Don't worry: if you make the book on Wikibooks, other people will have a chance to fix what you get wrong.
tokumaru wrote:
unregistered wrote:
tokumaru, wow, you could write an NES book!
Nah, I get things wrong more often than I'd like! =)
Ah, ok.
tokumaru wrote:
And the NMI will be similar to this:
Code:
NMI:
;skip the video updates if the frame calculations aren't over yet
bit FrameReady
bpl SkipUpdates
;PERFORM VIDEO UPDATES HERE
;modify the flag
inc FrameReady
SkipUpdates:
;PERFORM TASKS THAT MUST BE PERFORMED EVEN
;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
;THE MUSIC OR DRAWING A STATUS BAR
;return from the NMI
rti
I'm keeping
jsr react_to_input in the video updates because the video may be changed by the controller. Is that right? : )
Thanks for the awesome example code tokumaru!
unregistered wrote:
I'm keeping jsr react_to_input in the video updates because the video may be changed by the controller. Is that right? : )
I think it makes more sense to put it at the start of the main loop. After all, the way your game reacts to input is part of the game logic. In a way, EVERYTHING in the game affects the video, and that's why you run all the game logic first, to prepare everything for the video updates that will follow.
tokumaru wrote:
unregistered wrote:
I'm keeping jsr react_to_input in the video updates because the video may be changed by the controller. Is that right? : )
I think it makes more sense to put it at the start of the main loop. After all, the way your game reacts to input is part of the game logic. In a way, EVERYTHING in the game affects the video, and that's why you run all the game logic first, to prepare everything for the video updates that will follow.
You are so right. Glad I asked; thanks!
In your code you used
bit FrameReady
What is
bit for?
I spent a long while today trying to understand
bit.
I know that bits 7 and 6 of the memory are transfered to the
status register's bits 7 and 6. Then I think there was an AND that changes the Z flag. And so I understand how
bit works for
bpl because it branches when the N flag isn't set... and N flag is bit 7. Why did they make
bit? What is the point of
bit?
I tried searching the forum for bit but I failed, sorry.
BIT takes a value from memory. Whatever that memories bit 7 is the new Negative value. Overflow bit becomes the value of bit 6 from that too. And then it's AND'd with A. If equal or not equal to 0 afterwords, sets the zero flag accordingly.
http://www.obelisk.demon.co.uk/6502/reference.html#BIT
That site has a great reference to instructions and how they work, I'd advise you to bookmark it.
And it's made AFAIK to do bit tests easy. To test one bit of a certain location, it's pretty simple and you don't have to even LDA with anything to test the top 2 bits:
Code:
LDA #$02
BIT SomeFlags
BEQ FlatNotSet ;Bit #%00000010 is set?
;Flag is set if it makes it here.
Sucks you can't do it indirectly and with lots of addressing modes, though.
I believe that the main purpose of BIT is to test whether bits in memory are set or clear. You load your bit mask into the accumulator and BIT it with the value you want to test, if the Z flag is set, the bits you tested are clear.
But to me, the fact that bits 6 and 7 are copied to the V and N flags is what makes it actually useful. Whenever I want to test one of those 2 bits I use BIT. In my example code I was checking bit 7, which is clear when the flag is false ($00) and set when the flag is true ($ff).
3gengames wrote:
http://www.obelisk.demon.co.uk/6502/reference.html#BITThat site has a great reference to instructions and how they work, I'd advise you to bookmark it.
3gengames, thank you for this site!
3gengames wrote:
And it's made AFAIK to do bit tests easy. To test one bit of a certain location, it's pretty simple and you don't have to even LDA with anything to test the top 2 bits
Ah yes, that is a great point!
Thank you.
tokumaru wrote:
I believe that the main purpose of BIT is to test whether bits in memory are set or clear. You load your bit mask into the accumulator and BIT it with the value you want to test, if the Z flag is set, the bits you tested are clear.
But to me, the fact that bits 6 and 7 are copied to the V and N flags is what makes it actually useful. Whenever I want to test one of those 2 bits I use BIT.
Thank you tokumaru for sharing your wisdom.
tokumaru wrote:
In my example code I was checking bit 7, which is clear when the flag is false ($00) and set when the flag is true ($ff).
Yes, I agree.
tokumaru wrote:
How are your levels stored? Just like you read them in order to draw to the nametables, you have to read them to check for collisions. Each block in the level must have its own solidity information (you can use a table for that).
I remember searching the nesdev wiki for "table." I have reread all 4 of your links to threads where you've talked about collision. I'm excited to finally try to make my own! But, I dont understand how tables work. Is a table kind of like a 2dimentional matrix?
Yep, 2D usually table of data. Or one way and reads data in strips to the screen, or you could have 16 "tables" and read them all horizontally. Many ways to do it. RPG's is probably a 2D array, but for a horizontal scrolling game that doesn't scroll up and down wuld maybe do better with 16 tables to build 1 screen, or the vertical method. There's probably more as I haven't messed with data much, but those seem the most obvious to me.
So if I divide the screen into 16 rows, using a 16x16 grid size, that's quite a large grid size. Compared to an 8x8 grid size... that's what
tokumaru used... but that would be twice as much; I would hae to divide the screen into 32 rows. That's twice as much memory used and... ugg.
Start at the beginning
, is the screen 256 scanlines tall?
240 scanline tall, 15 data pointers for each row of the screen that are built into 2x2 tiles and then made into a column as they move over. All going horizontally compressed would take very little room. IIRC, SMB is like 4KB for levels out of the 32KB ROM. Not sure if that's what they used, but it in the end won't come out big at all if you use compression.
Compression may not be to good.... how many different background tiles can be created when using compression?
edit: Well we turned on Ninja Gaiden and saw its many
MANY background tiles. Did Ninja Gaiden use compression?
Not sure. But since probably all of your tiles will be 2x2 tiles, you only need 64 combinations of data and you build them from there. Check out how RLE compression works below. It basically tells it how many times one byte of data is repeated. So you can condense up to about 255 bytes of data to two bytes. It's a great thing to use, and needed in most games to fit them all in.
RLE on Wikipedia:
http://en.wikipedia.org/wiki/Run-length_encoding
And I'm pretty sure it's safe to say nearly every game uses some compression for probably all big stuff in their gams. Levels probably the main thing.
So ok, thank you for RLE and all your help;
I can use RLE compression for my collision tables because each of the background tiles can be represented by a zero. That seems to work in my head!
Yep, I dunno about that, but if you believe so you can do it.
Thinking about alot of things... and I have a few questions. 1.)Would it be notgood to store the level-nametables without RLE compression and then spend more space to store pre-made RLE compressed collision notes? 2.) Or would it be better to store the level-nametables RLE compressed and then create collision info at the drawing nametable faze like tokumaru explained?
I'm confused
about all of this right now... trying to see what would be good.
If there's lots of different data, you'd probably be farther ahead to not do RLE, or maybe any compression. If there's lots of the same data in a row, then sure. You just gotta decide. And you don't have to implement this now, you can always go back if you need to and add it once you get more gritty with your games levels data and stuff. Just make sure you program it so that it wouldn't be ridiculous to add it later.
tokumaru wrote:
Just like you read them in order to draw to the nametables, you have to read them to check for collisions.
Are the collision tables created on the fly while reading for drawing? Or are they susposed to be created separately ahead of time? Or should I reread the nametables and create the cllision table as i am rereading the nametable?
Super Mario Bros. decodes 16x208-pixel slices of the map to a 13-byte-long column buffer, where each byte represents one metatile. It passes this column to the screen drawing code (which prepares packets to be sent to the nametables), and then it copies the column to a 32x13-metatile buffer that holds a sliding window on the decoded map. One way you could use the data in such a window would be to give each metatile some properties, such as whether it can be walked through, and then use that map in your collision code.
I believe it's another thing that's just any way you want to do it. But collision tables can't be that big, so maybe leave uncompressed. Whatever you feel like doing.
Ok, one more question.
Would it be possible to make the game using a regular 1x1 tile size of 8x8 pixels? It would take twice as much memory as yall's recomendation of using a 2x2 tile metatile of 16x16 pixels... like SMB uses. Thank you, tepples, for that SMB info.
It helped me think!
Thank you also 3gengames... I think I understand now; it's up to me to make these choices.
Well, 16x16 is used because 1 tile is SMALL. And, then, most tiles you won't be able to put because of color differences, and it'll take 4x more memory to do it probably, because it's 4x more tiles. I think 16x16 tiles should be used pretty much always, unless you use MMC5 and can get single colored tiles. But even then it may be a waste. It'd certainly be waste of a mapper.
No matter what size you use, you have to decode the blocks all the way down to the 8x8 tiles for rendering, so you might as well go that deep for collision too, it's your choice. What doesn't make sense is not using any kind of compression (larger blocks, RLE, anything), because each screen would take an insane amount of ROM (with NROM you'd hardly be able to store more than 16 screens).
Working with bigger blocks sure makes the levels more compact, and easier to handle because there's less data to work with. On the NES, 32x32-pixel blocks are particularly interesting, because that's the size of the area affected by each attribute byte.
I can't find my list of addressing modes info.
I just want to find out what each mode does and how I could use them.
6502.txt is not helpful for me.. neither is the Addressing Modes part of the
Programming Manual's Appendixes. Do you know of another list of addressing modes info?
Sorry, I've been working on this collision detection for a good amount of time... I'm not ready to reply to yall tokumaru and 3gengames.
unregistered wrote:
I can't find my list of addressing modes info
http://www.obelisk.demon.co.uk/6502/reference.html
That shows which addressing modes are available for each instruction.
If you need something more specific than that, then I'm not quite sure what you're asking.
unregistered wrote:
This page describes how the various addressing modes work, but the full reference linked by Kasumi shows which addressing modes can be used by each instruction. You might have noticed that some combinations are not possible.
tokumaru wrote:
unregistered wrote:
This page describes how the various addressing modes work, but the full reference linked by Kasumi shows which addressing modes can be used by each instruction. You might have noticed that some combinations are not possible.
http://www.obelisk.demon.co.uk/6502/reference.html
Click instruction, get learned.
Uh... that's the link I just said Kasumi posted... Why are you posting it again?
Found it!!
It says "Assembly in One Step" at the top of the first page. This is my favorite explanation of how each addressing mode works.
edit: How do you keep your collision info while you are scrolling?
3gengames wrote:
Well, 16x16 is used because 1 tile is SMALL. And, then, most tiles you won't be able to put because of color differences,
What?
3gengames wrote:
and it'll take 4x more memory to do it probably, because it's 4x more tiles. I think 16x16 tiles should be used pretty much always, unless you use MMC5 and can get single colored tiles. But even then it may be a waste. It'd certainly be waste of a mapper.
That's right, 4x more tiles means 4x more memory. Thanks 3gengames!
That's quite alotlotlotlotlotlotlotlotlotlotlotlotlotlot of space... maybe I have a solution.
tokumaru wrote:
No matter what size you use, you have to decode the blocks all the way down to the 8x8 tiles for rendering, so you might as well go that deep for collision too, it's your choice. What doesn't make sense is not using any kind of compression (larger blocks, RLE, anything), because each screen would take an insane amount of ROM (with NROM you'd hardly be able to store more than 16 screens).
Working with bigger blocks sure makes the levels more compact, and easier to handle because there's less data to work with.
ok I've beeen thinking. Maybe I could use 2x2tiles for collision and break each byte up into 4 parts..... 1 part for each tile. That may work... 4 different collision blocks. There are rocks that need to be destroyed... Could those be sprites instead of counting as one of the collision blocks? Cause if so, that'd be sweet!
Though........., I guess maybe not. But, I can hope.
tokumaru wrote:
On the NES, 32x32-pixel blocks are particularly interesting, because that's the size of the area affected by each attribute byte.
ha! I'm not even sure what the attribute bytes do...
In games that use 16x16 pixel blocks, or 2x2 tiles, you can put ANY block there. But if you do a 1 tile per byte way, you won't be able to put pretty much any type of tile except one of the same colors, making it difficult to have an advantage at all of not using 2x2 tiles.
unregistered wrote:
3gengames wrote:
Well, 16x16 is used because 1 tile is SMALL. And, then, most tiles you won't be able to put because of color differences,
What?
He said that because the NES uses the same palette for all tiles in a 16x16-pixel area. If you try to put tiles that use different palettes in the same 16x16-pixel area you'll have problems.
Quote:
ok I've beeen thinking. Maybe I could use 2x2tiles for collision and break each byte up into 4 parts..... 1 part for each tile. That may work... 4 different collision blocks.
That would give you 4 different collision attributes... In my opinion that's too little, but could work in a simple game. Two types are mandatory: "empty" and "solid", and you have 2 left to choose from "water", "lava" (or anything that hurts), "platform" (only solid at the top), "conveyor belt", and so on. If your game can get away with only 4 types, that's OK, but many games need more.
Quote:
There are rocks that need to be destroyed... Could those be sprites instead of counting as one of the collision blocks?
If you don't need many of them aligned horizontally, then yes, using sprites would be more convenient. If there are too many destructible blocks though, the sprite limitations will get in the way.
Quote:
ha! I'm not even sure what the attribute bytes do...
Attribute bytes select which palettes are used for the background tiles. Each byte defines the 4 palettes used in a 32x32-pixel area, one palette for each 16x16-pixel area. The good thing about using 32x32-pixel blocks is that you don't have to do any sort of attribute byte manipulation, you can just copy attribute bytes straight to the attribute tables (as long as you don't scroll vertically).
3gengames wrote:
In games that use 16x16 pixel blocks, or 2x2 tiles, you can put ANY block there. But if you do a 1 tile per byte way, you won't be able to put pretty much any type of tile except one of the same colors, making it difficult to have an advantage at all of not using 2x2 tiles.
Still dont understand...
But it's ok. The advantage of not using 2x2 tiles is that the level creater can make thinner walls!
tokumaru wrote:
unregistered wrote:
3gengames wrote:
Well, 16x16 is used because 1 tile is SMALL. And, then, most tiles you won't be able to put because of color differences,
What?
He said that because the NES uses the same palette for all tiles in a 16x16-pixel area. If you try to put tiles that use different palettes in the same 16x16-pixel area you'll have problems.
Oh ok, yes that makes sense.
Thank you.
tokumaru wrote:
Quote:
ok I've beeen thinking. Maybe I could use 2x2tiles for collision and break each byte up into 4 parts..... 1 part for each tile. That may work... 4 different collision blocks.
That would give you 4 different collision attributes... In my opinion that's too little, but could work in a simple game. Two types are mandatory: "empty" and "solid", and you have 2 left to choose from "water", "lava" (or anything that hurts), "platform" (only solid at the top), "conveyor belt", and so on. If your game can get away with only 4 -types, that's OK, but many games need more.
Um, I think our game could use just 4... maybe. Thanks for this great explanation tokumaru! It helps me understand better about what to do.
tokumaru wrote:
Quote:
There are rocks that need to be destroyed... Could those be sprites instead of counting as one of the collision blocks?
If you don't need many of them aligned horizontally, then yes, using sprites would be more convenient. If there are too many destructible blocks though, the sprite limitations will get in the way.
YEAY!!! Thank you God's son! And thank you tokumaru!
tokumaru wrote:
Quote:
ha! I'm not even sure what the attribute bytes do...
Attribute bytes select which palettes are used for the background tiles. Each byte defines the 4 palettes used in a 32x32-pixel area, one palette for each 16x16-pixel area. The good thing about using 32x32-pixel blocks is that you don't have to do any sort of attribute byte manipulation, you can just copy attribute bytes straight to the attribute tables (as long as you don't scroll vertically).
Will tell my sister; thanks tokumaru!
This is interesting.
2x2 tiles can go in any order, if you use 1 tile, then you can't put any of a different tile inside an attribute, aka you can't give each one a color, you'll still have to use ones colored for that one.
Code:
screenArray .dsb 240
firstRow .dsb 32
ldy #$00
ldx #$04
;- sta screenArray,x this line is pointless right now
lda #<bg
sta $10
lda #>bg
sta $11
- lda ($10),y
;if a is "solid" then we mark the first tile '01'
;else if a is "empty" then we mark the first tile '00'
;else if a is "water" then we mark the first tile '11'
;else a is "_4" so mark the first tile '10'
sta firstRow,x
txa ;<-- transfer (x)firstRow into a
pha ;<-- push a on stack
iny
bne -
inc $11
;we need x to keep track of the pointer used with firstRow
dex
;we also need that dex counter x value to end this loop
bne -
Ok, I need help with this...
...so I wanted to then
pla (pull the counter x from the stack) and then
tax (transfer a to x) and I can't because I just pushed the other x value on the stack. My stack skills are very poor... they can only improve... what is the correct way to handle this?
Pleaes explain. Goodnight.
unregistered wrote:
3gengames wrote:
In games that use 16x16 pixel blocks, or 2x2 tiles, you can put ANY block there. But if you do a 1 tile per byte way, you won't be able to put pretty much any type of tile except one of the same colors, making it difficult to have an advantage at all of not using 2x2 tiles.
Still dont understand...
But it's ok. The advantage of not using 2x2 tiles is that the level creater can make thinner walls!
Not exactly true, no matter what size of metatiles you use, you can always have walls that are 8 pixels wide (or even thinner). It all depends on how you're handling collisions, not the actual graphical contents of the metatile/tiles. In this sense it's best to think of metatiles simply as a method of compression.
thefox wrote:
unregistered wrote:
3gengames wrote:
In games that use 16x16 pixel blocks, or 2x2 tiles, you can put ANY block there. But if you do a 1 tile per byte way, you won't be able to put pretty much any type of tile except one of the same colors, making it difficult to have an advantage at all of not using 2x2 tiles.
Still dont understand...
But it's ok. The advantage of not using 2x2 tiles is that the level creater can make thinner walls!
Not exactly true, no matter what size of metatiles you use, you can always have walls that are 8 pixels wide (or even thinner). It all depends on how you're handling collisions, not the actual graphical contents of the metatile/tiles. In this sense it's best to think of metatiles simply as a method of compression.
Ok, wow... thank you thefox!
edit: So does that mean it is possible to use 2x2 compression with more than 2 bits for the collision blocks and still have 8 pixel (or thinner) wide walls? How could I do that? I am betting I need more experience in order to understand that... ok.
Since you plan to have collision information for each of the 4 tiles in a 16x16-pixel block, you can very well make the left half "solid" and the right half "empty", which will result in a wall that's 8 pixels wide.
I can't really comment on the code you posted, because I don't know what you are trying to achieve with it.
tokumaru wrote:
Since you plan to have collision information for each of the 4 tiles in a 16x16-pixel block, you can very well make the left half "solid" and the right half "empty", which will result in a wall that's 8 pixels wide.
!!! Brilliant!
But I would still be limited by only having 4 collision block types. Which is perfectly good... thats what God says.
And I totally agree.
tokumaru wrote:
I can't really comment on the code you posted, because I don't know what you are trying to achieve with it.
I'm not trying to achieve much... it is a bit like working out the logic... I need another index register, i think, so I can keep track of three variables. But there isn't another index register... so I thought mabye it would be time to use the stack to switch between the 2 index-x-values. : )
unregistered wrote:
But I would still be limited by only having 4 collision block types. Which is perfectly good... thats what God says.
And I totally agree.
I have noticed you like to mention God a lot, but now you're kinda pushing it, aren't you? Somehow I doubt God has ever said that 4 collision types are enough in a NES game. I'm not a religious person at all, but doesn't that classify as "using the Lord's name in vain"?
tokumaru wrote:
I need another index register, i think, so I can keep track of three variables. But there isn't another index register... so I thought mabye it would be time to use the stack to switch between the 2 index-x-values. : )
I the stack is giving you trouble, just use temporary RAM locations for the registers you need to swap.
tokumaru wrote:
unregistered wrote:
But I would still be limited by only having 4 collision block types. Which is perfectly good... thats what God says.
And I totally agree.
I have noticed you like to mention God a lot, but now you're kinda pushing it, aren't you? Somehow I doubt God has ever said that 4 collision types are enough in a NES game. I'm not a religious person at all, but doesn't that classify as "using the Lord's name in vain"?
No, it's by God's grace that he allows me to have a relationship with him. He loves each of us much... so much!
He's my best friend and he waits to help you and me... each of us!
So, now that you know about him loving us, as I was thinking what to say he inserted the thought "perfectly good" in my head and I typed it out. And I thought about how he helped me with the using 4 objects idea and of how this game doesn't require more than 4 of them. And so I kept it there and returned later to add "And I totally agree." because after going through all of that I wanted to.
I'm sorry it came across as confusing.
tokumaru wrote:
tokumaru wrote:
I need another index register, i think, so I can keep track of three variables. But there isn't another index register... so I thought mabye it would be time to use the stack to switch between the 2 index-x-values. : )
I the stack is giving you trouble, just use temporary RAM locations for the registers you need to swap.
Yes! tokumaru, thank you, this thought ran through my head earlier and I cast it aside, for bringing it back to me.
: )
It's still possible to get a resolution of 8x8 pixels for collision, have one byte per unique metatile, and have more than three different collision responses.
Code:
76543210
||||||||
||||++++- Collision response class for 8x8 spaces that are full
|||| (e.g. solid, platform, water, hurt floor, conveyor, pieces of slope)
|||+----- 0: Top left is empty; 1: full
||+------ 0: Top right is empty; 1: full
|+------- 0: Bottom left is empty; 1: full
+-------- 0: Bottom right is empty; 1: full
Or did you plan on having multiple collision responses (other than air+something else) in one metatile?
That's an interesting idea, tepples. 16 types of responses should be enough for everything, and you can still make any of the 4 tiles empty. Pretty clever.
Slopes might need more information though, which could come from the tile's index. I would probably put all the slopes at the beginning of the tileset, and whenever a slope is found the index of the tile would be used as an index into the table of height maps.
Wow, tepples yes interesting and extra brilliant!! Thank you so much!
Tomorrow is going to be so much fun!
No, I dont remember having multiple collision tiles under 1 metatile. Will tell my sister.
tokumaru, great to read about your words about slopes... I don't know if slopes are going to be a part of our game... maybe they'll make it into the last 3 incomplete levels!
Hope they do so we can try your suggestion. Thanks!
I'm so tired... mustsleep. good night.
edited
tepples, im still awake. : ) Um, well, your suggestion is extra brilliant!
But, I dont think it will work for us
because there are many places with water and wall under the same metatile... like you said, parts other than solid or air. I'm still happy, thank you it is really a wonderful idea.
Ok to sleep. good night.
unregistered wrote:
tepples, im still awake. : ) Um, well, your suggestion is extra brilliant!
But, I dont think it will work for us
because there are many places with water and wall under the same metatile... like you said, parts other than solid or air.
Can't you just arrange the metatiles some other way? For example, instead of doing this (which you can't, because
Water and
Solid can't be in the same metatile):
Code:
+---+
|A A|
|A A|
+---+
|W W|
|S S|
+---+
A = Air;
W = Water;
S = Solid;
You can do this (which has the exact same effect, it's just aligned differently):
Code:
+---+
|A A|
|W W|
+---+
|S S|
|S S|
+---+
I really can't think of many cases where you'd absolutely need 2 different kinds of collisions in the same metatile. I'm pretty sure that in 99% of the cases the level design can be slightly altered to match the limitations.
tokumaru wrote:
unregistered wrote:
tepples, im still awake. : ) Um, well, your suggestion is extra brilliant!
But, I dont think it will work for us
because there are many places with water and wall under the same metatile... like you said, parts other than solid or air.
Can't you just arrange the metatiles some other way? For example, instead of doing this (which you can't, because
Water and
Solid can't be in the same metatile):
Code:
+---+
|A A|
|A A|
+---+
|W W|
|S S|
+---+
A = Air;
W = Water;
S = Solid;
You can do this (which has the exact same effect, it's just aligned differently):
Code:
+---+
|A A|
|W W|
+---+
|S S|
|S S|
+---+
I really can't think of many cases where you'd absolutely need 2 different kinds of collisions in the same metatile. I'm pretty sure that in 99% of the cases the level design can be slightly altered to match the limitations.
This must be part of the 1%.
Code:
+---+
|W W|
|W W|
+---+
|S S|
|W W|
+---+
|W W|
|W W|
+---+
Thank you though.
I'll tell my sister what you've said... when she wakes up.
I don't know why you absolutely need the wall to be 8 pixels thick, but I guess there are ways to work around this. Maybe you can find some other way to define the default collision type, so that instead of air it's water in this case.
Maybe you could have the default collision type be water or air depending on the index of the metatile, or the index of the palette used for the metatile, something like that.
Ah, that is a ggrand
(edit: I kind of had thought of something like that too. Thank you tokumaru for your challenges! ) idea tokumaru! Ok this is what we decided... after much talking... that we would set bit
3 to
1 for default water and leave it at
0 for default air. And that will bring our total collision blocks down from 16 to 8... 8 is all we
(edit: I remember saying this about the 4) need for this game. It's twice as much as 4! We realize we are throwing away 8... but my sister is estatic we can use 5! That leaves 3 more!
Thank you tepples and tokumaru so much!!
Heh, I was gonna suggest that but I thought you wouldn't want to give up half the collision types for this... Guess I was wrong! =)
This is what you are planning to do, right?
Code:
76543210
||||||||
|||||+++- Secondary collision type (solid, platform, danger, etc.);
||||+---- Primary collision type (0 = air, 1 = water);
|||+----- Primary or secondary collision for the top left tile;
||+------ Primary or secondary collision for the top right tile;
|+------- Primary or secondary collision for the bottom left tile;
+-------- Primary or secondary collision for the bottom right tile;
tokumaru wrote:
Heh, I was gonna suggest that but I thought you wouldn't want to give up half the collision types for this... Guess I was wrong! =)
Well, we talked for anothger extended time, cause I think that I'd have to set each metatile
and, oooh foood is for eating now, no... I'd have to set each metatile and that would be crazy and would be much better for it to be set in the
.chr file cause she is in charge of the graphics... and so it would be less work for me and we'd have a quicker game... that would be good.
Food would be goood too! I'll reply to the rest of your reply in a bit. tokumaru wrote:
This is what you are planning to do, right?
Code:
76543210
||||||||
|||||+++- Secondary collision type (solid, platform, danger, etc.);
||||+---- Primary collision type (0 = air, 1 = water);
|||+----- Primary or secondary collision for the top left tile;
||+------ Primary or secondary collision for the top right tile;
|+------- Primary or secondary collision for the bottom left tile;
+-------- Primary or secondary collision for the bottom right tile;
Ok, yes that is what we are planning to do, if
"Primary or Secondary collision" can be false.
and
Our efforts (below) don't make sense and cant work.
We decided to ask you: Could the last row of each
.chr file determine if the default collision would be water? (Moving all water tiles to row F) so if the tile is F3 then it's water? Could that work? (I don't know what "index of the metatile" means. That was my guess...)
unregistered wrote:
Could the last row of each .chr file determine if the default collision would be water? (Moving all water tiles to row F) so if the tile is F3 then it's water? Could that work? (I don't know what "index of the metatile" means. That was my guess...)
Yup, I suggested something like that. I'm usually against hardcoding logic decisions to visuals, but if done carefully it isn't necessarily bad.
Do you have only 1 water tile though? What about the water surface?
Well, yes, we have more than one water tiles... but they will all be moved to the last row of each
.chr file.
Please explain "water surface".
In
SMB1 maps, look at 2-2. The top row of water tiles uses a different tile from the rest. Then look at 3-1 and see the bridge about a third of the way through: the top tile is different. And any x-4 level uses that same top-of-water-area tile for its
boiling Kool-Aid. This tile is used at the water surface.
Ah, I understand now. Thanks.
Hahaha, boiling Kool-Aid.
We do have water surface tiles. They are inccluded in the water tiles.
This question is about
if,
elseif,
else, and
endif from asm6
Code:
0C2E6 B1 10 -- lda ($10),y
0C2E8 8D D0 C2 sta currenttile
0C2EB if ((currenttile > 15) && (currenttile < 32)) ; is solid
0C2EB lda #00000001b
0C2EB
0C2EB elseif (currenttile > 239) ; is water
0C2EB A9 03 lda #11b
0C2ED
0C2ED else ;(a) is empty
0C2ED lda #00000000b
0C2ED endif
0C2ED
0C2ED AE CF C2 ldx rowPointer
0C2F0 9D 8E C2 sta firstRow,x
0C2F3
0C2F3
0C2F3 E8 inx ;increment pointer used with firstRow
0C2F4 8E CF C2 stx rowPointer
0C2F7 C8 iny
0C2F8 D0 EC bne --
This is from my listing file
.lst.
I have tried these statements with $0F hex numbers and then changed the hex to decimal. Only the middle
lda #11b has hex codes on the left side. Why?
My problem is that I tried to use assembler directives as c++ statements... I think.?
Assembler directives can't be used for game logic. These expressions are evaluated when the program is assembled, not when the program runs.
EDIT: Let me try to explain what happened there: currenttile is a label that points to a RAM location, and this location is probably after address 239, which is why lda #11b got assembled.
If you want C, you know where to find it: Alter Ego by Shiru.
Thanks tokumaru!
It's good to ask a question; thank you God.
tepples wrote:
If you want C, you know where to find it: Alter Ego by Shiru.
A useful property of CMP is that it performs an equality comparison and an unsigned comparison. After a CMP, the Z flag contains the equality comparison result and the C flag contains the unsigned comparison result, specifically:
Quote:
If the Z flag is 0, then A <> NUM and BNE will branch
If the Z flag is 1, then A = NUM and BEQ will branch
If the C flag is 0, then A (unsigned) < NUM (unsigned) and BCC will branch
If the C flag is 1, then A (unsigned) >= NUM (unsigned) and BCS will branch
So if
A < NUM then BCS will not branch, right?
I want to branch if A is not < NUM. I'm kindof confused about this; could you help?
Quote:
In fact, many 6502 assemblers will allow BLT (Branch on Less Than) and BGE (Branch on Greater than or Equal) to be used as synonyms for BCC and BCS, respectively.
http://www.obelisk.demon.co.uk/6502/reference.html#CMP
If A<ComparedTo, then C=0, BCC taken.
If A>=ComparedTo, then C=1, BCS taken.
And yeah, the opposite branches for each taken will not be taken.
And to branch if a number is greater than A, do:
Code:
CMP #$20
BCS Somewhere ;BCS is taken because A is bigger or equal to #$20.
If A is not less than NUM then it must be equal to or greater than NUM.
Yes
In subtractions, the carry flag acts as a "borrow flag". Setting it before a subtraction is like putting a 1 in position to be borrowed, just in case. When the subtraction takes place, and the result is less than 0, the carry is borrowed and becomes 0. So you just have to peek at the carry afterward: if it's set, the result was equal to or greater than 0 (no need to borrow), if it's clear, the result was less than 0.
A CMP is exactly like a SBC, except that you don't have to set the carry before using it and it doesn't store the result, but all the other rules of subtraction apply to CMP.
unregistered wrote:
I want to branch if A is not < NUM.
If you subtract NUM from A, a borrow will only happen if A < NUM.
Code:
cmp #NUM
bcs Whatever
;A is less than NUM
jmp Skip
Whatever:
;A is equal to or larger than NUM
Skip:
Code:
-- lda ($10),y
sta currenttile
; if ((currenttile >= 15) && (currenttile < 32)) ; is solid
; lda #01b
cmp #15 ;if (currenttile >= 15)
bcc +
cmp #32 ;if (currenttile < 32)
bcs +
lda #01b ;a is solid
jmp ++done
+
Is my logic here ^ good? I am useing the inverse (bcc instead of bcs, bcs instead of bcc) because !0 == 1 and !1 == 0. Does this make any sense? I do understand what you, tokumaru, just explained very detailed and well; thank you so much!!
(Except your last comment; I'm still attempting to work that one out in my head.
) And thank you 3gengames for continuing to offer me help.
Looks correct to me. Does it work?
Getting a bit closer.
I am at the point, now, where I'm susposed to write the code that creates the
screenArray .dsb 240. I've thought about this and have tried to make
firstRow .dsb 32 and
secondRow .dsb 32 contain the contents of the first and second row of tiles. Then I get really lost... somehow I'm susposed to change
firstRow and
secondRow into the first row of screenArray... there is the two types of collision tiles... tokumaru typed "primary" and "secondary" to me in an earlier post.
I dont understand how these pieces of the puzzle fit together... it's really far far away from me now. If you could help with a helpful arrow that could guide me closer to this task ahread of me... that would be amazing!
Going tosleep now, love yall.
Maybe after sleep I'll be able to make more sense with ...these ideas... maybe reread this thread again... .
Well, you use x amount of bits per tile, and then each are flags. Like one would be a solid/not solid, the next for for not solid if it was a certain type, then other bits would mean different things too like if it's sloped or something.
tokumaru wrote:
unregistered wrote:
Could the last row of each .chr file determine if the default collision would be water? (Moving all water tiles to row F) so if the tile is F3 then it's water? Could that work? (I don't know what "index of the metatile" means. That was my guess...)
Yup, I suggested something like that. I'm usually against hardcoding logic decisions to visuals, but if done carefully it isn't necessarily bad.
When I'm trying to read this compressed data to create the level how can I tell if the 0s mean water? I drew this all on paper trying to figure it out.
^Zeros mean water if the tile number is on the last row. This is the collision detection info... not the level map.
If I ran into these eight tiles:
Code:
+---+---+
|S S|S W|
|A A|S W|
+---+---+
A = Air;
W = Water;
S = Solid; thanks tokumaru
They would be translated like:
Code:
+---+---+
|1 1|1 0|
|0 0|1 0|
+---+---+
and then, tepples will leave me with
11000001 and
10100001.
And so after this point I should store each of those bytes in my screenArray.
When I've completed screenArray and add gravity I will check after each move if the girl has collided with something. If that something is a solid
0001 I will have to pull her out of the solid. If that something is water.... How do I know if the something IS water?
How do I check the tile number?
unregistered wrote:
How do I know if the something IS water?
How do I check the tile number?
One of the ideas we had consisted in using 1 bit to select between water or air for the empty tiles, and 3 bits for the type of the solid tiles. In this case it would be easy to know if something is water, but since you decided to use the tile index...
Well, your
screenArray only has the basic collision info, it doesn't say which tiles each block uses. The only way is to make
screenArray hold the index of the metatiles instead of the collision info. That way, whenever you wanted to test for collisions, you'd have to get the metatile index at the position you want and use that to fetch the collision info from a table. With the same index you can easily access the tile indexes too. It's a bit more indirect, but that's not too bad.
tokumaru wrote:
Well, your screenArray only has the basic collision info, it doesn't say which tiles each block uses. The only way is to make screenArray hold the index of the metatiles instead of the collision info. That way, whenever you wanted to test for collisions, you'd have to get the metatile index at the position you want and use that to fetch the collision info from a table. With the same index you can easily access the tile indexes too. It's a bit more indirect, but that's not too bad.
sooooo, thank you for helpfull words
,
Code:
+---+---+
|S S|S W|
|A A|S W|
+---+---+
will translate into
Code:
+---+---+
|? |?+1|
| | |
+---+---+
The index of the metatiles will be stored in
screenArray. Would it look like this^? I'm going to guess no. I don't understand what you mean by the index of the metatiles; I'm sorry.
edit: Though it is nice to remember there is an easier way to do this, thanks.
You are using metatiles to build your maps, right? Each metatile has some info attached to it: the index of the 4 tiles it uses, its collision information and the palette it uses. If you have that index, you can access any of that information.
So, by keeping an array of metatile indexes in RAM you can access their collision information as well as the tiles that make them up, in order to decide whether the empty tiles are air or water.
I don't know how to view the info attached to each metatile. But, after asking my sister (artist who does the graphics), I remember that each name table has a metatile map of 16 columns by 15 rows. So are the metatile indexes like
$1A and
$00 and
$EF?
If so, then should I create an array something like (00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10, 11, 12... ...EC, ED, EE, EF)?
You could arrange the metatiles in ROM kinda like this:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
MetatileCollision:
.db %00001010, %01001100, $00011010
MetatilePalette:
.db %10101010, %00000000, %01010101
Here I have 3 metatiles, each using 6 bytes. If I want to get the collision info for any given tile, I can just put its index in X or Y and read it from the MetatileCollision table.
So, if your screenArray is an array of 16x15 metatile indexes, you can do something like this to read the collision info of any metatile:
Code:
;use 2 4-bit coordinates to calculate
;how far in the array the metatile is
lda metatileY
asl
asl
asl
asl
ora metatileX
tax
;get the index of the metatile
lda screenArray, x
tax
;get its collision information
lda MetatileCollision, x
Just as easily you now can use
lda MetatileTile0, x to read the index of its top left tile, and the other tables to find out anything you want about the metatile in that position.
tokumaru wrote:
You could arrange the metatiles in ROM kinda like this:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
MetatileCollision:
.db %00001010, %01001100, $00011010
MetatilePalette:
.db %10101010, %00000000, %01010101
Here I have 3 metatiles, each using 6 bytes. If I want to get the collision info for any given tile, I can just put its index in X or Y and read it from the MetatileCollision table.
So, if your screenArray is an array of 16x15 metatile indexes, you can do something like this to read the collision info of any metatile:
Code:
;use 2 4-bit coordinates to calculate
;how far in the array the metatile is
lda metatileY
asl
asl
asl
asl
ora metatileX
tax
;get the index of the metatile
lda screenArray, x
tax
;get its collision information
lda MetatileCollision, x
Just as easily you now can use
lda MetatileTile0, x to read the index of its top left tile, and the other tables to find out anything you want about the metatile in that position.
tokumaru, thank you for helping us so much!
...Thank you to God too!!
it's great to be here right now... I've got a lot of work to do and I'm so happpy!
Have a small question... is a nametable set up kindof like this:
tokumaru wrote:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
?
If so then I could
incbin the nametable like I aleady do.
You can incbin the metatiles, but there are consequences...
I defined them interleaved like that (i.e. all the top left tiles first, then all the right left tiles, etc.) because it's easier/faster to access them that way. If they are not interleaved, you can't use the metatile index directly for reading tiles, instead you have to multiply it by 4 (since there are 4 tiles in each metatile). Also, because of that multiplication, you can't have more than 64 metatiles (256 indexes / 4 tiles = 64 metatiles) unless you use Indirect Indexed addressing (LDA ($XX), Y).
If you can still keep them interleaved in the file you are incbin'ing, great, but you'll have to do something like this to set up the labels to access each of the 4 tiles:
Code:
MetatilesStart:
.incbin "metatiles.bin"
MetatilesEnd:
MetatileCount = (MetatilesEnd - MetatilesStart) / 4
MetatileTile0 = MetatilesStart
MetatileTile1 = MetatileTile0 + MetatileCount
MetatileTile2 = MetatileTile1 + MetatileCount
MetatileTile3 = MetatileTile2 + MetatileCount
And then you can use the 4 labels normally.
tokumaru wrote:
Also, because of that multiplication, you can't have more than 64 metatiles (256 indexes / 4 tiles = 64 metatiles) unless you use Indirect Indexed addressing (LDA ($XX), Y).
Or unless you do like Super Mario Bros. and have four separate tables of metatiles, selected by bits 7 and 6 of the metatile number. These upper 2 bits are also used as the attribute value.
tokumaru, thank you so much for that consequences explanation!
I thought about this idea a lot during the past 2 days.
tepples wrote:
tokumaru wrote:
Also, because of that multiplication, you can't have more than 64 metatiles (256 indexes / 4 tiles = 64 metatiles) unless you use Indirect Indexed addressing (LDA ($XX), Y).
Or unless you do like Super Mario Bros. and have four separate tables of metatiles, selected by bits 7 and 6 of the metatile number. These upper 2 bits are also used as the attribute value.
You are amazing and that's awesome to know, thank you tepples!
After me thinking and praying about what to do, my sister and I chatted about what yall have told us for around 2 hours! It was a great time; we finally decided to not try to
.incbin the nametables because of all of the consequences.
We also decided to try to not make mistakes during the conversion process; it was that or to accept using a faster-and-slower-more-confused-programming way of thinking about this collision process ("faster" bcause there would be no conversion-of-every-nametable time spent). But, she wawnts to convert our nametables to the new format that tokumaru mentioned because she had run out of game-work to do due to my lengthly programming stumbling along... way.
tepples, it was fun to think about doing this Nintendo's way.
tokumaru wrote:
You could arrange the metatiles in ROM kinda like this:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
MetatileCollision:
.db %00001010, %01001100, $00011010
MetatilePalette:
.db %10101010, %00000000, %01010101
Here I have 3 metatiles, each using 6 bytes.
Why would you say each metatile uses 6 bytes? Each byte in the first metatile is for a 16x16 area, except the MetatilePalette byte that covers a 32x32 area. Wouldn't each metatile use 5.25 bytes? edit:
, I dont know if 5.25 bytes would be the correct number... that is what my sister and I came up with; I am trying to figure out how to fill the MetatilePalette parts of our NametableCollision files.
32x32 Metatile definition as described by tokumaru.
Requires:
A byte for the top left tile. 1 byte total
A byte for the top right tile. 2 bytes total
A byte for the bottom left tile. 3 bytes total.
A byte for the bottom right tile. 4 bytes total.
A byte for the collision information. 5 bytes total.
A byte for the palette information. 6 bytes total.
Heh, I guess the way I defined the palettes can cause some confusion... If you pay attention you'll see that I just repeated the same 2 bits 4 times in each byte. That's because depending on where the metatile is used, its 2 attribute bits will in one of 4 different places of a byte, so if have the same bits in all 4 positions you can just mask out the ones you don't need.
That's a waste of space, yes, since each byte has the same information 4 times. That was just an example though, and you can certainly use other methods. Maybe doing it like SMB, as tepples suggested, would be a good idea.
tokumaru wrote:
Heh, I guess the way I defined the palettes can cause some confusion... If you pay attention you'll see that I just repeated the same 2 bits 4 times in each byte. That's because depending on where the metatile is used, its 2 attribute bits will in one of 4 different places of a byte, so if have the same bits in all 4 positions you can just mask out the ones you don't need.
That's a waste of space, yes, since each byte has the same information 4 times. That was just an example though, and you can certainly use other methods. Maybe doing it like SMB, as tepples suggested, would be a good idea.
Thank you for reading all of my post... yes, I guess my point could have been explained better.
I'm sorry.
Glad you understood me though!
Ok, I understand much better after talking it over with my sister. Right now, I don't understand what you mean by, "maybe doing it like SMB, as tepples suggested, would be a good idea." But, I'm going to chat with my sister about that tonight; maybe I'll learn a bit of what you ment.
I think I'll be able to post tomorrow night what I learned. good night yall. : )
I just meant that you could use the top (or bottom, it's up to you) 2 bits of the metatile index to specify the palette it uses, that way each metatile would be defined in only 5 bytes.
If you don't want to be restricted to only 64 metatiles, you can do *exactly* like SMB and have the palette bits double as metatile bank selectors. That way you can still have 256 metatiles, but divided in 4 groups of 64, and all the metatiles of the same group use the same palette.
It's a fairly compact format, and should work well for most games.
tokumaru, when you say "metatile index" what does that mean?
Is it a number that I'm susposed to give each metatile? This is not worked out well enough in my head...
yet. Yes, I would like to set the upper two bits of this metatile index to the pallet and decrease the number of bytes used for each metatile!
I thought that we wouldn't be limited to 64 metatiles because our tile data will be interleaved (like you talked about).
: )
An "index" is a number you use to access something. Since metatiles are dfined in the ROM sequentially, you can use numbers to access them. The first one is index 0, the second one is index 2, and so on. So if you are gonna use the metatile indexes to specify palettes, you just have to pay attention to the order in which you define them. Metatiles that use palette 0 must be between 0 and 63, metatiles that use palette 1 must be between 64 ans 127, and so on.
If you really plan on having only 64 metatiles at a time, you can have your screen array use only one byte for both the metatile index (6 bits) and the palette index (2 bits). That way you just have to mask out the bits you don't need depending on the info you want to use. For example, after calculating the position of the metatile in the screen array:
Code:
;calculate the position of the
;metatile from its coordinates
lda metatileY
asl
asl
asl
asl
ora metatileX
tax
You can do this to load its index (which you can then use to read tiles or collision information):
Code:
;get the index of the metatile
lda screenArray, x
and #%00111111 ;remove the palette bits
tax
Or you can AND the value with %11000000 instead to keep only the palette bits, which you'll need in order to compute the attribute table data.
tokumaru, thank you for all of your help!
I'm having a new problem... the attribute bits are bothering me because I must have more than 64 metatiles. So I thought maybe since the two attribute bits are important I could store 4 pairs of attrilbute bits in a byte... in my head i would need 15 rows of 4 bytes to hold all of it. So that is close to the
8 byte by 8 byte attribute table representation in the
nesdev wiki. Why does the nametable attribute table need 64 bytes? I think it should have 60 bytes. But it needs 64...
You can split that data away from that data and then use all 8-bits per metatile and then read the data from an attribute table for them too, and like you said use 1 byte for 4 attributes.
unregistered wrote:
Why does the nametable attribute table need 64 bytes? I think it should have 60 bytes. But it needs 64...
Yes, there are unused bits in the attribute tables. The reason why the PPU uses 64 bytes rather than 60 is because the attributes are arranged in squares. The bottom half of the last row of squares doesn't have correspondent tiles in the name table, because the name table is only 30 tiles high, so there are indeed a total of 32 bits (4 bytes) that don't do anything, but they are still part of the attribute tables because they share bytes with bits that ARE used.
I'm not gonna lie to you, attributes can be pretty hard to manage. Having to shift and combine bits to for the attribute bytes is not something you can easily do without some good amount of planning. So take your time, draw it all on paper and think hard about it, eventually you'll find a solution that works for you.
As I said before, quite a few NES games used 32x32-pixel metatiles, because that's the exact size of the area affected by an attribute byte. This means that the program can easily write the attribute bytes straight to the attribute table, without any sort of processing. I'm not saying this is the best solution for all cases, but if you're really having a hard time with attributes, this is the easiest solution there is.
tokumaru wrote:
This means that the program can easily write the attribute bytes straight to the attribute table
How am I susposed to write bytes to the attribute table? The attribute table must be part of the PPU... and it sits under the nametable, I think.
You write the high, then low byte of the address of the attribute byte you want to update to $2006, then you write the byte you want written to that attribute byte to $2007. This must be done during vblank or while rendering is disabled.
See this for the addresses:
http://wiki.nesdev.com/w/index.php/PPU_attribute_tables
Kasumi, thank you!
3gengames wrote:
You can split that data away from that data and then use all 8-bits per metatile and then read the data from an attribute table for them too, and like you said use 1 byte for 4 attributes.
... I don't understand.
Yeah, it's exactly the same as writing tiles to the name tables. After the 960 name table bytes there are 64 attribute table bytes.
Ah, so that's how it works... I remember now that my sister said it (NESst) saved the attributes with the nametable. Great to know, thank you tokumaru!
value = (topleft << 0) | (topright << 2) | (bottomleft << 4) | (bottomright << 6)
I think this means that this value byte would contain the bit order
bottomright,
bottomleft,
topright,
topleft... which seems backward to me. Could it be
00000000b instead?
It is how the wiki says it is... You can't really change how the PPU works, the best you can do if a particular order of bits doesn't please you is to use a translation table, to convert from your format to the hardware format.
There's little advantage in that though, because you'd be wasting 256 bytes of ROM and some CPU time reading from the table instead of directly writing values to VRAM without any benefit other than making the attribute data easier for you to type.
unregistered wrote:
value = (topleft << 0) | (topright << 2) | (bottomleft << 4) | (bottomright << 6)
Does the wiki say this value byte contains the bit order
bottomright,
bottomleft,
topright,
topleft?
(Thank you for helping me so much though tokumaru!
)
Heh, I always have to check, but yeah, it seems that the attribute bits are arranged like this:
Code:
Attribute byte:
DDCCBBAA
Attribute block:
+--+--+
|AA|BB|
+--+--+
|CC|DD|
+--+--+
It may look kind backwards since we read numbers from left to right, but when you consider that numbers grow from right to left it's not so weird.
Perhaps what's backwards is that the leftmost part of a pattern table byte is in the high bits, but the leftmost part of an attribute table byte is in the low bits.
^ I don't remember anything about pattern table bytes.
tokumaru wrote:
Heh, I always have to check, but yeah, it seems that the attribute bits are arranged like this:
Code:
Attribute byte:
DDCCBBAA
Attribute block:
+--+--+
|AA|BB|
+--+--+
|CC|DD|
+--+--+
Thank you, kind sir!
tokumaru wrote:
It may look kind backwards since we read numbers from left to right, but when you consider that numbers grow from right to left it's not so weird.
It took me a bit to understand because I've never ever thought about numbers growing from right to left... but they do!
You could arrange the metatiles in ROM kinda like this:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
MetatileCollision:
.db %00001010, %01001100, $00011010
MetatilePalette:
.db %10101010, %00000000, %01010101
Here I have 3 metatiles, each using 6 bytes. If I want to get the collision info for any given tile, I can just put its index in X or Y and read it from the MetatileCollision table.
Is there a possible way to make the 6 labels a part of the machine? Like, well, make them local labels that I could use. Would I have to make each label different in every file? I'd like to make an assembly object... something like
jmp bg0.@MetatileCollision. Is that possible?
So, if your screenArray is an array of 16x15 metatile indexes, you can do something like this to read the collision info of any metatile:
Code:
;use 2 4-bit coordinates to calculate
;how far in the array the metatile is
lda metatileY
asl
asl
asl
asl
ora metatileX
tax
;get the index of the metatile
lda screenArray, x
tax
;get its collision information
lda MetatileCollision, x
Just as easily you now can use
lda MetatileTile0, x to read the index of its top left tile, and the other tables to find out anything you want about the metatile in that position.
Sweet thank you for explaining to me how to use 4bit numbers.
I hope, after playing with them a bit, I'll get comfortable using them.
unregistered wrote:
Is there a possible way to make the 6 labels a part of the machine? Like, well, make them local labels that I could use. Would I have to make each label different in every file? I'd like to make an assembly object... something like
jmp bg0.@MetatileCollision. Is that possible?
I didn't really understand the question... Why would you JMP to a data table? That would most likely crash the program!
Do you want to use different data for each level or something like that? If that's the case, then the answer is the indirect indexed addressing mode (i.e.
LDA ($XX), Y). With that addressing mode you use pointers to define the tables that will be read, and you can alter the pointers as much as you want.
For example, you could have a different name for each collision table (or whatever table you want), and then make a table with all the addresses:
Code:
MetatileCollisionAdresses:
.dw MetatileCollisionLevel1, MetatileCollisionLevel2, MetatileCollisionLevel3
Then you can read the address for the current level and put it in a pointer using the level's index:
Code:
lda LevelIndex ;get the level's index
asl ;multiply by 2 because each address is 2 bytes
tax ;use that as an index into the table of addresses
lda MetatileCollisionAdresses+0, x ;copy the low byte
sta MetatileCollision+0
lda MetatileCollisionAdresses+1, x ;copy the high byte
sta MetatileCollision+1
Then you can use indirect indexed addressing to read the data, instead of what we had before. The only real difference is that now you'll have to use Y as your index register, because this addressing mode doesn't work with X:
Code:
;get collision information
lda (MetatileCollision), y
Quote:
thank you for explaining to me how to use 4bit numbers.
There are lots of ways to use 4-bit numbers. In this case it was convenient to use 4-bit values because 4 bits are enough to represent the metatile coordinates inside a single screen.
tokumaru wrote:
unregistered wrote:
Is there a possible way to make the 6 labels a part of the machine? Like, well, make them local labels that I could use. Would I have to make each label different in every file? I'd like to make an assembly object... something like
jmp bg0.@MetatileCollision. Is that possible?
I didn't really understand the question... Why would you JMP to a data table? That would most likely crash the program!
hahaha hahaha hahaha (laughing at myself) hahaha ok hahaha! No I shouldn't have typed JMP... ... ...should have gone with
lda bg0.@MetatileCollision, 3. I was trying to say I would make an assembly object named
bg0. And then I would try to access the third entry of its MetatileCollision array (labeled
@MetatileCollision). I wanted to use
@MetatileCollision because it is a local label/variable and then hopefully I could leave each file with a label named
@MetatileCollision. Or would I need to have a unique label name in each .NAC (nametable & collision) file?
We would replace each .NAM file with a .NAC file.tokumaru, thank you so very much for your explanation of the indirect indexed addressing mode and for the code examples.
: )
tokumaru wrote:
Quote:
thank you for explaining to me how to use 4bit numbers.
There are lots of ways to use 4-bit numbers. In this case it was convenient to use 4-bit values because 4 bits are enough to represent the metatile coordinates inside a single screen.
Ah yes! That's amazing!
unregistered wrote:
You could arrange the metatiles in ROM kinda like this:
Code:
MetatileTile0:
.db $00, $04, $08
MetatileTile1:
.db $01, $05, $09
MetatileTile2:
.db $02, $06, $0a
MetatileTile3:
.db $03, $07, $0b
MetatileCollision:
.db %00001010, %01001100, $00011010
MetatilePalette:
.db %10101010, %00000000, %01010101
Here I have 3 metatiles, each using 6 bytes. If I want to get the collision
info for any given tile, I can just put its index in X or Y and read it from the MetatileCollision table.
Ok here's^ 3 metatiles,each using 6 bytes. Does this mean each new metatile
will use 6 bytes? It's confusing
trying to complete one
.NAC (Nametable & Collision) file. For the attribute bytes we have 64 bytes (8 * 8 ). For the Collision part we will have 240 bytes (16 * 15). So 64 != 240.......... My solution is to increase our metatile size to 32x32. Then we would have 64 bytes for both of them, I think.
But, then it's confusing to think about, for me at least.
I need some help. Dear God, please help me. Thank you God! Amen.
I have 5 torches. Each torch gives me two hours of light when lit. I light them all at once. I get only two hours of light. What gives? I should have got ten!
Yes, if your file format demands that each metatile definition is six bytes, each metatile defintion MUST use six bytes.
Metatiles save space because they store that information only once, as opposed to every time it's needed.
Let's ignore collision data and attribute data. You just want to store tiles. You have a screen that's entirely the same tile. It takes (256/8)*(240/8) = 960 bytes to define that screen.
So you decide to use a 16x16 metatile. The definition of it takes 4 bytes. But to define that same screen takes (256/16)*(240/16)=240 bytes. PLUS FOUR! Because of the metatile definition. All that data technically takes 244 bytes. But metatiles only help you as long as they can be reused.
Let's do a situation where every 16x16 area is different on the screen. With no metatiles, it still takes 960 bytes. With 16x16 metatiles it takes 240 bytes. PLUS 240*4 bytes for the metatiles definitions. 240*4+240 = 1,200 bytes. Even more than just storing the tiles.
The way you decide whether or not you need another metatile level is by figuring out if you'll be able to effectively reuse that size metatile. Else you're wasting space, not saving it. Although even in that extreme case, if you made a few more screens with those same metatiles, you would begin to save space over just storing the tiles uncompressed.
I don't even get your second thing. Yes, 64 is different than 240. It has to be the same, why? Let's say you use tokumaru's 6 byte metatile plan. The attributes are defined already. So is the collision data. The metatile exists so that stuff never takes up extra space beyond the actual metatile definitions. So when you're storing a screen, it takes 240 bytes. The metatiles themselves only take up space for each unique one created. It could be exactly one, and you only add 6 to the 240. You could even define each 16x16 tile with a format that stores info for speed manipulation and other stuff where each metatile takes up 8 bytes. Each screen by itself would still only take 240 bytes.
Edit: tl;dr The metatile definition takes up X bytes. In this case 6. After that, each time the metatile is used in a structure (be it in a larger metatile or a screen or whatever) takes one. That one is the byte that refers to the metatile.
So if the metatile is used more than X times, you save space for that metatile.
I tell you this, because I'm not sure you understand all of this. Though, honestly I doubt we're doing you favors by offering so much help. I truly believe you can figure a lot of this out, if you think about it. It's a decision. If you "invest" in metatiles do you "save money" in the long run? If you don't know the answer, you may be able to figure it out with a calculator and some logical thought. I also think you are progressing too quickly, without fully understanding what you're actually doing. If the stuff we're talking about is going over your head, you need to go back to more basic things until you fully understand those.
It has been mentioned before, but I also find your mentions of God unnecessary. It's wonderful that you believe. But it could be off putting to Hindus or atheists or agnostics or whoever else may want to offer you help. Especially when you mention God actually speaking to you in your mind as was mentioned earlier in this topic which might even make fellow Christians do a double take. I would say it would make some less willing to help, and it certainly doesn't encourage help in those who would have already helped you. So since it's definitely not related to the topic at hand, and mentioning it probably lowers rather than raises the amount of help you get, does it really need to be mentioned?
I agree with everything Kasumi said.
unregistered, you seem to be very confused. It appears that you are trying to define all that data for each individual screen, which would require a lot of memory and result in no compression at all... in fact it would just make the screen need even more memory.
Metatiles work kinda like LEGOs. Every time the company needs to create a new kind of brick, they have to design it, make molds for it, etc. That's analogous to defining a metatile. After created, the new type of brick has a code (like the metatile has an index), and based on this code the company can easily create new bricks of that kind using the molds that were previously made, so the only expense is the plastic needed for the new piece. If you ask them "hey, make me a XFYZ54KV brick", they know which brick you're talking about, and they can make one for you at any time. Without that code it would be much harder, you'd have to describe the shape, dimensions, color, etc. of the brick, and they'd have to build an assembly line for it. Can you imagine how freaking expensive LEGOs would be if they had to go through all this process for each individual brick?
Metatiles are just like that. The initial definition takes up a lot of resources (memory), but once you have your dictionary of metatiles ready, you can reference them using very little memory (just a byte with a metatile index).
Thank you both Kasumi and tokumaru!
I've read each of yall's posts more than 11 times... over these days.
Kasumi, now I understand everything you talked about!
Kasumi wrote:
But, metatiles only help you as long as they can be reused.
We have decided to make a metatile dictionary for each level in our game. Is that normally what people have done in the past? ...It'd be quite hard for us to make just one large metatile dictionary for all of our levels. My sister has created 190 metatiles for most parts of level 1. We figured we'd store each level's metatile dictionary in a separate file.
tokumaru, I have been scared to define 5 parts of a metatile cause it seems that with each new part, beyond the first 4, would be making the metatile more complicated... and so it would take more metatiles to meet the immensley detailed needs of the game. More metatiles woould be, not good, and awful, I think. But, now I'm hoping that, after defining a palette part of a metatile, we could use the palette part independent of our use of the first 4 definitions... so the index number would mean different things depending on how it is used. Does that make sense?
Kasumi, I was worrying about only having 64 palette entries... much fewer compared with the rest of our metatile definition. Well, maybe it, using a max index of 64 for the palette part and having a larger maximum index of 240 for the remaining parts of the metatile definition, would be ok?
tokumaru, your lego analogy is awesome and easy to understand! Thanks for introducing me to these ideas!
That sounds like a lot of metatiles for just one level. I'm actually restricted to sixty-four 16x16 metatiles per set so I can just store the palette for each one in the two high bits.
8x8 tile $00 is a solid square. My 16x16 metatiles are defined in 5 bytes.
For 16x16 tile 0, the bytes are $00, $00, $00, $00, $00. (The fifth byte deals with its collision, but I won't go into how my collision works. Very game specific)
When I want to use that metatile, I can use 4 different numbers. $00, $40, $80, or $C0. They all refer to metatile 0, but will change what color it's displayed as. If a lot of your 190 metatiles are just the same 8x8 tiles with different palettes, you can save a bunch of bytes by not storing the repeats of ones that store the exact same 4 8x8 tiles and just change the palette information. Best case scenario savings for you would be 143 metatiles (3/4 of them) * 6 bytes (since those definitions aren't needed anymore, if they're just alternate palettes). And then you can save another 143 bytes because the palette information no longer needs to be defined with the metatile.
Of course that only works if less than 65 of your 16x16 metatiles are unique except for palette information. Just something to think about it.
Edit: And making a metatile dictionary for EVERY level is probably not wise. You have 190 metatiles for level one. That's 1,140 bytes, or more than a kilobyte. If that's the average for your levels, JUST for metatile definitions that seems pretty big.
Sonic games have two acts that (appear to) share the same tileset. And say... Mario Bros. 3 has the grass one it uses for sloped levels, and a generic overworld etc. It's good to have variety, but a different one for every level seems extreme, especially considering how little NES can even hold in CHR data.
tl;dr: Making a few for different areas is great, but one for every level is definitely extreme.
Edit 2: I have no idea what you mean by "only 64 palette entries". If you have 190 metatiles, you have 190 bytes that define the palette of the metatile using tokumaru's format. My format uses no bytes for palettes, since there are only four possibilities at any one time and I can afford to not have more than 64 different 16x16 metatiles per level.
There are 64 colors you can put in the 4 slots each palette set. But that shouldn't affect your metatile definition or anything else, since your metatiles can only choose the set, not the colors.
Kasumi wrote:
That sounds like a lot of metatiles for just one level.
I don't think that's too much. It really depends on the look you're going for... If you want to make the metatile grid less obvious to the player, you can end up with a fairly big amount of metatiles, especially on the NES, that doesn't allow flipping of background tiles.
Maybe we could circumvent the lack of background tile flipping by allowing the metatiles to be flipped anyway, and automatically adding a fixed offset to the tiles' indexes to locate the flipped versions. When designing the CHR you'd have to place the flipped tiles at the correct places, but the ability to flip metatiles would probably reduce the total amount in games with more complex geography.
tokumaru wrote:
Kasumi wrote:
That sounds like a lot of metatiles for just one level.
I don't think that's too much. It really depends on the look you're going for... If you want to make the metatile grid less obvious to the player, you can end up with a fairly big amount of metatiles, especially on the NES, that doesn't allow flipping of background tiles.
Maybe we could circumvent the lack of background tile flipping by allowing the metatiles to be flipped anyway, and automatically adding a fixed offset to the tiles' indexes to locate the flipped versions. When designing the CHR you'd have to place the flipped tiles at the correct places, but the ability to flip metatiles would probably reduce the total amount in games with more complex geography.
We just had a long great conversation about only having basic metatiles and 2 bit VH flipping. She is going through level 1's 190 metatiles and discarding all the flipped tiles... we are seeing how many metatiles we could save. We don't understand your way of redesigning the CHR cause the CHR uses 8x8 tiles; we think
we are dealing with flipping 16x16 metatiles.--
unregistered wrote:
We don't understand your way of redesigning the CHR cause the CHR uses 8x8 tiles; we think
we are dealing with flipping 16x16 metatiles.--
Well, if you're gonna flip the metatiles, you have to flip all 4 of its tiles, right? Say that you used a bit to indicate that a metatile is flipped horizontally. You'll put the two tiles from the left half in the right side and vice-versa. But you also need to flip the pattern, something that the NES can't do. My solution was to place the flipped tiles a constant number of tiles ahead of the original tile, so that you can simply add this number to the tile indexes in order to find the indexes of the flipped tiles.
You should do this only for the tiles you'll actually want to flip, because if you do it for all of them you'll waste a lot of CHR space.
tokumaru, thank you for helping us!
And thank you for helping us Kasumi!
The 64 metatile limit you introduced us to is too low for our game right now.
After much discussion we have concluded that level 1 will require about 190 * 4 == 760 metatiles; we have to multiply our first value by 4 because there are 4 different palettes. You can't choose a lego and then choose its color... you have to choose a blue lego or a red one, right? And if so, is that because it would require a much larger amount of bytes?
Maybe tokumaru was right in that 190 metatiles isn't too much for one level. I am just neurotic about saving bytes anywhere and everywhere I can.
But 760? I think anyone can agree that's way too much. (Since this is still just for one level, right?)
Keep in mind, you don't actually need a new metatile for all the palettes if you aren't going to USE them.
Let's say you have a metatile that represents a tuft of grass or the top of a tree. You'd want a definition of it that uses the palette with green colors, but there's no need to have one for say... a rock palette or a water palette.
Are you really going to USE all 190 * 4 of them? Because you do not seem to be doing a good job of designing around your limitations. If you want to make an NES game, you
must deal with its limitations or you would be better off making a game for something that does not limit you.
Let's pretend these (bad) example tiles are detailed grass and leaf flooring:
Here are some metatiles you might need:
Here are some metatiles you DON'T need, if you just design your levels around it.
Edit: I realize $06 and $13 are identical, so the count of these would be $14. My mistake.
See how quickly things get out of control when you try to have 8x8 pixel precision with metatiles? Now let's imagine I wanted extra metatiles for all four palettes despite the fact that these are grass tiles that would look weird with other colors. (We're pretending they're detailed
) That's all unnecessary, they'll never be used in a level. It's okay to have SOME metatiles like $09-$15 to break the 16x16 grid sometimes. But I guarantee you don't need as many metatiles as you think.
If shifting an object 8 pixels north, south, east or west in one of
my levels was going to save me 6, 12, 18, or 24 bytes I would do it 98% of the time. That's room for an entire SCREEN in my game.
With 760 metatiles you would need to use two bytes per map cell, which increases it size twice and decreases processing speed. It makes sense to fit metatile index in a byte, thus 256 max.
Yah, 760 is too freaking much. I can't even think of a 16-bit game that needs this many metatiles, so there's obviously something wrong here.
Most metatiles can't be used with all 4 palettes. Like Kasumi said, there's no point in offering a blue variation of a grass tile, since you'll never be using that.
Kasumi wrote:
(Since this is still just for one level, right?)
Yes one level... level 1.
Quote:
Kasumi wrote:
Are you really going to USE all 190 * 4 of them? Because you do not seem to be doing a good job of designing around your limitations. If you want to make an NES game, you must deal with its limitations...
Shiru wrote:
With 760 metatiles you would need to use two bytes per map cell, which increases it size twice and decreases processing speed. It makes sense to fit metatile index in a byte, thus 256 max.
tokumaru wrote:
Yah, 760 is too freaking much. I can't even think of a 16-bit game that needs this many metatiles, so there's obviously something wrong here.
No we are not going to use all 190*4 of them. Thank you Shiru, now my sister realizes that we are limited to 256 metatiles per level.
And after a time of rethinking she has said that level 1 will really be needing a little less than 256 metatiles without changing anything. So level 1 should work, if I could get it to...
<-- sarcastic| She said other levels will need to be changed to meet the 256 metatile limit. Sorry yall... it took us some time to learn that we might have to multiply by 4. I made a mistake...
we don't need most of those 760 metatiles for level 1.
Thank yall for answering all of my questions!
edit: Extra thanks to Kasumi! : )
Read my psuedocode:
Code:
1.) read list of metatiles
2.) read "start" screen
3.) display ' '
#1 isnt required because it is already listed as .db statements so all I have to do is include it.
#2 isnt required for the same reason.. i think.
So my question is about line #3. I'm worrying about changing the label names inside each list of metatiles. Is there a way to include each file without making label-name changes?
...Um that was about line #1... sorry (could you answer it though?
)
What are you trying to do? Is this for selecting on the main menu "Start" or then "Continue" or something, or trying to add it to the screen like "Level one!" then a little later "Go!" or something on the screen?
unregistered wrote:
Read my psuedocode:
Code:
1.) read list of metatiles
2.) read "start" screen
3.) display ' '
#1 isnt required because it is already listed as .db statements so all I have to do is include it.
#2 isnt required for the same reason.. i think.
So my question is about line #3. I'm worrying about changing the label names inside each list of metatiles. Is there a way to include each file without making label-name changes?
...Um that was about line #1... sorry (could you answer it though?
)
3gengames wrote:
What are you trying to do? Is this for selecting on the main menu "Start" or then "Continue" or something, or trying to add it to the screen like "Level one!" then a little later "Go!" or something on the screen?
No, sorry, my sister named each screen that you start on "...start." I was refering to that. Reading the "start" screen's metatile 240 byte representation...
Just text on the screen I'd maybe find a way to do that dynamically. I mean, mostly text isn't used in a game environment per say. I hope that may help some. Unless you need text on the screen in your game and adding it dynamically would be too hard/too CPU intensive.
3gengames wrote:
Just text on the screen I'd maybe find a way to do that dynamically. I mean, mostly text isn't used in a game environment per say. I hope that may help some. Unless you need text on the screen in your game and adding it dynamically would be too hard/too CPU intensive.
How would you add text dynamically?
Via a subroutine that adds it on to the screen when needed or have it put it on the background when you hit a certain part of the map, rewriting the meta tiles being written on the screen. Having metatiles just for some text messages might work good enough, but anything bigger and it'd create way too many.
Forgive me if I'm dense, but I'm still confused with what you're trying to do.
You have completed the following steps, correct?
1. Made a series of .db statements for your metatiles.
2. Made a .db statement with the label "start:" for each (starting) screen? (I am led to believe this from the following quote)
Quote:
No, sorry, my sister named each screen that you start on "...start." I was refering to that. Reading the "start" screen's metatile 240 byte representation...
So if this is your only question:
Quote:
Is there a way to include each file without making label-name changes?
No. You can't have two labels with the same name. That said, it's not that hard to fix with a find replace (usually ctrl+h) in each file. (Assuming I understand your problem correctly)
Edit: And btw, if I were you I would get a hex editor and make binary files for large collections of data.
Then you just do something like
Code:
label:
.incbin "metatileset00.bin"
instead of
Code:
label:
.db $00, $00, $01;etc, etc, etc
So you are now trying to display the start screen? Or you already have the start screen displayed want to display text over the start screen? (which is the understanding of the problem I believe 3gengames got)
Please explain fully, since "Display ' ' " was very ambiguous. Assume I have no knowledge of what you are trying to accomplish
OR what you have already done.
Edit2: Ooh, and can you post your metatile definition file? Something tells me you might be doing something in a much harder way than you need to.
Kasumi, please forgive me... I'm so sorry it is so confusing.
Kasumi wrote:
You have completed the following steps, correct?
1. Made a series of .db statements for your metatiles.
yes
Kasumi wrote:
2. Made a .db statement with the label "start:" for each (starting) screen?
No, my sister has labeled each level's starting screen with "start" at the end. I was just referring to that screen... for any one level.
Kasumi wrote:
So if this is your only question:
Quote:
Is there a way to include each file without making label-name changes?
No. You can't have two labels with the same name. That said, it's not that hard to fix with a find replace (usually ctrl+h) in each file... ...Edit: And btw, if I were you I would get a hex editor and make binary files for large collections of data.
Ah, that sounds interesting
, thank you for mentioning it and thank you for answering my question!
Kasumi wrote:
Please explain fully, since "Display ' ' " was very ambiguous. Assume I have no knowledge of what you are trying to accomplish OR what you have already done.
"
Display ' '" was easier to type for me than '
Display "start" screen.'... right now I'm about to try displaying the 240 byte "start" screen.
Kasumi wrote:
Edit2: Ooh, and can you post your metatile definition file? Something tells me you might be doing something in a much harder way than you need to.
That is very kind of you
, but my sister said wait. And she created our metatile definition file... so it's her decision.
edit: 3gengames, thanks for responding!
Sorry, I was planning on including this message to you in my post... but then I got very distracted and forgot. Now it's time to sleep. Good night.
Ah, I get it. So like:
Code:
start:
.db ;bytes for screen 0
start:
.db ;bytes for screen 1
Yeah, can't be done that way. How would the assembler know what label lda start refers to when there are two "start"s? Personally, I name them screenX_Y. X is the set number, Y is the actual screen number in hex.
Displaying a screen is where it starts to get a little tough.
To update tiles in a nametable, you use $2006, and $2007. Easy mode involves no scrolling (for now).
First disable rendering and set the PPU to increment by one by writing the relevant bits to $2000.
Then write the address of a name table to $2006 in two writes.
Code:
;No indentation for the instructions, but you'll get the point.
lda #$20
sta $2006;Sets the high byte of the address $2007 will write to.
lda #$00
sta $2006;Sets the low byte of the address $2007 will write to.
sta $2007;Writes #$00 to PPU address $2000. (which is the first tile of the first name table)
lda #$FF
sta $2007;Writes #$FF to PPU address $2001. (which is the tile to the right of the first tile)
You'll see that every write increases the destination address by one. This is so you don't have to keep setting $2006 which is slow. It's also possible to make the destination address increase by 32 each write. This is so you can write a column of tiles. (256/8 = 32 tiles across. So each write will give you the address of the tile below the last one written)
In any case:
You know that your metatiles contain a reference to 4 actual tiles.
After setting the name table address with $2006:
1. You load the first metatile in your screen's index number.
2. You load the top left tile of that metatile using the index.
3. You write this to $2007.
4. You load the top right tile of that metatile using the index.
5. You write this to $2007.
6. load the second metatile in your screen's index number.
(Why? Because the other two tiles in the first metatile refer to the row of tiles BELOW the one you're dealing with)
7. Repeat steps 2-5.
8. Load the third tile... etc etc etc until the entire of the first row is written
9. Load the first metatile in your screen's index number AGAIN.
10. Load the BOTTOM left tile of that metatile using the index.
11. You write this to $2007.
And from there you should be able to figure out the rest for tiles, but probably not attributes. Reenable rendering when you're done writing to $2007, and you'll see the result.
There are ways to load each metatile in the screen only once, but make some code that is easy to do and works before you get clever.
For attributes, you do kind of the same thing. Write the address of the attribute tile you want to update to $2006, then write the actual attribute to $2007. Because of your metatile format, this might actually be really easy for you to figure out. (It's hard as hell to do in my format
... I digress)
That's... about all I will say. Good luck! Try it, and post some code if you get stuck! I've never asked, will your game scroll? If yes, hopefully only in one direction at a time? If no..
For working with different sets of data (i.e. the different screens of different levels) you have to use pointers,
I already told you that. That example was for different sets of collision tables, but the principle is exactly the same for everything else.
A pointer is an address you can modify dynamically, so if you use pointers to access the data you can have the same code work with different sets of data just by modifying the pointer dynamically.
tokumaru wrote:
For working with different sets of data (i.e. the different screens of different levels) you have to use pointers,
I already told you that. That example was for different sets of collision tables, but the principle is exactly the same for everything else.
A pointer is an address you can modify dynamically, so if you use pointers to access the data you can have the same code work with different sets of data just by modifying the pointer dynamically.
Thank you for telling me again tokumaru!
It suddenly makes sense after I spent time understanding this part of 6502.txt:
Code:
10) Post-indexed indirect-
In this mode the contents of a zero-page address (and the following byte)
give the indirect addressm which is added to the contents of the Y-register
to yield the actual address of the operand. Again, inassembly language,
the instruction is indicated by parenthesis.
eg. LDA ($4C), Y
Note that the parenthesis are only around the 2nd byte of the instruction
since it is the part that does the indirection.
Assume the following - byte value
$004C $00
$004D $21
Y-reg. $05
$2105 $6D
Then the instruction above executes by:
(i) getting the address in bytes $4C, $4D = $2100
(ii) adding the contents of the Y-register = $2105
(111) loading the contents of the byte $2105 - i.e. $6D into the
accumulator.
Note: only the Y-register is used in this mode.
and then I reread your post you linked to
many times. Awesome to understand and know; now I need to use it!
added in edit
Kasumi wrote:
Displaying a screen is where it starts to get a little tough.
To update tiles in a nametable, you use $2006, and $2007. Easy mode involves no scrolling (for now).
First disable rendering and set the PPU to increment by one by writing the relevant bits to $2000.
Then write the address of a name table to $2006 in two writes.
Code:
;No indentation for the instructions, but you'll get the point.
lda #$20
sta $2006;Sets the high byte of the address $2007 will write to.
lda #$00
sta $2006;Sets the low byte of the address $2007 will write to.
sta $2007;Writes #$00 to PPU address $2000. (which is the first tile of the first name table)
lda #$FF
sta $2007;Writes #$FF to PPU address $2001. (which is the tile to the right of the first tile)
You'll see that every write increases the destination address by one. This is so you don't have to keep setting $2006 which is slow. It's also possible to make the destination address increase by 32 each write. This is so you can write a column of tiles. (256/8 = 32 tiles across. So each write will give you the address of the tile below the last one written)
In any case:
You know that your metatiles contain a reference to 4 actual tiles.
After setting the name table address with $2006:
1. You load the first metatile in your screen's index number.
2. You load the top left tile of that metatile using the index.
3. You write this to $2007.
4. You load the top right tile of that metatile using the index.
5. You write this to $2007.
6. load the second metatile in your screen's index number.
(Why? Because the other two tiles in the first metatile refer to the row of tiles BELOW the one you're dealing with)
7. Repeat steps 2-5.
8. Load the third tile... etc etc etc until the entire of the first row is written
9. Load the first metatile in your screen's index number AGAIN.
10. Load the BOTTOM left tile of that metatile using the index.
11. You write this to $2007.
And from there you should be able to figure out the rest for tiles, but probably not attributes. Reenable rendering when you're done writing to $2007, and you'll see the result.
There are ways to load each metatile in the screen only once, but make some code that is easy to do and works before you get clever.
For attributes, you do kind of the same thing. Write the address of the attribute tile you want to update to $2006, then write the actual attribute to $2007. Because of your metatile format, this might actually be really easy for you to figure out. (It's hard as hell to do in my format
... I digress)
That's... about all I will say. Good luck! Try it, and post some code if you get stuck!
Kasumi, thank you so much for this detailed explanation; loading a screen doesn't seem a little tough anymore!
It's about to become interesting though... I have to remove the loading of the nametables and replace that with a combination of your post and tokumaru's post he linked me to in this thread. Yes, this is taking me a long time to complete, but I feel blessed by yall's help... so thank you tokumaru and Kasumi!
Kasumi wrote:
I've never asked, will your game scroll? If yes, hopefully only in one direction at a time? If no..
Yes, it will scroll only horizontally.
So now I have two arrays...
firstRow and
secondRow. Each are 32bytes wide. There's also an array called
screenArray; it is 240 bytes wide. I want
screenArray to hold the contents of
firstRow and
secondRow. I dont know how to do this in 6502 assembly; could you help me with this? : )
edit: ...would I have to create two for loops like
Code:
for (int x := 0; x < 32; x++) {
screenArray[x] = firstRow[x];
}
for (int x := 33; x < 64; x++) {
screenArray[x] = secondRow[x];
}
Load the values in firstRow and secondRow, and store them to screenArray?
Code:
loop:
ldx #$1F
lda firstRow,x
sta screenArray,x
dex
bpl loop
loop2:
ldx #$1F
lda secondRow,x
sta screenArray+32,x
dex
bpl loop2
edit: Oooh, here's a faster way to do the above:
Code:
loop:
ldx #$1F
lda firstRow,x
sta screenArray,x
lda secondRow,x
sta screenArray+32,x
dex
bpl loop
Whenever you want to load one thing and put it someplace else, just use lda and sta. There's no trick to it.
Bonus:
If firstRow and secondRow are stored like this:
Code:
firstRow:
.db $00 $01 ;etc etc 32 bytes
secondRow:
.db $20 $21
;That is, they are stored such that there are no bytes between them
You can just do this:
Code:
ldx #$3F
loop:
lda firstRow,x
sta screenArray,x
dex
bpl loop
Though actually, that's slower than the above.
Edit in response to your edit:
Quote:
edit: ...would I have to create two for loops like
Code:
for (int x := 0; x < 32; x++) {
screenArray[x] = firstRow[x];
}
for (int x := 33; x < 64; x++) {
screenArray[x] = secondRow[x];
}
Almost.
secondRow is 32 bytes long. secondRow[0] refers to the first byte of secondRow. So that second loop can't work. X starts at 33 (which would be secondRow[33]) which is either the third row's data, or unknown data.
Also, you can't start X at 33. You stopped at a value < 32 in the first loop, therefore you have never written the 32nd byte of screenArray if X starts at 33 in the second loop.
YES!! Thank you so much Kasumi!
Spent a long time on my edit... sorry, I didn't see your post.
edit: Ahhh! Ok, thank you, I see what you are saying.
for the second loop:
Code:
for (int x=0; x<32; x++) {
screenArray[x+ #32] = secondRow[x];
}
Yep, you got it.
Just some additional info: The reason I went down in my asm code, not up (like the C code) is to avoid an extra instruction.
Code:
ldx #$00;If you go up in a loop like this
loop:;You need a cmp.
lda firstRow,x
sta screenArray,x
inx
cpx #$40;If X is < #$20
bcc loop;Go back to loop
That cpx takes 2 cycles every time. That loop runs 64 times. That's 128 cycles of unnecessary action.
Avoiding extra actions is also why this was faster than the first and last thing I posted:
Code:
loop:
ldx #$1F
lda firstRow,x
sta screenArray,x
lda secondRow,x
sta screenArray+32,x
dex
bpl loop
The first thing I posted had two loops, so dex was done 64 times compared to the 32 for this. The second thing would have also run dex 64 times.
Not that you should worry about optimization a lot right now, but when you're writing loops keep that sort of thing in the back of your mind.
Kasumi wrote:
Just some additional info: The reason I went down in my asm code, not up (like the C code) is to avoid an extra instruction.
Code:
ldx #$00;If you go up in a loop like this
loop:;You need a cmp.
lda firstRow,x
sta screenArray,x
inx
cpx #$40;If X is < #$20
bcc loop;Go back to loop
That cpx takes 2 cycles every time. That loop runs 64 times. That's 128 cycles of unnecessary action.
Avoiding extra actions is also why this was faster than the first and last thing I posted:
Code:
loop:
ldx #$1F
lda firstRow,x
sta screenArray,x
lda secondRow,x
sta screenArray+32,x
dex
bpl loop
The first thing I posted had two loops, so dex was done 64 times compared to the 32 for this. The second thing would have also run dex 64 times.
Not that you should worry about optimization a lot right now, but when you're writing loops keep that sort of thing in the back of your mind.
Kasumi, thank you!
!!!!!! I'll
try to keep that in the back of my mind.
*edited
If I have
00001111 in the accumulator... how could I check the bit value of the ones? Like how would I say check the value of bit #2? Is there a faster way to do this than
Code:
ror a
ror a
and #$01
beq +
;code for a value of 1
+
;code for a value of 0
Does that even work? I think it does.
How about just "AND #$04" to check bit 00000100?
Dwedit wrote:
How about just "AND #$04" to check bit 00000100?
But the result wouldn't be a 1 or a zero... But, beq only focuses on a 0. So... is that right?
edit: That's brilliant! Thanks Dwedit!
Have a happy new year!
unregistered wrote:
But the result wouldn't be a 1 or a zero... But, beq only focuses on a 0. So... is that right?
As you probably have realized, if the result is not 0 you know that the bit is set. If you wanted to copy any bit to the carry flag (sometimes we do that when rearranging bits) you could use an AND followed by a CMP:
Code:
;copy bit 2 to the carry flag
and #%00000100
cmp #%00000100
Then you can shift the bit into some variable using ROR or ROL, or do whatever else you want with it.
unregistered wrote:
How do I know if the something IS water?
How do I check the tile number?
One of the ideas we had consisted in
using 1 bit to select between water or air for the empty tiles, and 3 bits for the type of the solid tiles. In this case it would be easy to know if something is water, but since you decided to use the tile index...
Well, your
screenArray only has the basic collision info, it doesn't say which tiles each block uses. The only way is to make
screenArray hold
the index of the metatiles instead of the collision info. That way, whenever you wanted to test for collisions, you'd have to get the metatile index at the position you want and use that to fetch the collision info from a table. With the same index you can easily access the tile indexes too. It's a bit more indirect, but that's not too bad.
The last part has me confused. If
screenArray held "
the index of the metatiles instead of the collision info" would that destroy our library and collision files? That would be terrible! My sister has done so many of them so far.
That first thing you mentioned about "
using 1 bit to select between water or air for the empty tiles, and 3 bits for the type of the solid tiles." Since we have two extra bits could we set one of them to select between water or air for the empty tiles by itself (without the 3 bits for the solid type tiles)? Then wouldn't it be much easier to know if something is water?
tokumaru wrote:
unregistered wrote:
But the result wouldn't be a 1 or a zero... But, beq only focuses on a 0. So... is that right?
As you probably have realized, if the result is not 0 you know that the bit is set. If you wanted to copy any bit to the carry flag (sometimes we do that when rearranging bits) you could use an AND followed by a CMP:
Code:
;copy bit 2 to the carry flag
and #%00000100
cmp #%00000100
Then you can shift the bit into some variable using ROR or ROL, or do whatever else you want with it.
This is good... and it works because
a)The AND puts 4 into the accumulator...
b)then... the CMP puts (Accumulator - Memory) in the Accumulator?
c)so now the Accumulator has a 0. Does that 0 clear the Carry flag?
d) ...I dont know why this works...Could you help me please?
unregistered wrote:
a)The AND puts 4 into the accumulator...
No, it ANDs whatever is in the accumulator with 4, the result will only be 4 if bit 2 was set in the original value, otherwise it will be 0.
Quote:
b)then... the CMP puts (Accumulator - Memory) in the Accumulator?
No, CMP does not change the value of the accumulator. It does calculate A - 4, but the result is not stored anywhere. What matters to us is the value of the carry after this subtraction: if the accumulator is 4, the subtraction will "succeed" (i.e. there will be no underflow) and the carry will be set. If the accumulator is 0, the subtraction will cause an underflow, clearing the carry. This causes the carry to become whatever bit 2 was in the first place, so we have effectively "copied" bit 2 into the carry flag.
Tokumaru covered CMP, but here's a little more explanation on what AND and the other bitwise operators (hey, why not?) do, and why what Dwedit suggested works:
AND (like ORA and EOR) at a basic level gives an output of 1 bit from 2 input bits. Order doesn't matter in any of them, so there are three cases.
1. Both bits are 0.
2. Both bits are 1
3. 1 bit is 1, and the other is 0.
AND gives an output of 1 if both input bits were 1. (if input bit 1 is 1 [true] AND input bit 2 is 1 [true], the result is true.) See how it got its name?
So, AND will only return 1 in case 2, otherwise it gives 0.
ORA gives an output of 1 if either input bit is one. (If input bit 1 is 1 [true] OR input bit 2 is 1[true], the result is true.) See how it got its name?
So ORA will return 1 in every case except case 1. It will return 0 in case 1.
EOR (Exclusive OR) gives an output of 1 if EXACTLY one of the input bits is 1. (If input 1 and ONLY input is 1 [true], the result is true. If input 2 and ONLY input 2 is 1[true], the result is true)
So EOR will return 1 only in case 3, otherwise it gives 0.
Now, at a basic level they work one bit at a time. The instructions work a byte at a time with corresponding bits in a byte.
Code:
#%00000000
^^^^^^^^
||||||||
BIT#: 76543210
#%00000000
They check and return the result of bits 7 in each byte, and while that check is happening what's in bits 6-0 doesn't matter. Then it does bits 6, and while that check is happening what's in bits 7 and 5-0 doesn't matter. Etc.
AND is useful for checking the status of a specific bit, because by definition of AND, a bit with a 0 in either byte will return 0.
So if you AND with a number that has 7 bits as 0, the result of those seve bits will be 0. That means the bit you left 1 in the AND determines whether the accumulator is 0, or non-0.
Another use for AND is clearing a bit (setting it to 0). #%01111111 will clear the leftmost bit without changing the others. The 0 is guaranteed to clear the high bit because the 0 means both bits CAN'T be 1, and the 7 ones guarantee the other bits won't change.
EOR is useful for toggling a bit (0->1 or 1->0). This is because a 0 in an EOR is guaranteed to not change the bit it is being EOR'd with, while a 1 in an EOR is guaranteed TO change the bit it is being EOR'd with.
ORA is useful for setting a bit to 1. This is a because a 1 is guaranteed to give a result of 1, while a 0 will never change the bit you are ORA'ing with.
tokumaru, thank you so much for helping me with this!
tokumaru wrote:
if the accumulator is 4, the subtraction will "succeed" (i.e. there will be no underflow) and the carry will be set.
No underflow? So water will not flow under the subtraction and that sets the carry? (trying to get you to teach me more about "underflow" - im interested)
----
tried to make this easier to read... for me at least... just added colors
Kasumi wrote:
Tokumaru covered CMP, but here's a little more explanation on what AND and the other bitwise operators (hey, why not?) do, and why what Dwedit suggested works:
AND (like ORA and EOR) at a basic level gives an output of 1 bit from 2 input bits. Order doesn't matter in any of them, so there are three cases.
1. Both bits are
0.
2. Both bits are
13. 1 bit is
1, and the other is
0.
AND gives an output of
1 if both input bits were
1. (if input bit 1 is
1 [true] AND input bit 2 is
1 [true], the result is
true.) See how it got its name?
So, AND will only return
1 in case
2, otherwise it gives
0.
ORA gives an output of
1 if either input bit is one. (If input bit 1 is
1 [true] OR input bit 2 is
1[true], the result is
true.) See how it got its name?
So ORA will return
1 in every case except case
1. It will return
0 in case
1.
EOR (Exclusive OR) gives an output of
1 if EXACTLY one of the input bits is
1. (If input 1 and ONLY input is
1 [true], the result is
true. If input 2 and ONLY input 2 is
1[true], the result is
true)
So EOR will return
1 only in case
3, otherwise it gives
0.
Now, at a basic level they work one bit at a time. The instructions work a byte at a time with corresponding bits in a byte.
Code:
#%00000000
^^^^^^^^
||||||||
BIT#: 76543210
#%00000000
They check and return the result of bits 7 in each byte, and while that check is happening what's in bits 6-0 doesn't matter. Then it does bits 6, and while that check is happening what's in bits 7 and 5-0 doesn't matter. Etc.
AND is useful for checking the status of a specific bit, because by definition of AND, a bit with a
0 in either byte will return
0.
So if you AND with a number that has 7 bits as
0, the result of those seve bits will be
0. That means the bit you left
1 in the AND determines whether the accumulator is
0, or
non-0.
Another use for AND is clearing a bit (setting it to
0). #%01111111 will clear the leftmost bit without changing the others. The
0 is guaranteed to clear the high bit because the 0 means both bits CAN'T be
1, and the 7 ones guarantee the other bits won't change.
EOR is useful for toggling a bit (
0->
1 or
1->
0). This is because a 0 in an EOR is guaranteed to not change the bit it is being EOR'd with, while a 1 in an EOR is guaranteed TO change the bit it is being EOR'd with.
ORA is useful for setting a bit to 1. This is a because a 1 is guaranteed to give a result of
1, while a 0 will never change the bit you are ORA'ing with.
Kasumi, thanks for helping us!
The logic surrounding your cases is interesting.
: ) There are shortcuts or cycles that can be saved, right? I have tried to think of one, but I can't think of anything...
unregistered wrote:
No underflow?
You know how sbc works, right?
Let's assume the carry is set before the sbc. Let's assume we're working with unsigned numbers. (#$FF = 255. Signed would mean #$FF = -1)
So this
Code:
sbc foo
will subtract foo from the accumulator. If the accumulator ends up as a larger number than what you started with (example: 0 - 1 = 255), the carry is cleared. That's underflow. If there was no underflow, the carry stays set.
(Overflow is the term for addition when you end up with a number lower than what you started with [example: 255+1 = 0])
CMP does nearly the exact same thing as SBC, except for two things:
1. CMP doesn't care about the carry flag's status before it's run. It could be cleared and an extra one would not be subtracted.
2. CMP doesn't store the result of the subtraction in the accumulator. A never changes.
CMP (or even SBC) works to compare numbers because cmping a larger number guarantees the carry will be clear, and vice versa. Think: If you subtract a larger number from a smaller number you will always pass 0. (And wrap to #$FF on the 6502.)
If the number you subtract is the same, the carry stays sets and the zero flag is set because the result is 0.
What tokumaru was suggesting works because anything cmp'd with 0 that is not 0 gives a result that is not 0. And anything that is not zero clears the 0 flag.
Code:
lda byte
and #%00010000;This guarantees the accumulator is either
;#%00000000 (beq would branch)
;or
;#%00010000 (bne would branch)
;If you cmp #%00010000, it actually reverses that for reasons that should be clear given the above explanations.
cmp #%00010000
;if the accumulator has #%00000000 bne would branch. #%00000000 - #%00010000 is not 0.
if the accumulator has #%00010000 beq would branch. #%00010000 - #%00010000 is 0
unregistered wrote:
There are shortcuts or cycles that can be saved, right? I have tried to think of one, but I can't think of anything...
Optimization is my favorite business, but it's a tricky, possibly dangerous one that's always specific to what you want to do. Get your code to work first, then make it fast.
Quick tips:
Even though I just typed a book describing it, avoid cmping to reverse the 0 flag.
If you just use the other branch, you can avoid the cmp which always makes sense to me.
You shouldn't ever have to and #%10000000 to check that bit because bpl and bmi already check that highest bit when it's loaded
BIT puts the highest two bits (marked X) #%XXYYYYYY into flags you can branch on, but only for variables stored in a static place.
Because of that I always place the two things the need to be checked most often in those bits when I write data formats.
I know Kasumi already explained a lot but I still want to say a few things.
unregistered wrote:
No underflow? So water will not flow under the subtraction and that sets the carry? (trying to get you to teach me more about "underflow" - im interested)
An overflow is when the result is too large to be represented by the accumulator, and an underflow is when it's too small.
For unsigned numbers, an overflow is when an addition results in a number larger than 255, and an underflow is when the result of a subtraction is less than 0. For signed numbers, an overflow means an addition with a result larger than 127 and an underflow is a subtraction with a result of less than -128.
Kasumi wrote:
Because of that I always place the two things the need to be checked most often in those bits when I write data formats.
I often put my flags in the top 2 bits as well.
Thank you Kasumi and tokumaru!
Kasumi wrote:
First disable rendering... by writing the relevant bits to $2000
How do I disable rendering?
The 7th bit of $2000 starts or stops an NMI from running at the start of vertical blanking. Does that somehow disable rendering?
unregistered wrote:
Kasumi wrote:
First disable rendering... by writing the relevant bits to $2000
How do I disable rendering?
The 7th bit of $2000 starts or stops an NMI from running at the start of vertical blanking. Does that somehow disable rendering?
Kasumi probably meant $2001, where bits 3 and 4 enable or disable background and sprite rendering. To disable rendering you have to clear both bits.
Bit 7 of $2000 controls whether NMIs fire or not when VBlank starts, but the rendering process isn't affected at all.
tokumaru, thank you so much!
Do you want to use different data for each level or something like that? If that's the case, then the answer is the indirect indexed addressing mode (i.e.
LDA ($XX), Y). With that addressing mode you use pointers to define the tables that will be read, and you can alter the pointers as much as you want.
For example, you could have a different name for each collision table (or whatever table you want), and then make a table with all the addresses:
Code:
MetatileCollisionAdresses:
.dw MetatileCollisionLevel1, MetatileCollisionLevel2, MetatileCollisionLevel3
Then you can read the address for the current level and put it in a pointer using the level's index
When you say
level's index that could be 1 for level1 and 2 for level2 right?
Quote:
:
Code:
lda LevelIndex ;get the level's index
asl ;multiply by 2 because each address is 2 bytes
tax ;use that as an index into the table of addresses
lda MetatileCollisionAdresses+0, x ;copy the low byte
sta MetatileCollision+0
lda MetatileCollisionAdresses+1, x ;copy the high byte
sta MetatileCollision+1
MetatileCollision isnt set up for an address of 2 bytes...
Quote:
Then you can use indirect indexed addressing to read the data, instead of what we had before. The only real difference is that now you'll have to use Y as your index register, because this addressing mode doesn't work with X:
Code:
;get collision information
lda (MetatileCollision), y
I'm attempting to use this, for the first time, it's kind of confusing and kind of something to learn from. Hope it becomes less of the former and more of the latter; it will.
unregistered wrote:
When you say level's index that could be 1 for level1 and 2 for level2 right?
Yup. Or 0 for level 1, 1 for level 2, and so on... just make sure that everything lines up correctly.
Quote:
MetatileCollision isnt set up for an address of 2 bytes...
In that example, that variable is a
pointer. A pointer is a variable that contains the address of something (i.e. it "points" to that thing). Since addresses have 16 bits on the 6502, the variable must be 2 bytes long.
Quote:
I'm attempting to use this, for the first time, it's kind of confusing and kind of something to learn from. Hope it becomes less of the former and more of the latter; it will.
Just keep in mind that pointers are addresses that point to something else, and by manipulating them you can have the same variable point to different places in the ROM. The main advantage of using pointers is that the same code can access different data all across the ROM, you just have to "point" to the data you want to use at any given time. This is how you can make the same code (your game logic) access the data of the first level as well as the data of the second level, and the third, and so on.
tokumaru wrote:
Quote:
MetatileCollision isnt set up for an address of 2 bytes...
In that example, that variable is a
pointer. A pointer is a variable that contains the address of something (i.e. it "points" to that thing). Since addresses have 16 bits on the 6502, the variable must be 2 bytes long.
So each pointer variable must be 2 bytes long? I'm guessing you have already said yes to this, but I'd like confirmation... please.
tokumaru wrote:
Quote:
I'm attempting to use this, for the first time, it's kind of confusing and kind of something to learn from. Hope it becomes less of the former and more of the latter; it will.
Just keep in mind that pointers are addresses that point to something else, and by manipulating them you can have the same variable point to different places in the ROM. The main advantage of using pointers is that the same code can access different data all across the ROM, you just have to "point" to the data you want to use at any given time. This is how you can make the same code (your game logic) access the data of the first level as well as the data of the second level, and the third, and so on.
If I have a variable called
rowPointer that's a size of 2 bytes, then do I have to use the indirect index addressing to read
rowPointer as a pointer? Is indirect index addressing the only way to use pointers in assembly language?
I remember I could put a dot in front of a pointer's name to use it... maybe... in a language like c++? I just want to say "Go to
rowPointer's row." How can I say that in assembly 6502.
Quote:
So each pointer variable must be 2 bytes long? I'm guessing you have already said yes to this, but I'd like confirmation... please.
It says exactly this in the quote. He said it must be, and he said why.
Quote:
If I have a variable called rowPointer that's a size of 2 bytes, then do I have to use the indirect index addressing to read rowPointer as a pointer? Is indirect index addressing the only way to use pointers in assembly language?
Edit: On second thought, It is more or less the only way to read from rowPointer as a pointer. You will want to use indirect indexed for what you're trying now.
For question two: This is not the only way to use pointers in 6502. There's also indexed indirect, but people don't generally use that because it is a very indirect way to get data from an address. As well, you can jmp to a pointer which is just called indirect to my knowledge, and you can do creative things with the stack and RTS. But none of that matters for what you're doing right now.
Quote:
I remember I could put a dot in front of a pointer's name to use it... maybe... in a language like c++?
6502 assembly isn't "type safe" like C is. What this means is your program/assembler doesn't know or care what you are using your variables for. C will actually fix how operations work on a variable based on what you're using that variable for. For instance, pointer++; could increase the address the pointer is holding by 1, 2 or any other number depending on how large the type of data is that the pointer is supposed to point to. Assembly doesn't care, so it's up to you to keep track.
Quote:
I just want to say "Go to rowPointer's row." How can I say that in assembly 6502.
Edit 2: After writing this post, I realize I may have misunderstood your question. Assuming rowPointer and rowPointer+1 already contain the low and high bytes of the address you want to point to, it's as easy as
Code:
lda (rowPointer),y
where y contains how many places to add to that address to get the byte you want.
The below examples may still be helpful, though.
Code:
row:
.db $whatever, $whatever, $whatever; i.e. your data.
lda #low(row);Syntax for how to get the high and low part of an address may vary based on your assembler
sta $00;Store in a RAM location.
lda #high(row)
sta $01;Store in a RAM location.
lda ($00),y;This now gets the same result in A that lda row,y would.
$00, and $01 are your pointer variables.
;Note that the variable that holds the LOW BYTE of the pointer's address goes in between the paranthesis.
;Note: To use this addressing mode, the low byte of the address MUST BE stored in a RAM location that is EXACTLY one byte before the RAM location where the high byte is stored.
;$00 = low
;$01 = high; is fine.
;$01 = low
;$02 = high; is fine.
;F1 = low
;F2 = high; is fine.
;$01 = low
;$00 = high; is NOT fine.
Naturally the above code is pretty useless. You could use lda rowPointer,y instead. So here's something useful.
Code:
Row0:
.db $00, $00;Your data
Row1:
.db $FF, $FE; $50;Your data
Row2:
.db $05;You get the point.
;etc
;Now you keep two separate lists of the high and low byte for this data.
RowLow:
.db low(Row0);As said before, syntax for this may vary
.db low(Row1)
.db low(Row2);etc
RowHigh:
.db high(Row0)
.db high(Row1)
.db high(Row2)
ldy rownumber
lda RowLow,y
sta $00
lda RowHigh,y
sta $01
ldy #$00
lda ($00),y;Depending on what "rownumber" is you will load from a different address.
;if rownumber was 0:
;You load #$00 which is the first byte of data after the Row0 label.
;if rownumber was 1:
;You load #$FF which is the first byte of data after the Row1 label.
;if rownumber was 2:
;You load #$05 which is the first byte of data after the Row2 label.
That's how you do it. (Provided I didn't make a stupid mistake like saying $2000 instead of $2001.)
Are there any rules to be aware of when using the stack? Thought I would push processor status on stack 4 times so that I could use the plp same settings for each of the four times I read a bit from the 4bit metatile part inside my loops.
By "rules to be aware of" I mean is it possible that other things will use the stack? At any or at certain times?
Kasumi wrote:
That's how you do it. (Provided I didn't make a stupid mistake like saying $2000 instead of $2001.)
Ah, it's ok to make a mistake. That's what we learn from.
You have to pay attention not to push more bytes than you have allocated for the stack, but that's basically it. Most people just use the whole page ($0100-$01FF) for it, and filling 256 bytes with temporary data isn't easy, so you're probably safe.
The only events that use the stack non-explicitly are interrupts and jumps to subroutines. When you JSR to a subroutine, the current PC (the program counter, an internal register that points to the instruction being executed) is backed up to the stack, so that you can return to that point of the program with RTS. Interrupts also save the PC, as well as the processor status, so that the CPU can resume its work later (after an RTI), like the interrupt never happened.
Those things don't get in the way of using the stack normally (PHA, PLA, PHP and PLP), since even if an interrupt happens in the middle of you using the stack, whatever is put there will be removed when the returning from the interrupt, so the stack will be exactly like it was before the interrupt.
It's also pretty safe to manipulate the middle of the stack using the X register, since only the top of it will be modified in case of interrupts.
What can cause problems is an interrupt firing when you are directly manipulating the stack pointer (through TSX and TXS)... If S is not pointing to the top of the stack, parts of it might get corrupted. You probably want to avoid this kind of stack manipulation.
What you want to do (push 4 values for using later) is perfectly safe, you just have to remember to remove everything you put on the stack. I for example have made the mistake of putting some values in the stack, and then some logic decisions made the program not need those values anymore, and I just forgot to take them off the stack. Over time, the forgotten values accumulated and caused a stack overflow and the program crashed.
tokumaru, thank you so much!!!
Your stack explanation here brought back the feeling that you could make a great author!
I would be excited to buy your NES assembly-guide-book-type-thing.
And it would be nice to share with others.
Kasumi, I owe you a response... and it is coming, I promise. Hope you are doing well.
unregistered wrote:
I would be excited to buy your NES assembly-guide-book-type-thing.
Hell no, I'm a terrible teacher!
If I have an accumulator value id like to keep... for a little while; would it be dangerous to leave it in the accumulator for about 10-12 cycles? Is it safe, like the stack is, from interrupts and subroutines?
This might be a sub-par question.
What is in the accumulator will stay in the accumulator until power-off unless another instruction changes it. Interrupt handlers are supposed to always save and restore all registers that they use, but any other subroutine might preserve the accumulator, or it might overwrite it. When you make your subroutine, be sure to add a comment stating what registers it overwrites. For example, a controller reading subroutine might have a comment at the top that it "trashes A and X; preserves Y".
Wow that's really good.
Thanks tepples!
Could my code here be done in an easier way?
Code:
asl a ;checks and saves upper left tile
bmi +
lda #$00
jmp ++n
+ lda #$01
++n sta upleft
asl a ;checks and saves upper right tile
bmi +
lda #$00
jmp ++n
+ lda #$01
++n sta upright
asl a ;checks and saves lower left tile
bmi +
lda #$00
jmp ++n
+ lda #$01
++n sta loleft
asl a ;checks and saves lower right tile
bmi +
lda #$00
jmp ++n
+ lda #$01
++n sta loright
There must be an easier way to transfer a bit to the accumulator... please tell me that.
aaaaaaah!! Thank you Shiru!
...AND #1!!!!!!!!!!!!!!!!!!!!!!!!!! YEAY!!
Now it has come to using oddRow and evenRow... I have to ask this question... someone will have an answer. The first row of tiles of the screen is not the first scanline, but the first eight scanlines and so this does not concern scanlines. ...I think.
Should I start creating the screen with oddRow or evenRow? Is that top line up there line 0 - even - OR line 1 - odd?
No matter how many times I read I really can't understand the question... for what purpose would you be using these oddRow and evenRow variables you speak of?
tokumaru wrote:
... for what purpose would you be using these oddRow and evenRow variables you speak of?
In my code idea there are two rows each 32 bytes. Then there is the screenArray; 240 bytes. I'm trying to fill both rows with values for collision... then add them both to screenArray... then clear oddRow and evenRow and fill them both again with new values for collision... then add them both to screenArray...etc.
edit: ok, I found the first mention of my plan on
page 29. That was in December but I'm still learning and building; getting better.
I'm still confused.
oddRow and evenRow have to be RAM, otherwise you wouldn't be mentioning putting new values in them.
This being true, why do you want to put anything in them only to move them someplace else? Just put the values you will load (inevitably from ROM) directly into ScreenArray. Is there any reason for that first step of putting the values into oddRow and evenRow? What do you plan to do with ScreenArray? You're putting things here to do what with them? Load a value from them to check collision, or store them to the PPU? I've only got part of your plan.
The response I made to the post you linked made the assumption that the row labels were data in ROM. Your plan seems to be different, so help me understand all the steps.
Quote:
Should I start creating the screen with oddRow or evenRow? Is that top line up there line 0 - even - OR line 1 - odd?
I don't know. Isn't it your data format? You define what's stored, where it's stored, how it's stored, what it's called and what it does.
I'm as confused as Kasumi is...
The way you are asking makes it seem like there's only 1 way to do things, and that this way always uses these variables you mentioned. But actually, collision detection can be done in a thousand different ways, and we have given you some ideas to help shape the solution you'll be using in your game.
But since it's your game, everything is ultimately your choice and your design. Particularly, I don't remember everything we talked here, and I don't know exactly what solution you used, so if you want very specific help with your implementation you'll need to give us more details about how you're doing things.
All this data moving seems unnecessary to me as well, there must be a way to decode the maps from ROM into ScreenArray directly. For us to discuss that, you have to tell us how the maps and collision info are stored in the ROM, and what you want ScreenArray to contain.
Kasumi wrote:
why do you want to put anything in them only to move them someplace else? Just put the values you will load (inevitably from ROM) directly into ScreenArray. Is there any reason for that first step of putting the values into oddRow and evenRow?
I thought loading two rows of stuff would be hard, but still possible for me to do. And I thought that once I completed that it would be easy to make a loop to run it again. Now, I see your point!
It would be much faster if I wrote to ScreenArray directly.
Kasumi wrote:
What do you plan to do with ScreenArray? You're putting things here to do what with them? Load a value from them to check collision, or store them to the PPU? I've only got part of your plan.
I plan to use ScreenArray to check collision and to set the PPU. I'm going to try writing the values directly into ScreenArray.
tokumaru wrote:
The way you are asking makes it seem like there's only 1 way to do things, and that this way always uses these variables you mentioned. But actually, collision detection can be done in a thousand different ways, and we have given you some ideas to help shape the solution you'll be using in your game.
Yes, ok, then I need to create my way... just one of the thousand.
Thanks Kasumi and tokumaru!
I have a question. There are four variables that recieve a 0-or-1 value at about the same time. Then I want to reassign each variable a second-more-important value depending on their first 0-or-1 value. It seems to me to be good to reuse the same variables for these values because it is all going to be changed a whole lot throughout the game; reusing the same four variables for these 8 values seems like maybe a good idea to me. But, then I dont know...
...I think it is bad that the four variables recieve their 0-or-1 value at about the same time because that initial valuing that happens at about the same time... making it rough to use a loop to cycle through each of the four values. And I thought I could just unroll the loop and copy the middle code three more times but I dont know.
Maybe that's what I should do? Get the code to work... and then improve it. Do yall have any helpful ideas for me about this?
edit: In asm6 I know you can have multiple + labels... it just goes to the first + it finds. Can I also have multiple labels like +xyz and +xyz?
Both asm6 and ca65 support
cheap local labels that are visible only between two ordinary labels. Names of such labels start with an @ sign. For example, you could do something like this:
Code:
do_something:
blah
blah
@loop:
blah
blah
bne @loop
rts
do_something_else:
blah
blah
@loop:
blah
blah
bpl @loop
rts
(The ca65 manual uses the term "cheap" to denote @labels because ca65 also has
.proc, a more structured way to make labels visible only within a given scope.)
In asm6 you can have many different + and - labels like this:
Code:
LDX #$10
--
LDY #$10
-
DEY
BNE -
DEX
BNE --
The - and -- are treated just like normal labels but can be reused. You can also have multiple + labels. But using many of these labels can quickly make your program messy...
But they're still much better than the ugly anonymous labels in ca65 which makes your code really hard to understand...
You can also use temporary labels like tepples' already said. But unfortunately you can't use these temporary labels together with +/- so this code is not compileable:
Code:
code
@bla
code
-
more code
BNE -
even more code
BEQ @bla
The +/- labels destroys the scope of the @bla label, so you'll get an error that @bla is an unknown label.
unregistered wrote:
Can I also have multiple labels like +xyz and +xyz?
Yes. Anything starting with "+" or "-" can be reused. When you refer to those labels, the nearest one will be used.
Sorry if I don't have anything to say about the other question, but I found it very confusing. Wouldn't you rather present us the problem in a more abstract way instead of asking details about a possible solution that exists only in your head? I mean, you seem to have some idea of how to go about your problem because you gave some thought to it, but since we don't know how or why you came to that conclusion it's hard for us to visualize the problem.
unregistered wrote:
There are four variables that recieve a 0-or-1 value at about the same time. Then I want to reassign each variable a second-more-important value depending on their first 0-or-1 value. It seems to me to be good to reuse the same variables for these values because it is all going to be changed a whole lot throughout the game; reusing the same four variables for these 8 values seems like maybe a good idea to me.
I think this is too abstract. I want you to give what the goal of the code is, and then I can think of method to do it. You can also give a goal + a method, and then it's easier for me to tell you, "Yeah, that's great. That will work for that." or "I'd do this instead, but that will work." or whatever. But method without goal means I have to say almost anything will work, or I have to guess at what the goal is. Should you use one byte of RAM for related values? Sure, why not?
In any case, here's a couple of things I'll say based on what I think you're asking about.
You've got a set of four variables. They've got a format like:
%0000XYYY
Where YYY is only set to some other value if X is true (or only set if it's false. Doesn't really matter.) Y could be a value of 0 or 1 (in which case it would only take 1 bit.) or Y could be 0-8 (with 3 bits as in the example. I assumed it took more because you didn't say that Y was specifically 1 or 0 like you did with what I'm calling X.)
If both X and Y can really only be 0 or 1, you could actually use just one byte for all four of your variables.
%XXYYZZAA
Where XX = the first and second bit of the first variable,
YY = the first and second bit of the second variable,
etc. This setup may make your code a small bit slower. Does it matter for what your goal is? Will these values be referenced a lot of times in a frame? Optimizing for RAM, or optimizing for speed? What's most important?
The (possibly only slightly) faster way to do something like this
%XYYYYYYY This way you keep the 4 variable setup you have now, and can quickly check your first value (X) to see if you need to set your second value (Y). It also allows you to use up to 7 bits for Y, but you can only use one of them. It doesn't matter.
Code:
lda variable1
bpl nosetmoreimportant;If bit (X) is not set, don't reassign second
;whatever code sets the more important variable
nosetmoreimportant:
You could even just use more bytes if say... the second more important variable needed to use 8 bits. It'd make no difference, unless you're short on RAM. It all depends on what your goal is. If, for instance, these variables don't need to be persistent across frames, it matters almost zero how much RAM you use. You could use like 16 bytes of RAM. It wouldn't matter because once the routine is finished, they're free again. You say they'll be changed a lot, but I don't know if that means they'll need to be persistent.
I'd like a goal to give better advice.
Quote:
Get the code to work... and then improve it. Do yall have any helpful ideas for me about this?
Or tell us what the code is supposed to be doing, so we can really say if the method you're suggesting is a good one.
For the record, here's what some of one of my byte format documents looks like:
Code:
$060B = XXXYYYZZ
XXX = Subpixel X
YYY = Subpixel Y
ZZ = Controller to read from.
ZZ = 0: Controller 1
ZZ = 1: Controller 2
ZZ = 2: Controller 3
ZZ = 3: Controller 4
$060D = X Velocity unscaled
$060E = Direction
X0000YYY
X = Vertical Orientation
X = 0: is not vertically flipped
X = 1: is vertically flipped
YYY = Direction
YYY = 0: is facing up
YYY = 1: is facing up-right
YYY = 2: is right etc.
;This may seem a tad confusing. If X is 1 and Y is 2, he'll face right, but be upside down (feet in the air). if X is 0 and Y is 2, he'll face right and have his feet on the ground. To make him roll, X will stay the same value, and Y will increase (or decrease) by one, until it wraps. He can roll such that his feet will touch the ground while he's facing right, or such that his feet will touch the ground while facing left.
That's at least how I document my formats, it may not be the standard way, though. A 0 means the bit is free for whatever else I can pack into the byte. No further documentation means the entire byte is used for the one value. This format may help you keep track of things, or possibly communicate your ideas, or it may not help at all!
But here it is.
tepples wrote:
Both asm6 and ca65 support cheap local labels that are visible only between two ordinary labels. Names of such labels start with an @ sign.
tepples, thank you for reminding me about the local labels starting with @. I was thinking that labels with + or - at the front were local labels. Ha hahaha
<-- look I'm silly.
Yggi wrote:
The +/- labels destroys the scope of the @bla label, so you'll get an error that @bla is an unknown label.
Thank you Yggi for your help and especially your note that +/- destroys scope of local labels!
tokumaru wrote:
unregistered wrote:
Can I also have multiple labels like +xyz and +xyz?
Yes. Anything starting with "+" or "-" can be reused. When you refer to those labels, the nearest one will be used.
I was hoping for this reply. I am excited!
Quote:
Sorry if I don't have anything to say about the other question, but I found it very confusing. Wouldn't you rather present us the problem in a more abstract way instead of asking details about a possible solution that exists only in your head? I mean, you seem to have some idea of how to go about your problem because you gave some thought to it, but since we don't know how or why you came to that conclusion it's hard for us to visualize the problem.
Yes.
I'm sorry for asking something that is hard for yall to visualize. My sister suggested I make more notes to myself and after that when I have a real question ask that. That was a good suggestion!
Kasumi wrote:
I think this is too abstract. I want you to give what the goal of the code is, and then I can think of method to do it. You can also give a goal + a method, and then it's easier for me to tell you, "Yeah, that's great. That will work for that." or "I'd do this instead, but that will work." or whatever. But method without goal means I have to say almost anything will work, or I have to guess at what the goal is. Should you use one byte of RAM for related values? Sure, why not?
In any case, here's a couple of things I'll say based on what I think you're asking about.
You've got a set of four variables. They've got a format like:
Quote:
If both X and Y can really only be 0 or 1, you could actually use just one byte for all four of your variables.
All of this is important to me; Kasumi, thank you so much for this excellent help!
My mind is growing. Good things like this:
Kasumi wrote:
This setup may make your code a small bit slower. Does it matter for what your goal is? Will these values be referenced a lot of times in a frame? Optimizing for RAM, or optimizing for speed? What's most important?
My goal is to see this collision detection work well. (Optimizing for speed. right now... I think) And to find out why there are so many
Can't determine address. error lines when I try to assemble my nes file. But, that is for only me to worry about right now.
I will come back and reread especially Kasumi's post many many times.
Quote:
My goal is to see this collision detection work well.
I don't quite see the relation to collision detection.
But sure, I can sure talk about it. It's actually really easy and fast as long as you don't handle slopes, and don't have things like tiles you can jump up through or tiles you can drop down through.
You only really need to check 6 points to be TOTALLY safe, or 4 if the shape of your character never changes and he is smaller than the size of the collision tile for both X and Y. If he's say... 24 pixels tall with 16x16 collision tiles you have to check one more point. You also need to check more if your character moves more pixels in a frame than the size of your tile. For instance, he's capable of moving 9 pixels in a frame, but your collision tiles are only 8 wide.
Obviously, you first gotta find out whether or not the tile you're working with is collision enabled. I can't especially help with that unless you make a post about your current tile format. Post about that, and I can write a book for you about that.
I personally keep 4 screens of 32x32 metatiles in a page of RAM, which are updated when the game scrolls to a new screen. This means all my screen decompression happens once and only on a frame when the character passes a screen boundary.
The collision detection and parts of my code that tell the PPU what to draw always pull from this RAM, where it is much easier to figure out the math for me than pulling directly from the data in ROM.
I load the 32x32 tile from RAM, I find out the 16x16 tile I want in it. Then I know if I should eject or not.
It works exactly like this:
Code:
;Ignore the <'s, it's a nesasm thing.
lda <xhigh;Zero Page RAM I put any high X position into
ror a;puts the lowest bit of the high x pos into the carry
lda <yhigh;Zero Page RAM I put any high y position into
ror a;puts the lowest bit of the high y pos into the carry
;and puts the x bit into the high bit of the accumulator
ror a;Now the low x and y bits are in the high bits accumulator
and #%11000000;Removes the other junk in the accumulator
sta <reservedE;Temp RAM. The highest bits of it now contain
;which screen I'll read from RAM. Each screen is 64 bytes long, because it contains 8x8 32x32 metatiles.
lda <xlow
lsr a
lsr a
lsr a
lsr a
lsr a;divide by 32, since I'm trying to get a 32x32 metatile.
;The other bits of precision don't matter.
ora <reservedE;This combines my "which screen" bits with the X offset in RAM.
sta <reservedE
lda <ylow
lsr a
lsr a
and #%00111000;This takes away all unnecessary bits from
;my y position
ora <reservedE;I know have the offset in RAM that I need to load my 32x32 tile from.
tay;Y contains the address of the 32 x 32 metatile to look up
lda $0500,y;loads 32 x 32
tay;Y contains the 32 x 32 tile number
The tricky part is actually getting the 32x32 tiles into RAM in the first place, but you may not even need to do that depending on how your data is stored.
I then use a series small series of branches to determine which part of the 32x32 tile I'm in. Topleft, Topright, bottomleft, or bottomright. Once I know that, I load the tile and use its format to determine whether it's a collision or not.
Here's how I eject when I know the current tile is collision enabled
Code:
;Ejecting up/left
lda lowbyte
and #%00001111
clc
adc #$01
sta offset
rts
You want to eject left while traveling right. So imagine the right point of your character has just entered the first pixel of a tile. It's anded position would be 0. In this case, you obviously want to eject one. If you're going super fast, you'll end up further in the tile, so the and will be higher and you'll always eject one more than that. Easy. jsr to the routine, subtract offset from your character's position. (It should be set to 0 if the collision routine detects the current tile is not a collision tile.) You can even use the offset variable the routine returns to find out whether or not there WAS a collision if you want to do that to stop a walking animation or something.
Code:
;Ejecting down/right
lda lowbyte
and #%00001111
eor #%00001111
clc
adc #$01
sta offset
rts
This one is mostly the same, except it has an eor. Say you're traveling left and hit the first pixel of a tile. That's actually pixel $0F, but you only want to eject one! The eor makes $0F equal to one, then it works the same as before! As before, just write a 0 to offset if you've discovered the current tile is not solid. Jsr to routine, then add offset to player's position.
This works for any size tile that's a power of two. Just change the ands and eor to the number of bits your tile takes up.
If you're traveling left, move the player using the current speed value. You may be in a wall. You check the top and bottom left points of your character, and eject right. (add offset) If you're traveling right, check the top and bottom right points of your character and eject left. (subtract offset) you'll never be in the wall for a frame like Mario, and this will certainly be fast enough for how few tiles you'll need to check.
For ejecting up while falling you check the bottom left and right points and subtract offset from the player's position.
Pardon me Kasumi, but this is just crazy... doesn't make any sense.
0 to 31 ok, this is 32 values
32 to 63 now this is 31 values
64 to 95 ... 31 values
96 to 127 ... 31 values
How am I susposed to skip 32 values when every row beyond the first has only 31 values?
It doesn't work yet.
What am I missing?
32 to 63 is 32 values, like 4 to 7 is 4 values: 4,5,6,7.
ooooooh! Yes, thanks Shiru!!
Kasumi wrote:
I personally keep 4 screens of 32x32 metatiles in a page of RAM, which are updated when the game scrolls to a new screen. This means all my screen decompression happens once and only on a frame when the character passes a screen boundary.
Sounds like a great plan!
How many screens of 16x16 metatiles could fit in a page of RAM? We're ending 2 hours early today... and that's now... so I'll think more about this on Monday.
unregistered wrote:
How many screens of 16x16 metatiles could fit in a page of RAM?
Just 1 whole screen uncompressed.
(256/16)*(240/16)
16*15 = 240.
Or (256/16)*(256/16) = 256
if your "screens" are defined as 256x256 like mine are.
I use 32x32 metatiles because I only have 1 page of RAM allocated to this, and I need 4 screens stored. I store my screens in RAM because otherwise I would have to battle through a few levels of compression to check every point. This method lets me do the time consuming stuff (working through the RLE compression of the screens) only on the frame a new screen is scrolled to, while the fast decompression (which 16x16 tile of the 32x32 tile are we in?) is done every time a point needs to be checked.
If it is not time consuming to decompress your data, you could certainly just read from ROM rather than doing what I do.
If it is time consuming, you can get away with using only two pages of RAM to store separate screens of 16x16 tiles for this method since your game only scrolls horizontally.
SMB1 uses two 16x13 pages to cache the decompressed map.
Kasumi wrote:
unregistered wrote:
How many screens of 16x16 metatiles could fit in a page of RAM?
Just 1 whole screen uncompressed. ...
Kasumi, thank you for your explanation. That's what I just calculated too... can fit just one whole screen in a page of ram. I see what you are saying about we might only need 2 pages of ram... since we're only scrolling horizontally.
I don't know if we will use compression for our maps... Thank you for your recomendations!
tepples wrote:
SMB1 uses two 16x13 pages to cache the decompressed map.
I guess this works because of the blockage of 24 scanlines on the top and bottom of the NTSC SDTV screen... ? That's incredible to know! Thank you tepples!
Do you know if they use the same two 16x13 pages in the PAL version? (PAL TVs block the same number of scanlines?)
16x13 is used by Nintendo on SMB1... They know awesome things! That's a savings of 96 bytes right? That's quite a lot!
The 16x13 is because the top 32 scanlines are a status bar.
tepples wrote:
The 16x13 is because the top 32 scanlines are a status bar.
Oh... ok
thanks for replying tepples. : )
Hold on
...16 - 13 is 3.
3 * 16 is 48.
48 - 32 = 16.
16 / 2 = 8. So, are 8 scanlines hidden on the top and bottom edges of the screen?
16-13 is 3. But the screen is not 256 pixels high.
(240-32)/16 = 13.
Kasumi wrote:
16-13 is 3. But the screen is not 256 pixels high.
Ah... yes, you were able to figure out my math.
I didn't need to color the numbers; thank you Kasumi!
Kasumi wrote:
(240 - 32)/16 = 13.
(correct screen height - status bar height)/16 = 13.
Why divide by 16? How does 13 relate to the number of hidden scanlines at the top and bottom edges of the screen?
unregistered wrote:
(correct screen height - status bar height)/16 = 13.
Why divide by 16? How does 13 relate to the number of hidden scanlines at the top and bottom edges of the screen?
16 is the height of a metatile. When you divide by 16 you get the number of metatiles in each column, and with that you can calculate how many bytes a screen would take if stored uncompressed in RAM.
The fact that there are hidden scanlines doesn't mean every game has to do anything special about them. Most games show parts of the map in those areas normally, they just don't put important objects or structures there, but there's usually
something there.
ninja'd by Tokumaru, but as usual: Posting anyway.
You divide by 16 because that's the metatile size. You subtract the height of the status bar (32) from the screen height (240) and divide by the metatile height (16). You get 13.
You divide the screen width (256) by the metatile width (16). You get 16.
So, 16x13 is how much many bytes of RAM a screen takes up in Super Mario Bros. (According to Tepples, I haven't checked myself).
It's only related to the the possibilty of scanlines being hidden as a cause/effect thing. Super Mario Bros. probably couldn't store its "MARIO WORLD TIME" at the VERY top of the screen, because some TVs might cut it off. If there was just map up there, it wouldn't matter if it got cut off. But it wasn't, so they moved the bar down which happened to have the bonus of saving some extra RAM/ROM. Since the top of the screen is scroll locked to keep the status bar from moving, nothing solid could be put there anyway since it would look strange not scrolling.
I've said how I do it and why. We've said how Super Mario Bros. works. Decide if you like either, or come up with something that suits your own game's needs better. You should be making design decisions based on what YOU need. If Super Mario Bros. didn't need to display so much text, it could have used sprites to display its information. This would mean the top of the screen wouldn't need to be scroll locked, and it could use 16x15 screens. They made their decisions based on exactly what they wanted and needed, and you must do the same.
When I opened the debug menu in FCEUX and pressed
Step Into the PC changed from $8007 to $800A
Why is the PC inside $8000? My code starts at $C000...
btw, thank you tokumaru and Kasumi! I understand I can and must do this.
If you don't have any subroutines (or any kind of executable code) in $8000-$BFFF there certainly is a bug somewhere. Are all your vectors properly defined at $FFFA-$FFFF? Maybe an invalid IRQ or NMI address is throwing the PC off? Can't really say without seeing more.
tokumaru, thank you for your quick reply!
My vectors ($FFFA-$FFFF) seem to be fine... in the .lst file they are listed as 9E
C2 00
C0 37
C3 so they are all after $C000 i think...
The PC, within the $8000 - $BFFF range appears to be broken... it just switches between two values over and over and over again.
Try opening the ROM in FCEUX for Windows (or another similarly capable debugging emulator) and putting an execute breakpoint on $8000-$BFFF, and you may have a clue as to how you ended up there.
I havent used FCEUX for Windows very much... but I tried to set an execute break point on $8000-$BFFF. The game continues to run these two lines over and over:
Code:
00:8007:AD 02 20 LDA $2002 = #$00
00:800A:10 FB BPL $8007
unregistered wrote:
I havent used FCEUX for Windows very much... but I tried to set an execute break point on $8000-$BFFF. The game continues to run these two lines over and over:
Code:
00:8007:AD 02 20 LDA $2002 = #$00
00:800A:10 FB BPL $8007
Maybe a bug in FCEU debugger if your ROM is 16KB? Try it in some other debugging-capable tool.
EDIT: Many apologies if my response is way off the mark. I just realized I stepped into this thread on page 33!
cpow wrote:
unregistered wrote:
I havent used FCEUX for Windows very much... but I tried to set an execute break point on $8000-$BFFF. The game continues to run these two lines over and over:
Code:
00:8007:AD 02 20 LDA $2002 = #$00
00:800A:10 FB BPL $8007
Maybe a bug in FCEU debugger if your ROM is 16KB? Try it in some other debugging-capable tool.
Will try it in Nintendulator. ...At first after loading and running in nintendulator it stepped on about $C339 and I was thankful. And then after clicking on
run and then on
step again it was back to the line $8007 and then to $800A and back to $8007.
btw, thank you tepples for your help too.
An execute breakpoint; that breaks when something executes... like a subroutine from $800A? Or is it during every branch inside that range $8000-$BFFF?
cpow wrote:
EDIT: Many apologies if my response is way off the mark. I just realized I stepped into this thread on page 33!
it's ok, thank you cpow for helping me.
This conversation started about 5 posts ago...
If I were investigating it, I'd first look for how many frames it takes to get to $8007, then how many scanlines in that frame. At some point, you'll figure out what's happening just before it gets to $8007.
tepples wrote:
If I were investigating it, I'd first look for how many frames it takes to get to $8007, then how many scanlines in that frame. At some point, you'll figure out what's happening just before it gets to $8007.
I'd set an execute breakpoint on $8007, reset, and run. Look at the execution backtrace when the breakpoint hits...
It's not a bug in FCEUX, at least not one in version 2.1.5. I just ran Galaxy NES which is also has only 16 KB prg, and it never executes anything from 8000-BFFF.
BUT... nothing bad also happens if it does. Since the banks are just mirrored (AFAIK) when you have only 16K, it should actually still work fine. I changed Galaxy NES' reset vector to point to reset-$4000 instead of just reset, and it ran in the other bank fine until the first jmp sent it to a $C000-$FFFF where it still continued as normal.
unregistered wrote:
The game continues to run these two lines over and over:
Code:
00:8007:AD 02 20 LDA $2002 = #$00
00:800A:10 FB BPL $8007
The reason you'd see these over and over again is because you wait for vblank that way. Until $2002 returns a set negative bit, the loop will keep going thousands of times at maximum. That's not a bug, that's how your program is detecting when your frame starts.
What I'd do to avoid these affecting your breakpoint is look for the address of the instruction immediately after that loop, and start stepping from there.
In any case, I haven't read yet if there is an actual problem. Does your program run fine despite the fact that it is in the wrong place? Does your code ever actually run from $C000-$FFFF at all? (Set a breakpoint for that).
If it is running instructions from both $C000-$FFFF AND $8000-$BFFF, that is mighty strange. ctrl+f all the .org statements in your code and see what's up.
But if it doesn't run from both, it might just be something trying to be clever because the banks end up mirrored anyway.
tepples wrote:
If I were investigating it, I'd first look for how many frames it takes to get to $8007
When using WINDOWS FCEUX 2.0.3 how could I tell how many frames have passed?
tepples wrote:
, then how many scanlines in that frame. At some point, you'll figure out what's happening just before it gets to $8007.
Ok thanks! The code around $8007 looks exactly like my code at $C007. It's the same reset code... first it waits through two vblanks. That's why all the back and forth happens between those two lines of code I posted.
edit:Kasumi agrees with me!
If you try to step through the code during a wait for Vblank you'll go crazy... The code will loop several thousands of times, it would be insane to do that. Do like Kasumi said: If you hit a loop like this when debugging code, set up a breakpoint for the address that immediately follows the loop and click on "run", then you can continue to step through the code after the loop.
Why the CPU is running code at $8000-$BFFF is still a mystery though... I would probably set up a breakpoint for that range and reset, so that as soon as the first byte in $8000-$BFFF is read I could inspect everything.
What is the value in your IRQ vector? You know, even if you don't intentionally use IRQs, the APU might fire them if it's not properly initialized (and you don't have SEI in your startup code). The BRK instruction also causes an IRQ to happen, and since the opcode for that instruction is $00, it's fairly common for the CPU to interpret some $00 in your ROM as a BRK instruction if some bug cause the PC to run wild.
tokumaru, what do you mean by "you dont have SEI in your startup code"? That's the very first thing.
Code:
.org $C000
reset: sei
cld
; Wait two VBLANKs.
- lda $2002
bpl -
- lda $2002
bpl -
; Clear out RAM.
lda #$00
ldx #$00
- sta $000,x
sta $100,x
sta $200,x
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x
inx
bne -
; Reset the stack pointer.
edit: think I understand now... I have sei in my startup code and so that means the APU wont fire interrupts at me?
Kasumi wrote:
Does your program run fine despite the fact that it is in the wrong place?
No, it doesn't run fine despite the fact that it is in the wrong place.
Kasumi wrote:
Does your code ever actually run from $C000-$FFFF at all? (Set a breakpoint for that).
Thank you, yes it has started running
Code:
00:C096:9D 00 02 STA $0200,X @ $024C = #$00
00:C099:E8 INX
00:C09A:D0 FA BNE $C096
that code for a long while.
Kasumi wrote:
If it is running instructions from both $C000-$FFFF AND $8000-$BFFF, that is mighty strange. ctrl+f all the .org statements in your code and see what's up.
Thanks again
, there is only one .org statement in my code.
tokumaru wrote:
Why the CPU is running code at $8000-$BFFF is still a mystery though... I would probably set up a breakpoint for that range and reset, so that as soon as the first byte in $8000-$BFFF is read I could inspect everything.
That happens as soon as the cpu is reset... it goes into $8000 and runs its SEI.
tokumaru wrote:
What is the value in your IRQ vector?
Code:
vblank: inc FRAME_CNT
.incsrc "daprg-vblank.asm"
irq: rti
tokumaru wrote:
The BRK instruction also causes an IRQ to happen, and since the opcode for that instruction is $00, it's fairly common for the CPU to interpret some $00 in your ROM as a BRK instruction if some bug cause the PC to run wild.
Thanks for this info too!
unregistered wrote:
tepples wrote:
If I were investigating it, I'd first look for how many frames it takes to get to $8007
When using WINDOWS FCEUX 2.0.3 how could I tell how many frames have passed?
My bad; I remembered wrong. I thought there was a run 262 lines button, but there isn't; just a 128 lines button. In the debugger, at the lower right, is a display of how far the emulated PPU is down the screen. Three dots make one PPU cycle, and one line is 341 dots.
If your NMI handler is defined in the file "daprg-vblank.asm", are you sure it saves and restores all registers that it uses? Try putting a breakpoint at vblank: and single stepping through that; a stack manipulation problem might cause the program counter to go to hell.
unregistered wrote:
No, it doesn't run fine despite the fact that it is in the wrong place.
Okay. What goes wrong?
Also, do the following:
Open FCEUX's debugger, Add an execute breakpoint for $8000-$BFFF.
Go to Debug, Trace Logger. Log last 10,000 instructions. Check "Automatically update Window While Logging".
Press "step into" a single time on the debugger.
Go to NES, power.
Press "start logging" on the Trace Logger.
Press "run" on the debugger.
Press "step into" once after the break.
This will log the last 10,000 instructions before your code mysteriously ends up in $8000-$BFFF. Check the very last few instructions in the Trace logger to see the address where the code that's causing you to end up in $8000-$BFFF is. Find out what's going wrong using that.
You have a listing file. So when I end up with a bug like this, I look at the trace logger to find jmp/bpl nearest to the problem. Then I look the address that was bpl/jmp'd to to find where in the code the problem is occurring.
Edit: Wait... this happens immediately on reset? And you have one .org statement? How does your assembler set the vectors? Have you checked the actual rom's vectors and not just the listing file? I couldn't tell from your last post on the subject.
Debug, Hex Editor, view, rom file. ctrl+g 400A. What are the six bytes starting there? If it's something like: 9E 82 00 80 37 83, then somehow your listing file doesn't represent what's actually in your rom. If it's like: 9E C2 00 C0 37 C3, and you're still ending up at $8000 on reset I would just be really confused. But with my confusion, I would follow the above instructions and see how it ends up at $C000-$FFFF if it starts on $8000. Find out when it changes, either from $8000 to $C000 or $C000 to $8000.
Please post the contents of "daprg-vblank.asm".
tepples wrote:
unregistered wrote:
tepples wrote:
If I were investigating it, I'd first look for how many frames it takes to get to $8007
When using WINDOWS FCEUX 2.0.3 how could I tell how many frames have passed?
My bad; I remembered wrong. I thought there was a run 262 lines button, but there isn't; just a 128 lines button. In the debugger, at the lower right, is a display of how far the emulated PPU is down the screen. Three dots make one PPU cycle, and one line is 341 dots.
On my debugger screen the lower right is blank?
Kasumi wrote:
unregistered wrote:
No, it doesn't run fine despite the fact that it is in the wrong place.
Okay. What goes wrong?
Also, do the following:
Open FCEUX's debugger, Add an execute breakpoint for $8000-$BFFF.
Go to Debug, Trace Logger. Log last 10,000 instructions. Check "Automatically update Window While Logging".
Press "step into" a single time on the debugger.
Go to NES, power.
Press "start logging" on the Trace Logger.
Press "run" on the debugger.
Press "step into" once after the break.
This will log the last 10,000 instructions before your code mysteriously ends up in $8000-$BFFF. Check the very last few instructions in the Trace logger to see the address where the code that's causing you to end up in $8000-$BFFF is. Find out what's going wrong using that.
You have a listing file. So when I end up with a bug like this, I look at the trace logger to find jmp/bpl nearest to the problem. Then I look the address that was bpl/jmp'd to to find where in the code the problem is occurring.
Excellent!!! Thank you so much Kasumi! I really enjoyed your step by step tutorial!
My problem was that it stayed on $C000 until reaching a
rts and then it switched over to $7xxx. So I opened the file and found the
rts and finally noticed that the function was called four times with
jmp... not
jsr...
Now it stays on $C000-$FFFF. In the debugger it always stops at $C336... that has an
rti. Need to think a bit about what to do next... though there's no breakpoint. So why does it pause at $C336? Maybe because that's the last line of code... the
rti is after the label
irq:...
tokumaru wrote:
Please post the contents of "daprg-vblank.asm".
tokumaru, I don't believe I can do that.
I'm not 100% clear. You're saying fceux's debugger is stopping your code at $C336 despite you not having any active breakpoints? That's pretty strange. Is break on bad opcode checked? That's about my only guess. Are there any problems on a different emulator? Does anything else "run into" that RTI?
Something like:
Code:
NMI:
;NMI code here, without an rti
IRQ
RTI
?
Also: It is much easier to help you when you can post code. I understand the arrangement you've got with your sister, but there's really no way and no reason for us to steal your content by you posting small snippets.
I'd understand protecting your assets like graphics and music, and I understand maybe something totally proprietary like a unique compression scheme. But for what you're doing now, it's all problems a lot of us have faced and solved. So there's no reason for us to steal, and we can probably provide a great deal more help when we're not guessing.
unregistered wrote:
tokumaru wrote:
Please post the contents of "daprg-vblank.asm".
tokumaru, I don't believe I can do that.
I was only trying to help, I had no intentions of "stealing" anything. You do know that once a ROM is available everyone can see what the program is doing, right? Hiding assembly source code doesn't really protect you of anything.
Anyway, the problem has been found, so there's no need to post anything. RTS without JSR is indeed a nasty bug, you have to be careful about that. If a return address wasn't put on the stack, the RTS will get anything that is at the top of the stack and jump to a bad location.
tokumaru wrote:
RTS without JSR is indeed a nasty bug, you have to be careful about that. If a return address wasn't put on the stack, the RTS will get anything that is at the top of the stack and jump to a bad location.
Yes, thank you
, I agree... maybe the same thing happens here... it reaches the rts at the bottom and then pulls $37 and $C0 off the stack and goes to $C201 ???? Why?????????????????????????
Then $C201 is listed as "
UNDEFINED" (because that instruction starts at $C200) and so then it runs an incorrect byte again ($C203, shows "
PLP"). Then it runs the code at $C204
sta $0029. Something happens next I don't understand why!
It ends up at $C336
RTI and never does anything else...
Code:
0C15E see:
0C15E
0C15E A0 00 ldy #$00
0C160 A2 00 ldx #$00
0C162 A9 91 lda #<lvl1Screen1start
0C164 85 10 sta $10
0C166 A9 C8 lda #>lvl1Screen1start
0C168 85 11 sta $11
0C16A
0C16A
0C16A A9 20 lda #$20
0C16C 8D 06 20 sta $2006;Sets the high byte of the address $2007 will write to.
0C16F A9 00 lda #$00
0C171 8D 06 20 sta $2006;Sets the low bylte of the address $2007 will write to.
0C174
0C174 ;a is still 0
0C174 85 20 sta r
0C176 48 pha ;--------------------------->
0C177
0C177 B6 10 -- ldx ($10), y
0C179 BD 57 C3 lda MetatileTile0, x
0C17C 8D 07 20 sta $2007;Writes to PPU address $2000. (which is the first tile of the first name table)
0C17F BD 36 C4 lda MetatileTile1, x
0C182 8D 07 20 sta $2007;Writes to PPU address $2001. (which is the tile to the right of the first tile)
0C185 C8 iny
0C186 E6 20 inc r
0C188 ; pla ;<--------------------------
0C188 ; tax
0C188 ; inx
0C188 ; txa
0C188 ; pha ;-------------------------->
0C188 A5 20 lda r
0C18A 29 0F and #00001111b
0C18C F0 03 beq + ;branch if it a multiple of 16
0C18E 4C 77 C1 jmp --
0C191
0C191
0C191 C8 + iny
0C192
0C192 B6 10 - ldx ($10), y
0C194 BD 15 C5 lda MetatileTile2, x
0C197 8D 07 20 sta $2007;Writes to PPU address $2010. (which is the third tile, first tile on second row, of the first name table)
0C19A BD F4 C5 lda MetatileTile3, x
0C19D 8D 07 20 sta $2007;Writes to PPU address $2011. (which is the tile to the right of the third tile)
0C1A0 C8 iny
0C1A1 E6 20 inc r
0C1A3 ; pla ;<--------------------------
0C1A3 ; tax
0C1A3 ; inx
0C1A3 ; txa
0C1A3 ; pha ;--------------------------->
0C1A3 A5 20 lda r
0C1A5 C9 F0 cmp #11110000b ;if (r >= 240)
0C1A7 90 07 bcc +done
0C1A9 29 0F and #00001111b
0C1AB F0 CA beq --
0C1AD 4C 92 C1 jmp -
0C1B0
0C1B0 +done:
0C1B0 60 rts 0C1B1
Kasumi wrote:
I'm not 100% clear. You're saying fceux's debugger is stopping your code at $C336 despite you not having any active breakpoints? That's pretty strange.
Is break on bad opcode checked? That's about my only guess. Are there any problems on a different emulator?
Does anything else "run into" that RTI?Something like:
Code:
NMI:
;NMI code here, without an rti
IRQ
RTI
?
No, break on bad opcode isn't checked. No, right before the rti is an rts.
Code:
0C1A3 ; pha ;--------------------------->
0C1A3 A5 20 lda r
0C1A5 C9 F0 cmp #11110000b ;if (r >= 240)
0C1A7 90 07 bcc +done
0C1A9 29 0F and #00001111b
0C1AB F0 CA beq --
0C1AD 4C 92 C1 jmp -
0C1B0
0C1B0 +done:
0C1B0 60 rts 0C1B1
I could be wrong about this (I sorta have trouble following + and - branches):
Edit: CPOW points out the PHA here is commented out. I don't believe in mangling my posts even when I'm wrong, so see my next post for another guess.
Let's pretend we're at this point in the code. We push a value to the stack. We load 'r'. 'r' is less than #%11110000. We brach to done because the carry is clear. We RTS.
But the value we pushed to the stack is never pulled off. This is super bad, because then we return to the wrong place.
Example:
We jsr to the routine. On the top of the stack is: $00 $80. If we RTS'd right away, we'd return there.
But if you push something else to the stack (like $56 or whatever else) without pulling it off, your stack looks like this. $56 $00 $80. And you'll RTS to $0056(-1?) instead of where you jsr'd from.
Edit: I don't see much need to use pha and pla at all, but I may be missing something in the logic. It seems that a zero page variable would work just as well for this code, and would actually end up faster (but larger) and you wouldn't need to worry about stack corruption. PHA would become sta sometempvariable, and PLA would become lda sometempvariable. And I
think it would work exactly the same.
Kasumi wrote:
[code]
0C1A3 ; pha ;--------------------------->
0C1A3 A5 20 lda r
The [code]pha[/code] is commented out...
Oh, weird. Sorry, then. I assumed it was a disassembly, so I wasn't looking for comments. The addresses should have still been a clue, though. I'll keep looking then.
Edit: Maybe the issue is that he forgot to comment out one pha, then? There's one pha still uncommented, and I don't see a corresponding uncommented pla.
Kasumi wrote:
Edit: Maybe the issue is that he forgot to comment out one pha, then? There's one pha still uncommented, and I don't see a corresponding uncommented pla.
Certainly looks that way.
unregistered, it seems to me that all your problems are stack-related. Double check everything you do with it (pay attention to all PHA, PLA, JSR, RTS and RTI instructions) and make sure that everything that's put on the stack is pulled back in the proper order.
Avoid using PHA/PLA in places with lots of decisions (conditional branches), because depending on the combination of taken branches it's easy to forget data on the stack or pull more than was put there in the first place.
cpow wrote:
Kasumi wrote:
Edit: Maybe the issue is that he forgot to comment out one pha, then? There's one pha still uncommented, and I don't see a corresponding uncommented pla.
Certainly looks that way.
Thank you so much Kasumi!
All your work is really helpful to me. And thank you cpow for helping us again too; you're quite kind.
It was that way... now after commenting out that pha it works kind of well! There's a screen now... it's not displaying correctly but it shows up and you can see/move the character again.
tokumaru wrote:
unregistered, it seems to me that all your problems are stack-related. Double check everything you do with it (pay attention to all PHA, PLA, JSR, RTS and RTI instructions) and make sure that everything that's put on the stack is pulled back in the proper order.
Avoid using PHA/PLA in places with lots of decisions (conditional branches), because depending on the combination of taken branches it's easy to forget data on the stack or pull more than was put there in the first place.
Thank you so much tokumaru!
I will try tomorrow, what you've told me here, to make the screen appear correctly, i hope.
Yall are awesome!
About Attribute Tables
Kasumi, on page 29, wrote:
For attributes, you do kind of the same thing. Write the address of the attribute tile you want to update to $2006, then write the actual attribute to $2007. Because of your metatile format, this might actually be really easy for you to figure out. (It's hard as hell to do in my format
... I digress)
That's... about all I will say. Good luck! Try it, and post some code if you get stuck!
Code:
value = (topleft << 0) | (topright << 2) | (bottomleft << 4) | (bottomright << 6)
Code:
,---+---+---+---.
| | | | |
+ D1-D0 + D3-D2 +
| | | | |
+---+---+---+---+
| | | | |
+ D5-D4 + D7-D6 +
| | | | |
`---+---+---+---'
Ok this is how I understand this now...
1.) Each byte has bits in this order:
76543210
2.) So starting with each corner palette being a binary
2bit value (0,1,2,or 3) at the bottom of a byte (i.e.
#000000XXb)
3.) and from the first line of code in the
...from box, each
<< X means
shift left X spaces.
And each
| means
or.
TRUE or TRUE =
TRUE
TRUE or FALSE =
TRUE
FALSE or TRUE =
TRUE
FALSE or FALSE =
FALSE
4.) Starting out
top left =
#000000XXb
top right =
#000000XXb
bottom left =
#000000XXb
bottom right =
#000000XXb
5.) After
shifting left
top left =
#000000XXb
top right =
#0000XX00b
bottom left =
#00XX0000b
bottom right =
#XX000000b
6.) After
oring
value =
#XXXXXXXXb
...this is here to remind me of this cause I always get confused over this. Hope it helps a few of yall too.
Is there an assembly case statement? How would you code this:
Code:
case bla
0: (run code for zero)
1: (run code for one)
2: (run code for two)
3: (run code for three)
Yes. It's called a
jump table.
unregistered wrote:
Is there an assembly case statement? How would you code this:
Code:
case bla
0: (run code for zero)
1: (run code for one)
2: (run code for two)
3: (run code for three)
I decided to find out what CC65 does. I borrowed AlterEgo code and added a simple switch statement to see. Note, the assembly displayed is not contiguous...almost to the point of being utterly confusing. But that's what you get with mixed-mode disassembly/source views.
$9ABF: load switch comparison value into A.
$9AC2-$9ACA: case comparisons with compare and branch-if-equal to case handling code.
$9ACD-$9AD4: case 1 handling code.
$9AD7-$9AE1: case 2 handling code.
$9AE2: default fall-through.
There certainly are many other ways to skin this cat. EDIT: The previous statement is meant as a deterrent to those quick to reply that the solution I posted is suboptimal.
unregistered wrote:
Is there an assembly case statement?
Assembly doesn't have this kind of complex structures. In assembly, everything is branches and jumps based on the results of calculations/comparisons.
If you have just a few cases you can just use a few CMPs and branches, but with lots of possible cases it's better to use a jump table, or else it will be too slow.
Code:
;y contains the case number
lda jumptablelow,y
sta tempaddress
lda jumptablehigh,y
sta tempaddress+1
jmp [tempaddress]
jumptablehigh:
.db high(case0)
.db high(case1)
.db high(case2)
jumptablelow:
.db low(case0)
.db low(case1)
.db low(case2)
case0:
case1:
case2:
Underneath each case label would be the code corresponding to the case. Then, we store the addresses of all the cases in two tables. We load the low and high byte of the address we want, and store them in two contiguous bytes in low/high order. Then we jmp indirect to byte where the low part of the address was stored which will take us to the case label.
There's actually a small bug with the 6502 jmp indirection instruction. From
here:
An original 6502 has does not correctly fetch the target address if the indirect vector falls on a page boundary (e.g. $xxFF where xx is and value from $00 to $FF). In this case fetches the LSB from $xxFF as expected but takes the MSB from $xx00. This is fixed in some later chips like the 65SC02 so for compatibility always ensure the indirect vector is not at the end of the page.
When using jump tables, be very careful about safety. Don't allow out-of-range values to be used. Use bounds checking, or bit masking and dummy entries.
Dwedit wrote:
When using jump tables, be very careful about safety. Don't allow out-of-range values to be used. Use bounds checking, or bit masking and dummy entries.
I was gonna say something like that... Jump tables are great when your cases are all consecutive numbers (and you should always try to make it so, since it's easier that way), but when the cases are scattered values or ranges of different sizes, jump tables are not so good.
tokumaru wrote:
Dwedit wrote:
When using jump tables, be very careful about safety. Don't allow out-of-range values to be used. Use bounds checking, or bit masking and dummy entries.
I was gonna say something like that... Jump tables are great when your cases are all consecutive numbers (and you should always try to make it so, since it's easier that way)
That or when your cases become consecutive numbers when shifted to the right. For example, you might have 32 different commands that have up to 8 different subtypes. The commands might be numbered 0, 1, 2, 3; 8 and 9; 16 through 23; 24; etc. In this case, you can still use a jump table as long as you shift out the subtype first.
Code:
do_command:
and #$F8 ; clear the subtype
lsr a
lsr a ; divide by 4
tax
lda command_handlers+1,x
pha
lda command_handlers,x
pha
rts
command_handlers:
.addr wall_handler-1, powerup_handler-1, ...
Yeah, sure. I was mainly trying to point out that jump tables aren't the exact equivalent of the case statements of high level languages (which might or might not become jump tables when compiled), and if the distribution of your cases is weird you will have to go with the "bunch of IFs" solution.
And in the extreme, you can always use a simple hash function to calculate the index in the jump table (the right shift trick by Tepples is a kind of a simple hash function). Of course all the other entries have to be valid and point to a "default" case.
Thank you tepples, cpow, tokumaru, Kasumi, Dwedit, and ~J-@D!~!
tokumaru, my case statement was going to be used with four cases: 0, 1, 2, and 3. I think I'll go with the comparisons route... it seems possible to me.
If you are using a bunch of IF statements, you can rearrange them better to get a speedup, similar to Binary Search.
Dwedit wrote:
If you are using a bunch of IF statements, you can rearrange them better to get a speedup, similar to Binary Search.
You mean like:
Code:
lda caseExpr
check1:
cmp #1
bpl check2
beq case1
bmi case0
check2:
cmp #2
bpl case3
beq case2
jmp out
case0:
nop
jmp out
case1:
nop
jmp out
case2:
nop
jmp out
case3:
nop
jmp out
out:
nop
I suppose the ordering of the branches could be changed too if you know the relative distribution of expected values.
Now it has gone back to running without a screen; it's always gray. I know it is running because when I press select my song starts playing. I don't believe there are any extra
RTS'
PLAs or
PHAs. But the screen is always gray... was because of having a
JMP ending with
RTS...
It never goes through this code... i dont think. After reset it always starts at around
$C300.
Code:
0C23F attributetable: ;<this section isn't ever JSRed
0C23F ;76543210
0C23F ;saves the palette
0C23F
0C23F 85 2F sta palette ;<#xx000000b
0C241 A5 24 lda anum
0C243
0C243
0C243 C9 00 cmp #0
0C245 D0 03 bne +not0
0C247 4C 5B C2 jmp +action0
0C24A C9 01 +not0: cmp #1
0C24C D0 03 bne +not1
0C24E 4C 67 C2 jmp +action1
0C251 C9 02 +not1: cmp #2
0C253 D0 03 bne +not2
0C255 4C 73 C2 jmp +action2
0C258 4C 7F C2 +not2: jmp +action3
0C25B
0C25B A2 06 +action0: ldx #06
0C25D A5 2F lda palette
0C25F 20 0F C3 jsr shift_right ;takes a and x
0C262 85 20 sta att_upleft
0C264 4C A3 C2 jmp +out
0C267 A2 04 +action1: ldx #04
0C269 A5 2F lda palette
0C26B 20 0F C3 jsr shift_right ;takes a and x
0C26E 85 21 sta att_upright
0C270 4C A3 C2 jmp +out
0C273 A2 02 +action2: ldx #02
0C275 A5 2F lda palette
0C277 20 0F C3 jsr shift_right ;takes a and x
0C27A 85 22 sta att_loleft
0C27C 4C A3 C2 jmp +out
0C27F A2 00 +action3: ldx #00
0C281 86 24 stx anum
0C283 A5 2F lda palette
0C285 ;skip ;jsr shift_right cause x is 00
0C285 85 23 sta att_loright
0C287 ; since this is the last tile of this metatile:
0C287 ; let's put it together...
0C287 A5 20 lda att_upleft
0C289 05 21 ora att_upright
0C28B 05 22 ora att_loleft
0C28D 05 23 ora att_loright
0C28F 48 pha ;------------------------------------->
0C290 48 pha ;----------------------------------->
0C291 98 tya
0C292 4A lsr a ;/2
0C293 4A lsr a ;/2
0C294 A8 tay
0C295 68 pla ;<-----------------------------------
0C296 99 F0 04 sta attributeTable,y ;<8bit palette updated into attributeTable
0C299 98 tya
0C29A 0A asl a ;*2
0C29B 0A asl a ;*2
0C29C A8 tay
0C29D 68 pla ;<-------------------------------------
0C29E EA nop
0C29F EA nop
0C2A0 4C A5 C2 jmp +
0C2A3
0C2A3 +out:
0C2A3 ;ldx #06 ^loook above
0C2A3
0C2A3
0C2A3 E6 24 inc anum
0C2A5 + ;iny (...does it later... line 270)
and here is shift_right
Code:
0C30F ;shifts the accumulator right a certain amount (amount declared in x)
0C30F ;trashes x and a kind of... At the end, a holds the answer and x should be 0
0C30F shift_right:
0C30F 4A - lsr a ;>shifts the pal back right>
0C310 CA dex
0C311 D0 FC bne -
0C313 60 rts
(My general conduct has probably already made it clear, but I've never directly stated it. I truly and honestly have terrible reading comprehension, so that's why I ask so many questions for things that are probably very obviously clear to someone else.)
The screen is gray meaning no tiles are being updated in the nametable memory? Rendering is completely disabled? Perhaps just that the attributes aren't updating and are all gray? I guess the easy way to check for some of these is to check the contents of the nametables using a debug emulator. If what's in there looks right but the actual screen is gray, it might be you're disabling rendering somewhere but forgetting to turn it back on.
Quote:
It never goes through this code... i dont think.
Does this mean that code is/should not run at ALL or something else? If that section of code is run when you don't expect the PC to reach it, the direct problem isn't the section of code, it's something else.
There are some odd things in the logic of "attributetable:", but nothing that I think would cause the problems I think you're describing. If it SHOULD and DOES run, but it's just not jsr'd to, what's above what you've posted? What's below it? Have you changed what's above or below it recently?
Where do you check for select to start up your song? If it's in your NMI, that's no indication that your program isn't stuck in an infinite loop elsewhere. The NMI could interrupt, check for select, start the song, then return to an infinite loop even for something like:
Code:
forever:
jmp forever
Either way, I see nothing posted that I think would cause a gray screen. My best guess with the info I have is disabled rendering without re-enabling it. If you can check for select outside your NMI and it actually works, your program is probably not crashing, and probably looping just fine. I assume it wasn't gray before, so what was are the most recent set of changes you made where before them the screen wasn't gray, and after it was?
Edit: You know what? I have to say this:
Code:
attributetable:
attributeTable,y
is not the best idea. If you accidentally jmp to attributeTable, bad things will probably happen. If you store to attributetable bad things may not happen if you don't use a mapper, but you'll wonder why you don't get a result.
If you have variable/label names that can be typo'd into each other, change them. "Typing error bugs" that the assembler can't catch are the
WORST because you can look over the logic again and again and everything will look fine. Be nice to yourself and come up with some separate names.
Edit 2: And... wait, it kinda just struck me. You can get a song playing? Might it be something in your music engine that's causing the problem? If that's something you have now, but didn't have before it's worth you taking look at.
Kasumi wrote:
(My general conduct has probably already made it clear, but I've never directly stated it. I truly and honestly have terrible reading comprehension, so that's why I ask so many questions for things that are probably very obviously clear to someone else.)
The screen is gray meaning no tiles are being updated in the nametable memory? Rendering is completely disabled? Perhaps just that the attributes aren't updating and are all gray? I guess the easy way to check for some of these is to check the contents of the nametables using a debug emulator. If what's in there looks right but the actual screen is gray, it might be you're disabling rendering somewhere but forgetting to turn it back on.
The screen is gray meaning that both nintendulator 0.970 and FCEUX 2.0.3 do not show anything when viewing a debug visual PPU screen... it's all just gray.
Kasumi wrote:
Quote:
It never goes through this code... i dont think.
Does this mean that code is/should not run at ALL or something else? If that section of code is run when you don't expect the PC to reach it, the direct problem isn't the section of code, it's something else.
This section of code I posted is my new code. I dont think its ever been run... I do expect the pc to reach it... i think Going to look now.
Kasumi wrote:
There are some odd things in the logic of "attributetable:", but nothing that I think would cause the problems I think you're describing. If it SHOULD and DOES run, but it's just not jsr'd to, what's above what you've posted? What's below it? Have you changed what's above or below it recently?
Above what I've posted is the first part of
collisionU: and below it is the third last part of
collisionU:. Even though the palette is not part of collision... it has to be saved within because the code of collisionU explores our place where we store the palette.
Kasumi wrote:
I assume it wasn't gray before, so what was are the most recent set of changes you made where before them the screen wasn't gray, and after it was?
The recent changes have been the new code I've posted. I don't remember what was where when... In my coding process so far I try to write the code first... then I play debug... then I run it when it can be run. That's the best I can do right now... I think.
Kasumi wrote:
Edit: You know what? I have to say this:
Code:
attributetable:
attributeTable,y
is not the best idea. If you accidentally jmp to attributeTable, bad things will probably happen. If you store to attributetable bad things may not happen if you don't use a mapper, but you'll wonder why you don't get a result.
If you have variable/label names that can be typo'd into each other, change them. "Typing error bugs" that the assembler can't catch are the
WORST because you can look over the logic again and again and everything will look fine. Be nice to yourself and come up with some separate names.
Thank you very much!
(no, it's still messed up... but thank you all the same
) Your thoughts here have helped me!
Kasumi wrote:
Edit 2: And... wait, it kinda just struck me. You can get a song playing? Might it be something in your music engine that's causing the problem? If that's something you have now, but didn't have before it's worth you taking look at.
No my music engine was created by Shiru... it's working awesomly now.
unregistered wrote:
This section of code I posted is my new code. I dont think its ever been run... I do expect the pc to reach it... i think Going to look now.
Keep in mind, you never have to guess. You can put a breakpoint for that range.
But as for the problem, and there's not much to go on. I would just go through and put breakpoints for ranges of all the routines.
reset:
Otherstuff1:;Maybe checking the joypad?
Otherstuff2:
Otherstuff3:;Maybe updating tiles?
Otherstuff4:;Maybe updating the sprites?
Otherstuff5:
end;
Start at Otherstuff3. If that breakpoint hits, make one for end. If that doesn't hit, make one back at Otherstuff4. If it hits, try Otherstuff5. If it hits, step through it and find out why end never runs. If all the sections of your code are hit like you expect, you have to do a more indepth check in order. Put a breakpoint directly after all the routines that were recently changed.
For instance, right now, it seems like no tiles are being written to $2007 at all. (Either that, or your palettes are all gray for some reason.) Put a breakpoint directly after your tile update routine, and look in RAM for the proper values. If that works, check why those values are never being written in the NMI.
Quote:
The recent changes have been the new code I've posted. I don't remember what was where when... In my coding process so far I try to write the code first... then I play debug... then I run it when it can be run. That's the best I can do right now... I think.
Do you keep a changelog, or backups? If so, I'd start to. Even something simple like this is good.
Version 1:
Added animations.
Add movement.
Version 2:
Added horizontal scrolling.
Added 16bit movement for sprites.
I backup my entire source folder for every major change, and keep track of changes. This allows you to only check what was changed when you have an issue. "It was working. I added X, and changed Y. Now it's not. The problem is probably in X or Y, so I'll look at their logic." This also means if you REALLY screw up, you can just copy over the last backup.
Also, never write super large portions of code without checking them. For an example of something you've done before (tile updating), you could write something that gets the two right most 8x8 tile numbers of a known 16x16 metatile and writes them to ram. Run it, and check to see the write values are written to RAM. Then you write something that writes a whole known column of the tiles to RAM. If it doesn't work, or breaks your program you know exactly what to check. Then, you make your NMI write the tiles to $2007. Then you make it so can write a column at X position. If you write a single routine that does the equivalent of all that without checking any of it, you're in REALLY deep. Especially if you have to rewrite it ALL instead of just fix it.
And if you have no idea what was changed, you basically have to check the entire program.
So I guess those were debugging tips, because I have no idea about the actual problem with the information given. It's time to get your hands dirty!
Kasumi wrote:
Also, never write super large portions of code without checking them. For an example of something you've done before (tile updating), you could write something that gets the two right most 8x8 tile numbers of a known 16x16 metatile and writes them to ram. Run it, and check to see the write values are written to RAM. Then you write something that writes a whole known column of the tiles to RAM. If it doesn't work, or breaks your program you know exactly what to check.
In other words, a
unit test of each individual part of the subroutine. The problem comes when the existing emulators that run on your computer lack a debugger. It appears most emulators with a debugger are Windows-exclusive and either don't run at all (Nintendulator) or have noticeable practical problems (FCEUX) in Wine on Linux or Mac OS X. That's why I've started to write a 6502 simulator in Python so that I can run unit tests on my Ubuntu laptop. It already runs nestest correctly, matching the golden log from Nintendulator cycle for cycle.
Quote:
Then, you make your NMI write the tiles to $2007. Then you make it so can write a column at X position. If you write a single routine that does the equivalent of all that without checking any of it, you're in REALLY deep. Especially if you have to rewrite it ALL instead of just fix it.
And once you do get it correct, you can make an automated test suite that plugs in a few numbers, calls the subroutine, and compares its output to what was expected.
Maybe you can find the problem while I go eat.| At the bottom right before the +done: label it seems to never make it to the end. Maybe that's the reason for my gray screen. It runs through this code... without ending, I think. I have to reread Kasumi's post again... he listed what to do if it can't reach the end I think. Lunch time!
Code:
see:
ldy #$00
ldx #$00
pha ;------------------>
lda #$90 ;10010000b
sta $2000 ;PPUCTRL
lda #00000000b ; bit 3 and 4 are 0 so rendering will be off
sta $2001 ;PPUMASK
;rendering is off!!!!!!
pla ;<------------------
; lda #$20
sta $2006;Sets the high byte of the address $2007 will write to.
lda #$00
sta $2006;Sets the low bylte of the address $2007 will write to.
;a is still 0
; sta r
-- lda ($10), y
tax
lda MetatileTile0, x
sta $2007;Writes to PPU address $2000. (which is the first tile of the first name table)
lda MetatileTile1, x
sta $2007;Writes to PPU address $2001. (which is the tile to the right of the first tile)
iny
tya
and #00001111b
beq + ;branch if it a multiple of 16
jmp --
+ tya
sec
sbc #16 ;subtract 16 so we can finish the metatile row...
tay
- lda ($10), y
tax
lda MetatileTile2, x
sta $2007;Writes to PPU address $2010. (which is the third tile, first tile on second row, of the first name table)
lda MetatileTile3, x
sta $2007;Writes to PPU address $2011. (which is the tile to the right of the third tile)
iny
tya
cmp #11110000b ;if (y >= 240)
bcs +done;was bcc
and #00001111b
beq --
jmp -
+done:
;//attribute table time, i think...
ldx #63
lda $23
sta $2006 ;Sets the high byte of the address $2007 will write to.
lda $C0
sta $2006 ;Sets the low byte of the address $2007 will write to.
- lda attributes, x
sta $2007
dex
bne -
lda #$1E
sta $2001 ;PPUMASK
;again, rendering is on!
rts ;end of screeeens and see
Just a quick note on the code:
lda $23 and lda $c0 near the end - probably #$23 etc?
beq + jmp -- - why not just bne -- ?
Shiru wrote:
Just a quick note on the code:
lda $23 and lda $c0 near the end - probably #$23 etc?
YES... I can't believe I missed the # again.
Thank you for helping Shiru!
Shiru wrote:
beq + jmp -- - why not just bne -- ?
still thinking about this ....Ahhh!! Yes! Thanks again.
---
Screen is still gray.
Learn to trace in an emulator debugger, it'll help you a lot to figure out problems like this. It is difficult for other people to help you fix large chunks of the code, because they don't have information that you have (what it is, what it should do, how it should work etc).
Shiru wrote:
Learn to trace in an emulator debugger, it'll help you a lot to figure out problems like this. It is difficult for other people to help you fix large chunks of the code, because they don't have information that you have (what it is, what it should do, how it should work etc).
Ok, I understand that now.
Thank you!
When I open our game's nes file with FCEUX I am awarded with
...an 'FCEUX Error' message pops up and wrote:
Error opening Game Genie ROM image!
What does that mean?
Does it say that even if you turn off cheating?
tepples wrote:
Does it say that even if you turn off cheating?
Cheating?!
Um, how can I turn off cheating?
Config, uncheck game genie.
Ah, thank you Kasumi.
tepples wrote:
Does it say that even if you turn off cheating?
No it doesn't say anything now that cheating is off. Thank you for asking that.
It's not a horrible gray screen creater.
Kasumi wrote:
unregistered wrote:
This section of code I posted is my new code. I dont think its ever been run... I do expect the pc to reach it... i think Going to look now.
Keep in mind, you never have to guess. You can put a breakpoint for that range.
But as for the problem, and there's not much to go on. I would just go through and put breakpoints for ranges of all the routines.
reset:
Otherstuff1:;Maybe checking the joypad?
Otherstuff2:
Otherstuff3:;Maybe updating tiles?
Otherstuff4:;Maybe updating the sprites?
Otherstuff5:
end;
Start at Otherstuff3. If that breakpoint hits, make one for end. If that doesn't hit, make one back at Otherstuff4. If it hits, try Otherstuff5. If it hits, step through it and find out why end never runs. If all the sections of your code are hit like you expect, you have to do a more indepth check in order. Put a breakpoint directly after all the routines that were recently changed.
For instance, right now, it seems like no tiles are being written to $2007 at all. (Either that, or your palettes are all gray for some reason.) Put a breakpoint directly after your tile update routine, and look in RAM for the proper values. If that works, check why those values are never being written in the NMI.
Quote:
The recent changes have been the new code I've posted. I don't remember what was where when... In my coding process so far I try to write the code first... then I play debug... then I run it when it can be run. That's the best I can do right now... I think.
Do you keep a changelog, or backups? If so, I'd start to. Even something simple like this is good.
Version 1:
Added animations.
Add movement.
Version 2:
Added horizontal scrolling.
Added 16bit movement for sprites.
I backup my entire source folder for every major change, and keep track of changes. This allows you to only check what was changed when you have an issue. "It was working. I added X, and changed Y. Now it's not. The problem is probably in X or Y, so I'll look at their logic." This also means if you REALLY screw up, you can just copy over the last backup.
Also, never write super large portions of code without checking them. For an example of something you've done before (tile updating), you could write something that gets the two right most 8x8 tile numbers of a known 16x16 metatile and writes them to ram. Run it, and check to see the write values are written to RAM. Then you write something that writes a whole known column of the tiles to RAM. If it doesn't work, or breaks your program you know exactly what to check. Then, you make your NMI write the tiles to $2007. Then you make it so can write a column at X position. If you write a single routine that does the equivalent of all that without checking any of it, you're in REALLY deep. Especially if you have to rewrite it ALL instead of just fix it.
And if you have no idea what was changed, you basically have to check the entire program.
So I guess those were debugging tips, because I have no idea about the actual problem with the information given. It's time to get your hands dirty!
Kasumi,
thank you so much for all these incredible debugging tips!!
It really helps me to learn at debugging! So far I've determined that the function runs fully twice.
I'm going to try keeping the changelog you suggested with version numbers. And I'm going to save a copy of this once the Gray Screen goes away.
tepples wrote:
Quote:
Then, you make your NMI write the tiles to $2007. Then you make it so can write a column at X position. If you write a single routine that does the equivalent of all that without checking any of it, you're in REALLY deep. Especially if you have to rewrite it ALL instead of just fix it.
And once you do get it correct, you can make an automated test suite that plugs in a few numbers, calls the subroutine, and compares its output to what was expected.
Thanks tepples!
Maybe I will try that.
The NTSC video signal is made up of 262 scanlines, and 20 of those are spent in vblank state. After the program has received an NMI, it has about 2270 cycles to update the palette, sprites, and nametables as necessary before rendering begins.
So does that mean I might be messing something up because at the start of
screeeens I turn rendering off and then right before the end I turn rendering on again.?
Could those actions cause the gray screen? Maybe I spend more than 2270 cycles... I don't understand...
I've followed the program... it keeps on running... the only thing in my infinite MainLoop is something that reacts to input...
that's why my song plays when I press select. I just figured this out!
If you don't fit the VBlank time with VRAM access, program won't crash, it will just show jumpy or messy screen.
I've taken a hard look at what you've posted, and I don't see anything I think would cause your problem. In fact, the logic of it shows some really creative thinking. But I'm assuming lots of things based on how I'd organize this.
Can you describe the general structure of your program? Where is the routine you posted? Above your main loop? In your NMI? What does your NMI currently DO? How about your main loop? How about before that? No need to post code, just give a description what they currently do.
Or just log the entire game from the beginning, and compare it with your source code. Compare what's happening with what you expect.
Shiru wrote:
If you don't fit the VBlank time with VRAM access, program won't crash, it will just show jumpy or messy screen.
Ok that's good to know.
My program doesn't crash... I want it to though. Thank you Shiru.
My music keeps on playing!!
Kasumi wrote:
I've taken a hard look at what you've posted, and I don't see anything I think would cause your problem. In fact, the logic of it shows some really creative thinking.
Wow, thank you... gosh... really creative thinking... you are so kind!
Thank you for all of your help... I'm going to finish this reply soon.
Kasumi wrote:
Can you describe the general structure of your program?
I could try, I think... Um, well so far it's not a very big program... at $C000 it starts with the 'reset' code. That code ends at $C058. After that the MainLoop starts and it runs react_to_input which basicly covers reading the controller... then it
Code:
0C05A 58 cli
0C05B ;----------------------------END OF RESET-----------
-----------------------
0C05B ; Transfer control to the GAME LOGIC routines.
0C05B
0C05B
0C05B ;initialize the flag to "false"
0C05B A9 00 lda #$00
0C05D 85 1F sta FrameReady
0C05F
0C05F MainLoop:
0C05F
0C05F ;DO THE GAME LOGIC HERE (i.e. movement, collisions, etc.)
0C05F 20 DA C0 jsr react_to_input
0C062
0C062 ;indicate that the frame calculations are done
0C062 C6 1F dec FrameReady
0C064
0C064 WaitForUpdates:
0C064
0C064 ;wait for the flag to change
0C064 24 1F bit FrameReady
0C066 30 FC bmi WaitForUpdates
0C068
0C068 4C 5F C0 jmp MainLoop
That's pretty much it.
Kasumi wrote:
Where is the routine you posted? Above your main loop? In your NMI?
The better
see: routine I posted is in a file called
...screeeens.asm. It is called twice toward the end of the reset code inside an
init nametables so yes it runs above my MainLoop. Not in my NMI, I don't think.
Kasumi wrote:
What does your NMI currently DO?
My NMI? Guess you mean my
vblank: routine. Currently it
1.) preforms some video updates and then
2.) it runs FamiToneUpdate (sound) and then
3.) it quits.
Kasumi wrote:
How about your main loop? How about before that?
I posted code for my MainLoop above because I can't describe the end of it
... Before my MainLoop I set a variable to 0... and right before that is my reset code.
Kasumi wrote:
Or just log the entire game from the beginning, and compare it with your source code. Compare what's happening with what you expect.
Could I press the Next button with the keyboard some how? Cause then it would be easier to make it through the long loops. Just hold down the button... then it'd go pretty fast, I think.
Or maybe I could somehow make it not run loops?
unregistered wrote:
Or maybe I could somehow make it not run loops?
Make a breakpoint for the address right after the loop, then press "run".
When it is too difficult to find where to put a breakpoint from a debugger, you can put 'stop' code into the program, i.e. label: jmp label - so the program gets into an infinite loop after reaching that point, allowing you to examine system state. You can then resume execution by setting PC past the loop from the debugger.
That's a good idea Shiru, the only problem I see with it is that interrupts (NMIs and IRQs) might still mess up the system state. Personally, I always use writes to $FF as breakpoint triggers, so all I have to do is "sta $ff" at the point I want to debug.
I mostly use that method on another platform where it looks like di:halt (disable interrupts and stop CPU). On the NES there is a problem with IRQ and NMI, indeed - well, if one wants to prevent them, he can use a macro that will disable it before getting into endless loop. Of course this method in general is inferior to emulator-supported breakpoint triggers, but could help in some situations nevertheless.
Quote:
Could I press the Next button with the keyboard some how? Cause then it would be easier to make it through the long loops. Just hold down the button... then it'd go pretty fast, I think. Smile Or maybe I could somehow make it not run loops? Confused
Log, not step. It's a last resort, though. I would try the breakpoint testing first, though.
But, a giant TXT file is created with all the instructions that were run, and you browse that. You can do it with the Trace logger of FCEUX. Log to file. Get the debugger stopped at your reset address. Click start logging on the trace logger. Click run on the debugger. Let it run a couple of frames. Stop logging.
It'll still be trouble getting to the end of loops, but you can page down or use your text editor's scroll bar.
I would ctrl+f $2007 in the log, and see what's being written (if anything). If you can see this code being run, and it updates, I honestly have no idea what's going on. One thing that may be worth trying is either disabling NMI interrupts before writing to $2006, (remember to re enable them), or bit $2002 before writing to $2006.
One other question, do you see your palettes in FCEUX's PPU viewer?
Kasumi wrote:
One other question, do you see your palettes in FCEUX's PPU viewer?
No... they're just all black... just like the two screens above... just gray.
Then something is going bad very early on. Put a breakpoint right after the basic initialization (the 2 VBlank wait loops, memory clearing, etc.) and step from there.
tokumaru wrote:
unregistered wrote:
Or maybe I could somehow make it not run loops?
Make a breakpoint for the address right after the loop, then press "run".
That's brilliant! Thanks so much tokumaru!
tokumaru wrote:
Then something is going bad very early on. Put a breakpoint right after the basic initialization (the 2 VBlank wait loops, memory clearing, etc.) and step from there.
Well after doing this I have two ideas.
1.) What does
LDA $C3EC,X @ $C3EC = #$00 the = #$00 mean? Does that matter? Sometimes it makes some sense... but lots of times it is just #$00.
2.) ... um well I don't remember this o0ne.
OH NOW I REMEMBER!
In this code
Code:
0C1CE BD F0 04 - lda attributes, x
0C1D1 8D 07 20 sta $2007
0C1D4 CA dex
0C1D5 D0 F7 bne -
I go through attributes backwards... but I don't care what the attribute bytes say, could be anything. Think I could fix this later?
= $00 is the value that is currently stored at the $c3ec+x.
Shiru wrote:
= $00 is the value that is currently stored at the $c3ec+x.
Does is it mean that all the time?
STA $2007 = #$00 Thanks Shiru!
unregistered wrote:
Shiru wrote:
= $00 is the value that is currently stored at the $c3ec+x.
Does is it mean that all the time?
STA $2007 = #$00 Thanks Shiru!
That's true for memory (RAM at $0000-$07FF or $6000-$7FFF, or ROM at $8000-$FFFF). But it's not always true for memory-mapped ports where reading has a side effect or ports that can't be read, such as most PPU and APU ports.
tepples wrote:
unregistered wrote:
Shiru wrote:
= $00 is the value that is currently stored at the $c3ec+x.
Does is it mean that all the time?
STA $2007 = #$00 Thanks Shiru!
That's true for memory (RAM at $0000-$07FF or $6000-$7FFF, or ROM at $8000-$FFFF). But it's not always true for memory-mapped ports where reading has a side effect or ports that can't be read, such as most PPU and APU ports.
But is it true for
STA $2007 = #$00? Oh maybe thats a PPU port that can't be read...
So then what does the = #$00 mean for those types?
It means the designer of the debugger didn't want to make special cases for all those addresses.
It tells you what's in A in the column to the right.
Code:
A:40 X:00 Y:01 S:FC
That's what being written, and what you should pay attention to. I personally ignore the "= whatever" in most cases. It doesn't help when I load. Whatever was loaded is in A in the next row. It also doesn't help when I store. Because I know what I stored, it's in one of the registers, and listed in that column.
Edit: I guess it helps for bit test, and bitwise instructions, but that's pretty rare.
Kasumi wrote:
It means the designer of the debugger didn't want to make special cases for all those addresses.
Oh ok thanks Kasumi!
...and thank you tepples! Kasumi wrote:
It tells you what's in A in the column to the right.
Code:
A:40 X:00 Y:01 S:FC
That's what being written, and what you should pay attention to. I personally ignore the "= whatever" in most cases. It doesn't help when I load. Whatever was loaded is in A in the next row. It also doesn't help when I store. Because I know what I stored, it's in one of the registers, and listed in that column.
Ooooooooh, I thought it might be writing the $00's instead of what the accumulator had. This is good to know!
color of text added in edit
When storing the attribute table does each write to $2007 automaticly increment the $2007 write-to counter?
Autoincrement is for VRAM access in general, it doesn't do distinction between nametable, attributes, palettes or patterns.
Shiru wrote:
Autoincrement is for VRAM access in general, it doesn't do distinction between nametable, attributes, palettes or patterns.
Thank you Shiru!
unregistered wrote:
Kasumi wrote:
One other question, do you see your palettes in FCEUX's PPU viewer?
No... they're just all black... just like the two screens above... just gray.
tokumaru wrote:
Then something is going bad very early on. Put a breakpoint right after the basic initialization (the 2 VBlank wait loops, memory clearing, etc.) and step from there.
What could be going bad? It just ran through the code once and then it started the MainLoop again and is back to the same part of the loop... it'll keep going and going and going and going...
unregistered wrote:
What could be going bad? It just ran through the code once and then it started the MainLoop again and is back to the same part of the loop... it'll keep going and going and going and going... an...ing.
If you don't see smoke coming from your NES then don't panic...you haven't yet executed the self-destruct sequence.
Seriously though...not sure how anyone can help you beyond the 38 pages of help you've already received if you can't set breakpoints and step through your own creation.
EDIT: I crossed to the 39th page with my post but I'll leave my original count at 38 since my reply isn't "helpful".
Normally, if your large-scale code crashes, you need to check its smaller pieces that could be tested independently. Review them in the source code, maybe you made a typo. Trace them, maybe you made a mistake in its logic and it actually works not as you think it will work. That's a general debugging, no one can really help without having full access to code and an explaination what and how it should do.
unregistered wrote:
What could be going bad? It just ran through the code once and then it started the MainLoop again and is back to the same part of the loop... it'll keep going and going and going and going...
To clarify, you logged from the very first instruction (power on), compared the log with your source code and cannot find an error? If you haven't done this, do it, especially if you have no idea what else to do.
Are you waiting a couple of frames for the PPU to warm up before writing to $2006 for the first time? That's about the last thing I can really think of.
The main loop looping just means the main loop is working fine. There could still be something wrong before it. Your NMI could be doing something that affects your tile or palette writing, or who knows what else. As was said by shiru and cpow, there's not much we can say that hasn't already been said with the information we've been given.
If you looked through a log from the beginning and even that didn't help, If I were you, I'd get rid of all the code except the absolute standard stuff (clearing RAM, waiting for vblank twice) before your main loop, which seems to be working fine. Reimplement each thing one by one. (Save the old code in a separate file, that's not assembled so it's not lost)
I'd start with palettes. You said your palettes were all black. After getting rid of this new screen code, just put in what writes the palettes.
If that works, cool. Try to put another small piece back. If not, well, I have no idea. REALLY check your NMI? I would actually start over if that didn't work. You've only got an NMI and a function that reads the joypad from the look of things.
If it's taking you longer to debug than it would to rewrite you're losing time. If you choose to rewrite, keep careful track of changes so you don't run into a similar problem. Make sure each piece works before moving on.
Kasumi wrote:
unregistered wrote:
What could be going bad? It just ran through the code once and then it started the MainLoop again and is back to the same part of the loop... it'll keep going and going and going and going...
To clarify, you logged from the very first instruction (power on), compared the log with your source code and cannot find an error? If you haven't done this, do it, especially if you have no idea what else to do.
Just finished doing this... everything runs correctly... I did not find an error. I also did not spend the time to check FamiTone's code... one of his updates runs each vblank. And there was a vblank... everything looked and seemed correct... then it ended and my MainLoop repeated, so I stopped there.
Going to try working through the rest of your post by maybe tonight or by Monday.
Kasumi wrote:
Are you waiting a couple of frames for the PPU to warm up before writing to $2006 for the first time?
What do you mean by "a couple of frames"?
My code waits 2 vblanks at the start. The first write to $2006 happens shortly after that when it is loading our palette into $3F00.
The vblank signals the start of a frame, so you are waiting a couple of frames by doing that. I'm out of guesses.
If you would post your startup code this could be solved in a couple minutes.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Can see the palette and tiles again! YES! THANK YOU KASUMI!
edit: It was your focus on my palette... I was blessed to learn all of these debugging ideas! I remember now I renamed my "palette" to "Apalette" and it was missing the capital a! Joy!
If you renamed something, assembler would tell you that a label or a file is missing. How could you miss that?
Shiru wrote:
If you renamed something, assembler would tell you that a label or a file is missing. How could you miss that?
Cause I was forced to rename "palette" to something else cause I already had that one. So I put the capital A infront and I guess I don't know what happened... then I was awarded with a grey screen up until a few minutes ago.
unregistered wrote:
Shiru wrote:
If you renamed something, assembler would tell you that a label or a file is missing. How could you miss that?
Cause I was forced to rename "palette" to something else cause I already had that one. So I put the capital A infront and I guess I don't know what happened... then I was awarded with a grey screen up until a few minutes ago.
Um OK but a debugger of any worth will show you what addresses are being loaded from either in real-time [an execution trace] or in design-time [an IDE with a "Go to declaration" option for symbols]. Best of all...for NES they're all *free*!
You should be more careful with labels. Putting random letters to making them unique is a poor way to handle a situation like this. You should use more descriptive names (such as TitleScreenPalette, MenuPalette, and so on), or even look into local/temporary labels, so that you can reuse the names within different scopes.
When coding in assembly you should make thing as clear as possible, other wise it will be hell to maintain. If you pick up the code after a couple of weeks and all you have to tell labels apart is a (meaningless) prefix/suffix character, you'll be in trouble.
cpow wrote:
unregistered wrote:
Shiru wrote:
If you renamed something, assembler would tell you that a label or a file is missing. How could you miss that?
Cause I was forced to rename "palette" to something else cause I already had that one. So I put the capital A infront and I guess I don't know what happened... then I was awarded with a grey screen up until a few minutes ago.
Um OK but a debugger of any worth will show you what addresses are being loaded from either in real-time [an execution trace] or in design-time [an IDE with a "Go to declaration" option for symbols]. Best of all...for NES they're all *free*!
Thanks cpow!
Does the FCEUX debugger have those options?
tokumaru wrote:
You should be more careful with labels. Putting random letters to making them unique is a poor way to handle a situation like this. You should use more descriptive names (such as TitleScreenPalette, MenuPalette, and so on), or even look into local/temporary labels, so that you can reuse the names within different scopes.
When coding in assembly you should make thing as clear as possible, other wise it will be hell to maintain. If you pick up the code after a couple of weeks and all you have to tell labels apart is a (meaningless) prefix/suffix character, you'll be in trouble.
tokumaru, thank you so much!
Yes, you are right. I will make things as clear as possible from now on and I will try to use more descriptive variable names. I already have a lot of those temporary labels just add a + or - in front and then can reuse the variable name in different scopes!
edit: I'm unsure if + and - labels are temporary labels...
cpow wrote:
a debugger of any worth will show you what addresses are being loaded from either in real-time [an execution trace] or in design-time [an IDE with a "Go to declaration" option for symbols]. Best of all...for NES they're all *free*!
Almost. For NES, you don't get sound and a debugger in the same executable unless you pay $150 for Windows. FCEUX for Windows in Wine lacks sound, NESICIDE for Linux runs at less than full speed on my machine and therefore also lacks sound, and FCEUX for Linux lacks a debugger.
unregistered wrote:
Thanks cpow!
Does the FCEUX debugger have those options?
cpow is the developer of NESICIDE, an emulator containing such a debugger.
tepples wrote:
Almost. For NES, you don't get sound and a debugger in the same executable unless you pay $150 for Windows.
Or you stop being such a square and illegally download a 11-year operating system nobody even cares about anymore. Not pirating Windows 7 is one thing, but nobody gives a damn about XP anymore. Just get one of those pre-activated minified versions and be happy.
unregistered wrote:
I'm unsure if + and - labels are temporary labels...
Yeah, they are. When you use + and - you can reuse label names, and whenever you reference such labels the nearest one will be used.
tepples wrote:
NESICIDE for Linux runs at less than full speed on my machine and therefore also lacks sound
cpow is the developer of NESICIDE, an emulator containing such a debugger.
I won't hijack this thread I'll just mention that there have been some discoveries [thanks lidnariq, torrasque] regarding the poor performance on Linux. Having said that, the minimum system requirements are still pretty high yet.
Personally, I always use writes to $FF as breakpoint triggers, so all I have to do is "sta $ff" at the point I want to debug.
How could I do that too? ...with FCEUX.
Put 00FF in the address box of the add breakpoint dialog, then check the "write" box.
In your code, add an sta $FF before the routine you want to check.
Edit: Forgot to say, that's a pretty great idea, Tokumaru. I always just used to use odd opcodes.
Kasumi wrote:
Put 00FF in the address box of the add breakpoint dialog, then check the "write" box.
In your code, add an sta $FF before the routine you want to check.
Kasumi, thank you!
I'm really happy for the help, but im a little confused to... what do yall do next that helps you? I only know about pressing "Run" and nothing happens.
You have to use the step into button to step 1 line of a code at a time. Pressing RUN will run the program until that break point is hit again, not allowing you to see anything that is wrong.
3gengames wrote:
You have to use the step into button to step 1 line of a code at a time. Pressing RUN will run the program until that break point is hit again, not allowing you to see anything that is wrong.
Ah, thank you 3gengames!
This is becoming awesome! ...I didn't know what the
step into button did.
Thank you so much Kasumi and 3gengames!
edit:
Step Into rocks! It's so helpful!
hahaha I used to make breakpoints that ran to $FFFF so the
Run button would keep working.
(<-- me looking silly)
ok in my code there is
Code:
lda rc_upleft
sta screenArray,x
lda rc_upright
sta screenArray+1,x
and so there is another part where I would like to try something like
Code:
lda (rhombusCollision_low+1), y
but there are many questions I have
1.)Does the +1 only work on one group
stas or on both
stas and
ldas?
2.)Is
(this)+1,y possible?
3.)Is
(this+1),y possible?
ok, it fit all into 3 questions...
screenArray is a label that represents an address. Labels allow you to not care about actual physical addresses, assembler takes care on it. +1 etc is added to the address that is calculated by assembler for a label. So you can use it anywhere you could use an actual address, and it works exactly the same as for an address.
Like, sta $4000+1 is the same as sta $4001; if label=$4000, sta label+1 will be the same as sta $4001.
Shiru wrote:
screenArray is a label that represents an address. Labels allow you to not care about actual physical addresses, assembler takes care on it. +1 etc is added to the address that is calculated by assembler for a label. So you can use it anywhere you could use an actual address, and it works exactly the same as for an address.
Like, sta $4000+1 is the same as sta $4001; if label=$4000, sta label+1 will be the same as sta $4001.
Wow, Shiru, great explanation! I really understand the +1 etc thing now; thank you
so much!
just added
unregistered wrote:
2.)Is (this)+1,y possible?
3.)Is (this+1),y possible?
These will probably assemble, but they will not work like you think.
LDA (this)+1, y will most likely become
LDA this+1, y, and
LDA (this+1), y will use half of your pointer along with the next byte and form an invalid pointer, causing you to access garbage data.
To do what you want you'll either have to use 2 pointers (one of them pointing one byte ahead of the other), or increment Y or the low byte of the pointer between the two accesses. Indirect addressing on the 6502 is not as flexible as you'd like.
As long as This is a set value and not a RAM location, 2 will not work and 3 will. If This is a RAM value, neither will work as you just can't do that. And remember, since that's a word sized pointer, you may be needing to do +2 to move to the next pointer, keep that in mind!
If you have an array of pointers on zero page, LDA (d,x) might be your best bet. It didn't get much use on a lot of 6502-based home computers because the built-in BASIC interpreter ate up a lot of zero page.
tokumaru wrote:
unregistered wrote:
2.)Is (this)+1,y possible?
3.)Is (this+1),y possible?
These will probably assemble, but they will not work like you think.
LDA (this)+1, y will most likely become
LDA this+1, y, and
LDA (this+1), y will use half of your pointer along with the next byte and form an invalid pointer, causing you to access garbage data.
Thank you though for being brave and explaning to me the truth about my second and third question. I just coded a solution yesterday thinking the other two questions weren't important. Thank you tokumaru!
tokumaru wrote:
To do what you want you'll either have to use 2 pointers (one of them pointing one byte ahead of the other), or increment Y or the low byte of the pointer between the two accesses. Indirect addressing on the 6502 is not as flexible as you'd like.
Sorry, I dont understand this right now... but, maybe that'll be ok if one of the 2 ideas below becomes possible.
If not, I'll swing back to learn this.
3gengames wrote:
As long as This is a set value and not a RAM location, 2 will not work and 3 will. If This is a RAM value, neither will work as you just can't do that.
3gengames, thank you for explaining this.
I don't think I could use a set value though because I'd have to recalculate it every time I added a line of code before it. Maybe I'm wrong about that? I dont know.
tepples wrote:
If you have an array of pointers on zero page, LDA (d,x) might be your best bet.
Really?!
4.)Is LDA
(this+3, x) possible?
unregistered wrote:
tepples wrote:
If you have an array of pointers on zero page, LDA (d,x) might be your best bet.
Really?!
4.)Is LDA
(this+3, x) possible?
Yes, as long as This is a ROM location that doesn't change (when compiled, not over the projects whole creation) then that would work. But, the thing you're missing is if you have a lot of pointers in zero page, then you need to just do the begging of the array+X to point to one, because that's what it does. If you have 3 pointers in an array, you can select any of the three by putting in the code LDA (ZeroPageArray,X) because it takes the array location, adds X to the pointer of it, then gets wherever THAT points to.
If I could make something up in paint I would, but I'm too slow and my graduation party is begging in 2 hours.
Think of it as a normal array (LDA Array,X) then then whatever that points to, it then looks at it. Remember, each pointer is 2 bytes so moving 1 byte ahead in the array will mean you have to either do a INX INX or a ASL A if you do a TAX to get X to something you need.
unregistered wrote:
Is LDA (this+3, x) possible?
It is, but again, I don't think it will do what you want. The
($XX), y addressing mode is meant for accessing an array through a pointer, while
($XX, x) is meant for using arrays of pointers. In the first case, the index register (Y) indexes the targeted data, in the second case, the index register (X) indexes a pointer in an array of pointers.
The reason why
($XX, x) is rarely used is because it can't index the target data in any way. Once the address of the pointer is calculated ($XX + x) and the pointer is used, the target is a single byte. There's no way to access any bytes after that one. If you do wish to access the next bytes, you have to manipulate the pointer itself (INC the lower byte, if it overflows INC the higher byte), which is terribly slow.
The
+offset trick works with absolute addressing because the assembler calculates the absolute address for you based on the offsets you give it. With indirect addressing it's different, because it's not the assembler that will calculate the final address of the data, it's the CPU that will do it as the program runs. For this reason, if you try to use offsets they will not affect the final address, but rather the address of the pointer. I'm pretty sure that this is not what you want.
It seems you are a bit confused about the addressing modes, so I suggest you play a bit with
($XX, x) and
($XX), y using the 6502 simulator until you understand how they work.
Honestly, I can't remember a single time when the ($XX,x) addressing was useful for me, so I would say it is not urgent to learn it, as it probably won't help you much either.
The ($XX),y addressing is a very important thing to learn, though.
tokumaru wrote:
The reason why ($XX, x) is rarely used is because it can't index the target data in any way. Once the address of the pointer is calculated ($XX + x) and the pointer is used, the target is a single byte. There's no way to access any bytes after that one. If you do wish to access the next bytes, you have to manipulate the pointer itself (INC the lower byte, if it overflows INC the higher byte), which is terribly slow.
But if you do have an array of pointers, such as one pointer for each sound channel, and you only consume a few bytes per frame, 10.02 cycles per increment (INC ptr,x BNE :+ INC ptr+1,x : ) isn't
too much slower than 5.02 cycles per increment for (d),y (INY BNE :+ INC ptr+1 : ).
EDIT: cycle count corrected per tokumaru's clarification
Exactly. Reading data from music streams is the only legitimate use I can think of for this addressing mode. Also, incrementing the pointer is more like INC prt, x BNE :+ INC prt+1, x :, because if you knew what pointer to increment there would be no reason to use (ptr, x).
tokumaru wrote:
It seems you are a bit confused about the addressing modes, so I suggest you play a bit with ($XX, x) and ($XX), y using the 6502 simulator until you understand how they work.
What is a 6502 simulator?
I dont understand.
unregistered wrote:
What is a 6502 simulator?
I dont understand.
I'm talking about
this. It's a 6502 simulator with integrated debug features. You write some code, assemble it (to memory, no files are generated), and then you run it and use a multitude of debugging features to analyze what the program is doing.
I used it a lot when I was first learning to program in 6502 assembly, to make sure I completely understood how the CPU worked. I wrote all kinds of test code to understand how the carry and the other flags were affected, how the addressing modes worked, subroutines, the stack, everything. Later I practiced by making some useful routines, such as multiplication and division, to make sure I got the hang of it.
I feel
http://visual6502.org/welcome.html would be a more modern alternative. Reason being that it should be as accurate as a real 6502.
Jeroen wrote:
I feel
http://visual6502.org/welcome.html would be a more modern alternative. Reason being that it should be as accurate as a real 6502.
That's like killing a fly with a bazooka. As I see it, This is geared towards people researching more obscure behaviors of the 6502, things that haven't been properly documented yet and that until recently required expensive equipment and a real 6502 to test. It is, however, very demanding on computer resources and doesn't offer particularly friendly debugging controls. For a newbie that's just trying to get the hang of how each instruction works, Michal Kowalski's tool is much more appropriate IMO.
tokumaru wrote:
unregistered wrote:
What is a 6502 simulator?
I dont understand.
I'm talking about
this. It's a 6502 simulator with integrated debug features. You write some code, assemble it (to memory, no files are generated), and then you run it and use a multitude of debugging features to analyze what the program is doing.
I used it a lot when I was first learning to program in 6502 assembly, to make sure I completely understood how the CPU worked. I wrote all kinds of test code to understand how the carry and the other flags were affected, how the addressing modes worked, subroutines, the stack, everything. Later I practiced by making some useful routines, such as multiplication and division, to make sure I got the hang of it.
Wow THANK YOU so much tokumaru!
I tried this code
Code:
.org $0000
temp .DB 0
grate .DB 7
.ORG $1000
LDA #$03
STA temp
INC temp
LDY #101
STA ($34), y
INY
INY
But the only thing that seems to work is it puts a 7 in address $0001.
What is the problem with the rest of my code?
Guess I have to read some instructions.
Since this simulator doesn't use a ROM, it doesn't have the IRQ, NMI and Reset vectors, so it uses the first .ORG in your code as the reset vector. Since you used .ORG $0000 to define your variables, that's where execution starts. Since you put a 0 there, and that's the opcode for the BRK instruction (instruction that the simulator uses to terminate the program), nothing happens.
Just put ".START $1000" at the very top, to tell the simulator where the program actually starts.
Also, by default, the simulator doesn't know what's RAM and what's ROM, so it will allow you to .DB a value somewhere (i.e. the 7 at $0001) and still use that location as RAM, but you should know that this is not possible on actual hardware. .DB can only be used to set bytes in ROM, to put a value in RAM you have to manually LOAD it and STORE it there.
tokumaru wrote:
Since this simulator doesn't use a ROM, it doesn't have the IRQ, NMI and Reset vectors, so it uses the first .ORG in your code as the reset vector. Since you used .ORG $0000 to define your variables, that's where execution starts. Since you put a 0 there, and that's the opcode for the BRK instruction (instruction that the simulator uses to terminate the program), nothing happens.
Just put ".START $1000" at the very top, to tell the simulator where the program actually starts.
Also, by default, the simulator doesn't know what's RAM and what's ROM, so it will allow you to .DB a value somewhere (i.e. the 7 at $0001) and still use that location as RAM, but you should know that this is not possible on actual hardware. .DB can only be used to set bytes in ROM, to put a value in RAM you have to manually LOAD it and STORE it there.
tokumaru, THIS PROGRAM IS SO COOL!
THANK YOU SO MUCH!
Now there is an 8pixel sometimes-tile-#0 thin line at the top of the screen... the nametable is pushed underneath. Has anyone had this happen to them in
FCEUX and know what caused it?
In
nintendulator it pops up a small box that says "Bad opcode, CPU locked". So that is different...
unregistered wrote:
Now there is an 8pixel sometimes-tile-#0 thin line at the top of the screen... the nametable is pushed underneath. Has anyone had this happen to them in FCEUX and know what caused it?
Not really sure what you're talking about here. Did you put unused sprites off-screen? You must put sprites you are not using below the end of the screen (use a Y coordinate of 240 or more) in order to hide them, otherwise most emulators will put the sprites at the top left of the screen (coordinates 0, 0).
Quote:
In
nintendulator it pops up a small box that says "Bad opcode, CPU locked". So that is different...
The program is crashing, just like it was before. You have to solve this the same way, tracing the code and finding out where it derails.
unregistered wrote:
In
nintendulator it pops up a small box that says "Bad opcode, CPU locked". So that is different...
Just for the record, this specifically means your program has hit an invalid instruction. It usually happens when your program counter runs through data instead of code.
This should be a fairly easy one to debug by logging, because it's pretty likely the program halts very soon after the issue, instead of a wild goose chase when it could be anything like before.
It means you forgot to return or jump at the end of some function of code.
Another easy to overlook problem is pushing bytes onto the stack in some subroutine, but forgetting to pull them before the RTS is reached which will make the CPU jump off to some random location.
Kasumi wrote:
unregistered wrote:
In
nintendulator it pops up a small box that says "Bad opcode, CPU locked". So that is different...
Just for the record, this specifically means your program has hit an invalid instruction. It usually happens when your program counter runs through data instead of code.
This should be a fairly easy one to debug by logging, because it's pretty likely the program halts very soon after the issue, instead of a wild goose chase when it could be anything like before.
Debug by logging! Thank you Kasumi!
Thanks also Dwedit and smkd... yall's suggestions will help me too.
Ok, well I found something happened here's the code
Code:
0C23C 68 pla ;<---------------------------------------------------------0
0C23D ;now our attributetable is ready to be written
0C23D ;SO WE WRITE!!
0C23D
0C23D C9 14 cmp #20
0C23F F0 0D beq +
0C241
0C241 A9 23 lda #$23
0C243 8D 06 20 sta $2006 ;Sets the high byte of the address $2007 will write to.
0C246 A9 C0 lda #$C0
0C248 8D 06 20 sta $2006 ;Sets the low byte of the address $2007 will write to.
0C24B 4C 58 C2 jmp +write
0C24E
0C24E A9 27 + lda #$27
0C250 8D 06 20 sta $2006 ;Sets the high byte of the address $2007 will write to.
0C253 A9 C0 lda #$C0
0C255 8D 06 20 sta $2006 ;Sets the low byte of the address $2007 will write to.
0C258
0C258 +write:
0C258 BD F0 04 - lda attributes, x
0C25B 8D 07 20 sta $2007
0C25E E8 inx
0C25F E0 3F cpx #63
0C261 ; ; dex
0C261 D0 F5 bne -
0C263
0C263 60 rts ;end of attributetable ********************************************************
and here is the end-part of my logging...
Code:
C25E:E8 INX A:55 X:3C Y:00 P:nvUbdizc
$C25F:E0 3F CPX #$3F A:55 X:3D Y:00 P:nvUbdizc
$C261:D0 F5 BNE $C258 A:55 X:3D Y:00 P:NvUbdizc
$C258:BD F0 04 LDA $04F0,X @ $052D = #$55 A:55 X:3D Y:00 P:NvUbdizc
$C25B:8D 07 20 STA $2007 = #$00 A:55 X:3D Y:00 P:nvUbdizc
$C25E:E8 INX A:55 X:3D Y:00 P:nvUbdizc
$C25F:E0 3F CPX #$3F A:55 X:3E Y:00 P:nvUbdizc
$C261:D0 F5 BNE $C258 A:55 X:3E Y:00 P:NvUbdizc
$C258:BD F0 04 LDA $04F0,X @ $052E = #$55 A:55 X:3E Y:00 P:NvUbdizc
$C25B:8D 07 20 STA $2007 = #$00 A:55 X:3E Y:00 P:nvUbdizc
$C25E:E8 INX A:55 X:3E Y:00 P:nvUbdizc
$C25F:E0 3F CPX #$3F A:55 X:3F Y:00 P:nvUbdizc
$C261:D0 F5 BNE $C258 A:55 X:3F Y:00 P:nvUbdiZC
$C263:60 RTS A:55 X:3F Y:00 P:nvUbdiZC
$0001:00 BRK A:55 X:3F Y:00 P:nvUbdiZC
$C3DB:40 RTI A:55 X:3F Y:00 P:nvUbdIZC
$0003:00 BRK A:55 X:3F Y:00 P:nvUBdiZC
$C3DB:40 RTI A:55 X:3F Y:00 P:nvUBdIZC
$0005:8B UNDEFINED A:55 X:3F Y:00 P:nvUBdiZC
$0007:00 BRK A:00 X:3F Y:00 P:nvUBdiZC
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIZC
$0009:00 BRK A:00 X:3F Y:00 P:nvUBdiZC
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIZC
$000B:00 BRK A:00 X:3F Y:00 P:nvUBdiZC
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIZC
$000D:C7 UNDEFINED A:00 X:3F Y:00 P:nvUBdiZC
$000F:00 BRK A:00 X:3F Y:00 P:nvUBdizc
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIzc
$0011:CC 00 00 CPY $0000 = #$FF A:00 X:3F Y:00 P:nvUBdizc
$0014:00 BRK A:00 X:3F Y:00 P:nvUBdizc
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIzc
$0016:00 BRK A:00 X:3F Y:00 P:nvUBdizc
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIzc
$0018:00 BRK A:00 X:3F Y:00 P:nvUBdizc
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIzc
$001A:00 BRK A:00 X:3F Y:00 P:nvUBdizc
$C3DB:40 RTI A:00 X:3F Y:00 P:nvUBdIzc
$001C:02 UNDEFINED A:00 X:3F Y:00 P:nvUBdizc
$001C:02 UNDEFINED A:00 X:3F Y:00 P:nvUBdizc
$001C:02 UNDEFINED A:00 X:3F Y:00 P:nvUBdizc
$001C:02 UNDEFINED A:00 X:3F Y:00 P:nvUBdizc
And it just kept running that UNDEFINED instruction with
A:00 X:3F Y:00
until I stopped the logging.
It hits the rts and then it runs memory location $0001 and then it runs the RTI at $C3DB... over and over... Im confused!
Did you JSR to it? Was there something on the stack you needed to PLA to get or just mistakenly used it? Just some ideas to look into.
3gengames wrote:
Did you JSR to it? Was there something on the stack you needed to PLA to get or just mistakenly used it? Just some ideas to look into.
yes I did JSR to it. I don't have a clue what was on the stack... thank you 3gengames!
I don't think there's enough info here to help you out specifically, but I suggest you just use a ram value instead of pla/pha unless you absolutely need them. (You usually don't, and it's actually faster to not use them if the temp ram value is on the zero page.)
You've got a path in your code where it hits a pha without a pla or vice versa.
Things clearly went wrong after the RTS, when the CPU started trying to execute code at $0001. You probably misused the stack somehow, causing it to point to an invalid return address by the time the RTS is executed.
I second Kasumi's suggestion: avoid using the stack for temporary storage unless you absolutely need it. When you use the stack in the middle of code that makes decisions it's very easy forget bytes on it or pull more than you've pushed. A corrupted stack is absolutely disastrous, 99.99% of the time your program will crash because of it.
I don't get it...even when JSR'd too, does it just shove the attributes to VRAM? Because that should probably just be straight NMI update code with "switch" in the engine IMO. And what does it need to CMP to anyway? The pulled A value? If so you need it, it just seems out of place...there wouldn't be any more code from the subroutine not shown would there?
3gengames wrote:
there wouldn't be any more code from the subroutine not shown would there?
There MUST be a part of the subroutine he's not showing. If that's the complete routine, that PLA obviously screws things up by removing part of the return address from the stack.
unregistered wrote:
I don't have a clue what was on the stack...
Well, then that's what you have to find out now. When you do a JSR, the current program counter is put on the stack so that later you can return to the place where the call was made. If you do any stack manipulation after that, you must make sure to leave the stack exactly like it was before such manipulation, so that the return address can be used by the RTS. Your program crashed because it returned to an invalid address, so you have to find out why there was an invalid address on the stack by the time the RTS was executed.
I just spent hours searching for an answer to my question... tokumaru, it's on what we were talking about. Sorry for traveling back in time.
tokumaru, on page 41, wrote:
unregistered wrote:
What is a 6502 simulator?
I dont understand.
I'm talking about
this. It's a 6502 simulator with integrated debug features. You write some code, assemble it (to memory, no files are generated), and then you run it and use a multitude of debugging features to analyze...
Code:
LDA #<MetatileRhombus
STA rhombusCollision_low
LDA #>MetatileRhombus
STA rhombusCollision_high
How can I make this assemble correctly using the simulator?
I don't know what to use for obtaining the Least Significant Bit or Most Significant Bit. I even successfully updated the .hlp program so I could access the help, but now I'm missing the .hlp file. It didn't come with it.
unregistered wrote:
How can I make this assemble correctly using the simulator?
Does that not work? Then what errors are you getting?
I did this:
Code:
.org $8000
LDA #<label
lda #>label
.ORG $9001
label:
And it loaded #$01 and #$90 as expected.
Kasumi wrote:
unregistered wrote:
How can I make this assemble correctly using the simulator?
Does that not work? Then what errors are you getting?
I did this:
Code:
.org $8000
LDA #<label
lda #>label
.ORG $9001
label:
And it loaded #$01 and #$90 as expected.
sorry, i read the error message fully slowly and found that my sister's file wasn't included and can't be included. Thank you I'm sorry.
unregistered wrote:
i read the error message fully slowly and found that my sister's file wasn't included and can't be included.
Yeah, there are things this simulator just doesn't do. I find it good to test small pieces of code and study how the CPU works, but you really can't expect it to interpret correctly large portions of game code made for another assembler.
Back when I was getting started on NES programming, I actually used this simulator as my assembler, as it can save binaries. The biggest issue is the lack of "INCBIN", so all your data has to be defined in text form with "DB" and "DW" statements.
tokumaru wrote:
...biggest issue is the lack of "INCBIN", so all your data has to be defined in text form with "DB" and "DW" statements.
Ah well all of our files
of data are actually in text form
with "DB" statements... it's just that we made up our own extension
s. : )
edit.
tokumaru wrote:
Things clearly went wrong after the RTS, when the CPU started trying to execute code at $0001. You probably misused the stack somehow, causing it to point to an invalid return address by the time the RTS is executed.
Yes, I have found two zeros on the top of the stack and then it hits the rts and it returns to $0001. That's how it is supposed to work, I think.
I need to now figure out how they get to the top of the stack.
tokumaru wrote:
I second Kasumi's suggestion: avoid using the stack for temporary storage unless you absolutely need it. When you use the stack in the middle of code that makes decisions it's very easy forget bytes on it or pull more than you've pushed. A corrupted stack is absolutely disastrous, 99.99% of the time your program will crash because of it.
Thank you Kasumi and tokumaru for helping me to learn through all of this!
tokumaru wrote:
3gengames wrote:
there wouldn't be any more code from the subroutine not shown would there?
There MUST be a part of the subroutine he's not showing. If that's the complete routine, that PLA obviously screws things up by removing part of the return address from the stack.
Yes 3gengames that wasn't the whole routine... if it was I would have included the function's label at the top.
It's much longer.
tokumaru wrote:
unregistered wrote:
I don't have a clue what was on the stack...
Well, then that's what you have to find out now. When you do a JSR, the current program counter is put on the stack so that later you can return to the place where the call was made. If you do any stack manipulation after that, you must make sure to leave the stack exactly like it was before such manipulation, so that the return address can be used by the RTS. Your program crashed because it returned to an invalid address, so you have to find out why there was an invalid address on the stack by the time the RTS was executed.
Thank you tokumaru
; you are wise, I still think.
unregistered wrote:
Yes, I have found two zeros on the top of the stack and then it hits the rts and it returns to $0001. That's how it is supposed to work, I think.
I need to now figure out how they get to the top of the stack.
In a decent debugger, watching for writes to CPU memory $0100-$01FF should catch all pushes.
tepples wrote:
unregistered wrote:
Yes, I have found two zeros on the top of the stack and then it hits the rts and it returns to $0001. That's how it is supposed to work, I think.
I need to now figure out how they get to the top of the stack.
In a decent debugger, watching for writes to CPU memory $0100-$01FF should catch all pushes.
Is FCEUX a decent debugger? I tried to set that breakpoint and it only found all of the zeroing out at the beginning.
I'll have to try that later tonight to see why PHA instructions aren't being caught by FCEUX's debugger. I wonder, however, whether a bug report will be answered, seeing as the latest FCEUX is a year old.
Since there aren't too many NES emulators with debuggers in general, it could be considered a decent one, along with Nintendulator and Mednafen. I personally mostly use FCEUX.
Wow, ok thanks tepples.
Shiru wrote:
I personally mostly use FCEUX.
Me too. Though, I'm just beginning to understand a small bit of the nes... and while you have released some nes games and have made an awesome Famitone sound engine!
Thank you Shiru!
tepples wrote:
I'll have to try that later tonight to see why PHA instructions aren't being caught by FCEUX's debugger. I wonder, however, whether a bug report will be answered, seeing as the latest FCEUX is a year old.
Well, I just figured it out!
Yes, there were two 0s on the stack because my code pushed them there... and then a branch could occure to the wrong place... then there was .... well, like this:
Code:
0C291 ;checks and saves upper left tile
0C291 0A asl a ;<pushes bit #6 into carry.
0C292 48 pha ;----------------------->
0C293 48 pha ;---------------------->
0C294 29 00 and #$00 ; clear accumulator
0C296 2A rol a
0C297 85 2A sta solidMETATILE ;if this is true all the rc_s should be 1s
0C299 D0 03 bne +
0C29B 4C B1 C2 jmp +normal
0C29E 68 + pla ;<----------------------
0C29F 68 pla ;<-----------------------
0C2A0 A9 01 lda #$01
0C2A2 85 2B sta rc_upleft
0C2A4 85 2C sta rc_upright
0C2A6 85 2D sta rc_loleft
0C2A8 85 2E sta rc_loright
0C2AA 0A asl a
0C2AB 0A asl a
0C2AC 0A asl a
0C2AD 0A asl a
0C2AE 4C D1 C2 jmp +rc_s ; nooooooooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!!!!!!jmp attributetable
0C2B1 +normal:
0C2B1 68 pla ;<----------------------
0C2B2 0A asl a ;moves bit #5 into carry
0C2B3 29 00 and #%00
0C2B5 2A rol a
0C2B6 85 2B sta rc_upleft;
0C2B8 68 pla ;<-----------------------
Lines
0C29E and
0C29F weren't there and so the two 0s were pushed and missed their pull if the code chose that path. It also used to
jmp attributetable which was also not correct... that was into a totally different file... I remember writing that and then I changed the order of the code to correct a problem without changing that label back to what it had been. So now that 8 pixel black line never appears anymore!
And my sprite appears again!!
And the controller started to function again!!!
It's not fixed all the way yet... more learning and searching ahead.
That would be amazing if there was a bug fix... thanks for submitting it tepples.
Please give a temp variable a try.
Code:
0C292 48 pha ;----------------------->
0C293 48 pha ;---------------------->
You're pushing the same value twice. You'd only need it to store it to temp RAM once, and the value would be the same after you loaded it once, unlike if you pull from the stack.
Code:
0C299 D0 03 bne +
0C29B 4C B1 C2 jmp +normal
0C29E 68 + pla ;<----------------------
0C29F 68 pla ;<-----------------------
0C2A0 A9 01 lda #$01
You'll pulling twice here to fix the stack but not even using the values. If you used a variable you have no obligation to read the value back.
Another tiny thing:
Code:
0C2A0 A9 01 lda #$01;A is a known state. #%00000001
0C2A2 85 2B sta rc_upleft
0C2A4 85 2C sta rc_upright
0C2A6 85 2D sta rc_loleft
0C2A8 85 2E sta rc_loright
0C2AA 0A asl a;A is now guaranteed to be #%00000010
0C2AB 0A asl a;A is now guaranteed to be #%00000100
0C2AC 0A asl a;A is now guaranteed to be #%00001000
0C2AD 0A asl a;A is now guaranteed to be #%00010000
0C2AE 4C D1 C2 jmp +rc_s
I may be off base here but can't those asl instructions be replaced with just this?
Code:
lda #%00010000
Since that seems to be what A is guaranteed to be anyway.
But actually I suppose I shouldn't talk too much about this sort of thing. It's only worth thinking about that stuff too much when it does exactly what you want it to do.
Edit: Hmm, but I can't stop myself.
Code:
0C299 D0 03 bne +
0C29B 4C B1 C2 jmp +normal
0C29E 68 + pla ;<----------------------
0C29F 68 pla ;<-----------------------
+normal should be close enough to branch to.
Code:
0C299 D0 03 beq +normal
And here's something to consider:
Code:
0C291 ;checks and saves upper left tile
0C291 0A asl a ;<pushes bit #6 into carry.
bcs entry;Don't use a generic label name like I just did ;)
0C292 48 pha ;----------------------->
0C293 48 pha ;---------------------->
0C294 29 00 and #$00 ; clear accumulator
0C296 2A ; rol a;No need for this, we already branched on this condition above
0C297 85 2A sta solidMETATILE ;if this is true all the rc_s should be 1s
0C299 D0 03 beq +normal;This could actually be a jmp now
0C29E 68 entry:
0C2A0 A9 01 lda #$01
0C2A2 85 2B sta rc_upleft
0C2A4 85 2C sta rc_upright
0C2A6 85 2D sta rc_loleft
0C2A8 85 2E sta rc_loright
sta solidMETATILE;Since we skipped doing this above
0C2AA 0A asl a
0C2AB 0A asl a
0C2AC 0A asl a
0C2AD 0A asl a;lda #%00010000?
0C2AE 4C D1 C2 jmp +rc_s ; nooooooooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!!!!!!jmp attributetable
0C2B1 +normal:
That should skip using the stack entirely in the case you don't need it, if I'm understanding everything correctly. (I may not be, so if it looks wrong it probably is)
You have more than one condition dependent on that shifted bit six, and you check it multiple times. I check it once at the beginning, and skip the pha instructions and pla instruction, the sta solidMETATILE (I set it later), and the additional branch in the case where "bit 6" set. It's also the same speed when it's clear, because we skip out on the rol a. (Again I may not be doing something your code needs, but this seems right)
Sorry for going crazy, (which may not even help if the code is broken, I just tried to make it get the exact same end result) messing with assembly like this is actually pretty enjoyable for me.
I didn't submit anything.
Today I made a test ROM that executes several PHA and PLA instructions, one PHP and PLP, one each of BRK, /NMI, and /IRQ, each with its corresponding RTI, and a JSR and RTS pair. I ran it in FCEUX 2.1.5 with a breakpoint on reads and writes to $0100-$01FF (the stack), and it stopped on PHA, PLA, RTI, JSR, RTS, PHP, PLP. It didn't appear to stop on BRK or on the JMP instruction that gets interrupted, but most breakpoints on the stack appear to work.
tepples wrote:
I didn't submit anything.
Today I made a test ROM that executes several PHA and PLA instructions, one PHP and PLP, one each of BRK, /NMI, and /IRQ, each with its corresponding RTI, and a JSR and RTS pair. I ran it in FCEUX 2.1.5 with a breakpoint on reads and writes to $0100-$01FF (the stack), and it stopped on PHA, PLA, RTI, JSR, RTS, PHP, PLP. It didn't appear to stop on BRK or on the JMP instruction that gets interrupted, but most breakpoints on the stack appear to work.
tepples, thank you for telling me this!
I assumed that the one I had found, FCEUX 2.0.3, was the latest one...
FCEUX 2.1.5 looks amazing! That does work!!!
But maybe it would have worked in FCEUX 2.0.3... maybe it didn't work because of my subpar coding skills... I don't know; I do know that I set the breakpoint up correctly in 2.0.3.
---
Kasumi, that is also fun for me too! I will reply more later... sorry... it's lunch time...
Kasumi wrote:
Please give a temp variable a try.
Code:
0C292 48 pha ;----------------------->
0C293 48 pha ;---------------------->
You're pushing the same value twice. You'd only need it to store it to temp RAM once, and the value would be the same after you loaded it once, unlike if you pull from the stack.
Code:
0C299 D0 03 bne +
0C29B 4C B1 C2 jmp +normal
0C29E 68 + pla ;<----------------------
0C29F 68 pla ;<-----------------------
0C2A0 A9 01 lda #$01
You'll pulling twice here to fix the stack but not even using the values. If you used a variable you have no obligation to read the value back.
Another tiny thing:
Code:
0C2A0 A9 01 lda #$01;A is a known state. #%00000001
0C2A2 85 2B sta rc_upleft
0C2A4 85 2C sta rc_upright
0C2A6 85 2D sta rc_loleft
0C2A8 85 2E sta rc_loright
0C2AA 0A asl a;A is now guaranteed to be #%00000010
0C2AB 0A asl a;A is now guaranteed to be #%00000100
0C2AC 0A asl a;A is now guaranteed to be #%00001000
0C2AD 0A asl a;A is now guaranteed to be #%00010000
0C2AE 4C D1 C2 jmp +rc_s
I may be off base here but can't those asl instructions be replaced with just this?
Code:
lda #%00010000
Since that seems to be what A is guaranteed to be anyway.
But actually I suppose I shouldn't talk too much about this sort of thing. It's only worth thinking about that stuff too much when it does exactly what you want it to do.
Edit: Hmm, but I can't stop myself.
Code:
0C299 D0 03 bne +
0C29B 4C B1 C2 jmp +normal
0C29E 68 + pla ;<----------------------
0C29F 68 pla ;<-----------------------
+normal should be close enough to branch to.
Code:
0C299 D0 03 beq +normal
And here's something to consider:
Code:
0C291 ;checks and saves upper left tile
0C291 0A asl a ;<pushes bit #6 into carry.
bcs entry;Don't use a generic label name like I just did ;)
0C292 48 pha ;----------------------->
0C293 48 pha ;---------------------->
0C294 29 00 and #$00 ; clear accumulator
0C296 2A ; rol a;No need for this, we already branched on this condition above
0C297 85 2A sta solidMETATILE ;if this is true all the rc_s should be 1s
0C299 D0 03 beq +normal;This could actually be a jmp now
0C29E 68 entry:
0C2A0 A9 01 lda #$01
0C2A2 85 2B sta rc_upleft
0C2A4 85 2C sta rc_upright
0C2A6 85 2D sta rc_loleft
0C2A8 85 2E sta rc_loright
sta solidMETATILE;Since we skipped doing this above
0C2AA 0A asl a
0C2AB 0A asl a
0C2AC 0A asl a
0C2AD 0A asl a;lda #%00010000?
0C2AE 4C D1 C2 jmp +rc_s ; nooooooooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!!!!!!jmp attributetable
0C2B1 +normal:
That should skip using the stack entirely in the case you don't need it, if I'm understanding everything correctly. (I may not be, so if it looks wrong it probably is)
You have more than one condition dependent on that shifted bit six, and you check it multiple times. I check it once at the beginning, and skip the pha instructions and pla instruction, the sta solidMETATILE (I set it later), and the additional branch in the case where "bit 6" set. It's also the same speed when it's clear, because we skip out on the rol a. (Again I may not be doing something your code needs, but this seems right)
Sorry for going crazy, (which may not even help if the code is broken, I just tried to make it get the exact same end result) messing with assembly like this is actually pretty enjoyable for me.
This would be enjoyable for me too if I could accomplish making my code quicker. You are extreemly good at it Kasumi! Thanks!
Yes, I also agree that my code doesn't accomplish what its susposed to do; and I'll wait on attempting your suggestions until it seems to be surviving just fine.
Now the screen doesn't scroll to the right anymore. It struggles to but is always reset to 0.. so it appears to go eight pixels and then is reset to zero and then scrolls at most 8 pixels... and repeat until the count reaches 255 and it stops as normal, I guess, for that reason... . Can you think of what I should do... just asking for suggestions. So far I haven't worried about checking my scrolling code file because I never have changed it after it started working well.
unregistered wrote:
it appears to go eight pixels and then is reset to zero
Sounds to me like you are writing $00 to $2006, which resets the scroll. The only part of the scroll that isn't reset by this is the fine X scroll, which could be why you are able to scroll ~8 pixels. Make sure that the writes to $2005 are the last PPU operation of your VBlank handler.
tokumaru wrote:
unregistered wrote:
it appears to go eight pixels and then is reset to zero
Sounds to me like you are writing $00 to $2006, which resets the scroll. The only part of the scroll that isn't reset by this is the fine X scroll, which could be why you are able to scroll ~8 pixels. Make sure that the writes to $2005 are the last PPU operation of your VBlank handler.
Here's the end of the last routine
scroll_screen run in my vblank:
Code:
0C38F 85 1A sta currNameTable ;this is what you'll write to $2000 when setting the scroll
0C391
0C391 ; run other game graphics updating code here
0C391 8D 00 20 sta $2000
0C394
0C394 A5 1D lda CameraX+0 ; MOVE THE CAMERA OBJECT!
0C396 8D 05 20 STA $2005 ; write the horizontal scroll count register
0C399
0C399 A9 00 LDA #$00 ; no vertical scrolling
0C39B 8D 05 20 STA $2005
0C39E
0C39E ; ldx scroll ; Do we need to scroll at all?
0C39E ; beq no_scroll
0C39E ; dex
0C39E ; stx scroll
0C39E ; lda #$00
0C39E ; stx $2005 ;Write the value of 'scroll' for Horiz. Scroll value
0C39E ; sta $2005 ;Write 0 for Vert. Scroll value
0C39E ;
0C39E ;no_scroll:
0C39E 60 rts
So yes the last 2 writes to PPU is at the end.
After line 0C39E is the rti ending my vblank. But no, there
is other stuff inbetween! Here is the start of my vblank routine:
Code:
0C342 E6 1C vblank: inc FRAME_CNT
0C344 .incsrc "daprg-vblank.asm"
0C344 ;skip the video updates if the frame calculations aren't over yet
0C344 24 1F bit FrameReady
0C346 10 0D bpl SkipUpdates
0C348
0C348 ;PERFORM VIDEO UPDATES HERE
0C348 20 59 C3 jsr update_sprite
0C34B 20 5F C3 jsr scroll_screen
0C34E
0C34E
0C34E A6 0F ldx aFrame
0C350 20 9F C3 jsr draw_sprite
0C353
0C353
0C353 ;modify the flag
0C353 E6 1F inc FrameReady
0C355
0C355 SkipUpdates:
0C355
0C355 ;PERFORM TASKS THAT MUST BE PERFORMED EVEN
0C355 ;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
0C355 ;THE MUSIC OR DRAWING A STATUS BAR
0C355 20 3E DA jsr FamiToneUpdate
0C358
0C358
0C358 ;return from the NMI (vblank)
0C358 40 rti
Is my
draw_sprite routine causing the problem? Or maybe
FamiToneUpdate?
Include a little source and not a disassembly with half the stuff removed? Scared somebody will take your source? Get over it, nobody really cares what the source does, just finds it because stealing it is 200x harder than just making something themselves.
But I don't really see any problems, does the glitch happen with varying levels of height? it might be a wrong variable name somewhere or something like that. Log all your writes and make sure they're all right...
It seems you are updating the scroll and then updating other things, while it should be the other way around. Setting the scroll should ALWAYS be the very last thing in your VBlank handler. Any PPU operation involving $2006/$2007 messes with the VRAM address, which fucks up the scroll.
Sorry 3gengames.
I had my entire vblank routine ready to go... but the end was what I was really thinking about and so I deleted most of the first part. I remember having someone once complain that I posted too much code.
tokumaru wrote:
It seems you are updating the scroll and then updating other things, while it should be the other way around. Setting the scroll should ALWAYS be the very last thing in your VBlank handler. Any PPU operation involving $2006/$2007 messes with the VRAM address, which messes with the scroll.
It's quite alright to repeat the phrase "messes with" instead of cussing, I think. : )
...Thank you for saying so; I'll change that... put it at the very end.... after the rts comes the rti!
edit: woah not very good...it's kind of good... The screen scrolls at the right time but it goes back and forth between nametable1 and nametable2 really quickly.
Code:
0C340 E6 1C vblank: inc FRAME_CNT
0C342 .incsrc "daprg-vblank.asm"
0C342 ;skip the video updates if the frame calculations aren't over yet
0C342 24 1F bit FrameReady
0C344 10 0A bpl SkipUpdates
0C346
0C346 ;PERFORM VIDEO UPDATES HERE
0C346 20 57 C3 jsr update_sprite
0C349
0C349
0C349 A6 0F ldx aFrame
0C34B 20 9D C3 jsr draw_sprite
0C34E
0C34E
0C34E ;modify the flag
0C34E E6 1F inc FrameReady
0C350
0C350 SkipUpdates:
0C350
0C350 ;PERFORM TASKS THAT MUST BE PERFORMED EVEN
0C350 ;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
0C350 ;THE MUSIC OR DRAWING A STATUS BAR
0C350 20 3C DA jsr FamiToneUpdate
0C353
0C353 20 5D C3 jsr scroll_screen ;"Setting the scroll should ALWAYS be the very last thing in your VBlank handler." -tokumaru pg 43
0C356 ;return from the NMI (vblank)
0C356 40 rti
0C357
0C357
0C357
0C357
0C357
0C357
0C357 update_sprite:
0C357 A9 02 lda #>sprite
0C359 8D 14 40 sta $4014 ;OAM_DMA register ; Jam page $200-$2FF into SPR-RAM
0C35C ;takes 513 cycles.
0C35C
0C35C 60 rts
0C35D
0C35D scroll_screen:
0C35D .include "daprg-scrollland.asm"
0C35D ;PPUCTRL Controller ($2000) > write
0C35D ;PPUMASK Mask ($2001) > write
0C35D ;PPUSTATUS Status ($2002) < read
0C35D ;OAMADDR address ($2003) > write
0C35D ;OAMDATA OAM data ($2004) <> read/write
0C35D ;PPUSCROLL Scroll ($2005) >> write x2
0C35D ;PPUADDR Address ($2006) >> write x2
0C35D ;PPUDATA Data ($2007) <> read/write
0C35D
0C35D A2 00 ldx #$00 ; Reset VRAM
0C35F 8E 06 20 stx $2006
0C362 8E 06 20 stx $2006
0C365
0C365
0C365
0C365 ;ldx oX work on this now
0C365 ;if oX > 192
0C365 A5 04 lda oX
0C367 38 sec
0C368 E9 C0 sbc #192
0C36A 30 13 bmi +skipscroll
0C36C
0C36C A5 00 lda currControllerButtons ;make sure the
0C36E 29 01 and #00000001b ;Right Button is being pressed
0C370 F0 0D beq +skipscroll
0C372
0C372
0C372 ;lda #255
0C372 ;sta scroll
0C372 A5 1D lda CameraX+0
0C374 38 sec ;Set subtract. Clear Add.
0C375 E9 FF sbc #255
0C377 F0 06 beq +skipscroll ;skip incrementing scroll when it gets to #255
0C379
0C379 E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37B E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37D E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37F
0C37F
0C37F +skipscroll
0C37F A9 88 lda #%10001000
0C381 85 1A sta currNameTable
0C383 29 FE and #$fe ;clear the lowest bit
0C385 85 1A sta currNameTable
0C387
0C387 A5 1E lda CameraX+1 ;get the high byte of the camera
0C389 29 01 and #$01 ;keep only the lowest bit
0C38B 05 1A ora currNameTable ;combine with the other value
0C38D 85 1A sta currNameTable ;this is what you'll write to $2000 when setting the scroll
0C38F
0C38F ; run other game graphics updating code here
0C38F 8D 00 20 sta $2000
0C392
0C392 A5 1D lda CameraX+0 ; MOVE THE CAMERA OBJECT!
0C394 8D 05 20 STA $2005 ; write the horizontal scroll count register
0C397
0C397 A9 00 LDA #$00 ; no vertical scrolling
0C399 8D 05 20 STA $2005
0C39C
0C39C ; ldx scroll ; Do we need to scroll at all?
0C39C ; beq no_scroll
0C39C ; dex
0C39C ; stx scroll
0C39C ; lda #$00
0C39C ; stx $2005 ;Write the value of 'scroll' for Horiz. Scroll value
0C39C ; sta $2005 ;Write 0 for Vert. Scroll value
0C39C ;
0C39C ;no_scroll:
0C39C 60 rts
There 3gengames... it's all there.
I went through FamiToneUpdate and was looking for PPU accesses, but Shiru used names and at the top all the names are equal to
$40xx addresses... Those are APU addresses I remember it said that... my memory is getting better!
... so I wanted to tell you, tokumaru, that moving my scroll function up just above the
SkipUpdates: (line
$0C350) reverts my .nes file back to the problem preventing scrolling. ...So you are incredibly right when you say updating the scroll should always be the last step of vblank! Thank you so much telling me that!
I also want to say a little bit more about the quick flashing of alternate nametables that happens right now (after the screen has started scrolling). Before scrolling the screen the top half is the correct tiles... the bottom half is mostly tile 0. Like about three rows or so... ok so it's not mostly
...There are about three rows at the bottom of the screen; they're all tile 0. When scrolling starts there are a little over five rows of tile 0 at the bottom of the second screen that scrolls over. And then I can see both the first and second screen. In NINTENDULATOR as well as FCEUX 2.1.5. haha! I could try this on the powerpack... but... well maybe I shouldn't?
3gengames wrote:
But I don't really see any problems, does the glitch happen with varying levels of height? it might be a wrong variable name somewhere or something like that. Log all your writes and make sure they're all right...
3gengames, I'm sorry I don't think I noticed this help... no the glitch happens at any height. "Log all my writes..."; so I need to use the logger on FCEUX.........? How... well, I know I can click
writes in the debugger when setting a breakpoint... I'm confused; going to try doing ?
Thank you so much for helping me too.
edit.
Long post. Forgive spelling/grammar/forgotten words, please. Usually I spend more time proofreading, but I've got some things I have to do tonight.
What's draw_sprite?
Quote:
0C35D A2 00 ldx #$00 ; Reset VRAM
0C35F 8E 06 20 stx $2006
0C362 8E 06 20 stx $2006
I don't think there's a need for this. This would set the address $2007 would write to to zero if you wanted to do that, but it doesn't reset anything. If you're worried about the latches being out of sync, you could bit $2002.
Now, I don't understand your scrolling logic at all. Posting lots of code usually doesn't bother me, but I have to have an idea of what it's doing. Some stuff here is specific to your game which I have no clue about. I'm under the impression you're doing something like, "If the player is close to the edge of the screen, and he's holding right we scroll slightly faster than his walk speed" Is this correct?
Now, I just do this in my vblank:
Code:
lda scrollxhigh
and #%00000001
ora mir2000;A variable that contains a mirror of $2000 that has already had its low two bits cleared out
sta $2000
lda scrollxlow
sta $2005
lda scrollyscreenlow
sta $2005
My vblank routine doesn't care about what the values are, nor does it try to update them because my game logic should take care of that.
Code:
;ldx oX work on this now
0C365 ;if oX > 192
0C365 A5 04 lda oX
0C367 38 sec
0C368 E9 C0 sbc #192
0C36A 30 13 bmi +skipscroll
First question: What's oX? Second thing, I think this won't branch in some cases you'd want it to. Let's say oX is 0. #$00-#$C0 (192) = #$40. #$40 = #%01000000. The high bit isn't set. No branch. So from #$00-#$3F this would NOT branch to skipscroll. From #$40-#$BF it would. Then from #$C0-#$FF it would not.
You may want to use the carry flag if you're trying to check if oX-192 was less than 0.
Code:
0C36C
0C36C A5 00 lda currControllerButtons ;make sure the
0C36E 29 01 and #00000001b ;Right Button is being pressed
0C370 F0 0D beq +skipscroll
0C372
0C372
0C372 ;lda #255
0C372 ;sta scroll
0C372 A5 1D lda CameraX+0
0C374 38 sec ;Set subtract. Clear Add.
0C375 E9 FF sbc #255
0C377 F0 06 beq +skipscroll ;skip incrementing scroll when it gets to #255
If CameraX is 253 or 254, the beq won't skip the scroll and we'll add 3 to cameraX because of the code below, scrolling past zero which seems like something you don't want to have happen.
Code:
0C379
0C379 E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37B E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37D E6 1D INC CameraX+0 ; add one to our camera variable each frame
Code:
0C37F A9 88 lda #%10001000
0C381 85 1A ; sta currNameTable;No need to store here. You're doing it again without loading the value immediately after.
0C383 29 FE and #$fe ;clear the lowest bit;You don't need to do this. You loaded an immediate number that already has this bit clear.
0C385 85 1A sta currNameTable ;Als
As well, I can see no condition where CameraX+1 ever changes. This means that once you scroll hits 255 for nametable 1, you'll be seeing MOST of nametable 2. But then when it hits 0, you'll see none of nametable 2 and all of nametable 1 again. Right now this problem will happen for you at least some of the time, because if those three inc CameraX instructions I posted above wrap CameraX back to 0, the high byte is never updated.
Those are some problems I see. They actually probably won't fix the problem you describe, though. Please post anything that updates CameraX and CameraX+1 that outside of the vblank routine. (If there IS any code that updates them outside of the vblank.)
The camera should be a 16bit variable.
Want to scroll 8 pixels to the right?
Code:
lda CameraX
clc
adc #$08
sta CameraX
lda CameraX+1
adc #$00
sta CameraX+1
If you want to not scroll past a certain value, check the high byte after the add to see if it became too large. If it did, set both the high and low byte to the furthest you want it to scroll.
Do any camera logic outside of your vblank.
Kasumi wrote:
Long post. Forgive spelling/grammar/forgotten words, please. Usually I spend more time proofreading, but I've got some things I have to do tonight.
It's ok, thank you for the quick help!
In 30 minutes I have to go mow... will try to reply to as much as I can now.
Kasumi wrote:
What's draw_sprite?
Oh sorry the code at bottom of page 43 should have continued... to the end of my vblank file...
Code:
0C39D
0C39D
0C39D ;80 81
0C39D ;90 91
0C39D ;a0 a1
0C39D ;b0 b1
0C39D ; .db aY, $80, $00, aX, aY, $81, $00, aX+8,
0C39D ; aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
0C39D ; aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
0C39D ; aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
0C39D
0C39D draw_sprite:
0C39D ; t0-t2 are temporary zero page locations holding
0C39D ; the address of a particular sprite layout and the
0C39D ; number of sprites left to draw
0C39D BD 14 D9 lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C3A0 85 07 sta t0
0C3A2 BD 28 D9 lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C3A5 85 08 sta t0+1
0C3A7 BD 3C D9 lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C3AA 85 09 sta t2
0C3AC A0 00 ldy #0
0C3AE ; oamIndex is a variable tracking how far you've written
0C3AE ; into shadow OAM (customarily at $0200-$02FF)
0C3AE A6 0B ldx oamIndex
0C3B0 @loop:
0C3B0 ; If you have the address of an array in a zero page pointer,
0C3B0 ; you use the (d),y addressing mode and increase Y to go
0C3B0 ; to the next byte.
0C3B0 B1 07 lda (t0), y ;<-- INDIRECT INDEXED ADDRESSING
0C3B2 C8 iny
0C3B3 ;ect. start
0C3B3 18 clc
0C3B4 65 05 adc oY
0C3B6 9D 00 02 sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C3B9 E8 inx
0C3BA
0C3BA B1 07 lda (t0), y
0C3BC C8 iny
0C3BD 9D 00 02 sta sprite, x
0C3C0 E8 inx
0C3C1
0C3C1 B1 07 lda (t0), y
0C3C3 C8 iny
0C3C4 9D 00 02 sta sprite, x
0C3C7 E8 inx
0C3C8
0C3C8 B1 07 lda (t0), y
0C3CA C8 iny
0C3CB 18 clc
0C3CC 65 04 adc oX
0C3CE 9D 00 02 sta sprite, x
0C3D1 E8 inx
0C3D2
0C3D2 ;end ect.
0C3D2 C6 09 dec t2
0C3D4 D0 DA bne @loop
0C3D6 86 0B stx oamIndex
0C3D8 60 rts
there this is the very end of my vblank file.
Kasumi wrote:
Quote:
0C35D A2 00 ldx #$00 ; Reset VRAM
0C35F 8E 06 20 stx $2006
0C362 8E 06 20 stx $2006
I don't think there's a need for this. This would set the address $2007 would write to to zero if you wanted to do that, but it doesn't reset anything. If you're worried about the latches being out of sync, you could bit $2002.
Sorry, I don't understand. Kasumi wrote:
Now, I don't understand your scrolling logic at all. Posting lots of code usually doesn't bother me, but I have to have an idea of what it's doing. Some stuff here is specific to your game which I have no clue about. I'm under the impression you're doing something like, "If the player is close to the edge of the screen, and he's holding right we scroll slightly faster than his walk speed" Is this correct?
Yes, that's how it works now... not complicated... I know.
Kasumi wrote:
Now, I just do this in my vblank:
Code:
lda scrollxhigh
and #%00000001
ora mir2000;A variable that contains a mirror of $2000 that has already had its low two bits cleared out
sta $2000
lda scrollxlow
sta $2005
lda scrollyscreenlow
sta $2005
My vblank routine doesn't care about what the values are, nor does it try to update them because my game logic should take care of that.
...I can't follow... Kasumi wrote:
Code:
;ldx oX work on this now
0C365 ;if oX > 192
0C365 A5 04 lda oX
0C367 38 sec
0C368 E9 C0 sbc #192
0C36A 30 13 bmi +skipscroll
First question: What's oX?
oX is my variable for the x-coordinate location of my one sprite on the screen. oY is the y-coordinate... cause 'x' and 'y' refer to the x-register and y-register
Sorry, must go mow now...
edited.
Now, I dont have to mow... so here's the rest...
Kasumi wrote:
Second thing, I think this won't branch in some cases you'd want it to. Let's say oX is 0. #$00-#$C0 (192) = #$40. #$40 = #%01000000. The high bit isn't set. No branch. So from #$00-#$3F this would NOT branch to skipscroll. From #$40-#$BF it would. Then from #$C0-#$FF it would not.
Ah, yes... after trying this with calculator Hex it read FFFFFFFFFFF40 and so I didn't see the fact that FF40 means 1111111101000000. In my head all the Fs made it confusing for me; thank you so much for seeing this and pointing it out!
Kasumi wrote:
You may want to use the carry flag if you're trying to check if oX-192 was less than 0.
Yes, thank you, I understand that now!
Kasumi wrote:
Code:
0C36C
0C36C A5 00 lda currControllerButtons ;make sure the
0C36E 29 01 and #00000001b ;Right Button is being pressed
0C370 F0 0D beq +skipscroll
0C372
0C372
0C372 ;lda #255
0C372 ;sta scroll
0C372 A5 1D lda CameraX+0
0C374 38 sec ;Set subtract. Clear Add.
0C375 E9 FF sbc #255
0C377 F0 06 beq +skipscroll ;skip incrementing scroll when it gets to #255
If CameraX is 253 or 254, the beq won't skip the scroll and we'll add 3 to cameraX because of the code below, scrolling past zero which seems like something you don't want to have happen.
Code:
0C379
0C379 E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37B E6 1D INC CameraX+0 ; add one to our camera variable each frame
0C37D E6 1D INC CameraX+0 ; add one to our camera variable each frame
Code:
0C37F A9 88 lda #%10001000
0C381 85 1A ; sta currNameTable;No need to store here. You're doing it again without loading the value immediately after.
0C383 29 FE and #$fe ;clear the lowest bit;You don't need to do this. You loaded an immediate number that already has this bit clear.
0C385 85 1A sta currNameTable ;Als
As well, I can see no condition where CameraX+1 ever changes. This means that once you scroll hits 255 for nametable 1, you'll be seeing MOST of nametable 2. But then when it hits 0, you'll see none of nametable 2 and all of nametable 1 again. Right now this problem will happen for you at least some of the time, because if those three inc CameraX instructions I posted above wrap CameraX back to 0, the high byte is never updated.
Those are some problems I see. They actually probably won't fix the problem you describe, though. Please post anything that updates CameraX and CameraX+1 that outside of the vblank routine. (If there IS any code that updates them outside of the vblank.)
The camera should be a 16bit variable.
Want to scroll 8 pixels to the right?
Code:
lda CameraX
clc
adc #$08
sta CameraX
lda CameraX+1
adc #$00
sta CameraX+1
If you want to not scroll past a certain value, check the high byte after the add to see if it became too large.
Ahhh this is making sense!
Thank you, but I don't understand these next 2 parts... it's ok that I dont understand; you are not at fault.
Kasumi wrote:
If it did, set both the high and low byte to the furthest you want it to scroll.
Kasumi wrote:
Do any camera logic outside of your vblank.
Why?
unregistered wrote:
Ahhh this is making sense! Very Happy Thank you, but I don't understand these next 2 parts... it's ok that I dont understand; you are not at fault.
Kasumi wrote:
If it did, set both the high and low byte to the furthest you want it to scroll.
Say, you want to restrict scrolling to 1 (high byte) and 40(low byte).
So when it get bigger(for example 1 and 41) you manually set bytes back to 1 and 40.Of course you can make different approach. For example, instead of setting bytes manualy, simply jump over scrolling increasement code.
unregistered wrote:
Kasumi wrote:
Do any camera logic outside of your vblank.
Why?
Because Vblank time is precious and should not be wasted on thing that can be done in frame time. The thing is that during Vblank you can do various things that can't be done in frame time like writing to $2007(update tiles on screen, update pallete etc.) while screen rendering is turned ON.
unregistered wrote:
Kasumi wrote:
Quote:
0C35D A2 00 ldx #$00 ; Reset VRAM
0C35F 8E 06 20 stx $2006
0C362 8E 06 20 stx $2006
I don't think there's a need for this. This would set the address $2007 would write to to zero if you wanted to do that, but it doesn't reset anything. If you're worried about the latches being out of sync, you could bit $2002.
Sorry, I don't understand.
Writing zeroes to $2006 is something that bad NES programmers do when they don't know how to properly set the scroll and the screen is jumping. I seemingly fixes the problem, but it doesn't fully reset the scroll. BTW, since your game does scroll, you shouldn't be trying to reset the scroll at all, because if you were successful your screen just wouldn't move.
Anyway, the correct way to set the scroll is by writing once to $2000 (to select the name table) and twice to $2005 (to set the X and Y scroll values). $2006 should only be used for scrolling under special circumstances that newbies shouldn't be worried about (such as changing the scroll mid-frame). Under normal circumstances, $2006 should only be used for setting the PPU address before reading/writing data through $2007, and there's absolutely no need to reset that address.
unregistered wrote:
In my head all the Fs made it confusing for me; thank you so much for seeing this and pointing it out!
Windows calculator? If so, protip: Switch it to byte mode. (There's a choice between Qword, Dword, Word, Byte when it's in hex or binary mode) That will make $00-$C0=$40, or turn -192 into $40 when switching to hex mode. No extra F's.
On another note: Provided I'm understanding it correctly, draw_sprite seems to be updating at least part of $0200-02FF. But you run update_sprite (which actually copies the sprite's data from $0200-$02FF) before it. This means whatever draw_sprite is writing will only appear during the
NEXT vblank.
Edit: Hmm... I guess this DOES keep updates that don't need to be done before rendering after things that do which is good. But, I'd recommend moving it out of vblank entirely if that's possible.
Denine wrote:
unregistered wrote:
Ahhh this is making sense! Very Happy Thank you, but I don't understand these next 2 parts... it's ok that I dont understand; you are not at fault.
Kasumi wrote:
If it did, set both the high and low byte to the furthest you want it to scroll.
Say, you want to restrict scrolling to 1 (high byte) and 40(low byte).
So when it get bigger(for example 1 and 41) you manually set bytes back to 1 and 40.Of course you can make different approach. For example, instead of setting bytes manualy, simply jump over scrolling increasement code.
unregistered wrote:
Kasumi wrote:
Do any camera logic outside of your vblank.
Why?
Because Vblank time is precious and should not be wasted on thing that can be done in frame time. The thing is that during Vblank you can do various things that can't be done in frame time like writing to $2007(update tiles on screen, update pallete etc.) while screen rendering is turned ON.
Thank you Denine!
tokumaru wrote:
unregistered wrote:
Kasumi wrote:
Quote:
0C35D A2 00 ldx #$00 ; Reset VRAM
0C35F 8E 06 20 stx $2006
0C362 8E 06 20 stx $2006
I don't think there's a need for this. This would set the address $2007 would write to to zero if you wanted to do that, but it doesn't reset anything. If you're worried about the latches being out of sync, you could bit $2002.
Sorry, I don't understand. Writing zeroes to $2006 is something that bad NES programmers do when they don't know how to properly set the scroll and the screen is jumping. I seemingly fixes the problem, but it doesn't fully reset the scroll. BTW, since your game does scroll, you shouldn't be trying to reset the scroll at all, because if you were successful your screen just wouldn't move.
Anyway, the correct way to set the scroll is by writing once to $2000 (to select the name table) and twice to $2005 (to set the X and Y scroll values). $2006 should only be used for scrolling under special circumstances that newbies shouldn't be worried about (such as changing the scroll mid-frame). Under normal circumstances, $2006 should only be used for setting the PPU address before reading/writing data through $2007, and there's absolutely no need to reset that address.
Thank you tokumaru!
Kasumi wrote:
unregistered wrote:
In my head all the Fs made it confusing for me; thank you so much for seeing this and pointing it out!
Windows calculator? If so, protip: Switch it to byte mode. (There's a choice between Qword, Dword, Word, Byte when it's in hex or binary mode) That will make $00-$C0=$40, or turn -192 into $40 when switching to hex mode. No extra F's.
Kasumi, thanks for mentioning that!
It's good to know that there's a purpose for using byte mode.
Kasumi wrote:
On another note: Provided I'm understanding it correctly, draw_sprite seems to be updating at least part of $0200-02FF. But you run update_sprite (which actually copies the sprite's data from $0200-$02FF) before it. This means whatever draw_sprite is writing will only appear during the NEXT vblank.
Edit: Hmm... I guess this DOES keep updates that don't need to be done before rendering after things that do which is good. But, I'd recommend moving it out of vblank entirely if that's possible.
Hmm...
Kasumi wrote:
Code:
;ldx oX work on this now
0C365 ;if oX > 192
0C365 A5 04 lda oX
0C367 38 sec
0C368 E9 C0 sbc #192
0C36A 30 13 bmi +skipscroll
...Second thing, I think this won't branch in some cases you'd want it to. Let's say oX is 0. #$00-#$C0 (192) = #$40. #$40 = #%01000000. The high bit isn't set. No branch. So from #$00-#$3F this would NOT branch to skipscroll. From #$40-#$BF it would. Then from #$C0-#$FF it would not.
You may want to use the carry flag if you're trying to check if oX-192 was less than 0.
Do you mean to say that I could change the
bmi to
branch on
carry
set or
clear?
If not then I don't understand what you mean by, "You may want to use the carry flag if you're trying to check if oX-192 was less than 0."
I'm sorry Kasumi.
Code:
;ldx oX work on this now
0C365 ;if oX > 192 don't branch
0C365 A5 04 lda oX
0C367 38 sec
0C368 E9 C0 sbc #192
0C36A 30 13 bmi +skipscroll
For unsigned subtracting, the carry is clear when the result (in actual arithmetic, forgetting all the byte stuff) would have been negative. Otherwise, it's set.
The minus flag will check if the result byte (with wrap around) is signed negative. It's different in some cases.
Forgetting hex for a moment:
0 - 192 = -192.
The carry would be clear after this subtraction because the result in real world math < 0.
But a signed byte can only store negative numbers from -1 through -128. Since the above subtraction is < -128, we wrapped back to positive numbers. This is why the minus bit isn't useful for this particular check. 0-192= -192. But -192 = $40 which doesn't have the high bit set.
(Before this edit, I made a definitive statement that you wanted to use bcc)
Edit: Err... wait. I take that back. Give me a second to puzzle over which to use.
And, I think I had it right. Should be bcc. See below for the reason for my confusion.
I always get confused when if statements are mixed with branches. (Do we
not branch and run the code below
if the statement is true, or do we
branch if the statement is true?)
I avoid this confusion now by ending the if statements with the expected action. (If variable - 8 > 64, branch) or even (If variable - 8 <= 64, don't branch). It's a style choice you don't have to adopt, but it helps me a lot with reading old/unfamiliar code.
Now with it stated I may have it confused, I think your if statement comment may be incorrect. You may want it to say >=.
oX = 193
193 - 192 = 1.
Positive number. No branch.
193>192
No problem.
oX = 192
192-192 = 0.
Positive number. No branch.
192>192?
Nope.
The statement doesn't match the behavior, so whichever is "correct" should be updated to match the other.
I gotta learn to write shorter posts.
^Thank you so much Kasumi!!
Especially, thanks for letting me learn the logic below! Alot of your long letters have been lavishly learnable and likeable for me.
A
littler length may not only lose its luster, but it could also leave me lost, low, and lazily learning less.
Inside Nesst... there is an x variable that goes from 0 to 31 and thenthere's a y variable that increments to 1 as I move down to where x == 32. Is it possible to use a y variable... but how would you use it? Make it be a variable that increments on each #$20??
Kasumi wrote:
Those are some problems I see. They actually probably won't fix the problem you describe, though. Please post anything that updates CameraX and CameraX+1 that outside of the vblank routine. (If there IS any code that updates them outside of the vblank.)
Code:
0C048 ;matthew's init RAM
0C048 A9 8B lda #$8b
0C04A 85 04 sta $0004 ;oX
0C04C 85 05 sta $0005 ;oY
0C04E A9 00 lda #$00
0C050 85 06 sta $0006 ;scroll
0C052 85 1B sta $001b ;CameraX (object)
0C054 85 1C sta $001c ;CameraX+1
0C056
0C056
0C056 A9 00 lda #$00
0C058 85 1A sta currNameTable
0C05A
0C05A
0C05A 58 cli
0C05B ;----------------------------END OF RESET----------------------------------
There is the only time CameraX is updated (well initialized would be better) outside of vblank.
It is still messed up... with the screen switching back and forth from nametable 1 and nametable 2... they switch so fast I've been trying to find where in the code that it is doing this; after much efforts I have still not discovered it... and I've watched it through the repeating of the MainLoop... the screen never changed!
Provided you've made all the changes I've talked about, and provided I haven't told you to make a change that was messed up (which is certainly possible), I've really found about all I can while just reading the code and I cannot debug for you any other way. Whenever I end up with a problem that I absolutely cannot solve, I just rewrite it.
There are very few variables involved here.
There are only two registers that should be affecting how to scroll the nametables when your screen is being rendered. $2005, and $2000. (Well, others can affect it, but not if you write to these last.) Break on writes to them, and find out what variables are stored there. Then find the code that updates those variables and find out why it's wrong. The fact that CameraX+1 is basically never touched makes it seem like you should be checking CameraX to see if it's doing something like switching between 0 and 255. (The only way I think that the nametables could be switching that quickly without CameraX+1's help).
Edit: You know what? Try commenting out jsr draw_sprite and/or jsr FamiToneUpdate. If doing that works, you've run out of vblank time. It probably isn't FamiTone, but last time I check draw_sprite was before scroll_screen. It's tough for me to tell how much time draw_sprite would take, but if commenting it out helps it should be moved directly before or directly after FamiToneUpdate.
But at this point, if that edit isn't true, I'd honestly just rewrite it. Remove 100% of the code that affects scroll variables and scroll registers. Then rewrite them slowly, and make sure independent parts work before putting them together. I may be wrong here, but I think you're trying to write too large pieces of code at once without verifying each small part. There's no reason not to verify even tiny things. Even for stuff that doesn't have any graphical output, you can just look at RAM values in a memory viewer.
d-pad->moves character->affects scroll variables->affects scroll registers
is harder to debug than
d-pad->affects scroll variables->affects scroll registers
which is harder to debug than
d-pad->affects scroll variables
which is harder to debug than
d-pad (And I assume you already have working code to check for button presses!)
I did something like this before I made my scrolling dependent on my character.
(I was already sure my d-pad checking subroutines worked well)
Code:
;variables scrollxlow, scrollxhigh
jsr p1left;If left is not being pressed
bne skipscrollleft;branch
lda scrollxlow
sec
sbc #$01
sta scrollxlow
lda scrollxhigh
sbc #$00
sta scrollxhigh
skipscrollleft:
jsr p1right;if right is not being pressed
bne skipscrollright;branch
lda scrollxlow
clc
adc #$01
sta scrollxlow
lda scrollxhigh
adc #$00
sta scrollxhigh
skipscrollright:
You can actually look at those variables being updated in a memory viewer when you press the d-pad. If for some reason it's wrong (which it may be!), you'll only have to look at <30 lines of code.
When it's right, you can use those variables to actually update the scroll registers.
At the end of the NMI...
Code:
lda ppu2000;a variable that contains the same value in $2000
and #%11111100
sta ppu2000
lda scrollxhigh
and #%00000001
ora ppu2000
sta ppu2000
sta $2000
lda scrollxlow
sta $2005
lda #$00
sta $2005
If something goes wrong, I'll only have to look at <20 lines of code.
Once I'm sure that works, I can make it so the scroll variables can't scroll past a max value or min value I set. When that works, I can make it so the scroll values are only added to when the character is past 192. When I'm sure that works, I can make it so a heavy enemy landing can shake the screen a little. If at any point something goes wrong, you'll know the newest thing is what's going wrong. (Well, sometimes something new creates a situation something old handles wrong that was never tested. But then... that usually means something old wasn't as well tested as it could have been before something new was added
)
Doing this also really helps us, since we don't have to follow as much of the thread to help.
Short version: I've done about as much as I can with the info I've been given, and don't know what other info I could ask for that would help. I would rewrite it and check each
very small part before moving on. I'm almost certain it will be faster than debugging what you've got, since I am rather stumped.
Kasumi wrote:
Provided you've made all the changes I've talked about, and provided I haven't told you to make a change that was messed up (which is certainly possible), I've really found about all I can while just reading the code and I cannot debug for you any other way. Whenever I end up with a problem that I absolutely cannot solve, I just rewrite it.
There are very few variables involved here.
There are only two registers that should be affecting how to scroll the nametables when your screen is being rendered. $2005, and $2000. (Well, others can affect it, but not if you write to these last.) Break on writes to them, and find out what variables are stored there. Then find the code that updates those variables and find out why it's wrong. The fact that CameraX+1 is basically never touched makes it seem like you should be checking CameraX to see if it's doing something like switching between 0 and 255. (The only way I think that the nametables could be switching that quickly without CameraX+1's help).
So... I haven't rewritten my scrolling code because there is some other flashing of the nametables that happens if I press the A button 19 times in a row. And so I spent some time a few days trying to find a solution so that the A button could be pressed an infinite number of times in a row and the flashing would never happen. I kind of succeeded with this code
Code:
0C35C draw_sprite:
0C35C
0C35C 85 FF sta $ff
0C35E E0 13 cpx #19 ;this will prevent the screen from flashing
0C360 10 0F bpl +
0C362
0C362
0C362
0C362 ; t0-t2 are temporary zero page locations holding
0C362 ; the address of a particular sprite layout and the
0C362 ; number of sprites left to draw
0C362 BD D9 D8 lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C365 85 07 sta t0
0C367 BD ED D8 lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C36A 85 08 sta t0+1
0C36C BD 01 D9 lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C36F 85 09 sta t2
0C371 A0 00 + ldy #0
0C373 ; oamIndex is a variable tracking how far you've written
0C373 ; into shadow OAM (customarily at $0200-$02FF)
0C373 A6 0B ldx oamIndex
0C375 @loop:
0C375 ; If you have the address of an array in a zero page pointer,
0C375 ; you use the (d),y addressing mode and increase Y to go
0C375 ; to the next byte.
0C375 B1 07 lda (t0), y ;<-- INDIRECT INDEXED ADDRESSING
0C377 C8 iny
0C378 ;ect. start
0C378 18 clc
0C379 65 05 adc oY
0C37B 9D 00 02 sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
Up there at the top I added to the code two lines
1)
cpx #192)
bpl +Here's the place where the x is assigned right before jsring to... draw_sprite (^above).
Code:
0C347
0C347 A6 0F ldx aFrame
0C349 20 5C C3 jsr draw_sprite
0C34C
I was concerned that the X value could be more than #19... then the flashing would start I thought... but after having the
red and
orange line in place the flashing starts on the #19th press of the A button... and that flashing continues until B is pressed (the B button resets aFrame back down to 0).
edit: and by 'flashing' I mean the top of nametable 1 starts flashing on and off the lower part of the screen. And ok... I am going to rewrite scrollland. Maybe start tomorrow. good night yall.
Many thanks to Kasumi!!!!
I'd suggest going back and finding where the A 19 times bug as, as there shouldn't be any reason that happens, especially in such an odd circumstance. Patching code like that with fixes you don't know why they work instead of fixing it will make bad code quickly that's harder to manage.
Something I just thought of, (and I feel bad especially after harping so much to use temp RAM) is that you probably should not be using temp RAM in draw_sprite because that's run during your NMI.
If something that's not in your NMI uses those variables, and then the NMI interrupts and changes them before that something is done, bad things can happen.
But, something big I haven't seen must be up for the A button press thing. I can't even think of a theory for that one.
Edit: But I can say this. You've essentially got a check to make sure aFrame isn't an unsafe value. I can think of two things that aren't too great about how you're doing it.
One: You never fix aFrame if it's out of bounds!
Two: It's probably better to check/fix aFrame near whatever code actually updates its value.
I don't think doing so would fix your problem, but I do think it makes more sense.
3gengames wrote:
Patching code like that with fixes you don't know why they work instead of fixing it will make bad code quickly that's harder to manage.
I've thought about this very much; thank you for telling me!
Kasumi wrote:
Something I just thought of, (and I feel bad especially after harping so much to use temp RAM) is that you probably should not be using temp RAM in draw_sprite because that's run during your NMI.
If something that's not in your NMI uses those variables, and then the NMI interrupts and changes them before that something is done, bad things can happen.
I was floored when I read this... so happy because maybe if I could fix this it could start working again. Then I got distracted by our food... it was good. Then the olympic opening ceremony was great when the queen arrived in the stadium in a Bond way... and when Mr. Bean showed up! He kept pressing that key on the keyboard!
Hahahahahaha.
And then I lost more time from the DragonBallGT movie. And then was distracted even more because Paul McCartney preformed Hey Jude toward the end of the ceremonies. It wasn't a night for progress on the videogame. But this early saturday morning is helpful!
I've failed at finding a non NMI use of thoese temporary variables... so I renamed them VBt0 and VBt2 so it is very clear that they are used only in the NMI... vblank.
Kasumi wrote:
But, something big I haven't seen must be up for the A button press thing. I can't even think of a theory for that one.
Edit: But I can say this. You've essentially got a check to make sure aFrame isn't an unsafe value. I can think of two things that aren't too great about how you're doing it.
One: You never fix aFrame if it's out of bounds!
Two: It's probably better to check/fix aFrame near whatever code actually updates its value.
I don't think doing so would fix your problem, but I do think it makes more sense.
Thank you for this great advice Kasumi!
Now... I think I'll have to rewrite my scrolling code... when you rewrite code do you have to be prevented from thinking about the code you already wrote? I'm confused
...and tired... to sleep. Good morning to yall.
Guh have to be up in 15 minutes... we'll be off to New Orleans.
unregistered wrote:
I was floored when I read this... so happy because maybe if I could fix this it could start working again.
Well, it'll do exactly nothing if the RAM isn't used elsewhere.
Quote:
Now... I think I'll have to rewrite my scrolling code... when you rewrite code do you have to be prevented from thinking about the code you already wrote?
No. You can keep in mind what you think worked so the process goes faster. But as you reimplement each small part, verify it works exactly as expected before moving on.
When it doesn't, you'll generally have much fewer things it could be, because everything you wrote before should work. It was already checked.
Kasumi wrote:
Quote:
Now... I think I'll have to rewrite my scrolling code... when you rewrite code do you have to be prevented from thinking about the code you already wrote?
No. You can keep in mind what you think worked so the process goes faster. But as you reimplement each small part, verify it works exactly as expected before moving on.
Kasumi, thank you so much for answering my question!
Kasumi wrote:
When it doesn't, you'll generally have much fewer things it could be, because everything you wrote before should work. It was already checked.
Ok... my previous scrolling code is no longer there. But, the blinking that happens after 19 presses of the A button still occurs...
I hope this will work out good...
After much trying I was blessed to find the solution!!!!!!
Kasumi wrote:
Edit: ... You've essentially got a check to make sure aFrame isn't an unsafe value. I can think of two things that aren't too great about how you're doing it.
One: You never fix aFrame if it's out of bounds!
Two: It's probably better to check/fix aFrame near whatever code actually updates its value.
Wow, thank you Kasumi so very much for these brilliant bits of knowledge!
I tried to follow these steps....... they were the path!
Now I can press the A button an unlimited amount of times and it always ends well! NO BLINKING anymore!!
Kasumi, on the previous page, wrote:
For unsigned subtracting, the carry is clear when the result (in actual arithmetic, forgetting all the byte stuff) would have been negative. Otherwise, it's set.
I used a bcs and it
works!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
On the previous page, unregistered wrote:
Kasumi wrote:
On another note: Provided I'm understanding it correctly, draw_sprite seems to be updating at least part of $0200-02FF. But you run update_sprite (which actually copies the sprite's data from $0200-$02FF) before it. This means whatever draw_sprite is writing will only appear during the NEXT vblank.
Edit: Hmm... I guess this DOES keep updates that don't need to be done before rendering after things that do which is good. But, I'd recommend moving it out of vblank entirely if that's possible.
Hmm...
So
draw_sprite should be moved out of vblank enitrely... if that's possible? But,
draw_sprite writes to $0200-02FF... and that shouldn't be done outside of vblank, right?
unregistered wrote:
But,
draw_sprite writes to $0200-02FF... and that shouldn't be done outside of vblank, right?
$0200-$02FF is CPU memory and has absolutely nothing to do with the PPU, so it can be accessed at all times without problems. It's the sprite DMA (which copies the contents of $0200-$02FF to the OAM) that must take place during VBlank.
tokumaru wrote:
unregistered wrote:
But,
draw_sprite writes to $0200-02FF... and that shouldn't be done outside of vblank, right?
$0200-$02FF is CPU memory and has absolutely nothing to do with the PPU, so it can be accessed at all times without problems. It's the sprite DMA (which copies the contents of $0200-$02FF to the OAM) that must take place during VBlank.
YES!! THANKS SO MUCH TOKUMARU!
What are these .deb files? There is one for each new
.nes file.... why?
They're just debug files made when you use FCEUX's debugger.
Every time you use the debugger with a game an annoying .deb file is created. I find that really annoying when I'm developing, so I added a command to delete .deb files in the batch file that assembles the project.
How else would you recommend saving breakpoints from one run to the next?
Maybe putting it in it's own file directory and not in your projects directory, like it does with snapshots and such.
3gengames wrote:
Maybe putting it in it's own file directory and not in your projects directory, like it does with snapshots and such.
I find it really strange that anyone would complain about files being created by tools that they're using.
Having the debug file in the same location as the NES file makes it unambiguous as to the intent of the debug file and the NES file that it's connected to. If it's in another folder somewhere then some *other* file would have to be created to keep track of the fact that that debug file is associated with the NES file somewhere else. Maybe the tool would put that association in the registry but then a different group of people would complain about their registry being "polluted" with a key representing the NES/debug file association for each NES file they ever opened.
By the way, your web browser is creating hundreds of files just so you can read this message...
When it puts unneeded garbage in your project folder, you'd be a sheep not to dislike it. And plus the folders option works for EVERY other feature in FCEUX, why not the debug files? Makes zero sense. If the file was useful though, like NESASM3's list of all the pointers and stuff, I'd be okay with it. A debug file that you don't need after you recompile the project/close the ROM should have an option at minimum.
And by the way, it isn't putting it in my document or any other files directory, so it's not helping your case to bring up my web browser.
3gengames wrote:
When it puts unneeded garbage in your project folder, you'd be a sheep not to dislike it.
You really like those absolute statements. I don't care even a little bit, and I'm obviously not a "sheep" because (like you) I use nesasm despite more recommended alternatives. I actually prefer FCEUX's behavior. Nintendulator hides away potentially huge files when you use its debug logging. Then I have to actually navigate to its directory to delete them just to free the massive amount of hard drive space they can take up which is much more annoying for me, personally. But no one is "a sheep" or wrong because they prefer that behavior. I can see why they would. At least FCEUXs default allows them to be easily destroyed with a batch like Tokumaru and Shiru do. Customization
would be better, but eh. Whatever.
cpow wrote:
3gengames wrote:
Maybe putting it in it's own file directory and not in your projects directory, like it does with snapshots and such.
I find it really strange that anyone would complain about files being created by tools that they're using.
Well, my folder just had files I could use... and then the
.deb files were created
and now they are mixed with my
.nes files. The one I opened in Notepad was blank... it contained a lot of spaces. Files with tons of spaces aren't very useable right now for me... and so I too would like them to be created in another folder named "deb" cause I'm sure they are helpful. : ) Thank you 3gengames for your quick reply.
tokumaru wrote:
Every time you use the debugger with a game an annoying .deb file is created. I find that really annoying when I'm developing, so I added a command to delete .deb files in the batch file that assembles the project.
Shiru wrote:
The same here.
Ha ha. That's brilliant!
unregistered wrote:
Well, my folder just had files I could use... and then the
.deb files were created
and now they are mixed with my
.nes files. The one I opened in Notepad was blank... it contained a lot of spaces. Files with tons of spaces aren't very useable right now for me... and so I too would like them to be created in another folder named "deb" cause I'm sure they are helpful. : ) Thank you 3gengames for your quick reply.
Why do you need to be able to "use" the .deb file? Do you click on random links in emails?
tepples wrote:
How else would you recommend saving breakpoints from one run to the next?
I hardly need to save breakpoints, that's the thing. While developing, code and variables shift around a lot.
Anyway, it would be nice if the emulator just offered an option to not save debugging sessions at all.
I'm used to .deb files being software installation packages and double-clicking them to open them in the package installer. But then I use Ubuntu.
Hi. Do you know if Nintendo provided any materials to instruct NES programmers? I would like a Nintendo book, if there is such a thing.
cpow wrote:
unregistered wrote:
Well, my folder just had files I could use... and then the
.deb files were created
and now they are mixed with my
.nes files. The one I opened in Notepad was blank... it contained a lot of spaces. Files with tons of spaces aren't very useable right now for me... and so I too would like them to be created in another folder named "deb" cause I'm sure they are helpful. : ) Thank you 3gengames for your quick reply.
Why do you need to be able to "use" the .deb file?
Because they take up room in my folder in between each
.nes file. They're just unuseable bits and I wish they weren't there.
unregistered wrote:
Do yall know if Nintendo provided any materials to instruct NES programmers?
They sure did, but we couldn't get our hands on anything official. According to what we heard from developers, the documentation wasn't very good anyway. Some of it was even in japanese and the programmers were on their own in the task of making sense of it.
Quote:
I would like a book.
I'm pretty sure that the reverse engineered information we have today is better than what Nintendo made available to developers back in the day.
tokumaru wrote:
unregistered wrote:
Do yall know if Nintendo provided any materials to instruct NES programmers?
They sure did, but we couldn't get our hands on anything official. According to what we heard from developers, the documentation wasn't very good anyway. Some of it was even in japanese and the programmers were on their own in the task of making sense of it.
Quote:
I would like a book.
I'm pretty sure that the reverse engineered information we have today is better than what Nintendo made available to developers back in the day.
Thanks tokumaru.
It would be incredibly amazing to beable to read Nintendo's instructions... they made Super Mario Bros. and it was incredible! For me it would me good to read what Nintendo thought we needed to know... just cause Nintendo wrote the instructions. They are Nintendo after all.
What Nintendo thinks we need to know is that hobbyists should stick to Windows, Mac OS X, GNU/Linux, iOS, and Android, and prove their skills on one or more of those platforms and hire an experienced business manager before developing for one of Nintendo's platforms.
There is no official NES programming manual available, but you can easily find the SNES one, if you are just interested what an official doc looks like. There are official docs for some other platforms around as well (Atari 2600, Genesis, Neo Geo, maybe more).
tokumaru wrote:
I'm pretty sure that the reverse engineered information we have today is better than what Nintendo made available to developers back in the day.
Wonder if that's one reason why Rare's games were generally so good, since they basically did the same thing. Now I'd like to see
their old docs.
tepples wrote:
What Nintendo thinks we need to know is that hobbyists should stick to Windows, Mac OS X, GNU/Linux, iOS, and Android, and prove their skills on one or more of those platforms and hire an experienced business manager before developing for one of Nintendo's platforms.
I think that's a wise decision on Nintendo's part! This is very tough to do but also incredibly fun at the same time.
Shiru wrote:
There is no official NES programming manual available, but you can easily find the SNES one, if you are just interested what an official doc looks like. There are official docs for some other platforms around as well (Atari 2600, Genesis, Neo Geo, maybe more).
Shiru, thank you for mentioning this!
Is the SNES a truly super version of the NES... an improved 2a03? Just wondering... if it is then I was thinking about buying it. Nintendo's thoughts about a superior NES would be good to read... I think.
To find if (a < 13) a branch you could try, after the compare, might be
Code:
bcc solution
... if (a >= 13) ...
Code:
bcs solution
Are these correct? I spent a while searching this thread for what I thought tokumaru had posted in a reply to me... my searching was unsuccessful
edit:
Now, to understand this part you must understand how subtraction works on the 6502. You know that we always set the carry flag before a subtraction (and that the CMP instruction assumes that the carry is set). I like to think of that carry bit as a bit you place there to be borrowed during the operation in case the second number is larger than the first. Then, to know if a number is larger than the other, you check the state of the carry flag afterwards. If it's clear, the 1 was borrowed, meaning that the second number was larger than the first. If it's set, the first number is larger or they are equal.
Ahhh yes... this is good! Thanks tokumaru!
Yes, this is correct. I like to think about it this way: the carry flag, in subtractions, is actually a value that can be borrowed if necessary. After the subtraction (CMP acts just like SEC + SBC, except it doesn't change A), if the carry is still set, a borrow wasn't necessary, so the value in A was large enough for you to subtract from it as much as you did (it's >= the value you subtracted). If the carry is clear, a borrow was necessary and the value in A wasn't large enough (it's < than the subtracted value).
tokumaru wrote:
Yes, this is correct. I like to think about it this way: the carry flag, in subtractions, is actually a value that can be borrowed if necessary. After the subtraction (CMP acts just like SEC + SBC, except it doesn't change A), if the carry is still set, a borrow wasn't necessary, so the value in A was large enough for you to subtract from it as much as you did (it's >= the value you subtracted). If the carry is clear, a borrow was necessary and the value in A wasn't large enough (it's < than the subtracted value).
Ah yes, I was correct in my guess!! Thank
you so much tokumaru!
edited text
I have a loop in my code.
If I have to run one line of code only on the first time through my loop
Code:
lda t6+1
and then at all other times I'll need to run this line instead:
Code:
lda t6
How would be a good way to set that up... something simple maybe?
And these lines of code will be in the middle of my large loop. I'm trying to use one of those temp variables Kasumi recommended me.
It sorta depends on how long the loop is/what registers you need.
I'd just do the obvious and double the loop code if it's shortish. (Yeah, I realize you said it was long)
Code:
lda t6+1
;loop stuff
looplabel:
lda t6
;loop stuff
If it's long, I might just store whatever is in t6 or t6+1 into a new temp variable or a register and then just always use the new temp variable or register.
Code:
ldy t6+1
looplabel:
tya
;loopstuff
ldy t6
;conditional on whether you loop to looplabel again
Or... alternatively, depending on your conditional not requiring the use of A:
Code:
lda t6+1
looplabel:
;loopstuff
lda t6
;conditional on whether you loop to looplabel again
Edit:
Or this:
Code:
lda t6+1
jmp skiploadt6
looplabel:
lda t6
skiploadt6:
;loop stuff
;conditional on whether you loop to looplabel
That one is probably best, because it doesn't add setup time to each loop after the first like the others. It just came to mind third.
Or I might do something totally different depending on the setup of the loop. But there's some ideas.
Kasumi wrote:
Edit:
Or this:
Code:
lda t6+1
jmp skiploadt6
looplabel:
lda t6
skiploadt6:
;loop stuff
;conditional on whether you loop to looplabel
That one is probably best, because it doesn't add setup time to each loop after the first like the others. It just came to mind third.
This one rocks!! (I just figured it out!
) Thank you Kasumi!!!
They are all good though, thanks very much!
...NESDEV's improvements wrote:
AND GOODNESS!! THIS FORUM JUST KEEPS GETTING BETTER AND BETTER, THANKS YALL!
Edit: It's kindof slow right now...but maybe that's because there's been lot of my time coding these days.
unregistered un - wrote:
Kasumi wrote:
Edit:
Or this:
Code:
lda t6+1
jmp skiploadt6
looplabel:
lda t6
skiploadt6:
;loop stuff
;conditional on whether you loop to looplabel
That one is probably best, because it doesn't add setup time to each loop after the first like the others. It just came to mind third.
This one rocks!! (I just figured it out!
)
Thank you Kasumi!!! They are all good though, thanks very much!
Kasumi wrote:
If it's long, I might just store whatever is in t6 or t6+1 into a new temp variable or a register and then just always use the new temp variable or register.
Ahhh, I see now... after rereading and understanding your
dark orange text up there... now, now...
thank you so much Kasumi!!! Brilliant! I'm a step closer now! I'm in for some more gettin' dirty coding, I think.
Code:
lda t6 ;pla ;<--------------------------------------
;on every 8th run do this instead:
lda t6+1
Is there a simple way to do this? I'm trying to make a simple solution.
unregistered wrote:
Code:
lda t6 ;pla ;<--------------------------------------
;on every 8th run do this instead:
lda t6+1
Is there a simple way to do this? I'm trying to make a simple solution.
Have a counter that counts upwards each run. Calculate counter AND 7, if result is zero, lda t6+1, otherwise lda t6. If counter starts at zero this will load t6+1 on the first run too. If you don't want that you can initialize the counter to a different value.
This should be close:
Code:
ldx t6
lda runCount
and #$07
bne :+
ldx t6+1
:
txa
Another one
Code:
lda #$80
sta counter
LOOP
lda t6
lsr counter
bne SKIP
ror counter
lda t6+1
SKIP
.
.
I would point out that in Kasumi's code
Code:
lda t6+1
jmp skiploadt6
looplabel:
lda t6
skiploadt6:
;loop stuff
;conditional on whether you loop to looplabel
There's the possibility of using a branch instead of
a jmp if that seems desirable eg if you know
t6+1 will never be 0 you could bne
saves a byte, might cost you a cycle at a page boundary,
more relocatable
unregistered wrote:
Code:
lda t6 ;pla ;<--------------------------------------
;on every 8th run do this instead:
lda t6+1
Is there a simple way to do this? I'm trying to make a simple solution.
bogax wrote:
There's the possibility of using a branch instead of
a jmp if that seems desirable eg if you know
t6+1 will never be 0 you could bne
saves a byte, might cost you a cycle at a page boundary,
more relocatable
Using branch also loses some maintainability (in case the code above changes so that the zero flags value isn't a constant anymore). I tend to avoid this optimization. It sure would be nice to be able to do runtime assertions... (I'm considering implementing something like that in NintendulatorDX).
Emulator-side runtime assertions are
implemented in FCEUX.
tepples wrote:
Emulator-side runtime assertions are
implemented in FCEUX.
Not what I'm looking for. I don't want to specify the conditions manually and have to update them every time the addresses change. What I'm thinking about doing is a macro that outputs the assert expressions in a separate file (easily done with CC65). Something like this:
Code:
runTimeAssert "A == 0 && C"
runTimeAssert "current_bank == 0x12"
Et cetera.
You could implement an assert macro that reads or writes to a specific address, executes a bad opcode, or something else that is not dependent on the program counter. This would let you keep the same "assert" breakpoint from build to build.
Alternatively, you could try to auto-generate the .dbg file FCEUX uses based on compile symbols. This would be better, since you could then have asserts without any extra code in the ROM.
Code:
0002D attNextRowStart .dsb 1 ;thank you !!!! :D
0002E every8thRun .dsb 1 ;will stay at 0 until the 8th attribute table block, the start of a new row, is to b
0002F atttablerownums: .db $00, $20, $40,$60,$80,$a0,$c0,$e0
00037 tA .dsb 2
There's the ^ code that shows the addresses.
Code:
00:C26D:E6 37 INC $0037 = #$05
00:C26F:A6 37 LDX $0037 = #$05
00:C271:B5 2F LDA $2F,X @ $003D = #$00
00:C273:85 2D STA $002D = #$00
WHY IS $002D AT #$00????????? edit: Thank yall for helping with my last question. I'm tired, it is time to get away from this right now for me. Will elaborate my thanks soon, I promise. : )
That might be a function of the debugger you're using; FCEUX's debugger shows the current value of that memory location, so it will show the old value until you single-step past it.
Or I might be completely misunderstanding; if so, sorry.
unregistered wrote:
WHY IS $002D AT #$00????????? What were you expecting exactly? At address $002D you used the
.dsb statement, which reserves the specified amount of bytes, meaning you reserved one byte at that location. If your program doesn't store anything there, that memory position will remain undefined (RAM contains unpredictable values on power up). Emulators usually initialize the RAM to $00, $FF, or a mix of both, but on actual hardware the contents are unpredictable.
Now, it seems you are trying to initialize variables in RAM with .db statements. That doesn't work. Assemblers generate ROM code, and ROM files don't contain any RAM information, so you can't possibly ask for the assembler to initialize RAM values. This code:
Code:
atttablerownums: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
...will reserve 8 bytes from $002F to $0036, but it will not initialize the values to the ones you specified. To initialize the memory to those values you'll have to manually LDA + STA the values there, the assembler just can't do it.
You'd need something like this to initialize that table like you want:
Code:
;copy table from ROM to RAM
ldx #7
- lda table, x
sta atttablerownums, x
dex
bpl -
;table somewhere in ROM
table: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
And in RAM you'll just have:
Code:
;reserve 8 bytes for the table
atttablerownums .dsb 8
Thank you thefox, tepples, and bogax for helping
me all of us!
rainwarrior wrote:
You could implement an assert macro that reads or writes to a specific address, executes a bad opcode, or something else that is not dependent on the program counter. This would let you keep the same "assert" breakpoint from build to build.
Alternatively, you could try to auto-generate the .dbg file FCEUX uses based on compile symbols. This would be better, since you could then have asserts without any extra code in the ROM.
An assert macro? I think I don't understand this. It's ok though. Thanks rainwarrior.
tokumaru wrote:
unregistered wrote:
WHY IS $002D AT #$00????????? What were you expecting exactly?
THANK YOU SO MUCH tokumaru!!
I was expecting exactly what I thought wrong about .db statements... they seem to work correctly in another file... but I guess that file is included into RAM... I think i remember that...
tokumaru wrote:
At address $002D you used the
.dsb statement, which reserves the specified amount of bytes, meaning you reserved one byte at that location. If your program doesn't store anything there, that memory position will remain undefined (RAM contains unpredictable values on power up). Emulators usually initialize the RAM to $00, $FF, or a mix of both, but on actual hardware the contents are unpredictable.
Now, it seems you are trying to initialize variables in RAM with .db statements. That doesn't work. Assemblers generate ROM code, and ROM files don't contain any RAM information, so you can't possibly ask for the assembler to initialize RAM values. This code:
Code:
atttablerownums: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
...will reserve 8 bytes from $002F to $0036, but it will not initialize the values to the ones you specified. To initialize the memory to those values you'll have to manually LDA + STA the values there, the assembler just can't do it.
You'd need something like this to initialize that table like you want:
Code:
;copy table from ROM to RAM
ldx #7
- lda table, x
sta atttablerownums, x
dex
bpl -
;table somewhere in ROM
table: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
And in RAM you'll just have:
Code:
;reserve 8 bytes for the table
atttablerownums .dsb 8
tokumaru, you are good at teaching!
: ) Well... I think so.
Thanks so much!
unregistered wrote:
I was expecting exactly what I thought wrong about .db statements... they seem to work correctly in another file... but I guess that file is included into RAM... I think i remember that...
Yeah, some platforms do copy whole programs to RAM (mostly home computers which had significant amounts of RAM in order to load programs from disks or tapes), and in those cases
.db does work for initializing variables... On the NES however, that's usually not the case, since programs run mostly from ROM, so be careful with that.
Quote:
tokumaru, you are good at teaching!
: ) Well... I think so.
Thanks so much!
Heh, I normally suck at teaching stuff, so I'm glad you can understand my explanations.
lidnariq wrote:
That might be a function of the debugger you're using; FCEUX's debugger shows the current value of that memory location, so it will show the old value until you single-step past it.
Or I might be completely misunderstanding; if so, sorry.
Thanks for your help lidnariq.
FCEUX's debugger is so great!!
I think I've learned what
step-over and
step-out do too!
Step-over allows you to run the code that the method jsrs to but skip it entirely; it skips to the next line!
Step-out let's you step out of a method and it also skips to the same next line that step-over would have skipped to... that's there for people like me who accidentally press step-into too many times.
Now debugging is even
easier faster!
Please correct me on anything I've not described correctly... it's just what I've noticed while debugging.
edit
tokumaru wrote:
Quote:
tokumaru, you are good at teaching!
: ) Well... I think so.
Thanks so much!
Heh, I normally suck at teaching stuff, so I'm glad you can understand my explanations.
Your explanations have always worked out excellent, for me at least, because you can answer my questions!
tokumaru wrote:
Now, it seems you are trying to initialize variables in RAM with .db statements. That doesn't work. Assemblers generate ROM code, and ROM files don't contain any RAM information, so you can't possibly ask for the assembler to initialize RAM values. This code:
Code:
atttablerownums: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
...will reserve 8 bytes from $002F to $0036, but it will not initialize the values to the ones you specified. To initialize the memory to those values you'll have to manually LDA + STA the values there, the assembler just can't do it.
You'd need something like this to initialize that table like you want:
Code:
;copy table from ROM to RAM
ldx #7
- lda table, x
sta atttablerownums, x
dex
bpl -
;table somewhere in ROM
table: .db $00, $20, $40, $60, $80, $a0, $c0, $e0
And in RAM you'll just have:
Code:
;reserve 8 bytes for the table
atttablerownums .dsb 8
Where can I find ROM? I'm extreemly confused.
Why does your
table: .db $00, $20, $40, $60, $80, $a0, $c0, $e0seem so unfair... see I can understand why.... !!!!! OK!
So would this work?
Code:
lda #$00
sta atttablerownums+0
lda #$20
sta atttablerownums+1
lda #$40
sta atttablerownums+2
lda #$60
sta atttablerownums+3
lda #$80
sta atttablerownums+4
lda #$a0
sta atttablerownums+5
lda #$c0
sta atttablerownums+6
lda #$e0
sta atttablerownums+7
If that would work then I think I'll be able to understand your code better.
Yeah, basically. It's just a loop that takes the data from the pointer in your program ROM, and puts those bytes after in to the RAM space that you need. RAM needs to have all the data needed to be there put in to it from program and data tables like that, remember that.
Yes, that would work fine as well. I just used the loop as an example because with larger tables a long chain of LDA + STA would become impractical.
YES!!!!!!!!!!!!!!!!!!!!The attribute tables are looking correct now!
3gengames wrote:
Yeah, basically. It's just a loop that takes the data from the pointer in your program ROM, and puts those bytes after in to the RAM space that you need. RAM needs to have all the data needed to be there put in to it from program and data tables like that, remember that.
Ok... thank you 3gengames!
tokumaru wrote:
Yes, that would work fine as well. I just used the loop as an example because with larger tables a long chain of LDA + STA would become impractical.
Yes, I agree. Thank you tokumaru!
Ok, here is some code I don't know the best way to make it work
Code:
;macro increasesxby2andmaybeby32too h********************************************
; destroys x... kindof... X is destroyed and then saved at the very end after it gets it's new incremented value.
; increments x by 2...
; else, increments x by 32 if it becomes a multiple of 32...
;
;***********************************************************
macro increasesxby2or32 h
sta $ff
pha ;-------------------->
ldx h
inx ;always increment by 2
inx ;<--/
txa
;lda h
and #00011111b ;and then, if tileNum isn't a multiple of 32
bne + ;go and be done
clc
txa
adc #00011110b ;#30
+ sta h
tax
pla ;<--------------------
endm ;end of macro increasesxby2or32
This was rewritten today... moving the 2 inx-es to the top so that it would stop constantly incrementing by 32 everytime. It works correctly now until it reaches its first increment by 32... and then it continues to increment by 32 forever...
I know it needs some check that prevents it from incrementing 32 again. That seems like it could be a lot of code... thought maybe one of you could quickly find a grand solution... something that's easily added... but short and to the point.
I don't get what that's even doing. Either it needs to be incremented by 2 or 32. If it is by 2, just do INX INX. If not, do:
Code:
PHA
TXA
ASL A
ASL A
ASL A
ASL A
ASL A
TAX
PLA
That will multiply X by 32. If you need to test to see if a value is a multiple of 32 (in the A register), do:
Code:
AND #%00011111
And if the 0 condition code is set (BEQ if true) then it is a multiple of 32.
I'm sorry I can't help with THAT piece of code, but that's the best I can do at this hour to maybe give you better ideas to write it. I don't think that's a macro you should be using to often, it's big and seems weird and complex honestly.
ETA: Didn't realize you know how to check for the 32 division thing, good job on that. But honestly I don't see where you'd need a program like this...
When there's no need to increment 32, aren't you storing the wrong value into h? A is the altered value you used to check if X was a multiple of 32, why would you want to save that? If I'm reading it correctly, it would just be a matter of transformin + sta h into + stx h moving it to after tax.
Thank you for replying 3gengames! It's a macro because I want it to be a macro... it is only to be used once, you're right.
tokumaru wrote:
When there's no need to increment 32, aren't you storing the wrong value into h?
Umm.... well... yes I am.
tokumaru wrote:
A is the altered value you used to check if X was a multiple of 32, why would you want to save that?
No I guess me was confused.
Thank you for seeing that!
So glad I asked the question!
tokumaru wrote:
If I'm reading it correctly, it would just be a matter of transformin + sta h into + stx h moving it to after tax.
...... give me a min...
edit: I did this and it still keeps incrementing by 32 forever... once it becomes a multiple of 32. Here is my code:
Code:
;macro increasesxby2andmaybeby32too h********************************************
; destroys x... kindof. X is destroyed and then saved at the very end after it gets it's new incremented value.
; increments x by 2...
; else, increments x by 32 if it becomes a multiple of 32...
;
;***********************************************************
macro increasesxby2or32 h
sta $ff
pha ;-------------------->
ldx h
inx ;always increment by 2
inx ;<--/
txa
;lda h
and #00011111b ;and then, if tileNum isn't a multiple of 32
bne + ;go and be done
clc
txa
adc #00011110b ;#30
tax
+ stx h
pla ;<--------------------
endm ;end of macro increasesxby2or32
good night yall.
Good morning NesDev.
After rereading your also helpful post 3gengames I might try using that code... though I think my adding 30 takes less cycles than your x32.
If the code is a multiple of 32 I chose to add 30. Thats after always adding 2 in the front.edit: ok... kindof rewrote this...
Code:
dontIncrement32 = true;
if (!dontIncrement32 && it's a mtple of 32) {
clc
adc #32
dontIncrement32 = true;
}
elseif (dontIncrement32) {
inx
inx
dontIncrement32 = false;
}
Do you see any assembly problems or ways to improve this?
Kindof rewrote & corrected this pseudocode again...
(sorry about the harsh edits to my last post; I failed yall. Please trust me again.)Code:
haveAdded2 = false;
if (X is a multiple of 32) {
if (haveAdded2) {
txa
clc
adc #32
haveAdded2 = false;
}
else {
inx
inx
haveAdded2 = true;
}
}
else {
inx
inx
haveAdded2 = true
}
tax
Do you see possible improvments or corrections?
Now that I think of it, the code in
this post will indeed add 32 every time after the first time it reaches a multiple of 32. Say that the number is 30, after you add two it become 32, so you add another 30, making it 62, which is also 2 units away from a multiple of 32. If you add 32 to a number that is 2 units away from being a multiple of 32, it will
always be 2 units away from a multiple of 32.
So, I still don't understand where you're going with this. What behavior exactly do you expect when the number is 30? shouldn't it become 62? And why shouldn't the 62 become 94? Please show me a few steps of what should happen to that number after a couple of times of going through your macro.
tokumaru wrote:
Now that I think of it, the code in
this post will indeed add 32 every time after the first time it reaches a multiple of 32. Say that the number is 30, after you add two it become 32, so you add another 30, making it 62, which is also 2 units away from a multiple of 32. If you add 32 to a number that is 2 units away from being a multiple of 32, it will
always be 2 units away from a multiple of 32.
So, I still don't understand where you're going with this. What behavior exactly do you expect when the number is 30? shouldn't it become 62? And why shouldn't the 62 become 94? Please show me a few steps of what should happen to that number after a couple of times of going through your macro.
It should start at 0 and increment by 2 until it becomes a multiple of 32... then increment by 32... then increment by 2 again until it becomes a multiple of 32 again.
So it should go something like this: 00... 02... 04... 06... 08... 0A... 0C... 0E... 10... 12... 14... 16....18...1A...1C...1E...40... 42... 44... 46... 48... 4A... 4C... 4E... 50...52...54...56...58...5A...5C...5E...80...82...
Would you mind telling us what it's for? I don't get why this would be useful in any places honestly...and is this supposed to change a value it has in RAM on a per-run basis?
3gengames wrote:
Would you mind telling us what it's for? I don't get why this would be useful in any places honestly...
It's so...went to look at code... it's what I need to happen during writes to
screenArray.
screenArray is the memory that holds one screen's collision values... it's 1 byte per 8x8pixel square.
Actually, that sounds like something I fixed for the attribute tables... ...probably was... well this code was in my prg-collisionU.asm page... it's ok. It's gone now... well, I'll have to think this over. 3gengames wrote:
is this supposed to change a value it has in RAM on a per-run basis?
Yes.
editedit2: Well I'm sorry 3gengames and tokumaru... and everyone else who looked at this thread today. It my brain... it's getting so much better though... you'd have to understand the many hours I spent... ...it's ok.
unregistered wrote:
00... 02... 04... 06... 08... 0A... 0C... 0E... 10... 12... 14... 16....18...1A...1C...1E...40... 42... 44... 46... 48... 4A... 4C... 4E... 50...52...54...56...58...5A...5C...5E...80...82...
Then use the code in
this post but do
adc #32 instead of
adc #30. Then you'll get this sequence.
I don't want to be mean, but if you cared to write the desired sequence down you'd have realized that to jump from $1E to $40 you'd have to add 34 (2 INXs and then ADC #32), not 32.
tokumaru wrote:
unregistered wrote:
00... 02... 04... 06... 08... 0A... 0C... 0E... 10... 12... 14... 16....18...1A...1C...1E...40... 42... 44... 46... 48... 4A... 4C... 4E... 50...52...54...56...58...5A...5C...5E...80...82...
Then use the code in
this post but do
adc #32 instead of
adc #30. Then you'll get this sequence.
I don't want to be mean, but if you cared to write the desired sequence down you'd have realized that to jump from $1E to $40 you'd have to add 34 (2 INXs and then ADC #32), not 32.
That's an excellent
way of learning that I should write the desired sequence down in the future. Thanks very much tokumaru!
3gengames wrote:
Would you mind telling us what it's for? I don't get why this would be useful in any places honestly...
It's for running through all of our metatiles... I have this run during each time through the loop... incrementing x by either 2 or
32 34 each time. Remember we are useing 16x16 metatiles.
edit: and well tokumaru, I found the paper page where I figured this out so I could remember what I was thinking... and it says...from some pages ago I wrote:
x goes from 0 to 30
then skips 32
& goes from 64 to 94
...
It seems my math thought 30 + 32 = 64... though I think I remember keeping the 32 because it was a power of 2. Next time I'm going to write out what I expect... that was a great lesson, thanks.
I'm confused about my collision detection process.
I know that 1 page of RAM (256 bytes) is big enough to hold 1 screen of information (240 bytes) because 1 screen is 256 pixels wide and 240 scan lines tall. Each of my metatiles are 16 pixels high and 16 pixels wide. That 16 pixel square is big enough to hold four 8 pixel by 8 pixel tiles.
256 (screen width) / 16 (metatile width) = 16. And 240 (screen height) / 16 (metatile height) = 15. And so there are 16 x 15 = 240 metatiles. Since 1 screen of information is 240 bytes that allows 1 byte of collision data per metatile.
I don't understand... the part I'm having problems with is: How do I create a tile map with 240 bytes when each byte is supposed to represent the four 8x8 pixel tiles of my metatiles? Well, I am going to continue to try and find the posts where you all spent your time to teach this to me... my sister is helping me.
Usually, you'd make a lookup table from metatile numbers to the tile numbers for the top left, top right, bottom left, and bottom right tiles of the metatile. Then you'd either have a fifth table for the attribute value or do like SMB1 and use the upper 2 bits of the metatile number for the attribute.
And also a 6th table with collision information.
Do either of you have 16x16 metatiles? Is your collision table memory 240 bytes? How do you find out what value to store there? Does that value depend on what the four tiles are?
I do have 16x16 metatiles. But my map is 224 bytes long. I don't wirte collision table but metatiles ID themselfs to have more options.
I write the value into RAM while decompressing map from PRG.
Something like this:
-Decompres a map byte from ROM.
-Save the byte into Level table in RAM
-JSR to function that draws metatile.
Quote:
Does that value depend on what the four tiles are?
I use solid\non solid metatiles only and 1 metatile-one collision type(meaning, I can't make half of metatile as non solid and other half as solid).
When I want to check a Hero's collision with background, I calcutale his position and convert the result to X register. Later I use X to read from Level data in RAM that I saved before.
You don't need to have a separate collision table in RAM. In fact, I doubt most games have. Normally, if you wanted to test the solidity of a metatile, you'd access that metatile in the level map (which might be in RAM, if you decompressed it there, or in ROM), and then read the collision information associated with that metatile. It's a bit more indirect, but saves you a lot of memory. It goes something like this:
-calculate level map position of metatile to check;
-load metatile index from level map and put it into an index register;
-load from collision table in ROM, using the index from the previous step;
It makes little sense to store an entire screen of collision information separate from the metatiles, because the same kind of metatile will always have the same type of solidity, and you're not making use of that redundancy.
Denine wrote:
... When I want to check a Hero's collision with background, I calcutale his position and convert the result to X register. Later I use X to read from Level data in RAM that I saved before.
Thank you Denine; your help is much appreciated. Hope your game is growing healthyly too.
tokumaru wrote:
You don't need to have a separate collision table in RAM. In fact, I doubt most games have. Normally, if you wanted to test the solidity of a metatile, you'd access that metatile in the level map (which might be in RAM, if you decompressed it there, or in ROM), and then read the collision information associated with that metatile. It's a bit more indirect, but saves you a lot of memory. It goes something like this:
-calculate level map position of metatile to check;
-load metatile index from level map and put it into an index register;
-load from collision table in ROM, using the index from the previous step;
It makes little sense to store an entire screen of collision information separate from the metatiles, because the same kind of metatile will always have the same type of solidity, and you're not making use of that redundancy.
Wat is the level map? What info does it hold other than the metatile index? I don't think I have a level map. Was planning on creating one for collision... but I'm already susposed to have one? Hmm...
Thank you for helping me tokumaru!
unregistered wrote:
Wat is the level map? What info does it hold other than the metatile index? I don't think I have a level map.
If you have a level, you should have a level map. Storing straight name/attribute table is not efficient at all, even if you compress each screen, because you don't take advantage of all the redundancy of blocks that repeat.
A level map is usually a 2D grid of metatiles, where each byte is a metatile index. The level map can be used both for rendering (read a row/column of metatiles from the map, use their indexes to load the tiles that make up the metatiles and write them to VRAM) and for collision (get the metatile index and use it to get the metatile's collision information).
Here's a straightforward representation of a level that uses 32x32-pixel (4x4 tiles) metatiles:
Code:
;16 tiles for each of the 4 metatiles
;metatile: $00 $01 $02 $03
MatatileTile00:
.db $00, $04, $08, $0c
MatatileTile01:
.db $00, $04, $09, $0c
MatatileTile02:
.db $00, $04, $0a, $0c
MatatileTile03:
.db $00, $04, $0a, $0c
MatatileTile10:
.db $00, $05, $0b, $0d
MatatileTile11:
.db $00, $05, $0a, $0c
MatatileTile12:
.db $00, $05, $0a, $0c
MatatileTile13:
.db $00, $05, $0b, $0d
MatatileTile20:
.db $00, $06, $06, $0e
MatatileTile21:
.db $00, $06, $06, $0c
MatatileTile22:
.db $00, $06, $06, $0c
MatatileTile23:
.db $00, $06, $06, $0e
MatatileTile30:
.db $00, $07, $07, $0f
MatatileTile31:
.db $00, $07, $07, $0f
MatatileTile32:
.db $00, $07, $07, $0f
MatatileTile33:
.db $00, $07, $07, $0f
MatatileAttributes: ;4 palette indexes for each metatile
.db %00000000 ;palettes for metatile $00
.db %01010101 ;palettes for metatile $01
.db %01010000 ;palettes for metatile $02
.db %11000011 ;palettes for metatile $03
MetatileCollision: ;%00 = air, %01 = solid, %10 = water, %11 = hazard
.db %00000000 ;metatile $00 is all air
.db %01010101 ;metatile $01 is all solid
.db %01010000 ;only the top of metatile $02 is solid
.db %11110101 ;the top of metatile $03 hursts the player, the bottom is solid
LevelMap01: ;this small level is 16x8 metatiles (512x256 pixels) large
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $02, $02, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02, $02
.db $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $01, $01, $01, $01, $00, $00, $00, $00, $00, $01, $01, $01, $03, $03, $01, $01
.db $01, $01, $01, $01, $00, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
See how everything connects? Whenever you want to check what is present at a certain map location, you convert the coordinates into an index which you can use to read a metatile index from the level map. Once you know the metatile's index, you can use it to get any information you want about it. If you are rendering the background, read from the MatatileTileXX tables and and write the tile indexes to the name tables, and the attributes to the attribute tables. If you're testing for collisions, read from the MetatileCollision table to know how the objects should react to the metatile.
Se how one thing points to another, that points to another and so on? This is how you manage to reuse your level data effectively compressing it to much less than it would be if you had raw name/attribute table data. The data above is just 200 bytes. If I were to represent that in uncompressed form, this 2-screen wide level would have been 2048 bytes large.
Anyway, this is not a lesson about compression (although the maps are indeed compressed), this is to show you one of the possible ways in which a level can be represented, and how you can access everything on it. Does this make sense to you?
tokumaru wrote:
unregistered wrote:
Wat is the level map? What info does it hold other than the metatile index? I don't think I have a level map.
If you have a level, you should have a level map. Storing straight name/attribute table is not efficient at all, even if you compress each screen, because you don't take advantage of all the redundancy of blocks that repeat.
A level map is usually a 2D grid of metatiles, where each byte is a metatile index. The level map can be used both for rendering (read a row/column of metatiles from the map, use their indexes to load the tiles that make up the metatiles and write them to VRAM) and for collision (get the metatile index and use it to get the metatile's collision information).
Here's a straightforward representation of a level that uses 32x32-pixel (4x4 tiles) metatiles:
Code:
;16 tiles for each of the 4 metatiles
;metatile: $00 $01 $02 $03
MatatileTile00:
.db $00, $04, $08, $0c
MatatileTile01:
.db $00, $04, $09, $0c
MatatileTile02:
.db $00, $04, $0a, $0c
MatatileTile03:
.db $00, $04, $0a, $0c
MatatileTile10:
.db $00, $05, $0b, $0d
MatatileTile11:
.db $00, $05, $0a, $0c
MatatileTile12:
.db $00, $05, $0a, $0c
MatatileTile13:
.db $00, $05, $0b, $0d
MatatileTile20:
.db $00, $06, $06, $0e
MatatileTile21:
.db $00, $06, $06, $0c
MatatileTile22:
.db $00, $06, $06, $0c
MatatileTile23:
.db $00, $06, $06, $0e
MatatileTile30:
.db $00, $07, $07, $0f
MatatileTile31:
.db $00, $07, $07, $0f
MatatileTile32:
.db $00, $07, $07, $0f
MatatileTile33:
.db $00, $07, $07, $0f
MatatileAttributes: ;4 palette indexes for each metatile
.db %00000000 ;palettes for metatile $00
.db %01010101 ;palettes for metatile $01
.db %01010000 ;palettes for metatile $02
.db %11000011 ;palettes for metatile $03
MetatileCollision: ;%00 = air, %01 = solid, %10 = water, %11 = hazard
.db %00000000 ;metatile $00 is all air
.db %01010101 ;metatile $01 is all solid
.db %01010000 ;only the top of metatile $02 is solid
.db %11110101 ;the top of metatile $03 hursts the player, the bottom is solid
LevelMap01: ;this small level is 16x8 metatiles (512x256 pixels) large
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $02, $02, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02, $02
.db $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $01, $01, $01, $01, $00, $00, $00, $00, $00, $01, $01, $01, $03, $03, $01, $01
.db $01, $01, $01, $01, $00, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
See how everything connects? Whenever you want to check what is present at a certain map location, you convert the coordinates into an index which you can use to read a metatile index from the level map. Once you know the metatile's index, you can use it to get any information you want about it. If you are rendering the background, read from the MatatileTileXX tables and and write the tile indexes to the name tables, and the attributes to the attribute tables. If you're testing for collisions, read from the MetatileCollision table to know how the objects should react to the metatile.
Se how one thing points to another, that points to another and so on? This is how you manage to reuse your level data effectively compressing it to much less than it would be if you had raw name/attribute table data. The data above is just 200 bytes. If I were to represent that in uncompressed form, this 2-screen wide level would have been 2048 bytes large.
Anyway, this is not a lesson about compression (although the maps are indeed compressed), this is to show you one of the possible ways in which a level can be represented, and how you can access everything on it. Does this make sense to you?
Yes, tokumaru, that makes sense to me.
Thank you!
...I need to redo my attribute table code... cause it would save so much space if keep accessing the pallet values through my sister's metatile definitions. That will be challenging to accomplish... but space will be saved.
It'll be a learning experience!
In your metatile stream, you can use the top 2 bits of the unused metatile data for the attribute info.
Mine was just a straightforward example of how a bare bones metatile system works. Details such as metatile dimensions, types of collision, palette selection, bit ordering, etc. should be defined by the programmer according to the needs of their game.
Sorry for editing my post up there 3 times!
It's how it should be? With all of tokumaru's post copied from the bottom of the previous page? It could be bad for slow internet connections... but it kind of helps someone from having too much trouble going to the bottom of the previous page... in my opinion.
Thanks 3gengames.
And thanks, tokumaru, that helps me too.
Ok... so we represent a screen with 240 bytes if we have 16x16 metatiles... cause it takes less space!
But to move a player around in a screen it would require 240 x 4 = 960 bytes? Right? So there needs to be a routine that would create the screen in 960 bytes of memory. Then collision detection would be possible for me, I guess.
I cant overcome the want of 960 bytes per screen.
You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
unregistered wrote:
But to move a player around in a screen it would require 240 x 4 = 960 bytes?
That depends on how large you want your maps to be... Ideally, you will not store the whole map in RAM unless you're using extra WRAM, because the measly 2KB the NES has isn't enough for large levels, specially considering that this memory has to be used for many other things besides levels. If you are mot applying any other kind of compression on top of the metatiles, the obvious choice would be to just leave the maps in ROM. If you do decide to keep the maps in RAM, you'll need at least 3 screens worth of data that you can reload as the screen scrolls.
Quote:
Then collision detection would be possible for me, I guess.
As long as you can read data from the map, which you need to do for rendering the tiles to the name tables, for example, you can just as easily check for the solidity of the metatiles.
Quote:
I cant overcome the want of 960 bytes per screen.
Again, they don't need to be in RAM. You can access level maps from ROM just fine.
Shiru wrote:
You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
I'm so glad I asked that question! THANK YOU
SHIRU!
That's a great name for my method 'unpack'! It is going to replace my collisionU method!
tokumaru wrote:
unregistered wrote:
Then collision detection would be possible for me, I guess.
As long as you can read data from the map, which you need to do for rendering the tiles to the name tables, for example, you can just as easily check for the solidity of the metatiles.
IEncredible!! Yes you've told me this before, but NOW I UNDERSTAND!!!!!
I'm so excited... right now about trying collision without having to create a table/map correctly first!!
It's so much good news and happiness for me, Thank you so much Shiru and
tokumaru!!!!
---
3gengames, I understand now why you didn't understand my need for a method that would allow either an increment by 2 or an increment by 32. I don't need it anymore!!
I don't have to create a perfect table copy first because I already have access to the info.
edit
unregistered wrote:
Shiru wrote:
You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
I'm so glad I asked that question! THANK YOU
SHIRU!
That's a great name for my method 'unpack'! It is going to replace my collisionU method!
I'm working on GRAVITY now....
Our girl character falls down to the bottom of the screen. And keeps falling... through four screens... and then stops above the
two metatile rows of solid ground. Why does she fall four screens before stopping?
Ok, she stays on the same screen... she falls from top to the bottom like I want it to, but she keeps falling through the top of the screen and goes down toward the bottom. Sorry, it's lunch time right now and so I hope to return to an answer... if not I'll figure it out myself, i think.Code:
0C3AF unpack:
0C3AF
0C3AF ;128 64 32 16 8 4 2 1
0C3AF ;$80 $40 $20 $10 $08 $04 $02 $01
0C3AF A9 26 lda #<MetatileRhombus
0C3B1 85 0C sta rhombusCollision_low
0C3B3 A9 C8 lda #>MetatileRhombus
0C3B5 85 0D sta rhombusCollision_high
0C3B7
0C3B7 A9 05 lda #<MetatileCollision
0C3B9 85 1A sta UCollision_low
0C3BB A9 C9 lda #>MetatileCollision
0C3BD 85 1B sta UCollision_high
0C3BF
0C3BF A5 04 lda oX ;getting ready to divide my X coordinate by 8
0C3C1 4A lsr a
0C3C2 4A lsr a
0C3C3 4A lsr a
0C3C4 AA tax
0C3C5 ;sta tC+0
0C3C5
0C3C5
0C3C5 A5 05 lda oY
0C3C7 4A lsr a ;divide my Y coordinate by (2 * 2 * 2) = 8
0C3C8 4A lsr a ;<----------------------------*
0C3C9 4A lsr a ;<--------------------------------*
0C3CA A8 tay
0C3CB 85 32 sta tC+1
0C3CD
0C3CD ;x*16=row
0C3CD ;row+y=LINEAR_POSITION
0C3CD
0C3CD 8A txa
0C3CE 0A asl a ;multiply x by (2 * 2 * 2 * 2) = 16
0C3CF 0A asl a ;<----------------*
0C3D0 0A asl a ;<--------------------*
0C3D1 0A asl a ;<------------------------*
0C3D2 85 33 sta linearly
0C3D4
0C3D4 18 clc
0C3D5 98 tya
0C3D6 65 33 adc linearly
0C3D8 85 33 sta linearly
0C3DA A8 tay
0C3DB
0C3DB ;GRAVITY
0C3DB C6 31 dec tC
0C3DD 10 02 bpl +skip
0C3DF
0C3DF B1 0C lda (rhombusCollision_low), y
0C3E1 +skip
0C3E1 ;if the metatile is not solid
0C3E1 0A asl a ;<pushes bit #7 into carry.
0C3E2 90 09 bcc +skip
0C3E4
0C3E4 ;then fall to the metatile below...
0C3E4
0C3E4 B1 1A lda (UCollision_low), y
0C3E6 A5 05 lda oY
0C3E8 18 clc
0C3E9 69 08 adc #8
0C3EB 85 05 sta oY
0C3ED +skip
0C3ED
0C3ED E6 31 inc tC
0C3EF
0C3EF 60 rts ;end of unpack and end of daprg-collisionU
edit
There must be something wrong with the way you're reading the map... I can't make sense of the formula you're using to read metatiles... is it (x / 8) * 16 + (y / 8)? I have no idea what you're trying to do with that.
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16). If your metatiles are 32x32 pixels, it's (y / 32) * 8 + (x / 32). Once you have that offset, you can read a the index of the metatile at that location of the map and check it's collision information.
EDIT: Damn 8) smiley!
tokumaru wrote:
There must be something wrong with the way you're reading the map... I can't make sense of the formula you're using to read metatiles... is it (x /
* 16 + (y /
? I have no idea what you're trying to do with that.
YEAY THANK YOU SO MUCH TOKUMARU!
Ok... ...
My idea was pulled out of
this post:
You have a table arranged like this (I put random characters in it) :
A X U W O L T
S Z W K Y M Q
S U E U W P E
Q S G E I S L
Now you want to got the P that is in the third row and the sixth column.
You have two indexes, one is 2 (the count starts from zero, so the third row is number 2), and 5 (also start from zero).
The problem is that all the table is stored lineary in your ROM, so there it would be :
A X U W O L T S Z W K Y M Q S U E U W P E Q S G E I S L
The formula to know witch letter is the one you're looking for is :
There is 7 rows so, multiply the row index by 7. Scince the last letter of row 0 is at column 6, the first letter of the second row will be just after it, so effectively it's number 7.
Then, just add the column index to get the final index number.
In that case, 2*7=14; 14+5=19
Looking in the list above, the 20th letter is effectively P. It's number 19, but it's the 20th because the index starts from zero.
I guess the part that was kind of confusing to me was when Bregalad said, "
There is 7 rows so, multiply the row index by 7." There aren't 7 rows!!! More like 3 rows I think. 4 rows numbered 0 through 3. So that was difficult for me.
tokumaru wrote:
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16). If your metatiles are 32x32 pixels, it's (y / 32) * 8 + (x / 32). Once you have that offset, you can read a the index of the metatile at that location of the map and check it's collision information.
Yes, ok I'm going to try this out, tokumaru, right now...
tokumaru wrote:
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16).
Isn't that equal to
y + (x/16) ? That seems crazy to me.
edit: that's quite a lot to think about... I have to go mow now. So I'll have to come back to this...
Bregalad's formula is correct, for when there are 7 items per row. The formula for reading data from a 2D array which is stored in memory linearly is always
Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
Another thing that will affect how you apply the formula is how your levels are stored in RAM/ROM. If you store it screen by screen, then ElementsPerRow will always be the same, because the number of metatiles per screen doesn't change. If you don't divide your level in screens, then ElementsPerRow will be the length of the entire level, and it will vary from level to level. IMO, things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits, and the multiplications/divisions can be easily done with shifts.
Here comes another (untested) example of data arrangement (and how to access it):
Code:
LevelMap:
;first screen:
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;second screen:
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $20, $20, $20, $20, $20, $20, $20, $21, $22, $23, $00, $00, $00, $00
;third screen:
.db $22, $00, $22, $00, $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $20, $20, $20, $20, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;(keep going for as many screens as you want)
;Reads the index of the metatile at position (PointX, PointY) in the level map
ReadMetatile:
;get the index of the screen
ldy PointX+1
;multiply it by 240 (using a look-up table) and add it to the base
;address of the level to create a pointer to the screen we need
clc
lda ScreenOffsetLo, y
adc LevelMap+0
sta ScreenPointer+0
lda ScreenOffsetHi, y
adc LevelMap+1
sta ScreenPointer+1
;calculate the index of the metatile: (Y / 16) * 16 + (X / 16)
lda PointX+0
lsr
lsr
lsr
lsr
sta Temp
lda PointY+0
and #%11110000
ora Temp
tay
;read the metatile
lda (ScreenPointer), y
;return
rts
Now you can do whatever you want with that information. Want to know if that metatile is solid? Do something like this:
Code:
tax
lda MetatileCollision, x
and #BITTHATINDICATESWHETHERAMETATILEISSOLID
beq +NotSolid
Want to know what is the top left tile of that metatile?
Code:
tax
lda MetatileTopLeft, x
unregistered wrote:
Quote:
(y / 16) * 16 + (x /16).
Isn't that equal to
y + (x/16) ? That seems crazy to me.
Not really, because the lower 4 bits are cleared in the process of shifting right and back left. So as a shortcut you can just do
AND #%11110000 instead of
LSR LSR LSR LSR ASL ASL ASL ASL. It works the same. Stop thinking about math as you learned in school for a minute and try to see it as the 6502 sees it (which is much simpler than what you learned in school, IMO).
tokumaru wrote:
unregistered wrote:
Quote:
(y / 16) * 16 + (x /16).
Isn't that equal to
y + (x/16) ? That seems crazy to me.
Not really, because the lower 4 bits are cleared in the process of shifting right and back left. So as a shortcut you can just do
AND #%11110000 instead of
LSR LSR LSR LSR ASL ASL ASL ASL. It works the same. Stop thinking about math as you learned in school for a minute and try to see it as the 6502 sees it (which is much simpler than what you learned in school, IMO).
WOW!!!!! AGah... I've read through these posts so many times...
it's bed time... will finish this post tomorrow...
.
Ggoodnight yall.
edited once more: aaaah... tokumaru, thank you very very much! edits #2: Good morning! Goodness... i was really sleepy... sorry. THANK YOU SO VERY VERY VERY MUCH TOKUMARU!!
tokumaru wrote:
Bregalad's formula is correct, for when there are 7 items per row.
... ...Yeay!
Thanks tokumaru!
tokumaru wrote:
The formula for reading data from a 2D array which is stored in memory linearly is always Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
Another thing that will affect how you apply the formula is how your levels are stored in RAM/ROM. If you store it screen by screen, then ElementsPerRow will always be the same, because the number of metatiles per screen doesn't change. If you don't divide your level in screens, then ElementsPerRow will be the length of the entire level, and it will vary from level to level. IMO, things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits, and the multiplications/divisions can be easily done with shifts.
And cause it would be easy for my sister to edit the screens.
The blue part below is almost how she did it!
I never thought of not dividing the level into separate screens... that would be kind of insane
I think. But it does sound like it would be easy scrolling beyond 2 screens; though I don't know how to... ...yet.
tokumaru wrote:
Here comes another (untested) example of data arrangement (and how to access it):
code
LevelMap:
;first screen:
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;second screen:
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $20, $20, $20, $20, $20, $20, $20, $21, $22, $23, $00, $00, $00, $00
;third screen:
.db $22, $00, $22, $00, $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $20, $20, $20, $20, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;(keep going for as many screens as you want)
;Reads the index of the metatile at position (PointX, PointY) in the level map
ReadMetatile:
;get the index of the screen
ldy PointX+1
;multiply it by 240 (using a look-up table) and add it to the base
;address of the level to create a pointer to the screen we need
clc
lda ScreenOffsetLo, y
adc LevelMap+0
sta ScreenPointer+0
lda ScreenOffsetHi, y
adc LevelMap+1
sta ScreenPointer+1
;calculate the index of the metatile: (Y / 16) * 16 + (X / 16)
lda PointX+0
lsr
lsr
lsr
lsr
sta Temp
lda PointY+0
and #%11110000
ora Temp
tay
;read the metatile
lda (ScreenPointer), y
;return
rts/code
Now you can do whatever you want with that information. Want to know if that metatile is solid? Do something like this:
Code:
tax
lda MetatileCollision, x
and #BITTHATINDICATESWHETHERAMETATILEISSOLID
beq +NotSolid
Swell... incredible! That takes less cycles. But it takes a bit more
space no... memory. Fast is class!
tokumaru wrote:
Want to know what is the top left tile of that metatile?
Code:
tax
lda MetatileTopLeft, x
Aw yes faster code!
Ok I slowly changed my code to be just like your code all today.
Time to sleep. But, I haven't implemented any of this bit of
your yellow code up there... I just tried to use my variables.
I got the first part of my code to look like the first part of your code. Now the lady sprite falls just halfway down the screen and then pauses. She is susposed to fall to the bottom of the screen.
My code at the end is still the same:
Code:
0C3DB
0C3DB ;GRAVITY
0C3DB C6 31 dec tC
0C3DD 10 02 bpl +skip
0C3DF
0C3DF B1 0C lda (rhombusCollision_low), y
0C3E1
0C3E1 ;if the metatile is not solid
0C3E1 0A asl a ;<pushes bit #7 into carry.
0C3E2 90 09 bcc +skip
0C3E4
0C3E4 ;then fall to the metatile below...
0C3E4
0C3E4 B1 1A ;lda (UCollision_low), y
0C3E6 A5 05 lda oY
0C3E8 18 clc
0C3E9 69 08 adc #8
0C3EB 85 05 sta oY
0C3ED +skip
0C3ED
0C3ED E6 31 ;inc tC
0C3EF
0C3EF 60 rts ;end of unpack and end of daprg-collisionU
That line
;lda (UCollision_low), y is commented because it doesn't really work... should load accumulator with 0 for air or 1 for solid. But, I'm not working any more right now... haven't solved it yet... maybe you could? It's perfectly ok if yall don't. To sleep I go... goodnight. Ohhhh and thanks tokumaru, for all of the great help!!
I'm almost done... will post some more tomorrow afternoon.
edit: Ok... I want to say that I know that the yellow text will help me to make my game... almost... to make our game more like a machiene. A machiene with parts that work flawlessly together... I remember that tokumaru.
unregistered wrote:
I haven't implemented any of this bit of your yellow code
That code is just a multiplication by 240, which is faster with a table, but this table needs 512 bytes, which you might not want to dedicate to this purpose. Another way to multiply X by 240 is to do
(X * 256) - (X * 16). Here's a replacement for that calculation, without using tables:
Code:
;put (screen index * 16) into ScreenPointer
lda PointX+1
lsr
lsr
lsr
lsr
sta ScreenPointer+1
lda PointX+1
asl
asl
asl
asl
sta ScreenPointer+0
;subtract it from (screen index * 256)
sec
lda #$00
sbc ScreenPointer+0
sta ScreenPointer+0
lda PointX+1
sbc ScreenPointer+1
sta ScreenPointer+1
;add the result to the base address of the level
clc
lda ScreenPointer+0
adc #<LevelMap
sta ScreenPointer+0
lda ScreenPointer+1
adc #>LevelMap
sta ScreenPointer+1
It's much slower though. BTW, in my old code I used
LevelMap+0 and
LevelMap+1 by mistake, I should have used
#<LevelMap and
#>LevelMap instead.
My sprite has started out in the lower left corner all day today... I can't figure out why.
Is there something wrong in the code I've been trying to set?
Code:
0C3B3 ;**********************************************************************************************
0C3B3 unpack:
0C3B3
0C3B3 ;**********************************************************************************************
0C3B3
0C3B3 ;128 64 32 16 8 4 2 1
0C3B3 ;$80 $40 $20 $10 $08 $04 $02 $01
0C3B3 A9 3C lda #<MetatileRhombus
0C3B5 85 0C sta rhombusCollision_low
0C3B7 A9 C8 lda #>MetatileRhombus
0C3B9 85 0D sta rhombusCollision_high
0C3BB
0C3BB A9 1B lda #<MetatileCollision
0C3BD 85 1A sta UCollision_low
0C3BF A9 C9 lda #>MetatileCollision
0C3C1 85 1B sta UCollision_high
0C3C3
0C3C3
0C3C3 ; clc
0C3C3 ; lda ScreenOffsetLo, y
0C3C3 ; adc #<LevelMap
0C3C3 ; sta ScreenPointer+0
0C3C3 ; lda ScreenOffsetHi, y
0C3C3 ; adc #>LevelMap
0C3C3 ; sta ScreenPointer+1
0C3C3
0C3C3
0C3C3 A5 04 lda oX
0C3C5 85 28 sta PointX
0C3C7 A5 1C lda oY+23
0C3C9 85 2A sta PointY
0C3CB
0C3CB 20 F5 C3 jsr linear_position ;y holds new linear position
0C3CE
0C3CE
0C3CE ;GRAVITY
0C3CE
0C3CE 85 FF sta $ff
0C3D0 B1 10 lda (ten_low), y
0C3D2 A8 tay
0C3D3 B1 0C lda (rhombusCollision_low), y
0C3D5
0C3D5 ;if the metatile is not all the same tile
0C3D5 0A asl a ;<pushes bit #7 into carry.
0C3D6 90 1C bcc +skip
0C3D8
0C3D8 ; and if the ground below is not something solid to stand on...
0C3D8 A5 2C lda SomethingSolidtoStandon
0C3DA F0 18 beq +skip
0C3DC ;then fall to the metatile below...
0C3DC
0C3DC A5 05 -- lda oY
0C3DE 18 clc
0C3DF 69 08 adc #8
0C3E1 85 05 sta oY
0C3E3
0C3E3 85 2A sta PointY
0C3E5 20 F5 C3 jsr linear_position
0C3E8
0C3E8 ;and check the value of the block
0C3E8 ; tay
0C3E8 B1 10 lda (ten_low), y
0C3EA A8 tay
0C3EB B1 1A lda (UCollision_low), y
0C3ED 85 2C sta SomethingSolidtoStandon
0C3EF ;if it is 00 it's air
0C3EF D0 03 bne +notair
0C3F1 ;it is air!!!
0C3F1 4C DC C3 jmp --
0C3F4 +notair
0C3F4 ;and if it is 01 it's solid. :) So try to stop or move back to the previous tile?
0C3F4
0C3F4 ;it's probably solid!!!
0C3F4 ;so stop falling
0C3F4
0C3F4
0C3F4
0C3F4 +skip
0C3F4
0C3F4
0C3F4 60 rts ;end of unpack
0C3F5
0C3F5 linear_position:
0C3F5 ; Calculate the index of the metatile: (Y / 16) * 16 + (X / 16) = LINEAR_POSITION p.50
0C3F5 A5 28 lda PointX+0
0C3F7 4A lsr a ;divide my X coordinate by (2 * 2 * 2 * 2) = 16
0C3F8 4A lsr a ;<----------------------------*
0C3F9 4A lsr a ;<--------------------------------*
0C3FA 4A lsr a ;<------------------------------------*
0C3FB AA tax
0C3FC 85 37 sta tC+1
0C3FE A5 2A lda PointY+0
0C400 29 F0 and #11110000b
0C402 05 37 ora tC+1
0C404 A8 tay
0C405
0C405 60 rts
Yessss! Ok it's fixed now... I found out this morning that the -- loop is too tiny! When you delete that loop part and let it end with storing the new value of SomethingSolidtoStandon... then it works!!
She falls ............................................. down into the ground and sticks there. I would like to see her fall and stop with her feet on the ground. Is this a good method?: Calculate the block above her hand... she has her hands high in the air. Do I just subtract 16 inside each calculation? Or maybe I should add 16 to her height so that checks the block near her feet?
I'm kind confused about what to do. I'll figure it out sometime.
The exact points that make contact with surfaces can be anything you want. I have no idea how you're handling object coordinates, but I assume you have the current coordinates of the girl characters stored somewhere, which you use to calculate the coordinates of the individual sprites. Similarly to how you calculate sprite coordinates from these "master coordinates", you can also calculate the collision points.
If the character is 16x32 pixels, and its coordinates indicate its top left corner, you can calculate a point of collision at the middle bottom of the character with X+8, Y+32. Once you know this point (PointX, PointY), you can call that routine that checks the metatile at that location. If it's not solid, the character should fall. If it is solid, you can't simply stop it from falling (because the character could be stuck into the ground if moving faster than 1 pixel per frame), you have to push it back up so that it's exactly on top of the floor.
Grand!!! Thlink I have it!!
edit: Well... now she starts way up high and falls a little bit...then stops... well above the ground. It's kind of exciting!!!!!!!!!!!!!! ALMOST THERE... SHE JUST NEEDS TO FALL FOR A LITTLE BIT LONGER!! edit2: Now she falls to the ground and sticks inside it a little!
tokumaru wrote:
The exact points that make contact with surfaces can be anything you want. I have no idea how you're handling object coordinates, but I assume you have the current coordinates of the girl characters stored somewhere, which you use to calculate the coordinates of the individual sprites. Similarly to how you calculate sprite coordinates from these "master coordinates", you can also calculate the collision points.
If the character is 16x32 pixels, and its coordinates indicate its top left corner, you can calculate a point of collision at the middle bottom of the character with X+8, Y+32. Once you know this point (PointX, PointY), you can call that routine that checks the metatile at that location. If it's not solid, the character should fall. If it is solid, you can't simply stop it from falling (because the character could be stuck into the ground if moving faster than 1 pixel per frame), you have to push it back up so that it's exactly on top of the floor.
HOW?? I get that it is time to add eight... but where ever I put the
Code:
sec
lda oY
sbc #8
sta oY
it makes our girl character not move at all... every time my function subtracts 8 right after it added 8... and so she doesn't fall... ever!
How would you make
this work correctly? My logic has it
check the block below and add eight if
it's air and then quit after reading the type of block below. That happens every time.
You can't subtract by a fixed amount. If she's one pixel in the floor and you subtract eight, she's flying. If she's 9 pixels in the floor and you subtract 8 she's still in the floor.
You have to figure out how far she needs to be ejected, and then eject her.
If you've got 16x16 metatiles, only the lowest four bits of her lowest coordinate byte matter. You check if she's in a collision enabled tile.
Now think. If the lowest bits of her position are $0, how many pixels does she need to move up? How about when she's at $F? Once you figure that out, figure out some math to make it always eject her immediately out the tile.
unregistered wrote:
it makes our girl character not move at all... every time my function subtracts 8 right after it added 8... and so she doesn't fall... ever!
Pushing her back up is something you should do only if her feet are into the floor, not all the time. And like Kasumi said, you can't just push her a fixed amount of pixels.
The math should be quite simple really, since your solid metatiles are always 16x16 pixels... Say that you checked point X + 8, Y + 32 of a 16x32 character, and you found the metatile at that position to be solid. This means you have to make the character "snap" to the metatile that's above this one. You can ignore how far into the floor the character is with PointY AND #%f0, which is the pixel coordinate of the very top (first row) of the solid metatile. Then, it's just a matter of subtracting the character's height to find the new Y coordinate that will place it right above the floor.
Quote:
My logic has it check the block below and add eight if it's air and then quit after reading the type of block below. That happens every time. :?
And if the block below is solid, you must push the character back up. You can do it that way, but you'll have no acceleration at all (because you always add 8), and that looks very bad. The speed of falling objects increases with time, so it would be better that you added a variable instead of a constant number, and every frame that the character is in the air the value of that variable increases. However, if you increase the speed by whole pixels every frame it will grow too fast and you'll barely notice it, so you should probably look into fixed point math, so that you can add fractional numbers to the character's speed to make it more smooth.
tokumaru wrote:
(because you always add
Can we get rid of that smiley on the forum? Way more often I see it used accidentally rather than intentionally. Or maybe the accidental uses just stick out...
Yeah, I always get screwed by that stupid smiley...
tokumaru wrote:
unregistered wrote:
My logic has it
check the block below and add eight if
it's air and then quit after reading the type of block below. That happens every time.
And if the block below is solid, you must push the character back up. You can do it that way, but you'll have no acceleration at all (because you always add
, and that looks very bad. The speed of falling objects increases with time, so it would be better that you added a variable instead of a constant number, and every frame that the character is in the air the value of that variable increases. However, if you increase the speed by whole pixels every frame it will grow too fast and you'll barely notice it, so you should probably look into fixed point math, so that you can add fractional numbers to the character's speed to make it more smooth.
Fixed point math. OK... I found
this pdf and have read it and understand most of it... i think. I have kind of choose to use Q3.5 nums... on page 13 of that pdf i think I remember. Would something like that work well for this falling faster math? Or they show a Q3.13 also. That's a 2 byte value.
...While reading
the pdf it reminded me of Calculus... I was able to retake my calculus class... so I learned it twice... I guess. Those 2 semesters were quite a time ago though... I think.
edit: tokumaru, tokumaru, on page 50, wrote:
EDIT: Damn
smiley!
how did you do that?
In this case it's really more of change in perception than learning anything new.
You have two bytes. Together, they represent one number.
You're probably familiar with this way.
Number = hibyte*256+lobyte for a range of 0 through 65535.
But the computer really doesn't care about what the bytes represent to you.
Number could also represent onebyte + (otherbyte/256) to you for a range of 0 through 255+(255/256). In this case "otherbyte" represents the fractional part of a number.
In both cases additions/subtractions to the bytes are done in exactly the same way, they just represent something different for you, the programmer.
A speed like this:
onebyte = #$01
otherbyte = #$00
added to the characters position means the character moves one pixel every frame.
A speed like this:
onebyte = #$00
otherbyte = #$40
added means the character moves one pixel every 4 frames. (Because that's how many adds of that amount it would take to carry 1 pixel over)
(This also means your position will need another byte to represent the fractional part of the position, which if you scroll means you'll need at least 3 bytes for the position alone, two for the speed)
At least, that's one way to set it up. Make sense?
The code
8) used to make the emoticon with sunglasses. As of two minutes ago, I have turned it off in favor of
8-), which produces
.
unregistered wrote:
I have kind of choose to use Q3.5 nums... on page 13 of that pdf i think I remember. Would something like that work well for this falling faster math? Or they show a Q3.13 also.
Those will make it hard to use the integer part of the values... Kasumi's suggestion of using 1 byte for the integer part and another for the fractional part (i.e. 8.8) makes much more sense. That way you easily use the high byte as the integer part as is, without having to shift bits around.
Quote:
how did you do that? How did I do what? Show 8 and ) instead of the smiley? There's a checkbox in below the text area in the posting/editing page that disables smileys in the current post.
Kasumi wrote:
In this case it's really more of change in perception than learning anything new.
You have two bytes. Together, they represent one number.
You're probably familiar with this way.
Number = hibyte*256+lobyte for a range of 0 through 65535.
But the computer really doesn't care about what the bytes represent to you.
Number could also represent onebyte + (otherbyte/256) to you for a range of 0 through 255+(255/256). In this case "otherbyte" represents the fractional part of a number.
In both cases additions/subtractions to the bytes are done in exactly the same way, they just represent something different for you, the programmer.
A speed like this:
onebyte = #$01
otherbyte = #$00
added to the characters position means the character moves one pixel every frame.
A speed like this:
onebyte = #$00
otherbyte = #$40
added means the character moves one pixel every 4 frames. (Because that's how many adds of that amount it would take to carry 1 pixel over)
(This also means your position will need another byte to represent the fractional part of the position, which if you scroll means you'll need at least 3 bytes for the position alone, two for the speed)
At least, that's one way to set it up. Make sense?
Yes, it does finally after I read tokumaru's post. That short summary reminded me what I learned yesterday... and so coming back and reading your post again... your words are incredibly helpful during my second read! Thank you Kasumi!
Well, they will be incredibly helpful once I spend the time to explore 8.8 ...tokumaru wrote:
unregistered wrote:
I have kind of choose to use Q3.5 nums... on page 13 of that pdf i think I remember. Would something like that work well for this falling faster math? Or they show a Q3.13 also.
Those will make it hard to use the integer part of the values... Kasumi's suggestion of using 1 byte for the integer part and another for the fractional part (i.e. 8.8) makes much more sense. That way you easily use the high byte as the integer part as is, without having to shift bits around.
Quote:
how did you do that? How did I do what? Show 8 and ) instead of the smiley? There's a checkbox in below the text area in the posting/editing page that disables smileys in the current post.
All excellent words... thanks
very much tokumaru!
editedited again
In
that pdf I found yesterday... it trys to convince me that only 3 bits are needed for the integer part
in Example 5 on page 9... and I am wondering could I use the 3 bits for that and then use the other 5 bits to store something else? Using
and #00000111b before loading it... do you understand?
Or would it be ok to create an
Q8.8? With that I could use numbers in a
range from (-128 to 127.99609375). Does that work out ok? tokumaru (and Kasumi too) suggested using an
8.8 so that must be ok.
A
Q3.8 would be able to use numbers in a
range from (-4 to 3.99609375)Which one would you pick?
A
Q3.8 or a
Q8.8?
Hope all my math here is correct. edit: Woah, ok Kasumi, it doesn't have to be a signed value!!! YEAY! Thank you
a lot for this awesome help!
I dunno what you're doing, but if you're doing something with a sub-pixel or decimal to get a fine tune on things, just use a low byte that only changes the high byte when it "rolls over" so that way you can add stuff each from but nothing happens.
You absolutely can't work with a range smaller than 0-255, because you need that much to represent coordinates on the screen. The obvious choice is 8.8, unsigned. Your document insists on 3.13 probably because it's focused on other applications, like trigonometry or something, but this is not your case.
3gengames wrote:
... just use a low byte that only changes the high byte when it "rolls over" so that way you can add stuff each from but nothing happens.
Thanks 3gengames; I hope to achieve that.
tokumaru wrote:
You absolutely can't work with a range smaller than 0-255, because you need that much to represent coordinates on the screen. The obvious choice is 8.8, unsigned. Your document insists on 3.13 probably because it's focused on other applications, like trigonometry or something, but this is not your case.
Thank you tokumaru those are three
great wonderful points and they've answered all my questions!
I'll focus on 8.8, unsigned, numbers now.
editedit2: 8.8 Unsigned numbers range from (0 to 255.99609375)... that works incredibly well!
Now our sprite falls and lands on the ground!
Now I would like to see if I could move forward. I should just check each metatile below her feet... if it is solid she can move forward. Should I move her forward and then fix her position if needed? Yes, I should push her back if she has walked into a solid block. I can figure this out!
Wow, I can't believe this thread is still going
It's inspiring to see that you've got so much commitment to this!
qbradq wrote:
Wow, I can't believe this thread is still going
It's inspiring to see that you've got so much commitment to this!
My mom and dad have been inspiring toward me... they... well, my mom she has told me that I am going to finish programming this game.
There is so much that has to be done; my sister has finished most of the levels... I'm enjoying collision detection working now. It has been a little more than a year since I decided on attempting collision detection. That was on
page 19...
now it's something like 51 pages.I am so happy you've come back!
Thank you very much qbradq
.editedited once more: In my input code there's this part
Code:
@right lda currControllerButtons ;Is Right down?
and #BUTTON_RIGHT ;00000001b
beq not_dn
inc oX
How would you change this code? I want to replace that inc oX with code that checks the next space to see if it is solid. I'm not shure if a method should be jsr'd to... or maybe that would take too much time?
So I'm not going to read 52 pages back
I assume from the few pages I've caught up on this is not cell-based (like Zelda II's overworld), but subpixel-based (like Mario).
Typically I use a velocity variable for every object. So in the above code snippet I would set the player's X velocity value to some value. Then during the object update routine I apply the velocity to the position, then check for collisions, and if there is a collision react to it. If you do go this route, do Y movement and collision detection first, then X. Trust me on that one
If you want to take the immediate route I would recommend JSR'ing to a routine that calculates what the X value would be, then sees if that movement is possible. Depending on your level data layout this routine might be pretty complex, and I personally wouldn't want it cluttering up my input routine.
Also, sounds like you've got good parents and a pretty awesome sister
Not that I have programmed a game yet, but I always thought for velocity and stuff that a one byte value would work perfect, since I highly doubt your game can scroll 3 rows at a time, so 4.4 combined in to one byte will make you able to save a few bytes over all and work fine. Just my idea on it, but please don't let it confuse you, I'm just randomly blurbing it.
Controller input shouldn't directly affect the horizontal position of the player. Instead, it should affect the horizontal velocity (just like gravity affects its vertical velocity), and the velocity, whatever it is, should be added to the player's position every frame (again, like happens with vertical movement).
If a direction is pressed, increase velocity in that direction, if no direction is pressed, gradually decrease the velocity until it reaches 0 (this creates a nice inertia effect). After adding the velocity variable to the current position, check whether the block(s) at the edge of the player in the direction of the movement are solid, in which case you need to push the character back, like we discussed before.
Ejecting from the right should be exactly same as repelling from the ground, while the formula for ejecting from the left or from the ceiling is a bit different, but I'm sure you'll figure it out.
That is, unless you want physics like Mega Man or Contra, which don't really have a lot of inertia.
(Speaking of inertia, NovaYoshi giggled at this illustration.)
I remember Mega Man having a tiny bit of inertia... Yup, it's there, I just checked. It seems that he just accelerates and decelerates really really fast.
And additionally his speed downwards constantly grows if he's standing on a moving platform of some kind. When I was playing MM1 in the good ol' days it always puzzled me why falling off a platform would get you "down to earth" so much quicker than jumping off it. Little could I know that over 20 years later people would use such a peculiar behavior to beat each other's score in prerecorded demo modes (aka TAS) for the very same game...
Yea, platformer physics can be painful at times.
So OP, here's a list of games off the top of my head you can look at that do it right:
Zelda II: Adventure of Link, The
Super Mario Bro.s
Super Mario Bro.s 3
Metroid
And here's a few that do it very poorly:
EVERY Castlevania game, but especially CV1
Most Mega Man games, especially MM1
Any Donkey Kong game
Ghosts 'n Goblins
qbradq wrote:
So I'm not going to read 52 pages back
I assume from the few pages I've caught up on this is not cell-based (like Zelda II's overworld), but subpixel-based (like Mario).
Yes, I guess it's subpixel-based like Mario.
qbradq wrote:
Typically I use a velocity variable for every object. So in the above code snippet I would set the player's X velocity value to some value. Then during the object update routine I apply the velocity to the position, then check for collisions, and if there is a collision react to it. If you do go this route, do Y movement and collision detection first, then X. Trust me on that one
Ok, thank you qbradq!
qbradq wrote:
Also, sounds like you've got good parents and a pretty awesome sister
YES! Thank you so very much qbradq!
tokumaru wrote:
Controller input shouldn't directly affect the horizontal position of the player. Instead, it should affect the horizontal velocity (just like gravity affects its vertical velocity), and the velocity, whatever it is, should be added to the player's position every frame (again, like happens with vertical movement).
If a direction is pressed, increase velocity in that direction, if no direction is pressed, gradually decrease the velocity until it reaches 0 (this creates a nice inertia effect). After adding the velocity variable to the current position, check whether the block(s) at the edge of the player in the direction of the movement are solid, in which case you need to push the character back, like we discussed before.
Ejecting from the right should be exactly same as repelling from the ground, while the formula for ejecting from the left or from the ceiling is a bit different, but I'm sure you'll figure it out.
Thank you very much tokumaru!
qbradq wrote:
Yea, platformer physics can be painful at times.
So OP, here's a list of games off the top of my head you can look at that do it right:
Zelda II: Adventure of Link, The
Super Mario Bro.s
Super Mario Bro.s 3
Metroid
I have metroid but have not played it much. Though, I do remember the great platforming it has; it has jumps as in Mario.
qbradq wrote:
And here's a few that do it very poorly:
EVERY Castlevania game, but especially CV1
Most Mega Man games, especially MM1
Any Donkey Kong game
Ghosts 'n Goblins
I have CV1 and MM3 and Ghosts 'n Goblins. Castlevania is interesting... but I don't see how it does very poorly. I'm hoping you will explain what you mean by that. I'm eager to learn!
---
I spent some time last night looking up velocity in my Physics textbook. Velocity is speed/time, right? I'm going to look up inertia tomorrow.
For inertia in games you can do this:
1. When striking a solid object, zero out velocity
2. When trying to stop (I.E. letting off the DPad), you can reduce the velocity toward zero by a fixed amount.
In CV1, here's what they got wrong (or at least wrong in my opinion)
1. Jumps are not controllable at all
2. Difficult to jump horizontally from a standing start
3. No acceleration / deceleration while walking
4. Collision detection is VERY off. You don't collide with anything from the top, and there's all sorts of bugs with the collision detection they did implement
The main thing I don't like about CV1 is the inability to control your jump. That's very bad for a platformer.
tokumaru, on page 35, wrote:
Dwedit wrote:
When using jump tables, be very careful about safety. Don't allow out-of-range values to be used. Use bounds checking, or bit masking and dummy entries.
I was gonna say something like that... Jump tables are great when your cases are all consecutive numbers (and you should always try to make it so, since it's easier that way), but when the cases are scattered values or ranges of different sizes, jump tables are not so good.
Ok... I need your help. I currently have cases numbered 21 through 28. So they are all consecutive right now!
But I thought of trying something... would this improve my jump table? My code will follow Kasumi's example...
Kasumi, also on page 35, wrote:
Code:
;y contains the case number
lda jumptablelow,y
sta tempaddress
lda jumptablehigh,y
sta tempaddress+1
jmp [tempaddress]
jumptablehigh:
.db high(case0)
.db high(case1)
.db high(case2)
jumptablelow:
.db low(case0)
.db low(case1)
.db low(case2)
case0:
case1:
case2:
Underneath each case label would be the code corresponding to the case. Then, we store the addresses of all the cases in two tables. We load the low and high byte of the address we want, and store them in two contiguous bytes in low/high order. Then we jmp indirect to byte where the low part of the address was stored which will take us to the case label.
There's actually a small bug with the 6502 jmp indirection instruction. From
here:An original 6502 has does not correctly fetch the target address if the indirect vector falls on a page boundary (e.g. $xxFF where xx is and value from $00 to $FF). In this case fetches the LSB from $xxFF as expected but takes the MSB from $xx00. This is fixed in some later chips like the 65SC02 so for compatibility always ensure the indirect vector is not at the end of the page.
This is susposed to run animation as character moves across the screen... my sister is an animator.
Code:
;if left is pressed,
lda Next-1
jmp +
;if right is pressed,
lda Next+1
+ sta NewNext
;y contains the case number
lda jumptablelow,y
sta tempaddress
lda jumptablehigh,y
sta tempaddress+1
jmp [tempaddress] ;<??????????? what do [] mean? :?
jumptablehigh:
.db >case0, >case0, >case1, >case2, >case2, >case2, >case1, >case0
jumptablelow:
.db <case0, <case0, <case1, <case2, <case2, <case2, <case1, <case0
case0:
jsr record ;<--this is just an idea right now
bne case2
case1:
lda Next
sta NewNext
jmp endCaseJump
case2:
;this is just an idea also
endCaseJump:
...So if I modified Kasumi's jump table like this would this be bad or good? It would still work the same way... I think... let's say y is equal to
22 - 20...
2 ... it would run case 1 right? Well, that's what I want it to do....
If Y is in the range 20-28 inclusive, then you'll need to modify one section of code:
Code:
;y contains the case number (20-28 inclusive)
lda jumptablelow-20,y
sta tempaddress
lda jumptablehigh-20,y
sta tempaddress+1
jmp [tempaddress] ;<??????????? what do [] mean?
; It means Jump Indirect. Here's how it works:
; Load the value at tempaddress into the program counter
; Continue to next instruction, which is now the instruction at the address stored in tempaddress
Note that you're applying some static math to the pointer table address to account for the minimum value of Y. This is more efficient that doing math at run-time, but also a little less clear (to some folks).
Without doing this then when Y is 20 you'll be accessing the 20'th element of the array, which is well outside it's bounds, and you'll get junk data.
unregistered wrote:
It would still work the same way... I think... let's say y is equal to
22 - 20...
2 ...
it would run case 1 right? Well, that's what I want it to do....
edit: ... sorry. Thanks qbradq. Next time I hope to provide better coding that describes my purpose and logic better.
qbradq wrote:
Code:
jmp [tempaddress] ;<??????????? what do [] mean?
; It means Jump Indirect. Here's how it works:
; Load the value at tempaddress into the program counter
; Continue to next instruction, which is now the instruction at the address stored in tempaddress
I think I remember tokumaru telling me that [] aren't used in asm6, that's the assembler i'm using. Though, that's a great description... I'll have to memorize that!
The normal syntax would be:
Code:
jmp (tempaddress)
qbradq wrote:
The normal syntax would be:
Code:
jmp (tempaddress)
...well my assembler is complaining Unknown Label... so I'm guessing Kasumi ment
Code:
jmp (tempaddress), y
The (d),y addressing mode does not work with JMP. "Unknown label" means there isn't a symbol called tempaddress.
tepples wrote:
The (d),y addressing mode does not work with JMP. "Unknown label" means there isn't a symbol called tempaddress.
Oh
... well, it was actually my fault, I read the wrong line... after correcting it... it said Illegal Instruction to my JMP (d),y line and so I deleted the ,y part and it compiled correctly and made an nes file!
I didn't know that JMP (d) was possible and I was wrong to
say imply that it wouldn't work. Im sorry qbradq.
tepples, thank you for correcting me!
edited
OK!
I finally figured out what the problem is... the ground is continuous across the entire screen and so I move our girl character right and then it scrolls a little bit at first. Eventually a new screen scrolls over with a hole of water at the end, but I just figured out that our attribute table doesn't scroll yet and that's why she never decends into the water. And I haven't studied the scrolling knowledge that yall've taught me last... a little more than a year ago; I'll go read right that now. I don't think I got to this question back then; so, how can we get the attribute table to scroll with the screen? Sounds crazy...
edit: Ok... I read all of the scrolling knowledge shared... it starts on page 14. This picture is helpful to me on how to scroll the attribute tables... I would enjoy your help if you give it. Maybe I can do this. tokumaru wrote:
I just don't recommend it to newbies because sometimes it's hard for them to grasp the concept of the progressive name table updates
Would this GIF make the concept easier for them to grasp?
edit2: Right now my method for building attribute tables builds an entire attribute table... and that would take too long to build an attribute table for each screen that appears, right? Do you have any tips for just building the 2 collums of questionmark blocks each time? edit3: I remember that there's a way to go down a collum... it's somehow accomplished by setting a bit somewhere.
Going down a column by setting the PPU increment to 32 bytes per increment won't help with the attribute tables. You need an increment of 8 for that.
What I did when I made a scrolling engine (and there's plenty of folks on here with more experience than me) was to keep a copy of both nametable's attribute area in RAM. Then I could make my inserts, then write what I had to the VRAM during NMI.
If you're doing whole attribute bytes at once you don't have to do this though. Just calculate all the bytes, then write them and manually increment the VRAM pointer. You might do this with reads, or more efficiently by setting the address before each byte write.
Tip: If your levels are 12 metatiles tall (as in the GIF example above), you can calculate six bytes of attributes (one for each set of four metatiles in the strip), set the PPU to +32, and write bytes 0 and 4, then 1 and 5, then 2, then 3.
MIND = BLOWN
Cool! I never even thought of that
qbradq wrote:
Going down a column by setting the PPU increment to 32 bytes per increment won't help with the attribute tables. You need an increment of 8 for that.
Wow, I'm really glad you understood what I was mumbling about. Thank you!
Now I remember that the attribute table is much smaller than the screen! qbradq wrote:
What I did when I made a scrolling engine (and there's plenty of folks on here with more experience than me) was to keep a copy of both nametable's attribute area in RAM. Then I could make my inserts, then write what I had to the VRAM during NMI.
What do you mean by inserts?
qbradq wrote:
If you're doing whole attribute bytes at once you don't have to do this though. Just calculate all the bytes, then write them and manually increment the VRAM pointer. You might do this with reads, or more efficiently by setting the address before each byte write.
AH,
Setting the address before each byte write will be good... ok! Thank you incredibly much qbradq!
tepples wrote:
Tip: If your levels are 12 metatiles tall (as in the GIF example above), you can calculate six bytes of attributes (one for each set of four metatiles in the strip), set the PPU to +32, and write bytes 0 and 4, then 1 and 5, then 2, then 3.
qbradq wrote:
MIND = BLOWN
Cool! I never even thought of that
Thank you tepples... I don't quite understand... but I kind of do. It will make sense after I attempt what qbradq said. I do get that you are saying that setting the PPU to +32 could be a possibility for attribute tables. That's amazing to me!
I hope to have my mind blown out too
... that seems good to me... thinking.
Say you have prepared a column of attribute data. You want to write it to offsets 0, 8, 16, 24, 32, 40, 48, and 56 in the attribute table. But the PPU has no +8 increment mode, just +1 and +32. What you can do is this:
- Set the PPU to +32.
- Seek (set the address) to 0 and write the entries for 0 and 32.
- Seek to 8 and write the entries for 8 and 40.
- Seek to 16 and write the entries for 16 and 48.
- Seek to 24 and write the entries for 24 and 56.
Well, I just spent the last 4 hours reading through to the end of this thread.
I started on page 40. And I have just remembered that my post about attribute tables was destroyed... I don't remember why. I did find my post on
page 48 kind of confusing because I didn't find all the attribute table info that was in my destroyed post... ugh. Time to prepare supper for tonight... yall have a good evening!
tepples wrote:
Say you have prepared a column of attribute data. You want to write it to offsets 0, 8, 16, 24, 32, 40, 48, and 56 in the attribute table. But the PPU has no +8 increment mode, just +1 and +32. What you can do is this:
- Set the PPU to +32.
- Seek (set the address) to 0 and write the entries for 0 and 32.
- Seek to 8 and write the entries for 8 and 40.
- Seek to 16 and write the entries for 16 and 48.
- Seek to 24 and write the entries for 24 and 56.
MIND = VERY BLOWN
Wow, that's quite awesome tepples! THANK YOU!!! I get it now... with the PPU set to +32 you set the address to 0 and then write the entries for 0
(now the PPU increments to 32) and 32... thats just two writes with the PPU incrementing by 32 after each write!!!!!!
VERY BLOWN thanks tepples
And um well... you did say that this would be possible if our levels were 12 metatiles high... why? It is so grand and I don't understand why it wouldn't work with regular 15 metatile high land.
I'm afraid of your answer. Please respond.
unregistered wrote:
you did say that this would be possible if our levels were 12 metatiles high... why?
When tepples mentioned that in the case of 12 metatiles, he said you'd write bytes 0 and 4, 1 and 5, 2, then 3 (meaning you don't have to write attribute bytes 6 and 7), but the technique works just fine with 15 metatiles, you just have to write all 8 attribute bytes.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! THANKS TOKUMARU!!!
...
unregistered, on the previous page, wrote:
... so, how can we get the attribute table to scroll with the screen? ...
No, not the attribute table...
the collision table. I need to check the collision so our girl can fall into the non-solid water at the end of the second screen that kind of... visually scrolls over. How can we get the collision table to scroll with the screen? Wait... I remember that I didn't even have to create a collision table... it's already accessable right? Or does that sound crazy too... uhmmm... ...?
: )
How are you storing your collision data? That has a lot to do with how to scroll it
Typically I just keep a buffer of the metatiles that are on screen, and use the tile ID to index a table of flags that include collision information. To "scroll" this, you simply update the metatile buffer every time you scroll the screen to be in synch with it.
Or if you've got WRAM you can just store the entire level in RAM at once and not have to worry about scrolling the data, just the screen.
I think that the problem is that your current design is still based on the NES screen and the name tables. Most people start out that way, because it's simpler to move objects around that small area, but once you start messing with scrolling, you have to see things differently: the screen and the name tables are not the containers of the objects anymore, they are merely used as a viewport, to show a representation of part of the level, and the level is the actual container of the objects.
You should forget about the screen and the name tables for a moment, and think of the level map as the basis for your game world. Your objects exist in the level, so everything about them is relative to the level map. Sprite coordinates are not restricted to 8 bits anymore, since levels can be much wider than 256 pixels. To test for collisions, you have to do some math with the object coordinates (like we discussed before), and since the sprite coordinates are in the same domain as level map coordinates, scrolling is absolutely irrelevant to collisions.
To make things easier, ideally you'd have access to the whole map, either by decompressing it to WRAM or storing it uncompressed (or compressed in a way that allows for random access) in the ROM. Having access to the complete level makes it easy to move objects around and have them collide with the level regardless of what the screen and the name tables are showing.
Don't think of scrolling as "the level going by", but rather as "a camera panning across the level". The level is stationary, but a virtual "camera" moves around in order to display different parts of the level, and it's this camera that dictates what gets written to the name tables and the OAM. You camera must have its own coordinates, which are used to convert level coordinates into screen coordinates (i.e. level coordinates - camera coordinates = screen coordinates), and you'll have to perform this conversion whenever you render sprites to OAM or metatile to the name tables.
I know it sounds complicated, but nobody said that scrolling was easy, specially if done right. If you do it the wrong way and keep everything oriented to screen coordinates, things will surely get out of hand (like the problem you are having now, where objects aren't aligning with the collision data).
levels can be wider than 256 pixels... according to my math I could have a level with 256 256pixel wide screens... or a have a level at the amount of 65536 pixels wide with 16bit algebra... from 0 to 65535.
That's BIG! Though, I wonder how do you use 16bit algebra in 8bit CPU? Let's say we are using the number (8192+1)... tha'd be 8193 or
00100000 00000001b ...woah this is kind of like using the number 256 or
00000001 00000000b with numbers 0 to 255... but that works because there are 256 numbers... 255 and the 0. There are not 65536 numbers are there?
This is my beginning step with 16 bit math, I think... sorry.
You can't fit a 16-bit number in one byte, but you can fit it in two. Here's how to do 16 bit addition, using the example of 508 + 8 = 516 ($01FC + $0008 = $0204):
Code:
position_hi = $0701
position_lo = $0700
; set the position to $01FC
lda #$01
sta position_hi
lda #$fc
sta position_lo
; now add eight ($0008) to this position
clc
lda #$08 ; add the low byte first
adc position_lo
sta position_lo
lda #$00
adc position_hi
sta position_hi
When you add two 8-bit numbers and the result is more than 256, the CPU subtracts 256 and turns on the carry flag. The carry flag tells the CPU to add one extra the next time it adds anything, just as you'd carry the 1 when adding multi-digit base 10 numbers in first grade. Most of the time, an addition will start with the carry off; that's what the
clc does. (Clearing a flag means turning it off; setting means turning it on.)
What tepples said. We only have 10 different digits for representing numbers (0 to 9) but that doesn't mean it's impossible to count to 10: if we use more places for digits, we can represent bigger numbers.
With one digit we can only represent 10 different numbers (0 to 9), but with one more digit we have a total of 100 combinations (0 to 99: each new digit multiplies the number of possible combinations by 10). With bytes it's the same thing. A single byte can only represent 256 different numbers (0 to 255), but if we use a second byte we get a total of 65536 possible combinations (0 to 65535: each new byte multiplies the number of possible combinations by 256).
16, 32 and 64-bit CPUs make the whole thing easier (and faster) by manipulating multiples bytes at a time, while 8-bit CPUs have to do it byte by byte, but they can still perform math with big numbers.
tokumaru wrote:
I think that the problem is that your current design is still based on the NES screen and the name tables. Most people start out that way, because it's simpler to move objects around that small area, but once you start messing with scrolling, you have to see things differently: the screen and the name tables are not the containers of the objects anymore, they are merely used as a viewport, to show a representation of part of the level, and the level is the actual container of the objects.
You should forget about the screen and the name tables for a moment, and think of the level map as the basis for your game world. Your objects exist in the level, so everything about them is relative to the level map. Sprite coordinates are not restricted to 8 bits anymore, since levels can be much wider than 256 pixels. To test for collisions, you have to do some math with the object coordinates (like we discussed before), and since the sprite coordinates are in the same domain as level map coordinates, scrolling is absolutely irrelevant to collisions.
To make things easier, ideally you'd have access to the whole map, either by decompressing it to WRAM or storing it uncompressed (or compressed in a way that allows for random access) in the ROM. Having access to the complete level makes it easy to move objects around and have them collide with the level regardless of what the screen and the name tables are showing.
Don't think of scrolling as "the level going by", but rather as "a camera panning across the level". The level is stationary, but a virtual "camera" moves around in order to display different parts of the level, and it's this camera that dictates what gets written to the name tables and the OAM. You camera must have its own coordinates, which are used to convert level coordinates into screen coordinates (i.e. level coordinates - camera coordinates = screen coordinates), and you'll have to perform this conversion whenever you render sprites to OAM or metatile to the name tables.
I know it sounds complicated, but nobody said that scrolling was easy, specially if done right. If you do it the wrong way and keep everything oriented to screen coordinates, things will surely get out of hand (like the problem you are having now, where objects aren't aligning with the collision data).
Wow, Awesome! Thank you so very much tokumaru!!
Your words speak to me; they've spoken to me about this new way of thinking about the level staticly... it will all be accessable just like collision is? I'll probably see that answer when I read these important words once again. Thank you very
incredibly much for them!
! tepples wrote:
You can't fit a 16-bit number in one byte, but you can fit it in two. Here's how to do 16 bit addition, using the example of 508 + 8 = 516 ($01FC + $0008 = $0204):
Code:
position_hi = $0701
position_lo = $0700
; set the position to $01FC
lda #$01
sta position_hi
lda #$fc
sta position_lo
; now add eight ($0008) to this position
clc
lda #$08 ; add the low byte first
adc position_lo
sta position_lo
lda #$00
adc position_hi
sta position_hi
When you add two 8-bit numbers and the result is more than 256, the CPU subtracts 256 and turns on the carry flag. The carry flag tells the CPU to add one extra the next time it adds anything, just as you'd carry the 1 when adding multi-digit base 10 numbers in first grade. Most of the time, an addition will start with the carry off; that's what the
clc does. (Clearing a flag means turning it off; setting means turning it on.)
Thank you so much tepples!
This explains how to do 16 bit math on an 8 bit cpu; it will help me alot.
It has simplified and enritched the way I think about the CPU subtraction! ...And I will continue to keep 3gengames' "Set subtract. Clear add." in my head too! tokumaru wrote:
What tepples said. We only have 10 different digits for representing numbers (0 to 9) but that doesn't mean it's impossible to count to 10: if we use more places for digits, we can represent bigger numbers.
With one digit we can only represent 10 different numbers (0 to 9), but with one more digit we have a total of 100 combinations (0 to 99: each new digit multiplies the number of possible combinations by 10). With bytes it's the same thing. A single byte can only represent 256 different numbers (0 to 255), but if we use a second byte we get a total of 65536 possible combinations (0 to 65535: each new byte multiplies the number of possible combinations by 256).
16, 32 and 64-bit CPUs make the whole thing easier (and faster) by manipulating multiples bytes at a time, while 8-bit CPUs have to do it byte by byte, but they can still perform math with big numbers.
This is so excellent
and helpful to me too tokumaru... THANK YOU!!!
edit.edited again.last edit.
qbradq wrote:
How are you storing your collision data? That has a lot to do with how to scroll it
Typically I just keep a buffer of the metatiles that are on screen, and use the tile ID to index a table of flags that include collision information. To "scroll" this, you simply update the metatile buffer every time you scroll the screen to be in synch with it.
Or if you've got WRAM you can just store the entire level in RAM at once and not have to worry about scrolling the data, just the screen.
My sister doesn't like me asking questions... so I went to the nesdev wiki and searched for "wram". I learned that it stands for (
Work RAM (
Random
Access
Memory)). And then I read some of the 300 character search results and have concluded that WRAM is available on a mapper.
And after another search I learned that MMC5 is the biggest mapper on then NES. So then I went to retrousb.com and discovered that MMC5 support on the powerpak is
buggy. (I think mapper 5 is for MMC5.) And I also learned that my powerpak has enough hardware to run MMC5 games!
So now I'd like to ask yall what are some reasons I shouldn't choose and try to use MMC5 for having WRAM?
(And I'm aware of ideas that MMC5 is expensive... is it still expensive in todays market? The American economy isn't doing well... so that may be a yes. )edit: Thank you qbradq for help!
WRAM is great to have, I suggest you use it if you want it, it's cheap today. Especially for 32KB of it. But MMC5 would be bad because while it can support it, it's still a long ways from anywhere near 100% understood and nowhere near workable, it doesn't even work for most games with loopys latest release. Plus it'd be super expensive to recreate. MMC3 would offer the best features and price, and has WRAM support, I'd suggest looking at that if you need a mapper. That, UNROM, or MMC1 are the most commonly used.
Ok thank you 3gengames!!
unregistered wrote:
And then I read some of the 300 character search results and have concluded that WRAM is available on a mapper.
WRAM can be added to any cart, even NROM (i.e. no mapper) ones. The wiki has
some information about it.
Quote:
So now I'd like to ask yall what are some reasons I shouldn't choose and try to use MMC5 for having WRAM?
Because it's the most complex mapper there is. It's overkill to use such an advanced mapper just for the WRAM, while many other simpler and common mappers have support for WRAM as well. Also, the MMC5 still hasn't been fully cloned by anyone in the community, so it's not possible to make carts of new MMC5 games unless you use donor cartridges (MMC5 ones aren't even very common).
Quote:
(And I'm aware of ideas that MMC5 is expensive... is it still expensive in todays market? The American economy isn't doing well... so that may be a yes.
)
Current economy has very little to do with this. The MMC5 chip used in Nintendo cartridges simply isn't manufactured anymore, because they were never used for anything besides game cartridges, and Nintendo hasn't released an NES cart in over 15 years. The chips that are currently used to replicate mappers (CPLDs, FPGAs, etc.) become more expensive as they become more complex, so implementing an MMC5 clone will always be more expensive than implementing an MMC3 clone.
My advice is: don't chose a mapper just because of WRAM. Any cart, even NROM ones, can have WRAM. If you're gonna use a mapper, pick the simplest one that has the features you need.
tokumaru wrote:
... My advice is: don't chose a mapper just because of WRAM. Any cart, even NROM ones, can have WRAM. If you're gonna use a mapper, pick the simplest one that has the features you need.
Thanks tokumaru!
Let's say I've chosen a mapper... how can I use it? Is there something I could read to explain this?
unregistered wrote:
Let's say I've chosen a mapper... how can I use it?
The first thing to do is adjust your iNES header to specify that you're using the mapper (also change other fields in the header that might have something to do with the mapper, such as mirroring and CHR-RAM). Then you adjust your PRG and CHR (if any) banks to reflect the configuration(s) the mapper allows. This includes setting up a fixed bank, making all banks the correct size (PRG banks can be 8KB, 16KB and even 32KB depending on the mapper). Then you have to modify your initialization code to also initialize the mapper (things like setting the mirroring, the size of the banks, switching in the initial banks, etc.). Finally, you should code the routines that will make use of the mapper hardware, so you can call them whenever they're needed.
tokumaru wrote:
Then you adjust your PRG and CHR (if any) banks to reflect the configuration(s) the mapper allows. This includes setting up a fixed bank, making all banks the correct size (PRG banks can be 8KB, 16KB and even 32KB depending on the mapper).
Ok..well...the PRG has 16 k banks... . 1KB = #$400 right? So then #$1000 = 4KB right?
(Pretty sure that's correct!) ...Sorry ok, back to my PRG banks being 16KB... well, nevermind... um.
Code:
+---------------+
| { -1} |
+---------------+
What does ^ mean? I spent time reading reading about some of Nintendo's mappers. ...MMC1 is crazy and cool with 5bit registers that can only be written to by writing each bit... 5 writes bit-by-bit I think. ...I also think that I couldn't find the explianation of
{ -1}.
unregistered wrote:
Code:
+---------------+
| { -1} |
+---------------+
What does ^ mean? I spent time reading reading about some of Nintendo's mappers. ...MMC1 is crazy and cool with 5bit registers that can only be written to by writing each bit... 5 writes bit-by-bit I think. ...I also think that I couldn't find the explianation of
{ -1}.
That syntax was invented by Disch for his mapper documentation. He used "{-1}" to mean "the last bank".
The { -1} is indeed a little weird, but I guess the idea is that -1 in binary is always all bits set, so no matter how many bits you use to represent numbers and no matter how many banks there are, -1 is always the highest possible number, and thus, the last bank.
Thank you lidnariq and thank you so much tokumaru!
tokumaru wrote:
The { -1} is indeed a little weird, but I guess the idea is that -1 in binary is always all bits set, so no matter how many bits you use to represent numbers and no matter how many banks there are, -1 is always the highest possible number, and thus, the last bank.
That's brilliant!
---
Disch must have been an nes game developer... right lidnariq?
I mean how could someone figure out all Disch's notes? He could have found them in some Nintendo documentation... MMC1 is just insane!
And thanks Kasumi!
I don't know how you found those pages; they are helpful in my quest to understand
some a few of Disch's notes.
edit.
Isn't the 6502 a RISC machine?
"... an important aspect of RISC machines is that they overlap the execution of instructions." My textbook notes that one of the important aspects of RISC machines is that they overlap the execution of instructions...Mm My textbook is introducing us to
Branch Delay Slots. Does the NES have those?
: )
edit.unedit.
Some RISC architectures have delay slots after load and branch instructions because these instructions send data several stages back in the pipeline, and
reordering instructions was believed to be too expensive at the time. But 6502 has no branch delay slots, and neither does ARM. Instead, these architectures stall until the load or branch is complete before beginning the next instruction. Your textbook may be old, and that statement may have been written back when MIPS was the new hotness.
Thank you tepples!
Wow it is copy
writerighted 1993. And I'm not allowed to quote this text... so... well, I'll have to edit my earlier quote away... somehow. Boooo to them not allowing anyone to quote this book without the pr5ior written permission of the publish-people.
I didn't know this was so old
...my other textbook from this class was copy
writerighted 2004... and it is so much better, IMO. I don't know what MIPS was.
Appropriately short quotations are considered "fair use of a copyrighted work".
tepples wrote:
Appropriately short quotations are considered "fair use of a copyrighted work".
Thanks tepples.
unregistered wrote:
tokumaru wrote:
What tepples said. We only have 10 different digits for representing numbers (0 to 9) but that doesn't mean it's impossible to count to 10: if we use more places for digits, we can represent bigger numbers.
With one digit we can only represent 10 different numbers (0 to 9), but with one more digit we have a total of 100 combinations (0 to 99: each new digit multiplies the number of possible combinations by 10). With bytes it's the same thing. A single byte can only represent 256 different numbers (0 to 255), but if we use a second byte we get a total of 65536 possible combinations (0 to 65535: each new byte multiplies the number of possible combinations by 256).
16, 32 and 64-bit CPUs make the whole thing easier (and faster) by manipulating multiples bytes at a time, while 8-bit CPUs have to do it byte by byte, but they can still perform math with big numbers.
This is so excellent
and helpful to me too tokumaru... THANK YOU!!!
Well... so now I am wondering how I could use a number like 425? I am trying to make it clear that I want nametable $2000 to be replaced when I reach the last 3rd of the second nametable $2400. Can I use the number 425 for that?
I'm confused because I don't understand how our 8bit NES could think like a future 16bit machine would... you know?
An 8-bit CPU can do anything a 16-bit CPU can, it will just do it less efficiently (i.e. slower and with more instructions, because you have to handle 1 byte at a time). 425 in hex is $01A9, so $01 is the high byte, and $A9 is the low byte.
tepples wrote:
You can't fit a 16-bit number in one byte, but you can fit it in two. Here's how to do 16 bit addition, using the example of 508 + 8 = 516 ($01FC + $0008 = $0204):
Code:
position_hi = $0701
position_lo = $0700
; set the position to $01FC
lda #$01
sta position_hi
lda #$fc
sta position_lo
; now add eight ($0008) to this position
clc
lda #$08 ; add the low byte first
adc position_lo
sta position_lo
lda #$00
adc position_hi
sta position_hi
When you add two 8-bit numbers and the result is more than 256, the CPU subtracts 256 and turns on the carry flag. The carry flag tells the CPU to add one extra the next time it adds anything, just as you'd carry the 1 when adding multi-digit base 10 numbers in first grade. Most of the time, an addition will start with the carry off; that's what the
clc does. (Clearing a flag means turning it off; setting means turning it on.)
Um... so are we susposed to declare low values before high values?? Why is position_hi before position_lo?
I'm under some trouble with this code...
Code:
;6502 Simulator
.START $1000
.ORG $0000
.DB "ABC"
position_lo .DS 1
position_medium .ds 1
position_medihi .DS 1
position_hi .ds 1
oX .ds 4
.ORG $1000
; set the position to $01FC
lda #$01
sta position_hi
LDA #$01
sta position_medihi
LDA #$00
sta position_medium
LDA #$02
STA position_lo
LDa #$01
STa oX+3
LDa #$01
STa oX+2
lda #$00
sta oX+1
lda #$02
STA oX+0
; now subtract eight (#$0008) from this position
sec
lda position_lo ; subtract from the low byte first
sbc #$08
sta position_lo
LDA position_medium
SBC #$00
STA position_medium
LDA position_medihi
SBC #$00
sta position_medihi
LDA position_hi
sbc #$00
STA position_hi
; now subtract eight (#$0008) from this position
sec
lda oX+0 ; subtract from the low byte first
sbc #$08
sta oX+0
LDA oX+1
SBC #$00
STA oX+1
LDA oX+2
SBC #$00
sta oX+2
LDA oX+3
sbc #$00
STA oX+3
Ok well that is my code I've explored in the 6502 Simulator from tokumaru. The only thing is that at the top of it I had to reverse this code.
position_hi .DS 1
position_medihi .ds 1
position_medium .DS 1
position_lo .ds 1I started with that because tepples made his code up there very tricky... I think...
because he started with
Code:
position_hi = $0701
position_lo = $0700
and that has been confusing me. Should
position_lo be above
position_hi?
It's ok, cause I feel that
position_lo is above
position_hi. I want to help others not to have the confusion that I had. : )
The 6502 deals with numbers low byte first. This means something like jmp $8D9A is assembled to $4C(opcode for the jmp instruction) $9A (low byte) $8D (high byte).
It may appear to be stored "backwards" to you, but it represents the exact same value. That's just how the CPU does things.
The PPU appears to like the high byte first. You write $8D to $2006, and then $9A to $2006. $2007 would be geared to write to $8D9A, not $9A8D. On one console, you get to deal with with both types of
endianness!If the 6502 is going to read a 16 bit value as an address or pointer, it has to be two contiguous bytes, low byte first.
But... any other time (like adding to an objects position), it doesn't matter.
consider this code:
Code:
highbyte = $04
lowbyte = $EF
lda lowbyte
clc
adc #$08
sta lowbyte
lda highbyte
adc #$00
sta highbyte
You'd get the expected high byte in the RAM location named highbyte, and the expected low byte in the RAM location named lowbyte. They don't even have to be contiguous.
I tend to store things high byte first because it's much easier to read when I'm peeking at the RAM in a debugger. It doesn't matter one way or the other except with pointers and addresses, which the 6502 will always read in its own way.
Just be consistent in your own code, and do what makes sense for you. I hope this post didn't serve to confuse you more.
edit: Wait. Is this the heart of the matter that you expect ox, ox+1, ox+2, and ox+3 to have the same values in the same order as position_hi etc?
Reversing those .ds statements for position_hi etc. wouldn't change the represented value at all. Even if they appeared in the "right" order after reversal, they represent the same thing either way. You could even do this:
Code:
position_hi .DS 1
oX .ds 4
position_medium .DS 1
position_lo .ds 1
position_medihi .ds 1
The bytes that correspond with each other would still be equal, and together they would represent the same number, just in a different location in RAM. (which again, only matters for addresses and pointers.)
edit2: nevermind for what was once here. Not reading closely
Kasumi wrote:
The PPU appears to like the high byte first. You write $8D to $2006, and then $9A to $2006. $2007 would be geared to write to $8D9A, not $9A8D. On one console, you get to deal with with both types of
endianness!If the 6502 is going to read a 16 bit value as an address or pointer, it has to be two contiguous bytes, low byte first.
But... any other time (like adding to an objects position), it doesn't matter.
consider this code:
Code:
highbyte = $04
lowbyte = $EF
lda lowbyte
clc
adc #$08
sta lowbyte
lda highbyte
adc #$00
sta highbyte
You'd get the expected high byte in the RAM location named highbyte, and the expected low byte in the RAM location named lowbyte. They don't even have to be contiguous.
I'm dumb I dont understand what you are saying to me.
Won't they be contiguous (near each other) since highbyte is right above lowbyte at the top of your code there?
: )
Kasumi wrote:
edit: Wait. Is this the heart of the matter that you expect ox, ox+1, ox+2, and ox+3 to have the same values in the same order as position_hi etc?
Reversing those .ds statements for position_hi etc. wouldn't change the represented value at all. Even if they appeared in the "right" order after reversal, they represent the same thing either way. You could even do this:
Code:
position_hi .DS 1
oX .ds 4
position_medium .DS 1
position_lo .ds 1
position_medihi .ds 1
The bytes that correspond with each other would still be equal, and together they would represent the same number, just in a different location in RAM. (which again, only matters for addresses and pointers.)
Yes the different location in RAM
was is important to me. I was trying to understand how the code would work using the oX+0 oX+1 oX+2 and oX+3. More on that in a bit.
unregistered wrote:
I'm under some trouble with this code...
Code:
;6502 Simulator
.START $1000
.ORG $0000
.DB "ABC"
position_lo .DS 1
position_medium .ds 1
position_medihi .DS 1
position_hi .ds 1
oX .ds 4
.ORG $1000
; set the position to $01FC
lda #$01
sta position_hi
LDA #$01
sta position_medihi
LDA #$00
sta position_medium
LDA #$02
STA position_lo
LDa #$01
STa oX+3
LDa #$01
STa oX+2
lda #$00
sta oX+1
lda #$02
STA oX+0
; now subtract eight (#$0008) from this position
sec
lda position_lo ; subtract from the low byte first
sbc #$08
sta position_lo
LDA position_medium
SBC #$00
STA position_medium
LDA position_medihi
SBC #$00
sta position_medihi
LDA position_hi
sbc #$00
STA position_hi
; now subtract eight (#$0008) from this position
sec
lda oX+0 ; subtract from the low byte first
sbc #$08
sta oX+0
LDA oX+1
SBC #$00
STA oX+1
LDA oX+2
SBC #$00
sta oX+2
LDA oX+3
sbc #$00
STA oX+3
See what I was saying...? I reversed the order of the section of memory declarations and the RAM inverted... it was the same answer but the order of the values in RAM was upside down... and that had been confusing me... though now I understand. unregistered wrote:
Ok well that is my code I've explored in the 6502 Simulator from tokumaru. The only thing is that at the top of it I had to reverse this code.
position_hi .DS 1
position_medihi .ds 1
position_medium .DS 1
position_lo .ds 1I started with that because tepples made his code up there very tricky... I think...
because he started with
Code:
position_hi = $0701
position_lo = $0700
and that has been confusing me. Should
position_lo be above
position_hi?
See reversing the order of the declarations reversed my view of the memory. It was the same answer though, you are right, thank you for pointing that out! I really appreciate that!
I've learned a lot thinking about all of this. The coolest thing I've learned is that little-endianness makes sense when using one variable name for multiple bytes. You start with the lowest value... (i'm going to use oX for my variable name) oX+0. Ok, then as the
number increases the importantance increases!!! oX+
1 oX+
2 oX+
3.... that is AWESOME!
I'm happy Nintendo chose to use little-endianness! "Little-endian" won't confuse me as much anymore!
And thank you Kasumi for your endianness link!
I'm not extreemly excited about wikipedia though.
Kasumi wrote:
edit2: nevermind for what was once here. Not reading closely
Kasumi, it's ok
I don't remember reading it; I found this second edit when I got up this morning.
edit.
With the order of the RAM things (Your first reply) it doesn't matter where the bytes of RAM are, you just have to add them and store them to the right places. I keep all mine next to each other, but they don't have to be. The processor just look at where to load and store, which you decide. It doesn't care if it's right next to each other or not. Now when you use an addressing mode like [ZeropageLocation],Y, then you DO have to have them next to each other (Low byte first since it's little endian) because then it does "care" about where they're at because the processor is hardwired to take the zero page location, then the zeropage location+1 the next time! When you're working with single bytes as 16-bit values, you don't have to keep them together because you probably aren't using the data with an addressing mode or anything like that.
Thanks 3gengames for explaining my confusion away!
I understand
Kasumi more now.
I want to say that I edited my post up there twice because I came back to my computer and wrote that
colored part in the middle. Then I submitted it without making it colored for adding/editing it.
edit.
Quote:
I'm dumb I dont understand what you are saying to me.
Won't they be contiguous (near each other) since highbyte is right above lowbyte at the top of your code there?
: )
To expand on this point a little, there are things in your code that don't end up in your actual rom. For instance, labels. Labels actually take up zero space by themselves.
Code:
.org $8D9A
label1:
label2:
label3:
label4:
label5:
;etc, etc
You could put a million random labels in your code, and your program would (or at least should) be assembled exactly the same. They only take up space when they're used in an instruction. What a label does is names an address for you automatically.
I explained how jmp $8D9A was assembled in the last post. In the above code, jmp label1, jmp label2, jmp label3 etc would all assemble the same as jmp $8D9A. Because even though label2 is after label1, label1 doesn't take up any space. There are no instructions that do take up space between them, so they refer to the same location.
When you add code, it makes every label after the change need a new address. But all the code you previously made (in most cases) will work the same, despite the address move. Similarly, you can switch where the RAM your program is using is, and (in most cases) it will also work the same. That's what I was trying to demonstrate. It doesn't matter what order your DS statements are in.
Code:
highbyte = $04
lowbyte = $EF
Above, highbyte and lowbyte don't refer to contiguous RAM. (Like $04 and $05, or $EF and $F0). The fact they they're next to each other in your code doesn't matter.
You could add a million text = $whatever in your code and it shouldn't affect how your rom is assembled. My assembler (and I assume others as well), doesn't care where they are in the slightest. I could even do something like this:
Code:
lda #mario
mario = $04;Right in the middle of the code!
clc
adc #$08
And it would assemble the same as if mario = $04 was anywhere else in the code.
If the Z flag is 0, then A <> NUM and BNE will branch
Please help me understand what <> means?
unregistered wrote:
what <> means
Different.
tokumaru wrote:
unregistered wrote:
what <> means
Different.
THANK YOU TOKUMARU!!!
Kasumi wrote:
Quote:
I'm dumb I dont understand what you are saying to me.
Won't they be contiguous (near each other) since highbyte is right above lowbyte at the top of your code there?
: )
To expand on this point a little, there are things in your code that don't end up in your actual rom. For instance, labels. Labels actually take up zero space by themselves.
Code:
.org $8D9A
label1:
label2:
label3:
label4:
label5:
;etc, etc
You could put a million random labels in your code, and your program would (or at least should) be assembled exactly the same. They only take up space when they're used in an instruction. What a label does is names an address for you automatically.
I explained how jmp $8D9A was assembled in the last post. In the above code, jmp label1, jmp label2, jmp label3 etc would all assemble the same as jmp $8D9A. Because even though label2 is after label1, label1 doesn't take up any space. There are no instructions that do take up space between them, so they refer to the same location.
When you add code, it makes every label after the change need a new address. But all the code you previously made (in most cases) will work the same, despite the address move. Similarly, you can switch where the RAM your program is using is, and (in most cases) it will also work the same. That's what I was trying to demonstrate. It doesn't matter what order your DS statements are in.
Code:
highbyte = $04
lowbyte = $EF
Above, highbyte and lowbyte don't refer to contiguous RAM. (Like $04 and $05, or $EF and $F0). The fact they they're next to each other in your code doesn't matter.
You could add a million text = $whatever in your code and it shouldn't affect how your rom is assembled. My assembler (and I assume others as well), doesn't care where they are in the slightest. I could even do something like this:
Code:
lda #mario
mario = $04;Right in the middle of the code!
clc
adc #$08
And it would assemble the same as if mario = $04 was anywhere else in the code.
Thank you Kasumi. I kindof guessed that was true
("that" is a responce to your first sencence. After debugging with FCEUX for a whlile I noticed that my labels in the code were absent. It made me guess what you are talking about - I remember learning that labels dissappeared after assembly in one of my CS assembly @ut college courses.)...
XFactor is coming on... I must go see and listen! ... but didn't know for sure
("didn't know for sure" is a response to your statement about how labels take up no space. All of your explaination made sense to me; it kind of solidified it all in my head-thanku! ).
Kasumi, Tthanks for your detailed explanations!
edit.
Code:
7 bit 0
---- ----
Rxxx xxxD
| |
| +- Data bit to be shifted into shift register, LSB first
+--------- 1: Reset shift register and write Control with (Control OR $0C),
locking PRG ROM at $C000-$FFFF to the last bank.
When I come across something like this when looking through nes mappers on the nesdev wiki I always become confused. It looks like there is something there to write in a byte. But the bin is 8kb or something like that... and so I wonder if every byte in those 8kb should be setup in that form... but then I think that doesn't make sense... why would they require every byte to be like that?
That's MMC1. Mapper 1. It uses four 5-bit registers. You write the registers 1 bit at a time. Bit 7 is the reset bit and automatically assigns a few registers the default way. But you write the 5 bits of each register with data bit 0. You basically LDA with a number less then $80 (Because 80 will have bit 7 set, and will reset it, you don't want that when programming it) and you basically just STA (Memory region of the register you want to write: $8xxx,$Axxx,$Cxxx,$Exxx) and LSR.
Code:
...
LDA #$[ValueToWriteMMC1]
STA $8000 ;1st write
LSR A
STA $8000 ;2nd write
LSR A
STA $8000 ;3rd write
LSR A
STA $8000 ;4th write
LSR A
STA $8000 ;Register NOW is programmed and updated.
...
For MMC1, the last write also finally selects which register to update, so you can write any places you want above $8000 as long as the last write goes where you want it.
unregistered wrote:
the bin is 8kb or something like that
What bin are you talking about?
Anyway, those schematics show you how the mapper interprets writes to its registers, they have nothing to do with how data is set up. You know when you write to $2000 (one of the NES registers) and some bits select the active name table, another bit selects whether NMIs will happen, and so on? Mapper registers are just like that too. In the case of the MMC1, you have to write to the registers like 3gengames said.
tokumaru wrote:
unregistered wrote:
the bin is 8kb or something like that
What bin are you talking about?
Sorry... bank... an 8kb or a 16kb bank.
Lets say we are trying to use an 8kb register that is listed as $C000-$DFFF. How do you use it... write to it... it starts at $C000 so I could set that register with something like
Code:
lda #00001011
sta $C000
and then how could I write 8kb of data to $C000-$DFFF?
unregistered wrote:
Lets say we are trying to use an 8kb register
There's no such thing as an "8kb register".
Quote:
that is listed as $C000-$DFFF.
This just means that the register can be accessed in any address between $C000 and $DFFF: writing to $CF8B is the same thing as writing to $DD0F, it doesn't matter. The register is mirrored across that range of memory, but it's still 1 register.
Quote:
How do you use it... write to it... it starts at $C000 so I could set that register with something like
Code:
lda #00001011
sta $C000
That's exactly it, but you could have used any address between $C000 and $DFFF.
Quote:
and then how could I write 8kb of data to $C000-$DFFF?
There's no need to do this.
It's not right, you have to shift and store it 5 times, remember that! And only the last right matters, but don't worry about that, just write the register you need 5 times.
But yeah, what does copying 8KB of data to ROM? You can't over write ROM. It won't do anything. To switch more ROM data in, it basically looks at the address lines when read, and switches them! So if the ROM is in 8000-BFFF, it will "spit out" the new data that goes in that area. When it's C000-FFFF, it will spit out whatever it is assigned to put out there. that's how mappers work. All their locations they change is different, though. This example is for an MMC1, which seperates it into a 16KB "data" bank and then a 16KB "fixed" bank that can't be moved.
Also, worth noting: when mappers are present, they "consume" the WRITES to the ROM. When read, the mapper doesn't do anything except let the CPU look at the ROM.
Thank you tokumaru and 3gengames!
3gengames wrote:
It's not right, you have to shift and store it 5 times, remember that!
I was talking about generic register writes, but yeah, since the MMC1 only takes 1 bit of data at a time you have to make multiple writes.
I think unregistered is REALLY confused about how bankswitching works... Let me try to explain it. The 6502 can only see 64KB of memory, because it only has 16 address lines (2^16 = 65536). Only half of that is used to access the ROM, because the other half is used to access RAM and internal registers. 32KB of ROM is not much, and as games became more complex they needed more space.
You can't make the NES see more memory than the 6502 allows, but you can mix and match smaller banks of ROM and make them visible in the available memory range. You'll still see only 32KB at any given time, but you can map different parts of a bigger ROM into those 32KB. Each mapper has different bank sizes and different rules, but one thing that doesn't change is that you need to tell the mappers which banks to put where, and you do that by writing to their registers.
When you try to write to PRG-ROM ($8000-$FFFF) the mapper intercepts the writes and redirects them to one of its registers, depending on which address was used for the write. Simple mappers (CNROM, UxROM, AxROM, BxROM, etc.) only have one register, so you can access it by writing to any address between $8000-$FFFF. Mappers with more registers break up that space into multiple ranges where the different registers can be accessed.
tokumaru wrote:
3gengames wrote:
It's not right, you have to shift and store it 5 times, remember that!
I was talking about generic register writes, but yeah, since the MMC1 only takes 1 bit of data at a time you have to make multiple writes.
I think unregistered is REALLY confused about how bankswitching works... Let me try to explain it. The 6502 can only see 64KB of memory, because it only has 16 address lines (2^16 = 65536). Only half of that is used to access the ROM, because the other half is used to access RAM and internal registers. 32KB of ROM is not much, and as games became more complex they needed more space.
You can't make the NES see more memory than the 6502 allows, but you can mix and match smaller banks of ROM and make them visible in the available memory range. You'll still see only 32KB at any given time, but you can map different parts of a bigger ROM into those 32KB. Each mapper has different bank sizes and different rules, but one thing that doesn't change is that you need to tell the mappers which banks to put where, and you do that by writing to their registers.
When you try to write to PRG-ROM ($8000-$FFFF) the mapper intercepts the writes and redirects them to one of its registers, depending on which address was used for the write. Simple mappers (CNROM, UxROM, AxROM, BxROM, etc.) only have one register, so you can access it by writing to any address between $8000-$FFFF. Mappers with more registers break up that space into multiple ranges where the different registers can be accessed.
Ttokumaru, thanks so much for all this info! I can't reply more until Monday.
edit: Sorry tokumaru.
tokumaru wrote:
I think unregistered is REALLY confused about how bankswitching works... Let me try to explain it.
Thank you so much tokumaru!!
tokumaru wrote:
When you try to write to PRG-ROM ($8000-$FFFF) the mapper intercepts the writes and redirects them to one of its registers, depending on which address was used for the write. Simple mappers (CNROM, UxROM, AxROM, BxROM, etc.) only have one register, so you can access it by writing to any address between $8000-$FFFF. Mappers with more registers break up that space into multiple ranges where the different registers can be accessed.
If we use mmc4 which registers does a sprite file use? 3 and 4? It wouldn't want to switch between the sprite and the background file when one of the tiles from rows e and d are used, would it? Does 0fd0 - 0fdf refer to row d of the 4k chr file? If we want to use the same chr file in register 1 and tile d1 is read, does it create a problem with latch 0... or does it stay with the same chr file?
Pick MMC3 for IRQ's, or MMC1/UNROM and do sprite 0 hit to find where you need to swap the graphics, as MMC4 isn't easy to prototype on as there's only one game that uses it, and you really don't need it's features unless you're making a game like they did. Basically what you do is do a sprite 0 hit or NMI, then switch the graphics to the appropriate bank for the next scanline to use in the status bar or something.
3gengames wrote:
Pick MMC3 for IRQ's, or MMC1/UNROM and do sprite 0 hit to find where you need to swap the graphics, as MMC4 isn't easy to prototype on as there's only one game that uses it, and you really don't need it's features unless you're making a game like they did. Basically what you do is do a sprite 0 hit or NMI, then switch the graphics to the appropriate bank for the next scanline to use in the status bar or something.
Thank you 3gengames!
---
Ok here are 2 more questions:
1.)If we use 8kb CHR files... how do you select a tile in the second 4kb half? NESst uses 00 for the first tile of both A and B.
2.)When using MMC1 would it be hard (too slow)... for the cpu to use both 4 and 8 kb files?
On MMC1 you can just write the lower or higher graphics page register to the 4KB graphic page you want, swapping it all out. And as for no mapper using another tile from the opposite side, you can't on the background. You have to either switch the table the graphics use, or swap in the other graphics.
Make sense?
unregistered wrote:
1.)If we use 8kb CHR files... how do you select a tile in the second 4kb half?
The background can only use tiles from one of the pattern tables at a time... If you need more than 256 tiles for the background you either have to use the MMC5 (not recommended) or split the screen (with an IRQ or sprite 0 hit) and tell the background to start using tiles from the other pattern table.
Sprites can use patterns from both pattern tables if they're set to the size of 8x16 pixels.
Quote:
When using MMC1 would it be hard (too slow)... for the cpu to use both 4 and 8 kb files?
You should stop thinking about "files". You use files to build the ROM, but once the ROM is ready it's just a bunch of bytes, and the NES has no concept of files whatsoever.
The MMC1 doesn't do anything to help with using more than 256 tiles for the background. It has the capability of bankswitching 4KB or 8KB chunks, but that doesn't affect what the NES can use for backgrounds.
How am I to use the 16kb prg switching part of the memory managenent controller successfully? I understand how the switching works thanks to Matthew J. Richey's document! I am missing knowledge of how to create my .nes rom file program to use a MMC. How do I prepare my code to be used in bank 2 or bank 3?
3gengames wrote:
On MMC1 you can just write the lower or higher graphics page register to the 4KB graphic page you want, swapping it all out. And as for no mapper using another tile from the opposite side, you can't on the background. You have to either switch the table the graphics use, or swap in the other graphics.
Make sense?
Yes, I think so. Thanks 3gengames.
tokumaru wrote:
unregistered wrote:
1.)If we use 8kb CHR files... how do you select a tile in the second 4kb half?
The background can only use tiles from one of the pattern tables at a time... If you need more than 256 tiles for the background you either have to use the MMC5 (not recommended) or split the screen (with an IRQ or sprite 0 hit) and tell the background to start using tiles from the other pattern table.
This helped me to better understand what 3gengames wrote!
Thanks tokumaru!
tokumaru wrote:
Quote:
When using MMC1 would it be hard (too slow)... for the cpu to use both 4 and 8 kb files?
You should stop thinking about "files". You use files to build the ROM, but once the ROM is ready it's just a bunch of bytes, and the NES has no concept of files whatsoever.
The MMC1 doesn't do anything to help with using more than 256 tiles for the background. It has the capability of bankswitching 4KB or 8KB chunks, but that doesn't affect what the NES can use for backgrounds.
Excellent!! Now I'm fixed away from my past thoughts; thank you so much tokumaru!
Kasumi, on page 56, wrote:
Ok I added this code at the end of my fixed PRG bank:
Code:
.pad $FFF0
reset_stub:
sei
ldx #$FF
txs ; set the stack pointer
stx $8000 ; reset the mapper
jmp reset ; must be in $C000-$FFED
.word vblank, reset_stub, irq
and it built my nes file... but, then I tried to add the switchable PRG bank to my code:
Code:
.org $8000 ;empty switchable PRG bank
.pad $BFF0
reset_stub:
sei
ldx #$FF
txs ; set the stack pointer
stx $8000 ; reset the mapper
jmp reset ; must be in $C000-$FFED
.word vblank, reset_stub, irq
but it, asm6, told me that "...g.asm(596): Label already defined." (Line 596 of that ...g.asm file is simply "reset_stub:".) I do understand what the error asm6 was talking about.
But, I don't understand how too prevent that error
when I am told to:
To make sure your code works on all MMC1 revisions,
put the following code in the last 16 bytes of each 16384 byte bank. (Barbie uses almost identical code.)
Code:
reset_stub:
sei
ldx #$FF
txs ; set the stack pointer
stx $8000 ; reset the mapper
jmp reset ; must be in $C000-$FFED
.addr nmiHandler, reset_stub, irqHandler
I'd say make it a macro if you can. I myself just made 16 global labels, numbered MMC1Boot00-MMC1Boot15 so it wouldn't duplicate.
And you could also make it JMP [$FFFC], just to do it. But that's perfectly fine, but you can either make 16 global labels or a macro, both options will get you around that.
I use temporary labels (add a "+" or "-" to the beginning, depending on the location of the code that uses those labels) for labels I must repeat in various banks.
3gengames wrote:
I'd say make it a macro if you can. I myself just made 16 global labels, numbered MMC1Boot00-MMC1Boot15 so it wouldn't duplicate.
And you could also make it JMP [$FFFC], just to do it. But that's perfectly fine, but you can either make 16 global labels or a macro, both options will get you around that.
Thank you 3gengames!!
tokumaru wrote:
I use temporary labels (add a "+" or "-" to the beginning, depending on the location of the code that uses those labels) for labels I must repeat in various banks.
Thanks tokumaru!!
I have created my 16 files... each one has that code at the bottom
Code:
;PRG bank 15
.org $8000
.pad $BFF0 ;Fills bank15 with 0s.
reset_stub15:
sei
ldx #$FF
txs ; set the stack pointer
stx $8000 ; reset the mapper
jmp reset ; must be in $C000-$FFED
.word vblank, reset_stub15, irq
Hope that could work. Will try to build my nes file after lunch!
edit: Lunch was nice. So I've made 16 files... but how do I include each file? Do I need to write code like this
Code:
.org $8000 ;empty switchable PRG bank
.include "mmc1_bank0.asm" if switchable bank = 0
.include "mmc1_bank1.asm" if switchable bank = 1
...etc
.include "mmc1_bank15.asm" if switchable bank = 15
How could that be done?
You don't have to write various reset stubs with repeated code. That's redundant and prone to errors. You can make an include file with the stub, and call the label -reset_stub, so that the assembler doesn't complain about repeated labels, and then for the vectors you can just use .word vblank, -reset_stub, irq, still inside the include file, to completely get rid of redundancy. Just include that file at the end of every bank.
In my MMC1 projects I just had 32 different files. One for each half of the 16KB banks. I then just included them all in the main Program.asm file. But yeah, maybe use a Macro. Just so you don't have to change 16 boot programs if you want to change them.
Back when I was looking for a good way to handle multiple banks in ASM6 I tried using macros, but something went wrong, I don't remember what... Maybe it wasn't possible to reference labels defined inside macros or something like that... Whatever it was, I ended up making one include file to include in all switchable banks, like I explained above... it works great and is easy to maintain (if I need to modify the stub I only have to change 1 file, not 16 or 32!).
My ROM is having problems. In my code... there's this
Code:
; Actual program code. Our fixed bank is our last bank and the
; origin is $C000.
.org $C000
reset: sei
cld
; Wait two VBLANKs.
- lda $2002
bpl -
; Clear out RAM.
lda #$00
ldx #$00
- sta $000,x
sta $100,x
;sta $200,x ;Usually, RAM page 2 is used for the display list to be copied to OAM. OAM needs to be initialized to $EF-$FF, not 0, or you'll get a bunch of garbage sprites at (0, 0).
sta $300,x
sta $400,x
sta $500,x
sta $600,x
sta $700,x
inx
bne -
; Reset the stack pointer.
ldx #$FF
txs
; Disable all graphics.
inx ;lda #$00
stx $2000
stx $2001
- lda $2002
bpl - ;finish waiting for second vblank
jsr init_graphics
jsr init_input
jsr init_sound
; Set basic PPU registers. Load background from $0000,
; sprites from $1000, and the name table from $2000.
lda #%10001000
sta $2000
lda #%00011110
sta $2001
;matthew's init RAM
lda #$03
sta $0003 ;oX
sta $0006 ;oY
sta $0028 ;PointX
sta $002A ;PointY
lda #$00
sta $001d ;CameraX (object)
sta $001e ;CameraX+1
sta $002D ;ofracorhiX+0
sta $002E ;ofracorhiX+1
sta $002F ;ofracorhiY+0
sta $0030 ;ofracorhiY+1
sta tC
lda #$00
sta currNameTable
sta Next
sta Current
cli
;----------------------------END OF RESET----------------------------------
; Transfer control to the GAME LOGIC routines.
;initialize the flag to "false"
lda #$00
sta FrameReady
MainLoop:
;DO THE GAME LOGIC HERE (i.e. movement, collisions, etc.)
jsr react_to_input
jsr collisionU
ldx aFrame
jsr draw_sprite
sta $ff
;indicate that the frame calculations are done
dec FrameReady
WaitForUpdates:
;wait for the flag to change
bit FrameReady
bmi WaitForUpdates
jmp MainLoop
My init mapper routine is run at the top of init_graphics. When I open up my file in FCEUX 2.1.5 the screen is grey. My code appears at $8000 instead of $C000 like it should... at least it says that in FCEUX's debugger. Why?
Shiru, I know that is a big section of code that I'm sharing... I feel that it has already been debugged; it's there again though because I'm at a gray screen again.
Did you store $80+ to $8000+? You have to do that to make sure all MMC1's reset and put the code in $C000 page.
Also, why do you LDA #$00 in mathews init? As you cleared them before. Also, make them zeropage values to save a cycle each, as you write to a full address. Lastly, why not clear RAM, set the stack, etc. in one shot, then wait for vblanks? Last idea: use the first two bytes of RAM and clear RAM from $7FF down? You save a few bytes, and the speed doesn't matter since you're waiting for the PPU.
But yeah, if you reset your mapper right, I dunno what else to say....
3gengames wrote:
Did you store $80+ to $8000+? You have to do that to make sure all MMC1's reset and put the code in $C000 page.
Yes I did.
3gengames wrote:
Lastly, why not clear RAM, set the stack, etc. in one shot, then wait for vblanks
I thought clearing RAM, and setting the stack... all of that should be done between waiting for vblanks. But thanks 3gengames thatt does make sense!
I'll change that.
3gengames wrote:
Last idea: use the first two bytes of RAM and clear RAM from $7FF down? You save a few bytes, and the speed doesn't matter since you're waiting for the PPU.
But yeah, if you reset your mapper right, I dunno what else to say....
I think maybe it has something to do with the fact that I didn't include my 16 banks of memory... the code does follow tokumaru's idea. But I'm not sure where to place those
.includes.
Well you can just include all the files in order in one main file. Have each file from there just assign all the right locations. Or you could do it in the main file. Sorry, never messed with ASM6 deep enough to know how it works well enough to help. I'm sure somebody will post an example program or something to show ya though.
...it doesn't make sense to me... like there are 16 banks of memory but they arent all used at the same time. You have to write what bank you want to pick to the last 4 bits of the internal prg-bank. But, how am I susposed to .include those banks so they are like that?
You include them in order. I dunno, usually there's a .bank directive or something too. You did look at MMC1 ASM6 template didn't you? Or UNROM? They're on the forums here.
3gengames wrote:
You include them in order. I dunno, usually there's a .bank directive or something too. You did look at MMC1 ASM6 template didn't you? Or UNROM? They're on the forums here.
No, I didn't know there's a MMC1 ASM6 template... searched and found only your post here with "MMC1 ASM6 template" highlighted.
In ASM6 you can simply put the switchable banks one after the other, you just have to use the
.base directive to "reset" the program counter at the start of every bank (you can use .base instead of .org then). I know it isn't the exact same as MMC1, but
my UNROM template should make it clear how to organize a ROM with multiple switchable banks. It's basically missing the reset stub, but we've already estabilished how that works.
tokumaru wrote:
In ASM6 you can simply put the switchable banks one after the other, you just have to use the
.base directive to "reset" the program counter at the start of every bank (you can use .base instead of .org then). I know it isn't the exact same as MMC1, but
my UNROM template should make it clear how to organize a ROM with multiple switchable banks. It's basically missing the reset stub, but we've already estabilished how that works.
EXCELLENT!! THANK YOU SO MUCH TOKUMARU!!! And thanks 3gengames for all of your friendly help too... I almost made it to the UNROM asm6 template you were guiding me to!
---
3gengames, I'm sorry for my last reply to you... it isn't kind... my mistake.
Haha no mistake, I thought there were MMC1 and UNROM templates, guess there's only one.
YEAY!!!! OUR MAPPER WORKS!!! Now to continue with scrolling beyond two screens! Well... there's one problem... the screen changes to black at the start of scrolling. In FCEUX's "Name Table Viewer" it replaces the first nametable with the third screen at the exact same time the screen goes black. I guess that's my mistake; plan to fix
tomorrow soon!
edit.
With PPUCTRL register $2000... bits 0 and 1... The
nesdev wiki tells us that the X value bit is bit 0... and I'm only scrolling horrizontally so I only need to worry about bit 0. When switching in a new screen after scrolling to the end, do I need to to invert that bit 0? When bit 0 is true the wiki says the x-corridnate is incremented by 256. Which makes sense because the screen is just 256 pixels wide.
When you reach the end of nametable 1 and you set bit 0 to true... then it is time to replace nametable 0 with the
next screen. Is that correct?
Code:
[ 0 ][ 1 x]
Definitions:
The x spot at the end of nametable 1The next screen you would begin to see after scrolling to the right.
Yep, basically think of it as "Add 256 to the scroll"and that's how it works.
So when you get to scroll=256, you have to put X to zero, then set the bit. That moves it to the next nametable as the default that you then scroll within.
3gengames wrote:
Yep, basically think of it as "Add 256 to the scroll"and that's how it works.
So when you get to scroll=256, you have to put X to zero, then set the bit. That moves it to the next nametable as the default that you then scroll within.
^thank you 3gengames, when I didn't care anymore and just followed your advice it resulted in being able to keep scrolling between nametable 1 and 2.
---
Now my meandering remindes me of that gif that tepples posted that shows the game safely adding the next nametable collum by collum. Is that how I should be coding now? My goal is to safely scroll into the next new nametable.
...I guess the answer is yes. Does this sound correct:
Code:
set PPU so that it draws in a collum
- while p1 is pressing right on dpad
while there is no place to scroll to and our collum is incomplete
draw next pair of metatiles
scroll a pair of metatiles over
branch to -
Sort of. The "p1 is pressing right on dpad" controls the location of a character in the game world, and a camera data structure follows the location of this character. When the camera moves, you draw the metatiles onto which the camera is moving.
tepples wrote:
Sort of. The "p1 is pressing right on dpad" controls the location of a character in the game world, and a camera data structure follows the location of this character. When the camera moves, you draw the metatiles onto which the camera is moving.
Can you draw metatiles at anytime? I am worrying about the screen changing to black when I try to write nametable data. I should try to write during vblank, right?
Correct. Because the NES PPU lacks the write FIFO of the SMS VDP and TG16 VDC, writes to the nametables must happen during vertical blanking or forced blanking. Prepare the writes in a RAM buffer during draw time and then copy them to VRAM during vblank.
tepples wrote:
Correct. Because the NES PPU lacks the write FIFO of the SMS VDP and TG16 VDC, writes to the nametables must happen during vertical blanking or forced blanking. Prepare the writes in a RAM buffer during draw time and then copy them to VRAM during vblank.
Alright! Thank you tepples!
unregistered wrote:
tepples wrote:
Sort of. The "p1 is pressing right on dpad" controls the location of a character in the game world, and a camera data structure follows the location of this character. When the camera moves, you draw the metatiles onto which the camera is moving.
Can you draw metatiles at anytime?
I am worrying about the screen changing to black when I try to write nametable data. I should try to write during vblank, right?
Yesterday I learned that my
attempts at writing nametable data are being done during vblank. I wasn't aware of the fact that my scrolling code appears at the end of my vblank code. So, why does the screen turn black? Do you have any ideas?
edit: Ok... I remember this ...tokumaru, on page 43, wrote:
Setting the scroll should ALWAYS be the very last thing in your VBlank handler.
Well move it to the beginning of vblank? And are you updating your scroll after you do all nametable/$2006+$2007 writes? Is the scroll right? Are you catching when you have to write attributes to the new nametable?
3gengames wrote:
are you updating your scroll after you do all nametable/$2006+$2007 writes? Is the scroll right? Are you catching when you have to write attributes to the new nametable?
I have set the PPU to increment by 32, after each write, and it messed up my screen and attribute tables. So I changed it back. ...Is it possible to set $2000 twice in the same vblank?
That scares me.
unregistered wrote:
I have set the PPU to increment by 32, after each write, and it messed up my screen and attribute tables. So I changed it back. ...Is it possible to set $2000 twice in the same vblank?
Yes. Only the last write takes effect. Because the scroll position ($2000 and $2005) and the VRAM writing address ($2006) use the same variable inside the PPU, you need to rewrite that variable after you finish uploading nametable changes. Write the scroll position to $2005, write the upper bits of X and Y scroll, pattern table addresses, and sprite size to $2000, and turn rendering on in $2001.
Is PPUCTRL ($2000) register's bit 7 always susposed to be a one? Why would I ever need to not want an
NMI to occur at the start of vblank? I'm just trying to understand $2000 better right now.
If I were writing a bunch of tiles to $2007 (for say... drawing the initial screen after loading a level), and had rendering disabled I'd turn off NMIs. They would make the operation take slightly longer, because the NMI would have to run every frame while I'm writing the tiles instead of never. You could write the NMI routine to detect when you're doing this and immediately return in that case, but even then it's slower than not running at all and you have to code more.
Do my yellow additions below make sense? Or am I missing something?
Kasumi wrote:
If I were writing a bunch of tiles to $2007 (for say... drawing the initial screen after loading a level), and had rendering disabled I'd turn off NMIs. They would make the operation take slightly longer,... because then the NMI would have to run every frame, while I'm writing the tiles, instead of it never running at all. You could write the NMI routine to detect when you're doing this and immediately return in that case, but even then it's slower than not running at all and you have to code more.
When the $2000 register has bit 7 set, it fires a NMI at VBlank. If it is not, it does not fire an IRQ, so your program will do whatever it does 100% uninterrupted.
Keep in mind, if you disable it, do something, and you come back and want to wait a frame, BIT $2002 to be safe to enable the screen in VBlank again. If an NMI happened when they're disabled, and you enable them, it'll automatically fire an NMI regardless of if it's in VBlank or not, which would mean your vblank would then writes to Palette, VRam, PPUCtrl, PPUMask, etc all during rendering, and that's bad!
unregistered: Seems about right. If I already have rendering disabled and am updating the PPU outside the NMI, having the NMI interrupt this task is kind of useless. Is that more clear? Well... maybe not totally useless. If you have a total playtime counter and use the NMI to time it, you'd still want them enabled.
3gengames wrote:
it's automatically fire an NMI regardless of if it's in VBlank or not, which would mean your vblank would then writes to Palette, VRam, PPUCtrl, PPUMask, etc all during rendering, and that's bad!
Wow. I didn't know that. Good to know.
Say you have a mapper with 32K bank switching, and you have code running in RAM retrieving a whole bunch of data from one of the banks. This bank with the data has no NMI handler. So you'd want to disable NMI while accessing this bank.
3gengames wrote:
When the $2000 register has bit 7 set, it fires a NMI at VBlank. If it is not, it does not fire an IRQ, so your program will do whatever it does 100% uninterrupted.
Ah, thanks 3gengames, that clears it up for me!
Kasumi wrote:
unregistered: Seems about right. If I already have rendering disabled and am updating the PPU outside the NMI, having the NMI interrupt this task is kind of useless. Is that more clear? Well... maybe not totally useless. If you have a total playtime counter and use the NMI to time it, you'd still want them enabled.
Yes I'm understanding this much better now; thanks Kasumi very much!
Kasumi wrote:
3gengames wrote:
it's automatically fire an NMI regardless of if it's in VBlank or not, which would mean your vblank would then writes to Palette, VRam, PPUCtrl, PPUMask, etc all during rendering, and that's bad!
Wow. I didn't know that. Good to know.
Yes, that's good to know for me too.
tepples wrote:
Say you have a mapper with 32K bank switching, and you have code running in RAM retrieving a whole bunch of data from one of the banks. This bank with the data has no NMI handler. So you'd want to disable NMI while accessing this bank.
Thank you tepples!
This is the last quote but it helped me learn that disabling NMIs could be good to do sometimes.
---
All these helpful bits of knowledge are so cool! They've made the message so clear; thank you so much yall!
There are 3 main ways to set up NES programs:
1. Game logic in the main loop, video and audio updates in the NMI:
The whole game logic (i.e. movement, collisions, etc.) is in the main loop, and once it finishes the program waits for the NMI to fire. When it fires, it performs all the necessary video and audio updates, and then control is returned to the main loop, where the next frame will be processed.
........ just want to write what I've learned.
This was going to be a question about how to know when to write to VRAM... but this appeared:
tepples, on the previous page, wrote:
Correct. Because the NES PPU lacks the write FIFO of the SMS VDP and TG16 VDC, writes to the nametables must happen during vertical blanking or forced blanking. Prepare the writes in a RAM buffer during draw time and then copy them to VRAM during vblank.
And I remembered something about
force blank ( my brain is getting better! ) I had read:
If both the background and sprites are hidden, the PPU enters "forced blank" state, where it stops rendering and releases control of the address and data bus.
And so it doesn't matter whether or not it is vblank or forced blank, when the background and sprites are hidden by writing #$00 to PPUMASK $2001,... those are the only times VRAM (Video RAM maybe I think) can be written to!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Forced blanking is not easy for a beginner to pull off, because you have to be in full control of the frame's timing. I seriously do not recommend it for a person's first project.
tokumaru wrote:
Forced blanking is not easy for a beginner to pull off, because you have to be in full control of the frame's timing. I seriously do not recommend it for a person's first project.
Ok thank you tokumaru!
If you can optimize the VRAM transfers, that's a much better alternative than resorting to forced blanking. Unroll some loops, alternate different kinds of updates (e.g. you don't need to update the palette every frame), things like that.
tokumaru wrote:
If you can optimize the VRAM transfers, that's a much better alternative than resorting to forced blanking. Unroll some loops, alternate different kinds of updates (e.g. you don't need to update the palette every frame), things like that.
Thanks tokumaru!
VRAM transfers... that's what I'm trying to learn about now. VRAM holds more than the nametables? The
wiki reads, "Valid addresses are $0000-$3FFF;..." That's quite large 16kb... why?
$2000 is where your nametable data begins, all 2KB of it in the 4KB space (Which is why we have to mirror. Also, dunno what the top 4KB is. The 3F00 page is palette, but the $3000+ data is possibly another mirror of the nametable data? Whatever it is, we don't have to worry about it.) but the ROM for the graphics is at $0000 and $1000 pages, (4KB each) and so if you use RAM and want to write to it to put different graphics data there for a reason, that's where the data is.
$0000-$0FFF=Lower graphics page.
$1000-$1FFF=Higher graphics page.
$2000-$2FFF=Nametables location. (2KB of space mirrored to 4KB so how you write to the different nametables depends on mirroring)
$3000-$3EFF=I don't know, but I'm guessing just the $2000 page data in that space.
$3F00-$3FFF=Palette data, 32 bytes mirrored through the whole space.
3gengames wrote:
$2000 is where your nametable data begins, all 2KB of it in the 4KB space (Which is why we have to mirror. Also, dunno what the top 4KB is. The 3F00 page is palette, but the $3000+ data is possibly another mirror of the nametable data? Whatever it is, we don't have to worry about it.) but the ROM for the graphics is at $0000 and $1000 pages, (4KB each) and so if you use RAM and want to write to it to put different graphics data there for a reason, that's where the data is.
$0000-$0FFF=Lower graphics page.
$1000-$1FFF=Higher graphics page.
$2000-$2FFF=Nametables location. (2KB of space mirrored to 4KB so how you write to the different nametables depends on mirroring)
$3000-$3EFF=I don't know, but I'm guessing just the $2000 page data in that space.
$3F00-$3FFF=Palette data, 32 bytes mirrored through the whole space.
GRAND! Thank you so much 3gengames!
When writing tiles vertically is it like the graphic where a 16x16 metatile is written... and then the one below that... and then the one below that... or is it just a 8bit wide column of tiles? I'm trying to get the logic of how it works down right now.
You can do it either way. IMO, have a buffer, and just write what you can squeeze in. If you think you can upload a palette+2 columns, no harm in trying so. I don't know what the exact limit of how many tiles you can throw to the PPU, But I think 64+Palette is fine. Although, you really only have to ever update (scrollspeed/8) rows, so if your camera only ever moves 8 pixels maximum, then you only need to upload one column ever.
The practical limit without crazy stuff like self-modifying code is probably five rows or columns plus the OAM table, where an entire palette takes up one row.
3gengames wrote:
You can do it either way.
tepples wrote:
The practical limit without crazy stuff like self-modifying code is probably five rows or columns plus the OAM table, where an entire palette takes up one row.
^"is probably five rows or columns"? ...The PPU is set to increment by 32 each time... so I dont understand how both of my suggestions are possible. Is it possible to write a tile, PPU increments by 1, write another tile, PPU increments by 30, then write two more tiles below those?
In my head the PPU increments by 32 after each write... so thats one collum of tiles at a time... right?
edit: I feel like I missed something that both of you understand. I'm sorry 3gengames and tepples.
Quote:
The PPU is set to increment by 32 each time
It can also be set to to increment 1. This can be changed at any time. You write can write X columns of bytes, then change the bit and write a row of bytes. It's not picky. You could even write half a row, half a column, then the other half of the row, the other half of the column. It's not a good idea, just saying it because things aren't so rigid as you seem to be imagining.
Kasumi wrote:
Quote:
The PPU is set to increment by 32 each time
It can also be set to to increment 1. This can be changed at any time. You write can write X columns of bytes, then change the bit and write a row of bytes. It's not picky. You could even write half a row, half a column, then the other half of the row, the other half of the column. It's not a good idea, just saying it because things aren't so rigid as you seem to be imagining.
YES!!!!!!!!!! Thank you for this help Kasumi!! (thank you for your help 3gengames and tepples!
)
I didn't ever decide to invert the bit... thought that wouldn't be good cause it might take a while.
Kasumi wrote:
You could even write half a row, half a column, then the other half of the row, the other half of the column. It's not a good idea, just saying it because things aren't so rigid as you seem to be imagining.
One of my programs does exactly that to draw a box:
- Set the address to the top left and set the increment to +1.
- Draw the top left corner and the top border.
- Set the increment to +32.
- Draw the top right corner and the right side.
- Set the address to just below the top left.
- Draw the left side.
- Set the increment to +1.
- Draw the bottom left, bottom, and bottom right.
That's so cool tepples!
I can't wait to play with the non-rigid NES PPU!
edit: Ok, I think I understand why yall used a variable to keep track of what you've written to $2000 PPUCTRL; it is a write-only register, right? And so instead of reading it you keep a copy of the last byte you've written to it... I think!
Kasumi wrote:
Yep, you got it.
Thank you Kasumi
,; I'm so happy for
the your reply!
added.
So, when my PPU is incrementing by 32 after each write does it always do that forever... like can it automaticly start at the top of the next row? Or do I need to first set the address to zero?
You have to write a new address to $2006. It's especially annoying in whatever directions you're scrolling in. In 29 cases out of 30, you have to set the address again to write a whole column. In 31 cases out of 32, you have to set the address again to write a whole row.
Kasumi wrote:
You have to write a new address to $2006. It's especially annoying in whatever directions you're scrolling in. In 29 cases out of 30, you have to set the address again to write a whole column. In 31 cases out of 32, you have to set the address again to write a whole row.
Ahhh noooooooooo.
Hmm... what I said was misleading. If you only scroll horizontally, you'll only need to write the starting address for each column. You'll never need to write two for one column.
Same if you only scroll vertically, except replace column with row.
It's only an issue if you do both, which you are not. You still do need to write a new address for each NEW column, but there's much less math involved there. I apologize, not quite sure what I thinking in the last post.
Kasumi wrote:
Hmm... what I said was misleading. If you only scroll horizontally, you'll only need to write the starting address for each column. You'll never need to write two for one column.
Same if you only scroll vertically, except replace column with row.
It's only an issue if you do both, which you are not. You still do need to write a new address for each NEW column, but there's much less math involved there. I apologize, not quite sure what I thinking in the last post.
Thank you Kasumi!
: ) I was dissappointed in having to write the address for each new column... but it is really ok now...
it
is should be possible for me to do! I'm looking forward to making this work for two columns. And then there will be a point where I should not make a column until it is needed...
or, hey, I can create the column in my buffer and then wait to draw it on the screen.
edit.
sigh... why would my rom be built with the wrong chr screen?? In FCEUX 2.1.5's PPU viewer it shows that I've loaded the same pattern table for both screens... in my code there is:
Code:
.incbin "lvl1\lvl1.chr"
; Fill the rest of the first CHR-ROM block with zeroes.
.align $1000
; Here begins the second 4K block. The sprites (all one of them) get their data
; from this page.
.incbin "Character3.chr"
; Fill the rest of the CHR-ROM block with zeroes, giving us exactly 8K of data, which
; was what we want and need.
.align $1000
; now we have 16 banks of 8K CHR-ROM. (actually 32 banks cause we are using 4K files)
Why isn't Character3.chr present in FCEUX 2.1.5's "PPU Viewer..."? It looks like lvl1.chr is there twice!
Are you using a mapper? Have you selected the appropriate CHR-ROM banks? If you're using the MMC1 in 4KB CHR mode you might get the same 4KB mapped twice if you didn't explicitly switch the 2 4KB pages you want to use.
tokumaru wrote:
Are you using a mapper? Have you selected the appropriate CHR-ROM banks? If you're using the MMC1 in 4KB CHR mode you might get the same 4KB mapped twice if you didn't explicitly switch the 2 4KB pages you want to use.
And/or have you written the
right value to $2000 so that the sprites and background come from different parts of CHR?
Why not make one big 8/16/32/64/128KB file and just INCBIN that instead of messing with tiny 2KB char files and such?
lidnariq wrote:
have you written the
right value to $2000 so that the sprites and background come from different parts of CHR?
I think this wouldn't change what's displayed on FCEUX's debugger, though.
3gengames wrote:
Why not make one big 8/16/32/64/128KB file and just INCBIN that instead of messing with tiny 2KB char files and such?
If you have different tilesets for different levels for example, it makes sense to use smaller CHR files. I even have individual CHR files for each enemy/object, for example. This way I can easily rearrange them without the aid of graphical editors, or even calculate banks numbers or tile indexes using labels, instead of using hardcoded values. It's more organized (which is the same reason most people don't use a single huge ASM file for the whole game).
He's using MMC1 so the highest resolution is 4KB, and is using CHR-ROM, it's much easier to do a LUT to pick the right graphics bank per level unless he uses CHR-RAM or a mapper like MMC3, especially since he's having troubles. But still, besides the point. FCEUX does not change what it displays based on $2000 value...just tested. So be sure you are picking both chunks in 4KB mode.
tokumaru wrote:
Are you using a mapper? Have you selected the appropriate CHR-ROM banks? If you're using the MMC1 in 4KB CHR mode you might get the same 4KB mapped twice if you didn't explicitly switch the 2 4KB pages you want to use.
Thanks tokumaru. That ended up being the problem... figured this out during lunch... it all works correctly now!
---
It would be good right now for me to start with a version number like Kasumi suggested a while ago...
Kasumi, on page 36, wrote:
.... Even something simple like this is good.
Version 1:
Added animations.
Add movement.
Version 2:
Added horizontal scrolling.
Added 16bit movement for sprites.
I backup
my entire source folder for every major change, and keep track of changes. This allows you to only check what was changed when you have an issue. "It was working. I added X, and changed Y. Now it's not. The problem is probably in X or Y, so I'll look at their logic." This also means if you REALLY screw up, you can just copy over the last backup.
Also, never write super large portions of code without checking them. For an example of something you've done before (tile updating), you could write something that gets the two right most 8x8 tile numbers of a known 16x16 metatile and writes them to ram. Run it, and check to see the write values are written to RAM. Then you write something that writes a whole known column of the tiles to RAM. If it doesn't work, or breaks your program you know exactly what to check. Then, you make your NMI write the tiles to $2007. Then you make it so can write a column at X position. If you write a single routine that does the equivalent of all that without checking any of it, you're in REALLY deep. Especially if you have to rewrite it ALL instead of just fix it.
And if you have no idea what was changed, you basically have to check the entire program.
So I guess those were debugging tips, because I have no idea about the actual problem with the information given. It's time to get your hands dirty!
Thank you, this post is very wise... you are very wise Kasumi!
But I only mean to respond to that small yellow part I
highlighted. Your entire source folder? You have a folder just for your source code? That sounds interesting. My folder has the name "nesD" cause the year is 2013. It holds all of my nes stuff from 2013. How do you have a folder for just your source code? How could I build a nes file using something like that and asm6?
Animated GIF:
Just keep all the things you need to build your program in a folder. When you want to make a backup, copy that folder. There are better methods than this, but this is probably good enough for a solo project.
Edit: Inside the main source folder I have another folder named "Notes" where I keep a file called "changes.txt" that keeps track of changes in great detail, and a file called "general notes.txt" which is where I put possible ideas for the game/current bugs/a todo list. When the backup is made, these files are copied as well of course. So it allows me to see what I was working on in each version, which is sort of fun to look back on.
It's better to keep things like NES specific documents separate. I would say as a general rule that anything not made by you that is not needed to assemble your rom should not be in your source folder.
In your animated gif... the folder names are quite long... that would make the nes rom take a long while to build... right?
edit: I mean it would take a while to type out the folder name... and it would be lots of mistakes... for me at least.
Quote:
In your animated gif... the folder names are quite long... that would make the nes rom take a long while to build... right?
The NES is not even 2 MHz, and could parse like 8,000 characters in a 60th of a second. Your computer is probably thousands of times faster than this. I mean... maybe it takes longer, but it's not something I think about. Any C program takes longer.
Also, long folder names like "GalaxyNES 22 With Reworked Debug Mode, Faster Speed Curve and Greens that don't randomly die" (hah) are not even used to assemble the rom. They're backups. The actual folder I build from, is just "GalaxyNES". When I make a major change, I copy the GalaxyNES folder and give it a long descriptive name. (Which you don't have to do. You could just number/date it and keep track of what each dated folder is in a separate file. In fact, I recommend doing this.) If I want to restore it, I just copy it again, get rid of my old "GalaxyNES" folder and rename the "descriptive" one back to GalaxyNES.
Quote:
edit: I mean it would take a while to type out the folder name... and it would be lots of mistakes... for me at least.
Except you do it once every month, or whatever. I did it a lot in GalaxyNES, but that's because the entire thing took only like 4 months start to finish. My current project only has a backup every month to three months, unless I'm trying to really tricky stuff.
Edit: Ideally, you never even use the backups. I've had to restore to a previous version I think once. I'm really glad I had the option that one time, though. They're also good to refer to as well when you're rewriting stuff. I do
that quite a lot.
Kasumi wrote:
Quote:
In your animated gif... the folder names are quite long... that would make the nes rom take a long while to build... right?
The NES is not even 2 MHz, and could parse like 8,000 characters in a 60th of a second. Your computer is probably thousands of times faster than this. I mean... maybe it takes longer, but it's not something I think about. Any C program takes longer.
Also, long folder names like "GalaxyNES 22 With Reworked Debug Mode, Faster Speed Curve and Greens that don't randomly die" (hah) are not even used to assemble the rom. They're backups. The actual folder I build from, is just "GalaxyNES". When I make a major change, I copy the GalaxyNES folder and give it a long descriptive name. (Which you don't have to do. You could just number/date it and keep track of what each dated folder is in a separate file. In fact, I recommend doing this.) If I want to restore it, I just copy it again, get rid of my old "GalaxyNES" folder and rename the "descriptive" one back to GalaxyNES.
Quote:
edit: I mean it would take a while to type out the folder name... and it would be lots of mistakes... for me at least.
Except you do it once every month, or whatever. I did it a lot in GalaxyNES, but that's because the entire thing took only like 4 months start to finish. My current project only has a backup every month to three months, unless I'm trying to really tricky stuff.
Edit: Ideally, you never even use the backups. I've had to restore to a previous version I think once. I'm really glad I had the option that one time, though. They're also good to refer to as well when you're rewriting stuff. I do
that quite a lot.
Wow, this is so amazing!! Thank you for these detailed responses!
They will be helpful!!!
Here's how I organize my changes. The oldest version is at the bottom. The date to the right of the version number is the assemble time. This alone is enough to keep track of which versions are which, but I also do the long folder names because I'm lame.
Quote:
Version 5 (4/4/09 12:51 PM)
Sprites can now be linked, have small offsets every fourth frame
Version 4 (4/4/09 7:30 AM)
Rewrote Animations/big sprite handler from scratch. (Currently only supports sprites that animate themselves)
Version 3 (4/1/09 10:26 PM)
Fixed a glitch where the animations didn't give the proper attributes to their individual sprites.
added a way for follower sprites to keep the animation of their leader sprite
Version 2 (4/1/09 7:05 PM)
Basic Animation Handler Added
Version 1 (3/31/09 4:02 AM)
Just a simple dual sprite viewer.
When I finish out a version, I put a new version number on top with "(Current Version)", instead of the build date. This lets you see what bug fixes/features you'll lose by going back a few versions. I keep track of the file formats I've written as well. If they change, the current format is still documented with the source folder that uses it.
Keep track of stuff you're currently working on, and possible idea for how to fix problems you don't have time for right now.
See the "If Optimizations Are Desparately Needed" file in the GalaxyNES folder.
The bottom line is to keep organized. You don't have to do it this way, (I'm sure there are better ways that blow this out of the water) but make it so that if something terrible happens you'll have a safety net. Make it so that if you stop working on the program for a couple of months or years, you won't have to read through ALL of the source code to figure out what you were doing. Anyway, that's all I got. Keep it up!
Kasumi wrote:
The bottom line is to keep organized. You don't have to do it this way, (I'm sure there are better ways that blow this out of the water)
Perhaps
Git or
Mercurial might be a good match to your way of working.
Kasumi wrote:
Here's how I organize my changes. The oldest version is at the bottom. The date to the right of the version number is the assemble time. This alone is enough to keep track of which versions are which, but I also do the long folder names because I'm lame.
Quote:
Version 5 (4/4/09 12:51 PM)
Sprites can now be linked, have small offsets every fourth frame
Version 4 (4/4/09 7:30 AM)
Rewrote Animations/big sprite handler from scratch. (Currently only supports sprites that animate themselves)
Version 3 (4/1/09 10:26 PM)
Fixed a glitch where the animations didn't give the proper attributes to their individual sprites.
added a way for follower sprites to keep the animation of their leader sprite
Version 2 (4/1/09 7:05 PM)
Basic Animation Handler Added
Version 1 (3/31/09 4:02 AM)
Just a simple dual sprite viewer.
When I finish out a version, I put a new version number on top with "(Current Version)", instead of the build date. This lets you see what bug fixes/features you'll lose by going back a few versions. I keep track of the file formats I've written as well. If they change, the current format is still documented with the source folder that uses it.
Keep track of stuff you're currently working on, and possible idea for how to fix problems you don't have time for right now.
See the "If Optimizations Are Desparately Needed" file in the GalaxyNES folder.
The bottom line is to keep organized. You don't have to do it this way, (I'm sure there are better ways that blow this out of the water) but make it so that if something terrible happens you'll have a safety net. Make it so that if you stop working on the program for a couple of months or years, you won't have to read through ALL of the source code to figure out what you were doing. Anyway, that's all I got. Keep it up!
Many thanks this has been and is incredibly helpful for me!
tepples wrote:
Kasumi wrote:
The bottom line is to keep organized. You don't have to do it this way, (I'm sure there are better ways that blow this out of the water)
Perhaps
Git or
Mercurial might be a good match to your way of working.
Thanks tepples; I spent some time reading about Git... but it got too complicated and I am returning to my quest for scrolling beyond two screens. Now there's this super cool "general notes" text file
that Kasumi gave me and it has become important!
Kasumi, on page 59, wrote:
unregistered: Seems about right. If I already have rendering disabled and am updating the PPU outside the NMI, having the NMI interrupt this task is kind of useless. Is that more clear? Well... maybe not totally useless. If you have a total playtime counter and use the NMI to time it, you'd still want them enabled.
How do I update the PPU outside of the NMI? I seem to be doing this... am recieving a black screen though... so how do I update the PPU outside of the NMI without recieving a black screen? The
Name table viewer of FCEUX shows an almost correct updated nametable as the screen goes black... and until I reset the NES.
unregistered wrote:
How do I update the PPU outside of the NMI? I seem to be doing this... am recieving a black screen though... so how do I update the PPU outside of the NMI without recieving a black screen?
You disable rendering by writing 0 to $2001, update the PPU, then reenable rendering by writing something like #%00011000 to $2001. If the screen is black, but the game hasn't really crashed I would assume you're just forgetting to turn rendering back on.
Edit: Disabling rendering makes it so your screen is not drawn, so you can only write bytes to the PPU this way in between levels, not during gameplay when the player needs to see what's happening.
The super simplified explanation is that you cannot write bytes to the PPU while it's trying to draw the screen. It's not doing so for ~2,270 cycles after an NMI fires which is why you can write bytes then. It's also not doing so when the screen is not being drawn.
Kasumi wrote:
Disabling rendering makes it so your screen is not drawn, so you can only write bytes to the PPU this way in between levels, not during gameplay when the player needs to see what's happening.
Unless you're making a flip-screen platformer like Sivak's Battle Kid or thefox's port of Podunkian's Streemerz. Then you can turn rendering off for five whole frames to draw the new screen without the player caring.
tepples wrote:
Kasumi wrote:
Disabling rendering makes it so your screen is not drawn, so you can only write bytes to the PPU this way in between levels, not during gameplay when the player needs to see what's happening.
Unless you're making a flip-screen platformer like Sivak's Battle Kid or thefox's port of Podunkian's Streemerz.
Then you can turn rendering off for five whole frames to draw the new screen without the player caring. Ahhhhhhhhhh thank you Kasumi!
So tepples, since our game
is will be a flip-screen platformer vertically could we do
that?
edit.edit2.: Sorry for messing this post up. edit3.
unregistered wrote:
since our game is will be a flip-screen platformer vertically could we do that?
If it's flip-screen it can't be vertical or horizontal. Flip-screen means that there's no scrolling at all: when the player leaves the screen, it goes blank for a couple of frame (while the new screen is drawn) and then the new screen appears, with the player on the opposite edge. Concepts like "vertical" or "horizontal" don't mean anything in this case, so there must be some misunderstanding here.
Lots of games are flip-screen vertically but scrolling horizontally. Castlevania is one when going down or up stairs. Super Mario Bros. is one when going down or up the pipe in 1-2. Super Mario Bros. 2 is another when going down or up the vine at the right side of 1-1.
True, I didn't think about those cases... So yeah, you do have a 2 name table area you can use, so you can't have more than 2 screens between flips if you plan on turning rendering off to perform VRAM updates.
unregistered needs the screen-flipping because the time of a VBlank isn't enough for all the updates he needs, but these game you mentioned probably need it because of mirroring restrictions that make scrolling in both directions hard/impossible.
...my question today has to do with PPUADDR ($2006). Right now I've written #$203E. Why does my vertical line of 29 tile#3s start on the second to last column? Starts around $283E...
Isn't $2000 the upper left corner of nametable 0?
$203E is nametable 0 ($2000-$23FF), row 1 ($2020-$203F), column 30. The last column is column 31, so column 30 would be second to last.
tepples wrote:
$203E is nametable 0 ($2000-$23FF), row 1 ($2020-$203F), column 30. The last column is column 31, so column 30 would be second to last.
Well... that is exactly where it is... you are correct!
I'm confused... maybe I'll get less confused with more experience?
edit: Thank you tepples!
Here is the code I'm working on...
Code:
update_vram: ;testing... :)
lda my_copy_of_last_write_to_PPUCTRL
ora #00000100b ;change $2007 increment to +32
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
lda #$20
sta PPUADDR6
lda #$00
sta tE
sta PPUADDR6
ldy #$02
ldx #60 ;should hold last spot written to RAMbuffer. each column is always full so #60
;**********LOOK AT CODE BELOW HERE***********************
- cpx #30 ;if x == 30
bne +past30stop
;Welcome to 30stop.
inc tE ;+1 (moves over to next column)
lda #$20
sta PPUADDR6
lda tE
sta PPUADDR6
+past30stop:
lda RAMbuffer, x
sta PPUDATA7
dex
bne -
rts ;end of update_vram.
This kills my attribute table data. All my pallets entries look the same now... after forming that loop at the bottom. Do you see anything wrong with this code?
edit: Here is what it is susposed to do... it correctly shows two full columns of tile #00 on nametable 0. It is written backwards because it seems like it would be faster if the columns are upsidedown.
edit2: All the code that's being tested right now is below the ***********line. This means everything else... all the clean code that works is not below that line.
after-supper-edit3: Um... is there a rule that says I should quit the write to vram?... There is a line of odd tiles up at the top of nametable 0. Thought maybe somehow those tiles are the attribute table pushed out to the top of nametable 0. Somehow... if you never increment the value that you store to $2006... the tiles are written into the attribute table of nametable 0 and then on into the first column of nametable 1 ...I think...
Why would you not structure code like this?
Code:
(Write PPU column here)
LDX #$00
CDataWriteLoop:
LDA Buffer,X
STA PPUData
INX
CPX #30 ;30 tiles?
BNE .CDataWriteLoop
RTS
or even better, if you can put your buffer backwards: (Which you should do basically always)
Code:
LDX #30 ;30 Tiles
CDataWriteLoop:
LDA Buffer,X
STA PPUData
DEX
BNE .CDataWriteLoop
RTS
to write the column of data? You should *nearly* never have to do a compare right out of the gate in a routine, it's a waste because you'll have to branch back then check, which adds even more cycles to an already inefficient routine. Not trying to be harsh, but code structure is everything.
3gengames wrote:
or even better, if you can put your buffer backwards: (Which you should do basically always)
Code:
LDX #30 ;30 Tiles
CDataWriteLoop:
LDA Buffer,X
STA PPUData
DEX
BNE .CDataWriteLoop
RTS
to write the column of data? You should *nearly* never have to do a compare right out of the gate in a routine, it's a waste because you'll have to branch back then check, which adds even more cycles to an already inefficient routine. Not trying to be harsh, but code structure is everything.
Oooh...
well I started to think that... well I was thinking in a loop somehow... sorry, good point 3gengames. : )
edit: My code was like your second example... but it ended with a new address written to $2006... and I experienced the same problem... all the attribute tables left... everything was on the same palette... and so I guessed that it isn't good to have a loop end with a $2006 write.
This morning I found something that I dont understand. Could you help me?
Here is the code I'm working with
Code:
draw_RAMbuffer2: ;"Prepare the (new nametable) writes in a RAM buffer during draw time..." tepples pg 59
sta $ff
ldy #$00 ;e
ldx #$02
jsr load_screen ;this just enables us to use the lda ($10), y instruction
; and the ldx #$02 is used inside load_screen... loads screen #2
lda #30
sta t2
;---
;
-- lda ($10), y
tax
;29 lda MetatileTile3, x
lda MetatileTile3, x
pha ;--->
;30 lda MetatileTile1, x
lda MetatileTile1, x
ldx t2 ;<--- this correctly sets X to write to RAMbuffer2
sta RAMbuffer2, x
dex
pla ;<---
sta RAMbuffer2, x
;increment y by 16!!!!
tya
clc
adc #$10 ;#16
tay
dex
stx t2
bne --
rts ;end of draw_RAMbuffer2 and of nCPUmem
Ok, this code works almost perfectly... it messes up on the first run... instead of tile #00 it loads tile #04 on the top of the screen. But, as I follow the code it has an
= #$04 at the end of
Code:
0F:C47A:BD EC C7 LDA $C7EC,X @ $C80A = #$12
0F:C47D:48 PHA
0F:C47E:BD 2E C6 LDA $C62E,X @ $C64C = #$13
0F:C481:A6 31 LDX $0031 = #$1E
0F:C483:95 51 STA $51,X @ $006F = #$04
0F:C485:CA DEX
It shows the
= #$04 once the assembler gets to that line... but the accumulator always holds #$00 before the
step into button is clicked. I'm guessing that is trying to say that #$04 is already written there... that's confusing because after i click
step into the accumulator writes the zero it has been holding... and there is always a tile #04 at the top of the screen.
I'm having trouble following it, but:
Quote:
I'm guessing that is trying to say that #$04 is already written there
Correct, it's the value in that RAM location before the sta.
Quote:
and there is always a tile #04 at the top of the screen.
Break on writes to $006F. Is 4 written there ever? If not, it sounds more like there's an issue with your code that writes these bytes to $2007.
Also: It's tiny, but there's no need to do this:
Code:
adc #$10 ;#16
This would work fine on most assemblers:
Code:
adc #16
Edit: While trying to optimize this, I might have found a potential problem.
Code:
draw_RAMbuffer2: ;"Prepare the (new nametable) writes in a RAM buffer during draw time..." tepples pg 59
sta $ff
ldy #$00 ;e
ldx #$02
jsr load_screen ;this just enables us to use the lda ($10), y instruction
; and the ldx #$02 is used inside load_screen... loads screen #2
lda #30
sta t2
;---
-- lda ($10), y ;What's in y? Does it stay #$00 after the jsr load_screen?
If it were me, I would move the ldy #$00 directly above the loop. Because when you look at this code later, it will keep you from having to check whether load_screen changes it. Unless load_screen sets it up to some known expected value in which case, ignore what I just said.
Kasumi wrote:
I'm having trouble following it, but:
Quote:
I'm guessing that is trying to say that #$04 is already written there
Correct, it's the value in that RAM location before the sta.
Thanks very much!
Kasumi wrote:
Quote:
and there is always a tile #04 at the top of the screen.
Break on writes to $006F. Is 4 written there ever?
Yes there is a 4 written there! Wahoo! Guess I could change it to 5... and see if the tile changes...
(Looking back on this... I'm confused still because my code says Code:
0C4AF update_vram: ;testing... :)
0C4AF A5 6F lda my_copy_of_last_write_to_PPUCTRL
0C4B1 09 04 ora #00000100b ;change $2007 increment to +32
0C4B3 8D 00 20 sta $2000
0C4B6 85 6F sta my_copy_of_last_write_to_PPUCTRL
0C4B8
0C4B8 ...
and so it somehow gets that value???) edit: it does change to tile #5 Kasumi wrote:
Also: It's tiny, but there's no need to do this:
Code:
adc #$10 ;#16
This would work fine on most assemblers:
Code:
adc #16
Muhahaha... I was trying to teach me that $10 is not equal to 10.
Kasumi wrote:
Edit: While trying to optimize this, I might have found a potential problem.
Code:
draw_RAMbuffer2: ;"Prepare the (new nametable) writes in a RAM buffer during draw time..." tepples pg 59
sta $ff
ldy #$00 ;e
ldx #$02
jsr load_screen ;this just enables us to use the lda ($10), y instruction
; and the ldx #$02 is used inside load_screen... loads screen #2
lda #30
sta t2
;---
-- lda ($10), y ;What's in y? Does it stay #$00 after the jsr load_screen?
If it were me, I would move the ldy #$00 directly above the loop. Because when you look at this code later, it will keep you from having to check whether load_screen changes it. Unless load_screen sets it up to some known expected value in which case, ignore what I just said.
Haha it's ok, no it doesn't do anything with y... that's quite a good comment! Thanks Kasumi!
Quote:
Yes there is a 4 written there! Wahoo!
So you see the problem, right?
This: 0F:C483:95 51 STA $51,X @ $006F = #$04
And this: 0C4B6 85 6F sta my_copy_of_last_write_to_PPUCTRL
overlap. (Note that they both write to $6F.)
So anytime you write a number to "my_copy_of_last_write_to_PPUCTRL", it changes the tile because it overlaps the RAM you're using for the tile buffer (and vice versa). You gotta arrange your RAM so nothing overlaps.
Kasumi wrote:
Quote:
Yes there is a 4 written there! Wahoo!
So you see the problem, right?
This: 0F:C483:95 51 STA $51,X @ $006F = #$04
And this: 0C4B6 85 6F sta my_copy_of_last_write_to_PPUCTRL
overlap. (Note that they both write to $6F.)
So anytime you write a number to "my_copy_of_last_write_to_PPUCTRL", it changes the tile because it overlaps the RAM you're using for the tile buffer (and vice versa). You gotta arrange your RAM so nothing overlaps.
I didn't know RAM can overlap... thank you so much for telling me!
Now I'm going to
try to find out what to do to make it not overlap anymore.
edit.edit2: Well... um how can I make my RAM not overlap?
Code:
0002C SomethingSolidtoStandon .dsb 1
0002D ofracorhiX .dsb 2 ;<----.
0002F ofracorhiY .dsb 2 ;<--------- "... your position will need another byte to represent the fractional part of the position, which if you scroll means you'll need at least 3 bytes for the position alone ..." -Kasumi p. 51 +
00031 t2 .dsb 2
00033 RAMbuffer1 .dsb 30 ;just a temporary destination for writes to next collum of vram... they'll be written during vblank. :)
00051 RAMbuffer2 .dsb 30
0006F my_copy_of_last_write_to_PPUCTRL .dsb 1
00070
00070
Quote:
edit2: Well... um how can I make my RAM not overlap?
Well... maybe your RAM isn't overlapping, but you're running past what you've allocated for RAMbuffer2.
So..
Code:
00051 RAMbuffer2 .dsb 30
0006F my_copy_of_last_write_to_PPUCTRL .dsb 1
So you've allocated 30 bytes for RAMbuffer2. This means $51+0 through $51+29 are allocated to RAMbuffer2. This is a range from $51 to $6E. Much like 0 and 1 is two numbers, 0-29 is 30.
Knowing this, here's the issue:
Code:
lda #30
sta t2
So we've got #30 in T2.
Code:
ldx t2 ;<--- this correctly sets X to write to RAMbuffer2
sta RAMbuffer2, x
RAMbuffer2 ($51)+30(X) is outside the range of the buffer. Right now your code works from 30 to 1 instead of 29 to 0.
unregistered wrote:
I didn't know RAM can overlap
There are two kinds of assembly language programmers: those who have written a
buffer overflow and those who have yet to.
Kasumi wrote:
Quote:
edit2: Well... um how can I make my RAM not overlap?
Well... maybe your RAM isn't overlapping, but you're running past what you've allocated for RAMbuffer2.
So..
Code:
00051 RAMbuffer2 .dsb 30
0006F my_copy_of_last_write_to_PPUCTRL .dsb 1
So you've allocated 30 bytes for RAMbuffer2. This means $51+0 through $51+29 are allocated to RAMbuffer2. This is a range from $51 to $6E. Much like 0 and 1 is two numbers, 0-29 is 30.
Knowing this, here's the issue:
Code:
lda #30
sta t2
So we've got #30 in T2.
Code:
ldx t2 ;<--- this correctly sets X to write to RAMbuffer2
sta RAMbuffer2, x
RAMbuffer2 ($51)+30(X) is outside the range of the buffer. Right now your code works from 30 to 1 instead of 29 to 0.
Hmmm...
Thank you Kasumi for your explanation here!
I understand it much better after reading what tepples linked to...
tepples wrote:
unregistered wrote:
I didn't know RAM can overlap
There are two kinds of assembly language programmers: those who have written a
buffer overflow and those who have yet to.
It's quite
odd amazing that my buffer overflow overflows a variable named RAM
buffer2. ...Hahaha
edit.
Kasumi, when I change the code to
Code:
lda #29
it's all really confusing now... my character doesn't fall all the way to the ground... she falls for a small part of a second and then suddenly stops... cant move her around.
You would also need to change this:
Code:
dex
stx t2
bne --
to this
Code:
dex
stx t2
bpl --
Otherwise it never actually does 0. Whether or not that will fix the falling issue, I don't know. You've never posted that code, and I'm not magic. Does the movement routine use what's in RAMbuffer2? Because you've just changed how it works, and any other code that uses it also needs to updated to reflect that.
Kasumi wrote:
You would also need to change this:
Code:
dex
stx t2
bne --
to this
Code:
dex
stx t2
bpl --
Otherwise it never actually does 0. Whether or not that will fix the falling issue, I don't know. You've never posted that code, and I'm not magic. Does the movement routine use what's in RAMbuffer2? Because you've just changed how it works, and any other code that uses it also needs to updated to reflect that.
I know you aren't magic. Thank you for noticing bne-to-bpl that fixed part of it!
I kept thinking of other code needing to be updated and found two things that probably fixed the other part of it before reading your response. My brain is working today!
ok... this is kind of weird...
Code:
0C06A 58 cli
0C06B ;----------------------------END OF RESET----------------------------------
0C06B ; Transfer control to the GAME LOGIC routines.
0C06B
0C06B
0C06B ;initialize the flag to "false"
0C06B A9 00 lda #$00
0C06D 85 21 sta FrameReady
0C06F
0C06F MainLoop:
0C06F
0C06F ;DO THE GAME LOGIC HERE (i.e. movement, collisions, etc.)
0C06F 20 2F C1 jsr react_to_input
0C072 20 91 C3 jsr collisionU
0C075
0C075 A6 0F ldx aFrame
0C077 20 05 C4 jsr draw_sprite
0C07A ; hope this will be some good cement. :) tested... works good so far :D
0C07A EA nop
0C07B EA nop ;ldx #$02
0C07C EA nop ;ldy #$00
0C07D EA nop
0C07E 20 41 C4 jsr draw_RAMbuffers ;left & right column
0C081
0C081 ;indicate that the frame calculations are done
0C081 C6 21 dec FrameReady
0C083
0C083 WaitForUpdates:
0C083 ;wait for the flag to change
0C083 24 21 bit FrameReady
0C085 30 FC bmi WaitForUpdates
0C087
0C087 4C 6F C0 jmp MainLoop
So I added the
ldx #$02 and
ldy #$00 right above my
jsr draw_RAMbuffers. Then was wondering why the attribute tables changed. Ok so then after a while of testing it appears to me that it doesn't really matter what those instructions are... as long as they equal 4 bytes... my attribute tables are changed!!
This is a picture of the code with the changed attribute tables because of the 4
nops being 1 byte each. If there are 5 nops the attribute table is ok... if there are 3 nops it's ok. 4 nops before my
jsr draw_RAMbuffers is messed up atttribute table. I'm lost... don't have a clue where to fix; dose anyone have a helpful thought they would like to share with me?
So what's in draw_RAMbuffers? And your NMI?
Code:
0C441 ;*******************************************************
0C441 ; uses x for load_screen input... send value of screen to load in x
0C441 ; uses y to pick a column (1 16x16 metatile wide).
0C441 ;*******************************************************
0C441 draw_RAMbuffers:
0C441 ;"Prepare the (new nametable) writes in a RAM buffer during draw time..." tepples pg 59
0C441
0C441 85 FF sta $ff
0C443 A2 02 ldx #$02
0C445 20 24 C1 jsr load_screen
0C448
0C448 A9 1D lda #29
0C44A 85 31 sta t2
0C44C
0C44C ;tya
0C44C ;pha ;------->
0C44C A0 03 ldy #$03
0C44E ;---
0C44E ;
0C44E B1 10 -- lda ($10), y
0C450 AA tax
0C451 ;29 lda MetatileTile2, x
0C451 BD 52 C7 lda MetatileTile2, x
0C454 48 pha ;--->
0C455 ;30 lda MetatileTile0, x
0C455 BD 94 C5 lda MetatileTile0, x
0C458 A6 31 ldx t2
0C45A 95 33 sta RAMbuffer1, x
0C45C CA dex
0C45D 68 pla ;<---
0C45E 95 33 sta RAMbuffer1, x
0C460
0C460 98 tya
0C461 18 clc
0C462 69 10 adc #$10 ;increment y by 16!!!!
0C464 A8 tay
0C465
0C465 CA dex
0C466 86 31 stx t2
0C468 10 E4 bpl --
0C46A
0C46A
0C46A A9 1D lda #29 ;need to reset t2 back up to #29
0C46C 85 31 sta t2
0C46E
0C46E ;pla ;<--------
0C46E ;tay
0C46E A0 03 ldy #$03
0C470 ;---
0C470 B1 10 -- lda ($10), y
0C472 AA tax
0C473 ;29 lda MetatileTile3, x
0C473 BD 31 C8 lda MetatileTile3, x
0C476 48 pha ;--->
0C477 ;30 lda MetatileTile1, x
0C477 BD 73 C6 lda MetatileTile1, x
0C47A A6 31 ldx t2
0C47C 95 51 sta RAMbuffer2, x
0C47E CA dex
0C47F 68 pla ;<---
0C480 95 51 sta RAMbuffer2, x
0C482
0C482 98 tya
0C483 18 clc
0C484 69 10 adc #$10 ;increment y by 16!!!!
0C486 A8 tay
0C487
0C487 CA dex
0C488 86 31 stx t2
0C48A 10 E4 bpl --
0C48C
0C48C
0C48C 60 rts ;end of draw_RAMbuffers
And here is my mni: (vblank... this is my vblank procedure and scrolllandiii, at the end, is just some more scrolling-vblank-type prcedures; do you need those too?)
Code:
vblank: inc FRAME_CNT
;skip the video updates if the frame calculations aren't over yet
bit FrameReady
bpl SkipUpdates
;PERFORM VIDEO UPDATES HERE
jsr update_sprite
jsr update_vram
;modify the flag
inc FrameReady
SkipUpdates:
;PERFORM TASKS THAT MUST BE PERFORMED EVEN
;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
;THE MUSIC OR DRAWING A STATUS BAR
jsr FamiToneUpdate
;"Setting the scroll should ALWAYS be the very last thing in your VBlank handler." -tokumaru pg 43
jsr scroll_screen
;return from the NMI (vblank)
rti
;^ that's the end of my vblank: procedure... what follows are some vblank-type procedures...
update_sprite:
lda #>sprite
sta $4014 ;OAM_DMA register ; Jam sprite page ($200-$2FF) into SPR-RAM
;takes 513 cycles.
rts
.enum LocalVariables4vblank
tE .dsb 2
.ende
update_vram: ;testing... works grandly :)
lda my_copy_of_last_write_to_PPUCTRL
ora #00000100b ;change $2007 increment to +32
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
lda #$20
sta PPUADDR6
lda #$00
sta tE
sta PPUADDR6
ldx #29 ;should hold last spot written to RAMbuffer. each column is always full so #29 1XNJJ8WY
- lda RAMbuffer1, x
sta PPUDATA7
dex
bpl -
;this part should hold the increase +1 (moves over to next column) for tE, I think...
inc tE
lda #$20
sta PPUADDR6
lda tE
sta PPUADDR6
ldx #29
- lda RAMbuffer2, x
sta PPUDATA7
dex
bpl -
rts ;end of update_vram.
.include "daprg-scrolllandiii.asm"
Hope this helps...
edit: Sorry this is a bit late... it's Saturday morning right now... time for sleep.
Beats me. My guess is it's because you're not saving your registers in the NMI routine?
The NMI can interrupt your code at any time. If it changes the value in a register, the "wrong" value (i.e. a value that the code the NMI interrupted doesn't expect) will be there when it returns. To avoid this, just push the values of all the registers to the stack right at the beginning, and then pull them all at the end of the interrupt before the RTI.
Like this:
Code:
vblank:
pha
tya
pha
txa
pha
inc FRAME_CNT
;skip the video updates if the frame calculations aren't over yet
bit FrameReady
bpl SkipUpdates
And at the end...
Code:
;return from the NMI (vblank)
pla
tax
pla
tay
pla
rti
If that's not it, I'm not sure I can find anything else.
Kasumi wrote:
Beats me. My guess is it's because you're not saving your registers in the NMI routine?
The NMI can interrupt your code at any time. If it changes the value in a register, the "wrong" value (i.e. a value that the code the NMI interrupted doesn't expect) will be there when it returns. To avoid this, just push the values of all the registers to the stack right at the beginning, and then pull them all at the end of the interrupt before the RTI.
Like this:
Code:
vblank:
pha
tya
pha
txa
pha
inc FRAME_CNT
;skip the video updates if the frame calculations aren't over yet
bit FrameReady
bpl SkipUpdates
And at the end...
Code:
;return from the NMI (vblank)
pla
tax
pla
tay
pla
rti
If that's not it, I'm not sure I can find anything else.
Woah, ok, yes!!! Thank you 500 percent Kasumi!
You are very wise and thank you for
sharing your wisdom
with me.
edit.
When drawing columns while scrolling... is this the correct way to think about construction of my 16-bit level?
I should make sure to draw columns at the beginning of nametable 0 nearing the end of nametable 1 so that it appears to scroll the screen to the right into a new nametable (nametable 0). Is thinking like this bad? My level is some-how wider than the two nametables... but I dont understand how to start the next 3rd screen. I'm lost.
unregistered wrote:
I should make sure to draw columns at the beginning of nametable 0 nearing the end of nametable 1 so that it appears to scroll the screen to the right into a new nametable (nametable 0). Is thinking like this bad?
Not bad at all. Look at this example from
"PPU scrolling" on the wiki:
At the top is the nametables; at the bottom is the visible portion of the background. Columns of tiles are rewritten (symbolized by columns of ? blocks) during vertical blanking just before the visible area (symbolized by the red bracket) moves into them.
So, that makes sense! Thank you tepples! I will try to make it work like that... have to draw four columns at a time... right now I can draw two columns at a time. Then I have to wait for a new frame so the two columns can be replaced. Is it better to draw four columns at a time?
unregistered wrote:
I will try to make it work like that... have to draw four columns at a time
You don't need to draw more than one column at a time. I just showed four because it makes the GIF clearer. SMB1 actually draws four columns, one at a time, and then fills the attributes for those columns.
tepples wrote:
You don't need to draw more than one column at a time.
Actually, the number of columns you need to draw depends on how much you scroll each frame. Most games don't scroll any faster than 8 pixels at a time, so they can get away with updating only 1 column at a time. To scroll up to 16 pixels from one frame to the next, you need to update 2 columns, and so on.
Thank you incredibly much tepples and tokumaru!
I'm gonna try scrolling less than 16 pixels per frame. So two 8bit columns they are already drawn correctly... must color them. This code appears to be very complex... to color them correctly. So far I've chosen to keep the
incrament increment +32...
Code:
set 2006 to 23C0
Write to 23C0...
and to 23E0
set 2006 to 23C8
write to 23C8...
and to 23E8
set 2006 to 23D0
write to 23D0...
and to 23F0
set 2006 to 23D8
write to 23D8...
and to 23F8
ok...so I guess if it is susposed to work like tepples' graphic it will be written something like...
umm... ...
Code:
lda $2007
and #11001100b
ora tileA
ora tileC
sta $2007
or
Code:
lda $2007
and #00110011b
ora tileB
ora tileD
sta $2007
edit: ^...depending on
wheather whether the $2006 address is odd or even. So after writing the 2 8bit columns will there be any attribute table suprizes when scrolling the screen?
Ok, so finally... I'm making rules and choices about drawing a new column to the screen. If it is an even column I'll have to find the new palette values for AA and CC... and I guess get the values already on the screen for BB and DD. How do I get those old values? I can
read from $2007... but how do I specify the address?
(I know the address increments by 1 or 32 after each
write to $2007.)
unregistered wrote:
I can
read from $2007... but how do I specify the address?
(I know the address increments by 1 or 32 after each
write to $2007.)
Reading works exactly like writing (set address through $2006, increments of 1 or 32 are still used), except you ignore the first read value.
However, due to the limited duration of VBlank, which would make it nearly impossible to read-modify-write a significant amount of bytes, attribute data is often mirrored in RAM, so that you're free to do all the processing during rendering, and during VBlank you just copy the data to VRAM. Depending on the type of scrolling you use though, it would be much better to just calculate full attribute bytes (i.e. areas of 32x32 pixels) and not have to mirror anything or read-modify-write from/to VRAM.
tokumaru wrote:
unregistered wrote:
I can
read from $2007... but how do I specify the address?
(I know the address increments by 1 or 32 after each
write to $2007.)
Reading works exactly like writing (set address through $2006, increments of 1 or 32 are still used), except you ignore the first read value.
However, due to the limited duration of VBlank, which would make it nearly impossible to read-modify-write a significant amount of bytes, attribute data is often mirrored in RAM, so that you're free to do all the processing during rendering, and during VBlank you just copy the data to VRAM.
Ah ha!
Thank you tokumaru! I totally forgot that my attribute data should already be mirrored in RAM! Tomorrow will be a full working all day day for me!
Here is another thing I'm not sure how to fix:
Code:
draw_RAMbufferColors:
; lda RAMbufferColors, x ;<this is ordered differently from its attribute table array...
; sta PPUDATA7 ; ...2xF8, 2xD8, 2xF0, 2xD0, 2xE8, 2xC8, 2xE0, 2xC0
; dex ; it's upside down or backwards... so we decrement
;should be labeled draw-to-RAMbufferColors
;and this should always be run right after draw_RAMbuffers... so the screen is already loaded
sta $ff
lda CurrentColumn
lsr a ; / by 2
sta t2
lda #$07
sta t2+1
lda CurrentColumn
tay
and #00000001b
bne +arightColumn
-- lda ($10), y
tax
lda MetatileRhombus, x
and #$03
sta upleftAA
clc
tya
adc #$10 ;increment y by 16
tay
lda ($10), y
tax
lda MetatileRhombus, x
and #$03
ldx #$04 ;need to shift left 4 times...
jsr shift_left
sta loleftCC
lda t2 ;CurrentColumn
clc ; <?
tay
adc #$08 ;increment t2 by 8
sta t2
lda attributes, y
and #11001100b ;now the accumulator holds old BB and DD
dec t2+1
ora upleftAA
ora loleftCC
ldx t2+1
sta RAMbufferColors, x
sta attributes, y
bpl --
jmp +;end
+arightColumn:
-- lda ($10), y
tax
lda MetatileRhombus, x
and #$03
ldx #$02 ;must shift left twice...
jsr shift_left
sta uprghtBB ;<new
clc
tya
adc #$10 ;increment y by 16
tay
lda ($10), y
tax
lda MetatileRhombus, x
and #$03
ldx #$06 ;need to shift left 6 times...
jsr shift_left
sta lorghtDD ;<new
lda t2 ;CurrentColumn
clc ; <?
tay
adc #$08 ;increment t2 by 8
sta t2
lda attributes, y
and #00110011b ;now the accumulator holds old AA and CC
dec t2+1
ora uprghtBB
ora lorghtDD
ldx t2+1
sta RAMbufferColors, x
sta attributes, y
bpl --
+ rts ;end of draw_RAMbufferColors
What I just changed is the very bottom; it used to say
Code:
ldx t2+1
ora uprghtBB
ora lorghtDD
dex
sta RAMbufferColors, x
sta attributes, y
stx t2+1
bpl --
But after thinking about it for a bit... those
ldx and
stx are 3 cycles each and the
dex is 2 cycles... and it makes more sense to use less bytes of code... so since the
dec is 5 cycles the changes make 0 extra cycles and there's less lines of code that are annoying... it's better, i think. But now the column I'm playing with moved four blocks down on
FCEUX 2.1.5s "Name Table Viewer"
In my head it seems like the problem is beyond this code... but could you check and see & report back any errors or improvements you see?
edit: tokumaru wrote:
Depending on the type of scrolling you use though, it would be much better to just calculate full attribute bytes (i.e. areas of 32x32 pixels) and not have to mirror anything or read-modify-write from/to VRAM.
Ok, I agree, though this is what my code suposedly does above... it basicly initalizes some variables at the top and then a branch decides which loop to enter... the top loop draws the left side of an attribute column and substitutes recent values from my attribute ram buffer for the right side. The bottom loop does the exact same thing invertedly... it draws the right side with its palettes and then substitutes the left side with values from the attribute RAM buffer. It seems like it would be a good try. edit2.edit3: Ok I'm sorry tokumaru for saying I agree in my first edit... now I realize you mentioned, "not have to mirror anything" and that's exactly what I've done with my RAM buffer. And so now it seems awful of me to start a reply with "I agree" and then continue to describe my ideas that negate what you talked about. I lied and I'm sorry for doing so. edit4: After spending a lot of time with this code I would like to say that I was also mistaken in my explanation of my code... the code doesn't draw the columns of tiles... it draws the colors stored in the metatile arrays... or maybe it would make more sense to say that my code up there will write the correct background palette attributes to each 16x16 metatile in singular columns.
tepples wrote:
unregistered wrote:
I didn't know RAM can overlap
There are two kinds of assembly language programmers: those who have written a
buffer overflow and those who have yet to.
I just figured my problem out! It was another buffer overflow, I think. What was wrong was my change of bne to bpl; it's not magic!
What happened was that In my code at the end
Code:
dec t2+1
ora upleftAA
ora loleftCC
ldx t2+1
sta RAMbufferColors, x
sta attributes, y
bpl --
that code...
the problem was that my CurrentColumn variable is at $0070 and next is my array RAMbufferColors. Changing the bpl back to bne solved the problem.
.. because What happened was that the bpl ran the x variable to #$FF and that made the
sta RAMbufferColors, x change the value of CurrentColumn to 64... and that drew the column four squares below.
I'm so happy I could figure this out!
text added in my edit.
I increased the increment added to my screen's scroll each frame from 1 to 4... and now the screen scrolls quite fast and my character speeds up too!
Why?
I'm trying to make my game more like a real game where like Mario, he never reaches the edge of the screen... cause it scrolls as fast as he runs. Our lady always reaches the edge of the screen and then appears on the other side of the screen. I speeded up the scroll to try and prevent this and our lady increases her speed while the screen is scrolling so she reaches the edge anyway!
edit: I haven't found code that was written to speed her up. How do I slow her down?
Thank you for reading this.
Impossible to know without seeing your movement code.
The camera should be totally separate from how your character moves. She'll move as fast as she's programmed to. That's it for that.
Later in the code, you decide to move the camera based on where she is. Like... if her position is greater than half the screen, the camera moves to her position - 128 (half the screen).
So... cameraposition = ladyposition - 128.
If cameraposition > levellength -256, cameraposition = levellength-256.
If cameraposition < 0, cameraposition = 0.
If cameraposition/8 - oldcameraposition/8 is not equal to 0, we moved at least one tile and need to update the nametables.
You can do some more trick types of scrolling, like having the camera only move forward when she's say... 16 pixels to the right of the middle, and only move back when she's 16 pixels from the left of the middle. (So running back and forth in the middle would not cause jerky scrolling), but above is the super basics that are easy to adapt to things like that.
Kasumi wrote:
Impossible to know without seeing your movement code.
The camera should be totally separate from how your character moves. She'll move as fast as she's programmed to. That's it for that.
Later in the code, you decide to move the camera based on where she is. Like... if her position is greater than half the screen, the camera moves to her position - 128 (half the screen).
So... cameraposition = ladyposition - 128.
If cameraposition > levellength -256, cameraposition = levellength-256.
If cameraposition < 0, cameraposition = 0.
If cameraposition/8 - oldcameraposition/8 is not equal to 0, we moved at least one tile and need to update the nametables.
You can do some more trick types of scrolling, like having the camera only move forward when she's say... 16 pixels to the right of the middle, and only move back when she's 16 pixels from the left of the middle. (So running back and forth in the middle would not cause jerky scrolling), but above is the super basics that are easy to adapt to things like that.
Kasumi, incredible response! Thank you a bunch!
Oh my movement code isn't
very any good... that's going to change though!
added in edit
Kasumi wrote:
Later in the code, you decide to move the camera based on where she is. Like... if her position is greater than half the screen, the camera moves to her position - 128 (half the screen).
Woah... Kasumi, that changes my entire view on this. Right now our game starts out drawing my sister's first two nametables and attribute tables. My most recent goal has been to successfully scroll into a third screen. So far I have code that draws two 8 bit wide columns... and the attribute tables part isn't working for some reason... but, you just have to specify a value 0 through F and the selected 16 bit wide column will be drawn correctly! Now my next step was to achieve scrolling into a third screen, but you have changed that... my new next step goal is to set the game so that the camera adjusts to our ladie's starting point in the level. This is going to require more thought but it's a new goal - I'm satisfied with it!
I believe you should work with a single table, if your game scrolls one way. Use basically a revolving door. Keep the next column, bytes of attributes to update, etc. in a RAM buffer. You don't need to look at it as two screens and you scroll to a third, it's a single screen that you scroll to another on.
Basically, keep either 30 or 60 bytes (One or two columns vertically) of tiles to write to wherever they go, and then eight bytes or however many for the updated attribute data that you need to add after. Shove it all to the PPU, but then also shove it to a big "Nametable Current" buffer that is like a binary number, when it overflows, it doesn't go to the "2nd nametable" row then back to the first. It would go back onto it's self, so you basically only have one screen of data in it at a time...I mean, unless you're showing two screens at once.
But I mean if that works, you can keep it like that, I don't exactly know how your engine is set up, that way might be better for what you have, but I'm not sure. Just my input on that. It's something that if it works, I wouldn't change it. But you can do it later if you want to save RAM, or just to change how the scrolling works.
3gengames wrote:
I believe you should work with a single table, if your game scrolls one way.
Our game will be scrolling both ways... but one way is good at first... I guess. So when scrolling both ways we would use two tables... both in RAM buffers I think.
3gengames wrote:
You don't need to look at it as two screens and you scroll to a third, it's a single screen that you scroll to another on.
Yes, that would be the point of switching to where the lady starts...
it's a solid level... starts at 0 and then ends at something like 1024.
3gengames wrote:
but then also shove it to a big "Nametable Current" buffer that is like a binary number, when it overflows, it doesn't go to the "2nd nametable" row then back to the first. It would go back onto it's self, so you basically only have one screen of data in it at a time...
Thank you 3gengames, I think I understand what you mean.
We will have to have 2 screens of data I think... a lot to think about....
Yep, don't fudge up this part! But even scrolling 2 ways, 1 buffer will work still. Just have to make the buffer go forward and backward in writes, nothing too bad. Shouldn't be hard to make, compared to most other things.
How do I draw on the nametable that's not shown? If the screen starts out on nametable #0... the next column drawn would be column 0 on nametable #1 right?
To draw on column 0 of nametable #1:
- Wait for the start of vertical blanking.
- Set the VRAM increment to +32.
- Set the VRAM address to $2400, or $2480 if you have a top status bar.
- Write a bunch of bytes.
Where you draw doesn't depend on the scroll position because you're going to set the scroll position again before the end of vblank.
tepples, thank you.
unregistered wrote:
How do I draw on the nametable that's not shown? If the screen starts out on nametable #0... the next column drawn would be column 0 on nametable #1 right?
No no... so I should draw column 0 of nametable 0... after i've scrolled to nametable #1... Would column 0 of nametable 0 be the start of the third screen? Maybe?
unregistered wrote:
Would column 0 of nametable 0 be the start of the third screen?
Yes.
tokumaru wrote:
unregistered wrote:
Would column 0 of nametable 0 be the start of the third screen?
Yes.
Thank you tokumaru.
---
Code:
0C1CA camera_aim:
0C1CA
0C1CA ;determines how much to move based on the players position
0C1CA A5 03 lda oX+0 ;players position
0C1CC 38 sec
0C1CD E9 04 sbc #$04
0C1CF 85 1F sta CameraX+0 ;camera's position
0C1D1
0C1D1 A5 04 lda oX+1
0C1D3 E9 00 sbc #$00
0C1D5 85 20 sta CameraX+1
0C1D7
0C1D7 60 rts ;end of camera_aim
So here is my initial try at a camera moving based on players position. I'm trying to subtract 4 from the player's position and so the camera should always be on the player. This doesn't happen... she eventually reaches the otherside of the screen.
unregistered wrote:
she eventually reaches the otherside of the screen.
So
oX is the object's coordinate within the level, right? How are you calculating the sprite coordinate then? You can't simply take oX and use that for the sprites, unchanged.
You have to keep in mind that when you're dealing with scrolling games, you have 2 coordinate systems to care about: level coordinates and screen coordinates. Level coordinates are absolute, they indicate where in the level the objects are, and this doesn't change, no matter where the camera is. Screen coordinates, however, are relative to the camera, so before displaying objects on the screen you have to convert level coordinates into screen coordinates. Since the left edge of the camera represents the left edge of the screen, you have to subtract the camera's coordinate from the coordinate you're converting:
SpriteX = oX - CameraX.
You may take shortcuts, but generally speaking, you have to convert between coordinate systems for everything you'll display on the screen, since the relative position of everything changes as the camera moves. For example, if you want to render a metatile column at the right side of the screen, you have to convert the column number from screen coordinates into level coordinates so that you know which column to read from the level map. You may have taken shortcuts here, but conceptually, this is what's happening (or should be!).
So, to convert from screen coordinates to level coordinates... you would add CameraX right?
Yes, but I can't think of many cases where that would be necessary. Things often happen in the level, and you need to translate that to the screen so that the player can see what's happening, but the opposite should be very rare. In a device with a touch screen you'd do that to check what parts of the level are being touched, but this is obviously not the case of the NES. Would you mind telling us why you need this conversion?
Zapper provides a Y coordinate (more or less), Vaus provides an X coordinate, and Oeka Kids tablet provides both. These need to be translated to world space coords.
Super Mario World spawns a stored item into the level from a fixed place on the screen.
tepples wrote:
Zapper provides a Y coordinate (more or less), Vaus provides an X coordinate, and Oeka Kids tablet provides both. These need to be translated to world space coords.
Yeah, input devices are usually the ones responsible for the need to convert screen coordinates into world coordinates, but I don't think unregistered is using any of those special controllers.
Quote:
Super Mario World spawns a stored item into the level from a fixed place on the screen.
This is indeed a legitimate reason that's not related to input.
In the message where I first talked about coordinate conversion I used the example of rendering metatiles to the right side of the screen, which would require coordinates to be converted from screen space to level space. This wasn't such a good example though, since you could achieve the same effect by rendering metatiles at the right side of the camera (not the screen!), and using coordinate conversion to find their final position on the screen.
tokumaru wrote:
Would you mind telling us why you need this conversion?
yes... you were talking about doing that and so I was wondering...
Quote:
tokumaru wrote:
For example, if you want to render a metatile column at the right side of the screen, you have to convert the column number from screen coordinates into level coordinates so that you know which column to read from the level map.
tokumaru wrote:
In the message where I first talked about coordinate conversion I used the example of rendering metatiles to the right side of the screen, which would require coordinates to be converted from screen space to level space. This wasn't such a good example though, since you could achieve the same effect by rendering metatiles at the right side of the camera (not the screen!), and using coordinate conversion to find their final position on the screen.
I don't understand.
Sorry. .. well I do understand that you are talking about what you were talking about before. I'm confused now. tepples wrote:
Super Mario World spawns a stored item into the level from a fixed place on the screen.
... I remember that from the New Super Mario Brothers DS game.
Was pretty neat battling little browser with a Mega Mushroom from above!
edit.
unregistered wrote:
Quote:
tokumaru wrote:
For example, if you want to render a metatile column at the right side of the screen, you have to convert the column number from screen coordinates into level coordinates so that you know which column to read from the level map.
tokumaru wrote:
used the example of rendering metatiles to the right side of the screen, which would require coordinates to be converted from screen space to level space. This wasn't such a good example though, since you could achieve the same effect by rendering metatiles at the right side of the camera (not the screen!), and using coordinate conversion to find their final position on the screen.
I don't understand.
Sorry. .. well I do understand that you are talking about what you were talking about before. I'm confused now. I just meant that I think it's best to start at the source (level map) and then calculate the target (screen) when rendering scrolling backgrounds, but my first comment suggested the opposite (calculate the source from the target). Both work, I just happen to find one method more appropriate than the other.
^
---
On the screen it looks like the first column is repeated every 8 pixels.
Code:
Instead of this
\000
0\00
00\0
there's this
\\\\
0000
0000
It redraws the same four columns of tiles over and over as the screen scrolls. I just want one copy of those four columns of tiles. And then I would like a different four columns... and then another different four columns. So the background looks like it's scrolling by. Do you understand?
Could you write some psudocode to give me an idea of what to do?
Continuing from this:
Quote:
If cameraposition/8 - oldcameraposition/8 is not equal to 0, we moved at least one tile and need to update the nametables.
columntoupdate = cameraposition / 8.
Edit: Well, when scrolling left anyway. When scrolling right, columntoupdate = (cameraposition+256)/8
Decode and pass that column to whatever you're using to draw columns instead of always the same one, like it sounds like you're doing now. I can't really give more detail than that without a refresher on how your level is actually stored.
Kasumi wrote:
Continuing from this:
Quote:
If cameraposition/8 - oldcameraposition/8 is not equal to 0, we moved at least one tile and need to update the nametables.
columntoupdate = cameraposition / 8.
Edit: Well, when scrolling left anyway. When scrolling right, columntoupdate = (cameraposition+256)/8
Decode and pass that column to whatever you're using to draw columns instead of always the same one, like it sounds like you're doing now.
Thank you so very much Kasumi!
That passing of the column... that was what I needed to understand. Now it is almost working... it draws a 32 wide column... and then skips a 32 wide column... and draws the third 32 bit wide column... and then skips again... and draws then skips again. So in one screen it draws every other 32 bit wide column twice. And also it starts in the wrong nametable... it should take a break at the beginning while our girl travels across nametable 0 and nametable 1... now it starts drawing the columns on nametable 1. It's almost working!!
... well, I tried to get every column to display... instead of every other 32 bit wide column. That involved creating an ATTENTIONflag variable and it was lousy. So I deleted all of that code and decided to try to make it wait until our character reaches nametable 1 to start drawing columns... but I'm lost. Here is my scrolling the screen code. What have you done to make the code wait until your character reaches nametable 1?
no, I going to try this myself.
(edit) added.
Kasumi wrote:
Later in the code, you decide to move the camera based on where she is. Like... if her position is greater than half the screen, the camera moves to her position - 128 (half the screen).
So... cameraposition = ladyposition - 128.
If cameraposition > levellength -256, cameraposition = levellength-256.
If cameraposition < 0, cameraposition = 0.
I want to ask you a question about your last line right there. How and why does this need to be one of the checks?
If cameraposition < 0, cameraposition = 0. For me this means that cameraposition cant ever be greater than 127... if we have to check if it is less than zero. Right now there is one thing I notice that's wrong... cameraposition can be greater than 127 because the levellength+1 is = to 4. So would that mean that the negative value would span two bytes? I'm so confused.
2^15 = 32768... would be a two byte negative value I think.
edit: So after finding the fast way of writing 2s compliment video...
first I write the positive 16bit binary number... 1000 0000 0000 0001. That's equal to 32769.
Then I invert all the digits after passing the first "1" going from right to left.
I get 0111 1111 1111 1111. Um so that's odd it starts with a 0.... but it's negative... hmmm... last edit: ahh maybe -32769 isnt possible for 16 bits.
Quote:
I want to ask you a question about your last line right there. How and why does this need to be one of the checks? If cameraposition < 0, cameraposition = 0
Let's say lady position is 0.
Quote:
cameraposition = ladyposition - 128.
cameraposition = 0 - 128.
cameraposition = -128.
That's why you do that check. The camera shouldn't be negative.
As for how, just subtract the numbers and check for a clear carry which tells you you went below zero. If that happens set camera position to zero. It makes no difference if you're using 8bit, 16bit or 24bit numbers. (But if you want to actually scroll, you want a range greater than 8 bits will provide)
Think in unsigned bytes for this.
Edit:
Quote:
Right now there is one thing I notice that's wrong... cameraposition can be greater than 127 because the levellength+1 is = to 4. So would that mean that the negative value would span two bytes
Huh? Let's say your level is 256 pixels long. Your lady is at 256. This puts the camera at 128. (Because cameraposition = ladyposition - 128) Except the cameraposition is the leftmost point of a 256 pixel box. 128+256 = 384. You're showing 128 past the right edge of the level.
So if cameraposition is greater than levellength - 256, you set it to levellength - 256.
This works for any value of levellength that is equal to or greater than 256 pixels.
Kasumi wrote:
Quote:
I want to ask you a question about your last line right there. How and why does this need to be one of the checks? If cameraposition < 0, cameraposition = 0
Let's say lady position is 0.
Quote:
cameraposition = ladyposition - 128.
cameraposition = 0 - 128.
cameraposition = -128.
That's why you do that check. The camera shouldn't be negative.
As for how, just subtract the numbers and check for a clear carry which tells you you went below zero. If that happens set camera position to zero. It makes no difference if you're using 8bit, 16bit or 24bit numbers. (But if you want to actually scroll, you want a range greater than 8 bits will provide)
Think in unsigned bytes for this.
Brilliant! Thank you Kasumi!
...I remember that cmp would work because it acts just like subtraction only it discards the answer... and that would be fine... the carry would still be cleared.Kasumi wrote:
Edit:
Quote:
Right now there is one thing I notice that's wrong... cameraposition can be greater than 127 because the levellength+1 is = to 4. So would that mean that the negative value would span two bytes
Huh?
levellength+1 is 4... so cameraposition would have to be big enough to reach 768... my logic doesn't work well right now.
Kasumi wrote:
Let's say your level is 256 pixels long. Your lady is at 256. This puts the camera at 128. (Because cameraposition = ladyposition - 128) Except the cameraposition is the leftmost point of a 256 pixel box. 128+256 = 384. You're showing 128 past the right edge of the level.
So if cameraposition is greater than levellength - 256, you set it to levellength - 256.
This works for any value of levellength that is equal to or greater than 256 pixels.
Thank you so much for stepping through both of your ideas!
edit.
Quote:
levellength+1 is 4... so cameraposition would have to be big enough to reach 768... my logic doesn't work well right now.
Where is levellength+1 even coming from? You've used it in both your posts, and I'm not seeing it. Also 4 (in the examples I gave) would mean your level is only 4 pixels wide. If you're saying that's the number of screens across your level is, your levellength (following my example) is really 4*256 (1024) pixels, and your cameraposition/ladyposition have to be two bytes to hold that large a number.
Quote:
...I remember that cmp would work because it acts just like subtraction only it discards the answer... and that would be fine... the carry would still be cleared.
No, you cannot use cmp for 16bit numbers (which you'll need for scrolling past more than one screen) because cmp also ignores the state of the carry before the operation.
Edit: (Disclaimer: You can totally use cmp for 16 bit numbers with branches and such, but it's more work for probably no gain in this case.)
Code:
lda #$00
clc
sbc #$00
;Carry is clear
Code:
lda #$00
clc
cmp #$00
;Carry is set.
This will affect the higher bytes of all subtractions greater than 8 bits, so you must use sbc.
Edit:
Code:
lda camerapositionlow
sec
sbc ladypositionlow
sta camerapositionlow
lda camerapositionhigh
sbc ladypositionhigh
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
Edit: The above is totally wrong. See
this post for a fix.
Kasumi wrote:
Quote:
levellength+1 is 4... so cameraposition would have to be big enough to reach 768... my logic doesn't work well right now.
Where is levellength+1 even coming from? You've used it in both your posts, and I'm not seeing it. Also 4 (in the examples I gave) would mean your level is only 4 pixels wide. If you're saying that's the number of screens across your level is, your levellength (following my example) is really 4*256 (1024) pixels, and your cameraposition/ladyposition have to be two bytes to hold that large a number.
When I added the +1 at the end... that +1 means levellength is a two byte variable. I don't know if I also knew that cameraposition/ladyposition would have to be two bytes to hold that large a number. You should know that levellength+1 could be used for your subtract 256... decrement levellength+1 by 1.
I appreciate your helpfullness
...and I'm sorry for using +1 to mean my variable is the high byte.
To compare large numbers, try using cmp for the first and sbc for the rest.
Code:
lda this_lo
cmp that_lo ; set up carry for next sbc
lda this_hi
sbc that_hi
; right now, carry is set if and only if this >= that
Kasumi wrote:
Quote:
...I remember that cmp would work because it acts just like subtraction only it discards the answer... and that would be fine... the carry would still be cleared.
No, you cannot use cmp for 16bit numbers (which you'll need for scrolling past more than one screen) because cmp also ignores the state of the carry before the operation.
Why does the state of the carry before the operation matter?
Kasumi wrote:
Edit: (Disclaimer: You can totally use cmp for 16 bit numbers with branches and such, but it's more work for probably no gain in this case.)
Code:
lda #$00
clc
sbc #$00
;Carry is clear
Code:
lda #$00
clc
cmp #$00
;Carry is set.
This will affect the higher bytes of all subtractions greater than 8 bits, so you must use sbc.
Thank you for the correction!
Kasumi wrote:
Edit:
Code:
lda camerapositionlow
sec
sbc ladypositionlow
sta camerapositionlow
lda camerapositionhigh
sbc ladypositionhigh
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
Thought I understood this code. No I do not understand. edit.
Quote:
and I'm sorry for using +1 to mean my variable is the high byte.
I was just interpreting it as an addition to a value rather than address. No need to be sorry. I just always only see things one way.
tepples wrote:
To compare large numbers, try using cmp for the first and sbc for the rest.
YO!!!! That made my evening, I can use that in quite a few places!
I concede, it's not more work to do that at all.
Edit2: I really can't thank you enough. Off topic, but you caught me in the middle of rewriting some object collision/interaction stuff and I can now save a small amount of time in subroutines that will be run many times per frame.
Quote:
Why does the state of the carry before the operation matter?
Because the sbc/adc (but not cmp) take the carry before the operation into account.
Code:
lda #$00
sec
sbc #$00
;Result is #$00 with set carry.
Code:
lda #$00
clc
sbc #$00
;Result is #$FF, with clear carry.
And then the second subtraction of the 16 bit number needs to take the result of the carry from the first into account. (which cmp doesn't do.)
Quote:
No I do not understand.
Edit: Because I don't think things through sometimes. It should be this:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh
sbc #$00;High byte of $0080
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
Which will make infinitely more sense, because what I posted before will not work at all. My logic
here was sound, but what I wrote in 6502 wasn't that logic... Not thinking, not debugging, etc... I'm sorry. I kind of look forward to these posts, but then, sometimes... I hurt more than help.
Also, this does not include the "If cameraposition > levellength -256, cameraposition = levellength-256."
For more explanations about 16-bit comparisons, see
http://www.6502.org/tutorials/compare_beyond.html
Kasumi wrote:
tepples wrote:
To compare large numbers, try using cmp for the first and sbc for the rest.
YO!!!! That made my evening, I can use that in quite a few places!
I concede, it's not more work to do that at all.
Edit2: I really can't thank you enough. Off topic, but you caught me in the middle of rewriting some object collision/interaction stuff and I can now save a small amount of time in subroutines that will be run many times per frame.
sbc is the same as
cmp in cycles use... so after looking at tepples post again I'm guessing you save (don't require) an
sec. Is that correct?
I'm still confuzzled
about:
Kasumi wrote:
Quote:
Why does the state of the carry before the operation matter?
Because the sbc/adc (but not cmp) take the carry before the operation into account.
Code:
lda #$00
sec
sbc #$00
;Result is #$00 with set carry.
Code:
lda #$00
clc
sbc #$00
;Result is #$FF, with clear carry.
And then the second subtraction of the 16 bit number needs to take the result of the carry from the first into account. (which cmp doesn't do.)
I can see what happens but my brain is missing the why. Lunch...
(I'm going to come back to this... and honestly I am excited about learning about the carry and math. )edit.
Kasumi wrote:
Quote:
and I'm sorry for using +1 to mean my variable is the high byte.
I was just interpreting it as an addition to a value rather than address. No need to be sorry. I just always only see things one way.
Kasumi wrote:
Quote:
No I do not understand.
Edit: Because I don't think things through sometimes. It should be this:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh
sbc #$00;High byte of $0080
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
Which will make infinitely more sense, because what I posted before will not work at all. My logic
here was sound, but what I wrote in 6502 wasn't that logic... Not thinking, not debugging, etc... I'm sorry. I kind of look forward to these posts, but then, sometimes... I hurt more than help.
Aw... you are very kind!
Look forward to these posts. I appreciate your honesty and apology and I don't think you hurt more than help.
: )
Kasumi wrote:
Also, this does not include the "If cameraposition > levellength -256, cameraposition = levellength-256."
Thank you Kasumi!
Quote:
sbc is the same as cmp in cycles use... so after looking at tepples post again I'm guessing you save (don't require) an sec. Is that correct?
This is correct. Although, sometimes you can avoid the sec anyway, even when using sbc. What matters is not that sec is used, just that the carry is set.
Code:
bcc somewhere
;If we're here, we didn't branch, so the carry is set.
lda variable
;sec;Not needed right here, because we can guarantee the carry is already set
sbc variable2;
somewhere:
Stuff like this is why it takes forever for me to write code. I think I like optimizing more than getting things working. *shrug*
Quote:
I can see what happens but my brain is missing the why
I'm not sure I can explain that stuff better than the guides, but apparently I'm giving it a shot. Check the 6+5 = 11 on this poster:
http://www.abcteach.com/documents/poste ... elem-24639 (It's kiddy, but it actually shows what I want to show)
The orange 1 is basically like the carry bit. For the tens place, it's adding 0+0. PLUS CARRY (1 in this case)!
If you were adding 4+5, the tens place would add 0+0. PLUS CARRY (0, in that case! Because 4+5 doesn't carry to the tens place.)
So when you add on the 6502 with adc, you are
always adding the two numbers. PLUS CARRY! It just carries over to the next byte when it overflows, instead of the next place when you run out of digits.
This is why you clear the carry before most additions. If you don't, you will add an extra one if the carry was set!
Code:
00|FF (the | separates the two bytes into "places" like the one and tens place in the decimal addition example)
+00|01
------
01|00
You'll notice for the high bytes, you're doing 00+00, just like for the tens place in that example poster. But, like in the example poster, the addition of the lower place causes an overflow. FF+01 is greater than 255, so the carry ends up set. 5+6 is greater than 9, so you end up carrying one.
Anytime you add a number and result would have been greater than the byte can hold, the carry is set. Otherwise it is cleared.
For clarity: ADC will always set/clear the carry based on the result of the addition. See this code:
Code:
sec
lda #$00
adc #$00
;Carry is cleared.
The carry is not "left alone" (In this case, it does not stay set) if there was no carryover. If there was no carryover, it is clear. If there was, it is set. Nothing else.
This ensures that when you add the higher bytes (places) of the number, they get the correct result. This is why you DO NOT change the carry between operations that are part of the same multi byte add or subtract. The previous operations will make the carry right (whether there was a carry or not) and you don't have to worry about it to get the right result.
Now... subtraction is a bit different. It subtracts the two numbers and the OPPOSITE of the carry. So when the carry is set (1), it will just subtract one number from the other. When the carry is clear (0), it will subtract one number from the other, AND an additional one.
The way I used to remember it... If the carry is opposite what you would normally set it to before that operation, that's when you get the extra one. (You clear the carry before addition, so when it's set it adds one more. You set the carry before subtraction, so when it's clear, it subtracts one more.)
Three things to take away:
1. The carry is ALWAYS taken into account when you use adc or sbc, so make sure it's right for the operation you intend to do before that operation runs. (Clear before addition, set before subtraction)
2. The carry will become the opposite of what you would normally initialize it to if the operation goes outside the boundaries of a byte. (So if an addition would have yielded more than 255, or a subtraction would have yielded less than 0.) Otherwise, the carry becomes what you would normally initialize it to.
3. If the carry is the opposite of what you would normally initialize it to, one extra will be used in the operation. (One extra will be subtracted for sbc, or one extra will be added for adc.)
That's really all there is to it. The rest is the "why" behind it. With the knowledge, you can do fun stuff like this:
Code:
bcc somewhere
;The carry is set because we didn't branch
;We want to add eight to the accumulator
;clc;We could clear the carry
;adc #$08;And add 8.
adc #$07;Or... we could add 7. Because we know the carry is set, and 7+1 is eight.
But... don't do stuff like that in your game until you're really sure about it. If you understand it why it works, though, you've got a handle on the carry.
I really do look forward to these posts. This is my favorite section of the forum, and I always feel bad when I mislead people.
Kasumi wrote:
Quote:
sbc is the same as cmp in cycles use... so after looking at tepples post again I'm guessing you save (don't require) an sec. Is that correct?
This is correct. Although, sometimes you can avoid the sec anyway, even when using sbc. What matters is not that sec is used, just that the carry is set.
Code:
bcc somewhere
;If we're here, we didn't branch, so the carry is set.
lda variable
;sec;Not needed right here, because we can guarantee the carry is already set
sbc variable2;
somewhere:
Stuff like this is why it takes forever for me to write code. I think I like optimizing more than getting things working. *shrug*
Quote:
I can see what happens but my brain is missing the why
I'm not sure I can explain that stuff better than the guides, but apparently I'm giving it a shot. Check the 6+5 = 11 on this poster:
http://www.abcteach.com/documents/poste ... elem-24639 (It's kiddy, but it actually shows what I want to show)
The orange 1 is basically like the carry bit. For the tens place, it's adding 0+0. PLUS CARRY (1 in this case)!
If you were adding 4+5, the tens place would add 0+0. PLUS CARRY (0, in that case! Because 4+5 doesn't carry to the tens place.)
So when you add on the 6502 with adc, you are
always adding the two numbers. PLUS CARRY! It just carries over to the next byte when it overflows, instead of the next place when you run out of digits.
This is why you clear the carry before most additions. If you don't, you will add an extra one if the carry was set!
Code:
00|FF (the | separates the two bytes into "places" like the one and tens place in the decimal addition example)
+00|01
------
01|00
You'll notice for the high bytes, you're doing 00+00, just like for the tens place in that example poster. But, like in the example poster, the addition of the lower place causes an overflow. FF+01 is greater than 255, so the carry ends up set. 5+6 is greater than 9, so you end up carrying one.
Anytime you add a number and result would have been greater than the byte can hold, the carry is set. Otherwise it is cleared.
For clarity: ADC will always set/clear the carry based on the result of the addition. See this code:
Code:
sec
lda #$00
adc #$00
;Carry is cleared.
The carry is not "left alone" (In this case, it does not stay set) if there was no carryover. If there was no carryover, it is clear. If there was, it is set. Nothing else.
This ensures that when you add the higher bytes (places) of the number, they get the correct result. This is why you DO NOT change the carry between operations that are part of the same multi byte add or subtract. The previous operations will make the carry right (whether there was a carry or not) and you don't have to worry about it to get the right result.
Now... subtraction is a bit different. It subtracts the two numbers and the OPPOSITE of the carry. So when the carry is set (1), it will just subtract one number from the other. When the carry is clear (0), it will subtract one number from the other, AND an additional one.
The way I used to remember it... If the carry is opposite what you would normally set it to before that operation, that's when you get the extra one. (You clear the carry before addition, so when it's set it adds one more. You set the carry before subtraction, so when it's clear, it subtracts one more.)
Three things to take away:
1. The carry is ALWAYS taken into account when you use adc or sbc, so make sure it's right for the operation you intend to do before that operation runs. (Clear before addition, set before subtraction)
2. The carry will become the opposite of what you would normally initialize it to if the operation goes outside the boundaries of a byte. (So if an addition would have yielded more than 255, or a subtraction would have yielded less than 0.) Otherwise, the carry becomes what you would normally initialize it to.
3. If the carry is the opposite of what you would normally initialize it to, one extra will be used in the operation. (One extra will be subtracted for sbc, or one extra will be added for adc.)
That's really all there is to it. The rest is the "why" behind it. With the knowledge, you can do fun stuff like this:
Code:
bcc somewhere
;The carry is set because we didn't branch
;We want to add eight to the accumulator
;clc;We could clear the carry
;adc #$08;And add 8.
adc #$07;Or... we could add 7. Because we know the carry is set, and 7+1 is eight.
But... don't do stuff like that in your game until you're really sure about it. If you understand it why it works, though, you've got a handle on the carry.
Yes, I understand everything well
; it is written very well.
Great job Kasumi! Even your last code example... I was suprized you quoted your code... but suddenly I was happy to see those comments... it was a really cool experience!
(: THANK YOU KASUMI!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Kasumi wrote:
I really do look forward to these posts. This is my favorite section of the forum, and I always feel bad when I mislead people.
This is my favorite section of the forum too!
edit.once more.
Kasumi wrote:
Quote:
No I do not understand.
Edit: Because I don't think things through sometimes. It should be this:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh
sbc #$00;High byte of $0080
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
So... this code is susposed to take care of
cameraposition = ladyposition - 128.and
If cameraposition < 0, cameraposition = 0.because you ended with:
Kasumi wrote:
Also, this does not include the "If cameraposition > levellength -256, cameraposition = levellength-256."
Ok well I copied your code and wrote my questions:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh ;why do you load ladypositionhigh here?
sbc #$00;High byte of $0080 ;<-- what does this comment mean?
sta camerapositionhigh
bcs abovezero ;I understand this... it is going to a place where cameraposition > 0
lda #$00 ;I also understand these... cameraposition = 0
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
edit: Well... I looked at this some more and want to say that I notice you aren't using
sec before my first question line... so that 128 would be the low... and 00 would be the high... I still don't understand, I'm thinking... but Yes I dont understand.
Quote:
lda ladypositionhigh ;why do you load ladypositionhigh here?
Because ladyposition is one number stored in two bytes. If you want to subtract a number from it, you have to subtract both bytes of that number from both bytes of ladyposition. If you don't also subtract from the high byte, it's going to end up wrong when the low byte wraps. This is 16 bit subtraction.
Quote:
sbc #$00;High byte of $0080 ;<-- what does this comment mean?
We're subtracting 128. 128 is $80 if you use one byte. 128 is $0080 if you use two bytes. So the high byte for 128 is $00.
To add 256, you'd do this:
Code:
lda ladypositionlow
clc
adc #$00
sta camerapositionlow
lda ladypositionhigh
adc #$01;High byte of $0100 (256)
sta camerapositionhigh
;Well... for code clarity you'd do that, anyway.
to add one, you'd do this:
Code:
lda ladypositionlow
clc
adc #$01
sta camerapositionlow
lda ladypositionhigh
adc #$00;High byte of $0001
sta camerapositionhigh
Let's pretend ladyposition is $00FF or 255 (so ladypositionlow is #$FF and ladypositionhigh is #$00) in the above add one example.
Use what you've learned about the carry to see how ladyposition gets to $0100 or 256. (so ladypositionlow is #$00 and ladypositionhigh is #$01). Does it make sense? Then see how it goes to $0101. The carry is what allows one byte to overflow into the next without branching.
That's how you do 16bit math. I'm not prepared to write a huge post on it at the moment, though.
Kasumi wrote:
Quote:
lda ladypositionhigh ;why do you load ladypositionhigh here?
Because ladyposition is one number stored in two bytes. If you want to subtract a number from it, you have to subtract both bytes of that number from both bytes of ladyposition. If you don't also subtract from the high byte, it's going to end up wrong when the low byte wraps. This is 16 bit subtraction.
Quote:
sbc #$00;High byte of $0080 ;<-- what does this comment mean?
We're subtracting 128. 128 is $80 if you use one byte. 128 is $0080 if you use two bytes. So the high byte for 128 is $00.
To add 256, you'd do this:
Code:
lda ladypositionlow
clc
adc #$00
sta camerapositionlow
lda ladypositionhigh
adc #$01;High byte of $0100 (256)
sta camerapositionhigh
;Well... for code clarity you'd do that, anyway.
to add one, you'd do this:
Code:
lda ladypositionlow
clc
adc #$01
sta camerapositionlow
lda ladypositionhigh
adc #$00;High byte of $0001
sta camerapositionhigh
Let's pretend ladyposition is $00FF or 255 (so ladypositionlow is #$FF and ladypositionhigh is #$00) in the above add one example.
Use what you've learned about the carry to see how ladyposition gets to $0100 or 256. (so ladypositionlow is #$00 and ladypositionhigh is #$01). Does it make sense? Then see how it goes to $0101. The carry is what allows one byte to overflow into the next without branching.
That's how you do 16bit math. I'm not prepared to write a huge post on it at the moment, though.
I am going to understand 16bit math; thank you for answering me.
edit: Yes that makes sense. Now I must apply what I've learned. Give me a while to do this. It is 1:56pm n0ow.
Code:
;camera movement
;
;
;
;***********************
.enum LocalVariables4mvement
t12 .dsb 2
ladyposition .dsb 2
;ladypositionlow .dsb 1 ;players position
;ladypositionhigh .dsb 1 ;players position
cameraposition .dsb 2
;camerapositionlow .dsb 1
;camerapositionhigh .dsb 1
.ende
camera_aim:
;determines how much to move based on the players position
sta $ff
;set players position and cameraposition
lda oX+0
sta ladyposition+0
lda oX+1
sta ladyposition+1
lda CameraX+0
sta cameraposition+0
lda CameraX+1
sta cameraposition+1
;Is our players position is greater than half the screen
lda ladyposition+0 ;players position
bpl +question2
lda ladyposition+0
sec
sbc #128 ;cameraposition = ladyposition - 128.
sta CameraX+0
lda ladyposition+1
sbc #$00
sta CameraX+1
+question2
;Is cameraposition > levellength-256
lda cameraposition+1
cmp levellength_high
bcs +question3
;make cameraposition = levellength-256
clc
lda #$00
sta CameraX+0
lda levellength_high ;...is already set at levellength-256
sta CameraX+1
+question3
;Is cameraposition < 0,
lda CameraX+1
bpl +abovezero
;cameraposition = 0
lda #$00
sta CameraX+1 ;cameraposition+1
sta CameraX+0 ;cameraposition+0
jmp +end
+abovezero:
;move camera
+end rts ;end of camera_aim
My code is above... how do I determine if cameraposition < 0? I noticed you branched if the carry was set right in the middle of the first question. I'm trying to deal with each question individually.
If the camera position has fallen below 0, then the high byte will have become 0xFF.
tepples wrote:
If the camera position has fallen below 0, then the high byte will have become 0xFF.
Thanks tepples!!
Quote:
I noticed you branched if the carry was set right in the middle of the first question.
I did this because if the carry is set, we are guaranteed not below zero. (Remember that the carry will be cleared if the result of the subtraction would have been less than zero. So no need for another check.)
What you're doing now:
Code:
lda CameraX+1
bpl +abovezero
Will work, but it limits your level size. If you have a level that's $80 screens long, when you scroll to the $80th screen, the scrolling will be set to zero. This wouldn't happen by checking carry as I did, and your levels could be $FF screens long. There are some other reasons to use the carry instead of the minus bit for unsigned math, but I don't think they'd come up for scrolling.
There's not much need to change it (if your levels never get that big), but at least understanding this is important because it creates hard to find bugs. (My own scrolling had this issue at first. Actually... I think it still does...
)
Edit:
This is wrong.
Code:
;Is cameraposition > levellength-256
lda cameraposition+1
cmp levellength_high
bcs +question3;If the carry is set, that means cameraposition is greater than levellength-256
;So... if it's greater, we don't fix it, if it's less we do.
;make cameraposition = levellength-256
clc;Also, why do this? There isn't an add around.
lda #$00
sta CameraX+0
lda levellength_high ;...is already set at levellength-256
sta CameraX+1
Kasumi wrote:
Quote:
I noticed you branched if the carry was set right in the middle of the first question.
I did this because if the carry is set, we are guaranteed not below zero. (Remember that the carry will be cleared if the result of the subtraction would have been less than zero. So no need for another check.)
Ah SWEET! Thank you for your in-parenthesis help!
Kasumi wrote:
What you're doing now:
Code:
lda CameraX+1
bpl +abovezero
Will work, but it limits your level size. If you have a level that's $80 screens long, when you scroll to the $80th screen, the scrolling will be set to zero. This wouldn't happen by checking carry as I did, and your levels could be $FF screens long. There are some other reasons to use the carry instead of the minus bit for unsigned math, but I don't think they'd come up for scrolling.
There's not much need to change it (if your levels never get that big)
Haha that's a crazy long level 128 screens
Kasumi wrote:
, but at least understanding this is important because it creates hard to find bugs. (My own scrolling had this issue at first. Actually... I think it still does...
)
YES. Thank you for noteing this... I hope I can remember.
Kasumi wrote:
Edit:
This is wrong.
Code:
;Is cameraposition > levellength-256
lda cameraposition+1
cmp levellength_high
bcs +question3;If the carry is set, that means cameraposition is greater than levellength-256
; so... if it's greater, we do fix it, if it's less we don't.
; like I mean if cameraposition is greater than levellength-256... then we fix it.
; We fix it like this v
;make cameraposition = levellength-256
; cleared this unneeded clc away thank you Kasumi :)
lda #$00
sta CameraX+0
lda levellength_high ;...is already set at levellength-256
sta CameraX+1
unregistered wrote:
Haha that's a crazy long level 128 screens
Depends on the type of the game... if it's more puzzle/obstacle oriented, then yes, 128 screens is pretty long, but in speed-oriented games 128 screens isn't so big. There aren't many games with such huge levels on the NES, but several levels in Sonic 3 (& Knuckles) were that big, and several screens tall as well.
Quote:
like I mean if cameraposition is greater than levellength-256... then we fix it.
I know. That's the right logic, but the code you have isn't running that logic. That's what I'm saying.
Let's pretend these are our values.
Cameraposition+1 = #$03
levellength_high = #$02
Code:
lda cameraposition+1
cmp levellength_high
bcs +question3
3-2 is not less than 0. Carry stays set. So you branch passed the code that would fix it in the wrong case.
Edit: Or it's possible I'm confused by cameraposition and camerax being separate. In which case, my apologies again.
More:
Code:
lda ladyposition+0 ;players position
bpl +question2
That may not work either. (Unless you're doing Zelda Style scrolling, I suppose)
Lady position is at $0080. Okay. The screen will scroll with her until she gets to $0100. Then totally stop scrolling until she gets to $0180. You'd (probably) also want it to also scroll while she's traveling from $0100-$017F.
That said, I am indeed confused about cameraposition and camerax. The problem I see is that you're checking old values. At the very beginning, cameraposition is set to camerax. We then check cameraposition, but change cameraX. What this means is that if any of these conditionals changes cameraX to an out of bounds value, it won't be caught until the next frame, when cameraposition is set to cameraX again. At least that's my understanding of it.
It's actually not too big a problem with the current code, but if you change it to smooth scrolling (which may not even be your goal) it becomes a potential one. Apologies if I'm rambling about nothing. Like I said before, I tend to see things in just one way, so it's very possible you're doing something I'm not seeing.
tokumaru wrote:
unregistered wrote:
Haha that's a crazy long level 128 screens
Depends on the type of the game... if it's more puzzle/obstacle oriented, then yes, 128 screens is pretty long, but in speed-oriented games 128 screens isn't so big. There aren't many games with such huge levels on the NES, but several levels in Sonic 3 (& Knuckles) were that big, and several screens tall as well.
Oh yeah... I think I've watched my sister play Sonic 3 on our gamecube... yes, I can see how 128 screens just fly on by; thanks for pointing that out tokumaru!
Kasumi wrote:
Quote:
like I mean if cameraposition is greater than levellength-256... then we fix it.
I know. That's the right logic, but the code you have isn't running that logic. That's what I'm saying.
Let's pretend these are our values.
Cameraposition+1 = #$03
levellength_high = #$02
Code:
lda cameraposition+1
cmp levellength_high
bcs +question3
3-2 is not less than 0. Carry stays set. So you branch passed the code that would fix it in the wrong case.
Edit: Or it's possible I'm confused by cameraposition and camerax being separate.
cameraposition is given CameraX at the beginning... so they are the same. CameraX is always updated.
Kasumi wrote:
In which case, my apologies again.
More:
Code:
lda ladyposition+0 ;players position
bpl +question2
That may not work either. (Unless you're doing Zelda Style scrolling, I suppose)
Lady position is at $0080. Okay. The screen will scroll with her until she gets to $0100. Then totally stop scrolling until she gets to $0180. You'd (probably) also want it to also scroll while she's traveling from $0100-$017F.
That said, I am indeed confused about cameraposition and camerax. The problem I see is that you're checking old values. At the very beginning, cameraposition is set to camerax. We then check cameraposition, but change cameraX. What this means is that if any of these conditionals changes cameraX to an out of bounds value, it won't be caught until the next frame, when cameraposition is set to cameraX again.
And would that be a problem? ( I'm trying to understand my code too.
)
Kasumi wrote:
At least that's my understanding of it.
It's actually not too big a problem with the current code, but if you change it to smooth scrolling (which may not even be your goal) it becomes a potential one. Apologies if I'm rambling about nothing. Like I said before, I tend to see things in just one way, so it's very possible you're doing something I'm not seeing.
Yes, it's also very possible that you are alerting me to some of the problems with my code. Thank you for your help!
Quote:
cameraposition is given CameraX at the beginning... so they are the same. CameraX is always updated.
Then indeed, the example stands and you have that branch wrong.
Quote:
And would that be a problem? ( I'm trying to understand my code too.
)
Yes. Depending on how your level data subroutines work, passing them out of bounds places to work with could crash your game at worst.
Like... CameraX is set to one tile outside the level boundary. The part of your code that draws tiles uses this value of CameraX to know what tile it's supposed to draw. It tries to load data that's not part of your level data. You could end up in an infinite loop loading whatever bytes happen to be passed the end of your level, since those bytes are not actually formatted by you to be used by that subroutine.
There are other lesser potential issues, like your scrolling would jerk backwards at the edge of the levels.
Kasumi wrote:
There are other lesser potential issues, like your scrolling would jerk backwards at the edge of the levels.
I'm happy you have had all these problems... it makes my problems seem like actual problems.
Ha ha... good night.
yahning
Kasumi wrote:
Quote:
cameraposition is given CameraX at the beginning... so they are the same. CameraX is always updated.
Then indeed, the example stands and you have that branch wrong.
I am wondering if there is any book that I could buy that would teach me... it'd have a good 20 pages on scrolling. And other things about making an 8bit nintendo game. Just a generaly good book I could reread often because some days I forget things...
I remember that someone said there was a snes book... maybe I could read that. Thank you Kasumi and everyone else!
Google pygame book gave me a few results. If you work through one of those, you might be able to learn scrolling and other general game programming concepts in a more forgiving environment than the NES and then apply the concepts when porting your work in Pygame back to the NES.
tepples wrote:
Google pygame book gave me a few results. If you work through one of those, you might be able to learn scrolling and other general game programming concepts in a more forgiving environment than the NES and then apply the concepts when porting your work in Pygame back to the NES.
Thanks tepples!
I followed your google search and ended up with
a book in pdf form named
Making Games with Python & Pygame by Al Sweigart. I read it through to page 69.
(I think 69 was the last year we recieved an album release from the Beatles. ) During my reading I was extreemly excited to read about their graphics using 8x8 tiles... JUST LIKE OUR NES!
But then some disappointment surfaced as I read about the pygame tuples... but maybe that not-like-an-NESness is part of the reason Pygame is a more forgiving environment. I am enjoying the NES programming a bit more after pygame reading. It made me return to page 54 of this thread where I reread tokumaru's excellent scrolling is like a camera with a stationary level. I'm going to stay here in NES land.
edit:
Kasumi wrote:
Quote:
cameraposition is given CameraX at the beginning... so they are the same. CameraX is always updated.
Then indeed, the example stands and you have that branch wrong.
Does this code fair better?
Code:
camera_aim:
;determines how much to move based on the players position
sta $ff
;set players position and cameraposition
lda oX+0
sta ladyposition+0
lda oX+1
sta ladyposition+1
lda CameraX+0
sta cameraposition+0
lda CameraX+1
sta cameraposition+1
;Is our players position is greater than half the screen
lda ladyposition+0 ;players position
bpl +question2
lda ladyposition+0
sec
sbc #128 ;cameraposition = ladyposition - 128.
sta CameraX+0
lda ladyposition+1
sbc #$00
sta CameraX+1
+question2 ;problem: branch is incorrect
;Is cameraposition > levellength-256
lda cameraposition+1
cmp levellength_high
bpl +question3;was bcs
;make cameraposition = levellength-256
clc
lda #$00
sta CameraX+0
lda levellength_high ;...is already set at levellength-256
sta CameraX+1
+question3
;Is cameraposition < 0,
lda CameraX+1
bpl +abovezero
;cameraposition = 0
lda #$00
sta CameraX+1 ;cameraposition+1
sta CameraX+0 ;cameraposition+0
jmp +end
+abovezero:
;move camera
;lda t12+0
;sta CameraX+0
;lda t12+1
;sta CameraX+1
+end rts ;end of camera_aim
Changed the branch from bcs to bpl. We want to branch
(skip the fix) for all the positive values... I think. It runs somewhat better now.
edit2.
Ok I was able to figure this out!!!
This is the code I needCode:
+question2 ;branch is now CORRECT!
;Is cameraposition > levellength-256
sec
lda cameraposition+1
cmp levellength_high
bmi +question3
It took a long time!
I just needed to sit down with it and use a pencil (sp?) and most of an entire page of paper... and so now I can focus on the rest of the code on Monday!
The code runs kind of differently now but it's not good yet.
edit.
Kasumi wrote:
More:
Code:
lda ladyposition+0 ;players position
bpl +question2
That may not work either. (Unless you're doing Zelda Style scrolling, I suppose)
Lady position is at $0080. Okay. The screen will scroll with her until she gets to $0100.
Why will the screen scroll with her? This code is not scrolling code...it is camera movement code.
Kasumi wrote:
Then totally stop scrolling until she gets to $0180. You'd (probably) also want it to also scroll while she's traveling from $0100-$017F.
Well... the comment above that code...
Code:
;Is our players position is greater than half the screen
lda ladyposition+0 ;players position
bpl +question2
It guides the code... the bpl branches when the value of our players position is less than 128 (half the screen).
Quote:
it is camera movement code.
I assumed you'd want the camera to center on your character. Even assuming this isn't scrolling code, the point of the camera is to have the screen scroll to it, right? And so if the camera is centered on the character, the screen would scroll to her. If you don't want that, cool. Let me know specifically what type of scrolling/camera movement you're after so I can help better.
Quote:
It guides the code... the bpl branches when the value of our players position is less than 128 (half the screen)
It doesn't do that in the case I mentioned. You're assuming half the screen is always 128, but that's not true if you scroll. It's clear you know what you want to do but the code you're writing doesn't seem to be doing that. That's all I'm trying to point out. I could be misunderstanding, so here are some images.
The green and blue is the level, which the player can only see one screen worth of at a time. The white box is the camera's position in the level. This is what the player is currently seeing. i.e. what's on the screen.
The light colored box is the character. The Red line is the middle of the screen. (In between the bounds of what the player is seeing. So the middle of the white box)
So, here's the first image.
The lady's x position is 14 in decimal. $000E. She's to the left of the center of the screen. We load her low byte (#$0E). Its high bit isn't set. So we would take that branch.
Here's the second image.
The lady's x position is 142 in decimal. $008E. But note that the camera also moved. So she's to the left of the center of the screen. We load her low byte (#$8E). Its high bit is set. We would not take that branch.
In both images, she's to the left of the screen. In one we would take the branch, in the other we would not.
Here's both images together:
(The second image is probably an impossible scenario with your current code, but hopefully you see where I'm going with them. See below for what I think will happen with the code you have now.)
There's a new line. The black line represents 256. So when the lady crosses it, her low byte will be 00 again.
Note at the beginning, it works as expected. It doesn't scroll with her at the beginning, because it's branching based on her non negative low byte. Then, she reaches the center of the screen ($0080). Her low byte is now negative, and we start to scroll. Then she hits the black line($0100). At that point, her low byte is $00 again. So we stop scrolling because that's a positive low byte. The she reaches the edge of the screen. (Recall that the screen stopped scrolling with her at $00FF. Since the middle of the screen scrolls with her, this makes the right part of the level that the camera is showing $017F.) Then the camera JUMPS to her (from $00FF to $0180), because her low byte is non negative again. (Her full position is $0180).
From the player's point of view:
Does this make sense? This is what I think will happen with your current code.
Kasumi wrote:
Quote:
it is camera movement code.
I assumed you'd want the camera to center on your character.
Yes that would be good I think.
Kasumi wrote:
Even assuming this isn't scrolling code, the point of the camera is to have the screen scroll to it, right? And so if the camera is centered on the character, the screen would scroll to her. If you don't want that, cool. Let me know specifically what type of scrolling/camera movement you're after so I can help better.
Quote:
It guides the code... the bpl branches when the value of our players position is less than 128 (half the screen)
It doesn't do that in the case I mentioned. You're assuming half the screen is always 128, but that's not true if you scroll. It's clear you know what you want to do but the code you're writing doesn't seem to be doing that. That's all I'm trying to point out. I could be misunderstanding, so here are some images.
The green and blue is the level, which the player can only see one screen worth of at a time. The white box is the camera's position in the level. This is what the player is currently seeing. i.e. what's on the screen.
The light colored box is the character. The Red line is the middle of the screen. (In between the bounds of what the player is seeing. So the middle of the white box)
So, here's the first image.
The lady's x position is 14 in decimal. $000E. She's to the left of the center of the screen. We load her low byte (#$0E). Its high bit isn't set. So we would take that branch.
Here's the second image.
The lady's x position is 142 in decimal. $008E. But note that the camera also moved. So she's to the left of the center of the screen. We load her low byte (#$8E). Its high bit is set. We would not take that branch.
In both images, she's to the left of the screen. In one we would take the branch, in the other we would not.
Here's both images together:
(The second image is probably an impossible scenario with your current code, but hopefully you see where I'm going with them. See below for what I think will happen with the code you have now.)
There's a new line. The black line represents 256. So when the lady crosses it, her low byte will be 00 again.
Note at the beginning, it works as expected. It doesn't scroll with her at the beginning, because it's branching based on her non negative low byte. Then, she reaches the center of the screen ($0080). Her low byte is now negative, and we start to scroll. Then she hits the black line($0100). At that point, her low byte is $00 again. So we stop scrolling because that's a positive low byte. The she reaches the edge of the screen. (Recall that the screen stopped scrolling with her at $00FF. Since the middle of the screen scrolls with her, this makes the right part of the level that the camera is showing $017F.) Then the camera JUMPS to her (from $00FF to $0180), because her low byte is non negative again. (Her full position is $0180).
From the player's point of view:
Does this make sense? This is what I think will happen with your current code.
This does make sense, however on my screen it is kind of different. The camera doesn't center on the character... it still scrolls when she is past $80 and she eventually reaches the edge of the screen and appears on the other side. $80 is always middle of screen... I need to distroy that somehow. Your explaination of what my game should be doing with those images is good. I can read through your giant paragraph and understand everything... but then it all gets jumbled as I look at your graphic... this means I need to continue rereading and then stareing until it becomes unjumbled.
edit: Your graphic skills and brain skills are excellent Kasumi!
I must reach an unjumbled state.
Quote:
This does make sense, however on my screen it is kind of different. The camera doesn't center on the character... it still scrolls when she is past $80 and she eventually reaches the edge of the screen and appears on the other side.
Is this what you're seeing with the current code? Or is this what you want to have happen? It's true I can't account for her appearing on the other side, but that seems a lot like the image to me. My animation still moves when she's past $80, but it stops at $0100-$017F which is the problem I'm describing. I'm assuming this where she appears on the other side of the screen for you. Maybe the behavior is different, but the source of the problem is almost definitely the same.
I guess the bottom line is: Anyway I look at it $80 can't always be the middle of the screen. This is because if the camera moves, the middle of the screen moves with it. So you can't do a check for the being left of the screen just by checking the low byte of the position. You need to use both.
That's what I did in the
code here.
Kasumi wrote:
Quote:
This does make sense, however on my screen it is kind of different. The camera doesn't center on the character... it still scrolls when she is past $80 and she eventually reaches the edge of the screen and appears on the other side.
Is this what you're seeing with the current code?
Yes
Kasumi wrote:
It's true I can't account for her appearing on the other side, but that seems a lot like the image to me. My animation still moves when she's past $80, but it stops at $0100-$017F which is the problem I'm describing. I'm assuming this where she appears on the other side of the screen for you. Maybe the behavior is different, but the source of the problem is almost definitely the same.
I guess the bottom line is: Anyway I look at it $80 can't always be the middle of the screen. This is because if the camera moves, the middle of the screen moves with it. So you can't do a check for the being left of the screen just by checking the low byte of the position. You need to use both.
I happily agree!
Thank you this message got through to me!
Have to think about this for a bit. Thanks Kasumi for your efforts to get me to understand this!
How can I check if our lady's position is greater than half the screen. It's confusing because her position can be positive or negative when it's greater thabn half the screen.
edit: I just tried bcc and bcs and neither one is satisfing.
edit2: BNE and BVC are both better... the screen is slower scrolling but all the columns are drawn... incorrectly but they are drawn.
This is where you forget signed numbers exist. If you CMP #$(Number) with unsigned numbers, if it's above that number or equal, it'll be BCS. If it's lower, it'll be BCC.
Thanks 3gengames!
But, I'm still confused
because why should I subtract #128 when the number will be higher and lower? There must be something I don't understand...
I think the best way to think of it as two planes. Yes, you sorta have to use the sprite position to move in the map and make sure it doesn't go past the boundary, but once it hits that line, just move the map forward and keep here there. It's hard to explain because I've thought this out thoroughly and made the decision it'd have to be highly integrated with my camera system, but...I dunno, just look at it. Look at the variables you have that show the position of the person, the map, and the camera, and figure out which you can use to solve the problem.
unregistered wrote:
Thanks 3gengames!
But, I'm still confused
because why should I subtract #128 when the number will be higher and lower? There must be something I don't understand...
You just fix it when it's lower, and don't when it's not.
Think about it this way: To check if it's lower before you do the actual subtract, you STILL have to do a compare (subtract). So why do it before? Do the subtract regardless, and fix the result only if it needs it. (The carry will tell you if it needs fixing!)
Read this:
Quote:
Three things to take away:
1. The carry is ALWAYS taken into account when you use adc or sbc, so make sure it's right for the operation you intend to do before that operation runs. (Clear before addition, set before subtraction)
2. The carry will become the opposite of what you would normally initialize it to if the operation goes outside the boundaries of a byte. (So if an addition would have yielded more than 255, or a subtraction would have yielded less than 0.) Otherwise, the carry becomes what you would normally initialize it to.
3. If the carry is the opposite of what you would normally initialize it to, one extra will be used in the operation. (One extra will be subtracted for sbc, or one extra will be added for adc.)
If that's not enough explanation, try the whole post it's from again:
viewtopic.php?p=112830#p112830Then see how this works:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh
sbc #$00;High byte of $0080
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
I mean... I probably shouldn't encourage you to lift it without understanding, but this working code has been available to study.
What is the state of the carry when ladyposition is < 128 and you subtract 128 from it? What is the state of the carry when ladyposition is > 128 and you subtract 128 from it? In which case do you want to discard the result and use zero? Then branch passed the fix on the opposite state of the carry.
Code:
camera_aim:
;determines how much to move based on the players position
sta $ff
;set players position and cameraposition
lda oX+0
sta ladyposition+0
lda oX+1
sta ladyposition+1
; lda CameraX+0
; sta cameraposition+0
; lda CameraX+1
; sta cameraposition+1
;First set cameraposition = ladyposition - 128
lda ladyposition+0
sec
sbc #128 ;cameraposition = ladyposition - 128.
sta CameraX+0
lda ladyposition+1
sbc #$00
sta CameraX+1
+question1 ;branch is now CORRECT!
;Is cameraposition > levellength-256
lda CameraX+1 ;was lda cameraposition+1
cmp levellength_high
bmi +question2
;make cameraposition = levellength-256
lda #$00
sta CameraX+0
lda levellength_high ;...is already set at levellength-256
sta CameraX+1
jmp +end
+question2
;Is cameraposition < 0,
lda CameraX+1
bpl +abovezero
;cameraposition = 0
lda #$00
sta CameraX+1 ;cameraposition+1
sta CameraX+0 ;cameraposition+0
jmp +end
+abovezero:
;move camera
;lda t12+0
;sta CameraX+0
;lda t12+1
;sta CameraX+1
+end rts ;end of camera_aim
Is that perfect?
Wait!! No! There isn't any thing to do with the carry. =( I just figured out that there are only 2 questions asked after you set cameraposition = playerposition - 128. So yall have been so helpful thank you 3gengames and Kasumi!!
edit.edit2: Well sorry guys... I just spent a long time changing the code to how it is now above. My brain is gone... I have to wait till
tomorrow... or Monday. I'm hoping that my code up there works really well aside from the fact that it doesn't do anything with the carry.
I would really like to know if my code is good. Good night yall.
edit3.
Quote:
Is that perfect? Wait!! No! There isn't any thing to do with the carry. =(
You don't NEED to use the carry here (the one issue would be levels $80 screens long or longer), but you should definitely take steps to understand it fully because it will probably come up again.
What you have looks like it will work.
Kasumi wrote:
you should definitely take steps to understand the carry fully because it will probably come up again.
Ok I will.
Kasumi wrote:
What you have looks like it will work.
THANK YOU KASUMI!!! edit.
Kasumi wrote:
What is the state of the carry when ladyposition is < 128 and you subtract 128 from it?
The carry is set for subtraction but it changes to clear because the answer is negative.
Kasumi wrote:
What is the state of the carry when ladyposition is > 128 and you subtract 128 from it?
The carry is set for subtraction and it stays set because the answer is positive.
Kasumi wrote:
In which case do you want to discard the result and use zero?
The first one.
Kasumi wrote:
Then branch passed the fix on the opposite state of the carry.
So... bcc?
(I dont understand.) edit: Well maybe I do understand!
Code:
bcs notfixed
;the fix part: change answer to 0
notfixed:
Kasumi wrote:
I think you got it.
YEAY!!
Thank you.
tepples, on page 59, wrote:
Sort of. The "p1 is pressing right on dpad" controls the location of a character in the game world, and a camera data structure follows the location of this character. When the camera moves, you draw the metatiles onto which the camera is moving.
So, I'm confused, do I have to keep track of which metatiles have been drawn already?
Let's assume that your map is structured as columns of metatiles, and each column is 16 pixels (2 tiles, 1 color area) wide, and your PCB arranges nametables horizontally, resulting in vertical mirroring. There is enough video memory to keep 32 columns of metatiles valid. The NES picture is 256 pixels wide, meaning that 17 columns will be fully or partly visible.
You manage updates using two variables:
- visible_left, the left side (in columns) of the area. Normally, this will be about 8 columns to the left of the player. A camera system giving more room in front of the player than behind might cause it to be 5 to 11 columns in front.
- valid_left, which your scrolling code updates as it draws columns to the nametables.
What you want to do is make sure that the interval
visible_left through
visible_left + 16 is contained within
valid_left through
valid_left + 31. Here's the logic:
- At the start of the level, before turning on rendering, draw all columns from valid_left through valid_left + 31.
- If visible_left becomes less than valid_left, column valid_left - 1 is coming into view. Decrease valid_left by 1 and draw column valid_left.
- If visible_left becomes greater than valid_left + 15, column valid_left + 32 is coming into view. Increase valid_left by 1 and draw column valid_left + 31.
- Clamp the camera X position to valid_left * 16 through valid_left * 16 + 256 so that the camera falls behind instead of glitching if the worst happens.
But each byte in the attribute table spans two columns. Depending on how you organize the map data, you may have to either draw two columns at once like
Super Mario Bros. and
Contra or store enough information to regenerate the attributes for the column that you're updating.
tepples wrote:
Let's assume that your map is structured as columns of metatiles, and each column is 16 pixels (2 tiles, 1 color area) wide, and your PCB arranges nametables horizontally, resulting in vertical mirroring. There is enough video memory to keep 32 columns of metatiles valid. The NES picture is 256 pixels wide, meaning that 17 columns will be fully or partly visible.
You manage updates using two variables:
- visible_left, the left side (in columns) of the area. Normally, this will be about 8 columns to the left of the player.
- valid_left, which your scrolling code updates as it draws columns to the nametables.
What you want to do is make sure that the interval
visible_left through
visible_left + 16 is contained within
valid_left through
valid_left + 31. Here's the logic:
- At the start of the level, before turning on rendering, draw all columns from valid_left through valid_left + 31.
- If visible_left becomes less than valid_left, column valid_left - 1 is coming into view. Decrease valid_left by 1 and draw column valid_left.
- If visible_left becomes greater than valid_left + 15, column valid_left + 32 is coming into view. Increase valid_left by 1 and draw column valid_left + 31.
- Clamp the camera X position to valid_left * 16 through valid_left * 16 + 256 so that the camera falls behind instead of glitching if the worst happens.
But each byte in the attribute table spans two columns. Depending on how you organize the map data, you may have to either draw two columns at once like
Super Mario Bros. and
Contra or store enough information to regenerate the attributes for the column that you're updating.
tepples, thank you I appreciate all of this effort. : ) I shouldn't have asked that question. I'm sorry.
I'm uncomfortable...
tepples, thank you so much for your post
... I'm working with what you wrote... getting somewhere I think.
---
tokumaru, on page 21, wrote:
I believe that the main purpose of BIT is to test whether bits in memory are set or clear. You load your bit mask into the accumulator and BIT it with the value you want to test, if the Z flag is set, the bits you tested are clear.
Thanks!
I'm wondering about this code
Code:
+ lda currNameTable
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
bit PPUSTATUS2
lda CameraX+0 ; time to MOVE THE CAMERA OBJECT!
sta PPUSCROLL5 ; write the horizontal scroll count register
lda #$00 ; (no vertical srolling)
sta PPUSCROLL5 ; set the vertical scroll
rts ;end of scroll_screen
Why is
bit PPUSTATUS2 or
bit $2002 there? It doesn't have to do anything with the flags, I don't think, cause the loop ends with an rts.
$2005 (PPUSCROLL5), (and $2006) are double write registers. Reading from $2002 "resets" them so the next write will always go to the same place.
Code:
lda $2002;Now next write to $2005 will set X scroll
lda #$80
sta $2005;X scroll is #$80
lda #$00
sta $2005;Y scroll is #$00
Code:
lda $2002;Now next write to $2005 will set X scroll
lda #$80
sta $2005;X scroll is #$80
lda $2002;Now next write to $2005 will set X scroll
lda #$00
sta $2005;X scroll is #$00
It's just a way to make sure the values are being written to the right places.
If you just do this:
Code:
lda #$FF
sta $2005;Is this going to X scroll or Y scroll?
lda #$00
sta $2005;Same here.
It's just safe code, since it's possible the FF might become the y scroll and the 0 might become the X scroll if you're not sure which write the register is currently on.
Edit: Ah. And I did lda $2002 in this examples, but using bit lets you read it without changing A, so that's usually what you'd want to do.
Kasumi, excellent response! Thank you so much!
Is my debugger lieing to me?
Ok, the accumulator has #$06 in it...
the carry is set
and it subtracts #$80
THEN it says the answer is #$86...
Is that because it subtracted the negative 80 and that changes it to an add?
edit: If that's so then is it possible to subtract 80? Maybe im missing something...
unregistered wrote:
Ok, the accumulator has #$06 in it...
the carry is set
and it subtracts #$80
THEN it says the answer is #$86...
That's correct: 6 - 128 = -122 ($86, 8-bit, signed)
Quote:
Is that because it subtracted the negative 80 and that changes it to an add?
You can think of it this way, yes, but no matter how you look at it, the answer is correct:
6 - 128 = -122 ($86, 8 bit, signed)
6 - (-128) = 134 ($86, 8 bit, unsigned)
Quote:
edit: If that's so then is it possible to subtract 80? Maybe im missing something...
I'm not sure what your goal is, but maybe 8 bits aren't enough in this case? If you used 16 bits, the result wouldn't be so ambiguous. You see, when you use only 8 bits, 128 and -128 are the same in hex ($80), bit when you use 16 bits, they're very different: 128 = $0080, -128 = $FF80. So if you did $0006 - $0080 you'd get $FF86, which can't be mistaken for $0086.
My sister said that I should try adding 80 to 6 and see what that does.
unregistered wrote:
My sister said that I should try adding 80 to 6 and see what that does.
Your sister probably doesn't know much 6502 and is just guessing. Do you really want to program a game based on guesses? You need to know what you're doing in order to make good software. Adding $80 or subtracting $80 will have the same result if you keep using 8 bits, because 128 and -128 are the same in hex in this case.
The result you're getting is not wrong, just ambiguous, and this might be causing you trouble. Like I said, if you want to get rid of this ambiguity, you'll need to use 16-bit math.
tokumaru wrote:
unregistered wrote:
My sister said that I should try adding 80 to 6 and see what that does.
Your sister probably doesn't know much 6502 and is just guessing.
She reminded me that you can add a negative number to a positive one and it will be like subtracting it.
tokumaru wrote:
Do you really want to program a game based on guesses? You need to know what you're doing in order to make good software. Adding $80 or subtracting $80 will have the same result if you keep using 8 bits, because 128 and -128 are the same in hex in this case.
The result you're getting is not wrong, just ambiguous, and this might be causing you trouble. Like I said, if you want to get rid of this ambiguity, you'll need to use 16-bit math.
OOOOOOOH ok... this is math on a cpu. Then I understand now that it works kind of differently... Ill reread your first reply again.
edit:Ahhhhh yes!!!
tokumaru wrote:
unregistered wrote:
Ok, the accumulator has #$06 in it...
the carry is set
and it subtracts #$80
THEN it says the answer is #$86...
That's correct: 6 - 128 = -122 ($86, 8-bit, signed)
Quote:
Is that because it subtracted the negative 80 and that changes it to an add?
You can think of it this way, yes, but no matter how you look at it, the answer is correct:
6 - 128 = -122 ($86, 8 bit, signed)
6 - (-128) = 134 ($86, 8 bit, unsigned)
Quote:
edit: If that's so then is it possible to subtract 80? Maybe im missing something...
I'm not sure what your goal is, but maybe 8 bits aren't enough in this case? If you used 16 bits, the result wouldn't be so ambiguous. You see, when you use only 8 bits, 128 and -128 are the same in hex ($80), bit when you use 16 bits, they're very different: 128 = $0080, -128 = $FF80. So if you did $0006 - $0080 you'd get $FF86, which can't be mistaken for $0086.
That's what my 255 is for. Of course $ff86!!!!
YES THANK YOU SO MUCH TOKUMARU!!!!
Jeez guys, I am new to coding, but NOT to graphics hacking. Everything is made of 8x8 tiles. For example, loading the SMB rom and locating the mushroom won't show it in one piece. You see each 8x8 tile of it for all of it. A 16x16 sprite has 4 8x8 tiles, 32x32 has 16, so on.
As for the oddity of the 8x16 graphic being 16x32, it's probably something related to your hex for graphics, or something else.
ChipHomsar10 wrote:
Jeez guys, I am new to coding, but NOT to graphics hacking. Everything is made of 8x8 tiles.
This thread hasn't been about tiles for what now? 69 pages?
Quote:
As for the oddity of the 8x16 graphic being 16x32, it's probably something related to your hex for graphics, or something else.
Yup, that should narrow it down!
ChipHomsar10, sorry if I'm being rude. I know you're new and want to participate, but if you want to be a programmer, you have to pay attention to detail. If you gave this thread a second look you'd notice that the issue about sprite sizes has been solved ages ago, and now this is actually "unregistered's thread of general programming questions" (someone should change the subject to that!).
Edited to make it less of an
artifact title.
Quote:
8x16 and whatever else unreg wants to know
... well I surely want to know what is wrong with this code
Code:
update_colors: ;under development *Something definitly wrong with this code... makes screen all uncalm and jumpy during musicA
sta $ff
; ldx #$02 ;loads 3rd screen
; jsr load_screen
;set color of column
;must set 2006 with 23C0
lda #$23
sta PPUADDR6
lda #$C0
sta PPUADDR6
;write to 23C0 and 23E0
lda RAMbufferColors+7
sta PPUDATA7
lda RAMbufferColors+6;3
sta PPUDATA7
; must set 2006 with 23C8
lda #$23
sta PPUADDR6
lda #$C8
sta PPUADDR6
;write to 23C8 and 23E8
lda RAMbufferColors+5;6
sta PPUDATA7
lda RAMbufferColors+4;2
sta PPUDATA7
; must set 2006 with 23D0
lda #$23
sta PPUADDR6
lda #$D0
sta PPUADDR6
;write to 23D0 and 23F0
lda RAMbufferColors+3;5
sta PPUDATA7
lda RAMbufferColors+2;1
sta PPUDATA7
;must set 2006 with 23D8
lda $23
sta PPUADDR6
lda #$D8
sta PPUADDR6
;write to 23D8 and 23F8
lda RAMbufferColors+1;4
sta PPUDATA7
lda RAMbufferColors+0 ;<this is ordered differently from its attribute table array...
sta PPUDATA7 ; ...2xF8, 2xD8, 2xF0, 2xD0, 2xE8, 2xC8, 2xE0, 2xC0
;notice the numbers get smaller as we go
rts ;end of update_colors
Or would that be correct if RAMbufferColors is set up correctly? It is pretty simple code... isn't it?
I mean.. all it does is a double write to set $2006 and then 2 writes to $2007... and redo that like 4 times.edit: PPU is already set up to increment by 32 after each write to $2007.
edit2edit3: update_colors runs during vblank
At least you are using wrong adressing mode in one lda. I suppose it's a typo. Try finding it yourself.
ε-δ wrote:
At least you are using wrong adressing mode in one lda. I suppose it's a typo. Try finding it yourself.
GRAND!!! THANK YOU SO INCREDIBLY MUCH ε-δ!!! The screen doesn't jump around anymore and the colors just change in places!! Even when the music is playing!!!!!!!
edit: WOW!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! I MISSED A # SIGN AGAIN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SO HAPPY THANK YOU SO MUCH ε-δ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! I'm insanely excited! And I should be working on music today... so tomorrow is fun with colors.
Code:
scroll_screen:
;PPUCTRL Controller ($2000) > write
;PPUMASK Mask ($2001) > write
;PPUSTATUS Status ($2002) < read
;OAMADDR address ($2003) > write
;OAMDATA OAM data ($2004) <> read/write
;PPUSCROLL Scroll ($2005) >> write x2
;PPUADDR Address ($2006) >> write x2
;PPUDATA Data ($2007) <> read/write
sta $ff
;if CameraX == 0
lda CameraX+0
bne +
dec distance
inc CameraX+0
; lda nametable
; eor #$01
; sta nametable
+ ;if oX >= 425 (01a9)
sec ;Set Subtract. Clear Add.
lda oX+0
sbc #$80 ;32 ;e8 ;80 ;#$a9
bcc +postincrem
;if player is pressing right
lda currControllerButtons
and #BUTTON_RIGHT
beq +postincrem
+ ;increment SCROLL position
lda CameraX+0
clc
adc #$01
sta CameraX+0
lda CameraX+1
adc #$00
sta CameraX+1
+postincrem:
lda #10001000b
sta currNameTable
lda CameraX+1 ;get the high byte of the camera ((is #$ff in the start after reset) WHY?)
and #$01 ;keep only the lowest (first) bit
ora currNameTable ;combine with the other PPU settings
sta currNameTable ;this is what you'll write to $2000 when setting the scroll
; run other game graphic updating code here
;lda CameraX+0
;and #00000111b ; throw away higher bits
;bne + ; see if lower bits == 0
;jsr next ;sometimes sets correct address for drawing the next column
+ lda currNameTable
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
bit PPUSTATUS2 ;<reading $2002 resets both double write registers ($2005 and $2006) so that my first write will go to Xcoord.
lda CameraX+0 ; time to MOVE THE CAMERA OBJECT!
sta PPUSCROLL5 ; write the horizontal scroll count register
lda #$00 ; (no vertical srolling)
sta PPUSCROLL5 ; set the vertical scroll
rts ;end of scroll_screen
Quote:
lda CameraX+1 ;get the high byte of the camera ((is #$ff in the start after reset) WHY?)
and #$01 ;keep only the lowest (first) bit
ora currNameTable ;combine with the other PPU settings
sta currNameTable ;this is what you'll write to $2000 when setting the scroll
Ok I'm wondering if there is an easy way to make
this bit not be equal to 1 at the beginning when CameraX+1 is equal to #$FF. It is at #$FF in the beginning because it always subtracts 128 from CameraX and it's a 16 bit subtraction. CameraX is equal to 6 and so 6-128 = -122. CameraX+1 is set to #$FF.
How do you comment your code? I tend to have many thoughts while working with my code too many to type out quickly. But then after a few days of other work I have to return to digging out those undocumented thoughts. I guess "more" comments would be better?
Add whatever you think will help you understand the code and the intent behind it. As you gain more experience, you'll come to know what sort of information you need when coming back to code months later.
tepples wrote:
Add whatever you think will help you understand the code and the intent behind it. As you gain more experience, you'll come to know what sort of information you need when coming back to code months later.
Ah, thanks tepples, that's great!
I comment code in blocks... each comment in my source files describes what the block (block = a sequence of commands without blank lines between them) under it does. I only put comments to the right of the commands when it's really necessary to point something out (e.g. something was done in a very non-obvious way for speed or space). Functions/subroutines have more detailed comments that include input/output values and so on.
In addition to the comments in the source code, most algorithms used in the game are first documented in a text file before being implemented. I hardly update this after writing the respective code though, so if something changes during the implementation the original document becomes outdated, but I keep it just in case.
tokumaru wrote:
I comment code in blocks... each comment in my source files describes what the block (block = a sequence of commands without blank lines between them) under it does. I only put comments to the right of the commands when it's really necessary to point something out (e.g. something was done in a very non-obvious way for speed or space). Functions/subroutines have more detailed comments that include input/output values and so on.
Commenting in blocks sounds interesting... tried to make this code be like that:
Code:
update_colors: ;under development
;this is run after update_vram, which sets the PPU to increment by +32
;
;RAMbufferColors is ordered differently from its attribute table array...
; ...2xF8, 2xD8, 2xF0, 2xD0, 2xE8, 2xC8, 2xE0, 2xC0
sta $ff
;set color of column
;must set 2006 with 23C0
lda #$23
sta PPUADDR6
lda #$C0
sta PPUADDR6
;to write to 23C0 and 23E0
lda RAMbufferColors+7
sta PPUDATA7
lda RAMbufferColors+6
sta PPUDATA7
;must set 2006 with 23C8
lda #$23
sta PPUADDR6
lda #$C8
sta PPUADDR6
;to write to 23C8 and 23E8
lda RAMbufferColors+5
sta PPUDATA7
lda RAMbufferColors+4
sta PPUDATA7
;must set 2006 with 23D0
lda #$23
sta PPUADDR6
lda #$D0
sta PPUADDR6
;to write to 23D0 and 23F0
lda RAMbufferColors+3
sta PPUDATA7
lda RAMbufferColors+2
sta PPUDATA7
;must set 2006 with 23D8
lda #$23
sta PPUADDR6
lda #$D8
sta PPUADDR6
;to write to 23D8 and 23F8
lda RAMbufferColors+1
sta PPUDATA7
lda RAMbufferColors+0
sta PPUDATA7
rts
;end of update_colors
I'm going to try to comment other pieces of code in blocks. Thanks tokumaru!
Shiru, or someone informed about famitone, when you list the notes that famitone can support... C-1 to B-5... doesn't that include C-5, D-5, E-5, F-5, and G-5? Cause the key next to B-4 is C-5... and so it seems that you end with B-5 at the end of all the 5s. I'm excited because maybe I don't have to remove any of the dash5s in my musicB.
Here is my other question... it involves exporting my song from Famitracker 0.4.2. I created the plugins directory and added the TextExporter.dll and then I opened Famitracker and opened my songB.ftm and clicked File>Create NSF... and then I clicked TextExporter v1.26 and created my text file. Now I just needed to finish following your instructions... sorry. And I'm guessing the answer is yes to my first question... I can hear those high dash5 notes in FCEUX!!
Yes, it includes all notes from C-5 to B-5 as well. Other way to explain the supported range is full five octaves, 5*12 semitones, with lowest note being C-1.
Shiru wrote:
Yes, it includes all notes from C-5 to B-5 as well. Other way to explain the supported range is full five octaves, 5*12 semitones, with lowest note being C-1.
Sweet!! Thanks Shiru!
Ok, I made another song today... it uses famitone notes C-1 C#-1 D-1 D#-1 E-1 F-1 and F#-1... I just have a small normal analogue tv set in my room here and I can't hear any of the dash1 notes so my question is: What notes are not audible right now for me? What are a good nice set of speakers that I could get so I can hear the low notes? Would a subwoofer be important for these low notes? I know this is not an audio forum but I'm asking anyway... hope someone here can help me... thanks.
unregistered wrote:
Ok, I made another song today... it uses famitone notes C-1 C#-1 D-1 D#-1 E-1 F-1 and F#-1... I just have a small normal analogue tv set in my room here and I can't hear any of the dash1 notes so my question is: What notes are not audible right now for me?
Right now all the notes are audible me... here is my
test .ftm file that plays all of the notes for famitone... highest note to the lowest note and then lowest to highest. It is playing just fine right now. But... my musicC_ still will not play the lowest dash1 notes WHY???
Should I create a new file when adding it to my powerpack? I don't have a clue as to why I can't hear the dash1 notes in my musicC_ song.
It could be a function of the speakers in the television. (Hard to tell). To test, run the NES's audio into a stereo or headphones, either through the TV or not, whichever is more convenient.
lidnariq wrote:
It could be a function of the speakers in the television. (Hard to tell).
Thats what I was listening to... my songZ.ftm... through the speakers in my television... from on my powerpack inside my nes... and I was able to hear every note. So every note works clearly... except
in my game... in my other song in our game... the music still doesnt play the low dash1 notes.
Thank you for your help lidnariq!
edit.edit2: This is the process I went through to get my songZ.ftm to play from inside my nes:
1) In FamiTracker version 1.4.2.0 after loading and completing my songZ.ftm I clicked File, Create NSF.
2) In Type of file combo box I selected Text Exporter v. 1.26 item... then I clicked the Export button and saved my text file.
3) Then in Command Prompt I used the famitone text2data tool to convert my text file into data. songZ.ftm doesn't use DPCM, so I get a single *.asm file. It's called musicZ.asm. I used the -asm6 switch after the filename to make musicZ.asm work with my assembler asm6.
4) Next I edited my assembly code to use musicZ.asm, instead of musicC_.asm, whenever I press select on my nes controller. That requires me to
.include musicZ.asm in my game.
5) Then I copy musicZ.asm into my
dasource folder.
6) Finally, I reload Command Prompt and build our game into a .nes file.
7) That file is then transferred onto my compact flash card. I place my compact flash card in my powerpack and that goes into my nes. Done.
edit3: Each step above has been followed with my musicC_.asm. Do you see any thing that I missed? It doesn't make sense... I don't understand.
last edit: Well... I tried converting my musicCi.asm's 3rd instrument to be exactly like the triangle instrument from musicZ.asm... and it didn't help... there's weird noises that
I imagine play quietly when I think my triangle instrument should be playing those low dash1 notes. It is just odd.
edit once again.
Shiru, or someone familiar with famitone, can you please take my file
unregistered wrote:
Right now all the notes are audible me... here is my
test .ftm file that plays
and could you follow my colored steps below and send me back your .asm file?
unregistered wrote:
edit2: This is the process I went through to get my songZ.ftm to play from inside my nes:
1) In FamiTracker version 1.4.2.0 after loading and completing my songZ.ftm I clicked File, Create NSF.
2) In Type of file combo box I selected Text Exporter v. 1.26 item... then I clicked the Export button and saved my text file.
3) Then in Command Prompt I used the famitone text2data tool to convert my text file into data. songZ.ftm doesn't use DPCM, so I get a single *.asm file. It's called musicZ.asm. I used the -asm6 switch after the filename to make musicZ.asm work with my assembler asm6.
I think I must have done something wrong... so if someone familiar with famitone could please follow my steps and maybe then your asm file will play correctly on my machine.
Nevermind. That wouldn't help me at all. My file, songZ.ftm, already works and that's fantastic.
edit: I'm sorry yall
please forgive me.
How could this be done? THIS...
Code:
able: 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116
ldx able, x
lda
sta glove+0, x ;<something like this... I know it wont work but how could it work? :)
lda
sta glove+1, x
lda
sta glove+2, x
lda
sta glove+3, x
Ok thanks for reading and trying to help me here with this.
What's the input and output of that snippet of code supposed to be? I think I know what you want, but I'd like to be sure before I start writing code.
well, I'd just like to somehow
Code:
sta glove+0+x*4
sta glove+1+x*4
sta glove+2+x*4
sta glove+3+x*4
where x is some value
x: 0, 4, 8, 12, 16, 20,
edit: I'm sorry, I don't know input or output...
edit2: Well hmmm............... I want to always be able to specify what place (either 0, 1, 2 or 3) to store the value and I would always like to have it be a multiple of 4.
Perhaps I should ask a different question:
What are you trying to accomplish? What is the purpose of this code? I haven't been following this thread for very long, so I'm not at all familiar with what you've done so far.
I am trying to accomplish creating a new different way of having a RAMbuffer.
Having a ram buffer of size 60. And then I would like to find a way to not have to write out every + number...
Code:
like this
sta cup+0
sta cup+1
sta cup+2
sta cup+3
sta cup+4
sta cup+5
sta cup+6
sta cup+7
sta cup+8
etc....
I'm sorry again. Nevermind... I'll think this over tomorrow. Thank you for being willing to help!
I've seen some code do things like this:
Code:
ldx #$5C
loop:
lda #$AA
sta buffer+0,x
lda #$BB
sta buffer+1,x
lda #$CC
sta buffer+2,x
lda #$DD
sta buffer+3,x
dex
dex
dex
dex
bpl loop
Is this what you mean?
unregistered wrote:
And then I would like to find a way to not have to write out every + number...
If all you want to do is save on typing, assemblers usually offer some kind of repetition command. ASM6 for example has .REPT, which allows you to do this:
Code:
offset = 0
.rept 60
sta cup+offset
offset = offset + 1
.endr
This will generate 60 STAs to consecutive addresses, the 6502 will not run any sort of loop.
Joe wrote:
I've seen some code do things like this:
Code:
ldx #$5C
loop:
lda #$AA
sta buffer+0,x
lda #$BB
sta buffer+1,x
lda #$CC
sta buffer+2,x
lda #$DD
sta buffer+3,x
dex
dex
dex
dex
bpl loop
Is this what you mean?
This is what I wanted yes... but I'm sure there is a possible way to do this. (I think that tokumaru told me that something like
sta buffer+1,x isnt possible earlier in this thread.) Thank you Joe.
I'm going to spend time on this now.
tokumaru wrote:
unregistered wrote:
And then I would like to find a way to not have to write out every + number...
If all you want to do is save on typing, assemblers usually offer some kind of repetition command. ASM6 for example has .REPT, which allows you to do this:
Code:
offset = 0
.rept 60
sta cup+offset
offset = offset + 1
.endr
This will generate 60 STAs to consecutive addresses, the 6502 will not run any sort of loop.
I did not realise that; that is excellent! Thanks tokumaru!
I want to save on typing... by creating a loop so the code is cleaner. But I am happy that loopy provided the .REPT for us!
unregistered wrote:
Joe wrote:
I've seen some code do things like this:
Code:
ldx #$5C
loop:
lda #$AA
sta buffer+0,x
lda #$BB
sta buffer+1,x
lda #$CC
sta buffer+2,x
lda #$DD
sta buffer+3,x
dex
dex
dex
dex
bpl loop
Is this what you mean?
This is what I wanted yes... but I'm sure there is a possible way to do this. (I think that tokumaru told me that something like
sta buffer+1,x isnt possible earlier in this thread.) Thank you Joe.
I'm going to spend time on this now.
I figured it out!
Well almost.
---
I would really appreciate your help.
There is a procedure named
next that correctly determines the high and low address for the next column to be drawn... I would like to somehow delay it being used until directly before the second nametable switch. Right before nametable 0 becomes set to the left side again. How could this be done?
You know so it is like SMB...edit.
unregistered wrote:
I would really appreciate your help.
There is a procedure named
next that correctly determines the high and low address for the next column to be drawn... I would like to somehow delay it being used until directly before the second nametable switch. Right before nametable 0 becomes set to the left side again. How could this be done?
You know so it is like SMB...edit.Nevermind!!!!!!!! FIGURED IT OUT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Thank you tepples for trying to help me... well you were logged in right after my post. I have a small question... does the console start at reset: when you turn it on? I think maybe it does... but does anyone know for sure?
Essentially the CPU does this when you power it on:
Code:
sei
jmp ($FFFC)
So if the vector at $FFFC points to your reset handler, then yes, it'll start at the beginning of your reset handler.
tepples wrote:
...So if the vector at $FFFC points to your reset handler, then yes, it'll start at the beginning of your reset handler.
AWESOME, THANKS tepples!
tepples, on page 69, wrote:
Here's the logic:
- At the start of the level, before turning on rendering, draw all columns from valid_left through valid_left + 31.
My level starts out already drawn on nametable 0 and nametable 1. So could I pretend that they were drawn as columns?
If your metatiles are the same size as those of Super Mario Bros. series, then nametable 0 contains metatile columns 0-15, and nametable 1 contains metatile columns 16-31. Once the camera begins to advance toward the right, nametable 0 will fill up with columns 32-47, etc.
Yes I understand thanks! My RAMbuffer is now 16 pixels wide. ... it seems easier to keep track of two buffers instead of 4.
added.
tepples wrote:
If your metatiles are the same size as those of Super Mario Bros. series, then nametable 0 contains metatile columns 0-15, and nametable 1 contains metatile columns 16-31. Once the camera begins to advance toward the right, nametable 0 will fill up with columns 32-47, etc.
Ok so visible_left should be 16bit? My column 32 is 0 right now.
...well no not 16bit... I'm not sure what to do. hmmmmmm... edit.edit2: So, I need to store my value in visible_left after I divide ColumnX by 8... How do I divide a 16 bit number by 8?
To divide a number by 8, shift it to the right three times. For a 16-bit number it'll look like this:
Code:
lda num_lo
sta tmp0
lda num_hi
lsr a
ror tmp0
lsr a
ror tmp0
lsr a
ror tmp0
tepples wrote:
To divide a number by 8, shift it to the right three times. For a 16-bit number it'll look like this:
Code:
lda num_lo
sta tmp0
lda num_hi
lsr a
ror tmp0
lsr a
ror tmp0
lsr a
ror tmp0
like this:?
Code:
lda CameraX+1
lsr
ror visible_left
lsr CameraX+1
ror visible_left
lsr CameraX+1
ror visible_left
sta visible_left
but my column 32 is still 0...
edit: Thank you so much tepples!
It is nice to know that the carry will definitly keep track of the numbers... my code still says zero... but I'm gonna fix this.
edit2: I'm feeling proud of myself for trying and guessing correctly how to do this divide a 16bit number by 8. edit3: Woah I should say that I'm probably wrong... I guessed correctly about the lsr and ror... but have just noticed you usse a different variable at the top... why do you do that?
last edit: Ah yes, now it works great. I understand now... thank you for allowing me enough time to figure this out.
Code:
;/****************************************
next: ;me grab toward the next nametable column
; columnLo and columnHi are the address of the place to draw new columns
;****************************************/
;name table0 ($2000)
; wait until the second nametable switch
lda CameraX+0
lsr a
lsr a
lsr a ; shift right 3 times = divide by 8
sta columnLo ;instead of this dividing by 8... just add 2 to the previous value of columnLo...duh.
;well, adding 2 works fine until columnLo becomes > 32... then it becomes very not good.
lda currNameTable
and #$01
eor #$01 ; invert low bit, A = $00 or $01
asl a ; shift up, A = $00 or $02
asl a ; $00 or $04
clc
adc #$20 ; add high byte of nametable base address ($2000)
sta columnHi ; now address = $20 or $24 for nametable 0 or 1
rts ;end of next
This is just code from nerdy nights scrolling tutorial... The problem I'm having is that it draws the next column in the next 16 bits... and then draws the next column 8 pixels over....
screen looks like this
Code:
1111111112
1111111112
1111111112
instead of this
Code:
1212121212
1212121212
1212121212
How do I make it draw a column (each column is 16 pixels wide)... then skip 2 spaces and draw the next column? Does this make sense?
Im continuing with this today... had other work to do till now.
thought I would add that each 16 pixel column would look like this
Code:
12
12
12
Code:
update_vram: ;testing... good sofar!
sta $ff
lda my_copy_of_last_write_to_PPUCTRL
ora #00000100b ;change $2007 increment to +32
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
lda iBeginAtOne ;ahahahahahhahahahahahahahahaha! it works!!! :)
bne +end
bit PPUSTATUS2 ;"And I did lda $2002 in this examples, but using bit lets you read it without changing A, so that's usually what you'd want to do." -Kasumi p. 70;
lda #$20
sta PPUADDR6
lda #$00
sta PPUADDR6
tay
ldx #58 ;58 ;should hold last even spot written to RAMbufferw0. both columns are always full so #58 and #59
- lda RAMbufferw0, x
sta PPUDATA7
dex
dex ;subtract 2 to stay in the even column
bpl -
iny
lda #$20
sta PPUADDR6
sty PPUADDR6
ldx #59;59 for the odd column
- lda RAMbufferw0, x
sta PPUDATA7
dex
dex ;stay in odd column
bpl -
bit PPUSTATUS2 ;"And I did lda $2002 in this examples, but using bit lets you read it without changing A, so that's usually what you'd want to do." -Kasumi p. 70;
lda #$20
sta PPUADDR6
iny
sty PPUADDR6
tay
ldx #58 ;58 ;should hold last even spot written to RAMbufferw1. both columns are always full so #58 and #59
- lda RAMbufferw1, x
sta PPUDATA7
dex
dex ;subtract 2 to stay in the even column
bpl -
iny
lda #$20
sta PPUADDR6
sty PPUADDR6
ldx #59;59 for the odd column
- lda RAMbufferw1, x
sta PPUDATA7
dex
dex ;stay in odd column
bpl -
jsr update_colors
+end: rts ;end of update_vram.
This ^ is what I have so far minus the commented lines of code... it's just too much code to put here. I'm hoping that one of you can show me a way to draw each entire 16 pixel column... Each of them are specified in either RAMbufferw0 or RAMbufferw1.
edit: Ok what I'm wanting seems to be help with my scrolling code.
Code:
scroll_screen:
;PPUCTRL Controller ($2000) > write
;PPUMASK Mask ($2001) > write
;PPUSTATUS Status ($2002) < read
;OAMADDR address ($2003) > write
;OAMDATA OAM data ($2004) <> read/write
;PPUSCROLL Scroll ($2005) >> write x2
;PPUADDR Address ($2006) >> write x2
;PPUDATA Data ($2007) <> read/write
sta $ff
lda CameraX+0
sta visible_left
lda CameraX+1
lsr a
ror visible_left
lsr a
ror visible_left
lsr a
ror visible_left
;if CameraX == 0
lda CameraX+0
bne +
lda currNameTable
eor #$01 ;invert the nametable bit
sta currNameTable ;and make it current. :)
and #00000001b
beq +special
jmp +
+special:
lda #$00
sta iBeginAtOne
+ ;if oX >= 128
sec ;Set Subtract. Clear Add.
lda oX+0
sbc #$80
bcc +skipincrem
;if player is pressing right
lda currControllerButtons
and #BUTTON_RIGHT
beq +skipincrem
;lda CameraX+1
;beq +
; lda CameraX+0
; sec ;Set subtract. Clear Add.
; sbc #255
; bne +
;jsr camera_aim ; jsr next ;beq +skippingscroll
+ ;increment SCROLL position
lda CameraX+0
clc
adc #$01
sta CameraX+0
lda CameraX+1
adc #$00
sta CameraX+1
+skipincrem:
lda #10001000b
sta currNameTable
lda CameraX+1 ;get the high byte of the camera ((is #$ff in the start after reset) WHY?)
and #$01 ;keep only the lowest (first) bit
ora currNameTable ;combine with the other PPU settings
sta currNameTable ;this is what you'll write to $2000 when setting the scroll
; run other game graphic updating code here
;lda CameraX+0
;and #00000111b ; throw away higher bits
;bne + ; see if lower bits == 0
;jsr draw_me_a_column
+ lda currNameTable
sta PPUCTRL0
sta my_copy_of_last_write_to_PPUCTRL
bit PPUSTATUS2 ;<reading $2002 resets both double write registers ($2005 and $2006) so that my first write will go to Xcoord.
lda CameraX+0 ; time to MOVE THE CAMERA OBJECT!
sta PPUSCROLL5 ; write the horizontal scroll count register
lda #$00 ; (no vertical srolling)
sta PPUSCROLL5 ; set the vertical scroll
rts ;end of scroll_screen
I want each entire 16 bit column to be drawn. When the scrolling code scrolls the screen I still want it to look like this:
Code:
1212121212
1212121212
1212121212
edit2: 6:38PM NEVERMIND I GOTS IT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Ok, so now here's what's next. Right now I have a draw_me_a_column method that excellently draws one of the two 16pixelwide columns
RAMbufferw0 and
RAMbufferw1. It draws
RAMbufferw1 currently. I want to be able to choose which buffer on the fly... like with pointers...
tokumaru, on page 29, wrote:
A pointer is an address you can modify dynamically, so if you use pointers to access the data you can have the same code work with different sets of data just by modifying the pointer dynamically.
Excellent thanks again tokumaru!
I want my method to work with both sets of data (
RAMbufferw0 and
RAMbufferw1). In this thread, on
page 4, yall discussed pointer tables... I do think my pointers situation isn't as complex as pointer tables. Maybe I could use them... I'm don't know right now.
Kasumi, on page 31, wrote:
6502 assembly isn't "type safe" like C is. What this means is your program/assembler doesn't know or care what you are using your variables for. C will actually fix how operations work on a variable based on what you're using that variable for. For instance, pointer++; could increase the address the pointer is holding by 1, 2 or any other number depending on how large the type of data is that the pointer is supposed to point to. Assembly doesn't care, so it's up to you to keep track.
Now I'm scared to mess around with pointers in 6502 assembly.
So please could you help me figure these pointers out?
I'm susposed to use the addressing mode (), y to access the pointers; however, I don't know how to write the pointer code out... my brain is dead right now sorry.edit.
unregistered wrote:
I want to be able to choose which buffer on the fly... like with pointers...
tokumaru, on page 29, wrote:
A pointer is an address you can modify dynamically, so if you use pointers to access the data you can have the same code work with different sets of data just by modifying the pointer dynamically.
tokumaru, you mention that the pointer should be modified dynamically twice, could you show me an example of "modifying the pointer dynamically"? Dictionary.com says, "Dynamic memory must be constantly refreshed to avoid losing data." "...constantly refreshed" confuses me.
unregistered wrote:
tokumaru, you mention that the pointer should be modified dynamically twice, could you show me an example of "modifying the pointer dynamically"?
Modifying the pointer dynamically just means setting it to different values before calling the function. For example, the pointer might be stored in the zero page, and the function would do something like this:
Code:
lda (pointer), Y
Before calling the function that uses the pointer, you'd have to put a value into the pointer.
Code:
lda #>RAMbufferw0
sta pointer
lda #<RAMbufferw0
sta pointer+1
jsr function_that_uses_the_pointer
Edit: Let me try that again...
Code:
lda #<RAMbufferw0
sta pointer
lda #>RAMbufferw0
sta pointer+1
jsr function_that_uses_the_pointer
The "dynamic" part is the fact that you can set the pointer to different values each time you call the function.
unregistered wrote:
Dictionary.com says, "Dynamic memory must be constantly refreshed to avoid losing data." "...constantly refreshed" confuses me.
It sounds similar, but "dynamic memory" has nothing to do with "dynamically modifying memory". Don't worry; you don't have to constantly refresh anything.
Joe is correct, you just have to make your pointers point to the location you want to read/write before you do any reading/writing. The address is "dynamic" because it can be changed, it's not fixed like is the case with absolute addressing.
unregistered wrote:
Dictionary.com says, "Dynamic memory must be constantly refreshed to avoid losing data." "...constantly refreshed" confuses me.
This is a hardware detail (that "constant refreshing" is performed by the hardware itself in machines that need it) that has nothing to do with what you're doing now, so don't worry.
Joe wrote:
unregistered wrote:
tokumaru, you mention that the pointer should be modified dynamically twice, could you show me an example of "modifying the pointer dynamically"?
Modifying the pointer dynamically just means setting it to different values before calling the function. For example, the pointer might be stored in the zero page, and the function would do something like this:
Code:
lda (pointer), Y
Before calling the function that uses the pointer, you'd have to put a value into the pointer.
Code:
lda #>RAMbufferw0
sta pointer
lda #<RAMbufferw0
sta pointer+1
jsr function_that_uses_the_pointer
The "dynamic" part is the fact that you can set the pointer to different values each time you call the function.
unregistered wrote:
Dictionary.com says, "Dynamic memory must be constantly refreshed to avoid losing data." "...constantly refreshed" confuses me.
It sounds similar, but "dynamic memory" has nothing to do with "dynamically modifying memory". Don't worry; you don't have to constantly refresh anything.
Thank you Joe! Thanks for the example code!
In my opinion now, it seems that your pointer would be created backwards with the switched >< signs. The low byte should be stored first... then the high byte. That's what I've learned so far I think.
tokumaru wrote:
Joe is correct, you just have to make your pointers point to the location you want to read/write before you do any reading/writing. The address is "dynamic" because it can be changed, it's not fixed like is the case with absolute addressing.
unregistered wrote:
Dictionary.com says, "Dynamic memory must be constantly refreshed to avoid losing data." "...constantly refreshed" confuses me.
This is a hardware detail (that "constant refreshing" is performed by the hardware itself in machines that need it) that has nothing to do with what you're doing now, so don't worry.
Ok thanks tokumaru!
Joe and tokumaru and everyone else please forgive me... I've not achieved anything yet... it will take some time tomorrow... I have to think all of this over. Goodnight everyone.
unregistered wrote:
In my opinion now, it seems that your pointer would be created backwards with the switched >< signs. The low byte should be stored first... then the high byte. That's what I've learned so far I think.
Whoops! Good catch!
Let me just edit my post...
tepples, on pg 69, wrote:
Depending on how you organize the map data, you may have to either draw two columns at once like Super Mario Bros. and Contra or store enough information to regenerate the attributes for the column that you're updating.
.......so when drawing two columns at once... you have to wait on the columns to enter the rightmost attribute table spot before coloring them? That would be kind of odd. While playing
Super Mario Bros. there wasn't any flickering of the last two columns that I could see.
unregistered wrote:
so when drawing two columns at once... you have to wait on the columns to enter the rightmost attribute table spot before coloring them? That would be kind of odd. While playing Super Mario Bros. there wasn't any flickering of the last two columns that I could see.
Momentary miscoloring occurs at the seam.
Super Mario Bros. arranges its nametables horizontally ("vertical mirroring"), letting it keep the seam offscreen. Go play
Super Mario Bros. 3 once if you want to see color artifacts.
SMB3 uses two vertically arranged nametables ("horizontal mirroring") for a 27-metatile-tall playfield plus status bar at the cost of miscoloring the tiles at the far right.
tepples wrote:
unregistered wrote:
so when drawing two columns at once... you have to wait on the columns to enter the rightmost attribute table spot before coloring them? That would be kind of odd. While playing Super Mario Bros. there wasn't any flickering of the last two columns that I could see.
Momentary miscoloring occurs at the seam.
Super Mario Bros. arranges its nametables horizontally ("vertical mirroring"), letting it keep the seam offscreen. Go play
Super Mario Bros. 3 once if you want to see color artifacts.
SMB3 uses two vertically arranged nametables ("horizontal mirroring") for a 27-metatile-tall playfield plus status bar at the cost of miscoloring the tiles at the far right.
When scrolling the screen along to the right using vertical mirroring how does the correct coloring happen when there arent any attribute tables offscreen to color?
Because when the nametable arrangement matches the scrolling direction, as in SMB1 and Contra (both of which use vertical mirroring), there are attribute tables offscreen. Each nametable has its own attribute table. The attribute table for $2000 is at $23C0-$2FFF, the attribute table for $2400 is at $27C0-$27FF, the attribute table for $2800 is at $2BC0-$2BFF, and the attribute table for $2C00 is at $2FC0-$2FFF.
Ah! Ok I think I get it now... you draw 2 16bit columns to 2400's nametable... and then color it's first 32 bit attribute table column... and then scroll to the right?
Thank you tepples! forgetfulness.
I'm trying to create a variable that will have the most rescent column drawn incase my method trys to redraw the same column again. It shouldn't take over 3 hours to do that.
No it shouldn't take 3 hours... but it did today. Now each column is drawn only once. edit.
I was going to make a complaint about how it is unfair that I can't press right on my controller and scroll into back into nametable 0 and see why, why do 7 RAMbufferw1 columns appear after it draws my RAMbufferw0 column. Why?! And then suddenly I realized that yes it's possible to make my computer think I pressed right on my controller...
ANYTHING IS POSSIBLE!! At least that is what I've just realized and I wanted to share this with anyone who is just starting game creation like me. Tomorrow is going to be incredible cause I will... I still think... I'll know why 7 RAMbufferw1 collumns are drawn after it draws my RAMbuffer0 column! I'm gonna change some of the status flags and the values in some of my registers... so it will appear that I've scrolled into nametable 0 again! ...it is 6:00pm here in Texas time to eat.
edit: It's a bit hard to know the time right now... we gain an hour at 2:00am.. it's Sunday morning at 1:25am now. Want to say that anything is not possible. Yawn... goodnight.
edit2: Worked for 5 hours and 31 mins today. I've made progress!
Anything may be possible... that's correct.
Now it's 10:39pm Sunday night.
Now, here is my problem. The screen looks like this:
Code:
121212121234
121212121234
121212121234
and now I want it to look like this
Code:
123412341234
123412341234
123412341234
edit: like this
Code:
123456789ABC
123456789ABC
123456789ABC
*sigh...
this is the same problem I had on page 73. Well almost the same. I bet I'll do better with this tomorrow. : )
edit2: Guess what happened to me. There must be an idea you could find for me... I increased the divide by 8 to be a divide by 16. And suddenly my screen turned into what I have been searching for:
Code:
123456789ABC
123456789ABC
123456789ABC
Every column appeared at the correct column spot! But each column was shifted up a metatile... so the top line of metatiles appear at the bottom of the screen with everything shifted up to be on top of them. (Our metatiles are 16x16pixels.) From all of my experiences with my level shifting up... that tells me that something is not correct. As time has continued on... the game being run and I'm traveling right. After looping the tiles are shifted a metatile higher... then another loop shifts them a metatile even higher. You must have experienced this at one point in your game creating career. So, anyways, then I increased the divide by 16 to be a divide by 32. And every column is perfectly drawn now... now nothing is shifted up but each column is not correct. It looks like somone has smeared my sister's level... it's all too wide looking.
edit3: I'm looking forward to solving this. Yesterday, I wasn't detailed with the looping part. By looping I mean that I'm running along and columns are being drawn... it's when they are redrawn... that's the loop; each time it loops the columns are raised a metatile. Noone else has experienced this?
unregistered wrote:
...I wasn't detailed with the looping part. By looping I mean that I'm running along and columns are being drawn... it's when they are redrawn... that's the loop; each time it loops the columns are raised a metatile. Noone else has experienced this?
Ok, so you know, today, I successfully rewrote my
draw_me_a_column method. The main code loop appears only once now. It is run twice... one time for the even column... and then one more time for the odd column.
Code:
0C6E2 ;************************************************************************
0C6E2 ;you must dynamically modify p0ointer before jsring draw_me_a_column.
0C6E2 ; lda #<RAMbufferw1
0C6E2 ; sta p0ointer
0C6E2 ; lda #>RAMbufferw1
0C6E2 ; sta p0ointer+1
0C6E2 ; destroys a x and y ooooooooooohhhhh nooooooooooooooooo
0C6E2 ;************************************************************************
0C6E2 draw_me_a_column: ;(please)
0C6E2
0C6E2
0C6E2 ;increase valid_left by 1
0C6E2 ; inc valid_left
0C6E2 ;and draw column valid_left+31
0C6E2 ;set the address
0C6E2
0C6E2 20 AC C6 jsr next
0C6E5 A5 3B lda columnLo
0C6E7 C5 45 cmp playdough
0C6E9 F0 39 beq +end
0C6EB E6 47 inc t10 ;sets temporary zeropage variable to 1
0C6ED A5 3A lda columnHi
0C6EF 8D 06 20 sta $2006
0C6F2 A6 3B ldx columnLo
0C6F4 8E 06 20 stx $2006
0C6F7
0C6F7
0C6F7 ;start with even column part
0C6F7 4C 0C C7 jmp +even
0C6FA
0C6FA A0 3B -odd: ldy #59
0C6FC E8 inx
0C6FD A5 3A lda columnHi
0C6FF 8D 06 20 sta $2006
0C702 8E 06 20 stx $2006
0C705 A9 00 lda #$00
0C707 85 47 sta t10
0C709 4C 0E C7 jmp +draw
0C70C A0 3A +even: ldy #58
0C70E +draw: ;draw 16bit column, even and odd parts
0C70E B1 43 - lda (p0ointer),y ;RAMbufferw1, x
0C710 8D 07 20 sta $2007
0C713 88 dey
0C714 88 dey ;subtract 2 to stay in even or odd column
0C715 10 F7 bpl -
0C717
0C717 A5 47 lda t10
0C719 F0 03 beq +complete
0C71B ;now go back to odd column part
0C71B 4C FA C6 jmp -odd
0C71E
0C71E +complete
0C71E A5 3B lda columnLo
0C720 85 45 sta playdough
0C722
0C722 E6 30 inc valid_left
0C724
0C724
0C724 60 +end: rts ;end of draw_me_a_column scrolllandiii and vblank code
YEAY!!
So by the end a 16bit column is drawn. But I don't understand why my columns are one-metatile-too-high... one of yall must have experienced your columns being one-metatile-too-high. What did you do to fix this problem?
Quote:
; destroys a x and y ooooooooooohhhhh nooooooooooooooooo
As a solo developer, I approve of this type of comment.
Is the only problem that the columns are drawn too high? (One column or both?) Then check if what you're writing to $2006 is correct. Or check if what's written to $2005 is correct.
How do you calculate columnLo and columnHi?
What's "next" that you jsr to immediately?
Kasumi wrote:
Quote:
; destroys a x and y ooooooooooohhhhh nooooooooooooooooo
As a solo developer, I approve of this type of comment.
haha, I'm glad you approve. Thanks.
Kasumi wrote:
Is the only problem that the columns are drawn too high? (One column or both?)
Well there is a small extra grafiti that happens at the end after all the columns are drawn from that nametable. Also currently I'm trying to think of my columns being drawn - each column is 16 pixels wide... according to tepples.
Kasumi wrote:
Then check if what you're writing to $2006 is correct. Or check if what's written to $2005 is correct.
Ok, how could I check that? I am going to guess that I could run the debugger and see what value is written to the accumulator.
Kasumi wrote:
How do you calculate columnLo and columnHi?
What's "next" that you jsr to immediately?
columnLo and columnHi are determined inside my next... that's what next does.
Quote:
Ok, how could I check that? I am going to guess that I could run the debugger and see what value is written to the accumulator.
Or check the code that sets the values that eventually end up getting written there. Like next!
Is rendering disabled while this is happening? It may be taking too long if not. I dunno, I'm guessing at this point. I'm either missing something obvious or there's not enough info.
When all else fails, just rewrite it. There's lots of tiny things you could do, like just have even first instead of jumping over odd to get to even only to branch back to odd. You may not need t10. CPY #$FF could work if I'm understanding it correctly. The last value of y before that loop ends for even is 0. DEY twice, get #$FE. For odd, it's one. DEY twice, get #$FF, so you may be able to detect it that way instead. A total rewrite can be freeing. No worries about noodling around code you don't want to break when adding new features.
The first thing I'd do is make a loop that draws any one tile (say... #$FF. Any one that's different from the usual background) to the even column. This lets you be sure of two things. One: That you're getting the address correct for the update, and also that you're getting the number of loops correct. If it's too many updates, it will bleed into the next column, if you started in the wrong place it will be offset wrong.
Then add the loop that draws any different tile (#$80 or anything you didn't use for even that's also different from the usual background) to the odd column. Again, it lets you be sure you're starting in the right place etc.
After that, make it read from your buffer. If it was updating in the right places and right number of times before, but looks wrong after loading from the buffer instead of using a fixed tile then something is wrong with whatever code fills the buffer (or sets up the pointer TO the buffer)
I don't know what your process was for the current routine (haven't been following too closely recently), but never ever try to do too many things before verifying previous parts work. Lots of things could be wrong, and it makes things easier for both me and you to look at when you know "Column position worked, then I added the buffer code and it looks wrong." Then we at least know what to look at.
Edit: You know what? You can use the same steps to just work backwards, instead of rewriting. After even, add lda #$FF. After odd, add lda #$80. (or whatever tiles you're using.) Comment out "- lda (p0ointer),y ;RAMbufferw1, x". That will draw just columns of one tile so you can make sure your offsets/number of loops is correct. You can tell by the tile which is wrong (even or odd), if either one is. (Also, check in FCEUX's nametable viewer, on the off chance your emulator was clipping the top 8 pixels and you compensated for it or something.)
If that looks right, the issue is either your buffer or using too much time (assuming rendering is enabled). To check if using too much time, just disable rendering before, and enable it after. If the nametables look right in the nametable viewer, it ain't that. (It also ain't that if rendering is already disabled.
) Which leaves just whatever fills your buffer, or whatever sets up the pointer TO your buffer.
If it looks wrong after removing the buffer code, the issue is your number of loops, or what sets ColumnHi/ColumnLo. (Or maybe your vertical scroll value, you can check if it's that by looking at your nametables in the nametable viewer.)
Sigh, well I feel that rewriting that wont help me.
I strongly feel that the problem is with the code that fills the buffer. My coding life all just hasn't been any good. It seems frustrating but you are right, there needs to be some check that will guarentee my code is correct. I'm going to start with what you've suggested here... thank you very much Kasumi!
edit: Ok very good I see your edit now! This is going to work.
Thank you for being so helpful Kasumi!
edit2: I have to go fix supper now... I don't understand why
Kasumi wrote:
There's lots of tiny things you could do, like just have even first instead of jumping over odd to get to even only to branch back to odd. You may not need t10. CPY #$FF could work if I'm understanding it correctly. The last value of y before that loop ends for even is 0. DEY twice, get #$FE. For odd, it's one. DEY twice, get #$FF, so you may be able to detect it that way instead.
Detect what instead? Um... I've got a good tile #7E that hasnt been used for the background. Ooohh wait. Ok you are talking about me not needing t10. t10 is the way I've set it to know when to ignore my jump to -odd. You are correct.. $FE is reached at the end of even and #FF is reached at the end of odd... so you are telling me that I might be able to use that check of Y with CPY #FF. I do understand!
Ok, i have to go cook... give me some time to do that.
edit3: INCREDIBLY SWEET THANK YOU SO MUCH Kasumi!! Code:
0C6E2 ;************************************************************************
0C6E2 ;you must dynamically modify p0ointer before jsring draw_me_a_column.
0C6E2 ; lda #<RAMbufferw1
0C6E2 ; sta p0ointer
0C6E2 ; lda #>RAMbufferw1
0C6E2 ; sta p0ointer+1
0C6E2 ; destroys a x and y ooooooooooohhhhh nooooooooooooooooo
0C6E2 ;************************************************************************
0C6E2 draw_me_a_column: ;(please)
0C6E2
0C6E2
0C6E2 ;increase valid_left by 1
0C6E2 ; inc valid_left
0C6E2 ;and draw column valid_left+31
0C6E2 ;set the address
0C6E2
0C6E2 20 AC C6 jsr next
0C6E5 A5 3B lda columnLo
0C6E7 C5 45 cmp playdough
0C6E9 F0 33 beq +end
0C6EB A5 3A lda columnHi
0C6ED 8D 06 20 sta $2006
0C6F0 A6 3B ldx columnLo
0C6F2 8E 06 20 stx $2006
0C6F5
0C6F5
0C6F5 ;start with even column part
0C6F5 4C 06 C7 jmp +even
0C6F8
0C6F8 A0 3B -odd: ldy #59
0C6FA E8 inx
0C6FB A5 3A lda columnHi
0C6FD 8D 06 20 sta $2006
0C700 8E 06 20 stx $2006
0C703 4C 08 C7 jmp +draw
0C706 A0 3A +even: ldy #58
0C708 +draw: ;draw 16bit column, even and odd parts
0C708 B1 43 - lda (p0ointer),y ;RAMbufferw1, x
0C70A 8D 07 20 sta $2007
0C70D 88 dey
0C70E 88 dey ;subtract 2 to stay in even or odd column
0C70F 10 F7 bpl -
0C711
0C711 C0 FF cpy #$ff ;lda t10
0C713 F0 03 beq +complete
0C715 ;now go back to odd column part
0C715 4C F8 C6 jmp -odd
0C718
0C718 +complete
0C718 A5 3B lda columnLo
0C71A 85 45 sta playdough
0C71C
0C71C E6 30 inc valid_left
0C71E
0C71E
0C71E 60 +end: rts ;end of draw_me_a_column scrolllandiii and vblank code
edit4: Yes thank you so much Kasumi!!
I got to delete that slow 5 cycle inc t10... and it is so good to learn this way of smart programming. I really appreciate the cmp and cpy now!!
Ok sleep is going to start soon goodnight.
edit5:It's around 11:34am on Monday. I want to say
THANK YOU SO MUCH KASUMI!! I found the part that you started with saying that even should come first... instead of jumping over odd to run even...
Now everytime it runs odd my code doesn't have to jmp anymore!
edit6: Wow, I'm in the middle of this now! Want to write out here what's going on now so I could read it in the future. So far I've moved the code back to how it was before it was combined with one draw section. Then I made it so that the even column was drawn with the tile #$44... and the odd column was drawn with the tile #$45. It became interesting... the even column was correct... the odd column was kindof correct and there were many grafiti #$45 tiles. So that must have been a hint that the odd buffer creation code was problemed. Next I read the bottom part of your response another time and I was blown away!!! There actually was a solution that could work with the single draw section... I quickly moved the code back to use the single draw section... and loaded the acumulator with #$44 for the even section and loaded the accumulator with #$45 for the odd section. And I commented the lda (ptr),y line like you suggested. And then I ran the code and it ran perfectly!!!!!!!
Nothing was raised up when it restarted the nametable! Just kept going. So now the buffer code must be problem filled... that's my guess from all of your wise teaching Kasumi. Now I'll go look at my buffer code.
edit7: Column position worked, then I added the buffer code and it looks wrong. Here is my buffer code
Code:
0C4C5 ;*******************************************************
0C4C5 ; uses x for load_screen input... send value of screen to load in x
0C4C5 ; uses y to pick a column (1 16x16 metatile wide).
0C4C5 ;*******************************************************
0C4C5 draw_RAMbuffers: ;testing... goodsofar!!
0C4C5 ;"Prepare the (new nametable) writes in a RAM buffer during draw time..." tepples pg 59
0C4C5
0C4C5 85 FF sta $ff
0C4C7 ;ldx #$02
0C4C7 20 44 C1 jsr load_screen ;< X goes into to load_screen
0C4CA
0C4CA A9 3B lda #59 ;note: each RAMbufferw is 60 bytes long... 0 through 59
0C4CC 85 31 sta t2
0C4CE 85 41 sta goodLocation
0C4D0
0C4D0 84 34 sty CurrentColumn+0 ;ldy #$03
0C4D2 ;---
0C4D2 ;
0C4D2
0C4D2
0C4D2 B1 10 -- lda ($10), y
0C4D4 AA tax
0C4D5
0C4D5 BD DE C9 lda MetatileTile3, x
0C4D8 8D 31 05 sta RAMbufferw0+1
0C4DB BD FF C8 lda MetatileTile2, x
0C4DE 8D 30 05 sta RAMbufferw0+0
0C4E1 BD 20 C8 lda MetatileTile1, x
0C4E4 8D 33 05 sta RAMbufferw0+3
0C4E7 BD 41 C7 lda MetatileTile0, x
0C4EA 8D 32 05 sta RAMbufferw0+2
0C4ED
0C4ED A6 41 ldx goodLocation
0C4EF AD 33 05 lda RAMbufferw0+3
0C4F2 9D 30 05 sta RAMbufferw0, x
0C4F5 CA dex
0C4F6 AD 32 05 lda RAMbufferw0+2
0C4F9 9D 30 05 sta RAMbufferw0, x
0C4FC CA dex
0C4FD AD 31 05 lda RAMbufferw0+1
0C500 9D 30 05 sta RAMbufferw0, x
0C503 CA dex
0C504 AD 30 05 lda RAMbufferw0+0
0C507 9D 30 05 sta RAMbufferw0, x
0C50A 98 tya
0C50B 18 clc
0C50C 69 10 adc #$10 ;increment y by 16!!!!
0C50E A8 tay
0C50F CA dex
0C510 86 41 stx goodLocation
0C512 10 BE bpl -- ;if not we are ready to do the next metatile
0C514
0C514
0C514 A4 34 ldy CurrentColumn
0C516 C8 iny
0C517 84 35 sty CurrentColumn+1
0C519 A9 3B lda #59
0C51B 85 41 sta goodLocation ;our buffers are both 59 long
0C51D
0C51D B1 10 -- lda ($10), y
0C51F AA tax
0C520
0C520 BD DE C9 lda MetatileTile3, x
0C523 8D 6D 05 sta RAMbufferw1+1
0C526 BD FF C8 lda MetatileTile2, x
0C529 8D 6C 05 sta RAMbufferw1+0
0C52C BD 20 C8 lda MetatileTile1, x
0C52F 8D 6F 05 sta RAMbufferw1+3
0C532 BD 41 C7 lda MetatileTile0, x
0C535 8D 6E 05 sta RAMbufferw1+2
0C538
0C538 A6 41 ldx goodLocation
0C53A AD 6F 05 lda RAMbufferw1+3
0C53D 9D 6C 05 sta RAMbufferw1, x
0C540 CA dex
0C541 AD 6E 05 lda RAMbufferw1+2
0C544 9D 6C 05 sta RAMbufferw1, x
0C547 CA dex
0C548 AD 6D 05 lda RAMbufferw1+1
0C54B 9D 6C 05 sta RAMbufferw1, x
0C54E CA dex
0C54F AD 6C 05 lda RAMbufferw1+0
0C552 9D 6C 05 sta RAMbufferw1, x
0C555 98 tya
0C556 18 clc
0C557 69 10 adc #$10 ;increment y by 16!!!!
0C559 A8 tay
0C55A CA dex
0C55B 86 41 stx goodLocation
0C55D 10 BE bpl -- ;if not we are ready to do the next metatile
0C55F
0C55F 20 63 C5 jsr colors
0C562 ;lda #11111111b ;true ;draw_RAMbufferColors ends with #10100101b in the accumulator
0C562 ;sta DrawnRAMbuffers
0C562 60 rts ;end of draw_RAMbuffers
All of that looks correct to me. The jsr colors at the end doesnt do much of anything right now.
Kasumi wrote:
If that looks right, the issue is either your buffer or using too much time (assuming rendering is enabled). To check if using too much time, just disable rendering before, and enable it after.
When doing that my screen just slides down over and over again.
Code:
ldx #$02 ;load screen number with x
ldy visible_left;phase;#$00
lda #$00
sta PPUMASK1
jsr draw_RAMbuffers ;32 bits wide (2 columns)
lda #$1e
sta PPUMASK1
Ignore what the actual rom displays while running and check the nametable viewer in FCEUX to verify instead. If they don't differ, you're fine.
But I think you're fine anyway, based on this:
Quote:
And I commented the lda (ptr),y line like you suggested. And then I ran the code and it ran perfectly!!!!!!!
Nothing was raised up when it restarted the nametable! Just kept going.
edit to your post above. Then again, the difference between the two routines (that line commented out vs. not) is at least 360 cycles which isn't that small. draw_me_a_column is run at the beginning of the NMI, I'd assume?
Edit: You could actually benchmark it, but I don't know the best way to recommend doing that. I guess grab this build of virtuaNES:
viewtopic.php?p=47911#p47911Then, whereever jsr draw_me_a_column is, do this:
Code:
sta $401E
jsr draw_me_a_column
sta $401F
It will display on screen how long the routine took each frame. If virtuaNES doesn't work (language plugin complaint and you don't want to dig one up), NintendulatorDX:
viewtopic.php?t=6773 can do the same sort of thing.
I see the buffer routine, but yo... it's long, I'd just debug. Totally ignore if the code
looks right, see if the result is right. Check the RAM this routine writes to.
Put a breakpoint at
Quote:
0C562 60 rts ;end of draw_RAMbuffers
And look at RAMbufferw1 (or whatever RAM this is supposed to update) using FCEUX's hex editor. Are all 60 values correct?
If all 60 are correct, is there a possibility something else writes to this RAM before draw_me_a_column tries to read it? Find out! Break at the start of draw_me_a_column and find out if they're STILL right. If they aren't, break on write to whatever's the first one that's wrong. Use the trace logger to find out what wrote the wrong thing to it. If they are still right at the start of draw_me_a_column, is whatever sets the pointer to RAMbufferw1 doing it properly?
Does doing this:
lda RAMbufferw1, y;
instead of this:
lda (p0ointer),y ;RAMbufferw1, x
Make it work?
(I'm saying just replace the line that uses a pointer with the absolute location the pointer is supposed point to. For all I know, that's really not still RAMbufferw1, hence this explanation
)
If so, the problem is definitely your pointer.
If all 60 values aren't correct at the rts from draw_RAMbuffers, put a breakpoint on write for whatever RAM location is the first to have an error. Run FCEUX's trace logger. When the breakpoint hits, find the beginning of draw_RAMbuffers and go down to see what caused the wrong value to be written.
That's what I'd do to debug this. I'll read draw_RAMbuffers and see if I find anything off, and edit this post if I find a thing.
Edit2: Okay, I read it and I got nothing. So yeah, try 'dem debug steps.
Kasumi wrote:
And look at RAMbufferw1 (or whatever RAM this is supposed to update) using FCEUX's hex editor. Are all 60 values correct?
WOAH (There's even more hex values to look at)... all 60 values... my sister said she would help me with getting those tomorrow morning.
Goodnight.
Kasumi, thank you for all of this help... your whole post!!
edit: Ok all 60 values are correct inside RAMbufferw1... they are correctly incorrect...
Code:
0A 0A
0A 0A
...
27 27
27 27
Code:
27 27
27 27
...
0A 0A
0A 0A
That's how it is but it is incorrect because the 0A 0A 0A 0A metatile is susposed to be at the bottom (top) ... it's at the beginning (end)... everything else is shifted down (up)... That's how it is but it is incorrect because the 0A 0A 0A 0A metatile is susposed to be at the top (not at the bottom) ... it's at the end... everything else is shifted up... so my code is running correctly with incorrect data. I've gone through our level 1 DAC file twice... everything is correct... so now I'm lost. But im going to read your post again.
...afterlunch
edit2: I got lost in your nintendulator dx link... I read the entire 7 pages... and I was wondering does Nintendulator DX work with asm6 too? qbradq mentioned learning about ca65 and
that he would not like to return to non-ca65 life... but I'm only using asm6 right now.
edit3. edit4.edit5:
Kasumi wrote:
Then again, the difference between the two routines (that line commented out vs. not) is at least 360 cycles which isn't that small.
How do you know this?
: )
Kasumi wrote:
draw_me_a_column is run at the beginning of the NMI, I'd assume?
Draw_me_a_column is called twice in my update_vram method.
Code:
0C601 update_vram: ;testing... good sofar!
0C601 85 FF sta $ff
0C603
0C603
0C603 A5 33 lda my_copy_of_last_write_to_PPUCTRL
0C605 09 04 ora #00000100b ;change $2007 increment to +32
0C607 8D 00 20 sta $2000
0C60A 85 33 sta my_copy_of_last_write_to_PPUCTRL
0C60C
0C60C
0C60C A5 42 lda iBeginAtOne ;ahahahahahhahahahahahahahahaha! it works!!! :)
0C60E D0 1C bne +end
0C610 ; jsr next ;determines columnHi and columnLo
0C610 ; phases 0 4 8 12 16 20 24 28
0C610 ;
0C610
0C610
0C610 ;if columnsDrawn == columnLo
0C610 ; ldx phase
0C610 ; txa
0C610 ; pha ;---------->
0C610 ; lda phases, x
0C610 ; sec
0C610 ; sbc columnLo
0C610 ; bne +end ;only runs with 0
0C610 ;
0C610
0C610
0C610 A9 30 lda #<RAMbufferw0
0C612 85 43 sta p0ointer
0C614 A9 05 lda #>RAMbufferw0
0C616 85 44 sta p0ointer+1
0C618 20 E2 C6 jsr draw_me_a_column ;yes, <this draws one 16bit-wide column.
0C61B
0C61B 20 40 C6 jsr prepare4new_column
0C61E
0C61E A9 6C lda #<RAMbufferw1
0C620 85 43 sta p0ointer
0C622 A9 05 lda #>RAMbufferw1
0C624 85 44 sta p0ointer+1
0C626 20 E2 C6 jsr draw_me_a_column
0C629
0C629 20 2D C6 jsr update_colors
0C62C 60 +end: rts ;end of update_vram.
And update_vram is called near the middle of my vblank procedure.
Code:
0C5DC 48 vblank: pha
0C5DD 98 tya
0C5DE 48 pha
0C5DF 8A txa
0C5E0 48 pha
0C5E1
0C5E1 E6 1E inc FRAME_CNT
0C5E3 .incsrc "daprg-vblank.asm"
0C5E3 + ;skip the video updates if the frame calculations aren't over yet
0C5E3 24 21 bit FrameReady
0C5E5 10 08 bpl SkipUpdates
0C5E7
0C5E7 ;PERFORM VIDEO UPDATES HERE
0C5E7 20 FB C5 jsr update_sprite
0C5EA
0C5EA
0C5EA 20 01 C6 jsr update_vram ;sets increment to +32
0C5ED
0C5ED
0C5ED
0C5ED
0C5ED ;modify the flag
0C5ED E6 21 inc FrameReady
0C5EF
0C5EF
0C5EF
0C5EF SkipUpdates:
0C5EF
0C5EF ;PERFORM TASKS THAT MUST BE PERFORMED EVEN
0C5EF ;WHEN THE FRAME IS NOT READY, SUCH AS UPDATING
0C5EF ;THE MUSIC OR DRAWING A STATUS BAR
0C5EF 20 BE DE jsr FamiToneUpdate
0C5F2 ;"Setting the scroll should ALWAYS be the very last thing in your VBlank handler." -tokumaru pg 43
0C5F2 20 48 C6 jsr scroll_screen
0C5F5
0C5F5 ;return from the NMI (vblank)
0C5F5 68 pla
0C5F6 AA tax
0C5F7 68 pla
0C5F8 A8 tay
0C5F9 68 pla
0C5FA 40 rti
Quote:
I got lost in your nintendulator dx link... I read the entire 7 pages... and I was wondering does Nintendulator DX work with asm6 too? qbradq mentioned learning about ca65 and that he would not like to return to non-ca65 life... but I'm only using asm6 right now.
Err... for the source level debugging? I have no idea, I've never used it for that. I was just talking about its cycle timing stuff. What you use to assemble the rom doesn't matter for this.
Download it, open it. Run your rom. Open the Debug, Disassembly window, and you'll see a place called timers. If you store to $401E/$401F as I described in my previous post (or before and after the thing you want to benchmark), the number of cycles that routine took will be next to timer 1. (Read the readme to learn how to use more/other timers.)
Quote:
How do you know this?
: )
Hah, 360 is wrong because I looked at the wrong value. It'd really be 300. But... I know because lda (indirect),y (used by lda (p0ointer),y )
takes 5 cycles, and it's used in a loop that runs 60 times (well... it runs 30 times twice, anyway...). If you comment it out, it's no longer doing it so that time is saved.
Anyway, none of that matters until you find out how the wrong values get to that RAM in the first place.
Garbage in, garbage out so there's not much point to checking if this taking too long is part of the cause of bad output until you fix the bad input its getting.
And, I can't really help beyond steps outlined on my last post. Break on writes to the first wrong value. Which... actually seems to be the first value. If everything moved up as I'm understanding, the data is 100% wrong (because there is no value that is in the right place.). Run trace logger, break on writes to find out how the first thing ends up wrong. Fix it.
I'd bet it's the pointer, since you said you're VERY sure your actual level data is correct. So similar to what I asked before, does it work if you replace
Code:
0C4D2 B1 10 -- lda ($10), y
With lda leveldata,y where leveldata is wherever that pointer is supposed to point? If so, neat! If not, oh well, you gotta debug. Was just a guess.
Edit: Heck, other things it could be. I'm gonna guess that the metatile that should be on top is the same for both the column you're trying to read and the column next to it? Then it could be y just has the wrong value (one greater than it should be) when reading from the pointer for the first, and you're offset by 1. (Skipping the first tile in column A, getting the first tile of Column B at the bottom) So check load_screen.
Thank you Kasumi for helping me so much!
I did what you said
Kasumi wrote:
If everything moved up as I'm understanding, the data is 100% wrong (because there is no value that is in the right place.). Run trace logger, break on writes to find out how the first thing ends up wrong. Fix it.
I ran trace logger and figured out that if I added this code
Code:
0C662 A5 2F lda visible_left
0C664 38 sec
0C665 E9 10 sbc #$10
0C667 85 2F sta visible_left
right after the sixteen bit division of visible_left into CameraX+0 inside of my method scroll_screen... it draws the entire nametable columns 32-47 at the correct regular height.!!! Now I have
three four new goals:
1.) Decrease grafiti... something is kind of wrong... there's a little bit more grafiti now after my subtraction of #$10 from visible_left. It is run every vblank... it's not the best fix right?... there must be some other way... it is the fix that my brain chose cause visible_left was sixteen higher and that's why it started one row below where it should.
2.) Fix the colors of my columns so they are correct... that shouldn't be too hard.
3.) Draw columns 48-63 at the appropriate time... also shouldn't be too hard.
4.) Make our my girl travel on the level... she needs to stop moving beyond the right edge of the screen. ...Maybe that would decrease the grafiti. As the game gets bigger it blooms... this blooming is awesome!
Thank you Kasumi for teaching me about new ways of debugging and trace logger is really cool!! eedit.
Edit: Eh, got rid of that other stuff. The fix makes sense to me after reading scroll_screen. More theory, though.
scroll_screen (mostly) has zero need to be in your NMI.
The only thing that needs to be there is
Code:
lda CameraX+0 ; time to MOVE THE CAMERA OBJECT!
sta PPUSCROLL5 ; write the horizontal scroll count register
lda #$00 ; (no vertical srolling)
sta PPUSCROLL5 ; set the vertical scrol
Edit2: Ah, right. You also need a similar thing to get the lowest bit of CameraX+1 into $2000.
A good way to have your frame order is like this:
1. Start of main loop.
2. Update main character's position.
3. Update scroll position based on main character's position.
4. Use scroll position to find is new tiles need to be drawn. If so, put tiles in RAM buffer.
5. Set flag that tells the NMI it's safe to update the screen.
NMI:
1. Check flag to see if we should update the screen.
2. If yes, read from buffer and draw tiles to screen. (buffer was updated in step 4 of the main loop)
3. Write low byte of camera to $2005. (it was updated in step 3 of the main loop)
Right now, it seems like you're writing tiles in the NMI, THEN moving the camera in the NMI. This might scroll to new stuff, making the update you just did not necessarily cover the visible screen.
Simple question: What's the expected value of visible_left when camerax = $0000? If visible_left should be $00 (the first tile of camerax) in that case, your fix is not good and something else is wrong. If visible_left should be $F0, your fix is perfect. Same for if camerax = $0010 etc. Should visible_left be $10, or $00? (Note: The value it should be and what works might be different! If the subtract you added takes it to what you think it SHOULD be, you're golden. If you think it should be the value before the subtract, but just have the subtract because it works keep thinking.)
Quote:
1.)Decrease grafiti... something is kind of wrong... there's a little bit more grafiti now after my subtraction of #$10 from visible_left
This might be a time to benchmark your NMI code using Nintendulator DX or VirtuaNES. If all the input is now good (i.e. You verified the RAM now has the correct 60 values, and you can't find issues with the code that reads from it) and you're getting bad output, it's possible you're trying to write to $2007 when rendering has begun. ~2270 cycles before rendering begins after the NMI. ~513 of those are eaten by sprite DMA. Anything from the start of the NMI to the last write to $2006/$2007 should happen in less.
Code:
vblank: sta $401E
pha
tya
pha
txa
pha
*********SNIP*************
SkipUpdates: sta $401F
Wow! Once my game becomes alive... the Max part in Nintendulator debug says 2872... which is no good cause you said
Kasumi wrote:
If all the input is now good (i.e. You verified the RAM now has the correct 60 values, and you can't find issues with the code that reads from it) and you're getting bad output, it's possible you're trying to write to $2007 when rendering has begun. ~2270 cycles before rendering begins after the NMI. ~513 of those are eaten by sprite DMA. Anything from the start of the NMI to the last write to $2006/$2007 should happen in less.
Code:
vblank: sta $401E
pha
tya
pha
txa
pha
*********SNIP*************
SkipUpdates: sta $401F
so 2270-513 is quite less than 2000. Thank you for your example code... it helped me realize that my vblank is overused.
Need to spend some time on using it less.
I am going to reread your reply.
edit: Ok I'm going to think more about your simple question. It seems like a hard question right now. Ok it's supper time! Goodnight and thank you for all of this theory!
Quote:
edit: Ok I'm going to think more about your simple question. It seems like a hard question right now.
It is up to you to define every bit about how this should work. It works as defined or it works by breaking the definition.
1. Is it defined such that upon striking zero, the zeroth column is drawn? (Zero should draw zero, negative is impossible)
2. Is it defined such that upon striking one, the zeroth column is drawn? (One should draw zero, zero would be negative so that update is skipped)
Something else? If you don't know how it
should work, think about what makes the most sense, and commit to it. Then if something works by doing something that doesn't seem to agree with the definition, you can find out why and fix it. (Or fix your definition, if the working way makes sense and is actually better.) Just... don't guess. Find out why it works if it's better or if something else is wrong.
A bit more theory! Requires... well, somewhat large rewrites to put into place.
Think about the fastest way your updates could possibly happen.
Something like this?
Code:
loop:
lda buffer,y;4
sta $2007;5
dey;2
bpl loop;3 on all but last
(Well... unrolled or partially unrolled or stack magic would be faster. But follow me on this.
)
Obviously you need to load the value and store it. That's unavoidable. Then the end of your loop with just one dey. Why not set your buffer up beforehand so you can do exactly that? Or exactly whatever the fastest thing you can think of is?
Currently, you have the left and right columns interleaved. (left column tile) (right column tile) (left column tile) (right column tile) This means you have to loop through the list twice, and also use dey twice for each loop!
If you did this: (left column tile) (next left column tile) (etc.) ... (right column tile) (next right column table) (etc.)
One dey would take you to the next tile. Do you have to decide whether a column is even or odd? In your case, both are updated when they need updating, so you can just draw them in the same order.
Code:
lda evencolumnaddrhi;3
sta $2006;4
lda evencolumnaddrlo;3
sta $2006;4
ldy #29;2
loop:
lda buffereven,y;4
sta $2007;5
dey;2
bpl loop;3 on all but last
lda oddcolumnaddrhi;3
sta $2006;4
lda oddcolumnaddrlo;3
sta $2006;4
ldy #29;2
loop2:
lda bufferodd,y;4
sta $2007;5
dey;2
bpl loop2;3 on all but last
The above is pretty bare minimum, and still takes ~870 cycles assuming no page crosses. Those 870 cycles do not include the ~513 for sprites. They do not include attribute updates. This is why your NMI needs to make as few decisions as possible. Your goal outside of the NMI should be to make all the decisions and set the data up so that the NMI can use it in the fastest possible way.
In your case, you do even, you do odd. If the routine that updates the buffers just used the same place in RAM every time, you wouldn't need a pointer for your NMI updates. You need pointers to the metatiles in case you have different sets, but the buffer for the NMI can be static. Heck using a pointer takes an extra cycle per load, plus you have to set it up. Static all the way!
Now, I didn't mention it before because it would not have helped your issue. But you can also make draw_RAMbuffers both simpler and faster. (Using a non interleaved buffer format or not!)
In the current code, you work really hard to preserve y (it contains where you are in the pointer.). But think about this! It takes just six cycles to store and restore it, and you could REALLY use it for other stuff.
I may not fully understand draw_RAMbuffers, but I think you can do something like this:
Code:
;Metatile index is Y. Location in RAM buffer is in X.
lda MetatileTile0, y;Assuming this top left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile1,y;Assuming this is top right tile
sta RAMbufferodd, x;Odd buffer
dex;Takes us to the next tile for BOTH buffers
lda MetatileTile2, y;Assuming this bottom left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile3,y;Assuming this is bottom right tile
sta RAMbufferodd, x;Odd buffer
lda pointerposition;used to be tya. You lose just one cycle doing this instead
clc
adc #$10 ;increment y by 16!!!!
tay
dex
bpl
This avoids storing the tiles to temp RAM only to load them again. You only need to loop 30 times, and it covers two separate columns. You only lose one cycle from where the 16 is added to y, plus 3 for storing it someplace (not above, but of course needs to be done). But, because you no longer need to store/restore x in goodlocation you actually come out ahead. (Since you needed to move y anyway which was replaced with a load, but you didn't ever need to move x.) The added benefit is you can use y for something that really needs it.
I'm not sure if you're updating two 8x8 tiles, or two 16x16 tiles columns. It looks like your draw_RAMbuffers is doing two 16x16 columns, but I don't see much need for that. It's be tough to update that much in the NMI anyway.
You can also only update one 8x8 column at a time in your NMI. Even if you set up an even and odd buffer outside of the NMI, you can have the NMI draw the relevant column (just even or just odd) when scrolled to. It's not a problem if the data you setup isn't used exactly on the frame.
Anyway, enough from me, I start these posts and never stop writing...
Kasumi wrote:
I'm not sure if you're updating two 8x8 tiles, or two 16x16 tiles columns. It looks like your draw_RAMbuffers is doing two 16x16 columns, but I don't see much need for that. It's be tough to update that much in the NMI anyway.
You can also only update one 8x8 column at a time in your NMI. Even if you set up an even and odd buffer outside of the NMI, you can have the NMI draw the relevant column (just even or just odd) when scrolled to. It's not a problem if the data you setup isn't used exactly on the frame.
My draw_RAMbuffers is doing two 16x16 columns at once because then it would be easier to update the attribute table colors. I think tepples has commented that Mario draws 4 columns each frame.
edit: I'm going to update my code so it's faster... your help you gave me is highly appreciated Kasumi!
Super Mario Bros. only draws half of a 16-pixel-wide metatile column per frame, but I seem to remember it draws four half columns in successive frames followed by the updated attributes. Frame-stepping with a nametable viewer open will show you what a particular game does.
Yea, byte-wide attribute updates are much easier to deal with, but there's just not enough time to push that much data in one frame. You need to split it between multiple frames with an FSM. Basically have a variable that keeps track of what kind of update you're going to do on the next frame, be it Tile Column 1, Tile Column 2, Attribute Stripe or Nothing.
qbradq wrote:
but there's just not enough time to push that much data in one frame.
There actually is, if you optimize the hell out of the code (unrolling loops and such)... I've managed to update 1 column of metatiles (60 name table bytes + 8 attribute bytes) + 1 row of metatiles (68 name table bytes + 16 attribute bytes), along with sprite DMA, all in regular VBlank time.
I'm not sure what my suggestion is for a beginner who's having trouble with this... I mean, code optimization isn't trivial, but splitting updates across different frames isn't exactly piece of cake either.
tokumaru wrote:
splitting updates across different frames isn't exactly piece of cake either.
You should see what I have to juggle for RHDE. At last count I have eight different kinds of updates that can happen in vblank during gameplay: no operation, 6x24 area of playfield, put up a blank pop-up window, clear a row of tiles to a constant color, fill a nametable, copy a 16-tile line (or two 8-tile lines) of text, copy 8 graphical tiles, and erase the part of a pop-up window that covers the playfield border.
Worth it? Yes.
But in a side-scroller, you can easily fit the left and right halves of a column of metatiles plus attribute updates in one vblank.
I have a quick slow question: see, now I have four 30 byte RAMbuffers... RAMbufferw0even, RAMbufferw0odd, RAMbufferw1even, and RAMbufferw1odd. Currently I have decided that creating a copy of my draw_us_a_column method drawing RAMbufferw1even and RAMbufferw1odd might be too much waste of code... but it isn't very much code at all. What would you recommend I do instead? Is there some type of static pointer? Something to help me specify the address of either RAMbufferw0even or RAMbufferw1even and only requre 4* cycles? I feel a little bit more positive on my creating a copy idea. What would you do?
addition.
If you can stack them on top of each other, 4x 32 byte buffers all next to each other, you can do something like this:
Code:
LDA ColumnNeeded
ASL A;Multiply 2
ASL A;4
ASL A;8
ASL A;16
ASL A;32
TAX ;X indexing varibale is A*32, making A our "select the buffer" value.
LDY #$1F ;32 bytes
.Loop:
LDA FourBufferBegin,X
STA $2007
INX ;(DEX depending on how they are arranged and such.)
DEY ;Are we 32 tiles in yet?
BPL .Loop ;Nope
;32 bytes have been written from one of the selected column buffers by here.
ETA: You say 4x 30 buffers. You can just shove 2 bytes in between then to make them even with each other, just some random engine variable. And then adjust the loop number and you should be able to see how this idea works by then.
Quote:
ETA: You say 4x 30 buffers. You can just shove 2 bytes in between then to make them even with each other,
Or you can use a table which will allow you to have buffers that are 30 bytes long and will be smaller/faster anyhow. (5 asl instructions vs. 4 bytes that specify the offsets.)
Code:
ldx ColumnNeeded
lda bufferoffsettable,x
tax
A table can't be in the middle of this routine or it will be run as code, but elsewhere where it's safe put this:
Code:
bufferoffsettable:
.db 0, 30, 60, 90
or maybe this:
Code:
bufferoffsettable:
.db 29, 59, 89, 119
depending on if you're going forwards or backwards.
If you can't have 120 contiguous bytes, you have to use a pointer.
I'd be tempted to allocate five 32-byte buffers $0100, $0120, $0140, $0160, and $0180.
Kasumi wrote:
Now, I didn't mention it before because it would not have helped your issue. But you can also make draw_RAMbuffers both simpler and faster. (Using a non interleaved buffer format or not!)
In the current code, you work really hard to preserve y (it contains where you are in the pointer.). But think about this! It takes just six cycles to store and restore it, and you could REALLY use it for other stuff.
I may not fully understand draw_RAMbuffers, but I think you can do something like this:
Code:
;Metatile index is Y. Location in RAM buffer is in X.
lda MetatileTile0, y;Assuming this top left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile1,y;Assuming this is top right tile
sta RAMbufferodd, x;Odd buffer
dex;Takes us to the next tile for BOTH buffers
lda MetatileTile2, y;Assuming this bottom left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile3,y;Assuming this is bottom right tile
sta RAMbufferodd, x;Odd buffer
lda pointerposition;used to be tya. You lose just one cycle doing this instead
clc
adc #$10 ;increment y by 16!!!!
tay
dex
bpl
This avoids storing the tiles to temp RAM only to load them again. You only need to loop 30 times, and it covers two separate columns. You only lose one cycle from where the 16 is added to y, plus 3 for storing it someplace (not above, but of course needs to be done).
But, because you no longer need to store/restore x in goodlocation you actually come out ahead. (Since you needed to move y anyway which was replaced with a load, but you didn't ever need to move x.) The added benefit is you can use y for something that really needs it.
I
highlighted part of your post. Let's start there. Ok, now your awesome code and ideas are working!
Kasumi, thank you so much! ...I dont understand what you mean by "...you actually come out ahead." Well, I do understand what you mean, but I don't agree with you now. I'm lost please help me. I don't agree because I just decided to store my y value in goodLocation. So that's just the same... I really don't understand what you are talking about after the highlighted part. I needed to move y anyway, but i didn't need to move x?
Ok, now it's suppertime!! Must go eat!
The two pieces of code accomplish the same goal. (Though mine sets up the buffers differently. The different way would be faster for your NMI to read as well, though.) You don't need to store/restore X in goodlocation because it just stays in X. (I mean... you may still have to load it before the loop, but you no longer have to do it IN the loop.) You come out ahead because the code I added takes fewer cycles than the unneeded code I removed. (storing/restoring goodlocation)
I omitted some stuff, but the full thing would be like:
Code:
ldx #29;Before everything. So not during the loop. This is like goodlocation
;But we load it with #29 instead of #59 for other reasons.
loop:
lda ($10), y;Originally omitted. Have to do that still to get the index, of course
sty pointerposition;This wasn't needed before, so we're 3 cycles behind
tay;This was needed before. We overwrote what was in y, which is why we stored it above
;Metatile index is Y. Location in RAM buffer is in X.
lda MetatileTile0, y;Assuming this top left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile1,y;Assuming this is top right tile
sta RAMbufferodd, x;Odd buffer
dex;Takes us to the next tile for BOTH buffers
lda MetatileTile2, y;Assuming this bottom left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile3,y;Assuming this is bottom right tile
sta RAMbufferodd, x;Odd buffer
lda pointerposition;used to be tya. You lose just one cycle doing this instead
;But you gain that back by not having
;ldx goodLocation and stx goodLocation (which would take 6 cycles)
;because X doesn't jobs in mine. It's always where you are in the buffer.
clc
adc #$10 ;increment y by 16!!!!
tay
dex
bpl loop
After loading the metatile index, you did tax. Mine does this too (well... tay instead), in addition to storing the position to temp ram. That takes 3 extra cycles.
Later, you did tya because you can only add to A. Mine does lda tempRAM instead which takes 1 extra cycle than tya. (if zero page)
All together, I've made your metatile index transfer work another way. It takes 4 cycles extra.
Quote:
I needed to move y anyway, but i didn't need to move x?
Right. You need X/Y for three tasks. 1. Loading from the pointer. (can only be done with Y) 2. Loading tiles from the metatile. 3. Storing the tiles to the buffer. This means either X or Y must change jobs, because two things can't do three jobs without changing. This is true for mine, and it was true for yours.
Because of how I preserved X instead of Y (which needed to be change jobs in both because it's needed to access the pointer), I've eliminated stx goodLocation and ldx goodLocation (DURING the loop anyway) which would have taken 6 cycles. So it ends up 2 cycles faster.
But mine is also faster for other reasons related to why I did the transfers that way. I dex once for every two times you do, because I do both even and odd at once using separate buffers. I avoid storing each tile of the metatile in the very beginning of the buffer RAM, because there's no need. I have where I am in the buffer in X already when I load the metatile index in y (you load where you are in the buffer later), so they're just stored exactly where they need to be. No need for the temp stores.
It saves a lot of cycles per loop. I think 42. 4 for doing dex twice instead of four times, 9*4=36 for not doing the indexed temp stores, 6 for not storing/restoring goodlocation. -4 for things I added.
This loops 15 times, so that's 630 cycles. 630 more if you do it twice for two 16x16 columns like it seems you're planning.
All that said, I make no guarantees this will work verbatim. There may be some extra stuff you need to do before/after the loop I'm forgetting, but I can't imagine any of it not making the savings worth it.
Edit: Heck, I was being safe, but you can move the clc before the add from the loop to before the loop if the pointer is set up such that y = 0 to access the first element. Nothing in the loop changes the carry except the add, and the adds during the loop will NEVER set the carry. (You add 16 to Y 15 times, which would only make it 240. Not greater than 255, so carry would be clear throughout.). This saves another 28 cycles per loop. 2*15 for not doing it in the loop -2 because you still need to do it before the loop.
THANK YOU SO MUCH FOR YOUR EXPLANATIONS KASUMI! goodnight!
edit: Here's his post with great explanations. I understand almost all of it!
Kasumi wrote:
The two pieces of code accomplish the same goal. (Though mine sets up the buffers differently. The different way would be faster for your NMI to read as well, though.) You don't need to store/restore X in goodlocation because it just stays in X. (I mean... you may still have to load it before the loop, but you no longer have to do it IN the loop.) You come out ahead because the code I added takes fewer cycles than the unneeded code I removed. (storing/restoring goodlocation)
I omitted some stuff, but the full thing would be like:
Code:
ldx #29;Before everything. So not during the loop. This is like goodlocation
;But we load it with #29 instead of #59 for other reasons.
loop:
lda ($10), y;Originally omitted. Have to do that still to get the index, of course
sty pointerposition;This wasn't needed before, so we're 3 cycles behind
tay;This was needed before. We overwrote what was in y, which is why we stored it above
;Metatile index is Y. Location in RAM buffer is in X.
lda MetatileTile0, y;Assuming this top left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile1,y;Assuming this is top right tile
sta RAMbufferodd, x;Odd buffer
dex;Takes us to the next tile for BOTH buffers
lda MetatileTile2, y;Assuming this bottom left tile
sta RAMbuffereven, x;Even buffer
lda MetatileTile3,y;Assuming this is bottom right tile
sta RAMbufferodd, x;Odd buffer
lda pointerposition;used to be tya. You lose just one cycle doing this instead
;But you gain that back by not having
;ldx goodLocation and stx goodLocation (which would take 6 cycles)
;because X doesn't jobs in mine. It's always where you are in the buffer.
clc
adc #$10 ;increment y by 16!!!!
tay
dex
bpl loop
After loading the metatile index, you did tax. Mine does this too (well... tay instead), in addition to storing the position to temp ram. That takes 3 extra cycles.
Later, you did tya because you can only add to A. Mine does lda tempRAM instead which takes 1 extra cycle than tya. (if zero page)
All together, I've made your metatile index transfer work another way. It takes 4 cycles extra.
Quote:
I needed to move y anyway, but i didn't need to move x?
Right. You need X/Y for three tasks. 1. Loading from the pointer. (can only be done with Y) 2. Loading tiles from the metatile. 3. Storing the tiles to the buffer. This means either X or Y must change jobs, because two things can't do three jobs without changing. This is true for mine, and it was true for yours.
Because of how I preserved X instead of Y (which needed to be change jobs in both because it's needed to access the pointer), I've eliminated stx goodLocation and ldx goodLocation (DURING the loop anyway) which would have taken 6 cycles. So it ends up 2 cycles faster.
But mine is also faster for other reasons related to why I did the transfers that way. I dex once for every two times you do, because I do both even and odd at once using separate buffers. I avoid storing each tile of the metatile in the very beginning of the buffer RAM, because there's no need. I have where I am in the buffer in X already when I load the metatile index in y (you load where you are in the buffer later), so they're just stored exactly where they need to be. No need for the temp stores.
It saves a lot of cycles per loop. I think 42. 4 for doing dex twice instead of four times, 9*4=36 for not doing the indexed temp stores, 6 for not storing/restoring goodlocation. -4 for things I added.
This loops 15 times, so that's 630 cycles. 630 more if you do it twice for two 16x16 columns like it seems you're planning.
All that said, I make no guarantees this will work verbatim. There may be some extra stuff you need to do before/after the loop I'm forgetting, but I can't imagine any of it not making the savings worth it.
Edit: Heck, I was being safe, but you can move the clc before the add from the loop to before the loop if the pointer is set up such that y = 0 to access the first element. Nothing in the loop changes the carry except the add, and the adds during the loop will NEVER set the carry. (You add 16 to Y 15 times, which would only make it 240. Not greater than 255, so carry would be clear throughout.). This saves another 28 cycles per loop. 2*15 for not doing it in the loop -2 because you still need to do it before the loop.
Yes! Sweet, thanks... y must = 0 to access the first element? If I set y to zero before the loop... that wont help right? I need to go away and think about this some more... bye.
edit2: I guess y can be guarenteed to be less than 16... that would work right? It would be 240+15=255 so the carry would be clear because 255 is not greater than 255.
I'm not ever going to draw the bottom half of a column... it will start near 0 each time, I think.
Ok, how do I somehow create RAMbufferW? I've got this part...
Code:
;======================v
RAMbufferw0even .dsb 30
RAMbufferw1even .dsb 30
RAMbufferw0odd .dsb 30
RAMbufferw1odd .dsb 30
;======================^
... ok, now I want to add
Code:
RAMbufferW .dsb 120
in the exact same place where those are... I've thought about this part so much... I've figured out so much
using yall's info Kasumi, 3gengames, and tepples... thank yall so much!!!
Need to understand this blah
help me please.
You can use the first variable name as the entire space, because it's going to be the same spot as the different variable name, just make sure you comment "This variable is used as (whatever) in this code." or mark the label so you know.
3gengames wrote:
You can use the first variable name as the entire space, because it's going to be the same spot as the different variable name, just make sure you comment "This variable is used as (whatever) in this code." or mark the label so you know.
THANK YOU INCREDIBLY SO MUCH 3GENGAMES!!!
Rather than using a comment, it's usually a better idea to define the label so that the code is easier to read:
Code:
RAMbufferW = RAMbufferw0even
thefox wrote:
Rather than using a comment, it's usually a better idea to define the label so that the code is easier to read:
Code:
RAMbufferW = RAMbufferw0even
WOAH That's what I wanted.....!!!!!
[b] Thank you thefox!! [/b]
This is becoming so much fun creating labels and overextending my variable! SWEET!
edit: so here it is
Code:
0C6A9 ;************************************************************************
0C6A9 ; recieves x... x selects which buffers to use:
0C6A9 ; 0 for RAMbufferw0even and RAMbufferw0odd
0C6A9 ; 1 for RAMbufferw1even and RAMbufferw1odd
0C6A9 ; destroys a x and y ooooooooooohhhhh nooooooooooooooooo
0C6A9 ;************************************************************************
0C6A9 draw_us_a_column: ;(please)
0C6A9
0C6A9 20 73 C6 jsr next
0C6AC A5 3B lda columnLo
0C6AE C5 45 cmp playdough
0C6B0 F0 3F beq +end
0C6B2 A5 3A lda columnHi
0C6B4 8D 06 20 sta $2006
0C6B7 A5 3B lda columnLo
0C6B9 8D 06 20 sta $2006
0C6BC
0C6BC 86 47 stx t10
0C6BE ;start with the even column
0C6BE
0C6BE
0C6BE ;x should be 0 or 1. Set it before hand, please. :)
0C6BE BC C9 DD ldy bufferoffsettable, x ;lda RAMbufferw0even, y
0C6C1 A2 1D ldx #29
0C6C3 B9 A8 05 - lda RAMbufferW, y
0C6C6 8D 07 20 sta $2007
0C6C9 88 dey
0C6CA CA dex
0C6CB 10 F6 bpl -
0C6CD
0C6CD A6 3B ldx columnLo
0C6CF E8 inx
0C6D0 A5 3A lda columnHi
0C6D2 8D 06 20 sta $2006
0C6D5 8E 06 20 stx $2006
0C6D8 ;then do the odd one
0C6D8
0C6D8 A6 47 ldx t10
0C6DA E8 inx
0C6DB E8 inx
0C6DC
0C6DC BC C9 DD ldy bufferoffsettable, x
0C6DF A2 1D ldx #29
0C6E1 B9 A8 05 - lda RAMbufferW, y
0C6E4 8D 07 20 sta $2007
0C6E7 88 dey
0C6E8 CA dex
0C6E9 10 F6 bpl -
0C6EB
0C6EB +complete
0C6EB A5 3B lda columnLo
0C6ED 85 45 sta playdough
0C6EF
0C6EF E6 30 inc valid_left
0C6F1
0C6F1 60 +end: rts; end of draw_us_a_column
lunch time
unregistered wrote:
Wow! Once my game becomes alive...
the Max part in Nintendulator debug says 2872... which is no good cause you said
Kasumi wrote:
If all the input is now good (i.e. You verified the RAM now has the correct 60 values, and you can't find issues with the code that reads from it) and you're getting bad output, it's possible you're trying to write to $2007 when rendering has begun. ~2270 cycles before rendering begins after the NMI. ~513 of those are eaten by sprite DMA. Anything from the start of the NMI to the last write to $2006/$2007 should happen in less.
Code:
vblank: sta $401E
pha
tya
pha
txa
pha
*********SNIP*************
SkipUpdates: sta $401F
so 2270-513 is quite less than 2000. Thank you for your example code... it helped me realize that my vblank is overused.
Need to spend some time on using it less.
I am going to reread your reply.
edit: Ok I'm going to think more about your simple question. It seems like a hard question right now. Ok it's supper time! Goodnight and thank you for all of this theory!
Kasumi, now my NintendulatorDX Max says 2776... but average says 1630.26.... this still is much better than 2872. Maybe it would be better to just have draw_us_a_column and draw_us_another_column... both have same code with either (RAMbufferw1even and RAMbufferw1odd) or ( RAMbufferw0even and RAMbufferw0odd). Then my loops would be quicker by 2 cycles each time through, right?
edit: Average is at 1034... then it keeps rising... now at 2409!
edit2: it always keeps rising.. now at 2626 max is still 2776.
last edit: Average starts out at like 609 when my game comes alive!! That's with no changes code is exactly the same as above.
609 is probably when you are only updating sprites. Anything else is when you're updating tiles+sprites, which yeah, would result in an average in between. And if you never move so that new tiles scroll, the average would keep getting lower because no frames with tile updates are happening. And vice versa.
Focus only on max, which is still way too high. It's over the limit and you haven't even added attribute stuff. Benchmark each part of your NMI.
Code:
sta $401E
jsr draw_us_a_column
sta $401F
How many cycles is that taking? If you're doing it twice, how much does each take? That's a way you can find out what to optimize. Maybe your loops are fine, but the next routine that's run inside draw_us_a_column is really slow or something. Benchmark stuff to find out what's eating all your cycles.
And remember, you can prepare everything outside of the vblank. You can do what next does (prepare columnhi and columnlo) outside of vblank, store it in RAM guaranteed to not be used by anything else, then have the NMI read the decision rather than have the NMI MAKE the decision. Anytime you can avoid your NMI making a decision (before screen updates are finished at least), you should do it.
Things your NMI
needs to do:
Store registers A, X and Y so your program doesn't break upon return.
Decide whether or not the tile buffers are READY to update, so it can skip them if not.
Any writes to $2006 or $2007.
Sprite DMA.
Scroll Writes. (Well... sorta)
Restore registers A, X, and Y.
Anything not in the above list, including the preparation of said buffers can probably be moved out.
Here's a thought: Instead of having your buffers contain JUST the tiles, why not have them contain the address too? So now they're 32 bytes instead of 30. And you do this above the loop:
Code:
0C6BE BC C9 DD ldy bufferoffsettable, x ;(Gets a value of multiple of 32-1 rather than 30-1.)
lda RAMbufferW, y
sta $2006
dey
lda RAMbufferW,y
sta $2006
dey
0C6C1 A2 1D ldx #29
0C6C3 B9 A8 05 - lda RAMbufferW, y
0C6C6 8D 07 20 sta $2007
Then you have no need for columnhi or columnlo and next doesn't need to be run in the NMI. (You still need to prepare the addresses with the buffer OUTSIDE the NMI, of course.)
One other really tiny thing that won't save you a lot, but is worth seeing.
Code:
jsr update_sprite
This adds 12 cycles and 3 bytes to your NMI, as opposed to just putting sprite update stuff directly in.
Like so:
Code:
ldy #$00
sty $2003
lda #;Whatever page you're using for sprite updates
sta $4014
I understand it's sometimes nice to have a 1 line thing rather than more, but it costs you time and space if the subroutine is called from just that one place. If you really like the 1 line thing, you can still do that by making update_sprite or anything else like it a macro.
Quote:
Maybe it would be better to just have draw_us_a_column and draw_us_another_column...
...
Then my loops would be quicker by 2 cycles each time through, right?
Really there's no shame in doubling up code sometimes. And yes, you'd save 2 cycles per each loop, but be careful always! Sometimes the cycles you save will be overcome by the additional setup you need to do. (Probably not in this case, but think about it.)
Code:
lda RAMbufferW, y
0C6C6 8D 07 20 sta $2007
0C6C9 88 dey
0C6CA CA dex
0C6CB 10 F6 bpl -
This is 10 bytes. If you had one of these loops for each of the each buffers, it's not a huge hit. And like you said, you wouldn't need both dex and dey (because both would always be the same value between 0 and 29), so each would only really be 9 bytes. (A small bit more logic at the beginning to select which piece of the quadrupled up code you want, though.)
I quadrupled a larger routine (42 bytes, so 168...) for the sole reason I save a compare at the end of the loops. Didn't think twice about it. That said, I could probably use a pointer and come out VERY ahead on the current 168 bytes, and only slightly behind on cycles.
Anyway, what to do in optimization is a choice for you. I focus on speed at the expense of size 9 times out of 10. Now I'm out of space in my ROM and have to dial things back.
Sometimes fortune smiles on you and you find a way to make something both smaller and faster, but generally when you're making a thing faster, you're making it bigger. When you're making it smaller, you're making it slower.
See unrolled loops vs rolled loops, quadrupled code vs not, huge tables vs loops. You can find a middle ground most of the time, with semi unrolled loops, doubled up code with some extra stuff to setup each version, smaller tables + loops that don't run as many times...
Your NMI routine doesn't necessarily need to be AS FAST AS POSSIBLE, but right now it's not fast enough. And even the 60 or so cycles you'd save by doubling up the code doesn't get you anywhere near where you need to be. And I have no idea what's taking up all the time, so find out!
Kasumi wrote:
609 is probably when you are only updating sprites. Anything else is when you're updating tiles+sprites, which yeah, would result in an average in between. And if you never move so that new tiles scroll, the average would keep getting lower because no frames with tile updates are happening. And vice versa.
I've only read this so far and I want to say that 609 is after my game comes alive... after 2 16-bit wide columns are drawn my screen said Avg 609. That's very impressive!!! Thank you so much Kasumi and some others... 3gengames, tokumaru, tepples, thefox, and qbradq!!
And... ...well secondly, after coming alive, my game's average grows grows an d grows. That's all it does... there isn't any tiles scrolling... I'm just standing there after 2 16-bit wide columns are drawn and it grows! Up up up up up up up and up. It almost got higher than my new lower around 2776 Max... but I ended it. Ok that's all I can say now I'm going to be viewing Naruto Shippuden. Bye.
edit: It's Wednesday afternoon now. Ok I'm going to find out what is taking up all this time; thank you, Kasumi, you bless me with all this knowledge!
There's past words that I'm going to try too.
Kasumi wrote:
One other really tiny thing that won't save you a lot, but is worth seeing.
Code:
jsr update_sprite
This adds 12 cycles and 3 bytes to your NMI, as opposed to just putting sprite update stuff directly in.
Like so:
Code:
ldy #$00
sty $2003
lda #;Whatever page you're using for sprite updates
sta $4014
I understand it's sometimes nice to have a 1 line thing rather than more, but it costs you time and space if the subroutine is called from just that one place. If you really like the 1 line thing, you can still do that by making update_sprite or anything else like it a macro.
I'm trying making update_sprite a macro...
Code:
.MACRO update_sprite
lda #>sprite
sta $4014 ;OAM_DMA register ; Jam sprite page ($200-$2FF) into SPR-RAM
;takes 513 cycles.
.ENDM
but it says "Label already defined." I guess the code thinks my other "update_sprite" is a label instead of recognizing it's a macro?
unregistered wrote:
but it says "Label already defined." I guess the code thinks my other "update_sprite" is a label instead of recognizing it's a macro?
Sounds right. You can't have both a macro called update_sprite and a label called update_sprite. Just get rid of the version with the label, you don't need it when you have the macro.
Got a text editor that can find all instances of a thing in a directory? Search for it if it's not where you think.
Edit: Or wait, how are you calling the macro? Not sure how asm6 works, but you need to have space before it or it will think it's a label.
Code:
update_sprite;Is different than
update_sprite;
;at least in nesasm.
Kasumi wrote:
Sounds right. You can't have both a macro called update_sprite and a label called update_sprite. Just get rid of the version with the label, you don't need it when you have the macro.
Got a text editor that can find all instances of a thing in a directory? Search for it if it's not where you think.
but it's my method call...
Code:
MACRO / ENDM
MACRO name args...
Define a macro. Macro arguments are comma separated.
Labels defined inside macros are local (visible only to that macro).
MACRO setAXY x,y,z
LDA #x
LDX #y
LDY #z
ENDM
setAXY $12,$34,$56
;expands to LDA #$12
; LDX #$34
; LDY #$56
That's what README.TXT says. My macro doesn't have any arguments... so when I call it it is just
Code:
update_sprite
edit: there are two spaces infront of update_sprite... I guess asm6 has a problem with an argument-less macro call... I bet tokumaru would know.
Define your macro before you use it.
Code:
macro mario
lda #$00
endm
mario
works
and
Code:
mario
macro mario
lda #$00
endm
doesn't.
If it ain't that, I have NO idea.
edit: Anyway, typically when I do things like this (put a bunch of code into a macro instead for one line convenience), I put the hide-code macros in a different file and include that file at the top of the asm file that uses it.
Kasumi wrote:
Define your macro before you use it.
Code:
macro mario
lda #$00
endm
mario
works
and
Code:
mario
macro mario
lda #$00
endm
doesn't.
If it ain't that, I have NO idea.
That was it!
Thank you Kasumi! edit:
Your edit is a great idea!
Kasumi wrote:
Focus only on max, which is still way too high. It's over the limit and you haven't even added attribute stuff. Benchmark each part of your NMI.
Code:
sta $401E
jsr draw_us_a_column
sta $401F
How many cycles is that taking? If you're doing it twice, how much does each take?
The top one's cycles Max is 1051. And the lower one's cycles Max is 1068. Is that too much?I guess it is.
I'll continue benchmarking each part of draw_us_a_column after lunch.
edit: ok... each of these loops runs 30 times
Code:
sta $401e
- lda RAMbufferW, y
sta PPUDATA7
dey
dex
bpl -
sta $401f
The upper loop runs 449 cycles and the lower loop runs 479 cycles cause I guess it crosses a page boundary. The code in between runs 29 cycles once. So thats a Max of 957 cycles for the two loops and the inbetween. That's close to 1051 and 1068...
So you can partially unroll, fully unroll (probably not worth it) or do stack stuff.
Partially unrolled looks like this:
Code:
ldx #5
- lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
dex
bpl -
It has the inner part of your loop copied five times, so it loops 6 times at 55 cycles per loop. (330 total. Well... minus 1 because the branch not taken to end the loop takes 1 less cycle than a branch taken to loop again.)
Stack is tricky, because you have to store/restore the stack pointer. When your program returns from the NMI, the stack pointer needs to be what is what when the NMI started, since it uses values on the stack to return.
But it looks like a little like this:
Code:
;assume your data is at $0100-whatever instead of
;RAMbufferW-whatever
ldx #$FF;$FF not $00 because the stack pointer sets where
txs;a value is pushed to. Pulling from the stack takes the value
;one greater than that with wrap.
ldy #29
-pla;automatically increases the stack pointer
sta PPUDATA7
dey
bpl -
It's 13 cycles per loop, loops 30 times for 390-1 total. Only 60 cycles faster than what you've got now (assuming no page cross). Dex is 2 cycles, we avoid doing it 30 times. It's worth noting that pla is 2 bytes smaller than absolute,y, and removing the dex saves 1 more. So it doesn't take as much space to partially unroll or unroll.
Here's what a stack partially unrolled approach would look like:
Code:
ldx #$FF;$FF not $00 because the stack pointer sets where
txs;a value is pushed to. Pulling from the stack takes the value
;one greater than that with wrap.
ldy #2
-pla;automatically increases the stack pointer
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
pla
sta PPUDATA7
dey
bpl -
It loops 3 times, at 83 cycles a loop. 249-1 total. Also, even though it has the inner part of the loop copied ten times instead of the 5 used in the absolute,y unrolled example, it's only 5 bytes larger. Not quite twice as fast as what you have now, but pretty close. Requires some additional work to setup and restore the stack, though.
Another option is to update just one column in the NMI. Just save the attribute bytes to RAM to make those updates easier when you're only doing one. This would be faster for your NMI than doing anything above twice.
You could even do both! Have a faster update method AND use it for only one column at once.
unregistered wrote:
edit: ok... each of these loops runs 30 times
Code:
sta $401e
- lda RAMbufferW, y
sta PPUDATA7
dey
dex
bpl -
sta $401f
The upper loop runs 449 cycles and the lower loop runs 479 cycles cause I guess it crosses a page boundary.
How do I make it not cross a page boundary?... Then I'd save 30 cycles!
unregistered wrote:
How do I make it not cross a page boundary?... Then I'd save 30 cycles!
Make sure that the branch and the address branched to are in the same page. You might need to move some code/data around.
I often group routines that can't cross pages together, before all the code that's not timing sensitive, so that I can mix and match until I find the best order for them. You obviously can't keep changing the routines after you find a place for them, otherwise you might break the page alignment. During development you can use the .align command to align subroutines to page boundaries.
tokumaru wrote:
I often group routines that can't cross pages together, before all the code that's not timing sensitive, so that I can mix and match until I find the best order for them. You obviously can't keep changing the routines after you find a place for them, otherwise you might break the page alignment. During development you can use the .align command to align subroutines to page boundaries.
How do I use the .align command?
unregistered wrote:
How do I use the .align command?
Say that the PC (program counter) is $8d54. If you put an ".align 256" command, the assembler will pad the ROM to $8e00 (the next 256-byte boundary). This means that 172 bytes go to waste, which is why you shouldn't use this in your final program, just during development while you still don't know the sizes of all timing-sensitive routines.
Also, don't use .align in the middle of a routine, because the CPU will try to execute the padding bytes and will most likely crash. Use .align only between tables and subroutines.
If using ca65 I prefer to create a segment with the desired alignment and then use that segment.
I usually combine with something like this to make sure the code is placed where I expect:
Code:
.assert * = $8000, error, "This code is not at the correct position."
tokumaru wrote:
unregistered wrote:
How do I use the .align command?
Say that the PC (program counter) is $8d54. If you put an ".align 256" command, the assembler will pad the ROM to $8e00 (the next 256-byte boundary). This means that 172 bytes go to waste, which is why you shouldn't use this in your final program, just during development while you still don't know the sizes of all timing-sensitive routines.
Also, don't use .align in the middle of a routine, because the CPU will try to execute the padding bytes and will most likely crash. Use .align only between tables and subroutines.
Excellent!
Thanks tokumaru!! You answered both of my questions... thank you kind sir.
Kasumi wrote:
So you can partially unroll, fully unroll (probably not worth it) or do stack stuff.
Partially unrolled looks like this:
Code:
ldx #5
- lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
lda RAMbufferW, y
sta PPUDATA7
dey
dex
bpl -
It has the inner part of your loop copied five times, so it loops 6 times at 55 cycles per loop. (330 total. Well... minus 1 because the branch not taken to end the loop takes 1 less cycle than a branch taken to loop again.)
KASUMI!!!!!!!!!!!!!!!!!!!!!!!!!!!! THANK YOU SO INCREDIBLY MUCH!!! Well I triedmeasuring my enitre vblank and the max is 2259. That's using your partially unrolled loop for each column. It's crazy how that works unrolling partially... That 2259 is too high... looks like I'm going to have to draw one 16 bit wide column per vblank. And I'm happy about that.
16px per vblank is how fast Sonic scrolls, so you're in good company.
I want to do this the correct way... and I feel that 2 16bit columns per vblank is possible... maybe next could go away. 2223 cycles. That is without my attribute table coloring code. And that is ok because I haven't started writing it yet. And... there isn't any scroll_screen code to remove from that number because scroll_screen is after SkipUpdates: and Kasumi, you had me end cycle counting at SkipUpdates.
Quote:
I feel that 2 16bit columns per vblank is possible.
Tokumaru says it can be done. But if your game never actually scrolls that fast, doing the work it requires is wasted effort. For the record, my game is prepared to scroll 8 pixels in both directions every frame, and isn't optimized enough to even do 16 pixels in one direction and 8 in another. And it's already pretty large/optimized. Doing four 8 pixel wide updates is pretty intense.
Quote:
Kasumi, you had me end cycle counting at SkipUpdates.
The scroll stuff could take just 14 cycles, so it shouldn't be a problem. But technically the writes to $2005 should also be done before ~2270 cycles pass. It just won't break terribly much if you don't make it. (Maybe vertical scrolling will be off for a frame, with a wrong scanline or two of horizontal scrolling.) Unlike $2006 and $2007, $2005 is safe to write to during rendering.
Kasumi wrote:
Quote:
I feel that 2 16bit columns per vblank is possible.
Tokumaru says it can be done.
Indeed, but you won't be doing much else (sprite DMA is OK, but that's it)! =)
The way I did it is pretty intense, because of the 8-way scrolling, which means that new rows/columns can cross name table boundaries... To unroll the code without having to check for the boundaries I had to use a jump table (to jump to the middle of the unrolled data transfer routine, with the correct amount of bytes left to copy) and some index trickery (mostly explained
here and
here - man, this thing is OLD!).
Quote:
But if your game never actually scrolls that fast, doing the work it requires is wasted effort.
Yeah, I see no point in maxing out the amount of bytes you send to VRAM each frame if you don't need that much. You could better spend the time on CHR-RAM animations and things like that.
Thank you both Kasumi and tokumaru! You two have helped me realize that scrolling 16 px per vblank will be an effort supported by a welcoming relief. We will soundly build our 16 pixel column and save part of our coloring in RAM for the next vblank. Ah ok, it is 2 pm... time to begin!
tepples wrote:
I'd be tempted to allocate five 32-byte buffers $0100, $0120, $0140, $0160, and $0180.
Ah, I understand now why you would use 32 byte buffers... it's really confusing trying to compare three thirty-byte columns. Would have been easier to compare the buffers you suggested!
Thank you very much tepples!!
---
And GRAND EXCELLENT GREATNESS!!!
Ya'lls help has been excellent! Kasumi, I relearned how to use FCEUX's hex editor... and I had to check my buffers to see if they were correct... and they were! So
thank you Kasumi!! MY GRAFITTIE IS GONE AND ITS RUNNING EXTRA SMOOTH WITH YOUR UNROLLED LOOP!! I bet that's the cause of all the extra grafittie... now that my loop is running within vblank time (my MAX is 1410 cycles!
) the extra grafittie art that happened in nametable 01 while drawing nametable 00 columns is gone!! So if you are ever shown grafittie during column drawing don't worry about it... it will go away once your vblank is within the vblank limit (I'm sorry I don't know what the limit is right now) ...I'm ready to post this and take a break... you know relax! Sigh...
Cool, glad you've got it sorted!
Kasumi wrote:
Cool, glad you've got it sorted!
: )
---
Good morning
everyone! I have this small question/problem... my 16 bit columns are drawn starting with column 1. What happenes to column 0 of nametable 0? NOTHING... ever.
Do you have any ideas...oh here is what I've discovered...
1. the correct code for column 32 is present in my RAMbufferW.
2. all the collumns are correctly drawn to
incorrect places in vram... starting with column 32 in the place of column 1. why?
added.
No idea. Possible answer: You're incrementing to the next column, then updating, rather than updating then incrementing to the next column. (Or similar! As always it depends on how you put it together. What it's actually doing vs. what you designed it to do.)
Kasumi wrote:
No idea. Possible answer: You're incrementing to the next column, then updating, rather than updating then incrementing to the next column. (Or similar! As always it depends on how you put it together. What it's actually doing vs. what you designed it to do.)
I can do this. : )
continuing my previous post. I've got it now so that it draws column 32 twice... and then every column after is correct but not in the correct spot... so it all looks ok. I'm giving up on that for right now and attempting to color our columns. That may be difficult though; almost all of them except column 32 are incorrect. Maybe... it seems like coloring the wrong columns would be fun and crazy... and maybe I could fix them.
AND I must once again complain that colums are raising up! Every new screen is raised a metatile (16x16). There is something I'm doing that makes each new screen 16 pixels higher. ...for a little while it stopped raising up... but I continued down that road and I don't quite know what I did to make it reraise.
In my head I thought changing the pointer address to a new screen would stop the raising up. I was mistaken.
Something else is incorrect. Hmm from my notes... Nametable 0 contains metatile columns 0-15
Nametable 1 contains metatile columns 16-31.
Nametable 0 will fill up with columns 32-47
then Nametable 1 will fill up with columns 48-63
then Nametable 0 will fill up with columns 64-79. It's weird visible_left is at 50... and it just finished drawing drawing screen 4. Wonder what I could find that's wrong... this is so much fun!
See me
I'm trying...
edit: All those notes are from tepples. So if they are incorrect it must be my fault. Sorry.
edit2: Ok, I should start my column at 32... not 0.
tepples, on page 69, wrote:
Let's assume that your map is structured as columns of metatiles, and each column is 16 pixels (2 tiles, 1 color area) wide, and your PCB arranges nametables horizontally, resulting in vertical mirroring. There is enough video memory to keep 32 columns of metatiles valid. The NES picture is 256 pixels wide, meaning that 17 columns will be fully or partly visible.
You manage updates using two variables:
- visible_left, the left side (in columns) of the area. Normally, this will be about 8 columns to the left of the player.
- valid_left, which your scrolling code updates as it draws columns to the nametables.
What you want to do is make sure that the interval
visible_left through
visible_left + 16 is contained within
valid_left through
valid_left + 31. Here's the logic:
- At the start of the level, before turning on rendering, draw all columns from valid_left through valid_left + 31.
- If visible_left becomes less than valid_left, column valid_left - 1 is coming into view. Decrease valid_left by 1 and draw column valid_left.
- If visible_left becomes greater than valid_left + 15, column valid_left + 32 is coming into view. Increase valid_left by 1 and draw column valid_left + 31.
- Clamp the camera X position to valid_left * 16 through valid_left * 16 + 256 so that the camera falls behind instead of glitching if the worst happens.
But each byte in the attribute table spans two columns. Depending on how you organize the map data, you may have to either draw two columns at once like
Super Mario Bros. and
Contra or store enough information to regenerate the attributes for the column that you're updating.
Ok tepples I'm wondering about valid_left... how do you draw column
valid_left + 31? ...I mean how could I code this so that I could draw
valid_left + 31... that's just a number...
I bet this would be easier if I actually drew columns
valid_left to
valid_left + 31.
edit: Ah, ok... so valid_left is always keeping track of the memory that you would have to draw the columns in... so drawing column
valid_left + 31 is always drawing to the last column line of memory? Is that right?
edit2 - nevermind... I'm figuring this out.
um... I want to know how do I make command prompt bigger so that I could see what the error is. It just scrolls down past its max and I can't see the first error. I'm using windows vista and asm6. It lists a bunch of "Can't determine address." ...I'm confused.
unregistered wrote:
um... I want to know how do I make command prompt bigger so that I could see what the error is. It just scrolls down past its max and I can't see the first error.
You can redirect the output to a file by putting "> file.txt" at the end of the command and then open that file in Notepad (BTW, this can be done with all commands that output stuff to the prompt window, it's not an ASM6 feature). The assemble command would then be something like this: asm6 source.asm rom.nes > file.txt
Quote:
I'm using windows vista
I'm sorry.
Quote:
It lists a bunch of "Can't determine address." ...I'm confused.
One undetermined address is all it takes to screw up everything that comes after it. Try to fix the first error and the rest will probably be OK.
tokumaru wrote:
You can redirect the output to a file by putting "> file.txt" at the end of the command and then open that file in Notepad (BTW, this can be done with all commands that output stuff to the prompt window, it's not an ASM6 feature). The assemble command would then be something like this: asm6 source.asm rom.nes > file.txt
Almost. I don't know about ASM6 in particular, but most compilers and programs similar to compilers will print error messages on stderr (standard error output), not stdout (standard output). This redirection command redirects stdout, not stderr. Try
Google windows command prompt redirect stderr and you'll find your answer in the first few results.
Quote:
Try to fix the first error and the rest will probably be OK.
Agreed. The first error often throws the parser's state out of sync with the programmer's idea of the parser's state.
tepples wrote:
Almost. I don't know about ASM6 in particular, but most compilers and programs similar to compilers will print error messages on stderr (standard error output), not stdout (standard output). This redirection command redirects stdout, not stderr. Try
Google windows command prompt redirect stderr and you'll find your answer in the first few results.
Oh, I didn't know that. Oh well, I almost got it right, you just have to use a different symbol. Good to know, thanks.
tokumaru wrote:
Quote:
I'm using windows vista
I'm sorry.
Haha
hahaha. ...
I love Windows Vista.
tokumaru wrote:
unregistered wrote:
um... I want to know how do I make command prompt bigger so that I could see what the error is. It just scrolls down past its max and I can't see the first error.
You can redirect the output to a file by putting "> file.txt" at the end of the command and then open that file in Notepad (BTW, this can be done with all commands that output stuff to the prompt window, it's not an ASM6 feature). The assemble command would then be something like this:
asm6 source.asm rom.nes > file.txttepples wrote:
tokumaru wrote:
You can redirect the output to a file by putting "> file.txt" at the end of the command and then open that file in Notepad (BTW, this can be done with all commands that output stuff to the prompt window, it's not an ASM6 feature). The assemble command would then be something like this: asm6 source.asm rom.nes > file.txt
Almost. I don't know about ASM6 in particular, but most compilers and programs similar to compilers will print error messages on stderr (standard error output), not stdout (standard output). This redirection command redirects stdout, not stderr. Try
Google windows command prompt redirect stderr and you'll find your answer in the first few results.
Thank you both tepples and tokumaru!! My command ended up being like this:
asm6 source.asm rom.nes 2> file.txt. That 2> created the file.txt... with
all the stderr output from asm6.
tepples wrote:
(tokumaru) wrote:
Try to fix the first error and the rest will probably be OK.
Agreed.
Thank you both again!
It was a spelling error... I typed "CurrentColum"... added the n and now I'm sitting here with a new working .nes file! WOOHOO!!!
addition.another addition.addition3.highlight.
This is a general question about attribute tables... ok right now only the first column of attributes changes. I think this is because I'm only specifying that first column to be colored...
Code:
update_colors: ;under development
;this is run at the end of update_vram, which sets the PPU to increment by +32
;
;RAMbufferColors is ordered differently
; ...2xF8, 2xD8, 2xF0, 2xD0, 2xE8, 2xC8, 2xE0, 2xC0
sta $ff
lda CurrentColumn+0
ror a
bcc +end
lda #$23
sta PPUADDR6
lda #$C0
sta PPUADDR6
ldx #$02
jsr load_screen ;sets ten_low and ten_high for ams access
;write to 23C0 and 23E0
lda RAMbufferColors+7
sta PPUDATA7
lda RAMbufferColors+6
sta PPUDATA7
;must set 2006 with 23C8
lda #$23
sta PPUADDR6
lda #$C8
sta PPUADDR6
;write to 23C8 and 23E8
lda RAMbufferColors+5
sta PPUDATA7
lda RAMbufferColors+4
sta PPUDATA7
;must set 2006 with 23D0
lda #$23
sta PPUADDR6
lda #$D0
sta PPUADDR6
;write to 23D0 and 23F0
lda RAMbufferColors+3
sta PPUDATA7
lda RAMbufferColors+2
sta PPUDATA7
;must set 2006 with 23D8
lda #$23
sta PPUADDR6
lda #$D8
sta PPUADDR6
;write to 23D8 and 23F8
lda RAMbufferColors+1
sta PPUDATA7
lda RAMbufferColors+0
sta PPUDATA7
+end rts
;end of update_colors
What I need to do is to somehow select which column to update. Instead of $23C0 use... $23C1... or 23C2. Something like that right?
edit: Yes, ok that was easy... now I 've got to fix the colors and make this work on nametable 1... will get this I think.
Ok so my coloring is perfect!
! And so there are still two problems.
Ok, see I've made a change so that the third screen is on level. And I also know that there is a possibility of the ground not moving up... but I have lost that right now.... screens just keep rising up a metatile.
Extended effort to level each new screen has not been
preformed performed yet cause when it worked I had to go raise up my third screen
.added.correction.correction2.
I can work on movement now... I know that my sprite code is in draw_sprite. I don't know how to delete my sprite - it stays on screen too long, in my opinion right now. How do I clear my sprite screen? Right now up to 4 8tile sprites are drawn right away.
Shiru wrote:
With single screen game you can just unpack level from metatiles or anything in RAM buffer as simple tile/collision map, and have simple collision code, that's what I meant.
I see. Well, in scrolling games you can use the same type of progressive updates you use for the name tables with the collision data, it's basically the same principle.
Wow, thank you for these words. Can you explain a bit more on how "it's basically the same principle"?
I'm going to try to make my collision data scroll over
so I can fall down through the water.
edit:
tokumaru, on page 15, wrote:
Shiru wrote:
tokumaru, what about moving the objects and check collisions with them?
It's the same as in single-screen games, only the "screen" is actually the level, and it can be much larger than 256x256. Instead of 8-bit comparisons you'll be making 16-bit ones, that's the only difference in movement and collisions.
Oh ok I maybe see... I will have to try this on paper... I think I understand now.
Well, you know how there are only 2 name tables we have to use to display levels much longer than that? What you do is overwrite data for screens you left behind with data for new screens, so the 3rd screen overwrites the first, the 4th overwrites the second, and so on (of course this can happen in the other direction as well, unless your game is like SMB1 and you can't scroll backwards).
So, if you're decompressing collision data to ROM, the principle is the same. If you set aside enough memory to hold the collision data of 2 screens, once that memory is all used up you wrap back to the other end and start overwriting the data.
You can often use the AND instruction to find out the corresponding places of game world coordinates in the name tables or collision tables in RAM. If you want to know where in the name tables to draw metatile column number 237 of your level, you just mask off the bits that are outside of the range of the name tables. If the metatiles are 16 pixels wide, you can have 32 columns of them in the 512x240-pixel space provided by the name tables. You need 5 bits to count from 0 to 31 (those are your 32 columns), so anything past the 5th bit should be masked off. Since 237 AND %00011111 = 13, you know that column 237 of your level map will be drawn at column 13 of the name tables. With a few more calculations you can even convert that into addresses you can write to $2006 for updating tiles/attributes (but that's outside of the scope of this message!
).
The exact same thing happens with with collision data you keep in RAM. If you've been progressively decompressing it there and overwriting the data you don't need anymore, you can be sure that masking off the unnecessary bits from the X coordinate relative to the game world will give you the correct coordinate for the data you're after in your RAM table.
tokumaru wrote:
Well, you know how there are only 2 name tables we have to use to display levels much longer than that? What you do is overwrite data for screens you left behind with data for new screens, so the 3rd screen overwrites the first, the 4th overwrites the second, and so on (of course this can happen in the other direction as well, unless your game is like SMB1 and you can't scroll backwards).
So, if you're decompressing collision data to ROM, the principle is the same. If you set aside enough memory to hold the collision data of 2 screens, once that memory is all used up you wrap back to the other end and start overwriting the data.
You can often use the AND instruction to find out the corresponding places of game world coordinates in the name tables or collision tables in RAM. If you want to know where in the name tables to draw metatile column number 237 of your level, you just mask off the bits that are outside of the range of the name tables. If the metatiles are 16 pixels wide, you can have 32 columns of them in the 512x240-pixel space provided by the name tables. You need 5 bits to count from 0 to 31 (those are your 32 columns), so anything past the 5th bit should be masked off. Since 237 AND %00011111 = 13, you know that column 237 of your level map will be drawn at column 13 of the name tables. With a few more calculations you can even convert that into addresses you can write to $2006 for updating tiles/attributes (but that's outside of the scope of this message!
).
The exact same thing happens with with collision data you keep in RAM. If you've been progressively decompressing it there and overwriting the data you don't need anymore, you can be sure that masking off the unnecessary bits from the X coordinate relative to the game world will give you the correct coordinate for the data you're after in your RAM table.
Wow, sweet!!!! Thank you tokumaru!! I'm going to have to read this again tomorrow morning,,, haha... goodnight.
edit: Ok, this is the 8th. Um... ah there has to be a better way to keep collision info for the two screens... I'm planning on using 240 bytes per screen... then if the metatile (16x16) is not an
all (all 4 tiles are the same)... then I plan to access the tables in ROM to find the four values. That's good right? It sounds good to me so I'm going to start doing that.
um... I guess that it is impossible to use a variable larger than 256... cause it just doesn't work... I've run through my code trying to correctly fill my 480byte variable and it reaches 6FF and then reset back to 600 again. It is susposed to continue to fill my 480 byte variable so then... ...well, I was going to try to use 32bit wide row logic cause it seemed way easier.
I'm feeling kind of lost.
If the low byte wraps around from FF to 00, increment the high byte.
tepples wrote:
If the low byte wraps around from FF to 00, increment the high byte.
That's a great idea... not sure how to do that right now... BUT IT IS AN INCREDIBLE IDEA!
Thank you tepples!
edit: Goodness... its 2:00am now. Anyways just wanted to say that I'm looking forward to figureing this out myself. I am assuming that you know that that works... tepples, Kasumi, tokumaru, thefox, Shiru have any of you successfully used a larger than 256 byte variable?
Was it fun?
Ok I'm so looking forward to tomorrow figuring this out... must be up at 6:55am tomorrow. Goodnight.
My current project has a 768-byte (32 column by 24 row) playfield. There are subroutines to seek to a particular row, which puts the base address of that row in a 2-byte variable in zero page, and then accessing a particular tile on that row becomes LDA (fieldlo),Y with the column number in Y.
tepples wrote:
My current project has a 768-byte (32 column by 24 row) playfield. There are subroutines to seek to a particular row, which puts the base address of that row in a 2-byte variable in zero page, and then accessing a particular tile on that row becomes LDA (fieldlo),Y with the column number in Y.
That's really cool!! Thank you for sharing!
That's what mine will be; it'll have 32 columns! But maybe
16 15 rows. So far... I think.
I also think I'll use a subroutine to seek to a particular row... that sounds
wonderful! like fun; Thank you tepples!
edit.
When I try to assemble my nes file asm6 says
Quote:
pass 1..
Can't open file.
What does that mean? I found the .asm file with dir... but it still says "Can't open file." Why? I must have done something wrong...
unregistered wrote:
I found the .asm file with dir... but it still says "Can't open file." Why?
Even if you were able to find the file yourself, you're probably not giving the correct path to the assembler.
Are you calling the assembler from a .bat file? Double check the command line to make sure the path is correct, relative to the path where the call to the assembler is made.
For example, if you have the following folder structure:
Code:
projects
|_game
|_asm6
And you want to assemble "source.asm" in the "game" folder from a batch file in the "projects" folder, the correct command would be "asm6\asm6.exe game\source.asm". If the batch file was in the "game" folder, the command would be "..\asm6\asm6.exe source.asm".
If you can't get the paths right, just put everything in the same folder and it should work with just the file names in the command.
tokumaru wrote:
unregistered wrote:
I found the .asm file with dir... but it still says "Can't open file." Why?
Even if you were able to find the file yourself, you're probably not giving the correct path to the assembler.
Are you calling the assembler from a .bat file? Double check the command line to make sure the path is correct, relative to the path where the call to the assembler is made.
For example, if you have the following folder structure:
Code:
projects
|_game
|_asm6
And you want to assemble "source.asm" in the "game" folder from a batch file in the "projects" folder, the correct command would be "asm6\asm6.exe game\source.asm". If the batch file was in the "game" folder, the command would be "..\asm6\asm6.exe source.asm".
If you can't get the paths right, just put everything in the same folder and it should work with just the file names in the command.
hmm... this is very interesting.
That's what I've done... everything is in the same folder. I always type out...
Code:
l:
cd nesys\dasource\
asm6 -L source.asm da-0017v.nes
and then my nes file is made... and plus... ...well um ok... the problem was I typed
Code:
asm6 =L source.asm da-0017v.nes
and it doesn't like that equals sign... now I've written this down... so next time it happens I'll be able to find this here.
Thank you incredibly much tokumaru!
I think it's probably worth creating a .bat so this will never happen in the future.
Create a file in nesys\dasource\ called "assemble.bat". Open it with the text editor of your choice, and put the following into it:
Code:
asm6 -L source.asm da-0017v.nes
PAUSE
and save. (The PAUSE keeps the window from immediately closing afterwards, so you can read errors if there are any.)
Then, every time you double click assemble.bat, your rom will be built and you don't have to type anything.
edit: Alternatively if you like the command line and don't prefer to double click each time, you can also do this:
Code:
cd nesys\dasource\
assemble.bat
Though if you do this, it may be worth removing the PAUSE from the .bat file.
unregistered wrote:
tepples wrote:
My current project has a 768-byte (32 column by 24 row) playfield. There are subroutines to seek to a particular row, which puts the base address of that row in a 2-byte variable in zero page, and then accessing a particular tile on that row becomes LDA (fieldlo),Y with the column number in Y.
That's really cool!! Thank you for sharing!
That's what mine will be; it'll have 32 columns! But maybe
16 15 rows. So far... I think.
I also think I'll use a subroutine to seek to a particular row... that sounds
wonderful! like fun; Thank you tepples!
edit. Thank you so much tepples!! So far I'm two days into your wise words here. I'm not done but it seems like it is going to take extra care to increment the higher part of my 2-byte variable. I don't want it to ever increment twice... I guess if it does it will overwrite some of the mirrors part of RAM... would that then overwrite my regular ram variables?
Storing to a mirrored part of memory... overwrites the regular non-mirrored RAM right? Well I guess it should... that's scarry... maybe parts of my stack will become collision data.
edit: Um I quickly became sad... but I'm still incredibly happy about all this excellent help you've given me tepples.
Kasumi wrote:
Alternatively if you like the command line and don't prefer to double click each time, you can also do this:
Code:
cd nesys\dasource\
assemble.bat
Though if you do this, it may be worth removing the PAUSE from the .bat file.
Thank you Kasumi!
Yes, that was the second time I ran into that problem... couldn't find what I did to fix it... and now I won't have to find it.
tokumaru, on page 50, wrote:
unregistered wrote:
Quote:
(y / 16) * 16 + (x /16).
Isn't that equal to
y + (x/16) ? That seems crazy to me.
Not really, because the lower 4 bits are cleared in the process of shifting right and back left. So as a shortcut you can just do
AND #%11110000 instead of
LSR LSR LSR LSR ASL ASL ASL ASL. It works the same. Stop thinking about math as you learned in school for a minute and try to see it as the 6502 sees it (which is much simpler than what you learned in school, IMO).
Ok... now my row metatiles per row is 32... but
AND #11100000b isn't right because
LSR LSR LSR LSR ASL ASL ASL ASL ASL it wasn't shifted right
32 five times. I think
AND #11110000b ASL would be fine... right?
edit.
tokumaru, a little earlier, also, on page 50, wrote:
...The formula for reading data from a 2D array which is stored in memory linearly is always Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
Another thing that will affect how you apply the formula is how your levels are stored in RAM/ROM. If you store it screen by screen, then ElementsPerRow will always be the same, because the number of metatiles per screen doesn't change. If you don't divide your level in screens, then ElementsPerRow will be the length of the entire level, and it will vary from level to level. IMO, things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits, and the multiplications/divisions can be easily done with shifts.
Aaaahhhhh, yes now I understand what I'm doing wrong... there are 32 ElementsPerRow so I need to use 16 bits. In your opinion things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits... and that makes sense!
I kindof agree right now, but now that I seem to know what to do to make 32 elements per row work I'm kind of excited too!!
Thank you tokumaru!!
tepples wrote:
If the low byte wraps around from FF to 00, increment the high byte.
I just am happily joyous about discovering that if I increment the high byte... then it starts at $700 and so the code that does $600 can just be repeated again!!! I never have to attempt reading row 15... just increment the high byte and it would be row 7 right?
edit: no... row 7 doesn't exist in $700 land... it would be 6!
These 32 byte-wide-rows are befuddling me. I know my RAM buffer is filled with the correct collision screens; that's what fceu's hex editor shows me. I know that each of these rows are 32 bytes wide, but is confusing to me the way that it should be useed. Each row starts with 600... 620... 640 and so on but they are listed in sets of 16. The #$0x0 value has always determined what row I have been using... and now it doesn't matter cause the rows are 32 bytes wide.
edit: im missing somethin g.
edit2: aha! the row 0... 600. row 1... 620. row 2... 640. row 3... 6(multiply row num by 2)60!!!
edit3: hmm... no, I guess the best way would be to multiply the row number by #32. That's five
asls... that's ok, right?
...that seems good to me right now.
tepples, the high byte... is there a good way to increment it whenever the low byte wraps from #$ff to #$00? All of my efforts so far have been to insert a
n uncomfortable branch past an uncomfortable
inc fieldlo+1.
last_edit.
Goodness, there must be 240 scanlines right? Something like that...?
Does each scanline take about 90 cycles? When does vblank happen... what scanline? I was on scanline 55... wondering how long that would have taken to finally make it to vblank... glad I didn't wait... still would be clicking
Step Into over and over. I did that one time... took forever
.! addition.
unregistered wrote:
Goodness, there must be 240 scanlines right?
Visible, yes. After those 240 comes 1 dummy post-render scanline, 20 VBlank scanlines and 1 pre-render scanline, for a total of 262. These numbers are for NTSC. PAL has a much longer VBlank, 70 scanlines.
Quote:
Does each scanline take about 90 cycles?
Each NTSC scanline is 341 / 3 = 113.6666... cycles long, while PAL scanlines are 341 / 3.2 = 106.5625 cycles long.
Quote:
When does vblank happen... what scanline?
VBlank happens after the 240 rendered scanlines and the dummy scanline.
Quote:
wondering how long that would have taken to finally make it to vblank... glad I didn't wait... still would be clicking Step Into over and over.
Would setting a breakpoint on the address of the NMI handler solve your problem? You could click "run" and the debugger would pause when the NMI is called, which is when VBlank starts.
tokumaru wrote:
unregistered wrote:
Goodness, there must be 240 scanlines right?
Visible, yes. After those 240 comes 1 dummy post-render scanline, 20 VBlank scanlines and 1 pre-render scanline, for a total of 262. These numbers are for NTSC. PAL has a much longer VBlank, 70 scanlines.
So then... PAL has 312 scanlines right?
tokumaru wrote:
Quote:
Does each scanline take about 90 cycles?
Each NTSC scanline is 341 / 3 = 113.6666... cycles long, while PAL scanlines are 341 / 3.2 = 106.5625 cycles long.
Quote:
When does vblank happen... what scanline?
VBlank happens after the 240 rendered scanlines and the dummy scanline.
Thanks so incredibly much tokumaru!! tokumaru wrote:
Would setting a breakpoint on the address of the NMI handler solve your problem? You could click "run" and the debugger would pause when the NMI is called, which is when VBlank starts.
No my problem was already solved because I changed the active breakpoint... and that once earlier started on the first breakpoint during vblank... that happened again. I guess that setting a new breakpoint and making it active (double click on it/make the E appear) resets the debugger to the first breakpoint during vblank. Is that correct? Is there never a chance to pick a new breakpoint and have the debugger just go to the closest breakpoint nearest your position in the
Step Into button? Does that make sense?
My brain isn't working very well right now... that's what 9 hours of searching with the debugger does to me. Goodnight.
Ahaha... I'm trying to get my girl to fall down a hole of water... it's on the second screen... but my xvalue is only 8bit... hahaha... I'm dumb.
So it will work better when I go to 16bits... cause that's what everyone was saying... on page 15.
edit: Ok so it is 16 bits... all of that is already set up... it's just that she never travels past 256... that's what I ment.
Quote:
edit: Ok so it is 16 bits... all of that is already set up... it's just that she never travels past 256... that's what I ment.
So post the code that's supposed to move her?
For 16 bits, it should just look like this if she's moving right:
Code:
lda herlowposition
clc
adc howmanypixelssheshouldmove
sta herlowposition
lda herhighposition
adc #0;This would be the high byte of pixels to move
;But I'm assuming it will never be 256 or greater
sta herhighposition
If that's already what it looks like, the guess is that you haven't updated the code that draws her to the screen to use both her high and low bytes.
I don't know which thing is wrong, be more specific!
Ah, but where do I put the code that moves her? Currently, it's at the spot where CameraX is incremented... it's not too good right there... but maybe it will be better after some more thought is applied. Sorry, I'm needed to watch Naruto Shippuden... thank you for replying Kasumi!
It's something I need to think about some more I think.
You put the code to move her with everything else that updates her. Where you check if she has jumped/collided with a wall etc.
Somewhere not in the NMI, and separated from actual scrolling code, yes.
Edit:
My game loop looks like:
1. Wait for vblank
2. Read joypads
3. Update all objects.
*****1. Update main character's position, and check collisions with enemies. Stop him if he collided with a wall etc.
*****2. Update every other object in order. Check their collisions with walls etc.
4. Scroll background. I do this after I update the objects, because scrolling is relative to the main character's position and I want it final for that frame.
5. Render all objects. I do this after I scroll because where they're drawn is relative to where we ended up scrolling (and for one other reason specific to my game). But you can render each object as part of 3 if you like.
6. Render the HUD.
Code:
MainLoop:
;DO THE GAME LOGIC HERE (i.e. movement, collisions, etc.)
;jsr camera_aim ;<this works... not very well... the screen jumps too often...we think. :)
jsr react_to_input
jsr collisionU
ldx aFrame
jsr draw_sprite
; hope this will be some good cement. :) tested... works good so far :D
lda levellength_high
cmp currScreen
bcc +end
ldx currScreen ;load screen number with x
ldy valid_left;isible_left;phase;#$00
jsr draw_RAMbuffers ;16 bits wide (1 column)
lda iBeginAtOne
bne +end
lda valid_left;isible_left
beq +end
and #00001111b
bne +mid
lda flg_nextScreen
bne +end
inc currScreen ;increment currScreen to load new nametable !!!!!!!!!
lda #$01
sta flg_nextScreen ;as long as flg_nextScreen is set #$01 it prevents the currScreen from incrementing
jmp +end
+mid: lda #$00
sta flg_nextScreen
+end:
;indicate that the frame calculations are done
dec FrameReady
WaitForUpdates:
;wait for the flag to change
bit FrameReady
bmi WaitForUpdates
sta $ff
jmp MainLoop
The last thing my game loop does is wait for vblank.
Read joypads is handled first in react_to_input.
Then movement and collisions happens... guess that I could update her oX value in there. I have a small question... how should I make oX increment beyond 255? Whenever I add one to oX it moves my character... she always moves past my barrier that trys to prevent her from appearing on the opposite side of the screen.
Quote:
I have a small question... how should I make oX increment beyond 255? Whenever I add one to oX it moves my character...
I dunno. Post the code that actually deals with oX? But chances are you just need to rename oX to oXlow, create a variable called oXhigh, and then do what I said two posts ago. Didn't I also ask you to post the code that moves her in that same post? Here I am again! If there isn't currently code that moves her, say that. I'm led to believe there is because you know that adding to oX moves her. But that could also mean it's just something you're aware of and no code actually does it, like I'm aware that in my game incrementing an object's grab requests makes an object impossible to grab. Edit2: Actually, no there's definitely code, because you said this:"Ah, but where do I put the code that moves her? Currently, it's at the spot where CameraX is incremented." So it does exist! Maybe oX is actually defined as two bytes, in which case you don't need to create/rename anything, but you still need to add 0 to the high byte as oX+1 similar to what I said two posts ago. The fact is I don't know either way, so let me know!
You have to help people help you. I haven't done a lot of posting in this topic recently because you're asking questions that are extremely specific to your game, but then not posting the info we need about it.
Like the 32 byte rows. I have no idea what any of that means. You have to describe your level format or something. Post code.
Before you post, read your post. Think about the questions someone would ask that has no idea about the specifics of your game, but has 6502 and NES knowledge. Answer the questions you can think of before anyone has to ask, because those are the people that are going to end up helping you. Even if you feel you've posted some info before, post it again. Like... I'm pretty sure we've talked about oX before, but this is an 80 page topic and oX is too short a string to search for on the forum.
Edit:
Here's a post I made about 16 bit math. Here's anotherDo you fully understand 16 bit math? If not, let's cover that. I'm not trying to be mean, but I honestly don't understand the issue here.
Edit3: Or the problem is the character's movement is already 16bit, but she's not drawn correctly when her position has a higher value than 256 because draw_sprite is wrong. It could be all kinds of things and as I said before, I don't know which thing is wrong, be more specific!
Kasumi wrote:
You have to help people help you. I haven't done a lot of posting in this topic recently because you're asking questions that are extremely specific to your game, but then not posting the info we need about it.
Like the 32 byte rows. I have no idea what any of that means. You have to describe your level format or something. Post code.
Attachment:
32bitrows.png [ 51.55 KiB | Viewed 2280 times ]
See the 32 byte rows... the 01s at the bottom are solid blocks... the 00s are air... and the 02s are water. It's confusing for me because the rows are 32 bytes wide... and when the screen scrolls over to the second screen... the hole of water is never reached because in my code
Code:
0C49D unpack:
0C49D
0C49D
0C49D
0C49D ;**********************************************************************************************
0C49D ;**********************************************************************************************
0C49D
0C49D ;128 64 32 16 8 4 2 1
0C49D ;$80 $40 $20 $10 $08 $04 $02 $01
0C49D A9 B5 lda #<MetatileRhombus
0C49F 85 0C sta rhombusCollision_low
0C4A1 A9 CC lda #>MetatileRhombus
0C4A3 85 0D sta rhombusCollision_high
0C4A5
0C4A5 A9 94 lda #<MetatileCollision
0C4A7 85 1A sta UCollision_low
0C4A9 A9 CD lda #>MetatileCollision
0C4AB 85 1B sta UCollision_high
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD
0C4AD ;GRAVITY
0C4AD
0C4AD 85 FF sta $ff
0C4AF
0C4AF ; lda (ten_low), y
0C4AF ; tay
0C4AF ; lda (UCollision_low), y
0C4AF ; tya
0C4AF A5 06 lda oY
0C4B1 4A lsr a
0C4B2 4A lsr a
0C4B3 4A lsr a
0C4B4 4A lsr a
0C4B5 AA tax ;ldx rowValue ;y must be 0-15
0C4B6 20 81 C5 jsr seekRow_fieldlo
0C4B9 20 9D C5 jsr adjustRow
0C4BC
0C4BC A5 03 lda oX ;***this never makes it past 16. :(
0C4BE 4A lsr a
0C4BF 4A lsr a
0C4C0 4A lsr a
0C4C1 4A lsr a
0C4C2 A8 tay ;ldy colValue ;x must be 0-31
0C4C3 B1 4C lda (fieldlo), y
0C4C5
0C4C5 ;if the metatile is not all the same tile
0C4C5 0A asl a ;<pushes bit #7 into carry.
0C4C6 B0 1F bcs +end;bcc +end
0C4C8
0C4C8 ; and if the ground below is not something solid to stand on...
0C4C8 A6 2C ldx SomethingSolidtoStandon
0C4CA CA dex
0C4CB F0 1A beq +end
0C4CD ;then fall to the metatile below...
0C4CD
0C4CD A5 06 lda oY
0C4CF ;clc ;We could clear the carry ;carry is clear
0C4CF ;adc #8 ;AND add 8.
0C4CF 69 08 adc #8;Or... we could add 7. Because we know the carry is set, and 7+1 is eight.
0C4D1 85 06 sta oY
0C4D3
0C4D3 A9 08 lda #8
0C4D5 85 28 sta PointX
0C4D7 A9 20 lda #32
0C4D9 85 2A sta PointY
0C4DB 20 F6 C4 jsr checkPointXY ;correctly calls linear_position
0C4DE F0 07 beq +end
0C4E0 A5 06 lda oY
0C4E2 20 1B C5 jsr fallFix
0C4E5 85 06 sta oY
0C4E7
0C4E7
0C4E7 60 +end rts ;end of unpack
y never gets past 16. edit: see my *** comment
Your post was apparently severely edited before I made my reply. But I'm still curious about this one bit:
Quote:
sta #$ff
That has since been fixed in your edit, but I'm kind of amazed that didn't throw an error when you assembled the rom. It did end up assembling to what you meant (sta $FF), but... it should not have assembled at all. You're using ASM6, right? Because whatever you're using should uh... fix that.
Quote:
y never gets past 16. edit: see my *** comment
Code:
0C4BC A5 03 lda oX ;***this never makes it past 16. :(
0C4BE 4A lsr a
0C4BF 4A lsr a
0C4C0 4A lsr a
0C4C1 4A lsr a
0C4C2 A8 tay ;ldy colValue ;x must be 0-31
This confuses me slightly. The comment is next to oX, which makes me think the "this" in the comment refers to oX". The actual post says Y never gets past 16. I promise I'm not trying to obtuse, but for technical stuff I really, really need specifics.
Is your problem:
A: That oX itself is never a value greater than 16?
or
B: That Y after the TAY is never a value greater than 16?
If A, there's not enough info to help.
If B, it's because you're dividing oX by 16, so the largest remaining value cannot be greater than 15.
Because the largest value a single byte can hold is 255.
255
lsr
127
lsr
63
lsr
31
lsr
15
If you want to do a 16bit divide by 16, you want this:
Code:
;I'm assuming regular oX is the low byte
;And oX+1 is the high byte. Adapt the code accordingly
lda oX+1
lsr a;Divide the high byte by 2
;This shifts the lowest bit from the high byte
;Into the carry
sta temp
lda oX
ror a;Divide the low byte by 2,
;shifting in the lowest bit of the
;high byte from the carry
;into the highest bit of the low byte.
sta temp2
;Then we just do it as many more times as we want to divide by 2.
lda temp
lsr a
sta temp
lda temp2
ror a
sta temp2
lda temp
lsr a
sta temp
lda temp2
ror a
sta temp2
lda temp
lsr a
;sta temp;At this point we don't need to store
;to temp, because we won't use the value again
lda temp2
ror a
;sta temp2;At this point we don't need to store
;to temp, because we won't use the value again
tay
Is that what you need?
Edit: Oh... context makes me think you actually want this:
Code:
and #%00011111;Make sure A is 0-31 before transferring to y
tay
Edit2: I must be tired. You don't need to load store, you can work directly on the temp variables (after the oX values are safely stored there) like below:
Code:
lda oX+1
lsr a
sta temp
lda oX
ror a
sta temp2
lsr temp
ror temp2
lsr temp
ror temp2
lsr temp
lda temp2
ror a
and #%00011111
tay
Kasumi wrote:
Your post was apparently severely edited before I made my reply. But I'm still curious about this one bit:
Quote:
sta #$ff
That has since been fixed in your edit, but I'm kind of amazed that didn't throw an error when you assembled the rom. It did end up assembling to what you meant (sta $FF), but... it should not have assembled at all. You're using ASM6, right? Because whatever you're using should uh... fix that.
Yes, I'm using ASM6. Sorry about the severe edit. Decided to give you a different method of code.
That code is still in my other method... it is
Code:
0C529 85 FF sta #$ff
still like that. I will change it. Kasumi wrote:
Quote:
y never gets past 16. edit: see my *** comment
Code:
0C4BC A5 03 lda oX ;***this never makes it past 16. :(
0C4BE 4A lsr a
0C4BF 4A lsr a
0C4C0 4A lsr a
0C4C1 4A lsr a
0C4C2 A8 tay ;ldy colValue ;x must be 0-31
This confuses me slightly. The comment is next to oX, which makes me think the "this" in the comment refers to oX". The actual post says Y never gets past 16. I promise I'm not trying to obtuse, but for technical stuff I really, really need specifics.
Is your problem:
A: That oX itself is never a value greater than 16?
or
B: That Y after the TAY is never a value greater than 16?
If A, there's not enough info to help.
If B, it's because you're dividing oX by 16, so the largest remaining value cannot be greater than 15.
Because the largest value a single byte can hold is 255.
255
lsr
127
lsr
63
lsr
31
lsr
15
It's B. Oh, goodness gracious... Thank you for telling me this Kasumi.
I seem to think that I kind of figured this out myself already... kind of.
Kasumi wrote:
If you want to do a 16bit divide by 16, you want this:
Code:
;I'm assuming regular oX is the low byte
;And oX+1 is the high byte. Adapt the code accordingly
lda oX+1
lsr a;Divide the high byte by 2
;This shifts the lowest bit from the high byte
;Into the carry
sta temp
lda oX
ror a;Divide the low byte by 2,
;shifting in the lowest bit of the
;high byte from the carry
;into the highest bit of the low byte.
sta temp2
;Then we just do it as many more times as we want to divide by 2.
lda temp
lsr a
sta temp
lda temp2
ror a
sta temp2
lda temp
lsr a
sta temp
lda temp2
ror a
sta temp2
lda temp
lsr a
;sta temp;At this point we don't need to store
;to temp, because we won't use the value again
lda temp2
ror a
;sta temp2;At this point we don't need to store
;to temp, because we won't use the value again
tay
Is that what you need?
Edit: Oh... context makes me think you actually want this:
Code:
and #%00011111;Make sure A is 0-31 before transferring to y
tay
Edit2: I must be tired. You don't need to load store, you can work directly on the temp variables (after the oX values are safely stored there) like below:
Code:
lda oX+1
lsr a
sta temp
lda oX
ror a
sta temp2
lsr temp
ror temp2
lsr temp
ror temp2
lsr temp
lda temp2
ror a
and #%00011111
tay
Excellent!! This will work good. I wrote this out on paper and worked through it with a pencil. Thank you Kasumi!!
It's ok you were tired because your first load and store attempt helped me to remember how the carry works in the ror and lsr! I really appreciate this!
edit.
Kasumi, on page 51, wrote:
If she's one pixel in the floor and you subtract eight, she's flying.
Thank you Kasumi!!
I know now that it is like I thought, the pixels start in the screen's upper left corner. I was wondering about that... According to my math, I need to figure out my row value and find (Row - 8) * 32.
Code:
;/*********************
; send row number in X :(0-15)
;distroys a preserves x
;*********************/
seekRow_fieldlo:
;set 2-byte variable in zero page
lda #$01
sta once
lda #$06
sta fieldlo+1
txa
beq +theend ;this is good... don't need any shifting if the value is 0. :)
and #00000111b
; (row - 8)*32
asl a
asl a
asl a ;>*32
asl a
asl a
+theend sta fieldlo+0
rts ;end of seekRow_fieldlo
;/*************************
; Distroys a. Need to have
; correct row x value.
; only runs once per seekRow
;*************************/
adjustRow:
lda once
beq +end
;need to have a comparision of the row number with a certain value that represents the last row of 6xx values (row 8)
cpx #$08 ;X-M
bcc +end ;if x <= 7... end
inc fieldlo+1
dec once
+end rts ;end of adjustRow
I did figure out that I don't have to figure out Row - 8 because, this is really cool
, the bits are the same for 0-7 and 8-15... I think the only thing different is bit 3... so I just thought of adding
and #00000111b and it works!!
I recieved the exact same answer though without that
and #00000111b so I am just wondering if what I'm doing makes sense. Does this make sense? I guess I just need to try harder to figure this out... thank you for reading.
Writing this out surely does help me.
Code:
;this is how those two functions up there work...
lda currRow
tax
jsr seekRow_fieldlo
jsr adjustRow
edit: [s]lda (fieldlo), y[/s]
edit2: ;blaaaaaaaaaaaah ok, let me try this again... pretend those strike tags work... pretend that my first edit
;never happened. Thank you. :)
;So now fieldlo can be accessed correctly... you just have to specify what metatile to access. Since the rows
;are 32 bytes wide they can take a value anywhere from 0 to 31. You have to specify this number in y.
lda current_column
tay
lda (fieldlo), y
;Also there is an image above this post on page 80... I think it will help you to understand what my collision
;values look like on pages-of-memory 6 and 7.
This is a random thought:
Code:
beq +theend ;this is good... don't need any shifting if the value is 0. :)
and #00000111b
; (row - 8)*32
asl a
asl a
asl a ;>*32
asl a
asl a
+theend
The optimization here hurts more than it helps. In the case of zero in X, you do end up saving 11 cycles over every other case. An and instruction and 5 asl instructions that take 2 cycles, -1 because of the branch taken that every other case does not have.
Now, consider that the code gets the same result whether the branch is there or not. Assuming the other cases (1-7) are equally likely (they probably are if this is collision stuff), that branch (not taken) being there adds 2 cycles for every non zero case. There are 7 of them. Say we're moving to a different row every frame, and we advance for 8 frames. You start on zero with -11 cycles saved. 1 adds 2 cycles, 2 adds 2, etc. On average, you've saved 11, but gained 14. Also, you've lost 2 bytes to do it!
Edit2: Fixed the math above. Thought it was and #%00001111. You still come out behind, but not as much.
In this case, that doesn't REALLY matter, but sometimes optimizing for the best case makes the worst cases even worse. But only if the cases are equally likely! It's worth doing things like this if your worst case is rare, and your best case is common. Just a thing to think about.
Code:
lda currRow
tax
to
Code:
ldx currRow
Anyway, that sort of nitpick might get annoying, but I see it if I post about it or not, so there it is.
More stuff:
It should probably be adjustRow's responsibility to set once, because it is the function that actually uses it, not seekRow_fieldlo. Or, if these two functions are always meant to be run one after the other, why not just remove the RTS that ends seekRow_fieldlo so they're one subroutine? If not, isn't it still easier to just not call adjustRow twice after seekRow_fieldlo rather than hope that variable catches the case?
I guess your RAM map is like this:
$0600-$063F = Data for screen 1
$0700-$073F = Data for screen 2
Edit: Reading your edit after I already made this post, Hey, I might be right! But this is the kind of info that should be in the initial post from the beginning! Or maybe I'm wrong and a screen really does take up the whole page, but see how I might get confused? I assumed 32x32 data, when maybe we're dealing with 8x8 data? Assume I have ZERO info about your code, and that anything you don't tell me I'll guess (and be wrong). To keep me from guessing, just be specific.
Part of the confusion is that you state HOW you're going to do something (find (Row - 8) * 32), but not WHAT you're really trying to accomplish. And if the how is a bad method, I'll think you're trying to do something that you're not. What's the absolute end goal of all of this? To use a position to check a collision tile in an array?
Why not do this:
$0600-$063F = Data for screen 1
$0640-$067F = Data for screen 2
Because then you can do this:
Code:
fieldlo = $0600
lda fieldlo, y;Not indirect, so it's faster.
;You also avoid having to set the pointer
;up.
;No lda #$06
;sta fieldlo+1
And you don't need any of this
Code:
cpx #$08 ;X-M
bcc +end ;if x <= 7... end
inc fieldlo+1
dec once
Because a row past eight would just load what's next in the buffer (now in $0640-$067F) anyhow. (Well, I guess it depends on how y is set...)
Quote:
I recieved the exact same answer though without that and #00000111b
Indeed, you don't need "and #00000111b" at all. If I have a byte like this: "76543210" and my goal is to get this from it "21000000", the asl instructions alone will do that. bits 7, 6, 5, 4, and 3 will be shifted out so it doesn't matter what they are. 0s will be shifted in.
After all that, I'm not sure your code is doing what you want it to. You have a position. You want to get load the first byte of the row for that position in the array. Correct?
It seems your data is 32x32. If your position looks like this in binary: AAXXXYYY, consider that the Y bits don't matter because they don't affect which 32x32 tile you're on. So you and them out.
Code:
lda positionylo
and #%11111000
sta temp
Currently if looks like you're using the lowest 3 bits, which I don't think is your goal. Something else to note: The bits marked AA in the example can be used to determine which screen you're on. If you only want changes in X position to affect how to load from new screen, you should and them as well.
Edit: And looking at your edit, it looks like I got all that totally wrong! I had written a bunch of stuff about stating goals, and deleted it. Maybe it was needed anyway.
row = positiony/tilesize
column = positionx/tilesize
y=row*arraywidth+column
lda array,y
is as complicated as it need to be.
As well, related to collision: Consider that I know the tile a position is in is collision enabled. I need to eject upwards. My tile size is a power of two.
Code:
lda position
and #%00001111;For 16
sta temp
lda position
clc
sbc temp
sta position
Done. How does it work? A tile for this example is 16 pixels. So the lowest 4 bits of the position will tell us how far we are into the tile. Say we're at 0000, the very top of the tile. We want to eject one pixel. So we clear the carry to subtract 1 extra. And subtract 0.
Say we're at 1111 ($F). That's the bottom of the tile. To get outside, we want to subtract sixteen. Which... is $F+1, so the same thing works. It works in all the cases. (with square tiles.)
Kasumi wrote:
This is a random thought:
Code:
beq +theend ;this is good... don't need any shifting if the value is 0. :)
and #00000111b
; (row - 8)*32
asl a
asl a
asl a ;>*32
asl a
asl a
+theend
The optimization here hurts more than it helps. In the case of zero in X, you do end up saving 11 cycles over every other case. An and instruction and 5 asl instructions that take 2 cycles, -1 because of the branch taken that every other case does not have.
Now, consider that the code gets the same result whether the branch is there or not. Assuming the other cases (1-7) are equally likely (they probably are if this is collision stuff), that branch (not taken) being there adds 2 cycles for every non zero case. There are 7 of them. Say we're moving to a different row every frame, and we advance for 8 frames. You start on zero with -11 cycles saved. 1 adds 2 cycles, 2 adds 2, etc. On average, you've saved 11, but gained 14. Also, you've lost 2 bytes to do it!
Edit2: Fixed the math above. Thought it was and #%00001111. You still come out behind, but not as much.
You confused the math above... I think because we were talking about my beq branch... and nothing about "and #00000111b." Not yet, at least... I think.
Kasumi wrote:
In this case, that doesn't REALLY matter, but sometimes optimizing for the best case makes the worst cases even worse. But only if the cases are equally likely! It's worth doing things like this if your worst case is rare, and your best case is common. Just a thing to think about.
Thank you Kasumi, that makes sense to me!
Kasumi wrote:
Code:
lda currRow
tax
to
Code:
ldx currRow
Anyway, that sort of nitpick might get annoying, but I see it if I post about it or not, so there it is.
...it's easier for me to be less confused about... "wow, why did I load the x register with currRow... it's a y value afterall." The tax makes it better because then I can easily understand the fact that it's a guest in the x register. Hope that makes sense... I don't know how I could explain better.
Kasumi wrote:
More stuff:
It should probably be adjustRow's responsibility to set once, because it is the function that actually uses it, not seekRow_fieldlo. Or, if these two functions are always meant to be run one after the other, why not just remove the RTS that ends seekRow_fieldlo so they're one subroutine?
Cause it works better because the logic is already worked out... I don't have to rethink it.
Kasumi wrote:
If not, isn't it still easier to just not call adjustRow twice after seekRow_fieldlo rather than hope that variable catches the case?
That was a way to stop hackers from messing up the game... sorry.
Kasumi wrote:
I guess your RAM map is like this:
$0600-$063F = Data for screen 1
$0700-$073F = Data for screen 2
Edit: Reading your edit after I already made this post, Hey, I might be right! But this is the kind of info that should be in the initial post from the beginning! Or maybe I'm wrong and a screen really does take up the whole page, but see how I might get confused? I assumed 32x32 data, when maybe we're dealing with 8x8 data? Assume I have ZERO info about your code, and that anything you don't tell me I'll guess (and be wrong). To keep me from guessing, just be specific.
Ok.
16x16 data.
Must go watch Naruto Shippuden. sry.
edit: ok done.
Kasumi wrote:
Part of the confusion is that you state HOW you're going to do something (find (Row - 8) * 32), but not WHAT you're really trying to accomplish. And if the how is a bad method, I'll think you're trying to do something that you're not. What's the absolute end goal of all of this? To use a position to check a collision tile in an array?
Why not do this:
$0600-$063F = Data for screen 1
$0640-$067F = Data for screen 2
Because then you can do this:
Code:
fieldlo = $0600
lda fieldlo, y;Not indirect, so it's faster.
;You also avoid having to set the pointer
;up.
;No lda #$06
;sta fieldlo+1
And you don't need any of this
Code:
cpx #$08 ;X-M
bcc +end ;if x <= 7... end
inc fieldlo+1
dec once
Because a row past eight would just load what's next in the buffer (now in $0640-$067F) anyhow. (Well, I guess it depends on how y is set...)
Woah, I thought every thing using a pointer was indirect... maybe I have to think more on this.
... I kindof understand...
Kasumi wrote:
Quote:
I recieved the exact same answer though without that and #00000111b
Indeed, you don't need "and #00000111b" at all. If I have a byte like this: "76543210" and my goal is to get this from it "21000000", the asl instructions alone will do that. bits 7, 6, 5, 4, and 3 will be shifted out so it doesn't matter what they are. 0s will be shifted in.
Your mind is much better than mine... but mine is being trained at lumosity.com and it will get better.
Kasumi wrote:
After all that, I'm not sure your code is doing what you want it to. You have a position. You want to get load the first byte of the row for that position in the array. Correct?
...well I want to load whatever byte my lady is standing above.
Kasumi wrote:
It seems your data is 32x32. If your position looks like this in binary: AAXXXYYY, consider that the Y bits don't matter because they don't affect which 32x32 tile you're on. So you and them out.
Code:
lda positionylo
and #%11111000
sta temp
Currently if looks like you're using the lowest 3 bits, which I don't think is your goal. Something else to note: The bits marked AA in the example can be used to determine which screen you're on. If you only want changes in X position to affect how to load from new screen, you should and them as well.
Edit: And looking at your edit, it looks like I got all that totally wrong! I had written a bunch of stuff about stating goals, and deleted it. Maybe it was needed anyway.
I'm learning reading these posts over and over... I promise.
Kasumi wrote:
row = positiony/tilesize
column = positionx/tilesize
y=row*arraywidth+column
lda array,y
is as complicated as it need to be.
Yes! It is.
Kasumi wrote:
As well, related to collision: Consider that I know the tile a position is in is collision enabled. I need to eject upwards. My tile size is a power of two.
Code:
lda position
and #%00001111;For 16
sta temp
lda position
clc
sbc temp
sta position
Done. How does it work? A tile for this example is 16 pixels. So the lowest 4 bits of the position will tell us how far we are into the tile. Say we're at 0000, the very top of the tile. We want to eject one pixel. So we clear the carry to subtract 1 extra. And subtract 0.
Say we're at 1111 ($F). That's the bottom of the tile. To get outside, we want to subtract sixteen. Which... is $F+1, so the same thing works. It works in all the cases. (with square tiles.)
I'm going to answer this part later... thank you for giving me some more time to think about it.
Quote:
You confused the math above... I think because we were talking about my beq branch... and nothing about "and #00000111b." Not yet, at least... I think.
Another way to say it is the average is better. I suppose the math is wrong, because there really are still 15 ways to enter the function even if it gets truncated to 8 there. But eh, that just makes the average worse.
3 cycles for case 0. (only the branch, the other stuff isn't run and the rest is the same.
14 cycles for the other 15 cases. (2 for the branch not taken, 2 for the and, 2*5 for the ASL instructions.)
If the branch was not there, all cases would take 12 cycles. (2 for the and, 2*5 for the ASL instructions.)
So the average is (15*14+3)/16. 13.3125
Versus (16*12)/16 = 12. It's about a cycle behind on average, heh. But if you do it 16 times (in all the different ways), it's
(15*14+3) = 213
vs
(16*12) = 192
Either way It's not a huge deal, but it's just a general thing to think about. "Am I making my worst cases worse without improving my average?"
Quote:
That was a way to stop hackers from messing up the game... sorry.
Stop them how? Here's the deal about this: NES is a well documented system with sophisticated debuggers available. You can do all sorts of quirky, obfuscated things in software, but you can't (easily) change how you interact with the hardware. One can find what page your sprites are in by a write breakpoint to the sprite DMA address. One can find which ones belong to your main character by matching the tile IDs there and in CHR. Then one can find where the actual position of the character is in RAM, by finding out how those sprites' positions are updated.
Not to scare ya, but I'm saying I wouldn't sacrifice code readability, speed or size as a hacker deterrent. At least until after the game is done, anyway. Then you can do back and try to make it as impossible to follow as you want. I do get the concern, though. I do.
Quote:
Woah, I thought every thing using a pointer was indirect... maybe I have to think more on this.
Everything using a pointer is indirect. But everything ,y or ,x isn't.
Code:
lda #$06
sta fieldlo+1
lda #$00
sta fieldlo
ldy tile
lda (fieldlo),y
will get the same result in A as
Code:
fieldlo = $0600;This is a define, so the value can't be changed at runtime the above
ldy tile
lda fieldlo,y
You actually do want indirect, because a screen of 16x16 tiles does indeed take a full page. (That's actually why I only store 32x32 tiles in RAM. I needed 4 screens stored because I 8 way scroll, and I didn't have 4 pages. As a bonus, now all 4 screens fit in just one page.)
You could also do this to not use indirect and still have multiple screens:
Code:
fieldlo1 = $0600
fieldlo2 = $0700
ldy tile
lda positionhigh
ror a;(lowest bit is in carry, which would decide which screen you're on)
bcs usescreen2
lda fieldlo1,y
bcc end;Will always branch, because above doesn't change the carry
usescreen2:
lda fieldlo2,y
end:
Using indirect is fine, I got confused because I wasn't sure what type of data we were dealing with. In that case, I tend to assume it's like my game.
In particular, I was confused by the multiply by 32.
Here's a diagram:
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY
It is 32 bytes in order starting at $0600 where each pair of letter (either XX or YY) represents a byte.
My original assumption was that all the XXs were Row 00 from screen 1. And all of the YYs were Row 01 from screen 1.
I gather now that all the XXs are still Row 00 from screen 1, but all the YYs are actually Row 00 from screen 2.
That makes the multiply by 32 make sense. As well as the row 8 compare.
(This is why I ask about your level data always. There are lots of ways to store it, and if it's stored differently than I guess it is, the code I give you won't work, or I'll give you fixes that break it further. Like the fieldlo1/fieldlo2 won't work if the YYs are row 00 on screen 2.)
Anyway, (after many bad assumptions on my part) if the above is true I see no problems with what you have. The row number is a number 0-15. You multiply it by 32, which actually mean there are only 8 possibilities instead of 15! You lose the bit that selects which screen, but you fix that later
in adjust row.
But... with some carry flag magic, you don't have to fix it later.
Code:
seekRow_fieldlo:
;jsr here with a row number (0-15) in A
;It sets a 2-byte variable in zero page
;to point to the first byte in that row
; (row - 8)*32
asl a
asl a
asl a ;>*32
asl a
asl a
sta fieldlo+0
lda #$06
adc #$00
sta fieldlo+1
rts ;end of seekRow_fieldlo
Done. No need for adjust row. How does it work? Think about which bit you were checking with that cpx #$08. It is the set bit here: 00001000. Since the row only has a range of 0-15, no other bit will change the result of that compare.
What happens when we asl bits again? Let's take a look at two numbers. I'll mark where the bit you're checking is with X.
Code:
lda #%0000X000;lda #%0000X111
asl a;(Highest bit goes into carry. 0 for both)
#%000X0000;lda #%000X1110;These would now be in A
asl a;(Highest bit goes into carry. 0 for both)
#%00X00000;lda #%00X11100;These would now be in A
asl a;(Highest bit goes into carry. 0 for both)
#%0X0000000;lda #%0X111000;These would now be in A
asl a;(Highest bit goes into carry.0 for both)
#%X00000000;lda #%X1110000; These would now be in A
asl a;(Highest bit goes into carry. X for both)
#%000000000;lda #%11100000;These would now be in A
Hey, what do you know? That bit you were checking is conveniently in the carry. And what happens when you add zero with carry? If it's clear, you add zero, if it's set... you add 1!
Anyway, both yours and mine accomplish the goal of setting the pointer to the first byte of a row in the collision table being given that row's number. Goal stating. The rest of your code goes on to load the proper byte from the collision table after being given a column.
I assume your next goal is then to get a row and column to give to this function.
row = positiony/tilesize
column = positionx/tilesize
(both need a high bit from said divide, so make sure it's 16 bit)
From there you're on your way.
Kasumi wrote:
Quote:
You confused the math above... I think because we were talking about my beq branch... and nothing about "and #00000111b." Not yet, at least... I think.
Another way to say it is the average is better. I suppose the math is wrong, because there really are still 15 ways to enter the function even if it gets truncated to 8 there. But eh, that just makes the average worse.
3 cycles for case 0. (only the branch, the other stuff isn't run and the rest is the same.
14 cycles for the other 15 cases. (2 for the branch not taken, 2 for the and, 2*5 for the ASL instructions.)
If the branch was not there, all cases would take 12 cycles. (2 for the and, 2*5 for the ASL instructions.)
So the average is (15*14+3)/16. 13.3125
Versus (16*12)/16 = 12. It's about a cycle behind on average, heh. But if you do it 16 times (in all the different ways), it's
(15*14+3) = 213
vs
(16*12) = 192
Either way It's not a huge deal, but it's just a general thing to think about. "Am I making my worst cases worse without improving my average?"
Thank you Kasumi!
Kasumi wrote:
Quote:
That was a way to stop hackers from messing up the game... sorry.
Stop them how? Here's the deal about this: NES is a well documented system with sophisticated debuggers available. You can do all sorts of quirky, obfuscated things in software, but you can't (easily) change how you interact with the hardware. One can find what page your sprites are in by a write breakpoint to the sprite DMA address. One can find which ones belong to your main character by matching the tile IDs there and in CHR. Then one can find where the actual position of the character is in RAM, by finding out how those sprites' positions are updated.
Not to scare ya, but I'm saying I wouldn't sacrifice code readability, speed or size as a hacker deterrent. At least until after the game is done, anyway. Then you can do back and try to make it as impossible to follow as you want. I do get the concern, though. I do.
Thanks for all of this kind advise!
Kasumi wrote:
Quote:
Woah, I thought every thing using a pointer was indirect... maybe I have to think more on this.
Everything using a pointer is indirect. But everything ,y or ,x isn't.
Code:
lda #$06
sta fieldlo+1
lda #$00
sta fieldlo
ldy tile
lda (fieldlo),y
will get the same result in A as
Code:
fieldlo = $0600;This is a define, so the value can't be changed at runtime the above
ldy tile
lda fieldlo,y
You actually do want indirect, because a screen of 16x16 tiles does indeed take a full page. (That's actually why I only store 32x32 tiles in RAM. I needed 4 screens stored because I 8 way scroll, and I didn't have 4 pages. As a bonus, now all 4 screens fit in just one page.)
That's really amazing! All 4 screens in just one page... wow 32x32 tiles are incredible!
Kasumi wrote:
You could also do this to not use indirect and still have multiple screens:
Code:
fieldlo1 = $0600
fieldlo2 = $0700
ldy tile
lda positionhigh
ror a;(lowest bit is in carry, which would decide which screen you're on)
bcs usescreen2
lda fieldlo1,y
bcc end;Will always branch, because above doesn't change the carry
usescreen2:
lda fieldlo2,y
end:
Using indirect is fine, I got confused because I wasn't sure what type of data we were dealing with. In that case, I tend to assume it's like my game.
In particular, I was confused by the multiply by 32.
Here's a diagram:
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY
It is 32 bytes in order starting at $0600 where each pair of letter (either XX or YY) represents a byte.
My original assumption was that all the XXs were Row 00 from screen 1. And all of the YYs were Row 01 from screen 1.
I gather now that all the XXs are still Row 00 from screen 1, but all the YYs are actually Row 00 from screen 2.
That makes the multiply by 32 make sense. As well as the row 8 compare.
YEAY!!
Kasumi wrote:
(This is why I ask about your level data always. There are lots of ways to store it, and if it's stored differently than I guess it is, the code I give you won't work, or I'll give you fixes that break it further. Like the fieldlo1/fieldlo2 won't work if the YYs are row 00 on screen 2.)
Ah yes I understand what you are talking about and hope to state my goals more clearly in the future.
Have to go mow now... will finish this in a bit.
edit:
Kasumi wrote:
Anyway, (after many bad assumptions on my part) if the above is true I see no problems with what you have. The row number is a number 0-15. You multiply it by 32, which actually mean there are only 8 possibilities instead of 15! You lose the bit that selects which screen, but you fix that later
in adjust row.
But... with some carry flag magic, you don't have to fix it later.
Code:
seekRow_fieldlo:
;jsr here with a row number (0-15) in A
;It sets a 2-byte variable in zero page
;to point to the first byte in that row
; (row - 8)*32
asl a
asl a
asl a ;>*32
asl a
asl a
sta fieldlo+0
lda #$06
adc #$00
sta fieldlo+1
rts ;end of seekRow_fieldlo
Done. No need for adjust row. How does it work? Think about which bit you were checking with that cpx #$08. It is the set bit here: 00001000. Since the row only has a range of 0-15, no other bit will change the result of that compare.
What happens when we asl bits again? Let's take a look at two numbers. I'll mark where the bit you're checking is with X.
Code:
lda #%0000X000;lda #%0000X111
asl a;(Highest bit goes into carry. 0 for both)
#%000X0000;lda #%000X1110;These would now be in A
asl a;(Highest bit goes into carry. 0 for both)
#%00X00000;lda #%00X11100;These would now be in A
asl a;(Highest bit goes into carry. 0 for both)
#%0X0000000;lda #%0X111000;These would now be in A
asl a;(Highest bit goes into carry.0 for both)
#%X00000000;lda #%X1110000; These would now be in A
asl a;(Highest bit goes into carry. X for both)
#%000000000;lda #%11100000;These would now be in A
Hey, what do you know? That bit you were checking is conveniently in the carry. And what happens when you add zero with carry? If it's clear, you add zero, if it's set... you add 1!
THANKS SO MUCH Kasumi!! This is really incredible!!!!!!!!!!!!!!!!!!Kasumi wrote:
Anyway, both yours and mine accomplish the goal of setting the pointer to the first byte of a row in the collision table being given that row's number. Goal stating. The rest of your code goes on to load the proper byte from the collision table after being given a column.
I assume your next goal is then to get a row and column to give to this function.
row = positiony/tilesize
column = positionx/tilesize
(both need a high bit from said divide, so make sure it's 16 bit)
From there you're on your way.
Thank you so much Kasumi, I really learned a lot from all of your help.
Yes I think that would be my next goal. I have to go... sorry.
edit2: I'm back.
I want to add that I'm not really sure about my next goal... I think I'll have a better idea after I apply your awesome magical carry idea.
edit3.edit4:
unregistered wrote:
Kasumi wrote:
As well, related to collision: Consider that I know the tile a position is in is collision enabled. I need to eject upwards. My tile size is a power of two.
Code:
lda position
and #%00001111;For 16
sta temp
lda position
clc
sbc temp
sta position
Done. How does it work? A tile for this example is 16 pixels. So the lowest 4 bits of the position will tell us how far we are into the tile. Say we're at 0000, the very top of the tile. We want to eject one pixel. So we clear the carry to subtract 1 extra. And subtract 0.
Say we're at 1111 ($F). That's the bottom of the tile. To get outside, we want to subtract sixteen. Which... is $F+1, so the same thing works. It works in all the cases. (with square tiles.)
I'm going to answer this part later... thank you for giving me some more time to think about it.
Ahhh, yes, that's great! Thank you for this too!!! :D
tokumaru, near the bottom of page 50, wrote:
The formula for reading data from a 2D array which is stored in memory linearly is always Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
... ok so now, with my 32 byte rows... Elements per row would be 32... so the formula would be (Y/16) * 32 + (X/16). edit: Would this code below be correct?
Code:
0C4FE ;********************************************************************
0C4FE ;linear_position finds linear position of metatile declared in index x and index y
0C4FE ; destroys a
0C4FE ; recieves x and y registers
0C4FE ; ends with linear position in a
0C4FE ;********************************************************************
0C4FE linear_position:
0C4FE ; Calculate the index of the metatile: (Y / 16) * 32 + (X / 16) = LINEAR_POSITION p.50 -Not me.-
0C4FE 8A txa ;X is in x-register
0C4FF 4A lsr a ;divide my X coordinate by (2 * 2 * 2 * 2) = 16
0C500 4A lsr a ;---------------------------->*
0C501 4A lsr a ;-------------------------------->*
0C502 4A lsr a ;------------------------------------>*
0C503 85 59 sta tC+1
0C505 98 tya ;Y is in y-register
0C506 29 F0 and #11110000b
0C508 0A asl a ;hopefully works... is added for the 32
0C509 05 59 ora tC+1
0C50B
0C50B 60 rts ;end of linear_position
No, because as stated before you need to do a 16 bit divide:
Quote:
row = positiony/tilesize
column = positionx/tilesize
(both need a high bit from said divide, so make sure it's 16 bit)
X is supposed to give you a value anywhere from zero to 31 because your rows are 32 elements long. If you just LSR the low byte 4 times, you cannot get a value higher than 15.
You already have a high bit from your divide of y by 16 (because it increments to the next page of RAM when that bit is set), you still need one from the divide of X. (And if you're feeling tricky... because you need JUST ONE of the high bits, you can get away with doing the 16bit divide with ror/lsr just once, then use lsr for the rest of the time.)
As well, you actually don't need to do ANYTHING with Y anymore. The pointer that you set up at seekRow_fieldlo already points to the first byte of the row. So the red part of the formula is done:
(Y/16) * 32 + (X/16)
All you need to do is what's necessary for X, then do
Code:
ldy (whatever the value of X/16 is)
lda (fieldlo),y
Edit: Actually, I suppose it's not quite true you don't need to do anything for Y. You still need to feed the proper row to seekRow_fieldlo by dividing the y position by 16. (Previously, you just loaded currRow, which I assume wasn't actually set up.) But it doesn't make sense to set up y here based on how your previous code is set up.
Code:
;16 bit divide y by 16
;Result is in A
jsr seekRow_fieldlo
;16 bit divide x by 16
;Result is in A
tay
lda (fieldlo), y
I don't understand why this is happening
Code:
0C5D3 ;/*********************
0C5D3 ; send row number in X :(0-15) but it really only can accept 0-7 WAHHAHAHAhahahahaha! ...8bit sweetness :)
0C5D3 ;distroys a preserves x
0C5D3 ;*********************/
0C5D3 seekRow_fieldlo:
0C5D3 ;set 2-byte variable in zero page
0C5D3 ;
0C5D3 8A txa
0C5D4 ; beq +theend ;this is good... don't need any shifting if the value is 0. :)
0C5D4 ; and #00000111b
0C5D4 ; (row - 8)*32
0C5D4 0A asl a
0C5D5 0A asl a
0C5D6 0A asl a ;>*32
0C5D7 0A asl a
0C5D8 0A asl a
0C5D9 85 4C sta fieldlo+0
0C5DB
0C5DB A9 06 lda #$06
0C5DD 69 00 adc #$00
0C5DF 85 4D sta fieldlo+1
0C5E1
0C5E1
0C5E1 60 rts ;end of seekRow_fieldlo
Ok so it looks like seekRow_fieldlo starts at $C5D3... the txa... but
Attachment:
seekRow_fieldlo in 6502 Debugger.png [ 64.99 KiB | Viewed 2907 times ]
it looks like it starts at $C5D4... why?
I know that this code above was taken out of my .lst file. It is always correct; it tells me when it's changed... from when I build my .nes file. And I know that I loaded the correct .nes file... it's always named the same thing in the same folder. I'm using Fceux 2.1.5.
Be back after I mow.
edit: um... well, I just built my .nes file again and my .lst file changed... seekRow_fieldlo starts at $C5D4 now... so nevermind...
Kasumi wrote:
No, because as stated before you need to do a 16 bit divide:
Quote:
row = positiony/tilesize
column = positionx/tilesize
(both need a high bit from said divide, so make sure it's 16 bit)
X is supposed to give you a value anywhere from zero to 31 because your rows are 32 elements long. If you just LSR the low byte 4 times, you cannot get a value higher than 15.
You already have a high bit from your divide of y by 16 (because it increments to the next page of RAM when that bit is set), you still need one from the divide of X. (And if you're feeling tricky... because you need JUST ONE of the high bits, you can get away with doing the 16bit divide with ror/lsr just once, then use lsr for the rest of the time.)
As well, you actually don't need to do ANYTHING with Y anymore. The pointer that you set up at seekRow_fieldlo already points to the first byte of the row. So the red part of the formula is done:
(Y/16) * 32 + (X/16)
All you need to do is what's necessary for X, then do
Code:
ldy (whatever the value of X/16 is)
lda (fieldlo),y
Edit: Actually, I suppose it's not quite true you don't need to do anything for Y. You still need to feed the proper row to seekRow_fieldlo by dividing the y position by 16. (Previously, you just loaded currRow, which I assume wasn't actually set up.) But it doesn't make sense to set up y here based on how your previous code is set up.
Code:
;16 bit divide y by 16
;Result is in A
jsr seekRow_fieldlo
;16 bit divide x by 16
;Result is in A
tay
lda (fieldlo), y
Yes, currRow wasn't set up... it just sounded nice... : )
Code:
;16 bit divide y by 16
;Result is in A
tax
jsr seekRow_fieldlo
;16 bit divide x by 16
;Result is in A
tay
lda (fieldlo), y
that is how it is set up now... with the tax. That doesn't seem odd anymore... index x holding y and index y holding x... cause I've stepped through this so many times. I'm almost there!
Thank you Kasumi for this amazing extra help!! But, I want to ask something else... about distanceX.
Now, distanceX is there because changing oX kept moving my girl character around the screen... I didn't understand, and still don't understand, how to make oX grow above 255 and still remain in the right spot on screen. What do ya'll do to increase your X value (my X value is oX)? I don't want distanceX anymore... oX would be just fine for holding values beyond 255... it has an extra byte ready for info... I just don't understand how to make oX increase and keep my lady on the correct part of the screen.
edit.edit2: What have I thought about this question? Hmm... the lady would have to somehow increase her oX value by like 257 so she could move forward an entire screen + 2. I don't understand.
edit3: What is distanceX? It is a seperate 16 bit value... that increments when the player presses right on the control pad. See distanceX is not oX... so it is free of being tied to the screen.
I've asked before if you understand 16 bit math. Do you? There's absolutely no more to getting a value higher than 255 than understanding 16 bit math.
There are three things that affect where a sprite is drawn to the screen if you scroll. The most direct are the X and Y values in whatever page you use with the sprite DMA, because they are obviously exactly where your sprite is drawn. To make this value correct, you must do some math with the other two things that matter. One of them is the sprite's ACTUAL (read: non 8bit) position. What's the other important value?
Kasumi wrote:
I've asked before if you understand 16 bit math. Do you?
Yes, I think so. No, I don't think so because I haven't been making progress... I'm sorry. I need to be refreshed.Kasumi wrote:
There's absolutely no more to getting a value higher than 255 than understanding 16 bit math.
Ok, I will try my best to figure this out. I'm glad to know that that is absolute. Thanks Kasumi!
Kasumi wrote:
There are three things that affect where a sprite is drawn to the screen if you scroll. The most direct are the X and Y values in whatever page you use with the sprite DMA, because they are obviously exactly where your sprite is drawn. To make this value correct, you must do some math with the other two things that matter. One of them is the sprite's ACTUAL (read: non 8bit) position. What's the other important value?
I don't know.
I must sleep now... goodnight. : )
edit: Could the other important value have something to do with the level map?
I just read this by tokumaru:
tokumaru, on page 54, wrote:
I think that the problem is that your current design is still based on the NES screen and the name tables. Most people start out that way, because it's simpler to move objects around that small area, but once you start messing with scrolling, you have to see things differently: the screen and the name tables are not the containers of the objects anymore, they are merely used as a viewport, to show a representation of part of the level, and the level is the actual container of the objects.
You should forget about the screen and the name tables for a moment, and think of the level map as the basis for your game world. Your objects exist in the level, so everything about them is relative to the level map. Sprite coordinates are not restricted to 8 bits anymore, since levels can be much wider than 256 pixels. To test for collisions, you have to do some math with the object coordinates (like we discussed before), and since the sprite coordinates are in the same domain as level map coordinates, scrolling is absolutely irrelevant to collisions.
To make things easier, ideally you'd have access to the whole map, either by decompressing it to WRAM or storing it uncompressed (or compressed in a way that allows for random access) in the ROM. Having access to the complete level makes it easy to move objects around and have them collide with the level regardless of what the screen and the name tables are showing.
Don't think of scrolling as "the level going by", but rather as "a camera panning across the level". The level is stationary, but a virtual "camera" moves around in order to display different parts of the level, and it's this camera that dictates what gets written to the name tables and the OAM. You camera must have its own coordinates, which are used to convert level coordinates into screen coordinates (i.e. level coordinates - camera coordinates = screen coordinates), and you'll have to perform this conversion whenever you render sprites to OAM or metatile to the name tables.
I know it sounds complicated, but nobody said that scrolling was easy, specially if done right. If you do it the wrong way and keep everything oriented to screen coordinates, things will surely get out of hand (like the problem you are having now, where objects aren't aligning with the collision data).
edit2. edit3.
Progress has been made!! Right now there's a hole near the water graphics and my lady just keeps falling...
This is incredible. Posting this as a new post so that you will notice this. Kasumi or someone else if you are
almost done with involved in a post that teaches me 16 bit math please finish... I would still like to read it.
last edit.
The unsigned range for a set of X bytes is 256 to the Xth power.
A value contained in one byte is 256^1. 256 possible values, ranging from 0 to 255.
A value contained in two bytes is 256^2. 65536 possible values, ranging from 0 to 65535.
A value contained in three bytes is 256^3. 16777216 possible values, ranging from 0 to 16777215.
Etc.
This is true because each byte represents a range of 256 values by itself. Assume we have two bytes.
If byte 1 is always zero, there are still 256 values possible in byte 2.
If byte 1 is always one, there are still 256 values possible in byte 2.
If byte 1 is either zero or one, there are 512 possible values. The 256 values of byte 2 when byte 1 is zero. Plus the 256 values of byte 2 when byte 1 is one.
If byte 1 can be any of its own 256 values, there are 256 values possible in byte 2 FOR EVERY POSSIBLE VALUE in byte 1. And of course there are 256 values possible for byte 1.
256*256 = 65536 possible values for the two bytes.
256*256*256 = 16777216 possible values for three bytes.
That is the stated truth about the ranges of multibyte values, and that is why it is true. Any questions?
The value for any X byte number can be found with ( highestbytevalue*256^(X-1) )+( lowerbytevalue*256^(X-2) ) continuing as needed until X-the number of bytes is less than 0.
Consider a value contained in one byte. This one byte can be considered the highest byte representing the value. Therefore the number it represents is
( value * 256^(1-1) )
Anything to the 0th power is 1.
( value * 1 )
Anything multiplied by one is itself.
( value )
So the value contained in one byte is just the value of that byte.
Consider a value contained in two bytes. One byte will be considered the high byte of the value. One byte will be the low byte.
( highbytevalue * 256^(2-1) )+( lowbytevalue * 256^(2-2) )
( highbytevalue * 256^1 )+( lowbytevalue * 256^0 )
( highbytevalue * 256 )+( lowbytevalue )
As for why this is so. Recall the example above with byte 1 and byte 2. In that example Byte 2 was the lowest byte, and byte 1 was the highest byte.
If byte 1 is always zero, there are still 256 values possible in byte 2.
( 0 * 256 )+( lowbytevalue )
So if the highest byte is zero, the value of the low byte is the whole number.
If byte 1 is always one, there are still 256 values possible in byte 2.
(1 * 256)+( lowbytevalue )
So if the highest byte is one, the value is 256+whatever the lowbyte value.
Consider that the high byte value is 0, and the low byte value is 255. 255 is the highest value a single byte can contain. We have exhausted its range. Adding 1 will make it equal zero.
1+255 would be 256.
But adding one to 255 stored in a byte makes it zero.
The way to represent a value of 256 using the above equation is a high byte of 1 with a lowbyte of zero.
( highbytevalue * 256 )+( lowbytevalue )
(1*256)+(0)
The value of the two bytes is 256.
The carry flag is designed to tell you when you have exhausted the range of the byte. So you can CARRY the value into a higher byte (if there is one.)
Let's do some math using the above example. The highbyte is 0. The lowbyte is 255. We will add the number 1. (which has a high byte of 0, and a low byte of 1)
Code:
lda lowbyte
clc
adc #$01
sta lowbyte
;At this point the carry is set, because 255+1 would have been greater than 255
lda highbyte
adc #$00
sta highbyte
;At this point the carry is clear, because 0+1 would not have been greater than 255.
;So if there was a third byte, we needed to add it would not add an extra from the carry.
;Thanks to the carry flag, you can have a value held in any number of bytes, limited only by RAM.
How does one represent 384 using two bytes? What is 384+128 as two bytes? How would you represent such an addition in code?
You clear the carry ONLY before you add the lowest two bytes, because before then its state is not related to the current value of the number. After that its set properly for however many adds you want to do without you manually changing it, because the previous adds were set correctly by math relating to the value of the multibyte number.
Those are the basics. What are your questions? Anything you don't understand will continue to haunt you, so ask now.
So lets say we have 8 bytes... like Nintendo's third console.
We would be able to use numbers from 0 to 18446744073709551615.
That's 18,446,744,073,709,551,616 numbers... I'm so glad that ends with a 6, just like 256.
How do you say that number? Haha it would be eightteen million four hundred fourty
-six thousand seven hundred and fourty
-four
trillion, seventy
-three
billion, seven hundred and nine
million, five hundred and fifty
-one
thousand, six hundred and sixteen. That's quite huge.
00001111 00001111 00001111 00001111 00001111 00001111 00001111 00001111 would be
=(8thbytevalue * 256^(8-1))+(7thbytevalue * 256^(8-2))+(6thbytevalue * 256^(8-3))+(5thbytevalue * 256^(8-4))+(4thbytevalue * 256^(8-5))+(3rdbytevalue * 256^(8-6))+(2ndbytevalue * 256^(8-7))+(1stbytevalue * 256^(8-8))
=(8thbytevalue * 256^(7))+(7thbytevalue * 256^(6))+(6thbytevalue * 256^(5))+(5thbytevalue * 256^(4))+(4thbytevalue * 256^(3))+(3rdbytevalue * 256^(2))+(2ndbytevalue * 256^(1))+(1stbytevalue * 256^(0))
=(8thbytevalue * 256^(7))+(7thbytevalue * 256^(6))+(6thbytevalue * 256^(5))+(5thbytevalue * 256^(4))+(4thbytevalue * 256^(3))+(3rdbytevalue * 256^(2))+(2ndbytevalue * 256)+(1stbytevalue * 1)
=(8thbytevalue * 72057594037927936)+(7thbytevalue * 281474976710656)+(6thbytevalue * 1099511627776)+(5thbytevalue * 4294967296)+(4thbytevalue * 16777216)+(3rdbytevalue * 65536)+(2ndbytevalue * 256)+1stbytevalue
=(15 * 72057594037927936)+(15 * 281474976710656)+(15 * 1099511627776)+(15 * 4294967296)+(15 * 16777216)+(15 * 65536)+(15 * 256)+15
=(1080863910568919040)+(4222124650659840)+(16492674416640)+(64424509440)+(251658240)+(983040)+(3840)+15
=1080863910568919040 + 4222124650659840 + 16492674416640 + 64424509440 + 251658240 + 983040 + 3840 + 15
=1085102592571150095
=1,085,102,592,571,150,095
So
#0000111100001111000011110000111100001111000011110000111100001111b = One million eighty
-five thousand one hundred and two
trillion, five hundred nin
ety
-two
billion, five hundred seventy
-one
million, one hundred fifty
thousand, and nin
ety
-five.
I UNDERSTAND THANK YOU INCREDIBLY MUCH KASUMI!!!!!!!!!!!!!!!!!!!!!!!! Working with the
Nindendo Nintendo 64 would be insane and terrible... in my opinion.
edit.edit3.last_edit.adding_correct_es.
On the topic of 16 bit operations on the 6502, I highly recommend reading this article about comparisons:
http://www.6502.org/tutorials/compare_beyond.html
unregistered wrote:
Working with the
Nindendo Nintendo 64 would be insane and terrible... in my opinion.
IIRC, most Nintendo 64 games run in 32-bit mode. Still, just because you CAN use a lot of bits in your calculations it doesn't mean you necessarily HAVE TO work with huge numbers. Having big registers usually makes things simpler, because you don't have to worry about carry propagation and that sort of thing.
rainwarrior wrote:
On the topic of 16 bit operations on the 6502, I highly recommend reading this article about comparisons:
http://www.6502.org/tutorials/compare_beyond.html Thank you so much rainwarrior!!
So far I have learned about the Z flag after CMP... that's awesome!! And so is section 2.2, 2.3, and 2.4!! Example 3.1 3.2 and 3.3 will be helpful to me for 16 bit comparisons!
tokumaru wrote:
unregistered wrote:
Working with the
Nindendo Nintendo 64 would be insane and terrible... in my opinion.
IIRC, most Nintendo 64 games run in 32-bit mode.
Sweet!!
tokumaru wrote:
Still, just because you CAN use a lot of bits in your calculations it doesn't mean you necessarily HAVE TO work with huge numbers. Having big registers usually makes things simpler, because you don't have to worry about carry propagation and that sort of thing.
Wow, I totally understand, thanks tokumaru!!
edit: Yes, it occured to me after my last_edit that maybe they would have something that lessened the load of 64 bits... I was going to say that after reading rainwarrior's page. You beat me to it! Ninja'd by tokumaru... haha I think that's what ya'll would say.
edit2: removed laughing face from edit... sorry.
Kasumi wrote:
The unsigned range for a set of X bytes is 256 to the Xth power.
A value contained in one byte is 256^1. 256 possible values, ranging from 0 to 255.
A value contained in two bytes is 256^2. 65536 possible values, ranging from 0 to 65535.
A value contained in three bytes is 256^3. 16777216 possible values, ranging from 0 to 16777215.
Etc.
This is true because each byte represents a range of 256 values by itself. Assume we have two bytes.
If byte 1 is always zero, there are still 256 values possible in byte 2.
If byte 1 is always one, there are still 256 values possible in byte 2.
If byte 1 is either zero or one, there are 512 possible values. The 256 values of byte 2 when byte 1 is zero. Plus the 256 values of byte 2 when byte 1 is one.
If byte 1 can be any of its own 256 values, there are 256 values possible in byte 2 FOR EVERY POSSIBLE VALUE in byte 1. And of course there are 256 values possible for byte 1.
256*256 = 65536 possible values for the two bytes.
256*256*256 = 16777216 possible values for three bytes.
That is the stated truth about the ranges of multibyte values, and that is why it is true. Any questions?
The value for any X byte number can be found with ( highestbytevalue*256^(X-1) )+( lowerbytevalue*256^(X-2) ) continuing as needed until X-the number of bytes is less than 0.
Consider a value contained in one byte. This one byte can be considered the highest byte representing the value. Therefore the number it represents is
( value * 256^(1-1) )
Anything to the 0th power is 1.
( value * 1 )
Anything multiplied by one is itself.
( value )
So the value contained in one byte is just the value of that byte.
Consider a value contained in two bytes. One byte will be considered the high byte of the value. One byte will be the low byte.
( highbytevalue * 256^(2-1) )+( lowbytevalue * 256^(2-2) )
( highbytevalue * 256^1 )+( lowbytevalue * 256^0 )
( highbytevalue * 256 )+( lowbytevalue )
As for why this is so. Recall the example above with byte 1 and byte 2. In that example Byte 2 was the lowest byte, and byte 1 was the highest byte.
If byte 1 is always zero, there are still 256 values possible in byte 2.
( 0 * 256 )+( lowbytevalue )
So if the highest byte is zero, the value of the low byte is the whole number.
If byte 1 is always one, there are still 256 values possible in byte 2.
(1 * 256)+( lowbytevalue )
So if the highest byte is one, the value is 256+whatever the lowbyte value.
Consider that the high byte value is 0, and the low byte value is 255. 255 is the highest value a single byte can contain. We have exhausted its range. Adding 1 will make it equal zero.
1+255 would be 256.
But adding one to 255 stored in a byte makes it zero.
The way to represent a value of 256 using the above equation is a high byte of 1 with a lowbyte of zero.
( highbytevalue * 256 )+( lowbytevalue )
(1*256)+(0)
The value of the two bytes is 256.
The carry flag is designed to tell you when you have exhausted the range of the byte. So you can CARRY the value into a higher byte (if there is one.)
Let's do some math using the above example. The highbyte is 0. The lowbyte is 255. We will add the number 1. (which has a high byte of 0, and a low byte of 1)
Code:
lda lowbyte
clc
adc #$01
sta lowbyte
;At this point the carry is set, because 255+1 would have been greater than 255
lda highbyte
adc #$00
sta highbyte
;At this point the carry is clear, because 0+1 would not have been greater than 255.
;So if there was a third byte, we needed to add it would not add an extra from the carry.
;Thanks to the carry flag, you can have a value held in any number of bytes, limited only by RAM.
How does one represent 384 using two bytes?
00000001 10000000
Kasumi wrote:
What is 384+128 as two bytes?
Code:
00000001 10000000
+ 00000000 10000000
---------------------------
00000010 00000000 (512)
Kasumi wrote:
How would you represent such an addition in code?
this code is untested...
Code:
lda #10000000b
sta byte1_lo
lda #00000001b
sta byte1_hi
lda #10000000b
sta byte2_lo
lda #00000000b
sta byte2_hi
lda byte1_lo
clc
adc byte2_lo
sta answer_lo
lda byte1_hi
adc byte2_hi
sta answer_hi
Kasumi wrote:
You clear the carry ONLY before you add the lowest two bytes, because before then its state is not related to the current value of the number. After that its set properly for however many adds you want to do without you manually changing it, because the previous adds were set correctly by math relating to the value of the multibyte number.
Those are the basics. What are your questions? Anything you don't understand will continue to haunt you, so ask now.
Em... I don't understand how I'm susposed to increase x while my player stays on the screen... cause Mario is always on the screen... I don't get that part right now... I'm going to try working with this... maybe I can increase a high byte while leaving a low byte alone...
Keep in mind that you need two sets of coordinates for game objects: one that's relative to the game world/level (used for movement, collision, etc) and another one to draw sprites to the screen. You usually calculate the latter by subtracting the camera's coordinates (which are also relative to the world/level) from the former. Mario is always on the screen because MarioX - CameraX is always between 0 and 255, since the camera follows him, but both Mario and the camera have have 16-bit coordinates relative to the map so they can walk through it.
unregistered wrote:
Em... I don't understand how I'm susposed to increase x while my player stays on the screen... cause Mario is always on the screen... I don't get that part right now... I'm going to try working with this... maybe I can increase a high byte while leaving a low byte alone...
It all goes back to a question I asked before all the 16bit stuff.
Quote:
There are three things that affect where a sprite is drawn to the screen if you scroll. The most direct are the X and Y values in whatever page you use with the sprite DMA, because they are obviously exactly where your sprite is drawn. To make this value correct, you must do some math with the other two things that matter. One of them is the sprite's ACTUAL (read: non 8bit) position. What's the other important value?
Tokumaru gave you this answer. The third important value is the camera's X position.
The way to solve problems in programming is to think about what information you have. Then what information you need. Then an equation to get the information you need from what you have.
In this case, how do you get an on screen position from the X position? Tokumaru gave you this answer too.
The information you have is the X position of the object, and the camera's X position. The information you need is an on screen value (0-255) from these two values.
If the camera's position is 0, and the player's position is 0, the on screen value should be 0.
If the camera's position is 0, and the player's position is 128, the on screen value should be 128.
If the camera's position is 128, and the player's position is 256, the on screen value should be 128.
If the camera's position is 384 and the player's position is 511, the on screen value should be 127.
So that's what you have, and the value you want to get from what you have written out. And so you find the equation that gets it. It's playerposition-cameraposition = on screen value.
If the on screen value is < 0 or > 255, you're off screen.
Not really sure what you're talking about here. Did you put unused sprites off-screen? You must put sprites you are not using below the end of the screen (use a Y coordinate of 240 or more) in order to hide them, otherwise most emulators will put the sprites at the top left of the screen (coordinates 0, 0).
I'm so glad you misunderstood me; thanks tokumaru!
Though, right now my code for this... it changes the first value on page 2...
Code:
;/*************************
; hide sprite 0
;*************************/
hide_sprite:
ldx #$00
lda sprite, x
sta hidden_spritesY
lda #240
sta sprite, x
sta oY
rts ;end of hide_sprite
Now I think that is correct... it would be faster if I changed it to
Code:
lda #240
sta sprite+0
But is that ok? Is it ok that I use the x register? It's not working like I want it to. I want the sprite to go away... now it disables my A button from working correctly and the sprite continues to stay on the screen. Maybe that's because I
jsr hide_sprite... I don't know and am confused about how to fix the problem.
My A button cycles through all of the animation frames of our ladie's movement. Now it changes the spacing correctly but always stays on the default frame.
my highlight.
unregistered wrote:
Code:
;/*************************
; hide sprite 0
;*************************/
hide_sprite:
ldx #$00
lda sprite, x
sta hidden_spritesY
lda #240
sta sprite, x
sta oY
rts ;end of hide_sprite
That's weird... Why would you use indexed addressing if the index is hardcoded? We use indexes when when we want to manipulate different addresses with the same code, but here you're always manipulating the same address. Also, why are you backing up the Y value to another variable (hidden_spritesY)?
Quote:
Now I think that is correct... it would be faster if I changed it to
Code:
lda #240
sta sprite+0
But is that ok?
Definitely, if you don't plan on modifying other addresses with this code.
Quote:
Is it ok that I use the x register? It's not working like I want it to. I want the sprite to go away... now it disables my A button from working correctly and the sprite continues to stay on the screen. Maybe that's because I
jsr hide_sprite... I don't know and am confused about how to fix the problem.
Can't tell without looking at the rest of the code, but I'd guess you're destroying registers/variables related to the controller reading. Your "hide_sprite" subroutine destroys A, X, hidden_spritesY and oY... are any of these being used for processing input without being initialized after the call to hide_sprite?
tokumaru wrote:
unregistered wrote:
Code:
;/*************************
; hide sprite 0
;*************************/
hide_sprite:
ldx #$00
lda sprite, x
sta hidden_spritesY
lda #240
sta sprite, x
sta oY
rts ;end of hide_sprite
That's weird... Why would you use indexed addressing if the index is hardcoded? We use indexes when when we want to manipulate different addresses with the same code, but here you're always manipulating the same address. Also, why are you backing up the Y value to another variable (hidden_spritesY)?
...I guess this shows my efforts
...I started out thinking "well, why dont I make this hide_sprite routine great... it could be really cool
starting out recieving a value encompassing the sprite to hide... in the x register. And then if I saved the current value, there could be a show_sprite routine that restored the hidden sprite back to where it was before hide_sprite was called... that could be good.
" After trying to do that the "this should only work on sprite 0" idea came to me but I thought, "maybe I could use the comma-x index thing later after this somehow works." So I ended up with that.
tokumaru wrote:
Quote:
Now I think that is correct... it would be faster if I changed it to
Code:
lda #240
sta sprite+0
But is that ok?
Definitely, if you don't plan on modifying other addresses with this code.
Quote:
Is it ok that I use the x register? It's not working like I want it to. I want the sprite to go away... now it disables my A button from working correctly and the sprite continues to stay on the screen. Maybe that's because I
jsr hide_sprite... I don't know and am confused about how to fix the problem.
Can't tell without looking at the rest of the code, but I'd guess you're destroying registers/variables related to the controller reading. Your "hide_sprite" subroutine destroys A, X, hidden_spritesY and oY... are any of these being used for processing input without being initialized after the call to hide_sprite?
[/quote] Sweet!!! I bet that is the problem... must go check.
edit: well, um... the only time oY is used
is after the down button is pressed... it is
inc oY in my react_to_input routine...
Code:
react_to_input:
jsr lookatController
lda newControllerButtons ; Is the A button down?
and #BUTTON_A ;10000000b
beq @b
lda aFrame
cmp #27 ;19 is the highest possible aFrame value
bcs @b
inc aFrame ;run only once per press.
lda aFrame
and #00000111b
tax
lda oX+0 ;<^doesn't affect clear carry
adc oooo_frame_distance, x
sta oX+0
lda oX+1
adc #$00
sta oX+1
@b lda newControllerButtons ;Is the B button down?
and #BUTTON_B ;01000000b
beq @select
lda aFrame
cmp #21 ;20 is the lowest possible aFrame value
bcc @select
dec aFrame ;run only once per press. (Please)
lda aFrame
and #00000111b
tax
lda oX+0 ;<^doesn't affect set carry
sbc oooo_frame_distanceR, x
sta oX+0
lda oX+1
sbc #$00
sta oX+1
;lda #20
;sta aFrame
;jsr low_c ;low_c is just small code that plays a note
@select lda newControllerButtons ; Select does something
and #BUTTON_SELECT ;00100000b
beq @start
inc music
lda music
and #00000011b
tay
ldx musicspread_low, y ;ldx #<musicCi_module ;also songA ;tested using "danger_streets_test_module"
stx t0
tax
ldy musicspread_high, x ;ldy #>musicCi_module
ldx t0
jsr FamiToneMusicStart ;jsr FamiToneMusicStart
@start lda newControllerButtons ; Start
and #BUTTON_START ;00010000b
beq @up
jsr FamiToneMusicStop
@up lda currControllerButtons ;Is Up down?
and #BUTTON_UP ;00001000b
beq @down
;dec oY
@down lda currControllerButtons ;is Down down?
and #BUTTON_DOWN ;00000100b
beq @left
inc oY
@left lda currControllerButtons ;is Left down?
and #BUTTON_LEFT ;00000010b
beq @right
;dec oX
jsr oooveLeft
@right lda currControllerButtons ;Is Right down?
and #BUTTON_RIGHT ;00000001b
beq +end
;inc oX
;lda #$00
;sta onceagain
jsr oooveRight
+end rts
edit2.edit3: "be SPACIFIC." ...it's ok that A and X die... it happens often in our game. hidden_spritesY is ok too... I made it, especially, to be overwritten with a new value every frame.
final_edit(#4): here is the rest of the code... as it is now...
Code:
draw_sprite:
;"(level coordinates - camera coordinates = screen coordinates)" wrote tokumaru on page 54
lda oX+0 ;oX is my level coordinate for my sister's character
sec
sbc CameraX+0
sta onscreenX+0
lda oX+1
sbc CameraX+1
sta onscreenX+1 ;onscreenX+1 should always be 0. If ever it is nonzero than it's also off the screen.
beq @continue ;new codee begins right here
jsr hide_sprite
jmp +end
@continue:
jsr show_sprite ;end of new code
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
;1l1 nothing here.
sta t0
lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t0+1
lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t2
+ ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc oY
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
lda (t0+0), y
iny
sta sprite, x
inx
lda (t0+0), y
iny
sta sprite, x
inx
lda (t0+0), y
iny
clc
adc onscreenX+0
sta sprite, x
inx
;end ect.
dec t2
bne @loop
stx oamIndex
+end: rts ;end of draw_sprite
;/*************************
; hide sprite 0
;*************************/
hide_sprite:
lda #240
sta sprite+0
rts ;end of hide_sprite
;/*************************
; show sprite 0
;*************************/
show_sprite:
lda hidden_spritesY
sta sprite+0
rts ;end of show_sprite
The "new code" part in draw_sprite... when it is commented out (each line beginning with a semicolon) my
lady moves correctly normaly throughout all of her animation frames. Now, like I said before, she is always stuck in the default frame... moving her is correct... but it's none of her frames... just the default frame. Correct movement through the level is my goal right now. I'm going to have to think more about what you've said tokumaru, THANK YOU TOKUMARU!! I must be destroying variables... something like that. I'll figure this out. One more thing hide_sprite and show_sprite there is something incorrect about how I'm calling them... hide_sprite never hides my sprite... it's animation is just stopped at the edge of the screen.... will finish tomorrow... have to shower and sleep now. goodnight. final_edit(#4) is finished.
Now my code is working better!!
I decided that my
jmp +end is causing my sprite to stay on screen because the code jumps over the code that draws my sprite... and so my sprite isn't redrawn off screen. That's what I think.
I need to figure this out... how to just redraw sprite+0 offscreen.
Code:
draw_sprite:
lda sprite+0
cmp #240
beq +
sta hidden_spritesY
;"(level coordinates - camera coordinates = screen coordinates)" wrote tokumaru on page 54
;"...playerposition-cameraposition = on screen value" wrote Kasumi on page 82
+ lda oX+0 ;oX is my level coordinate for my sister's character
sec
sbc CameraX+0
sta onscreenX+0
lda oX+1
sbc CameraX+1
sta onscreenX+1 ;onscreenX+1 should always be 0. If ever it is nonzero than it's also off the screen.
beq @continue
jsr hide_sprite
jmp +end
@continue:
jsr show_sprite
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
;1l1 nothing here.
sta t0
lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t0+1
lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t2
+ ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc oY
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
lda (t0+0), y
iny
sta sprite, x
inx
lda (t0+0), y
iny
sta sprite, x
inx
lda (t0+0), y
iny
clc
adc oX
sta sprite, x
inx
;end ect.
dec t2
bne @loop
stx oamIndex
+end rts ;end of draw_sprite
;/*************************
; hide sprite 0
;*************************/
hide_sprite:
lda #240
sta sprite+0
rts ;end of hide_sprite
;/*************************
; show sprite 0
;*************************/
show_sprite:
lda hidden_spritesY
sta sprite+0
rts ;end of show_sprite
Ok, so I have tried to work with my camera movement code. Part of my camera_aim code was like this:
Code:
+question2
;Is cameraposition < 0
lda red_herring+1
bcs +abovezero
;then cameraposition is set to 0
lda #$00
sta CameraX+1 ;cameraposition+1
sta CameraX+0 ;cameraposition+0
+abovezero:
;move camera
lda red_herring+0
sta CameraX+0
lda red_herring+1
sta CameraX+1
+end rts ;end of camera_aim
And so my lda there does not affect the carry. So the bcs is always jumping from a previous carry set... which caused me to comment out the
jsr camera_aim because my screen wasn't moving. Now it moves somewhat because I added a
cmp #$00 right in there so it looks like this
Code:
;Is cameraposition < 0
lda red_herring+1
cmp #$00
bcs +abovezero
But, that isn't right... I need to use a signed comparision so that there can be numbers less than 0. According to your site rainwarrior a signed comparision would look like this
Code:
SEC ; prepare carry for SBC
SBC NUM ; A-NUM
BVC LABEL ; if V is 0, N eor V = N, otherwise N eor V = N eor 1
EOR #$80 ; A = A eor $80, and N = N eor 1
LABEL
If the N flag is 1, then A (signed) < NUM (signed) and BMI will branch
If the N flag is 0, then A (signed) >= NUM (signed) and BPL will branch
I don't understand... I understand now!
my camera_aim code looks like this now
Code:
0C25A camera_aim:
0C25A
0C25A
0C25A
0C25A ;determines how much to move based on the players position
0C25A 85 FF sta $ff
0C25C
0C25C ;set players position and cameraposition
0C25C A5 03 lda oX+0
0C25E 85 51 sta ladyposition+0
0C260 A5 04 lda oX+1
0C262 85 52 sta ladyposition+1
0C264 A5 1F lda CameraX+0
0C266 85 3D sta red_herring+0
0C268 A5 20 lda CameraX+1
0C26A 85 3E sta red_herring+1
0C26C
0C26C
0C26C +question0
0C26C
0C26C ;First, if our ladyposition > 128, we set cameraposition = ladyposition - 128
0C26C A5 51 lda ladyposition+0
0C26E C9 80 cmp #128
0C270 90 0D bcc +question1
0C272
0C272 A5 51 lda ladyposition+0
0C274 38 sec
0C275 E9 80 sbc #128 ;cameraposition = ladyposition - 128.
0C277 85 3D sta red_herring+0
0C279
0C279 A5 52 lda ladyposition+1
0C27B E9 00 sbc #$00
0C27D 85 3E sta red_herring+1
0C27F
0C27F
0C27F
0C27F +question1
0C27F
0C27F ;Is cameraposition > levellength-256
0C27F A5 3E lda red_herring+1
0C281 C5 0E cmp levellength_high
0C283 30 0B bmi +question2 ;<branch is now CORRECT! ?...assuming we never reach #1024
0C285
0C285 ;make cameraposition = levellength-256
0C285 A9 00 lda #$00
0C287 85 3D sta red_herring+0
0C289 A5 0E lda levellength_high ;...is already set at levellength-256
0C28B 85 3E sta red_herring+1
0C28D
0C28D 4C A4 C2 jmp +abovezero
0C290
0C290 +question2
0C290
0C290 ;Is cameraposition < 0,
0C290 ; lda red_herring+1 ;<----------LDA DOES NOT AFFECT CARRY!!!!
0C290 ; cmp #$00
0C290 ; bcs +abovezero;pl ...bcs is correct! bpl is incorrect.
0C290 A5 3E lda red_herring+1
0C292 38 SEC ; prepare carry for SBC
0C293 E9 00 SBC #$00;NUM ; A-NUM
0C295 50 02 BVC +LABEL ; if V is 0, N eor V = N, otherwise N eor V = N eor 1
0C297 49 80 EOR #$80 ; A = A eor $80, and N = N eor 1
0C299 10 09 +LABEL bpl +abovezero
0C29B
0C29B
0C29B
0C29B ;then cameraposition is set to 0
0C29B A9 00 lda #$00
0C29D 85 20 sta CameraX+1 ;cameraposition+1
0C29F 85 1F sta CameraX+0 ;cameraposition+0
0C2A1 4C AC C2 jmp +end
0C2A4
0C2A4 +abovezero:
0C2A4
0C2A4 ;move camera
0C2A4 A5 3D lda red_herring+0
0C2A6 85 1F sta CameraX+0
0C2A8 A5 3E lda red_herring+1
0C2AA 85 20 sta CameraX+1
0C2AC
0C2AC 60 +end rts ;end of camera_aim
My question is... Does this aiming of the camera make sense to you? Does question0 look correct?
Does question1 look correct?
Whee, I guess it's more tough love time.
Quote:
my camera_aim code looks like this now
Don't think it'll work as expected, like I probably said a long time ago. I remember making gifs.
Yep:
viewtopic.php?f=10&t=7451&p=113939&hilit=gif#p113939Code:
0C26C ;First, if our ladyposition > 128, we set cameraposition = ladyposition - 128
0C26C A5 51 lda ladyposition+0
0C26E C9 80 cmp #128
0C270 90 0D bcc +question1
That doesn't actually check if ladyposition is > 128. It checks if ladyposition's LOW BYTE is. Which means it does absolutely nothing from $0000-$007F as expected. Does what you want from $0080-$00FF. Does nothing from $0100-$017F which you wouldn't want. Just like the gif. Déjà vu. I'll admit I didn't even read the rest of what was there.
Edit: Interesting. Rereading the gif post, the images still display what I think would happen, but it happens for a slightly different reason this time around. lda whatever cmp #128 bcc will branch in all the same cases as lda whatever bpl. But still...
I made this post:
viewtopic.php?p=114057#p114057that explains this post:
viewtopic.php?p=112764#p112764made more than a year ago with 90% of the code you need for this kind of camera. And I mean... I guess I'm happy you didn't just copy and paste it to be done, but still there's only so many different ways I can explain a thing. The only thing it doesn't do is stop scrolling beyond the level to the right. (It does for the left).
I don't know what to recommend. Maybe don't put things directly into your game. Prototype them in a mostly blank rom first.
Attached is a very old build of my game.
Attachment:
main.nes [24.02 KiB]
Downloaded 92 times
It does nothing except moving the camera and moving the "character" (replaced by a black box). The two numbers on the left are the high and low bytes of the X scroll position. (which can go below zero or whatever in this case.) The two numbers on the right are the high and low bytes of the character's X scroll position. Player 2's dpad moves the scroll position. Player 1's d-pad moves the character. (I set p2's controls to be easily accessible on the keyboard. ijkl for directions. asdf for a, b, autofire a, autofire b. P1 is arrow keys and zxcv for the same stuff. So to play as player 2, I just move my hands up. Playing as both players this way is easy too. I put debug stuff on p2's controller all the time.)
Make a rom like this. Make sure you get the concepts instead of only trying to brute force them into your game. The want to complete/see result in your game is getting in the way of you learning a good foundation. I even doubt it'd do you harm to start totally over. Work in SMALL parts. Sure you have question0, 1 etc. Did you verify the question0 part even worked before adding all the code for the others? That's all you gotta do. Then you can post less code, because there's less things that could possibly be wrong. Like 10 lines of code instead of whatever everything you posted is.
Earlier I said you can't be too specific and this is true. BUT in this case all the code you posted is only telling me I'm going to be doing work for you, because you haven't tested parts to identify what might be wrong.
A while ago, I said something along the lines of programming is thinking about what you have. Then what you need. Then how to get what you need from what you have.
Example: The famous 8bit absolute value thing. As you know $FF is -1. So $FF should return 1. $FE should return 2. 1 should return 1. 2 should return 2.
What I have: An 8 bit value.
What I need: The absolute value of the 8 bit value.
That is to say that input:output like below
$FE:$02
$FF:$01
$00:$00
$01:$01
etc. in both directions.
You look for a pattern in what you have and what you need. Every positive value obviously doesn't need to be changed.
Code:
absolutevalue:
lda 8bitvalue
bpl done
done:
So the question becomes how do we make $FF = $01? You could add two to it. Hmm... but $FE+2 doesn't = $02, so that doesn't work for the range of values you need it to work on. eor $FF makes $FF = $00. eor $FF makes $FE = $01. That's really close to what I need. Wait! I can just add 1 after the eor!
Code:
absolutevalue:
lda 8bitvalue
bpl done
eor #$FF
clc
adc #$01
done:
This is the programming process. Sometimes you need to think through A LOT to find a way to get what you need from what you have.
For scrolling, what you have is the lady's position.
What you need is the camera position from it. It can't be less than zero or greater than the level length-half the screen size.
input(ladyposition):output(cameraposition) looks like...
0000:0000
through
0080:0000
0081:0001
0082:0002
etc.
through
00FF:007F
0100:0080
0101:0081
How do you get what you need from what you have? Back to the start:
Quote:
Does this aiming of the camera make sense to you?
Does it seem like it will give the above output from the above input? Verify it. Don't know how to verify it? New chain:
What you have: A routine you think will work.
What you need: A program that tests your routine against known input and output.
This is what you have to do for EVERYTHING you want to have. Think of a thing that might work. Then think of cases where it won't work. When you can't think of cases where it won't work, AND no cases come up in practice where it doesn't work, it is PROBABLY right.
If you can't think of why it won't work BUT cases come up in practice where it doesn't, it's wrong. AND now you have the exact values you need to run through your code to find out why they don't work as expected. It fails when Ladyposition is $0100? Oh, that's because we're only checking that the low byte is less 128 instead of the whole 16 bit value.
Fix that. Check it again until you can't think of cases where it won't work, AND no cases come up in practice where it doesn't work. If you find another case where it doesn't work... find out why. Fix it. And repeat. Forever until the thing is done.
I feel like a large difference between an experienced coder and a beginner is being able to identify problems by the code itself. I can look at your code and see it will fail in that case without ever running the program. Cool. But even if you can't, you can run the thing and then you'll see it. And know exactly what case fails. So then use that info to fix it! And you'll get better at identifying problems in code the next time.
Every Kasumi post gets too long...
Edit: Nah, there's even more. You can even break what you need into smaller parts before getting to your end goal.
What you have is this:
0C26C ;First, if our ladyposition > 128, we set cameraposition = ladyposition - 128
0C26C A5 51 lda ladyposition+0
0C26E C9 80 cmp #128
0C270 90 0D bcc +question1
What you need is a way to find out if a 16bit value is < 128.
Input(16bit value):Output(C)
0000:0
through
007F:0
0080:1
through
FFFF:1
Does the above code from your post fail in any of those cases? (Yes.) Why or why not?
Break it down the smallest possible part, then VERIFY that before you dare write more that might muddy up the large parts.
Kasumi, on page 67, wrote:
Edit: Because I don't think things through sometimes. It should be this:
Code:
lda ladypositionlow
sec
sbc #128;#$80
sta camerapositionlow
lda ladypositionhigh
sbc #$00;High byte of $0080
sta camerapositionhigh
bcs abovezero
lda #$00
sta camerapositionhigh
sta camperapositionlow
abovezero:
;Can be optimized in cute ways... ^_^
Which will make infinitely more sense, because what I posted before will not work at all. My logic
here was sound, but what I wrote in 6502 wasn't that logic... Not thinking, not debugging, etc... I'm sorry. I kind of look forward to these posts, but then, sometimes... I hurt more than help.
Also, this does not include the "If cameraposition > levellength -256, cameraposition = levellength-256."
...but... this isn't useable for me cause you've combined two of my tests together, I think...
Um... these are my questions
Code:
;question0
;Is our ladyposition > 128? If so we set cameraposition = ladyposition - 128.
;question1
;Is cameraposition > levellength-256? If so then make cameraposition = levellength-256.
;question2
;Is cameraposition < 0? If so then cameraposition is set to 0.
And so your code above asks question0 and uses the answer from question2. I think this is correct.
Please be ok... it's alright... I am learning about 16 bit comparisons from your code there even though it's not what I need. I need to figure this out myself; I agree with you!
thefox, on page 67, wrote:
For more explanations about 16-bit comparisons, see
http://www.6502.org/tutorials/compare_beyond.htmlThank you thefox... this is great review; helps me a lot!
edit: ok I just realized this... rainwarrior must have linked me to the same site... but you know, rainwarrior, thefox sent it to me first. Thank you both thefox and rainwarrior!! ---
And thanks Kasumi... your wise words from your most recent post are scrambling around in my head currently... but you do make sense.
edit1.
It doesn't matter if two tests are combined. What matters is the output. HOW you get there does not matter as long as you DO get there.
You have the character's position. You want a scroll position. This is all that matters.
As stated, the code I wrote doesn't answer question 1. It answers questions 0, then 2. But the ORDER of the questions doesn't matter either. What matters is it gets the right output from the input.
I'll explain why my code does question 0, then 2. At the most basic level, you want to set the cameraposition to ladyposition-128. However, the reason question 2 exists, is that you also don't want cameraposition to be less than 0. A compare is basically a subtract. When we subtract 128 from ladyposition to get the new value for cameraposition, we have also done a compare with ladyposition and 128. If the result of the compare tells us ladyposition was greater than 128, we have the right value for cameraposition. If it was less than 128, we want to load 0. Considering this subtract is the only thing that would make it go below zero (at this time), there is no need for another check for below 0. It simplifies the logic, to get the same result. Because the output is what matters and not how you get there!
"Think of a thing that might work. Then think of cases where it won't work. When you can't think of cases where it won't work, AND no cases come up in practice where it doesn't work, it is PROBABLY right. If you can't think of why it won't work BUT cases come up in practice where it doesn't, it's wrong. AND now you have the exact values you need to run through your code to find out why they don't work as expected. Fix that. Check it again until you can't think of cases where it won't work, AND no cases come up in practice where it doesn't work. If you find another case where it doesn't work... find out why. Fix it. And repeat. Forever until the thing is done."
So, my code won't work in cases where the input (character's position) is > levellength-256. Which is a case you'd have to fix.
Anyway, the issue is not your meta-logic (that is your questions and your thinking about how to accomplish things outside of the actual code). The issue is the code you're writing is not DOING this logic and you're not doing enough testing to see this before writing even MORE code. You have posted 81 lines of code. The first issue is slightly more than a quarter of the way through it. Which means the remaining 3/4s are using input that is potentially wrong. And the right input matters if you want the right output.
Don't even start with question0. Start with the first thing you want to do to do begin to TEST question0.
Code:
0C26C ;First, if our ladyposition > 128, we set cameraposition = ladyposition - 128
0C26C A5 51 lda ladyposition+0
0C26E C9 80 cmp #128
0C270 90 0D bcc +question1
Like I said, this code doesn't check if ladyposition > 128, because ladyposition is a 16bit value. All that matters is input(position), output(carry). Write down input:output charts for what you want. Then for what actually happens. And you'll see when this is wrong. Which you can use to find WHY it is wrong.
Once you have the carry correct for the check if ladyposition > 128, you can do the first set of cameraposition properly. Then do input(position), output(cameraposition), where cameraposition is never less than 0.
And once input:output is VERIFIED for that, check if the cameraposition you have set above is greater levellength-256. Make sure whatever code you're writing to DO this compare actually gives you a result you can use. input(cameraposition), output(carry).
Then once this is VERIFIED, set cameraposition again if it is greater than the levellength-256, otherwise leave it alone. Then, verify THIS is correct. input(cameraposition), output(cameraposition such that it isn't greater than the level length).
Verify here means you have looked at your code, and can think of no cases where it would fail. Then, you have run the code and run into no cases where it might fail. (And yes it might still be wrong, but really that's the best you can do
)
Only then are you done.
What you've done here is written a lotta, lotta code without verifying it like above. DO NOT continue until you have verified your solution. Ask us when either you do not know how to go about verifying your solution, or you have tried and do not know where the code wrong. In the second case give us the code, the input, the expected output, and the output you get instead.
Preferably do this before your routine gets huge. (Which should never happen, if you're diligent about testing. You'll know exactly which part fails and when.) Had you tried to verify the first part, your post would contain three lines of code and a specific question about comparisons which is much easier to answer than 81 lines with "This might be wrong somewhere. Check it for me."
Even if you prefer to write a lot of code at once, like I do, you can still verify it one piece at a time. There are two ways to do this. One is what I call "debughex", related to what PC programmers call "tracing", "logging", or "printf debugging". I rig up a program to always display two bytes as a 4-digit hex value, then have the subroutine being developed write key intermediate values to these. Another is to step through the code in a debugger like FCEUX or Nintendulator to see the first place where what is in a particular register doesn't match what I think is in the register. You can configure your assembler or linker to produce a listing file or map file and use that to find where to place a breakpoint for your debugger.
Kasumi wrote:
Once you have the carry correct for the check if ladyposition > 128, you can do the first set of cameraposition properly. Then do input(position), output(cameraposition), where cameraposition is never less than 0.
Ok so I'm confused now
My problem is that I don't know how to verify this... you tell me to do input(position), output (cameraposition) where cameraposition is never less than 0. Now is this second input(position) the same as the first input(position)? I verified the first one; it was input(position), output(carry).
Quote:
Now is this second input(position) the same as the first input(position)?
Yes, it is the same input.
The first issue with your code that I saw is that it would fail for the output of the carry (which is what you used to decide whether or not to change cameraposition). This meant the change would be wrong in some cases, so things that use the result (cameraposition) after this point might also be wrong because of bad input.
Provided the output for the compare with 128 is now correct, you'd be checking a step beyond the carry. That is that the branch you take and don't take based on the state of the carry after the compare outputs the right values for cameraposition.
Quote:
Ok so I'm confused now
My problem is that I don't know how to verify this
There are just two possible caes for this first check. 1. That ladyposition < 127, in which case cameraposition should be zero. 2. That ladyposition is >=128 in which case cameraposition should be ladyposition-128.
Just looking at RAM is probably good enough in this case. Move the character. When the character is at a position between 0 and 127, is cameraposition 0? When the character is at a position from 128 to 65535, is cameraposition that value-128? If so, that's all you need.
To verify in general, you think about the possible cases. For the compare with 128, it branches and does something, or it doesn't branch and does something else. You make sure the "something" and "something else" it does for your input are correct (give you the output from your chart. The value you expect) when you run the program.
If you check the RAM where she's below 127 and it's always zero, and when she's above, it's always the position -128 it's probably fine. You don't have to check the full range of values between 0 and 65535. You can check just a few from both cases (below and above 128), and if you see no problems with the logic of the code itself, nor problems while running the code you've done enough.
Edit: The hope with doing the checks at all is that you check just enough in practice to catch cases that exist that you did not think about. The old compare 128 works most of the time, looks good on paper if you're not so familiar with 16 bit stuff. But if you ran it and walked the lady a full screen to the right, it would start to do some wrong things, which you could then fix. If you don't check (or barely check) before you add more code, when something goes wrong there's a lot more to look at. Which is the difference between, "There's a problem in these 80 lines of code and I don't know what it is," and "There's a problem in these 20 lines and it's because the state of the carry is wrong here." You may not know WHY it's wrong, but that's cool and easier to help with.
Kasumi wrote:
Quote:
Now is this second input(position) the same as the first input(position)?
Yes, it is the same input.
Wow, ok sweet!! Thank you so much Kasumi... your words and explanations are really good!
Thank you so much!
unregistered wrote:
Kasumi wrote:
Once you have the carry correct for the check if ladyposition > 128, you can do the first set of cameraposition properly. Then do input(position), output(cameraposition), where cameraposition is never less than 0.
Ok so I'm confused now
My problem is that I don't know how to verify this... you tell me to do input(position), output (cameraposition) where cameraposition is never less than 0. Now is this second input(position) the same as the first input(position)? I verified the first one; it was input(position), output(carry).
Ok here is what is wrong... I verified question0... then question2 happens and it always sets my camera at x,y 0,0 because my camera value is always less than #127 because in question0 it subtracts #128 from my camera position. How is my camera susposed to ever go anywhere? I'm confused and lost now
...I'm sorry.
edit: well you said I didn't verify question0... I verified that the carry was correctly set... maybe I should combine question0 and question2?
Quote:
Ok here is what is wrong... I verified question0... then question2 happens and it always sets my camera at x,y 0,0 because my camera value is always less than #127 because in question0 it subtracts #128 from my camera position. How is my camera susposed to ever go anywhere?
It should subtract 128 from the character's position, not the camera's position. It should then use this value (character's position-128) as the camera's position. It would go somewhere besides zero when the character's position is greater than 128. (Because at this point character's position-128 is greater than 0).
edit2: Another thing that may be getting in your way. The value of camera position before this code is run absolutely doesn't matter. It will be set correctly if the character's position is correct. This is why you do first piece math with character position and set camera position based on this. The rest of the code is focused on fixing the new value for camera position if it's wrong (out of bounds). Which is the point of the questions.
edit:
Quote:
maybe I should combine question0 and question2?
That's probably the best way. A subtract is a compare. If you subtract 128 from the character's position to get the camera's position, the carry will tell you if it was below zero, in which case you can set it to zero, otherwise it's right. That's how the code I posted works.
If you wait (long enough for the carry to change), you have to rely on if the high byte of camera position is negative to check for below zero. A negative high byte doesn't necessarily mean it got that way because it was below zero, it could simply be a large unsigned number. (Although that only matters if you have a level greater than 32768 pixels, which you probably won't. But it's still good practice to check for below zero directly after a subtract.)
Kasumi, on page 82, wrote:
...All that matters is input(position), output(carry). Write down input:output charts for what you want. Then for what actually happens. And you'll see when this is wrong. Which you can use to find WHY it is wrong.
Once you have the carry correct for the check if ladyposition > 128, you can do the first set of cameraposition properly. Then do input(position), output(cameraposition), where cameraposition is never less than 0.
And once input:output is VERIFIED for that, check if the cameraposition you have set above is greater levellength-256. Make sure whatever code you're writing to DO this compare actually gives you a result you can use. input(cameraposition), output(carry).
Then once this is VERIFIED, set cameraposition again if it is greater than the levellength-256, otherwise leave it alone. Then, verify THIS is correct. input(cameraposition), output(cameraposition such that it isn't greater than the level length).
Verify here means you have looked at your code, and can think of no cases where it would fail. Then, you have run the code and run into no cases where it might fail. (And yes it might still be wrong, but really that's the best you can do
)
Only then are you done.
...
Yeay!!! I'm done... here is what I ended with:
Code:
0C25A camera_aimi:
0C25A
0C25A
0C25A
0C25A
0C25A
0C25A ;determines how much to move based on the players position
0C25A 85 FF sta $ff
0C25C
0C25C ;set players position and cameraposition
0C25C A5 03 lda oX+0
0C25E 85 51 sta ladyposition+0
0C260 A5 04 lda oX+1
0C262 85 52 sta ladyposition+1
0C264 A5 1F lda CameraX+0
0C266 85 3D sta red_herring+0
0C268 A5 20 lda CameraX+1
0C26A 85 3E sta red_herring+1
0C26C
0C26C
0C26C A5 51 lda ladyposition+0
0C26E 38 sec
0C26F E9 80 sbc #128
0C271 85 3D sta red_herring+0
0C273
0C273 A5 52 lda ladyposition+1
0C275 E9 00 sbc #$00;High byte of $0080
0C277 85 3E sta red_herring+1
0C279 B0 06 bcs +abovezero
0C27B A9 00 lda #$00
0C27D 85 3E sta red_herring+1
0C27F 85 3D sta red_herring+0
0C281
0C281
0C281 +abovezero:
0C281
0C281 ;One more question: Is cameraposition > levellength-256
0C281 A5 3E lda red_herring+1
0C283 C5 0E cmp levellength_high
0C285 90 08 bcc +movecamera
0C287
0C287 ;if so then make cameraposition = levellength-256
0C287 A9 00 lda #$00
0C289 85 3D sta red_herring+0
0C28B A5 0E lda levellength_high ;...is already set at levellength-256
0C28D 85 3E sta red_herring+1
0C28F
0C28F
0C28F
0C28F
0C28F +movecamera: ;move camera
0C28F A5 3E lda red_herring+1
0C291 85 20 sta CameraX+1
0C293 A5 3D lda red_herring+0
0C295 85 1F sta CameraX+0
0C297
0C297 60 +end rts ;end of camera_aimi and of mvementi
See, I ended with this code after you reminded me of your code that works for question0 and question2... and so I added your code and I verified the last two parts... and I'm done! But... well, honestly I don't think I could have figured out your different parts to verify. ... hmm as I look over your instructions they are very similar... how did you figure out your list of things to verify? Could you tell me how I should make a list like yours... with the input(something), output(carry) and input(something), output(something)? Those two things are very helpful... I wrote each of them at the top of a new page. I feel like I would get a lot out of making my own list of things to verify; that would be good for me!
I'll do that after I write some other code.
KASUMI, THANK YOU SO INCREDIBLY MUCH!! edit.
Quote:
how did you figure out your list of things to verify?
If you know what you want, you know what values you want to verify. If you don't know what you want, the computer sure won't know either. So think it over more.
You want something. The output is the values you want. You design code to give you what you want, so it should... actually give you that. Verifying is just making sure what you get with the code is actually what you want. If it isn't, you find out why and fix it.
1. If you want your character to accelerate by two every frame, the output is two plus the input. To verify, you make sure that's what you're getting.
2. If you want a way to tell if two objects are colliding, that's what you want. The output is true if they are, and false if they aren't. Verifying is checking that the code you wrote works in all the cases they're colliding.
Basically if you know what you want, you know what the output should be. The hard part is designing code to give it to you, not knowing what you want.
Now, what you need to verify and what you want isn't necessarily a static thing. For instance, 1 above. You figured that's what you wanted. Then you actually do it, and realize wait! You want a max speed of 16 too! So then what you want changes, and the output chart changes slightly with it.
0:2
1:3
2:4
etc.
16:16
17:16
18:16
19:16
(etc)
Sometimes by trying (and even succeeding) in getting what you want, you realize there are still some cases in that you don't want. And then you fix those.
Granted, that's a fairly simple example. But even if you want a complex thing, you need to be able to break what you want into values. If you can't, you're not yet ready to tell the computer how to give you what you want because all it understands IS values. You don't even necessarily need to create a chart every time (keep it in your head), but if you find yourself REALLY stumped, it can be helpful to make one.
Thanks Kasumi, that's exactly what I needed to read!!
The verify part seems like it will become easier as I get better at figuring it out. Thank you so much for all of your awesome teaching and explanations
;!!I hope others will be helped too!
edit.
tepples, on page 82, wrote:
You can configure your assembler or linker to produce a listing file or map file and use that to find where to place a breakpoint for your debugger.
tepples, this has helped me to debug better! Thank you!
I've been able to verify parts of my code with these breakpoints; I never knew that I could break at any address just by typing it in and not having to use a
sta #ff. I'm sure tokumaru has tried to explain that somewhere but I just never understood that for some reason.
---
I also wanted to tell the world that two days ago I finally fixed my screen drawing code so the whole level is on the same height... no screens magically raise up anymore!
Sweet! This is the code I added to
draw_RAMbuffersCode:
tya
sec
sbc helpJoe, x ;this helps Joe, the y value, to be more correct and every screen to be on level.
tay
The yvalue is the value of Joe, which is a name I chose to rename this value... it used to be named
valid_left... now
valid_left is correct. It needed changing for some reason... so I changed it!
The xvalue is
currScreen. Oh, and helpJoe is here...
Code:
0E0BC helpJoe: ;this should help our screens to be all level!
0E0BC 00 00 00 10 20 30 .db 0, 0, 0, $10, $20, $30
edit: I fixed my code... it said "
;this helps Joe, the x value"...
Ok - this is my question because I can't figure this out
...well, I must explain first. I'm trying to figure out what row my character is standing on... starting with the oY value. She falls at the beginning and lands on the ground; her oY value is 168. Using my calculator 168 / 16 = 10.5. Now I add 2 to that because she is 32 pixels tall... so 12.5. Rounding up makes that row 13 which is good, I think. Now I clear the calculator memory; it's at 0. 13 * 16 = 208 and then clicking hex button makes that $D0. I don't understand that $D0 because row 14 in FCEUX's hex editor starts at $7C0... and I'm at row 13 ($7A0). My two screen wide table starts at $600... each row is 32 bytes wide. I know Kasumi would know what I'm doing wrong... please help me someone!
Thanks... I must go cook supper now.
Ay, been a while.
unregistered wrote:
Rounding up makes that row 13 which is good, )
Divisions don't round the way you'd probably be doing them.
0 divided by 16 (lsr, lsr, lsr, lsr) = 0
1 divided by 16 = 0
15 divided by 16 =0
12.5 = 12, not 13, basically.
Edit: Actually even before that, 10.5 = 10. The fraction should have been lost at this step.
Edit4: And that's the output you'd want from your input anyway. If she has a position from pixel 0-15, she's still on row zero. You wouldn't want your code to say she was on row one when she reaches pixel 8 if your rows are 16 pixels tall.
Edit2: When you divide by a power of two, and then multiply by the same power of two, what you're essentially doing is just clearing the low bits. 168 = $A8. Dividing it by 16 (lsr, lsr, lsr, lsr) gives you $0A. Or 10. No fraction, no rounding. Multiplying it by 16 (asl, asl, asl, asl) gives you $A0.
So it follows that and #$F0 will get the same result in less time. #$A8 and #$F0 = #$A0.
Edit 3:
Code:
lda oY
and #$F0
clc
adc #$20;Adding 2*16. 2 tiles down to get the bottom of the sprite, times 16 to match the "multiplied/divided" row.
Edit 5 for future readers.
Good Catch.
Kasumi wrote:
unregistered wrote:
Rounding up makes that row 13 which is good, )
Divisions don't round the way you'd probably be doing them.
They do if you don't clear the carry before the next addition.
tepples wrote:
They do if you don't clear the carry before the next addition.
SWEET!!! THANK YOU SO MUCH TEPPLES!! Code:
;verified code!
lda oY ;a holds A8
lsr a ;a holds 54
lsr a ;a holds 2A
lsr a ;a holds 15
lsr a ;a holds 0A
adc #$00 ;since carry is set, a holds 0B!
Kasumi wrote:
Ay, been a while.
unregistered wrote:
Rounding up makes that row 13 which is good, )
Divisions don't round the way you'd probably be doing them.
0 divided by 16 (lsr, lsr, lsr, lsr) = 0
1 divided by 16 = 0
15 divided by 16 =0
12.5 = 12, not 13, basically.
Edit: Actually even before that, 10.5 = 10. The fraction should have been lost at this step.
Yes.
Kasumi wrote:
Edit4: And that's the output you'd want from your input anyway. If she has a position from pixel 0-15, she's still on row zero. You wouldn't want your code to say she was on row one when she reaches pixel 8 if your rows are 16 pixels tall.
Now that I've thought about it, I agree.
Kasumi wrote:
Edit2: When you divide by a power of two, and then multiply by the same power of two, what you're essentially doing is just clearing the low bits. 168 = $A8. Dividing it by 16 (lsr, lsr, lsr, lsr) gives you $0A. Or 10. No fraction, no rounding. Multiplying it by 16 (asl, asl, asl, asl) gives you $A0.
So it follows that and #$F0 will get the same result in less time. #$A8 and #$F0 = #$A0.
Edit 3:
Code:
lda oY
and #$F0
clc
adc #20;Adding 2*16. 2 tiles down to get the bottom of the sprite, times 16 to match the "multiplied/divided" row.
Yes. (Assuming you ment to write
Code:
adc #$20
)
The $D0 is troubling my mind because row 13 is on line $7
A0. (D0 is not A0.) That's where I am right now... but after 6 hours on Friday... working through this I'll understand... I hope.
tepples wrote:
Kasumi wrote:
unregistered wrote:
Rounding up makes that row 13 which is good, )
Divisions don't round the way you'd probably be doing them.
They do if you don't clear the carry before the next addition.
Rounding doesn't make much sense in collision detection, though. You either use the fractional part as an indication of how far a point is inside a block or completely ignore it.
unregistered wrote:
Yes. (Assuming you ment to write
Code:
adc #$20
)
Good catch! Edited in for future readers.
If her position is #$A8, her top is in the tenth (or... Ath) row. 32 pixels below that is the twelth (or... Cth) row. I only added the #$20 to get the row her bottom is in. No need if you just want to know where her top is.
With the rounding you presented earlier:
0-7 = row 0
8-23 = row 1
24-39 = row 2
etc.
Without:
0-15 = row 0
16-31 = row 1
32-47 = row 2
etc.
You probably want the second one. Edit: Only reason I'm mentioning the above again is because I'm not sure where else #$D0 could come from without rounding.
Kasumi wrote:
Edit: Only reason I'm mentioning the above again is because I'm not sure where else #$D0 could come from without rounding.
unregistered wrote:
I'm trying to figure out what row my character is standing on... starting with the oY value. She falls at the beginning and lands on the ground; her oY value is 168. Using my calculator 168 / 16 = 10.5. Now I add 2 to that because she is 32 pixels tall... so 12.5. Rounding up makes that row 13 which is good, I think. Now I clear the calculator memory; it's at 0. 13 * 16 = 208 and then clicking hex button makes that $D0.
Ok, I'm not rounding up... so I have 12.5... which is 12. 12 * 16 = 192... or $C0. But that $C0 doesn't make since because my character is standing on row $A0. I added 2 (32) to 10.5 to get the value at her feet. ...
...
(mad at myself cause I'm still confused)
unregistered wrote:
Ok, I'm not rounding up... so I have 12.5... which is 12. 12 * 16 = 192... or $C0. But that $C0 doesn't make since because my character is standing on row $A0. I added 2 (32) to 10.5 to get the value at her feet. ...
...
(mad at myself cause I'm still confused)
She is 32 pixels tall, right? Her position (oY) is where the top of her head is right?
So if her head is at $A0, Her feet are at $C0. If her head is at $C0, her feet are at $E0. There's nothing more to it.
If you want what row her head is on, don't add the 32 to oY. If you want what row her feet are on, add the 32 to oY. I get the impression you're overthinking this.
unregistered wrote:
Kasumi wrote:
Edit: Only reason I'm mentioning the above again is because I'm not sure where else #$D0 could come from without rounding.
unregistered wrote:
I'm trying to figure out what row my character is standing on... starting with the oY value. She falls at the beginning and lands on the ground; her oY value is 168. Using my calculator 168 / 16 = 10.5. Now I add 2 to that because she is 32 pixels tall... so 12.5. Rounding up makes that row 13 which is good, I think. Now I clear the calculator memory; it's at 0. 13 * 16 = 208 and then clicking hex button makes that $D0.
Ok, I'm not rounding up... so I have 12.5... which is 12. 12 * 16 = 192... or $C0. But that $C0 doesn't make since because my character is standing on row $A0. I added 2 (32) to 10.5 to get the value at her feet. ...
...
(mad at myself cause I'm still confused)
Oh yes, ok... now the $d0 makes sense.
I'm looking at FCEUX's hex editor... the screen has 15 rows and 16 columns right? My character is standing on row 13... 13x16=$D0... in the hex editor...each row is labeled:
Code:
$0600: Row 0
$0620: Row 1
$0640: Row 2
$0660: Row 3
$0680: Row 4
$06A0: Row 5
$06C0: Row 6
$06E0: Row 7
$0700: Row 8
$0720: Row 9
$0740: Row A
$0760: Row B
$0780: Row C
$07A0: Row D
$07C0: Row E
So... my girl is standing on row $7A0... row 13 (D)... hmm... $D0 is not good... it's Row D...
Ok, yes I'm overthinking this.
00 gives you 00
01 gives you 02
02 gives you 04
03 gives you 06. Etc. This is what you want.
So it's doubled because rows are 32 bytes long. You have what row she's standing on.
Multiply it by two. The carry is clear if you're reading from $0600 and set if you're reading from $0700.
Say $00 is the row. ASL. Result is $00, Carry clear. You want $0600.
Say $20 is the row. ASL. Result is $40, Carry clear. You want $0640.
Say $80 is the row. ASL. Result is $00, Carry set. You want $0700.
Say $A0 is the row. ASL. Result is $40, Carry set. You want $0740.
Once you've got the row, multiply it by two, transfer that result to y and branch to either a read from $0600,y or $0700,y depending on the carry.
It's all input output. Write out what you want like you did. Find the pattern that can make it happen.
Kasumi wrote:
00 gives you 00
01 gives you 02
02 gives you 04
03 gives you 06. Etc. This is what you want.
So it's doubled because rows are 32 bytes long. You have what row she's standing on.
Multiply it by two. The carry is clear if you're reading from $0600 and set if you're reading from $0700.
Say $00 is the row. ASL. Result is $00, Carry clear. You want $0600.
Say $20 is the row. ASL. Result is $40, Carry clear. You want $0640.
Say $80 is the row. ASL. Result is $00, Carry set. You want $0700.
Say $A0 is the row. ASL. Result is $40, Carry set. You want $0740.
Once you've got the row, multiply it by two, transfer that result to y and branch to either a read from $0600,y or $0700,y depending on the carry.
It's all input output. Write out what you want like you did. Find the pattern that can make it happen.
Kasumi, thanks so very much!!
Does asm6, my assembler, automatically cut off the top and bottom 8 pixels of the screen?
Assemblers only assemble code, it doesn't know what a screen is or anything, so no it doesn't handle anything with the system. But also isn't something to worry about so much, just don't put anything important in the left+right 8 pixels, and top and bottom 16 pixels on the maps or UI or whatnot.
unregistered wrote:
Does asm6, my assembler, automatically cut off the top and bottom 8 pixels of the screen?
Like 3gengames said, the assembler has absolutely nothing to do with this. The assembler only translates written code into instructions for the CPU, and it never tells the system to do anything other than what you have specified in the source code. The assembler doesn't even know what system will run the binary it outputs (only that the CPu is a 6502!), so it can't possibly do anything that's specific to the NES.
That being said, a lot of emulators are configured to hide those scanlines, because a lot of TVs cut a part of the picture. All emulators should allow you to change this configuration in the video settings.
Thank you 3gengames!! Thank you tokumaru!!
tokumaru wrote:
That being said, a lot of emulators are configured to hide those scanlines, because a lot of TVs cut a part of the picture. All emulators should allow you to change this configuration in the video settings.
Woah, wow!!! In my emulator, FCEUX 2.1.5, clicking "Config">"Video..." let me change it to show the whole screen!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! And doing that didn't affect any of my variables!!
I recommend every programmer do that!
Now, here's my next question... In Super Mario Brothers where (how far down the screen) does the brick ground start?
It begins at pixel 208 (#$D0), and is 32 pixels tall.
SMB1 has 32 lines of status bar and 208 lines of playfield, organized as 13 rows each 16 pixels tall. In land and underground levels, the bottom two rows of metatiles, or 32 pixels, are normally filled with the cracked blocks.
So the screen is divided as follows:
0-31: Status
32-207: Playfield above ground level
208-239: Ground level and below
SMB1 (for Super Mario All-Stars) uses a 224-line screen mode, where only 208-223 are ground.
Kasumi wrote:
It begins at pixel 208 (#$D0), and is 32 pixels tall.
tepples wrote:
SMB1 has 32 lines of status bar and 208 lines of playfield, organized as 13 rows each 16 pixels tall. In land and underground levels, the bottom two rows of metatiles, or 32 pixels, are normally filled with the cracked blocks.
So the screen is divided as follows:
0-31: Status
32-207: Playfield above ground level
208-239: Ground level and below
SMB1 (for Super Mario All-Stars) uses a 224-line screen mode, where only 208-223 are ground.
THANK YOU KASUMI AND TEPPLES SO INCREDIBLLY MUCH!!!
Honestly, I don't understand draw_sprite.
Code:
0C6D1 draw_sprite: ;must set x prior to calling draw_sprite
0C6D1
0C6D1 ;jsr onscreen
0C6D1
0C6D1
0C6D1 ; t0-t2 are temporary zero page locations holding
0C6D1 ; the address of a particular sprite layout and the
0C6D1 ; number of sprites left to draw
0C6D1 BD 7A E0 lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C6D4 ;1l1 nothing here.
0C6D4 85 09 sta t0
0C6D6 BD AE E0 lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C6D9 85 0A sta t0+1
0C6DB BD E2 E0 lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C6DE 85 31 sta t2
0C6E0 A0 00 ldy #0
0C6E2 ; oamIndex is a variable tracking how far you've written
0C6E2 ; into shadow OAM (customarily at $0200-$02FF)
0C6E2 A6 0B ldx oamIndex
0C6E4 @loop:
0C6E4 ; If you have the address of an array in a zero page pointer,
0C6E4 ; you use the (d),y addressing mode and increase Y to go
0C6E4 ; to the next byte.
0C6E4 B1 09 lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
0C6E6 C8 iny
0C6E7 ;ect. start
0C6E7 18 clc
0C6E8 65 06 adc oY
0C6EA 9D 00 02 sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
0C6ED E8 inx
0C6EE
0C6EE B1 09 lda (t0+0), y
0C6F0 C8 iny
0C6F1 9D 00 02 sta sprite, x
0C6F4 E8 inx
0C6F5
0C6F5 B1 09 lda (t0+0), y
0C6F7 C8 iny
0C6F8 9D 00 02 sta sprite, x
0C6FB E8 inx
0C6FC
0C6FC B1 09 lda (t0+0), y
0C6FE C8 iny
0C6FF 18 clc
0C700 65 03 adc oX
0C702 9D 00 02 sta sprite, x
0C705 E8 inx
0C706
0C706 ;end ect.
0C706 C6 31 dec t2
0C708 D0 DA bne @loop
0C70A 86 0B stx oamIndex
0C70C 60 +end rts ;end of draw_sprite
Ok so I've been through this code... it has undergone verification... except for one thing. My oY value is at #$A8, but on my emulator window the sprite is drawn standing on #$D0. The sprite is 32 pixels tall and so it should be standing on #$C8 . If I give it #$B0 she will be standing on #$D8 which is not good because the ground is at #$D0. I don't understand why this is happening and I hope you can help me.
Thanks for reading though this,
Matthew (unregistered)
edit: I guess I should say that I've searched my .lst file for "sprite" and I've stepped through each search result and only one instance seemed like a possible problem... it was in this code
Code:
init_sprites:
; Clear page #2, which we'll use to hold sprite data
lda #$00
ldx #$00
- sta sprite, x
inx
bne -
; initialize Sprite 0
lda #$00;70
sta sprite ; Y coordinate
lda #$4
sta sprite+1 ; Pattern number
sta sprite+3 ; X coordinate
; sprite+2, color, stays 0.
rts
it was under my
;initialize Sprite 0 comment... it initialized sprite 0 to #$70... but I changed that to #$00 and the problem is the same.
I've got 16 bytes at the end of zero page RAM I use for debugging. $F0-$FF. So whenever something is happening that I don't expect, I begin writing variables to $F0-$FF to see which of them is the culprit. Then I can find out why. I'm using $F0-$FF in these in the examples, but just replace my writes to $F0-$FF with whatever RAM you have entirely free.
Code:
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
sta $F0;Is the offset we're adding correct?
iny
;ect. start
clc
adc oY
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
lda oY
sta $F1;Is oY correct at this point in time?
The above will only get you the result of one of the sprite's positions, but from the sound of things they're all 8 pixels down, so it's probably all the same problem. You can also break on writes to $F1 to see what these values look like for each of the sprites.
The reason separate RAM is used, is because values like oY change multiple times a frame and you only care about what it is at this specific moment. (And also so you can break on writes more easily.)
Another approach is to briefly replace variables with the constants you expect.
Code:
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc #$A8;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
If (for example) the above code still draws the sprite 8 pixels down, you may have ruled out oY as the cause. So the data itself at the address stored in t0 and t1 is wrong. Or it's not pointing where you think it's pointing.
So you can then do this
Code:
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc #$A8;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
lda t0
sta $F0
lda t0+1
sta $F1
Verify the address stored in $F0 and $F1 with the label-it-points-to's address in your listing file. If it matches, the data itself is probably wrong. If it doesn't, the table that feeds the address to t0 and t0+1 (sprite_layouts_lo and sprite_layouts_hi) is probably wrong.
Debugging is identifying which of your variables doesn't have the value you expect by process of elimination, then tracing back how the wrong values might have gotten there. The above lists a couple of ways to think about problems like this.
Hmm... and actually, I'll take a guess. The data t0 and t0+1 is pointing to is wrong, but the address is correct. You were previously unaware that your emulator was hiding scanlines. So you may have created this data long ago and made the data compensate for this. Just a guess, though. The methods will help if it's wrong.
Kasumi, thank you so much for these debugging methods!! I will always remember to yuse $E0 through $EF to check variable values... and
SWEET thank you so much for showing me how to eliminate
oY as the cause!
...well, there is still the problem because I am certain the address in
sprite_layouts_lo and
sprite_layouts_hi is correct... and the data it points to are our sprite definitions... it pointed to
oY... all of those definitions are correct we think because they all start at
oY. Do you have any other methods or ideas?
Oh I want to say that while laying on my bed and thinking all of this over I was happy to get the idea that there might be something wrong with variable values set near the beginning of
reset: because I hadn't checked that part in forever... so I checked it, actually I checked all of
reset: down to the
cli... and well, nope - they are all correct.
The
oY problem could still be there but I don't know exactly where to look. My sister and I think I'm trying to find a needle in a haystack.
Quote:
well, there is still the problem because I am certain the address in sprite_layouts_lo and sprite_layouts_hi is correct... and the data it points to are our sprite definitions
That doesn't prevent your sprite definitions from being wrong. Did you check the actual data? Can you just post that table?
And you are certain the addresses in sprite_layouts_lo/hi are correct, but did you actually check the addresses?
If something is wrong, you really can't be sure without checking. So actually check if you haven't. If you have, tell me what you actually did check or I can't help.
If this adds 8 to your sprites
Code:
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc #$A8;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
And this doesn't
Code:
lda #$00 ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc oY;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
(Note that above will obviously draw all sprites at the same y location, but if it's the right starting location the problem is probably your definitions)
If the top of the sprite is offset by 8 in both of these, then something is probably changing sprite,x later.
What this all boils down to is this: You are checking if the constant you put in works, while reading the data doesn't. If the correct constant works, and the data doesn't, that means you're obviously not getting that value from the data. Which means the data is wrong, or the pointer is set up incorrectly.
If the correct constant and data both give the same result, that rules out that variable/data as the problem. (Or the correct constant you are supplying is actually not correct.
So be specific about what happens when you do both of the above, share the data tables and their addresses, post what's in t0 and t0+1 when you do this.
Kasumi wrote:
Can you just post that table?
I would have posted the table, but my sister is the boss and she didn't want me to post the table.
The table is correct.
Kasumi wrote:
And you are certain the addresses in sprite_layouts_lo/hi are correct, but did you actually check the addresses?
Yes I remember checking the addresses in my listing file and they pointed to oY. I just checked the t0 offset after she has fallen to the ground... it is either $06, $0E, $16, or $1E. oY is always $A8. I'm sorry, I hope to regain and retain my thoroughness.
Kasumi wrote:
If something is wrong, you really can't be sure without checking. So actually check if you haven't. If you have, tell me what you actually did check or I can't help.
That makes sense, ok.
Kasumi wrote:
If this adds 8 to your sprites
Code:
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc #$A8;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
Yes it does... my sprite is standing on #$D0.
Kasumi wrote:
And this doesn't
Code:
lda #$00 ;<-- INDIRECT INDEXED ADDRESSING
iny
;ect. start
clc
adc oY;The value that we expect should work
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
No. Actually, it does add eight... she (32 bits tall) is still standing on #$D0.
Kasumi wrote:
(Note that above will obviously draw all sprites at the same y location, but if it's the right starting location the problem is probably your definitions)
If the top of the sprite is offset by 8 in both of these, then something is probably changing sprite,x later.
What this all boils down to is this: You are checking if the constant you put in works, while reading the data doesn't. If the correct constant works, and the data doesn't, that means you're obviously not getting that value from the data. Which means the data is wrong, or the pointer is set up incorrectly.
If the correct constant and data both give the same result, that rules out that variable/data as the problem. (Or the correct constant you are supplying is actually not correct.
So be specific about what happens when you do both of the above, share the data tables and their addresses, post what's in t0 and t0+1 when you do this.
Thank you for all of your help so far. You have really helped me Kasumi!
I need to do the rest of this by myself. Please forgive me for asking you, "Do you have any other methods or ideas?" I don't mean to milk or squeeze every bit of help out of you. Thank you for all of your help so far; I can do this.
Thank you so much Kasumi!!!!!!!!!
unregistered wrote:
Kasumi wrote:
As well, related to collision: Consider that I know the tile a position is in is collision enabled. I need to eject upwards. My tile size is a power of two.
Code:
lda position
and #%00001111;For 16
sta temp
lda position
clc
sbc temp
sta position
Done. How does it work? A tile for this example is 16 pixels. So the lowest 4 bits of the position will tell us how far we are into the tile. Say we're at 0000, the very top of the tile. We want to eject one pixel. So we clear the carry to subtract 1 extra. And subtract 0.
Say we're at 1111 ($F). That's the bottom of the tile. To get outside, we want to subtract sixteen. Which... is $F+1, so the same thing works. It works in all the cases. (with square tiles.)
I'm going to answer this part later... thank you for giving me some more time to think about it.
edit: Rolly noly goaly toly... well, I just used this
SWEET code that works for everything... and it ejected my character to $B0... which is looks like she is standing 8 pixels below $D0... it says 176... which = $B0. How does that happen? I remember doing something a long time ago... but I can't remember right now... well... back to this fun problem I go.
Right now I'm getting the address $06 as data. How do I get my accumulator value "$06" to become an address? Please help me.
unregistered wrote:
edit: Rolly noly goaly toly... well, I just used this SWEET code that works for everything... and it ejected my character to $B0... which is looks like she is standing 8 pixels below $D0... it says 176... which = $B0. How does that happen?
This isn't a forum that makes a topic unread upon edits, so I never saw this until today. And I have no idea. I can't know from the information I have.
Quote:
Right now I'm getting the address $06 as data. How do I get my accumulator value "$06" to become an address? Please help me.
I don't understand the question. Are you asking how to indirectly access data starting from $0006? $0600 maybe? Something else?
I'm assuming $06 is some sort of offset? If that's the case, here's my answer:
If the data you want to access is 256 bytes or less, the easiest thing to do is transfer the number (offset) from A to X or Y and use absolute indexed addressing. It's as simple as TAX; LDA BaseAddress, X;
If the total of data is more than 256 bytes, you'll need to create a pointer in Zero Page and use Indirect Indexed addressing. You'll have to do a 16-bit addition of the offset with the base address and store the result in Zero Page. Then you can LDA (Pointer), Y to read the data. Y should be 0 in this case, because the full address is in the pointer already, but the 6502 doesn't have an indirect addressing mode that isn't indexed.
tokumaru wrote:
I'm assuming $06 is some sort of offset? If that's the case, here's my answer:
If the data you want to access is 256 bytes or less, the easiest thing to do is transfer the number (offset) from A to X or Y and use absolute indexed addressing. It's as simple as TAX; LDA BaseAddress, X;
SWEET SASSY MOLASSY!!!! TOKUMARU WE SHOULD REQUEST ALL NES 6502 PROGRAMMERS TO ENGRAVE THIS ON ONE OF THEIR ARMS tokumaru wrote:
If the total of data is more than 256 bytes, you'll need to create a pointer in Zero Page and use Indirect Indexed addressing. You'll have to do a 16-bit addition of the offset with the base address and store the result in Zero Page. Then you can LDA (Pointer), Y to read the data. Y should be 0 in this case, because the full address is in the pointer already, but the 6502 doesn't have an indirect addressing mode that isn't indexed.
THANKS TOKUMARU FOR THIS ENTIRE RESPONSE!! Here is my code now... all of the new code instructions are CAPITALIZED
Code:
draw_sprite: ;must set x prior to calling draw_sprite
;jsr onscreen
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
lda sprite_layouts_lo, x ;<-- ABSOLUTE INDEXED ADDRESSING
;1l1 nothing here.
sta t0
lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t0+1
lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t2
ldy #0
; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
ldx oamIndex
@loop:
; If you have the address of an array in a zero page pointer,
; you use the (d),y addressing mode and increase Y to go
; to the next byte.
TYA
PHA ;------------->
lda (t0+0), y ;<-- INDIRECT INDEXED ADDRESSING
TAY
LDA $0000, y ;<-- engrave this on your arm! :P :)
STA t2+1
PLA ;<-------------
TAY
iny
;ect. start
LDA t2+1
clc
adc oY
sta sprite, x ;<-- ABSOLUTE INDEXED ADDRESSING
inx
lda oY
sta $E1
lda t0
sta $E2
lda t0+1
sta $E3
lda (t0+0), y
sta $E6
iny
sta sprite, x
inx
lda (t0+0), y
sta $E7
iny
sta sprite, x
inx
TYA
PHA ;--------------->
lda (t0+0), y
TAY
LDA $0000, y
STA t2+1
PLA ;<---------------
TAY
iny
LDA t2+1
clc
adc oX
sta sprite, x
inx
;end ect.
dec t2
bne @loop
stx oamIndex
+end rts ;end of draw_sprite
...Now Kasumi and tokumaru, I don't mean for yall to trudge through all of that code... I just want yall to see the CAPITALIZED instructions that I added. Now my code doesn't add #$03 to every x value. Our sprite is drawn at the oX value #$00 now
(right up next to the left side of the screen). My sister helped me to learn that it was starting with #$03 because oX is at address $0003. oY is at address $0006 so the same should happen to that value... but it doesn't yet for some reason... it's kind of weird.
edit.edit2: I want to point out that there are two groups of CAPITALIZED code... one before the
adc oY part... and then one before the
adc oX part. Both groups should be the exact same code... I will double check that tomorrow... goodnight
yalll everyone.
edit3.
I'm not sure I understand the purpose of the new code. t0 and t1 have the address we want to load the data from. Y keeps track of how far into the data we are.
Code:
TYA;We're pushing the value in Y
PHA;To the stack
lda (t0+0), y ;We're loading from the data. So shouldn't this mean the value we've meant to load is now in A?
TAY;But then we move it into Y
LDA $0000, y;And load a zero page variable?
STA t2+1
PLA ;<-------------
TAY
This code suggests your table whose address is stored at t0 contains a zero page index that contains the actual value you want. This is not necessarily bad, but why the extra step? Why not just store the actual value you want rather than a reference to a variable that contains that value?
Quote:
My sister helped me to learn that it was starting with #$03 because oX is at address $0003
I'm not sure I understand. Where the variable is on the zero page should have no bearing on what its initial/current value would ever be.
If your table was set up like this:
Code:
sprite_layout_one:
.db $00;Y offset for sprite 0
.db $00;Tile for sprite 0
.db %00000000;Attributes for sprite 0
.db $00;X offset for sprite 0
.db $00;Y offset for sprite 1
.db $01;Tile for sprite 1
.db %00000000;Attributes for sprite 1
.db $08;X offset for sprite 1
.db $08;Y offset for sprite 2
.db $02;Tile for sprite 2
.db %00000000;Attributes for sprite 2
.db $00;X offset for sprite 2
.db $08;Y offset for sprite 3
.db $03;Tile for sprite 3
.db %00000000;Attributes for sprite 3
.db $08;X offset for sprite 3
;Sprite 0 is drawn using tile index #$00 directly at the X and Y position.
;Sprite 1 is drawn using tile index #$01 eight pixels to the right of the X position, and directly at the Y position
;Sprite 2 is drawn using tile index #$02 directly at the X position, and eight pixels below the Y position
;Sprite 3 is drawn using tile index #$03 eight pixels to the right of the X position, and eight pixels below the Y position
You'd already have all the info you need by accessing sprite_layout_one. Like so:
Code:
@loop:
lda sprite_layout_one,y;This is how far the sprite is OFFSET from our Y position
clc;So we add it to the Y position
adc oY
sta sprite,x
iny
inx
lda sprite_layout_one,y;Tile index
sta sprite,x
iny
inx
lda sprite_layout_one,y;Sprite attributes
sta sprite,x
iny
inx
lda sprite_layout_one,y;This is how far the sprite is OFFSET from our X position
clc;So we add it to the x position
adc oX
sta sprite,x
iny
inx
dec t2
bne @loop
stx oamIndex
rts
The only thing missing in the above is the indirect part.
Code:
;If the low and high bytes of sprite_layout_one were in t0 and t1, the following two lines of code would access the same value. (And put it into A)
lda sprite_layout_one,y
lda (t0),y
So a full version might look like...
Code:
; t0-t2 are temporary zero page locations holding
; the address of a particular sprite layout and the
; number of sprites left to draw
draw_sprite:
lda sprite_layouts_lo, x
sta t0
lda sprite_layouts_hi, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t0+1
lda sprite_layouts_count, x ;<-- ABSOLUTE INDEXED ADDRESSING
sta t2
ldy #0
ldx oamIndex; oamIndex is a variable tracking how far you've written
; into shadow OAM (customarily at $0200-$02FF)
@loop:
lda (t0),y;This is how far the sprite is OFFSET from our Y position
clc;So we add it to the Y position
adc oY
sta sprite,x
iny
inx
lda (t0),y;Tile index
sta sprite,x
iny
inx
lda (t0),y;Sprite attributes
sta sprite,x
iny
inx
lda (t0),y;This is how far the sprite is OFFSET from our X position
clc;So we add it to the x position
adc oX
sta sprite,x
iny
inx
dec t2
bne @loop:
stx oamIndex
rts
sprite_layout_one:
.db $00;Y offset for sprite 0
.db $00;Tile for sprite 0
.db %00000000;Attributes for sprite 0
.db $00;X offset for sprite 0
.db $00;Y offset for sprite 1
.db $01;Tile for sprite 1
.db %00000000;Attributes for sprite 1
.db $08;X offset for sprite 1
.db $08;Y offset for sprite 2
.db $02;Tile for sprite 2
.db %00000000;Attributes for sprite 2
.db $00;X offset for sprite 2
.db $08;Y offset for sprite 3
.db $03;Tile for sprite 3
.db %00000000;Attributes for sprite 3
.db $08;X offset for sprite 3
;Sprite 0 is drawn using tile index #$00 directly at the X and Y position.
;Sprite 1 is drawn using tile index #$01 eight pixels to the right of the X position, and directly at the Y position
;Sprite 2 is drawn using tile index #$02 directly at the X position, and eight pixels below the Y position
;Sprite 3 is drawn using tile index #$03 eight pixels to the right of the X position, and eight pixels below the Y position
sprite_layouts_lo:
.db <sprite_layout_one
sprite_layouts_hi:
.db >sprite_layout_one
sprite_layouts_count:
.db 4;There are four sprites in sprite_layout_one
So if you did
Code:
ldx #$00
jsr draw_sprite
You'd get a 16x16 metasprite composed of tile $00 for top left corner, tile $01 for the top right corner, tile $02 for the bottom left corner, tile $03 for the bottom right corner. All with no flipping, and palette 0.
I have zero idea what your tables look like or how your data is stored. So here's a way store the data and access it that might be different from what you're doing.
I did test the above, but full disclosure: I tested it very briefly and I had to convert its syntax to NESASM, so something small done in that process may have fixed a potential issue. But I can fix this if it's broken. I cannot help fix what you have without... the rest of what you have.
edit: I made a test rom, because lately I've been making a lot of mistakes by trying to help without testing my code. And since the test rom exists, may as well attach it. It is the definition of quick and dirty, you've got to edit RAM manually to see the sprite move. ($03 and $04 are its X and Y positions respectively.) After initialization it literally does nothing but run this routine in the main loop every frame. The NMI updates palettes so the sprite is visible, and of course does the sprite DMA. Nothing else. (Also, adc oY and adc oX end up assembled as absolute not zero page, because I didn't bother doing zero page the nesasm way when converting.)
qbradq, on page 3, wrote:
So your example above becomes:
Code:
aY EQU 10
aX EQU 20
.db aY, $80, $00, aX, aY, $81, $00, aX +1
Remember, the .byte and .db directives emit a byte of data into the output ROM file. The EQU directive (and it's cousin, the = operator) create symbols within the assembler that you can use latter on.
tokumaru, also, a post or two later, on page 3, wrote:
Symbols are just "nicknames" for numbers. These numbers can represent memory locations (which is the case of labels and variables) or numeric constants (values you commonly use throughout the program).
They exist only in the source code to make programming easier (just imagine if you had to remember the addresses of everything!), they do not exist in the assembled program.
So you would think I would have learned that symbols do not exist in the assembled program... but no after being given this on page 4
tepples, on page 4, wrote:
Code:
hero_frame1:
.db aY, $80, $00, ax, aY, $81, $00, aX+8
.db aY+8, $90, $00, aX, aY+8, $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
I changed the
aYs to
oYs and the
aXes to
oXen... and so that is how our sprite definitions have been till now... but tomorrow I am going to change
everything all of them back to
aY and
aX. That will be a start in the right direction, I think.
edit.
Kasumi wrote:
edit: I made a test rom, because lately I've been making a lot of mistakes by trying to help without testing my code. And since the test rom exists, may as well attach it. It is the definition of quick and dirty, you've got to edit RAM manually to see the sprite move. ($03 and $04 are its X and Y positions respectively.) After initialization it literally does nothing but run this routine in the main loop every frame. The NMI updates palettes so the sprite is visible, and of course does the sprite DMA. Nothing else. (Also, adc oY and adc oX end up assembled as absolute not zero page, because I didn't bother doing zero page the nesasm way when converting.)
Kasumi, thank you so much for this test rom!!
I just figured out how to edit RAM manually in FCEUX 2.1.5! I clicked Tools>Cheats... and this window came up where I then entered 0003 in the "Address:" box and 80 in the "Value:" box... and then I clicked the Add button and WALAAH!!!
It moved the sprite
down to the middle of the screen! (So I think your definitions are backwards...)
Quote:
($03 and $04 are its X and Y positions respectively.)
should be "($03 and $04 are its Y and X positions respectively)", I think. : )
edit: And guess what you were right to say you didn't understand why I added my capitalized code to draw_sprite. I just commented all of those capitalized code lines away and our 32 pixel tall girl is standing on $D0! JUST LIKE MARIO!!!
AND ALL OF THE OYS AND OXEN ARE AYS AND AXES NOW!!!!! IT'S SWEEEEEEEEEEET!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WOO HOOO THANK YOU KASUMI!!!!!
unregistered wrote:
I just figured out how to edit RAM manually in FCEUX 2.1.5! I clicked Tools>Cheats... and this window came up where I then entered 0003 in the "Address:" box and 80 in the "Value:" box... and then I clicked the Add button
That's actually patching, not RAM editing. The difference is that patched bytes can't be changed by the game program (i.e. $03 will always read back as $80 even if the game program tries to store something there). Sometimes that may even be the desired behavior, but other times you may actually want to change values and let the game code manipulate them after that. When that's the case, you can use FCEUX's hex editor. There you can select what kind of memory you want to see (e.g. ROM, RAM, VRAM), and then you can just click on the bytes to input new values. There are also options to freeze and unfreeze values.
tokumaru wrote:
unregistered wrote:
I just figured out how to edit RAM manually in FCEUX 2.1.5! I clicked Tools>Cheats... and this window came up where I then entered 0003 in the "Address:" box and 80 in the "Value:" box... and then I clicked the Add button
That's actually patching, not RAM editing. The difference is that patched bytes can't be changed by the game program (i.e. $03 will always read back as $80 even if the game program tries to store something there). Sometimes that may even be the desired behavior, but other times you may actually want to change values and let the game code manipulate them after that. When that's the case, you can use FCEUX's hex editor. There you can select what kind of memory you want to see (e.g. ROM, RAM, VRAM), and then you can just click on the bytes to input new values. There are also options to freeze and unfreeze values.
Thank you so much tokumaru!! Well... um... how can I just click on the bytes to input new values? I've never tried that... will try that! Thanks!
Hah, yep $03 and $04 backwards. Sorry about that. Even when I made sure the code worked, I messed up the description.
Quote:
I just figured out how to edit RAM manually in FCEUX 2.1.5!
Tokumaru ninja'd, but I'm adding that cheats also might affect later boots. (Even if you close FCEUX and open it again, the value may remain frozen or "cheat'd" depending on your settings.) I definitely prefer the hex editor.
Edit: Yes, just scroll to the byte you want, click it and start typing.
Quote:
I am going to change everything all of them back to aY and aX.
It's not even required to do that. You can have just the offset value. (If aY and aX are both zero, anyway. Otherwise it may still be convenient to have them as global offsets to add to the other offsets.) Glad it's working either way!
Kasumi wrote:
Hah, yep $03 and $04 backwards. Sorry about that. Even when I made sure the code worked, I messed up the description.
Glad you are laughing.
Kasumi wrote:
Quote:
I just figured out how to edit RAM manually in FCEUX 2.1.5!
Tokumaru ninja'd, but I'm adding that cheats also might affect later boots. (Even if you close FCEUX and open it again, the value may remain frozen or "cheat'd" depending on your settings.) I definitely prefer the hex editor.
Thanks, me too.
Kasumi wrote:
Edit: Yes, just scroll to the byte you want, click it and start typing.
This would have been very helpful... thank you Kasumi!
I reached a point of confusion where I didn't know what to do to make the hex editor work like tokumaru described... so I just tried typing.
Kasumi wrote:
Quote:
I am going to change everything all of them back to aY and aX.
It's not even required to do that. You can have just the offset value. (If aY and aX are both zero, anyway. Otherwise it may still be convenient to have them as global offsets to add to the other offsets.) Glad it's working either way!
Thanks! edit.
Shiru, on page 46, wrote:
There is no official NES programming manual available, but you can easily find the SNES one, if you are just interested what an official doc looks like.
Shiru, I want to buy the SNES one... how do I find it?
unregistered wrote:
Shiru, I want to buy the SNES one... how do I find it?
I don't have a link (and I think we're not allowed to link to that kind of thing here), but I'm pretty sure it's available online.
Keep in mind that official manuals usually only contain explanations about the capabilities of the system, and detailed descriptions of the various registers used to control the hardware. It's nothing that will help you with the actual programming of games if you're not familiar with all the logic involved in creating a game world.
To give you an idea of what to expect: At best, an official manual will be about as detailed as the official manual for something like
SDL or
Pygame. For example, where the SDL or Pygame docs explain the arguments of a function call, official console devkit docs might explain the bits of a register.
Thank you tokumaru and tepples for replying. I didn't realize that the documentation was destroyed due to contractual agreements and legal stuff... I'm no longer interested.
tokumaru, somewhere you wrote that if I draw 16 pixels, one metatile column, a frame... that would prevent my girl from reaching the side of the screen. Well I checked my drawRAMbuffer and drawOurColumn and both of them draw two 8pixel columns...
one two to my RAMbuffer and
one two to the screen, respectively. That will prevent her from reaching the edge of the screen (cause the max she moves is 10 pixels during a frame) right? Cause, she reaches the edge and I'm going insane trying to figure this out. Could it be only drawing one 8bit column of tiles? What else do you think it could it be?
edit.edit2: Do you think keeping track of each 8 bit column of tiles with a counter would be a good idea? Or a waste of time, why?
unregistered wrote:
Well I checked my drawRAMbuffer and drawOurColumn and both of them draw two 8pixel columns... one two to my RAMbuffer and one two to the screen, respectively. That will prevent her from reaching the edge of the screen (cause the max she moves is 10 pixels during a frame) right?
Yeah, if a character moves less than the area you can update each frame it's mathematically impossible for it to reach the edge.
Quote:
Cause, she reaches the edge and I'm going insane trying to figure this out. Could it be only drawing one 8bit column of tiles? What else do you think it could it be?
I can't guess. You should be able to debug this with FCEUX or Nintendulator. Use the name table viewer to see how the column updates are working.
Quote:
edit2: Do you think keeping track of each 8 bit column of tiles with a counter would be a good idea? Or a waste of time, why?
What do you mean keep track? Normally these things can be deduced from the camera position. For example, if the camera (it's left side) is at position $00FD, and moves to $0102 (5 pixels to the right), it crosses a 16-pixel boundary, meaning you have to draw a new column. In order to know which column to draw, you can simply add the width of the screen (256 pixels) to the camera coordinate and you'll know exactly which column you have to update.
Keeping many separate counters for things that are interconnected is an invitation for bugs, because updating everything in sync is way harder than keeping track of a few numbers/positions and calculating the rest from those whenever necessary.
tokumaru wrote:
unregistered wrote:
Well I checked my drawRAMbuffer and drawOurColumn and both of them draw two 8pixel columns... one two to my RAMbuffer and one two to the screen, respectively. That will prevent her from reaching the edge of the screen (cause the max she moves is 10 pixels during a frame) right?
Yeah, if a character moves less than the area you can update each frame it's mathematically impossible for it to reach the edge.
WOOOHOOOO!!!!!!!! Thanks so much tokumaru!! tokumaru wrote:
Quote:
Cause, she reaches the edge and I'm going insane trying to figure this out. Could it be only drawing one 8bit column of tiles? What else do you think it could it be?
I can't guess. You should be able to debug this with FCEUX or Nintendulator. Use the name table viewer to see how the column updates are working.
Ha ha I can't do that cause the columns aren't being drawn right now; my fault
. I'll do this!
tokumaru wrote:
Quote:
edit2: Do you think keeping track of each 8 bit column of tiles with a counter would be a good idea? Or a waste of time, why?
What do you mean keep track? Normally these things can be deduced from the camera position. For example, if the camera (it's left side) is at position $00FD, and moves to $0102 (5 pixels to the right), it crosses a 16-pixel boundary, meaning you have to draw a new column. In order to know which column to draw, you can simply add the width of the screen (256 pixels) to the camera coordinate and you'll know exactly which column you have to update.
SWEET MESQUITE! I can see itwadbe
inc CameraX+1 tokumaru wrote:
Keeping many separate counters for things that are interconnected is an invitation for bugs, because updating everything in sync is way harder than keeping track of a few numbers/positions and calculating the rest from those whenever necessary.
THANKS TOKUMARU!!
Kasumi, on page 66, wrote:
Continuing from this:
Quote:
If cameraposition/8 - oldcameraposition/8 is not equal to 0, we moved at least one tile and need to update the nametables.
columntoupdate = cameraposition / 8.
Edit: Well, when scrolling left anyway. When scrolling right, columntoupdate = (cameraposition+256)/8
My general question is: Since I have chosen to divide my cameraposition by 16 to find my columntoupdate... does that account for the screen scrolling slower? I was thinking that maybe that's my problem where my character still reaches the edge of the screen which is mathematically impossible. Just wondering... thought I would ask, thanks.
edit: forgot to add this note.
Quote:
Since I have chosen to divide my cameraposition by 16 to find my columntoupdate... does that account for the screen scrolling slower?
Your scroll keeps track of a pixel position. The division is because tiles/attribute columns are not 1 pixel wide. If you move one pixel, you can still be in the same tile/attribute column. It's wherever the scroll is, divided. How fast it got there doesn't matter. (Well... if you scroll faster than 8 pixels in a frame, a tile could be skipped without extra logic. But going slow doesn't matter at all.)
The difference between dividing by 8 or dividing by 16 is just because you're choosing whether you're updating a tile at a time (8 pixels across) or two tiles at a time (16 pixels across).
Edit: So short answer: If you've implemented that correctly, that's not why the character reaches the edge of the screen.
Edit2: More edition: Your character's on screen position is their actual position minus the scroll. When a character is in the middle of the screen while moving right, it means that the scroll position and the character's position are increasing at the same rate. (128-0 is 128. 256-128 is 128. 404-276 is 128.) They reach the edge of the screen when the scroll is not allowed to increase further with the character's position. Level is 1024 pixels wide, scroll has a maximum of 768. If the player moves beyond 896, they are now more than 128 pixels from the scroll. If they keep moving right, they will get closer to the edge of the screen because the scroll cannot follow them further.
Kasumi wrote:
Edit2: More edition: Your character's on screen position is their actual position minus the scroll.
!!!!!!THANKS KASUMI!!!!! The rest of your post is really helpful... I just quoted that part because I totally forgot that and your reminder is so appreciated!!
Thank you for your entire
enabling and extremely helpful post!!
edit.
unregistered, on page 48, wrote:
...FCEUX's debugger is so great!!
I think I've learned what
step-over and
step-out do too!
Step-over allows you to run the code that the method jsrs to but skip it entirely; it skips to the next line!
Step-out let's you step out of a method and it also skips to the same next line that step-over would have skipped to... that's there for people like me who accidentally press step-into too many times.
Now debugging is even faster!
Please correct me on anything I've not described correctly... it's just what I've noticed while debugging.
I just figured out something else about
Step-out... sometimes after pressing
Step-out I get a message "A Step-out is already in progress... cancel it and run a new Step-out?" and I never understood that until today. Here is what my code looks like
in the debugger...
Code:
[draw_RAMbuffers]
sta $FF
...
jsr colors
[colors]
sta $FF
...
rts
rts
lda iBeginAtOne
...tokumaru gave me the idea of setting a breakpoint for a write to address $ff and so each
sta $ff causes my debugger to take a break.
So, today, I returned to my computer and found it inside draw_RAMbuffers taking a break at its
sta $ff. Then I thought I would like to
Step-out of draw_RAMbuffers and I'd be at the first instruction after draw_RAMbuffers ends... the
lda iBeginAtOne. But
that didn't happen!! It stopped at another
sta $ff instead
and I became mad thinking I had clicked the
Step-out button, but it misunderstood me and had responded to the
Run button being clicked. I had a feeling that this had happened before.
OH GOODNESS GEE! I need to go run before it gets too dark... sorry brb...
edit:back. Ok... I was very mad... for totally the wrong reason. ...I'll explain...
edit2: ...wooooooooooah.... water with ice is excellent!!!
... ... ...my feeling of it happening before was because these exact feelings had happened like at least twice in the past... but this time I handled it well. I clicked
Step-out again and it gave me another message like, "A Step-out is already in progress... cancel it and run a new Step-out?"... and I had noticed that
colors runs at the end of
draw_RAMbuffers ...and so I thought well maybe it could be working correctly now...maybe I should click the
no button
(that would be the first time clicking the "no" button... all the other times I clicked "yes".). I clicked the
no button and after an uncertain pause I then clicked the
Run button
AND IT PAUSED AT lda iBeginAtOne!!! Food is here Amazing Race is starting. Must go eat!
last edit. (frosty the snowman rocks! : ))edited once more because I needed to add a note above.
tokumaru, on page 50, wrote:
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight).
tokumaru, a few posts further down... also on page 50, wrote:
The formula for reading data from a 2D array which is stored in memory linearly is always Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
I highlighted your words so that others might read all of them. The problem I'm experiencing now is that my code is working weirdly... and I was wondering, well, I'm trying to use metatile with a row-length of 32. So my pseudo code almost reads like
Code:
;Calculate the index of the metatile: (Y / 16) * 32 + (X / 16) = LINEAR_POSITION p.50 -Not me.-
;= ... (Y / 16) * (2 * 16) + (X / 16)
;= ... ((Y / 16) * 16) * 2 + (X / 16)
Is that correct? I mean... is it ok that I'm trying to cram two screens of 16x16 metatiles into one row?
How are you storing your collision data? That has a lot to do with how to scroll it
My collision data is kept in a 15 rows by 32 columns table in 0600-07DF. This is so I don't have to scroll it... I just have to update it once every two screens.
unregistered wrote:
Code:
;= ... (Y / 16) * (2 * 16) + (X / 16)
;= ... ((Y / 16) * 16) * 2 + (X / 16)
Is that correct?
Looks correct to me. Take care with that optimization you did though: you might look at (Y / 16) * 16 and feel tempted to skip all the shifting, but then you'll be keeping the lower 4 bits of Y, which will screw up the result when you add (X / 16) later. The correct way to optimize this formula is (Y AND $F0) * 2 + (X / 16). Multiplications and divisions are obviously done by shifting bits.
Also, since you have only 2 screens worth of collision data, you should ignore the higher bits (you only need bits 0 through 8) of the X coordinate, otherwise you might end up with a pointer to a memory location past the area you have defined for collision data.
unregistered wrote:
My collision data is kept in a 15 rows by 32 columns table in 0600-07DF.
If you add $600 to the result from the formula above, you'll end up with a 16-bit pointer you can use to read from this table. You could even skip adding (X / 16) to the pointer and put that into the Y registers instead. The complete code might look like this:
Code:
lda PixelY ;8-bit Y coordinate
and #$0f ;same as shifting right 4 times and then left 4 times
asl ;shift one more time to complete the multiplication by 32
sta Pointer+0 ;the low byte of the pointer is ready
lda #$06 ;prepare to add the high byte of the base address
adc #$00 ;add to the highest bit (carry) of PixelY * 32
sta Pointer+1 ;the high byte of the pointer is ready
lda PixelX+1 ;high byte of 16-bit X coordinate
lsr ;get the only bit we need from it
lda PixelX+0 ;low byte of the 16-bit X coordinate
ror ;put the high bit in
lsr ;shift 3 times to complete the division by 16
lsr
lsr
tay ;put the row offset in the index register
lda (Pointer), y ;get the collision data
This is just one way to do it. Another approach would be to store the collision data a bit differently, as 2 16x15 blocks, instead of a single 32x15 one. That way you could use only the lower bytes of the coordinates to form an 8-bit index and check the 9th bit of the X coordinate to decide between reading from the first or the second collision table. The options are endless, so pick what works best for you. =)
tokumaru wrote:
Another approach would be to store the collision data a bit differently, as 2 16x15 blocks, instead of a single 32x15 one.
This is what
Super Mario Bros. does: a 32x13-metatile sliding window stored as two 16x13 halves.
tepples wrote:
This is what Super Mario Bros. does: a 32x13-metatile sliding window stored as two 16x13 halves.
This might be a good approach because it better matches the name table layout, in addition to the possibility of accessing any part of each table with an 8-bit index.
tokumaru wrote:
unregistered wrote:
Code:
;= ... (Y / 16) * (2 * 16) + (X / 16)
;= ... ((Y / 16) * 16) * 2 + (X / 16)
Is that correct?
Looks correct to me. Take care with that optimization you did though: you might look at (Y / 16) * 16 and feel tempted to skip all the shifting, but then you'll be keeping the lower 4 bits of Y, which will screw up the result when you add (X / 16) later. The correct way to optimize this formula is (Y AND $F0) * 2 + (X / 16). Multiplications and divisions are obviously done by shifting bits.
Also, since you have only 2 screens worth of collision data, you should ignore the higher bits (you only need bits 0 through 8) of the X coordinate, otherwise you might end up with a pointer to a memory location past the area you have defined for collision data.
unregistered wrote:
My collision data is kept in a 15 rows by 32 columns table in 0600-07DF.
If you add $600 to the result from the formula above, you'll end up with a 16-bit pointer you can use to read from this table. You could even skip adding (X / 16) to the pointer and put that into the Y registers instead. The complete code might look like this:
Code:
lda PixelY ;8-bit Y coordinate
and #$0f ;same as shifting right 4 times and then left 4 times
asl ;shift one more time to complete the multiplication by 32
sta Pointer+0 ;the low byte of the pointer is ready
lda #$06 ;prepare to add the high byte of the base address
adc #$00 ;add to the highest bit (carry) of PixelY * 32
sta Pointer+1 ;the high byte of the pointer is ready
lda PixelX+1 ;high byte of 16-bit X coordinate
lsr ;get the only bit we need from it
lda PixelX+0 ;low byte of the 16-bit X coordinate
ror ;put the high bit in
lsr ;shift 3 times to complete the division by 16
lsr
lsr
tay ;put the row offset in the index register
lda (Pointer), y ;get the collision data
This is just one way to do it. Another approach would be to store the collision data a bit differently, as 2 16x15 blocks, instead of a single 32x15 one. That way you could use only the lower bytes of the coordinates to form an 8-bit index and check the 9th bit of the X coordinate to decide between reading from the first or the second collision table. The options are endless, so pick what works best for you. =)
Thank you * 1,000,000 tokumaru!!!
Being able to see just how it should work meant volumes for me because... I have received help from other threads... I think that's where I got the function
linear_position from... and I also had another function called
use_colTables that I got from Kasumi... and somehow I mixed them both together so our girl would fall and reach the ground and stop!!
And then after fixing some things she began to fall through the ground... and I messed up and couldn't remember how the two functions worked together... until just a little while ago I read your code there and I went and changed
use_colTables and
linear_position16bit a little and IT WORKS!!!!!!!!!!!!!!!!!!!!!!!!!!!! I'M SO HAPPY THANK YOU SO MUCH TOKUMARU!!
It was so nice to finally get some code that encompassed the entire method for linear position! WOOOOOHOOOOOO!!
edit:
tokumaru wrote:
tepples wrote:
This is what Super Mario Bros. does: a 32x13-metatile sliding window stored as two 16x13 halves.
This might be a good approach because it better matches the name table layout, in addition to the possibility of accessing any part of each table with an 8-bit index.
this is part of my edit... wow, it would be fun to try this Mario's way (thank U tepples
)... but it works so far with the 32x15 way that my code does... and I do think that it would be nice to access with an 8 bit index AND also it would be easier to read the two 16x15 halves ( 16x15 instead of 16x13 because my game doesn't use a status bar unlike Mario who does) instead of the 32x15 thing that I have because the hex editor in FCEUX lists everything in 16 sections... the extra lines will be confusing... but I'm going to create a Monitor covering
that so I'll be able to move
it to see the different sections... that will be fun to make I think...
final edit.
Ok... I want to make our sprite character disappear... my goal is also to have her stop being animated when she reaches the end of my sister's level. In the past I've been told to set her sprite y coord to "F0" and since she is the only sprite right now I have the code
Code:
lda #$F0
sta sprite+0
...
...it runs right after
Code:
sta onscreenX+1 ;onscreenX+1 should always be 0. If ever it is nonzero than it's also off the screen.
beq @continue
. This code worked before... but I changed or fixed something else and it stopped working.
It continues to not work... she continues to run across the screen after, being teleported to the other side of the screen and, memory location $0200 holds a #$F0. That's verified.
Before, when it worked, her last frame would just remain on the screen but she would not be on the screen anymore!
Well, I don't remember what I changed or fixed... please help me. Supper time!
Hello good people. How do I divide by 6 in assembly? My sister suggested I could divide by 2 three times... but that doesn't work in assembly... I think... because dividing by 2 three times is dividing by 8.
The most efficient method depends on what you need to divide by 6 for. You could multiply by $2A. Or use an actual divide loop. Or if you're doing random number generator, multiply by 6 instead of mod 6.
I found a nice divide by 6 routine in
this thread by Omegamatrix.
I will reply to his thread in a bit.
More importantly, you should think hard and decide whether you really need a division by 6. So far it seems you're programming a platform engine, and I can't really think of any reason to divide by 6 in such an engine. Why exactly do you need that division? The best thing to do would be to avoid divisions altogether.
Tokumaru makes a great point. This is low level optimization vs higher level optimization. Maybe think of a way to use divide by 8; come at it with a different approach. But for low level, I would look at factors in the original expression. As in, what's the range for the value that's divided by 6? Is it something between 0 and 31, or such? Is speed a concern (this is done more than once per frame)? Then use a small look-up table.
On a side note, there's:
A/6 = A/2 - A/3. But it has a side effect of rounding up/down depending on if the results of the two operations before subtraction (if your division results in tourniqueting the decimal part). It's based on A/C - A/(C+1) = A/C*(C+1) = A/C^2+C.
tomaitheous wrote:
Tokumaru makes a great point. This is low level optimization vs higher level optimization. Maybe think of a way to use divide by 8; come at it with a different approach.
tokumaru does make a great point. Maybe I could use a divide by 8... I thought of doing that... but then I asked my question.
tomaitheous wrote:
But for low level, I would look at factors in the original expression. As in, what's the range for the value that's divided by 6? Is it something between 0 and 31, or such?
That's an interesting question... um the value divided by 6 ranges from 20 to 67.
tomaitheous wrote:
Is speed a concern (this is done more than once per frame)? Then use a small look-up table.
Speed is always a concern. Thank you that's a great idea... a small lookup table... I have a good idea about how to make one.
This page helped me to understand what a lookup table is.
Thank you tepples, tokumaru, and tomaitheous for your responses and tokumaru you do make a great point... like tomaitheous said... but I can't tell you why I think I need my divide by 6. It makes sense to me to use it right now so yall will just have to trust me.
unregistered wrote:
I can't tell you why I think I need my divide by 6. It makes sense to me to use it right now so yall will just have to trust me.
Then definitely use a look-up table, since the range of numbers is so small. With the number to be divided loaded in the X register you can simply do
LDA DivideBy6Table-20, x to get the result.
tokumaru wrote:
unregistered wrote:
I can't tell you why I think I need my divide by 6. It makes sense to me to use it right now so yall will just have to trust me.
Then definitely use a look-up table, since the range of numbers is so small. With the number to be divided loaded in the X register you can simply do
LDA DivideBy6Table-20, x to get the result.
Thank you tokumaru and tomaitheous! How do I start my lookup table? Maybe I can figure this out... I'll play with it.
unregistered wrote:
How do I start my lookup table?
You could write all the values by hand, but it's usually a better option to let the assembler do the hard work. If you're using ASM6, this should do it:
Code:
DivideBy6Table:
Value = 20
.rept 48 ;there are 48 values between 20 and 67
.db Value / 6
Value = Value + 1
.endr
There's nothing special about look-up tables, they simply contain pre-calculated values so that you don't have to calculate them during runtime, which saves you some CPU time. Whether you should use look-up tables for specific tasks depends on how much ROM you're willing to dedicate to such tables and how often said tasks are performed. Sometimes they can be prohibitively large, but for something as small as 48 bytes it just makes sense to have all the results pre-calculated.
tokumaru wrote:
unregistered wrote:
How do I start my lookup table?
You could write all the values by hand, but it's usually a better option to let the assembler do the hard work. If you're using ASM6, this should do it:
Code:
DivideBy6Table:
Value = 20
.rept 48 ;there are 48 values between 20 and 67
.db Value / 6
Value = Value + 1
.endr
!!!!!!!!!!!!!!!!!WOW!!!!!!!!!!!!!!!!!!!!!!!!!!tokumaru wrote:
There's nothing special about look-up tables, they simply contain pre-calculated values so that you don't have to calculate them during runtime, which saves you some CPU time. Whether you should use look-up tables for specific tasks depends on how much ROM you're willing to dedicate to such tables and how often said tasks are performed. Sometimes they can be prohibitively large, but for something as small as 48 bytes it just makes sense to have all the results pre-calculated.
Thank you incredibly much tokumaru! I feel everyone can benefit and learn everything there is to know about lookup tables! Those words are gold and a fitting close to this lookup table grandness...
THANKS SO MUCH TOKUMARU!! ...AND THANK YOU TOMAITHEOUS FOR MENTIONINGRECOMMENDING LOOKUP TABLES! edit.edit2.
LUTs or look-up tables, are the
magic elixir of the 65x processors. Free indexing is really nice
They can really speed up the processor operations (albeit at the expense of memory). I tend to have LUTs just for shift+Add or shift+OR for quick address translation for larger tables (it gets the upper address bits for larger tables). Don't forget split tables too (lsb/msb), for faster access. But yeah, a table could have multiple operations/calculations in them.
Other helpful things when debugging:
1. Debug > Trace logger.
This lets you dump a text file containing every instruction executed and the status of every register/flag at each step.
2. Conditional breakpoints.
This lets you set a breakpoint that also has a condition.
See:
FCEUX debugger guideFor example, if you know that if something is wrong if A is 5 at line $8075, then what you can do it start a trace log at a time before things go wrong, create an execuition breakpoint on 8075 with the condition A==#5, then run until the breakpoint is hit. Stop the trace at this point, and you will have a log of everything that happened up until that breakpoint. From here you can work backwards from the end of the file until you see exactly what caused the problem.
Thank you * 100000000 rainwarrior!! That FCEUX debugger guide page helped me to set an extremely helpful conditional breakpoint! Wooooooooohoooo!
Now I have a question for anyone... How do you accomplish Symbolic Debugging?
I read about that on the site that rainwarrior linked to... in the section under "Symbolic Debugging". In the debug menu I right click on an address and a menu pops up and then I type "AddHealthpoints" and all that happens is that all of the text in the debugger is highlighted.
I just want the address I clicked on to show up as
JSR AddHealthpoints... just like the paragraph says.
To enable symbolic debugging, you need to make a .nl (name list) file. There should be tools to turn the list of exported symbols produced by an assembler into a .nl file; if not, you could always whip one up in JScript or Python.
tepples wrote:
To enable symbolic debugging, you need to make a .nl (name list) file. There should be tools to turn the list of exported symbols produced by an assembler into a .nl file; if not, you could always whip one up in JScript or Python.
Ok, so I went back and reread that "Symbolic Debugging" part again and I noticed, thanks to you, the nl link and I clicked it and read it and now I have 17 extra .nl files. It said to make one for each bank and our game has 16 banks... and then there is the ram nl file... I must be going insane... it's just too many files. But, they are created now and when I right click and type "a" all the text gets highlighted and nothing happens again. And, I shut down fceux and reopened it all over again and I'm still confused.
Do I have to get an nl file created by a tool?
edit: I think I should add that I've worked myself out of ideas to try... other than creating my nl files with a tool. And I also want to add rainwarrior's link from
rainwarrior
, here, wrote:
2. Conditional breakpoints.
This lets you set a breakpoint that also has a condition.
See:
FCEUX debugger guide
The FCEUX NL file format is documented here:
http://www.fceux.com/web/help/fceux.html?NLFilesFormat.htmlI gave an example that converts ca65 label files into NL with a python script here:
http://forums.nesdev.com/viewtopic.php?f=10&t=11151
unregistered, on page 81, wrote:
Kasumi wrote:
Quote:
I recieved the exact same answer though without that and #00000111b
Indeed, you don't need "and #00000111b" at all. If I have a byte like this: "76543210" and my goal is to get this from it "21000000", the asl instructions alone will do that. bits 7, 6, 5, 4, and 3 will be shifted out so it doesn't matter what they are. 0s will be shifted in.
Your mind is much better than mine... but mine is being trained at lumosity.com and it will get better.
Now I understand this well... lumosity.com, indeed, has helped my brain!
Thank you for all of your help Kasumi!
I've been working on the game like it's my job... at least six hours a day four days a week
while slowly moving away from Schizophrenia.
Recently, I've made much progress not asking questions and attempting to solve things myself. Now there is much improved character movement... perfect scrolling right... once I finish working on the character movement it will be time to attempt scrolling left.
It's growing!!! edit.
unregistered, on page 46, wrote:
tokumaru wrote:
Every time you use the debugger with a game an annoying .deb file is created. I find that really annoying when I'm developing, so I added a command to delete .deb files in the batch file that assembles the project.
Shiru wrote:
The same here.
Ha ha. That's brilliant!
Um well, I downloaded the most newest FCEUX program a few days ago and I just discovered that there is a box that's checked now next to "deb files" at the bottom of the debugger screen. So maybe that will solve the problem Shiru and tokumaru. Just wanted to let yall know.
Ok yall I need some debugging help...
I've been using FCEUX 2.2.2 for debugging for a while now... today I created a breakpoint for writes to CPU memory $0047 with a condition
$47 != $64. So far I've figured out that writes do not include
dec $0047 or
inc $0047. WHY NOT??!?
And it didn't even break when I turned around and headed left and $0047 decreased instantly from 5 to 2. ... ok
$0047 is
currScreen and
$0064 is
cmpi... and that will help you with this code I added to the very beginning of my game loop...
Code:
lda currScreen
sta cmpi
That code runs every frame... this was my solution because the condition
$47 <> $64 didn't work... it was an "invalid" condition. (You can visit page 56 of this thread to see what <> means.
) Please help me if you can... I'm really confused... I just want to find out why
currScreen decreases instantly from 5 to 2.
A break on writes does include dec and inc. (At least in my admittedly old version of FCEUX.) Try it without the condition first to see if this is true.
The condition applies before the write takes effect, as far as I can tell.
I have a break on writes on $6F, with the condition that $6F == #FF. $6F is made #$00 right when the game starts. Later in my code, I have this:
lda #$FF
sta $6F.
When the debugger breaks, $6F is ALREADY equal to #$FF. It doesn't break when $6F still has a value of #$00, and this code makes it #$FF.
In your case, this means the write that causes $47 to be different from $64 will not be caught. If there's another write AFTER that, that one will get caught. Probably leaving you with the wrong impression. If there is only ever one update to currScreen between the
lda currScreen
sta cmpi
code, it's impossible for it to break using that condition.
That's admittedly really, really annoying if you do use dec to change it. What I typically do is break on writes with the value I know to be wrong in A, X, or Y. In your case, you want to know how currScreen becomes 2.
So normally I'd do break on writes for currScreen, A == #02 || X == #02 || Y == #02
This works even though the break is BEFORE that value is written. Because you're checking the value to be written instead. So if currScreen is #05, and it breaks on sta currScreen that means running that STA will cause currScreen to be #02, because #$02 is what's in A. Again, know that you'd never catch that write with the condition currScreen == #02, because that will never break when currScreen was something different than #02 before the write happened.
If it is a dec that's causing your problem, you could break on the value above the wrong one.
break on writes currScreen, currScreen == #03.
If it breaks on a dec, that means currScreen is about to be #02.
or
break on writes currScreen, currScreen == #01
to catch an inc that might be responsible. Or convert all your
dec currScreen
to
lda currScreen
sec
sbc #$01
sta currScreen
just for this check.
Kasumi wrote:
A break on writes does include dec and inc. (At least in my admittedly old version of FCEUX.) Try it without the condition first to see if this is true.
I can't do that because every time draw_RAMbuffers runs it stores the x register into currScreen. And draw_RAMbuffers runs each time left or right is pressed on controller 1. So without the condition it would break every time I pressed left or right... which would be all the time since I'm trying to complete scrolling left successfully right now.
...I'm going to try to understand the rest of this post... not understanding it much right now. I'm sorry this reply is so late today. Well... here I go...
Kasumi wrote:
The condition applies before the write takes effect, as far as I can tell.
Ok... well, I gather this is the central point of your post... but, I disagree because - well, I haven't experienced a problem with this condition... it breaks every time valid_left becomes less than visible_left (valid_left and visible_left come from tepples at the bottom of page 69)... the condition is
$2F < $30... and it works... every time valid_left becomes less than visible_left, it breaks. So maybe your theory only applies to == and != conditions... or maybe I still am not understanding what you said. I can't spend much more time with this tonight... I'm done with my 6 hours right now... must go play the rest of my lumosity for today. Thank you so much for your response... hope to understand your theory better sometime tomorrow. Goodnight Kasumi.
Matthew
p.s. I'll finish this reply sometime tomorrow.
unregistered wrote:
I can't do that
Sure you can! It says try just that
first because it lets you see for yourself whether dec/inc really will cause a break. If it does work for that, the problem is your condition. (That must be why it wasn't breaking for inc/dec, but why take my word for it?) It's part of a process. Always break the thing not working as expected to the smallest possible piece.
Quote:
Ok... well, I gather this is the central point of your post... but, I disagree because - well, I haven't experienced a problem with this condition... it breaks every time valid_left becomes less than visible_left (valid_left and visible_left come from tepples at the bottom of page 69)... the condition is $2F < $30... and it works... every time valid_left becomes less than visible_left, it breaks. So maybe your theory only applies to == and != conditions... or maybe I still am not understanding what you said.
I have no information about valid_left and visible_left or how you're using them, but what I stated is provably true. Try this:
Break on writes $80. Condition: $80 == #08
Code:
reset:
;Intialization junk. Or not! Shouldn't matter just for this test.
lda #$00
sta $80;It may break here
lda #$08
sta $80;It will never break here
infiniteloop:
inc $00;This allows you to see whether it has broken or not with the hex editor.
;if you see the value spinning, it hasn't broken.
jmp infiniteloop
IRQ:
NMI:
rti
.org $FFFA
.dw NMI
.dw reset
.dw IRQ
Now try this small change:
Code:
lda #$08
sta $80;It will never break here
;Add these following two lines
lda #$42
sta $80;It will always break here
infiniteloop:
inc $00;This allows you to see whether it has broken or not with the hex editor.
;if you see the value spinning, it hasn't broken.
jmp infiniteloop
I did try this exact code on FCEUX 2.2.2, and it works exactly as stated. This is what I mean by it will only pick up the second write. It's made zero in the beginning. Then it is changed to #$08, the value you are actually checking for. This is the first write. This write is not broken on. Only a write when the variable ALREADY HAS the value you're checking for will be detected with that particular method.
Quote:
the condition is $2F < $30... and it works... every time valid_left becomes less than visible_left, it breaks.
I can write some code where it doesn't break when valid_left is less than visible_left. Or at least, doesn't break like I think you're expecting it to.
Break on writes $2F
Condition: $2F < $30
Try this:
Code:
lda #$80
sta $30
sta $2F;won't break because they're equal.
lda #$00
sta $2F;0 < 128, but it won't break here for the reason I stated.
lda #$FF
sta $2F;It would break here, though.
Now try this condition on the same code:
Break on writes $2F
Condition: A < $30
That condition will break when I think you'd want it to.
So if you haven't experienced a problem, it's either that I'm misunderstanding what you're expecting from these breakpoints, or the breaks you're getting are actually not the writes that are making the changes you're looking for.
Kasumi wrote:
What I typically do is break on writes with the value I know to be wrong in A, X, or Y. In your case, you want to know how currScreen becomes 2.
So normally I'd do break on writes for currScreen, A == #02 || X == #02 || Y == #02
This works even though the break is BEFORE that value is written. Because you're checking the value to be written instead. So if currScreen is #05, and it breaks on sta currScreen that means running that STA will cause currScreen to be #02, because #$02 is what's in A. Again, know that you'd never catch that write with the condition currScreen == #02, because that will never break when currScreen was something different than #02 before the write happened.
If it is a dec that's causing your problem, you could break on the value above the wrong one.
break on writes currScreen, currScreen == #03.
If it breaks on a dec, that means currScreen is about to be #02.
or
break on writes currScreen, currScreen == #01
to catch an inc that might be responsible. Or convert all your
dec currScreen
to
lda currScreen
sec
sbc #$01
sta currScreen
just for this check.
Ok I agree with you, now, I see that break on writes to currScreen using a condition currScreen == 2 will only break on a write when currScreen is already 2 (before the write)... and then breaking on writes to currScreen with the condition A == #02 || X == #02 || Y == #02 will break when currScreen is about to equal #02 because A, for instance, is already equal to #02. That makes sense to me... now I'm going to check to see if I'm correct... yeah, I think I got it now... I'm going to reread your post following this one again.... yes,
THANK YOU SO VERY MUCH KASUMI!! I must go now and finish lumosity for today... thank you for helping me to better understand debugging with conditional breakpoints! Bye Kasumi!
(Oh yes... valid_left and visible_left are both from a post by tepples on page 69 of this thread... you know, if you wanted to understand what I'm using them for. They are really helpful for me!
)
Just wanted everyone to know... scrolling left works perfectly today!!!
It has been a fantastic learning experience!
Any updates to this?
3gengames wrote:
Any updates to this?
Yes ok well yeah there have been updates to this... I'm working on character movement again right now... it's going good... redoing what I did before; it's a slow process but I think I'm almost done!
And do not worry, if this fails to work it is easy to return to a backup. I had this idea and it seems to be a good idea so far... the character is kind of hard to control right now but I think it will improve! My brain is doing better these days! Lumosity score is improving!
Thank you for asking 3gengames!
I'm using asm6 and trying to build our nes file, but there's an error "*** Branch out of range." underneath my bpl @end at address 0C301. @end is at address 0C3C5. How is that out of range? The assembly listed for 0C301 is 10 C2. That's correct... 0C303 + C2 == 0C3C5. I bet that error is there because of something else in my code; y'all have solved this problem before for me, I think... and I bet someone will ask for a piece of code. Sorry, I promised my sister to not post code anymore. I'll figure this out myself... maybe asm6 needs upgrading?! :)
Branches can go between 129 bytes forward and 126 bytes backwards, and 197 bytes forward is out of range.
That's 129 bytes forward or 126 bytes backward from the address of the branch instruction's opcode. The apparent imbalance is because the offset is actually measured from the address of the opcode that would be executed if the branch were not taken, and it has the typical range of an 8-bit signed integer: 127 bytes forward or 128 bytes backward.
The usual way to work around the 6502 branch distance limit is to use the opposite branch across a JMP.
Code:
; This produces a range error.
bpl @toofar
; 190+ bytes of whatever
@toofar:
; But this works.
bmi @no_toofar
jmp @toofar
@no_toofar:
; 190+ bytes of whatever
@toofar:
lidnariq wrote:
Branches can go between 129 bytes forward and 126 bytes backwards, and 197 bytes forward is out of range.
Ah ok, thanks lidnariq!!
Why is it limited to 129 bytes forward? It uses positive and negative numbers maybe... so oh ok, windows 10 calculator in programmer's mode is fantastic!!
So if... how does that work? How does it differentiate between forwards and backwards? Well, it must use positive and negative numbers... I remember it using negative numbers when it went backwards. But 129 is 1000 0001 and 126 is 0111 1110... but it's confusing to me beyond the fact that those two numbers are inverses. Help?
*NVM tepples answered!
tepples wrote:
That's 129 bytes forward or 126 bytes backward from the address of the branch instruction's opcode. The apparent imbalance is because the offset is actually measured from the address of the opcode that would be executed if the branch were not taken, and it has the typical range of an 8-bit signed integer: 127 bytes forward or 128 bytes backward.
Thank you so much tepples!!
Makes sense!
---
found Loopy's new site (from Memblers) (through google):
http://3dscapture.com/NES/
I'm trying to get a new screen to load when our lady reaches the top of the screen (oY == 00). Almost everything works; as soon as oY reaches 00, currFloor is decremented and stairs is jsred. Stairs tries to disable vblank by anding my copy of $2000 with #01111111b. Then see is jsred correctly.
The only problem is that see never finishes because a vblank is activated and see is called again... the hex editor shows the stack is constantly decremented because see keeps being run because vblanks keep being called.
My function see sets up the screens before the game starts. It works wonderfully.
Do you have any advice for me to turn off vblanks successfully? I have found where the wiki advises reading $2002 before turning on bit 7 of $2000, but I`m trying to turn bit 7 of $2000 off.
I'm of the opinion that it's better to leave NMIs always on and control what happens each vblank entirely in software. You can even use the shadow $2001 register to detect when rendering is off, in which case the vblank handler skips all code related to the PPU (after updating register $2001 with the shadow value, that is), so it doesn't interfere with any PPU operations the main thread may be doing (e.g. large updates between levels), but still updates the APU so that music and sound effects aren't interrupted by screen transitions.
That's brilliant tokumaru!!
Will definitely try using shadow $2001; I didn't even consider the music needing vblank updates. Thank you!
If you wanted to disable rendering for a big update using this method, you'd first change the $2001 shadow value, and then wait for vblank (using whatever communication system you have between the main thread and the NMI thread), when $2001 will effectively be written to and rendering will be disabled. From then on you can update VRAM freely (keep in mind that palette and OAM updates should only take place during vblank, to completely avoid colorful glitches and OAM corruption), because the NMI thread will know not to do any PPU operations other than copying the shadow $2001 value to register $2001, a write that should be harmless even if it interrupts other PPU operations the main thread might be doing.
So I need to wait for NMI so soft2001shadow can be copied to $2001 because then rendering will be disabled. And that will help me to be able to run see in the main thread! That`s so cool!
Is it ok to just set $2001 each vblank without checking first to see if I can? All the pointless cycles wasted; it makes me so happy to not waste cycles.
Thank you tokumaru for all of your wisdom you share.
Rewriting $2001 in every vblank is perfectly fine. So is $2000 as long as the main thread isn't in the middle of a bulk upload.
unregistered wrote:
Is it ok to just set $2001 each vblank without checking first to see if I can?
As far as I know, yes. Writing to $2001 shouldn't have any undesirable side effects, even if it happens in the middle of other PPU operations. But you do need to test whether rendering is on or off to decide whether to do other potentially dangerous PPU operations (updating VRAM, setting the scroll, etc.). Your NMI handler could contain something like this:
Code:
lda PPUMaskShadow
sta $2001
and $%00011000
beq PPUStuffDone
;(PPU updates here)
PPUStuffDone:
;(APU and other mandatory updates here)
Just wanted to quickly say that your helpful advice made the screen appear. It's not the correct screen, but it is a screen! Tons better than the black screen. Thank you tokumaru so much!!
Have to go make supper; thanks tepples for confirming that 2001 can be assigned every frame.
Thank you all so very much!
tokumaru wrote:
unregistered wrote:
Is it ok to just set $2001 each vblank without checking first to see if I can?
As far as I know, yes. Writing to $2001 shouldn't have any undesirable side effects, even if it happens in the middle of other PPU operations. But you do need to test whether rendering is on or off to decide whether to do other potentially dangerous PPU operations (updating VRAM, setting the scroll, etc.). Your NMI handler could contain something like this:
Code:
lda PPUMaskShadow
sta $2001
and $%00011000
beq PPUStuffDone
;(PPU updates here)
PPUStuffDone:
;(APU and other mandatory updates here)
Thank you tokumaru for this extra help! Someone recommended I name my 2001 shadow variable soft2001; I also like your PPUMaskShadow but soft2001 is less characters.
That won't affect the ROM size, but it will cause my trace log files to be smaller when I want to write the variable name before its address; that helps me to quickly read through certain sections of code when I return to the file.
edit: I made a mistake. I posted somewhere that trace log files would be smaller if they were recorded before hitting frame 1000, but that isn't true because there are 6 characters allotted for the frame #. If it is frame 1 then there's a "1" followed by five spaces. I didn't notice this when I made that untrue statement; I'm sorry. I'm using FCEUX 2.2.2
tepples, on page 41, wrote:
tokumaru wrote:
The reason why ($XX, x) is rarely used is because it can't index the target data in any way. Once the address of the pointer is calculated ($XX + x) and the pointer is used, the target is a single byte. There's no way to access any bytes after that one. If you do wish to access the next bytes, you have to manipulate the pointer itself (INC the lower byte, if it overflows INC the higher byte), which is terribly slow.
But if you do have an array of pointers, such as one pointer for each sound channel, and you only consume a few bytes per frame, 10.02 cycles per increment (INC ptr,x BNE :+ INC ptr+1,x : ) isn't
too much slower than 5.02 cycles per increment for (d),y (INY BNE :+ INC ptr+1 : ).
EDIT: cycle count corrected per tokumaru's clarification
I've never understood what the colons are there for in the code
Code:
inc ptr,x
bne :
+ inc ptr+1,x
:
Is that what y'all mean tepples and tokumaru?
The function of the colon is described in
ca65 Users Guide: Unnamed labels. The
+ goes on the same line as the
:, not on the next line.
This code:
Code:
inc ptr,x
bne :+
inc ptr+1,x
:
Is equivalent to this code:
Code:
inc ptr,x
bne @ptr_hi_does_not_need_to_be_incremented
inc ptr+1,x
@ptr_hi_does_not_need_to_be_incremented:
Thanks tepples!
:+ doesnt work in asm6 and
+ is one character less
edit: (Fewer characters to look at makes it easier for me to comprehend what is going on.
)
In ASM6 that'd be:
Code:
inc ptr,x
bne +
inc ptr+1,x
+
+ (plus) is for forward references, - (minus) is for backward references. If you need more than one unnamed label in the same area you can use ++, +++, and so on.
You shouldn't use these types of labels too much, otherwise your code can get really confusing. I only use unnamed labels for really short jumps and in the same "logic block".
I guess you already know this, tokumaru, but I use unnamed labels most of the time. They really help with padding!
+jDS means to me that the label is only branched to from < #$81 bytes before the label.
+jDS jmp DrawSprite is paddable afterwards because everything reaching it will jump to DrawSprite.
I'm stumped. After drawing and coloring the nametables correctly after going "upstairs" the lines on FCEUX`s Name Table Viewer show that nametable 03 is being shown. And that still happens after adding this short amount of code to the end of see2vi
Code:
lda my_copy_of_last_write_to_PPUCTRL
and #11111101b
sta $2000
sta my_copy_of_last_write_to_PPUCTRL
see2vi runs while rendering is disabled. After checking the code in a tracelog file, my_copy_of_last_write_to_PPUCTRL holds #$8D; that's the correct nametable! What other things could affect what nametable is being shown?
edit: just read that I'm not to write $2000 outside vertical blanking, on the wiki, so I'm going to change that. Fixed that and it is still showing nametable 03.
After removing my turning off scrolling when in a certain mode the new screen is displayed correctly now!!
I had turned off scrolling because I thought that it would be less for the game to worry about when in that mode, but I guess turning off scrolling while in the mode that happens when going upstairs or downstairs is not good; or, maybe, it was because
needscroll was disabled twice in the same frame. In my tracelog file it was evident that
needscroll was 00 when being assigned 00 and so we fixed that!!
edit: already removed my code from the previous post. Wasn't needed.
edit2: in my scrolling code $2000 is set. It must be good to set $2000, at most, once per frame, right?
edit3: nvrmnd, I think one of you has already tried to teach me this.
I know setting $2000 more than once in the same frame causes tiles to be drawn incorrectly.
I think that $2000 has to be set each frame to prevent nametable weirdness.
Maybe $2000 needs to be set after drawing the nametables. It wasn't being set because I had turned it off in a certain mode. But, as soon as I exited the mode the two bars of black tiles at the top of the screen disappeared. Maybe $2000 just needs to be set once after drawing the nametables.
unregistered wrote:
Maybe $2000 just needs to be set once after drawing the nametables.
That is correct. When you use $2006/$2007 to update video memory, the top-left pointer
t gets clobbered. This is important because
t controls the horizontal and initial vertical scroll position. To reset
t, you need to rewrite the scroll position through $2000 and $2005.
tepples wrote:
That is correct. When you use $2006/$2007 to update video memory, the top-left pointer t gets clobbered. This is important because t controls the horizontal and initial vertical scroll position. To reset t, you need to rewrite the scroll position through $2000 and $2005.
Thank you so much tepples for this info and for agreeing with me!!
Just want to send some love to
php and
plp. They push the processor flags to the stack and then pull them back! That's outstanding and exciting to me because they allowed me to
Code:
lda FORWARD_last
php
sta tA+1
iny ;it is 01 now
sty FORWARD_last
lda CurrentColumn
and #11110000b
plp
beq +
sec
sbc #16
+ sta t18
That runs before the main loop of see2vi. It fixes the colors and the screens from messing up when she turns around.
Those two instructions allow for the flags to be recorded, load the accumulator, and then branch according to the recorded flags! Awesome!!
edit:
php and
plp use the stack so you should read tokumaru's stack explaination on page 31 of this thread
https://forums.nesdev.com/viewtopic.php?f=10&t=7451&start=454. 1.) Make sure everything you push on the stack inside of a function is pulled from the stack before the
rts at the end of the function. 2.) Since jsring pushes 2 bytes on the stack and
rts pulls two bytes from the stack, you shouldn't ever try to do something like
Code:
function1:
lda variable
php
jsr function2
rts ;end of function1
function2:
plp
bne +
;other code here
+ rts ;end of function2
That would mess up your game considerably because after a
jsr function1 it would push 2 bytes on the stack for its rts to use, then the
php would push a third byte, then the
jsr function2 would push a fourth and fifth byte on the stack to be used by function2's rts, and the plp would pull the fifth byte pushed on the stack in this example. Your game would perish.
Hopefully that will help you to fully understand tokumaru's explaination. The stack is fun and helpful; just be cautious.
edit: added "a" before "jsr function1"; hopefully you can understand this easier.
tepples wrote:
unregistered wrote:
Maybe $2000 just needs to be set once after drawing the nametables.
That is correct. When you use $2006/$2007 to update video memory, the top-left pointer
t gets clobbered. This is important because
t controls the horizontal and initial vertical scroll position. To reset
t, you need to rewrite the scroll position through $2000 and $2005.
tepples, is there any other reason
t can get clobbered? (A few times, after our lady attaches to a certain object, the ground has lowered and random tiles appear at the top of the screen. So
t must be getting clobbered sometimes. Maybe $2006/$2007 are being used incorrectly... will look into that now.
)
It was because scrolling is disabled everytime she attaches to that object... and the $2005 updates are performed at the end of my scroll_screen methods. So the fix required:
Code:
lda needscroll
beq +end
lda needppu2005reg
beq +end
bi-directional_scrolling ; <I`m a macro too! runs draw_our_columns and update_colors
But, changed the 4 lines I added to
Code:
lda needscroll
and needppu2005reg
beq +end
needscroll and needppu2005reg are both absolute, non zero page, values so the
lda and
and both take 4 cycles. Changing
lda to
and didn't slow it down and we saved a
beq!!
I'm pretty sure that will work. They are both 1 when activated and so it will only run bi-directional scrolling if they are both 1. 1 and 1 == 1.
beq will fail cause 1 <> 0.
unregistered wrote:
tepples, is there any other reason
t can get clobbered? (A few times, after our lady attaches to a certain object, the ground has lowered and random tiles appear at the top of the screen. So
t must be getting clobbered sometimes. Maybe $2006/$2007 are being used incorrectly... will look into that now.
)
If the Y scroll is >= 240 you can get "garbage" at the top of the screen. Instead of wrapping to the next nametable it'll render the 64 bytes of attribute as if it was tiles.
^Thank you rainwarrior!!
unregistered wrote:
They are both 1 when activated and so it will only run bi-directional scrolling if they are both 1.
needscroll is actually #$ff when activated because that's non-zero and the xregister is always #$ff where
stx needscroll was placed. And the ff is ok because bit 0 is set in #$ff (#11111111b) and the
and with needppu2005reg, #$01 when activated, will work the same.
Now, after redrawing both nametables, it displays nametable 00 for one frame despite me waiting until the next vblank to set $2000 to nametable 01. Why does this happen? tepples said that
t gets clobbered after writing $2006 and $2007 and that that messes up the scrolling. On the nesdev wiki it lists bit 0 of $2000 as incrementing the X scroll value by 256. I am missing something.
Does writing $2000 twice in the same vblank cause problems like this?
Forcing $2000 to be written to only once per vblank took away the two 16bit columns of random tiles drawn between two of the screens on floor 3; but, the one frame of the wrong nametable shown still happens. I understand you don't have any code so I'll have to figure this one out myself.
edit: No, see
this post for a good correction to the first sentence of what I wrote here.
Writing $2000 twice in one vblank causes the screen to slightly shudder. It's smooth now.
unregistered wrote:
Writing $2000 twice in one vblank causes the screen to slightly shudder.
That shouldn't happen. The problem was probably something else about the second write (wrong time, wrong value, whatever), not the fact that there were 2 of them.
tokumaru wrote:
unregistered wrote:
Writing $2000 twice in one vblank causes the screen to slightly shudder.
That shouldn't happen. The problem was probably something else about the second write (wrong time, wrong value, whatever), not the fact that there were 2 of them.
Ok, thank you tokumaru.
unregistered wrote:
Forcing $2000 to be written to only once per vblank took away the two 16bit columns of random tiles drawn between two of the screens on floor 3
No, the two incorrect columns of 16bit tiles were there because valid_left was incorrect so it wasn't drawing the new columns of tiles at the correct time. Near the end of see2vi, which redraws both screens when changing floors, there's
Code:
lda visible_left
and #11111100b;10b
sta valid_left ; <this fixes the screen so it always works :mrgreen: :D
Without that code scrolling left is broken for about a screen. When bit1 was 1 it missed drawing two 16bit columns when scrolling right. Scrolling left wasn't broken, I think, when I posted my incorrect post that's quoted.
So nametable 00, should be 01, is shown for about one frame because after drawing both screens, when changing floors, collisionTables is jsred immediately; we need correct collision values for the new floor.
Jsring collisionTables causes the update_vram function, in vblank, to be skipped since FrameReady isn't negative (the previous frame calculations aren't over yet). Jsring update_vram is important because $2006 is written to inside draw_our_column, which runs inside update_vram. $2000 and
$2006 $2005 must be written to in order to undo the confusion that
t causes after drawing the screens; that's what tepples told me.
Should I write $2006 $2005 outside of update_vram outside of scroll_screen? That seems better to me because trying to make collisionTables run right after the first vblank after the screens are drawn would be bad since: 1. there may be problems caused by updating the collision information a frame later and 2. if there is ever a time when frame calculations aren't done yet
$2006 $2005 won't be written to.
edit3:
tepple's post I referred toedit4.
Sorry if I can't help more, but there are a lot of details regarding the design of your own engine that we can't really follow anymore, such as the name of various subroutines and what each one of them does.
tokumaru wrote:
Sorry if I can't help more, but there are a lot of details regarding the design of your own engine that we can't really follow anymore, such as the name of various subroutines and what each one of them does.
I'm sorry, I didn't even consider you not understanding what each subroutine does.
Your comment helped me so much though, thank you!
Next time I'll include a short summary of each routine; thanks tokumaru this comment is really helpful too.
unregistered wrote:
unregistered wrote:
Forcing $2000 to be written to only once per vblank took away the two 16bit columns of random tiles drawn between two of the screens on floor 3
No, the two incorrect columns of 16bit tiles were there because valid_left was incorrect so it wasn't drawing the new columns of tiles at the correct time. Near the end of see2vi, which redraws both screens when changing floors, there's
Code:
lda visible_left
and #11111100b;10b
sta valid_left ; <this fixes the screen so it always works :mrgreen: :D
Without that code scrolling left is broken for about a screen. When bit1 was 1 it missed drawing two 16bit columns when scrolling right. Scrolling left wasn't broken, I think, when I posted my incorrect post that's quoted.
After being able to successfully travel over the first hole on level 1 and then travel left and fall down through the hole to floor 3 and then travel right, 4 columns of blocks were present between two of the screens because valid_left was 4 ahead of visible_left... so just needed to make a change to the code I posted above. Ended up setting:
Code:
lda visible_left
and #11110000b
sta valid_left
Felt I should have done that earlier... if anyone is using tepples' visible_left and valid_left scrolling controls at the bottom of
page69 you may want to implement that code above. It is so helpful!!
The hole is near the end of the second screen. Going over the hole takes you to the third screen and then traveling left to fall in the hole crosses the character over to the second screen again. So maybe my logic at changing screens is conveluded somehow. Just wanted to explain this a bit more so you all could understand the post above a little better better.
After setting up some of the banks on MMC1 to hold all of my sister's screens for her game, two of the levels' screens took up more than 16 kb; so, we split each of those screen-groups into two banks; the same file needs to be included in each of those level's banks and asm6 doesn't build the NES ROM. It says "Label already defined." Is there a way to make asm6 build the NES ROM anyway or do I have to rename each label that appears twice?
I understand why it won't build the ROM with two identical labels, but there is no way that both of those banks will be in memory at the same time, because our MMC1 is set up so that $8000-$BFFF is the switchable bank and $C000-$FFFF is fixed.
Thank you for reading this.
One way to repeat labels in ASM6 is to use MyLabel = $ instead of MyLabel:, but you have to make sure that every instance of each label always points to the same address (i.e. the file must be included in the same address in every bank it's needed), or the program could crash. I used this when working with 32KB PRG-ROM banks, to include the CPU vectors, interrupt handlers and trampoline routines at the end of every bank.
Thank you so much tokumaru!!
That is so very helpful!!
p.s. tokumaru, you mentioned in some recent thread that if we setup a page of memory to hold each low byte value like (just figured this out):
Code:
oldaddr=$
.base $6000
i=0
.rept 256
.db i
i=i+1
.endr
.base oldaddr
That works with asm6 and it was found inside asm6's README.TXT. That sets the first page of MMC1's PRG-RAM. You said we could use this to do addition faster and you also said we could use it to replace
txa tay, but
ldy $6000,x takes 3 bytes and 4 cycles and
txa tay takes 2 bytes and 4 cycles. Is this correct?
In the beginning of the quest to create this page of memory with as few instructions as possible we started with this:
Code:
oldaddr=$
.base $6000
.align 256, <$
.base oldaddr
, but that just fills the first page with 00s because the PC doesn't increment after each byte written.
edit: these don't use instructions, so I'm really happy
But, if there is a way to make the PC increment by one after each byte written that would be extremely cool!
YEAY!!
I'm so blessed to find this works too:
Code:
oldaddr=$
.base $6000
.rept 256
.db <$
.endr
.base oldaddr
unregistered wrote:
Code:
oldaddr=$
.base $6000
i=0
.rept 256
.db i
i=i+1
.endr
.base oldaddr
This doesn't look right... It does compile, but maybe it isn't doing what you think it is. The .base statement won't assemble to a different location (not that you could assemble to RAM anyway, which $6000 usually is), it just changes the value of the PC for labels that follow it. You're not even using any labels here, so this will just output 0 through 255 at the current location. Those .base statements are doing basically nothing. ASM6 assembles code sequentially, you can never output to a different location like you can when using segments in ca65, for example.
Quote:
That works with asm6 and it was found inside asm6's README.TXT. That sets the first page of MMC1's PRG-RAM.
Like I said above, these values are not going to PRG-RAM. To put things in RAM you have to use 6502 code at runtime, you can't use compile-time commands. You need a good old 6502 loop to put this 256-byte table of sequential values in RAM:
Code:
ldx #$00
Loop:
txa
sta $6000, x
inx
bne Loop
Quote:
You said we could use this to do addition faster and you also said we could use it to replace
txa tay, but
ldy $6000,x takes 3 bytes and 4 cycles and
txa tay takes 2 bytes and 4 cycles. Is this correct?
Individual operations may not always be faster, but consider that you may be using the accumulator for something else, and saving/restoring it could take 6 to 8 cycles, so being able to do some operations without involving the accumulator could result in savings in the end.
unregistered wrote:
YEAY!!
I'm so blessed to find this works too:
Code:
oldaddr=$
.base $6000
.rept 256
.db <$
.endr
.base oldaddr
Yeah, it works, but the values are going to ROM, not to PRG-RAM $6000. RAM on the NES is never populated automatically, it can only be done through 6502 code.
Yes, now I remember you explaining about RAM needing to be loaded with 6502 instructions a long time ago. Thank you so much tokumaru for teaching me again!
My ROM just had a gray screen forever after enabling MMC1's PRG-RAM so I just disabled it and it works great now. Also, I moved my code near the end of each of the first 15 banks and changed it to:
Code:
.pad $BE00
.rept 256
.db <$
.endr
and it works awesomely now!!
edit: Honestly, I made the mistake of just looking at the .lst file and it showed the values written to $6000 just like it always shows the values written to ROM. I hadn't gotten our game to work yet so I'm sorry and I will try to be more careful in my postings in the future.
tokumaru wrote:
Quote:
You said we could use this to do addition faster and you also said we could use it to replace
txa tay, but
ldy $6000,x takes 3 bytes and 4 cycles and
txa tay takes 2 bytes and 4 cycles. Is this correct?
Individual operations may not always be faster, but consider that you may be using the accumulator for something else, and saving/restoring it could take 6 to 8 cycles, so being able to do some operations without involving the accumulator could result in savings in the end.
That is an excellent point that I didn't remember; thank you so much tokumaru!!
That will definitly result in savings!
This idea is so helpful, thank you for sharing it with all of us!
unregistered wrote:
Honestly, I made the mistake of just looking at the .lst file and it showed the values written to $6000 just like it always shows the values written to ROM.
Yeah, the listing will show $6000 because you told the assembler that the PC for that part was $6000, but that doesn't mean the code will actually end up at $6000.
Contrary to what may seem at first, commands like .org and .base don't force data into a particular address, they just let the assembler know where the code will be loaded so that labels and stuff have the correct values during execution.
NES programs don't start with
.org $8000 because that causes the code to be put at $8000. The code will be loaded into $8000 no matter what, even if you write
.org $3A9B instead, but all the labels will be wrong and the program won't function correctly.
EDIT: BTW, I said that .base wasn't doing anything in the code you posted, but now that I think of it, it's probably causing your ROM to be 256 bytes longer than it should, making it invalid. I believe that's the case because later you reset the PC to the value it had before the table, so the table is inserted into the ROM but the space it takes is not taken into consideration by the assembler since you manipulated the PC for that part.
tokumaru wrote:
unregistered wrote:
Honestly, I made the mistake of just looking at the .lst file and it showed the values written to $6000 just like it always shows the values written to ROM.
Yeah, the listing will show $6000 because you told the assembler that the PC for that part was $6000, but that doesn't mean the code will actually end up at $6000.
Contrary to what may seem at first, commands like .org and .base don't force data into a particular address, they just let the assembler know where the code will be loaded so that labels and stuff have the correct values during execution.
NES programs don't start with
.org $8000 because that causes the code to be put at $8000. The code will be loaded into $8000 no matter what, even if you write
.org $3A9B instead, but all the labels will be wrong and the program won't function correctly.
EDIT: BTW, I said that .base wasn't doing anything in the code you posted, but now that I think of it, it's probably causing your ROM to be 256 bytes longer than it should, making it invalid. I believe that's the case because later you reset the PC to the value it had before the table, so the table is inserted into the ROM but the space it takes is not taken into consideration by the assembler since you manipulated the PC for that part.
Thank you so much tokumaru; that is extremely helpful to learn.
Returned early in the morning because I wanted to say that the code has been moved into its own file and I placed the
.incsrcs at the top of each bank so that there wouldn't be 240 bytes of nothing between the end of that code I posted above (the beginning of page $BF) and the start of
-reset_stub. And I also wanted to say that while laying in my bed I remembered
this very helpful post where booker did a superb job of helping me to grasp the RAM/ROM issue that you, tokumaru, explained to me again.
edit: tokumaru,
here is where you recommended me to use -reset_stub. If you scroll up there is the code found on the nesdev wiki, a link to that page, and some conversation about the problem I was having. This is linked here to help y'all to fully understand this post.
I'm sure the linked post can't be found using this fourm's search function because it refuses to search through old (2012) posts. Hope you see this edit.
edit2 20171006: thought it would be good to say that the code I mentioned "posted above" was reduced to
Code:
.rept 256
.db <$
.endr
because the beginning of the first 15 banks in our game always start at $8000
CLV clears V. Simple enough. It is most frequently used in conjunction with a BVC to simulate a forced branch (a.k.a a branch always) in relocatable routines.
What is a relocatable routine?
jmp is 3 bytes big and takes 3 cycles, while
clv bvc + is 3 bytes big and takes 4 cycles, I think (if it doesn't branch to different page). So a relocatable routine must be pretty useful?
unregistered wrote:
What is a relocatable routine?
jmp is 3 bytes big and takes 3 cycles, while
clv bvc + is 3 bytes big and takes 4 cycles, I think (if it doesn't branch to different page). So a relocatable routine must be pretty useful?
A relocatable routine is one that doesn't use hardcoded/absolute addresses (this rules out JMP), so it can be placed anywhere in memory.
Are macros relocateable routines then? I'm using a
jmp +endmR inside a macro and I guess that works because + and - lables can be repeated.
(It only takes 3 cycles!
)
Macros are
inlined routines. If a macro contains a self-reference with absolute addressing mode, the subroutine it is used in is not relocatable. An example of a macro with such a self-reference is a macro for generating a bus-conflict-compliant bank switch:
Code:
.macro set_imm_bank value
lda #value
sta *-1 ; overwrite the instruction byte with itself
.endmacro
unregistered wrote:
Are macros relocateable routines then?
No, macros are just a convenience for programmers so they don't have to type common sequences of commands over and over. Each time you use a macro, the code it contains is written to the ROM in full, and any labels defined inside it will be absolute for the specific place where you used the macro.
tepples wrote:
Macros are
inlined routines. If a macro contains a self-reference with absolute addressing mode, the subroutine it is used in is not relocatable. An example of a macro with such a self-reference is a macro for generating a bus-conflict-compliant bank switch:
Code:
.macro set_imm_bank value
lda #value
sta *-1 ; overwrite the instruction byte with itself
.endmacro
Thanks tepples, I didn't know the word 'inline'.
And would your code, in asm6, be:
Code:
.macro set_imm_bank value
lda #value
sta $-1 ;overwrite the instruction byte with itself
.endm
? After searching asm6's README.TXT there was only one * character so I don't know what the * symbol means. If it should be '$' that would overwrite the third byte of the macro, how would this macro switch banks?
tokumaru, thanks, I guess I should search the wiki for 'relocatable routine'... I can't grasp why or how a relocatable routine would work.
edit: the wiki doesn't contain "relocatable code"
unregistered wrote:
And would your code, in asm6, be:
Yes, $ in ASM6 is the equivalent of * in ca65. It translates to the current value of the program counter. For example, this:
Code:
.org $8000
.db $00
.dw $
Assembles into:
Code:
$00, $01, $80
Quote:
If it should be '$' that would overwrite the third byte of the macro, how would this macro switch banks?
Nothing gets overwritten, because it's ROM. Several discrete logic mappers (U*ROM, A*ROM, and many others) switch banks on writes to PRG-ROM, and to avoid bus conflicts, the ROM location being written to must contain the same value that's being written. Using $-1 is just a clever way to address such a location, since the argument for the instruction that loaded the value into the accumulator is stored at that address.
Quote:
tokumaru, thanks, I guess I should search the wiki for 'relocatable routine'... I can't grasp why or how a relocatable routine would or should work.
Don't worry about relocatable code, it's very unlikely that you'll ever need it for NES development. I can't think of any scenario where it would be useful in NES games.
After thinking about your words for a long while: Ahha! The program counter must, after each instruction, increase by the number of bytes of the previous instruction. So if the code was:
Code:
.macro set_imm_bank value
lda #value
sta $-1
.endm
.pad $c100
set_imm_bank 3
It would try to assemble to some rom with
A5 03 85 03 at $c100, because the PC started at $C100, the first instruction was assembled, the PC increased by 2... $C100 + 2 = $C102, and then the second instruction is assembled with a
03 at $C103 because the PC, $C102, -1 ($C101) holds
03, and then the PC increases to $C104. Right?
MMC1 isn't one of those discrete mappers right? It takes a lot of code to switch banks on MMC1, I believe.
Thank you so much tokumaru!!
I won't worry about relocatable code.
You got some of that right. If I'm not mistaken, the assembled binary at $c100 would actually be
A9 03 8D 01 C1. LDA immediate is $A9, followed by an 8-bit value, while STA absolute is $8D, followed by a 16-bit address (low byte first).
unregistered wrote:
MMC1 isn't one of those discrete mappers right?
No, and it's not subject to bus conflicts, so there's no need for the whole "write value to a location that contains the same value" thing.
Quote:
It takes a lot of code to switch banks on MMC1, I believe.
I personally think the MMC1 is one of the worst mappers ever created... it's so awkward and slow to write to, and offers little advantage over the common discrete logic mappers. The CHR switching sucks (4KB banks, really? If you can only change ALL sprite or ALL background tiles at once, that pretty much means it can only be done on screen transitions, so you might as well use CHR-RAM and have the freedom to mix and match tiles or even animate them during gameplay), the mirroring switch is hardly useful (you can code even 8-way scrolling with hardwired mirroring if you know what you're doing, and that's more versatile than 4-way scrolling), and the battery-backed PRG-RAM can be added to any cartridge with just a few discrete components.
If discrete logic mappers aren't enough for a project I'm working on, I'd rather make the jump to MMC3-level mappers than working with that abomination that's the MMC1.
I get the impression from "Why Your Game Paks Never Forget", an article in the third year of
Nintendo Power, that Nintendo wanted to save pins, presumably to reduce PCB space and soldering time. The article compares "Logic Gates" to a buffet dinner and Memory Management Controllers (MMCs) to skipping to dessert.
- UNROM + WRAM is three narrow DIPs: a 74161 (16 pins), a 7432 (14 pins), and a 7420 (14 pins). That's a total of 44 pins to solder down.
- MMC1 is a 24-pin shrink DIP.
- A hypothetical parallel MMC1 would need 27 pins, replacing the CPU D7 connection with D4-D1.
The 4K mode of MMC1 lets you do CHR rotation on the background but not on the sprites. Or if the background doesn't use all 256 tiles, it lets you stash some level-specific 8x16 sprites in the background side of the pattern table. while keeping the general sprites in the sprite side.
tepples wrote:
A hypothetical parallel MMC1 would need 27 pins, replacing the CPU D7 connection with D4-D1.
And we'd call it the VRC1.
tokumaru wrote:
You got some of that right. If I'm not mistaken, the assembled binary at $c100 would actually be A9 03 8D 01 C1. LDA immediate is $A9, followed by an 8-bit value, while STA absolute is $8D, followed by a 16-bit address (low byte first).
Oh, another mistake on my part. Sorry.
A9 03 8D 03 C1 would be correct? (I don't understand why $c103 would be #$01
)
tokumaru wrote:
unregistered wrote:
MMC1 isn't one of those discrete mappers right?
No, and it's not subject to bus conflicts, so there's no need for the whole "write value to a location that contains the same value" thing.
Quote:
It takes a lot of code to switch banks on MMC1, I believe.
I personally think the MMC1 is one of the worst mappers ever created... it's so awkward and slow to write to, and offers little advantage over the common discrete logic mappers. The CHR switching sucks (4KB banks, really? If you can only change ALL sprite or ALL background tiles at once, that pretty much means it can only be done on screen transitions, so you might as well use CHR-RAM and have the freedom to mix and match tiles or even animate them during gameplay), the mirroring switch is hardly useful (you can code even 8-way scrolling with hardwired mirroring if you know what you're doing, and that's more versatile than 4-way scrolling), and the battery-backed PRG-RAM can be added to any cartridge with just a few discrete components.
If discrete logic mappers aren't enough for a project I'm working on, I'd rather make the jump to MMC3-level mappers than working with that abomination that's the MMC1.
Well, my sister has already designed our game's graphics knowing about the 4KB CHR banks so I think MMC1 will work just fine for us.
Our game uses just 2-way scrolling, horrisontal. And it's not subject to bus conflicts! That's good to know, thank you tokumaru!
tepples, that's a lot of interesting info and I don't understand all the numbers and that's ok, your brain is tremendous!
The MMC1 was efficently built; it isn't very user friendly, but it is great for our game.
unregistered wrote:
tokumaru wrote:
You got some of that right. If I'm not mistaken, the assembled binary at $c100 would actually be A9 03 8D 01 C1. LDA immediate is $A9, followed by an 8-bit value, while STA absolute is $8D, followed by a 16-bit address (low byte first).
Oh, another mistake on my part. Sorry.
A9 03 8D 03 C1 would be correct? (I don't understand why $c103 would be #$01
)
When you use $ or *, that translates to the PC value at the beginning of the current line, since the PC doesn't change until the instruction is output. Your example started at $c100. The first instruction,
lda #3, uses 2 bytes, so the PC for the next line is $c102. So when you do
sta $-1, $-1 evaluates to $c102-1, or $c101.
Quote:
The MMC1 was efficently built; it isn't very user friendly, but it is great for our game.
I'm not against other people using the MMC1, it's just that I don't like it, mainly because of the time it takes to switch banks, since some of my engines have to do it dozens of time per frame.
tokumaru wrote:
When you use $ or *, that translates to the PC value at the beginning of the current line, since the PC doesn't change until the instruction is output. Your example started at $c100. The first instruction, lda #3, uses 2 bytes, so the PC for the next line is $c102. So when you do sta $-1, $-1 evaluates to $c102-1, or $c101.
I agree, that makes sense.
Thank you tokumaru.
tokumaru wrote:
Quote:
The MMC1 was efficently built; it isn't very user friendly, but it is great for our game.
I'm not against other people using the MMC1, it's just that I don't like it, mainly because of the time it takes to switch banks, since some of my engines have to do it dozens of time per frame.
That's cool; I didn't even know that was possible. Sounds extravagant!
We're only going to have to switch banks during a few of the levels when changing floors. So the longer bank switch time won't be too bad, I think. Changing floors takes like 2 frames so it definitly won't happen multiple times in one frame.
edit (@ ~11pm here): Was talking about switching prg banks... and maybe it will have to be done more often if we run out of room for prg code in our fixed prg bank #15. Chr banks will have to be switched every level change and for some other things. But, that's in-between levels so it should be ok.
VRC1, Bisqwit's MMC4 subset (doesn't use tile switching), and NINA-001 seem to all be reasonable ways to get 4+4 CHR banking with PRG banking, for less overhead than MMC1.
Other than the complication of possibly needing to handle interrupting MMC1 writes, I'm not clear the extra 4×(4+2)=24 cycles per bankswitch is particularly worth caring about, though.
lidnariq wrote:
I'm not clear the extra 4×(4+2)=24 cycles per bankswitch is particularly worth caring about, though.
Depends on how often you switch banks. If you do it 20 times or more per frame, it really starts to add up! 20 x 24 = 480 cycles, which's 1.7% of the 240 scanlines of picture. And it should actually be a little slower than that, because if you switch banks in the NMI for playing music, for example, the bankswitching routine used in the main thread has to do some additional steps to verify whether it was interrupted.
unregistered, on top of page 92 wrote:
edit2 20171006: thought it would be good to say that the code I mentioned "posted above" was reduced to
Code:
.rept 256
.db <$
.endr
because the beginning of the first 15 banks in our game always start at $8000
Just wanted to say that this code was reduced by one more character:
Code:
.rept 256
.dl $
.endr
that works with asm6 assembler.
edit: fills a page of ROM with the low byte of the address. This helps implement tokumaru's idea (allows
txy using
ldy $8000, x and
tyx using
ldx $8000, y; takes an extra byte when compared with
txa tay or
tya tax, but tokumaru says his idea doesn't use the accumulator and that may result in saving space when not having to save and restore the accumulator; after rewriting the sections of code where this might be used, it is beneficial for me to use
txa tay so haven't been able to use this yet) without using assembly instructions.
tokumaru, on page 43, wrote:
Setting the scroll should ALWAYS be the very last thing in your VBlank handler.
This is not true for our game.
The end of my VBlank handler looked something like this:
Code:
jsr update_vram
;modify the flag
inc FrameReady
SkipUpdates:
jsr FamiToneUpdate
;"Setting the scroll should ALWAYS be the very last thing in your VBlank handler." -tokumaru pg 43
lda needscroll
beq +end
lda FORWARD_scroll
beq +
jsr scroll_screen
jmp +end
+ jsr scroll_screen_left
+end ;return from the NMI (vblank)
pla
tax
pla
tay
pla
rti
Now, after being blessed with fixing a lot of the problems we found, I turned on the music and the screen started being lowered two rows (16 bits) whenever draw_our_column was being called. And that reminded me of tepples' guidance, that
t gets clobbered everytime $2006 and $2007 are written to and so I should be sure to write $2000 and $2005 afterwards to fix
t (his post is linked to at top of page 91). After checking, it was clear that $2000 and $2005 were being written to, inside a scroll_screen, every frame $2006 and $2007 were being written to, somewhere inside update_vram, and so it seemed that
jsr FamiToneUpdate was taking too long. So after finding
this earlier advice from tokumaru also on page 43 I tried moving
jsr FamiToneUpdate to a spot right after
+end because it doesn't include PPU operations. The game works and the screens don't lower 16 pixels for instances after draw_our_column is called inside update_vram while the music is playing!!
edit: tokumaru, I understand now that there was a hidden reason behind your words that are quoted above... posted this to help others who read your quoted statement.
I really respect you tokumaru; thank you so much for all of your fantastic help and ideas!
edit3: changed a "to" to "too" and changed text referring to page 92 to refer to page 91. Two mistakes corrected
Correcting myself: setting the scroll should be the last PPU-related thing in your vblank handler. And you must keep track of how much time your vblank handler takes, because the scroll has to be set before the vertical blank ends. After setting the scroll, you're free to do things that do not affect rendering, such as playing music, reading the controllers, and whatever else you need to do at 60/50Hz.
If you know what you're doing, you can do a number of PPU operations outside of vblank, but if you're not sure, it's better to ask.
^Thanks tokumaru!!
tokumaru, your page of low bytes of the address, it has helped me to save two cycles and a zero page variable!
in some code like this:
Code:
;sty ejectLRvalue
;code affected by y here
lda altoX
ldx FORWARD
beq +zero
clc
adc $8000, y;will add whatever value is in y :) ;was adc ejectLRvalue
;rest of addition to 16bit value altoX here
jmp DrawSprite
+zero
sec
sbc $8000, y ;was sbc ejectLRvalue
;rest of subtraction from 16bit value altoX here
DrawSprite:
adc $8000, y takes 4 cycles and is 3 bytes big
adc ejectLRvalue took 3 cycles and was 2 bytes
but, being able to comment the
sty ejectLRvalue saved 3 cycles and 2 bytes!
So the code is the exact same size with the changes and it is 2 cycles faster!
(it will never run both sides of the branch)
Code:
0C1FC A6 60 ldx FORWARD
0C1FE 4C 01 C2 jmp +
0C201 .pad $c201
0C201 F0 0F + beq +zero
You know how branches start with the following address and then add the second byte to reach the address branched to? Like, the
beq +zero will branch to 0C212 and so it takes the next address, 0C203, and adds 0F and reaches 0C212.
Does jmp act the same way? No, I think, because why would the 6502 waste time thinking about the next address? Just set the PC to the address in bytes three and two. If so, then our game would be frustrated, haha
, cause it wouldn't really jump anywhere.
But, the
jmp, being 3 cycles, is faster than two
nops, 4 cycles. For me, it is important that
beq +zero not branch to a different page. And, if bytes are ever removed from before 0C1FC, the code would still work fine.
edit.
unregistered wrote:
You know how branches start with the following address and then add the second byte to reach the address branched to?
That's called relative addressing. Since most conditional jumps don't need to go very far, the 6502 was designed to make these jumps smaller and faster, by encoding the target address as an 8-bit displacement rather than a 16-bit absolute address. The downside of that is that when you do have to conditionally jump to an address that's far away, you have to branch (testing for the opposite condition) over a JMP instruction.
Quote:
Does jmp act the same way? No, I think, because why would the 6502 waste time thinking about the next address? Just set the PC to the address in bytes three and two.
JMP uses absolute addressing, so the actual destination address is the instruction's operand.
Quote:
For me, it is important that beq +zero not branch to a different page.
People normally use macros to ensure that no unwanted page-crossing happens... In ca65 it's trivial to write a macro that checks whether a branch instruction's address is in the same page as its destination address, and throws an error/warning if it isn't.
I personally tend to put code with this kind of sensitive timing near the start of the bank, where it's easier to predict where things will end up, and there are less things changing and shifting addresses.
Relative and Absolute addressing... thank you tokumaru, I forgot about those terms.
A macro that gives an error if the branch crosses a page? That's interesting; thanks for introducing me to that!
The assembler always gives an error if the code before a
.pad overflows the pad address... so that has been working well for me.
Branches used at the start of pages is a great idea, that's what I try to do too; this branch is in the middle of my main loop so it just needs to be moved to the next page. My knowledge of code that needs to have perfect timing is pretty vague. It really helps sometimes when branches are moved/changed to prevent page-crossings so all of my branches are imprisoned within their respective pages. After doing that for a while it's not hard, or painful, and just part of the journey.
shiru, in famitone's readme, wrote:
- Only Volume, Arpeggio, and Pitch envelopes (no Pitch for noise channel) supported
shiru, in famitone2's readme, wrote:
- Only Volume, Arpeggio, and Pitch sequences (no Pitch for noise channel) supported
Does that change from "envelopes" to "sequences" mean that shiru has added support, in famitone2, for using the "Duty / Noise" effect when "#" is
1? For me a sequence implies 2 or more; I'm hoping shiru thinks the same way.
I don't have famitone2 set up; I tried to set it up once, but failed to get it to act normally; have set up a new instrument in famitracker 0.4.6 and checked the box next to "Duty / Noise" so that the same notes following each other, when assigned different instruments, are recognizable; sounds pretty cool; hence my question.
edit: I guess the answer is no because the window is titled "sequence editor" and so I have a sequence of 1 and "Duty / Noise" sequences aren't supported.
I understand "sequence" and "envelope" in FamiTracker to refer to the same concept. FamiTone2 supports only sequences of length 1 in Instrument editor > Instrument settings > Duty / Noise > Sequence editor. Some other NES audio drivers, including some that can import from FamiTracker, do not have this limit.
^tepples, that's great news, THANK YOU SO MUCH!
So, if "sequence" and "envelope" refer to the same concept in famitracker... does famitone support a "Duty / Noise" sequence of length 1 too? I'll try that tomorrow.
As far as I can tell, in both major versions, a "Duty / Noise" sequence has a maximum length 1. This means instruments' attacks are slightly less interesting than they'd otherwise be. You can't, say, make a piano patch that starts at duty 2 and switches to 1, or a brass patch that starts at 1 and switches to 0, or a hi-hat patch that alternates duty 0 and 1.
Two years ago, I ran a
poll about which features not in FamiTone2 are most important to composers. The winners were "Cymbal duty" and "Pulse duty sweep".
Wow, tepples, after following your link to your poll, I understand now that FamiTone failed to include the 1/8 1/4 1/2 pulse duty sweep; but, FamiTone does include setting a "Duty / Noise" sequence to 1 and that's exactly what I need!!
Thank you so much for your replies! You seem to be extremely experienced in forming an exhaustive range of sounds on the NES!
I turned up the sound on Thwaite, never have played it with loud volume, and your sound expertise is so much fun to listen too!
Why doesn't FamiTone (or FamiTone2) support Bxx forward references? It's crucial for the song that was just finished. Why must FamiTone songs always require full frames? It's so restrictive and unfair, to me.
edit: FamiTone supports Bxx backward references, if that is the correct term, so how much bigger would it have to be to support forward references? Could I easily add that ability to Famitone? Well, maybe no cause I bet I'd have to rewrite the
TextExporter.dll and changing a dll file doesn't make sense to me.
edit2: There is only one Bxx forward reference in the song... it happens at the end of frame 02, but the third frame has Pulse 1 04 and that's repeated in another frame so can't simply move the notes backwards to fill up the rest of Pulse 1 04.
final edit: Does Famitone support just 1 forward reference? Maybe my file isn't being exported by TextExporter because Pulse 1 starts with 01 instead of 00? My mistake.
Bxx means jump to a position in the order table. In order to best accomplish what you're trying to do, I'd like to see a .ftm file representing a simplified use case where forward Bxx is helpful? If you do, perhaps one of us can help you work around the lack of forward Bxx.
Well, I was blessed with the song using a keyboard/piano. And I wrote down all the notes and transferred them to the computer. It sounds best to me if the last note on frame 02 in Pulse 1 and Pulse 2 is a whole note. That leaves about a dotted quarter note unused when using the forward Bxx. But, maybe I can remove the forward Bxx and somehow fill the dotted quarter note worth of space. You did not answer any of my questions tepples, but thank you for your reply.
Figured out, after some good work, that I receive the same error,
Unhandled exception C0000005, dispite remapping the entire song so that all channels start with 00.
FamiTone2 does support D 00 (According to the readme, I haven't tried), which may or may not accomplish what you want? Like Tepples said, if you could provide an ftm that shows that kind of jump you want to do, it'd be helpful for us to figure out a good workaround. It doesn't have to be the actual song.
Hi Kasumi! How are you?
After removing the forward Bxx and filling the small space that appeared it still gives the same error
FamiTrackerThis application has encountered a problem and needs to close.Unhandled exception C0000005. The finished song we are trying to import into our game has:
Song settings in FamiTracker 0.4.6 wrote:
Speed 6
Tempo 150
Rows 65
Frames 10
Does 65 Rows create Unhandled exception C0000005? Guess I should look at the FamiTracker site.
I'm alright.
So what exactly are you doing? You have to be super specific. That famitracker itself is crashing is interesting, but I have no idea what you're actually doing to cause the crash.
Anyway, gifs, because lack of ambiguity.
Step one, export your text:
Is Famitracker crashing on that step or are you doing something different? If you were doing something different, do that instead.
Step two, drag the exported text onto text2data.exe included with FamiTone2
And if you get no output from dragging, you do have to run it in the commandline to see what it's complaining about. But it seems like your issue is happening before then.
Unhandled exception C0000005 is an access violation. Learned that after searching on duckduckgo.
This site says something about deleting instruments used to result in crashes. Well, maybe if I remove all of the 6 instruments and recreate them C0000005 will go away. I remember using
Instrument>Clone instrument and then editing some of the clones... maybe that caused the problem.
@Kasumi: thank you for your visual help, just saw it... will work on this tomorrow.
Kasumi, thank you so much for the visual instruction!
I was following the instructions in famitone's readme.txt. Doing it your way successfully created the .txt file! Then text2data didn't produce a .asm file so followed your advise and ran it in Command Prompt and got this message:
text2data in Command Prompt wrote:
song
Output format: Asm6
Parsing...
Global parameters
Can't find any frames
After looking at the .txt file it has PATTERN 00 through PATTERN 08. But, I thought the PATTERNs were "frames".
So could you help me again? (I don't understand.)
edit: Does "Can't find any frames" mean that I messed up when I tried to make the data as small as possible? Like the orders at the top are like:
Quote:
ORDER 00: 00 00 00 00 00
ORDER 01: 01 00 00 01 00
ORDER 02: 02 01 01 02 00
ORDER 03: 03 02 00 03 00
ORDER 04: 04 00 00 04 00
ORDER 05: 05 00 00 05 00
ORDER 06: 03 02 00 03 00
etc.
I'm using FamiTone.
I'd recommend you just use FamiTone2. As
Shiru's website says,
Quote:
FamiTone v1.24 (112K)
Audio library for music and sound effects. This version is fully obsoleted by FamiTone2, and is not recommended to use.
Ok Kasumi, FamiTone2 is playing the song in our game now!!
Good advice, FamiTone2's text2data.exe is much newer than FamiTone's.
3 notes:
1.) Following your visual example of dragging the .txt file over FamiTone2's text2data.exe created the .asm file in NESASM format. Needed to use the Command Prompt and add the -asm6 flag. Is that a "flag"?
2.) My other music is in FamiTone's text2data format... it must be possible to convert that into an nsf and then convert that nsf into data with FamiTone2's text2data, right? I don't have the FamiTracker file anymore for one of those songs.
3.)
The song's tempo is a bit faster than it was in FamiTracker. Is that normal? Or must something be fixed in my code? Could just lower the tempo in FamiTracker, I guess, but don't know if that would be the best solution.How are sub songs added? Right now there is just sub song 0.
edit.
Quote:
Needed to use the Command Prompt and add the -asm6 flag. Is that a "flag"?
Yes. Also sometimes called a switch.
Quote:
2.) My other music is in FamiTone's text2data format... it must be possible to convert that into an nsf and then convert that nsf into data with FamiTone2's text2data, right? I don't have the FamiTracker file anymore for one of those songs.
You could
create an NSF with the FamiTone data you have (assuming its error free), and then use something like
nsfimport to get an ftm again. But it would be notably different from what you started with. (No instruments, Speed 1, the resulting FamiTone2 export would probably be huge if it could be exported at all.) You'd probably still want to rebuild your original .ftm from that output. Maybe that's easier than rebuilding it by just reading the .asm and maybe it's not...
I think the better bet would be to write a program to convert FamiTone's text2data info to FamiTracker's txt format and then use File, Import text... within FamiTracker. But I don't think there's really gonna be a simple solution to get that music back. You'll likely have to write a difficult program, or do lots of manual transcription.
Quote:
3.) How are sub songs added? Right now there is just sub song 0.
Add them to the famitracker file and they're automatically included in the export txt.
edit:
Quote:
3.) The song's tempo is a bit faster than it was in FamiTracker. Is that normal? Or must something be fixed in my code? Could just lower the tempo in FamiTracker, I guess, but don't know if that would be the best solution.
Tempos differing slightly will be par for the course. One of the better things you can do is leave tempo at 150 and only change speed for future songs. You can read some of the why of this here: http://famitracker.com/forum/posts.php? ... &pid=15140 but only speed changes doesn't really cover the whole range of things you might want to do. There are some things where you'll just have to deal with it.
Edit: Well, wait, reading your previous post it seems like you're totally using tempo 150 and speed 6. So... ignore the above, I guess?
But also, make sure you're calling FamiToneInit correctly. If the value in A before you call it is 0, the song will play slightly faster. (Update code for 50 FPS being played at 60FPS) But the pitch would also be wrong, so it may not be this.
Kasumi, thank you for your helpful response! Wanted to apologize for making a mistake in note 2. It should have referred to converting the nsf file to FamiTone2 data with FamiTone2's
nsf2data.exe. I've never used it, but noticed it sitting there in the tools folder and thought, "that would be really cool if that worked; I'll ask Kasumi about it," but I failed when writing out my previous post.
Will reply to the rest after game work begins tomorrow.
Ah. If you have an NSF of the old song, that's a little better. You do still kind of have to rebuild your song from an NSFimported version:
http://rainwarrior.ca/projects/nes/nsfimport.htmlnsf2data unfortunately won't help you. That's for sound effects, and it's limited to 256 bytes of data. Even something very short like the Super Mario Bros. death jingle is too long for it.
Edit: Well, I guess I should not be surprised that since there is an NSF importer, there is also a famitone NSF decompiler. This is an older version since the link is dead on the newer version:
http://famitracker.com/forum/posts.php?id=5483 Drag your NSF onto it and you may just get your FTM back.
Kasumi wrote:
Ah. If you have an NSF of the old song, that's a little better. You do still kind of have to rebuild your song from an NSFimported version:
http://rainwarrior.ca/projects/nes/nsfimport.htmlnsf2data unfortunately won't help you. That's for sound effects, and it's limited to 256 bytes of data. Even something very short like the Super Mario Bros. death jingle is too long for it.
Edit: Well, I guess I should not be surprised that since there is an NSF importer, there is also a famitone NSF decompiler. This is an older version since the link is dead on the newer version:
http://famitracker.com/forum/posts.php?id=5483 Drag your NSF onto it and you may just get your FTM back.
It turns out, all I have is FamiTone's asm file of "musicA". Searched for a way to convert that file into a FamiTone2 asm file, but did not succeed in learning anything. The setup of the asm files is different for FamiTone and FamiTone2, which makes sense because FamiTone2's readme has Shiru writing about how the new asm files are smaller.
I have the external harddrive where musicA's .ftm file was but that hd doesn't work anymore. Would be excellent if FamiTone2 could be backwards compatible. Now I have a FamiTone song and a FamiTone2 song. Could the FamiTone2 song become a FamiTone song if I downloaded an older version of FamiTracker?
Is there a way to convert my FamiTone asm file into a FamiTone2 asm file?
There is likely no existing and automatic way to do any of that.
Kasumi wrote:
There is likely no existing and automatic way to do any of that.
Maybe the external harddisk could be repaired... I think there was a power outage and afterwards it stopped working. My computer was plugged into a powerstrip so maybe all the 1s and 0s are still there. Going to try
getting that
done; that feels like the right thing to do. Well, thanks again Kasumi for all of your much appreciated help!!
edit.
Kasumi wrote:
Quote:
3.) The song's tempo is a bit faster than it was in FamiTracker. Is that normal? Or must something be fixed in my code? Could just lower the tempo in FamiTracker, I guess, but don't know if that would be the best solution.
Tempos differing slightly will be par for the course. One of the better things you can do is leave tempo at 150 and only change speed for future songs. You can read some of the why of this here: http://famitracker.com/forum/posts.php? ... &pid=15140 but only speed changes doesn't really cover the whole range of things you might want to do. There are some things where you'll just have to deal with it.
Edit: Well, wait, reading your previous post it seems like you're totally using tempo 150 and speed 6. So... ignore the above, I guess?At the top the tempo is 150 and the speed is 6, but after adding
F07 to the fx1 column at the top of Pulse 1, the song sounds just fine now!!
Guess my FamiTone2 speeds the song up by 1 for some reason.
(what I learned (for others): FamiTracker uses an inversed speed setting... raising the speed setting to 7 actually slows the song. And "speeds the song up by 1" means the speed setting must have been set to 5 tempo must be increased by my FamiTone2. )edit: maybe the speedup is a result of using 65 rows?edit2: Well, maybe not; because 65 rows just makes each frame a 16th note longer and the length of the song has nothing to do with how fast it is played; at least that's what occured to me after reading and rereading the page you linked to, Kasumi. Thank you so much for that link!! final edit.edited again: strikeout and italics; it makes more sense, now, I think.
unregistered wrote:
Kasumi wrote:
There is likely no existing and automatic way to do any of that.
Maybe the external harddisk could be repaired... I think there was a power outage and afterwards it stopped working. My computer was plugged into a powerstrip so maybe all the 1s and 0s are still there. Going to try
getting that
done; that feels like the right thing to do. Well, thanks again Kasumi for all of your much appreciated help!!
edit.I've been so blessed!!
The harddrive started working after the repair shop guy plugged it in! Have the old famitone files now too and after following your instruction Kasumi, now there's a FamiTone2 asm file holding both songs!
And that
D00 you suggested works wonderfully! Thank you so much Kasumi!
Both modules need to start with
F07 for them to play at a FamiTracker-normal speed. : )
The section at the top of my FamiTracker's .txt music file lists 14 MACRO lines, but there are only 7 instruments. Are all of those lines needed? I see that some of them list the volume settings that some of the instruments use and two others list the arpeggios of two of the instruments, but there is:
a MACRO line at the bottom wrote:
Code:
MACRO 4 3 -1 -1 0 : 1
That 1, after the colon, must be for Duty 1, but I removed all of the Duty 1s from the song. Does FamiTracker not remove things like that even after a user has removed them? If so, is there a way to clear and rebuild its MACRO memory?
You are correct in guessing that the duty, arpeggio, and volume of an instrument count as separate envelopes. The general form of an envelope is as follows, where the
asterisk represents an element repeated 0 or more times:
Code:
MACRO type index looppoint releasepoint arpstyle : value value*
The
type parameter means the following:
- MACRO 0 is volume
- MACRO 1 is arpeggio, where arpstyle is 0 for absolute, 1 for fixed, or 2 for relative
- MACRO 2 is pitch
- MACRO 3 is hi-pitch (rarely used)
- MACRO 4 is duty[1]
Thus
MACRO 4 3 -1 -1 0 : 1 means "Duty envelope 3, no loop point, no release point, value 1".
Try "Edit > Cleanup > Remove unused instruments" before exporting the text file. Does that remove envelopes not used by any instrument as well? If not, let me know, and I'll post it on the issue tracker section of FamiTracker forums and HertzDevil forums.
[1] In an FTM authored for FamiTone2, each duty envelope will have only one value. FamiTone reads only the first value from each duty envelope, whereas Pently reads as many steps of duty as there are steps in the volume envelope.
^Thanks so much tepples!
I think you gave me the same response before; sorry I forgot about the "Edit>Cleanup>Remove unused instruments". Spent a while learning, by trial and error, how to manually remove all the extra envelopes, in FamiTracker, and now my file is down to having only 7 MACROS!
FamiTracker should have a "Edit>Cleanup>Remove unused sequences"... because the only way to remove that
MACRO 4 line was to double-click on instrument 00, click on "Duty / Noise", increase the "Sequence #" box until the "Sequence editor" value box read
1, delete that value, press enter, decrease the "Sequence #" box back down to where it started, and uncheck the "Duty / Noise" box (because instrument 00 doesn't use Duty).
After going back and adding that unused Duty1 envelope, "Remove unused instruments" did not remove it.
KIND NOTE:
tepples wrote:
MACRO 4 is duty[1]
That "[1]" refers to the note at the bottom of his post, not duty1.
I figured out why "Remove unused instruments" isn't working and
reported the bug on famitracker.com.
What's happening is that if a used instrument refers to a particular sequence number, FamiTracker doesn't consider whether it's checked or unchecked before determining whether or not to remove the sequence. So a workaround is to change all unchecked sequences to use either a used Sequence # or a nonexistent one before removing unused instruments.
Ah-ha tepples, you figured out my actions when I quickly went back and created that unused Duty1 sequence!
Good bug report; thank you so much!
Sorry, it was a mistake
for me to write:
unregistered wrote:
... because the only way to remove that MACRO 4 line was to
You solved the problem that I didn't understand or even recognize.
edit: Added the mug smiley because I feel tepples should be toasted; honestly, I have never consumed and will never consume beer. : )
After rereading the
the wiki's "MMC1 technical reference":
- MMC1 always has an 8kb PRG-RAM bank at $6000-$7FFF
- This bank is switchable in only SOROM and SXROM.
- When using SOROM or SXROM, you actually have 32kbs of PRG-RAM in banks 0 through 3.
That's true right?
1.) I don't know how to switch the PRG-RAM banks.
How does one accomplish that? Thought it would be cool if you could use bit4 to switch between setting bank of PRG-ROM or PRG-RAM, but it is used for something else:
nesdev wiki's MMC1 technical reference wrote:
PRG bank (internal, $E000-$FFFF)Code:
4bit0
-----
RPPPP
|||||
|++++- Select 16 KB PRG ROM bank (low bit ignored in 32 KB mode)
+----- PRG RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A)
2.) How would you set a game to use SOROM or SXROM?
Those are two questions that seem unsolvable to just me. Right now, 8kb of PRG-RAM seems like a continent of space to me; but, I think, having access to 4 continents wouldn't be a hindrance.
lidnariq wrote:
Thank you very much lidnariq!
In the "Heuristic disambiguation" part it says I can't use 32 KiB of PRG-RAM because our game uses the "large" CHR-ROM (128 KiB) and that makes sense. It is important to read everything carefully and that's what I understand now. Hopefully I'll be blessed with learning that.
Would it be possible to load all 128 KiB of CHR-ROM into PRG-ROM and then, after switching to use "small" CHR-RAM, just copy it into the 8 KiB CHR-RAM when needed? But, then I'd need 128 KiB of PRG-ROM. That would take 4 PRG-ROM banks, I think, and so that's not possible for our game. : )
Will use the default 8 KiB continent of PRG-RAM.
unregistered wrote:
Would it be possible to load all 128 KiB of CHR-ROM into PRG-ROM and then, after switching to use "small" CHR-RAM, just copy it into the 8 KiB CHR-RAM when needed?
Yes. You could watch
Battletoads (AOROM) or the
Haunted: Halloween '85 demo (BNROM) stream stuff into CHR RAM during gameplay, or watch the Action 53 menu,
RHDE: Furniture Fight, or my port of the 240p Test Suite stream software-composited text in a proportional font into CHR RAM whenever a menu or help screen is open.
unregistered wrote:
But, then I'd need 128 KiB of PRG-ROM. That would take 4 PRG-ROM banks, I think, and so that's not possible for our game. : )
Switching from SKROM (128-256 KiB PRG ROM, 128 KiB CHR ROM, 8 KiB WRAM) to SUROM (512 KiB PRG ROM, 8 KiB CHR RAM, 8 KiB WRAM) already buys you an extra 256 KiB of PRG ROM. Or are you already using 512 KiB on another mapper (like UNROM 512 or oversize BNROM)?
Wow!!
Switching to SUROM would give us
32 16KiB banks? We must be using SKROM with 256 KiB PRG-ROM.
Now the
P bit on the SOROM, SUROM, SXROM "CHR bank 0" and "CHR bank 1" makes sense. That allows 16 16KiB banks when that bit is 0 and 16 new 16KiB banks when it is 1. But, I have to set both of those CHR banks each time I want to change that
P bit or change the SXROM PRG-RAM banks. This so great to understand all of this!
Thank you very much tepples and lidnariq!
edit: We aren't restricted to 128KiB of space for CHR anymore! So good!
I bet the creators, including you tepples, of Battletoads and the other games you suggested me to look at were joyful to learn about this too.
final edit: ...I don't mean to imply that our game will reach the same league as any of the games tepples mentioned; am just so happy and thought those people must have felt the same happiness. : )
After rereading the
lidnariq wrote:
link, I noticed that if we use 8KiB CHR bank switching then: we just have to set "CHR bank 0"; "CHR bank 1" is ignored!
Also want to thank Banshaku for his helpful SXROM research, in November 2008, nearly 10 years ago.
On
Programming_MMC1 it has a section that says the "MMC1 1C" might not power on in
fixed-$C000 mode so I should add the reset_stub in the last 16 bytes of each bank. I would like to place 16KiB of CHR files in one bank; the bank is 16KiB so there wouldn't be room for the reset_stub. Before our game starts (is playable), it sets MMC1 into
fixed-$C000 mode. So that would nullify the reset_stub's usefulness in each PRG bank, I think; but, that's just me thinking... am I right?
Can the data be compressed? If the data cannot be compressed because you need fine-grained random access, can the last tile of the bank remain unused?
unregistered wrote:
Before our game starts (is playable), it sets MMC1 into
fixed-$C000 mode. So that would nullify the reset_stub's usefulness in each PRG bank, I think; but, that's just me thinking... am I right?
Your reset code (where you set the fixed $C000 mode) won't run if the cart boots in a bank without a reset stub. The reset stub is necessary precisely to guarantee that your reset code gets called.
Can't you sacrifice 1 or 2 tiles to make room for the reset stub? Or implement some simple compression of the CHR data (e.g. good old RLE) to gain ~25% (~4KB) of the space back without sacrificing any tiles?
Thank you tokumaru for explaining why I was wrong; that's really helpful for me.
Thank you very much tepples and tokumaru for helping me to not follow the last chr file with
.align $1000.
Some of my sister's 4KiB CHR files don't use the last tile. Haven't built the game yet, but it seems like SXROM is coming together nicely now.
Is there a way to copy the contents of block 16 to block 32? Was blessed to think maybe we could use
.incbin $C000, $000, $4000 inside block 32; but, that wouldn't work because... maybe it could work cause that would only happen when the rom is assembled. Do all versions of MMC1's SXROM start with the
P bit of "CHR bank 0" at 0? Or would that not matter cause each bank contains the reset_stub and so reset will run? Hmm, maybe that
.incbin $C000, $000, $4000 won't work cause it only loads from files... but, when our game is assembled it does work with memory
.db.
I just would like to not have to copy all of block 16, our fixed bank is $C000 in bank 15, into block 32, because then I'd have to change the code in both blocks when debugging. Do you understand my desire?
edit: Is block 16 the same as bank 15? Because the banks start with bank 0.
The value in the P bit of CHR bank 0 isn't guaranteed at power-on either. This means some of the MMC1 init code, including setting the P bit of CHR bank 0, will need to exist in the last 16 KiB bank of both 256 KiB outer banks. The way to accomplish that is similar to how you put the reset stub in the last 16 bytes of every 16 KiB bank.
To avoid having to duplicate
everything in bank 15 into bank 31, you could put only those subroutines that operate on data in the first outer bank into the first outer bank's fixed bank, and only those subroutines that operate on data in the second outer bank into the second outer bank's fixed bank. The practicality of that may depend on how complex your NMI handler is. Switching banks in NMI on MMC1 is very tricky. You might want to use a simplified NMI handler in the second outer bank.
The overall code might look like this:
Code:
; This goes at $FFF0 in all 16K banks
resetpart1:
sei
ldx #$FF
stx resetpart1+2 ; Clear shift register; set fixed $C000
txs ; Initialize stack pointer
jmp resetpart2
.addr nmi_handler, resetstub1, irq_handler
; This goes at the same place in both fixed banks, such as $FF80
resetpart2:
inx ; now X is 0
stx $2000 ; disable NMI handler
stx $2001 ; disable rendering
; Set CHR window to bank 0
stx $A000
stx $A000
stx $A000
stx $A000
stx $A000
; Set mode to $0E: vertical mirroring, fixed $C000, 8K CHR
lda #1
stx $8000 ; X writes a 0, A writes a 1
sta $8000
sta $8000
sta $8000
stx $8000
jmp resetpart3
; And this goes in your primary fixed bank
resetpart3:
cld
bit $2002
@vsync1:
bit $2002
bpl @vsync1
@vsync2:
bit $2002
bpl @vsync2
; Hardware init continues here, including choosing a PRG bank
; at $8000 by writing to $E000
Thank you very much tepples! It is also really good to understand that we don't necessarily need the
lsrs when switching banks.
I believe this is going to work!
.incbin $C000, $000, $4000 won't work because I need to put a
.base $C000 at the top of block 32. Is there a way to specify the block number? Then it wouldn't matter what the P bit of "CHR bank 0" is at startup, cause my reset is always run thanks to reset_stub (and that P bit is set inside of reset). Is that good thinking?
That would be pretty cool to be able to write something like
.incbin ]16, $000, $4000, inside of block 32, with the "]" indicating that a block number follows. That's the only character not used in asm6, I think.
Guess that I don't have to put anything inside block 32 now because I don't need to set P bit of "CHR bank 0" to 1 now. ...Maybe I need to reread tokumaru's explaination and think about how that logic could affect the need of applying tepples' wisdom.
Lunch time.
Quote:
My goal is to see this collision detection work well.
I don't quite see the relation to collision detection.
But sure, I can sure talk about it. It's actually really easy and fast as long as you don't handle slopes, and don't have things like tiles you can jump up through or tiles you can drop down through.
You only really need to check 6 points to be TOTALLY safe, or 4 if the shape of your character never changes and he is smaller than the size of the collision tile for both X and Y. If he's say... 24 pixels tall with 16x16 collision tiles you have to check one more point. You also need to check more if your character moves more pixels in a frame than the size of your tile. For instance, he's capable of moving 9 pixels in a frame, but your collision tiles are only 8 wide.
Obviously, you first gotta find out whether or not the tile you're working with is collision enabled. I can't especially help with that unless you make a post about your current tile format. Post about that, and I can write a book for you about that.
I personally keep 4 screens of 32x32 metatiles in a page of RAM, which are updated when the game scrolls to a new screen. This means all my screen decompression happens once and only on a frame when the character passes a screen boundary.
The collision detection and parts of my code that tell the PPU what to draw always pull from this RAM, where it is much easier to figure out the math for me than pulling directly from the data in ROM.
I load the 32x32 tile from RAM, I find out the 16x16 tile I want in it. Then I know if I should eject or not.
It works exactly like this:
Code:
;Ignore the <'s, it's a nesasm thing.
lda <xhigh;Zero Page RAM I put any high X position into
ror a;puts the lowest bit of the high x pos into the carry
lda <yhigh;Zero Page RAM I put any high y position into
ror a;puts the lowest bit of the high y pos into the carry
;and puts the x bit into the high bit of the accumulator
ror a;Now the low x and y bits are in the high bits accumulator
and #%11000000;Removes the other junk in the accumulator
sta <reservedE;Temp RAM. The highest bits of it now contain
;which screen I'll read from RAM. Each screen is 64 bytes long, because it contains 8x8 32x32 metatiles.
lda <xlow
lsr a
lsr a
lsr a
lsr a
lsr a;divide by 32, since I'm trying to get a 32x32 metatile.
;The other bits of precision don't matter.
ora <reservedE;This combines my "which screen" bits with the X offset in RAM.
sta <reservedE
lda <ylow
lsr a
lsr a
and #%00111000;This takes away all unnecessary bits from
;my y position
ora <reservedE;I know have the offset in RAM that I need to load my 32x32 tile from.
tay;Y contains the address of the 32 x 32 metatile to look up
lda $0500,y;loads 32 x 32
tay;Y contains the 32 x 32 tile number
The tricky part is actually getting the 32x32 tiles into RAM in the first place, but you may not even need to do that depending on how your data is stored.
I then use a series small series of branches to determine which part of the 32x32 tile I'm in. Topleft, Topright, bottomleft, or bottomright. Once I know that, I load the tile and use its format to determine whether it's a collision or not.
Here's how I eject when I know the current tile is collision enabled
Code:
;Ejecting up/left
lda lowbyte
and #%00001111
clc
adc #$01
sta offset
rts
You want to eject left while traveling right. So imagine the right point of your character has just entered the first pixel of a tile. It's anded position would be 0. In this case, you obviously want to eject one. If you're going super fast, you'll end up further in the tile, so the and will be higher and you'll always eject one more than that. Easy. jsr to the routine, subtract offset from your character's position. (It should be set to 0 if the collision routine detects the current tile is not a collision tile.) You can even use the offset variable the routine returns to find out whether or not there WAS a collision if you want to do that to stop a walking animation or something.
Code:
;Ejecting down/right
lda lowbyte
and #%00001111
eor #%00001111
clc
adc #$01
sta offset
rts
This one is mostly the same, except it has an eor. Say you're traveling left and hit the first pixel of a tile. That's actually pixel $0F, but you only want to eject one! The eor makes $0F equal to one, then it works the same as before! As before, just write a 0 to offset if you've discovered the current tile is not solid. Jsr to routine, then add offset to player's position.
This works for any size tile that's a power of two. Just change the ands and eor to the number of bits your tile takes up.
If you're traveling left, move the player using the current speed value. You may be in a wall. You check the top and bottom left points of your character, and eject right. (add offset) If you're traveling right, check the top and bottom right points of your character and eject left. (subtract offset) you'll never be in the wall for a frame like Mario, and this will certainly be fast enough for how few tiles you'll need to check.
For ejecting up while falling you check the bottom left and right points and subtract offset from the player's position.
1.) How many points does one need to check if the sprite is 8 pixels wide and she is colliding into an 8 pixel wall, but she moves more than 8 pixels per frame sometimes. I been attempting to solve this for a while. Now, two points are checked and she used to never enter the wall, but she is ejected 8 pixels away from the wall every other time it checks collision.
2.) The code I was blessed to discover for
checking collisions while moving left ejecting collisions to the left or right, uses an
eor, but it makes sense to me:
Code:
lda altoX ;<our major X variable
and #00000111b ;and with 7 because our collision deals with 8x8 tiles :) ;always clears bit3
ldx FORWARD ;<00 for left, 01 for right, and ff for neither
;it is verified that FORWARD won't be ff if this code is reached :)
bne + ;if going right we don't want to invert bits 0 through 2
eor #00000111b
+ ;some other code here
sta t16 ;a temp variable
lda altoX
ldx FORWARD
beq +
clc
sbc t16
jmp ++
+ sec
adc t16
++
sta altoX
Left out some of the code because it doesn't matter for what my goal is. I just want to know how many points should be checked. I'm not hoping you'll solve my problem; just hope you'll answer my question.
And I wanted to post a solution to left collision correction that works, for me at least.
3.) Your usage of
ror to move the low bits of yhigh and xhigh into the carry is really brilliant Kasumi!
p.s. I hope I haven't posted this earlier.
edit: Sorry, I was wrong, we
chechcheck two points underneath her and two points on the direction she is moving. And awhile ago I wrote code that would check two more points, 8 pixels beyond the points checked... was blessed with getting that to work and she collided as I mentioned above... then I thought we could skip checking those points and
and #$08 t16 based on a
variable set earlier in the frame (that's why it is important that bit3 was cleared in the code I just shared). Now she goes through the 8 pixel wall when moving left, but collides with the wall when moving right; every other collision when moving right she is ejected 8 pixels too far. Maybe I'm setting the
variable incorrectly!
But, maybe not because this pulsing away from the wall 8 pixels happened when 4 points were checked in the direction she is moving.
edit2: Sorry, noticed that my summary of
our code was wrong so created a strike-through and followed that with a valid summary, I think.
final edit.
You have to check just enough pixels that the character can't pass through a single tile.
Say your tilesize is 16x16. You have a character that is 18 pixels tall. They can move through a single tile if they're positioned perfectly and you only check the top and bottom points.
(All example images in this post are zoomed to 400%) Black is one 16x16 tile, red is the character, and yellow are the points checked.
That image shows what happens if you only check the top and bottom points. If your character is tall enough, a tile can pass through their middle!
So you have to check the top point always. Then 16 pixels below that.
Note even when the top point is one above the tile, the collision still works. Move it down and it still works. This ensures there will always be at least ONE point for every tile the player might hit on the grid. You still need to check the bottom, though! (Unless the bottom is exactly 16 pixels below the top point.) Otherwise the character could move their bottom through tiles!
This image shows which points you'd have to check for characters of lots of heights:
Top. Then 16 pixels below that. (Then 16 pixels below that. As many times until 16 pixels below the last point is outside the character.) Then you have to check the bottom point. This will eliminate any blindspot.
Thank you Kasumi! Your graphic making abilities are very scrupulous and excellent!
If anyone ever wants to learn how to write to CHR-RAM read
this excellent reply by tokumaru.
Ok, so my nes rom is successfully built!
But, the nametables aren't filled and the CHR RAM is empty. My tracelog file starts out:
Code:
FCEUX 2.2.3 - Trace Log File
f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF0:78 SEI
f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF1:A2 FF LDX #$FF
f1 A:00 X:FF Y:00 S:C2 P:nvubdIzc $BFF3:9A TXS
f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF4:8E 00 80 STX $8000 = $00
f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $BFF7:4C A0 FF JMP $FFA0
f1 A:00 X:FF Y:00 S:FF P:NvubdIzc $FFA0:00 BRK
f1 A:00 X:FF Y:00 S:FC P:NvubdIzc $D1BA:00 BRK
etc.
It keeps hitting the 00 at $D1BA. Maybe I set up the header or the blocks wrong. In the header it has
|iNES header| 4E 45 53 1A |Number of PRG-ROM blocks| 20 |Number of CHR-ROM blocks| 00 |ROM control bytes: Vertical mirroring, yes SRAM, no trainer, Mapper #1| 13 00 |Filler| 00 00 00 00 00 00 00 00For my PRG-ROM blocks I just added 15 more blocks after the first 14 and started bank!15, the last bank in the the second outer bank, with
.base $C000... and also had to change the
.org $C000 in my main fixed bank, that follows it, to
.base $C000.
Ohhh!! Maybe I need to place the other 15 blocks after my main fixed bank!
Hahha!! It totally works now; thank you so much tepples for your wise teachings!!
edit: Back to collision now.
Ok, honestly, there is one problem, the .chr file isn't being read correctly because I'm using one label in multiple banks to mark the 4KiB background files. I'm using
CHR4kb = $, but some of the files start at $B000 and others start at $AFF0. The last time CHR4kb is assigned it gets $AFF0, so that is why the chr file, that starts at $B000, that I'm trying to load into CHR-RAM is off by one tile and making all of level 1 look crazy. My question is: is there another simple way to make this work other than rearranging the banks so that CHR4kb is always assigned the same value? I thought that CHR4kb would be updated depending on which bank was loaded into $8000-$BFFF.
Rearranged the banks containing
CHR4kb = $ so that they are all assigned the same value and our game looks like it used to before CHR-RAM was enabled. I'm so thankful!
edit: Also want to say that now the nesdev wiki makes sense, to me, when it says, "Valid addresses are $0000-$3FFF," at the end of
the PPUADDR section. Honestly, I always thought that was a mistake; but after writing to CHR-RAM ($0000-$1FFF) it is nice to know that that statement is valid when CHR-RAM is enabled.
Want to write that here to help some others to understand that too.
Even when using CHR-ROM, addresses $0000-$1FFF can be used to READ from the pattern tables, as some NROM and CNROM games did back in the day. Since these games had very limited PRG-ROM space, they used some of the CHR-ROM space to hold data.
tokumaru wrote:
Even when using CHR-ROM, addresses $0000-$1FFF can be used to READ from the pattern tables, as some NROM and CNROM games did back in the day. Since these games had very limited PRG-ROM space, they used some of the CHR-ROM space to hold data.
That's really cool! Thank you for sharing, tokumaru!
I never thought of doing that
; but, was pondering if we could somehow transfer bank15 from ROM to CHR-RAM to bank!15, our 15th bank if the P bit is set inside "CHR bank 0" (using SXROM), in ROM; but, that's not possible, I think, because ROM can only be set during assembly and RAM must be set after assembly.
There isn't ever a time when one could make a transfer from RAM to ROM. Remember learning that earlier in this thread, I think.
unregistered wrote:
There isn't ever a time when one could make a transfer from RAM to ROM.
True of mask ROM, false of flash memory. Some recent homebrew games, such as
Study Hall, save by writing back to flash instead of using battery RAM.
Trying to use a signed comparison because a part of our game needs one, I believe; my question, after reading
this really helpful page, is: is the SO pin free from connections to it in our game?
After using
bvs in our game before and it, our game, working just fine on the powerpack, it seems to me that the 6502.org article, linked above, is talking about editing other already-created games.
unregistered wrote:
Trying to use a signed comparison because a part of our game needs one, I believe; my question, after reading
this really helpful page, is: is the SO pin free from connections to it in our game?
Yes, the SO pin is floating inside the 2A03. (Visual2A03 node 11246)
Thanks lidnariq!
So there must be different 2A03s inside NESes? Or did 6502.org make that 5.2 section for people who attach things to the SO pin? Well, that pin is inside the 2A03 chip... so maybe I'll go research this on my own so my questions aren't rediculous.
The 2A03 contains a second source 6502, a PSG, and a primitive DMA controller. The SO pin of the 6502 in the 2A03 isn't connected to anything.
tepples wrote:
The 2A03 contains a second source 6502, a PSG, and a primitive DMA controller. The SO pin of the 6502 in the 2A03 isn't connected to anything.
Thank you so much tepples! That's excellent! It's great to not have to worry about the SO pin!
Learned from
this wikipedia page that the SO pin is at the upper right corner of the 6508 chip... and so that's what it means to connect something to the pin.
Thank you lidnariq and tepples for helping my small understanding of the SO pin.
edit: It is also nice to understand that 6502.org explains about 6502 chips used in many different types of machines. And that the NES's 2A03 is a unique 6502-based chip.
Just trying to get a variable to display in hex format using the Lua Script Window of Mesen. After some searching I discovered that C functions can be used in Lua and that I would need to use a C function to convert the integer to hex format. And after opening a pdf titled
libc.pdf pages 286-290 showed me that
printf can transform integers into hex formated numbers. (I've never used C or Lua before; well, played with Lua yesterday.) My Lua file says something that includes:
Code:
altoX = emu.readWord(0x0038, emu.memaType.cpuDebug)
emu.drawString(12, 30, "38 | altoX: " .. printf("%4X"), 0xFFFFFF, 0xFF000000, 1)
It says:
Quote:
Loading script...
Script loaded successfully.
MemDisplay00.lua:16: attempt to call a nil value (global 'printf')
etc.
Did try to add
int before the text on the line where altoX was assigned but, received an error. Please help me; I don't know how to make this work.
You'll want to use string.format (e.g., string.format("%4X", 1234))
Thank you so much thefox!
Replacing
printf("%4X") with
string.format("%4.4X", altoX) caused the 80 to be preceeded by 00 (for the high byte)! Precision is so cool! (When our game starts she is at 0080 and that's what it shows.
)
Anytime you add a number and result would have been greater than the byte can hold, the carry is set. Otherwise it is cleared.
I realize now that you were talking about addition, but wouldn't the carry always be affected after subtraction too?
If so, since the carry flag is affected by cpx, why does
cpx #00 not clear the carry... ohh, think I get it now... you would say, "Anytime you subtract a number and the result would have been less than the byte can hold, the carry is cleared. Otherwise it is set." That seems correct to me. I'm so happy now!!
edit: Kasumi already answered this in the same post in page 67:
Kasumi wrote:
1. The carry is ALWAYS taken into account when you use adc or sbc, so make sure it's right for the operation you intend to do before that operation runs. (Clear before addition, set before subtraction)
2. The carry will become the opposite of what you would normally initialize it to if the operation goes outside the boundaries of a byte. (So if an addition would have yielded more than 255, or a subtraction would have yielded less than 0.) Otherwise, the carry becomes what you would normally initialize it to.
final-edit: highlighted most important part of quote. | 20181012: corrected 69 to 67
hi everyone,
I am blessed to be able to present a small asm6 fork named asm6_. I'm clueless about if anyone will care. All it does is allow a user to use a -u flag when creating a NES file. That -u flag currently allows the listing file to be created with:
1.) REPTs contracted
2.) MACROs expanded
This really helps my browsing through our .lst file because most of our banks begin with a .rept 256 and that causes lots of repetitions of 256 lines. Thankfully, now with the -u flag none of those 256 lines appear in our listing file.
I really like looking at our expanded macros and, if God works with me on this again, I hope to also remove the macro definitions from the .lst file when using the -u flag bc they just sit there doing nothing for me.
The asm6_.zip contains:
asm6_.c,
asm6_.exe, and
README_.TXT. ...in the same format as asm6.zip.
asm6_.zip <that's hosted on my unsecure website. My website has been unchanged for a looong time, oh well.
If anyone cares, the asm6_.zip file^ now contains asm6_ version 1.6_0.11... its -? flag has been updated to show "README_.TXT". Sorry for the wasted post.
Thought it was interesting that my
, edit of loopy's asm6.exe, asm6_.exe was so much larger than loopy's asm6.exe after really trying to accomplish asm6_.exe working with as little changes as possible. Now, it is much smaller, after I used certain gcc compiler options and then running strip with certain options on that asm6_.exe, and still works the same, at least it does for me after some quick testing.
The file, link is in my post two-posts-above this post, has been replaced again.
This is my first time creating an executable file for others to use.
final edit. 20190202 ~11AM
So after working on asm6_ today we now provide -U flag for listing file creation.
The -U flag expands only MACROS while they are being used; their definitions are hidden! I'm so thankful to God and happy!!
The -u flag still exists incase anyone uses this and wants the MACRO definitions shown.
-l and -L flags still work like they were designed to
asm6_.zip //current version is now 1.6_0.12
p.s. This asm6_.zip is clean (no viruses) and its executable is way smaller than asm6.exe v1.6. And after some testing I think it performs just as good.
ASM6_ INFO FOR INEXPERIENCED:
asm6_'s -u and -U flags, and the original -l and -L flags, only affect the listing file .lst that's created... i.e. using the -U flag will not hide your macro definitions in any of your code files .asm.
Listing files are super helpful, but are only there to visually help you... changing a listing file has no effect on your .nes file.
No effect on your .nes file itself... but, changes to your .lst may very well affect your ability to benefit while viewing your .lst, which may prevent progress on your game design. That's easy to fix: just rerun asm6_ with a different listing flag (-l, -L, -u, or -U) and the appropriate listing file will replace your current .lst file
(caution: your .nes file will also be updated) .
Note: running asm6_ with all of the listing flags will build a -L listing file just like loopy designed asm6 to do.
edit.
After some reading it seems the limit to an asm6 .hex command is 252 hex codes. (Bc strings in c have, or had, a 509 character limit. 509-5=504. Taking off the 5 characters for
.hex and the following space. 504/2=252. Divide by 2 bc each hex code takes 2 characters.) Is this 252 hex code limit for asm6's .hex command true? Or is the limit even lower? If so, why?
Asking this bc I'm trying to include my sister's RLE .hex lines... and they are very long, a line per screen, and the line being tested has been trimmed down to 127 hex codes, the line is 261 characters long bc of the extra space in front of
.hex, and I'm still receiving address out of bounds errors while running gdb.
edit: is each line limited to around 256 characters? That nes limitation doesn't make sense, to me, if it applies to asm6 written in c.
unregistered wrote:
After some reading it seems the limit to an asm6 .hex command is 252 hex codes. (Bc strings in c have, or had, a 509 character limit.
C strings have no length limit beyond
SIZE_MAX, which is in the tens of kilobytes at minimum and the gigabytes on modern platforms. A particular implementation that allocates a buffer on the stack to hold a string limits the length of the string to the length of the buffer. For example, a 512 (that is, 1<<9) byte buffer can hold 509 characters plus CR, LF, and NUL.
tepples wrote:
unregistered wrote:
After some reading it seems the limit to an asm6 .hex command is 252 hex codes. (Bc strings in c have, or had, a 509 character limit.
C strings have no length limit beyond
SIZE_MAX, which is in the tens of kilobytes at minimum and the gigabytes on modern platforms. A particular implementation that allocates a buffer on the stack to hold a string limits the length of the string to the length of the buffer. For example, a 512 (that is, 1<<9) byte buffer can hold 509 characters plus CR, LF, and NUL.
That's kind of what was learned from StackOverflow, 512-CR-LF-NUL=509, so, let's say I'm restricted to a 512 byte buffer, what is asm6's limit on the length of the
.hex line?
I'm going to try reducing the line to less than 256 characters, even though that doesn't make sense to me bc .hex tells asm6 how to behave in c. As long as the hex codes get into the right spots in the NES' ROM I'll be overjoyed.
Why do you need such long strings of hex data anyway? Having lots of data in text format isn't very efficient (takes over twice the space, takes longer to parse), so unless you're constantly hand editing that data, it's best to just include it from binary files.
tokumaru wrote:
Why do you need such long strings of hex data anyway? Having lots of data in text format isn't very efficient (takes over twice the space, takes longer to parse), so unless you're constantly hand editing that data, it's best to just include it from binary files.
unregistered wrote:
Asking this bc I'm trying to include my sister's RLE .hex lines... and they are very long, a line per screen
1.) If my sister decides to change something on a screen, long lines of text would be easier to change than a binary file.
2.) Takes over twice the space before the game is assembled bc a text hex code consists of 2 bytes/characters and a binary byte takes only 1 byte? And it takes longer to parse while assembling the game because the data is twice as big? If you mean both of those, then the .hex text format will run just well as the binary format inside the NES game (bc they'll both end up in binary format). Without a doubt, we decided on using the .hex text format bc, as you mentioned, it is easier to edit. We aren't constantly editing the screens, though it takes a very small amount of time to assemble our game right now, before trying to add .hex text, so the longer parse time seems reasonable. I'll post asm6's .hex limitations if this is figured out.
Ok, so asm6 is limited to 64 hex codes following its .hex. (i.e. When our game assembles, the cursor at the end of most of our .hex lines is in column 135. The hex codes start, after .hex, in column 7. 135-7=128. 128/2=64.) Why this limitation-have no clue right now. With 65 hex codes per line asm6 gives, "Not a number."
unregistered wrote:
1.) If my sister decides to change something on a screen, long lines of text would be easier to change than a binary file.
But doesn't she find it hard to find what exactly needs to be changed in these huge strings? If the screens are made of metatiles in a grid, she should at least consider to have one hex string for each row, as opposed to one the entire screen. It'll be easier to edit and will not cause problems doe to the emulator's maximum line length.
Quote:
2.) Takes over twice the space before the game is assembled bc a text hex code consists of 2 bytes/characters and a binary byte takes only 1 byte?
Yes. Not that this is a big deal considering today's typical storage capacity, but still.
Quote:
And it takes longer to parse while assembling the game because the data is twice as big?
It takes longer because the data is twice as big, and it's read as text, meaning it has to be converted to byte values before they can be written to the output. Again, not a big deal considering today's typical processing power, but still.
Quote:
the .hex text format will run just well as the binary format inside the NES game (bc they'll both end up in binary format).
Yes, whether you use
.hex,
.db or
.incbin has no impact on the final binary.
Anyway, just consider breaking the giant hex strings into smaller units, such as parts representing rows of metatiles. It will be much more readable and easier to edit by hand. I don't think it's even good programming practice to have insanely huge lines of code in your source files.
Thank you for your wisdom tokumaru!
Good to think about; will tell my sister. I believe she doesn't want to install a hex editor... but, you do make lots of sense.
edit: Oh, remember these are screens made of RLE hex codes so separating it into lines of metatiles wouldn't be necessarily pretty. However, our metatiled screens using .db are separated into rows of metatiles like you suggested.
I don't see why you'd ever need more than 80 characters per line. Any more and you can't guarantee that it isn't cropped when you view the file on another device or environment with a smaller screen, and it also is less readable. If one line represents something in your game (like a row of tiles) but is too long, just break it into two (or more) lines and separate it from other lines with an empty line.
Also a hexeditor doesn't require installation. There are free ones like XVI32 that just needs to be uncompressed. It's a very basic computer tool that I think about anyone working with slightly technical stuff with computers needs, just like a text editor.
135 characters per .hex line is really ok for me.
That's a good point about 80 characters should be the max; though, I don't ever plan to work on this game using a smaller screen. After the game is complete: it seems good to me to convert these long lines into binary files. Thank you, Pokun, for the note that hex editors don't have to be installed!
It may not make a difference, but I'll tell my sister.
Does the PPU only display a screen after it has been in the viewable nametable for an entire frame? It must be frowned on to: clear 2006-and-2007's address latch, disable vblank, disable rendering, write screen to a nametable, clear vblank flag to avoid graphical errors (bit $2002), enable vblank, set variable to enable rendering, and then, inside vblank, enable rendering. "Frowned on" bc the game's screen is black until we run out of screens to display even though each screen is correctly written to nametable.
unregistered wrote:
Does the PPU only display a screen after it has been in the viewable nametable for an entire frame?
In an emulator, probably. It can't/shouldn't display the future, because it won't know what's going to be there, and it oughtn't display the past, because the resultant tearing would be misleading.
Quote:
the game's screen is black until we run out of screens to display even though each screen is correctly written to nametable.
Are you waiting for the screen you want displayed to be displayed after you finish uploading it?
lidnariq wrote:
Are you waiting for the screen you want displayed to be displayed after you finish uploading it?
Actually, no. Right now, the game loads a screen correctly, then sets $2001 inside vblank, then repeats that loop starting with loading the next screen correctly while in the next frame... until all the screens have been cycled through.
"the game loads a screen correctly" is valid to me bc I can see the screen correct in Mesen's Nametable viewer.
How long should I have the game wait before the screen is viewable in most emulators? Maybe I should figure that out myself... at least with Mesen.
Thank you lidnariq!
edit: you already answered my question in the first, unquoted, part of your reply; thanks!
After turning rendering off, the screen is wiped. Why? Did they design the PPU to do that?
Maybe it makes sense though, bc, I guess, the screen requires the PPU to draw it every frame; and turning off the PPU's drawing restricts future screens from showing. If that's so, it would have been ultra cool to have installed a feature that made the PPU just draw the last screen written until rendering is reenabled. Maybe that would be to complex.
Too bad that $2001 can only be written to during vblank. It seems to me: if that wasn't the case the screen wouldn't clear bc rendering would be reenabled before the end of the frame.
Is the only way to avoid blank-screens appearing inbetween screens drawn by the PPU, is to draw each screen inside vblank so that rendering isn't disabled? I'm asking this here bc the nesdev wiki claims that screens can't be drawn outside of vblank.
Note to myself: have I asked this before?
unregistered wrote:
After turning rendering off, the screen is wiped. Why? Did they design the PPU to do that?
Maybe it makes sense though, bc, I guess, the screen requires the PPU to draw it every frame; and turning off the PPU's drawing restricts future screens from showing.
The picture literally only exists at the exact moment that each pixel is sent to the CRT. The rest is just the optical illusion known as persistence of vision.
Quote:
Too bad that $2001 can only be written to during vblank. It seems to me: if that wasn't the case the screen wouldn't clear bc rendering would be reenabled before the end of the frame.
$2001 can be written at any time. But for the image to appear, you have to wait for the CRT to draw it. Turning on rendering using $2001 doesn't cause the entire set of pixels to be sent all at once; instead it starts sending pixels gradually, one new pixel every .000000182 seconds.
Quote:
I'm asking this here bc the nesdev wiki claims that screens can't be drawn outside of vblank.
You cannot write new data during rendering, no.
lidnariq wrote:
unregistered wrote:
After turning rendering off, the screen is wiped. Why? Did they design the PPU to do that?
Maybe it makes sense though, bc, I guess, the screen requires the PPU to draw it every frame; and turning off the PPU's drawing restricts future screens from showing.
The picture literally only exists at the exact moment that each pixel is sent to the CRT. The rest is just the optical illusion known as persistence of vision.
Quote:
Too bad that $2001 can only be written to during vblank. It seems to me: if that wasn't the case the screen wouldn't clear bc rendering would be reenabled before the end of the frame.
$2001 can be written at any time. But for the image to appear, you have to wait for the CRT to draw it. Turning on rendering using $2001 doesn't cause the entire set of pixels to be sent all at once; instead it starts sending pixels gradually, one new pixel every .000000182 seconds.
Quote:
I'm asking this here bc the nesdev wiki claims that screens can't be drawn outside of vblank.
You cannot write new data during rendering, no.
There are two issues at play here:
1. The NES is designed to drive a device that draws a picture by modulating a moving electron beam; the NES sends out a signal that controls how bright the spot under the beam should be at any given moment. Turning off rendering will cause the beam to just keep outputting a solid color until rendering is turned back on, but that will only affect the parts of the picture drawn by the beam while rendering was off. If rendering is left off, then it will affect the whole picture as the beam passes over it. Rendering is only enabled when the render control bits are set
and the beam's scanning process isn't between the bottom of one frame and the start of the next--a time known as the "vertical blanking interval", or "vblank".
2. On machines designed for connect to NTSC-based televisions, the memory cells used to hold sprite positions and attributes (Object Attribute Memory, or OAM) may arbitrarily flip state if rendering is disabled for much longer than vblank. So leaving rendering disabled for too long may effectively "wipe" the OAM.
The first issue is common to many vintage systems. The latter is so far as I can tell unique to the NES.
lidnariq wrote:
Quote:
Too bad that $2001 can only be written to during vblank. It seems to me: if that wasn't the case the screen wouldn't clear bc rendering would be reenabled before the end of the frame.
$2001 can be written at any time. But for the image to appear, you have to wait for the CRT to draw it. Turning on rendering using $2001 doesn't cause the entire set of pixels to be sent all at once; instead it starts sending pixels gradually, one new pixel every .000000182 seconds.
supercat wrote:
There are two issues at play here:
1. The NES is designed to drive a device that draws a picture by modulating a moving electron beam; the NES sends out a signal that controls how bright the spot under the beam should be at any given moment. Turning off rendering will cause the beam to just keep outputting a solid color until rendering is turned back on, but that will only affect the parts of the picture drawn by the beam while rendering was off. If rendering is left off, then it will affect the whole picture as the beam passes over it. Rendering is only enabled when the render control bits are set and the beam's scanning process isn't between the bottom of one frame and the start of the next--a time known as the "vertical blanking interval", or "vblank".
2. On machines designed for connect to NTSC-based televisions, the memory cells used to hold sprite positions and attributes (Object Attribute Memory, or OAM) may arbitrarily flip state if rendering is disabled for much longer than vblank. So leaving rendering disabled for too long may effectively "wipe" the OAM.
The first issue is common to many vintage systems. The latter is so far as I can tell unique to the NES.
So, since there seem to be 61,440 pixels in an entire (not just the visible part of NTSC) NES screen (32x8+30x8==61440) and
drawing a screen it must take 61440 x .000000182 == 0.01118208 seconds
after rendering is reenabled for the CRT to fully draw it, it must be possible to disable rendering, write a screen to the PPU, and reenable rendering a little over one hundreth of a second before vblank so that the screen appears before the OAM is wiped right?
But, supercat says that the OAM can be wiped if rendering is off for a little longer than vblank, but let's say it doesn't matter that the OAM is wiped. That just affects coloring for us, I guess... it would be really cool to see it work without blinking.
Going to try the current code on the powerpak tomorrow to see if the blinking is just Mesen.lidnariq wrote:
Quote:
I'm asking this here bc the nesdev wiki claims that screens can't be drawn outside of vblank.
You cannot write new data during rendering, no.
Recently, it did not say something like, "screens can't be drawn outside of vblank
, unless rendering is disabled." Leaving that bold part off makes it seem like, to me at least, that screens must be drawn during vblank.
Thank you lidnariq and supercat!
edit.final edit.
unregistered wrote:
So, since there seem to be 61,440 pixels in an entire (not just the visible part of NTSC) NES screen (32x8+30x8==61440) and drawing a screen must take 61440 x .000000182 == 0.01118208 seconds, it must be possible to disable rendering,
The CRT and PPU are both busy between each horizontal line of pixels, so the math is not 256x240 but instead 341x240=81840; 81840x.000000182 = .0149. So, in practice, you only have the 20-ish scanlines between when NMI is asserted and when rendering starts on its own again later, or approximately .0013 seconds.
Quote:
it would be really cool to see it work without blinking.
The only way you're getting that is by either rationing the amount you upload per vblank, or using advanced tricks to turn on rendering late or turn it off early.
unregistered wrote:
Recently, it did not say something like, "screens can't be drawn outside of vblank, unless rendering is disabled." Leaving that bold part off makes it seem like, to me at least, that screens must be drawn during vblank.
If you can point where on the wiki it says that, I'd love to fix it.
lidnariq wrote:
unregistered wrote:
Recently, it did not say something like, "screens can't be drawn outside of vblank, unless rendering is disabled." Leaving that bold part off makes it seem like, to me at least, that screens must be drawn during vblank.
If you can point where on the wiki it says that, I'd love to fix it.
Hmm... sorry, maybe I read this too fast:
Quote:
A value of $00 or %00000000 disables all rendering. It is usually best practice to write this register only during vblank, to prevent partial-frame visual artifacts.
In my head I read, "It is usually best practice to write this (info to the PPU) only during vblank, to prevent partial-frame artifacts." I guess I read "this" and thought you were talking about writing info to the PPU bc that's what the wiki was talking about... rendering. Sorry lidnariq, reading is sometimes problematic for me. I will respond, if I have a response, to the rest of your post tomorrow.
oh that quote is from
https://wiki.nesdev.com/w/index.php/PPU ... rs#PPUMASK
unregistered wrote:
Hmm... sorry, maybe I read this too fast:
Quote:
A value of $00 or %00000000 disables all rendering. It is usually best practice to write this register only during vblank, to prevent partial-frame visual artifacts.
In my head I read, "It is usually best practice to write this (info to the PPU) only during vblank, to prevent partial-frame artifacts." I guess I read "this" and thought you were talking about writing info to the PPU bc that's what the wiki was talking about... rendering. Sorry lidnariq, reading is sometimes problematic for me. I will respond, if I have a response, to the rest of your post tomorrow.
One may disable rendering at any time during a frame, with the effect that if one doesn't re-enable it, everything from there on down will be blanked. Enabling the PPU during a frame, however, is tricky. The counters are that are used to update the display address while rendering a display are also used to increment the address after each CPU write; consequently, they don't increment while rendering is disabled. As a consequence, if one enables rendering in the middle of the frame, the PPU will show things in the wrong places unless the CPU first loads the counters with correct values, which would in turn require that the CPU know what those correct values should be. Because the correct values of the counters depend upon the position of the beam, which in turn depends on the amount of time elapsed since the end of vertical blank, the only way the CPU can know what values to load into the counters is by knowing how much time has elapsed since vertical blank. There are ways this can be done, but it is generally difficult.
supercat wrote:
One may disable rendering at any time during a frame,
Unfortunately, there's some obscure bug with the OAM DRAM controller that causes it to malfunction the next time rendering is enabled, if it's not turned off at the right time. Either we've never figured out exactly what's going wrong, or I've forgotten where it was stated, but on the wiki we've only documented the window in which it's safe to do so. (between x≥192 for lines with no sprites, x≥240 for lines with at least one sprite, and ... the wiki doesn't say what the upper bound is?
this thread implies it's 255. Note that although the discussion in that thread makes it sound like the bug due to starting rendering with OAMADDR nonzero, but it persists across writing to OAMADDR and OAMDMA)
lidnariq wrote:
unregistered wrote:
So, since there seem to be 61,440 pixels in an entire (not just the visible part of NTSC) NES screen (32x8+30x8==61440) and drawing a screen must take 61440 x .000000182 == 0.01118208 seconds, it must be possible to disable rendering,
The CRT and PPU are both busy between each horizontal line of pixels, so the math is not 256x240 but instead 341x240=81840; 81840x.000000182 = .0149. So, in practice, you only have the 20-ish scanlines between when NMI is asserted and when rendering starts on its own again later, or approximately .0013 seconds.
Quote:
it would be really cool to see it work without blinking.
The only way you're getting that is by either rationing the amount you upload per vblank, or using advanced tricks to turn on rendering late or turn it off early.
Wow, thank you so much lidnariq for correcting my math!
After reading supercat's and your most recent reply to this thread it seems like the best way to do this would be to disable rendering when x>=192 (while each screen is drawn horizontaly... right?) draw my screen and reenable rendering before vblank. However, when x >=192 doesn't leave much time for screen drawing... so my screen drawing code would have to be sped up (by me), I think... I have to go... to know exactly how much time elapsed since vblank it would require me counting cycles used between end of vblank and the end of me drawing the screen... many of the branches are permanent during this screen drawing so that might not be too bad... then I load that cycle number and write it to somewhere; and then turn on rendering before vbank starts. Seems difficult but, maybe possible? Drawing screens during vblank seems simpler, but then I have to think about redoing my vblank code. Ok, bye, thank you lidnariq and supercat so much!
Mesen is so helpful!
It seems like, from reading Sour's notes in Mesen's Conditional:
at the bottom of the Breakpoint window, each scanline takes 341 cycles. So, since vblank starts at scanline 241, then 241-192=49 ... 49*341=16,709 cycles available to draw the screen outside of vblank... and my screen drawing currently takes 16,832 cycles, which leaves only 123 excess cycles... and we'll have to turn rendering on again so if the screen drawing can be reduced by ~150 cycles then that would be enough? This is so much fun!
Keep in mind that there are PPU cycles and CPU cycles... In NTSC, the PPU is 3 times faster than the CPU (i.e. during one CPU cycle, the PPU draws 3 pixels). Make sure you're not mixing the cycle types in your calculations.
tokumaru wrote:
Keep in mind that there are PPU cycles and CPU cycles... In NTSC, the PPU is 3 times faster than the CPU (i.e. during one CPU cycle, the PPU draws 3 pixels). Make sure you're not mixing the cycle types in your calculations.
Reference:
https://wiki.nesdev.com/w/index.php/Cyc ... cle_counts -- total number of CPU cycles available in NMI, on NTSC, is 2273. An entire frame -- when the PPU begins drawing one -- takes about 29780 CPU cycles to complete. So the latter is how much time essentially you have for your "main loop", and the former is how much time you have available in NMI.
To further expand on what tokumaru said, with regards to Mesen specifically: in Debugger, there are two fields -- "CPU Status: Cycles" and "PPU Status: Cycle". Note that one is plural, the other singular.
"CPU Status: Cycles" is a 32-bit signed counter, indicating how many CPU cycles have passed since the game was reset. In more recent AppVeyor builds, its been upgraded to a 64-bit signed counter (which is not backwards-compatible with previous save states, just as a comment in passing).
I also don't know why this value isn't *unsigned*, but whatever. Edit: Sour and I have discussed this. The number is actually an unsigned 64-bit integer; the Trace Logger documentation says 32-bit signed, AppVeyor commit comments said "64-bit int" (when increasing the size), etc. but it is in fact unsigned.
"PPU Status: Cycle" effectively ranges from 0 to 340, incrementing gradually when/as the PPU draws. It's reset to 0 every frame. Details of what the PPU does during each PPU cycle
is available.
Wow tokumaru... ok, so that means we only have around 5,569 cpu cycles to draw an entire screen? And my screen drawing function was just reduced to 16,536 cycles... which, would have been sufficient with my messed up math. Ahh, ok, I guess it's not possible for me to draw blinkingless screens outside of vblank. Maybe I'll try again tomorrow.
Thank you koitsu for the info and links... will look at them tomorrow.
tokumaru, thank you so much for teaching me 1 CPU cycle==3 PPU cycles.
unregistered wrote:
disable rendering when x>=192 (while each screen is drawn horizontally... right?)
No. Screens are drawn from top to bottom. The range of 192<=x<256 is when, within any given scanline, you have to turn off rendering, IF you do, in order for sprites to not be broken the next time rendering turns on.
There's no restriction on which scanline number (Y) you turn off rendering, but it will produce a solid-colored bar at the bottom of the screen.
unregistered wrote:
and my screen drawing currently takes 16,832 cycles,
The entire amount of time the CPU has per redraw, regardless of whether the PPU is drawing, is 341*262/3 ~~ 29780. Your code needs to be a
lot faster for "only letting the PPU draw part of the time" to be an adequate solution.
I'd
strongly recommend figuring out how to divide uploads across multiple refreshes. You're going to need it anyway.
Thank you koitsu for the wiki links, hadn't read those pages... that's really cool that info is available!
lidnariq wrote:
unregistered wrote:
disable rendering when x>=192 (while each screen is drawn horizontally... right?)
No. Screens are drawn from top to bottom. The range of 192<=x<256 is when, within any given scanline, you have to turn off rendering, IF you do, in order for sprites to not be broken the next time rendering turns on.
There's no restriction on which scanline number (Y) you turn off rendering, but it will produce a solid-colored bar at the bottom of the screen.
By horizontally, I meant that bit2 of $2000 was 0. After looking at that PPU Registers wiki page again, it says that bit determines how the CPU writes to VRAM. So writing to VRAM is filling that buffer with info and then the PPU reads that RAM buffer while drawing a screen? If so, I'm sorry for my mistake. But mistakes are ok.
Ahh! So that's much better! We just have to disable rendering when the PPU is drawing pixels, from pixel 192 to pixel 255, on any scanline!
Sigh
, I thought your 'x' was just a variable name, but it also was specifying that the limit you were trying to teach is a horizontal limit. So, that is only necessary to protect sprite locations? Thought you said it also affected the attribute tables...
A solid bar at the bottom of the screen has never appeared for me.
lidnariq wrote:
unregistered wrote:
and my screen drawing currently takes 16,832 cycles,
The entire amount of time the CPU has per redraw, regardless of whether the PPU is drawing, is 341*262/3 ~~ 29780. Your code needs to be a
lot faster for "only letting the PPU draw part of the time" to be an adequate solution.
My code is now 16536 cycles; it's plenty quick for just updating the screen.
lidnariq wrote:
I'd strongly recommend figuring out how to divide uploads across multiple refreshes. You're going to need it anyway.
Thank you for your recomendation!
I'll definitly think about this... time for lunch.
The 16,536 cycles need to happen while rendering is turned off. Turning rendering off and on rapidly will cause flicker, which could trigger seizures in sensitive players. If you want to update anything without cutting to a blank screen, you'll need to break the update into shorter chunks that fit into 2200 cycles or so. For example, the Action 53 menu and 240p Test Suite help system update one 128-byte line of VWF text per frame, taking roughly a third of a second in all to get the new text up. I guess you could heavily letterbox the display so that only about 120 scanlines are visible in order to increase the update time every frame, but I doubt that'd go over very well.
unregistered wrote:
We just have to disable rendering when the PPU is drawing pixels, from pixel 192 to pixel 255, on any scanline!
Sigh
, I thought your 'x' was just a variable name, but it also was specifying that the limit you were trying to teach is a horizontal limit. So, that is only necessary to protect sprite locations? Thought you said it also affected the attribute tables...
Sorry again lidnariq. It was supercat who told me:
supercat, on page 99, wrote:
2. On machines designed for connect to NTSC-based televisions, the memory cells used to hold sprite positions and attributes (Object Attribute Memory, or OAM) may arbitrarily flip state if rendering is disabled for much longer than vblank.
and I made another mistake thinking "attributes" meant attribute tables.
But, now it makes sense that I don't need to worry about 192>=x<256 bc we aren't using OAM while the screens are being drawn. Sprites are disabled.
tepples wrote:
The 16,536 cycles need to happen while rendering is turned off. Turning rendering off and on rapidly will cause flicker, which could trigger seizures in sensitive players. If you want to update anything without cutting to a blank screen, you'll need to break the update into shorter chunks that fit into 2200 cycles or so.
I don't want the screen to blink either.
lidnariq, on page 99, wrote:
Quote:
it would be really cool to see it work without blinking.
The only way you're getting that is by either rationing the amount you upload per vblank, or using advanced tricks to turn on rendering late or turn it off early.
So lidnariq and tepples, you both recommend portioning screen drawing inside of vblank. But, it is also possible to avoid the blinking buy turning rendering on late?
Hmm... I'll have to spend time with the wiki, I guess... currently, I'd like to draw the screen all at once, but you both are recommending otherwise.
unregistered wrote:
So lidnariq and tepples, you both recommend portioning screen drawing inside of vblank. But, it is also possible to avoid the blinking buy turning rendering on late?
Turning rendering on late poses two problems:
1. The position of all the tiles in the background will be affected by the position of the beam when rendering starts. If you know where the beam will be, this can be accounted for, but most techniques of figuring out where the beam is (e.g. sprite 0 hits) would require that rendering already be on.
2. NTSC machines will bounce between two chroma phases if rendering is enabled at the start of line 20, or cycle among three chroma phases if it isn't. For most kinds of display content, cycling among three will create an annoying shimmer effect.
While there are ways by which those problems can be overcome, it's probably easier to try to steal time at the bottom of the screen by disabling rendering early.
supercat wrote:
While there are ways by which those problems can be overcome, it's probably easier to try to steal time at the bottom of the screen by disabling rendering early.
Thank you supercat.
When you and lidnariq say "early", does that mean rendering should be disabled before pixel 8 on scanline 0 (when horiz (v) is first incremented)?
No, that would only give me two cpu cycles... so maybe should rendering be disabled on scanline -1? Is it possible to disable rendering near the end of vblank? Guess I'll try that.
unregistered wrote:
supercat wrote:
While there are ways by which those problems can be overcome, it's probably easier to try to steal time at the bottom of the screen by disabling rendering early.
Thank you supercat.
When you and lidnariq say "early", does that mean rendering should be disabled before pixel 8 on scanline 0 (when horiz (v) is first incremented)?
No, that would only give me two cpu cycles... so maybe should rendering be disabled on scanline -1? Is it possible to disable rendering near the end of vblank? Guess I'll try that.
If you draw e.g. a solid line at the bottom of the "interesting" part of the screen using background tiles, and place sprite 0 so that it hits that line, the "sprite 0 collision" flag will go high when scanning reaches that part of the screen. If you wait for that flag to go high and then disable rendering, you'll be able to render whatever you want provided that before the end of vblank you reload the OAM and re-enable rendering. Note that if you fail to re-enable rendering before the end of vblank, your background tiles will likely appear shifted vertically. Note that this or other factors may result in the sprite zero trigger failing to occur. You should probably arrange things so that if that happens and the NMI fires, the game can recover, but that otherwise the NMI will do nothing.
supercat wrote:
unregistered wrote:
When you and lidnariq say "early", does that mean rendering should be disabled before pixel 8 on scanline 0 (when horiz (v) is first incremented)?
No, that would only give me two cpu cycles... so maybe should rendering be disabled on scanline -1? Is it possible to disable rendering near the end of vblank? Guess I'll try that.
If you draw e.g. a solid line at the bottom of the "interesting" part of the screen using background tiles, and place sprite 0 so that it hits that line, the "sprite 0 collision" flag will go high when scanning reaches that part of the screen. If you wait for that flag to go high and then disable rendering, you'll be able to render whatever you want provided that before the end of vblank you reload the OAM and re-enable rendering. Note that if you fail to re-enable rendering before the end of vblank, your background tiles will likely appear shifted vertically. Note that this or other factors may result in the sprite zero trigger failing to occur. You should probably arrange things so that if that happens and the NMI fires, the game can recover, but that otherwise the NMI will do nothing.
So this is a reply about how to successfuly disable rendering late to my question about disabling rendering "early"? After being confused for a bit, and then rereading your previous reply, that is my question. Just trying to understand...
unregistered wrote:
supercat wrote:
unregistered wrote:
When you and lidnariq say "early", does that mean rendering should be disabled before pixel 8 on scanline 0 (when horiz (v) is first incremented)?
No, that would only give me two cpu cycles... so maybe should rendering be disabled on scanline -1? Is it possible to disable rendering near the end of vblank? Guess I'll try that.
If you draw e.g. a solid line at the bottom of the "interesting" part of the screen using background tiles, and place sprite 0 so that it hits that line, the "sprite 0 collision" flag will go high when scanning reaches that part of the screen. If you wait for that flag to go high and then disable rendering, you'll be able to render whatever you want provided that before the end of vblank you reload the OAM and re-enable rendering. Note that if you fail to re-enable rendering before the end of vblank, your background tiles will likely appear shifted vertically. Note that this or other factors may result in the sprite zero trigger failing to occur. You should probably arrange things so that if that happens and the NMI fires, the game can recover, but that otherwise the NMI will do nothing.
So this is a reply about how to successfuly disable rendering late to my question about disabling rendering "early"? After being confused for a bit, and then rereading your previous reply, that is my question. Just trying to understand...
Vertical blank starts (and rendering stops, even if otherwise enabled) when the beam gets very close to the bottom of the screen. If there's a point higher on the frame beyond which everything just shows the background color, however, one can disable rendering for the balance of the frame without causing visual disruption because everything on the frame will either have been drawn already (so nothing afterward would affect it) or simply show the background color (which it would do whether rendering is on or off).
The goal, then, is to disable rendering at a point which is late in the frame (after everything of interest has been drawn), but earlier than the point when rendering would otherwise stop (the beginning of vblank).
supercat wrote:
unregistered wrote:
supercat wrote:
If you draw e.g. a solid line at the bottom of the "interesting" part of the screen using background tiles, and place sprite 0 so that it hits that line, the "sprite 0 collision" flag will go high when scanning reaches that part of the screen. If you wait for that flag to go high and then disable rendering, you'll be able to render whatever you want provided that before the end of vblank you reload the OAM and re-enable rendering. Note that if you fail to re-enable rendering before the end of vblank, your background tiles will likely appear shifted vertically. Note that this or other factors may result in the sprite zero trigger failing to occur. You should probably arrange things so that if that happens and the NMI fires, the game can recover, but that otherwise the NMI will do nothing.
So this is a reply about how to successfuly disable rendering late to my question about disabling rendering "early"? After being confused for a bit, and then rereading your previous reply, that is my question. Just trying to understand...
Vertical blank starts (and rendering stops, even if otherwise enabled) when the beam gets very close to the bottom of the screen. If there's a point higher on the frame beyond which everything just shows the background color, however, one can disable rendering for the balance of the frame without causing visual disruption because everything on the frame will either have been drawn already (so nothing afterward would affect it) or simply show the background color (which it would do whether rendering is on or off).
The goal, then, is to disable rendering at a point which is late in the frame (after everything of interest has been drawn), but earlier than the point when rendering would otherwise stop (the beginning of vblank).
Ah, thank you so much supercat!
Understand "early" so much better now!
Think I understand now why my >16k-CPU-cycles screen drawing code is way to many cycles... drawing a solid line underneath the "interesting" part would only give me a row of 16x16 pixel metatiles so about a little less than 1818 CPU cycles to draw part of the screen and reenable rendering... I guess then vblank could draw part of that screen too. So, I'll follow lidnariq's and tepples' advice and draw my screen in sections. !
!! But, when drawing the screen in sections after the first little section is drawn, vblank drawing can be skipped bc, since most of the screen will be blank, we'll be able to draw so much more of the screen! This is so exciting!
Was thinking maybe we could just draw the solid line at the top of the screen in those 1818 cycles so that most of the next frame could be used for drawing the screen, but then that would cause a blank screen to be displayed and blinking to occur, I guess.
This will be fun to attempt.
supercat wrote:
If you draw e.g. a solid line at the bottom of the "interesting" part of the screen using background tiles, and place sprite 0 so that it hits that line, the "sprite 0 collision" flag will go high when scanning reaches that part of the screen. If you wait for that flag to go high and then disable rendering, you'll be able to render whatever you want provided that before the end of vblank you reload the OAM and re-enable rendering.
You still have to mind that OAM refresh bug though... OAM corruption doesn't happen when you disable rendering, it happens when rendering resumes, so even an OAM DMA won't save you from that.
tokumaru wrote:
You still have to mind that OAM refresh bug though... OAM corruption doesn't happen when you disable rendering, it happens when rendering resumes, so even an OAM DMA won't save you from that.
From my understanding, the bug hits when the OAM address changes, hitting the old and/or new row. If one sets the address to zero before an OAM DMA, the corruption would occur (if it's going to at all) just before the DMA operation.
I won't pretend I know the details, but my understanding is that there's nothing you can do to prevent corruption (or make it happen before the DMA) if you have disabled rendering at a "bad time". Maybe there actually is some sort of magic trick that will fix it, but I seriously doubt it's as simple as setting the OAM address to 0 before the DMA, because that is what 99% of people do already and OAM corruption is still a thing.
Yes, if you have disabled rendering at a "bad" time, the only thing that will fix sprites is rendering another screen with garbage sprites.
I guess maybe it has something to do with what Visual2C02 calls "spr_ptr", but "spr_addr" is what is set by writes to $2003.
Maaaaybe you just need to turn off rendering at a "good" time, such as by letting rendering end naturally at Y=240, in order to let sprites work at all again.
After reading about changing
lda (frog), y into
lda frog, y to save a cycle, and reading that I would have to keep updating that frog address to change memory locations... I felt the only way to do that would be to use PRG-RAM. So after trying to set up PRG-RAM, the nes file assembles, but Mesen shows that $6000-$6FFF is "Save RAM" and our ROM is extremely conveluded... i.e. it doesn't seem to be correct (like reset at $C000 starts with many brk in the debugger, while it is correct in the .lst file).
My question is: can this easily be fixed? My questions are: is PRG-RAM the same as RAM? Like, I can't initialize it before the game starts? That would mean we'd have to... it wouldn't work like writeable PRG-ROM?
So, is it not possible to save a cycle on the NES like I read about on "6502 Hacks" by Mark S. Ackerman?
This would be so much faster if this was possible bc the y register wouldn't have to be reloaded... it's much quicker to reload the x register with tsx, since rendering is off.
lda (frog), y could be
lda frog, x... I was hoping that at least.
final edit: makes sense now bc lda can be assembled as an absolute. Sorry.
edit: I don't understand how that would even be possible since frog in lda (frog), y is a 16bit pointer and frog in lda frog, x is an 8bit address. Doesn't make sense... but, maybe that doesn't matter.
unregistered wrote:
After reading about changing lda (frog), y into lda frog, y to save a cycle, and reading that I would have to keep updating that frog address to change memory locations... I felt the only way to do that would be to use PRG-RAM. So after trying to set up PRG-RAM, the nes file assembles, but Mesen shows that $6000-$6FFF is "Save RAM" and our ROM is extremely conveluded... i.e. it doesn't seem to be correct (like reset at $C000 starts with many brk in the debugger, while it is correct in the .lst file).
After moving the_function that needs to be in PRG-RAM into an unused bank, and writting a short new-function to copy the_function into an appropriate spot in bank 0 of PRG-RAM and change its jmp correctly, and creating a label to mark that appropriate spot... the game assembles, but reset: still begins with a bunch of zeros. Any idea why? Is it not possible to run functions after they land in PRG-RAM?
If you're calling subroutines in RAM, whether for self-modifying code or for having additional fixed bank space, you have to copy them to RAM before you call them. See
"LOAD and RUN addresses" in ld65 Users Guide.
tepples wrote:
If you're calling subroutines in RAM, whether for self-modifying code or for having additional fixed bank space, you have to copy them to RAM before you call them. See
"LOAD and RUN addresses" in ld65 Users Guide.
unregistered wrote:
After moving the_function that needs to be in PRG-RAM into an unused bank, and writting a short new-function to copy the_function into an appropriate spot in bank 0 of PRG-RAM and change its jmp correctly, and creating a label to mark that appropriate spot... the game assembles, but reset: still begins with a bunch of zeros. Any idea why?
See my function has already been copied to RAM (for self-modifying code), though I can't see that bc our game never makes it through reset.
But, thank you tepples for making it clear that subroutines can be called in RAM on the NES!
I'll visit your link.
...maybe the PPU registers are set incorrectly...
Ah! So my asm6_ has successfully created a faulty .nes file (lots of brks), but when trying to assemble our game with asm6 it quits assembling after
pass 1...
tepples, can subroutines be run when they are inside 'Save RAM' $6000-$7000 in MMC1?
The site you linked to must be experiencing problems... I'll try again.
edit: sigh, guess I caused my asm6_ to sometimes assemble corrupt binaries.
tokumaru recommends asm6:
NESASM and ASM6 are equally simple IMO: both can create a ROM from nothing more than a single ASM file, without configuration files or complex command lines. If you're going for simplicity, you should pick one of these 2. I prefer ASM6because NESASM uses non-standard 6502 syntax for some things and it has been known to fail silently in the past, producing corrupt binaries without reporting any errors (some people say these bugs have been fixed).
I need to fix this sometime.
I was under the impression that Nintendo's big four FDS-to-cartridge ports (Metroid, Kid Icarus, The Legend of Zelda, and Super Mario Bros. 2: Mario Madness) run code in RAM at $6000. If you have dumped a game with such RAM, you can set an execute breakpoint on $6000-$7FFF to see for yourself.
Both ASM6 and NESASM have quirks that will cause you to scratch your head sometimes... You just have to keep looking for the cause of the problem so you can get to know your tools better.
It's definitely possible to run code at $6000-$7FFF, but you need to be careful when setting up the addresses for this code. Like tepples said, ca65 has a way for you to define the LOAD address (where the code is stored in ROM) and the RUN address (the PRG-RAM address you'll copy it to), but in ASM6 you have to do this manually, possibly like this:
Code:
;(...)
;save ROM address for later
ROMAddress = $
;change address to PRG-RAM
.base $6000
SubroutineStart:
;(...)
SubroutineEnd:
;restore ROM address, taking the code size into consideration
.base ROMAddress + (SubroutineEnd - SubroutineStart)
If you don't change the address to PRG-RAM, references to absolute addresses in that subroutine will use the ROM address, which is not where the subroutine be running from. And if you forget to take the subroutine's size into consideration when restoring the ROM address, the final size of the ROM bank will be wrong, resulting in an invalid NES file.
Ah, so PRG-RAM doesn't work like PRG-ROM? That's pretty obvious, but when setting up PRG-RAM like you showed, tokumaru, the address, changed with .base to PRG-RAM, helps absolute addresses be correct inside that subroutine, but we have to adjust the PRG-ROM address bc that subroutine is also in PRG-ROM? The addresses in the subroutine are pointing to PRG-RAM so the game will never get mixed up and try to run that section of PRG-ROM?
It seems I incorrectly set up the 4 PRG-RAM banks so that they were exactly like the PRG-ROM banks... bank0:
Code:
.base $6000
;seei is transfered here form bank 13
seei_:
.pad $6FF0
-reset_part1:
sei
ldx #$FF
txs ;set the stack pointer
stx $8000 ;reset the mapper
jmp reset_part2 ; must be in $C000-$FFED
.word vblank, -reset_part1, irq
and then bank1 through bank3 are then set up the same way (without the seei_ label).
Thank you so much tokumaru!
Going to try setting it up like you recommended.
edit: It seems my code wouldn't work anyways bc RAM never holds anything you want unless you load/store into it from ROM, like booker and you all taught me.
ENDEDIT.
tepples, your link always worked; rather, I forgot what I had learned earlier that github links don't work on this old computer.
Yeah, the complication comes from the fact that even though the code is supposed to run from RAM (which means that the PC must be set to the correct RAM section so all absolute references are correct), it's still stored in ROM, taking up space there.
I don't know if this is what you're doing, but you can't have that PRG-RAM code stored separately in the NES file, it must be stored within the PRG-ROM space. My example was a generic one that allows PRG-RAM code to be stored anywhere in PRG-ROM, but since you're working with full banks, you could just use the lower part of a PRG-ROM bank for your PRG-RAM stuff and manually .base the address to, say, $D000 ($1000 bytes after $C000) for the rest of the PRG-ROM. That way you won't need all the label math from my example. Either way, PRG-ROM banks should remain the same size as before, and any PRG-RAM code you have must be stored inside them, but with the correct PC value for the address where that code will run from.
YEAY! PRAISE GOD!!
My function is successfully running inside PRG-RAM!
Thank you so much tokumaru! Now to attempt to make it self-modify itself. This is so much fun!
It self-modifies itself successfully!
Have to reduce the blinking, but one more thing works! PRAISE GOD!!
ok, I'll be silent now.
After writting an address to $2006, so that writes to $2007 go to the correct spot on the screen, do I have an unlimited amount of time to write to that spot on the screen?
tepples has told me that writes to registers and RAM last indefinitly, until loss of power or the data is overwritten... $2006 is a PPU register, but can I quit drawing a screen and then resume at the next vblank without having to write $2006 before each section of the screen is drawn? That would be so cool!
edit: this would only be a valid question if $2006 was incremented after every $2007 write; I'll check the wiki
ENDEDIT.
I guess tepples would recommend I try this myself.
Yes, the VRAM address that you loaded through $2006 is incremented by 1 or 32 (depending on $2000 setting) after each $2007 write.
If rendering is turned off through $2001, you have as long as you need after setting the VRAM address to write data to $2007. If rendering is turned on, however, the PPU will continuously overwrite the VRAM address during rendering. This means you have to fit the upload into vertical blanking and reset the scroll position through $2005 and $2000 afterward.
One exception to RAM writes lasting until power loss is dynamic random access memory (DRAM). Unlike static random access memory (SRAM), values stored in DRAM will decay into unspecified values if not refreshed every so often, but more DRAM will fit on a chip of a given size than SRAM. Unlike other memory in the NES, OAM is DRAM, and OAM on the NTSC PPU is particularly touchy. The PPU automatically refreshes OAM when rendering is on, but turning rendering off for much longer than the duration of one vblank (1.3 ms) will cause OAM values to begin to decay.
tepples wrote:
Yes, the VRAM address that you loaded through $2006 is incremented by 1 or 32 (depending on $2000 setting) after each $2007 write.
If rendering is turned off through $2001, you have as long as you need after setting the VRAM address to write data to $2007. If rendering is turned on, however, the PPU will continuously overwrite the VRAM address during rendering. This means you have to fit the upload into vertical blanking and reset the scroll position through $2005 and $2000 afterward.
Ah so we also have to store that VRAM address and rewrite it to $2006 before drawing each section of the screen, since rendering will be enabled bc I'm going to try only writing the screen during vblank. You said earlier that we should try to write sections in 2200 cycles... it that the length of vblank? If so it would take around 8 frames for a screen to be written.
tepples wrote:
One exception to RAM writes lasting until power loss is dynamic random access memory (DRAM). Unlike static random access memory (SRAM), values stored in DRAM will decay into unspecified values if not refreshed every so often, but more DRAM will fit on a chip of a given size than SRAM. Unlike other memory in the NES, OAM is DRAM, and OAM on the NTSC PPU is particularly touchy. The PPU automatically refreshes OAM when rendering is on, but turning rendering off for much longer than the duration of one vblank (1.3 ms) will cause OAM values to begin to decay.
That's very cool to now know, thank you tepples!
tepples, on page 100, wrote:
The 16,536 cycles need to happen while rendering is turned off. Turning rendering off and on rapidly will cause flicker, which could trigger seizures in sensitive players. If you want to update anything without cutting to a blank screen, you'll need to break the update into shorter chunks that fit into 2200 cycles or so. For example, the Action 53 menu and 240p Test Suite help system update one 128-byte line of VWF text per frame, taking roughly a third of a second in all to get the new text up. I guess you could heavily letterbox the display so that only about 120 scanlines are visible in order to increase the update time every frame, but I doubt that'd go over very well.
You say, "Turning rendering off and on rapidly will cause flicker... ." tepples, waiting a frame with rendering unchanged, is that a long enough wait to eliminate flicker? Please define "rapidly"
or I guess I could just attempt this myself.
It seems that waiting 1 frame is much quicker and easier, less cycles, than getting my code to
wait 8 frames while drawing the screen in sections (even though that new code might work if I would complete its changing-$2006-appropriatelyness). This waiting a frame just occurred to me.
If you're cutting to a different scene, such as if the player character went off the side of the map or into a door, the player won't mind if the screen is off for a few frames. (If the transition occurs mid-jump, it needs to be faster.) But if you're repeatedly updating a single scene, on for 7 frames and off for 1 and repeat, that's a bit more annoying. What are you actually trying to do by updating the entire map? Are you trying to show the player character entering a new area, or are you trying to animate changes to the background in the same area, or are you trying to do neither of the above (in which case please describe)?
We're trying to animate changes to the background in the same Nametable. Would turning rendering off, drawing the entire screen, turning rendering on, waiting a frame, turning rendering off, drawing the entire next screen, and turning rendering on eleminate the flicker? I'm going to guess no bc that's a simple solution.
But, could it work?
I'm coming in late, but: you generally do not:
1. Try to write an entire nametable (read: entire screen) worth of data in VBlank/NMI -- reasons have been covered already I think, but it takes too long / too much data
2. Turn off rendering unless you are absolutely positively 100% certain you know why you need to do it. In general you need to design your game/thing so that you do not have to do this.
I don't understand why you're trying to do #1. I don't think Tepples does either. Is this because you want to make it easy on yourself? If so, you're not going to win this battle. You need to learn how to update the edges of the screen (left and right columns when using the increment-by-32 mode of PPU addressing). Yes, writing the engine code that does all of this is (IMO) complicated and "not fun", there's a lot you have to keep track of.
Does this game/thing use panning/scrolling at all? If you're trying to do everything in single-screen and you aren't using panning/scrolling, my advice is: don't. Make use of the memory in the adjacent nametable and flip-flop between them using bits in $2000. You draw to the one that isn't visible, then switch to it, rinse lather repeat. This is effectively what's called "double buffering" (probably a term you've encountered somewhere before).
If you're trying to animate a lot of stuff within the nametable, as in the FMV demos "Motion" by Chris Covell and "Bad Apple!! PV-FC 2" by someone else, then you should probably be using double buffering.
koitsu wrote:
Does this game/thing use panning/scrolling at all? If you're trying to do everything in single-screen and you aren't using panning/scrolling, my advice is: don't. Make use of the memory in the adjacent nametable and flip-flop between them using bits in $2000. You draw to the one that isn't visible, then switch to it, rinse lather repeat. This is effectively what's called "double buffering" (probably a term you've encountered somewhere before).
Scrolling can be combined with double-buffered animation provided one isn't scrolling too fast, by using a few frames to draw new screen content in the parts of the nametable that aren't being displayed, and then in the last VBLANK update the areas that overlap the old frame and switch to showing the new one.
koitsu wrote:
If you're trying to do everything in single-screen and you aren't using panning/scrolling, my advice is: don't. Make use of the memory in the adjacent nametable and flip-flop between them using bits in $2000. You draw to the one that isn't visible, then switch to it, rinse lather repeat. This is effectively what's called "double buffering" (probably a term you've encountered somewhere before).
Thank you koitsu for recommending double buffering!
It should be all set up now, but there is a weird problem, Mesen switches to delay-display the nametable that we're writing to the instant that $2006 is completely written to (after the low byte). By delay-display, I mean that nametable 1 is highlighted in Mesen's Nametable Viewer after $2400 is written to $2006, and also that nametable 1 is displayed the next frame. This is weird bc $2000 is never changed... like you said, "Make use of the memory in the adjacent nametable and flip-flop between then using bits in $2000."
My goal was to see the entire screen drawn in nametable 1 b4 flip-flopping. Now it's way confusing. Is the NES susposed to behave this way? If so, why?
And, otherwise I guess Mesen has been set up incorrectly by me.
edit: Also, the wiki didn't mention anything about $2006 changing what nametable is displayed. So maybe I've set Mesen up incorrectly?
$2006, $2005, and the two nametable bits in $2000 are all ways to access the same thing.
nesdevwiki:PPU scrolling
lidnariq wrote:
$2006, $2005, and the two nametable bits in $2000 are all ways to access the same thing.
nesdevwiki:PPU scrollingAhha! Thank you lidnariq!
I forgot they shared an internal register.
edit: Maybe that page could be linked to from the PPU registers page in the $2000, $2005, $2006 register sections... or on the PPU registers page maybe say something like, "these share an internal register"... that would be really helpful, IMHO.
Now, the blinking is gone since the game now draws a 5th of each screen inside vblank! Thank you all so much!
Time to decompress my sister's RLE... I'll be silent unless questions arrise.
edit: (this is an edit bc it's not important) our game isn't that efficient bc it
does draw an
8th of each screen inside of vblank. Not a 5th, I'm sorry for my mistake.
final-edit-note: it actually draws an 8th, not a 7th, so the first edit was adjusted accordingly.
unregistered, on pg. 98, wrote:
Ok, so asm6 is limited to 64 hex codes following its .hex. (i.e. When our game assembles, the cursor at the end of most of our .hex lines is in column 135. The hex codes start, after .hex, in column 7. 135-7=128. 128/2=64.) Why this limitation-have no clue right now. With 65 hex codes per line asm6 gives, "Not a number."
Now, it's evident that while, 64 hex codes following asm6's
.hex will assemble, the 64th hex code is incorrectly tranferred to the ROM during assembly. The 64th code on the first RLE line in our game is #$BC but it was assembled as #$0B, #$0C... so after limiting each line of the first screen to 62 hex codes they assemble just fine.
It makes some sense that each line would be limited to less than 64 hex codes, but we're using 62 per line bc RLE uses 2 hex codes for each different value-set (though I'm sure 63 would work just fine too).