• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Forum moderator applications are now open! Click here for details.
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Development: Custom Title Screen Written in C

Shiny Quagsire

I'm Still Alive, Elsewhere
697
Posts
14
Years
So the other day I was on the RomHack.me IRC and I had the inspiration after finishing the Crystal Intro that it'd be cool to replace the title screen too. At the time it was just a small tease, a hypothetical idea, but a month later I had the desire to go and implement this idea, and this is what I created:



When I was first making this title screen I started by finding the callback associated with the title screen. Once I found it, I discovered that when replacing the existing pointer with the pointer to my custom intro, it would actually go to my intro upon triggering the intro to title screen change (which effectively made my intro loop forever at the time). Since it worked without problems, I got out my Intro Template and began making modifications to create a custom title screen.

A few notes about this hack:
  • Since it modifies the game engine code, it is not reliant on my custom intro, meaning that it plays nice with both the original intro as well as my custom intro.
  • It works seamlessly within the game and properly goes to the Continue, New Game menu
  • The Berry Update and Save Delete menus are properly implemented with the same shortcut keys as before, making it have no disadvantages when compared to the original title screen (while giving it several advantages)

Since I already detailed how I hacked the GameFreak intro using callbacks, I'm going to leave that out and start with how the title screen works.

Basically, our title screen runs in steps. We start by loading all of our resources and setting things up in this clippet of code:
Code:
if(init[27] == 0)
{
	initTitle();
	killSong();
	playSong(278);
	unfadeScreenWhite();
}
Here we use init[27] as a sort of step counter, which is increased in initTitle(). We then kill the previous song from the intro and start to play ours while unfading the screen.

Over the next few steps I modified BG0VOFF to bring in the Skitty and Wailord from the bottom of the screen where they were tucked away:
Code:
else if(init[27] == 2)
	{
		moveSkitty();
		if(VAR[4] == 1)
		{
			VAR[5] = VAR[5] + 3;
			VAR[4] = 0;
		}
		else
		{
			VAR[5] = VAR[5] + 2;
			VAR[4] = 1;
		}
		
		if(VAR[5] == 0 || VAR[5] > 0x1FF)
		{
			VAR[5] = 0;
			moveSkitty();
			init[27] = 3;
			TIMER[0] = 0;
		}
	}
The code here is a bit messy, but it's mostly due in part to the fact that we cannot use variables. To simplify, VAR[4] toggles on and off to increase VAR[5] by either 2 or 3. VAR[5] is then read and written to BG0VOFF to move the Skitty up from the screen. Once it has moved all the way (or too far) we reset it to 0 and begin the next steps to moving the logo, which is the same except it uses BG2Y since we're in mode 1.

Once everything is settled in we start spawning our OAMs into Game Freak's OAM system. All of the OAM info is located within the same file as the sprites that go with the OAM. Since the OAM system is quite complicated, I'll keep it a bit more simple. If you're interested in seeing how the OAM system works you can check out knizz's OAM-system Research Thread.

Code:
void spawnFlavor()
{
	int x = (240 / 2) - (64 / 2);
	int y = 70;
	int i = spawnOamFromUnk(&flavorTemplate,x,y,0x0);
	OAMBuffer[i].oam.tileProPal = 0;
	i = spawnOamFromUnk(&flavor2Template,x+64,y,0x0);
	OAMBuffer[i].oam.tileProPal = 0;
}

This code (located in titlescreen.c) spawns the OAMs for the "Hot Skitty on Wailord Edition". Each is a 64x32 OAM layed out to a 128x32 sprite. This function spawns each half in the center of the screen and changes it's palette to use palette 0.

After spawning the bottom text and flavor, we then begin the looping of our routine, making skitty's eyes glow while also spawning particles that float up the screen.
Code:
	else if(init[27] >= 6)
	{
		if(random(3) == 2)
			spawnParticle();
		moveUp();
		if(!isFading() && VAR[0] < 0x20)
			pulseEyes();
	}
In this code, we create a random condition that will spawn a particle on a 1/3 chance, which gives us a good amount of particles while also not flooding the screen. I should note that since GameFreak's system has a lot of extra data, only 64 out of the 128 possible OAMs are able to be used, which is quite limiting in some aspects.

