# 高爾夫整理: ## 1、分數表紀錄存取機制: -POST方法: APIManager.cs line 322 ``` public void POST_Record(TypeFlag.API.POST.RecordData recordData) ``` 資料結構: ``` public class RecordData { public string session_id; public string user_id; public string map_id; public int[] par_score; public int hole_count; public int player_count; public string mode_type; public string timestamp; public int rank; } ``` -GET 方法 APIManager.cs line 172 ``` public void Get_Record(string playerID, TypeFlag.Menu.RoomMode roomMode, Action<List<TypeFlag.RecordInfo>> callback) ``` 資料結構: ``` public class Get_Record { public string status; public RecordData[] result; [System.Serializable] public class RecordData { public string id; public string session_id; public string user_id; public string map_id; public int[] par_score; public int hole_count; public int player_count; public string mode_type; public string create_time; public string timestamp; } } ``` ## 2、小地圖呈現與座標轉換做法 ### 觀賽模式 MinimapManager.cs ![image](https://hackmd.io/_uploads/HJ61m88KA.png) ![image](https://hackmd.io/_uploads/H1UZQULFR.png) - 這些是類中用於管理玩家圖標和位置差異的私有變量 ``` private void Start() { WatchModeSubScreenPanelController.instance.SetMap(CameraManager.Instance.miniMapCameras[0].targetTexture); InstantiatePlayerIcon(); CountPositionRelative(); } ``` - 當遊戲開始時,此方法通過設置小地圖紋理、創建玩家圖標和計算初始位置差來初始化小地圖。 ``` private void InstantiatePlayerIcon() { for (int i = 0; i < InGamePlayerDataManager.Instance.GetPlayerTotal(); i++) { GameObject playerIcon = Instantiate(_playerIconTemplate); playerIcon.transform.GetChild(0).GetComponent<SpriteRenderer>().sprite = InGamePlayerDataManager.Instance.GetPlayerSpriteByIndex(i); playerIcon.SetActive(true); playerIcons.Add(playerIcon); } } ``` - 該方法根據玩家總數創建玩家目標跟位置。 ``` private void CountPositionRelative() { _differencePosition = InGamePlayerDataManager.Instance.GetCurrentBallPosition() - transform.position; Debug.Log("differenceposition " + _differencePosition); } ``` - 此方法計算當前球位置與小地圖管理器位置之間的差異。 ``` void Update() { if (InGamePlayerDataManager.Instance.GetIsBetweenSceneFinished()) { Vector3 tempPosition = InGamePlayerDataManager.Instance.GetCurrentBallPosition(); tempPosition.y = transform.position.y; transform.position = tempPosition; RotateGreenAreaCamera(); RoomPathFinder(); } } ``` - 此方法每幀調用一次。 如果場景過渡完成,它會更新小地圖位置並執行其他更新。 ``` - public void RotateGreenAreaCamera() { Vector3 direction = _movePositionTransform.position - transform.position; Quaternion targetRotation = Quaternion.LookRotation(direction); _flagIcon.transform.rotation = Quaternion.Euler(90f, targetRotation.eulerAngles.y, targetRotation.eulerAngles.z); targetRotation = Quaternion.Euler(90f, targetRotation.eulerAngles.y - 180, targetRotation.eulerAngles.z); _greenAreaCamera.transform.rotation = targetRotation; } ``` - 此方法在觸發數據更新事件時更新玩家視圖數據。 ``` private void UpdateInGamePlayerViewData() { if (playerIcons.Count == 0) { InstantiatePlayerIcon(); } if (InGamePlayerDataManager.Instance.GetAllPlayersData().Count > 0 && playerIcons.Count > 0) { for (int i = 0; i < InGamePlayerDataManager.Instance.GetPlayerTotal(); i++) { Vector3 ballPosition = InGamePlayerDataManager.Instance.GetBallPositionByIndex(i); ballPosition.y = transform.position.y; playerIcons[i].transform.position = ballPosition; playerIcons[i].transform.GetChild(0).GetComponent<SpriteRenderer>().sprite = InGamePlayerDataManager.Instance.GetPlayerSpriteByIndex(i); } } } ``` - 此方法根據當前玩家數據更新玩家目標位置。 ``` void RoomPathFinder() { float distance = Vector3.Distance(transform.position, _movePositionTransform.position); Vector3 direction = _movePositionTransform.position - transform.position; float limitedDistance = Mathf.Min(direction.magnitude, 100f); Vector3 middlePointPosition = new Vector3(); if (CameraManager.Instance.cameraPositions.Count > 0) { Vector3 tempVector = CameraManager.Instance.cameraPositions[CameraManager.Instance.cameraRotationIndex]; middlePointPosition.x = tempVector.x; middlePointPosition.z = tempVector.z; middlePointPosition.y = transform.position.y; if (!float.IsNaN(middlePointPosition.x) && !float.IsNaN(middlePointPosition.y) && !float.IsNaN(middlePointPosition.z)) { _middlePoint.transform.position = middlePointPosition; } DrawLine(transform.position, _middlePoint.transform.position, _lineRenderer1); DrawLine(_middlePoint.transform.position, _movePositionTransform.position, _lineRenderer2); DrawLineGreenArea(transform.position, _flagIcon.transform.position, _lineRenderer3); } Vector3 tempVector2 = InGamePlayerDataManager.Instance.GetCurrentHolePosition(); tempVector2.y = transform.position.y; _movePositionTransform.transform.position = tempVector2; _flagIcon.transform.position = tempVector2; } ``` - 此方法計算玩家要遵循的路徑,更新中間點位置並繪製線條以表示路徑。 ``` void DrawLine(Vector3 start, Vector3 end, LineRenderer lineRenderer) { lineRenderer.gameObject.SetActive(true); Vector3 modifiedPositionStart = new Vector3(start.x, start.y + 2f, start.z); Vector3 modifiedPositionEnd = new Vector3(end.x, start.y + 2f, end.z); lineRenderer.SetPosition(0, modifiedPositionStart); lineRenderer.SetPosition(1, modifiedPositionEnd); } void DrawLineGreenArea(Vector3 start, Vector3 end, LineRenderer lineRenderer) { lineRenderer.gameObject.SetActive(true); Vector3 modifiedPositionStart = new Vector3(start.x, start.y + 2f, start.z); Vector3 modifiedPositionEnd = new Vector3(end.x, start.y + 2f, end.z); lineRenderer.SetPosition(0, modifiedPositionStart); lineRenderer.SetPosition(1, modifiedPositionEnd); } ``` ### 競賽模式 -比賽開始時,生成所有玩家的icon ``` public void InstantiateMapIcon() { mapPlayerIconList = new List<RectTransform>(); for (int i = 0; i < CompetitionModeManager.instance.playerDataListInGame.players.Count; i++) { int index = i; Transform playerIcon = Instantiate(mapPlayerIconPrefab, mapPlayerIconParent).transform; playerIcon.GetChild(2).GetChild(0).GetChild(0).GetComponent<Image>().sprite = CompetitionModeManager.instance.userImageData.userSprites[CompetitionModeManager.instance.playerDataListInGame.players[index].profilePictureNum]; mapPlayerIconList.Add(playerIcon.GetComponent<RectTransform>()); mapPlayerIconList[i].GetChild(0).GetComponent<UILineRenderer>().points = new Vector2[2]; UpdateMap(CompetitionModeManager.instance.playerDataListInGame.players[index].playerId, CompetitionModeGameingController.instance.StartPosToMapPos(CompetitionModeManager.instance.ballStartPositionParent.GetChild(index))); } } ``` -透過 map3DCenterTsf 定位小地圖中心在場景上的位置 -透過 BallToMapPos() 計算玩家Icon在地圖上的位置,mapRatio為3d空間至2dMap的縮放比率 ``` public Vector2 BallToMapPos() { Vector2 v = new Vector2(CompetitionModeGameingController.instance.ballRb.position.x - map3DCenter.x, CompetitionModeGameingController.instance.ballRb.position.z - map3DCenter.z); return v * -mapRatio; } ``` -打擊完成後透過RPC將自身的id和位置送出 ``` PunNetworkRPCManager.instance.CallRPCCompettitionMapIconPos(PlayerManager.instance.hostPlayerData.playerId, mapPos.x, mapPos.y); ``` -接收到資料根據id更新對應icon ``` public void UpdateMap(string hostID, Vector2 mapPlayerIconPos) { for (int i = 0; i < CompetitionModeManager.instance.playerDataListInGame.players.Count; i++) { if (hostID == CompetitionModeManager.instance.playerDataListInGame.players[i].playerId) { int index = i; mapPlayerIconList[i].anchoredPosition = mapPlayerIconPos; Vector2 mapPlayerPos = Vector2.zero; if (mapPlayerIconPos.x < 0) mapPlayerPos = new Vector2(-100, 0); else mapPlayerPos = new Vector2(100, 0); mapPlayerIconList[i].GetChild(2).GetComponent<RectTransform>().anchoredPosition = mapPlayerPos; mapPlayerIconList[i].GetChild(0).GetComponent<UILineRenderer>().points[0] = Vector2.zero; mapPlayerIconList[i].GetChild(0).GetComponent<UILineRenderer>().points[1] = mapPlayerPos; mapPlayerIconList[i].GetChild(0).GetComponent<UILineRenderer>().Set(); Canvas.ForceUpdateCanvases(); CompetitionModeSubScreenPanelController.instance.UpdateMap(hostID, mapPlayerIconPos); CompetitionModeDigitalTwinManager.instance.SetHostDigitalTwinPos(CompetitionModeGameingController.instance.GetDigitalPos()); return; } } } ``` ## 3、優化擊球打擊參數(除了摩擦力還有其他可調控的部分嗎) 要調整打擊參數直接將BallData hitBallData的資料直接*上調整比率就可以了 ``` public class BallData { public float speed; public float sideSpin; public float backSpin; public float directionAngle; public float takeoffAngle; } ``` 如球速快一點 hitBallData.speed *= 1.5f; BallMovement.cs line.1172 CompetitionModeGameingController.cs line.182 ``` //計算方向,由camera正面為基準 Quaternion direction = Quaternion.Euler(0, CompetitionModeManager.instance.mainVcam.transform.eulerAngles.y, 0) * Quaternion.Euler(-hitBallData.takeoffAngle, hitBallData.directionAngle, 0); //計算向量 Vector3 launchVelocity = direction * Vector3.forward * hitBallData.speed; //給球體添加打擊力 ballRb.AddForce(launchVelocity, ForceMode.VelocityChange); //計算旋轉 Vector3 backSpinAxis = Vector3.Cross(launchVelocity.normalized, Vector3.up).normalized; //給球體添加力矩 ballRb.AddTorque(Vector3.up * hitBallData.sideSpin); // Sidespin ballRb.AddTorque(backSpinAxis * hitBallData.backSpin); ... //根據球體角速度計算馬格納斯效應,並在FixedUpdate中不斷施加 //https://www.youtube.com/watch?v=EfJYTD7Q2pY private void AddMagnitubeForce() { var direction = Vector3.Cross(ballRb.angularVelocity, ballRb.velocity); var magnitude = 4 / 3f * Mathf.PI * 0.001f * Mathf.Pow(0.5f, 3); ballRb.AddForce(magnitude * direction); } private void FixedUpdate() { if (isAddMagnitude) { AddMagnitubeForce(); } } ``` ## 4、多人連線功能與設定與機制 多人連線使用Photon Pun -主要腳本 PunNetworkManager.cs //管理連線相關 PunNetworkLobbyManager.cs //管理大廳相關 PunNetworkRoomManager.cs //管理房間相關 PhotonVoiceManager.cs //管理語音相關 PunNetworkRPCManager.cs //管理RPC發送相關 NetworkHostTeam.cs //各Client端的主控腳本,RPC接收等 PunNetworkCompititionModeManager.cs //管理競賽模式相關同步物件 -玩家進入大廳時會透過 ``` PunNetworkManager.instance.StartPunNetwork(); ``` 連接上伺服器 並獲取伺服器上的房間資料 -進入房間後(加入、創建) 生成NetworkHostTeam,在各client端也會同步生成 ``` PunNetworkManager.instance.CreateNetworkHostTeam(); ``` -開始比賽後(競賽模式) 生成球及數位分身 ``` PunNetworkCompititionModeManager.instance.InstantiateBall() PunNetworkCompititionModeManager.instance.InstantiateCompetitionModeDigitalTwinCenter() ``` 數位分身的fbx為及時從網路下載的,故無法直接同步,由client端根據id自行下載。 -資料同步 頻繁更新的資料如 球的位置 ,透過Pun內建的功能同步。 不頻繁更新的資料,如小地圖位置,透過RPC發送同步。 特殊的資料,如數位分身Rig等也透過RPC發送JSON,本地自行解析更新,減少網路直接更新位置的壓力。