#200行で動くオセロから学ぶ ~ Unity2Dでのゲーム制作入門
###対象
- Unityで2Dゲームを作りたいと考えている入門者
- 他のプログラミング言語を少しでも触った事のあるUnity未経験者
###ゲーム概要
オセロ (オフライン、AI対戦もない)
###参照
#####Unityパッケージ
https://github.com/Dary1/UnityOthello/raw/master/UnityOthello.unitypackage
#####リポジトリ
https://github.com/Dary1/UnityOthello
#####プレイ動画
https://youtu.be/Vg9vMW991xg
###処理手順
- 初期化 - OthelloBoard.cs内のvoid Start()がエントリー
2. ボードのサイズを画面に合わせる
3. 8x8のセルをボードに配置する
4. ゲームの初期化 - 中央にセルを2つづつ配置する - プレイヤーターン処理 - OthelloCell.cs内のvoid CellPressedがエントリー
3. セルを押下を拾う
4. 押下された位置が反転可能かの判定
5. オセロを配置した後の処理
6. ターン終了処理
7. 次のプレイヤーが置く所があれば、ターンを交代する
8. 次のプレイヤーが置く所がなければ、ターンを継続する
9. 8.も置く所がなければ、ゲーム終了 - ゲーム終了処理 OthelloBoard.cs内のGameOver()から
4. 盤上のオセロの数をカウント
5. 勝者の判定
6. 1.3.に戻る
ソースコードを読んでも分かりにくい2.2~2.4だけコメントを追加した物で説明します
####『2.2 押下された位置が反転可能かの判定』の説明
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 オセロを配置した後の処理』の説明
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ターン終了処理』の説明
//敵の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
思いつくままに、楽しい事と感じる物を作るのが一番長続きすると思います!