💻

人はIntel(R) Celeron(R) CPU 3865U @ 1.80GHzで3Dゲームを開発できるのか? Part07

に公開

はじめに

この記事は前回の続きです。とはいってもここから読んでも問題ないと思われます。

人はIntel(R) Celeron(R) CPU 3865U @ 1.80GHzで3Dゲームを開発できるのか? -Zenn版-

以下本文

初めましての人は初めまして、普段ゲーム制作やら3D数学の勉強やらやっているえうなひゃともうします。

現在Youtubeの企画で以下のスペックのPCを使ってGodot Engineを使ったゲーム開発を行なっております。

  • CPU:Intel(R) Celeron(R) CPU 3865U @ 1.80GHz
  • MEMORY:4GB
  • GRAPHICS:Intel HD Graphics 610

ちなみにOSはWindowsではなくLinuxのディストリビューションの一つDebianでデスクトップ環境はXfceにしております。

なぜこのようにしたのかの経緯は上記の記事にございます。

私のYoutubeチャンネル

私のX

ここに書くこと

今回は以下のアーカイブで行ったことと配信裏でやったことをブログにしてまとたものです。

人はIntel(R) Celeron(R) CPU 3865U @ 1.80GHzで3Dゲームを開発できるのか? その9

今回実装したのはジャンプ、または攻撃で壊れる箱と、壊した箱の数を数えるUIの実装になります。

実装自体はそんなに難しくありません。

箱ノードについて解説

シーンの構成から

元ネタのゲームのと見た目が近い物を選択して新規に継承シーンを作成しました。

箱のシーン構成

StaticBody3Dはキャラクターボディよりリソースを食いません、元々床や壁などを運用するために使われます。今回は素直に採用を決めました。

実際にアタッチしたスクリプトは以下の通りです。

./3DModel/Ultimate Platformer Pack - Dec 2021/Cubes/tscn/cube_crate.gd
extends Node3D

class_name Box
## ノードの名前違う可能性があるため、get_childで取得
@onready var static_body : StaticBody3D = get_child(0).get_child(0)
@onready var area_3d: Area3D = $Area3D

signal breaked(_my_position:Vector3)

## 破壊された時のエフェクトを再生するためどうするか?
## 答えは簡単、親から取るのでシグナルを使う。

func _on_area_3d_body_entered(body: Node3D) -> void:
	if Input.is_action_pressed("ui_accept"):
		body.bounce(1.2)
	else:
		body.bounce()
	i_was_break()


func _on_area_3d_area_entered(area: Area3D) -> void:
	print(area.name)
	i_was_break()

func i_was_break() -> void:
    # 箱そのものと箱を踏んだ時に発動するAreaを無効化
	static_body.get_child(0).disabled = true
	area_3d.get_node("CollisionShape3D").disabled = true
	breaked.emit(global_position)
	hide()

class_nameをBoxにすることで派生を作りやすくして新しいシグナルとしてbreakedを生成、それの引数に現在の位置を入れているのがわかります。何故そうするのか?のちの破壊エフェクトのためです。

また、壊れた時の処理についてですがリソースの負担を嫌って当たり判定を無効化し、箱そのものが消える処理に落ち着きました。

これにより、worldのスクリプトも一部書き換えました。

extends Node3D

@onready var boxs: Node = $Boxs

func _ready():
    # 中略
	# 箱ノード関係で破壊されたときの動作を行う。
	for b : Box in boxs.get_children():
		b.breaked.connect(_on_box_breaked)
		box_counter += 1

# 箱が壊された時のシグナルを受け取った時に発火するメソッド
func _on_box_breaked(box_pos:Vector3) -> void:
	box_break_particle.global_position = box_pos
	box_break_particle.emitting = true
	stage_ui.set_box_label()

破壊のエフェクトそのものをworldシーンに1つだけ追加し、壊れた箱の位置まで移動してそれを再生する処理を入れます。こうすることで箱ノード1つに対するリソースを節約することに成功しております。

1つ問題があるとするならまだエフェクトがしょぼいのでもう少しいじる必要性があることぐらいでしょうか?

