我如何在Unigine收集轮物理

美好的一天。

这次我们将讨论俄罗斯Unigine引擎,以及如何在其中组装简单的机器。该方案不是理想的,因为我在某些方面还没有弄清楚,但是至少我会分享我的经验。



首先,要了解车轮的物理原理,我们去了官方文档并找到了这个例子

顺便说一下,请注意,这是车轮接头的示例,但由于某些原因,它位于地址/ car_suspension_joints /。您还需要确保文档不会跳到旧版本(即DOCS(2.11)已打开),并在文章顶部标记“ C#”框。

通过研究示例,我们可以看到它是用早期的C ++解决方案类推编写的。也就是说,一切都是通过代码创建的。对于可视化编程已经非常重要,我们已经到了。好吧,并不是所有的事情都那么令人难过,但是您可以在代码中看到所有这些是如何完成的。并在链接上运行后,全面了解了车轮关节装置和物理设置。
( ). , -, UnigineScript, . LOD'.
当然,我期望有一个关于视觉组装的现成的循序渐进指南,以便使某些东西滚动,而花费很少的血液。结果,我仍然不得不全神贯注于所有这些,以便制定出一些近似的管道。这部分获得了成功,但是在所有方面都还远未得到理解,并且仍然存在很多问题,例如,是否应将机壳和车轮节点构建到一定的层次结构中,这些点的轴是否正确定向等等。

通常,此说明将帮助您掌握基本原理,但是为了理解设置的复杂性,您仍然必须学习文档(或者也许它们会在注释中告诉您一些有用的信息)。

框架组装


1.创建一个新项目。选择C#选项。在编辑器中打开项目。







2.我们创建机器的基础。

为此,请右键单击场景,然后选择“ 创建” >“ 原始” >“ 框”。结果是一个长方体对象。将其略微升高到“地板”上方。



3.我们制造轮子。

再次单击鼠标,然后选择“ 创建” >“ 原始” >“ 圆柱体”。这将需要4件。



为了不与方向混淆,您可以转动相机,使-Y在立方体中从上方发光(也就是说,我们现在正在从后面看未来的机器),并将两个圆柱体放在长方体后面,另外两个圆柱体放在其前面。无需以某种方式旋转和自定义它们。为了方便起见,可以在背面和正面分配不同的颜色。

4.添加物理基础。

选择Cuboid,然后在左侧Node标签Parameters窗口中切换到Physics。在“主体”列中,选择“ 刚性类型” 。之后,向下滚动并在打开的Shapes列中选择Box 我们看到了出现的ShapeBox





。我们将进入其设置,并立即将基准设置为建议的质量(Mass)64千克。



5.向轮子添加物理特性。

依次选择圆柱体,然后将其设为“刚性 ”标签。
他们不需要创建形状,因为车轮接头可以在rakcast上使用。

6.将车轮系到底座上。

是时候分配车轮节了。为此,请正确选择长方体,然后向下滚动到“物理”选项卡中的“ 关节”。在列表中,选择“ 滚轮”,然后单击“ 添加”按钮,将出现选择车身”窗口。。在此处选择一个圆柱体,该圆柱体在场景中以白色网格突出显示,然后按“确定”。WheelJoint绑定将出现在列表中。在这里已经需要重命名它们(为此,您需要单击“ JointWheel”行并在“名称”列中写一个新名称)。





同样,将其余的圆柱体连接到长方体上。这样他的积分榜上最后就烧掉了4条线。


如果不重命名,则会有4行WheelJoint。

请注意,您需要从长方体进行绑定,而不是从圆柱体本身的“物理”选项卡中进行绑定。尽管在此之后,如果您选择一个特定的圆柱体并转到其“物理”,则其创建的关节也会显示在此处,您可以编辑其参数。

7.设置绑定的配置。

再次选择长方体,在物理学中找到其点的列表,然后选择其中之一。我们看到长方体内部的场景中出现了带有正方形的白色角。
在交界处的设置中,找到向量Anchor 0的图,并调整其三个字段的值,以使此角沿X轴移动,在长方体外部并沿Z轴稍微降低。





