見出し画像

【C#+Godot4.13】RenderingServer2Dで大量のオブジェクトを生成しよう。

どうも、Glowth/輝きです。
今回はRenderingServer2Dを使ってできる限り軽くするためのコーディングを行っていきます。
これに4分木空間分割法を用いた当たり判定検知をすると弾幕STGなどで4000発をこえるオブジェクトを配置できますよ!


「RenderingServer」ってなんやねん?

この名前だけ聞いて、
「サーバーで描画を管理?どう言うこと?」
となる方が多くいらっしゃると思います。
(私もその一人でした。)
簡単に言うとシーン上で触れる高レベルの処理をすっ飛ばして、低レベルな層をさわれると考えて良いです。
以下のような感じでしょうか。(間違えていたらすみません。)


あくまで今回はRender2Dだけ。Sound TextServerを使うとそれだけ軽くなります。

そう考えるとかなり軽くなりそうですよね!
というわけで触っていきましょう〜!

CanvasItemを作る!

ひとまずスプライトを描画しましょう!
そのための第1歩がCreateCanvasItemです。
これで描画する物体を作るための枠組みを用意します!

Rid rs = RenderingServer.CanvasItemCreate();

これで枠組みを用意しましたが、もちろんこれだけでは画面に何も出てきません。
そもそもなんじゃこりゃと思うところがあると思うので細かく砕いて説明しましょう。

「RID」って何?

まぶたのパーツに名前つけようとなると、この単語をつけますよねw
…というのはさておき。

これは「ResourceID」。
C++とかで言うところのポインタのようなものみたいです。
この中にはスプライトや音声などのリソースを指すIDが格納されているようです。
IDがいわゆるアドレスに似てるのでホントにポインタに似てますよね〜

というわけでRid型の'rs'には何も入っていない空の情報のアドレスが詰まっているわけです。
ここに情報を突っ込んでいきましょう!

いろいろな情報を付加する!

 ひとまず実行するうえで必要な情報を突っ込んでいきましょうか。
何をしているかはコメントのまんまなので、深く考えなくて良いですよ。

Rid rs = RenderingServer.CanvasItemCreate();

// 親をセット
// GetCanvasItem()は実行した対象のRidを取得するものです
// この場合はこのC#スクリプトをアタッチしたNodeのRidが返却されるはず。
RenderingServer.CanvasItemSetParent(rs,GetCanvasItem());

//描画位置
RenderingServer.CanvasItemSetZIndex(rs, 30);
//↑が同じ値の場合の描画優先位置
RenderingServer.CanvasItemSetDrawIndex(rs, 50);

//位置情報
Transform2D trans = new(
    /* ラジアン */,
    /* スケール */,
    /* 傾き */,
    /* 位置 */
);
RenderingServer.CanvasItemSetTransform(rs, trans);

// 描画テクスチャ、大きさ指定
// Texture2D texture 
// ここに書かなくても良いです。
// クラス変数にしてを[Export]をつけて使ったほうが楽です。
Rect2 rect = new Rect2(-64, -64, 128, 128),
RenderingServer.CanvasItemAddTextureRect(rs, rect, texture.GetRid());

// もう描画させないとき(不要なとき)
RenderingServer.FreeRid(rs);

はい。
何かとたくさん書きましたが、最低限これがあると描画、描画後の破棄ができるはずです。
インスペクタ上で設定するものたちをプログラム側で指定している形ですね。

あとは
1ループ目
 ・描画情報を代入、
 ・CanvasItemを生成したRidを保持。
 ⇓
Nループ目
 ・前のループで作成したRidを破棄
 ・新たに描画情報を代入、CanvasItemを生成したRidを保持

というループさえ作れば動くものができますよ〜!

結局どう使えばいいの?

方法としては
個々のオブジェクト側に位置、スケール、zindexの値を持たせ、
オブジェクトを管理するマネージャークラスで個々のオブジェクトの管理をする形で実装するといいと思います。

オブジェクト側

using Godot;

public class Object {
	private Vector2 position = Vector2.Zero; // 位置
	private Vector2 scale = 1.0f; // 拡大率
	private float radian = 0; // ラジアン
	private Rid canvasItem = null; //キャンバスアイテム持ち
	private int zIndex = 20; 
	private int drawIndex = 10;

	public Vector2 getPosition() {
		return position;
	}

	public void setPosition(Vector2 val) {
		position = val
	}
	// ... あとはほかのGetter、Setterを用意しましょう
}

マネージャー側

using Godot;
using System.Collections.Generic;

public partial class Manager : Node2D {
	// このListに対してどうオブジェクトを格納するかは任せます。
	private readonly List<Object> objects = new();
	[Export] private Texture2D texture;

	public override void _Process(double delta){
		// objectたちをぶん回す
		for ( int i = 0; i < objects.Count; i++) {
			var obj = objects[i];

			// ここらへんでオブジェクトのポジションを動かす工夫などするといい感じに動きます

			// 次のループからはここが通って毎フレーム解放します。
			if (obj.getCanvasItem() != null) {
				RenderingServer.FreeRid(obj.getCanvasItem());
			}
			Rid rs = RenderingServer.CanvasItemCreate();
			RenderingServer.CanvasItemSetParent(rs,GetCanvasItem());
			RenderingServer.CanvasItemSetZIndex(rs, obj.getZIndex());
			RenderingServer.CanvasItemSetDrawIndex(rs, obj.getDrawIndex());

			//位置情報
			Transform2D trans = new(
			    obj.getRadian(),
			    obj.getScale(),
			    0,
	   			obj.getPosition()
			);
			RenderingServer.CanvasItemSetTransform(rs, trans);

			Rect2 rect = new Rect2(-64, -64, 128, 128),
			RenderingServer.CanvasItemAddTextureRect(rs, rect, texture.GetRid());
			obj.setCanvasItem(rs);
		}
	}

	// このマネージャークラスがアタッチされているノードが消えるときは作ったものを全部消しましょう
	public override void _ExitTree() {
		for ( int i = 0; i < objects.Count; i++) {
			RenderingServer.FreeRid(objects[i].GetTexture());
		}
	}
}

という形で実装ができるはず・・・。
ヘンな所があったらすみません・・・!

当たり判定(参考文献)

4分木探索法を用いた当たり判定検知は取り扱いません。
先人の方々が残していらっしゃる記事を元に作成するとできるはずです。
私は以下の記事の考え方を参考に作成しました!
この判定の考え方、理にかないすぎててとんでもねぇなと思いながら作ってました。

といった形で書いてみましたが、いかがでしたか?
何気にプログラムを解説する記事を書いたのは初めてだったので新鮮でしたw
また機会があれば書きますね

この記事が気に入ったらサポートをしてみませんか?