Lovely lady into cute and cozy things, coffee, books, games, and music. Also lead engineer for Slime Rancher 2.

 

🏳️‍⚧️ Trans 🏳️‍🌈 Gay ♾️ Neurodivergent

 

All views are my own.



One thing I'm doing on my current project (called "Rose") that's quite different is I'm starting from the editor instead of the game. I'm doing this for a few reasons:

  • I want to avoid hard coding things into the game as much as possible so I don't end up creating a bunch of weird coupling that's hard to break later
  • I want to explore the editor creation process because I think good tools are really interesting
  • I eventually want to build some more interesting editor tech such as CSG for level editing, visual scripting with nodes (like Blueprints), and more

At this point I've gotten a pretty reasonable base and I'm very excited to keep building and sharing more about it. Keep on reading if you want to get into some technical details about what I've got so far.


I've got my viewport window which shows me the current view and allows for first person style navigation when I hold middle mouse button down.

I've got a console window hooked up to my custom logging which shows me the last 1000 lines logged. This was pretty straightforward once I added a simple ring buffer to my log to keep track of some finite amount of past logs.

I've got a very basic hierarchy window that shows all the entities in my world. I'm currently using EnTT for my ECS so this is printing out all the entities in the game. It uses a NameComponent to know what to display, falling back to some generic numeric name if that component isn't present on the entity.

The inspector window is my most recent addition and is shaping up well, though needs a lot of polish. I'm using EnTT's reflection system to dynamically render the components for the selected entity. Since this was somewhat complicated I'll explain a bit here for other folks looking to do something similar.

First I register my component types with the reflection system using a custom prop for the display name of each field:

entt::meta<Transform>()
	.data<&Transform::position>("position"_hs).prop("display_name"_hs, "Position")
	.data<&Transform::rotation>("rotation"_hs).prop("display_name"_hs, "Rotation")
	.data<&Transform::scale>("scale"_hs).prop("display_name"_hs, "Scale");

I also have a static map that goes from field types to my inspector functions. This is how I know which widgets to render for any given field

void inspect_vector3_field(entt::meta_data& field, entt::meta_any& component_data)
{
	auto v = field.get(component_data).cast<Vector3f>();
	if (ImGui::DragFloat3(field.prop("display_name"_hs).value().cast<char const*>(), &v.x, 0.1f))
	{
		field.set(component_data, v);
	}
}

void inspect_quaternion_field(entt::meta_data& field, entt::meta_any& component_data)
{
	auto quat = normalize(field.get(component_data).cast<Quaternion>());
	auto v = radians_to_degrees(to_euler_angles(quat));
	if (ImGui::DragFloat3(field.prop("display_name"_hs).value().cast<char const*>(), &v.x, 0.1f))
	{
		v = degrees_to_radians(v);
		field.set(component_data, normalize(Quaternion::create_from_yaw_pitch_roll(v.y, v.x, v.z)));
	}
}

using FieldInspectorFn = void (*)(entt::meta_data& field, entt::meta_any& component_data);

std::unordered_map<entt::id_type, FieldInspectorFn> g_data_inspectors
{
	{ entt::type_id<Vector3f>().hash(), inspect_vector3_field },
	{ entt::type_id<Quaternion>().hash(), inspect_quaternion_field },
};

With those in place I can render my inspector.

void inspector_window()
{
	auto& editor = g_world.ctx().get<SingletonEditor>();

	if (ImGui::Begin("Inspector"))
	{
		if (editor.selection.size() > 1)
		{
			ImGui::Text("Editing multiple entities is not currently supported");
		}
		else if (editor.selection.size() == 1)
		{
			auto entity = *editor.selection.begin();

			for (auto&& curr : g_world.storage())
			{
				if (curr.second.contains(entity))
				{
					entt::id_type component_type_id = curr.first;
					auto type = entt::resolve(component_type_id);
					if (type)
					{
						auto component_data = type.from_void(curr.second.get(entity));

						std::string component_type{ type.info().name() };
						if (ImGui::CollapsingHeader(component_type.c_str(), ImGuiTreeNodeFlags_DefaultOpen))
						{
							for (auto&& data : type.data())
							{
								auto& field = data.second;

								auto itr = g_data_inspectors.find(field.type().info().hash());
								if (itr != g_data_inspectors.end())
								{
									itr->second(field, component_data);
								}
								else
								{
									ImGui::Text(field.prop("display_name"_hs).value().cast<char const*>());
								}
							}
						}
					}
				}
			}
		}
	}

	ImGui::End();
}

This is a little complicated looking but it's iterating through all the storage in EnTT (for components) to find any components for the entity. When it finds those it attempts to resolve the metadata for the component. If it does that we render a section for the component. Then it iterates all the fields of the component and looks up the inspector functions. If it doesn't find one, currently I just print the name of the field but that's mostly a WIP thing and I'll probably just assert that there is an inspector function for each field instead.

So yeah that's where I'm at so far. Next up I'm going to add some 3D gizmos and a grid to the scene view as well as hiding editor specific entities (like my editor camera) from the hierarchy. After that I want to start putting in some basic gameplay logic so I can test out switching from editor mode to game mode.

So exciting!


You must log in to comment.