
AIリフティング対戦のルール作り
前回は、AIに対戦させてみました。
今回は、対戦ルールを作ろうと思います。
今日のハイライト

対戦ルール
・3点先取した方が勝ち
・何回リフティングしても、壁に当たってもOK
・敵陣の床にボールが当たったら得点が入る
・しばらくしたら「Ready?」を表示し空中からボールが降ってくる
得点システム
まずは、敵陣の床にボールが当たったら得点が入るようにしてみます。
対戦用のScoreシーン(VS_Score)を作る
協力とはルールが変わるため、対戦専用のScoreシーンを作ります。
・新しいシーンをCanvasLayerで作り、名前を「VS_Score」に変更
・子ノードにラベルをつけ、名前を「ScoreLabel」に変更
・ScoreLabelのアンカーを中央上に設定
・ScoreLabelのインスペクターのTextに「0 - 0」と入力
・Label Settingの<空>を新規で選び、できたLabelSettingをクリック
・Label SettingのFontにSystemFontを選択し、Sizeを32に設定
・OutlineのSizeを3にしてColorを黒くする
・Horizontal AlignをCenterに設定

Stage_TossにVS_Scoreを追加
・Stage_Tossの子ノードのScoreを右クリックして削除を選択
・Stage_TossにVS_Scoreをドラッグ&ドロップして追加

AIキャラのスクリプトを修正
VS_Score用のパスに変更し、リフティング回数は必要なくなったのでコメントアウトします。
Pathを変更
・「AI_Toss_L」「AI_Toss_R」のスクリプト内の「Count」のパスを「Score」から「VS_Score」に変更
@onready var count = get_node("/root/Stage_Toss/VS_Score")
リフティング回数をコメントアウト
・AI_Toss_LとAI_Toss_Rの両方ともCTR+Kでコメントアウト
#func _on_area_2d_area_entered(area: Area2D) -> void:
#if area.is_in_group("balls"): # ball
#print("hit_balls")
#count.increase_count()
BallAreaのスクリプトで得点の判定
・VS_ScoreとBallのPathを定義
・Ballの位置からLかRのどちらの陣地に落ちたかを判定
・VS_Scoreに得点を送る
・床でバウンドしても1点しかカウントしない処理
eextends Area2D
@onready var count = get_node("/root/Stage_Toss/VS_Score") # Scoreシーンの場所を定義
@onready var ball = get_node("/root/Stage_Toss/Ball") # Ballシーンの場所を定義
var scored = false # 得点したかどうかのフラグ
func _on_area_entered(area): # エリア内への接触を感知する関数
if area.is_in_group("floor") and not scored: # 床に接触(得点していなければ得点)
scored = true # 得点したかどうかフラグをON
if ball.position.x > 320:
count.L_Score() # Rの陣地に落ちたらLの得点
print("Lの得点!")
else:
count.R_Score() # Lの陣地に落ちたらRの得点
print("Rの得点!")
func restart():
scored = false # 得点したかどうかのフラグを解除
VS_Scorのスクリプト
・LとRの得点を定義
・床にボールが落ちた落ちた時のLとRの得点計算を関数で定義
・スコア表示を更新
extends CanvasLayer
# スコアのラベルのパスを設定
@onready var score_label = $ScoreLabel
var SCORE_L = 0 # Lの得点
var SCORE_R = 0 # Rの得点
# Lの得点計算
func L_Score():
SCORE_L += 1
Update_Score()
# Rの得点計算
func R_Score():
SCORE_R += 1
Update_Score()
# Score表示を更新
func Update_Score():
score_label.text = str(SCORE_L) + " - " + str(SCORE_R)
得点システムを実行
F5で実行すると、得点が加算されました。

リスタート
得点が入った後にリスタートするシステムを作ります。
VS_ScoreにMessageLabelを追加
リスタート時などにメッセージを表示するためのLabelを2個追加します。
・1つのLabelをMessageLabelに名前を変更し画面中央に配置
・MessageLabelのtextにReady?を追加
・LabelSettingを新規で追加し、fontsizeを48、outlineのsizeを3で黒に変更
・もう1つのLabelをSubLabelに名前を変更し画面中央に配置(変更不要)

