Programming NES games in C article
Moderator: Moderators
Programming NES games in C article
Last edited by Shiru on Sun Jan 29, 2012 2:02 am, edited 2 times in total.
- cpow
- NESICIDE developer
- Posts: 1097
- Joined: Mon Oct 13, 2008 7:55 pm
- Location: Minneapolis, MN
- Contact:
Re: Programming NES games in C article
Shiru, very nice article! One thing I'd added to NESICIDE is the ability to see mixed-mode source during debugging. That helps -- I think, anyway -- people that want to develop in C see exactly what the C compiler generates for any particular C source line of code. Example:Shiru wrote:Read the article
Large screenshot
Also, this statement:
is not really true. NESICIDE can do that. I've been stepping through AlterEgo at the C level for quite a while now. Thank you for such a polished and useful example of programming for NES in C.Shiru wrote:The problem is that there are no comfortable debuggers for C code compiled into 6502 assembly around (yet) - ones that allow to put breakpoints on random C lines and see what the variables contain at the moment. Usually you only have an assembly level debugger in some emulators, and it is not very helpful with compiled code.
Congratulations, that was interesting.
Something I don't understand so well :
Something I don't understand so well :
Then you write C code that is less portable and less readable, and more low level. I don't have anything against it but doesn't this kill the purpose of using C instead of assembly ?Due to very limited NES resources, such as CPU speed, RAM and ROM size, writting a proper, clean C code isn't very effective. To make it faster and shorter you have to do things that otherwise aren't considered acceptable. They are disable some of C advantages, making the code more low level and less structured.
There are suggestions that will make your code more effective, but certainly less readable:
....
Useless, lumbering half-wits don't scare us.
It is a bit low level this way, but certainly not down to the assembly level - it is still rather high level.
Random example, you have a level map that is larger than 256 bytes, lets say 32x32, and need to get a value from it using two 8-bit vars, mx and my. So, for C code:
Assembly code counterpart is something like this:
One line of the code is faster to write and easier to mantain than 20+. You probably could optimize the assembly version as it was written off the top of my head, but certainly not down to a single line.
Random example, you have a level map that is larger than 256 bytes, lets say 32x32, and need to get a value from it using two 8-bit vars, mx and my. So, for C code:
Code: Select all
n=map[(my<<5)+mx]
Code: Select all
lda my
sta ptr_l
lda #0
sta ptr_h
dup 5
asl ptr_l ;<<5
rol ptr_h
edup
lda ptr_l
clc
adc mx
sta ptr_l
lda ptr_h
adc #0
sta ptr_h
lda ptr_l
clc
adc #<map
sta ptr_l
lda ptr_h
adc #>map
sta ptr_h
ldy #0
lda [ptr_l],y
I can see using a hybrid model of C and Assembly. Consider an RPG, where the battle logic is better expressed in C, but code the rest (NMI handler, tile display logic, sound engine, etc...) in asm.
In the past Tepples has stated that the Koei simulation games were written in C, and that is probably why they seem sluggish. I bet that we could do better.
I seriously considered making some sort of an RPG as my next nesdev project, and coding part of it in C, but I lack the time to take on such a project right now.
In the past Tepples has stated that the Koei simulation games were written in C, and that is probably why they seem sluggish. I bet that we could do better.
I seriously considered making some sort of an RPG as my next nesdev project, and coding part of it in C, but I lack the time to take on such a project right now.
That's how it is done in my NES games - C code only works with hardware through special functions written in assembly. So NMI handler, tile updater, sound code is written in assembly, and game logic in C.
You'll run into the other problem with RPG battle logic in C - the resulting code will be huge, so the problem with bankswitching have to be solved somehow.
You'll run into the other problem with RPG battle logic in C - the resulting code will be huge, so the problem with bankswitching have to be solved somehow.
Not sure what exactly you're asking here. As long as the runtime functions (pusha/popa etc) are placed in a fixed bank it should work OK. The switching itself has to be done manually of course.mic_ wrote:Do you know if CC65 handles cross-bank calls well (linking symbols that have been compiled into separate banks)? This was quite a hassle with SDCC (Z80) and I ended up having to write some custom tools for it, and it still didn't work perfectly.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
It doesn't happen automagically, but depending on the calling convention used, you may be able to write a bunch of stubs that make cross-bank calls using a trampoline in the fixed bank.mic_ wrote:Nice info and cleanly written.
Do you know if CC65 handles cross-bank calls well (linking symbols that have been compiled into separate banks)?
Let's say you split your code/data into separate banks which are compiled and linked separately (to avoid having duplicates of the same stuff for different combinations of banks) and then combined into a .nes file.Not sure what exactly you're asking here.Do you know if CC65 handles cross-bank calls well
So e.g.
bank0.c -> bank0.obj -> bank0.bin
bank1.c -> bank1.obj -> bank1.bin
...
header + bank0.bin + bank1.bin + ... -> game.nes
If bank0 wants to call a function in bank1, can you tell CC65 to resolve the address of that function without actually putting a copy of the function in bank0 as well? Using hardcoded addresses is a PITA, and using a proxy function at a fixed address to delegate calls isn't really that nice either IMO.
With SDCC I ended up writing a tool that parsed the .SYM files generated by the linker and it would output a header file with named function pointers for any given bank that other banks could use. It didn't work when there were cross-dependencies (bank X and bank Y both wanted to call eachother), so in some instances I still had to use hardcoded addresses.