ブロック崩しを作る (5) - 階層構造にしたバージョンのコード
下のスクリーンショットのようなGodotのノードによる階層的なゲームのシーンの表現をイメージして作り直したコードです。
クラス導入前のコードとクラスを導入してフラットな構造にしたコードをベースに書き直しました。
#include <SDL2/SDL.h>
#include <vector>
#include <string>
#include <algorithm>
using std::vector;
using std::string;
using std::remove_if;
using std::copy_if;
const int ScreenWidth = 400;
const int ScreenHeight = 400;
class Node;
void resolve_collision(Node* node, Node* root);
bool overlaps_x(const SDL_Rect& a, const SDL_Rect& b);
bool overlaps_y(const SDL_Rect& a, const SDL_Rect& b);
bool is_dead(const Node* node);
struct Vec2 {
double x;
double y;
};
Vec2 normalized(const Vec2& v)
{
double length = sqrt(v.x * v.x + v.y * v.y);
return Vec2{v.x / length, v.y / length};
}
Vec2 scalar_multiplied(const Vec2& v, double s)
{
return Vec2{v.x * s, v.y * s};
}
class Node {
public:
enum State { Active, Dead };
Node(const string& tag, const SDL_Rect& rect, const SDL_Color& color);
virtual ~Node();
void update(Node* root);
void render(SDL_Renderer* renderer);
virtual void on_collision_enter(Node* other);
void add_subnode(Node* subnode);
Node* find_subnode_by_tag(const string& tag);
void kill_dead_subnodes();
string tag() const { return tag_; }
SDL_Rect rect() const { return rect_; }
SDL_Color color() const { return color_; }
State state() const { return state_; }
void set_rect(const SDL_Rect& rect) { rect_ = rect; }
void set_state(State state) { state_ = state; }
Node(const Node&) = delete;
Node(Node&&) = delete;
Node& operator=(const Node&) = delete;
Node& operator=(Node&&) = delete;
private:
virtual void self_update(Node* root);
virtual void self_render(SDL_Renderer* renderer);
private:
vector<Node*> subnodes_;
string tag_;
SDL_Rect rect_;
SDL_Color color_;
State state_;
};
Node::Node(const string& tag, const SDL_Rect& rect, const SDL_Color& color)
: tag_{tag}, rect_{rect}, color_{color}, state_{State::Active}
{}
Node::~Node()
{
for (auto subnode : subnodes_) {
delete subnode;
}
}
void Node::update(Node* root)
{
self_update(root);
for (auto subnode : subnodes_) {
subnode->update(root);
}
}
void Node::render(SDL_Renderer* renderer)
{
self_render(renderer);
for (auto subnode : subnodes_) {
subnode->render(renderer);
}
}
void Node::on_collision_enter(Node* other) {}
void Node::add_subnode(Node* node)
{
subnodes_.push_back(node);
}
Node* Node::find_subnode_by_tag(const string& tag)
{
if (tag_ == tag) {
return this;
}
for (auto n : subnodes_) {
if (n->find_subnode_by_tag(tag) != nullptr) {
return n;
}
}
return nullptr;
}
void Node::kill_dead_subnodes()
{
vector<Node*> dead_nodes;
copy_if(subnodes_.begin(), subnodes_.end(),
back_inserter(dead_nodes), is_dead);
auto p = remove_if(subnodes_.begin(), subnodes_.end(), is_dead);
subnodes_.erase(p, subnodes_.end());
for (auto d : dead_nodes) {
delete d;
}
for (auto n : subnodes_) {
n->kill_dead_subnodes();
}
}
void Node::self_update(Node* root) {}
void Node::self_render(SDL_Renderer* renderer)
{
SDL_SetRenderDrawColor(renderer, color_.r, color_.g, color_.b, color_.a);
SDL_RenderFillRect(renderer, &rect_);
}
class Paddle : public Node {
public:
Paddle(const string& tag, const SDL_Rect& rect, const SDL_Color& color);
~Paddle();
void set_move_scale(double move_scale) { move_scale_ = move_scale; }
private:
void self_update(Node* root) override;
private:
double move_scale_;
const Uint8* key_state_;
};
Paddle::Paddle(const string& tag, const SDL_Rect& rect, const SDL_Color& color)
: Node{tag, rect, color}
, move_scale_{0.0}
, key_state_{SDL_GetKeyboardState(nullptr)}
{}
Paddle::~Paddle() {}
void Paddle::self_update(Node* root)
{
double dx = 0.0;
if (key_state_[SDL_SCANCODE_LEFT]) {
dx -= 1.0;
}
if (key_state_[SDL_SCANCODE_RIGHT]) {
dx += 1.0;
}
SDL_Rect r = rect();
r.x += move_scale_ * dx;
set_rect(r);
resolve_collision(this, root);
}
class Ball : public Node {
public:
Ball(const string& tag, const SDL_Rect& rect, const SDL_Color& color);
~Ball();
void on_collision_enter(Node* other) override;
Vec2 move_dir() const { return move_dir_; }
void set_move_dir(const Vec2& move_dir) { move_dir_ = normalized(move_dir); }
void set_move_scale(double move_scale) { move_scale_ = move_scale; }
private:
void self_update(Node* root) override;
private:
Vec2 move_dir_;
double move_scale_;
};
Ball::Ball(const string& tag, const SDL_Rect& rect, const SDL_Color& color)
: Node{tag, rect, color}
, move_dir_{0.0}
, move_scale_{0.0}
{}
Ball::~Ball() {}
void Ball::on_collision_enter(Node* other)
{
SDL_Rect prev;
Vec2 move = scalar_multiplied(move_dir_, move_scale_);
prev.x = round(rect().x - move.x);
prev.y = round(rect().y - move.y);
prev.w = rect().w;
prev.h = rect().h;
if (!overlaps_x(prev, other->rect())) {
move_dir_.x *= -1;
}
if (!overlaps_y(prev, other->rect())) {
move_dir_.y *= -1;
}
}
void Ball::self_update(Node* root)
{
Vec2 move = scalar_multiplied(move_dir_, move_scale_);
SDL_Rect r = rect();
r.x = round(rect().x + move.x);
r.y = round(rect().y + move.y);
set_rect(r);
}
class Brick : public Node {
public:
Brick(const string& tag, const SDL_Rect& rect, const SDL_Color& color);
~Brick();
void on_collision_enter(Node* other) override;
private:
void self_update(Node* root) override;
};
Brick::Brick(const string& tag, const SDL_Rect& rect, const SDL_Color& color)
: Node{tag, rect, color}
{}
Brick::~Brick() {}
void Brick::self_update(Node* root)
{
resolve_collision(this, root);
}
void Brick::on_collision_enter(Node* other)
{
set_state(Node::State::Dead);
}
class Wall : public Node {
public:
Wall(const string& tag, const SDL_Rect& rect, const SDL_Color& color);
~Wall();
private:
void self_update(Node* root) override;
};
Wall::Wall(const string& tag, const SDL_Rect& rect, const SDL_Color& color)
: Node{tag, rect, color}
{}
Wall::~Wall() {}
void Wall::self_update(Node* root)
{
resolve_collision(this, root);
}
bool is_ball(const Node* node) { return node->tag() == "ball"; }
bool is_dead(const Node* node) { return node->state() == Node::State::Dead; }
Node* find_ball(Node* node)
{
return node->find_subnode_by_tag("ball");
}
int left(const SDL_Rect& rect) { return rect.x; }
int top(const SDL_Rect& rect) { return rect.y; }
int right(const SDL_Rect& rect) { return rect.x + rect.w - 1; }
int bottom(const SDL_Rect& rect) { return rect.y + rect.h - 1; }
bool overlaps_x(const SDL_Rect& a, const SDL_Rect& b)
{
return !(left(a) > right(b) || right(a) < left(b));
}
bool overlaps_y(const SDL_Rect& a, const SDL_Rect& b)
{
return !(top(a) > bottom(b) || bottom(a) < top(b));
}
void collision_entered(Node* a, Node* b)
{
a->on_collision_enter(b);
b->on_collision_enter(a);
}
void resolve_collision(Node* self, Node* root)
{
auto ball = find_ball(root);
if (ball == nullptr) {
return; // not found
}
SDL_Rect rect1 = self->rect();
SDL_Rect rect2 = ball->rect();
if (SDL_HasIntersection(&rect1, &rect2)) {
collision_entered(self, ball);
}
}
void kill_dead_nodes(Node* node)
{
if (is_dead(node)) {
delete node;
return;
}
node->kill_dead_subnodes();
}
void update(Node* root_node)
{
root_node->update(root_node);
kill_dead_nodes(root_node);
}
void render(SDL_Renderer* renderer, Node* root_node)
{
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
root_node->render(renderer);
SDL_RenderPresent(renderer);
}
Node* setup_nodes()
{
Node* root_node = new Node{"root", {}, {}};
// temporary vars
int w, h, x, y;
SDL_Rect rect;
SDL_Color color;
// setup paddle
w = 50;
h = 10;
x = ScreenWidth / 2 - w / 2; // horizontal center of screen
y = ScreenHeight - 2 * h; // 1 unit above from bottom of screen
rect = { x, y, w, h };
color = { 222, 222, 255, 255 };
auto paddle = new Paddle{"paddle", rect, color};
paddle->set_move_scale(3.0);
root_node->add_subnode(paddle);
// setup ball
w = 1.2 * paddle->rect().h;
h = w; // square
x = paddle->rect().x + (paddle->rect().w / 2) - (w / 2); // center of paddle
y = paddle->rect().y - h; // upon paddle
rect = { x, y, w, h };
color = { 255, 128, 200, 255 };
auto ball = new Ball{"ball", rect, color};
ball->set_move_dir(Vec2{1.0, -1.0});
ball->set_move_scale(3.0);
root_node->add_subnode(ball);
// setup bricks
Node* bricks = new Node{"bricks", {}, {}};
const int space = 4;
const int margin = 50;
const int columns = 10;
const int rows = 15;
const int brick_width =
(ScreenWidth - (2 * margin) - ((columns - 1) * space)) / columns;
const int brick_height = brick_width * 0.3;
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < columns; ++x) {
rect.x = x * (brick_width + space) + margin;
rect.y = y * (brick_height + space) + margin;
rect.w = brick_width;
rect.h = brick_height;
color.r = (columns - x) * (255 / columns);
color.g = (y + 1) * (255 / rows);
color.b = (x + 1) * (255 / columns);
color.a = 255;
auto brick = new Brick{"brick", rect, color};
bricks->add_subnode(brick);
}
}
root_node->add_subnode(bricks);
// setup walls
Node* walls = new Node{"walls", {}, {}};
const int v_wall_width = 10;
const int v_wall_height = ScreenHeight;
const int h_wall_width = ScreenWidth - 2 * v_wall_width;
const int h_wall_height = v_wall_width;
color = {99, 38, 255, 255};
rect = {v_wall_width, 0, h_wall_width, h_wall_height};
auto top_wall = new Wall{"wall", rect, color};
rect = {0, 0, v_wall_width, v_wall_height};
auto left_wall = new Wall{"wall", rect, color};
rect = {ScreenWidth - v_wall_width, 0, v_wall_width, v_wall_height};
auto right_wall = new Wall{"wall", rect, color};
walls->add_subnode(left_wall);
walls->add_subnode(right_wall);
walls->add_subnode(top_wall);
root_node->add_subnode(walls);
return root_node;
}
void main_loop(SDL_Renderer* renderer, Node* root_node)
{
bool running = true;
bool paused = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
} else if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
case SDLK_RETURN:
running = false;
break;
case SDLK_SPACE:
paused = !paused;
break;
default:
break;
}
}
}
if (!paused) {
update(root_node);
}
render(renderer, root_node);
SDL_Delay(16);
}
}
int main()
{
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("SDL_Init() error: %s", SDL_GetError());
exit(1);
}
SDL_Window* window =
SDL_CreateWindow("Bricks SDL Version 5", 0, 0,
ScreenWidth, ScreenHeight, 0);
if (window == nullptr) {
SDL_Log("SDL_CreateWindow() error: %s", SDL_GetError());
exit(1);
}
SDL_Renderer* renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == nullptr) {
SDL_Log("SDL_CreateRenderer() error: %s", SDL_GetError());
exit(1);
}
Node* root_node = setup_nodes();
main_loop(renderer, root_node);
// cleanup
delete root_node;
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}