feat: added CustomizableCharacter class

To manage Poly Modular hero instances
main
Sara 2024-02-15 22:30:44 +01:00
parent 02dd1c4b61
commit 02506acf67
10 changed files with 264 additions and 218 deletions

View File

@ -1,13 +1,10 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://bycoxdvsaakl"]
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://dnpkeelcoonfh"]
[ext_resource type="Texture2D" uid="uid://dcjja0gufc40f" path="res://Models/Characters/Textures/PolygonFantasyHero_Texture_01_B.png" id="1_ysskd"]
[ext_resource type="Texture2D" uid="uid://clhhkyqcf5h8f" path="res://Models/Characters/Textures/FantasyRivals_Texture_01_A.png" id="1_soi1r"]
[resource]
resource_name = "lambert2"
diffuse_mode = 2
specular_mode = 2
albedo_texture = ExtResource("1_ysskd")
rim_tint = 1.0
grow_amount = -0.264
distance_fade_min_distance = 2.4
distance_fade_max_distance = 3.25
albedo_color = Color(0.906332, 0.906332, 0.906332, 1)
albedo_texture = ExtResource("1_soi1r")
metallic = 0.2
roughness = 0.8

View File

@ -0,0 +1,13 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://bycoxdvsaakl"]
[ext_resource type="Texture2D" uid="uid://dcjja0gufc40f" path="res://Models/Characters/Textures/PolygonFantasyHero_Texture_01_B.png" id="1_ysskd"]
[resource]
resource_name = "lambert2"
diffuse_mode = 2
specular_mode = 2
albedo_texture = ExtResource("1_ysskd")
rim_tint = 1.0
grow_amount = -0.264
distance_fade_min_distance = 2.4
distance_fade_max_distance = 3.25

View File

