🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 太空工程师编程笔记5 - 运动与姿态 ### 飞行控制 无人机是游戏里相当有意思一部分,不论是采矿无人机,还是战斗无人机,或者仅仅是货运无人机,都是可以实现的。除去无人机外,程序还可以实现飞船的辅助飞行控制,比如说驱动重力引擎,让重力引擎和普通引擎一样可以随鼠标与方向键控制。 实现这些,都需要通过编程去调整运动和姿态。具体来说,就是感知当前状态并控制加速和旋转。 核心的操作就是控制推子与重力发生器、控制陀螺仪、以及各类坐标转换。 比如说,如果你要让无人机飞向某个坐标或达到某个速度,那么就要获取到当前运动状态,得到和目标的偏差,然后把偏差转换成自身的上下左右前后需要的变化,随后控制六个方向的引擎出力,最终修正当前运动状态至目标状态。当然,这个过程也需要控制陀螺仪以实现正确的旋转和指向。 要说最复杂的地方,大概是一些坐标转换和控制算法吧。块的控制其实很容易。 ### 编程 #### 接管飞行控制-引擎 手动进行六向引擎控制,并检测驾驶块的运动指示进行运动。 ~~~csharp public Program() { Runtime.UpdateFrequency = UpdateFrequency.Update1; } public void Main(string argument, UpdateType updateSource) { //获取当前玩家所在驾驶舱 List<IMyShipController> cockpits=new List<IMyShipController>(); GridTerminalSystem.GetBlocksOfType<IMyShipController>(cockpits,Controller=>Controller.IsUnderControl); if(cockpits.Count<1)return; IMyShipController cockpit=cockpits[0]; Vector3D targetThrustPower=new Vector3D(cockpit.MoveIndicator.X*100f,cockpit.MoveIndicator.Y*100f,cockpit.MoveIndicator.Z*100f); //自动减速(简单) //转换为相对速度(坐标系转换) MatrixD refLookAtMatrix =MatrixD.CreateLookAt(Vector3D.Zero,cockpit.WorldMatrix.Forward,cockpit.WorldMatrix.Up); if(cockpit.DampenersOverride&&targetThrustPower==Vector3D.Zero)targetThrustPower=-Vector3D.TransformNormal(cockpit.GetShipVelocities().LinearVelocity,refLookAtMatrix); cockpit.IsMainCockpit=false; log("引擎控制接管中...\n"+targetThrustPower.ToString()); //获取所有引擎 List<IMyThrust> thrusts=new List<IMyThrust>(); GridTerminalSystem.GetBlocksOfType<IMyThrust>(thrusts); if(thrusts.Count<1)return; //确定引擎方向 foreach (var thrust in thrusts) { //判断引擎喷口对于驾驶舱的相对方向 switch (cockpit.WorldMatrix.GetClosestDirection(thrust.WorldMatrix.Backward)) { //设定推力 case Base6Directions.Direction.Backward: thrust.CustomName="Backward"; thrust.ThrustOverridePercentage=(targetThrustPower.Z>0)?(float)targetThrustPower.Z:0; thrust.Enabled=(targetThrustPower.Z>0)?true:false; break; case Base6Directions.Direction.Forward: thrust.CustomName="Forward"; thrust.ThrustOverridePercentage=(targetThrustPower.Z<0)?(float)-targetThrustPower.Z:0; thrust.Enabled=(targetThrustPower.Z<0)?true:false; break; case Base6Directions.Direction.Right: thrust.CustomName="Right"; thrust.ThrustOverridePercentage=(targetThrustPower.X>0)?(float)targetThrustPower.X:0; thrust.Enabled=(targetThrustPower.X>0)?true:false; break; case Base6Directions.Direction.Left: thrust.CustomName="Left"; thrust.ThrustOverridePercentage=(targetThrustPower.X<0)?(float)-targetThrustPower.X:0; thrust.Enabled=(targetThrustPower.X<0)?true:false; break; case Base6Directions.Direction.Up: thrust.CustomName="Up"; thrust.ThrustOverridePercentage=(targetThrustPower.Y>0)?(float)targetThrustPower.Y:0; thrust.Enabled=(targetThrustPower.Y>0)?true:false; break; case Base6Directions.Direction.Down: thrust.CustomName="Down"; thrust.ThrustOverridePercentage=(targetThrustPower.Y<0)?(float)-targetThrustPower.Y:0; thrust.Enabled=(targetThrustPower.Y<0)?true:false; break; } } } private void log(string log){ IMyTextPanel debug=(IMyTextPanel)GridTerminalSystem.GetBlockWithName("debug"); if(debug==null)Echo(log); else { debug.ShowPublicTextOnScreen(); debug.WritePublicText(log); } } ~~~ **执行结果** 程序接管并模拟实现了通常的推进器控制。 这里面主要要注意是,在游戏中,右上后是正方向,左下前是负方向,虽然这就是三维坐标系,但有时相当诡异。 #### 接管飞行控制-旋转 手动进行三向旋转控制,并根据驾驶舱和旋转速度进行调整。 不得不说,要真正搞明白陀螺仪的控制是需要一些数学基础,要熟悉Pitch/Yaw/Roll这种旋转坐标系,并且要熟悉坐标变换。不过变换的基本思路很简单,将驾驶舱的旋转参数变换飞船的旋转参数,再变换成陀螺仪的旋转参数。 ~~~csharp public Program() { Runtime.UpdateFrequency = UpdateFrequency.Update1; } public void Main(string argument, UpdateType updateSource) { //获取当前玩家所在驾驶舱 List<IMyShipController> cockpits=new List<IMyShipController>(); GridTerminalSystem.GetBlocksOfType<IMyShipController>(cockpits,Controller=>Controller.IsUnderControl); if(cockpits.Count<1)return; IMyShipController cockpit=cockpits[0]; cockpit.IsMainCockpit=false; Vector3 targetPYR=new Vector3(cockpit.RotationIndicator,cockpit.RollIndicator); log("陀螺仪控制接管中...\n"+targetPYR.ToString()); //转换为飞船旋转参数(坐标系转换) Matrix cockpitTransform; cockpit.Orientation.GetMatrix(out cockpitTransform); Vector3 GridPYR=Vector3.Transform(targetPYR,Matrix.Transpose(cockpitTransform)); //获取所有引擎 List<IMyGyro> gyros=new List<IMyGyro>(); GridTerminalSystem.GetBlocksOfType<IMyGyro>(gyros,block=>block.Enabled); if(gyros.Count<1)return; foreach (var gyro in gyros) { //转换为陀螺仪旋转参数 Matrix gyroTransform; gyro.Orientation.GetMatrix(out gyroTransform); Vector3 gyroPYR=Vector3.Transform(GridPYR,Matrix.Invert(gyroTransform)); gyro.Pitch=gyroPYR.X; gyro.Yaw=gyroPYR.Y; gyro.Roll=gyroPYR.Z; } } private void log(string log){ IMyTextPanel debug=(IMyTextPanel)GridTerminalSystem.GetBlockWithName("debug"); if(debug==null)Echo(log); else { debug.ShowPublicTextOnScreen(); debug.WritePublicText(log); } } ~~~ **执行结果** 程序接管了并模拟实现了通常的旋转控制。 #### 含重力引擎的飞控程序(基本型) 重力引擎的操作和一般的推进器无二,只是没有被接入游戏内飞控而已。 写这个飞控用到了C#的拓展方法语法,目的是让代码更加可读。 ~~~csharp public Program() { Runtime.UpdateFrequency = UpdateFrequency.Update1; } public void Main(string argument, UpdateType updateSource) { //获取当前玩家所在驾驶舱 List<IMyShipController> cockpits=new List<IMyShipController>(); GridTerminalSystem.GetBlocksOfType<IMyShipController>(cockpits,Controller=>Controller.IsUnderControl); if(cockpits.Count<1)return; IMyShipController cockpit=cockpits[0]; //获取运动参数 cockpit.IsMainCockpit=false; Vector3 targetPYR=cockpit.PYRIndicator(); Vector3 targetThrustPower=new Vector3D(cockpit.MoveIndicator.X*100f,cockpit.MoveIndicator.Y*100f,cockpit.MoveIndicator.Z*100f); MatrixD refLookAtMatrix =MatrixD.CreateLookAt(Vector3D.Zero,cockpit.WorldMatrix.Forward,cockpit.WorldMatrix.Up); //线速度减速(简单) if(cockpit.DampenersOverride&&targetThrustPower==Vector3D.Zero)targetThrustPower=-Vector3D.TransformNormal(cockpit.GetShipVelocities().LinearVelocity,refLookAtMatrix); //角速度减速(简单) targetPYR+=Vector3D.TransformNormal(cockpit.GetShipVelocities().AngularVelocity,refLookAtMatrix)*0.5; log("飞行控制接管中...\n"+targetPYR.ToString()+"\n"+Vector3D.TransformNormal(cockpit.GetShipVelocities().AngularVelocity,refLookAtMatrix)); //转换为飞船运动参数(坐标系转换) Matrix cockpitTransform; cockpit.Orientation.GetMatrix(out cockpitTransform); Vector3 GridPYR=Vector3.Transform(targetPYR,cockpitTransform); Vector3 GridOutput=Vector3.Transform(targetThrustPower,cockpitTransform); //获取并设置推进器和陀螺仪 List<IMyGyro> gyros=new List<IMyGyro>(); GridTerminalSystem.GetBlocksOfType<IMyGyro>(gyros,block=>block.Enabled); gyros.setPYR(GridPYR); List<IMyThrust> thrusts=new List<IMyThrust>(); GridTerminalSystem.GetBlocksOfType<IMyThrust>(thrusts); thrusts.setOutput(GridOutput); //重力引擎 List<IMyGravityGenerator> gravities=new List<IMyGravityGenerator>(); GridTerminalSystem.GetBlocksOfType<IMyGravityGenerator>(gravities); gravities.setOutput(GridOutput); } private void log(string log){ IMyTextPanel debug=(IMyTextPanel)GridTerminalSystem.GetBlockWithName("debug"); if(debug==null)Echo(log); else { debug.ShowPublicTextOnScreen(); debug.WritePublicText(log); } } } public static class Utils{ public static Vector3 PYRIndicator(this IMyShipController controller){ return new Vector3(controller.RotationIndicator,controller.RollIndicator); } public static void setPYR(this List<IMyGyro> gyros,Vector3 GridPYR){ foreach (var gyro in gyros) { //转换为陀螺仪旋转参数 Matrix gyroTransform; gyro.Orientation.GetMatrix(out gyroTransform); Vector3 gyroPYR=Vector3.Transform(GridPYR,Matrix.Invert(gyroTransform)); gyro.Pitch=gyro.Pitch*0.5f+gyroPYR.X; gyro.Yaw=gyro.Yaw*0.5f+gyroPYR.Y; gyro.Roll=gyro.Roll*0.5f+gyroPYR.Z; } } public static void setOutput(this List<IMyThrust> thursts,Vector3 GridOutput){ foreach (var thrust in thursts) { //转换为陀螺仪旋转参数 Matrix gyroTransform; thrust.Orientation.GetMatrix(out gyroTransform); Vector3 thrustOutput=Vector3.Transform(GridOutput,Matrix.Invert(gyroTransform)); thrust.ThrustOverridePercentage=(thrustOutput.Z>0)?(float)thrustOutput.Z:0; thrust.Enabled=(thrustOutput.Z>0)?true:false; } } public static void setOutput(this List<IMyGravityGenerator> gravities,Vector3 GridOutput){ foreach (var gravity in gravities) { //转换为陀螺仪旋转参数 Matrix gyroTransform; gravity.Orientation.GetMatrix(out gyroTransform); Vector3 thrustOutput=Vector3.Transform(GridOutput,Matrix.Invert(gyroTransform)); gravity.GravityAcceleration=-(float)thrustOutput.Y; gravity.Enabled=(Math.Abs(thrustOutput.Y)>0.01f)?true:false; } } ~~~ **执行结果** 重力引擎被接管成为飞行控制的一部分,可以随玩家操作而动。 引擎并不负责人工质量块的开关,可以通过开关人工质量块来开关引擎。 ### 参考 #### 关于人工重力块 部分API不在Sandbox.Common.dll里,而在SpaceEngineers.Game.dll里 ~~~csharp public interface IMyGravityGeneratorBase : IMyFunctionalBlock, IMyTerminalBlock, IMyCubeBlock, IMyEntity { // Properties [Obsolete("Use GravityAcceleration.")] float Gravity { get; } float GravityAcceleration { get; set; } } public interface IMyGravityGenerator : IMyGravityGeneratorBase, IMyFunctionalBlock, IMyTerminalBlock, IMyCubeBlock, IMyEntity { // Properties [Obsolete("Use FieldSize.X")] float FieldWidth { get; } [Obsolete("Use FieldSize.Y")] float FieldHeight { get; } [Obsolete("Use FieldSize.Z")] float FieldDepth { get; } Vector3 FieldSize { get; set; } } public interface IMySpaceBall : IMyVirtualMass, IMyFunctionalBlock, IMyTerminalBlock, IMyCubeBlock, IMyEntity { // Properties float Friction { get; set; } float Restitution { get; set; } [Obsolete("Use IMySpaceBall.Broadcasting")] bool IsBroadcasting { get; } bool Broadcasting { get; set; } float VirtualMass { get; set; } } ~~~ #### 关于陀螺仪 ~~~csharp public interface IMyGyro : IMyFunctionalBlock, IMyTerminalBlock, IMyCubeBlock, IMyEntity { // Properties float GyroPower { get; set; } bool GyroOverride { get; set; } float Yaw { get; set; } float Pitch { get; set; } float Roll { get; set; } } ~~~ #### 关于推进器 ~~~csharp public interface IMyThrust : IMyFunctionalBlock, IMyTerminalBlock, IMyCubeBlock, IMyEntity { // Properties float ThrustOverride { get; set; } float ThrustOverridePercentage { get; set; } float MaxThrust { get; } float MaxEffectiveThrust { get; } float CurrentThrust { get; } Vector3I GridThrustDirection { get; } } ~~~