今回は、第4回のリーチの認識に続いて、役またはリーチのカードに輪郭(アウトライン)を付ける処理を追加します。
輪郭(アウトライン)を追加する
複雑な形状に輪郭を追加する場合、shaderを使った方法などがあるようですが、今回輪郭を追加するのは単純な形状(角丸の四角)なので、Image を重ね合わせて実現します。
RectTransformオブジェクトを重ねる時は、いくつか制限がありそうでした。
●RectTransformの場合、表示順は z軸 ではなくHierarchyの階層で行う。
●子は親より手前に表示される。
●同じ階層(たとえば親同士)の場合、Hierarchyに沿う。
(以下を参考にしました)
blog.cfm-art.net
子が親より手前に表示されますので、輪郭を親、カードイメージを子にします。(2枚の Image は移動させる場合に座標を合わせたいので、親子にしています)
これによって、カードイメージの階層は1段階、深くなります。
// gameObject.transform.GetChild(no).GetChild(0).GetComponent<Image>(); gameObject.transform.GetChild(no).GetChild(0).GetChild(0).GetComponent<Image>();
カードイメージの Anchor はcenter/centerにします。あとは輪郭のサイズ(Width/Height)を変更すれば、輪郭線の太さを調節できます。
輪郭に任意の色を付ける処理は以下の通りです。
void 輪郭表示(int no, Color color) { gameObject.GetComponent<Image>().color = color; }
優先順位
輪郭表示で表現する場合、前回のシグナルのように、あまり複数の状態を表示するのには向いていません。どちらかというと、今一番どの役に近いのかを表示してくれた方が便利です。
基本的に、ツーペア、スリーカード以上の役は、その役を崩して次の役を狙いに行くことは少ないと思います。また、ペアは常にリーチ状態のため、輪郭でリーチを表現しないものとします。
ワンペアだけは役が揃う確率が高い(42%)だけに、より高い役を狙いに行ける時には、ワンペアを崩して狙いに行く選択肢もあると思います。そこで、ワンペアの場合、ストレートまたはフラッシュのリーチとワンペアの両方を表示するようにします。
そこで、以下の順に表示することにします。
(1) ワンペア以外の役は、出来上がった役を優先(役を成しているカードだけ色を付ける)
(2) ワンペアの場合、
あと1枚でフラッシュ、またはストレートの場合、ワンペア+リーチ表示
それ以外はワンペア
(3) ハイカードの場合、
フラッシュまたはストレートのリーチの場合、リーチしているカードだけ色を付ける
判別式は以下のようになります。
void 輪郭表示() { 役の名前 優先輪郭; switch ( 役 ) { case ワンペア: case ハイカード: { if( リーチ_フラッシュ ){ 輪郭優先 = フラッシュリーチ; } else if( リーチ_ストレート ){ 輪郭優先 = ストレートリーチ; } else { 輪郭優先 = リーチ; } } default: 輪郭優先 == 役; break; } }
リーチしているカードだけ色を付ける
フラッシュのリーチ
フラッシュのリーチは、最も高い頻度で出ているマークの数が3以上でリーチが確定するのでした。
カードの各マークがこの最頻値に一致した時に、カードの色を付けることにします。
void フラッシュ条件() { int i; for( i = 0; i < 手持ちカード枚数; i++ ){ if( カード[i]/13 == 最頻マーク ){ 輪郭表示( i, 赤 ); } else { 輪郭表示( i, 透明 ); } } }
ストレートのリーチ
ストレートのリーチは、{A, 2, 3, 4, 5} から {10, J, Q, K, A} までの10パターンについて、3つ以上一致したらリーチが確定するのでした。
その中で、最も強い役の組み合わせを待っているものとします。
下の表では、下の行ほど強いパターンになります。この場合、{5, 6, 7, 8, 9} が最も強いパターンになります。
void ストレート条件(){ int i, j; for( i = 0; i < 手持ちカード枚数; i++ ){ ストレートのリーチカード = false; // 最大ストレート条件のテーブルを作成して確認(総当たり) for( j = 0; j < 手持ちカード枚数; j++ ){ if( カード[i]%13 == (最強ストレートパターンの最小値 + j)%13 ){ ストレートのリーチカード = true; } } if( ストレートのリーチカード ){ 輪郭表示( i, 橙 ); } else { 輪郭表示( i, 透明 ); } } }
ペアのリーチ
ペアについては、いつもリーチがかかった状態になるので、アウトラインでは確定した役だけを表示するようにします。
なお、ペアについても、どのカードで役が成立しているかを識別できるようにするために、認識方法に少し修正を入れます。
bool CheckPair() { 最大カウント = ペア数 = 0; for( int i = 0; i < 手持ちカード枚数; i++ ){ if( ナンバー出現数[i] >= 2 ){ ペア数++; if( 最大カウント < ナンバー出現数[i] ){ 最大カウント = ナンバー出現数[i]; } } } if( (最大カウント == 0) && (ペア数 == 0) ){ return false; } else { return true; } }
ナンバー出現数(int[13])は、前回(第4回)の「ストレートのリーチ」で実装したものです。
このナンバー出現数を使用すると、ペア条件での輪郭表示処理は以下になります。
bool ペア条件( int i, Color 指定色 ){ bool ret = false; if( ナンバー出現数[ cards[i]%13 ] >= 2 ){ 輪郭表示( i, 指定色 ); ret = true; } else { 輪郭表示( i, 透明 ); } return ret; } void ペア条件( Color 指定色 ){ for( i = 0; i < 手持ちカード枚数; i++ ){ ペア条件( i, 指定色 ); } }
ペア条件だけ、便宜的にforループ文と各カードの判定条件を分けておきます。
さて、
これまでは確定済みの役だったのですが、ここからは、確定済みの役とリーチとの合わせ技になります。
ワンペア+フラッシュ or ストレートのリーチ
ワンペアのリーチの場合、フラッシュまたはストレートの走査中に、ペアの判定条件を追加します。(しれっと書きましたが、この辺りはトライ&エラーでした)
ワンペア+フラッシュのリーチ
ワンペア+フラッシュのリーチの場合、先ほどのフラッシュの走査条件を、以下のように書き換えます。
void フラッシュ条件() { int i; for( i = 0; i < 手持ちカード枚数; i++ ){ if( !ペア条件( i, 紫 ) ){ // 追加 if( カード[i]/13 == 最頻マーク ){ 輪郭表示( i, 赤 ); } else { 輪郭表示( i, 透明 ); } } } }
ワンペア+ストレートのリーチ
ストレートも同様の修正で処理できます。
void ストレート条件(){ int i, j; for( i = 0; i < 手持ちカード枚数; i++ ){ // ストレートのリーチカード = false; if( !ペア条件( i, 紫 ) ){ // 最大ストレート条件のテーブルを作成して確認(総当たり) for( j = 0; j < 手持ちカード枚数; j++ ){ if( カード[i]%13 == (最強ストレートパターンの最小値 + j)%13 ){ // ストレートのリーチカード = true; 輪郭表示( i, 橙 ); } } } // if( ストレートのリーチカード ){ // 輪郭表示( i, 橙 ); // } else { // 輪郭表示( i, 透明 ); // } } }
いくつかコメントアウトしてあるのは、ペア条件の判定を追加したことで、透明輪郭表示を省略できるためです。
ハイカード+フラッシュ or ストレートのリーチ
ハイカードとワンペアでの違いは、リーチを認識する枚数です。これは、冒頭の輪郭を決める処理で吸収します。
void 輪郭表示() { int しきい値; 優先輪郭 = 役; switch( 役 ){ case ワンペア: case ハイカード: { if( リーチ_フラッシュ ){ しきい値 = フラッシュリーチしきい値; if( 役 == ワンペア ){ しきい値++; } if( 最頻マーク >= しきい値 ){ 優先輪郭 = フラッシュ; } } else if( リーチ_ストレート ){ しきい値 = ストレートリーチしきい値; if( 役 == ワンペア ){ しきい値++; } if( ストレート該当数 >= しきい値 ){ 優先輪郭 = ストレート; } } break; } default: break; } }
コード
前回からの修正部分のみ抜粋。
public class CardSuit : MonoBehaviour { /* 略 */ // 輪郭線の色 Color[] outlineColor = new Color[] { new Color(0.8f, 0.8f, 0.3f, 1.0f), // STRAIGHT_FLUSH, // 茶色 Color.red, // FLUSH, new Color(1.0f, 0.5f, 0.0f, 1.0f), // STRAIGHT, Color.green, // FOUR_OF_A_KIND, Color.green, // THREE_OF_A_KIND, Color.blue, // TWO_PAIR, new Color(0.8f, 0.0f, 0.8f, 1.0f), // PAIR, // 紫 Color.blue, // FULL_HOUSE, new Color(0.0f, 0.0f, 0.0f, 0.0f), // HIGH_CARD, // 透明 new Color(0.0f, 0.0f, 0.0f, 0.0f), // BUG, // 透明 // HAND_MAX }; void SetOutlineColor(int no, Color color) { if( ReferenceEquals( color, null ) ){ return; } gameObject.transform.GetChild(no).GetChild(0).GetComponent<Image>().color = color; } void SetOutlineColor(Color color) { if( ReferenceEquals( color, null ) ){ return; } for( int i = 0; i < MAX_CARDS; i++ ){ SetOutlineColor(i, color); } } void CheckFlushOutline( HAND_NAME specHand ) { int i; for( i = 0; i < MAX_CARDS; i++ ){ if( !CheckPairOutline( i, outlineColor[(int)HAND_NAME.PAIR] ) ){ if( cards[i]/13 == maxMarkId ){ SetOutlineColor( i, outlineColor[(int)specHand] ); } } } } void CheckStraightOutline(){ int i, j; for( i = 0; i < MAX_CARDS; i++ ){ // 最大ストレート条件のテーブルを作成して確認(総当たり) for( j = 0; j < MAX_CARDS; j++ ){ if( !CheckPairOutline( i, outlineColor[(int)HAND_NAME.PAIR] ) ){ if( cards[i]%13 == (maxStraightPtnCountIdx + j)%13 ){ SetOutlineColor( i, outlineColor[(int)HAND_NAME.STRAIGHT] ); break; } } } } } bool CheckPairOutline( int i, Color specColor ){ bool ret = false; if( numbersCount[ cards[i]%13 ] >= 2 ){ SetOutlineColor( i, specColor ); ret = true; } else { SetOutlineColor( i, outlineColor[(int)HAND_NAME.HIGH_CARD] ); } return ret; } void CheckPairOutline( Color specColor ){ for( int i = 0; i < MAX_CARDS; i++ ){ CheckPairOutline( i, specColor ); } } HAND_NAME recommend; // 揃っている役を表示する public void RecommendHand(HAND_NAME hand) { // 輪郭表示の決定 switch( hand ){ case HAND_NAME.PAIR: case HAND_NAME.HIGH_CARD: { if( r_flush ){ recommend = HAND_NAME.FLUSH; } else if( r_straight ){ recommend = HAND_NAME.STRAIGHT; } else { recommend = hand; } break; } default: recommend = hand; break; } // 表示方式を指定 switch( recommend ){ case HAND_NAME.STRAIGHT_FLUSH: case HAND_NAME.FLUSH: CheckFlushOutline( recommend ); break; case HAND_NAME.STRAIGHT: CheckStraightOutline(); break; case HAND_NAME.FOUR_OF_A_KIND: case HAND_NAME.THREE_OF_A_KIND: case HAND_NAME.FULL_HOUSE: case HAND_NAME.TWO_PAIR: case HAND_NAME.PAIR: CheckPairOutline( outlineColor[(int)recommend] ); break; default: SetOutlineColor( outlineColor[(int)HAND_NAME.HIGH_CARD] ); break; } } // トリガ public void ChangeCards( bool force ) { /* 略 */ : // リーチ表示 CheckWaitingHands( hand ); // ここまで前回 // カードのアウトラインに表示 // ここから追加 RecommendHand( hand ); // debug if( !ReferenceEquals(handText, null) ){ if( (hand == HAND_NAME.PAIR) || (hand == HAND_NAME.HIGH_CARD) ){ if( hand != recommend ){ handText.text += "\n(reaching " + hand_name[(int)recommend] + ")"; } } } /* 以下略 */ } }
単独のクラスにできそうですが、既存のコード(CardSuit.cs)と共有している領域も多いため、今回は切り離し作業はしませんでした。
プレイ画面
(前回までと同じURLです。最新の状態に更新されています)
tomo-mana.hatenablog.com
次回
●どのカードがリーチしているのか(表示)
●どのカードが来たら揃うのか(処理、表示)
本記事に使用しているトランプの絵柄は、いらすとやさんの素材を使用させていただいています。ありがとうございます。
トランプのイラスト(54枚まとめ) | かわいいフリー素材集 いらすとや