diff --git a/data/maps/demo/audio/license.txt b/data/maps/demo/audio/license.txt new file mode 100644 index 0000000000..365991cca7 --- /dev/null +++ b/data/maps/demo/audio/license.txt @@ -0,0 +1,2 @@ +CC0 https://opengameart.org/content/town-theme-rpg +CC0 https://opengameart.org/content/512-sound-effects-8-bit-style \ No newline at end of file diff --git a/data/maps/demo/audio/sfx_coin_single1.ogg b/data/maps/demo/audio/sfx_coin_single1.ogg new file mode 100644 index 0000000000..05a3039e7e Binary files /dev/null and b/data/maps/demo/audio/sfx_coin_single1.ogg differ diff --git a/data/maps/demo/audio/sfx_coin_single1.ogg.meta b/data/maps/demo/audio/sfx_coin_single1.ogg.meta new file mode 100644 index 0000000000..b734ea9a3e --- /dev/null +++ b/data/maps/demo/audio/sfx_coin_single1.ogg.meta @@ -0,0 +1,2 @@ +volume = 1.000 +looped = false \ No newline at end of file diff --git a/data/maps/demo/button.lua b/data/maps/demo/button.lua new file mode 100644 index 0000000000..014d7492f9 --- /dev/null +++ b/data/maps/demo/button.lua @@ -0,0 +1,38 @@ +local math = require "scripts/math" +local interactive = false + +label = {} +player = {} +sound = -1 + +Editor.setPropertyType(this, "label", Editor.ENTITY_PROPERTY) +Editor.setPropertyType(this, "player", Editor.ENTITY_PROPERTY) +Editor.setPropertyType(this, "sound", Editor.RESOURCE_PROPERTY, "clip") + +function playSound(sound) + local path = this.world.lua_script:getResourcePath(sound) + this.world:getModule("audio"):play(this, path, false) +end + +function update(time_delta) + -- check if player is close + local dist_squared = math.distXZSquared(this.position, player.position) + interactive = dist_squared < 2 + -- animate the label if player is close + label.property_animator.enabled = interactive +end + +function onInputEvent(event : InputEvent) + -- check if player pressed "F" and is close + if interactive and event.type == "button" and event.device.type == "keyboard" then + if event.key_id == string.byte("F") then + if event.down then + -- play the sound + playSound(sound) + -- press (move) the button + this.property_animator.enabled = false + this.property_animator.enabled = true + end + end + end +end diff --git a/data/maps/demo/button_label.anp b/data/maps/demo/button_label.anp new file mode 100644 index 0000000000..13ec1379ee --- /dev/null +++ b/data/maps/demo/button_label.anp @@ -0,0 +1,16 @@ +{ + type = "Scale X", + keyframes = [ +0, 15, 30], + values = [ +10.000000, 20.000000, 10.000000] +}, + +{ + type = "Scale Y", + keyframes = [ +0, 15, 30], + values = [ +10.000000, 20.000000, 10.000000] +}, + diff --git a/data/maps/demo/button_press.anp b/data/maps/demo/button_press.anp new file mode 100644 index 0000000000..e1841a8b06 --- /dev/null +++ b/data/maps/demo/button_press.anp @@ -0,0 +1,8 @@ +{ + type = "Local position Z", + keyframes = [ +0, 8, 15], + values = [ +-0.220000, -0.150000, -0.220000] +}, + diff --git a/data/maps/demo/demo.unv b/data/maps/demo/demo.unv new file mode 100644 index 0000000000..b43206b3d6 Binary files /dev/null and b/data/maps/demo/demo.unv differ diff --git a/data/maps/demo/red.mat b/data/maps/demo/red.mat new file mode 100644 index 0000000000..96fa344c5e --- /dev/null +++ b/data/maps/demo/red.mat @@ -0,0 +1,11 @@ +shader "/shaders/standard.hlsl" +backface_culling true +layer "default" +texture "" +texture "" +texture "" +uniform "Material color", { 1.000000, 0.000000, 0.000000, 1.000000 } +uniform "Roughness", 0.000000 +uniform "Metallic", 0.000000 +uniform "Emission", 0.000000 +uniform "Translucency", 0.000000 diff --git a/data/universes/physics_stress_test.unv b/data/maps/physics_stress_test/physics_stress_test.unv similarity index 100% rename from data/universes/physics_stress_test.unv rename to data/maps/physics_stress_test/physics_stress_test.unv diff --git a/data/scripts/math.lua b/data/scripts/math.lua index 6af03bbd3c..1f6e835e1e 100644 --- a/data/scripts/math.lua +++ b/data/scripts/math.lua @@ -44,4 +44,18 @@ return { return {a[1] * f, a[2] * f, a[3] * f} end, + distSquared = function(a, b) + local xd = a[1] - b[1] + local yd = a[2] - b[2] + local zd = a[3] - b[3] + + return xd * xd + yd * yd + zd * zd + end, + + distXZSquared = function(a, b) + local xd = a[1] - b[1] + local zd = a[3] - b[3] + + return xd * xd + zd * zd + end, } diff --git a/data/tests/property_animation/property_animation.anp b/data/tests/property_animation/property_animation.anp deleted file mode 100644 index 99100674fc..0000000000 --- a/data/tests/property_animation/property_animation.anp +++ /dev/null @@ -1,18 +0,0 @@ -{ - component = "gui_rect", - property = "Top Points", - keyframes = [ -0, 20, 40], - values = [ -0.000000, 100.000000, 0.000000] -}, - -{ - component = "gui_rect", - property = "Bottom Points", - keyframes = [ -0, 20, 40], - values = [ -0.000000, 100.000000, 0.000000] -}, - diff --git a/data/universes/demo.unv b/data/universes/demo.unv deleted file mode 100644 index 62fb1395d7..0000000000 Binary files a/data/universes/demo.unv and /dev/null differ diff --git a/data/universes/tests/property_animation.unv b/data/universes/tests/property_animation.unv deleted file mode 100644 index fb39e4d627..0000000000 Binary files a/data/universes/tests/property_animation.unv and /dev/null differ diff --git a/src/animation/animation_module.cpp b/src/animation/animation_module.cpp index 2dab238074..8725eee998 100644 --- a/src/animation/animation_module.cpp +++ b/src/animation/animation_module.cpp @@ -386,23 +386,31 @@ struct AnimationModuleImpl final : AnimationModule { return animator.resource ? animator.resource->getPath() : Path(""); } - bool isPropertyAnimatorEnabled(EntityRef entity) override - { - return !isFlagSet(m_property_animators[entity].flags, PropertyAnimator::DISABLED); + bool isPropertyAnimatorLooped(EntityRef entity) { + return isFlagSet(m_property_animators[entity].flags, PropertyAnimator::LOOPED); } + bool isPropertyAnimatorEnabled(EntityRef entity) override { + return !isFlagSet(m_property_animators[entity].flags, PropertyAnimator::DISABLED); + } - void enablePropertyAnimator(EntityRef entity, bool enabled) override - { + void enablePropertyAnimator(EntityRef entity, bool enabled) override { PropertyAnimator& animator = m_property_animators[entity]; + bool is_enabled = !isFlagSet(animator.flags, PropertyAnimator::DISABLED); + if (enabled == is_enabled) return; + setFlag(animator.flags, PropertyAnimator::DISABLED, !enabled); animator.time = 0; - if (!enabled) - { + if (!enabled) { applyPropertyAnimator(entity, animator); } } + void setPropertyAnimatorLooped(EntityRef entity, bool looped) { + PropertyAnimator& animator = m_property_animators[entity]; + setFlag(animator.flags, PropertyAnimator::LOOPED, looped); + } + Path getPropertyAnimation(EntityRef entity) override { @@ -636,38 +644,90 @@ struct AnimationModuleImpl final : AnimationModule { } } - void applyPropertyAnimator(EntityRef entity, PropertyAnimator& animator) - { + void applyPropertyAnimator(EntityRef entity, PropertyAnimator& animator) { + const bool is_looped = animator.flags & PropertyAnimator::LOOPED; const PropertyAnimation* animation = animator.animation; - int frame = int(animator.time * animation->fps + 0.5f); - frame = frame % animation->curves[0].frames.back(); - for (PropertyAnimation::Curve& curve : animation->curves) - { + float frame = animator.time * animation->fps; + DVec3 local_pos = m_world.getLocalTransform(entity).pos; + DVec3 pos = m_world.getPosition(entity); + Vec3 scale = m_world.getScale(entity); + bool set_local_pos = false; + bool set_pos = false; + bool set_scale = false; + if (is_looped) { + frame = fmodf(frame, (float)animation->curves[0].frames.back()); + } + else { + frame = minimum(frame, (float)animation->curves[0].frames.back()); + } + for (PropertyAnimation::Curve& curve : animation->curves) { if (curve.frames.size() < 2) continue; - for (int i = 1, n = curve.frames.size(); i < n; ++i) - { - if (frame <= curve.frames[i]) - { + for (int i = 1, n = curve.frames.size(); i < n; ++i) { + if (frame <= curve.frames[i]) { float t = (frame - curve.frames[i - 1]) / float(curve.frames[i] - curve.frames[i - 1]); float v = curve.values[i] * t + curve.values[i - 1] * (1 - t); - ComponentUID cmp; - cmp.type = curve.cmp_type; - cmp.module = m_world.getModule(cmp.type); - cmp.entity = entity; - ASSERT(curve.property->setter); - curve.property->set(cmp, -1, v); + switch (curve.type) { + case PropertyAnimation::CurveType::PROPERTY: { + ComponentUID cmp; + cmp.type = curve.cmp_type; + cmp.module = m_world.getModule(cmp.type); + cmp.entity = entity; + ASSERT(curve.property->setter); + curve.property->set(cmp, -1, v); + break; + } + case PropertyAnimation::CurveType::LOCAL_POS_X: + local_pos.x = v; + set_local_pos = true; + break; + case PropertyAnimation::CurveType::LOCAL_POS_Y: + local_pos.y = v; + set_local_pos = true; + break; + case PropertyAnimation::CurveType::LOCAL_POS_Z: + local_pos.z = v; + set_local_pos = true; + break; + case PropertyAnimation::CurveType::POS_X: + pos.x = v; + set_pos = true; + break; + case PropertyAnimation::CurveType::POS_Y: + pos.y = v; + set_pos = true; + break; + case PropertyAnimation::CurveType::POS_Z: + set_pos = true; + pos.z = v; + break; + case PropertyAnimation::CurveType::NOT_SET: + ASSERT(false); + break; + case PropertyAnimation::CurveType::SCALE_X: + scale.x = v; + set_scale = true; + break; + case PropertyAnimation::CurveType::SCALE_Y: + scale.y = v; + set_scale = true; + break; + case PropertyAnimation::CurveType::SCALE_Z: + scale.z = v; + set_scale = true; + break; + } break; } } } + if (set_pos) m_world.setPosition(entity, pos); + if (set_local_pos) m_world.setLocalPosition(entity, local_pos); + if (set_scale) m_world.setScale(entity, scale); } - - void updatePropertyAnimators(float time_delta) - { + void updatePropertyAnimators(float time_delta) { PROFILE_FUNCTION(); - for (int anim_idx = 0, c = m_property_animators.size(); anim_idx < c; ++anim_idx) - { + for (int anim_idx = 0, c = m_property_animators.size(); anim_idx < c; ++anim_idx) { EntityRef entity = m_property_animators.getKey(anim_idx); PropertyAnimator& animator = m_property_animators.at(anim_idx); const PropertyAnimation* animation = animator.animation; @@ -787,6 +847,7 @@ void AnimationModule::reflect(Engine& engine) { .LUMIX_CMP(PropertyAnimator, "property_animator", "Animation / Property animator") .LUMIX_PROP(PropertyAnimation, "Animation").resourceAttribute(PropertyAnimation::TYPE) .prop<&AnimationModule::isPropertyAnimatorEnabled, &AnimationModule::enablePropertyAnimator>("Enabled") + .prop<&AnimationModule::isPropertyAnimatorLooped, &AnimationModule::setPropertyAnimatorLooped>("Looped") .LUMIX_CMP(Animator, "animator", "Animation / Animator") .function<(void (AnimationModule::*)(EntityRef, u32, float))&AnimationModule::setAnimatorInput>("setFloatInput", "AnimationModule::setAnimatorInput") .function<(void (AnimationModule::*)(EntityRef, u32, bool))&AnimationModule::setAnimatorInput>("setBoolInput", "AnimationModule::setAnimatorInput") diff --git a/src/animation/animation_module.h b/src/animation/animation_module.h index 60927c422f..19e81682c4 100644 --- a/src/animation/animation_module.h +++ b/src/animation/animation_module.h @@ -28,6 +28,8 @@ struct AnimationModule : IModule { virtual void setPropertyAnimation(EntityRef entity, const Path& path) = 0; virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0; virtual void enablePropertyAnimator(EntityRef entity, bool enabled) = 0; + virtual bool isPropertyAnimatorLooped(EntityRef entity) = 0; + virtual void setPropertyAnimatorLooped(EntityRef entity, bool looped) = 0; virtual struct Animation* getAnimableAnimation(EntityRef entity) = 0; virtual Path getAnimation(EntityRef entity) = 0; virtual void setAnimation(EntityRef entity, const Path& path) = 0; diff --git a/src/animation/editor/animation_plugins.cpp b/src/animation/editor/animation_plugins.cpp index ecccc05c9e..8473a1cf63 100644 --- a/src/animation/editor/animation_plugins.cpp +++ b/src/animation/editor/animation_plugins.cpp @@ -39,7 +39,50 @@ static const ComponentType ENVIRONMENT_PROBE_TYPE = reflection::getComponentType static const ComponentType ENVIRONMENT_TYPE = reflection::getComponentType("environment"); namespace { - + +static struct { + const char* label; + PropertyAnimation::CurveType type; +} g_transform_descs[] = { + { "Local position X", PropertyAnimation::CurveType::LOCAL_POS_X }, + { "Local position Y", PropertyAnimation::CurveType::LOCAL_POS_Y }, + { "Local position Z", PropertyAnimation::CurveType::LOCAL_POS_Z }, + { "Position X", PropertyAnimation::CurveType::POS_X }, + { "Position Y", PropertyAnimation::CurveType::POS_Y }, + { "Position Z", PropertyAnimation::CurveType::POS_Z }, + //{ "Rotation X", PropertyAnimation::CurveType::ROT_X }, + //{ "Rotation Y", PropertyAnimation::CurveType::ROT_Y }, + //{ "Rotation Z", PropertyAnimation::CurveType::ROT_Z }, + { "Scale X", PropertyAnimation::CurveType::SCALE_X }, + { "Scale Y", PropertyAnimation::CurveType::SCALE_Y }, + { "Scale Z", PropertyAnimation::CurveType::SCALE_Z }, +}; + +static PropertyAnimation::CurveType toCurveType(StringView str) { + for (auto& desc : g_transform_descs) { + if (equalStrings(str, desc.label)) return desc.type; + } + return PropertyAnimation::CurveType::NOT_SET; +} + +static const char* toString(PropertyAnimation::CurveType type) { + switch (type) { + case PropertyAnimation::CurveType::LOCAL_POS_X: return "Local position X"; + case PropertyAnimation::CurveType::LOCAL_POS_Y: return "Local position Y"; + case PropertyAnimation::CurveType::LOCAL_POS_Z: return "Local position Z"; + case PropertyAnimation::CurveType::POS_X: return "Position X"; + case PropertyAnimation::CurveType::POS_Y: return "Position Y"; + case PropertyAnimation::CurveType::POS_Z: return "Position Z"; + case PropertyAnimation::CurveType::SCALE_X: return "Scale X"; + case PropertyAnimation::CurveType::SCALE_Y: return "Scale Y"; + case PropertyAnimation::CurveType::SCALE_Z: return "Scale Z"; + case PropertyAnimation::CurveType::NOT_SET: return "Not set"; + case PropertyAnimation::CurveType::PROPERTY: return "Property"; + } + return "ERROR"; +} + + template static bool consumeNumberArray(Tokenizer& tokenizer, Array& array) { if (!tokenizer.consume("[")) return false; @@ -421,8 +464,11 @@ struct PropertyAnimationPlugin : AssetBrowser::IPlugin, AssetCompiler::IPlugin { for (PropertyAnimation::Curve& curve : anim.curves) { blob << "{\n"; - blob << "\t component = \"" << reflection::getComponent(curve.cmp_type)->name << "\",\n"; - blob << "\t property = \"" << curve.property->name << "\",\n"; + blob << "\t type = \"" << toString(curve.type) << "\",\n"; + if (curve.type == PropertyAnimation::CurveType::PROPERTY) { + blob << "\t component = \"" << reflection::getComponent(curve.cmp_type)->name << "\",\n"; + blob << "\t property = \"" << curve.property->name << "\",\n"; + } blob << "\tkeyframes = [\n"; for (int i = 0; i < curve.frames.size(); ++i) { if (i != 0) blob << ", "; @@ -481,9 +527,42 @@ struct PropertyAnimationPlugin : AssetBrowser::IPlugin, AssetCompiler::IPlugin { for (int i = 0, n = m_resource->curves.size(); i < n; ++i) { PropertyAnimation::Curve& curve = m_resource->curves[i]; - const char* cmp_name = m_app.getComponentTypeName(curve.cmp_type); - StaticString<64> tmp(cmp_name, " - ", curve.property->name); - if (ImGui::Selectable(tmp, m_selected_curve == i)) m_selected_curve = i; + switch (curve.type) { + case PropertyAnimation::CurveType::PROPERTY: { + const char* cmp_name = m_app.getComponentTypeName(curve.cmp_type); + StaticString<64> tmp(cmp_name, " - ", curve.property->name); + if (ImGui::Selectable(tmp, m_selected_curve == i)) m_selected_curve = i; + break; + } + case PropertyAnimation::CurveType::LOCAL_POS_X: + if (ImGui::Selectable("Local position X", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::LOCAL_POS_Y: + if (ImGui::Selectable("Local position Y", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::LOCAL_POS_Z: + if (ImGui::Selectable("Local position Z", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::POS_X: + if (ImGui::Selectable("Position X", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::POS_Y: + if (ImGui::Selectable("Position Y", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::POS_Z: + if (ImGui::Selectable("Position Z", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::SCALE_X: + if (ImGui::Selectable("Scale X", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::SCALE_Y: + if (ImGui::Selectable("Scale Y", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::SCALE_Z: + if (ImGui::Selectable("Scale Z", m_selected_curve == i)) m_selected_curve = i; + break; + case PropertyAnimation::CurveType::NOT_SET: ASSERT(false); break; + } } if (m_selected_curve >= m_resource->curves.size()) m_selected_curve = -1; @@ -556,7 +635,22 @@ struct PropertyAnimationPlugin : AssetBrowser::IPlugin, AssetCompiler::IPlugin { void ShowAddCurveMenu(PropertyAnimation* animation) { if (!ImGui::BeginMenu("Add curve")) return; - + + if (ImGui::BeginMenu("Transform")) { + for (auto& v : g_transform_descs) { + if (ImGui::MenuItem(v.label)) { + PropertyAnimation::Curve& curve = animation->addCurve(); + curve.type = v.type; + curve.frames.push(0); + curve.frames.push(animation->curves.size() > 1 ? animation->curves[0].frames.back() : 1); + curve.values.push(0); + curve.values.push(0); + } + } + ImGui::EndMenu(); + } + + for (const reflection::RegisteredComponent& cmp_type : reflection::getComponents()) { const char* cmp_type_name = cmp_type.cmp->name; if (!hasFloatProperty(cmp_type.cmp)) continue; @@ -661,7 +755,12 @@ struct PropertyAnimationPlugin : AssetBrowser::IPlugin, AssetCompiler::IPlugin { if (!tokenizer.consume("=")) return false; - if (key == "component") { + if (key == "type") { + StringView value; + if (!tokenizer.consume(value)) return false; + curve.type = toCurveType(value); + } + else if (key == "component") { StringView value; if (!tokenizer.consume(value)) return false; curve.cmp_type = reflection::getComponentType(value); @@ -720,9 +819,12 @@ struct PropertyAnimationPlugin : AssetBrowser::IPlugin, AssetCompiler::IPlugin { compiled.write(header); compiled.write((u32)curves.size()); for (PropertyAnimation::Curve& curve : curves) { - const char* cmp_typename = reflection::getComponent(curve.cmp_type)->name; - compiled.writeString(cmp_typename); - compiled.writeString(curve.property->name); + compiled.write(curve.type); + if (curve.type == PropertyAnimation::CurveType::PROPERTY) { + const char* cmp_typename = reflection::getComponent(curve.cmp_type)->name; + compiled.writeString(cmp_typename); + compiled.writeString(curve.property->name); + } compiled.write((u32)curve.frames.size()); for (i32 frame : curve.frames) compiled.write(frame); for (float value : curve.values) compiled.write(value); diff --git a/src/animation/property_animation.cpp b/src/animation/property_animation.cpp index e4eee2a71a..13aaec30eb 100644 --- a/src/animation/property_animation.cpp +++ b/src/animation/property_animation.cpp @@ -7,7 +7,6 @@ namespace Lumix { - const ResourceType PropertyAnimation::TYPE("property_animation"); PropertyAnimation::PropertyAnimation(const Path& path, ResourceManager& resource_manager, IAllocator& allocator) @@ -34,11 +33,13 @@ bool PropertyAnimation::load(Span mem) { InputMemoryStream stream(mem); Header header; stream.read(header); + if (header.magic != Header::MAGIC) { logError(getPath(), ": invalid file"); return false; } - if (header.version != 0) { + + if (header.version > Version::LATEST) { logError(getPath(), ": unsupported version"); return false; } @@ -47,15 +48,34 @@ bool PropertyAnimation::load(Span mem) { curves.reserve(num_curves); for (u32 i = 0; i < num_curves; ++i) { Curve& curve = curves.emplace(m_allocator); - const char* cmp_typename = stream.readString(); - const char* property_name = stream.readString(); - const u32 num_frames = stream.read(); - curve.cmp_type = reflection::getComponentType(cmp_typename); - curve.property = static_cast*>(reflection::getProperty(curve.cmp_type, property_name)); - curve.frames.resize(num_frames); - curve.values.resize(num_frames); - stream.read(curve.frames.begin(), curve.frames.byte_size()); - stream.read(curve.values.begin(), curve.values.byte_size()); + if (header.version > Version::TRANSFORM) { + curve.type = stream.read(); + } + else { + curve.type = CurveType::PROPERTY; + } + switch (curve.type) { + case CurveType::PROPERTY: { + const char* cmp_typename = stream.readString(); + const char* property_name = stream.readString(); + const u32 num_frames = stream.read(); + curve.cmp_type = reflection::getComponentType(cmp_typename); + curve.property = static_cast*>(reflection::getProperty(curve.cmp_type, property_name)); + curve.frames.resize(num_frames); + curve.values.resize(num_frames); + stream.read(curve.frames.begin(), curve.frames.byte_size()); + stream.read(curve.values.begin(), curve.values.byte_size()); + break; + } + default: { + const u32 num_frames = stream.read(); + curve.frames.resize(num_frames); + curve.values.resize(num_frames); + stream.read(curve.frames.begin(), curve.frames.byte_size()); + stream.read(curve.values.begin(), curve.values.byte_size()); + break; + } + } } return !stream.hasOverflow(); diff --git a/src/animation/property_animation.h b/src/animation/property_animation.h index 8bb8402c24..031ef75767 100644 --- a/src/animation/property_animation.h +++ b/src/animation/property_animation.h @@ -7,9 +7,30 @@ namespace Lumix { namespace reflection { template struct Property; } struct PropertyAnimation final : Resource { + enum class Version { + TRANSFORM, + + LATEST + }; + + enum class CurveType : u32 { + NOT_SET, + PROPERTY, + LOCAL_POS_X, + LOCAL_POS_Y, + LOCAL_POS_Z, + POS_X, + POS_Y, + POS_Z, + SCALE_X, + SCALE_Y, + SCALE_Z, + }; + struct Curve { Curve(IAllocator& allocator) : frames(allocator), values(allocator) {} + CurveType type = CurveType::NOT_SET; ComponentType cmp_type; const reflection::Property* property = nullptr; @@ -21,7 +42,7 @@ struct PropertyAnimation final : Resource { static const u32 MAGIC = '_PRA'; u32 magic = MAGIC; - u32 version = 0; + Version version = Version::LATEST; }; PropertyAnimation(const Path& path, ResourceManager& resource_manager, IAllocator& allocator);