using UnityEngine; using System.IO.Ports; using System.Collections; using System.Globalization; using System.Collections.Generic; public class BoatController : MonoBehaviour { [Header("串口设置")] public string portName = "COM4"; public int baudRate = 9600; [Header("移动设置")] public float moveSpeed = 1.125f; public float rotateFactor = 0.4f; [Header("陀螺仪设置")] public float gyroDeadZone = 5f; public int calibrationSamples = 50; private SerialPort serialPort; private float currentGyroZ = 0f; private float gyroOffset = 0f; private bool isCalibrated = false; private List calibrationData = new List(); private Rigidbody2D rb; private bool isStopped = false; void Start() { rb = GetComponent(); StartCoroutine(OpenSerialPort()); } IEnumerator OpenSerialPort() { yield return new WaitForSecondsRealtime( 0.5f ); int retryCount = 0; int maxRetry = 3; while (retryCount < maxRetry) { bool success = TryOpenPort(); if (success) { Debug.Log("✅ 串口连接成功!"); StartCoroutine(CalibrateGyro()); yield break; } retryCount++; Debug.LogWarning( "⚠️ 串口开启失败第" + retryCount + "次" ); yield return new WaitForSecondsRealtime( 1f ); } Debug.LogError("❌ 串口开启失败"); } IEnumerator CalibrateGyro() { Debug.Log( "🔄 开始校准陀螺仪,请保持静止..." ); calibrationData.Clear(); yield return new WaitForSecondsRealtime(1f); while (calibrationData.Count < calibrationSamples) { if (serialPort != null && serialPort.IsOpen && serialPort.BytesToRead > 0) { try { string data = serialPort.ReadLine().Trim(); if (float.TryParse( data, NumberStyles.Float, CultureInfo.InvariantCulture, out float gz)) { calibrationData.Add(gz); Debug.Log( "校准采样 " + calibrationData.Count + "/" + calibrationSamples + " 值:" + gz ); } } catch { } } yield return null; } float sum = 0f; foreach (float val in calibrationData) sum += val; gyroOffset = sum / calibrationData.Count; isCalibrated = true; Debug.Log( "✅ 校准完成!零点偏移:" + gyroOffset ); } bool TryOpenPort() { try { serialPort = new SerialPort( portName, baudRate ); serialPort.ReadTimeout = 5; serialPort.Open(); return true; } catch (System.Exception e) { Debug.LogWarning( "串口错误:" + e.Message ); return false; } } void Update() { if (isStopped) return; if (!isCalibrated) return; if (serialPort != null && serialPort.IsOpen && serialPort.BytesToRead > 0) { try { string data = serialPort.ReadLine().Trim(); if (float.TryParse( data, NumberStyles.Float, CultureInfo.InvariantCulture, out float gz)) { // 减去零点偏移 float correctedGz = gz - gyroOffset; // 死区过滤 if (Mathf.Abs(correctedGz) < gyroDeadZone) correctedGz = 0f; currentGyroZ = correctedGz; } else if (data == "RESTART") { ArduinoInput arduinoInput = FindObjectOfType (); if (arduinoInput != null) arduinoInput .HandleData(data); } } catch { } } // 加负号反转旋转方向 transform.Rotate( 0, 0, -currentGyroZ * rotateFactor * Time.deltaTime ); } void FixedUpdate() { if (isStopped) { rb.velocity = Vector2.zero; return; } rb.velocity = transform.right * moveSpeed; } public void StopAndTurnAround() { StartCoroutine(StopCoroutine()); } IEnumerator StopCoroutine() { isStopped = true; rb.velocity = Vector2.zero; currentGyroZ = 0f; yield break; } public void ResumeAfterAnimation() { StartCoroutine(TurnAndResume()); } IEnumerator TurnAndResume() { float startAngle = transform.eulerAngles.z; float targetAngle = startAngle + 180f; float duration = 0.5f; float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; float angle = Mathf.Lerp( startAngle, targetAngle, elapsed / duration ); transform.eulerAngles = new Vector3(0, 0, angle); yield return null; } transform.eulerAngles = new Vector3(0, 0, targetAngle); isStopped = false; currentGyroZ = 0f; } public bool IsStopped => isStopped; void OnDestroy() { CloseSerialPort(); } void OnApplicationQuit() { CloseSerialPort(); } void CloseSerialPort() { if (serialPort != null && serialPort.IsOpen) { serialPort.Close(); serialPort = null; Debug.Log("✅ 串口已关闭"); } } }