Skip to main content

[ChaEngine] 基本思想

ChaEngineは C++ で DirectX 11 を学び、その知識を活かしつつ
ゲームを完成させることを最大の目標にして作られています。

ゲームの完成というゴールを第一においているため、
一部の機能は一般的に言われるプログラミングのアンチパターンを意図的に採用しています。

その象徴的な機能の一つが、強力な参照機能であるGod Get(Get関数)です。
クラスObjectBaseを継承したクラス内では、Get関数を使うことができます。
Get関数を呼び出すだけでManagerクラス、SystemObject、他Objectなど
あらゆるオブジェクトへ即座にアクセスします。
これは、依存関係を複雑にするというデメリットがあります。
その代わりに、参照したいオブジェクトを直感的に書き表すことができ、
参照先のクラスの所属(Manager/SystemObject/Object)を気にしなくて良い状態、
ゲームプログラマーとしてこれ以上に無い快適な書き心地を実現します。

他のエンジン・ベース・ライブラリとの違い

C++でDirectX 11 を学んでいる方はきっと以下の名前を聞いたことがあり、
学習していると思います。

  • DxLib
  • Unity
  • Unreal Engine
  • 提供されたエンジン (DxLibを使ったエンジン)
  • 学校のゲームベース (DirectX 11のエンジン)

ここでは違いとChaEngineである理由について説明します。

DxLibは、DirectXでの描画関連を扱いやすくしていますが、
シーンやゲームオブジェクトの処理を自分で書く必要があります。
また、C言語で書けるよう設計されているため、C++言語の機能をうまく行かせない部分があります。
※主にマクロ多用やリソースのハンドル部分
ある程度機能が整っている分、今まで学んだ知識を活かせる自由度と、
ブラックボックスの部分が多く、今まで学んだことを活かしづらいです。

Unityはゲームエンジンとして広く認知されていて、
使う言語もC#と学習コストも低く、誰でもゲームを作りやすい環境です。
しかし、私たちが学んできたC++の知識を活かすことは難しいです。

🚧このページは書き途中です。🚧

Unreal Engineは高品質な3Dゲームを作りやすく、
C++言語を使うことができます。開発に必要な機能は統一されているため、

classよりstructを使う

一般的なプログラミングの思想は基本メンバー変数・関数は非公開です。
一方ChaEngineでは基本的に、メンバー変数は限定公開、メンバー関数は公開、という緩い思想を持ちます。

そのため、ChaEngineのSystemObjectやObjectはclassではなくstruct(構造体)で宣言定義します。
構造体はクラスと違いデフォルトでメンバーが公開されます。
コンストラクタやデストラクタを明示的に公開する手間を省きます。

また、ChaEngineではObjectを以下のように書き、メンバー関数とメンバー変数を分離します。

Player.h

#pragma once
#include <Game/Game.h>

struct PlayerData  // こっちにメンバー変数
{
    Component<Transform> transform{};   // 座標系コンポーネント
    float moveSpeedPerSec{ 2.0f };      // 移動速度(/秒)
    PlayerControlState controlState{};  // 操作状態
    // ...
};

struct Player : ObjectBase<PlayerData>  // こっちにメンバー関数
{
    void Start() override;                           // 開始処理
    void Update() override;                          // 毎フレームの更新処理
    void SetControlState(const PlayerControlState);  // 操作状態を設定 (操作可能/操作不可能 など)
};

これにより、メンバー変数を直接公開せずに済みます。
Player構造体が継承しているObjectBaseクラスでは、内部で型引数指定したPlayerDataを継承しています。
よって以下のように定義でき、メンバー変数も通常通りアクセスできます。

Player.cpp

#include "Player.h"

void Player::Start()
{
    transform.position = Vec3{ 2, 1, 10 };  // 初期座標はここにしとく!
}

void Player::Update()
{
    // ...移動処理...
}

void Player::SetControlState(const PlayerControlState _controlState)
{
    controlState = _controlState;  // そのままセット!
}

外部オブジェクトからPlayerオブジェクトへのアクセスに関してですが、
メンバー関数は通常通りアクセスできます。
一方のメンバー変数はData関数を呼び出してアクセス必要があります。
Data関数を介することで、メンバー変数へアクセスしている外部のオブジェクトを特定可能にします。

SceneEventController.cpp

...
#include "Player.h"  // 参照する場合include忘れずに
...
void SceneEventController::OnEvent(const SceneEvent _event)        // シーンイベント受信時の処理
{
    switch (_event)
    {
        case SceneEvent_StartedTutorial:                           // チュートリアルが始まったイベントを受信した
            Player& player = Get<Player>(objPlayer);               // God Getでプレイヤーの実体を参照する
            
            player.SetControlState(PlayerControlState_NoControl);  // メンバー関数は普通にアクセス!
            
            PlayerData& playerData = player.Data();                // データ(メンバー変数)へのアクセス
            playerData.transform.position = Vec3::Zero();          // プレイヤーの座標は原点に戻す!
            // 冗長であればこう書いても良い ↓
            // player.Data().transform.position = Vec3::Zero();
            break;
        default:
            //...
            break;
    }
}

ゲームオブジェクトの位置付け

TODO: ゲームオブジェクトはシーンに所属します。ゲームオブジェクトはコンポーネントを持ちます。