diff options
Diffstat (limited to 'scene')
-rw-r--r-- | scene/2d/cpu_particles_2d.cpp | 2 | ||||
-rw-r--r-- | scene/2d/physics_body_2d.cpp | 6 | ||||
-rw-r--r-- | scene/2d/physics_body_2d.h | 2 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 46 | ||||
-rw-r--r-- | scene/2d/tile_map.h | 2 | ||||
-rw-r--r-- | scene/3d/bone_attachment_3d.cpp | 2 | ||||
-rw-r--r-- | scene/3d/physics_body_3d.cpp | 846 | ||||
-rw-r--r-- | scene/3d/physics_body_3d.h | 188 | ||||
-rw-r--r-- | scene/3d/skeleton_3d.cpp | 1 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 4 | ||||
-rw-r--r-- | scene/gui/color_picker.cpp | 2 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 6 | ||||
-rw-r--r-- | scene/gui/graph_node.cpp | 2 | ||||
-rw-r--r-- | scene/gui/popup.cpp | 8 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 4 | ||||
-rw-r--r-- | scene/gui/scroll_bar.cpp | 2 | ||||
-rw-r--r-- | scene/gui/scroll_container.cpp | 2 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 14 | ||||
-rw-r--r-- | scene/resources/bit_map.cpp | 2 | ||||
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 4 | ||||
-rw-r--r-- | scene/resources/font.cpp | 16 | ||||
-rw-r--r-- | scene/resources/tile_set.cpp | 391 | ||||
-rw-r--r-- | scene/resources/tile_set.h | 36 |
23 files changed, 1226 insertions, 362 deletions
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index b836497627..cf2632f380 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -155,7 +155,7 @@ void CPUParticles2D::_update_mesh_texture() { Vector<Vector2> vertices; vertices.push_back(-tex_size * 0.5); vertices.push_back(-tex_size * 0.5 + Vector2(tex_size.x, 0)); - vertices.push_back(-tex_size * 0.5 + Vector2(tex_size.x, tex_size.y)); + vertices.push_back(-tex_size * 0.5 + tex_size); vertices.push_back(-tex_size * 0.5 + Vector2(0, tex_size.y)); Vector<Vector2> uvs; AtlasTexture *atlas_texure = Object::cast_to<AtlasTexture>(*texture); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 799ed47862..c3dc9ab92b 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -58,7 +58,8 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_t PhysicsServer2D::MotionResult result; if (move_and_collide(p_motion, result, p_margin, p_test_only)) { - if (motion_cache.is_null()) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -1369,7 +1370,8 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { slide_colliders.resize(p_bounce + 1); } - if (slide_colliders[p_bounce].is_null()) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index e9930b13a0..5d0d98a2df 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -334,7 +334,7 @@ private: int max_slides = 4; int platform_layer; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - float floor_snap_length = 0; + real_t floor_snap_length = 0; real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); uint32_t moving_platform_floor_layers = UINT32_MAX; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index a139a92ab4..03db9c0d32 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -891,7 +891,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List modulate.a *= 0.3; } } - draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, modulate); + draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate); // --- Occluders --- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { @@ -1008,15 +1008,19 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { +void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation) { ERR_FAIL_COND(!p_tile_set.is_valid()); ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } + // Get the texture. Ref<Texture2D> tex = atlas_source->get_texture(); if (!tex.is_valid()) { @@ -1032,13 +1036,15 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Get tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); - // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords); + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; + + // Compute the offset. Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile); - // Compute the destination rectangle in the CanvasItem. + // Get destination rect. Rect2 dest_rect; - dest_rect.size = source_rect.size; + dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size; dest_rect.size.x += FP_ADJUST; dest_rect.size.y += FP_ADJUST; @@ -1057,12 +1063,28 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe dest_rect.size.y = -dest_rect.size.y; } - // Get the tile modulation. - Color modulate = tile_data->get_modulate(); - modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); - // Draw the tile. - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t time = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, 0.0); + + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + + time += frame_duration; + } + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + } } } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 3ac50fc7cc..ca38baa550 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -305,7 +305,7 @@ public: void set_quadrant_size(int p_size); int get_quadrant_size() const; - static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); + static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); // Layers management. int get_layers_count() const; diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 70361f4787..c34c150145 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -161,7 +161,7 @@ void BoneAttachment3D::_check_bind() { bone_idx = sk->find_bone(bone_name); } if (bone_idx != -1) { - sk->call_deferred("connect", "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); + sk->call_deferred(SNAME("connect"), "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); bound = true; call_deferred(SNAME("on_bone_pose_update"), bone_idx); } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 35f6d0930a..48da186860 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -38,8 +38,8 @@ #endif void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001)); + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -95,10 +95,11 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { PhysicsServer3D::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } -Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin) { +Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin, int p_max_collisions) { PhysicsServer3D::MotionResult result; - if (move_and_collide(p_motion, result, p_margin, p_test_only)) { - if (motion_cache.is_null()) { + if (move_and_collide(p_motion, result, p_margin, p_test_only, p_max_collisions)) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -111,9 +112,9 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t return Ref<KinematicCollision3D>(); } -bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { +bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, int p_max_collisions, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Transform3D gt = get_global_transform(); - bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); + bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_max_collisions, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, @@ -125,9 +126,9 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::M if (colliding) { // Can't just use margin as a threshold because collision depth is calculated on unsafe motion, // so even in normal resting cases the depth can be a bit more than the margin. - precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction); + precision += motion_length * (r_result.unsafe_fraction - r_result.safe_fraction); - if (r_result.collision_depth > (real_t)p_margin + precision) { + if (r_result.collisions[0].depth > (real_t)p_margin + precision) { p_cancel_sliding = false; } } @@ -167,7 +168,7 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::M return colliding; } -bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision, real_t p_margin) { +bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision, real_t p_margin, int p_max_collisions) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; @@ -176,7 +177,7 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result); } - return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); + return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r, p_max_collisions); } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -1037,64 +1038,116 @@ void RigidDynamicBody3D::_reload_physics_characteristics() { #define FLOOR_ANGLE_THRESHOLD 0.01 bool CharacterBody3D::move_and_slide() { - bool was_on_floor = on_floor; - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - + previous_position = get_global_transform().origin; for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { linear_velocity[i] = 0.0; } } - Vector3 current_floor_velocity = floor_velocity; - if ((on_floor || on_wall) && on_floor_body.is_valid()) { - //this approach makes sure there is less delay between the actual body velocity and the one we saved - PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(on_floor_body); - if (bs) { - Transform3D gt = get_global_transform(); - Vector3 local_position = gt.origin - bs->get_transform().origin; - current_floor_velocity = bs->get_velocity_at_local_position(local_position); + Vector3 current_platform_velocity = platform_velocity; + + if ((collision_state.floor || collision_state.wall) && platform_rid.is_valid()) { + bool excluded = false; + if (collision_state.floor) { + excluded = (moving_platform_floor_layers & platform_layer) == 0; + } else if (collision_state.wall) { + excluded = (moving_platform_wall_layers & platform_layer) == 0; + } + if (!excluded) { + //this approach makes sure there is less delay between the actual body velocity and the one we saved + PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid); + if (bs) { + Transform3D gt = get_global_transform(); + Vector3 local_position = gt.origin - bs->get_transform().origin; + current_platform_velocity = bs->get_velocity_at_local_position(local_position); + } + } else { + current_platform_velocity = Vector3(); } } motion_results.clear(); - on_floor = false; - on_ceiling = false; - on_wall = false; - floor_normal = Vector3(); - floor_velocity = Vector3(); - if (!current_floor_velocity.is_equal_approx(Vector3()) && on_floor_body.is_valid()) { + bool was_on_floor = collision_state.floor; + collision_state.state = 0; + + if (!current_platform_velocity.is_equal_approx(Vector3())) { PhysicsServer3D::MotionResult floor_result; Set<RID> exclude; - exclude.insert(on_floor_body); - if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, false, exclude)) { + exclude.insert(platform_rid); + if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, 1, false, false, exclude)) { motion_results.push_back(floor_result); - _set_collision_direction(floor_result); + + CollisionState result_state; + _set_collision_direction(floor_result, result_state); } } - on_floor_body = RID(); - Vector3 motion = linear_velocity * delta; + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor); + } else { + _move_and_slide_free(delta); + } + + // Compute real velocity. + real_velocity = get_position_delta() / delta; + + if (moving_platform_apply_velocity_on_leave != PLATFORM_VEL_ON_LEAVE_NEVER) { + // Add last platform velocity when just left a moving platform. + if (!collision_state.floor && !collision_state.wall) { + if (moving_platform_apply_velocity_on_leave == PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY && current_platform_velocity.dot(up_direction) < 0) { + current_platform_velocity = current_platform_velocity.slide(up_direction); + } + linear_velocity += current_platform_velocity; + } + } + + // Reset the gravity accumulation when touching the ground. + if (collision_state.floor && linear_velocity.dot(up_direction) <= 0) { + linear_velocity = linear_velocity.slide(up_direction); + } + + return motion_results.size() > 0; +} + +void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) { + Vector3 motion = linear_velocity * p_delta; + Vector3 motion_slide_up = motion.slide(up_direction); + Vector3 prev_floor_normal = floor_normal; + + platform_rid = RID(); + platform_velocity = Vector3(); + floor_normal = Vector3(); + wall_normal = Vector3(); // No sliding on first attempt to keep floor motion stable when possible, - // when stop on slope is enabled. + // When stop on slope is enabled or when there is no up direction. bool sliding_enabled = !floor_stop_on_slope; + // Constant speed can be applied only the first time sliding is enabled. + bool can_apply_constant_speed = sliding_enabled; + bool first_slide = true; + bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + Vector3 total_travel; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; - bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); + bool collided = move_and_collide(motion, result, margin, false, 4, !sliding_enabled); + if (collided) { motion_results.push_back(result); - _set_collision_direction(result); - if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + CollisionState previous_state = collision_state; + + CollisionState result_state; + _set_collision_direction(result, result_state); + + if (collision_state.floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { Transform3D gt = get_global_transform(); - if (result.travel.length() > margin) { - gt.origin -= result.travel.slide(up_direction); - } else { + real_t travel_total = result.travel.length(); + if (travel_total <= margin + CMP_EPSILON) { gt.origin -= result.travel; } set_global_transform(gt); @@ -1105,96 +1158,355 @@ bool CharacterBody3D::move_and_slide() { if (result.remainder.is_equal_approx(Vector3())) { motion = Vector3(); + last_motion = result.travel; break; } - if (sliding_enabled || !on_floor) { - Vector3 slide_motion = result.remainder.slide(result.collision_normal); - if (slide_motion.dot(linear_velocity) > 0.0) { - motion = slide_motion; - } else { - motion = Vector3(); + // Apply regular sliding by default. + bool apply_default_sliding = true; + + // Wall collision checks. + if (result_state.wall && (motion_slide_up.dot(wall_normal) <= 0)) { + // Move on floor only checks. + if (floor_block_on_wall) { + // Needs horizontal motion from current motion instead of motion_slide_up + // to properly test the angle and avoid standing on slopes + Vector3 horizontal_motion = motion.slide(up_direction); + Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized(); + real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(horizontal_motion.normalized()))); + + // Avoid to move forward on a wall if floor_block_on_wall is true. + // Applies only when the motion angle is under 90 degrees, + // in order to avoid blocking lateral motion along a wall. + if (motion_angle < .5 * Math_PI) { + apply_default_sliding = false; + + if (p_was_on_floor && !vel_dir_facing_up) { + // Cancel the motion. + Transform3D gt = get_global_transform(); + real_t travel_total = result.travel.length(); + real_t cancel_dist_max = MIN(0.1, margin * 20); + if (travel_total < margin + CMP_EPSILON) { + gt.origin -= result.travel; + } else if (travel_total < cancel_dist_max) { // If the movement is large the body can be prevented from reaching the walls. + gt.origin -= result.travel.slide(up_direction); + // Keep remaining motion in sync with amount canceled. + motion = motion.slide(up_direction); + } + set_global_transform(gt); + result.travel = Vector3(); // Cancel for constant speed computation. + + // Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations. + _snap_on_floor(true, false); + } else { + // If the movement is not cancelled we only keep the remaining. + motion = result.remainder; + } + + // Apply slide on forward in order to allow only lateral motion on next step. + Vector3 forward = wall_normal.slide(up_direction).normalized(); + motion = motion.slide(forward); + // Avoid accelerating when you jump on the wall and smooth falling. + linear_velocity = linear_velocity.slide(forward); + + // Allow only lateral motion along previous floor when already on floor. + // Fixes slowing down when moving in diagonal against an inclined wall. + if (p_was_on_floor && !vel_dir_facing_up && (motion.dot(up_direction) > 0.0)) { + // Slide along the corner between the wall and previous floor. + Vector3 floor_side = prev_floor_normal.cross(wall_normal); + if (floor_side != Vector3()) { + motion = floor_side * motion.dot(floor_side); + } + } + + // Stop all motion when a second wall is hit (unless sliding down or jumping), + // in order to avoid jittering in corner cases. + bool stop_all_motion = previous_state.wall && !vel_dir_facing_up; + + // Allow sliding when the body falls. + if (!collision_state.floor && motion.dot(up_direction) < 0) { + Vector3 slide_motion = motion.slide(wall_normal); + // Test again to allow sliding only if the result goes downwards. + // Fixes jittering issues at the bottom of inclined walls. + if (slide_motion.dot(up_direction) < 0) { + stop_all_motion = false; + motion = slide_motion; + } + } + + if (stop_all_motion) { + motion = Vector3(); + linear_velocity = Vector3(); + } + } + } + + // Stop horizontal motion when under wall slide threshold. + if (p_was_on_floor && (wall_min_slide_angle > 0.0) && result_state.wall) { + Vector3 horizontal_normal = wall_normal.slide(up_direction).normalized(); + real_t motion_angle = Math::abs(Math::acos(-horizontal_normal.dot(motion_slide_up.normalized()))); + if (motion_angle < wall_min_slide_angle) { + motion = up_direction * motion.dot(up_direction); + linear_velocity = up_direction * linear_velocity.dot(up_direction); + + apply_default_sliding = false; + } + } + } + + if (apply_default_sliding) { + // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. + if ((sliding_enabled || !collision_state.floor) && (!collision_state.ceiling || slide_on_ceiling || !vel_dir_facing_up)) { + const PhysicsServer3D::MotionCollision &collision = result.collisions[0]; + Vector3 slide_motion = result.remainder.slide(collision.normal); + if (slide_motion.dot(linear_velocity) > 0.0) { + motion = slide_motion; + } else { + motion = Vector3(); + } + if (slide_on_ceiling && result_state.ceiling) { + // Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall. + if (vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(collision.normal); + } else { + // Avoid acceleration in slope when falling. + linear_velocity = up_direction * up_direction.dot(linear_velocity); + } + } + } + // No sliding on first attempt to keep floor motion stable when possible. + else { + motion = result.remainder; + if (result_state.ceiling && !slide_on_ceiling && vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + motion = motion.slide(up_direction); + } } - } else { - motion = result.remainder; } + + // Apply Constant Speed. + if (p_was_on_floor && floor_constant_speed && collision_state.floor && !motion.is_equal_approx(Vector3())) { + motion = motion.normalized() * MAX(0, (motion_slide_up.length() - result.travel.slide(up_direction).length() - total_travel.slide(up_direction).length())); + } + + total_travel += result.travel; + } + // When you move forward in a downward slope you don’t collide because you will be in the air. + // This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied. + else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) { + can_apply_constant_speed = false; + sliding_enabled = true; + Transform3D gt = get_global_transform(); + gt.origin = gt.origin - result.travel; + set_global_transform(gt); + + Vector3 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; } + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; sliding_enabled = true; + first_slide = false; + + if (!motion.is_equal_approx(Vector3())) { + last_motion = motion; + } if (!collided || motion.is_equal_approx(Vector3())) { break; } } - if (was_on_floor && !on_floor && !snap.is_equal_approx(Vector3())) { - // Apply snap. - Transform3D gt = get_global_transform(); + _snap_on_floor(p_was_on_floor, vel_dir_facing_up); + + // Reset the gravity accumulation when touching the ground. + if (collision_state.floor && !vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + } +} + +void CharacterBody3D::_move_and_slide_free(double p_delta) { + Vector3 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector3(); + platform_velocity = Vector3(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionResult result; - if (move_and_collide(snap, result, margin, true, false, true)) { - bool apply = true; - if (up_direction != Vector3()) { - if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - if (floor_stop_on_slope) { - // move and collide may stray the object a bit because of pre un-stucking, - // so only ensure that motion happens on floor direction in this case. - if (result.travel.length() > margin) { - result.travel = result.travel.project(up_direction); - } else { - result.travel = Vector3(); - } - } - } else { - apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. + + bool collided = move_and_collide(motion, result, margin, false, 1, false); + + if (collided) { + motion_results.push_back(result); + + CollisionState result_state; + _set_collision_direction(result, result_state); + + if (wall_min_slide_angle != 0 && Math::acos(wall_normal.dot(-linear_velocity.normalized())) < wall_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + motion = Vector3(); + if (result.travel.length() < margin + CMP_EPSILON) { + Transform3D gt = get_global_transform(); + gt.origin -= result.travel; + set_global_transform(gt); } + } else if (first_slide) { + Vector3 motion_slide_norm = result.remainder.slide(wall_normal).normalized(); + motion = motion_slide_norm * (motion.length() - result.travel.length()); + } else { + motion = result.remainder.slide(wall_normal); } - if (apply) { - gt.origin += result.travel; - set_global_transform(gt); + + if (motion.dot(linear_velocity) <= 0.0) { + motion = Vector3(); } } + + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector3())) { + break; + } } +} - if (!on_floor && !on_wall) { - // Add last platform velocity when just left a moving platform. - linear_velocity += current_floor_velocity; +void CharacterBody3D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { + if (collision_state.floor || !was_on_floor || vel_dir_facing_up) { + return; } - // Reset the gravity accumulation when touching the ground. - if (on_floor && linear_velocity.dot(up_direction) <= 0) { - linear_velocity = linear_velocity.slide(up_direction); + real_t length = MAX(floor_snap_length, margin); + Transform3D gt = get_global_transform(); + PhysicsServer3D::MotionResult result; + if (move_and_collide(-up_direction * length, result, margin, true, 4, false, true)) { + CollisionState result_state; + // Apply direction for floor only. + _set_collision_direction(result, result_state, CollisionState(true, false, false)); + + if (result_state.floor) { + if (floor_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + if (result.travel.length() > margin) { + result.travel = up_direction * up_direction.dot(result.travel); + } else { + result.travel = Vector3(); + } + } + + gt.origin += result.travel; + set_global_transform(gt); + } } +} - return motion_results.size() > 0; +bool CharacterBody3D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { + if (Math::is_zero_approx(floor_snap_length) || up_direction == Vector3() || collision_state.floor || !was_on_floor || vel_dir_facing_up) { + return false; + } + + PhysicsServer3D::MotionResult result; + if (move_and_collide(-up_direction * floor_snap_length, result, margin, true, 4, false, true)) { + CollisionState result_state; + // Don't apply direction for any type. + _set_collision_direction(result, result_state, CollisionState()); + + return result_state.floor; + } + + return false; } -void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result) { - if (up_direction == Vector3()) { - //all is a wall - on_wall = true; - } else { - if (p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - on_floor = true; - floor_normal = p_result.collision_normal; - on_floor_body = p_result.collider; - floor_velocity = p_result.collider_velocity; - } else if (p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; - } else { - on_wall = true; +void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state) { + r_state.state = 0; + + real_t wall_depth = -1.0; + real_t floor_depth = -1.0; + + bool was_on_wall = collision_state.wall; + Vector3 prev_wall_normal = wall_normal; + int wall_collision_count = 0; + Vector3 combined_wall_normal; + + for (int i = p_result.collision_count - 1; i >= 0; i--) { + const PhysicsServer3D::MotionCollision &collision = p_result.collisions[i]; + + if (motion_mode == MOTION_MODE_GROUNDED) { + // Check if any collision is floor. + real_t floor_angle = collision.get_angle(up_direction); + if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.floor = true; + if (p_apply_state.floor && collision.depth > floor_depth) { + collision_state.floor = true; + floor_normal = collision.normal; + floor_depth = collision.depth; + _set_platform_data(collision); + } + continue; + } + + // Check if any collision is ceiling. + real_t ceiling_angle = collision.get_angle(-up_direction); + if (ceiling_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.ceiling = true; + if (p_apply_state.ceiling) { + collision_state.ceiling = true; + } + continue; + } + } + + // Collision is wall by default. + r_state.wall = true; + + if (p_apply_state.wall && collision.depth > wall_depth) { + collision_state.wall = true; + wall_depth = collision.depth; + wall_normal = collision.normal; + // Don't apply wall velocity when the collider is a CharacterBody3D. - if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { - on_floor_body = p_result.collider; - floor_velocity = p_result.collider_velocity; + if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(collision.collider_id)) == nullptr) { + _set_platform_data(collision); + } + } + + // Collect normal for calculating average. + combined_wall_normal += collision.normal; + wall_collision_count++; + } + + if (r_state.wall) { + if (wall_collision_count > 1 && !r_state.floor) { + // Check if wall normals cancel out to floor support. + if (!r_state.floor && motion_mode == MOTION_MODE_GROUNDED) { + combined_wall_normal.normalize(); + real_t floor_angle = Math::acos(combined_wall_normal.dot(up_direction)); + if (floor_angle <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + r_state.floor = true; + r_state.wall = false; + if (p_apply_state.floor) { + collision_state.floor = true; + floor_normal = combined_wall_normal; + } + if (p_apply_state.wall) { + collision_state.wall = was_on_wall; + wall_normal = prev_wall_normal; + } + return; + } } } } } +void CharacterBody3D::_set_platform_data(const PhysicsServer3D::MotionCollision &p_collision) { + platform_rid = p_collision.collider; + platform_velocity = p_collision.collider_velocity; + platform_layer = PhysicsServer3D::get_singleton()->body_get_collision_layer(platform_rid); +} + void CharacterBody3D::set_safe_margin(real_t p_margin) { margin = p_margin; } @@ -1212,40 +1524,56 @@ void CharacterBody3D::set_linear_velocity(const Vector3 &p_velocity) { } bool CharacterBody3D::is_on_floor() const { - return on_floor; + return collision_state.floor; } bool CharacterBody3D::is_on_floor_only() const { - return on_floor && !on_wall && !on_ceiling; + return collision_state.floor && !collision_state.wall && !collision_state.ceiling; } bool CharacterBody3D::is_on_wall() const { - return on_wall; + return collision_state.wall; } bool CharacterBody3D::is_on_wall_only() const { - return on_wall && !on_floor && !on_ceiling; + return collision_state.wall && !collision_state.floor && !collision_state.ceiling; } bool CharacterBody3D::is_on_ceiling() const { - return on_ceiling; + return collision_state.ceiling; } bool CharacterBody3D::is_on_ceiling_only() const { - return on_ceiling && !on_floor && !on_wall; + return collision_state.ceiling && !collision_state.floor && !collision_state.wall; } Vector3 CharacterBody3D::get_floor_normal() const { return floor_normal; } +Vector3 CharacterBody3D::get_wall_normal() const { + return wall_normal; +} + +Vector3 CharacterBody3D::get_last_motion() const { + return last_motion; +} + +Vector3 CharacterBody3D::get_position_delta() const { + return get_transform().origin - previous_position; +} + +Vector3 CharacterBody3D::get_real_velocity() const { + return real_velocity; +}; + real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const { ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); return Math::acos(floor_normal.dot(p_up_direction)); } Vector3 CharacterBody3D::get_platform_velocity() const { - return floor_velocity; + return platform_velocity; } int CharacterBody3D::get_slide_collision_count() const { @@ -1263,7 +1591,8 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { slide_colliders.resize(p_bounce + 1); } - if (slide_colliders[p_bounce].is_null()) { + // Create a new instance when the cached reference is invalid or still in use in script. + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } @@ -1287,6 +1616,62 @@ void CharacterBody3D::set_floor_stop_on_slope_enabled(bool p_enabled) { floor_stop_on_slope = p_enabled; } +bool CharacterBody3D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; +} + +void CharacterBody3D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; +} + +bool CharacterBody3D::is_floor_block_on_wall_enabled() const { + return floor_block_on_wall; +} + +void CharacterBody3D::set_floor_block_on_wall_enabled(bool p_enabled) { + floor_block_on_wall = p_enabled; +} + +bool CharacterBody3D::is_slide_on_ceiling_enabled() const { + return slide_on_ceiling; +} + +void CharacterBody3D::set_slide_on_ceiling_enabled(bool p_enabled) { + slide_on_ceiling = p_enabled; +} + +uint32_t CharacterBody3D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} + +void CharacterBody3D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; +} + +uint32_t CharacterBody3D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; +} + +void CharacterBody3D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_layers = p_exclude_layers; +} + +void CharacterBody3D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} + +CharacterBody3D::MotionMode CharacterBody3D::get_motion_mode() const { + return motion_mode; +} + +void CharacterBody3D::set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_apply_velocity) { + moving_platform_apply_velocity_on_leave = p_on_leave_apply_velocity; +} + +CharacterBody3D::MovingPlatformApplyVelocityOnLeave CharacterBody3D::get_moving_platform_apply_velocity_on_leave() const { + return moving_platform_apply_velocity_on_leave; +} + int CharacterBody3D::get_max_slides() const { return max_slides; } @@ -1304,12 +1689,21 @@ void CharacterBody3D::set_floor_max_angle(real_t p_radians) { floor_max_angle = p_radians; } -const Vector3 &CharacterBody3D::get_snap() const { - return snap; +real_t CharacterBody3D::get_floor_snap_length() { + return floor_snap_length; +} + +void CharacterBody3D::set_floor_snap_length(real_t p_floor_snap_length) { + ERR_FAIL_COND(p_floor_snap_length < 0); + floor_snap_length = p_floor_snap_length; } -void CharacterBody3D::set_snap(const Vector3 &p_snap) { - snap = p_snap; +real_t CharacterBody3D::get_wall_min_slide_angle() const { + return wall_min_slide_angle; +} + +void CharacterBody3D::set_wall_min_slide_angle(real_t p_radians) { + wall_min_slide_angle = p_radians; } const Vector3 &CharacterBody3D::get_up_direction() const { @@ -1317,6 +1711,7 @@ const Vector3 &CharacterBody3D::get_up_direction() const { } void CharacterBody3D::set_up_direction(const Vector3 &p_up_direction) { + ERR_FAIL_COND_MSG(p_up_direction == Vector3(), "up_direction can't be equal to Vector3.ZERO, consider using Free motion mode instead."); up_direction = p_up_direction.normalized(); } @@ -1324,12 +1719,10 @@ void CharacterBody3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { // Reset move_and_slide() data. - on_floor = false; - on_floor_body = RID(); - on_ceiling = false; - on_wall = false; + collision_state.state = 0; + platform_rid = RID(); motion_results.clear(); - floor_velocity = Vector3(); + platform_velocity = Vector3(); } break; } } @@ -1344,14 +1737,32 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin); ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody3D::is_floor_stop_on_slope_enabled); ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody3D::set_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody3D::is_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody3D::set_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody3D::is_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody3D::set_slide_on_ceiling_enabled); + ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody3D::is_slide_on_ceiling_enabled); + + ClassDB::bind_method(D_METHOD("set_moving_platform_floor_layers", "exclude_layer"), &CharacterBody3D::set_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_floor_layers"), &CharacterBody3D::get_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_wall_layers", "exclude_layer"), &CharacterBody3D::set_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_wall_layers"), &CharacterBody3D::get_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody3D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody3D::set_max_slides); ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody3D::get_floor_max_angle); ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody3D::set_floor_max_angle); - ClassDB::bind_method(D_METHOD("get_snap"), &CharacterBody3D::get_snap); - ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CharacterBody3D::set_snap); + ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody3D::get_floor_snap_length); + ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody3D::set_floor_snap_length); + ClassDB::bind_method(D_METHOD("get_wall_min_slide_angle"), &CharacterBody3D::get_wall_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_wall_min_slide_angle", "radians"), &CharacterBody3D::set_wall_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody3D::get_up_direction); ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody3D::set_up_direction); + ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody3D::set_motion_mode); + ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody3D::get_motion_mode); + ClassDB::bind_method(D_METHOD("set_moving_platform_apply_velocity_on_leave", "on_leave_apply_velocity"), &CharacterBody3D::set_moving_platform_apply_velocity_on_leave); + ClassDB::bind_method(D_METHOD("get_moving_platform_apply_velocity_on_leave"), &CharacterBody3D::get_moving_platform_apply_velocity_on_leave); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody3D::is_on_floor); ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody3D::is_on_floor_only); @@ -1360,21 +1771,49 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody3D::is_on_wall); ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody3D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody3D::get_floor_normal); + ClassDB::bind_method(D_METHOD("get_wall_normal"), &CharacterBody3D::get_wall_normal); + ClassDB::bind_method(D_METHOD("get_last_motion"), &CharacterBody3D::get_last_motion); + ClassDB::bind_method(D_METHOD("get_position_delta"), &CharacterBody3D::get_position_delta); + ClassDB::bind_method(D_METHOD("get_real_velocity"), &CharacterBody3D::get_real_velocity); ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody3D::get_floor_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody3D::get_platform_velocity); - ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody3D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision); ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "snap"), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); + ADD_GROUP("Free Mode", "free_mode_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle"); ADD_GROUP("Floor", "floor_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_GROUP("Moving platform", "moving_platform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_apply_velocity_on_leave", PROPERTY_HINT_ENUM, "Always,Upward Only,Never", PROPERTY_USAGE_DEFAULT), "set_moving_platform_apply_velocity_on_leave", "get_moving_platform_apply_velocity_on_leave"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + + BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); + BIND_ENUM_CONSTANT(MOTION_MODE_FREE); + + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_ALWAYS); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY); + BIND_ENUM_CONSTANT(PLATFORM_VEL_ON_LEAVE_NEVER); +} + +void CharacterBody3D::_validate_property(PropertyInfo &property) const { + if (motion_mode == MOTION_MODE_FREE) { + if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } CharacterBody3D::CharacterBody3D() : @@ -1391,14 +1830,6 @@ CharacterBody3D::~CharacterBody3D() { /////////////////////////////////////// -Vector3 KinematicCollision3D::get_position() const { - return result.collision_point; -} - -Vector3 KinematicCollision3D::get_normal() const { - return result.collision_normal; -} - Vector3 KinematicCollision3D::get_travel() const { return result.travel; } @@ -1407,41 +1838,60 @@ Vector3 KinematicCollision3D::get_remainder() const { return result.remainder; } -real_t KinematicCollision3D::get_angle(const Vector3 &p_up_direction) const { +int KinematicCollision3D::get_collision_count() const { + return result.collision_count; +} + +Vector3 KinematicCollision3D::get_position(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].position; +} + +Vector3 KinematicCollision3D::get_normal(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].normal; +} + +real_t KinematicCollision3D::get_angle(int p_collision_index, const Vector3 &p_up_direction) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0.0); ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); - return result.get_angle(p_up_direction); + return result.collisions[p_collision_index].get_angle(p_up_direction); } -Object *KinematicCollision3D::get_local_shape() const { +Object *KinematicCollision3D::get_local_shape(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr); if (!owner) { return nullptr; } - uint32_t ownerid = owner->shape_find_owner(result.collision_local_shape); + uint32_t ownerid = owner->shape_find_owner(result.collisions[p_collision_index].local_shape); return owner->shape_owner_get_owner(ownerid); } -Object *KinematicCollision3D::get_collider() const { - if (result.collider_id.is_valid()) { - return ObjectDB::get_instance(result.collider_id); +Object *KinematicCollision3D::get_collider(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr); + if (result.collisions[p_collision_index].collider_id.is_valid()) { + return ObjectDB::get_instance(result.collisions[p_collision_index].collider_id); } return nullptr; } -ObjectID KinematicCollision3D::get_collider_id() const { - return result.collider_id; +ObjectID KinematicCollision3D::get_collider_id(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, ObjectID()); + return result.collisions[p_collision_index].collider_id; } -RID KinematicCollision3D::get_collider_rid() const { - return result.collider; +RID KinematicCollision3D::get_collider_rid(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, RID()); + return result.collisions[p_collision_index].collider; } -Object *KinematicCollision3D::get_collider_shape() const { - Object *collider = get_collider(); +Object *KinematicCollision3D::get_collider_shape(int p_collision_index) const { + Object *collider = get_collider(p_collision_index); if (collider) { CollisionObject3D *obj2d = Object::cast_to<CollisionObject3D>(collider); if (obj2d) { - uint32_t ownerid = obj2d->shape_find_owner(result.collider_shape); + uint32_t ownerid = obj2d->shape_find_owner(result.collisions[p_collision_index].collider_shape); return obj2d->shape_owner_get_owner(ownerid); } } @@ -1449,45 +1899,101 @@ Object *KinematicCollision3D::get_collider_shape() const { return nullptr; } -int KinematicCollision3D::get_collider_shape_index() const { - return result.collider_shape; +int KinematicCollision3D::get_collider_shape_index(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, 0); + return result.collisions[p_collision_index].collider_shape; } -Vector3 KinematicCollision3D::get_collider_velocity() const { - return result.collider_velocity; +Vector3 KinematicCollision3D::get_collider_velocity(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Vector3()); + return result.collisions[p_collision_index].collider_velocity; } -Variant KinematicCollision3D::get_collider_metadata() const { +Variant KinematicCollision3D::get_collider_metadata(int p_collision_index) const { + ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, Variant()); return Variant(); } +Vector3 KinematicCollision3D::get_best_position() const { + return result.collision_count ? get_position() : Vector3(); +} + +Vector3 KinematicCollision3D::get_best_normal() const { + return result.collision_count ? get_normal() : Vector3(); +} + +Object *KinematicCollision3D::get_best_local_shape() const { + return result.collision_count ? get_local_shape() : nullptr; +} + +Object *KinematicCollision3D::get_best_collider() const { + return result.collision_count ? get_collider() : nullptr; +} + +ObjectID KinematicCollision3D::get_best_collider_id() const { + return result.collision_count ? get_collider_id() : ObjectID(); +} + +RID KinematicCollision3D::get_best_collider_rid() const { + return result.collision_count ? get_collider_rid() : RID(); +} + +Object *KinematicCollision3D::get_best_collider_shape() const { + return result.collision_count ? get_collider_shape() : nullptr; +} + +int KinematicCollision3D::get_best_collider_shape_index() const { + return result.collision_count ? get_collider_shape_index() : 0; +} + +Vector3 KinematicCollision3D::get_best_collider_velocity() const { + return result.collision_count ? get_collider_velocity() : Vector3(); +} + +Variant KinematicCollision3D::get_best_collider_metadata() const { + return result.collision_count ? get_collider_metadata() : Variant(); +} + void KinematicCollision3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_position"), &KinematicCollision3D::get_position); - ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision3D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision3D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision3D::get_remainder); - ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); - ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision3D::get_local_shape); - ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision3D::get_collider); - ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision3D::get_collider_id); - ClassDB::bind_method(D_METHOD("get_collider_rid"), &KinematicCollision3D::get_collider_rid); - ClassDB::bind_method(D_METHOD("get_collider_shape"), &KinematicCollision3D::get_collider_shape); - ClassDB::bind_method(D_METHOD("get_collider_shape_index"), &KinematicCollision3D::get_collider_shape_index); - ClassDB::bind_method(D_METHOD("get_collider_velocity"), &KinematicCollision3D::get_collider_velocity); - ClassDB::bind_method(D_METHOD("get_collider_metadata"), &KinematicCollision3D::get_collider_metadata); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "normal"), "", "get_normal"); + ClassDB::bind_method(D_METHOD("get_collision_count"), &KinematicCollision3D::get_collision_count); + ClassDB::bind_method(D_METHOD("get_position", "collision_index"), &KinematicCollision3D::get_position, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_normal", "collision_index"), &KinematicCollision3D::get_normal, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_angle", "collision_index", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(0), DEFVAL(Vector3(0.0, 1.0, 0.0))); + ClassDB::bind_method(D_METHOD("get_local_shape", "collision_index"), &KinematicCollision3D::get_local_shape, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider", "collision_index"), &KinematicCollision3D::get_collider, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_id", "collision_index"), &KinematicCollision3D::get_collider_id, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_rid", "collision_index"), &KinematicCollision3D::get_collider_rid, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_shape", "collision_index"), &KinematicCollision3D::get_collider_shape, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_shape_index", "collision_index"), &KinematicCollision3D::get_collider_shape_index, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_velocity", "collision_index"), &KinematicCollision3D::get_collider_velocity, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_collider_metadata", "collision_index"), &KinematicCollision3D::get_collider_metadata, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("get_best_position"), &KinematicCollision3D::get_best_position); + ClassDB::bind_method(D_METHOD("get_best_normal"), &KinematicCollision3D::get_best_normal); + ClassDB::bind_method(D_METHOD("get_best_local_shape"), &KinematicCollision3D::get_best_local_shape); + ClassDB::bind_method(D_METHOD("get_best_collider"), &KinematicCollision3D::get_best_collider); + ClassDB::bind_method(D_METHOD("get_best_collider_id"), &KinematicCollision3D::get_best_collider_id); + ClassDB::bind_method(D_METHOD("get_best_collider_rid"), &KinematicCollision3D::get_best_collider_rid); + ClassDB::bind_method(D_METHOD("get_best_collider_shape"), &KinematicCollision3D::get_best_collider_shape); + ClassDB::bind_method(D_METHOD("get_best_collider_shape_index"), &KinematicCollision3D::get_best_collider_shape_index); + ClassDB::bind_method(D_METHOD("get_best_collider_velocity"), &KinematicCollision3D::get_best_collider_velocity); + ClassDB::bind_method(D_METHOD("get_best_collider_metadata"), &KinematicCollision3D::get_best_collider_metadata); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "travel"), "", "get_travel"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "remainder"), "", "get_remainder"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "local_shape"), "", "get_local_shape"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider"), "", "get_collider"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_id"), "", "get_collider_id"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "collider_rid"), "", "get_collider_rid"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider_shape"), "", "get_collider_shape"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_shape_index"), "", "get_collider_shape_index"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "collider_velocity"), "", "get_collider_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_collider_metadata"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_count"), "", "get_collision_count"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "", "get_best_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "normal"), "", "get_best_normal"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "local_shape"), "", "get_best_local_shape"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider"), "", "get_best_collider"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_id"), "", "get_best_collider_id"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "collider_rid"), "", "get_best_collider_rid"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider_shape"), "", "get_best_collider_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_shape_index"), "", "get_best_collider_shape_index"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "collider_velocity"), "", "get_best_collider_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_best_collider_metadata"); } /////////////////////////////////////// diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index d29241cdce..96f3d7d747 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -50,11 +50,11 @@ protected: uint16_t locked_axis = 0; - Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001); + Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001, int p_max_collisions = 1); public: - bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); - bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); + bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, int p_max_collisions = 1, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); + bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001, int p_max_collisions = 1); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; @@ -306,76 +306,148 @@ class KinematicCollision3D; class CharacterBody3D : public PhysicsBody3D { GDCLASS(CharacterBody3D, PhysicsBody3D); +public: + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, + }; + enum MovingPlatformApplyVelocityOnLeave { + PLATFORM_VEL_ON_LEAVE_ALWAYS, + PLATFORM_VEL_ON_LEAVE_UPWARD_ONLY, + PLATFORM_VEL_ON_LEAVE_NEVER, + }; + bool move_and_slide(); + + virtual Vector3 get_linear_velocity() const override; + void set_linear_velocity(const Vector3 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector3 get_last_motion() const; + Vector3 get_position_delta() const; + Vector3 get_floor_normal() const; + Vector3 get_wall_normal() const; + Vector3 get_real_velocity() const; + real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; + Vector3 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody3D(); + ~CharacterBody3D(); + private: real_t margin = 0.001; + MotionMode motion_mode = MOTION_MODE_GROUNDED; + MovingPlatformApplyVelocityOnLeave moving_platform_apply_velocity_on_leave = PLATFORM_VEL_ON_LEAVE_ALWAYS; + union CollisionState { + uint32_t state = 0; + struct { + bool floor; + bool wall; + bool ceiling; + }; + + CollisionState() { + } + CollisionState(bool p_floor, bool p_wall, bool p_ceiling) { + floor = p_floor; + wall = p_wall; + ceiling = p_ceiling; + } + }; + + CollisionState collision_state; bool floor_stop_on_slope = false; - int max_slides = 4; + bool floor_constant_speed = false; + bool floor_block_on_wall = true; + bool slide_on_ceiling = true; + int max_slides = 6; + int platform_layer; + RID platform_rid; + uint32_t moving_platform_floor_layers = UINT32_MAX; + uint32_t moving_platform_wall_layers = 0; + real_t floor_snap_length = 0.1; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - Vector3 snap; + real_t wall_min_slide_angle = Math::deg2rad((real_t)15.0); Vector3 up_direction = Vector3(0.0, 1.0, 0.0); - Vector3 linear_velocity; - Vector3 floor_normal; - Vector3 floor_velocity; - RID on_floor_body; - bool on_floor = false; - bool on_ceiling = false; - bool on_wall = false; + Vector3 wall_normal; + Vector3 last_motion; + Vector3 platform_velocity; + Vector3 previous_position; + Vector3 real_velocity; + Vector<PhysicsServer3D::MotionResult> motion_results; Vector<Ref<KinematicCollision3D>> slide_colliders; - Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); - Ref<KinematicCollision3D> _get_last_slide_collision(); - - void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result); - void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; bool is_floor_stop_on_slope_enabled() const; void set_floor_stop_on_slope_enabled(bool p_enabled); + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); + + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); + + bool is_slide_on_ceiling_enabled() const; + void set_slide_on_ceiling_enabled(bool p_enabled); + int get_max_slides() const; void set_max_slides(int p_max_slides); real_t get_floor_max_angle() const; void set_floor_max_angle(real_t p_radians); - const Vector3 &get_snap() const; - void set_snap(const Vector3 &p_snap); + real_t get_floor_snap_length(); + void set_floor_snap_length(real_t p_floor_snap_length); - const Vector3 &get_up_direction() const; - void set_up_direction(const Vector3 &p_up_direction); + real_t get_wall_min_slide_angle() const; + void set_wall_min_slide_angle(real_t p_radians); -protected: - void _notification(int p_what); - static void _bind_methods(); + uint32_t get_moving_platform_floor_layers() const; + void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); -public: - bool move_and_slide(); + uint32_t get_moving_platform_wall_layers() const; + void set_moving_platform_wall_layers(const uint32_t p_exclude_layer); - virtual Vector3 get_linear_velocity() const override; - void set_linear_velocity(const Vector3 &p_velocity); + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() const; - bool is_on_floor() const; - bool is_on_floor_only() const; - bool is_on_wall() const; - bool is_on_wall_only() const; - bool is_on_ceiling() const; - bool is_on_ceiling_only() const; - Vector3 get_floor_normal() const; - real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; - Vector3 get_platform_velocity() const; + void set_moving_platform_apply_velocity_on_leave(MovingPlatformApplyVelocityOnLeave p_on_leave_velocity); + MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const; - int get_slide_collision_count() const; - PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; + void _move_and_slide_free(double p_delta); + void _move_and_slide_grounded(double p_delta, bool p_was_on_floor); - CharacterBody3D(); - ~CharacterBody3D(); + Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); + Ref<KinematicCollision3D> _get_last_slide_collision(); + const Vector3 &get_up_direction() const; + bool _on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up); + void set_up_direction(const Vector3 &p_up_direction); + void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result, CollisionState &r_state, CollisionState p_apply_state = CollisionState(true, true, true)); + void _set_platform_data(const PhysicsServer3D::MotionCollision &p_collision); + void _snap_on_floor(bool was_on_floor, bool vel_dir_facing_up); + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; }; +VARIANT_ENUM_CAST(CharacterBody3D::MotionMode); +VARIANT_ENUM_CAST(CharacterBody3D::MovingPlatformApplyVelocityOnLeave); + class KinematicCollision3D : public RefCounted { GDCLASS(KinematicCollision3D, RefCounted); @@ -388,19 +460,31 @@ protected: static void _bind_methods(); public: - Vector3 get_position() const; - Vector3 get_normal() const; Vector3 get_travel() const; Vector3 get_remainder() const; - real_t get_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; - Object *get_local_shape() const; - Object *get_collider() const; - ObjectID get_collider_id() const; - RID get_collider_rid() const; - Object *get_collider_shape() const; - int get_collider_shape_index() const; - Vector3 get_collider_velocity() const; - Variant get_collider_metadata() const; + int get_collision_count() const; + Vector3 get_position(int p_collision_index = 0) const; + Vector3 get_normal(int p_collision_index = 0) const; + real_t get_angle(int p_collision_index = 0, const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; + Object *get_local_shape(int p_collision_index = 0) const; + Object *get_collider(int p_collision_index = 0) const; + ObjectID get_collider_id(int p_collision_index = 0) const; + RID get_collider_rid(int p_collision_index = 0) const; + Object *get_collider_shape(int p_collision_index = 0) const; + int get_collider_shape_index(int p_collision_index = 0) const; + Vector3 get_collider_velocity(int p_collision_index = 0) const; + Variant get_collider_metadata(int p_collision_index = 0) const; + + Vector3 get_best_position() const; + Vector3 get_best_normal() const; + Object *get_best_local_shape() const; + Object *get_best_collider() const; + ObjectID get_best_collider_id() const; + RID get_best_collider_rid() const; + Object *get_best_collider_shape() const; + int get_best_collider_shape_index() const; + Vector3 get_best_collider_velocity() const; + Variant get_best_collider_metadata() const; }; class PhysicalBone3D : public PhysicsBody3D { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 0f5de621ea..b7a79a2645 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -520,6 +520,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); + ERR_FAIL_COND(p_bone == p_parent); bones.write[p_bone].parent = p_parent; process_order_dirty = true; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 4dfd6902e6..5d1106bb41 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -296,7 +296,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int line = pos.y; int col = pos.x; @@ -321,7 +321,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int line = pos.y; int col = pos.x; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index efb6b7d200..046715e17e 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -996,7 +996,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mev = p_event; if (mev.is_valid()) { Viewport *r = get_tree()->get_root(); - if (!r->get_visible_rect().has_point(Point2(mev->get_global_position().x, mev->get_global_position().y))) { + if (!r->get_visible_rect().has_point(mev->get_global_position())) { return; } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index b9b02b1427..bca3b2059d 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -697,7 +697,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } else if (!just_disconnected) { String from = connecting_from; int from_slot = connecting_index; - Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y); + Vector2 ofs = mb->get_position(); if (!connecting_out) { emit_signal(SNAME("connection_from_empty"), from, from_slot, ofs); @@ -2103,7 +2103,7 @@ void GraphEdit::arrange_nodes() { largest_node_size = 0.0f; } - emit_signal("begin_node_move"); + emit_signal(SNAME("begin_node_move")); for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); gn->set_drag(true); @@ -2116,7 +2116,7 @@ void GraphEdit::arrange_nodes() { gn->set_position_offset(pos); gn->set_drag(false); } - emit_signal("end_node_move"); + emit_signal(SNAME("end_node_move")); arranging_graph = false; } diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 08c8c60d7a..cbdf2a498a 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -871,7 +871,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node."); if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Vector2 mpos = Vector2(mb->get_position().x, mb->get_position().y); + Vector2 mpos = mb->get_position(); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent get_parent_control()->grab_focus(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index e9414598a2..be05fd5a60 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -194,7 +194,7 @@ Popup::~Popup() { } Size2 PopupPanel::_get_contents_minimum_size() const { - Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name()); + Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); Size2 ms; @@ -217,7 +217,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const { } void PopupPanel::_update_child_rects() { - Ref<StyleBox> p = get_theme_stylebox("panel", get_class_name()); + Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); Vector2 cpos(p->get_offset()); Vector2 csize(get_size() - p->get_minimum_size()); @@ -244,9 +244,9 @@ void PopupPanel::_update_child_rects() { void PopupPanel::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED) { - panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name())); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); } else if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_ENTER_TREE) { - panel->add_theme_style_override("panel", get_theme_stylebox("panel", get_class_name())); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); _update_child_rects(); } else if (p_what == NOTIFICATION_WM_SIZE_CHANGED) { _update_child_rects(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index eb88570663..3eaae5181d 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -333,7 +333,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { @@ -578,7 +578,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 4edf373fbf..4a3a6837d5 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -560,7 +560,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { if (mm.is_valid()) { if (drag_node_touching && !drag_node_touching_deaccel) { - Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); + Vector2 motion = mm->get_relative(); drag_node_accum -= motion; Vector2 diff = drag_node_from + drag_node_accum; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 0c051f61e2..0c0ec39c7f 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -167,7 +167,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mm.is_valid()) { if (drag_touching && !drag_touching_deaccel) { - Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); + Vector2 motion = mm->get_relative(); drag_accum -= motion; if (beyond_deadzone || (scroll_h && Math::abs(drag_accum.x) > deadzone) || (scroll_v && Math::abs(drag_accum.y) > deadzone)) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 06dfc31621..09899413f2 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1444,7 +1444,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { _reset_caret_blink_timer(); - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; @@ -1547,7 +1547,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); - Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; @@ -5385,7 +5385,7 @@ void TextEdit::_update_selection_mode_pointer() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5402,7 +5402,7 @@ void TextEdit::_update_selection_mode_word() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5450,7 +5450,7 @@ void TextEdit::_update_selection_mode_line() { dragging_selection = true; Point2 mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; @@ -5765,7 +5765,7 @@ void TextEdit::_update_minimap_hover() { return; } - const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + const int row = get_minimap_line_at_pos(mp); const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); if (new_hovering_minimap != hovering_minimap) { @@ -5786,7 +5786,7 @@ void TextEdit::_update_minimap_click() { minimap_clicked = true; dragging_minimap = true; - int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + int row = get_minimap_line_at_pos(mp); if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) { minimap_scroll_ratio = v_scroll->get_as_ratio(); diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index de557494c3..49ed9dcb82 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -48,7 +48,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol img->convert(Image::FORMAT_LA8); ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8); - create(Size2(img->get_width(), img->get_height())); + create(img->get_size()); const uint8_t *r = img->get_data().ptr(); uint8_t *w = bitmask.ptrw(); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 7346b6efc7..492e0512e4 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -51,7 +51,7 @@ static Ref<StyleBoxTexture> make_stylebox(T p_src, float p_left, float p_top, fl } else { texture = Ref<ImageTexture>(memnew(ImageTexture)); Ref<Image> img = memnew(Image(p_src)); - const Size2 orig_size = Size2(img->get_width(), img->get_height()); + const Size2 orig_size = img->get_size(); img->convert(Image::FORMAT_RGBA8); img->resize(orig_size.x * scale, orig_size.y * scale); @@ -97,7 +97,7 @@ template <class T> static Ref<Texture2D> make_icon(T p_src) { Ref<ImageTexture> texture(memnew(ImageTexture)); Ref<Image> img = memnew(Image(p_src)); - const Size2 orig_size = Size2(img->get_width(), img->get_height()); + const Size2 orig_size = img->get_size(); img->convert(Image::FORMAT_RGBA8); img->resize(orig_size.x * scale, orig_size.y * scale); texture->create_from_image(img); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 29bd56eebd..9b403a18f0 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -1214,6 +1214,14 @@ void Font::add_data(const Ref<FontData> &p_data) { if (data[data.size() - 1].is_valid()) { data.write[data.size() - 1]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + Dictionary data_var_list = p_data->get_supported_variation_list(); + for (int j = 0; j < data_var_list.size(); j++) { + int32_t tag = data_var_list.get_key_at_index(j); + Vector3i value = data_var_list.get_value_at_index(j); + if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { + variation_coordinates[TS->tag_to_name(tag)] = value.z; + } + } } cache.clear(); @@ -1233,6 +1241,14 @@ void Font::set_data(int p_idx, const Ref<FontData> &p_data) { data.write[p_idx] = p_data; rids.write[p_idx] = RID(); + Dictionary data_var_list = p_data->get_supported_variation_list(); + for (int j = 0; j < data_var_list.size(); j++) { + int32_t tag = data_var_list.get_key_at_index(j); + Vector3i value = data_var_list.get_value_at_index(j); + if (!variation_coordinates.has(tag) && !variation_coordinates.has(TS->tag_to_name(tag))) { + variation_coordinates[TS->tag_to_name(tag)] = value.z; + } + } if (data[p_idx].is_valid()) { data.write[p_idx]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 918fc3fe9c..67cbd0e094 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3119,7 +3119,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const { } bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); + Vector<String> components = String(p_name).split("/", true, 3); // Compute the vector2i if we have coordinates. Vector<String> coords_split = components[0].split(":"); @@ -3138,8 +3138,32 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) // Properties. if (components[1] == "size_in_atlas") { move_tile_in_atlas(coords, coords, p_value); + return true; } else if (components[1] == "next_alternative_id") { tiles[coords].next_alternative_id = p_value; + return true; + } else if (components[1] == "animation_columns") { + set_tile_animation_columns(coords, p_value); + return true; + } else if (components[1] == "animation_separation") { + set_tile_animation_separation(coords, p_value); + return true; + } else if (components[1] == "animation_speed") { + set_tile_animation_speed(coords, p_value); + return true; + } else if (components[1] == "animation_frames_count") { + set_tile_animation_frames_count(coords, p_value); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (components[2] == "duration") { + if (frame >= get_tile_animation_frames_count(coords)) { + set_tile_animation_frames_count(coords, frame + 1); + } + set_tile_animation_frame_duration(coords, frame, p_value); + return true; + } + return false; } else if (components[1].is_valid_int()) { int alternative_id = components[1].to_int(); if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { @@ -3185,6 +3209,28 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { } else if (components[1] == "next_alternative_id") { r_ret = tiles[coords].next_alternative_id; return true; + } else if (components[1] == "animation_columns") { + r_ret = get_tile_animation_columns(coords); + return true; + } else if (components[1] == "animation_separation") { + r_ret = get_tile_animation_separation(coords); + return true; + } else if (components[1] == "animation_speed") { + r_ret = get_tile_animation_speed(coords); + return true; + } else if (components[1] == "animation_frames_count") { + r_ret = get_tile_animation_frames_count(coords); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= get_tile_animation_frames_count(coords)) { + return false; + } + if (components[2] == "duration") { + r_ret = get_tile_animation_frame_duration(coords, frame); + return true; + } + return false; } else if (components[1].is_valid_int()) { int alternative_id = components[1].to_int(); if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { @@ -3226,6 +3272,40 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { } tile_property_list.push_back(property_info); + // animation_columns. + property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_columns == 0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_separation. + property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_separation == Vector2i()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_speed. + property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().animation_speed == 1.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_frames_count. + tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NETWORK)); + + // animation_frame_*. + bool store_durations = tiles[E_tile->key()].animation_frames_durations.size() >= 2; + for (int i = 0; i < (int)tiles[E_tile->key()].animation_frames_durations.size(); i++) { + property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (!store_durations) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + } + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { // Add a dummy property to show the alternative exists. tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); @@ -3234,11 +3314,10 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> alternative_property_list; E_alternative->get()->get_property_list(&alternative_property_list); for (PropertyInfo &alternative_property_info : alternative_property_list) { - bool valid; - Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name, &valid); + Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name); Variant value = E_alternative->get()->get(alternative_property_info.name); - if (valid && value == default_value) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; + if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { + alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE; } alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), alternative_property_info.name); tile_property_list.push_back(alternative_property_info); @@ -3257,33 +3336,27 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector // Create a tile if it does not exists. ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0); ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); - for (int x = 0; x < p_size.x; x++) { - for (int y = 0; y < p_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - ERR_FAIL_COND_MSG(tiles.has(coords), vformat("Cannot create tile at position %s with size %s. Already a tile present at %s.", p_atlas_coords, p_size, coords)); - } - } + + bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile, tiles are already present in the space the tile would cover."); + + // Initialize the tile data. + TileAlternativesData tad; + tad.size_in_atlas = p_size; + tad.animation_frames_durations.push_back(1.0); + tad.alternatives[0] = memnew(TileData); + tad.alternatives[0]->set_tile_set(tile_set); + tad.alternatives[0]->set_allow_transform(false); + tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tad.alternatives[0]->notify_property_list_changed(); + tad.alternatives_ids.append(0); // Create and resize the tile. - tiles.insert(p_atlas_coords, TileSetAtlasSource::TileAlternativesData()); + tiles.insert(p_atlas_coords, tad); tiles_ids.append(p_atlas_coords); tiles_ids.sort(); - tiles[p_atlas_coords].size_in_atlas = p_size; - tiles[p_atlas_coords].alternatives[0] = memnew(TileData); - tiles[p_atlas_coords].alternatives[0]->set_tile_set(tile_set); - tiles[p_atlas_coords].alternatives[0]->set_allow_transform(false); - tiles[p_atlas_coords].alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); - tiles[p_atlas_coords].alternatives[0]->notify_property_list_changed(); - tiles[p_atlas_coords].alternatives_ids.append(0); - - // Add all covered positions to the mapping cache - for (int x = 0; x < p_size.x; x++) { - for (int y = 0; y < p_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache[coords] = p_atlas_coords; - } - } + _create_coords_mapping_cache(p_atlas_coords); emit_signal(SNAME("changed")); } @@ -3292,14 +3365,7 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); // Remove all covered positions from the mapping cache - Size2i size = tiles[p_atlas_coords].size_in_atlas; - - for (int x = 0; x < size.x; x++) { - for (int y = 0; y < size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache.erase(coords); - } - } + _clear_coords_mapping_cache(p_atlas_coords); // Free tile data. for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) { @@ -3326,6 +3392,118 @@ Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const { return _coords_mapping_cache[p_atlas_coords]; } +void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frame_columns < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, p_frame_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_columns = p_frame_columns; + + _create_coords_mapping_cache(p_atlas_coords); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_columns; +} + +void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_separation.x < 0 || p_separation.y < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, p_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_separation = p_separation; + + _create_coords_mapping_cache(p_atlas_coords); + + emit_signal(SNAME("changed")); +} + +Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_separation; +} + +void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_speed <= 0); + + tiles[p_atlas_coords].animation_speed = p_speed; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1.0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_speed; +} + +void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frames_count < 1); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + int old_size = tiles[p_atlas_coords].animation_frames_durations.size(); + tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count); + for (int i = old_size; i < p_frames_count; i++) { + tiles[p_atlas_coords].animation_frames_durations[i] = 1.0; + } + + _create_coords_mapping_cache(p_atlas_coords); + + notify_property_list_changed(); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_frames_durations.size(); +} + +void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size()); + ERR_FAIL_COND(p_duration <= 0.0); + + tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size(), 0.0); + return tiles[p_atlas_coords].animation_frames_durations[p_frame_index]; +} + +real_t TileSetAtlasSource::get_tile_animation_total_duration(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + real_t sum = 0.0; + for (int frame = 0; frame < (int)tiles[p_atlas_coords].animation_frames_durations.size(); frame++) { + sum += tiles[p_atlas_coords].animation_frames_durations[frame]; + } + return sum; +} + Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); @@ -3341,16 +3519,34 @@ Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { return tiles_ids[p_index]; } -Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords) const { +bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const { + for (int frame = 0; frame < p_frames_count; frame++) { + Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < p_size.x; x++) { + for (int y = 0; y < p_size.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) { + return false; + } + } + } + } + return true; +} + +Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); + + const TileAlternativesData &tad = tiles[p_atlas_coords]; - Vector2i size_in_atlas = tiles[p_atlas_coords].size_in_atlas; + Vector2i size_in_atlas = tad.size_in_atlas; Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); - Vector2 origin = margins + (p_atlas_coords * (texture_region_size + separation)); + Vector2i frame_coords = p_atlas_coords + (size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); + Vector2 origin = margins + (frame_coords * (texture_region_size + separation)); return Rect2(origin, region_size); - ; } Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const { @@ -3369,56 +3565,23 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_ return effective_texture_offset; } -bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; - if (new_atlas_coords.x < 0 || new_atlas_coords.y < 0) { - return false; - } - - Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; - ERR_FAIL_COND_V(size.x <= 0 || size.y <= 0, false); - - Size2i grid_size = get_atlas_grid_size(); - if (new_atlas_coords.x + size.x > grid_size.x || new_atlas_coords.y + size.y > grid_size.y) { - return false; - } - - Rect2i new_rect = Rect2i(new_atlas_coords, size); - // Check if the new tile can fit in the new rect. - for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) { - for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) { - Vector2i coords = get_tile_at_coords(Vector2i(x, y)); - if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) { - return false; - } - } - } - - return true; -} - void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { - bool can_move = can_move_tile_in_atlas(p_atlas_coords, p_new_atlas_coords, p_new_size); - ERR_FAIL_COND_MSG(!can_move, vformat("Cannot move tile at position %s with size %s. Tile already present.", p_new_atlas_coords, p_new_size)); + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; // Compute the actual new rect from arguments. Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; - Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; + Vector2i new_size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tad.size_in_atlas; - if (new_atlas_coords == p_atlas_coords && size == tiles[p_atlas_coords].size_in_atlas) { + if (new_atlas_coords == p_atlas_coords && new_size == tad.size_in_atlas) { return; } - // Remove all covered positions from the mapping cache. - Size2i old_size = tiles[p_atlas_coords].size_in_atlas; - for (int x = 0; x < old_size.x; x++) { - for (int y = 0; y < old_size.y; y++) { - Vector2i coords = p_atlas_coords + Vector2i(x, y); - _coords_mapping_cache.erase(coords); - } - } + bool room_for_tile = has_room_for_tile(new_atlas_coords, new_size, tad.animation_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, vformat("Cannot move tile at position %s with size %s. Tile already present.", new_atlas_coords, new_size)); + + _clear_coords_mapping_cache(p_atlas_coords); // Move the tile and update its size. if (new_atlas_coords != p_atlas_coords) { @@ -3429,15 +3592,9 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_ tiles_ids.append(new_atlas_coords); tiles_ids.sort(); } - tiles[new_atlas_coords].size_in_atlas = size; + tiles[new_atlas_coords].size_in_atlas = new_size; - // Add all covered positions to the mapping cache again. - for (int x = 0; x < size.x; x++) { - for (int y = 0; y < size.y; y++) { - Vector2i coords = new_atlas_coords + Vector2i(x, y); - _coords_mapping_cache[coords] = new_atlas_coords; - } - } + _create_coords_mapping_cache(new_atlas_coords); emit_signal(SNAME("changed")); } @@ -3566,12 +3723,25 @@ void TileSetAtlasSource::_bind_methods() { // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); + ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS)); + ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); + ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns); + ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns); + ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation); + ClassDB::bind_method(D_METHOD("get_tile_animation_separation", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_separation); + ClassDB::bind_method(D_METHOD("set_tile_animation_speed", "atlas_coords", "speed"), &TileSetAtlasSource::set_tile_animation_speed); + ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed); + ClassDB::bind_method(D_METHOD("set_tile_animation_frames_count", "atlas_coords", "frames_count"), &TileSetAtlasSource::set_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("get_tile_animation_frames_count", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("set_tile_animation_frame_duration", "atlas_coords", "frame_index", "duration"), &TileSetAtlasSource::set_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_frame_duration", "atlas_coords", "frame_index"), &TileSetAtlasSource::get_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_total_duration", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_total_duration); + // Alternative tiles ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); @@ -3584,7 +3754,7 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); - ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords"), &TileSetAtlasSource::get_tile_texture_region); + ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); } TileSetAtlasSource::~TileSetAtlasSource() { @@ -3618,6 +3788,45 @@ void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coo }; } +void TileSetAtlasSource::_clear_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (!_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("TileSetAtlasSource has no cached tile at position %s, the position cache might be corrupted.", coords)); + } else { + if (_coords_mapping_cache[coords] != p_atlas_coords) { + WARN_PRINT(vformat("The position cache at position %s is pointing to a wrong tile, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache.erase(coords); + } + } + } + } +} + +void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("The cache already has a tile for position %s, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache[coords] = p_atlas_coords; + } + } + } +} + /////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// void TileSetScenesCollectionSource::_compute_next_alternative_id() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index ba7207241a..46cb1fb36d 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -447,16 +447,23 @@ public: class TileSetAtlasSource : public TileSetSource { GDCLASS(TileSetAtlasSource, TileSetSource); -public: +private: struct TileAlternativesData { Vector2i size_in_atlas = Vector2i(1, 1); Vector2i texture_offset; + + // Animation + int animation_columns = 0; + Vector2i animation_separation; + real_t animation_speed = 1.0; + LocalVector<real_t> animation_frames_durations; + + // Alternatives Map<int, TileData *> alternatives; Vector<int> alternatives_ids; int next_alternative_id = 1; }; -private: Ref<Texture2D> texture; Vector2i margins; Vector2i separation; @@ -471,6 +478,9 @@ private: void _compute_next_alternative_id(const Vector2i p_atlas_coords); + void _create_coords_mapping_cache(Vector2i p_atlas_coords); + void _clear_coords_mapping_cache(Vector2i p_atlas_coords); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -513,18 +523,32 @@ public: Vector2i get_texture_region_size() const; // Base tiles. - void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); // Create a tile if it does not exists, or add alternative tile if it does. - void remove_tile(Vector2i p_atlas_coords); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative + void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); + void remove_tile(Vector2i p_atlas_coords); virtual bool has_tile(Vector2i p_atlas_coords) const override; - bool can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)) const; void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)); Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const; virtual int get_tiles_count() const override; virtual Vector2i get_tile_id(int p_index) const override; + bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const; + Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; + // Animation. + void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns); + int get_tile_animation_columns(const Vector2i p_atlas_coords) const; + void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation); + Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const; + void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed); + real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const; + void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count); + int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const; + void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration); + real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const; + real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const; + // Alternative tiles. int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1); void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile); @@ -542,7 +566,7 @@ public: Vector2i get_atlas_grid_size() const; bool has_tiles_outside_texture(); void clear_tiles_outside_texture(); - Rect2i get_tile_texture_region(Vector2i p_atlas_coords) const; + Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const; ~TileSetAtlasSource(); |