目次
- 移動・フリップ可能カード
■移動・フリップ可能カード
カードは、プリフロップにて中央デッキから各プレイヤーに配布(移動)され、人間プレイヤーのカードのみオープンされる (これは人間が手札を認識するためで、他のAIプレイヤーはその情報を参照したりはしない ;^p)。 さらに、ショーダウンでは、降りていないプレイヤー全員の手札がオープンされる。
CardBF はそのためのシーンで、カードのランク(数字)・スート(マーク)が表示が可能で、上記の移動・オープン機能を持つ。
ノードツリーは下図のような構成になっている。ルートは Node2D で、表面と裏面用のノードを子ノードとして所有している。

A, 2, 3, … K のランクは Label で、アライアルボールドフォントの文字を表示している。
スートは Sprite で、すべてのスートを横に並べた画像(下図)をアニメーションで切り替える。

また、カード裏画像も保持し、カードをめくるアニメーションででは表・裏を切り替える。
CardBF はランク・スート情報を持ち、それらゲッター・セッター関数を持つ(下記コード参照)。
const NumTable = "234567890JQKA"
var sr = 0 # (suit << 4) | rank
var rank = 0
func get_sr(): # スート、ランク取得
return sr
func set_suit(st): # スート、ランク設定
$Front/Suit.set_frame(st)
func get_rank(): return rank
func set_rank(r): # ランク設定、表示更新
rank = r
if rank == RANK_10:
$Front/Label.text = "10"
else:
$Front/Label.text = NumTable[rank]
func set_sr(st, rank): # スート・ランク設定
sr = (st << N_RANK_BITS) | rank
set_suit(st)
set_rank(rank)
CardBF は移動機能も持つ。実装は下記の通りで、Chip とほぼ同じだ。
signal moving_finished
var moving = false
var waiting_time = 0.0 # ウェイト時間(単位:秒)
func wait_move_to(wait : float, dst : Vector2, dur : float):
waiting_time = wait
move_to(dst, dur)
func move_to(dst : Vector2, dur : float):
src_pos = get_position()
dst_pos = dst
move_dur = dur
move_elapsed = 0.0
moving = true
func _process(delta):
if waiting_time > 0.0:
waiting_time -= delta
return
if moving: # 移動処理中
move_elapsed += delta # 経過時間
move_elapsed = min(move_elapsed, move_dur) # 行き過ぎ防止
var r = move_elapsed / move_dur # 位置割合
set_position(src_pos * (1.0 - r) + dst_pos * r) # 位置更新
if move_elapsed == move_dur: # 移動終了の場合
moving = false
emit_signal("moving_finished") # 移動終了シグナル発行
....
Chip と異なるのは、移動開始までのウェイト時間を指定できる wait_move_to() 関数があることだ。 これはデッキから各プレイヤーにカードを配布するとき、全枚数を同時に移動させるのではなく、 ディーラの次から順番に配布しているように見せるためだ。
次に、カードめくり関連の実装を下記に示す。
signal opening_finished
enum { # state
STATE_NONE = 0,
OPENING_FH, # オープン中 前半
OPENING_SH, # オープン中 後半
CLOSING_FH, # オープン中 前半
CLOSING_SH, # オープン中 後半
}
var bFront = false # 表示面
var state : int = 0
var theta = 0.0
func do_open():
state = OPENING_FH
theta = 0.0
$Front.hide()
$Back.show()
$Back.set_scale(Vector2(1.0, 1.0))
func do_close():
state = CLOSING_FH
theta = 0.0
$Back.hide()
$Front.show()
$Front.set_scale(Vector2(1.0, 1.0))
func _process(delta):
.....
if state == OPENING_FH: # カードめくり前半
theta += delta * TH_SCALE # 角度更新
if theta < PI/2: # まだ裏面の場合
$Back.set_scale(Vector2(cos(theta), 1.0)) # 角度を考慮した裏面表示
else: # 半分を超えた場合
state = OPENING_SH # 状態遷移
$Front.show() # 表面表示
$Back.hide() # 裏面非表示
theta -= PI # 角度補正
$Front.set_scale(Vector2(cos(theta), 1.0)) # 角度を考慮した表面表示
elif state == OPENING_SH: # カードめくり後半
theta += delta * TH_SCALE
theta = min(theta, 0)
if theta < 0:
$Front.set_scale(Vector2(cos(theta), 1.0)) # 角度を考慮した表面表示
else:
state = STATE_NONE # カードめくり終了
$Front.set_scale(Vector2(1.0, 1.0))
emit_signal("opening_finished") # オープニング終了シグナル発行
elif state == CLOSING_FH:
.....
elif state == CLOSING_SH:
.....
オープン開始時は裏面を表示し、theta を 0.0 に設定する。経過時間に従い、横方向スケールを cos(theta) とする。 半分を経過したら、theta からπを引き、後半は表面を表示し、前半同様に横方向スケールを cos(theta) とする。
オープン処理が終了したら opening_finished シグナルを発行する。
下記に、次のラウンドに遷移する関数である next_round() で、初期状態からプリフロップに遷移する処理部分のコードを示す。
var deck_ix = 0 # デッキトップインデックス
var deck = [] # 要素:(suit << 4) | rank (※ rank:0~12 の数値、0 for 2,... 11 for King, 12 for Ace)
var CardBF = load("res://CardBF.tscn") # カード裏表面
func next_round(): # 次のラウンドに遷移
if state == INIT: # 初期状態の場合
state = PRE_FLOP # プリフロップに遷移
.....
players_card1.resize(N_PLAYERS)
for i in range(N_PLAYERS):
var di = (dealer_ix + 1 + i) % N_PLAYERS # カード配布先インデックス
var cd = CardBF.instance() # カード裏面
players_card1[di] = cd
# カードは deck 配列にあり、すでにシャフルされているものとする
cd.set_sr(card_to_suit(deck[deck_ix]), card_to_rank(deck[deck_ix]))
deck_ix += 1 # 次のカード
cd.set_position(deck_pos)
$Table.add_child(cd) # テーブルの子ノードとして追加
cd.connect("moving_finished", self, "on_moving_finished") # シグナルを処理関数に接続
cd.connect("opening_finished", self, "on_opening_finished") # シグナルを処理関数に接続
var dst = players[di].get_position() + Vector2(-CARD_WIDTH/2, -4) # 移動先
cd.wait_move_to(i * 0.1, dst, 0.3) # 時間差でカードを移動
プリフロップでは、中央のデッキから各プレイヤーに手札を配布する。
CardBF.instance() でカードノードをインスタンス化し、deck 配列を参照し、スート・ランクを設定する。 カードの初期位置は中央デッキ位置とし、wait_move_to() で各プレイヤー背景位置まで移動する。
移動が終わると moving_finished シグナルが発行されるので、それを処理するために、self.on_opening_finished() に接続する。
これで、移動が終わると on_moving_finished() がコールされるので、次に人間の手札についてのみ do_open() をコールして、 手札をめくって表にする。
TechProjin Godot入門 関連連載リンク
Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次
標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次