壊した箱を数えるUIの実装

次に取り掛かったのはUIです。

今回作成するゲームの元ネタは3つ並んでいる中で壊した箱の数を数える処理があり、そのまま実装いたしました。

どこのノードに配置したのかこちらに貼ります。

ステージのUI

シグナルの発火等を考慮して今回はステージそのものにUIを追加いたしました。

現在のシーン構成は以下になります。

ステージのUIシーン構成

とてもシンプルでわかりやすいですね。

ソースコードはこちらになります。

extends Control

class_name StageUI

@onready var box_count_label: Label = $HBoxContainer/BoxCountLabel
@onready var score_label: Label = $HBoxContainer/ScoreLabel
@onready var info_label: Label = $HBoxContainer/InfoLabel

@export var box_num : int = 0
@export var max_box_num : int = 0
@export var box_counter : String = ""

@export var score : int = 0
@export var better_score : int = 100000

signal better_score_entered
signal all_box_breaked

func set_box_label(counted:int = 0) -> void:
	if max_box_num == 0:
		max_box_num = counted
	box_count_label.text = "{count}/{max_box_num}".format({"count":box_num,"max_box_num":max_box_num})
	box_num += 1
	if box_num >= max_box_num:
        # 全ての箱を壊したら隠しアイテム等を出す。
		all_box_breaked.emit()

func set_score(score_num : int=0) -> void:
	score += score_num
	score_label.text = "SCORE:{score_num}".format({"score_num":score})
	if score > better_score:
        # 今回はスコア制を導入し、隠しエリアを開放するように設計してみる。
		better_score_entered.emit()

元ネタのゲームにはスコア制はありませんが今回はあえて導入してみました。というのも死んだら1からやり直さないと隠し要素が解放されないのは個人的に今のゲーム設計にそぐわないのでは?そう感じたからです。

worldの処理を見てみましょう。

extends Node3D

@onready var fruits: Node = $fruits
@onready var stage_ui: StageUI = $StageUI

var se_pitch_limit : int = 0

func _ready():
	var box_counter :int = 0
    # 中略
	stage_ui.set_score(0)
	stage_ui.set_box_label(box_counter)

func _on_enemy_area_entered(area: Area3D) -> void:
	if area.get_collision_layer_value(6) or area.get_collision_layer_value(2):
		attack_se.play()
        # とりあえずスピンで敵を倒して巻き込んだら高得点はどうだろうか?
		stage_ui.set_score(5000)

func _on_enemy_weak_area_entered(body : CharacterBody3D) -> void:
	if body is Player:
		jump_bounce_se.play()
        # 敵を倒したらスコアを足すように変更
		var score : int = 1000
		if !body.is_on_floor() and se_pitch_limit < 18:
			jump_bounce_se.pitch_scale += 0.1
			se_pitch_limit += 1
			stage_ui.set_score(score)
			score += score

func _on_box_breaked(box_pos:Vector3) -> void:
	box_break_particle.global_position = box_pos
	box_break_particle.emitting = true
    # 破壊した箱の数を1足している
	stage_ui.set_box_label()

発想自体はとても単純でソースコードはまだ見れる部類だとは思いますが一方でこれから見見通し悪くなってしまうのでは?という懸念を持ってしまうコードになってますね・・・

とはいえ、ゲームのキャプチャはこのようになったので続行いたします。

作成した上部のUI

次なるTODOは以下の通りです。

  • 穴から飛び上がって邪魔する敵
  • 敵のカラバリを作成 ← 敵のスクリプトや必要なノードをインポートするスクリプトを作成、ここは裏でいいかも
  • ステージの作成、一つだけ行ったらソースコードとプロジェクトをオープンソースで公開する。

最後に

普段は以下のコンテンツを配信しております。

  • この記事で述べたロースペPCでの開発配信
  • goghを使った視聴者参加型作業配信
  • Godot Engineの使い方をまとめた動画
  • Godot Engineのソースコードを閲覧する配信

よかったらチャンネル登録・高評価等をよろしくおねがいします。

以下は私のチャンネルです。

えうなひゃ

Discussion