From f2caab469198fe33649b487546fb5d8e12007296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Wed, 15 Sep 2021 15:23:58 +0200 Subject: [PATCH] Improve TileMap physics for moving platforms and conveyor belts like movements --- doc/classes/TileData.xml | 30 +++ doc/classes/TileMap.xml | 18 ++ editor/plugins/tiles/tile_data_editors.cpp | 91 +++++-- scene/2d/tile_map.cpp | 270 ++++++++++++--------- scene/2d/tile_map.h | 13 +- scene/resources/tile_set.cpp | 74 ++++-- scene/resources/tile_set.h | 8 +- 7 files changed, 346 insertions(+), 158 deletions(-) diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index 0d3282c6d3..81c5743ccc 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -37,6 +37,20 @@ Returns how many polygons the tile has for TileSet physics layer with index [code]layer_id[/code]. + + + + + Returns the constant angular velocity applied to objects colliding with this tile. + + + + + + + Returns the constant linear velocity applied to objects colliding with this tile. + + @@ -123,6 +137,22 @@ Sets the polygons count for TileSet physics layer with index [code]layer_id[/code]. + + + + + + Sets the constant angular velocity. This does not rotate the tile. This angular velocity is applied to objects colliding with this tile. + + + + + + + + Sets the constant linear velocity. This does not move the tile. This linear velocity is applied to objects colliding with this tile. This is useful to create conveyor belts. + + diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 4621d138ac..e5fe823be6 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -29,6 +29,13 @@ Clears all cells. + + + + + Clears all cells on the given layer. + + @@ -62,6 +69,13 @@ Returns the tile source ID of the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. + + + + + Returns the coodinates of the tile for given physics body RID. Such RID can be retrieved from [member KinematicCollision2D.collider_rid], when colliding with a tile. + + @@ -220,6 +234,10 @@ The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size. + + If enabled, the TileMap will see its collisions synced to the physics tick and change its collision type from static to kinematic. This is required to create TileMap-based moving platform. + [b]Note:[/b] Enabling [code]collision_animatable[/code] may have a small performance impact, only do it if the TileMap is moving and has colliding tiles. + Show or hide the TileMap's collision shapes. If set to [code]VISIBILITY_MODE_DEFAULT[/code], this depends on the show collision debug settings. diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 216c5f7c7d..5f72cfe313 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -231,10 +231,14 @@ void GenericTilePolygonEditor::_zoom_changed() { void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { switch (p_item_pressed) { - case RESET_TO_DEFAULT_TILE: + case RESET_TO_DEFAULT_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); - undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon()); + Vector polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } + undo_redo->add_do_method(this, "add_polygon", polygon); undo_redo->add_do_method(base_control, "update"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); @@ -244,8 +248,8 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; - case CLEAR_TILE: + } break; + case CLEAR_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); undo_redo->add_do_method(base_control, "update"); @@ -257,7 +261,7 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; + } break; default: break; } @@ -308,6 +312,9 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur ERR_FAIL_COND(!tile_set.is_valid()); Vector polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } Point2 snapped_point = r_point; // Snap to polygon vertices. @@ -539,7 +546,11 @@ void GenericTilePolygonEditor::set_tile_set(Ref p_tile_set) { // Set the default tile shape clear_polygons(); if (p_tile_set.is_valid()) { - add_polygon(p_tile_set->get_tile_shape_polygon()); + Vector polygon = p_tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * p_tile_set->get_tile_size(); + } + add_polygon(polygon); } } tile_set = p_tile_set; @@ -1265,17 +1276,21 @@ void TileDataCollisionEditor::_polygons_changed() { } Variant TileDataCollisionEditor::_get_painted_value() { + Dictionary dict; + dict["linear_velocity"] = dummy_object->get("linear_velocity"); + dict["angular_velocity"] = dummy_object->get("angular_velocity"); Array array; for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant()); - Dictionary dict; - dict["points"] = polygon_editor->get_polygon(i); - dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); - dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = polygon_editor->get_polygon(i); + polygon_dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); + polygon_dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); + array.push_back(polygon_dict); } + dict["polygons"] = array; - return array; + return dict; } void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { @@ -1291,6 +1306,8 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_ } _polygons_changed(); + dummy_object->set("linear_velocity", tile_data->get_constant_linear_velocity(physics_layer)); + dummy_object->set("angular_velocity", tile_data->get_constant_angular_velocity(physics_layer)); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i)); dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i)); @@ -1306,13 +1323,16 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so TileData *tile_data = Object::cast_to(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND(!tile_data); - Array array = p_value; + Dictionary dict = p_value; + tile_data->set_constant_linear_velocity(physics_layer, dict["linear_velocity"]); + tile_data->set_constant_angular_velocity(physics_layer, dict["angular_velocity"]); + Array array = dict["polygons"]; tile_data->set_collision_polygons_count(physics_layer, array.size()); for (int i = 0; i < array.size(); i++) { - Dictionary dict = array[i]; - tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]); - tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]); - tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]); + Dictionary polygon_dict = array[i]; + tile_data->set_collision_polygon_points(physics_layer, i, polygon_dict["points"]); + tile_data->set_collision_polygon_one_way(physics_layer, i, polygon_dict["one_way"]); + tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); @@ -1322,15 +1342,19 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas TileData *tile_data = Object::cast_to(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND_V(!tile_data, Variant()); + Dictionary dict; + dict["linear_velocity"] = tile_data->get_constant_linear_velocity(physics_layer); + dict["angular_velocity"] = tile_data->get_constant_angular_velocity(physics_layer); Array array; for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { - Dictionary dict; - dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); - dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); - dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); + polygon_dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); + polygon_dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); + array.push_back(polygon_dict); } - return array; + dict["polygons"] = array; + return dict; } void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map p_previous_values, Variant p_new_value) { @@ -1378,6 +1402,27 @@ TileDataCollisionEditor::TileDataCollisionEditor() { polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); add_child(polygon_editor); + dummy_object->add_dummy_property("linear_velocity"); + dummy_object->set("linear_velocity", Vector2()); + dummy_object->add_dummy_property("angular_velocity"); + dummy_object->set("angular_velocity", 0.0); + + EditorProperty *linear_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + linear_velocity_editor->set_object_and_property(dummy_object, "linear_velocity"); + linear_velocity_editor->set_label("linear_velocity"); + linear_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + linear_velocity_editor->update_property(); + add_child(linear_velocity_editor); + property_editors["linear_velocity"] = linear_velocity_editor; + + EditorProperty *angular_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + angular_velocity_editor->set_object_and_property(dummy_object, "angular_velocity"); + angular_velocity_editor->set_label("angular_velocity"); + angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + angular_velocity_editor->update_property(); + add_child(angular_velocity_editor); + property_editors["angular_velocity"] = linear_velocity_editor; + _polygons_changed(); } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 929233e4e0..78d018ac53 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -452,6 +452,19 @@ int TileMap::get_layer_z_index(int p_layer) const { return layers[p_layer].z_index; } +void TileMap::set_collision_animatable(bool p_enabled) { + collision_animatable = p_enabled; + _clear_internals(); + set_notify_local_transform(p_enabled); + set_physics_process_internal(p_enabled); + _recreate_internals(); + emit_signal(SNAME("changed")); +} + +bool TileMap::is_collision_animatable() const { + return collision_animatable; +} + void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { collision_visibility_mode = p_show_collision; _clear_internals(); @@ -508,7 +521,6 @@ Map::Element *TileMap::_create_quadrant(int p_layer, // Call the create_quadrant method on plugins if (tile_set.is_valid()) { _rendering_create_quadrant(&q); - _physics_create_quadrant(&q); } return layers[p_layer].quadrant_map.insert(p_qk, q); @@ -1092,26 +1104,69 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Refis_editor_hint(); +#endif + if (is_inside_tree() && collision_animatable && !in_editor) { + // Update tranform on the physics tick when in animatable mode. + last_valid_transform = new_transform; + set_notify_local_transform(false); + set_global_transform(new_transform); + set_notify_local_transform(true); + } + } break; case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { - // Update the bodies transforms. - if (is_inside_tree()) { + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (is_inside_tree() && (!collision_animatable || in_editor)) { + // Update the new transform directly if we are not in animatable mode. + Transform2D global_transform = get_global_transform(); for (int layer = 0; layer < (int)layers.size(); layer++) { - Transform2D global_transform = get_global_transform(); - for (Map::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { TileMapQuadrant &q = E->get(); - Transform2D xform; - xform.set_origin(map_to_world(E->key() * get_effective_quadrant_size(layer))); - xform = global_transform * xform; - - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(map_to_world(bodies_coords[body])); + xform = global_transform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } } } } } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (is_inside_tree() && !in_editor && collision_animatable) { + // Only active when animatable. Send the new transform to the physics... + new_transform = get_global_transform(); + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (Map::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(map_to_world(bodies_coords[body])); + xform = new_transform * xform; + + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + } + } break; } } @@ -1120,29 +1175,23 @@ void TileMap::_physics_update_dirty_quadrants(SelfList::List &r ERR_FAIL_COND(!tile_set.is_valid()); Transform2D global_transform = get_global_transform(); + last_valid_transform = global_transform; + new_transform = global_transform; PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + RID space = get_world_2d()->get_space(); SelfList *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { TileMapQuadrant &q = *q_list_element->self(); - Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); - - LocalVector body_shape_count; - body_shape_count.resize(q.bodies.size()); - - // Clear shapes. - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - ps->body_clear_shapes(q.bodies[body_index]); - body_shape_count[body_index] = 0; - - // Position the bodies. - Transform2D xform; - xform.set_origin(quadrant_pos); - xform = global_transform * xform; - ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + // Clear bodies. + for (RID body : q.bodies) { + bodies_coords.erase(body); + ps->free(body); } + q.bodies.clear(); + // Recreate bodies and shapes. for (Set::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { TileMapCell c = get_cell(q.layer, E_cell->get(), true); @@ -1158,26 +1207,53 @@ void TileMap::_physics_update_dirty_quadrants(SelfList::List &r if (atlas_source) { TileData *tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - int &body_shape_index = body_shape_count[body_index]; + for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) { + Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); + uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); + uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); - // Add the shapes again. - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); - float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); + // Create the body. + RID body = ps->body_create(); + bodies_coords[body] = E_cell->get(); + ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_space(body, space); - int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get())); + xform = global_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + + ps->body_attach_object_instance_id(body, get_instance_id()); + ps->body_set_collision_layer(body, physics_layer); + ps->body_set_collision_mask(body, physics_mask); + ps->body_set_pickable(body, false); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer)); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer)); + + if (!physics_material.is_valid()) { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + + q.bodies.push_back(body); + + // Add the shapes to the body. + int body_shape_index = 0; + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { + // Iterate over the polygons. + bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); + float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); + int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); for (int shape_index = 0; shape_index < shapes_count; shape_index++) { - Transform2D xform = Transform2D(); - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); - // Add decomposed convex shapes. - Ref shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); - ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); - ps->body_set_shape_metadata(q.bodies[body_index], body_shape_index, E_cell->get()); - ps->body_set_shape_as_one_way_collision(q.bodies[body_index], body_shape_index, one_way_collision, one_way_collision_margin); + Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index); + ps->body_add_shape(body, shape->get_rid()); + ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); - ++body_shape_index; + body_shape_index++; } } } @@ -1189,54 +1265,11 @@ void TileMap::_physics_update_dirty_quadrants(SelfList::List &r } } -void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { - ERR_FAIL_COND(!tile_set.is_valid()); - - //Get the TileMap's gobla transform. - Transform2D global_transform; - if (is_inside_tree()) { - global_transform = get_global_transform(); - } - - // Clear all bodies. - p_quadrant->bodies.clear(); - - // Create the body and set its parameters. - for (int layer = 0; layer < tile_set->get_physics_layers_count(); layer++) { - RID body = PhysicsServer2D::get_singleton()->body_create(); - PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); - - PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, get_instance_id()); - PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer)); - PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer)); - - Ref physics_material = tile_set->get_physics_layer_physics_material(layer); - if (!physics_material.is_valid()) { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); - } - - if (is_inside_tree()) { - RID space = get_world_2d()->get_space(); - PhysicsServer2D::get_singleton()->body_set_space(body, space); - - Transform2D xform; - xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer))); - xform = global_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - - p_quadrant->bodies.push_back(body); - } -} - void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Remove a quadrant. - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); + for (RID body : p_quadrant->bodies) { + bodies_coords.erase(body); + PhysicsServer2D::get_singleton()->free(body); } p_quadrant->bodies.clear(); } @@ -1252,7 +1285,7 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { bool show_collision = false; switch (collision_visibility_mode) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_collisions_hint()); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_collision = false; @@ -1266,39 +1299,28 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } RenderingServer *rs = RenderingServer::get_singleton(); - - Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); Color debug_collision_color = get_tree()->get_debug_collisions_color(); - for (Set::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = get_cell(p_quadrant->layer, E_cell->get(), true); + Vector color; + color.push_back(debug_collision_color); - Transform2D xform; - xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Transform2D qudrant_xform; + qudrant_xform.set_origin(quadrant_pos); + Transform2D global_transform_inv = (get_global_transform() * qudrant_xform).affine_inverse(); + + for (RID body : p_quadrant->bodies) { + Transform2D xform = Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)) * global_transform_inv; rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); - - if (tile_set->has_source(c.source_id)) { - TileSetSource *source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - // Draw the debug polygon. - Vector polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); - if (polygon.size() >= 3) { - Vector color; - color.push_back(debug_collision_color); - rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); - } - } - } + for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { + const RID &shape = ps->body_get_shape(body, shape_index); + PhysicsServer2D::ShapeType type = ps->shape_get_type(shape); + if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { + Vector polygon = ps->shape_get_data(shape); + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); + } else { + WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); } } rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); @@ -1858,6 +1880,11 @@ Map *TileMap::get_quadrant_map(int p_layer) { return &layers[p_layer].quadrant_map; } +Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { + ERR_FAIL_COND_V_MSG(!bodies_coords.has(p_physics_body), Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); + return bodies_coords[p_physics_body]; +} + void TileMap::fix_invalid_tiles() { ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); @@ -3007,6 +3034,8 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("set_collision_animatable", "enabled"), &TileMap::set_collision_animatable); + ClassDB::bind_method(D_METHOD("is_collision_animatable"), &TileMap::is_collision_animatable); ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "collision_visibility_mode"), &TileMap::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMap::get_collision_visibility_mode); @@ -3018,10 +3047,14 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "layer", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords); ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid); + ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles); - ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer); ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear); + ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect); @@ -3037,6 +3070,7 @@ void TileMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index ca38baa550..2faede2445 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -202,6 +202,7 @@ private: // Properties. Ref tile_set; int quadrant_size = 16; + bool collision_animatable = false; VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; @@ -229,6 +230,9 @@ private: LocalVector layers; int selected_layer = -1; + // Mapping for RID to coords. + Map bodies_coords; + // Quadrants and internals management. Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; @@ -259,9 +263,10 @@ private: void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + Transform2D last_valid_transform; + Transform2D new_transform; void _physics_notification(int p_what); void _physics_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); - void _physics_create_quadrant(TileMapQuadrant *p_quadrant); void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); @@ -325,6 +330,9 @@ public: void set_selected_layer(int p_layer_id); // For editor use. int get_selected_layer() const; + void set_collision_animatable(bool p_enabled); + bool is_collision_animatable() const; + void set_collision_visibility_mode(VisibilityMode p_show_collision); VisibilityMode get_collision_visibility_mode(); @@ -364,6 +372,9 @@ public: virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; + // For finding tiles from collision. + Vector2i get_coords_for_body_rid(RID p_physics_body); + // Fixing a nclearing methods. void fix_invalid_tiles(); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 5a8c5b3782..15e622c9d6 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3124,7 +3124,7 @@ Vector2i TileSetAtlasSource::get_atlas_grid_size() const { } bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { - Vector components = String(p_name).split("/", true, 3); + Vector components = String(p_name).split("/", true, 2); // Compute the vector2i if we have coordinates. Vector coords_split = components[0].split(":"); @@ -4316,9 +4316,26 @@ Ref TileData::get_occluder(int p_layer_id) const { } // Physics -int TileData::get_collision_polygons_count(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); - return physics[p_layer_id].polygons.size(); +void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].linear_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector2()); + return physics[p_layer_id].linear_velocity; +} + +void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].angular_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +real_t TileData::get_constant_angular_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); + return physics[p_layer_id].angular_velocity; } void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { @@ -4329,6 +4346,11 @@ void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count emit_signal(SNAME("changed")); } +int TileData::get_collision_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + return physics[p_layer_id].polygons.size(); +} + void TileData::add_collision_polygon(int p_layer_id) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); @@ -4530,11 +4552,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { // Physics layers. int layer_index = components[0].trim_prefix("physics_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); - if (components.size() == 2 && components[1] == "polygons_count") { - if (p_value.get_type() != Variant::INT) { - return false; - } - + if (components.size() == 2) { if (layer_index >= physics.size()) { if (tile_set) { return false; @@ -4542,8 +4560,19 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { physics.resize(layer_index + 1); } } - set_collision_polygons_count(layer_index, p_value); - return true; + if (components[1] == "linear_velocity") { + set_constant_linear_velocity(layer_index, p_value); + return true; + } else if (components[1] == "angular_velocity") { + set_constant_angular_velocity(layer_index, p_value); + return true; + } else if (components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } + set_collision_polygons_count(layer_index, p_value); + return true; + } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -4645,9 +4674,18 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const { if (layer_index >= physics.size()) { return false; } - if (components.size() == 2 && components[1] == "polygons_count") { - r_ret = get_collision_polygons_count(layer_index); - return true; + + if (components.size() == 2) { + if (components[1] == "linear_velocity") { + r_ret = get_constant_linear_velocity(layer_index); + return true; + } else if (components[1] == "angular_velocity") { + r_ret = get_constant_angular_velocity(layer_index); + return true; + } else if (components[1] == "polygons_count") { + r_ret = get_collision_polygons_count(layer_index); + return true; + } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -4718,6 +4756,8 @@ void TileData::_get_property_list(List *p_list) const { // Physics layers. p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < physics.size(); i++) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/linear_velocity", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/angular_velocity", i), PROPERTY_HINT_NONE)); p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/polygons_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); for (int j = 0; j < physics[i].polygons.size(); j++) { @@ -4807,8 +4847,12 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder); // Physics. - ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity", "layer_id"), &TileData::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "layer_id", "velocity"), &TileData::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity", "layer_id"), &TileData::get_constant_angular_velocity); ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); + ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 46cb1fb36d..42b82957fb 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -645,6 +645,8 @@ private: float one_way_margin = 1.0; }; + Vector2 linear_velocity; + float angular_velocity; Vector polygons; }; Vector physics; @@ -718,8 +720,12 @@ public: Ref get_occluder(int p_layer_id) const; // Physics - int get_collision_polygons_count(int p_layer_id) const; + void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); + Vector2 get_constant_linear_velocity(int p_layer_id) const; + void set_constant_angular_velocity(int p_layer_id, real_t p_velocity); + real_t get_constant_angular_velocity(int p_layer_id) const; void set_collision_polygons_count(int p_layer_id, int p_shapes_count); + int get_collision_polygons_count(int p_layer_id) const; void add_collision_polygon(int p_layer_id); void remove_collision_polygon(int p_layer_id, int p_polygon_index); void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector p_polygon);