405 lines
12 KiB
C#
405 lines
12 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using Mapbox.Unity.Map;
|
||
using Mapbox.Utils;
|
||
using Assets.Scripts.Apis.Models;
|
||
using static Assets.Scripts.Apis.Models.MapDataModel;
|
||
using System;
|
||
using Random = UnityEngine.Random;
|
||
using Assets.Scripts.Apis;
|
||
using GeoJSON.Net.Geometry;
|
||
using TurfCS;
|
||
using System.Linq;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using System.Globalization;
|
||
using Assets.Scenes.Ride.Scripts.Model;
|
||
using static CyclingController;
|
||
using System.IO;
|
||
|
||
namespace Assets.Scenes.Ride.Scripts
|
||
{
|
||
public class PlayerController : MonoBehaviour
|
||
{
|
||
[Header("Character")]
|
||
[SerializeField]
|
||
GameObject character;
|
||
[SerializeField]
|
||
Animator characterAnimator;
|
||
//[SerializeField]
|
||
AbstractMap map;
|
||
|
||
#region 动画控制参数
|
||
Vector3 nextPos;
|
||
Vector3 prePos;
|
||
Vector2d currentlatlong; //当前坐标
|
||
Vector2d nextlatlong; //下一个点的坐标
|
||
float timer = 1.0f;//计时器
|
||
#endregion
|
||
|
||
#region 选手骑行数据
|
||
bool isStart;
|
||
bool isMajor;//是否是主人公
|
||
MapDataModel mapData;
|
||
int userId;
|
||
int routeId;
|
||
DateTime startTime;//开始骑行时间
|
||
|
||
double speed;
|
||
double power;
|
||
double gradev;
|
||
double elevation;
|
||
double cadance;
|
||
int heartRate;
|
||
int ticks;
|
||
double totalDistance;
|
||
double currentSlope;
|
||
double nextSlope;
|
||
double nextSlopeDistance;
|
||
double distance;
|
||
|
||
public int UserId { get => userId; }
|
||
public bool IsStart { get => isStart; }
|
||
public double Speed { get => speed; }
|
||
public double Power { get => power; }
|
||
public double Cadance { get => cadance; }
|
||
public double HeartRate { get => heartRate; }
|
||
public int TotalTicks { get => ticks; }
|
||
public double TotalDistance { get => totalDistance; }
|
||
public double CurrentSlope { get => currentSlope; }
|
||
public double NextSlope { get => nextSlope;}
|
||
public double NextSlopeDistance { get => nextSlopeDistance; }
|
||
public double Gradev { get => gradev; }
|
||
public double Elevation { get => elevation; }
|
||
#endregion
|
||
|
||
void Start()
|
||
{
|
||
characterAnimator = GetComponentInChildren<Animator>();
|
||
var mainController = transform.parent.GetComponent<CyclingController>();
|
||
map = FindObjectOfType<AbstractMap>();
|
||
mapData = mainController.GetMapData();//获取路书信息
|
||
routeId = mainController.RouteId;//路数id
|
||
Initialize(mainController.GetCenterCoordinate());//初始人物
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
timer -= Time.deltaTime;
|
||
if (timer <= 0)//定时器 一秒执行一次
|
||
{
|
||
Run();
|
||
timer = 1.0f;
|
||
}
|
||
}
|
||
|
||
#region 游戏状态控制
|
||
//开始骑行
|
||
public void SetStart()
|
||
{
|
||
isStart = true;
|
||
startTime = DateTime.Now;
|
||
}
|
||
//继续骑行
|
||
public void SetContinue()
|
||
{
|
||
isStart = true;
|
||
}
|
||
//暂停骑行
|
||
public void SetPause()
|
||
{
|
||
isStart = false;
|
||
Complete();
|
||
}
|
||
#endregion
|
||
|
||
#region 骑行逻辑
|
||
|
||
//初始化玩家距离/朝向
|
||
void Initialize(double[] coordinates)
|
||
{
|
||
ticks = 0;//当前骑行时间
|
||
//初始化人物位置
|
||
totalDistance = 0;//TODO:根据骑行模式动态取值 KM
|
||
currentlatlong = Along(totalDistance);//new Vector2d(coordinates[0], coordinates[1]);//当前坐标
|
||
nextlatlong = currentlatlong;//下一秒坐标
|
||
////初始化人物转向
|
||
//var secondPlace = mapData.List[10].Point;
|
||
//var secondVect3d = map.GeoToWorldPosition( new Vector2d(secondPlace[0], secondPlace[1]));
|
||
//Quaternion firstRotation = Quaternion.LookRotation(transform.localPosition - secondVect3d);
|
||
//character.transform.rotation = Quaternion.Euler(0, firstRotation.eulerAngles.y, 0);
|
||
}
|
||
|
||
public List<TargetData> RiderDatas = new List<TargetData>();
|
||
//骑行中
|
||
void Run()
|
||
{
|
||
if (IsStart)
|
||
{
|
||
//CamControl();
|
||
ComputeMapData();//计算海拔&坡度&下一个点信息
|
||
power = 2000;//功率
|
||
speed = Helper.CalculateSpeed(elevation, 0, power, 65, 7);
|
||
distance = Math.Round(speed / 3600, 6);
|
||
totalDistance += distance;
|
||
|
||
characterAnimator.SetBool("IsRide", false);//初始化动画状态
|
||
if (totalDistance <= mapData.TotalDistance)
|
||
{
|
||
//数据处理
|
||
ticks++;
|
||
nextlatlong = Along(totalDistance);//下一个坐标
|
||
nextPos = map.GeoToWorldPosition(nextlatlong);//下一个点
|
||
nextPos.y += 0.3f;
|
||
prePos = transform.localPosition;//当前点
|
||
//记录骑行数据
|
||
var recordText = string.Format($"{ ticks },{ power.ToString(CultureInfo.InvariantCulture) },{ speed.ToString(CultureInfo.InvariantCulture) },{ Math.Round(totalDistance, 6).ToString(CultureInfo.InvariantCulture) },{ cadance.ToString(CultureInfo.InvariantCulture) },{ heartRate.ToString(CultureInfo.InvariantCulture) },{ Math.Round(nextlatlong.x, 6).ToString(CultureInfo.InvariantCulture) },{ Math.Round(nextlatlong.y, 6).ToString(CultureInfo.InvariantCulture) }");
|
||
Debug.Log(recordText);
|
||
|
||
RiderDatas.Add(new TargetData
|
||
{
|
||
Ticks = ticks,
|
||
_Power = power,
|
||
_Speed = speed,
|
||
_Distance = totalDistance,
|
||
_Cadence = cadance,
|
||
_HeartRate = heartRate,
|
||
_Lat = nextlatlong.x,
|
||
_Lon = nextlatlong.y
|
||
});
|
||
|
||
//动画控制
|
||
if (distance > 0)
|
||
{
|
||
characterAnimator.SetBool("IsRide", true);//开始移动动画
|
||
StartCoroutine(LookAtNextPos());//转向
|
||
StartCoroutine(MoveTo());//移动
|
||
}
|
||
}
|
||
else
|
||
{
|
||
totalDistance = mapData.TotalDistance;
|
||
//记录骑行数据
|
||
var recordText = string.Format($"{ ticks },{ power.ToString(CultureInfo.InvariantCulture) },{ speed.ToString(CultureInfo.InvariantCulture) },{ Math.Round(totalDistance, 6).ToString(CultureInfo.InvariantCulture) },{ cadance.ToString(CultureInfo.InvariantCulture) },{ heartRate.ToString(CultureInfo.InvariantCulture) },{ Math.Round(nextlatlong.x, 6).ToString(CultureInfo.InvariantCulture) },{ Math.Round(nextlatlong.y, 6).ToString(CultureInfo.InvariantCulture) }");
|
||
Debug.Log(recordText);
|
||
RiderDatas.Add(new TargetData
|
||
{
|
||
Ticks = ticks,
|
||
_Power = power,
|
||
_Speed = speed,
|
||
_Distance = totalDistance,
|
||
_Cadence = cadance,
|
||
_HeartRate = heartRate,
|
||
_Lat = nextlatlong.x,
|
||
_Lon = nextlatlong.y
|
||
});
|
||
Complete();
|
||
isStart = false;
|
||
characterAnimator.SetBool("ReachEnd", true);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
characterAnimator.SetBool("IsRide", false);
|
||
}
|
||
}
|
||
//发送当前用户位置给UDPs
|
||
void SendUdp() {
|
||
|
||
}
|
||
//TODO:骑行结束
|
||
private int weight = 65;
|
||
private int bicycleWeight = 7;
|
||
private string ContinueMark = "TODO";
|
||
private int ContinueIndex=0;
|
||
private int Competitionid =0;
|
||
private bool IsNeedRanking = true;
|
||
private double CurrentRouteStartDistance = 0;
|
||
private int ManufacturerId=0;
|
||
private int AntModelId =0 ;
|
||
private CyclingModel cyclingModel = CyclingModel.Single;
|
||
private DateTime EndTime;
|
||
void Complete()
|
||
{
|
||
EndTime = startTime.AddSeconds(ticks);
|
||
MapInterruptRecordApi api = new MapInterruptRecordApi();
|
||
string newFileName = Guid.NewGuid().ToString();
|
||
int FTP = Helper.GetFtp();
|
||
double NP = Helper.GetNP(RiderDatas);
|
||
//强度
|
||
double IF = NP / FTP;
|
||
//训练量
|
||
double TSS = (RiderDatas.Count * NP * IF) / (FTP * 3600) * 100;
|
||
var recordId = Guid.NewGuid().ToString();
|
||
var averagePower = Helper.AveragePower(RiderDatas);
|
||
var interruptRecord = new MapInterruptRecord
|
||
{
|
||
Id = recordId,
|
||
RouteId = routeId,
|
||
RouteName = "",
|
||
TotalDistance = mapData.TotalDistance,
|
||
UserId = userId,
|
||
RecordFileName = newFileName + ".txt",
|
||
Ftp = FTP,//FTP设置
|
||
IF = Math.Round(IF, 2),
|
||
Kj = RiderDatas.Sum(a => a._Power) / 1000,//消耗
|
||
Tss = Math.Round(TSS, 2),
|
||
EndDistance = totalDistance,
|
||
IsCompleted = totalDistance >= mapData.TotalDistance,
|
||
NormalizedPower = Math.Floor(NP),//标准化功率
|
||
AveragePower = averagePower,//平均功率
|
||
MaxPower = RiderDatas.Max(a => a._Power),//最大功率
|
||
WeightKg = Math.Round(averagePower / weight, 2),
|
||
Weight = weight,
|
||
BicycleWeight = bicycleWeight,
|
||
ContinueMark = ContinueMark,
|
||
ContinueIndex = ContinueIndex,
|
||
IsDelete = false,
|
||
MapCompetitionId = Competitionid,
|
||
ManufacturerName = "TODO",
|
||
DeviceNumber = "TODO",
|
||
IsRanking = IsNeedRanking,
|
||
CurrentRouteStartDistance = CurrentRouteStartDistance,
|
||
ManufacturerId = ManufacturerId,
|
||
AntModelId = AntModelId,
|
||
StartTime = startTime,
|
||
CreateTime = EndTime,
|
||
Ticks = RiderDatas.Last().Ticks,
|
||
Mode = cyclingModel.ToString(),
|
||
//Param = Newtonsoft.Json.JsonConvert.SerializeObject(selectParam),
|
||
//GlobalCyclingId = selectParam.GlobalCyclingId
|
||
};
|
||
interruptRecord.SpeedRange = null;
|
||
double process = Math.Round((totalDistance - interruptRecord.CurrentRouteStartDistance) /mapData.TotalDistance, 2);
|
||
interruptRecord.Progress = process > 1 ? 1 : process;
|
||
|
||
var cadences = RiderDatas.Where(a => a._Cadence.HasValue && a._Cadence.Value > 0);
|
||
if (cadences.Any())
|
||
{
|
||
interruptRecord.AverageCadence = Math.Round(cadences.Average(a => a._Cadence.GetValueOrDefault(0)));
|
||
}
|
||
interruptRecord.MaxCadence = Math.Round(RiderDatas.Max(a => a._Cadence.GetValueOrDefault(0)));
|
||
interruptRecord.AverageHeartRate = Math.Round(RiderDatas.Average(a => a._HeartRate.GetValueOrDefault(0)));
|
||
interruptRecord.MaxHeartRate = RiderDatas.Max(a => a._HeartRate.GetValueOrDefault(0));
|
||
|
||
var path = Helper.GetDataDire("MapWorkoutRecords");
|
||
var fname = path + "/" + newFileName + ".txt";
|
||
var files = new List<string>();
|
||
using (var fs = new FileInfo(fname).OpenWrite())
|
||
{
|
||
var stream = new StreamWriter(fs);
|
||
stream.BaseStream.Seek(0, SeekOrigin.End);
|
||
foreach (var item in RiderDatas)
|
||
{
|
||
stream.Write(item.Write() + "\r\n");
|
||
}
|
||
stream.Flush();
|
||
stream.Close();
|
||
files.Add(fname);
|
||
}
|
||
|
||
var result = api.Add(interruptRecord, files);
|
||
}
|
||
#endregion
|
||
|
||
#region 工具类
|
||
public int CurrentIndex;
|
||
//当前距离所在的海拔/坡度/距离 下一个点的坡度以及剩余距离
|
||
void ComputeMapData()
|
||
{
|
||
double sumDistance = 0;
|
||
var pointList = mapData.List;
|
||
int index = 0;
|
||
for (int i = 0; i < pointList.Count; i++)
|
||
{
|
||
sumDistance += pointList[i].Distance;
|
||
if (totalDistance <= sumDistance)
|
||
{
|
||
index = i;
|
||
break;
|
||
}
|
||
}
|
||
//计算当前海拔和坡度
|
||
elevation = pointList[index].Elevation;
|
||
gradev = pointList[index].Grade;
|
||
//计算下一个点的坡度和距离
|
||
int nextIndex = index == pointList.Count - 1 ? index : index + 1;
|
||
CurrentIndex = nextIndex;
|
||
nextSlope = pointList[nextIndex].Grade;
|
||
nextSlopeDistance = sumDistance + pointList[nextIndex].Distance - totalDistance;
|
||
}
|
||
//根据距离计算坐标
|
||
Vector2d Along(double endDistance)
|
||
{
|
||
if (mapData != null)
|
||
{
|
||
var list = mapData.List.Select(p => new GeoJSON.Net.Geometry.GeographicPosition(p.Point[0], p.Point[1]));
|
||
LineString lineString = new LineString(list);
|
||
var pt1 = Turf.Along(lineString, endDistance);
|
||
var ll = ((GeographicPosition)((GeoJSON.Net.Geometry.Point)pt1.Geometry).Coordinates);
|
||
return new Vector2d(ll.Latitude, ll.Longitude);
|
||
}
|
||
return nextlatlong;
|
||
}
|
||
#endregion
|
||
|
||
#region 人物移动与转向控制
|
||
IEnumerator LookAtNextPos()
|
||
{
|
||
Quaternion neededRotation = Quaternion.LookRotation(transform.localPosition - nextPos);
|
||
Quaternion thisRotation = character.transform.localRotation;
|
||
|
||
float t = 0;
|
||
while (t < 1.0f)
|
||
{
|
||
t += Time.deltaTime / 0.25f;
|
||
var rotationValue = Quaternion.Slerp(thisRotation, neededRotation, t);
|
||
character.transform.rotation = Quaternion.Euler(0, rotationValue.eulerAngles.y, 0);
|
||
yield return null;
|
||
}
|
||
}
|
||
|
||
//人物移动控制
|
||
IEnumerator MoveTo()
|
||
{
|
||
//让人物移动分点增加动画的流畅度
|
||
float t = 0;
|
||
while (t < 1)
|
||
{
|
||
t += Time.deltaTime;
|
||
Vector3 v = Vector3.Lerp(prePos, nextPos, t);
|
||
transform.localPosition = v;
|
||
//控制海拔图的位置
|
||
|
||
yield return null;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 相机控制
|
||
//[Header("CameraSettings")]
|
||
//[SerializeField]
|
||
//Camera cam;
|
||
//Vector3 previousPos = Vector3.zero;
|
||
//Vector3 deltaPos = Vector3.zero;
|
||
|
||
// IEnumerator CamControl()//替换成cinemachine
|
||
//{
|
||
// if (cam != null)
|
||
// {
|
||
// float t = 0;
|
||
// while (t < 1.0f)
|
||
// {
|
||
// t += Time.deltaTime / 0.5f;
|
||
// deltaPos = transform.position - previousPos;
|
||
// //deltaPos.y = 0;
|
||
// cam.transform.position = Vector3.Lerp(cam.transform.position, cam.transform.position + deltaPos, t);
|
||
// previousPos = transform.position;
|
||
// yield return null;
|
||
// }
|
||
// }
|
||
//}
|
||
#endregion
|
||
|
||
}
|
||
} |