Actor Tutorials

This tutorial requires C++ knowledge, as well as knowledge on how to create a new Code module using Syati. Currently, coding a custom actor only properly works with SMG2, though SMG1 support is planned.

What is an actor?

The base class of every object in Super Mario Galaxy is NameObj. These are basic objects that can be drawn, but that do not have a physical position and state. Most objects goes a step further and inherit from LiveActor. The LiveActor class provides the necessary structure to create more complex objects, NameObjs on the other hand cannot really do that much. For the rest of this guide, when referring to an "actor", I am referring to an object that inherits LiveActor.

Creating your own actor

Once you have created a new module, create a new CustomActor.h file in the include/ folder. In here we first define the class of our actor:

#pragma once
#include "syati.h"

class CustomActor : public LiveActor {
public:
    CustomActor(const char *pName);
    virtual void init(const JMapInfoIter &rIter);
    virtual void movement();
    virtual void control();
};

Now, inside the source/ directory, create a new CustomActor.cpp file and include your class:

#include "CustomActor.h"

CustomActor::CustomActor(const char *pName) : LiveActor(pName) {
	
}

The constructor (ctor) gets called when the actor first gets created. For creating placeable objects, you have to inherit LiveActor::LiveActor(pName) (shortened here to simply LiveActor(pName)).
Both movement and control are called on every frame, so you can put your own code in there. However, there are important differences:


The init function gets called on the first frame the actor exists, and this is what it should look like. Depending on how complex your actor is, yours may look different:

void CustomActor::init (const JMapInfoIter &rIter) {
	MR::initDefaultPos(this, rIter);
	MR::processInitFunction(this, rIter, false);
	MR::connectToSceneMapObj(this);
	makeActorAppeared();
	MR::validateClipping(this);
}

MR::initDefaultPos places the actor at it's intended default position.
MR::processInitFunction does basic actor initialization. You can also call it with a model name instead of a JMapInfoIter, in case the name of the model is different to the name of your actor.
MR::connectToSceneMapObj adds it to the current game scene. You can also call a variation of this like MR::connectToSceneEnemy depending on your use case.
makeActorAppeared is not just required for visual models to appear, but also for the actor to start functioning properly.
Finally, we call MR::validateClipping to make the actor disappear when the player is far enough away. If you want it to always stay visible, call MR::invalidateClipping instead.

Now, to make the game recognize our new actor, we need to add an entry to the NameObjFactory. To do that, clone its Syati module and then add this to your own ModuleInfo.json file:

"ModuleData" : [
  {
    "NameObjFactory" : [
	  {
	    "Name" : "CustomActor",
	    "Function" : "createExtActor<CustomActor>",
	    "Include" : "CustomActor.h"
	  }
	]
  }
]

This makes the game know what your actor is called. From now on, placing a CustomActor object in the level will spawn in your actor.

If your actor is visible, make sure you also have a CustomActor.arc file in the ObjectData/ folder of SMG2.

Some additional functions that could be useful as well:

About HitSensors

To allow your actor to have a "hit sphere" that can interact with the player or other actors, you need to add a HitSensor. HitSensors are always spherical, and you need to specify a radius in game units when creating it. Add the following to your init function:

initHitSensor(1);
MR::addHitSensorMapObj(this, "Body", 8, 100.0f, TVec3f(0.0f, 0.0f, 0.0f));

Firstly, we initialize exactly one HitSensor. Then we add it to our object with the following parameters:

It is also possible to add different types of HitSensors by using a different MR::addHitSensor... function, or by simply using MR::addHitSensor itself, which adds a u32 parameter for the type of sensor:

MR::addHitSensor(this, "Body", ATYPE_MAP_OBJ, 8, 100.0f, TVec3f(0));

To see which types you can use or what functions are available, check the HitSensor.h file in Syati's include/Game/LiveActor directory.

Once our HitSensor is added, we also need to handle something interacting with that HitSensor. For this, you can use four different functions depending on your needs.

class CustomActor : public LiveActor {
public:
	// ...
	virtual void attackSensor(HitSensor *, HitSensor *);
	virtual bool receiveMsgPlayerAttack(u32, HitSensor *, HitSensor *);
	virtual bool receiveMsgEnemyAttack(u32, HitSensor *, HitSensor *);
	virtual bool receiveOtherMsg(u32, HitSensor *, HitSensor *);
	// ...
}
To see which message you are working with or what functions are available, look at the HitSensor.h file in Syati's include/Game/LiveActor directory.

After that, to actually define the function, place this into your C++ file:

bool CustomActor::receiveMsgPlayerAttack (u32 msg, HitSensor *pSender, HitSensor *pReceiver) {

}

To figure out what type the sender is, you can call one of the 25 MR::isSensor... functions. When checking for the player, using MR::isSensorPlayerOrYoshiOrRide(pSender) is recommended. To figure out what type of interaction ocurred, use one of the 105 MR::isMsg functions or manually compare it to the respective number. For example, the player spin-attacking your actor can be checked by calling MR::isMsgPlayerSpinAttack(msg). Remember that any of these functions (except for attackSensor) has to return a bool. This is needed to let the sender of the message know that there was an interaction (true) or that it was simply ignored (false). For example, if the player interacts with it and true is returned, a certain particle effect may be created to show the impact.
If you need to check which one of your multiple HitSensors got interacted with, you can compare pReceiver with getSensor(X) (replace X with the name of the HitSensor you want to check for).

About Collision

Some actors also feature collision on top of a HitSensor, which needs to be initialized seperately. To do that, add this to the init function of your actor:

MR::initCollisionParts(this, "Custom", getSensor("body"), NULL);

This function gets passed the following arguments:

From there, you can also turn this collision on or off by using MR::validateCollisionParts(this); or MR::invalidateCollisionParts(this);

Using ActorInfo

You can eliminate the need for a bunch of code in your init function by using a few ActorInfo bcsv files. Here is an example of a InitActor.bcsv from the Project Template's RedCoin:

Here's a breakdown of what these entries do: