diff options
37 files changed, 1463 insertions, 241 deletions
diff --git a/core/engine.cpp b/core/engine.cpp index 9607dedb3c..50822244cf 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -38,6 +38,7 @@ void Engine::set_iterations_per_second(int p_ips) { + ERR_FAIL_COND(p_ips <= 0); ips = p_ips; } int Engine::get_iterations_per_second() const { diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 3d71e66f80..0b6e9ae929 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -99,14 +99,22 @@ void AStar::remove_point(int p_id) { Point *p = points[p_id]; - Map<int, Point *>::Element *PE = points.front(); - while (PE) { - for (Set<Point *>::Element *E = PE->get()->neighbours.front(); E; E = E->next()) { - Segment s(p_id, E->get()->id); - segments.erase(s); - E->get()->neighbours.erase(p); - } - PE = PE->next(); + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { + + Segment s(p_id, E->get()->id); + segments.erase(s); + + E->get()->neighbours.erase(p); + E->get()->unlinked_neighbours.erase(p); + } + + for (Set<Point *>::Element *E = p->unlinked_neighbours.front(); E; E = E->next()) { + + Segment s(p_id, E->get()->id); + segments.erase(s); + + E->get()->neighbours.erase(p); + E->get()->unlinked_neighbours.erase(p); } memdelete(p); @@ -125,6 +133,8 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { if (bidirectional) b->neighbours.insert(a); + else + b->unlinked_neighbours.insert(a); Segment s(p_id, p_with_id); if (s.from == p_id) { @@ -147,7 +157,9 @@ void AStar::disconnect_points(int p_id, int p_with_id) { Point *a = points[p_id]; Point *b = points[p_with_id]; a->neighbours.erase(b); + a->unlinked_neighbours.erase(b); b->neighbours.erase(a); + b->unlinked_neighbours.erase(a); } bool AStar::has_point(int p_id) const { diff --git a/core/math/a_star.h b/core/math/a_star.h index fac8a9d312..ba35d929b3 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -54,6 +54,7 @@ class AStar : public Reference { bool enabled; Set<Point *> neighbours; + Set<Point *> unlinked_neighbours; // Used for pathfinding Point *prev_point; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index eb612191e7..ba31a774fc 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1013,7 +1013,7 @@ Trigger on a VR controller </constant> <constant name="JOY_OCULUS_AX" value="7" enum="JoystickList"> - A button on the right Oculus Touch controller, X button on the left controller (also when used in OpenVR) + A button on the right Oculus Touch controller, X button on the left controller (also when used in OpenVR) </constant> <constant name="JOY_OCULUS_BY" value="1" enum="JoystickList"> B button on the right Oculus Touch controller, Y button on the left controller (also when used in OpenVR) diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml index 59f7bec889..757c6c6a34 100644 --- a/doc/classes/Bone2D.xml +++ b/doc/classes/Bone2D.xml @@ -1,8 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Bone2D" inherits="Node2D" category="Core" version="3.2"> <brief_description> + Joint used with [Skeleton2D] to control and animate other nodes. </brief_description> <description> + Use a hierarchy of [code]Bone2D[/code] bound to a [Skeleton2D] to control, and animate other [Node2D] nodes. + You can use [code]Bone2D[/code] and [code]Skeleton2D[/code] nodes to animate 2D meshes created with the Polygon 2D UV editor. + Each bone has a [member rest] transform that you can reset to with [method apply_rest]. These rest poses are relative to the bone's parent. + If in the editor, you can set the rest pose of an entire skeleton using a menu option, from the code, you need to iterate over the bones to set their individual rest poses. </description> <tutorials> </tutorials> @@ -11,25 +16,30 @@ <return type="void"> </return> <description> + Stores the node's current transforms in [member rest]. </description> </method> <method name="get_index_in_skeleton" qualifiers="const"> <return type="int"> </return> <description> + Returns the node's index as part of the entire skeleton. See [Skeleton2D]. </description> </method> <method name="get_skeleton_rest" qualifiers="const"> <return type="Transform2D"> </return> <description> + Returns the node's [member rest] [code]Transform2D[/code] if it doesn't have a parent, or its rest pose relative to its parent. </description> </method> </methods> <members> <member name="default_length" type="float" setter="set_default_length" getter="get_default_length"> + Length of the bone's representation drawn in the editor's viewport in pixels. </member> <member name="rest" type="Transform2D" setter="set_rest" getter="get_rest"> + Rest transform of the bone. You can reset the node's transforms to this value using [method apply_rest]. </member> </members> <constants> diff --git a/doc/classes/CPUParticles.xml b/doc/classes/CPUParticles.xml index 5458a87a9e..c9c92102f3 100644 --- a/doc/classes/CPUParticles.xml +++ b/doc/classes/CPUParticles.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CPUParticles" inherits="GeometryInstance" category="Core" version="3.2"> <brief_description> + CPU-based 3D particle emitter. </brief_description> <description> + CPU-based 3D particle node used to create a variety of particle systems and effects. + See also [Particles], which provides the same functionality with hardware acceleration, but may not run on older devices. </description> <tutorials> </tutorials> @@ -13,56 +16,77 @@ <argument index="0" name="particles" type="Node"> </argument> <description> + Sets this node's properties to match a given [Particles] node with an assigned [ParticlesMaterial]. </description> </method> <method name="restart"> <return type="void"> </return> <description> + Restarts the particle emitter. </description> </method> </methods> <members> <member name="amount" type="int" setter="set_amount" getter="get_amount"> + Number of particles emitted in one emission cycle. </member> <member name="angle" type="float" setter="set_param" getter="get_param"> + Initial rotation applied to each particle, in degrees. </member> <member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's rotation will be animated along this [Curve]. </member> <member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Rotation randomness ratio. Default value: [code]0[/code]. </member> <member name="angular_velocity" type="float" setter="set_param" getter="get_param"> + Initial angular velocity applied to each particle. Sets the speed of rotation of the particle. </member> <member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's angular velocity will vary along this [Curve]. </member> <member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Angular velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="anim_offset" type="float" setter="set_param" getter="get_param"> + Particle animation offset. </member> <member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's animation offset will vary along this [Curve]. </member> <member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Animation offset randomness ratio. Default value: [code]0[/code]. </member> <member name="anim_speed" type="float" setter="set_param" getter="get_param"> + Particle animation speed. </member> <member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's animation speed will vary along this [Curve]. </member> <member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Animation speed randomness ratio. Default value: [code]0[/code]. </member> <member name="color" type="Color" setter="set_color" getter="get_color"> + Unused for 3D particles. </member> <member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> - Each particle's vertex color will vary along this [GradientTexture]. + Unused for 3D particles. </member> <member name="damping" type="float" setter="set_param" getter="get_param"> + The rate at which particles lose velocity. </member> <member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Damping will vary along this [Curve]. </member> <member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Damping randomness ratio. Default value: [code]0[/code]. </member> <member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="CPUParticles.DrawOrder"> + Particle draw order. Uses [enum DrawOrder] values. Default value: [constant DRAW_ORDER_INDEX]. </member> <member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents"> + The rectangle's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX]. </member> <member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors"> </member> @@ -71,134 +95,199 @@ <member name="emission_points" type="PoolVector3Array" setter="set_emission_points" getter="get_emission_points"> </member> <member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="CPUParticles.EmissionShape"> + Particles will be emitted inside this region. Use [enum EmissionShape] for values. Default value: [constant EMISSION_SHAPE_POINT]. </member> <member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius"> + The sphere's radius if [enum EmissionShape] is set to [constant EMISSION_SHAPE_SPHERE]. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting"> + If [code]true[/code], particles are being emitted. Default value: [code]true[/code]. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio"> + How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code]. </member> <member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> + The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the particle system itself. </member> <member name="flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> + Align y-axis of particle with the direction of its velocity. </member> <member name="flag_disable_z" type="bool" setter="set_particle_flag" getter="get_particle_flag"> + If [code]true[/code], particles will not move on the z axis. Default value: [code]false[/code]. </member> <member name="flag_rotate_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> + If [code]true[/code], particles rotate around y-axis by [member angle]. </member> <member name="flatness" type="float" setter="set_flatness" getter="get_flatness"> + Amount of [member spread] in Y/Z plane. A value of [code]1[/code] restricts particles to X/Z plane. Default [code]0[/code]. </member> <member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> + If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code] </member> <member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity"> + Gravity applied to every particle. Default value: [code](0, -9.8, 0)[/code]. </member> <member name="hue_variation" type="float" setter="set_param" getter="get_param"> + Initial hue variation applied to each particle. </member> <member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's hue will vary along this [Curve]. </member> <member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Hue variation randomness ratio. Default value: [code]0[/code]. </member> <member name="initial_velocity" type="float" setter="set_param" getter="get_param"> + Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. </member> <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Initial velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime"> + Amount of time each particle will exist. Default value: [code]1[/code]. </member> <member name="linear_accel" type="float" setter="set_param" getter="get_param"> + Linear acceleration applied to each particle in the direction of motion. </member> <member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's linear acceleration will vary along this [Curve]. </member> <member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Linear acceleration randomness ratio. Default value: [code]0[/code]. </member> <member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates"> + If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code]. </member> <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + The [Mesh] used for each particle. If [code]null[/code], particles will be spheres. </member> <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> + If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code]. </member> <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> + Orbital velocity applied to each particle. Makes the particles circle around origin in the local XY plane. Specified in number of full rotations around origin per second. + This property is only available when [member flag_disable_z] is [code]true[/code]. </member> <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's orbital velocity will vary along this [Curve]. </member> <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Orbital velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> + Particle system starts as if it had already run for this many seconds. </member> <member name="radial_accel" type="float" setter="set_param" getter="get_param"> + Radial acceleration applied to each particle. Makes particle accelerate away from origin. </member> <member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's radial acceleration will vary along this [Curve]. </member> <member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Radial acceleration randomness ratio. Default value: [code]0[/code]. </member> <member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio"> + Emission lifetime randomness ratio. Default value: [code]0[/code]. </member> <member name="scale_amount" type="float" setter="set_param" getter="get_param"> + Initial scale applied to each particle. </member> <member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's scale will vary along this [Curve]. </member> <member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Scale randomness ratio. Default value: [code]0[/code]. </member> <member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale"> + Particle system's running speed scaling ratio. Default value: [code]1[/code]. A value of [code]0[/code] can be used to pause the particles. </member> <member name="spread" type="float" setter="set_spread" getter="get_spread"> + Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Default value: [code]45[/code]. </member> <member name="tangential_accel" type="float" setter="set_param" getter="get_param"> + Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. </member> <member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's tangential acceleration will vary along this [Curve]. </member> <member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Tangential acceleration randomness ratio. Default value: [code]0[/code]. </member> </members> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> + Particles are drawn in the order emitted. </constant> <constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder"> + Particles are drawn in order of remaining lifetime. </constant> <constant name="DRAW_ORDER_VIEW_DEPTH" value="2" enum="DrawOrder"> + Particles are drawn in order of depth. </constant> <constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set initial velocity properties. </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties. </constant> <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set radial acceleration properties. </constant> <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set tangential acceleration properties. </constant> <constant name="PARAM_DAMPING" value="6" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set damping properties. </constant> <constant name="PARAM_ANGLE" value="7" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angle properties. </constant> <constant name="PARAM_SCALE" value="8" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties. </constant> <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation offset properties. </constant> <constant name="PARAM_MAX" value="12" enum="Parameter"> + Represents the size of the [enum Parameter] enum. </constant> <constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> + Use with [method set_flag] to set [member flag_align_y]. </constant> <constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> + Use with [method set_flag] to set [member flag_rotate_y]. </constant> <constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> + Use with [method set_flag] to set [member flag_disable_z]. </constant> <constant name="FLAG_MAX" value="3" enum="Flags"> + Represents the size of the [enum Flags] enum. </constant> <constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> + All particles will be emitted from a single point. </constant> <constant name="EMISSION_SHAPE_SPHERE" value="1" enum="EmissionShape"> + Particles will be emitted in the volume of a sphere. </constant> <constant name="EMISSION_SHAPE_BOX" value="2" enum="EmissionShape"> + Particles will be emitted in the volume of a box. </constant> <constant name="EMISSION_SHAPE_POINTS" value="3" enum="EmissionShape"> + Particles will be emitted at a position chosen randomly among [member emission_points]. Particle color will be modulated by [member emission_colors]. </constant> <constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> + Particles will be emitted at a position chosen randomly among [member emission_points]. Particle velocity and rotation will be set based on [member emission_normals]. Particle color will be modulated by [member emission_colors]. </constant> </constants> </class> diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index 17c68ccb87..4351a0b4d4 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -1,10 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CPUParticles2D" inherits="Node2D" category="Core" version="3.2"> <brief_description> + CPU-based 2D particle emitter. </brief_description> <description> + CPU-based 2D particle node used to create a variety of particle systems and effects. + See also [Particles2D], which provides the same functionality with hardware acceleration, but may not run on older devices. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/2d/particle_systems_2d.html</link> </tutorials> <methods> <method name="convert_from_particles"> @@ -13,53 +17,74 @@ <argument index="0" name="particles" type="Node"> </argument> <description> + Sets this node's properties to match a given [Particles2D] node with an assigned [ParticlesMaterial]. </description> </method> <method name="restart"> <return type="void"> </return> <description> + Restarts the particle emitter. </description> </method> </methods> <members> <member name="amount" type="int" setter="set_amount" getter="get_amount"> + Number of particles emitted in one emission cycle. </member> <member name="angle" type="float" setter="set_param" getter="get_param"> + Initial rotation applied to each particle, in degrees. </member> <member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's rotation will be animated along this [Curve]. </member> <member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Rotation randomness ratio. Default value: [code]0[/code]. </member> <member name="angular_velocity" type="float" setter="set_param" getter="get_param"> + Initial angular velocity applied to each particle. Sets the speed of rotation of the particle. </member> <member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's angular velocity will vary along this [Curve]. </member> <member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Angular velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="anim_offset" type="float" setter="set_param" getter="get_param"> + Particle animation offset. </member> <member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's animation offset will vary along this [Curve]. </member> <member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Animation offset randomness ratio. Default value: [code]0[/code]. </member> <member name="anim_speed" type="float" setter="set_param" getter="get_param"> + Particle animation speed. </member> <member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's animation speed will vary along this [Curve]. </member> <member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Animation speed randomness ratio. Default value: [code]0[/code]. </member> <member name="color" type="Color" setter="set_color" getter="get_color"> + Each particle's initial color. If [member texture] is defined, it will be multiplied by this color. </member> <member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> + Each particle's color will vary along this [Gradient]. </member> <member name="damping" type="float" setter="set_param" getter="get_param"> + The rate at which particles lose velocity. </member> <member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Damping will vary along this [Curve]. </member> <member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Damping randomness ratio. Default value: [code]0[/code]. </member> <member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="CPUParticles2D.DrawOrder"> + Particle draw order. Uses [enum DrawOrder] values. Default value: [constant DRAW_ORDER_INDEX]. </member> <member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors"> </member> @@ -68,132 +93,194 @@ <member name="emission_points" type="PoolVector2Array" setter="set_emission_points" getter="get_emission_points"> </member> <member name="emission_rect_extents" type="Vector2" setter="set_emission_rect_extents" getter="get_emission_rect_extents"> + The rectangle's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_RECTANGLE]. </member> <member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="CPUParticles2D.EmissionShape"> + Particles will be emitted inside this region. Use [enum EmissionShape] for values. Default value: [constant EMISSION_SHAPE_POINT]. </member> <member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius"> + The circle's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_CIRCLE]. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting"> + If [code]true[/code], particles are being emitted. Default value: [code]true[/code]. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio"> + How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code]. </member> <member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> + The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself. </member> <member name="flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> + Align y-axis of particle with the direction of its velocity. </member> <member name="flatness" type="float" setter="set_flatness" getter="get_flatness"> </member> <member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> + If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code] </member> <member name="gravity" type="Vector2" setter="set_gravity" getter="get_gravity"> + Gravity applied to every particle. Default value: [code](0, 98)[/code]. </member> <member name="hue_variation" type="float" setter="set_param" getter="get_param"> + Initial hue variation applied to each particle. </member> <member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's hue will vary along this [Curve]. </member> <member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Hue variation randomness ratio. Default value: [code]0[/code]. </member> <member name="initial_velocity" type="float" setter="set_param" getter="get_param"> + Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. </member> <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Initial velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime"> + Amount of time each particle will exist. Default value: [code]1[/code]. </member> <member name="linear_accel" type="float" setter="set_param" getter="get_param"> + Linear acceleration applied to each particle in the direction of motion. </member> <member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's linear acceleration will vary along this [Curve]. </member> <member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Linear acceleration randomness ratio. Default value: [code]0[/code]. </member> <member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates"> + If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code]. </member> <member name="normalmap" type="Texture" setter="set_normalmap" getter="get_normalmap"> + Normal map to be used for the [member texture] property. </member> <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> + If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code]. </member> <member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> + Orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second. </member> <member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's orbital velocity will vary along this [Curve]. </member> <member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Orbital velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> + Particle system starts as if it had already run for this many seconds. </member> <member name="radial_accel" type="float" setter="set_param" getter="get_param"> + Radial acceleration applied to each particle. Makes particle accelerate away from origin. </member> <member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's radial acceleration will vary along this [Curve]. </member> <member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Radial acceleration randomness ratio. Default value: [code]0[/code]. </member> <member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio"> + Emission lifetime randomness ratio. Default value: [code]0[/code]. </member> <member name="scale_amount" type="float" setter="set_param" getter="get_param"> + Initial scale applied to each particle. </member> <member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's scale will vary along this [Curve]. </member> <member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Scale randomness ratio. Default value: [code]0[/code]. </member> <member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale"> + Particle system's running speed scaling ratio. Default value: [code]1[/code]. A value of [code]0[/code] can be used to pause the particles. </member> <member name="spread" type="float" setter="set_spread" getter="get_spread"> + Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Default value: [code]45[/code]. </member> <member name="tangential_accel" type="float" setter="set_param" getter="get_param"> + Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. </member> <member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> + Each particle's tangential acceleration will vary along this [Curve]. </member> <member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> + Tangential acceleration randomness ratio. Default value: [code]0[/code]. </member> <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> + Particle texture. If [code]null[/code] particles will be squares. </member> </members> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> + Particles are drawn in the order emitted. </constant> <constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder"> + Particles are drawn in order of remaining lifetime. </constant> <constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set initial velocity properties. </constant> <constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties. </constant> <constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set radial acceleration properties. </constant> <constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set tangential acceleration properties. </constant> <constant name="PARAM_DAMPING" value="6" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set damping properties. </constant> <constant name="PARAM_ANGLE" value="7" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angle properties. </constant> <constant name="PARAM_SCALE" value="8" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties. </constant> <constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation offset properties. </constant> <constant name="PARAM_MAX" value="12" enum="Parameter"> + Represents the size of the [enum Parameter] enum. </constant> <constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> + Use with [method set_flag] to set [member flag_align_y]. </constant> <constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> + Present for consistency with 3D particle nodes, not used in 2D. </constant> <constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> + Present for consistency with 3D particle nodes, not used in 2D. </constant> <constant name="FLAG_MAX" value="3" enum="Flags"> + Represents the size of the [enum Flags] enum. </constant> <constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> + All particles will be emitted from a single point. </constant> <constant name="EMISSION_SHAPE_CIRCLE" value="1" enum="EmissionShape"> + Particles will be emitted on the perimeter of a circle. </constant> <constant name="EMISSION_SHAPE_RECTANGLE" value="2" enum="EmissionShape"> + Particles will be emitted in the area of a rectangle. </constant> <constant name="EMISSION_SHAPE_POINTS" value="3" enum="EmissionShape"> + Particles will be emitted at a position chosen randomly among [member emission_points]. Particle color will be modulated by [member emission_colors]. </constant> <constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> + Particles will be emitted at a position chosen randomly among [member emission_points]. Particle velocity and rotation will be set based on [member emission_normals]. Particle color will be modulated by [member emission_colors]. </constant> </constants> </class> diff --git a/doc/classes/ClippedCamera.xml b/doc/classes/ClippedCamera.xml index b7f158dd65..c6dcd6cd96 100644 --- a/doc/classes/ClippedCamera.xml +++ b/doc/classes/ClippedCamera.xml @@ -29,6 +29,12 @@ <description> </description> </method> + <method name="get_clip_offset" qualifiers="const"> + <return type="float"> + </return> + <description> + </description> + </method> <method name="get_collision_mask_bit" qualifiers="const"> <return type="bool"> </return> diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 953f364bdb..908c017ac9 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -138,5 +138,7 @@ </theme_item> <theme_item name="reload" type="Texture"> </theme_item> + <theme_item name="toggle_hidden" type="Texture"> + </theme_item> </theme_items> </class> diff --git a/doc/classes/MeshInstance2D.xml b/doc/classes/MeshInstance2D.xml index 39a733fdb3..4b38b9aa96 100644 --- a/doc/classes/MeshInstance2D.xml +++ b/doc/classes/MeshInstance2D.xml @@ -22,6 +22,12 @@ The [Texture] that will be used if using the default [CanvasItemMaterial]. Can be accessed as [code]TEXTURE[/code] in CanvasItem shader. </member> </members> + <signals> + <signal name="texture_changed"> + <description> + </description> + </signal> + </signals> <constants> </constants> </class> diff --git a/doc/classes/MultiMeshInstance2D.xml b/doc/classes/MultiMeshInstance2D.xml index 0bf243ee24..8509986c3c 100644 --- a/doc/classes/MultiMeshInstance2D.xml +++ b/doc/classes/MultiMeshInstance2D.xml @@ -22,6 +22,12 @@ The [Texture] that will be used if using the default [CanvasItemMaterial]. Can be accessed as [code]TEXTURE[/code] in CanvasItem shader. </member> </members> + <signals> + <signal name="texture_changed"> + <description> + </description> + </signal> + </signals> <constants> </constants> </class> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 2592bc6775..e994c24582 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -508,7 +508,8 @@ <argument index="0" name="tag_name" type="String"> </argument> <description> - Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. See feature tags documentation. + Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. + Note that tag names are case-sensitive. </description> </method> <method name="has_touchscreen_ui_hint" qualifiers="const"> diff --git a/doc/classes/Particles.xml b/doc/classes/Particles.xml index 7820c63ad7..a29b621406 100644 --- a/doc/classes/Particles.xml +++ b/doc/classes/Particles.xml @@ -55,8 +55,10 @@ Time ratio between each emission. If [code]0[/code] particles are emitted continuously. If [code]1[/code] all particles are emitted simultaneously. Default value: [code]0[/code]. </member> <member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> + The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself. </member> <member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> + If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code]. </member> <member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime"> Amount of time each particle will exist. Default value: [code]1[/code]. diff --git a/doc/classes/Particles2D.xml b/doc/classes/Particles2D.xml index de4877b639..78114e985d 100644 --- a/doc/classes/Particles2D.xml +++ b/doc/classes/Particles2D.xml @@ -40,7 +40,7 @@ How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code]. </member> <member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> - The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the particle system itself. + The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself. </member> <member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code] @@ -52,7 +52,7 @@ If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code]. </member> <member name="normal_map" type="Texture" setter="set_normal_map" getter="get_normal_map"> - Normal map to be used for the [code]texture[/code] property. + Normal map to be used for the [member texture] property. </member> <member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code]. diff --git a/doc/classes/ParticlesMaterial.xml b/doc/classes/ParticlesMaterial.xml index dd7a7cd151..cb06593bc2 100644 --- a/doc/classes/ParticlesMaterial.xml +++ b/doc/classes/ParticlesMaterial.xml @@ -100,7 +100,7 @@ Amount of [member spread] in Y/Z plane. A value of [code]1[/code] restricts particles to X/Z plane. Default [code]0[/code]. </member> <member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity"> - Gravity applied to every particle. Default value: [code](0, 98, 0)[/code]. + Gravity applied to every particle. Default value: [code](0, -9.8, 0)[/code]. </member> <member name="hue_variation" type="float" setter="set_param" getter="get_param"> Initial hue variation applied to each particle. @@ -112,13 +112,13 @@ Hue variation randomness ratio. Default value: [code]0[/code]. </member> <member name="initial_velocity" type="float" setter="set_param" getter="get_param"> - Initial velocity magnitude for each particle. Direction comes from [member spread]. + Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation. </member> <member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> Initial velocity randomness ratio. Default value: [code]0[/code]. </member> <member name="linear_accel" type="float" setter="set_param" getter="get_param"> - Linear acceleration applied to each particle. Acceleration increases velocity magnitude each frame without affecting direction. + Linear acceleration applied to each particle in the direction of motion. </member> <member name="linear_accel_curve" type="Texture" setter="set_param_texture" getter="get_param_texture"> Each particle's linear acceleration will vary along this [CurveTexture]. @@ -184,7 +184,7 @@ Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties. </constant> <constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital_velocity properties. + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties. </constant> <constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties. @@ -205,7 +205,7 @@ Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties. </constant> <constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> - Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue_variation properties. + Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties. </constant> <constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties. diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 569ad0297e..6d3603f31b 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -363,7 +363,7 @@ ConnectDialog::ConnectDialog() { tree->connect("node_selected", this, "_tree_node_selected"); tree->set_connect_to_script_mode(true); - Node *mc = vbc_left->add_margin_child(TTR("Connect To Script:"), tree, true); + Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), tree, true); connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1)); error_label = memnew(Label); @@ -413,7 +413,7 @@ ConnectDialog::ConnectDialog() { vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true); HBoxContainer *dstm_hb = memnew(HBoxContainer); - vbc_left->add_margin_child("Method to Create:", dstm_hb); + vbc_left->add_margin_child("Receiver Method:", dstm_hb); dst_method = memnew(LineEdit); dst_method->set_h_size_flags(SIZE_EXPAND_FILL); @@ -489,8 +489,26 @@ void ConnectionsDock::_make_or_edit_connection() { bool oshot = connect_dialog->get_oneshot(); cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); - // Conditions to add function, must have a script and must have a method. - bool add_script_function = !target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method); + // Conditions to add function: must have a script and must not have the method already + // (in the class, the script itself, or inherited). + bool add_script_function = false; + Ref<Script> script = target->get_script(); + if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method)) { + // There is a chance that the method is inherited from another script. + bool found_inherited_function = false; + Ref<Script> inherited_script = script->get_base_script(); + while (!inherited_script.is_null()) { + int line = inherited_script->get_language()->find_function(cToMake.method, inherited_script->get_source_code()); + if (line != -1) { + found_inherited_function = true; + break; + } + + inherited_script = inherited_script->get_base_script(); + } + + add_script_function = !found_inherited_function; + } PoolStringArray script_function_args; if (add_script_function) { // Pick up args here before "it" is deleted by update_tree. @@ -507,8 +525,7 @@ void ConnectionsDock::_make_or_edit_connection() { _connect(cToMake); } - // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, - // which will delete the object "it" is pointing to. + // IMPORTANT NOTE: _disconnect and _connect cause an update_tree, which will delete the object "it" is pointing to. it = NULL; if (add_script_function) { @@ -677,7 +694,7 @@ void ConnectionsDock::_open_connection_dialog(Connection cToEdit) { if (src && dst) { connect_dialog->set_title(TTR("Edit Connection:") + cToEdit.signal); - connect_dialog->popup_centered_ratio(); + connect_dialog->popup_centered(); connect_dialog->init(cToEdit, true); } } diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index cddabbc4e4..cf14cf3e00 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -206,7 +206,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { dfmono->set_font_ptr(_font_Hack_Regular, _font_Hack_Regular_size); //dfd->set_force_autohinter(true); //just looks better..i think? - int default_font_size = int(EditorSettings::get_singleton()->get("interface/editor/main_font_size")) * EDSCALE; + int default_font_size = int(EDITOR_GET("interface/editor/main_font_size")) * EDSCALE; // Default font MAKE_DEFAULT_FONT(df, default_font_size); @@ -221,9 +221,9 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font("title", "EditorFonts", df_title); // Doc font - MAKE_BOLD_FONT(df_doc_title, int(EDITOR_DEF("text_editor/help/help_title_font_size", 23)) * EDSCALE); + MAKE_BOLD_FONT(df_doc_title, int(EDITOR_GET("text_editor/help/help_title_font_size")) * EDSCALE); - MAKE_DEFAULT_FONT(df_doc, int(EDITOR_DEF("text_editor/help/help_font_size", 15)) * EDSCALE); + MAKE_DEFAULT_FONT(df_doc, int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE); p_theme->set_font("doc", "EditorFonts", df_doc); p_theme->set_font("doc_title", "EditorFonts", df_doc_title); @@ -236,13 +236,13 @@ void editor_register_fonts(Ref<Theme> p_theme) { p_theme->set_font("rulers", "EditorFonts", df_rulers); // Code font - MAKE_SOURCE_FONT(df_code, int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) * EDSCALE); + MAKE_SOURCE_FONT(df_code, int(EDITOR_GET("interface/editor/code_font_size")) * EDSCALE); p_theme->set_font("source", "EditorFonts", df_code); - MAKE_SOURCE_FONT(df_expression, (int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) - 1) * EDSCALE); + MAKE_SOURCE_FONT(df_expression, (int(EDITOR_GET("interface/editor/code_font_size")) - 1) * EDSCALE); p_theme->set_font("expression", "EditorFonts", df_expression); - MAKE_SOURCE_FONT(df_output_code, int(EDITOR_DEF("run/output/font_size", 13)) * EDSCALE); + MAKE_SOURCE_FONT(df_output_code, int(EDITOR_GET("run/output/font_size")) * EDSCALE); p_theme->set_font("output_source", "EditorFonts", df_output_code); MAKE_SOURCE_FONT(df_text_editor_status_code, default_font_size); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a6746c6d25..58e3cc6fc1 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -320,19 +320,19 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/custom_display_scale", 1.0f); hints["interface/editor/custom_display_scale"] = PropertyInfo(Variant::REAL, "interface/editor/custom_display_scale", PROPERTY_HINT_RANGE, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font_size", 14); - hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "10,40,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); - _initial_set("interface/editor/code_font_size", 14); - hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); + hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font_antialiased", true); - _initial_set("interface/editor/code_font_antialiased", true); _initial_set("interface/editor/main_font_hinting", 2); hints["interface/editor/main_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/main_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); - _initial_set("interface/editor/code_font_hinting", 2); - hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font", ""); hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/main_font_bold", ""); hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); + _initial_set("interface/editor/code_font_size", 14); + hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT); + _initial_set("interface/editor/code_font_antialiased", true); + _initial_set("interface/editor/code_font_hinting", 2); + hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/code_font", ""); hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/dim_editor_on_dialog_popup", true); @@ -491,8 +491,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Help _initial_set("text_editor/help/show_help_index", true); - _initial_set("text_editor/help_source_font_size", 14); - hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::REAL, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "10, 50, 1"); + _initial_set("text_editor/help/help_font_size", 15); + hints["text_editor/help/help_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_font_size", PROPERTY_HINT_RANGE, "8,48,1"); + _initial_set("text_editor/help/help_source_font_size", 14); + hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "8,48,1"); + _initial_set("text_editor/help/help_title_font_size", 23); + hints["text_editor/help/help_title_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_title_font_size", PROPERTY_HINT_RANGE, "8,48,1"); /* Editors */ @@ -600,7 +604,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("run/auto_save/save_before_running", true); // Output - hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); + _initial_set("run/output/font_size", 13); + hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,48,1"); _initial_set("run/output/always_clear_output_on_play", true); _initial_set("run/output/always_open_output_on_play", true); _initial_set("run/output/always_close_output_on_stop", false); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index cea65bb9b4..c7a438948d 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -882,26 +882,29 @@ void ScriptTextEditor::_update_connected_methods() { continue; } - int line = script->get_language()->find_function(connection.method, text_edit->get_text()); - if (line < 0) { - // There is a chance that the method is inherited from another script. - bool found_inherited_function = false; - Ref<Script> inherited_script = script->get_base_script(); - while (!inherited_script.is_null()) { - line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code()); - if (line != -1) { - found_inherited_function = true; - break; + if (!ClassDB::has_method(script->get_instance_base_type(), connection.method)) { + + int line = script->get_language()->find_function(connection.method, text_edit->get_text()); + if (line < 0) { + // There is a chance that the method is inherited from another script. + bool found_inherited_function = false; + Ref<Script> inherited_script = script->get_base_script(); + while (!inherited_script.is_null()) { + line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code()); + if (line != -1) { + found_inherited_function = true; + break; + } + + inherited_script = inherited_script->get_base_script(); } - inherited_script = inherited_script->get_base_script(); - } - - if (!found_inherited_function) { - missing_connections.push_back(connection); + if (!found_inherited_function) { + missing_connections.push_back(connection); + } + } else { + text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method); } - } else { - text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method); } } } diff --git a/main/main.cpp b/main/main.cpp index 3f01a17ea7..7e4fdeeaa7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1020,6 +1020,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater")); Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0)); ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater")); diff --git a/modules/webrtc/config.py b/modules/webrtc/config.py index 2e3a18ad0e..48b4c33c5d 100644 --- a/modules/webrtc/config.py +++ b/modules/webrtc/config.py @@ -7,7 +7,8 @@ def configure(env): def get_doc_classes(): return [ "WebRTCPeerConnection", - "WebRTCDataChannel" + "WebRTCDataChannel", + "WebRTCMultiplayer" ] def get_doc_path(): diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index dcc14d4ddb..7cce97244d 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -11,85 +11,106 @@ <return type="void"> </return> <description> + Closes this data channel, notifying the other peer. </description> </method> <method name="get_id" qualifiers="const"> <return type="int"> </return> <description> + Returns the id assigned to this channel during creation (or auto-assigned during negotiation). + If the channel is not negotiated out-of-band the id will only be available after the connection is established (will return [code]65535[/code] until then). </description> </method> <method name="get_label" qualifiers="const"> <return type="String"> </return> <description> + Returns the label assigned to this channel during creation. </description> </method> <method name="get_max_packet_life_time" qualifiers="const"> <return type="int"> </return> <description> + Returns the [code]maxPacketLifeTime[/code] value assigned to this channel during creation. + Will be [code]65535[/code] if not specified. </description> </method> <method name="get_max_retransmits" qualifiers="const"> <return type="int"> </return> <description> + Returns the [code]maxRetransmits[/code] value assigned to this channel during creation. + Will be [code]65535[/code] if not specified. </description> </method> <method name="get_protocol" qualifiers="const"> <return type="String"> </return> <description> + Returns the sub-protocol assigned to this channel during creation. An empty string if not specified. </description> </method> <method name="get_ready_state" qualifiers="const"> <return type="int" enum="WebRTCDataChannel.ChannelState"> </return> <description> + Returns the current state of this channel, see [enum WebRTCDataChannel.ChannelState]. </description> </method> <method name="is_negotiated" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if this channel was created with out-of-band configuration. </description> </method> <method name="is_ordered" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if this channel was created with ordering enabled (default). </description> </method> <method name="poll"> <return type="int" enum="Error"> </return> <description> + Reserved, but not used for now. </description> </method> <method name="was_string_packet" qualifiers="const"> <return type="bool"> </return> <description> + Returns [code]true[/code] if the last received packet was transferred as text. See [member write_mode]. </description> </method> </methods> <members> <member name="write_mode" type="int" setter="set_write_mode" getter="get_write_mode" enum="WebRTCDataChannel.WriteMode"> + The transfer mode to use when sending outgoing packet. Either text or binary. </member> </members> <constants> <constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode"> + Tells the channel to send data over this channel as text. An external peer (non-Godot) would receive this as a string. </constant> <constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode"> + Tells the channel to send data over this channel as binary. An external peer (non-Godot) would receive this as array buffer or blob. </constant> <constant name="STATE_CONNECTING" value="0" enum="ChannelState"> + The channel was created, but it's still trying to connect. </constant> <constant name="STATE_OPEN" value="1" enum="ChannelState"> + The channel is currently open, and data can flow over it. </constant> <constant name="STATE_CLOSING" value="2" enum="ChannelState"> + The channel is being closed, no new messages will be accepted, but those already in queue will be flushed. </constant> <constant name="STATE_CLOSED" value="3" enum="ChannelState"> + The channel was closed, or connection failed. </constant> </constants> </class> diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml new file mode 100644 index 0000000000..2b0622fffa --- /dev/null +++ b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebRTCMultiplayer" inherits="NetworkedMultiplayerPeer" category="Core" version="3.2"> + <brief_description> + A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. + </brief_description> + <description> + This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.network_peer]. + You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. + [signal NetworkedMultiplayerPeer.connection_succeeded] and [signal NetworkedMultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [NetworkedMultiplayerPeer]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_peer"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="peer" type="WebRTCPeerConnection"> + </argument> + <argument index="1" name="peer_id" type="int"> + </argument> + <argument index="2" name="unreliable_lifetime" type="int" default="1"> + </argument> + <description> + Add a new peer to the mesh with the given [code]peer_id[/code]. The [WebRTCPeerConnection] must be in state [constant WebRTCPeerConnection.STATE_NEW]. + Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]). + </description> + </method> + <method name="close"> + <return type="void"> + </return> + <description> + Close all the add peer connections and channels, freeing all resources. + </description> + </method> + <method name="get_peer"> + <return type="Dictionary"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Return a dictionary representation of the peer with given [code]peer_id[/code] with three keys. [code]connection[/code] containing the [WebRTCPeerConnection] to this peer, [code]channels[/code] an array of three [WebRTCDataChannel], and [code]connected[/code] a boolean representing if the peer connection is currently connected (all three channels are open). + </description> + </method> + <method name="get_peers"> + <return type="Dictionary"> + </return> + <description> + Returns a dictionary which keys are the peer ids and values the peer representation as in [method get_peer] + </description> + </method> + <method name="has_peer"> + <return type="bool"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though). + </description> + </method> + <method name="initialize"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <argument index="1" name="server_compatibility" type="bool" default="false"> + </argument> + <description> + Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). + If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED] and [signal NetworkedMultiplayerPeer.connection_succeeded] will not be emitted. + If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal NetworkedMultiplayerPeer.peer_connected] signals until a peer with id [constant NetworkedMultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal NetworkedMultiplayerPeer.connection_succeeded]. After that the signal [signal NetworkedMultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal NetworkedMultiplayerPeer.server_disconnected] will be emitted and state will become [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED]. + </description> + </method> + <method name="remove_peer"> + <return type="void"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Remove the peer with given [code]peer_id[/code] from the mesh. If the peer was connected, and [signal NetworkedMultiplayerPeer.peer_connected] was emitted for it, then [signal NetworkedMultiplayerPeer.peer_disconnected] will be emitted. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 8b14c60deb..ae709877f4 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -1,8 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="WebRTCPeerConnection" inherits="Reference" category="Core" version="3.2"> <brief_description> + Interface to a WebRTC peer connection. </brief_description> <description> + A WebRTC connection between the local computer and a remote peer. Provides an interface to connect, maintain and monitor the connection. + Setting up a WebRTC connection between two peers from now on) may not seem a trivial task, but it can be broken down into 3 main steps: + - The peer that wants to initiate the connection ([code]A[/code] from now on) creates an offer and send it to the other peer ([code]B[/code] from now on). + - [code]B[/code] receives the offer, generate and answer, and sends it to [code]B[/code]). + - [code]A[/code] and [code]B[/code] then generates and exchange ICE candidates with each other. + After these steps, the connection should become connected. Keep on reading or look into the tutorial for more information. </description> <tutorials> </tutorials> @@ -17,12 +24,14 @@ <argument index="2" name="name" type="String"> </argument> <description> + Add an ice candidate generated by a remote peer (and received over the signaling server). See [signal ice_candidate_created]. </description> </method> <method name="close"> <return type="void"> </return> <description> + Close the peer connection and all data channels associated with it. Note, you cannot reuse this object for a new connection unless you call [method initialize]. </description> </method> <method name="create_data_channel"> @@ -35,18 +44,38 @@ }"> </argument> <description> + Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW]. + There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code]. + Valid [code]options[/code] are: + [codeblock] + { + "negotiated": true, # When set to true (default off), means the channel is negotiated out of band. "id" must be set too. data_channel_received will not be called. + "id": 1, # When "negotiated" is true this value must also be set to the same value on both peer. + + # Only one of maxRetransmits and maxPacketLifeTime can be specified, not both. They make the channel unreliable (but also better at real time). + "maxRetransmits": 1, # Specify the maximum number of attempt the peer will make to retransmits packets if they are not acknowledged. + "maxPacketLifeTime": 100, # Specify the maximum amount of time before giving up retransmitions of unacknowledged packets (in milliseconds). + "ordered": true, # When in unreliable mode (i.e. either "maxRetransmits" or "maxPacketLifetime" is set), "ordered" (true by default) specify if packet ordering is to be enforced. + + "protocol": "my-custom-protocol", # A custom sub-protocol string for this channel. + } + [/codeblock] + NOTE: You must keep a reference to channels created this way, or it will be closed. </description> </method> <method name="create_offer"> <return type="int" enum="Error"> </return> <description> + Creates a new SDP offer to start a WebRTC connection with a remote peer. At least one [WebRTCDataChannel] must have been created before calling this method. + If this functions returns [code]OK[/code], [signal session_description_created] will be called when the session is ready to be sent. </description> </method> <method name="get_connection_state" qualifiers="const"> <return type="int" enum="WebRTCPeerConnection.ConnectionState"> </return> <description> + Returns the connection state. See [enum ConnectionState]. </description> </method> <method name="initialize"> @@ -57,12 +86,29 @@ }"> </argument> <description> + Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection. + Valid [code]options[/code] are: + [codeblock] + { + "iceServers": [ + { + "urls": [ "stun:stun.example.com:3478" ], # One or more STUN servers. + }, + { + "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers. + "username": "a_username", # Optional username for the TURN server. + "credentials": "a_password", # Optional password for the TURN server. + } + ] + } + [/codeblock] </description> </method> <method name="poll"> <return type="int" enum="Error"> </return> <description> + Call this method frequently (e.g. in [method Node._process] or [method Node._physics_process]) to properly receive signals. </description> </method> <method name="set_local_description"> @@ -73,6 +119,8 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Sets the SDP description of the local peer. This should be called in response to [signal session_description_created]. + If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created]. </description> </method> <method name="set_remote_description"> @@ -83,6 +131,9 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Sets the SDP description of the remote peer. This should be called with the values generated by a remote peer and received over the signaling server. + If [code]type[/code] is [code]offer[/code] the peer will emit [signal session_description_created] with the appropriate answer. + If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created]. </description> </method> </methods> @@ -91,6 +142,8 @@ <argument index="0" name="channel" type="Object"> </argument> <description> + Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). + The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel] </description> </signal> <signal name="ice_candidate_created"> @@ -101,6 +154,7 @@ <argument index="2" name="name" type="String"> </argument> <description> + Emitted when a new ICE candidate has been created. The three parameters are meant to be passed to the remote peer over the signaling server. </description> </signal> <signal name="session_description_created"> @@ -109,21 +163,28 @@ <argument index="1" name="sdp" type="String"> </argument> <description> + Emitted after a successful call to [method create_offer] or [method set_remote_description] (when it generates an answer). The parameters are meant to be passed to [method set_local_description] on this object, and sent to the remote peer over the signaling server. </description> </signal> </signals> <constants> <constant name="STATE_NEW" value="0" enum="ConnectionState"> + The connection is new, data channels and an offer can be created in this state. </constant> <constant name="STATE_CONNECTING" value="1" enum="ConnectionState"> + The peer is connecting, ICE is in progress, non of the transports has failed. </constant> <constant name="STATE_CONNECTED" value="2" enum="ConnectionState"> + The peer is connected, all ICE transports are connected. </constant> <constant name="STATE_DISCONNECTED" value="3" enum="ConnectionState"> + At least one ICE transport is disconnected. </constant> <constant name="STATE_FAILED" value="4" enum="ConnectionState"> + One or more of the ICE transports failed. </constant> <constant name="STATE_CLOSED" value="5" enum="ConnectionState"> + The peer connection is closed (after calling [method close] for example). </constant> </constants> </class> diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 44e072cc89..58b68d926b 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -40,6 +40,7 @@ #include "webrtc_data_channel_gdnative.h" #include "webrtc_peer_connection_gdnative.h" #endif +#include "webrtc_multiplayer.h" void register_webrtc_types() { #ifdef JAVASCRIPT_ENABLED @@ -54,6 +55,7 @@ void register_webrtc_types() { ClassDB::register_class<WebRTCDataChannelGDNative>(); #endif ClassDB::register_virtual_class<WebRTCDataChannel>(); + ClassDB::register_class<WebRTCMultiplayer>(); } void unregister_webrtc_types() {} diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 2e7c64aa72..ce2fada634 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -205,30 +205,45 @@ String WebRTCDataChannelJS::get_label() const { } /* clang-format off */ -#define _JS_GET(PROP) \ +#define _JS_GET(PROP, DEF) \ EM_ASM_INT({ \ var dict = Module.IDHandler.get($0); \ if (!dict || !dict["channel"]) { \ - return 0; \ - }; \ - return dict["channel"].PROP; \ + return DEF; \ + } \ + var out = dict["channel"].PROP; \ + return out === null ? DEF : out; \ }, _js_id) /* clang-format on */ bool WebRTCDataChannelJS::is_ordered() const { - return _JS_GET(ordered); + return _JS_GET(ordered, true); } int WebRTCDataChannelJS::get_id() const { - return _JS_GET(id); + return _JS_GET(id, 65535); } int WebRTCDataChannelJS::get_max_packet_life_time() const { - return _JS_GET(maxPacketLifeTime); + // Can't use macro, webkit workaround. + /* clang-format off */ + return EM_ASM_INT({ + var dict = Module.IDHandler.get($0); + if (!dict || !dict["channel"]) { + return 65535; + } + if (dict["channel"].maxRetransmitTime !== undefined) { + // Guess someone didn't appreciate the standardization process. + return dict["channel"].maxRetransmitTime; + } + var out = dict["channel"].maxPacketLifeTime; + return out === null ? 65535 : out; + }, _js_id); + /* clang-format on */ } int WebRTCDataChannelJS::get_max_retransmits() const { - return _JS_GET(maxRetransmits); + return _JS_GET(maxRetransmits, 65535); } String WebRTCDataChannelJS::get_protocol() const { @@ -236,7 +251,7 @@ String WebRTCDataChannelJS::get_protocol() const { } bool WebRTCDataChannelJS::is_negotiated() const { - return _JS_GET(negotiated); + return _JS_GET(negotiated, false); } WebRTCDataChannelJS::WebRTCDataChannelJS() { diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp new file mode 100644 index 0000000000..17dafff93a --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -0,0 +1,384 @@ +/*************************************************************************/ +/* webrtc_multiplayer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "webrtc_multiplayer.h" + +#include "core/io/marshalls.h" +#include "core/os/os.h" + +void WebRTCMultiplayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility"), &WebRTCMultiplayer::initialize, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayer::add_peer, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayer::remove_peer); + ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayer::has_peer); + ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebRTCMultiplayer::get_peer); + ClassDB::bind_method(D_METHOD("get_peers"), &WebRTCMultiplayer::get_peers); + ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayer::close); +} + +void WebRTCMultiplayer::set_transfer_mode(TransferMode p_mode) { + transfer_mode = p_mode; +} + +NetworkedMultiplayerPeer::TransferMode WebRTCMultiplayer::get_transfer_mode() const { + return transfer_mode; +} + +void WebRTCMultiplayer::set_target_peer(int p_peer_id) { + target_peer = p_peer_id; +} + +/* Returns the ID of the NetworkedMultiplayerPeer who sent the most recent packet: */ +int WebRTCMultiplayer::get_packet_peer() const { + return next_packet_peer; +} + +bool WebRTCMultiplayer::is_server() const { + return unique_id == TARGET_PEER_SERVER; +} + +void WebRTCMultiplayer::poll() { + if (peer_map.size() == 0) + return; + + List<int> remove; + List<int> add; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + Ref<ConnectedPeer> peer = E->get(); + peer->connection->poll(); + // Check peer state + switch (peer->connection->get_connection_state()) { + case WebRTCPeerConnection::STATE_NEW: + case WebRTCPeerConnection::STATE_CONNECTING: + // Go to next peer, not ready yet. + continue; + case WebRTCPeerConnection::STATE_CONNECTED: + // Good to go, go ahead and check channel state. + break; + default: + // Peer is closed or in error state. Got to next peer. + remove.push_back(E->key()); + continue; + } + // Check channels state + int ready = 0; + for (List<Ref<WebRTCDataChannel> >::Element *C = peer->channels.front(); C && C->get().is_valid(); C = C->next()) { + Ref<WebRTCDataChannel> ch = C->get(); + switch (ch->get_ready_state()) { + case WebRTCDataChannel::STATE_CONNECTING: + continue; + case WebRTCDataChannel::STATE_OPEN: + ready++; + continue; + default: + // Channel was closed or in error state, remove peer id. + remove.push_back(E->key()); + } + // We got a closed channel break out, the peer will be removed. + break; + } + // This peer has newly connected, and all channels are now open. + if (ready == peer->channels.size() && !peer->connected) { + peer->connected = true; + add.push_back(E->key()); + } + } + // Remove disconnected peers + for (List<int>::Element *E = remove.front(); E; E = E->next()) { + remove_peer(E->get()); + if (next_packet_peer == E->get()) + next_packet_peer = 0; + } + // Signal newly connected peers + for (List<int>::Element *E = add.front(); E; E = E->next()) { + // Already connected to server: simply notify new peer. + // NOTE: Mesh is always connected. + if (connection_status == CONNECTION_CONNECTED) + emit_signal("peer_connected", E->get()); + + // Server emulation mode suppresses peer_conencted until server connects. + if (server_compat && E->get() == TARGET_PEER_SERVER) { + // Server connected. + connection_status = CONNECTION_CONNECTED; + emit_signal("peer_connected", TARGET_PEER_SERVER); + emit_signal("connection_succeeded"); + // Notify of all previously connected peers + for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { + if (F->key() != 1 && F->get()->connected) + emit_signal("peer_connected", F->key()); + } + break; // Because we already notified of all newly added peers. + } + } + // Fetch next packet + if (next_packet_peer == 0) + _find_next_peer(); +} + +void WebRTCMultiplayer::_find_next_peer() { + Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.find(next_packet_peer); + if (E) E = E->next(); + // After last. + while (E) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + if (F->get()->get_available_packet_count()) { + next_packet_peer = E->key(); + return; + } + } + E = E->next(); + } + E = peer_map.front(); + // Before last + while (E) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + if (F->get()->get_available_packet_count()) { + next_packet_peer = E->key(); + return; + } + } + if (E->key() == (int)next_packet_peer) + break; + E = E->next(); + } + // No packet found + next_packet_peer = 0; +} + +void WebRTCMultiplayer::set_refuse_new_connections(bool p_enable) { + refuse_connections = p_enable; +} + +bool WebRTCMultiplayer::is_refusing_new_connections() const { + return refuse_connections; +} + +NetworkedMultiplayerPeer::ConnectionStatus WebRTCMultiplayer::get_connection_status() const { + return connection_status; +} + +Error WebRTCMultiplayer::initialize(int p_self_id, bool p_server_compat) { + ERR_FAIL_COND_V(p_self_id < 0 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); + unique_id = p_self_id; + server_compat = p_server_compat; + + // Mesh and server are always connected + if (!server_compat || p_self_id == 1) + connection_status = CONNECTION_CONNECTED; + else + connection_status = CONNECTION_CONNECTING; + return OK; +} + +int WebRTCMultiplayer::get_unique_id() const { + ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, 1); + return unique_id; +} + +void WebRTCMultiplayer::_peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict) { + Array channels; + for (List<Ref<WebRTCDataChannel> >::Element *F = p_connected_peer->channels.front(); F; F = F->next()) { + channels.push_back(F->get()); + } + r_dict["connection"] = p_connected_peer->connection; + r_dict["connected"] = p_connected_peer->connected; + r_dict["channels"] = channels; +} + +bool WebRTCMultiplayer::has_peer(int p_peer_id) { + return peer_map.has(p_peer_id); +} + +Dictionary WebRTCMultiplayer::get_peer(int p_peer_id) { + ERR_FAIL_COND_V(!peer_map.has(p_peer_id), Dictionary()); + Dictionary out; + _peer_to_dict(peer_map[p_peer_id], out); + return out; +} + +Dictionary WebRTCMultiplayer::get_peers() { + Dictionary out; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + Dictionary d; + _peer_to_dict(E->get(), d); + out[E->key()] = d; + } + return out; +} + +Error WebRTCMultiplayer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime) { + ERR_FAIL_COND_V(p_peer_id < 0 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_unreliable_lifetime < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(refuse_connections, ERR_UNAUTHORIZED); + // Peer must be valid, and in new state (to create data channels) + ERR_FAIL_COND_V(!p_peer.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_peer->get_connection_state() != WebRTCPeerConnection::STATE_NEW, ERR_INVALID_PARAMETER); + + Ref<ConnectedPeer> peer = memnew(ConnectedPeer); + peer->connection = p_peer; + + // Initialize data channels + Dictionary cfg; + cfg["negotiated"] = true; + cfg["ordered"] = true; + + cfg["id"] = 1; + peer->channels[CH_RELIABLE] = p_peer->create_data_channel("reliable", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_RELIABLE].is_valid(), FAILED); + + cfg["id"] = 2; + cfg["maxPacketLifetime"] = p_unreliable_lifetime; + peer->channels[CH_ORDERED] = p_peer->create_data_channel("ordered", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_ORDERED].is_valid(), FAILED); + + cfg["id"] = 3; + cfg["ordered"] = false; + peer->channels[CH_UNRELIABLE] = p_peer->create_data_channel("unreliable", cfg); + ERR_FAIL_COND_V(!peer->channels[CH_UNRELIABLE].is_valid(), FAILED); + + peer_map[p_peer_id] = peer; // add the new peer connection to the peer_map + + return OK; +} + +void WebRTCMultiplayer::remove_peer(int p_peer_id) { + ERR_FAIL_COND(!peer_map.has(p_peer_id)); + Ref<ConnectedPeer> peer = peer_map[p_peer_id]; + peer_map.erase(p_peer_id); + if (peer->connected) { + peer->connected = false; + emit_signal("peer_disconnected", p_peer_id); + if (server_compat && p_peer_id == TARGET_PEER_SERVER) { + emit_signal("server_disconnected"); + connection_status = CONNECTION_DISCONNECTED; + } + } +} + +Error WebRTCMultiplayer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + // Peer not available + if (next_packet_peer == 0 || !peer_map.has(next_packet_peer)) { + _find_next_peer(); + ERR_FAIL_V(ERR_UNAVAILABLE); + } + for (List<Ref<WebRTCDataChannel> >::Element *E = peer_map[next_packet_peer]->channels.front(); E; E = E->next()) { + if (E->get()->get_available_packet_count()) { + Error err = E->get()->get_packet(r_buffer, r_buffer_size); + _find_next_peer(); + return err; + } + } + // Channels for that peer were empty. Bug? + _find_next_peer(); + ERR_FAIL_V(ERR_BUG); +} + +Error WebRTCMultiplayer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, ERR_UNCONFIGURED); + + int ch = CH_RELIABLE; + switch (transfer_mode) { + case TRANSFER_MODE_RELIABLE: + ch = CH_RELIABLE; + break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: + ch = CH_ORDERED; + break; + case TRANSFER_MODE_UNRELIABLE: + ch = CH_UNRELIABLE; + break; + } + + Map<int, Ref<ConnectedPeer> >::Element *E = NULL; + + if (target_peer > 0) { + + E = peer_map.find(target_peer); + if (!E) { + ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); + ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); + return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); + + } else { + int exclude = -target_peer; + + for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { + + // Exclude packet. If target_peer == 0 then don't exclude any packets + if (target_peer != 0 && F->key() == exclude) + continue; + + ERR_CONTINUE(F->value()->channels.size() <= ch || !F->value()->channels[ch].is_valid()); + F->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); + } + } + return OK; +} + +int WebRTCMultiplayer::get_available_packet_count() const { + if (next_packet_peer == 0) + return 0; // To be sure next call to get_packet works if size > 0 . + int size = 0; + for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { + for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { + size += F->get()->get_available_packet_count(); + } + } + return size; +} + +int WebRTCMultiplayer::get_max_packet_size() const { + return 1200; +} + +void WebRTCMultiplayer::close() { + peer_map.clear(); + unique_id = 0; + next_packet_peer = 0; + target_peer = 0; + connection_status = CONNECTION_DISCONNECTED; +} + +WebRTCMultiplayer::WebRTCMultiplayer() { + unique_id = 0; + next_packet_peer = 0; + target_peer = 0; + transfer_mode = TRANSFER_MODE_RELIABLE; + refuse_connections = false; + connection_status = CONNECTION_DISCONNECTED; + server_compat = false; +} + +WebRTCMultiplayer::~WebRTCMultiplayer() { + close(); +} diff --git a/modules/webrtc/webrtc_multiplayer.h b/modules/webrtc/webrtc_multiplayer.h new file mode 100644 index 0000000000..82bbfd4f68 --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.h @@ -0,0 +1,116 @@ +/*************************************************************************/ +/* webrtc_multiplayer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef WEBRTC_MULTIPLAYER_H +#define WEBRTC_MULTIPLAYER_H + +#include "core/io/networked_multiplayer_peer.h" +#include "webrtc_peer_connection.h" + +class WebRTCMultiplayer : public NetworkedMultiplayerPeer { + + GDCLASS(WebRTCMultiplayer, NetworkedMultiplayerPeer); + +protected: + static void _bind_methods(); + +private: + enum { + CH_RELIABLE = 0, + CH_ORDERED = 1, + CH_UNRELIABLE = 2, + CH_RESERVED_MAX = 3 + }; + + class ConnectedPeer : public Reference { + + public: + Ref<WebRTCPeerConnection> connection; + List<Ref<WebRTCDataChannel> > channels; + bool connected; + + ConnectedPeer() { + connected = false; + for (int i = 0; i < CH_RESERVED_MAX; i++) + channels.push_front(Ref<WebRTCDataChannel>()); + } + }; + + uint32_t unique_id; + int target_peer; + int client_count; + bool refuse_connections; + ConnectionStatus connection_status; + TransferMode transfer_mode; + int next_packet_peer; + bool server_compat; + + Map<int, Ref<ConnectedPeer> > peer_map; + + void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict); + void _find_next_peer(); + +public: + WebRTCMultiplayer(); + ~WebRTCMultiplayer(); + + Error initialize(int p_self_id, bool p_server_compat = false); + Error add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime = 1); + void remove_peer(int p_peer_id); + bool has_peer(int p_peer_id); + Dictionary get_peer(int p_peer_id); + Dictionary get_peers(); + void close(); + + // PacketPeer + Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); ///< buffer is GONE after next get_packet + Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + int get_available_packet_count() const; + int get_max_packet_size() const; + + // NetworkedMultiplayerPeer + void set_transfer_mode(TransferMode p_mode); + TransferMode get_transfer_mode() const; + void set_target_peer(int p_peer_id); + + int get_unique_id() const; + int get_packet_peer() const; + + bool is_server() const; + + void poll(); + + void set_refuse_new_connections(bool p_enable); + bool is_refusing_new_connections() const; + + ConnectionStatus get_connection_status() const; +}; + +#endif diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index a00f2173c0..c7d6919a2b 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -869,6 +869,11 @@ void ClippedCamera::clear_exceptions() { exclude.clear(); } +float ClippedCamera::get_clip_offset() const { + + return clip_offset; +} + void ClippedCamera::set_clip_to_areas(bool p_clip) { clip_to_areas = p_clip; @@ -912,6 +917,8 @@ void ClippedCamera::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clip_to_areas", "enable"), &ClippedCamera::set_clip_to_areas); ClassDB::bind_method(D_METHOD("is_clip_to_areas_enabled"), &ClippedCamera::is_clip_to_areas_enabled); + ClassDB::bind_method(D_METHOD("get_clip_offset"), &ClippedCamera::get_clip_offset); + ClassDB::bind_method(D_METHOD("set_clip_to_bodies", "enable"), &ClippedCamera::set_clip_to_bodies); ClassDB::bind_method(D_METHOD("is_clip_to_bodies_enabled"), &ClippedCamera::is_clip_to_bodies_enabled); diff --git a/scene/3d/camera.h b/scene/3d/camera.h index 1cd729199d..c0a4f77435 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -234,6 +234,8 @@ public: void remove_exception(const Object *p_object); void clear_exceptions(); + float get_clip_offset() const; + ClippedCamera(); ~ClippedCamera(); }; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 23998183b8..c70e58564f 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -34,10 +34,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8, const Variant &p_arg9, const Variant &p_arg10) { + // Add a new pending command and reference it pending_commands.push_back(PendingCommand()); PendingCommand &cmd = pending_commands.back()->get(); + // Update the command with the target key cmd.key = p_key; + + // Determine command argument count int &count = cmd.args; if (p_arg10.get_type() != Variant::NIL) count = 10; @@ -59,6 +63,9 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const count = 2; else if (p_arg1.get_type() != Variant::NIL) count = 1; + + // Add the specified arguments to the command + // TODO: Make this a switch statement? if (count > 0) cmd.arg[0] = p_arg1; if (count > 1) @@ -83,10 +90,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const void Tween::_process_pending_commands() { + // For each pending command... for (List<PendingCommand>::Element *E = pending_commands.front(); E; E = E->next()) { + // Get the command PendingCommand &cmd = E->get(); Variant::CallError err; + + // Grab all of the arguments for the command Variant *arg[10] = { &cmd.arg[0], &cmd.arg[1], @@ -99,16 +110,20 @@ void Tween::_process_pending_commands() { &cmd.arg[8], &cmd.arg[9], }; + + // Execute the command (and retrieve any errors) this->call(cmd.key, (const Variant **)arg, cmd.args, err); } + + // Clear the pending commands pending_commands.clear(); } bool Tween::_set(const StringName &p_name, const Variant &p_value) { + // Set the correct attribute based on the given name String name = p_name; - - if (name == "playback/speed" || name == "speed") { //bw compatibility + if (name == "playback/speed" || name == "speed") { // Backwards compatibility set_speed_scale(p_value); } else if (name == "playback/active") { @@ -122,69 +137,78 @@ bool Tween::_set(const StringName &p_name, const Variant &p_value) { bool Tween::_get(const StringName &p_name, Variant &r_ret) const { + // Get the correct attribute based on the given name String name = p_name; - - if (name == "playback/speed") { //bw compatibility - + if (name == "playback/speed") { // Backwards compatibility r_ret = speed_scale; - } else if (name == "playback/active") { + } else if (name == "playback/active") { r_ret = is_active(); - } else if (name == "playback/repeat") { + } else if (name == "playback/repeat") { r_ret = is_repeat(); } - return true; } void Tween::_get_property_list(List<PropertyInfo> *p_list) const { - + // Add the property info for the Tween object p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::REAL, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01")); } void Tween::_notification(int p_what) { - + // What notification did we receive? switch (p_what) { case NOTIFICATION_ENTER_TREE: { - + // Are we not already active? if (!is_active()) { - //make sure that a previous process state was not saved - //only process if "processing" is set + // Make sure that a previous process state was not saved + // Only process if "processing" is set set_physics_process_internal(false); set_process_internal(false); } } break; - case NOTIFICATION_READY: { + case NOTIFICATION_READY: { + // Do nothing } break; + case NOTIFICATION_INTERNAL_PROCESS: { + // Are we processing during physics time? if (tween_process_mode == TWEEN_PROCESS_PHYSICS) + // Do nothing since we aren't aligned with physics when we should be break; + // Should we update? if (is_active()) + // Update the tweens _tween_process(get_process_delta_time()); } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Are we processing during 'regular' time? if (tween_process_mode == TWEEN_PROCESS_IDLE) + // Do nothing since we whould only process during idle time break; + // Should we update? if (is_active()) + // Update the tweens _tween_process(get_physics_process_delta_time()); } break; - case NOTIFICATION_EXIT_TREE: { + case NOTIFICATION_EXIT_TREE: { + // We've left the tree. Stop all tweens stop_all(); } break; } } void Tween::_bind_methods() { - + // Bind getters and setters ClassDB::bind_method(D_METHOD("is_active"), &Tween::is_active); ClassDB::bind_method(D_METHOD("set_active", "active"), &Tween::set_active); @@ -197,6 +221,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Tween::set_tween_process_mode); ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Tween::get_tween_process_mode); + // Bind the various Tween control methods ClassDB::bind_method(D_METHOD("start"), &Tween::start); ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Tween::reset, DEFVAL("")); ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); @@ -211,6 +236,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("tell"), &Tween::tell); ClassDB::bind_method(D_METHOD("get_runtime"), &Tween::get_runtime); + // Bind interpolation and follow methods ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_property, DEFVAL(0)); ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_method, DEFVAL(0)); ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5"), &Tween::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant())); @@ -220,18 +246,22 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_property, DEFVAL(0)); ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_method, DEFVAL(0)); + // Add the Tween signals ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::REAL, "elapsed"), PropertyInfo(Variant::OBJECT, "value"))); ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); ADD_SIGNAL(MethodInfo("tween_all_completed")); + // Add the properties and tie them to the getters and setters ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); + // Bind Idle vs Physics process BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE); + // Bind the Transition type constants BIND_ENUM_CONSTANT(TRANS_LINEAR); BIND_ENUM_CONSTANT(TRANS_SINE); BIND_ENUM_CONSTANT(TRANS_QUINT); @@ -244,6 +274,7 @@ void Tween::_bind_methods() { BIND_ENUM_CONSTANT(TRANS_BOUNCE); BIND_ENUM_CONSTANT(TRANS_BACK); + // Bind the easing constants BIND_ENUM_CONSTANT(EASE_IN); BIND_ENUM_CONSTANT(EASE_OUT); BIND_ENUM_CONSTANT(EASE_IN_OUT); @@ -252,27 +283,30 @@ void Tween::_bind_methods() { Variant &Tween::_get_initial_val(InterpolateData &p_data) { + // What type of data are we interpolating? switch (p_data.type) { case INTER_PROPERTY: case INTER_METHOD: case FOLLOW_PROPERTY: case FOLLOW_METHOD: + // Simply use the given initial value return p_data.initial_val; case TARGETING_PROPERTY: case TARGETING_METHOD: { - + // Get the object that is being targeted Object *object = ObjectDB::get_instance(p_data.target_id); ERR_FAIL_COND_V(object == NULL, p_data.initial_val); + // Are we targeting a property or a method? static Variant initial_val; if (p_data.type == TARGETING_PROPERTY) { - + // Get the property from the target object bool valid = false; initial_val = object->get_indexed(p_data.target_key, &valid); ERR_FAIL_COND_V(!valid, p_data.initial_val); } else { - + // Call the method and get the initial value from it Variant::CallError error; initial_val = object->call(p_data.target_key[0], NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val); @@ -281,64 +315,75 @@ Variant &Tween::_get_initial_val(InterpolateData &p_data) { } case INTER_CALLBACK: + // Callback does not have a special initial value break; } + // If we've made it here, just return the delta value as the initial value return p_data.delta_val; } Variant &Tween::_get_delta_val(InterpolateData &p_data) { + // What kind of data are we interpolating? switch (p_data.type) { case INTER_PROPERTY: case INTER_METHOD: + // Simply return the given delta value return p_data.delta_val; case FOLLOW_PROPERTY: case FOLLOW_METHOD: { - + // We're following an object, so grab that instance Object *target = ObjectDB::get_instance(p_data.target_id); ERR_FAIL_COND_V(target == NULL, p_data.initial_val); + // We want to figure out the final value Variant final_val; - if (p_data.type == FOLLOW_PROPERTY) { - + // Read the property as-is bool valid = false; final_val = target->get_indexed(p_data.target_key, &valid); ERR_FAIL_COND_V(!valid, p_data.initial_val); } else { - + // We're looking at a method. Call the method on the target object Variant::CallError error; final_val = target->call(p_data.target_key[0], NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val); } - // convert INT to REAL is better for interpolaters + // If we're looking at an INT value, instead convert it to a REAL + // This is better for interpolation if (final_val.get_type() == Variant::INT) final_val = final_val.operator real_t(); + + // Calculate the delta based on the initial value and the final value _calc_delta_val(p_data.initial_val, final_val, p_data.delta_val); return p_data.delta_val; } case TARGETING_PROPERTY: case TARGETING_METHOD: { - + // Grab the initial value from the data to calculate delta Variant initial_val = _get_initial_val(p_data); - // convert INT to REAL is better for interpolaters + + // If we're looking at an INT value, instead convert it to a REAL + // This is better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); - //_calc_delta_val(p_data.initial_val, p_data.final_val, p_data.delta_val); + // Calculate the delta based on the initial value and the final value _calc_delta_val(initial_val, p_data.final_val, p_data.delta_val); return p_data.delta_val; } case INTER_CALLBACK: + // Callbacks have no special delta break; } + // If we've made it here, use the initial value as the delta return p_data.initial_val; } Variant Tween::_run_equation(InterpolateData &p_data) { - + // Get the initial and delta values from the data Variant &initial_val = _get_initial_val(p_data); Variant &delta_val = _get_delta_val(p_data); Variant result; @@ -346,48 +391,59 @@ Variant Tween::_run_equation(InterpolateData &p_data) { #define APPLY_EQUATION(element) \ r.element = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, i.element, d.element, p_data.duration); + // What type of data are we interpolating? switch (initial_val.get_type()) { case Variant::BOOL: + // Run the boolean specific equation (checking if it is at least 0.5) result = (_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, initial_val, delta_val, p_data.duration)) >= 0.5; break; case Variant::INT: + // Run the integer specific equation result = (int)_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int)initial_val, (int)delta_val, p_data.duration); break; case Variant::REAL: + // Run the REAL specific equation result = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (real_t)initial_val, (real_t)delta_val, p_data.duration); break; case Variant::VECTOR2: { + // Get vectors for initial and delta values Vector2 i = initial_val; Vector2 d = delta_val; Vector2 r; + // Execute the equation and mutate the r vector + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); - result = r; } break; case Variant::VECTOR3: { + // Get vectors for initial and delta values Vector3 i = initial_val; Vector3 d = delta_val; Vector3 r; + // Execute the equation and mutate the r vector + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); APPLY_EQUATION(z); - result = r; } break; case Variant::BASIS: { + // Get the basis for initial and delta values Basis i = initial_val; Basis d = delta_val; Basis r; + // Execute the equation on all the basis and mutate the r basis + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(elements[0][0]); APPLY_EQUATION(elements[0][1]); APPLY_EQUATION(elements[0][2]); @@ -397,55 +453,63 @@ Variant Tween::_run_equation(InterpolateData &p_data) { APPLY_EQUATION(elements[2][0]); APPLY_EQUATION(elements[2][1]); APPLY_EQUATION(elements[2][2]); - result = r; } break; case Variant::TRANSFORM2D: { + // Get the transforms for initial and delta values Transform2D i = initial_val; Transform2D d = delta_val; Transform2D r; + // Execute the equation on the transforms and mutate the r transform + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(elements[0][0]); APPLY_EQUATION(elements[0][1]); APPLY_EQUATION(elements[1][0]); APPLY_EQUATION(elements[1][1]); APPLY_EQUATION(elements[2][0]); APPLY_EQUATION(elements[2][1]); - result = r; } break; case Variant::QUAT: { + // Get the quaternian for the initial and delta values Quat i = initial_val; Quat d = delta_val; Quat r; + // Execute the equation on the quaternian values and mutate the r quaternian + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); APPLY_EQUATION(z); APPLY_EQUATION(w); - result = r; } break; case Variant::AABB: { + // Get the AABB's for the initial and delta values AABB i = initial_val; AABB d = delta_val; AABB r; + // Execute the equation for the position and size of the AABB's and mutate the r AABB + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(position.x); APPLY_EQUATION(position.y); APPLY_EQUATION(position.z); APPLY_EQUATION(size.x); APPLY_EQUATION(size.y); APPLY_EQUATION(size.z); - result = r; } break; case Variant::TRANSFORM: { + // Get the transforms for the initial and delta values Transform i = initial_val; Transform d = delta_val; Transform r; + // Execute the equation for each of the transforms and their origin and mutate the r transform + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(basis.elements[0][0]); APPLY_EQUATION(basis.elements[0][1]); APPLY_EQUATION(basis.elements[0][2]); @@ -458,40 +522,45 @@ Variant Tween::_run_equation(InterpolateData &p_data) { APPLY_EQUATION(origin.x); APPLY_EQUATION(origin.y); APPLY_EQUATION(origin.z); - result = r; } break; case Variant::COLOR: { + // Get the Color for initial and delta value Color i = initial_val; Color d = delta_val; Color r; + // Apply the equation on the Color RGBA, and mutate the r color + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(r); APPLY_EQUATION(g); APPLY_EQUATION(b); APPLY_EQUATION(a); - result = r; } break; default: { + // If unknown, just return the initial value result = initial_val; } break; }; #undef APPLY_EQUATION - + // Return the result that was computed return result; } bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) { + // Get the object we want to apply the new value to Object *object = ObjectDB::get_instance(p_data.id); ERR_FAIL_COND_V(object == NULL, false); + // What kind of data are we mutating? switch (p_data.type) { case INTER_PROPERTY: case FOLLOW_PROPERTY: case TARGETING_PROPERTY: { + // Simply set the property on the object bool valid = false; object->set_indexed(p_data.key, value, &valid); return valid; @@ -500,85 +569,112 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) { case INTER_METHOD: case FOLLOW_METHOD: case TARGETING_METHOD: { + // We want to call the method on the target object Variant::CallError error; + + // Do we have a non-nil value passed in? if (value.get_type() != Variant::NIL) { + // Pass it as an argument to the function call Variant *arg[1] = { &value }; object->call(p_data.key[0], (const Variant **)arg, 1, error); } else { + // Don't pass any argument object->call(p_data.key[0], NULL, 0, error); } + // Did we get an error from the function call? if (error.error == Variant::CallError::CALL_OK) return true; return false; } case INTER_CALLBACK: + // Nothing to apply for a callback break; }; + // No issues found! return true; } void Tween::_tween_process(float p_delta) { - + // Process all of the pending commands _process_pending_commands(); + // If the scale is 0, make no progress on the tweens if (speed_scale == 0) return; - p_delta *= speed_scale; + // Update the delta and whether we are pending an update + p_delta *= speed_scale; pending_update++; - // if repeat and all interpolates was finished then reset all interpolates - bool all_finished = true; - if (repeat) { + // Are we repeating the interpolations? + if (repeat) { + // For each interpolation... + bool repeats_finished = true; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the data from it InterpolateData &data = E->get(); + // Is not finished? if (!data.finish) { - all_finished = false; + // We aren't finished yet, no need to check the rest + repeats_finished = false; break; } } - if (all_finished) + // If we are all finished, we can reset all of the tweens + if (repeats_finished) reset_all(); } - all_finished = true; + // Are all of the tweens complete? + bool all_finished = true; + + // For each tween we wish to interpolate... for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the data from it InterpolateData &data = E->get(); + + // Track if we hit one that isn't finished yet all_finished = all_finished && data.finish; + // Is the data not active or already finished? No need to go any further if (!data.active || data.finish) continue; + // Get the target object for this interpolation Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + // Are we still delaying this tween? bool prev_delaying = data.elapsed <= data.delay; data.elapsed += p_delta; if (data.elapsed < data.delay) continue; else if (prev_delaying) { - + // We can apply the tween's value to the data and emit that the tween has started _apply_tween_value(data, data.initial_val); emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false)); } + // Are we at the end of the tween? if (data.elapsed > (data.delay + data.duration)) { - + // Set the elapsed time to the end and mark this one as finished data.elapsed = data.delay + data.duration; data.finish = true; } + // Are we interpolating a callback? if (data.type == INTER_CALLBACK) { + // Is the tween completed? if (data.finish) { + // Are we calling this callback deferred or immediately? if (data.call_deferred) { - + // Run the deferred function callback, applying the correct number of arguments switch (data.args) { case 0: object->call_deferred(data.key[0]); @@ -600,6 +696,7 @@ void Tween::_tween_process(float p_delta) { break; } } else { + // Call the function directly with the arguments Variant::CallError error; Variant *arg[5] = { &data.arg[0], @@ -612,23 +709,35 @@ void Tween::_tween_process(float p_delta) { } } } else { + // We can apply the value directly Variant result = _run_equation(data); _apply_tween_value(data, result); + + // Emit that the tween has taken a step emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result); } + // Is the tween now finished? if (data.finish) { + // Set it to the final value directly _apply_tween_value(data, data.final_val); + + // Mark the tween as completed and emit the signal data.elapsed = 0; emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false)); - // not repeat mode, remove completed action + + // If we are not repeating the tween, remove it if (!repeat) call_deferred("_remove_by_uid", data.uid); - } else if (!repeat) + } else if (!repeat) { + // Check whether all tweens are finished all_finished = all_finished && data.finish; + } } + // One less update left to go pending_update--; + // If all tweens are completed, we no longer need to be active if (all_finished) { set_active(false); emit_signal("tween_all_completed"); @@ -636,76 +745,75 @@ void Tween::_tween_process(float p_delta) { } void Tween::set_tween_process_mode(TweenProcessMode p_mode) { - tween_process_mode = p_mode; } Tween::TweenProcessMode Tween::get_tween_process_mode() const { - return tween_process_mode; } bool Tween::is_active() const { - return is_processing_internal() || is_physics_processing_internal(); } void Tween::set_active(bool p_active) { - + // Do nothing if it's the same active mode that we currently are if (is_active() == p_active) return; + // Depending on physics or idle, set processing switch (tween_process_mode) { - case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break; case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break; } } bool Tween::is_repeat() const { - return repeat; } void Tween::set_repeat(bool p_repeat) { - repeat = p_repeat; } void Tween::set_speed_scale(float p_speed) { - speed_scale = p_speed; } float Tween::get_speed_scale() const { - return speed_scale; } bool Tween::start() { + // Are there any pending updates? if (pending_update != 0) { + // Start the tweens after deferring call_deferred("start"); return true; } + // We want to be activated set_active(true); return true; } bool Tween::reset(Object *p_object, StringName p_key) { - + // Find all interpolations that use the same object and target string pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + // Do we have the correct object and key? if (object == p_object && (data.concatenated_key == p_key || p_key == "")) { - + // Reset the tween to the initial state data.elapsed = 0; data.finish = false; + + // Also apply the initial state if there isn't a delay if (data.delay == 0) _apply_tween_value(data, data.initial_val); } @@ -715,13 +823,15 @@ bool Tween::reset(Object *p_object, StringName p_key) { } bool Tween::reset_all() { - + // Go through all interpolations pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target data and set it back to the initial state InterpolateData &data = E->get(); data.elapsed = 0; data.finish = false; + + // If there isn't a delay, apply the value to the object if (data.delay == 0) _apply_tween_value(data, data.initial_val); } @@ -730,15 +840,19 @@ bool Tween::reset_all() { } bool Tween::stop(Object *p_object, StringName p_key) { - + // Find the tween that has the given target object and string key pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the object the tween is targeting InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // Is this the correct object and does it have the given key? if (object == p_object && (data.concatenated_key == p_key || p_key == "")) + // Disable the tween data.active = false; } pending_update--; @@ -746,12 +860,13 @@ bool Tween::stop(Object *p_object, StringName p_key) { } bool Tween::stop_all() { - + // We no longer need to be active since all tweens have been stopped set_active(false); + // For each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Simply set it inactive InterpolateData &data = E->get(); data.active = false; } @@ -760,16 +875,20 @@ bool Tween::stop_all() { } bool Tween::resume(Object *p_object, StringName p_key) { - + // We need to be activated + // TODO: What if no tween is found?? set_active(true); + // Find the tween that uses the given target object and string key pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Grab the object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // If the object and string key match, activate it if (object == p_object && (data.concatenated_key == p_key || p_key == "")) data.active = true; } @@ -778,12 +897,14 @@ bool Tween::resume(Object *p_object, StringName p_key) { } bool Tween::resume_all() { - + // Set ourselves active so we can process tweens + // TODO: What if there are no tweens? We get set to active for no reason! set_active(true); + // For each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Simply grab it and set it to active InterpolateData &data = E->get(); data.active = true; } @@ -792,35 +913,46 @@ bool Tween::resume_all() { } bool Tween::remove(Object *p_object, StringName p_key) { + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("remove", p_object, p_key); return true; } + + // For each interpolation... List<List<InterpolateData>::Element *> for_removal; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // If the target object and string key match, queue it for removal if (object == p_object && (data.concatenated_key == p_key || p_key == "")) { for_removal.push_back(E); } } + + // For each interpolation we wish to remove... for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) { + // Erase it interpolates.erase(E->get()); } return true; } void Tween::_remove_by_uid(int uid) { + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("_remove_by_uid", uid); return; } + // Find the interpolation that matches the given UID for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { if (uid == E->get().uid) { + // It matches, erase it and stop looking E->erase(); break; } @@ -829,49 +961,61 @@ void Tween::_remove_by_uid(int uid) { void Tween::_push_interpolate_data(InterpolateData &p_data) { pending_update++; + + // Add the new interpolation p_data.uid = ++uid; interpolates.push_back(p_data); + pending_update--; } bool Tween::remove_all() { - + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("remove_all"); return true; } + // We no longer need to be active set_active(false); + + // Clear out all interpolations and reset the uid interpolates.clear(); uid = 0; + return true; } bool Tween::seek(real_t p_time) { - + // Go through each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target data InterpolateData &data = E->get(); + // Update the elapsed data to be set to the target time data.elapsed = p_time; - if (data.elapsed < data.delay) { + // Are we at the end? + if (data.elapsed < data.delay) { + // There is still time left to go data.finish = false; continue; } else if (data.elapsed >= (data.delay + data.duration)) { - - data.finish = true; + // We are past the end of it, set the elapsed time to the end and mark as finished data.elapsed = (data.delay + data.duration); + data.finish = true; } else { + // We are not finished with this interpolation yet data.finish = false; } + // If we are a callback, do nothing special if (data.type == INTER_CALLBACK) { continue; } + // Run the equation on the data and apply the value Variant result = _run_equation(data); - _apply_tween_value(data, result); } pending_update--; @@ -879,13 +1023,16 @@ bool Tween::seek(real_t p_time) { } real_t Tween::tell() const { - + // We want to grab the position of the furthest along tween pending_update++; real_t pos = 0; - for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // For each interpolation... + for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the data and figure out if it's position is further along than the previous ones const InterpolateData &data = E->get(); if (data.elapsed > pos) + // Save it if so pos = data.elapsed; } pending_update--; @@ -893,55 +1040,63 @@ real_t Tween::tell() const { } real_t Tween::get_runtime() const { - + // If the tween isn't moving, it'll last forever if (speed_scale == 0) { return INFINITY; } pending_update++; + + // For each interpolation... real_t runtime = 0; for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the tween data and see if it's runtime is greater than the previous tweens const InterpolateData &data = E->get(); real_t t = data.delay + data.duration; if (t > runtime) + // This is the longest running tween runtime = t; } pending_update--; + // Adjust the runtime for the current speed scale return runtime / speed_scale; } bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) { + // Get the initial, final, and delta values const Variant &initial_val = p_initial_val; const Variant &final_val = p_final_val; Variant &delta_val = p_delta_val; + // What kind of data are we interpolating? switch (initial_val.get_type()) { case Variant::BOOL: - //delta_val = p_final_val; - delta_val = (int)p_final_val - (int)p_initial_val; - break; - + // We'll treat booleans just like integers case Variant::INT: + // Compute the integer delta delta_val = (int)final_val - (int)initial_val; break; case Variant::REAL: + // Convert to REAL and find the delta delta_val = (real_t)final_val - (real_t)initial_val; break; case Variant::VECTOR2: + // Convert to Vectors and find the delta delta_val = final_val.operator Vector2() - initial_val.operator Vector2(); break; case Variant::VECTOR3: + // Convert to Vectors and find the delta delta_val = final_val.operator Vector3() - initial_val.operator Vector3(); break; case Variant::BASIS: { + // Build a new basis which is the delta between the initial and final values Basis i = initial_val; Basis f = final_val; delta_val = Basis(f.elements[0][0] - i.elements[0][0], @@ -956,6 +1111,7 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final } break; case Variant::TRANSFORM2D: { + // Build a new transform which is the difference between the initial and final values Transform2D i = initial_val; Transform2D f = final_val; Transform2D d = Transform2D(); @@ -967,15 +1123,21 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final d[2][1] = f.elements[2][1] - i.elements[2][1]; delta_val = d; } break; + case Variant::QUAT: + // Convert to quaternianls and find the delta delta_val = final_val.operator Quat() - initial_val.operator Quat(); break; + case Variant::AABB: { + // Build a new AABB and use the new position and sizes to make a delta AABB i = initial_val; AABB f = final_val; delta_val = AABB(f.position - i.position, f.size - i.size); } break; + case Variant::TRANSFORM: { + // Build a new transform which is the difference between the initial and final values Transform i = initial_val; Transform f = final_val; Transform d; @@ -994,124 +1156,157 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final delta_val = d; } break; + case Variant::COLOR: { + // Make a new color which is the difference between each the color's RGBA attributes Color i = initial_val; Color f = final_val; delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a); } break; default: + // TODO: Should move away from a 'magic string'? ERR_PRINT("Invalid param type, except(int/real/vector2/vector/matrix/matrix32/quat/aabb/transform/color)"); return false; }; return true; } -bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - if (pending_update != 0) { - _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); - return true; - } - p_property = p_property.get_as_property_path(); - - if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - - // convert INT to REAL is better for interpolaters - if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); - if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - - ERR_FAIL_COND_V(p_object == NULL, false); - ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); - ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); - ERR_FAIL_COND_V(p_duration <= 0, false); - ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); - ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); - ERR_FAIL_COND_V(p_delay < 0, false); +bool Tween::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - bool prop_valid = false; - p_object->get_indexed(p_property.get_subnames(), &prop_valid); - ERR_FAIL_COND_V(!prop_valid, false); + // TODO: Add initialization+implementation for remaining interpolation types + // TODO: Fix this method's organization to take advantage of the type + // Make a new interpolation data InterpolateData data; data.active = true; - data.type = INTER_PROPERTY; + data.type = p_interpolation_type; data.finish = false; data.elapsed = 0; + // Validate and apply interpolation data + + // Give it the object + ERR_EXPLAIN("Invalid object provided to Tween!"); + ERR_FAIL_COND_V(p_object == NULL, false); // Is the object real + ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); // Is the object a valid instance? data.id = p_object->get_instance_id(); - data.key = p_property.get_subnames(); - data.concatenated_key = p_property.get_concatenated_subnames(); + + // Validate the initial and final values + ERR_EXPLAIN("Initial value type does not match final value type!"); // TODO: Print both types to make debugging easier + ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); // Do the initial and final value types match? data.initial_val = p_initial_val; data.final_val = p_final_val; + + // Check the Duration + ERR_EXPLAIN("Only non-negative duration values allowed in Tweens!"); + ERR_FAIL_COND_V(p_duration < 0, false); // Is the tween duration non-negative data.duration = p_duration; + + // Tween Delay + ERR_EXPLAIN("Only non-negative delay values allowed in Tweens!"); + ERR_FAIL_COND_V(p_delay < 0, false); // Is the delay non-negative? + data.delay = p_delay; + + // Transition type + ERR_EXPLAIN("Invalid transition type provided to Tween"); + ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); // Is the transition type valid data.trans_type = p_trans_type; + + // Easing type + ERR_EXPLAIN("Invalid easing type provided to Tween"); + ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); // Is the easing type valid data.ease_type = p_ease_type; - data.delay = p_delay; + // Is the property defined? + if (p_property) { + // Check that the object actually contains the given property + bool prop_valid = false; + p_object->get_indexed(p_property->get_subnames(), &prop_valid); + ERR_EXPLAIN("Tween target object has no property named: " + p_property->get_concatenated_subnames()); + ERR_FAIL_COND_V(!prop_valid, false); + + data.key = p_property->get_subnames(); + data.concatenated_key = p_property->get_concatenated_subnames(); + } + + // Is the method defined? + if (p_method) { + // Does the object even have the requested method? + ERR_EXPLAIN("Tween target object has no method named: " + *p_method); // TODO: Fix this error message + ERR_FAIL_COND_V(!p_object->has_method(*p_method), false); + + data.key.push_back(*p_method); + data.concatenated_key = *p_method; + } + + // Is there not a valid delta? if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add this interpolation to the total _push_interpolate_data(data); return true; } -bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are busy updating, call this function again later if (pending_update != 0) { - _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters - if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); - if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - ERR_FAIL_COND_V(p_object == NULL, false); - ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); - ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); - ERR_FAIL_COND_V(p_duration <= 0, false); - ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); - ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); - ERR_FAIL_COND_V(p_delay < 0, false); + // Get the property from the node path + p_property = p_property.get_as_property_path(); - ERR_EXPLAIN("Object has no method named: %s" + p_method); - ERR_FAIL_COND_V(!p_object->has_method(p_method), false); + // If no initial value given, grab the initial value from the object + // TODO: Is this documented? This is very useful and removes a lot of clutter from tweens! + if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - InterpolateData data; - data.active = true; - data.type = INTER_METHOD; - data.finish = false; - data.elapsed = 0; + // Convert any integers into REALs as they are better for interpolation + if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - data.id = p_object->get_instance_id(); - data.key.push_back(p_method); - data.concatenated_key = p_method; - data.initial_val = p_initial_val; - data.final_val = p_final_val; - data.duration = p_duration; - data.trans_type = p_trans_type; - data.ease_type = p_ease_type; - data.delay = p_delay; + // Build the interpolation data + bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, NULL, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return result; +} - if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) - return false; +bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are busy updating, call this function again later + if (pending_update != 0) { + _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return true; + } - _push_interpolate_data(data); - return true; + // Convert any integers into REALs as they are better for interpolation + if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + + // Build the interpolation data + bool result = _build_interpolation(INTER_METHOD, p_object, NULL, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return result; } bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); return true; } + // Check that the target object is valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + + // Duration cannot be negative ERR_FAIL_COND_V(p_duration < 0, false); + // Check whether the object even has the callback ERR_EXPLAIN("Object has no callback named: %s" + p_callback); ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); + // Build a new InterpolationData InterpolateData data; data.active = true; data.type = INTER_CALLBACK; @@ -1119,12 +1314,14 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c data.call_deferred = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_callback); data.concatenated_key = p_callback; data.duration = p_duration; data.delay = 0; + // Add arguments to the interpolation int args = 0; if (p_arg5.get_type() != Variant::NIL) args = 5; @@ -1146,23 +1343,30 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c data.arg[3] = p_arg4; data.arg[4] = p_arg5; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); return true; } + + // Check that the target object is valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + + // No negative durations allowed ERR_FAIL_COND_V(p_duration < 0, false); + // Confirm the callback exists on the object ERR_EXPLAIN("Object has no callback named: %s" + p_callback); ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); + // Create a new InterpolateData for the callback InterpolateData data; data.active = true; data.type = INTER_CALLBACK; @@ -1170,12 +1374,14 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S data.call_deferred = true; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_callback); data.concatenated_key = p_callback; data.duration = p_duration; data.delay = 0; + // Collect arguments for the callback int args = 0; if (p_arg5.get_type() != Variant::NIL) args = 5; @@ -1197,32 +1403,46 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S data.arg[3] = p_arg4; data.arg[4] = p_arg5; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay); return true; } + + // Get the two properties from their paths p_property = p_property.get_as_property_path(); p_target_property = p_target_property.get_as_property_path(); + // If no initial value is given, grab it from the source object + // TODO: Is this documented? It's really helpful for decluttering tweens if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they are better for interpolation if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + // Confirm the source and target objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_target == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Confirm the source and target objects have the desired properties bool prop_valid = false; p_object->get_indexed(p_property.get_subnames(), &prop_valid); ERR_FAIL_COND_V(!prop_valid, false); @@ -1231,16 +1451,20 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid); ERR_FAIL_COND_V(!target_prop_valid, false); - // convert INT to REAL is better for interpolaters + // Convert target INT to REAL since it is better for interpolation if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t(); + + // Verify that the target value and initial value are the same type ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); + // Create a new InterpolateData InterpolateData data; data.active = true; data.type = FOLLOW_PROPERTY; data.finish = false; data.elapsed = 0; + // Give the InterpolateData it's configuration data.id = p_object->get_instance_id(); data.key = p_property.get_subnames(); data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1252,46 +1476,59 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini data.ease_type = p_ease_type; data.delay = p_delay; + // Add the interpolation _push_interpolate_data(data); return true; } bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they are better for interpolation if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + // Verify the source and target objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_target == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure that the transition and ease types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Confirm both objects have the target methods ERR_EXPLAIN("Object has no method named: %s" + p_method); ERR_FAIL_COND_V(!p_object->has_method(p_method), false); ERR_EXPLAIN("Target has no method named: %s" + p_target_method); ERR_FAIL_COND_V(!p_target->has_method(p_target_method), false); + // Call the method to get the target value Variant::CallError error; Variant target_val = p_target->call(p_target_method, NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); - // convert INT to REAL is better for interpolaters + // Convert target INT values to REAL as they are better for interpolation if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t(); ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); + // Make the new InterpolateData for the method follow InterpolateData data; data.active = true; data.type = FOLLOW_METHOD; data.finish = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_method); data.concatenated_key = p_method; @@ -1303,31 +1540,41 @@ bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initi data.ease_type = p_ease_type; data.delay = p_delay; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } + // Grab the target property and the target property p_property = p_property.get_as_property_path(); p_initial_property = p_initial_property.get_as_property_path(); - // convert INT to REAL is better for interpolaters + // Convert the initial INT values to REAL as they are better for Interpolation if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + // Verify both objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_initial == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Ensure the initial and target properties exist on their objects bool prop_valid = false; p_object->get_indexed(p_property.get_subnames(), &prop_valid); ERR_FAIL_COND_V(!prop_valid, false); @@ -1336,16 +1583,18 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_ Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid); ERR_FAIL_COND_V(!initial_prop_valid, false); - // convert INT to REAL is better for interpolaters + // Convert the initial INT value to REAL as it is better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); + // Build the InterpolateData object InterpolateData data; data.active = true; data.type = TARGETING_PROPERTY; data.finish = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key = p_property.get_subnames(); data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1358,49 +1607,64 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_ data.ease_type = p_ease_type; data.delay = p_delay; + // Ensure there is a valid delta if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add the interpolation _push_interpolate_data(data); return true; } bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters + + // Convert final INT values to REAL as they are better for interpolation if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + // Make sure the given objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_initial == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Make sure both objects have the given method ERR_EXPLAIN("Object has no method named: %s" + p_method); ERR_FAIL_COND_V(!p_object->has_method(p_method), false); ERR_EXPLAIN("Initial Object has no method named: %s" + p_initial_method); ERR_FAIL_COND_V(!p_initial->has_method(p_initial_method), false); + // Call the method to get the initial value Variant::CallError error; Variant initial_val = p_initial->call(p_initial_method, NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they aer better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); + // Build the new InterpolateData object InterpolateData data; data.active = true; data.type = TARGETING_METHOD; data.finish = false; data.elapsed = 0; + // Configure the data data.id = p_object->get_instance_id(); data.key.push_back(p_method); data.concatenated_key = p_method; @@ -1413,16 +1677,17 @@ bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_in data.ease_type = p_ease_type; data.delay = p_delay; + // Ensure there is a valid delta if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add the interpolation _push_interpolate_data(data); return true; } Tween::Tween() { - - //String autoplay; + // Initialize tween attributes tween_process_mode = TWEEN_PROCESS_IDLE; repeat = false; speed_scale = 1; diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 6fe3bffdbe..64ce099ecd 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -135,6 +135,7 @@ private: void _tween_process(float p_delta); void _remove_by_uid(int uid); void _push_interpolate_data(InterpolateData &p_data); + bool _build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay); protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index bdb1342019..bc8dcf0e82 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -48,8 +48,9 @@ void FileDialog::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - refresh->set_icon(get_icon("reload")); dir_up->set_icon(get_icon("parent_folder")); + refresh->set_icon(get_icon("reload")); + show_hidden->set_icon(get_icon("toggle_hidden")); } if (p_what == NOTIFICATION_POPUP_HIDE) { @@ -393,20 +394,19 @@ void FileDialog::update_file_list() { List<String> files; List<String> dirs; - bool isdir; - bool ishidden; - bool show_hidden = show_hidden_files; + bool is_dir; + bool is_hidden; String item; - while ((item = dir_access->get_next(&isdir)) != "") { + while ((item = dir_access->get_next(&is_dir)) != "") { if (item == "." || item == "..") continue; - ishidden = dir_access->current_is_hidden(); + is_hidden = dir_access->current_is_hidden(); - if (show_hidden || !ishidden) { - if (!isdir) + if (show_hidden_files || !is_hidden) { + if (!is_dir) files.push_back(item); else dirs.push_back(item); @@ -873,6 +873,13 @@ FileDialog::FileDialog() { refresh->connect("pressed", this, "_update_file_list"); hbc->add_child(refresh); + show_hidden = memnew(ToolButton); + show_hidden->set_toggle_mode(true); + show_hidden->set_pressed(is_showing_hidden_files()); + show_hidden->set_tooltip(RTR("Toggle Hidden Files")); + show_hidden->connect("toggled", this, "set_show_hidden_files"); + hbc->add_child(show_hidden); + drives = memnew(OptionButton); hbc->add_child(drives); drives->connect("item_selected", this, "_select_drive"); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 85edac0b12..9f7ea1a2f2 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -90,6 +90,7 @@ private: ToolButton *dir_up; ToolButton *refresh; + ToolButton *show_hidden; Vector<String> filters; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index e8359e3dfe..2c5dfc375c 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -541,8 +541,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // File Dialog - theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png)); theme->set_icon("parent_folder", "FileDialog", make_icon(icon_parent_folder_png)); + theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png)); + theme->set_icon("toggle_hidden", "FileDialog", make_icon(icon_visibility_png)); // Popup diff --git a/scene/resources/default_theme/icon_visibility.png b/scene/resources/default_theme/icon_visibility.png Binary files differnew file mode 100644 index 0000000000..6402571f3e --- /dev/null +++ b/scene/resources/default_theme/icon_visibility.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 87b8afd6a3..5e13a6625a 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -194,6 +194,10 @@ static const unsigned char icon_stop_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x2, 0x7c, 0x60, 0x26, 0x28, 0xf3, 0xf0, 0x3f, 0x76, 0x8, 0x94, 0xa2, 0x97, 0x82, 0x51, 0x5, 0x84, 0x23, 0x8b, 0x30, 0x0, 0x0, 0x66, 0x60, 0x11, 0xdc, 0x92, 0xb3, 0xb7, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char icon_visibility_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0xdb, 0xe1, 0x4f, 0xe0, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc4, 0x0, 0x0, 0xe, 0xc4, 0x1, 0x95, 0x2b, 0xe, 0x1b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xdf, 0xdf, 0xdf, 0xe3, 0xe3, 0xe3, 0xe6, 0xe6, 0xe6, 0xd5, 0xd5, 0xd5, 0xd8, 0xd8, 0xd8, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xb7, 0x7e, 0xd, 0xb6, 0x0, 0x0, 0x0, 0x32, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x8, 0x9, 0xa, 0xc, 0xd, 0xe, 0xf, 0x11, 0x12, 0x13, 0x2e, 0x2f, 0x32, 0x33, 0x36, 0x37, 0x38, 0x48, 0x49, 0x4b, 0x50, 0x53, 0x55, 0x56, 0x6c, 0x6d, 0x6e, 0x70, 0x77, 0x79, 0x7b, 0x7c, 0xc5, 0xd7, 0xd8, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xf0, 0xf2, 0xf3, 0xf4, 0xfe, 0x5e, 0x62, 0x1a, 0x26, 0x0, 0x0, 0x0, 0x86, 0x49, 0x44, 0x41, 0x54, 0x18, 0x19, 0x8d, 0xc1, 0xb, 0x16, 0x42, 0x40, 0x0, 0x86, 0xd1, 0x2f, 0xa2, 0x77, 0x2a, 0x85, 0xde, 0x91, 0x5e, 0x33, 0x8a, 0x7f, 0xff, 0x9b, 0xcb, 0x99, 0x63, 0x1, 0xee, 0xa5, 0x9f, 0xc1, 0x3e, 0x6f, 0xea, 0xfc, 0xe0, 0xd1, 0x99, 0xdd, 0xe5, 0x94, 0xb, 0x9c, 0xf9, 0x57, 0x26, 0x9, 0x82, 0x5d, 0xa1, 0xdf, 0x92, 0x96, 0xf7, 0x94, 0x99, 0xd0, 0x1a, 0x1b, 0x7d, 0x7c, 0xe0, 0x2c, 0x25, 0x6c, 0x2b, 0x1b, 0x93, 0x4a, 0x17, 0xa0, 0x94, 0x2, 0xac, 0x64, 0x8, 0xa5, 0x12, 0x78, 0x48, 0x1, 0x56, 0x32, 0x8c, 0xa4, 0x7, 0x70, 0x93, 0x76, 0xc4, 0xd6, 0x6c, 0xc8, 0xa4, 0x2b, 0x30, 0xac, 0x54, 0x8c, 0x69, 0x4d, 0xad, 0xcc, 0x90, 0xd6, 0xba, 0x91, 0x49, 0xc3, 0x30, 0xb3, 0x6a, 0x22, 0x9c, 0xd5, 0x5b, 0xce, 0x2b, 0xa2, 0xe3, 0x9f, 0xf2, 0xba, 0xce, 0x8f, 0x3e, 0xbd, 0xfc, 0x1, 0xdb, 0xf3, 0x10, 0xc5, 0x78, 0x85, 0x14, 0x89, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char icon_zoom_less_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x13, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x18, 0x31, 0xe0, 0xc1, 0x7f, 0x3c, 0x90, 0xb0, 0x82, 0x11, 0x2, 0x0, 0xbf, 0x57, 0x36, 0x25, 0x52, 0x24, 0x7b, 0x26, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; |