PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

Lumberyard C++入門

この記事はLumberyard Advent Calendar 2017の11日目の記事です。
Lumberyard Advent Calendar 2017 - Qiita

概要

今回作るもの
C++で新規のコンポーネントを作成
コンポーネントのビルド、登録まで行います。
コンポーネントの中身は空なので、ログを出したり
更新処理や当たり判定を取ったりはまだやっていません。


環境

Lumberyard 1.10.0.0
Visual Studio Community2015


Solutionファイルの生成

新規プロジェクトを作成すると下記にソリューションファイルも生成されます。
Lumberyard\(EngineVersion)\dev\Solutions\(ProjectName)

プロジェクトの作成方法についてはこちらを参照ください。
papersloth.hatenablog.com


さて、ソリューションエクスプローラーを見てみると
ソリューション(ProjectName)
の下に幾つかフォルダが並んでいて、その中に自分のProject名のフォルダもありますね。
今回はCPPProjectという名前で作成しました。
f:id:PaperSloth:20171208003814p:plain

ソースコードの追加

続いて、コードを追加していきましょう。

先ず、ソースコードは下記に保存されています。
Lumberyard\1.10.0.0\dev\(ProjectName)\Gem\Code\Source

今回はここにComponentsというフォルダを追加しました。
f:id:PaperSloth:20171209134725p:plain


ソースコードを追加する場所はProject名のフォルダの下のProjectファイルの中です。
f:id:PaperSloth:20171208004431p:plain

UnityやUE4だとエディターから追加すると思いますが、Lunberyardは通常のC++ソースコードの追加方法になります。
先ずはHeaderの追加
保存場所は先程追加したComponents以下を指定します。
f:id:PaperSloth:20171209135341p:plain


ドキュメントを見ながらそれっぽいコードを書いていきます。
コンポーネントの作成 - Lumberyard

ExampleComponent.h

#pragma once
#include <AzCore/Component/Component.h>

namespace LYGame
{
	class ExampleComponent : public AZ::Component
	{
	public:
		AZ_COMPONENT(ExampleComponent, "{9D793D70-7616-4936-A45F-F921C4B4985A}");

		static void Reflect(AZ::ReflectContext* context);

		void Init() override;
		void Activate() override;
		void Deactivate() override;

	private:
		AZStd::string exampleProperty;
	};
}

ちょっと特殊なのが、AZ_COMPONENTの第2引数ですね。
一意の UUIDを入れる必要があるようです。
Visual Studioのツールから作成して設定しておきます。


続いてHeader同様にcppファイルも書いていきます。
ExampleComponent.cpp

#include "StdAfx.h"
#include "ExampleComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>

namespace LYGame
{
	void ExampleComponent::Reflect(AZ::ReflectContext* context)
	{
		AZ::SerializeContext* const serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
		if (serializeContext == nullptr)
		{
			return;
		}
		serializeContext->Class<ExampleComponent>()->Version(1)->Field("ExampleProperty", &ExampleComponent::exampleProperty);

		AZ::EditContext* const editContext = serializeContext->GetEditContext();
		if (editContext == nullptr)
		{
			return;
		}


		editContext->Class<ExampleComponent>("ExampleComponent", "Example Component Description")
			->ClassElement(AZ::Edit::ClassElements::EditorData, "")
			->Attribute(AZ::Edit::Attributes::Category, "ExampleCategoryName")
			->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
			->DataElement(0, &ExampleComponent::exampleProperty, "ExampleProperty", "Example Property Description");
	}

	void ExampleComponent::Init()
	{
	}

	void ExampleComponent::Activate()
	{
	}

	void ExampleComponent::Deactivate()
	{
	}
}


Init, Activate, Deactivateは先程のドキュメントを見てもらえればわかりますが
Initだけ軽く説明をこちらにも書いておきます。

Entity(Unity:GameObject, UE4:Actor or UObject)に対して一度だけ呼び出されます。
ComponentはすぐにActiveにならないことがあるため、ここで最小限のセットアップを行います。
コンストラクタ(ctor)に近いイメージですね(Unity:Awake, UE4:ctor)

処理を長々とReflectに書きました。Reflectについてはこちら。
シリアル化と編集のためのコンポーネントのリフレクション - Lumberyard

この辺りは自分もお作法として書いてるところですので、詳しい解説はまだできそうにないです。
詳しく知りたい方はドキュメントを読んでいただければと思います。

ビルドシステムへの通知

続いて、Wafビルドシステムへの通知を行います。
Waf ビルドシステム - Lumberyard

Lumberyardのビルドシステムにコンポーネントの追加を知らせる処理を書きます。
(ProjectName).waf_filesを開き
f:id:PaperSloth:20171209222009p:plain

下記のように記述します。

{
    "none": {
        "Source": [
            "Source/StdAfx.cpp",
            "Source/StdAfx.h"
        ]
    },
    "auto": {
        "Include": [
            "Include/CPPProject/CPPProjectBus.h"
        ],
        "Source": [
            "Source/CPPProjectModule.cpp",
            "Source/CPPProjectSystemComponent.cpp",
            "Source/CPPProjectSystemComponent.h"
        ],
        "Source/Core": [
            "Source/Core/EditorGame.cpp",
            "Source/Core/EditorGame.h",
            "Source/Core/CPPProjectGame.cpp",
            "Source/Core/CPPProjectGame.h",
            "Source/Core/CPPProjectGameRules.cpp",
            "Source/Core/CPPProjectGameRules.h"
        ],
        // 追加部分
        "Source/Components": [
            "Source/Components/ExampleComponent.cpp",
            "Source/Components/ExampleComponent.h"
        ],
        // 追加終了
        "Source/Game": [
            "Source/Game/Actor.cpp",
            "Source/Game/Actor.h"
        ],
        "Source/System": [
            "Source/System/GameStartup.cpp",
            "Source/System/GameStartup.h"
        ]
    }
}

コンポーネントの登録

続いてコンポーネントの登録を行います。
登録するには先ず
Source\Core\(ProjectName)Game.cppを開きます。
f:id:PaperSloth:20171209223712p:plain

全部をまるっと載せるのかなりの行数になってしまうため、変更点だけ載せます。
includeの追加

#include <AzCore/Component/ComponentApplicationBus.h>
#include "Components/ExampleComponent.h"


CompleteInitへの追加

bool CPPProjectGame::CompleteInit()
{
    // EBUS_EVENTを追加
    EBUS_EVENT(AZ::ComponentApplicationBus, RegisterComponentDescriptor, LYGame::ExampleComponent::CreateDescriptor());
    return true;
}

動作確認

エンジンをビルドしてEditorを立ち上げます。

ソースコードからEditorを立ち上げる場合はSandbox/Editorを使用します。
f:id:PaperSloth:20171210022017p:plain

Sandbox/Editorをスタートアッププロジェクトに設定し
[All] Debugで実行してみましょう。
f:id:PaperSloth:20171210022203p:plain

エラーがなければ、エディターが立ち上がります。


Level, Entityの追加方法等はこちらを参考にしてみてください。
papersloth.hatenablog.com


無事にコードが反映されていればAdd Componentから"Example Component"が選択できます。
f:id:PaperSloth:20171210022328p:plain

f:id:PaperSloth:20171210022445p:plain

今回はここまで。以上となります。

バージョンによってはソースコードの場所やソリューションの構成まで少し変わっているところがあるようです。
ドキュメントやチュートリアルを見る時はバージョンが同じか、違っていても同じような機能かを確認しながら作成するとなんとかなりそうです。