VS_Scoreにスクリプトを追加
初期設定
・関連するパスを設定
・フラグを設定
・勝利ポイントを設定
・ボールやメッセージを非表示
extends CanvasLayer
# 関連するパスの設置
@onready var score_label = $ScoreLabel # スコアのパス
@onready var message = $MessageLabel # メッセージのパス
@onready var sub_message = $SubLabel # サブメッセージのパス
@onready var ai_l = get_node("/root/Stage_Toss/AI_Toss_L") # Lのパス
@onready var ai_r = get_node("/root/Stage_Toss/AI_Toss_R") # Rのパス
@onready var ball = get_node("/root/Stage_Toss/Ball") # ボールのパス
@onready var ball_area = get_node("/root/Stage_Toss/Ball/BallArea") # ボールの当たり判定のパス
var SCORE_L = 0 # Lの得点
var SCORE_R = 0 # Rの得点
var reset_turn = false # ターンをリセットするフラグ
var game_start = true # ゲームスタートかどうかのフラグ
var win_score = 3 # 勝利ポイント
# 準備
func _ready() -> void:
game_start = true # スタートフラグをON
ball.hide() # ボールを非表示
message.hide() # メッセージを非表示
reset() # リセットへ
ターンのリセット処理
・ゲーム開始時のメッセージ処理
・ゲーム途中でのスコア表示
・勝利ポイントに達していたらゲームオーバー処理へ
・ゲームオーバーでなければ、スタートへ進む
func reset():
if not reset_turn: # リセットが繰り返さないようにフラグ管理
reset_turn = true # リセットされたのでフラグをON
message.show() # メッセージを表示
if game_start == true: # ゲーム開始時だったらタイトルを表示
message.text = "Kick Ball"
else:
score_label.hide() # スコアを非表示
message.text = str(SCORE_L) + " - " + str(SCORE_R) # 中央に大きくスコア表示
await get_tree().create_timer(2).timeout # 2秒待つ
if SCORE_L >= win_score or SCORE_R >= win_score: # 勝利ポイントが入っていたらゲームオーバーへ
game_over()
else:
game_start = false # スタートフラグをOFFしてスタートへ
start()
ターンのスタート処理
・スタートの合図「Ready?」を表示
・少し待ってからボールをランダム位置から落とす
func start():
message.show() # メッセージを表示
message.text = "Ready?"
ball.hide() # ボールを非表示
PhysicsServer2D.set_active(false) # 物理を切る
await get_tree().create_timer(1).timeout # 1秒待つ
message.hide() # メッセージを非表示
ball.show() # ボールを表示
PhysicsServer2D.set_active(true) # 物理を作動
var start_x = randi_range(100, 540) # ランダムなx座標からボールを落とす
if start_x > 240 and start_x < 400:
start_x = 240
var start_y = 0
ball.global_position = Vector2(start_x, start_y) # ボールを移動
ball.linear_velocity = Vector2.ZERO # ボールの移動速度をゼロに
ball.angular_velocity = 0 # 角速度もゼロにして自由落下でスタート
print("ball ", str(ball.position.x, ball.position.y)) # デバッグ用
ball_area.restart() # ボールの床判定を再開
score_label.show() # スコアを表示
reset_turn = false # ターンリセットのフラグをOFF
ゲームオーバー処理
・勝利メッセージを表示
・少し待って「Presss Escape」を表示
・Escapeキーが押されたら、ゲームをリスタート
func game_over():
if SCORE_L >= win_score:
message.show() # Lが勝った時の処理
message.text = "Blue won!"
else:
message.show() # Rが勝った時の処理
message.text = "Red won!"
await get_tree().create_timer(2).timeout #2秒待つ
sub_message.text = "Press Escape" # Escapeキーを押したらリスタート
ゲームの流れが完成
スパイクの打ち合いを制したBlueが先制点を取る

2-1と後がないRedは長いラリーを粘って同点に追いつく

最後はラリーを制してRedが逆転勝利!

Godotで一通りの流れが作れたかな。。。
AIに個性つけたり、プレイヤーと対戦してみたりもできますね。
誰かの参考になれば幸いです。