然后根据绑定,沿Y轴移动角当前选择哪个轮子。

我们对其余的点重复该过程,沿Z轴设置完全相同的位移,并且沿其他轴设置相同的方向,但多方向设置。

如果一次选择所有点,则整个设计将变得可见。您还可以在场景上方的特殊面板上启用助手中的关节显示:助手> 物理 > 关节


带有辅助工具的框架后视图

8.我们看看发生了什么。

在编辑器中,有一个用于计算物理的按钮。如果启用它,则将开始物理模拟(脚本不起作用),您可以检查科学怪人的行为。
开始之前,请确保在“楼层”设置中选中了“ 碰撞物理相交”已启用的复选框。否则,我们的创作可能完全在地板下或仅在轮子下完全失败。



注意,在开启仿真之前,最好保存一下,并且在其操作过程中更正的参数在断开连接后将恢复为它们的值。

如我们所见,车轮在正确的位置被拾起,但结构落在圆柱体末端的表面上。



您可以先在“身体”列的“物理”选项卡上启用“不动”复选框,将长方体自身冻结在适当的位置。

9.调整车轮的轮轴。

禁用物理模拟。选择长方体,转到其“物理”选项卡,为每个结点编辑轴00轴10轴11。请注意,在每个向量的字段总和中,取值从0到1,如果您首先将1放到新轴而不将向量清零,则编辑器将尝试自动更正值。

显然,我还没有开发出矢量,助手显示左,右部分的方向相同,但是它们没有清楚地了解哪个轴在哪里,这使得调整变得很困难。

无论如何,这种布局或多或少地起作用。在这里,我设置轴矢量如下:轴00(001),轴线10(100),轴11(001)。



如果现在开始物理模拟,则设计应落在正确旋转的圆柱体上,圆柱体随后将沿着正确的轴旋转。

10。车轮质量和行为,物理迭代。

最有可能的是,结构跌落到地面后现在部分失效。

首先,让我们进入物理学的一般设置。我们单击编辑器的第一行。Windows > 设置,然后在打开的设置选项卡中,找到物理(运行时/世界/)。我们在“ 迭代次数”字段中至少设置5个



我们再次进入每个关节的设置。
在列表的顶部,每个人都有一个Iteration字段,设置为8
线性恢复角度恢复设置为0.1
线性自更改-0.15,将线性更改0.15
在底部,我们车轮质量字段中将其25千克 当模拟开始时,物理“机器”仍然必须部分失败/失败。 将每个线性阻尼设置50。并将线性弹簧设置10 现在,在模拟过程中,结构应掉落并在地板上轻微反弹。如果这不起作用,请尝试使用线性阻尼和线性弹簧值。





文档建议设置阻尼400和弹簧100,但就个人而言,“机器”同时开始像直升机一样旋转,弹跳并飞走。

11.框架的最终设置。

保存并尝试单击“ 播放”(包含物理模拟的旁边的上方的按钮),以第一人称视角浏览场景。 “机器”最有可能悬在空中,但如果将其推入中央立方体,则会滚动。



现在,您可以在这些点上调整“ 锚点0”,以使它们与中心大约等距(尽管不是必需的)。然后开始仿真,并在其间更改线性弹簧以找到每个车轮正常接触地面的最佳值。

完成所有这些操作后,您应该使“机器”正确掉落到地板上,在游戏模式下按下时,该机器会略微滚动,默认情况下,您在具有第一人称视角的单独控制器上运行。



编写控制脚本



我们有一个空白的“打字机”,现在我们将在C#中为其编写控制代码。
为此,请在场景下的窗口中单击鼠标右键,然后选择“ 创建” >“ 创建C#组件”在出现的窗口中输入脚本的名称。





双击出现的脚本文件,开发环境开始。



选择图片中用蓝色填充的行,将其删除,然后添加以下代码,我在其中编辑了文档中建议的脚本:

