Say you have entities in a 2D game framework or something similar -- e.g. a GUI framework -- where there are various types of entities that share common properties like position and rotation but where some of these properties must be handled on a per-entity type basis e.g. rotating a simple sprite is performed differently than rotating a rigged 2D animation skeleton.
Obviously this could be handled by a traditional OOP inheritance hierarchy ... However, I'm interested instead in representing such entities using "composition over inheritance" by having a concrete class that nothing inherits from called actor
that has vanilla member variables for state that is handled the same way across entity types but also has a has-a relationship with a variant containing the state that must be handled in a per-entity type way.
This design can be made to work as below:
#include <iostream>
#include <variant>
struct actor_state_1 {
float rotation_;
//point position;
// etc...
void set_rotation(float theta) {
rotation_ = theta;
// say there are different things that need to happen here
// for different actor types...
// e.g. if this is an animation skeleton you need to find the
// the root bone and rotate that, etc.
std::cout << "actor_state_1 set_rotation\n";
}
void set_position(const std::tuple<float, float>& pos) {
// etc ...
}
float get_rotation() const {
return rotation_;
}
// get_position, etc...
};
struct actor_state_2 {
float rotation_;
void set_rotation(float theta) {
rotation_ = theta;
std::cout << "actor_state_2 set_rotation\n";
}
void set_position(const std::tuple<float, float>& pos) {
// etc ...
}
float get_rotation() const {
return rotation_;
}
// get_position, etc...
};
using state_variant = std::variant<actor_state_1, actor_state_2>;
class actor {
private:
state_variant state_;
// common properties...
float alpha_transparency; // etc.
public:
actor(const actor_state_1& state) :
state_(state)
{}
actor(const actor_state_2& state) :
state_(state)
{}
void rotate_by(float theta) {
auto current_rotation = get_rotation();
std::visit(
[current_rotation, theta](auto& a) { a.set_rotation(current_rotation + theta); },
state_
);
}
float get_rotation() const {
return std::visit(
[](const auto& a) {return a.get_rotation(); },
state_
);
}
void move_by(const std::tuple<float, float>& translation_vec);
std::tuple<float, float> get_postion() const; // etc.
};
int main() {
auto a = actor(actor_state_2{ 90.0f });
a.rotate_by(45.0f);
std::cout << a.get_rotation() << "\n";
}
However I feel as though the level of verbosity and the amount of repeated boilerplate code per property makes such a design unwieldy. I however can't figure out a way to cut down on the boilerplate by using templates. It seems like there should be a way to at least make a template for "pass-through getters" like actor::get_rotation()
in the above but I don't know a way of parametrizing on a member function which is what it seems like you would need to do.
Does anyone have ideas for a design like this that is less verbose or uses less boilerplate?