# 高爾夫整理:
## 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


- 這些是類中用於管理玩家圖標和位置差異的私有變量
```
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,本地自行解析更新,減少網路直接更新位置的壓力。