@ -13,8 +13,8 @@ dest_files=["res://.godot/imported/ModularCharacters.fbx-ce6e3e30d0ba5e348821aac
[params]
nodes/root_type=""
nodes/root_name="RootNode"
nodes/root_type="CustomizableCharacter"
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
meshes/ensure_tangents=true
@ -33,11 +33,11 @@ _subresources={
"materials": {
"lambert2": {
"use_external/enabled": true,
"use_external/path": "res://Models/Characters/Materials/lambert2.tres"
"use_external/path": "res://Models/Characters/Materials/mod_chars_1.tres"
},
"lambert3": {
"use_external/enabled": true,
"use_external/path": "res://Models/Characters/Materials/lambert3.tres"
"use_external/path": "res://Models/Characters/Materials/mod_chars_2.tres"
}
},
"nodes": {

View File

@ -375,32 +375,6 @@ radius = 0.548248
height = 1.85415
[node name="Player" type="Player"]
active_customization = {
"ArmLowerLeft": 2,
"ArmLowerRight": 3,
"ArmUpperLeft": 1,
"ArmUpperRight": 3,
"BackAttachment": 1,
"Ear": -1,
"ElbowAttachLeft": -1,
"ElbowAttachRight": -1,
"Eyebrow": 0,
"Hair": 13,
"HandLeft": 0,
"HandRight": 4,
"Head": 15,
"HeadCoverings": -1,
"HelmetAttachment": -1,
"Hips": 3,
"HipsAttachment": 3,
"KneeAttachLeft": -1,
"KneeAttachRight": -1,
"LegLeft": 12,
"LegRight": 12,
"ShoulderAttachLeft": 16,
"ShoulderAttachRight": -1,
"Torso": 5
}
slide_on_ceiling = false
[node name="AnimationTree" type="AnimationTree" parent="."]
@ -424,20 +398,46 @@ shape = SubResource("CapsuleShape3D_hqeid")
[node name="PlayerInput" type="PlayerInput" parent="."]
[node name="Model" parent="." instance=ExtResource("1_b4mv3")]
active_customization = {
"ArmLowerLeft": 5,
"ArmLowerRight": 5,
"ArmUpperLeft": 1,
"ArmUpperRight": 1,
"BackAttachment": -1,
"Ear": -1,
"ElbowAttachLeft": 1,
"ElbowAttachRight": 1,
"Eyebrow": 1,
"Hair": -1,
"HandLeft": 1,
"HandRight": 1,
"Head": 8,
"HeadCoverings": -1,
"HelmetAttachment": -1,
"Hips": 2,
"HipsAttachment": 5,
"KneeAttachLeft": -1,
"KneeAttachRight": -1,
"LegLeft": 6,
"LegRight": 6,
"ShoulderAttachLeft": -1,
"ShoulderAttachRight": -1,
"Torso": 6
}
[node name="GeneralSkeleton" parent="Model/RootNode" index="0"]
bones/0/rotation = Quaternion(-2.98023e-08, 5.55112e-17, 5.55112e-17, 1)
bones/1/position = Vector3(0.00655603, 0.823848, -0.0163162)
bones/1/rotation = Quaternion(-0.0184563, -0.0841674, 0.0119519, 0.996209)
bones/2/rotation = Quaternion(0.0087954, 0.00637039, 0.000268324, 0.999941)
bones/3/rotation = Quaternion(0.00863832, 0.00637029, 0.000271212, 0.999942)
bones/4/rotation = Quaternion(0.00889527, 0.00637052, 0.000266287, 0.99994)
bones/5/rotation = Quaternion(-0.0138771, 0.0325252, -0.0066613, 0.999352)
bones/6/rotation = Quaternion(-0.0138652, 0.0325115, -0.00665304, 0.999353)
bones/1/position = Vector3(0.0064089, 0.824015, -0.0161932)
bones/1/rotation = Quaternion(-0.0186555, -0.0848505, 0.0122633, 0.996144)
bones/2/rotation = Quaternion(0.00877745, 0.00637038, 0.000268667, 0.999941)
bones/3/rotation = Quaternion(0.00861796, 0.00637028, 0.0002716, 0.999943)
bones/4/rotation = Quaternion(0.0088558, 0.00637049, 0.000267043, 0.99994)
bones/5/rotation = Quaternion(-0.0139191, 0.0328765, -0.00679956, 0.999339)
bones/6/rotation = Quaternion(-0.0139146, 0.0328705, -0.00679594, 0.99934)
bones/10/rotation = Quaternion(0.519859, 0.470118, 0.477057, -0.530238)
bones/11/rotation = Quaternion(0.247026, 0.839357, -0.458215, -0.156513)
bones/12/rotation = Quaternion(-0.0388633, -0.696869, -0.0391896, 0.715072)
bones/13/rotation = Quaternion(-0.0420051, 0.558654, -0.140026, 0.816415)
bones/11/rotation = Quaternion(0.247455, 0.839095, -0.458569, -0.156207)
bones/12/rotation = Quaternion(-0.039877, -0.696571, -0.0402117, 0.715249)
bones/13/rotation = Quaternion(-0.0420627, 0.558109, -0.139609, 0.816857)
bones/14/rotation = Quaternion(-0.256269, 0.776338, 0.203807, 0.538599)
bones/15/rotation = Quaternion(0.14485, 0.51961, 0.145065, 0.829446)
bones/16/rotation = Quaternion(0.159353, 0.516644, 0.140377, 0.829446)
@ -448,9 +448,9 @@ bones/21/rotation = Quaternion(0.494946, 0.0399225, 0.106743, 0.861418)
bones/22/rotation = Quaternion(0.554517, 0.0271624, 0.0237916, 0.831389)
bones/23/rotation = Quaternion(0.554045, 0.0389721, 0.0175394, 0.831389)
bones/27/rotation = Quaternion(0.5168, -0.475173, -0.481158, -0.52499)
bones/28/rotation = Quaternion(-0.116079, 0.860613, -0.45194, 0.204012)
bones/29/rotation = Quaternion(0.0201014, 0.71151, -0.0202694, 0.702096)
bones/30/rotation = Quaternion(0.00817088, -0.59897, 0.0699655, 0.797667)
bones/28/rotation = Quaternion(-0.115133, 0.860735, -0.451588, 0.20481)
bones/29/rotation = Quaternion(0.0209625, 0.711686, -0.0211379, 0.701867)
bones/30/rotation = Quaternion(0.00773145, -0.600065, 0.0707188, 0.796782)
bones/31/rotation = Quaternion(-0.256268, -0.776338, -0.203807, 0.538599)
bones/32/rotation = Quaternion(0.14485, -0.51961, -0.145065, 0.829446)
bones/33/rotation = Quaternion(0.159352, -0.516644, -0.140377, 0.829446)
@ -460,19 +460,19 @@ bones/36/rotation = Quaternion(0.49701, -0.238709, -0.0692157, 0.831389)
bones/38/rotation = Quaternion(0.494946, -0.0399222, -0.106743, 0.861418)
bones/39/rotation = Quaternion(0.554517, -0.0271621, -0.0237913, 0.831389)
bones/40/rotation = Quaternion(0.554045, -0.0389717, -0.0175393, 0.831389)
bones/50/rotation = Quaternion(-0.083346, 0.046056, 0.991826, 0.0849313)
bones/50/rotation = Quaternion(-0.0826276, 0.0457204, 0.991888, 0.085091)
bones/50/scale = Vector3(1.00002, 0.999999, 1)
bones/51/rotation = Quaternion(0.00967535, 0.960162, -0.279261, -0.00298304)
bones/51/rotation = Quaternion(0.00967221, 0.960188, -0.27917, -0.00298206)
bones/51/scale = Vector3(1.00002, 0.999999, 1.00002)
bones/52/rotation = Quaternion(0.0779514, 0.536554, -0.840102, -0.0161721)
bones/52/rotation = Quaternion(0.0778185, 0.536789, -0.839965, -0.0161634)
bones/52/scale = Vector3(0.999948, 0.999973, 1.00001)
bones/54/rotation = Quaternion(9.7728e-07, 1, -1.50204e-05, 3.50609e-06)
bones/54/scale = Vector3(0.999999, 0.999986, 1.00002)
bones/56/rotation = Quaternion(0.187667, 0.133003, 0.969895, -0.0799738)
bones/56/rotation = Quaternion(0.188468, 0.132146, 0.969868, -0.079841)
bones/56/scale = Vector3(1.00002, 0.999999, 1)
bones/57/rotation = Quaternion(-0.0100425, 0.957016, -0.289844, 0.00309644)
bones/57/rotation = Quaternion(-0.0100069, 0.957327, -0.288819, 0.00308538)
bones/57/scale = Vector3(1.00001, 0.999999, 1.00001)
bones/58/rotation = Quaternion(-0.0689188, 0.610305, -0.788841, 0.0225531)
bones/58/rotation = Quaternion(-0.069029, 0.610718, -0.78851, 0.0226031)
bones/58/scale = Vector3(0.999956, 0.999987, 1.00001)
bones/60/rotation = Quaternion(-6.79618e-07, 1, -9.98378e-06, -2.30227e-06)
bones/60/scale = Vector3(0.999999, 0.999991, 1.00001)
@ -600,6 +600,9 @@ visible = false
[node name="Chr_Hair_13" parent="Model/RootNode/GeneralSkeleton" index="40"]
visible = false
[node name="Chr_Hair_14" parent="Model/RootNode/GeneralSkeleton" index="41"]
visible = false
[node name="Chr_Hair_15" parent="Model/RootNode/GeneralSkeleton" index="42"]
visible = false
@ -714,6 +717,9 @@ visible = false
[node name="Chr_BackAttachment_01" parent="Model/RootNode/GeneralSkeleton" index="79"]
visible = false
[node name="Chr_BackAttachment_02" parent="Model/RootNode/GeneralSkeleton" index="80"]
visible = false
[node name="Chr_BackAttachment_03" parent="Model/RootNode/GeneralSkeleton" index="81"]
visible = false
@ -864,6 +870,9 @@ visible = false
[node name="Chr_ShoulderAttachLeft_16" parent="Model/RootNode/GeneralSkeleton" index="130"]
visible = false
[node name="Chr_ShoulderAttachLeft_17" parent="Model/RootNode/GeneralSkeleton" index="131"]
visible = false
[node name="Chr_ShoulderAttachLeft_18" parent="Model/RootNode/GeneralSkeleton" index="132"]
visible = false
@ -879,9 +888,6 @@ visible = false
[node name="Chr_ElbowAttachRight_01" parent="Model/RootNode/GeneralSkeleton" index="136"]
visible = false
[node name="Chr_ElbowAttachRight_02" parent="Model/RootNode/GeneralSkeleton" index="137"]
visible = false
[node name="Chr_ElbowAttachRight_03" parent="Model/RootNode/GeneralSkeleton" index="138"]
visible = false
@ -897,9 +903,6 @@ visible = false
[node name="Chr_ElbowAttachLeft_01" parent="Model/RootNode/GeneralSkeleton" index="142"]
visible = false
[node name="Chr_ElbowAttachLeft_02" parent="Model/RootNode/GeneralSkeleton" index="143"]
visible = false
[node name="Chr_ElbowAttachLeft_03" parent="Model/RootNode/GeneralSkeleton" index="144"]
visible = false
@ -921,10 +924,10 @@ visible = false
[node name="Chr_HipsAttachment_03" parent="Model/RootNode/GeneralSkeleton" index="150"]
visible = false
[node name="Chr_HipsAttachment_05" parent="Model/RootNode/GeneralSkeleton" index="152"]
[node name="Chr_HipsAttachment_04" parent="Model/RootNode/GeneralSkeleton" index="151"]
visible = false
[node name="Chr_HipsAttachment_06" parent="Model/RootNode/GeneralSkeleton" index="153"]
[node name="Chr_HipsAttachment_05" parent="Model/RootNode/GeneralSkeleton" index="152"]
visible = false
[node name="Chr_HipsAttachment_07" parent="Model/RootNode/GeneralSkeleton" index="154"]
@ -1044,9 +1047,6 @@ visible = false
[node name="Chr_Head_No_Elements_Female_08" parent="Model/RootNode/GeneralSkeleton" index="192"]
visible = false
[node name="Chr_Head_No_Elements_Female_09" parent="Model/RootNode/GeneralSkeleton" index="193"]
visible = false
[node name="Chr_Head_No_Elements_Female_10" parent="Model/RootNode/GeneralSkeleton" index="194"]
visible = false
@ -1065,6 +1065,9 @@ visible = false
[node name="Chr_Head_Female_01" parent="Model/RootNode/GeneralSkeleton" index="199"]
visible = false
[node name="Chr_Head_Female_02" parent="Model/RootNode/GeneralSkeleton" index="200"]
visible = false
[node name="Chr_Head_Female_03" parent="Model/RootNode/GeneralSkeleton" index="201"]
visible = false
@ -1125,7 +1128,7 @@ visible = false
[node name="Chr_Head_Female_22" parent="Model/RootNode/GeneralSkeleton" index="220"]
visible = false
[node name="Chr_Eyebrow_Female_02" parent="Model/RootNode/GeneralSkeleton" index="222"]
[node name="Chr_Eyebrow_Female_01" parent="Model/RootNode/GeneralSkeleton" index="221"]
visible = false
[node name="Chr_Eyebrow_Female_03" parent="Model/RootNode/GeneralSkeleton" index="223"]
@ -1158,7 +1161,7 @@ visible = false
[node name="Chr_Torso_Female_04" parent="Model/RootNode/GeneralSkeleton" index="232"]
visible = false
[node name="Chr_Torso_Female_06" parent="Model/RootNode/GeneralSkeleton" index="234"]
[node name="Chr_Torso_Female_05" parent="Model/RootNode/GeneralSkeleton" index="233"]
visible = false
[node name="Chr_Torso_Female_07" parent="Model/RootNode/GeneralSkeleton" index="235"]
@ -1230,10 +1233,10 @@ visible = false
[node name="Chr_ArmUpperRight_Female_00" parent="Model/RootNode/GeneralSkeleton" index="257"]
visible = false
[node name="Chr_ArmUpperRight_Female_01" parent="Model/RootNode/GeneralSkeleton" index="258"]
[node name="Chr_ArmUpperRight_Female_02" parent="Model/RootNode/GeneralSkeleton" index="259"]
visible = false
[node name="Chr_ArmUpperRight_Female_02" parent="Model/RootNode/GeneralSkeleton" index="259"]
[node name="Chr_ArmUpperRight_Female_03" parent="Model/RootNode/GeneralSkeleton" index="260"]
visible = false
[node name="Chr_ArmUpperRight_Female_04" parent="Model/RootNode/GeneralSkeleton" index="261"]
@ -1356,10 +1359,10 @@ visible = false
[node name="Chr_ArmLowerRight_Female_02" parent="Model/RootNode/GeneralSkeleton" index="301"]
visible = false
[node name="Chr_ArmLowerRight_Female_04" parent="Model/RootNode/GeneralSkeleton" index="303"]
[node name="Chr_ArmLowerRight_Female_03" parent="Model/RootNode/GeneralSkeleton" index="302"]
visible = false
[node name="Chr_ArmLowerRight_Female_05" parent="Model/RootNode/GeneralSkeleton" index="304"]
[node name="Chr_ArmLowerRight_Female_04" parent="Model/RootNode/GeneralSkeleton" index="303"]
visible = false
[node name="Chr_ArmLowerRight_Female_06" parent="Model/RootNode/GeneralSkeleton" index="305"]
@ -1407,15 +1410,15 @@ visible = false
[node name="Chr_ArmLowerLeft_Female_01" parent="Model/RootNode/GeneralSkeleton" index="319"]
visible = false
[node name="Chr_ArmLowerLeft_Female_02" parent="Model/RootNode/GeneralSkeleton" index="320"]
visible = false
[node name="Chr_ArmLowerLeft_Female_03" parent="Model/RootNode/GeneralSkeleton" index="321"]
visible = false
[node name="Chr_ArmLowerLeft_Female_04" parent="Model/RootNode/GeneralSkeleton" index="322"]
visible = false
[node name="Chr_ArmLowerLeft_Female_05" parent="Model/RootNode/GeneralSkeleton" index="323"]
visible = false
[node name="Chr_ArmLowerLeft_Female_06" parent="Model/RootNode/GeneralSkeleton" index="324"]
visible = false
@ -1458,15 +1461,15 @@ visible = false
[node name="Chr_HandRight_Female_00" parent="Model/RootNode/GeneralSkeleton" index="337"]
visible = false
[node name="Chr_HandRight_Female_01" parent="Model/RootNode/GeneralSkeleton" index="338"]
visible = false
[node name="Chr_HandRight_Female_02" parent="Model/RootNode/GeneralSkeleton" index="339"]
visible = false
[node name="Chr_HandRight_Female_03" parent="Model/RootNode/GeneralSkeleton" index="340"]
visible = false
[node name="Chr_HandRight_Female_04" parent="Model/RootNode/GeneralSkeleton" index="341"]
visible = false
[node name="Chr_HandRight_Female_05" parent="Model/RootNode/GeneralSkeleton" index="342"]
visible = false
@ -1506,7 +1509,7 @@ visible = false
[node name="Chr_HandRight_Female_17" parent="Model/RootNode/GeneralSkeleton" index="354"]
visible = false
[node name="Chr_HandLeft_Female_01" parent="Model/RootNode/GeneralSkeleton" index="356"]
[node name="Chr_HandLeft_Female_00" parent="Model/RootNode/GeneralSkeleton" index="355"]
visible = false
[node name="Chr_HandLeft_Female_02" parent="Model/RootNode/GeneralSkeleton" index="357"]
@ -1563,7 +1566,7 @@ visible = false
[node name="Chr_Hips_Female_01" parent="Model/RootNode/GeneralSkeleton" index="374"]
visible = false
[node name="Chr_Hips_Female_02" parent="Model/RootNode/GeneralSkeleton" index="375"]
[node name="Chr_Hips_Female_03" parent="Model/RootNode/GeneralSkeleton" index="376"]
visible = false
[node name="Chr_Hips_Female_04" parent="Model/RootNode/GeneralSkeleton" index="377"]
@ -1659,9 +1662,6 @@ visible = false
[node name="Chr_LegRight_Female_05" parent="Model/RootNode/GeneralSkeleton" index="407"]
visible = false
[node name="Chr_LegRight_Female_06" parent="Model/RootNode/GeneralSkeleton" index="408"]
visible = false
[node name="Chr_LegRight_Female_07" parent="Model/RootNode/GeneralSkeleton" index="409"]
visible = false
@ -1677,6 +1677,9 @@ visible = false
[node name="Chr_LegRight_Female_11" parent="Model/RootNode/GeneralSkeleton" index="413"]
visible = false
[node name="Chr_LegRight_Female_12" parent="Model/RootNode/GeneralSkeleton" index="414"]
visible = false
[node name="Chr_LegRight_Female_13" parent="Model/RootNode/GeneralSkeleton" index="415"]
visible = false
@ -1716,9 +1719,6 @@ visible = false
[node name="Chr_LegLeft_Female_05" parent="Model/RootNode/GeneralSkeleton" index="427"]
visible = false
[node name="Chr_LegLeft_Female_06" parent="Model/RootNode/GeneralSkeleton" index="428"]
visible = false
[node name="Chr_LegLeft_Female_07" parent="Model/RootNode/GeneralSkeleton" index="429"]
visible = false
@ -1734,6 +1734,9 @@ visible = false
[node name="Chr_LegLeft_Female_11" parent="Model/RootNode/GeneralSkeleton" index="433"]
visible = false
[node name="Chr_LegLeft_Female_12" parent="Model/RootNode/GeneralSkeleton" index="434"]
visible = false
[node name="Chr_LegLeft_Female_13" parent="Model/RootNode/GeneralSkeleton" index="435"]
visible = false

View File

@ -0,0 +1,115 @@
#include "customizable_character.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "godot_macros.h"
namespace godot {
void CustomizableCharacter::_bind_methods() {
#define CLASSNAME CustomizableCharacter
GDPROPERTY(active_customization, Variant::DICTIONARY);
}
void CustomizableCharacter::set_customization_active(Node3D *node, bool active) {
node->set_visible(active);
node->set_process(active);
}
void CustomizableCharacter::_enter_tree() {
}
void CustomizableCharacter::_ready() {
this->customization_init();
}
void CustomizableCharacter::select_customization(String category, std::optional<size_t> index) {
if (this->customization.find(category) == this->customization.end())
return;
CustomizationState& pair = this->customization.at(category);
// invalid keys are stored as the size of the array
if (index.has_value() && index.value() >= pair.options.size())
index = std::nullopt;
// nothing changes
if (index == pair.currentSelected)
return;
// disable previous chosen option, if any
if (pair.currentSelected.has_value())
CustomizableCharacter::set_customization_active(pair.options.at(pair.currentSelected.value()), false);
// enable chosen option, if any
if (index.has_value())
CustomizableCharacter::set_customization_active(pair.options.at(index.value()), true);
pair.currentSelected = index;
}
Dictionary CustomizableCharacter::get_active_customization() const {
Dictionary result{};
// translate the map to a dictionary of categories -> indexes
for (std::pair<String, CustomizationState> const& pair : this->customization) {
result[pair.first] = pair.second.currentSelected.has_value()
? pair.second.currentSelected.value()
: -1; // an invalid key will always be -1
}
return result;
}
void CustomizableCharacter::set_active_customization(Dictionary value) {
for (size_t i = 0; i < value.size(); ++i) {
String key = value.keys()[i];
size_t selection = value[key];
// wether or not the a given key exists in the customization map
bool exists = this->customization.find(key) != this->customization.end();
// if not, it can be added, as long as the customization map has not yet
// been initialized by _ready
if (!exists && this->customizationParent == nullptr)
this->customization.insert({key, {std::optional{selection}, {}}});
// if the key does not exist in customization and _ready is already
// called, the key is not valid and discarted
else if (!exists)
return;
else
this->select_customization(key, selection >= 0
? std::optional{selection}
: std::nullopt);
}
}
void CustomizableCharacter::customization_init() {
this->customizationParent =
this->get_node<Skeleton3D>("RootNode/GeneralSkeleton");
if (this->customizationParent == nullptr)
return;
for (int i = 0; i < customizationParent->get_child_count(); ++i) {
// get the next valid child
Node3D* child = Object::cast_to<Node3D>(this->customizationParent->get_child(i));
if (child == nullptr)
continue;
// split it's name into parts based on _
// the format for the names will be Chr_<CATEGORY NAME>_<ADDITIONAL IDENTIFIERS>_<NUMBER> numbering IS NOT guaranteed to start at 0 OR 1
// the category name will serve as the key to the customization map
// additional identifiers are ignored
PackedStringArray slices = child->get_name().split("_", false);
// create a new customization state if one does not exist for this category
if (this->customization.find(slices[1]) == this->customization.end())
this->customization.insert({slices[1], {std::optional{0}, {}}});
// add the new child to the customization options for this category
CustomizationState& state = this->customization.at(slices[1]);
state.options.push_back(child);
// set invisible if this child is not the current selection
CustomizableCharacter::set_customization_active(child, state.currentSelected == state.options.size() - 1);
}
// collect customization categories that do not contain any options
std::vector<String> empty{};
for (std::pair<const String, CustomizationState>& pair :
this->customization) {
if (pair.second.options.size() == 0)
empty.push_back(pair.first);
if (pair.second.currentSelected.has_value() &&
pair.second.currentSelected.value() > pair.second.options.size())
pair.second.currentSelected = std::nullopt;
UtilityFunctions::print("- ", pair.first);
}
// remove collected empty categories
for (String const& key : empty) {
this->customization.erase(key);
UtilityFunctions::print("removing ", key);
}
}
}

View File

@ -0,0 +1,45 @@
#ifndef CUSTOMIZABLE_CHARACTER_HPP
#define CUSTOMIZABLE_CHARACTER_HPP
#include <optional>
#include <map>
#include "godot_cpp/classes/node3d.hpp"
#include "godot_cpp/classes/skeleton3d.hpp"
namespace godot {
class CustomizableCharacter : public Node3D {
GDCLASS(CustomizableCharacter, Node3D);
static void _bind_methods();
// the current customization state of a body part/attachment
struct CustomizationState {
// current selected option
std::optional<size_t> currentSelected{std::nullopt};
// cache of all options
std::vector<Node3D *> options{};
};
static void set_customization_active(Node3D *node, bool active);
private:
// all customization options categorized according to body part
std::map<String, CustomizationState> customization{};
// the skeleton of the customizable character model
Skeleton3D *customizationParent{nullptr};
public:
virtual void _enter_tree() override;
virtual void _ready() override;
// Swap out the currently active customization for this category
void select_customization(String category, std::optional<size_t> value);
// Construct a dictionary of [String, int] where the key is a category
// from the customization map. And the value is the index of the
// active element from the category.
Dictionary get_active_customization() const;
// change active customization as if select_customization was called for
// every element in a [String, int] dictionary, where key is the category
// to assign to and the value is the index of the element to show.
void set_active_customization(Dictionary value);
void customization_init();
};
}
#endif // !CUSTOMIZABLE_CHARACTER_HPP

View File

@ -14,9 +14,6 @@
namespace godot {
void Player::_bind_methods() {
#define CLASSNAME Player
#ifndef NDEBUG
GDPROPERTY(active_customization, Variant::DICTIONARY);
#endif
GDFUNCTION_ARGS(on_horizontal, "event", "value");
GDFUNCTION_ARGS(on_vertical, "event", "value");
GDFUNCTION(get_is_moving);
@ -30,7 +27,6 @@ void Player::_enter_tree() {
}
void Player::_ready() {
this->customization_init();
GDGAMEONLY();
this->flat_view_update();
this->input->listen_to(PlayerInput::Listener("move_left", "move_right", this, "on_horizontal"));
@ -58,11 +54,6 @@ void Player::_physics_process(double deltaTime) {
void Player::damage(Node *origin, int amount) {
}
void Player::set_customization_active(Node3D *node, bool active) {
node->set_visible(active);
node->set_process(active);
}
void Player::on_horizontal(Ref<InputEvent> event, float value) {
this->moveInput.x = value;
}
@ -71,58 +62,6 @@ void Player::on_vertical(Ref<InputEvent> event, float value) {
this->moveInput.y = value;
}
void Player::select_customization(String category,
std::optional<size_t> index) {
if (this->customization.find(category) == this->customization.end())
return;
CustomizationState& pair = this->customization.at(category);
// invalid keys are stored as the size of the array
if (index.has_value() && index.value() > pair.options.size())
index = std::nullopt;
// nothing changesw
if (index == pair.currentSelected)
return;
// disable previous chosen option, if any
if (pair.currentSelected.has_value())
Player::set_customization_active(pair.options.at(pair.currentSelected.value()), false);
// enable chosen option, if any
if (index.has_value())
Player::set_customization_active(pair.options.at(index.value()), false);
pair.currentSelected = index;
}
Dictionary Player::get_active_customization() const {
Dictionary result{};
// translate the map to a dictionary of keys and indexes
for (std::pair<String, CustomizationState> const& pair :
this->customization)
result[pair.first] = pair.second.currentSelected.has_value()
? pair.second.currentSelected.value()
: -1; // an invalid key will always be -1
return result;
}
void Player::set_active_customization(Dictionary value) {
for (size_t i = 0; i < value.size(); ++i) {
String key = value.keys()[i];
size_t selection = value[key];
// wether or not the a given key exists in the customization map
bool exists = this->customization.find(key) != this->customization.end();
// if not, it can be added, as long as the customization map has not yet
// been initialized by _ready
if (!exists && !this->is_inside_tree())
this->customization.insert({key, {std::optional{selection}, {}}});
// if the key does not exist in customization and _ready is already
// called, the key is not valid and discarted
else if (!exists)
return;
else
this->select_customization(key, selection >= 0
? std::optional{selection}
: std::nullopt);
}
}
Vector3 Player::get_world_move_input() const {
Vector2 n = this->moveInput.normalized();
return flatViewBasis.get_column(0) * n.x
@ -149,50 +88,6 @@ void Player::flat_view_update() {
this->flatViewBasis = Basis{right, right.cross(forward), forward};
}
void Player::customization_init() {
this->customizationParent =
this->get_node<Skeleton3D>("Model/RootNode/GeneralSkeleton");
if (this->customizationParent == nullptr)
return;
for (int i = 0; i < customizationParent->get_child_count(); ++i) {
// get the next valid child
Node3D* child = Object::cast_to<Node3D>(this->customizationParent->get_child(i));
if (child == nullptr)
continue;
// split it's name into parts based on _
// the format for the names will be Chr_<CATEGORY NAME>_<ADDITIONAL IDENTIFIERS>_<NUMBER> numbering IS NOT guaranteed to start at 0 OR 1
// the category name will serve as the key to the customization map
// additional identifiers are ignored
PackedStringArray slices = child->get_name().split("_", false);
// create a new customization state if one does not exist for this category
if (this->customization.find(slices[1]) == this->customization.end())
this->customization.insert({slices[1], {std::optional{0}, {}}});
// add the new child to the customization options for this category
CustomizationState& state = this->customization.at(slices[1]);
state.options.push_back(child);
// set invisible if this child is not the current selection
if (state.currentSelected != state.options.size() - 1) {
Player::set_customization_active(child, false);
}
}
// collect customization categories that do not contain any options
std::vector<String> empty{};
for (std::pair<const String, CustomizationState>& pair :
this->customization) {
if (pair.second.options.size() == 0)
empty.push_back(pair.first);
if (pair.second.currentSelected.has_value() &&
pair.second.currentSelected.value() > pair.second.options.size())
pair.second.currentSelected = std::nullopt;
UtilityFunctions::print("- ", pair.first);
}
// remove collected empty categories
for (String const& key : empty) {
this->customization.erase(key);
UtilityFunctions::print("removing ", key);
}
}
bool Player::get_is_moving() const {
return this->is_on_floor() && !this->moveInput.is_zero_approx();
}

View File

@ -6,30 +6,19 @@
#include "godot_cpp/classes/animation_tree.hpp"
#include "godot_cpp/classes/character_body3d.hpp"
#include "godot_cpp/classes/input_event.hpp"
#include "godot_cpp/classes/skeleton3d.hpp"
#include "hitable_interface.hpp"
#include "player_input.hpp"
#include <optional>
namespace godot {
class Player : public CharacterBody3D, public HitableInterface {
GDCLASS(Player, CharacterBody3D)
static void _bind_methods();
// the current customization state of a body part/attachment
struct CustomizationState {
std::optional<size_t> currentSelected{std::nullopt}; // current selected option
std::vector<Node3D *> options{}; // cache of all options
};
protected:
// all customization options categorized according to body part
std::map<String, CustomizationState> customization{};
// the root of the character model
Node3D* model{nullptr};
// the character animation tree
AnimationTree* animTree{nullptr};
// the skeleton of the customizable character model
Skeleton3D *customizationParent{nullptr};
PlayerInput *input{nullptr};
Basis flatViewBasis{identity::Basis};
@ -45,28 +34,15 @@ public:
virtual void damage(Node *origin, int amount) override;
static void set_customization_active(Node3D *node, bool active);
void on_horizontal(Ref<InputEvent> event, float value);
void on_vertical(Ref<InputEvent> event, float value);
// Swap out the currently active customization for this category
void select_customization(String category, std::optional<size_t> value);
// Construct a dictionary of [String, int] where the key is a category
// from the customization map. And the value is the index of the
// active element from the category.
Dictionary get_active_customization() const;
// change active customization as if select_customization was called for
// every element in a [String, int] dictionary, where key is the category
// to assign to and the value is the index of the element to show.
void set_active_customization(Dictionary value);
// returns moveInput transformed to world space
Vector3 get_world_move_input() const;
private:
void rotate_to_movement();
void flat_view_update();
void customization_init();
bool get_is_moving() const;
};
} // namespace godot

View File

@ -4,6 +4,7 @@
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
#include "customizable_character.hpp"
#include "entrance.hpp"
#include "game_mode.hpp"
#include "level.hpp"
@ -21,6 +22,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) {
ClassDB::register_class<Entrance>();
ClassDB::register_class<GameMode>();
ClassDB::register_class<PlayerInput>();
ClassDB::register_class<CustomizableCharacter>();
}
extern "C" {