So I made a post in the NESDev forums a while back, and said I would make a small platformer game to test out how viable making a full NES homebrew project in Millfork would be. I've come back today to let everyone know that for the most part, it is viable! (With some caveats as well, however.)
Click here for the link to the github repository, and click here for an up-to-date version of the compiled .nes file
I'll talk a bit about what it's like building a project like this in Millfork (rather than assembly) later in the post, but for now I'll list out some of the key features in this game I was able to implement:
- - Full 2D platformer physics (collision detection, sub-pixel movement, etc...)
- Standard game system (loading levels, preparing gamestates, etc...)
- 16x16 pixel metatile system for background tiles
- Run-length encoding compressed maps
- Left and right scrolling system with no screen-edge glitches (except when game is slowed down)
- Separate game and NMI threads for handling slowdown properly
- Generalized animation system
- Sprite object manager system
- Enemy manager system (that I will probably change to a general object manager system)
- Famitone5 sound system (Famitone2 compatible)
- Level/map editing using the Tiled map editor!
The last feature is one that I'm really excited about. I've made a Tiled python plugin script to quickly and easily make/export maps to the necessary file format for use in this engine, but you could also use this as a base to implement your own file formats for Tiled to export to.
In addition, I've tried to make this game fairly easy to add on to, whether it be adding music/sfx, animations, enemies, etc..., I have provided READMEs and instructions on how to easily add in new objects and hook them up to the game engine. There's still some documentation that I have to fill out, and I will definitely be refactoring some of the code that's a bit cumbersome to add on to, but for the most part the current base engine can be fairly quickly added on to. To prove this, take a look at the version from 2 days ago and the latest version. I was able to add in a new enemy, finish up the logic for an existing enemy, and make a whole new map in just a little over a day. As I said before, I do still plan to work on the engine and improve its use/efficiency, so if you plan to tinker around with it, keep in mind that I may change things in the future.
So anyways, onto the Millfork stuff.
A Few Words On Millfork:
For those of you who don't know what Millfork is, it's a higher-level programming language for 6502 and other processors. I'd recommend reading up about it on its website, but the gist is that it's meant to be a C-like language for old processors that is somewhere in-between assembly and high-level languages like C.
The reason I chose to use it for this project is that I wanted the code to be more readable than assembly, while still retaining most of the performance. With that in mind, here are the pros and cons I've encountered that are relevant to developing NES homebrew in this language:
First, the pros:
- - Easy to read/maintain : The biggest advantage of anything other than assembly is, of course, that it's not assembly. In Millfork's case especially, its C-like syntax makes it a breeze to learn if you're familiar with any modern C-like language.
- Still in active development : Millfork receives regular updates, and bug reports are fairly quickly addressed by the lead developer. Whenever I encountered a compiler bug and reported the issue on the main github page, the lead dev always fixed it super quickly.
- Fairly efficient (in terms of speed) : I haven't ported this game to any other languages to do an official benchmark, but for the most part Millfork's optimizer does a good job in translating your code to decently quick machine code. Much of the slowdown in Twin Dragons may be attributed to my own programming, so it's kind of hard to guage, but the fact that it doesn't slow down at all in most cases makes me believe that most projects won't have any speed issues related to the language itself.
Now, the cons:
- - Occasional compiler bugs : Since Millfork is still in active development, you may come across bugs in your game that occur by no fault of your own. This means you'll probably still have to read the outputted assembly using a debugger (like in fceux) in order to track down bugs. During the development of this game/engine, I ran into a couple of game-breaking bugs that I managed to track down to the compiler and not my own coding. I reported these bugs to the Millfork dev and they fixed them fairly quickly, so as long as you're willing to go bug hunting in assembly this shouldn't be ~too~ big an issue, but that also sadly means that I can't really recommend Millfork for beginners until it becomes stable.
- Inefficient in terms of space : This may be due to my own programming faults, but I found that by the end of this project almost an entire PRGROM chip was taken up by just the code. This left me with only enough room for 3 levels, which is why the demo ends at level 3. Again, I don't want to attribute this solely to Millfork's compiler/optimizer, but it's something to keep in mind.
I plan to continue to add to this project with more refactoring/general improvements, with my next big milestone being to port this over to a different mapper, most likely UxROM. So if you want to use this as a base to create your own project (which is what I actually intended!), keep in mind that you may want to just read over the code but hold off on modifying it until I release a more stable version.
Thanks for reading, and I hope this was useful to you!