目次
- 移動・フリップ可能カード
■移動・フリップ可能カード
カードは、プリフロップにて中央デッキから各プレイヤーに配布(移動)され、人間プレイヤーのカードのみオープンされる (これは人間が手札を認識するためで、他のAIプレイヤーはその情報を参照したりはしない ;^p)。 さらに、ショーダウンでは、降りていないプレイヤー全員の手札がオープンされる。
CardBF はそのためのシーンで、カードのランク(数字)・スート(マーク)が表示が可能で、上記の移動・オープン機能を持つ。
ノードツリーは下図のような構成になっている。ルートは Node2D で、表面と裏面用のノードを子ノードとして所有している。
A, 2, 3, … K のランクは Label で、アライアルボールドフォントの文字を表示している。
スートは Sprite で、すべてのスートを横に並べた画像(下図)をアニメーションで切り替える。
また、カード裏画像も保持し、カードをめくるアニメーションででは表・裏を切り替える。
CardBF はランク・スート情報を持ち、それらゲッター・セッター関数を持つ(下記コード参照)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 とほぼ同じだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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() 関数があることだ。 これはデッキから各プレイヤーにカードを配布するとき、全枚数を同時に移動させるのではなく、 ディーラの次から順番に配布しているように見せるためだ。
次に、カードめくり関連の実装を下記に示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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() で、初期状態からプリフロップに遷移する処理部分のコードを示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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++ライブラリ 連載目次