这算是一个忙了大半年的比赛项目了,终于完成了来补个文章Orz
先上效果:
万有引力
首先要让星体之间能相互吸引,互相施加一个万有引力。这个我实现起来就很粗暴了,直接用过刚体施加一个力:
//计算万有引力数值 private float CalculateGravityModulus(float targetMass, float distance) { return PhysicBase.GetG() * (GetMass() * targetMass / (distance * distance)); }
//计算万有引力
private Vector3 GetGravityVector3(Rigidbody rigidbody)
{
var distance = Vector3.Distance(transform.position, rigidbody.position);
var normalizedDirection = (transform.position - rigidbody.position).normalized;
return CalculateGravityModulus(rigidbody.mass, distance) * normalizedDirection;
}
不过由于一个星球会受到好几个其他星球的影响,这里建了一个List来计算合力,用Trigger来判断是否计算两个星球之间的引力:
// 对之产生影响的星球
public List<AstralBody> affectedPlanets = new List<AstralBody>();private void OnTriggerEnter(Collider other)
{
var astral = other.GetComponent<AstralBody>();
if (astral != null &&
!other.isTrigger &&
enableAffect &&
!astral.banAffectedPlanets.Contains(this) &&
!astral.affectedPlanets.Contains(this))
astral.affectedPlanets.Add(this);
}private void OnTriggerExit(Collider other)
{
var astral = other.GetComponent<AstralBody>();
if (astral != null && enableAffect && astral.affectedPlanets.Contains(astral))
astral.affectedPlanets.Remove(this);
}//计算受力
public Vector3 CalculateForce()
{
var forceResult = new Vector3(0, 0, 0);
foreach (var astralBody in affectedPlanets)
forceResult += astralBody.GetGravityVector3(astralBodyRigidbody);return forceResult;
}
然后直接暴力运算:
protected virtual void FixedUpdate()
{
if (!astralBodyRigidbody.isKinematic) lastVelocity = astralBodyRigidbody.velocity;
astralBodyRigidbody.AddForce(CalculateForce());
}
效果还行:
轨道预测
接下来就是重点了,怎么根据已知星球信息推出它在固定时间之后的位置呢?
我这里的解决方案是,将所有的星球的质量、速度、坐标、影响星球各种信息存在一起,预测时做这样的计算:
- 根据星球各自的位置和当前速度,用匀速直线运动近似,计算其在n秒后所处的位置。
- 根据每个星球各自的坐标和质量计算它们各自当前受力。
- 用加速度公式计算星球在进行前面那段位移后速度的改变量,得出新速度。
- 重复上面三步。
//开始采样
for (var i = 0; i < sample; i++)
//遍历星体
{
foreach (var astralBody in _astralBodies)
{
//加速度
/*
* F=ma
* delta v=at
* s = vt + 0.5a(t^2)
*/
var acceleration = CalculateForce(astralBody, i, astralBodyMasses) / astralBodyMasses[astralBody];_orbitPoints[astralBody].Add(_orbitPoints[astralBody].Last() + astralBodyVelocities[astralBody] * _deltaTime + .5f * acceleration * Mathf.Pow(_deltaTime, 2)); //加速后速度 astralBodyVelocities[astralBody] += acceleration * _deltaTime; }
}
这部分计算如果全部放到主线程做真的就挺卡的,这里做了个多线程:
//引力步进
private void TraceGravity()
{
var astralBodyVelocities = new Dictionary<ITraceable, Vector3>();
foreach (var astralBody in _astralBodies)
{
astralBodyVelocities[astralBody] = astralBody.GetVelocity();
//起始点改为当前位置
if (_orbitPoints.ContainsKey(astralBody)) _orbitPoints[astralBody].Clear();
_orbitPoints[astralBody].Add(astralBody.GetPosition());
}
var astralBodyMasses = new Dictionary<ITraceable, float>();
_astralBodies.ForEach(a => astralBodyMasses.Add(a, a.GetMass()));
StartNewThread(new[]
{
astralBodyVelocities,
(object) astralBodyMasses
});
}private void StartNewThread(object[] dict)
{
_thread = new Thread(Sample);
_thread.Start(dict);
}
private void Sample(object objs)
{
var dicts = (object[]) objs;
var astralBodyVelocities = (Dictionary<ITraceable, Vector3>) dicts[0];
var astralBodyMasses = (Dictionary<ITraceable, float>) dicts[1];
//开始采样
for (var i = 0; i < sample; i++)
//遍历星体
{
foreach (var astralBody in _astralBodies)
{
//加速度
/*
* F=ma
* delta v=at
* s = vt + 0.5a(t^2)
*/
var acceleration = CalculateForce(astralBody, i, astralBodyMasses) / astralBodyMasses[astralBody];
_orbitPoints[astralBody].Add(_orbitPoints[astralBody].Last() +
astralBodyVelocities[astralBody] * _deltaTime +
.5f * acceleration * Mathf.Pow(_deltaTime, 2));
//加速后速度
astralBodyVelocities[astralBody] += acceleration * _deltaTime;
}
}
_actionTypes.Add(ActionType.Finished);
}
路径显示
显示其实挺容易的,用个LineRenderer把坐标全都放进去就可以了。后来发现了一个叫Dreamteck Splines的插件可以做自动平滑,输进去的点能少一些,之后在做轨道拟合的时候用的还挺爽的,不过当时工期紧张之前的代码就没有重构233
这里随便写了个Shader来做线的显示:
Shader "Line/UILine"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
[HDR]_Color("Color",Color) = (0,0,0,0)
_Speed ("Speed",Range(0,1)) = .5
_Density("Density",float) = 30
}
SubShader
{
Tags
{
"RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent"
}
ZTest Off
LOD 200
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma surface surf NoLight vertex:vert alpha noforwardaddfloat4 LightingNoLight(SurfaceOutput s, float3 lightDir, half3 viewDir, half atten) { float4 c; c.rgb = s.Albedo; c.a = s.Alpha; return c; } sampler2D _MainTex; fixed4 _SelfCol; fixed4 _Color; fixed _Speed; fixed _Density; struct Input { float2 uv_MainTex; float4 vertColor; }; void vert(inout appdata_full v, out Input o) { o.vertColor = v.color; o.uv_MainTex = v.texcoord; } void surf(Input IN, inout SurfaceOutput o) { fixed2 uv = float2(IN.uv_MainTex.x * _Density ,IN.uv_MainTex.y) + float2((IN.uv_MainTex.x- _Time.y * _Speed )+1,0); half4 c = tex2D(_MainTex, frac(uv)); o.Alpha = IN.vertColor.a * c.a; o.Albedo = c * _Color; } ENDCG } FallBack "Diffuse"
}
效果:
多个相互影响的星球放在一起也没有问题:
项目仓库:
HkingAuditore/Kepler