This is an outline for the MaruMari homebrew project I’m working on for NES. As of posting this outline, the game is very incomplete. Things are subject to change, and suggestions are welcome.
Neither the outline nor the spec are complete, but if you think I’ve overlooked something essential, please say so.
Things I Would Love To Have Help With
- Artwork. Can be shamelessly plundered from metroid games, but some unique artwork might be nice, too.
- Music. Famitone/Famitracker is probably the easiest and best approach, but music needs to be composed/extracted,
and I have no experience with this toolset. I’ve already written a utility that can extract music data from Metroid.
- Game takes original metroid as its primary inspiration, with each level based on a level from the original game.
- Player will play as samus in ball form.
- Game will used forced downward scrolling.
- The player loses by running out of health or getting crushed at the top.
- It won’t be that hard to avoid getting crushed. The forced scrolling is to motivate the player to keep moving rather than taking a slow cautious approach at the game.
- The game will include powerups and enemies inspired by other metroid games.
- Enemies will probably be based strongly on enemies from the same level of the original game.
- Powerups will be somewhat different from original game (outlined below)
- Each level will end with a door bubble in the floor (as in Super Metroid)
- Player will transition to next level with super-metroid style screen transition
- When player enters door, music stops and screen fades to black, except for doorway and bubble.
- Doorway scrolls to top, next level fades in, bubble opens, and player is dropped down
- The game will not be overly difficult.
- The player should not beat in on first couple tries, but:
- The goal is to complete the game with the highest score possible
- Maybe I can work a “completion percentage” type scoring system in for metroid-y goodness
- Different difficulty settings are a possibility
- Different endings!
- Limited quantity.
- Enemies may drop pickups, or they may be placed or randomly placed in levels, or both
- A pickup will reward five bombs, player can carry max of eight.
- Rational: This will create a situation where you can’t completely neglect collecting bombs, but you don’t have to hoard them and seek them out obsessively.
- Temporary, reduces damage
- Exactly what it sounds like
- Energy Tank
- Permanently increases health capacity
- Spring ball
- Can work one of two ways:
- Temporary. Lasts a certain amount of time, or jump height decreases gradually.
- Cumulative. Each spring ball pickup increases player’s height
- Charge bomb
- Not really sure about this one
- Player would be able to hold limit quantity (or they could be very rare)
- Would be switched on with select like missiles in “Metroid”
- A longer hold produces a larger blast and bounces player higher
- Space Jump
- Temporary. Allows player to jump from the air anytime he is falling downward.
- Spring ball may or may not be prerequisite
- Screw Attack
- Temporary. Player in invincible and kills enemies on contact while moving upward from a jump.
- Player does not have invincibility while falling down. He still needs to be careful.
- It might be a good idea to have a plain-old invincibility power up.
- It would be cool to have power bombs instead of/in addition to charge bombs, but how would these work on NES?
- To be determined. Enemy lineup will probably be strongly based on Metroid.
- Certain objects, such as the player, score, and health display will have dedicated program.
- Other sprites are implemented as either “objects” or “projectiles”
- Objects are used for more complex sprites (e.g. enemies and powerups) and have the following features
- Animated composite sprites
- Event flags
- collision with player
- collision with top of screen
- Speed variables
- Limit of 16 objects
- Projectiles are simpler objects typically spawned by the player or an object
- Single tile
- Limit of 8 projectiles
- The game will use a slow forced scrolling to motivate the player to keep moving, to emphasize this is an action game, not an exploration game.
- Scrolling speed will increase with each level to marginal difficulty. Death-by-scroll-pinch isn’t meant to be the primary hazard.
- If the game has difficulty levels, the scrolling could a much more serious hazard in hard difficulty.
- There will be a “scroll line” about halfway down the screen.
- When the player falls past this line, the screen will scroll more quickly to keep the player visually at or above this line.
- This is so the player can proceed at his own pace, and so he can see what’s ahead (he will always be able to see at least 1/2 a screen ahead of the samus)
- The engine will be limited to scrolling at 8 px per frame, so this is the maximum fall speed
Player physics: Movement
- The player has 16-bit speed variables, and
8-bit position variables. Speed is a fixed-point number (i.e. [-]##.##, e.g. -01.C0)
- Position will be stored as a 16-bit value as well.
The low bytes (fractional part) of speed are not applied to position, they are used only for gradual acceleration
When the player presses left or right
If his speed is 00.00, he assumes a speed of 01.00 in the pressed direction so there is no lag before movement.
For a speed of “01.00” to the left, we’ll use FF.C0 (-00.40). This is because we always round DOWN rather than toward zero.
- While the player continues to hold left or right
- He accelerates at a rate of 00.40 in the appropriate direction, to a maximum of +/- 2 (FE.C0 to 02.00)
- Note: the speed limit (2) might be a little low.
- If the player exceeds a speed of 2 to the left or right from any yet unplanned mechanism (boost tile or something)
- He decelerates at a rate of 00.40 until he reaches the normal max speed
- If the player is not holding left or right
- He decelerates at a rate of 00.40 until he stops
- When the player jumps
- His upward velocity is set based on current equipment, but assumes full speed instantly. There is no “bounce”.
- When the player is “In-air”
- His downward velocity increases at a constant speed (00.80?) until terminal velocity is reached (06?)
- The player’s sprite should rotate in the direction he most recently moved (left or right)
Player physics: Collision
- Player’s speed will be added to his position (movement) immediately before processing collision
- Tiles with same physics will be stored in groups (consecutive indecies). Groups will appear in the following order.
- Damage: air-like (tile can be passed through)
- Damage: solid-like (tile can not be passed through)
- Vertical Collision
- At any point the player will be in one of two modes. Each will have a different handler.
- If the player touches the top of the screen in the on-ground state, he loses (he has been pinched).
- Each frame test ground below player, @ $4,$16 and C,$16. If neither one hits a solid tile, player transitions to in-air state with an initial veloctiy of 01.00
- If the player jumps, he transitions to an in-air state with a negative velocity based on his equipment
- Checks for collision above or below player, depending on the direction of his motion
- Test for collision at $4,-0 and $4C, -0.
- Player will collide with top of screen
- Upon collision with top, test collision beneath player. If player is touching ground, he loses (he has been pinched)
- Upon collision, player is placed directly beneath the collided-with tile: playerY = (playerY + $F) & $F0
- Player “bounces” downward by assuming a downward velocity that is a fraction of the previous upward velocity
- Test for collision at same points as ‘on-ground’. If a collision occurs:
- Position player onto tile (this can be done by taking the players position, relative to the level grid, and ANDing Y-position with $F0)
- Behavior depends on player’s downward velocity
- If the player has sufficient velocity, “bounce” the player upward (remain in “In-air” mode) at a fraction of his previous speed
- Otherwise, Transition to on-ground state
- Sideways Collision
- Collision is tested in the direction the player is moving (left/right). If the player has no sideways motion, no collision testing is needed.
- Leftward collision points are $4,0 and $4,$F. If the player collides:
- place him $C pixels to the right of the tile that his left side occupies (playerX = (playerX & $F0) + $C)
- Set horizontal speed to zero
- Rightward collision points are $C,0 and $C,$F. If the player collides:
- place him $4 pixels to the right of the tile that his left edge occupies (playerX = (playerX & $F0) + $4)
- set horizontal speed to zero
- Background-based hazard detection (spikes, lava, etc) should be included in above collision logic.
- Sprite-based objects (enemies, items) will be processed separately using a collision rectangle created by the collision points detailed above
Level Data Format/Compression
- Level data will be stored in the following format:
- The data stream will alternate between attribute entries and pairs of row entries.
- After every eighth attribute entry, there will be only one row entry instead of two.
- An attribute entry will consist of eight bytes of raw attribute table (palette) data, enough to define palettes for two rows of 16×16 tiles.
- A row entry will define sixteen tile numbers, using an RLE encoding scheme, where each byte in the stream has the format ATTT BTTT
- The tile number is the value with bits A and B cleared (value AND $EE)
- A and B make up a two-bit number (‘A’ being the low bit) which yields a number 0 to 3. Add 1 to this value to get the run length, in the range of 1 to 4.
Sprite animation data
- Sprite animation data is stored in the form of “sprite layouts”, “sprite tile lists”, and “sprite animations”.
- For each type of data (layouts, tile lists, and animations), there will be a pointer table that references each entry.
- The index of the pointer to a piece of data will be referred to as that data’s ID.
- Sprite Layout:
- Contains a sequence of pairs of bytes that specify X and Y coordinates as signed bytes.
- FF terminated.
- Sprite Tile List:
- Contains a sequence of non-zero tile numbers intermixed with “attribute modifications”
- An attribute modification is a zero byte followed by a byte containing the following bit flags:
- $01 – Add 1 to palette number
- $02 – Add 2 to palette number
- $40 – Toggle horizontal tile flip
- $80 – Toggle vertical tile flip
- FF terminated
- Sprite Animation:
- Contains a sequence of frame entries (3 bytes ea):
- Layout ID
- Tile list ID
- Screen frames for this animation frame
- FF terminated
- When a bomb detonates, the tile the bomb occupied and any adjacent tile (diagonals excluded) will be destroyed if it is breakable (the bomb has a + shaped destructive blast).
- The tile above the bomb is excluded if it is not at least partially (i.e. if the bomb explodes within the first row of visible (partially or fully) tiles, the tile above it needn’t be considered)
- The nametable address calculations are as follows
- The below is in hexadecimal exclusively
- Assume a coordinate range of 0 to 1BF. Operations that exceed this range wrap around (i.e. 1B0 + 20 = 10)
- ViewY is the pixel coordinate of the first visible row of pixels within said range
- BombY is 10 pixels above the center of the bomb in screen pixel coordinates. If the bomb’s center is above the 10th pixel, BombY will simply be the bomb’s center instead
- In other words, BombY occupies the first tile affected by the bomb blast
- BombAbsY = ViewY + BombY
- If BombAbsY >= F0, the first affected tile appears on nametable 2. Otherwise the first affected tile appears on nametable 1.
- BombNtY (The y pixel coordinate within the affected nametable) = bombAbsY % E0
- NameTableRowAddress = NameTableBase + 4 * (BombNtY $ F0)