Skip to content

3. Recipes

Triangly edited this page Aug 10, 2026 · 2 revisions

Creating a New Stage

Stage Controller

First, you should create a controller object that will handle our stage

  1. Create a new object, for example obj_rm_stage_test
  2. Set its parent object to obj_rm_stage
  3. Override User Event 0

User Event 0 is where we set up the stage. Start by calling setup_stage():

setup_stage(_name, _act_index, _bgm, _animals, _next_room, _progress_flag);

Here's what each parameter does:

  • _name: stage name shown on the title card
  • _act_index: act number starting from 0
    • 0 = Act 1
    • 1 = Act 2
    • 2 = Act 3
    • Use ACT_SINGLE if you don't want to display an act number
  • _bgm: background music
  • _animals: array of animals dropped by badniks
  • _next_room: room loaded after finishing the stage
  • _progress_flag: unique value from 1 to 255 saved into the save file after completing the stage
    • Set it to -1 if you don't need to save progress

For example:

setup_stage("TUTORIAL", ACT_SINGLE, snd_bgm_ehz, [spr_animal_flicky, spr_animal_ricky], rm_level_select, 99);

Room Setup

Now create a room for the stage. We'll call it rm_stage_test

  1. Create a separate instance layer for room controllers and place it at the very top of the layer list. GameMaker already creates one by default, so you can simply rename it to "System" if you want
  2. On this layer, outside the room boundaries, place:
    • obj_game
    • obj_rm_stage_test
      Make sure they're placed exactly in this order. These need to be the first instances created in the room
  3. Lock the layer. You most likely won't need to touch it again
  4. Create two tile layers:
    • "Collision_Main"
    • "Markers_Main"
  5. Assign the collision tile set to "Collision_Main", for example, ts_collision_default. For your own tilesets, note that collision data must be generated in the scr_game_setup() script before it can be used
  6. Assign ts_markers to "Markers_Main"
  7. Create another instance layer for gameplay objects
  8. Place obj_player_spawn on this layer wherever you want the player to spawn

Your stage should now appear in the default menu! Load into it and see if everything works as expected. You can also change global.start_room inside scr_game_setup() if you want the game to load directly into the stage

You're done!

Further Configuration

If you want loops or branching paths, you should create additional collision and marker layers:

  • "Collision_A"
  • "Collision_B"
  • "Markers_A"
  • "Markers_B"

You can look at the default Orbinaut stages to see how things like backgrounds, dynamic palettes and extra stage effects are set up.

Creating a New Object

All game objects in the framework should inherit from obj_gameobject or from another object that already inherits from obj_gameobject. Orbinaut provides four base parent types out of the box:

  • Regular object
  • Solid object
  • Enemy object
  • Projectile object

For all game objects, you can call event_animator() and event_culler() in the Create event to enable the animator and culling modules respectively. These functions should be called after event_inherited()

Regular Object

  1. Create a new object
  2. Set its parent object to obj_gameobject
  3. Inherit the Create event

The object's collision box will be used as its hitbox for interactions with other objects. To check for collision with the player, use collision_player()

Solid Object

  1. Create a new object
  2. Set its parent object to obj_solid
  3. Inherit the Create event
  4. Call solid_object(_player, _type) in the Step event so the object can run its solid collision checks

The object's collision box will be used as the solid area the player collides with

If you want the solid to form a custom shape, override the solid_offsets array with values that shift the collision surface vertically across the full width of the solid's collision box

If you want to prevent the player from balancing on the edges of the object, set solid_balance to false

Enemy Object

  1. Create a new object
  2. Set its parent object to obj_enemy
  3. Inherit the Create event

The enemy object will follow the default rules for how badniks react to players and to other conditions under which they can be destroyed. If you want custom behaviour, override the Step event

Bosses should also inherit from this base object, then override the Step event with their own behaviour and call react_to_players(REACT_TYPE.BOSS) to check for collisions with players

Projectile Object

  1. Create a new object
  2. Set its parent object to obj_projectile
  3. Inherit the Create event

Projectile objects automatically react to all players. You can configure projectile's movement using vel_x, vel_y, and grv

Adding a New Character

Creating a Character Slot

You should add a character slot and register the character's type first. To do this:

  1. Open obj_player's Create Event.
  2. Find the PLAYER enum.
  3. Add a new enum entry and manually assign an index to it. For example, if we were to add Shadow, we would do this:
enum PLAYER
{
	SONIC = 0,
	TAILS = 1,
	KNUCKLES = 2,
	AMY = 3,
	SHADOW = 4,
	NONE = 255
}

Adding the Character to the Dev Menu

To make your character selectable from the Dev Menu, all you need to do is add two lines of code:

  1. Open obj_rm_dev_menu's Create Event
  2. Scroll all the way down and locate the initialisation function calls for category ID 4 (Player 1 Select) and category ID 5 (Player 2 Select)
  3. Add your character's name to both of these. They must strictly follow the order in which they are defined in the PLAYER enum
add_category    // ID 4
(
    "PLAYER 1 SELECT",
	[
	    "SONIC",
	    "TAILS",
	    "KNUCKLES",
	    "AMY",
		"SHADOW"
	]
);
add_category    // ID 5
(
    "PLAYER 2 SELECT",
	[
	    "SONIC",
	    "TAILS",
	    "KNUCKLES",
	    "AMY",
		"SHADOW",
	    "NO PLAYER 2"
	]
);

Adding Animations

The character's presentation will default to Sonic's, i.e. its sprites and HUD elements. To make the character use its own set of sprites and animations, do the following:

  1. Create a copy of the scr_player_animate_sonic script and rename it to include your character's name (or however you wish to name it; it doesn't matter)
  2. Open the scr_player_animate script and add a case for your character (in our case, PLAYER.SHADOW, i.e. the enum we created) and call the newly created animation script from it. Make sure your case comes before the default case, as the default case is used as a fallback
  3. Modify your animation script as needed

Adding an Ability

First, register a new ability by opening obj_player's Create Event once again and adding a new ACTION enum entry:

enum ACTION
{
	NONE,
	SPIN_DASH,
	DASH,
	DROP_DASH,
	GLIDE,
	CLIMB,
	FLIGHT,
	TRANSFORM,
	HAMMER_DASH,
	HAMMER_SPIN,
	CARRIED,
	MY_NEW_COOL_ACTION
}

Create a script that will contain your ability code. The general structure would be as follows:

if action != ACTION.MY_NEW_COOL_ACTION
{
	return;
}

/* your action code */

If your ability should be triggered when the player is grounded, you'll need to add its initialisation code here:

if action != ACTION.MY_NEW_COOL_ACTION
{
	if action == ACTION.NONE && player_type == PLAYER.SHADOW && /*your conditions*/
	{
		action = ACTION.MY_NEW_COOL_ACTION;

		/*your action initialization code*/
	}
	else
	{
		return;
	}
}

/* your action code */

If your ability should be triggered mid-jump, you'll need to initialise the ability under a new case in the scr_player_jump script instead:

case PLAYER.SHADOW:

	if action != ACTION.NONE
	{
		break;
	}

	if /*your conditions*/
	{
		action = ACTION.MY_NEW_COOL_ACTION;

		/*your action initialization code*/
	}

break;

The best place to inject your script call into obj_player's state machine would be:

  • before the if animation == ANIM.SPIN check for the grounded substate;
  • before the scr_player_movement_air function call for the airborne substate.