A little history: Mars was actually a game I made before to be cross platform between PC and mobile devices from a single code base. However, even though I fully completed the game, I never relased it publicly. Hence why the project is called "Mars3DS" and not just "Mars". Fast forward to now (when this readme was made), I was thinking about working on a C++ project. I have done a bit of C++, but did no projects on it. After coming off from making homebrew for the NDS using C, I thought well it make sense now to make a 3DS homebrew using C++! Just like that, the idea of remaking Mars for 3DS came to be. Even when I made Mars originally, I always thought it would cool if this was some sort of homebrew for a Nintendo platfrom (maybe just the retro aesthetics was hitting me), but at the time I didn't have any C/C++ experience at all. But now that orignal idea is a reailty! The idea behind Mars is supposed to be a "lost retro game" hence why the game has a mininal feel (if that made sense). Hope you enjoy! :)
Should work on both real hardware (old 3DS/2DS, and new 3DS/2DS) and 3DS emulators like Citra. Note: for real hardware, you do need a homebrewed system.
- Download the .3dsx ROM file here or from the releases.
- You can now run 3dsx file via the Homebrew Launcher.
- Download the .cia ROM file here or from the releases.
- You can install the .cia file via tools like FBI.
- (+) or CirclePad to move
- (A) to shoot
- (B) to jump
- Collect the mushrooms!
3DS | 3DS |
---|---|
Game play |
---|
Want to tinker around, modify, make your own, learn a bit about 3DS homebrew, or contribute? Here's how you can get started with the code and compile.
To make 3DS homwbrew, a open-source toolchain known as DevKitPro is used. DevKitPro comes with many compilers and libraries to start homebrew development on many consoles! The specific library were using is citro2d which should come with DevKitPro.
- Download DevKitPro: https://devkitpro.org/, please do look over their getting started.
- After DevKitPro is set up, download this repository through zip, or git, then open up your terminal, pointing inside the project directory.
- Run
make
in the terminal, andMars3D.3dsx
will be made!
This is only for if your interested if you want to produces a .cia file which is a 3DS installable file. These instructions are not only revelent for this project, but you can apply this to own 3DS homebrew project if you are interested, hence why I included this part, just to share this information if you are curious. With this in mind, these steps are written to be very general.
- Do the previous steps mentioned before from steps 1 to 3.
- Along with the
.3dsx
file, you may notice a.elf
is also made. This is like a raw binary, we need this produce a.cia
file. - With the
Mars3D.elf
and the files in themeta
directory you make the Mars3D.cia with tools makerom and bannertool. You can get makerom and bannertool here:- makerom (Credits to 3DSGuy)
- bannertool (Credits to Steveice10)
- Since these are command line tool (CLI), you can add them to your local PATH, or have the excutable with the .elf files, and all the files in the meta directory, the romfs directory, all in one directory.
- Side note : As you may know, the cool part of having your homebrew made into a .cia file is that it's installable to the 3DS, so it can be on the 3DS menu with a custom banner. To learn how to make a custom 3D banner, here is a great guide on GBATemp made by MovieAutomotive here.
-
First to build the banner:
bannertool makebanner -ci "banner.cgfx" -a "banneraudio.wav" -o "banner.bnr"
Note: If don't want a 3D banner but just 2D banner, a
.png
banner, replace-ci
with-i
. Also the banner audio should a.wav
format that is 3 seconds or less. -
Then to build the icon and the meta data:
bannertool makesmdh -i "icon.png" -s "Name" -l "Description" -p "Author" -o "icon.icn"
-
Finally to build the .cia file:
makerom -f cia -target t -exefslogo -o "rom.cia" -elf "rom.elf" -rsf "build.rsf" -banner "banner.bnr" -icon "icon.icn"
If you're wondering what the
build.rsf
is, it's essentailly meta data for your 3DS game/application. This includes Unique ID, path to romfs, permissions, etc.
Here's a little on the program layout!
Looking over the code, the program is quite simple, don't worry! This portation was written to be simple, so no matter of your skill level, anybody should get the idea of the program works, it's sort of the reason why I write these parts! :)
C++ is a object oriented language. With this is mind, this game is built off of objects. The player, Astro
, is a object, the bullets for both the player and enemy are objects, the Enemy
is a object, etc. Each object, like the player, will have values that is only relevant to them self. For example: the x
and y
position, the height, h
, the width w
, speedX
, speedY
, etc. Each object also have methods that handle behaviours of the object. For example, going back to the player, it has a method for void Astro::movement()
to handle movement controls, and void Astro::collision(std::vector<Platform>& platList)
to handle collision on platforms, which themselves are objects.
Then each objects will have these 2 methods look like this:
//Example tooken from the Astro (player) object
void Astro::update(std::vector<Platform>& platList)
{
if (health >= 1)
{
collision(platList);
movement();
shoot();
}
//code cont...
}
void Astro::render()
{
if (health >= 1)
{
C2D_DrawSprite(&astro);
}
C2D_DrawSprite(&healthBar);
for (std::size_t i = 0; i < bullList.size(); i++)
{
bullList[i]->render();
}
}
After creating methods relating to behaviours, I call these methods in a update method or a render method. The update handles anything that relates to changes of values, and render handles all methods relating to draw the sprite on screen. You may be wondering, why did I do this? Well, we have all these objects, but nothing to put them together. This brings on the segway to scenes!
In the main.cpp
file, the main game loop is divided into 2 parts. A update
part and the render
part. Sounds familiar right? Using citro2d, Any functions that relates to drawing sprites on screen have to be called in between C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
and C3D_FrameEnd(0);
This is why I divided the object's methods were funnel into one of the two update and render parts. This is not only better for organization, but makes it easier to track a object within a scene. Now the scene itself is not object. I made the choice to not make the scenes in to object as this a very simple game, and didn't want to complicate it more. To understand this better, here's a example of how the scenes are handle:
//Main game loop
while(aptMainLoop())
{
//Update
hidScanInput();
hidTouchRead(&touch);
if (hidKeysDown() & KEY_START) break; //Back to Homebrew Menu
if (scene == 0)
{
//code...
}
...
//Render
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
C2D_SceneBegin(top); //For drawing on the top screen
C2D_TargetClear(top, C2D_Color32(0.0f, 0.0f, 0.0f, 0.0f)); //Background color
if (scene == 0)
{
//code...
}
...
C2D_SceneBegin(bot); //For drawing on the bottom screen
C2D_TargetClear(bot, C2D_Color32(219.0f, 95.0f, 27.0f, 0.0f));
if (scene == 0)
{
//code...
}
...
C3D_FrameEnd(0);
}
...
As seen in the basic structure for the main game loop, the transition between the scenes are handled by a scene
variable, with the if statement handling that specfic methods and function calls needed for that scene.
Outside the main function there a few function. These few functions relate to the scene, handling actions such as void spawnEnemy(std::vector<Enemy*>& paraList, RNG& gen, int minX, int maxX, int minY, int maxY, std::chrono::steady_clock::time_point& startTime)
If I had each scene as a objects, these functions would basically be methods of the scene.
Do note I used very simple memory management. In C++, there are smart pointers, however since the program is simple, I chose the route of using the new
and delete
. Memory management is used for spawning and deleting bullets of both player and enemies, as well the ememies themselves.
So far I haven't really talked on how citro2d works, and that's because citro2d itself is really simple in way where it has function to make sprites, and draw them to screen, and that's pretty much it. For those are curious, citro2d it self is built on citro3d and libctru, which both are also included with devkitpro. If you ever used frameworks like raylib, or NightFox's Lib with libnds for NDS homebrew, it's pretty simular to those. I recommend looking at the citro2d documentation, and for example, the player object code to understand the basics of citro2d. Here is citro2d documentation: https://citro2d.devkitpro.org/ Here is libctru documentation: https://libctru.devkitpro.org/
If you are interested in the 3DS and or want to know about hardware in enhance your 3DS homebrew skills, this is a good read:
- Rodrigo Copetti's Nintendo 3DS Architechture Analysis: https://www.copetti.org/writings/consoles/nintendo-3ds/
- CRT Fillter
- Something I had in the orignal game, but couldn't implement in this 3DS version
- Saving. Save high score.
- Add sound effects - Not too sure how to this, but any help would be great!
- Post any feature request in the Issues tab!
- Text rendering is not efficient!
- After a while the score text and the anti-gravity mushroom counter disappear! I'm not really too sure on how text rendering works in citro2d, so any help on this would be great! :)
- Enemies can sometimes get stuck on the edge of platforms
- Values for spawing can be tweaked
- Overall code can be refactor (especially in
main.cpp
) - If you find any other issues/bugs, post about it on the issues tab
This project is open-source under the MIT License, meaning your free to do what ever you want with it. This project is freely available for anyone to contribute, 3DS lover, homebrew developers, or someone who is new to it all.
If you plan on contributing, a good place to start is to look at upcoming wanted features, and known issues. If you find a new bug, or have feature ideas of your own, posted first to the Issues tab before hand. You can even fork it and make it your own!
To get started on contributing:
- Fork or Clone the Project
- Once you have your own repository (it can be a public repository) to work in, you can get started on what you want to do!
- Make sure you git Add and git Commit your Changes to your repository
- Then git push to your repository
- Open a Pull Request in this repositroy, where your changes will be look at to be approved
- Once it's approved, it will be in a development branch, soon to be merge to the main branch
Distributed under the MIT License. See LICENSE.txt
for more information.