脚本片段
	[ShowInEditor][Parameter(Tooltip = "Left Wheel")]
	private Node targetWheelL = null;
	[ShowInEditor][Parameter(Tooltip = "Right Wheel")]
	private Node targetWheelR = null;

	[ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
	private Node targetFWL = null;
	[ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
	private Node targetFWR = null;

	[ShowInEditor][Parameter(Tooltip = "theCar")]
	private Node targetCar = null;

    private JointWheel my_jointL;
    private JointWheel my_jointR;
    private JointWheel my_jointFL;
    private JointWheel my_jointFR;

	Controls controls = null;
	float angle = 0.0f;
	float velocity = 0.0f;
	float torque = 0.0f;

	private float ifps;

	private void Init()
	{
	my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;

	PlayerPersecutor player = new PlayerPersecutor();
	controls = player.Controls;
	}
	
	private void Update()
	{
			ifps = Game.IFps;

			if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
			{
				velocity = MathLib.Max(velocity, 0.0f);
				velocity += ifps * 50.0f;
				torque = 5.0f;
			}
			else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
			{
				velocity = MathLib.Min(velocity, 0.0f);
				velocity -= ifps * 50.0f;
				torque = 5.0f;
			}
			else
			{
				velocity *= MathLib.Exp(-ifps);
			}
			velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);

			if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
				angle += ifps * 100.0f;
			else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
				angle -= ifps * 100.0f;
			else
			{
				if (MathLib.Abs(angle) < 0.25f) angle = 0.0f;
				else angle -= MathLib.Sign(angle) * ifps * 45.0f;
			}
	
			angle = MathLib.Clamp(angle, -10.0f, 10.0f);

			float base_a = 3.3f;
			float width = 3.0f;
			float angle_0 = angle;
			float angle_1 = angle;
			if (MathLib.Abs(angle) > MathLib.EPSILON)
			{
				float radius = base_a / MathLib.Tan(angle * MathLib.DEG2RAD);
				angle_0 = MathLib.Atan(base_a / (radius + width / 2.0f)) * MathLib.RAD2DEG;
				angle_1 = MathLib.Atan(base_a / (radius - width / 2.0f)) * MathLib.RAD2DEG;
			}

			my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
			my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

			if (controls.GetState(Controls.STATE_USE) == 1)
			{
				velocity = 0.0f;
					my_jointL.AngularDamping = 20000.0f;
					my_jointR.AngularDamping = 20000.0f;
					my_jointFL.AngularDamping = 20000.0f;
					my_jointFR.AngularDamping = 20000.0f;
			}
			else
			{
					my_jointL.AngularDamping = 0.0f;
					my_jointR.AngularDamping = 0.0f;
					my_jointFL.AngularDamping = 0.0f;
					my_jointFR.AngularDamping = 0.0f;
			}

			if (Input.IsKeyDown(Input.KEY.Q))
			{
				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
			}
			if (Input.IsKeyDown(Input.KEY.E))
			{
				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
			}

	}

		private void UpdatePhysics()
		{
			my_jointL.AngularVelocity = velocity;
			my_jointR.AngularVelocity = velocity;
			
			my_jointL.AngularTorque = torque;
			my_jointR.AngularTorque = torque;
		}



:

TheVehicle.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "ca695c8787d5703a22a6c2516a3c177cddf38cab")]
public class TheVehicle : Component
{

, , . , .

[ShowInEditor][Parameter(Tooltip = "Left Wheel")]
private Node targetWheelL = null;
[ShowInEditor][Parameter(Tooltip = "Right Wheel")]
private Node targetWheelR = null;

[ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
private Node targetFWL = null;
[ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
private Node targetFWR = null;

[ShowInEditor][Parameter(Tooltip = "theCar")]
private Node targetCar = null;

. tooltip , .

private JointWheel my_jointL;
private JointWheel my_jointR;
private JointWheel my_jointFL;
private JointWheel my_jointFR;

.

Controls controls = null;

float angle = 0.0f;
float velocity = 0.0f;
float torque = 0.0f;

private float ifps;

.

private void Init()
{
my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;

, JointWheel.
, , ( ).

			
// setting up player and controls
PlayerPersecutor player = new PlayerPersecutor();
controls = player.Controls;
}

, PlayerPresecutor, Controls player .

private void Update()
{
ifps = Game.IFps;

// forward and backward movement by setting joint motor's velocity and torque
			if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
			{
				velocity = MathLib.Max(velocity, 0.0f);
				velocity += ifps * 50.0f;
				torque = 5.0f;
			}//Input.IsKeyDown(Input.KEY.DOWN)
			else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
			{
				velocity = MathLib.Min(velocity, 0.0f);
				velocity -= ifps * 50.0f;
				torque = 5.0f;
			}
			else
			{
				velocity *= MathLib.Exp(-ifps);
			}
			velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);

			// steering left and right by changing Axis01 for front wheel joints
			if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
				angle += ifps * 100.0f;
			else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
				angle -= ifps * 100.0f;
			else
			{
				if (MathLib.Abs(angle) < 0.25f) angle = 0.0f;
				else angle -= MathLib.Sign(angle) * ifps * 45.0f;
			}

			angle = MathLib.Clamp(angle, -10.0f, 10.0f);//      30  (angle, -30.0f, 30.0f)

			// calculating steering angles for front joints (angle_0 and angle_1)
			float base_a = 3.3f;
			float width = 3.0f;
			float angle_0 = angle;
			float angle_1 = angle;
			if (MathLib.Abs(angle) > MathLib.EPSILON)
			{
				float radius = base_a / MathLib.Tan(angle * MathLib.DEG2RAD);
				angle_0 = MathLib.Atan(base_a / (radius + width / 2.0f)) * MathLib.RAD2DEG;
				angle_1 = MathLib.Atan(base_a / (radius - width / 2.0f)) * MathLib.RAD2DEG;
			}

Update, , .

my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

// enabling or disabling a brake
if (controls.GetState(Controls.STATE_USE) == 1)
{
velocity = 0.0f;
my_jointL.AngularDamping = 20000.0f;
my_jointR.AngularDamping = 20000.0f;
my_jointFL.AngularDamping = 20000.0f;
my_jointFR.AngularDamping = 20000.0f;
}
else
{
my_jointL.AngularDamping = 0.0f;
my_jointR.AngularDamping = 0.0f;
my_jointFL.AngularDamping = 0.0f;
my_jointFR.AngularDamping = 0.0f;
}

, , . , 4 . , , 10 .

if (Input.IsKeyDown(Input.KEY.Q))
{
targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
}

if (Input.IsKeyDown(Input.KEY.E))
{
targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
}

}

Update , . , — «this.» .

private void UpdatePhysics()
{
// set angular velocity for rear joints
my_jointL.AngularVelocity = velocity;
my_jointR.AngularVelocity = velocity;

// set torque for rear joints
my_jointL.AngularTorque = torque;
my_jointR.AngularTorque = torque;

}

}

Update Physics , velocity torque.


之后,保存脚本文件并返回到编辑器窗口。 Unigine将检查程序是否有错误,如果一切正常,则绿色的矩形应显示在右下角,并通知项目构建成功。

现在选择Cuboid,向其添加component字段并将我们的脚本拖到那里。出现一些字段,需要填写。在目标车轮L字段中,轻轻拖动左后轮(即表示它的圆柱体),目标Whell R-右后轮,目标FWL-左前,目标FWR-右前,以及长方体本身在目标车字段中。


要添加一个组件,我们在长方体属性中找到该字段并单击它,将出现一个


空行,您可以在其中拖动脚本文件(或通过文件夹图标找到其路径)。


填写“

运行播放”字段 -现在机器开始移动。没错,与此同时,您正在代表这个角色去看世界。

她的控制如下:W-向前滚动,S-向后滚动,AD-分别左右旋转前轮。一个冲动放弃了Q,机器,因为它是,跳跃。相反,E上,脉冲向下移动。

您可能会注意到机器像月亮一样缓慢下落。为了改善这一时刻,有两种选择。该文档建议对AppWorldLogic.cs文件进行更改。但是我们不需要这样做,因为我在打字机脚本中进行了初始化。除了统治全球物理学的几条线之外。

事实是您可以独立输入这些设置并进行更改。为此,请单击编辑器的顶行。 Windows>设置,在打开的窗口中,找到物理列,并将冻结线速度的值设置为0.1,冻结角速度的值也设置为0.1,并将最低重力向量从(0,0,-9,8)更改为(0,0,- 19.6)。

通过在Init中添加以下行,可以在代码中完成相同的操作:

Physics.Gravity = new vec3(0.0f, 0.0f, -9.8f * 2.0f);
Physics.FrozenLinearVelocity = 0.1f;
Physics.FrozenAngularVelocity = 0.1f;

如果立即开始播放,则“机器”可能会再次变质并开始出现故障。要解决此问题,请尝试在每个车轮上旋转线性弹簧。提高两次以开始。设置好后,您会注意到跳跃过程中的重力更强(尽管默认情况下我将跳跃动量设置为很高)。

既然您在设置此喜怒无常的购物车时已经弄湿了,是时候放下firts_person_controller的束缚,并使用第三人称视角从机器本身的侧面观察发生了什么。
已下载,禁用first_person_controller(单击他的名字旁边的框,使其变为空)。
创建一个相机。为此,请右键单击场景,然后选择“创建”>“相机”>“ Presecutor”。将显示PlayerPresecutor摄像机本身及其虚拟目标Presecutor_Target。
假人可以关闭。我们单击相机本身,请确保选中其属性中的Main Player复选框(以免启动时出现黑屏)。在目标节点字段中,我们在下拉列表中找到长方体。

保存并单击播放。

现在您可以完全骑车了。没错,相机可以飞很远。要解决此问题,您需要调整其限制。我们返回到编辑器并在摄影机属性中进行设置,例如,最小距离10和最大距离30。

Unigine引擎中的Neungers


学会了收集近似的车轮物理知识后,我使用Nevanger项目中的生物回声模型制作了一个测试场景。这是现在的样子:



屏幕截图







目前的主要问题是设计的不稳定。这又是由于我尚未完全了解设置的所有方面和细微差别。有时,机器偶然发现某些东西并开始剧烈旋转。虽然我还没有扭曲或配置过一些东西,但是减少线性阻尼似乎也无济于事,也可以扭曲其他参数,但这并不全面。

另一个错误-在某些情况下,前车轮粘在地板上,而后车轮在地面下,总卡在地板上。或高速穿过景观本身,然后在其下方飞走。所有这些显然都可以通过设置公差和迭代进行校正,但是到目前为止,尚未找到最佳值。

通常,在相当平坦的表面上,运动已经足够了。正如我在过时的汽车视频演示中(似乎)所了解的那样,尚不清楚车轮接头在理想的情况下在不平整表面上的性能如何,但实际上是“诚实的”悬挂接头(顺便说一句,我没有尝试在它们上组装机器) ,那里的一切都稍微容易一些)。“车轮节”是否考虑了地形的摩擦参数,还是只对是否打开了物理标记感兴趣。

引擎的一般印象


至于使用编辑器,发生的事情使我最想起使用CryEngine的过程。尽管在某些地方更方便。就像有人一样,在同一个CryEngine中,可以直接在编辑器中建模。这对我而言无关紧要,因为为此,我使用Blender,在这里您可以对模型进行更多的控制,而对于某些人来说,这可能很重要。

在下面的图片中,左侧显示材料清单。网格是通过一组基于网格的材料绘制的,您可以从中克隆您的材料选项。用纹理喷涂地形已经很困难,但是蒙版已被完全使用-如果您了解设备,则可以混合纹理,高度图等。对于相同的水,有其自己的单独的材料组,只需单击一下就可以将其逐字添加,并且看起来还不错。

右边是场景对象和属性窗口的层次结构。快速操作数值有点不方便-您需要使用小箭头进入图标。某些滑块最初会产生误导,因为它们没有显示参数的整个可能范围,而是仅显示从0到1的间隔。摄像机的飞行速度是在田野里有个奔跑的人的情况下进行调节的,默认情况下,它会以某种感觉缓慢地移动。实际使用模型时,这可能更方便。


All Articles