Gizmo
中级 程序员
Gizmo 是一种可以在组件上实现的工具,用于在设计师调整组件值时提供可视化的辅助。
下面是一个可以实现的完整示例:
using Stride.Core;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Engine.Gizmos;
using Stride.Graphics;
using Stride.Graphics.GeometricPrimitives;
using Stride.Rendering;
using Stride.Rendering.Materials;
using Stride.Rendering.Materials.ComputeColors;
// 我们将为下面这个类实现一个 Gizmo
public class MyScript : StartupScript
{
}
// 这个特性向引擎说明下面的 gizmo 类绑定到 'MyScript',
// Game Studio 会识别它,并为场景中的每个 'MyScript' 生成该类的实例
[GizmoComponent(typeof(MyScript), isMainGizmo:false/*为 true 时,实体上只有第一个为 true 的 gizmo 可见,为 false 表示始终可见*/)]
public class Gizmo : IEntityGizmo
{
private bool _selected, _enabled;
private MyScript _component;
private ModelComponent _model;
private Material _material, _materialOnSelect;
// 当编辑器视图设置中全局开启或关闭 gizmo 时,会设置这个属性
public bool IsEnabled
{
get
{
return _enabled;
}
set
{
_enabled = value;
_model.Enabled = _enabled;
}
}
// 视图设置面板中的大小滑块值
public float SizeFactor { get; set; }
// 当该组件所在实体被选中时,编辑器会设置这个属性
public bool IsSelected
{
get
{
return _selected;
}
set
{
_selected = value;
_model.Materials[0] = _selected ? _materialOnSelect : _material;
// 下面的逻辑让 gizmo 在设置开启时对所有组件显示,
// 关闭时只显示所选实体的 gizmo
// 删除这一行会让 gizmo 在设置关闭时即使被选中也不显示
_model.Enabled = _selected || _enabled;
}
}
// 此构造函数由编辑器调用,
// Gizmo 类必须包含一个带单个组件类型参数的构造函数
public Gizmo(MyScript component)
{
_component = component;
}
public bool HandlesComponentId(OpaqueComponentId pickedComponentId, out Entity? selection)
{
// 当在场景中对 gizmo 进行场景拾取/鼠标点击时会调用此函数,
// 引擎会逐个调用每个 gizmo,gizmo 又通过返回 true 来通知引擎
// 该组件来自它们,并向编辑器提供此 gizmo 所代表的对应实体
if (pickedComponentId.Match(_model))
{
selection = _component.Entity;
return true;
}
selection = null;
return false;
}
public void Initialize(IServiceRegistry services, Scene editorScene)
{
// 作为初始化的一部分,我们在编辑器场景中创建一个模型来可视化 gizmo
var graphicsDevice = services.GetSafeServiceAs<IGraphicsDeviceService>().GraphicsDevice;
// 在我们的例子中,我们直接依赖 GeometricPrimitive API 为我们创建一个球体
// 你不必立即创建它,可以延迟到组件处于合适的状态时再创建
// 你也可以在下面的 Update() 函数中动态地创建和更新它
var sphere = GeometricPrimitive.Sphere.New(graphicsDevice);
var vertexBuffer = sphere.VertexBuffer;
var indexBuffer = sphere.IndexBuffer;
var vertexBufferBinding = new VertexBufferBinding(vertexBuffer, new VertexPositionNormalTexture().GetLayout(), vertexBuffer.ElementCount);
var indexBufferBinding = new IndexBufferBinding(indexBuffer, sphere.IsIndex32Bits, indexBuffer.ElementCount);
_material = Material.New(graphicsDevice, new MaterialDescriptor
{
Attributes =
{
Emissive = new MaterialEmissiveMapFeature(new ComputeColor(new Color4(0.25f,0.75f,0.25f,0.05f).ToColorSpace(graphicsDevice.ColorSpace))) { UseAlpha = true },
Transparency = new MaterialTransparencyBlendFeature()
},
});
_materialOnSelect = Material.New(graphicsDevice, new MaterialDescriptor
{
Attributes =
{
Emissive = new MaterialEmissiveMapFeature(new ComputeColor(new Color4(0.25f,0.75f,0.25f,0.5f).ToColorSpace(graphicsDevice.ColorSpace))) { UseAlpha = true },
Transparency = new MaterialTransparencyBlendFeature()
},
});
_model = new ModelComponent
{
Model = new Model
{
(_selected ? _materialOnSelect : _material),
new Mesh
{
Draw = new MeshDraw
{
StartLocation = 0,
// 你可以换成 LineList 或 LineStrip,以线框模式显示模型,
// 不过需要相应地调整你的索引缓冲区类型
PrimitiveType = PrimitiveType.TriangleList,
VertexBuffers = new[] { vertexBufferBinding },
IndexBuffer = indexBufferBinding,
DrawCount = indexBuffer.ElementCount,
}
}
},
RenderGroup = IEntityGizmo.PickingRenderGroup, // 这个 RenderGroup 允许场景拾取/选择,如果不想要选择功能请换用另一个
Enabled = _selected || _enabled
};
var entity = new Entity($"{nameof(Gizmo)} for {_component.Entity.Name}"){ _model };
entity.Transform.UseTRS = false; // 在本例中我们直接控制矩阵
entity.Scene = editorScene;
vertexBuffer.DisposeBy(entity);
indexBuffer.DisposeBy(entity); // 将缓冲区附加到实体上,以便稍后手动释放
}
public void Dispose()
{
_model.Entity.Scene = null;
_model.Entity.Dispose(); // 清除我们上面附加的两个缓冲区
}
public void Update()
{
// 在这里根据 MyScript 的状态更新 gizmo 的外观
// 这里我们只是确保 gizmo 跟随它所代表的实体移动,
// 注意上面禁用了 UseTRS 以提升性能并确保不会出现世界空间的问题
_model.Entity.Transform.LocalMatrix = _component.Entity.Transform.WorldMatrix;
}
}
效果如下:

请注意,如果你在编辑器处于打开状态时添加了这个新的 gizmo,可能需要重启编辑器。