Hobo Life: Actors, Acting, and other Sundries
Most games that aren’t some degenerate variation on solitaire are substantially more fun when they contain some kind of player-reactive challenge element. Think for a moment about the classic italian plumber game. Would it be nearly as interesting without dangerous, unaccountable turtles flying around everywhere? I don’t think so!
Much like italian plumber game, Custom Hobo is also a game, although this one is about hobos. In addition to being about tricky platforming, Custom Hobo is about hobo combat:
Hobo combat that would be stultifyingly boring if every hobo was an inert object that didn’t try to fight back:
We’re getting a bit ahead of ourselves here, though. In order to have fighting hobos, we need to know what hobos are.
This post and its follow-up will answer (some of) your existential questions about hobos. Strap in.
First, an ode to pieces
At Piece Factory, pieces is our religion. In Custom Hobo, every physical object that exists in the game world is CHPhysPiece, as lovingly detailed in a prior devlog. As you may recall, CHPhysPiece inherits from CHPhysicsObject, which is, essentially, a “composite piece” with special logic around it:
class CHPhysicsObject : public CHRenderTarget
{
... important stuff
private:
std::vector<std::shared_ptr<CHPhysPiece>> m_pieces;
std::vector<std::shared_ptr<CHPhysJoint>> m_joints;
}
CHPhysicsObject is a bag of pieces and joints, and it occurs to us that our NPCs want to be made of pieces and joints too. Sounds like a CHPhysicsObject is a good fit. To wit, we subclass it:
class CHActor : public CHPhysicsObject
{
... important stuff ...
//Lots of actor specific logic!
virtual void update(float dt) override;
... important stuff ...
private:
... important stuff ...
/// @brief Actions implemented at the actor level that need whole-body coordination
std::vector<CHActorAction<CHActor>> m_implementedActions;
// In CHPhysicsObject. Can contain CHActorPiece!
std::vector<std::shared_ptr<CHPhysPiece>> m_pieces;
}
and voila. We have CHActor, the backbone of Custom Hobo’s dynamic gameplay elements.
Actors
CHActor is an animate physics object that is made out of Special pieces called CHActorPiece, which is a subclass of CHPhysPiece. This means it’s not only a physical “thing” in the world, we can also extend it so it knows how to implement actions:
class CHActorPiece : public CHPhysPiece
{
... important composite piece related doings ...
private:
// The actions this piece can do, and what they do when activated!
// Data locality fanpeople might be frothing at the mouth
// at the implications of this by this point
std::vector<CHActorAction<CHActorPiece>> m_implementedActions;
}
Then, we encapsulate behavior expression in CHActorAction.
Pieces What Do Stuff
CHActorAction is a bundle of callbacks and state. It doesn’t know anything about when or why CHActor might be doing the action, but it knows it has to act when told.
CHActorAction expresses the “how” the action is done, if you will:
template <typename Owner>
struct CHActorAction
{
std::function<bool(Owner* owner)> enabledWhen = [](Owner* owner) { return true; };
std::optional<std::function<void(Owner* owner,
const CHActionData& desiredAction, float dt)>> onStartAction;
std::optional<std::function<void(Owner* owner,
const CHActionData& desiredAction, float dt)>> onContinueAction;
std::optional<std::function<void(Owner* owner, float dt)>> onEndAction;
std::optional<std::function<void(Owner* owner, float dt)>> onCooledDownAction;
std::optional<std::function<void(Owner* owner, float dt)>> onNotDoingAction;
...
}
Through flagrant abuse of std::function, CHActorAction lets us describe behaviors without knowing where and when the behavior might happen.
For example: in a very similar way to how your gamey gams know how to ripple on command, hobo legs know how to jump when their feet are on the ground:
CHActorAction<CHActorPiece> jump;
jump.action = CHActorActionEnum::JUMP;
jump.onStartAction = [](auto piece, const auto& actionData, float dt)
{
//Simple as.
if (piece->canJump())
{
piece->doJump();
}
}
and some heads know, among other things, how to scream properly (a seriously underrated skill in my opinion):
CHActorAction<CHActorPiece> expressPain;
expressPain.action = CHActorActionEnum::EXPRESS_PAIN;
// Pay no attention to the mysterious `actionData` We'll explain more on that in a future post.
expressPain.onStartAction = [](auto piece, const auto& actionData, float dt)
{
piece->saySomething("{shaking}AAAAAhhhhh!!!!{}", 1.f);
};
head->addAction(expressPain);
If you think about it hard enough, you could model your own meatbody this way. It’s essentially a bunch of individual parts that get coordinated. Your butt knows how to sit, but not why, or where. If your brain gets lazy, you’re sittin’, that’s all I’m saying. Your legs know how to flex to impress the locals, but they don’t know if there are locals around to watch.
It all just works
With CHActorAction, we achieve a really cool separation-of-concerns that thematically matches up perfectly with Custom Hobo’s core ethos, i.e. horrible maimings. In Custom Hobo, hobos get mangled and lose limbs constantly. The CHActorAction system means, if your hobo loses a leg to a horrible explosive barrel accident, they literally lose the ability to walk with that leg:
Stitching the pieces together
classDiagram
direction LR
CHRenderTarget <|-- CHPhysicsObject
CHPhysicsObject <|-- CHActor
CHPhysicsObject <|-- CHPhysPiece
CHPhysPiece <|-- CHActorPiece
CHActor *-- CHActorPiece
CHActor *-- CHActorAction
CHPhysicsObject *-- CHPhysPiece
CHActorPiece *-- CHActorAction
As you can see, we now have a pretty convenient way to make fairly expressive NPCs just by taping these concepts together. A hobo is a CHActor with a big bag of meaty pieces, and those pieces have CHActorAction implementations with carte blanche to make the CHActors desires become reality in the physics world.
If that hobo wants to jump, you better believe that his legs are gonna try to jump if they can. If that biomaggot wants to shoot acid in your eyes, it’s gonna happen, don’t fight it. Got an armless hobo? That hobo can’t point, but boy can they (probably) still walk. Etc.
This post solves one mystery that no one was asking about (what and how is a hobo?), and creates at least one new one - how does a hobo decide when and where to act? Where does their “control impulse” come from?
Don’t try to guess, it’s not the Chrysler building. We’ll answer these questions and more in our next actor-related post.