# Documentation : Boom Boom Beach | TAUTAN | | ------ | |[Itch.io](https://adriian.itch.io/boom-boom-bounce)| |[Game Design Document](https://docs.google.com/document/d/1uD4IUk_nac0poJq0-FCWCGvMN8eTdy7v2Kjez_GtfMw/edit)| ## User Authentication ### Login Menyamakan data antara database dengan GET request dan input ``` public void LoginRequest() { //Match database with input RestClient.Get<UserData>("https://praktikum-jaringan-komputer-default-rtdb.asia-southeast1.firebasedatabase.app/" + usernameField.text + ".json").Then((System.Action<UserData>)(response => { if (response.username == usernameField.text && response.password == passwordField.text) { Debug.Log("login success"); } else { errorMsgLogin.text = "Login failed!"; } }), response => { errorMsgLogin.text = "Login failed!"; }) } ``` ### Register Memeriksa apakah username sudah ada, jika belum maka melakukan PUT request ke database. ``` public void Register() { if (regInputValid) { //Get request to database if whether username is exist RestClient.Get<UserData>("https://praktikum-jaringan-komputer-default-rtdb.asia-southeast1.firebasedatabase.app/" + regUsernameField.text + ".json").Then(response => { errorMsgReg.text = "Username already exist!"; }, response => { Debug.Log("registering"); userData = new UserData(regUsernameField.text, regPasswordField.text, false); RestClient.Put("https://praktikum-jaringan-komputer-default-rtdb.asia-southeast1.firebasedatabase.app/" +regUsernameField.text + ".json", userData).Done(responseSucces => { errorMsgReg.text = "Register done!"; } } } ``` ## Spawn Mendapatkan informasi dari pemain yang baru terhubung berupa username dan karakter yang dipilih. ``` public static void WelcomeReceived(int _fromClient, Packet _packet) { int _clientIdCheck = _packet.ReadInt(); string _username = _packet.ReadString(); int _animalId = _packet.ReadInt(); Debug.Log("username = " + _username); Debug.Log($"{Server.clients[_fromClient].tcp.socket.Client.RemoteEndPoint} connected successfully and is now player {_fromClient}."); if (_fromClient != _clientIdCheck) { Debug.Log($"Player \"{_username}\" (ID: {_fromClient}) has assumed the wrong client ID ({_clientIdCheck})!"); } Server.clients[_fromClient].SendIntoGame(_username, _animalId); } ``` Mengirim informasi ke pemain baru, semua pemain yang sebelumnya terhubung dan ke pemain yang sudah terhubung tentang pemain baru. ``` public void SendIntoGame(string _playerName, int _animalId) { player = NetworkManager.instance.InstantiatePlayer(); player.Initialize(id, _playerName, _animalId); // Send all players to the new player foreach (Client _client in Server.clients.Values) { if (_client.player != null) { if (_client.id != id) { ServerSend.SpawnPlayer(id, _client.player); } } } // Send the new player to all players (including himself) foreach (Client _client in Server.clients.Values) { if (_client.player != null) { ServerSend.SpawnPlayer(_client.id, player); } } } ``` Mengirim informasi berupa id, username, posisi, rotasi awal, dan karakter yang dipilih pemain untuk kemudian di-instantiate oleh masing masing client. ``` public static void SpawnPlayer(int _toClient, Player _player) { using (Packet _packet = new Packet((int)ServerPackets.spawnPlayer)) { _packet.Write(_player.id); _packet.Write(_player.username); _packet.Write(_player.transform.position); _packet.Write(_player.transform.rotation); _packet.Write(_player.animalId); SendTCPData(_toClient, _packet); } } ``` >Informasi spawn player dikirim melalui protokol TCP karena keutuhan datanya dianggap penting. Sisi client membaca paket dan mendapatkan informasi mengenai pemain yang perlu di spawn. Client melakukan instantiate berdasarkan informasi tersebut. ``` public static void SpawnPlayer(Packet _packet) { int _id = _packet.ReadInt(); string _username = _packet.ReadString(); Vector3 _position = _packet.ReadVector3(); Quaternion _rotation = _packet.ReadQuaternion(); int animalId = _packet.ReadInt(); GameManager.instance.SpawnPlayer(_id, _username, _position, _rotation, animalId); } ``` ## Player Control Mengirim input berupa boolean array private void SendInputToServer() { bool[] _inputs = new bool[] { Input.GetKey(KeyCode.W), Input.GetKey(KeyCode.S), Input.GetKey(KeyCode.A), Input.GetKey(KeyCode.D), Input.GetKey(KeyCode.Space) }; ClientSend.PlayerMovement(_inputs); }; Diatas ini merupakan fungsi yang menangkap input dari user yang nantinya dikirim melalui fungsi yang ada di cliend send. private void FixedUpdate() { SendInputToServer(); } Fungsi berjalan terus selama aplikasi berlangsung. public static void PlayerMovement(bool[] _inputs) { using (Packet _packet = new Packet((int)ClientPackets.playerMovement)) { _packet.Write(_inputs.Length); foreach (bool _input in _inputs) { _packet.Write(_input); } _packet.Write(GameManager.players[Client.instance.myId].transform.rotation); SendUDPData(_packet); } } Fungsi yang merubah inputan user sebelumnya menjadi sebuah paket baru dan dikirim dengan UDP ke server. public enum ClientPackets { welcomeReceived = 1, playerMovement, } Memberikan identitas kepada paket yang sudah dibuat sebelumnya di client. public enum ClientPackets { welcomeReceived = 1, playerMovement, } Mengidentifikasi identitas paket yang sudah diterima sebelumnya dari client, pendeklarasian enum paket harus sama antara client dan server. private static void InitializeServerData() { for (int i = 1; i <= MaxPlayers; i++) { clients.Add(i, new Client(i)); } packetHandlers = new Dictionary<int, PacketHandler>() { { (int)ClientPackets.welcomeReceived, ServerHandle.WelcomeReceived }, { (int)ClientPackets.playerMovement, ServerHandle.PlayerMovement } }; Debug.Log("Initialized packets."); } Menginsialisasi enum yang diteruskan ke fungsi pembedahan paket lokal di server. public static void PlayerMovement(int _fromClient, Packet _packet) { bool[] _inputs = new bool[_packet.ReadInt()]; for (int i = 0; i < _inputs.Length; i++) { _inputs[i] = _packet.ReadBool(); } Quaternion _rotation = _packet.ReadQuaternion(); Server.clients[_fromClient].player.SetInput(_inputs, _rotation); } Diatas ini merupakan fungsi pembedahan paket di server. public void SetInput(bool[] _inputs, Quaternion _rotation) { inputs = _inputs; transform.rotation = _rotation; } Menginsialisasi data dari pembedahan paket ke variabel lokal yang ada di server. Vector2 _inputDirection = Vector2.zero; if (inputs[0]) { _inputDirection.y += 1; } if (inputs[1]) { _inputDirection.y -= 1; } if (inputs[2]) { currentRotation += rotationSensitivity; Rotation(); } if (inputs[3]) { currentRotation -= rotationSensitivity; Rotation(); } Move(_inputDirection); Input dan rotasi yang telah diinisialisasi pada variabel lokal, dimasukkan ke fungsi yang bertugas untuk meneruskan ke perhitungan lokal posisi x, y dan juga rotasi yang dilakukan di server. void Rotation() { transform.rotation = Quaternion.Euler(new Vector3(transform.rotation.x, transform.rotation.y, currentRotation)); ServerSend.PlayerRotation(this); } Fungsi perhitungan rotasi pemain di server. private void Move(Vector2 _inputDirection) { if (!berhentiMaju) { rb.AddForce(transform.up.normalized * moveSpeed, ForceMode2D.Impulse); } ServerSend.PlayerPosition(this); } Fungsi perhitungan posisi pemain di server. public static void PlayerRotation(Player _player) { using (Packet _packet = new Packet((int)ServerPackets.playerRotation)) { _packet.Write(_player.id); _packet.Write(_player.transform.rotation); SendUDPDataToAll(_packet); } } Fungsi yang bertugas memaketkan nilai variabel rotasi lalu dilanjutkan dengan mengirim via UDP di server. public static void PlayerPosition(Player _player) { using (Packet _packet = new Packet((int)ServerPackets.playerPosition)) { _packet.Write(_player.id); _packet.Write(_player.transform.position); SendUDPDataToAll(_packet); } } Fungsi yang bertugas memaketkan nilai variabel posisi lalu dilanjutkan dengan mengirim via UDP di server. public enum ServerPackets { welcome = 1, spawnPlayer, playerPosition, playerRotation, playerDisconnected, cdStart, playerDie, gameOver, zoneStart } Memberikan identitas kepada paket yang sudah dibuat sebelumnya di server. public enum ServerPackets { welcome = 1, spawnPlayer, playerPosition, playerRotation, playerDisconnected, cdStart, playerDie, gameOver, zoneStart } Mengecek identitas paket yang dikirim dari server di client. Pendeklarasian enum paket harus sama antara client dan server. private void InitializeClientData() { packetHandlers = new Dictionary<int, PacketHandler>() { { (int)ServerPackets.welcome, ClientHandle.Welcome }, //{ (int)ServerPackets.loginReq, ClientHandle.Login }, { (int)ServerPackets.spawnPlayer, ClientHandle.SpawnPlayer }, { (int)ServerPackets.playerPosition, ClientHandle.PlayerPosition }, { (int)ServerPackets.playerRotation, ClientHandle.PlayerRotation }, { (int)ServerPackets.playerDisconnected, ClientHandle.PlayerDisconnected }, { (int)ServerPackets.cdStart, ClientHandle.CountdownStart }, { (int)ServerPackets.zoneStart, ClientHandle.ZoneStart }, { (int)ServerPackets.playerDie, ClientHandle.PlayerDie }, { (int)ServerPackets.gameOver, ClientHandle.GameOver }, }; Debug.Log("Initialized packets."); } Menginsialisasi enum yang diteruskan ke fungsi pembedahan paket lokal di client. public static void PlayerPosition(Packet _packet) { int _id = _packet.ReadInt(); Vector3 _position = _packet.ReadVector3(); if (GameManager.players.TryGetValue(_id, out PlayerManager _player)) { _player.rb.MovePosition(_position); } } public static void PlayerRotation(Packet _packet) { int _id = _packet.ReadInt(); Quaternion _rotation = _packet.ReadQuaternion(); if (GameManager.players.TryGetValue(_id, out PlayerManager _player)) { _player.transform.rotation = _rotation; } } Fungsi bedah paket di client dan langsung merubah nilai yang ada pada variabel lokal dengan nilai baru dari hasil pembedahan paket. ## Dead Zone Merupakan sebuah makanisme yang membuat pergerakan pemain semakin lama semakain mengecil. if (stopwatchActive == true) { currentTime = currentTime + Time.deltaTime; } TimeSpan time = TimeSpan.FromSeconds(currentTime); if (time.TotalSeconds >= 30 && time.TotalSeconds < 31) { ServerSend.ZoneStart((int)currentTime, zonePosition, zoneSize); } if (time.TotalSeconds >= 40 && time.TotalSeconds < 70) //zona 1 { scaleDownZone1(); } if (time.TotalSeconds >= 110 && time.TotalSeconds < 111) { ServerSend.ZoneStart((int)currentTime, zonePosition, zoneSize); } if (time.TotalSeconds >= 120) //zona 1 { scaleDownZone2(); } Fungsi diatas merupakan fungsi update yang ada di server untuk melakukan perhitungan secara terus menerus dari fungsi lokal yang nantinya akan dikirimkan ke client. public void scaleDownZone1() { targetZoneSize = new Vector3(2f, 2f); targetZonePosition = new Vector3(-0.5f, 1.1f); Vector3 sizeChange = (targetZoneSize - zoneSize).normalized; Vector3 newZoneSize = zoneSize + sizeChange * Time.deltaTime * zoneSpeed; Vector3 zoneMoveDir = (targetZonePosition - zonePosition).normalized; Vector3 newZonePosition = zonePosition + zoneMoveDir * Time.deltaTime * zoneSpeed; setZoneSize(newZonePosition, newZoneSize); ServerSend.ZoneStart((int)currentTime, zonePosition, zoneSize); } private void setZoneSize(Vector3 position,Vector3 size) { zonePosition = position; zoneSize = size; transform.position = position; ZonaMati.localScale = size; } Diatas ini merupakan fungsi perhitungan posisi dan ukuran dari zona yang perhitungannya dilakukan di server dan hasilnya akan dikirimkan melalui fungsi mengirim kepada client yang ada pada fungsi update sebelumnya. public void StartStopwatch() { stopwatchActive = true; } public void StopStopwatch() { stopwatchActive = false; } public void ResetStopwatch() { stopwatchActive = false; currentTime = 0; setZoneSize(new Vector3(-0.6499689f, -1f), new Vector3(6, 6)); } Diatas ini merupakan fungsi stopwatch server yang berfungsi untuk mentrigger berjalannya fungsi pengubahan posisi dan ukuran pada zona yang juga dikirim kan ke client yang berfungsi untuk mentriger munculnya perhitungan mundur pada client sebelum pengecilan zona dilakukan. public static void ZoneStart(int time, Vector3 position, Vector3 size) { using (Packet _packet = new Packet((int)ServerPackets.zoneStart)) { _packet.Write(time); _packet.Write(position); _packet.Write(size); SendUDPDataToAll(_packet); } } Fungsi diatas berguna untuk mempaketkan hasil dari perhitungan variabel lokal yaitu ukuran zona, posisi zona serta stopwatch lalu mengirimkannya kepada client melalui UDP. public enum ServerPackets { welcome = 1, spawnPlayer, playerPosition, playerRotation, playerDisconnected, cdStart, playerDie, gameOver, zoneStart } Memberikan identitas kepada paket yang sudah dibuat sebelumnya di server. public enum ServerPackets { welcome = 1, spawnPlayer, playerPosition, playerRotation, playerDisconnected, cdStart, playerDie, gameOver, zoneStart } Mengecek identitas paket yang dikirim dari server di client. Pendeklarasian enum paket harus sama antara client dan server. private void InitializeClientData() { packetHandlers = new Dictionary<int, PacketHandler>() { { (int)ServerPackets.welcome, ClientHandle.Welcome }, //{ (int)ServerPackets.loginReq, ClientHandle.Login }, { (int)ServerPackets.spawnPlayer, ClientHandle.SpawnPlayer }, { (int)ServerPackets.playerPosition, ClientHandle.PlayerPosition }, { (int)ServerPackets.playerRotation, ClientHandle.PlayerRotation }, { (int)ServerPackets.playerDisconnected, ClientHandle.PlayerDisconnected }, { (int)ServerPackets.cdStart, ClientHandle.CountdownStart }, { (int)ServerPackets.zoneStart, ClientHandle.ZoneStart }, { (int)ServerPackets.playerDie, ClientHandle.PlayerDie }, { (int)ServerPackets.gameOver, ClientHandle.GameOver }, }; Debug.Log("Initialized packets."); } Menginsialisasi enum yang diteruskan ke fungsi pembedahan paket lokal di client. public static void ZoneStart(Packet _packet) { int time = _packet.ReadInt(); Vector3 position = _packet.ReadVector3(); Vector3 size = _packet.ReadVector3(); GameManager.instance.StartZone(time, position, size); } Fungsi bedah paket di client dan langsung merubah nilai yang ada pada variabel lokal dengan nilai baru dari hasil pembedahan paket. public void StartZone(int time, Vector3 position, Vector3 size) { zona.GetComponent<DieZone>().setZoneSize(position, size); if(time >= 30 && time < 40 ) { FindObjectOfType<Countdown>().startZoneCount(); } if (time >= 110 && time < 120) { FindObjectOfType<Countdown>().startZoneCount(); } } Setelah perubahan data terjadi, untuk beberapa kondisi akan menjalankan fungsi lokal diantaranya munculnya notifikasi peringatan sebelum zona mengecil dengan stopwatch dari server sebagai triggernya. ## Game Over Setiap pemain yang collision dengan pembatas (mati) akan memanggil fungsi ini dengan memberikan parameter id-nya untuk menghitung jumlah pemain yang kalah. Id dimasukkan ke dalam list sehingga memberikan informasi urutan pemain kalah. Jika pemain yang hidup tersisa 1 maka akan menambahkan id tersebut ke dalam list dan mengirimkan list tersebut sebagai hasil permainan dan tanda berakhirnya permainan. ``` public void DeadCount(int id) { playerAlive--; playerRankSort.Add(id); Debug.Log("current player alive : " + playerAlive); if (playerAlive <= 1) { playerRankSort.Add(getMissingNo(playerRankSort, playerRankSort.Count)); //getting last man standing id ServerSend.GameOver(playerRankSort); ResetGame(); } } ``` Meguraikan list menjadi int untuk dimasukkan ke dalam packet stream secara berurutan dan dikirmkan. ``` public static void GameOver(List<int> _playerDeadSort) { using (Packet _packet = new Packet((int)ServerPackets.gameOver)) { _packet.Write(_playerDeadSort.Count); _playerDeadSort.ForEach(_packet.Write); SendTCPDataToAll(_packet); } } ``` >Informasi hasil permainan / ranking pemain dikirim melalui protokol TCP karena keutuhan datanya dianggap penting. Pada sisi client, paket diterima dengan urutan id pemain kalah. Urutan tersebut dimasukkan ke dalam list rank pemain untuk dicari informasi pemain terkait dan ditampilkan berurutan saat permainan usai. ``` public static void GameOver(Packet _packet) { int length = _packet.ReadInt(); List<int> playerRankSort = new List<int>(length); for (int i = 1; i <= length; i++) { playerRankSort.Add(_packet.ReadInt()); } UIManager.instance.GameOver(playerRankSort); } ``` ---