雙峰祭で展示したミニゲームを公開しました

UWE published on
5 min, 926 words

Categories: article

ShiftFlameworkを使って実装し、雙峰祭で展示したミニゲームを公開します。

プレイ

Google Chrome, Microsoft EdgeやChromiumブラウザの新しいバージョンで実行できます。 Braveなど一部ブラウザで起動時に数秒フリーズが起こることを確認しています。

前回のポストのように画面だけで埋め込めると見栄えが良いのですが、シェル部分を雑に消すとキーボード入力を受け付けなくなりました。 HTMLとJavaScriptは少しもわからないですが、気が向いたら必要な部分を特定してきれいな見た目にしたいです。

プラグイン(スクリプト)が書けないため、ゲーム部分はすべてメイン関数に記述しています。以下実装

// flappy bird
std::shared_ptr<Entity> player;
std::vector<std::shared_ptr<Entity>> pipes{};
float player_a = 0.07;
std::shared_ptr<Entity> title;
std::vector<std::shared_ptr<Entity>> score_numbers{};
std::shared_ptr<Entity> background;

bool in_game = false;
float pipe_interval = 1.2f;
float pipe_randomness_coeff = 0.1f;
float pipe_gap = 0.4f;
float pipe_speed = 0.5f / 60.0f;
float flame_sinse_last_pipe = 0;
float player_rad = 0.2f;
float pipe_width = 0.3f;
std::queue<std::shared_ptr<Entity>> unused_pipe{};

int score = 0;

void update_score() {
  int score_copy = score;
  for (int i = 3; i >= 0; i--) {
    score_numbers.at(i)->get_component<Material>()->tile_scale =
        Math::Vector2f({0.1f, 1.0f});
    auto d = 1;
    for (int j = 0; j < i; j++) d *= 10;
    score_numbers.at(i)->get_component<Material>()->uv_offset =
        Math::Vector2f({0.1f * (float)(score_copy / d), 0.0f});
    score_copy -= (score_copy / d) * d;

    score_numbers.at(i)->get_component<Material>()->update_texture_sampling();
  }
}

void restart_flappy() {
  while (!unused_pipe.empty()) unused_pipe.pop();
  for (int i = 0; i < pipes.size(); i++) {
    auto pipe_t = pipes.at(i)->get_component<ScreenSpaceTransform>();
    pipe_t->position = Math::Vector2f({-1.5f, i % 2 == 0.0f ? -1.0f : 1.0f});
    pipe_t->scale = Math::Vector2f({pipe_width, 1.0f});
  }

  flame_sinse_last_pipe = 0;
  player_a = 0.07;

  title->get_component<ScreenSpaceMesh>()->is_active = true;
}

void main_loop() {
  // user script
  if (in_game) {
    if (Engine::get_module<Input>()->get_keyboard_state(Keyboard::ESCAPE) ==
        ButtonState::DOWN) {
      in_game = false;
      restart_flappy();
    }

    if (Engine::get_module<Input>()->get_keyboard_state(Keyboard::W) ==
        ButtonState::DOWN) {
      player_a = 0.07f;
    } else {
      player_a -= 0.007f;
    }
    player->get_component<ScreenSpaceTransform>()->position =
        player->get_component<ScreenSpaceTransform>()->position +
        Math::Vector2f({0, player_a});
    float current_y = player->get_component<ScreenSpaceTransform>()
                          ->position.internal_data[1];
    if (current_y < -1.2 || current_y > 1.0) {
      in_game = false;
      restart_flappy();
    }

    player->get_component<ScreenSpaceTransform>()->angle =
        (player_a <= -0.03) ? -0.4f : 0.0f;

    // respawn pipe
    flame_sinse_last_pipe++;
    if (flame_sinse_last_pipe > pipe_interval / pipe_speed) {
      flame_sinse_last_pipe = 0;
      std::random_device seed_gen;
      std::mt19937 engine(seed_gen());
      std::normal_distribution<float> r(-pipe_randomness_coeff,
                                        pipe_randomness_coeff);
      auto rand = r(engine);
      for (int i = 0; i < 2; i++) {
        unused_pipe.front()
            ->get_component<ScreenSpaceTransform>()
            ->position.internal_data[0] = 1.5f;
        unused_pipe.front()
            ->get_component<ScreenSpaceTransform>()
            ->position.internal_data[1] = (i % 2 == 0.0f ? -1.0f : 1.0f) + rand;
        unused_pipe.pop();
      }

      score++;
      update_score();
    }
    for (int i = 0; i < pipes.size(); i++) {
      // move pipe
      auto pipe_t = pipes.at(i)->get_component<ScreenSpaceTransform>();
      pipe_t->position.internal_data[0] -= pipe_speed;

      if (pipe_t->position.internal_data[0] < -1.5f) {
        unused_pipe.push(pipes.at(i));
        pipes.at(i)->get_component<ScreenSpaceTransform>()->position =
            Math::Vector2f({10000, 0});
      }
    }

    // scrol background
    background->get_component<Material>()->uv_offset +=
        Math::Vector2f({pipe_speed / 2.0f * 0.8f, 0.0f});
    background->get_component<Material>()->update_texture_sampling();

    // hit
    for (int i = 0; i < pipes.size(); i++) {
      auto pipe = pipes.at(i);
      auto pipe_t = pipe->get_component<ScreenSpaceTransform>();
      if (std::abs(pipe_t->position.internal_data[0] -
                   player->get_component<ScreenSpaceTransform>()
                       ->position.internal_data[0]) <
          (pipe_width + player_rad * 0.8f) / 2.0f) {
        if (std::abs(pipe_t->position.internal_data[1] -
                     player->get_component<ScreenSpaceTransform>()
                         ->position.internal_data[1]) <
            (1.0f + player_rad * 0.8f) / 2.0f) {
          in_game = false;
          restart_flappy();
        }
      }
    }
  } else {
    // not in game
    if (Engine::get_module<Input>()->get_keyboard_state(Keyboard::R) ==
        ButtonState::DOWN) {
      in_game = true;
      title->get_component<ScreenSpaceMesh>()->is_active = false;

      auto p_transform = player->get_component<ScreenSpaceTransform>();
      p_transform->scale = Math::Vector2f({0.2f, 0.2f});
      p_transform->position = Math::Vector2f({-0.3, 0});
      score = 0;
      update_score();
    }
  }

  Engine::get_module<ScreenSpacePhysics>()->update();
  Engine::get_module<Input>()->update();
  Engine::get_module<ScreenSpaceMeshRenderer>()->render(
      Engine::get_module<Window>()->get_swap_chain().GetCurrentTextureView());
}

