Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@Unitier

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

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

対象

  1. Unityで2Dゲームを作りたいと考えている入門者
  2. 他のプログラミング言語を少しでも触った事のあるUnity未経験者

ゲーム概要

オセロ (オフライン、AI対戦もない)

参照

Unityパッケージ

リポジトリ

プレイ動画

処理手順

  1. 初期化 - OthelloBoard.cs内のvoid Start()がエントリー
    1. ボードのサイズを画面に合わせる
    2. 8x8のセルをボードに配置する
    3. ゲームの初期化 - 中央にセルを2つづつ配置する
  2. プレイヤーターン処理 - OthelloCell.cs内のvoid CellPressedがエントリー
    1. セルを押下を拾う
    2. 押下された位置が反転可能かの判定
    3. オセロを配置した後の処理
    4. ターン終了処理
      1. 次のプレイヤーが置く所があれば、ターンを交代する
      2. 次のプレイヤーが置く所がなければ、ターンを継続する
      3. 8.も置く所がなければ、ゲーム終了
  3. ゲーム終了処理 OthelloBoard.cs内のGameOver()から
    1. 盤上のオセロの数をカウント
    2. 勝者の判定
    3. 1.3.に戻る

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

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

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 オセロを配置した後の処理』の説明

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ターン終了処理』の説明

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

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

20
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
20
Help us understand the problem. What is going on with this article?