Merge pull request #72233 from TokageItLab/audio-blending
Implement blending audio feature to AnimationTree
This commit is contained in:
commit
e5752fdfd3
|
@ -104,6 +104,13 @@
|
|||
[param stream] is the [AudioStream] resource to play. [param start_offset] is the number of seconds cut off at the beginning of the audio stream, while [param end_offset] is at the ending.
|
||||
</description>
|
||||
</method>
|
||||
<method name="audio_track_is_use_blend" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="track_idx" type="int" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the track at [code]idx[/code] will be blended with other animations.
|
||||
</description>
|
||||
</method>
|
||||
<method name="audio_track_set_key_end_offset">
|
||||
<return type="void" />
|
||||
<param index="0" name="track_idx" type="int" />
|
||||
|
@ -131,6 +138,14 @@
|
|||
Sets the stream of the key identified by [param key_idx] to value [param stream]. The [param track_idx] must be the index of an Audio Track.
|
||||
</description>
|
||||
</method>
|
||||
<method name="audio_track_set_use_blend">
|
||||
<return type="void" />
|
||||
<param index="0" name="track_idx" type="int" />
|
||||
<param index="1" name="enable" type="bool" />
|
||||
<description>
|
||||
Sets whether the track will be blended with other animations. If [code]true[/code], the audio playback volume changes depending on the blend value.
|
||||
</description>
|
||||
</method>
|
||||
<method name="bezier_track_get_key_in_handle" qualifiers="const">
|
||||
<return type="Vector2" />
|
||||
<param index="0" name="track_idx" type="int" />
|
||||
|
|
|
@ -232,6 +232,10 @@
|
|||
<member name="assigned_animation" type="String" setter="set_assigned_animation" getter="get_assigned_animation">
|
||||
If playing, the the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation].
|
||||
</member>
|
||||
<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
|
||||
The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
|
||||
For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
|
||||
</member>
|
||||
<member name="autoplay" type="String" setter="set_autoplay" getter="get_autoplay" default="""">
|
||||
The key of the animation to play when the scene loads.
|
||||
</member>
|
||||
|
|
|
@ -110,6 +110,10 @@
|
|||
<member name="anim_player" type="NodePath" setter="set_animation_player" getter="get_animation_player" default="NodePath("")">
|
||||
The path to the [AnimationPlayer] used for animating.
|
||||
</member>
|
||||
<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
|
||||
The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
|
||||
For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
|
||||
</member>
|
||||
<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationTree.AnimationProcessCallback" default="1">
|
||||
The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
|
||||
</member>
|
||||
|
|
|
@ -28,6 +28,12 @@
|
|||
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer].
|
||||
</description>
|
||||
</method>
|
||||
<method name="has_stream_playback">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="play">
|
||||
<return type="void" />
|
||||
<param index="0" name="from_position" type="float" default="0.0" />
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer2D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="has_stream_playback">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="play">
|
||||
<return type="void" />
|
||||
<param index="0" name="from_position" type="float" default="0.0" />
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="has_stream_playback">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="play">
|
||||
<return type="void" />
|
||||
<param index="0" name="from_position" type="float" default="0.0" />
|
||||
|
|
|
@ -1954,6 +1954,10 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")),
|
||||
get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons"))
|
||||
};
|
||||
Ref<Texture2D> blend_icon[2] = {
|
||||
get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")),
|
||||
get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")),
|
||||
};
|
||||
|
||||
int ofs = get_size().width - timeline->get_buttons_width();
|
||||
|
||||
|
@ -1982,6 +1986,11 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
|
||||
draw_texture(update_icon, update_mode_rect.position);
|
||||
}
|
||||
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
|
||||
Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
|
||||
Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
|
||||
draw_texture(use_blend_icon, use_blend_icon_pos);
|
||||
}
|
||||
// Make it easier to click.
|
||||
update_mode_rect.position.y = 0;
|
||||
update_mode_rect.size.y = get_size().height;
|
||||
|
@ -1990,13 +1999,12 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
update_mode_rect.size.x += hsep / 2;
|
||||
|
||||
if (!read_only) {
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
|
||||
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
|
||||
update_mode_rect.size.x += down_icon->get_width();
|
||||
} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
|
||||
Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
|
||||
update_mode_rect.size.x += down_icon->get_width();
|
||||
|
||||
update_mode_rect = Rect2();
|
||||
} else {
|
||||
update_mode_rect = Rect2();
|
||||
|
@ -2439,7 +2447,11 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
|
|||
}
|
||||
|
||||
if (update_mode_rect.has_point(p_pos)) {
|
||||
return TTR("Update Mode (How this property is set)");
|
||||
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
|
||||
return TTR("Use Blend");
|
||||
} else {
|
||||
return TTR("Update Mode (How this property is set)");
|
||||
}
|
||||
}
|
||||
|
||||
if (interp_mode_rect.has_point(p_pos)) {
|
||||
|
@ -2641,9 +2653,14 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
|||
menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
|
||||
}
|
||||
menu->clear();
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
|
||||
if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
|
||||
menu->add_icon_item(get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
|
||||
} else {
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
|
||||
}
|
||||
menu->reset_size();
|
||||
|
||||
Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
|
||||
|
@ -2662,7 +2679,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
|||
menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
|
||||
// Check is angle property.
|
||||
// Check whether it is angle property.
|
||||
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
|
||||
if (ape) {
|
||||
AnimationPlayer *ap = ape->get_player();
|
||||
|
@ -3055,6 +3072,16 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
|
|||
emit_signal(SNAME("delete_request"));
|
||||
|
||||
} break;
|
||||
case MENU_USE_BLEND_ENABLED:
|
||||
case MENU_USE_BLEND_DISABLED: {
|
||||
bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Change Animation Use Blend"));
|
||||
undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
|
||||
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
|
||||
undo_redo->commit_action();
|
||||
queue_redraw();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3488,6 +3515,9 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Anim
|
|||
if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
|
||||
undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
|
||||
}
|
||||
if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
|
||||
undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
|
||||
}
|
||||
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
@ -5618,6 +5648,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
|
|||
if (tc.track_type == Animation::TYPE_VALUE) {
|
||||
tc.update_mode = animation->value_track_get_update_mode(idx);
|
||||
}
|
||||
if (tc.track_type == Animation::TYPE_AUDIO) {
|
||||
tc.use_blend = animation->audio_track_is_use_blend(idx);
|
||||
}
|
||||
tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
|
||||
tc.enabled = animation->track_is_enabled(idx);
|
||||
for (int i = 0; i < animation->track_get_key_count(idx); i++) {
|
||||
|
@ -5662,6 +5695,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
|
|||
if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
|
||||
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
|
||||
}
|
||||
if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
|
||||
undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
|
||||
}
|
||||
|
||||
for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);
|
||||
|
|
|
@ -220,7 +220,9 @@ class AnimationTrackEdit : public Control {
|
|||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_ADD_RESET,
|
||||
MENU_KEY_DELETE
|
||||
MENU_KEY_DELETE,
|
||||
MENU_USE_BLEND_ENABLED,
|
||||
MENU_USE_BLEND_DISABLED,
|
||||
};
|
||||
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
|
@ -566,6 +568,7 @@ class AnimationTrackEditor : public VBoxContainer {
|
|||
Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
|
||||
bool loop_wrap = false;
|
||||
bool enabled = false;
|
||||
bool use_blend = false;
|
||||
|
||||
struct Key {
|
||||
float time = 0;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-591 421.5h7v14h-7z" fill="#a3e595" opacity=".5"/><g fill="none" stroke="#a3e595" stroke-linecap="square" stroke-width="2"><path d="m-585 434.5v-12"/><path d="m-590 422.5v12"/><path d="m-581.5 422.5h-12"/></g></svg>
|
After Width: | Height: | Size: 361 B |
|
@ -0,0 +1 @@
|
|||
<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-587.5 423.244c-3.995 2.354-7 6.775-7 11.256v1h14v-1c0-4.48-3.005-8.901-7-11.256z" fill="#95c6e8" opacity=".5"/><g fill="none" stroke="#95c6e8" stroke-linecap="square" stroke-width="2"><path d="m-581.5 422.5c-6 0-12 6-12 12"/><path d="m-581.5 434.5c0-6-6-12-12-12"/></g></svg>
|
After Width: | Height: | Size: 422 B |
|
@ -72,12 +72,10 @@ void AudioStreamPlayer2D::_notification(int p_what) {
|
|||
_update_panning();
|
||||
}
|
||||
|
||||
if (setplay.get() >= 0 && stream.is_valid()) {
|
||||
if (setplayback.is_valid() && setplay.get() >= 0) {
|
||||
active.set();
|
||||
Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
|
||||
ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
|
||||
AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
|
||||
stream_playbacks.push_back(new_playback);
|
||||
AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
|
||||
setplayback.unref();
|
||||
setplay.set(-1);
|
||||
}
|
||||
|
||||
|
@ -255,7 +253,11 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
|
|||
if (stream->is_monophonic() && is_playing()) {
|
||||
stop();
|
||||
}
|
||||
Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
|
||||
ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
|
||||
|
||||
stream_playbacks.push_back(stream_playback);
|
||||
setplayback = stream_playback;
|
||||
setplay.set(p_from_pos);
|
||||
active.set();
|
||||
set_physics_process_internal(true);
|
||||
|
@ -390,6 +392,10 @@ bool AudioStreamPlayer2D::get_stream_paused() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AudioStreamPlayer2D::has_stream_playback() {
|
||||
return !stream_playbacks.is_empty();
|
||||
}
|
||||
|
||||
Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
|
||||
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
|
||||
return stream_playbacks[stream_playbacks.size() - 1];
|
||||
|
@ -458,6 +464,7 @@ void AudioStreamPlayer2D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength);
|
||||
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
|
||||
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
|
||||
|
|
|
@ -56,6 +56,7 @@ private:
|
|||
|
||||
SafeFlag active{ false };
|
||||
SafeNumeric<float> setplay{ -1.0 };
|
||||
Ref<AudioStreamPlayback> setplayback;
|
||||
|
||||
Vector<AudioFrame> volume_vector;
|
||||
|
||||
|
@ -129,6 +130,7 @@ public:
|
|||
void set_panning_strength(float p_panning_strength);
|
||||
float get_panning_strength() const;
|
||||
|
||||
bool has_stream_playback();
|
||||
Ref<AudioStreamPlayback> get_stream_playback();
|
||||
|
||||
AudioStreamPlayer2D();
|
||||
|
|
|
@ -284,14 +284,12 @@ void AudioStreamPlayer3D::_notification(int p_what) {
|
|||
volume_vector = _update_panning();
|
||||
}
|
||||
|
||||
if (setplay.get() >= 0 && stream.is_valid()) {
|
||||
if (setplayback.is_valid() && setplay.get() >= 0) {
|
||||
active.set();
|
||||
Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
|
||||
ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
|
||||
HashMap<StringName, Vector<AudioFrame>> bus_map;
|
||||
bus_map[_get_actual_bus()] = volume_vector;
|
||||
AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
|
||||
stream_playbacks.push_back(new_playback);
|
||||
AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
|
||||
setplayback.unref();
|
||||
setplay.set(-1);
|
||||
}
|
||||
|
||||
|
@ -580,14 +578,21 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
|
|||
if (stream->is_monophonic() && is_playing()) {
|
||||
stop();
|
||||
}
|
||||
Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
|
||||
ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
|
||||
|
||||
stream_playbacks.push_back(stream_playback);
|
||||
setplayback = stream_playback;
|
||||
setplay.set(p_from_pos);
|
||||
active.set();
|
||||
set_physics_process_internal(true);
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::seek(float p_seconds) {
|
||||
stop();
|
||||
play(p_seconds);
|
||||
if (is_playing()) {
|
||||
stop();
|
||||
play(p_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStreamPlayer3D::stop() {
|
||||
|
@ -783,6 +788,10 @@ bool AudioStreamPlayer3D::get_stream_paused() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AudioStreamPlayer3D::has_stream_playback() {
|
||||
return !stream_playbacks.is_empty();
|
||||
}
|
||||
|
||||
Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
|
||||
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
|
||||
return stream_playbacks[stream_playbacks.size() - 1];
|
||||
|
@ -875,6 +884,7 @@ void AudioStreamPlayer3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength);
|
||||
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
|
||||
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
|
||||
|
|
|
@ -69,6 +69,7 @@ private:
|
|||
|
||||
SafeFlag active{ false };
|
||||
SafeNumeric<float> setplay{ -1.0 };
|
||||
Ref<AudioStreamPlayback> setplayback;
|
||||
|
||||
AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE;
|
||||
float volume_db = 0.0;
|
||||
|
@ -188,6 +189,7 @@ public:
|
|||
void set_panning_strength(float p_panning_strength);
|
||||
float get_panning_strength() const;
|
||||
|
||||
bool has_stream_playback();
|
||||
Ref<AudioStreamPlayback> get_stream_playback();
|
||||
|
||||
AudioStreamPlayer3D();
|
||||
|
|
|
@ -431,6 +431,17 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
|
|||
}
|
||||
}
|
||||
|
||||
if (a->track_get_type(i) == Animation::TYPE_AUDIO) {
|
||||
if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) {
|
||||
TrackNodeCache::AudioAnim aa;
|
||||
aa.object = (Object *)child;
|
||||
aa.audio_stream.instantiate();
|
||||
aa.audio_stream->set_polyphony(audio_max_polyphony);
|
||||
|
||||
node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa;
|
||||
}
|
||||
}
|
||||
|
||||
node_cache->last_setup_pass = setup_pass;
|
||||
}
|
||||
}
|
||||
|
@ -820,52 +831,40 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
|||
if (!nc->node || is_stopping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_seeked) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (!can_call) {
|
||||
continue; // To avoid spamming the preview in editor.
|
||||
}
|
||||
if (p_seeked && !can_call) {
|
||||
continue; // To avoid spamming the preview in editor.
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
int idx = a->track_find_key(i, p_time);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
HashMap<StringName, TrackNodeCache::AudioAnim>::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names());
|
||||
ERR_CONTINUE(!E); //should it continue, or create a new one?
|
||||
|
||||
TrackNodeCache::AudioAnim *aa = &E->value;
|
||||
Node *asp = Object::cast_to<Node>(aa->object);
|
||||
if (!asp) {
|
||||
continue;
|
||||
}
|
||||
aa->length = a->get_length();
|
||||
aa->time = p_time;
|
||||
aa->loop = a->get_loop_mode() != Animation::LOOP_NONE;
|
||||
aa->backward = backward;
|
||||
if (aa->accum_pass != accum_pass) {
|
||||
ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX);
|
||||
cache_update_audio[cache_update_audio_size++] = aa;
|
||||
aa->accum_pass = accum_pass;
|
||||
}
|
||||
|
||||
HashMap<int, TrackNodeCache::PlayingAudioStreamInfo> &map = aa->playing_streams;
|
||||
// Find stream.
|
||||
int idx = -1;
|
||||
if (p_seeked) {
|
||||
idx = a->track_find_key(i, p_time);
|
||||
// Discard previous stream when seeking.
|
||||
if (map.has(idx)) {
|
||||
aa->audio_stream_playback->stop_stream(map[idx].index);
|
||||
map.erase(idx);
|
||||
}
|
||||
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (!stream.is_valid()) {
|
||||
nc->node->call(SNAME("stop"));
|
||||
nc->audio_playing = false;
|
||||
playing_caches.erase(nc);
|
||||
} else {
|
||||
float start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
start_ofs += p_time - a->track_get_key_time(i, idx);
|
||||
float end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
float len = stream->get_length();
|
||||
|
||||
if (start_ofs > len - end_ofs) {
|
||||
nc->node->call(SNAME("stop"));
|
||||
nc->audio_playing = false;
|
||||
playing_caches.erase(nc);
|
||||
continue;
|
||||
}
|
||||
|
||||
nc->node->call(SNAME("set_stream"), stream);
|
||||
nc->node->call(SNAME("play"), start_ofs);
|
||||
|
||||
nc->audio_playing = true;
|
||||
playing_caches.insert(nc);
|
||||
if (len && end_ofs > 0) { //force an end at a time
|
||||
nc->audio_len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
nc->audio_len = 0;
|
||||
}
|
||||
|
||||
nc->audio_start = p_time;
|
||||
}
|
||||
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
if (p_started) {
|
||||
int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
|
||||
|
@ -875,55 +874,47 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
|||
}
|
||||
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
idx = to_play.back()->get();
|
||||
}
|
||||
}
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (!stream.is_valid()) {
|
||||
nc->node->call(SNAME("stop"));
|
||||
nc->audio_playing = false;
|
||||
playing_caches.erase(nc);
|
||||
} else {
|
||||
float start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
float end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
float len = stream->get_length();
|
||||
// Play stream.
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (stream.is_valid()) {
|
||||
double start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
double end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
double len = stream->get_length();
|
||||
|
||||
nc->node->call(SNAME("set_stream"), stream);
|
||||
nc->node->call(SNAME("play"), start_ofs);
|
||||
|
||||
nc->audio_playing = true;
|
||||
playing_caches.insert(nc);
|
||||
if (len && end_ofs > 0) { //force an end at a time
|
||||
nc->audio_len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
nc->audio_len = 0;
|
||||
}
|
||||
|
||||
nc->audio_start = p_time;
|
||||
}
|
||||
} else if (nc->audio_playing) {
|
||||
bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
|
||||
|
||||
bool stop = false;
|
||||
|
||||
if (!loop) {
|
||||
if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (nc->audio_len > 0) {
|
||||
float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
|
||||
|
||||
if (len > nc->audio_len) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
//time to stop
|
||||
nc->node->call(SNAME("stop"));
|
||||
nc->audio_playing = false;
|
||||
playing_caches.erase(nc);
|
||||
if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) {
|
||||
aa->object->call(SNAME("set_stream"), aa->audio_stream);
|
||||
aa->audio_stream_playback.unref();
|
||||
if (!playing_audio_stream_players.has(asp)) {
|
||||
playing_audio_stream_players.push_back(asp);
|
||||
}
|
||||
}
|
||||
if (!aa->object->call(SNAME("is_playing"))) {
|
||||
aa->object->call(SNAME("play"));
|
||||
}
|
||||
if (!aa->object->call(SNAME("has_stream_playback"))) {
|
||||
aa->audio_stream_playback.unref();
|
||||
continue;
|
||||
}
|
||||
if (aa->audio_stream_playback.is_null()) {
|
||||
aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback"));
|
||||
}
|
||||
|
||||
TrackNodeCache::PlayingAudioStreamInfo pasi;
|
||||
pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs);
|
||||
pasi.start = p_time;
|
||||
if (len && end_ofs > 0) { // Force an end at a time.
|
||||
pasi.len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
pasi.len = 0;
|
||||
}
|
||||
map[idx] = pasi;
|
||||
}
|
||||
|
||||
} break;
|
||||
|
@ -1223,6 +1214,53 @@ void AnimationPlayer::_animation_update_transforms() {
|
|||
ERR_CONTINUE(ba->accum_pass != accum_pass);
|
||||
ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
|
||||
}
|
||||
|
||||
for (int i = 0; i < cache_update_audio_size; i++) {
|
||||
TrackNodeCache::AudioAnim *aa = cache_update_audio[i];
|
||||
|
||||
ERR_CONTINUE(aa->accum_pass != accum_pass);
|
||||
|
||||
// Audio ending process.
|
||||
LocalVector<int> erase_list;
|
||||
for (const KeyValue<int, TrackNodeCache::PlayingAudioStreamInfo> &K : aa->playing_streams) {
|
||||
TrackNodeCache::PlayingAudioStreamInfo pasi = K.value;
|
||||
|
||||
bool stop = false;
|
||||
if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) {
|
||||
stop = true;
|
||||
}
|
||||
if (!aa->loop) {
|
||||
if (!aa->backward) {
|
||||
if (aa->time < pasi.start) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (aa->backward) {
|
||||
if (aa->time > pasi.start) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pasi.len > 0) {
|
||||
double len = 0.0;
|
||||
if (!aa->backward) {
|
||||
len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start;
|
||||
} else {
|
||||
len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time;
|
||||
}
|
||||
if (len > pasi.len) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
if (stop) {
|
||||
// Time to stop.
|
||||
aa->audio_stream_playback->stop_stream(pasi.index);
|
||||
erase_list.push_back(K.key);
|
||||
}
|
||||
}
|
||||
for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) {
|
||||
aa->playing_streams.erase(erase_list[erase_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_animation_process(double p_delta) {
|
||||
|
@ -1238,6 +1276,7 @@ void AnimationPlayer::_animation_process(double p_delta) {
|
|||
cache_update_size = 0;
|
||||
cache_update_prop_size = 0;
|
||||
cache_update_bezier_size = 0;
|
||||
cache_update_audio_size = 0;
|
||||
|
||||
AnimationData *prev_from = playback.current.from;
|
||||
_animation_process2(p_delta, started);
|
||||
|
@ -1675,6 +1714,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
|
|||
}
|
||||
|
||||
if (get_current_animation() != p_name) {
|
||||
_clear_audio_streams();
|
||||
_stop_playing_caches(false);
|
||||
}
|
||||
|
||||
|
@ -1856,6 +1896,7 @@ void AnimationPlayer::_node_removed(Node *p_node) {
|
|||
}
|
||||
|
||||
void AnimationPlayer::clear_caches() {
|
||||
_clear_audio_streams();
|
||||
_stop_playing_caches(true);
|
||||
|
||||
node_cache_map.clear();
|
||||
|
@ -1867,10 +1908,19 @@ void AnimationPlayer::clear_caches() {
|
|||
cache_update_size = 0;
|
||||
cache_update_prop_size = 0;
|
||||
cache_update_bezier_size = 0;
|
||||
cache_update_audio_size = 0;
|
||||
|
||||
emit_signal(SNAME("caches_cleared"));
|
||||
}
|
||||
|
||||
void AnimationPlayer::_clear_audio_streams() {
|
||||
for (int i = 0; i < playing_audio_stream_players.size(); i++) {
|
||||
playing_audio_stream_players[i]->call(SNAME("stop"));
|
||||
playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
|
||||
}
|
||||
playing_audio_stream_players.clear();
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_active(bool p_active) {
|
||||
if (active == p_active) {
|
||||
return;
|
||||
|
@ -1950,6 +2000,15 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode()
|
|||
return method_call_mode;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) {
|
||||
ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
|
||||
audio_max_polyphony = p_audio_max_polyphony;
|
||||
}
|
||||
|
||||
int AnimationPlayer::get_audio_max_polyphony() const {
|
||||
return audio_max_polyphony;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) {
|
||||
movie_quit_on_finish = p_enabled;
|
||||
}
|
||||
|
@ -1978,6 +2037,7 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) {
|
|||
}
|
||||
|
||||
void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
|
||||
_clear_audio_streams();
|
||||
_stop_playing_caches(p_reset);
|
||||
Playback &c = playback;
|
||||
c.blend.clear();
|
||||
|
@ -2198,6 +2258,9 @@ void AnimationPlayer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony);
|
||||
ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled);
|
||||
|
||||
|
@ -2223,6 +2286,7 @@ void AnimationPlayer::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled");
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/animation_library.h"
|
||||
#include "scene/resources/audio_stream_polyphonic.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
class AnimatedValuesBackup : public RefCounted {
|
||||
|
@ -147,6 +148,26 @@ private:
|
|||
|
||||
HashMap<StringName, BezierAnim> bezier_anim;
|
||||
|
||||
struct PlayingAudioStreamInfo {
|
||||
int64_t index = -1;
|
||||
double start = 0.0;
|
||||
double len = 0.0;
|
||||
};
|
||||
|
||||
struct AudioAnim {
|
||||
Ref<AudioStreamPolyphonic> audio_stream;
|
||||
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
|
||||
HashMap<int, PlayingAudioStreamInfo> playing_streams;
|
||||
Object *object = nullptr;
|
||||
uint64_t accum_pass = 0;
|
||||
double length = 0.0;
|
||||
double time = 0.0;
|
||||
bool loop = false;
|
||||
bool backward = false;
|
||||
};
|
||||
|
||||
HashMap<StringName, AudioAnim> audio_anim;
|
||||
|
||||
uint32_t last_setup_pass = 0;
|
||||
TrackNodeCache() {}
|
||||
};
|
||||
|
@ -187,7 +208,10 @@ private:
|
|||
int cache_update_prop_size = 0;
|
||||
TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
|
||||
int cache_update_bezier_size = 0;
|
||||
TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX];
|
||||
int cache_update_audio_size = 0;
|
||||
HashSet<TrackNodeCache *> playing_caches;
|
||||
Vector<Node *> playing_audio_stream_players;
|
||||
|
||||
uint64_t accum_pass = 1;
|
||||
float speed_scale = 1.0;
|
||||
|
@ -263,6 +287,7 @@ private:
|
|||
bool reset_on_save = true;
|
||||
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
|
||||
AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED;
|
||||
int audio_max_polyphony = 32;
|
||||
bool movie_quit_on_finish = false;
|
||||
bool processing = false;
|
||||
bool active = true;
|
||||
|
@ -278,6 +303,7 @@ private:
|
|||
void _animation_process(double p_delta);
|
||||
|
||||
void _node_removed(Node *p_node);
|
||||
void _clear_audio_streams();
|
||||
void _stop_playing_caches(bool p_reset);
|
||||
|
||||
// bind helpers
|
||||
|
@ -377,6 +403,9 @@ public:
|
|||
void set_method_call_mode(AnimationMethodCallMode p_mode);
|
||||
AnimationMethodCallMode get_method_call_mode() const;
|
||||
|
||||
void set_audio_max_polyphony(int p_audio_max_polyphony);
|
||||
int get_audio_max_polyphony() const;
|
||||
|
||||
void set_movie_quit_on_finish_enabled(bool p_enabled);
|
||||
bool is_movie_quit_on_finish_enabled() const;
|
||||
|
||||
|
|
|
@ -486,13 +486,7 @@ void AnimationTree::set_active(bool p_active) {
|
|||
}
|
||||
|
||||
if (!active && is_inside_tree()) {
|
||||
for (const TrackCache *E : playing_caches) {
|
||||
if (ObjectDB::get_instance(E->object_id)) {
|
||||
E->object->call(SNAME("stop"));
|
||||
}
|
||||
}
|
||||
|
||||
playing_caches.clear();
|
||||
_clear_caches();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,6 +525,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
|||
if (!player->has_node(player->get_root())) {
|
||||
ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
|
||||
set_active(false);
|
||||
_clear_caches();
|
||||
return false;
|
||||
}
|
||||
Node *parent = player->get_node(player->get_root());
|
||||
|
@ -763,6 +758,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
|||
|
||||
track_audio->object = child;
|
||||
track_audio->object_id = track_audio->object->get_instance_id();
|
||||
track_audio->audio_stream.instantiate();
|
||||
track_audio->audio_stream->set_polyphony(audio_max_polyphony);
|
||||
|
||||
track = track_audio;
|
||||
|
||||
|
@ -860,14 +857,32 @@ void AnimationTree::_animation_player_changed() {
|
|||
}
|
||||
|
||||
void AnimationTree::_clear_caches() {
|
||||
_clear_audio_streams();
|
||||
_clear_playing_caches();
|
||||
for (KeyValue<NodePath, TrackCache *> &K : track_cache) {
|
||||
memdelete(K.value);
|
||||
}
|
||||
playing_caches.clear();
|
||||
track_cache.clear();
|
||||
cache_valid = false;
|
||||
}
|
||||
|
||||
void AnimationTree::_clear_audio_streams() {
|
||||
for (int i = 0; i < playing_audio_stream_players.size(); i++) {
|
||||
playing_audio_stream_players[i]->call(SNAME("stop"));
|
||||
playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
|
||||
}
|
||||
playing_audio_stream_players.clear();
|
||||
}
|
||||
|
||||
void AnimationTree::_clear_playing_caches() {
|
||||
for (const TrackCache *E : playing_caches) {
|
||||
if (ObjectDB::get_instance(E->object_id)) {
|
||||
E->object->call(SNAME("stop"));
|
||||
}
|
||||
}
|
||||
playing_caches.clear();
|
||||
}
|
||||
|
||||
static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
|
||||
// Separate function to use alloca() more efficiently
|
||||
const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
|
||||
|
@ -1007,6 +1022,13 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
|
||||
t->value = t->init_value;
|
||||
} break;
|
||||
case Animation::TYPE_AUDIO: {
|
||||
TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
|
||||
for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
|
||||
PlayingAudioTrackInfo &track_info = L.value;
|
||||
track_info.volume = 0.0;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
|
@ -1026,8 +1048,8 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
bool seeked = as.seeked;
|
||||
Animation::LoopedFlag looped_flag = as.looped_flag;
|
||||
bool is_external_seeking = as.is_external_seeking;
|
||||
bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
|
||||
#ifndef _3D_DISABLED
|
||||
bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
|
||||
bool calc_root = !seeked || is_external_seeking;
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
|
@ -1046,9 +1068,6 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
int blend_idx = state.track_map[path];
|
||||
ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
|
||||
real_t blend = (*as.track_blends)[blend_idx] * weight;
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
|
||||
Animation::TrackType ttype = a->track_get_type(i);
|
||||
if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
|
||||
|
@ -1060,6 +1079,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
switch (ttype) {
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
if (track->root_motion && calc_root) {
|
||||
double prev_time = time - delta;
|
||||
|
@ -1151,6 +1173,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
if (track->root_motion && calc_root) {
|
||||
double prev_time = time - delta;
|
||||
|
@ -1241,6 +1266,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
if (track->root_motion && calc_root) {
|
||||
double prev_time = time - delta;
|
||||
|
@ -1332,6 +1360,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
} break;
|
||||
case Animation::TYPE_BLEND_SHAPE: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
|
||||
|
||||
float value;
|
||||
|
@ -1348,6 +1379,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
|
||||
|
||||
Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
|
||||
|
@ -1414,6 +1448,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
continue;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
|
||||
|
||||
if (seeked) {
|
||||
|
@ -1435,6 +1472,9 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
}
|
||||
} break;
|
||||
case Animation::TYPE_BEZIER: {
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
|
||||
|
||||
real_t bezier = a->bezier_track_interpolate(i, time);
|
||||
|
@ -1445,110 +1485,87 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
case Animation::TYPE_AUDIO: {
|
||||
TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
|
||||
|
||||
Node *asp = Object::cast_to<Node>(t->object);
|
||||
if (!asp) {
|
||||
t->playing_streams.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectID oid = a->get_instance_id();
|
||||
if (!t->playing_streams.has(oid)) {
|
||||
t->playing_streams[oid] = PlayingAudioTrackInfo();
|
||||
}
|
||||
// The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that.
|
||||
PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
|
||||
track_info.length = a->get_length();
|
||||
track_info.time = time;
|
||||
track_info.volume += blend;
|
||||
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
|
||||
track_info.backward = backward;
|
||||
track_info.use_blend = a->audio_track_is_use_blend(i);
|
||||
|
||||
HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
|
||||
// Find stream.
|
||||
int idx = -1;
|
||||
if (seeked) {
|
||||
int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
|
||||
// Discard previous stream when seeking.
|
||||
if (map.has(idx)) {
|
||||
t->audio_stream_playback->stop_stream(map[idx].index);
|
||||
map.erase(idx);
|
||||
}
|
||||
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (!stream.is_valid()) {
|
||||
t->object->call(SNAME("stop"));
|
||||
t->playing = false;
|
||||
playing_caches.erase(t);
|
||||
} else {
|
||||
double start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
start_ofs += time - a->track_get_key_time(i, idx);
|
||||
double end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
double len = stream->get_length();
|
||||
|
||||
if (start_ofs > len - end_ofs) {
|
||||
t->object->call(SNAME("stop"));
|
||||
t->playing = false;
|
||||
playing_caches.erase(t);
|
||||
continue;
|
||||
}
|
||||
|
||||
t->object->call(SNAME("set_stream"), stream);
|
||||
t->object->call(SNAME("play"), start_ofs);
|
||||
|
||||
t->playing = true;
|
||||
playing_caches.insert(t);
|
||||
if (len && end_ofs > 0) { //force an end at a time
|
||||
t->len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
t->len = 0;
|
||||
}
|
||||
|
||||
t->start = time;
|
||||
}
|
||||
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (!stream.is_valid()) {
|
||||
t->object->call(SNAME("stop"));
|
||||
t->playing = false;
|
||||
playing_caches.erase(t);
|
||||
} else {
|
||||
double start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
double end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
double len = stream->get_length();
|
||||
|
||||
t->object->call(SNAME("set_stream"), stream);
|
||||
t->object->call(SNAME("play"), start_ofs);
|
||||
|
||||
t->playing = true;
|
||||
playing_caches.insert(t);
|
||||
if (len && end_ofs > 0) { //force an end at a time
|
||||
t->len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
t->len = 0;
|
||||
}
|
||||
|
||||
t->start = time;
|
||||
}
|
||||
} else if (t->playing) {
|
||||
bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
|
||||
|
||||
bool stop = false;
|
||||
|
||||
if (!loop) {
|
||||
if (delta > 0) {
|
||||
if (time < t->start) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
if (time > t->start) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
} else if (t->len > 0) {
|
||||
double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
|
||||
|
||||
if (len > t->len) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
//time to stop
|
||||
t->object->call(SNAME("stop"));
|
||||
t->playing = false;
|
||||
playing_caches.erase(t);
|
||||
}
|
||||
idx = to_play.back()->get();
|
||||
}
|
||||
}
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Play stream.
|
||||
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
|
||||
if (stream.is_valid()) {
|
||||
double start_ofs = a->audio_track_get_key_start_offset(i, idx);
|
||||
double end_ofs = a->audio_track_get_key_end_offset(i, idx);
|
||||
double len = stream->get_length();
|
||||
|
||||
if (t->object->call(SNAME("get_stream")) != t->audio_stream) {
|
||||
t->object->call(SNAME("set_stream"), t->audio_stream);
|
||||
t->audio_stream_playback.unref();
|
||||
if (!playing_audio_stream_players.has(asp)) {
|
||||
playing_audio_stream_players.push_back(asp);
|
||||
}
|
||||
}
|
||||
if (!t->object->call(SNAME("is_playing"))) {
|
||||
t->object->call(SNAME("play"));
|
||||
}
|
||||
if (!t->object->call(SNAME("has_stream_playback"))) {
|
||||
t->audio_stream_playback.unref();
|
||||
continue;
|
||||
}
|
||||
if (t->audio_stream_playback.is_null()) {
|
||||
t->audio_stream_playback = t->object->call(SNAME("get_stream_playback"));
|
||||
}
|
||||
|
||||
PlayingAudioStreamInfo pasi;
|
||||
pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs);
|
||||
pasi.start = time;
|
||||
if (len && end_ofs > 0) { // Force an end at a time.
|
||||
pasi.len = len - start_ofs - end_ofs;
|
||||
} else {
|
||||
pasi.len = 0;
|
||||
}
|
||||
map[idx] = pasi;
|
||||
}
|
||||
|
||||
real_t db = Math::linear_to_db(MAX(blend, 0.00001));
|
||||
t->object->call(SNAME("set_volume_db"), db);
|
||||
} break;
|
||||
case Animation::TYPE_ANIMATION: {
|
||||
if (Math::is_zero_approx(blend)) {
|
||||
continue; // Nothing to blend.
|
||||
}
|
||||
TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
|
||||
|
||||
AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object);
|
||||
|
@ -1694,6 +1711,64 @@ void AnimationTree::_process_graph(double p_delta) {
|
|||
t->object->set_indexed(t->subpath, t->value);
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_AUDIO: {
|
||||
TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
|
||||
|
||||
// Audio ending process.
|
||||
LocalVector<ObjectID> erase_maps;
|
||||
for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
|
||||
PlayingAudioTrackInfo &track_info = L.value;
|
||||
float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
|
||||
LocalVector<int> erase_streams;
|
||||
HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
|
||||
for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
|
||||
PlayingAudioStreamInfo pasi = M.value;
|
||||
|
||||
bool stop = false;
|
||||
if (!t->audio_stream_playback->is_stream_playing(pasi.index)) {
|
||||
stop = true;
|
||||
}
|
||||
if (!track_info.loop) {
|
||||
if (!track_info.backward) {
|
||||
if (track_info.time < pasi.start) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (track_info.backward) {
|
||||
if (track_info.time > pasi.start) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pasi.len > 0) {
|
||||
double len = 0.0;
|
||||
if (!track_info.backward) {
|
||||
len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start;
|
||||
} else {
|
||||
len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time;
|
||||
}
|
||||
if (len > pasi.len) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
if (stop) {
|
||||
// Time to stop.
|
||||
t->audio_stream_playback->stop_stream(pasi.index);
|
||||
erase_streams.push_back(M.key);
|
||||
} else {
|
||||
t->audio_stream_playback->set_stream_volume(pasi.index, db);
|
||||
}
|
||||
}
|
||||
for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) {
|
||||
map.erase(erase_streams[erase_idx]);
|
||||
}
|
||||
if (map.size() == 0) {
|
||||
erase_maps.push_back(L.key);
|
||||
}
|
||||
}
|
||||
for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) {
|
||||
t->playing_streams.erase(erase_maps[erase_idx]);
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
} //the rest don't matter
|
||||
}
|
||||
|
@ -1819,6 +1894,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const {
|
|||
return advance_expression_base_node;
|
||||
}
|
||||
|
||||
void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) {
|
||||
ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
|
||||
audio_max_polyphony = p_audio_max_polyphony;
|
||||
}
|
||||
|
||||
int AnimationTree::get_audio_max_polyphony() const {
|
||||
return audio_max_polyphony;
|
||||
}
|
||||
|
||||
bool AnimationTree::is_state_invalid() const {
|
||||
return !state.valid;
|
||||
}
|
||||
|
@ -2034,6 +2118,9 @@ void AnimationTree::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
|
||||
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony);
|
||||
ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position);
|
||||
ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation);
|
||||
ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale);
|
||||
|
@ -2052,6 +2139,8 @@ void AnimationTree::_bind_methods() {
|
|||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback");
|
||||
ADD_GROUP("Audio", "audio_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
|
||||
ADD_GROUP("Root Motion", "root_motion_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/audio_stream_polyphonic.h"
|
||||
|
||||
class AnimationNodeBlendTree;
|
||||
class AnimationNodeStartState;
|
||||
|
@ -252,10 +253,26 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
struct TrackCacheAudio : public TrackCache {
|
||||
bool playing = false;
|
||||
struct PlayingAudioStreamInfo {
|
||||
int64_t index = -1;
|
||||
double start = 0.0;
|
||||
double len = 0.0;
|
||||
};
|
||||
|
||||
struct PlayingAudioTrackInfo {
|
||||
HashMap<int, PlayingAudioStreamInfo> stream_info;
|
||||
double length = 0.0;
|
||||
double time = 0.0;
|
||||
real_t volume = 0.0;
|
||||
bool loop = false;
|
||||
bool backward = false;
|
||||
bool use_blend = false;
|
||||
};
|
||||
|
||||
struct TrackCacheAudio : public TrackCache {
|
||||
Ref<AudioStreamPolyphonic> audio_stream;
|
||||
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
|
||||
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Animation resource RID & AudioTrack key index: PlayingAudioStreamInfo.
|
||||
|
||||
TrackCacheAudio() {
|
||||
type = Animation::TYPE_AUDIO;
|
||||
|
@ -272,6 +289,7 @@ private:
|
|||
|
||||
HashMap<NodePath, TrackCache *> track_cache;
|
||||
HashSet<TrackCache *> playing_caches;
|
||||
Vector<Node *> playing_audio_stream_players;
|
||||
|
||||
Ref<AnimationNode> root;
|
||||
NodePath advance_expression_base_node = NodePath(String("."));
|
||||
|
@ -279,6 +297,7 @@ private:
|
|||
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
|
||||
bool active = false;
|
||||
NodePath animation_player;
|
||||
int audio_max_polyphony = 32;
|
||||
|
||||
AnimationNode::State state;
|
||||
bool cache_valid = false;
|
||||
|
@ -287,6 +306,8 @@ private:
|
|||
void _setup_animation_player();
|
||||
void _animation_player_changed();
|
||||
void _clear_caches();
|
||||
void _clear_playing_caches();
|
||||
void _clear_audio_streams();
|
||||
bool _update_caches(AnimationPlayer *player);
|
||||
void _process_graph(double p_delta);
|
||||
|
||||
|
@ -348,6 +369,9 @@ public:
|
|||
void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node);
|
||||
NodePath get_advance_expression_base_node() const;
|
||||
|
||||
void set_audio_max_polyphony(int p_audio_max_polyphony);
|
||||
int get_audio_max_polyphony() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
bool is_state_invalid() const;
|
||||
|
|
|
@ -307,6 +307,10 @@ void AudioStreamPlayer::_bus_layout_changed() {
|
|||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool AudioStreamPlayer::has_stream_playback() {
|
||||
return !stream_playbacks.is_empty();
|
||||
}
|
||||
|
||||
Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() {
|
||||
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
|
||||
return stream_playbacks[stream_playbacks.size() - 1];
|
||||
|
@ -347,6 +351,7 @@ void AudioStreamPlayer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony);
|
||||
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer::has_stream_playback);
|
||||
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
|
||||
|
|
|
@ -107,6 +107,7 @@ public:
|
|||
void set_stream_paused(bool p_pause);
|
||||
bool get_stream_paused() const;
|
||||
|
||||
bool has_stream_playback();
|
||||
Ref<AudioStreamPlayback> get_stream_playback();
|
||||
|
||||
AudioStreamPlayer();
|
||||
|
|
|
@ -127,6 +127,10 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
|
|||
}
|
||||
}
|
||||
return true;
|
||||
} else if (what == "use_blend") {
|
||||
if (track_get_type(track) == TYPE_AUDIO) {
|
||||
audio_track_set_use_blend(track, p_value);
|
||||
}
|
||||
} else if (what == "interp") {
|
||||
track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
|
||||
} else if (what == "loop_wrap") {
|
||||
|
@ -528,7 +532,10 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
|
|||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else if (what == "use_blend") {
|
||||
if (track_get_type(track) == TYPE_AUDIO) {
|
||||
r_ret = audio_track_is_use_blend(track);
|
||||
}
|
||||
} else if (what == "interp") {
|
||||
r_ret = track_get_interpolation_type(track);
|
||||
} else if (what == "loop_wrap") {
|
||||
|
@ -834,6 +841,9 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
}
|
||||
if (track_get_type(i) == TYPE_AUDIO) {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/use_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3581,6 +3591,27 @@ real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
|
|||
return at->values[p_key].value.end_offset;
|
||||
}
|
||||
|
||||
void Animation::audio_track_set_use_blend(int p_track, bool p_enable) {
|
||||
ERR_FAIL_INDEX(p_track, tracks.size());
|
||||
Track *t = tracks[p_track];
|
||||
ERR_FAIL_COND(t->type != TYPE_AUDIO);
|
||||
|
||||
AudioTrack *at = static_cast<AudioTrack *>(t);
|
||||
|
||||
at->use_blend = p_enable;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
bool Animation::audio_track_is_use_blend(int p_track) const {
|
||||
ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
|
||||
Track *t = tracks[p_track];
|
||||
ERR_FAIL_COND_V(t->type != TYPE_AUDIO, false);
|
||||
|
||||
AudioTrack *at = static_cast<AudioTrack *>(t);
|
||||
|
||||
return at->use_blend;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) {
|
||||
|
@ -3813,6 +3844,8 @@ void Animation::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "track_idx", "key_idx"), &Animation::audio_track_get_key_stream);
|
||||
ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
|
||||
ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
|
||||
ClassDB::bind_method(D_METHOD("audio_track_set_use_blend", "track_idx", "enable"), &Animation::audio_track_set_use_blend);
|
||||
ClassDB::bind_method(D_METHOD("audio_track_is_use_blend", "track_idx"), &Animation::audio_track_is_use_blend);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key);
|
||||
ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
|
||||
|
|
|
@ -213,6 +213,7 @@ private:
|
|||
|
||||
struct AudioTrack : public Track {
|
||||
Vector<TKey<AudioKey>> values;
|
||||
bool use_blend = true;
|
||||
|
||||
AudioTrack() {
|
||||
type = TYPE_AUDIO;
|
||||
|
@ -447,6 +448,8 @@ public:
|
|||
Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const;
|
||||
real_t audio_track_get_key_start_offset(int p_track, int p_key) const;
|
||||
real_t audio_track_get_key_end_offset(int p_track, int p_key) const;
|
||||
void audio_track_set_use_blend(int p_track, bool p_enable);
|
||||
bool audio_track_is_use_blend(int p_track) const;
|
||||
|
||||
int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation);
|
||||
void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
|
||||
|
|
Loading…
Reference in New Issue