void start() {
  {
    Window window("game window", 1080, 1080);
    std::get<std::shared_ptr<Window>>(Engine::modules) =
        std::make_shared<Window>(window);
  }

  wgpu::SupportedLimits supported_limits;
  Engine::get_module<Graphics>()->device.GetLimits(&supported_limits);
  Engine::get_module<Graphics>()->limits = supported_limits.limits;

  Engine::get_module<Window>()->initialize_swap_chain(
      Engine::get_module<Graphics>()->instance,
      Engine::get_module<Graphics>()->device);
  Engine::get_module<ScreenSpaceMeshRenderer>()->initialize();

  // game initialize
  // background
  background = std::make_shared<Entity>();
  background->add_component<ScreenSpaceMesh>();
  background->add_component<ScreenSpaceTransform>()->scale =
      Math::Vector2f({2, 2});
  background->add_component<Material>()->create_gpu_buffer(
      wall_height, wall_width, wall_data);

  // pipe
  pipes = std::vector<std::shared_ptr<Entity>>(8, nullptr);
  for (int i = 0; i < pipes.size(); i++) {
    auto pipe = std::make_shared<Entity>();
    pipe->add_component<ScreenSpaceMesh>();
    auto pipe_t = pipe->add_component<ScreenSpaceTransform>();
    pipe_t->position = Math::Vector2f({-1.5f, i % 2 == 0.0f ? -1.0f : 1.0f});
    pipe_t->scale = Math::Vector2f({pipe_width, 1.0f});
    auto mat = pipe->add_component<Material>();
    mat->create_gpu_buffer(piller_height, piller_width, piller_data);
    mat->uv_offset = Math::Vector2f({0, 0});
    mat->tile_scale = Math::Vector2f({1, 1});
    mat->update_texture_sampling();
    pipes.at(i) = pipe;
    unused_pipe.push(pipe);
  }

  // player
  player = std::make_shared<Entity>();
  player->add_component<ScreenSpaceMesh>();
  player->add_component<ScreenSpaceTransform>();
  auto p_transform = player->get_component<ScreenSpaceTransform>();
  p_transform->scale = Math::Vector2f({player_rad, player_rad});
  p_transform->position = Math::Vector2f({-0.3, 0});
  player->add_component<Material>()->create_gpu_buffer(
      player_height, player_width, player_data);
  player->get_component<Material>()->tile_scale = Math::Vector2f({-1, 1});
  player->get_component<Material>()->update_texture_sampling();

  // score
  score_numbers = std::vector<std::shared_ptr<Entity>>(4, nullptr);
  for (int i = 0; i < 4; i++) {
    auto n = std::make_shared<Entity>();
    n->add_component<ScreenSpaceMesh>();
    auto t = n->add_component<ScreenSpaceTransform>();
    t->scale = Math::Vector2f({1.0 / 10.0, 104.0 / 1080.0});
    t->position =
        Math::Vector2f({1.0f - (float)(i + 1.0f) * 1.0f / 10.0f, 1.0f - 0.1f});
    n->add_component<Material>()->create_gpu_buffer(number_height, number_width,
                                                    number_data);
    score_numbers.at(i) = n;
  }
  update_score();

  // title
  title = std::make_shared<Entity>();
  title->add_component<ScreenSpaceMesh>();
  title->add_component<ScreenSpaceTransform>()->scale = Math::Vector2f({2, 2});
  title->add_component<Material>()->create_gpu_buffer(title_height, title_width,
                                                      title__data);

  // start main loop
  Engine::get_module<Window>()->start_main_loop(main_loop);
}