In the moveUp() function we have the logic that will move our particles. One of the small details in the video that you may or may not have noticed is that there are actually 2 types of particles. The fast animating ones that scroll up quickly and the large dots that scroll up slower, giving it a 32-ish view of the particles. To do this, we assign each particle at random a type by not only setting it's animation data but also the derp1 property to either 1 or 0. Since this property is unused we can store data in it.
Code:
void moveUp()
{
	if(VAR[14] == 0xFFFF) //Make sure we don't start scrolling until all of our text and stuff is there.
		return;
	for(int i = VAR[14]; i < 0x3F; i++)
	{
		if(OAMBuffer[i].derp1 == 1)
			OAMBuffer[i].pos1.y -= 2;
		else
			OAMBuffer[i].pos1.y -= 1;
		if((int)OAMBuffer[i].pos1.y > 0x160 || (int)OAMBuffer[i].pos1.y < 0)
		{
			deleteOAM(i);
		}
	}
}
To make this system a bit more flexible, we assign VAR[14] to the last OAM from the text and previous OAMs, which gives us the range of OAMs we actually want to scroll. Faster particles move in increments of 2 while the others move in increments of 1. If our particle leaves the screen, we delete it.

Now the pulseEyes() method is a bit more complicated. To do this, we increase and decrease skitty's eye palette by 0x1 every 6 frames. In addition to this, we also pause for 20 frames after it goes completely dark or bright so that it isn't just pure oscillation.

Once we have reached the main loop, we start to allow key input. If the users press A or Start, we lock that input, play the cries, and fade out into the next menu. However, if the player presses B and Select at the same time, this code is executed:
Code:
else if(keyDown(KEY_SELECT) && keyDown(KEY_B))
	{
		if(keyDown(KEY_UP))
		{
			int (*func)(void) = (int (*)(void))0x080796CD; //Save Delete
			func();
		}
		else
		{
			int (*func)(void) = (int (*)(void))0x080796E9; //Berry Update
			func();
		}
		return;
	}
This basically allows us to keep the old functions from the previous title screen in case the game is loaded onto a flash cartridge and used to fix the RS berry glitch or they need to delete their save data. It was also cool to add in there. :P

Just as a last note, I think there's still some things that can be improved on with this system while also giving some key advantages. Since we have the entire code, it is possible to change anything with the title screen we want. However, this also means that it is prone to bugs and other oddities. For the full source code, you can download it and contribute back to it on my GitHub. If you have any questions, feel free to comment on this thread or PM me. And don't forget that since this is released under GPLv3 any hacks that use it are required to open-source any modifications done to it. :)
 

PokeBunny

Pokemon Game Maker
34
Posts
11
Years
This is a little bit complicated. So you replaced the pointer to your own code but you used some of Gamefreak's code.
 

GoGoJJTech

(☞゚ヮ゚)☞ http://GoGoJJTech.com ☜(゚ヮ゚☜)
2,475
Posts
11
Years
This is a little bit complicated. So you replaced the pointer to your own code but you used some of Gamefreak's code.

What we do is we use our own code but call functions from the engine. Like fadescreen. We're not going to rewrite the entire engine :P
 

Touched

Resident ASMAGICIAN
625
Posts
9
Years
  • Age 122
  • Seen Feb 1, 2018
What we do is we use our own code but call functions from the engine. Like fadescreen. We're not going to rewrite the entire engine :P

Don't be silly GoGo. Of course we're going to rewrite the entire engine. How can we trust any code that hasn't been written by ourselves?
 

PokeBunny

Pokemon Game Maker
34
Posts
11
Years
Don't be silly GoGo. Of course we're going to rewrite the entire engine. How can we trust any code that hasn't been written by ourselves?

But, we can't rewrite any of Gamefreak's code unless we have an operation document on Pokemon FireRed. Something I'm dying to find...
 

Touched

Resident ASMAGICIAN
625
Posts
9
Years
  • Age 122
  • Seen Feb 1, 2018
But, we can't rewrite any of Gamefreak's code unless we have an operation document on Pokemon FireRed. Something I'm dying to find...

We can rewrite their code, we don't necessarily need to know everything about their code in order to replicate the output. But you're not going to get such a document, even if you knew how to steal the game devs notes. The best you can do is disassemble code and contribute by identifying routines.
 

Touched

Resident ASMAGICIAN
625
Posts
9
Years
  • Age 122
  • Seen Feb 1, 2018
Well, that's not supposed to do anything besides explain certain parts of the engine. So it won't compile or anything. Also knizz has said that he is done with Pokemon for the time being, so that probably won't see any progress for a while. If you want an engine rewrite you should check out Jambo's work: https://github.com/Jambo51/GBA-Pokemon-Engine
 

Full Metal

C(++) Developer.
810
Posts
16
Years
I was responding to this bit:
The best you can do is disassemble code and contribute by identifying routines.

I talk enough with Knizz, I know his plans for this project. I'm also a member of that repo, and I'm aware that the project doesn't compile. I'm also aware of Jambo's engine.
 
Back
Top