200行で動くオセロから学ぶ ～ Unity2Dでのゲーム制作入門

#200行で動くオセロから学ぶ ～ Unity2Dでのゲーム制作入門
###対象
1. Unityで2Dゲームを作りたいと考えている入門者
2. 他のプログラミング言語を少しでも触った事のあるUnity未経験者

###ゲーム概要
オセロ  (オフライン、AI対戦もない)
###参照
#####Unityパッケージ
https://github.com/Dary1/UnityOthello/raw/master/UnityOthello.unitypackage
#####リポジトリ
https://github.com/Dary1/UnityOthello
#####プレイ動画
https://youtu.be/Vg9vMW991xg
###処理手順
1. 初期化 - OthelloBoard.cs内のvoid Start()がエントリー
    2. ボードのサイズを画面に合わせる
    3. 8x8のセルをボードに配置する
    4. ゲームの初期化 - 中央にセルを2つづつ配置する
2. プレイヤーターン処理 - OthelloCell.cs内のvoid CellPressedがエントリー
    3. セルを押下を拾う 
    4. 押下された位置が反転可能かの判定
    5. オセロを配置した後の処理
    6. ターン終了処理
        7. 次のプレイヤーが置く所があれば、ターンを交代する
        8. 次のプレイヤーが置く所がなければ、ターンを継続する
        9. 8.も置く所がなければ、ゲーム終了
3. ゲーム終了処理 OthelloBoard.cs内のGameOver()から
    4. 盤上のオセロの数をカウント
    5. 勝者の判定
    6. 1.3.に戻る

ソースコードを読んでも分かりにくい2.2～2.4だけコメントを追加した物で説明します

####『2.2 押下された位置が反転可能かの判定』の説明 

```c#:OthelloBoard.cs
    public List<Vector2> DirectionList; // Vector2(-1,1)は左上, Vector2(0,1)は上、Vector(1,0)は右上… Scene内で定義している
    OthelloCell[,] OthelloCells; // 8x8のOthelloCell.csの配列参照 (Start()で生成している)
    //中略

    //押下された位置が反転可能かの判定
    internal bool CanPlaceHere(Vector2 location)
    {
        //押下された位置は、そもそも空いているか
        if (OthelloCells[(int)location.x, (int)location.y].OwnerID != -1)
            return false;
        //全方向に対して、挟めるか方角があるかを判定
        for (int direction = 0; direction < DirectionList.Count; direction++)
        {
            Vector2 directionVector = DirectionList[direction];
            //指定された方角に対して、挟む自分のコマが存在しているか？
            if (FindAllyChipOnOtherSide(directionVector, location, false) != null)
            {
                //一つの方向でも見つかればそれで終わり
                return true;
            }
        }
        return false;
    }
    //指定された方角に対して、挟む事ができるかを判定する再帰メソッド
    private OthelloCell FindAllyChipOnOtherSide(Vector2 directionVector, Vector2 fromV, bool EnemyFound)
    {
        Vector2 to = fromV + directionVector;
        //ボードの外に出ていないか、空マスでないか
        if (IsInRangeOfBoard(to) && OthelloCells[(int)to.x, (int)to.y].OwnerID != -1)
        {
            //見つかったマスのオセロは自分のオセロか
            if (OthelloCells[(int)to.x, (int)to.y].OwnerID == OthelloBoard.Instance.CurrentTurn)
            {
               //既に間に一回敵オセロを見つけているか(つまり挟んだか)
                if (EnemyFound)
                    return OthelloCells[(int)to.x, (int)to.y];
                return null;
            }
            else
               //見つかったのは敵オセロなので、EnemyFoundを真にし、自分のオセロを見つけるまで再帰的に同メソッドを呼ぶ
                return FindAllyChipOnOtherSide(directionVector, to, true);
        }
        //ここまでにreturnされない場合nullを返す
        return null;
    }
```

####『2.3 オセロを配置した後の処理』の説明 
```c#:OthelloBoard.cs
    internal void PlaceHere(OthelloCell othelloCell)
    {
        //全方位に対して挟む事が可能かを検地 (パフォーマンスを考慮する場合、CanPlaceHereの途中経過を渡すべきだが割愛)
        for (int direction = 0; direction < DirectionList.Count; direction++)
        {
            Vector2 directionVector = DirectionList[direction];
            //2.2のメソッドを再利用
            OthelloCell onOtherSide = FindAllyChipOnOtherSide(directionVector, othelloCell.Location, false);
            if (onOtherSide != null)
            {
                //挟んだ敵のセルをひっくり返す
                ChangeOwnerBetween(othelloCell, onOtherSide, directionVector);
            }
        }
        OthelloCells[(int)othelloCell.Location.x, (int)othelloCell.Location.y].OwnerID = CurrentTurn;
    }
    //fromからtoの間で、挟んだ敵のセルをひっくり返す
    private void ChangeOwnerBetween(OthelloCell from, OthelloCell to, Vector2 directionVector)
    {
        for (Vector2 location = from.Location + directionVector; location != to.Location; location += directionVector)
        {
            OthelloCells[(int)location.x, (int)location.y].OwnerID = CurrentTurn;
        }
    }
```
####『2.4ターン終了処理』の説明

```c#:OthelloBoard.cs
    //敵のIDは、今のプレイヤーが0なら1、1なら0を返す
    public int EnemyID { get { return (CurrentTurn+1) % 2; } } 


    internal void EndTurn(bool isAlreadyEnded)
    {                
        //今のターンのプレイヤーを入れ替える
        CurrentTurn = EnemyID;
        //入れ替えた上で、配置できる場所があるかをボード全体から探す
        for (int y = 0; y < BoardSize; y++)
        {
            for (int x = 0; x < BoardSize; x++)
            {                
                if (CanPlaceHere(new Vector2(x, y)))
                {
                    //見つかったので無事次のプレイヤーの順番
                    return;
                }
            }
        }
        //見つかっていない
        if (!isAlreadyEnded)
            //もう一度同処理を再帰的に呼ぶ ただしisAlreadyEndedフラグを立てて呼ぶ
            EndTurn(true);
        else {
            //両プレイヤー共に置く所がない
            GameOver();
        }            
    }
```
###終わりに
気が向いたら、黒プレイヤーをAIにしてみたり、置けるセルを色付き表示してみたり、ここから出来る事は色々あると思います！

例えば、オセロと格闘ゲームをミックスさせちゃった物とか…
https://twitter.com/_Daryoon/status/1053793906708570112

思いつくままに、楽しい事と感じる物を作るのが一番長続きすると思います！