목차
Tic Tac Toe 서버 만들기
25.03.10
멀티 플레이 구현
>> Chatting Panel Prefab화 후, Hierarchy에서 삭제
>> 'Main Panel'의 'Buttons'에 있는 'Score Button'을 'MultiplayButton'으로 수정
: Text도 '멀티 플레이'로 변경, OnClick()에 함수 바인딩
>> 기존 코드 개선
: 멀티 플레이를 적용하기에 더 용이하도록 개선
- MainPanelController.cs
- GameManager.cs
- AIController.cs --> 삭제
- Constants.cs
- BlockController.cs
- MinimaxAIController.cs
- BattlePanelController.cs --> 삭제
>> GameLogic.cs 생성
: GameManager에서 GameLogic을 분리, 상태 패턴 활용
클라우드 서버 연결
>> MongoDB
1. Create a cluster
2. Create a database user
: Username과 Password 생성
3. Connect
4. 나온 deployment 링크를 복사하여 app.js파일의 MongoDB 관련 ConnectDB()에 복붙
--> 아래 사진은 예시, <db_username>과 <db_password>에 <> 없이 각각 username과 password를 넣어야 한다.
>> Koyeb
1. 회원가입 후, Create Service --> Git 선택
2. Github로 가서 tictactoe-server git의 HTTPS주소를 복사, public GitHub repository에 붙여넣기
: public repository가 아니라면 Github와 연결하여 선택 가능
3. Free 버전 선택
: latency를 보고 국가 선택
4. service 선택
: Source에서 repository 선택
--> 마찬가지로 public repository라면 URL로, 아니라면 연결한 것 선택, 이후 Deploy 클릭
5. Build
: 이것까지 완료되면 게임 서버가 배포될 것
--> 자동으로 완료된다.
6. Public URL 설정
: client에 있는 서버 URL을 나온 Public URL로 바꿔야 한다.
--> Overview에 있는 Public URL 복사
--> Constants.cs에 있는 serverURL과 GameServerURL을 수정
- ServerURL 앞의 'http://'와 뒤의 ':3000' 는 그대로 유지하고 중간에 붙여넣기
- GameServerURL 앞의 'ws://'와 뒤의 ':3000' 는 그대로 유지하고 중간에 붙여넣기
※ Window로 테스트할 때 설정
: 설정을 바꾼 뒤 Build
최종 코드
>> GameManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : Singleton<GameManager>
{
[SerializeField] private GameObject settingsPanel;
[SerializeField] private GameObject confirmPanel;
[SerializeField] private GameObject signinPanel;
[SerializeField] private GameObject signupPanel;
private GameUIController _gameUIController;
private Canvas _canvas;
private Constants.GameType _gameType;
private GameLogic _gameLogic;
private void Start()
{
// 로그인
//OpenSigninPanel();
}
public void ChangeToGameScene(Constants.GameType gameType)
{
_gameType = gameType;
SceneManager.LoadScene("Game");
}
public void ChangeToMainScene()
{
_gameLogic?.Dispose();
_gameLogic = null;
SceneManager.LoadScene("Main");
}
public void OpenSettingsPanel()
{
if (_canvas != null)
{
var settingsPanelObject = Instantiate(settingsPanel, _canvas.transform);
settingsPanelObject.GetComponent<PanelController>().Show();
}
}
public void OpenConfirmPanel(string message, ConfirmPanelController.OnConfirmButtonClick onConfirmButtonClick)
{
if (_canvas != null)
{
var confirmPanelObject = Instantiate(confirmPanel, _canvas.transform);
confirmPanelObject.GetComponent<ConfirmPanelController>()
.Show(message, onConfirmButtonClick);
}
}
public void OpenSigninPanel()
{
if (_canvas != null)
{
var signinPanelObject = Instantiate(signinPanel, _canvas.transform);
}
}
public void OpenSignupPanel()
{
if (_canvas != null)
{
var signupPanelObject = Instantiate(signupPanel, _canvas.transform);
}
}
public void OpenGameOverPanel()
{
_gameUIController.SetGameUIMode(GameUIController.GameUIMode.GameOver);
}
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (scene.name == "Game")
{
// Scene에 배치된 Object 찾기 (BlockController, GameUIController)
var blockController = GameObject.FindObjectOfType<BlockController>();
_gameUIController = GameObject.FindObjectOfType<GameUIController>();
// BlockController 초기화
blockController.InitBlocks();
// Game UI 초기화
_gameUIController.SetGameUIMode(GameUIController.GameUIMode.Init);
// Game Logic 객체 생성 --> 생성자 호출
if (_gameLogic != null) _gameLogic.Dispose();
_gameLogic = new GameLogic(blockController, _gameType);
}
// Canvas는 Main과 Game 모두 필요하기 때문에 if 밖에서 찾음
_canvas = GameObject.FindObjectOfType<Canvas>();
}
private void OnApplicationQuit()
{
_gameLogic?.Dispose();
_gameLogic = null;
}
}
>> GameLogic.cs
using System;
using UnityEngine;
public abstract class BasePlayerState
{
public abstract void OnEnter(GameLogic gameLogic); // 해당 State가 할당될 때 처리
public abstract void OnExit(GameLogic gameLogic); // 해당 State가 교체될 때 처리
public abstract void HandleMove(GameLogic gameLogic, int row, int col); // 해당 State의 행위에 따라 마커 표시
protected abstract void HandleNextTurn(GameLogic gameLogic); // State 전환
// 마커 표시 및 결과 처리
protected void ProcessMove(GameLogic gameLogic, Constants.PlayerType playerType, int row, int col)
{
if (gameLogic.SetNewBoardValue(playerType, row, col))
{
var gameResult = gameLogic.CheckGameResult();
if (gameResult == GameLogic.GameResult.None)
{
HandleNextTurn(gameLogic);
}
else
{
gameLogic.EndGame(gameResult);
}
}
}
}
// 직접 플레이 (싱글, 네트워크)
public class PlayerState : BasePlayerState
{
private Constants.PlayerType _playerType;
private bool _isFirstPlayer;
private MultiplayManager _multiplayManager;
private string _roomId;
private bool _isMultiplay;
public PlayerState(bool isFirstPlayer)
{
_isFirstPlayer = isFirstPlayer;
_playerType = _isFirstPlayer ? Constants.PlayerType.PlayerA : Constants.PlayerType.PlayerB;
_isMultiplay = false;
}
public PlayerState(bool isFirstPlayer, MultiplayManager multiplayManager, string roomId) : this(isFirstPlayer)
{
_multiplayManager = multiplayManager;
_roomId = roomId;
_isMultiplay = true;
}
public override void OnEnter(GameLogic gameLogic)
{
gameLogic.blockController.OnBlockClickedDelegate = (row, col) =>
{
HandleMove(gameLogic, row, col);
};
}
public override void OnExit(GameLogic gameLogic)
{
gameLogic.blockController.OnBlockClickedDelegate = null; // 게임이 끝났을 때 board를 Click해도 바뀌지 않도록
}
public override void HandleMove(GameLogic gameLogic, int row, int col)
{
ProcessMove(gameLogic, _playerType, row, col);
if (_isMultiplay)
{
_multiplayManager.SendPlayerMove(_roomId, row * 3 + col);
}
}
protected override void HandleNextTurn(GameLogic gameLogic)
{
if (_isFirstPlayer)
{
gameLogic.SetState(gameLogic.secondPlayerState);
}
else
{
gameLogic.SetState(gameLogic.firstPlayerState);
}
}
}
// AI 플레이
public class AIState : BasePlayerState
{
public override void OnEnter(GameLogic gameLogic)
{
// AI 연산
var result = MinimaxAIController.GetBestMove(gameLogic.GetBoard());
if (result.HasValue)
{
HandleMove(gameLogic, result.Value.row, result.Value.col);
}
else
{
gameLogic.EndGame(GameLogic.GameResult.Draw);
}
}
public override void OnExit(GameLogic gameLogic)
{
}
public override void HandleMove(GameLogic gameLogic, int row, int col)
{
ProcessMove(gameLogic, Constants.PlayerType.PlayerB, row, col);
}
protected override void HandleNextTurn(GameLogic gameLogic)
{
gameLogic.SetState(gameLogic.firstPlayerState);
}
}
// 네트워크 플레이 --> 서버로부터 상대 Player를 기다림
public class MultiplayState : BasePlayerState
{
private Constants.PlayerType _playerType;
private bool _isFirstPlayer;
private MultiplayManager _multiplayManager;
public MultiplayState(bool isFirstPlayer, MultiplayManager multiplayManager)
{
_isFirstPlayer = isFirstPlayer;
_playerType = _isFirstPlayer ? Constants.PlayerType.PlayerA : Constants.PlayerType.PlayerB;
_multiplayManager = multiplayManager;
}
public override void OnEnter(GameLogic gameLogic)
{
_multiplayManager.OnOpponentMove = moveData =>
{
var row = moveData.position / 3;
var col = moveData.position % 3;
UnityThread.executeInUpdate(() =>
{
HandleMove(gameLogic, row, col);
});
};
}
public override void OnExit(GameLogic gameLogic)
{
_multiplayManager.OnOpponentMove = null;
}
public override void HandleMove(GameLogic gameLogic, int row, int col)
{
ProcessMove(gameLogic, _playerType, row, col);
}
protected override void HandleNextTurn(GameLogic gameLogic)
{
if (_isFirstPlayer)
{
gameLogic.SetState(gameLogic.secondPlayerState);
}
else
{
gameLogic.SetState(gameLogic.firstPlayerState);
}
}
}
public class GameLogic : IDisposable
{
public BlockController blockController;
private Constants.PlayerType[,] _board;
public BasePlayerState firstPlayerState; // 첫 번째 턴 상태 객체
public BasePlayerState secondPlayerState; // 두 번째 턴 상태 객체
private BasePlayerState _currentPlayerState; // 현재 턴 상태 객체
private MultiplayManager _multiplayManager;
private string _roomId;
public enum GameResult { None, Win, Lose, Draw }
public GameLogic(BlockController blockController, Constants.GameType gameType,
MultiplayManager multiplayManager = null, string roomId = null, bool isFirstPlayer = true)
{
this.blockController = blockController;
// _board 초기화
_board = new Constants.PlayerType[3, 3];
switch (gameType)
{
case Constants.GameType.SinglePlayer:
firstPlayerState = new PlayerState(true);
secondPlayerState = new AIState();
// 게임 시작
SetState(firstPlayerState);
break;
case Constants.GameType.DualPlayer:
firstPlayerState = new PlayerState(true);
secondPlayerState = new PlayerState(false);
// 게임 시작
SetState(firstPlayerState);
break;
case Constants.GameType.MultiPlayer:
// Multiplay Manager 생성
_multiplayManager = new MultiplayManager((state, roomId) =>
{
_roomId = roomId;
switch (state)
{
case Constants.MultiplayManagerState.CreateRoom:
Debug.Log("## Create Room ##");
// TODO: 대기 화면 표시
// GameManager에게 대기 화면 표시 요청
break;
case Constants.MultiplayManagerState.JoinRoom: // 게임 실행 --> Room에 입장한 사람
Debug.Log("## Join Room ##");
firstPlayerState = new MultiplayState(true, _multiplayManager);
secondPlayerState = new PlayerState(false, _multiplayManager, roomId);
// 게임 시작
SetState(firstPlayerState);
break;
case Constants.MultiplayManagerState.ExitRoom:
Debug.Log("## Exit Room ##");
// TODO: Exit Room 처리
break;
case Constants.MultiplayManagerState.StartGame: // 대기 화면을 닫고 게임 실행 --> Create Room을 한 사람
Debug.Log("## Start Game ##");
firstPlayerState = new PlayerState(true, _multiplayManager, roomId);
secondPlayerState = new MultiplayState(false, _multiplayManager);
// 게임 시작
SetState(firstPlayerState);
break;
case Constants.MultiplayManagerState.EndGame:
Debug.Log("## End Game ##");
// TODO: End Game 처리
break;
}
});
break;
}
}
public void SetState(BasePlayerState state)
{
_currentPlayerState?.OnExit(this);
_currentPlayerState = state;
_currentPlayerState?.OnEnter(this);
}
/// <summary>
/// _board에 새로운 값을 할당하는 함수
/// </summary>
/// <param name="playerType">할당하고자 하는 플레이어 타입</param>
/// <param name="row">Row</param>
/// <param name="col">Col</param>
/// <returns>False : 할당할 수 없음, True : 할당이 완료됨</returns>
public bool SetNewBoardValue(Constants.PlayerType playerType, int row, int col)
{
if (_board[row, col] != Constants.PlayerType.None) return false; // 중복 체크 방지
if (playerType == Constants.PlayerType.PlayerA)
{
_board[row, col] = playerType;
blockController.PlaceMarker(Block.MarkerType.O, row, col);
return true;
}
else if (playerType == Constants.PlayerType.PlayerB)
{
_board[row, col] = playerType;
blockController.PlaceMarker(Block.MarkerType.X, row, col);
return true;
}
return false;
}
/// <summary>
/// 게임 결과 확인 함수
/// </summary>
/// <returns>플레이어 기준 게임 결과</returns>
public GameResult CheckGameResult()
{
if (CheckGameWin(Constants.PlayerType.PlayerA)) return GameResult.Win;
if (CheckGameWin(Constants.PlayerType.PlayerB)) return GameResult.Lose;
if (MinimaxAIController.IsAllBlocksPlaced(_board)) return GameResult.Draw;
return GameResult.None;
}
// 게임의 승패를 판단하는 함수
private bool CheckGameWin(Constants.PlayerType playerType)
{
// 가로로 마커가 일치하는지 확인
for (var row = 0; row < _board.GetLength(0); row++)
{
if (_board[row, 0] == playerType && _board[row, 1] == playerType && _board[row, 2] == playerType)
{
(int, int)[] blocks = { (row, 0), (row, 1), (row, 2) };
blockController.SetBlockColor(playerType, blocks);
return true;
}
}
// 세로로 마커가 일치하는지 확인
for (var col = 0; col < _board.GetLength(1); col++)
{
if (_board[0, col] == playerType && _board[1, col] == playerType && _board[2, col] == playerType)
{
(int, int)[] blocks = { (0, col), (1, col), (2, col) };
blockController.SetBlockColor(playerType, blocks);
return true;
}
}
// 대각선으로 마커가 일치하는지 확인
if (_board[0, 0] == playerType && _board[1, 1] == playerType && _board[2, 2] == playerType)
{
(int, int)[] blocks = { (0, 0), (1, 1), (2, 2) };
blockController.SetBlockColor(playerType, blocks);
return true;
}
if (_board[0, 2] == playerType && _board[1, 1] == playerType && _board[2, 0] == playerType)
{
(int, int)[] blocks = { (0, 2), (1, 1), (2, 0) };
blockController.SetBlockColor(playerType, blocks);
return true;
}
return false;
}
/// <summary>
/// 게임 오버 시, 호출되는 함수
/// gameResult에 따라 결과 출력
/// </summary>
/// <param name="gameResult">win, lose, draw</param>
public void EndGame(GameResult gameResult)
{
SetState(null);
firstPlayerState = null;
secondPlayerState = null;
// 게임 오버 표시
GameManager.Instance.OpenGameOverPanel();
}
public Constants.PlayerType[,] GetBoard()
{
return _board;
}
public void Dispose()
{
_multiplayManager?.LeaveRoom(_roomId);
_multiplayManager?.Dispose();
}
}
>> MultiplayManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using SocketIOClient;
// game.js 에서 보내는 값을 받음
public class RoomData
{
[JsonProperty("roomId")]
public string roomId { get; set; }
}
public class UserData
{
[JsonProperty("userId")]
public string userId { get; set; }
}
public class MoveData
{
[JsonProperty("position")]
public int position { get; set; }
}
public class MessageData
{
[JsonProperty("nickName")]
public string nickName { get; set; }
[JsonProperty("message")]
public string message { get; set; }
}
public class MultiplayManager : IDisposable
{
private SocketIOUnity _socket;
private event Action<Constants.MultiplayManagerState, string> _onMultiplayStateChanged;
public Action<MoveData> OnOpponentMove;
public Action<MessageData> OnReceivedMessage;
public MultiplayManager(Action<Constants.MultiplayManagerState, string> onMultiplayStateChanged)
{
_onMultiplayStateChanged = onMultiplayStateChanged;
var uri = new Uri(Constants.GameServerURL);
_socket = new SocketIOUnity(uri, new SocketIOOptions
{
Transport = SocketIOClient.Transport.TransportProtocol.WebSocket
});
// 서버가 보낼 event(message)에 따라 작동
// "createRoom"이라는 event가 왔을 때 CreateRoom이 작동하도록 CreateRoom()이라고 적지 않는다.
_socket.On("createRoom", CreateRoom);
_socket.On("joinRoom", JoinRoom);
_socket.On("exitRoom", ExitRoom);
_socket.On("startGame", StartGame);
_socket.On("endGame", EndGame);
_socket.On("doOpponent", DoOpponent);
_socket.On("receiveMessage", ReceiveMessage);
_socket.Connect();
}
private void CreateRoom(SocketIOResponse response) // 매개변수로 SocketIOResponse가 필수로 있어야 한다.
{
var data = response.GetValue<RoomData>();
_onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.CreateRoom, data.roomId);
}
private void JoinRoom(SocketIOResponse response)
{
var data = response.GetValue<RoomData>();
_onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.JoinRoom, data.roomId);
}
private void ExitRoom(SocketIOResponse response)
{
_onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.ExitRoom, null);
}
private void StartGame(SocketIOResponse response)
{
var data = response.GetValue<RoomData>();
_onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.StartGame, data.roomId);
}
private void EndGame(SocketIOResponse response)
{
_onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.EndGame, null);
}
// 서버로부터 상대방의 마커 정보를 받기 위한 Method
private void DoOpponent(SocketIOResponse response)
{
var data = response.GetValue<MoveData>();
OnOpponentMove?.Invoke(data);
}
// Player의 마커 위치를 서버로 전달하기 위한 Method
public void SendPlayerMove(string roomId, int position)
{
_socket.Emit("doPlayer", new { roomId, position });
}
// 서버로부터 Message를 받음
private void ReceiveMessage(SocketIOResponse response)
{
var data = response.GetValue<MessageData>();
OnReceivedMessage?.Invoke(data);
}
// 서버로 Message를 보냄
public void SendMessage(string roomId , string nickName, string message)
{
_socket.Emit("sendMessage", new { roomId, nickName, message });
}
public void LeaveRoom(string roomId)
{
_socket.Emit("leaveRoom", new { roomId });
}
public void Dispose()
{
if (_socket != null)
{
_socket.Disconnect();
_socket.Dispose();
_socket = null;
}
}
}
>> Constants.cs
public class Constants
{
public const string ServerURL = "http://localhost:3000";
public const string GameServerURL = "ws://localhost:3000";
public enum MultiplayManagerState
{
CreateRoom, // 방 생성
JoinRoom, // 생성된 방에 참여
ExitRoom, // 자신이 방을 빠져 나왔을 때
StartGame, // 생성한 방에 다른 유저가 참여하여 게임 시작
EndGame // 상대방이 접속을 끊거나 방을 나갔을 때
};
public enum PlayerType { None, PlayerA, PlayerB }
public enum GameType { SinglePlayer, DualPlayer, MultiPlayer }
}
>> BlockController.cs
using UnityEngine;
public class BlockController : MonoBehaviour
{
[SerializeField] private Block[] blocks;
public delegate void OnBlockClicked(int row, int col);
public OnBlockClicked OnBlockClickedDelegate;
public void InitBlocks()
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].InitMarker(i, blockIndex =>
{
var clickedRow = blockIndex / 3;
var clickedCol = blockIndex % 3;
OnBlockClickedDelegate?.Invoke(clickedRow, clickedCol);
});
}
}
/// <summary>
/// 특정 Block에 Marker를 표시하는 함수
/// </summary>
/// <param name="markerType">마커 타입</param>
/// <param name="row">Row</param>
/// <param name="col">Col</param>
public void PlaceMarker(Block.MarkerType markerType, int row, int col)
{
// row, col을 index로 변환
var markerIndex = row * 3 + col;
// Block에게 마커 표시
blocks[markerIndex].SetMarker(markerType);
}
public void SetBlockColor(Constants.PlayerType playerType, (int row, int col)[] blockPositions)
{
if (playerType == Constants.PlayerType.None) return;
foreach (var blockPosition in blockPositions)
{
var blockIndex = blockPosition.row * 3 + blockPosition.col;
Color32 markerColor;
if (playerType == Constants.PlayerType.PlayerA)
markerColor = new Color32(0, 166, 255, 255);
else if (playerType == Constants.PlayerType.PlayerB)
markerColor = new Color32(255, 0, 94, 255);
else
markerColor = Color.black;
blocks[blockIndex].SetColor(markerColor);
}
}
}
>> MinimaxAIController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class MinimaxAIController
{
public static void printBoard(Constants.PlayerType[,] board)
{
string boardString = "";
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
boardString += $"{(int)board[row, col]} "; // PlayerType 값을 숫자로 출력
}
boardString += "\n"; // 한 줄 출력 후 줄 바꿈
}
Debug.Log("\n" + boardString); // 콘솔 창에 보드 출력
}
public static (int row, int col)? GetBestMove(Constants.PlayerType[,] board)
{
float bestScore = -1000;
(int row, int col)? bestMove = null;
for (var row = 0; row < board.GetLength(0); row++)
{
for (var col = 0; col < board.GetLength(1); col++)
{
if (board[row, col] == Constants.PlayerType.None)
{
board[row, col] = Constants.PlayerType.PlayerB;
var score = DoMinimax(board, 0, false);
board[row, col] = Constants.PlayerType.None;
if (score > bestScore)
{
bestScore = score;
bestMove = (row, col);
}
}
}
}
return bestMove;
}
private static float DoMinimax(Constants.PlayerType[,] board, int depth, bool isMaximizing)
{
if (CheckGameWin(Constants.PlayerType.PlayerA, board))
return -10 + depth;
if (CheckGameWin(Constants.PlayerType.PlayerB, board))
return 10 - depth;
if (IsAllBlocksPlaced(board))
return 0;
if (isMaximizing)
{
var bestScore = float.MinValue;
for (var row = 0; row < board.GetLength(0); row++)
{
for (var col = 0; col < board.GetLength(1); col++)
{
if (board[row, col] == Constants.PlayerType.None)
{
board[row, col] = Constants.PlayerType.PlayerB;
var score = DoMinimax(board, depth + 1, false); // 재귀함수
board[row, col] = Constants.PlayerType.None;
bestScore = Math.Max(bestScore, score); // Math.Max(a, b) : a와 b 중에 큰 값을 반환
}
}
}
return bestScore;
}
else
{
var bestScore = float.MaxValue;
for (var row = 0; row < board.GetLength(0); row++)
{
for (var col = 0; col < board.GetLength(1); col++)
{
if (board[row, col] == Constants.PlayerType.None)
{
board[row, col] = Constants.PlayerType.PlayerA;
var score = DoMinimax(board, depth + 1, true); // 재귀함수
board[row, col] = Constants.PlayerType.None;
bestScore = Math.Min(bestScore, score); // Math.Min(a, b) : a와 b 중에 작은 값을 반환
}
}
}
return bestScore;
}
}
/// <summary>
/// 모든 마커가 보드에 배치 되었는지 확인하는 함수
/// </summary>
/// <returns>True : 모두 배치</returns>
public static bool IsAllBlocksPlaced(Constants.PlayerType[,] board)
{
for (var row = 0; row < board.GetLength(0); row++)
{
for (var col = 0; col < board.GetLength(1); col++)
{
if (board[row, col] == Constants.PlayerType.None)
return false;
}
}
return true;
}
/// <summary>
/// 게임의 승패를 판단하는 함수
/// </summary>
/// <param name="playerType"></param>
/// <param name="board"></param>
/// <returns></returns>
private static bool CheckGameWin(Constants.PlayerType playerType, Constants.PlayerType[,] board)
{
// 가로로 마커가 일치하는지 확인
for (var row = 0; row < board.GetLength(0); row++)
{
if (board[row, 0] == playerType && board[row, 1] == playerType && board[row, 2] == playerType)
return true;
}
// 세로로 마커가 일치하는지 확인
for (var col = 0; col < board.GetLength(1); col++)
{
if (board[0, col] == playerType && board[1, col] == playerType && board[2, col] == playerType)
return true;
}
// 대각선으로 마커가 일치하는지 확인
if (board[0, 0] == playerType && board[1, 1] == playerType && board[2, 2] == playerType)
return true;
if (board[0, 2] == playerType && board[1, 1] == playerType && board[2, 0] == playerType)
return true;
return false;
}
}
>> MainPanelController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainPanelController : MonoBehaviour
{
public void OnClickSinglePlayButton()
{
GameManager.Instance.ChangeToGameScene(Constants.GameType.SinglePlayer);
}
public void OnClickDualPlayButton()
{
GameManager.Instance.ChangeToGameScene(Constants.GameType.DualPlayer);
}
public void OnClickMultiplayButton()
{
GameManager.Instance.ChangeToGameScene(Constants.GameType.MultiPlayer);
}
public void OnClickSettingsButton()
{
GameManager.Instance.OpenSettingsPanel();
}
}
>> game.js
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 72일차 (0) | 2025.03.12 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 71일차 (2) | 2025.03.11 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 69일차 (0) | 2025.03.07 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 68일차 (0) | 2025.03.06 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 67일차 (0) | 2025.03.05 |