MM9 famicom project: status

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
MM9 famicom project: status
by on (#50702)
Since I have been asking all kind of questions regarding sprite flipping etc, I think the least I could do is show a build of my current status, which is available here.

At less than 1h/40min per programming session, it takes an eternity to reach new milestone. I reached a new one today: my concept for sprite animation for the data using meta-meta sprite is working.

What is does:
- Play concrete man song made by Tssf (he re-made most of the soundtrack indirectly because of me so it's the least I can do is use his song right away)
- Scroll and show meta-tile based map for first part of level. Doesn't check limits.
- Animate megaman in running mode (non-stop)
- Switch mega man side based on direction (test of sprite flipping)
- Pressing up switch animation to running with gun (test switch animation)
- Pressing down switch animation to running without the gun (test switch animation)

There is no collision or anything, I'm not there yet. I have to celebrate every milestone as small victories.

I work on it everyday at lunch time only. I don't know when I will finish it but I didn't give up. I hope to see the light someday. It's hard but I'm ok with it. It I make my building blocks properly, later it should be faster to make it.

I hope you will enjoy it.

by on (#50703)
Very interesting indeed. Scrolling is the one thing that is tough. it seems you draw two columns of 16x16 tiles every few steps, which seems like it would work.

Are you going to modify Famitracker to make sound effects actually work? That's the one problem with using that engine.

This project is more than I would have done for a game, heh.

by on (#50704)
Sivak wrote:
Very interesting indeed. Scrolling is the one thing that is tough. it seems you draw two columns of 16x16 tiles every few steps, which seems like it would work.


For the scrolling, for now I use 2x2 meta-tiles and write 2 meta-tile columns at a time in inc32 mode. The columns are decoded in the main loop upon request and when ready, are drawn in the NMI. After passing 4 columns of tile, I update the content that you just passed. This procedure may change a little bit in the future. I may change back to meta-meta tiles if the size of the data is too big. For now I still have time to think about it.

Sivak wrote:
Are you going to modify Famitracker to make sound effects actually work? That's the one problem with using that engine.


To reuse patterns for the sound effect at the same time would be quite a task. You would basically need twice the variable to make it work or some fancy trick once you understand the core well (which I don't yet). So the answer is yes and no. I already modified the engine so you can disable channels on demand then you can play you own sound effect then re-activate the channel. Sound fx shouldn't be that complicated to make by hand compared to music so it's less an issue. If I find a way to re-use some track data and play it at the same time would be great but it's not one of my main goal for now. Just to be able to use the code for the music is more than a time saver. This way, it's easier for someone that know how to track to make music too. Once I have a way to play sound fx, custom way or using FT code, I will release the code for sure.

by on (#50705)
Well, it's interesting. What's inc32 mode?

MMC3 is the way to go. It'll be interesting to get it to scroll vertically.

I was wondering if you plan to setup a palette cycling thing to get the waterfalls to move (among other animations seen in other stages).

Also, just a very minor nitpick and I don't mean disrespect: I think the trees should use $08,$18 for their palettes instead of $07,$26.

by on (#50706)
Sivak wrote:
Well, it's interesting. What's inc32 mode?


Sorry, that's the way I'm used to call it ;) It means increment the VRAM address of the ppu by 32 so I can write colums of tiles.

Sivak wrote:
I was wondering if you plan to setup a palette cycling thing to get the waterfalls to move (among other animations seen in other stages).


Yes, this is planned but I'm not at that stage yet. I build small blocks by small blocks for the main engine. Once objects can move on the screen and interact between them, I will add those small details.

Sivak wrote:
Also, just a very minor nitpick and I don't mean disrespect: I think the trees should use $08,$18 for their palettes instead of $07,$26.


I was using different color at the beginning. If you use FECUX, the color will look red and I'm aware so you're not nitpicking actually. On my old (1998) japanese TV, it look the right way, like the original when the TV setting are to their default values with my famicom. Let's just say that colors don't look the same that I was used with my US nes. This is why I chose those color. I will test it back when I have a chance with the one you proposed on my famicom.

by on (#50707)
This is a great start.
BTW I'm pretty sure all Mega Man games used 32x32 meta-meta tiles made of 4 16x16 metatiles, altough that don't imply MM9 have to be this way.

About sound effects, the only thing you really have to do is double the amount of channels (and of memory reserved to them...), and write to hardware regs of the lower 4 channels (music) only if corresponding channel in the upper 4 channels (SFX) are inactive. You just need to init some pointers and counters to start a SFX like you do it for the music.
Also if you don't use all channels for SFX (for example if you never use triangle) you can save memory by removing the triangle-SFX channel.

by on (#50710)
@Sivak:

I checked my code and the colors I was using before are exactly the one you said. On my TV, it was looking more muddy and green. I will need to think what to do about it in the future.

@Bregalad:

Thanks for the comment. As for the virtual channels, it is a possibility. The only thing is that the sound driver already has a memory footprint of ~200 bytes: this mean the memory could double easily. Modifying to allow this feature will require to update most of the code of the driver. Just thinking about it, I feel pain everywhere ;) If I can find the time, I will see what I can do. That would be useful to use the same format music and sfx.

by on (#51031)
Wow, very neat demo. I drool just thinking of the future this could have. Mind you it could end up a 16meg NES game. :P

by on (#51035)
It will all depends how far that I will make that proof of concept go. For now, my first target is to make 1 level, intro and selection screens. After having the code working for one level, it will be easier to evaluate how much time it could take to make the other ones.

For now, at less than 30 min per day of coding (when I can code, and often only during the week).. The road will be long. But for now this is an interesting project.

by on (#54970)
At last, after a few months that I couldn't touch it I was able to work on my project this weekend. Here's the current build.

What is different compared to the first build in september:
- Animation of character work (not hard coded)
- Can change state from stand still/blink, run, jump, and weapon use
- Basic collision detection is done
- Velocity for jump is done
- Palette animation

It's quite a big step compared to the first build I posted but it's still a looonng way to go.

It's not easy to reproduce an already existing game since you have to follow the rules exactly. One rule that I need to implement is the velocity factor when walking. For now, 1 pixel per frame is too slow. I will need to adapt to something like MM2 which is 1.25 pixel per frame. This as an impact on many things but I will figure out something. So I'm still working on it.

Any comment will be appreciated.

by on (#54979)
That's pretty cool!

You're right, it does need work, the physics aren't quite there yet but it's still very impressive. It feels like it takes slightly longer than it should to start moving when you press a button, but maybe this is just due to the slow running speed.

Did you notice that if you're right next to a block and press towards it briefly, Megaman's leg bends for some reason?

by on (#54981)
Great, it's really starting to look like a game! Collision still needs a lot of work, I was able to do many strange things like getting Mega Man stuck with the jump frame while standing and walking, getting him to stand on blocks that are part of walls, things like that.

But it's starting to look pretty good. Even though you said you wouldn't do it, it would be cool if you made the whole game, because then I'd be able to play it! =) This would be my only chance, since I don't have any of the current gen consoles. I was able to emulate the Wii version, but it was too slow and flickery to be enjoyable.

by on (#54982)
Great work so far! I'm with tokumaru on this one. I probably won't be able to play the Wii version, since I have no newest gen consoles. I would definitely love to be able to play this one.

And yes, I agree that the collision needs some work. What I do for my game is I have 4 universal routines I use for rejecting up, down, left, and right from a solid metatile. These routines are called only if an object moves inside of a solid tile, of course. For rejecting an object down (for if they hit their head on a ceiling), you take the Y pixel coordinate, AND it with #$F0, and then add #$10 (assuming you have 16x16 pixel metatiles). The same thing goes for rejecting to the right, you just do the same thing with the X coordinate. This also is assuming the X/Y coordinates represent the top-left corner of the object. For rejecting left and up, you have to know the exact width or height of the object respectively. Knowing this, you take the height or width (EOR #$FF) + 1, then AND that by #$0F to get the low 4 bits, then you save that value to add it to the X or Y coordinate (depending on if you're rejecting left or up respectively) ANDed by #$F0.

If you pull that off correctly, you have simple and reliable rejection from solid tiles so you don't have this stuck-in-the-wall dilemma. Sorry if that makes no sense, but that's how I handle collision with solid tiles.

by on (#54983)
Pretty nice. Obviously getting stuck in walls is wrong, but it's nothing to sneer at at this point.

I wonder if I'll ever do a game that scrolls. :shock:

by on (#54985)
Sivak wrote:
I wonder if I'll ever do a game that scrolls. :shock:

At the rate you are progressing, this is probably the next logical step! =)

THE NES NEEDS MOAR SCROLLING HOMEBREWS!!!

by on (#54999)
I should have wrote by "basic collision detection" it meant "not finished" ;) The only one that "eject" the character is when you hit the floor. I extract the value of how many pixel to eject based on the direction you enter the block when extracting the metatile attribute (tentative code). I didn't do the left/right one yet since I need to re-factor the velocity and want to do it at the same time.

UncleSporky wrote:
It feels like it takes slightly longer than it should to start moving when you press a button, but maybe this is just due to the slow running speed.


I guess in that case it just the frame timer to when the actor can actually start to move that is too long (6 or 8 frames?). It's hard to get exact value like the real game. Those details I will need to tweak by hand at a later stage.

For the "leg stuck" in the wall, I didn't know. The leg bend is normal when you start running but it shouldn't stay that way and should reset to stand still. It must be caused by one of the re-factoring I did yesterday and the animation is not reset properly. Thanks to point it out.

tokumaru wrote:
Even though you said you wouldn't do it, it would be cool if you made the whole game, because then I'd be able to play it! =)


If you're ready to wait many, many years, I can see what I can do :) Once my second child get born next month... Let just say my free time will be almost non existent. I will have higher priority things to take care of.

@Celius

On the first read I didn't get the way you do it but I will try to read it again since it could be simpler than my way. Here's how I do it (but don't use the results yet for left/right ejection yet):

Code:
   ; First check if we have to extract the index of how deep inside the block
   ; is posX/posY and from which direcion
   lda Param::direction
   beq directionInfoFinish      ; Nothing set? No direction to extract

   lda #DIRECTION_LEFT      ; Comes from left side of block?
   and Param::direction
   beq notFromLeftSide      ; No, try right side
   
   lda zpView_firstMetatileColumnIndex ; Yes, use first metatile column index for value inside block
   sta Local::columnIndex      ; and store inside temp variable
   jmp notFromRightSide      ; Skip right since cannot be both at same time

notFromLeftSide:   
   lda #DIRECTION_RIGHT      ; Comes from right side of the block?
   and Param::direction
   beq notFromRightSide      ; No, try to test from top
   
   sec
   lda #METATILE_SIZE      ; To find value, we have to take the max of metatile size
   sbc zpView_firstMetatileColumnIndex ; minus position of index
   sta Local::columnIndex      ; and store it in temp variable. No skip here
   
notFromRightSide:   
   lda #DIRECTION_UP      ; Comes from top of the block?
   and Param::direction
   beq notFromTopSide      ; No, try bottom side

   lda Param::posY         ; Remove higher part so we get value from 0 to 15
   and #$0F
   sta Local::rowIndex      ; and store it in temp variable
   jmp directionInfoFinish      ; Skip bottom since cannot be both at same time

notFromTopSide:
   lda Param::posY         ; Remove higher part so we get value from 0 to 15
   and #$0F         ;
   sta Local::rowIndex      ; And save it
   lda #METATILE_SIZE      ; To find value, we have to take max of metatile size
   sbc Local::rowIndex      ; minus location found
   sta Local::rowIndex      ; And save it
   
directionInfoFinish:   


Maybe it's more complicated than nothing but once I use the value for left/right, it should work. The view prepare the values. The actor manager take care of what to do with the results.

Sivak wrote:
I wonder if I'll ever do a game that scrolls. :shock:


I'm sure you will. You already have a lot of code that can be re-used in your next game (game ai, animation of sprite etc). Once you get in your engine a way to scroll you're map, you're ready to go. And it's has nothing to do with MMC3: my demo in it's current state would run without a mapper at all if I wanted to. You can always ask me question if there is something that you want to know on how I did it.

Now the scroll is working fine because the actor movement is 1 pixel per frame. Once I add 1.25 at the velocity... I guess I will have to fix a lot of limit bugs..

@everyone:

By the way, is there any other homebrew that actually scroll like what I'm trying to do for my MM9 proof of concept except for sack of floor wich is in nbasic? I want to check how they do it for velocity. For now I have a blank and can't remember one homebrew with source.

by on (#55002)
tokumaru wrote:
Sivak wrote:
I wonder if I'll ever do a game that scrolls. :shock:

At the rate you are progressing, this is probably the next logical step! =)

THE NES NEEDS MOAR SCROLLING HOMEBREWS!!!


You know, despite that Sivak has already made one, I'm working on a game that doesn't scroll like his. It's not due to difficulties in writing tiles in real time, it's because the game I am trying to emulate doesn't scroll either. :) It's all a matter of style.

by on (#55004)
I didn't mean that a non-scrolling game can't possibly be fun/good (it can still be an interesting game if something else is original enough among the homebrews), but that's the one thing that the homebrew scene is really lacking.

This game for example, doesn't really have any scrolling except for screen transitions (which is completely different than continuous scrolling) but it appears so well made that I really feel like playing it. I have several good things to say about it, even if scrolling isn't one of them.

by on (#55006)
If I'm not going to have a lot of time to work on President, perhaps I should just package up its source tree so far so that others can look at it.

by on (#55259)
I was able to work this week to correct the camera so it would have the same velocity as the real game (or really close). I use the same parameters as MM2 since they said many time that the game was based on this one. Here's the 2010-01-17 build.

@UncleSporky:
The scrolling speed should now be up to par with MM9 or close. Does it still feel like a slow start or it seems better? The count of frame before starting (feet move forward before running) was not adjusted, just the velocity.

Regarding collision detection, I just added a quick hack before posting the file to block left and right a little bit. There is one bug were you can jump on top of something and MM will stay in the jump animation and I will fix it later. My main goal was to fix the scrolling so it could accept more than 1 pixel per frame. It was hell and took a lot of re-factoring but now it seems to work. It has more the MM feel and speed compared to before. It works on my famicom too.

by on (#55262)
Amazing and ambitious project... keep up the good work :-)

by on (#55263)
nesworld wrote:
Amazing and ambiguous project...

Are you sure this is the word you were going for? Didn't you mean "ambitious"? =)

by on (#55266)
Yeah, dunno what I was smoking when I wrote that ;)

by on (#55277)
Banshaku wrote:
@UncleSporky:
The scrolling speed should now be up to par with MM9 or close. Does it still feel like a slow start or it seems better? The count of frame before starting (feet move forward before running) was not adjusted, just the velocity.

No, it still feels slow and weird to me. For comparison, load up Megaman 6 and tap forward rapidly. You'll see his leg bend first, but you'll also be able to inch forward. In your engine all this accomplishes is wiggling his leg.

Primarily it just takes a few frames too long for him to actually begin moving.

by on (#55281)
UncleSporky wrote:
No, it still feels slow and weird to me. For comparison, load up Megaman 6 and tap forward rapidly. You'll see his leg bend first, but you'll also be able to inch forward. In your engine all this accomplishes is wiggling his leg.

Primarily it just takes a few frames too long for him to actually begin moving.


Depending on which version of MM you play the delay before moving is different. MM6 does react a lot faster when you tap rapidly, I remember that. I need to re-test MM9 to see how close is it to MM6. If I remember well, you could tap very little and only the leg was moving, which I reproduced. If you did press a little bit longer, it started to move without walking but yes, it does feel "sticky" in a way. Changing the delay is only one constant so I will try to reduce it and see how it goes.

by on (#55284)
Banshaku wrote:
Depending on which version of MM you play the delay before moving is different.

If you are actually treating it as a "delay", that might be the problem. I just checked all MM games, and in the first 2 he doesn't always move on quick button taps, but from 3 onwards he always does.

No matter the game though, there is no delay in the programmatic sense of the word, it just happens that a fractional displacement is applied every time. So, in MM 1 and 2, even though he doesn't always move on quick taps, eventually he'll move a pixel if you tap the button enough times for the fractions to add up to a whole pixel.

In your demo, we can tap forever and he'll never move, he'll just twitch the leg and remain on the same spot. So I suggest you should add another byte to the coordinates in order to handle the fractional parts, if you aren't already doing so.

Other than that, things have improved a lot. Collision detection is much better and it's got a much better MM feel now. You just have to fix these walking issues.

by on (#55286)
When inching forward in Megaman 2, you move forward one half pixel each try.
In Megaman 3-6, it's a whole pixel.
In Megaman 1, it seems to move you forward 3/5 of the time.

by on (#55287)
Dwedit wrote:
In Megaman 3-6, it's a whole pixel.

He appeared to move 2 pixels every few tries, so I'm guessing it's slightly more than a whole pixel every try, as the fractions eventually add up to an extra pixel.

by on (#55295)
It's a known fact that the ground is less "slippery" in Mega Man 3-6. Probably Megaman "accelerate" in 1-2 while he's straight at his max speed in 3-6, I might be wrong tough. Samme applies when he stops.

Personally what mother me the most is something when Megaman is landing. It really doesn't look real to me - but I might be wrong.

by on (#55297)
Bregalad wrote:
Personally what mother me the most is something when Megaman is landing. It really doesn't look real to me - but I might be wrong.

I noticed the same, he seems to sink in to the floor for one frame or so.

by on (#55298)
For now I just delay it a few frames so if you press "just enough" and release the button, it will move one pixel. This is the code I did before I started to re-factor with velocity. This part has an impact then.

For the information about how much pixel to move in X condition, I found that site useful, a TAS side about Mega man. There was a lot of interesting tidbits there.

Regarding the jump, I didn't saw it yet maybe because I was focusing more on the scrolling to work properly. I knew people would argument more on small details since I try to reproduce a mega man's game but that was to be expected ;) Either the collision logic is wrong for floor or the frames for the jump animation is not centered properly, causing it to enter the floor. The jumping frame is taller than the usual ones so I will need to check the data.

edit:

Tested all nes version of mega man very fast and for the jump, 1 to 5 doesn't enter the floor but 6 does (why). One thing they all do but I don't is that before touching the floor, they go 1 frame in running state then stop moving. In my own code, if there if no button pressed, I go standing right away, if not, I just run. I guess they did it that way to simplify the logic. I may change it later too if it does make it simpler.

For the tapping, all of them are sensible to different degree (less in earlier ones and more later on), more then my current build so I will take that into consideration.

Edit2:

In MM9, Mega man's feet do enter the floor so it is "faithful" to the original hmm.. So it becomes more a matter of taste. For the tapping, it is quite sensible so this is a fix for sure. But the "landing in the feet in the ground" bug, if we can call it that way, doesn't have much impact on game play so I will think about it if I should improve MM9 or stick to the way it is ;)

by on (#55299)
Sorry for the double post since I think that question should be in that thread anyway. I still have all kind of small bugs to fix but I think now I'm ready for the next step. I need to scroll vertically. A normal scroll is not really an issue IF there is no gap between the screens (...). MM9 doesn't have 16x15 metatiles per screen but 16x14... So either you create new data or you will have to be fancy with the raster effect during scrolling to hide the black tiles.

This is how the map looks like at the moment:

Image

This screenshot from fceux, as you can see, there is a gap between the 2 screens. Now what I want to know is (and I think we talked about it a little before and maybe it was Tokumaru that was giving ideas on the subject), can I do some raster effect to skip the empty spot while scrolling? I guess that must be possible since people used raster tricks to show some info bar or something. I don't mind to use any IRQ if I have to since it's just a proof of concept anyway. If I don't need then even better. I don't mind using any mapper to do it but first, is it possible at all? I think the answer is yes but I'm not sure yet how.

For the people that likes details, I changed the logic for the tapping by using velocity. The new build is here. I think it's better now ;)

Edit:
After retesting the build, the tapping could be a little bit too fast. I may have to adjust the ratio to something slower but that's not a difficult task now that it's working.

Edit2:
Updated the build again to be slower for tapping. Just updated this message since it was not worth it to make new one.

by on (#55300)
It's possible to skip the gap without a raster effect by only scrolling 224 pixels. The most obvious way complicates map updates: you're splitting map writes across the bottom and top of the nametable all the time.

But if you want to simplify your map update routines, use a sprite 0-triggered scroll split.

by on (#55301)
I guess I'm not in the mood for re-factoring (again) the scrolling so I will investigate the sprite-0 hit since it will really simplify the nt update. There must be a sample somewhere, hopefully on the wiki or is it?

by on (#55302)
Banshaku wrote:
I guess I'm not in the mood for re-factoring (again) the scrolling so I will investigate the sprite-0 hit since it will really simplify the nt update. There must be a sample somewhere, hopefully on the wiki or is it?

stuff by Damian Yerrick: My "Tall Pixel" demo does a scroll split every 3 scanlines, and it works on my NES+PowerPak.

by on (#55304)
Banshaku wrote:
Tested all nes version of mega man very fast and for the jump, 1 to 5 doesn't enter the floor but 6 does (why). One thing they all do but I don't is that before touching the floor, they go 1 frame in running state then stop moving. In my own code, if there if no button pressed, I go standing right away, if not, I just run. I guess they did it that way to simplify the logic. I may change it later too if it does make it simpler.


This is what I was talking about. Sorry, but I think it's very important you make Mega Man landing correctly. It really makes the animation smoother to have it landing on both of his feet before standing up. You do that when you jump in real life too (you absorb the energy in your knees instead of your back).

I don't know what to suggest for the vertical scrolling. A raster effect is certainly possible (writing $00 twice to $2006 when you'd encounter the "black bar" should do the trick) - the hard part is that the time you'd do the write will scroll across the whole screen so you'd want to wait for the sprite zero hit BEFORE the frame logic if it's in the first half of the screen, and AFTER the frame logic if it's in the lower half. This assumes the frame logic takes less than 50% of the CPU.

Otherwise I'd say create data for 15 rows or just deal with 14 rows and non-aligned level to nametable - I deal with screejs non-aligned vertically with nametable in my game, but aligned horizontally, as it would be your case. You just need a few more variables to know where the topleft of the screen is in the nametable, and code able to split attribute blocks in lower/upper half.

by on (#55329)
Banshaku wrote:
Updated the build again to be slower for tapping.

Ah! That's more like it! =)

tepples wrote:
It's possible to skip the gap without a raster effect by only scrolling 224 pixels. The most obvious way complicates map updates: you're splitting map writes across the bottom and top of the nametable all the time.

I was gonna suggest the same thing, because I don't know if you can guarantee a sprite 0 hit every time (what if a particular screen has transparent background where the hit is supposed to happen?).

Banshaku wrote:
I guess I'm not in the mood for re-factoring (again) the scrolling

I just though of a trick that won't require any refactoring, or IRQs. I looked at how MM1 does it, and it first decodes the new screen horizontally (apparently the decoding is meant to work horizontally only) to the hidden NT, and then copies it to the visible one row by row as the screen scrolls up. What if you do the exact same thing, but when copying from the hidden NT to the visible one you skip the unused row?

What I mean is, the screen that is decoded to the hidden NT is perfectly aligned vertically, but when you copy it for the scrolling sequence the copy is not aligned, but once the scrolling sequence is over you can set the scrolling to show the other NT where you copied the data from, which IS aligned vertically, so you can continue scrolling from it without a single change to your scrolling engine.

Let me show it with drawings, which are much better than words. Here is Mega Man going up some stairs:

Code:
+------------+------------+
|  (hidden)  |           -|
|            |           M|
|            |           -|
|            |           -|
|            |FFFFFFFFFFFF|
|            |FFFFFFFFFFFF|
|            S............|
+------------+------------+

The floor is "F", Mega Man is "M" and "............" is the blank row of blocks. The left NT is hidden because the scroll is at "S". OK, when MM touches the top of the screen, you decode the screen that goes on top of the current one to the hidden NT like this:

Code:
+------------+------------+
|           -|           M|
|  -FFFFFFFFF|           -|
|  -         |           -|
|  -         |           -|
|  FFFFFFFFF-|FFFFFFFFFFFF|
|  FFFFFFFFF-|FFFFFFFFFFFF|
|............S............|
+------------+------------+

Yup, with the blank row at the bottom, so it's aligned as your scrolling engine likes it. Now you copy the contents of the hidden NT to the visible one row by row as you scroll up, but shifted one row down so that there is no empty row between the screens:

Code:
+------------+------------+
|           -S............|
|  -FFFFFFFFF|           -|
|  -         |  -FFFFFFFFF|
|  -         |  -         |
|  FFFFFFFFF-|  -         |
|  FFFFFFFFF-|  FFFFFFFFF-|
|............|  FFFFFFFFFM|
+------------+------------+

So now you've scrolled to the top and the whole new scree is visible, but it's not vertically aligned to the NT, but the left side is perfectly aligned and has the same contents, so you can just move the scroll to it and continue as if nothing happened:

Code:
+------------+------------+
|           -|............|
|  -FFFFFFFFF|           -|
|  -         |  -FFFFFFFFF|
|  -         |  -         |
|  FFFFFFFFF-|  -         |
|  FFFFFFFFFM|  FFFFFFFFF-|
S............|  FFFFFFFFF-|
+------------+------------+

Mirroring is set to vertical all the time, no need to change it.

The thing you do have to worry about no matter what solution you pick (sprite 0, IRQs or my suggestion) are the black borders. You do have a black border at the top, but when scrolling you want the next screen to connect to the current one without gaps. With whatever method you use, the black border will turn into actual tiles when scrolling vertically, and once the scrolling is done the borders will come back. I guess the only way to avoid this is using IRQs to mask the top and bottom 8 scanlines, but IMO this is a lot of work and you're better off simply accepting some garbage at the edges (which several official MM games have - not 9, for obvious reasons).

Oh, one thing I forgot to mention about my method is that although the rows of tiles can be copied directly from one NT to the other, you will need to do some nibble work on the attributes, but honesty, that shouldn't be hard at all.

by on (#55337)
OK, I gave some more thought to my idea and I think I have the answer. I don't know at what rate (how many pixels per frame) the original game scrolls vertically, but if you are OK with 8 pixels per frame, my method can work without any glitches.

Here's an animated GIF of the process:

Image

I saw that you already have the new screen completely decoded by the time MM reaches the ladder, so it will be even easier. Every frame you copy a row of tiles from the hidden NT to the visible one, and blank the two scanlines immediately above it (this will keep the top and bottom 8 scanlines of the screen always black). Just do this until all rows are copied (you don't need to copy the 2 blank ones, because you are already blanking 2 rows above the each one copied).

The only thing that's slightly complicated about this process is that attributes can't be directly copied from one side to the other because the screens are offset by half an attribute block. So every 2 rows of tiles copied from the hidden side you have to take the corresponding attribute bytes, move the nibbles to the other half of the byte so that they can be inserted at the correct place in the other NT. Not complicated at all really, just slithly annoying to code.

Well, this is it. If you think 8 pixels per frame is OK when scrolling vertically I'm sure this is the most straightforward way to do it, and much easier than messing with sprite 0 hits, IRQs and mid-frame scroll changes.

by on (#55348)
That's a really cool trick tokumaru. I never would have thought to use one nametable to scroll and then flip the scroll to an aligned copy.

by on (#55351)
@Bregalad:

I should be able to add this extra running frame when mega man land. The frame is so fast that you have a hard time to see it, if you ever see it at all thought. I only saw it when doing some frame per frame analysis with Nintendulator. I never could discern it in the real MM9 jumping animation but I could be wrong. It's a small fix so I will do it for the sake of completeness.

@Tepples:

I checked the code and it seems to rely on precise timing to do the job. There is no code in the NMI or anything else. What would happen if you use this trick with a sound engine and other things running in the nmi a the same time, hmmm..

@Tokumaru:

Like you mentioned and I remember seeing it once while testing it, MM1 does a similar trick for scrolling. I will think about it as one possibility. The only draw back is now you have to keep track of the starting location of the map to re-adjust the scroll so you won't see the black bars. This affect the metatile column drawing a little bit.

Edit:

For the screen already decoded, I didn't do much. I saw that MM2 always had one screen decoded in advance before scrolling. I did the same thing by adding every row of the second map data just after the first map row. So when you reach the end of the screen, it's ends up decoding the next one without asking anything.

by on (#55354)
Banshaku wrote:
The only draw back is now you have to keep track of the starting location of the map to re-adjust the scroll so you won't see the black bars. This affect the metatile column drawing a little bit.

I must admit I don't understand what you mean here.

You mean that if you move the X scroll to the hidden NT it will be desynchronized with the X coordinate of the camera? If this is it, maybe after you switch the scroll to the hidden NT you could copy the data to the other (now hidden) NT again, but this time vertically aligned, and switch back to it.

I believe that the NES games have a pause before scrolling and after scrolling, so you could easily use the second pause for this second copy process.

If this is not what you meant, I'm lost. I can't see what the start of the level has to do with the black bars.

Quote:
For the screen already decoded, I didn't do much. I saw that MM2 always had one screen decoded in advance before scrolling. I did the same thing by adding every row of the second map data just after the first map row. So when you reach the end of the screen, it's ends up decoding the next one without asking anything.

That works great for the first vertical screen after a few horizontal ones, but you still have to think of a way to load the vertical screens after the first in advance too... Maybe you can load the next vertical screen as soon as the scroll sequence to the current one finishes.

by on (#55357)
I guess my comment is quite unclear actually and I should rephrase it. What I meant is in map 1, the black bar is at the bottom. So you have to scroll the Y at $E8 to split it as 1 tile row at the top and one at the bottom to hide them. When you write the next map this way, 2 small things changes (but not big): the black bar is now at the top and where you will need to start to write metatile in the NT changed too. So you have to keep track of those 2 details. It's a very small compromise that could simplify a lot of the screen transition so I will consider this scrolling system since it quite simple.

For the buffering of the next map, this was just a test I did so I could start to try to do some quick experimenting for map transitions. I'm not sure if I will keep it that way. In MM2, when the screen finish to scroll, the next possible screen is loaded in the hidden NT. Maybe I should try to check what MM1 did since the process was similar.

by on (#55360)
Banshaku wrote:
When you write the next map this way, 2 small things changes (but not big): the black bar is now at the top and where you will need to start to write metatile in the NT changed too. So you have to keep track of those 2 details.

But the whole point of the trick is that in the end you move the scroll to the hidden NT (see the green frame at the end of the animation I posted), which has the black bar at the bottom, like it was supposed to be, so you don't have to mess with the metatile rendering at all.

Look at the animation carefully, because I don't think you understood the trick. =) The idea is to leave your current rendering completely untouched, you'd only have to code a new routine to copy the hidden NT to the visible one row by row (but shifted 2 rows down) and at the end switch to the hidden NT, and from there you can continue as normal, the black bar will still be at the bottom.

The trick is that by the end of the scrolling sequence both NTs contain the same thing, the screen that just scrolled in. The one at the right is not aligned to your map rendering engine because we had to skip the black bar so that the screens would connect, but the one on the left is aligned to your scrolling engine, so you can just switch to it, the player will never notice the switch because the exact same contents will be displayed.

by on (#55362)
Well I also never fully understood how MM1 and MM2 scrolls, but I guess they do the following :
- When scrolling horizontally, always load data from the next screen and write it to VRAM (even if it's impossible to scroll to it)
- When finished fast-scrolling vertically, load data from the following screen so that it's ready to scroll horizontally.

I don't think it ever "copies" data from one nametable to the other - it just happen that when scrolling vertically the data from the map above or below the one Megaman was is already in VRAM. In fact there is some case in MM2 where this is not the case I belive. I might be wrong tough.

Aside of that tokumaru's suggetion is a good one if you don't want to add a 15th row. Altough you'd need a piece of code to write metatiles one row lower (which implies crossing attribute nybbles) - but that's nothing really overcomplicated I think.

by on (#55363)
Bregalad wrote:
I don't think it ever "copies" data from one nametable to the other - it just happen that when scrolling vertically the data from the map above or below the one Megaman was is already in VRAM.

I assumed it was copying because if you look at the NT viewer in FCEUXD when going up ladders in MM! you can see that the next screen is quickly loaded to the hidden NT horizontally (that's the pause before the vertical scroll), then the exact same data is written to the visible NT as the vertical scroll happens.

Then, when the vertical scroll finishes, the scroll is set to display the previously hidden NT and the one that was used during the vertical scroll is loaded with the next screen (that's the second pause).

Now, if it's actually copying from one NT to the other I don't know. I set up a breakpoint for $2007 reads and in fact it does happen during the vertical scroll, but it seems to be reading just 2 bytes per frame, not a lot of data, so maybe it's just doing it for the attributes or something.

If it was my game, I'd copy from the other NT. There is enough time in VBlank to read a row of tiles from a NT and writing it to the other, and it's much easier to copy this already decompressed data than to make a different map decoding routine that outputs rows rather than columns.

Quote:
Altough you'd need a piece of code to write metatiles one row lower (which implies crossing attribute nybbles) - but that's nothing really overcomplicated I think.

Like always, attributes are a pain to handle, but shifting them 1 nibble down isn't so hard.

by on (#55373)
tokumaru wrote:
I assumed it was copying because if you look at the NT viewer in FCEUXD when going up ladders in MM! you can see that the next screen is quickly loaded to the hidden NT horizontally (that's the pause before the vertical scroll), then the exact same data is written to the visible NT as the vertical scroll happens.

Then, when the vertical scroll finishes, the scroll is set to display the previously hidden NT and the one that was used during the vertical scroll is loaded with the next screen (that's the second pause).

Now, if it's actually copying from one NT to the other I don't know. I set up a breakpoint for $2007 reads and in fact it does happen during the vertical scroll, but it seems to be reading just 2 bytes per frame, not a lot of data, so maybe it's just doing it for the attributes or something.

Well, it seems MM1 only read attribute table, not the name table. I haven't tried MM2, but very likely it's exactly the same scrolling engine (and MM3-6 uses another engine).

It's actually quite weird how MM1&2 scrolling works, I admit I don't quite understand how it works ! Anyway, tokumaru's solution seems a good one for banshaku's problem in my opinion - altough I don't think it's exactly what MM1&2 does (they don't shift the screen one row lower or anything). Also tokumarus animation is only for the case of scrolling up, it would have to be adapted when scrolling down.

The following algorithm could work for Banshaku :
- Before scrolling up or down, decode the next map and write it into the invisible nametable
- While scrolling, "copy" data in real time from the invisible nametable to the visible one : Going upwards and shifting one row down when climbing up, and going downwards and shifting one row up when clibming down
- Once all scrolling is done ($e8 -> $d8 when doing down, or $e8->$08 when going up), exchange which nametable is active, and decode the next screen on the inactive nametable
- Gameplay can resume

As far I see it there is 2 ways the "copying" could be done, either by decoding the level data rows and write them to the nametable (as it would normally be done), or by reading the nametable via $2007, and write data back.

by on (#55375)
You are right, I didn't think of how to scroll down. Shifting one row up, like you said, will work, but this means that the topmost row of the new screen will have to be copied over the black bar in the visible NT, and the copying continues downwards, wrapping to the top.

IMO it works perfectly both ways. I think that the original games scroll slower than 8 scanlines per frame, but if you want to keep it glitch free I think you should use 8 anyway, even if you scroll 8 scanlines every 2 frames, to make it slower. In fact, if timing is a problem, you could use 1 frame for reading the data from the hidden NT, and the next frame for writing it to the visible NT.

This is the easiest solution I can think of, that doesn't require modifications to your current BG rendering engine and that doesn't need special PPU tricks.

by on (#55388)
I didn't "put the dots together" and realized that once the scrolling is finished, you just switch to the normal NT.

Another possibility is to just decode the next map in the "scrolling" position first in the hidden NT then once the scrolling is finished, re-decode again the proper way in the hidden NT then switch but that seems over complicated. But, hmmm... this way may not work for scrolling down because of the black bar hmmm..

Many good ideas I will try to test them once I find time. Thanks guys.

by on (#55390)
Banshaku wrote:
Another possibility is to just decode the next map in the "scrolling" position first in the hidden NT then once the scrolling is finished, re-decode again the proper way in the hidden NT then switch but that seems over complicated.

Yeah, you'd have to mess with your map decoding routines, something I tried to avoid when coming up with a solution.

Quote:
this way may not work for scrolling down because of the black bar hmmm..

It works with my method, you just have to copy the rows from the hidden NT from top to bottom, and on the visible side you have to start by overwriting the black bar at the bottom and wrapping back to the top. Like, rows 0 and 1 from the hidden NT will overwrite the black bar, and row 3 will replace row 0 of the visible NT.