diff options
31 files changed, 1186 insertions, 639 deletions
diff --git a/core/project_settings.cpp b/core/project_settings.cpp index a74917162b..23e4961138 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -114,7 +114,15 @@ String ProjectSettings::globalize_path(const String &p_path) const { return p_path.replace("res:/", resource_path); }; return p_path.replace("res://", ""); - }; + } else if (p_path.begins_with("user://")) { + + String data_dir = OS::get_singleton()->get_data_dir(); + if (data_dir != "") { + + return p_path.replace("user:/", data_dir); + }; + return p_path.replace("user://", ""); + } return p_path; } diff --git a/doc/base/classes.xml b/doc/base/classes.xml index ba9b037fb6..f5721a9bbd 100644 --- a/doc/base/classes.xml +++ b/doc/base/classes.xml @@ -9025,8 +9025,10 @@ </class> <class name="BitMap" inherits="Resource" category="Core"> <brief_description> + Boolean matrix. </brief_description> <description> + A two-dimensional array of boolean values, can be used to efficiently store a binary matrix (every matrix element takes only one bit) and query the values using natural cartesian coordinates. </description> <methods> <method name="create"> @@ -9035,6 +9037,7 @@ <argument index="0" name="size" type="Vector2"> </argument> <description> + Creates a bitmap with the specified size, filled with false. </description> </method> <method name="create_from_image_alpha"> @@ -9043,6 +9046,7 @@ <argument index="0" name="image" type="Image"> </argument> <description> + Creates a bitmap that matches the given image dimensions, every element of the bitmap is set to false if the alpha value of the image at that position is 0, and true in other case. </description> </method> <method name="get_bit" qualifiers="const"> @@ -9051,18 +9055,21 @@ <argument index="0" name="pos" type="Vector2"> </argument> <description> + Returns bitmap's value at the specified position. </description> </method> <method name="get_size" qualifiers="const"> <return type="Vector2"> </return> <description> + Returns bitmap's dimensions. </description> </method> <method name="get_true_bit_count" qualifiers="const"> <return type="int"> </return> <description> + Returns the amount of bitmap elements that are set to true. </description> </method> <method name="set_bit"> @@ -9073,6 +9080,7 @@ <argument index="1" name="bit" type="bool"> </argument> <description> + Sets the bitmap's element at the specified position, to the specified value. </description> </method> <method name="set_bit_rect"> @@ -9083,6 +9091,7 @@ <argument index="1" name="bit" type="bool"> </argument> <description> + Sets a rectangular portion of the bitmap to the specified value. </description> </method> </methods> @@ -9370,35 +9379,30 @@ <return type="Texture"> </return> <description> - Return the button icon. </description> </method> <method name="get_clip_text" qualifiers="const"> <return type="bool"> </return> <description> - Return the state of the [i]clip_text[/i] property (see [method set_clip_text]) </description> </method> <method name="get_text" qualifiers="const"> <return type="String"> </return> <description> - Return the button text. </description> </method> <method name="get_text_align" qualifiers="const"> <return type="int" enum="Button.TextAlign"> </return> <description> - Return the text alignment policy. </description> </method> <method name="is_flat" qualifiers="const"> <return type="bool"> </return> <description> - Return the state of the [i]flat[/i] property (see [method set_flat]). </description> </method> <method name="set_button_icon"> @@ -9407,7 +9411,6 @@ <argument index="0" name="texture" type="Texture"> </argument> <description> - Set the icon that will be displayed next to the text inside the button area. </description> </method> <method name="set_clip_text"> @@ -9416,7 +9419,6 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> - Set the [i]clip_text[/i] property of a Button. When this property is enabled, text that is too large to fit the button is clipped, when disabled (default) the Button will always be wide enough to hold the text. </description> </method> <method name="set_flat"> @@ -9425,7 +9427,6 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> - Set the [i]flat[/i] property of a Button. Flat buttons don't display decoration unless hovered or pressed. </description> </method> <method name="set_text"> @@ -9434,7 +9435,6 @@ <argument index="0" name="text" type="String"> </argument> <description> - Set the button text, which will be displayed inside the button area. </description> </method> <method name="set_text_align"> @@ -9443,20 +9443,24 @@ <argument index="0" name="align" type="int" enum="Button.TextAlign"> </argument> <description> - Set the text alignment policy, using one of the ALIGN_* constants. </description> </method> </methods> <members> <member name="align" type="int" setter="set_text_align" getter="get_text_align" brief="" enum="Button.TextAlign"> + Text alignment policy for the button's text, use one of the ALIGN_* constants. </member> <member name="clip_text" type="bool" setter="set_clip_text" getter="get_clip_text" brief=""> + When this property is enabled, text that is too large to fit the button is clipped, when disabled the Button will always be wide enough to hold the text. This property is disabled by default. </member> - <member name="flat" type="bool" setter="set_flat" getter="is_flat" brief=""> + <member name="flat" type="bool" setter="set_flat" getter="is_flat" brief="button decoration mode"> + Flat buttons don't display decoration. </member> <member name="icon" type="Texture" setter="set_button_icon" getter="get_button_icon" brief=""> + Button's icon, if text is present the icon will be placed before the text. </member> <member name="text" type="String" setter="set_text" getter="get_text" brief=""> + The button's text that will be displayed inside the button's area. </member> </members> <constants> @@ -9464,7 +9468,7 @@ Align the text to the left. </constant> <constant name="ALIGN_CENTER" value="1"> - Center the text. + Align the text to the center. </constant> <constant name="ALIGN_RIGHT" value="2"> Align the text to the right. @@ -11033,8 +11037,10 @@ </class> <class name="CapsuleMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a capsule-shaped [PrimitiveMesh]. </brief_description> <description> + Class representing a capsule-shaped [PrimitiveMesh]. </description> <methods> <method name="get_mid_height" qualifiers="const"> @@ -11096,12 +11102,16 @@ </methods> <members> <member name="mid_height" type="float" setter="set_mid_height" getter="get_mid_height" brief=""> + Height of the capsule mesh from the center point. Defaults to 1.0. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" brief=""> + Number of radial segments on the capsule mesh. Defaults to 64. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" brief=""> + Radius of the capsule mesh. Defaults to 1.0. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings" brief=""> + Number of rings along the height of the capsule. Defaults to 8. </member> </members> <constants> @@ -12155,8 +12165,10 @@ </class> <class name="CollisionShape" inherits="Spatial" category="Core"> <brief_description> + Node that represents collision shape data in 3D space. </brief_description> <description> + Editor facility for creating and editing collision shapes in 3D space. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area] to give it a detection shape, or add it to a [PhysicsBody] to give create solid object. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method get_shape] to get the actual shape. </description> <methods> <method name="get_shape" qualifiers="const"> @@ -12175,6 +12187,7 @@ <return type="void"> </return> <description> + Sets the collision shape's shape to the addition of all its convexed [MeshInstance] siblings geometry. </description> </method> <method name="resource_changed"> @@ -12204,8 +12217,10 @@ </methods> <members> <member name="disabled" type="bool" setter="set_disabled" getter="is_disabled" brief=""> + A disabled collision shape has no effect in the world. </member> <member name="shape" type="Shape" setter="set_shape" getter="get_shape" brief=""> + The actual shape owned by this collision shape. </member> </members> <constants> @@ -12213,17 +12228,16 @@ </class> <class name="CollisionShape2D" inherits="Node2D" category="Core"> <brief_description> - Editor-only class for easy editing of shapes. + Node that represents collision shape data in 2D space. </brief_description> <description> - Editor-only class. This is not present when running the game. It's used in the editor to properly edit and position collision shapes in [CollisionObject2D]. This is not accessible from regular code. + Editor facility for creating and editing collision shapes in 2D space. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area2D] to give it a detection shape, or add it to a [PhysicsBody2D] to give create solid object. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method get_shape] to get the actual shape. </description> <methods> <method name="get_shape" qualifiers="const"> <return type="Shape2D"> </return> <description> - Return this shape's [Shape2D]. </description> </method> <method name="is_disabled" qualifiers="const"> @@ -12260,16 +12274,18 @@ <argument index="0" name="shape" type="Shape2D"> </argument> <description> - Set this shape's [Shape2D]. This will not appear as a node, but can be directly edited as a property. </description> </method> </methods> <members> <member name="disabled" type="bool" setter="set_disabled" getter="is_disabled" brief=""> + A disabled collision shape has no effect in the world. </member> <member name="one_way_collision" type="bool" setter="set_one_way_collision" getter="is_one_way_collision_enabled" brief=""> + Sets whether this collision shape should only detect collision on one side (top or bottom). </member> <member name="shape" type="Shape2D" setter="set_shape" getter="get_shape" brief=""> + The actual shape owned by this collision shape. </member> </members> <constants> @@ -14171,8 +14187,10 @@ </class> <class name="CubeMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Generate an axis-aligned cuboid [PrimitiveMesh]. </brief_description> <description> + Generate an axis-aligned cuboid [PrimitiveMesh]. </description> <methods> <method name="get_size" qualifiers="const"> @@ -14234,12 +14252,16 @@ </methods> <members> <member name="size" type="Vector3" setter="set_size" getter="get_size" brief=""> + Size of the cuboid mesh. Defaults to (2, 2, 2). </member> <member name="subdivide_depth" type="int" setter="set_subdivide_depth" getter="get_subdivide_depth" brief=""> + Number of extra edge loops inserted along the z-axis. Defaults to 0. </member> <member name="subdivide_height" type="int" setter="set_subdivide_height" getter="get_subdivide_height" brief=""> + Number of extra edge loops inserted along the y-axis. Defaults to 0. </member> <member name="subdivide_width" type="int" setter="set_subdivide_width" getter="get_subdivide_width" brief=""> + Number of extra edge loops inserted along the x-axis. Defaults to 0. </member> </members> <constants> @@ -14929,8 +14951,10 @@ </class> <class name="CylinderMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a cylindrical [PrimitiveMesh]. </brief_description> <description> + Class representing a cylindrical [PrimitiveMesh]. </description> <methods> <method name="get_bottom_radius" qualifiers="const"> @@ -15006,14 +15030,19 @@ </methods> <members> <member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" brief=""> + Bottom radius of the cylinder. Defaults to 1.0. </member> <member name="height" type="float" setter="set_height" getter="get_height" brief=""> + Full height of the cylinder. Defaults to 2.0. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" brief=""> + Number of radial segments on the cylinder. Defaults to 64. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings" brief=""> + Number of edge rings along the height of the cylinder. Defaults to 4. </member> <member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" brief=""> + Top radius of the cylinder. Defaults to 1.0. </member> </members> <constants> @@ -19249,20 +19278,31 @@ </class> <class name="GDScript" inherits="Script" category="Core"> <brief_description> + A script implemented in the GDScript programming language. </brief_description> <description> + A script implemented in the GDScript programming language. The script exends the functionality of all objects that instance it. + [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <methods> <method name="get_as_byte_code" qualifiers="const"> <return type="PoolByteArray"> </return> <description> + Returns byte code for the script source code. </description> </method> <method name="new" qualifiers="vararg"> <return type="Object"> </return> <description> + Returns a new instance of the script. + For example: + [codeblock] + var MyClass = load("myclass.gd") + var instance = MyClass.new() + assert(instance.get_script() == MyClass) + [/codeblock] </description> </method> </methods> @@ -20461,8 +20501,10 @@ </class> <class name="GradientTexture" inherits="Texture" category="Core"> <brief_description> + Gradient filled texture. </brief_description> <description> + Uses a [Gradient] to fill the texture data, the gradient will be filled from left to right using colors obtained from the gradient, this means that the texture does not necessarily represent an exact copy of the gradient, but instead an interpolation of samples obtained from the gradient at fixed steps (see [method set_width]). </description> <methods> <method name="get_gradient" qualifiers="const"> @@ -20490,8 +20532,10 @@ </methods> <members> <member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient" brief=""> + The [Gradient] that will be used to fill the texture. </member> <member name="width" type="int" setter="set_width" getter="get_width" brief=""> + The number of color samples that will be obtained from the [Gradient]. </member> </members> <constants> @@ -23249,6 +23293,7 @@ </class> <class name="InputEvent" inherits="Resource" category="Core"> <brief_description> + Generic input event </brief_description> <description> </description> @@ -23259,24 +23304,28 @@ <argument index="0" name="event" type="InputEvent"> </argument> <description> + Returns true if this input event matches the event passed. </description> </method> <method name="as_text" qualifiers="const"> <return type="String"> </return> <description> + Returns a [String] representation of the event. </description> </method> <method name="get_device" qualifiers="const"> <return type="int"> </return> <description> + Returns the id of the device that generated the event. </description> </method> <method name="get_id" qualifiers="const"> <return type="int"> </return> <description> + Returns the id of the event. </description> </method> <method name="is_action" qualifiers="const"> @@ -23285,7 +23334,7 @@ <argument index="0" name="action" type="String"> </argument> <description> - Return if this input event matches a pre-defined action, no matter the type. + Returns true if this input event matches a pre-defined action, no matter the type. </description> </method> <method name="is_action_pressed" qualifiers="const"> @@ -23294,7 +23343,7 @@ <argument index="0" name="action" type="String"> </argument> <description> - Return whether the given action is being pressed (and is not an echo event for KEY events). Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. + Returns true if the given action is being pressed (and is not an echo event for KEY events). Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. </description> </method> <method name="is_action_released" qualifiers="const"> @@ -23303,7 +23352,7 @@ <argument index="0" name="action" type="String"> </argument> <description> - Return whether the given action is released (i.e. not pressed). Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. + Returns true if the given action is released (i.e. not pressed). Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. </description> </method> <method name="is_action_type" qualifiers="const"> @@ -23316,14 +23365,14 @@ <return type="bool"> </return> <description> - Return if this input event is an echo event (only for events of type KEY, it will return false for other types). + Returns true if this input event is an echo event (only for events of type KEY, it will return false for other types). </description> </method> <method name="is_pressed" qualifiers="const"> <return type="bool"> </return> <description> - Return if this input event is pressed. Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. + Returns true if this input event is pressed. Not relevant for the event types MOUSE_MOTION, SCREEN_DRAG and NONE. </description> </method> <method name="set_device"> @@ -26260,10 +26309,10 @@ </class> <class name="LightOccluder2D" inherits="Node2D" category="Core"> <brief_description> - Occludes light cast by a Light2D, thus casting shadows. + Occludes light cast by a Light2D, casting shadows. </brief_description> <description> - Occludes light cast by a Light2D, thus casting shadows. The LightOccluder2D must be provided with a shape (see OccluderPolygon2D) that allows the shadow to be computed. This shape affects the resulting shadow, while the shape of the representating asset shadowed does not actually affect shadows. + Occludes light cast by a Light2D, casting shadows. The LightOccluder2D must be provided with an [OccluderPolygon2D] in order for the shadow to be computed. </description> <methods> <method name="get_occluder_light_mask" qualifiers="const"> @@ -26301,8 +26350,10 @@ </methods> <members> <member name="light_mask" type="int" setter="set_occluder_light_mask" getter="get_occluder_light_mask" brief=""> + The LightOccluder2D's light mask. The LightOccluder2D will cast shadows only from Light2D(s) that have the same light mask(s). </member> <member name="occluder" type="OccluderPolygon2D" setter="set_occluder_polygon" getter="get_occluder_polygon" brief=""> + The [OccluderPolygon2D] used to compute the shadow. </member> </members> <constants> @@ -26310,8 +26361,10 @@ </class> <class name="Line2D" inherits="Node2D" category="Core"> <brief_description> + A 2D line. </brief_description> <description> + A line through several points in 2D space. </description> <methods> <method name="add_point"> @@ -26320,6 +26373,7 @@ <argument index="0" name="pos" type="Vector2"> </argument> <description> + Add a point at the x/y position in the supplied [Vector2] </description> </method> <method name="get_begin_cap_mode" qualifiers="const"> @@ -26408,6 +26462,7 @@ <argument index="0" name="i" type="int"> </argument> <description> + Remove the point at index 'i' from the line. </description> </method> <method name="set_begin_cap_mode"> @@ -29667,7 +29722,7 @@ <return type="void"> </return> <description> - Destroy this Node and its children when they are not in use. + Queues a node for deletion at the end of the current frame. When deleted, all of its children nodes will be deleted as well. This method ensures it's safe to delete the node, contrary to [method Object.free]. Use [method Object.is_queued_for_deletion] to know whether a node will be deleted at the end of the frame. </description> </method> <method name="raise"> @@ -38019,8 +38074,10 @@ </class> <class name="PlaneMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a planar [PrimitiveMesh]. </brief_description> <description> + Class representing a planar [PrimitiveMesh]. This flat mesh does not have a thickness. </description> <methods> <method name="get_size" qualifiers="const"> @@ -38068,10 +38125,13 @@ </methods> <members> <member name="size" type="Vector2" setter="set_size" getter="get_size" brief=""> + Size of the generated plane. Defaults to (2.0, 2.0). </member> <member name="subdivide_depth" type="int" setter="set_subdivide_depth" getter="get_subdivide_depth" brief=""> + Number of subdivision along the z-axis. Defaults to 0. </member> <member name="subdivide_width" type="int" setter="set_subdivide_width" getter="get_subdivide_width" brief=""> + Number of subdivision along the x-axis. Defaults to 0. </member> </members> <constants> @@ -39729,8 +39789,10 @@ </class> <class name="PrimitiveMesh" inherits="Mesh" category="Core"> <brief_description> + Base class for all primitive meshes. Handles applying a [Material] to a primitive mesh. </brief_description> <description> + Base class for all primitive meshes. Handles applying a [Material] to a primitive mesh. </description> <methods> <method name="get_material" qualifiers="const"> @@ -39750,6 +39812,7 @@ </methods> <members> <member name="material" type="Material" setter="set_material" getter="get_material" brief=""> + The current [Material] of the primitive mesh. </member> </members> <constants> @@ -39757,8 +39820,10 @@ </class> <class name="PrismMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a prism-shaped [PrimitiveMesh]. </brief_description> <description> + Class representing a prism-shaped [PrimitiveMesh]. </description> <methods> <method name="get_left_to_right" qualifiers="const"> @@ -39834,14 +39899,19 @@ </methods> <members> <member name="left_to_right" type="float" setter="set_left_to_right" getter="get_left_to_right" brief=""> + Displacement of of the upper edge along the x-axis. 0.0 positions edge straight above the bottome left edge. Defaults to 0.5 (positioned on the midpoint). </member> <member name="size" type="Vector3" setter="set_size" getter="get_size" brief=""> + Size of the prism. Defaults to (2.0, 2.0, 2.0). </member> <member name="subdivide_depth" type="int" setter="set_subdivide_depth" getter="get_subdivide_depth" brief=""> + Number of added edge loops along the z-axis. Defaults to 0. </member> <member name="subdivide_height" type="int" setter="set_subdivide_height" getter="get_subdivide_height" brief=""> + Number of added edge loops along the y-axis. Defaults to 0. </member> <member name="subdivide_width" type="int" setter="set_subdivide_width" getter="get_subdivide_width" brief=""> + Number of added edge loops along the x-axis. Defaults to 0. </member> </members> <constants> @@ -40391,8 +40461,10 @@ </class> <class name="QuadMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a square mesh. </brief_description> <description> + Class representing a square mesh with size (2,2,0). Consider using a [PlaneMesh] if you require a differently sized plane. </description> <methods> </methods> @@ -43248,38 +43320,55 @@ </methods> <members> <member name="angular_damp" type="float" setter="set_angular_damp" getter="get_angular_damp" brief=""> + Dampens rotational forces of the Rigid body by the 'angular_damp' rate. </member> <member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" brief=""> + The current rotational velocity of the Rigid body </member> <member name="axis_lock" type="int" setter="set_axis_lock" getter="get_axis_lock" brief="" enum="RigidBody.AxisLock"> + Locks the rotational forces to a particular axis, preventing rotations on other axes. </member> <member name="bounce" type="float" setter="set_bounce" getter="get_bounce" brief=""> + Bounciness of the Rigid body. </member> <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" brief=""> + If true, the Rigid body will no longer calculate forces when there is no movement and will act as a static body. It will wake up when other forces are applied through other collisions or when the 'apply_impulse' method is used. </member> <member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" brief=""> + If true, the Rigid body will emit signals when it collides with another Rigid body. </member> <member name="contacts_reported" type="int" setter="set_max_contacts_reported" getter="get_max_contacts_reported" brief=""> + The maximum contacts to report. Bodies can keep a log of the contacts with other bodies, this is enabled by setting the maximum amount of contacts reported to a number greater than 0. </member> <member name="continuous_cd" type="bool" setter="set_use_continuous_collision_detection" getter="is_using_continuous_collision_detection" brief=""> + Continuous collision detection tries to predict where a moving body will collide, instead of moving it and correcting its movement if it collided. The first is more precise, and misses less impacts by small, fast-moving objects. The second is faster to compute, but can miss small, fast-moving objects. </member> <member name="custom_integrator" type="bool" setter="set_use_custom_integrator" getter="is_using_custom_integrator" brief=""> + If true, internal force integration will be disabled (like gravity or air friction) for this body. Other than collision response, the body will only move as determined by the [method _integrate_forces] function, if defined. </member> <member name="friction" type="float" setter="set_friction" getter="get_friction" brief=""> + The body friction, from 0 (frictionless) to 1 (max friction). </member> <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" brief=""> + The 'gravity_scale' for this Rigid body will be multiplied by the global 3d gravity setting found in "Project > Project Settings > Physics > 3d". A value of 1 will be normal gravity, 2 will apply double gravity, and 0.5 will apply half gravity to this object. </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" brief=""> + The linear damp for this body. Default of -1, cannot be less than -1. If this value is different from -1, any linear damp derived from the world or areas will be overridden. </member> <member name="linear_velocity" type="Vector3" setter="set_linear_velocity" getter="get_linear_velocity" brief=""> + The body linear velocity. Can be used sporadically, but [b]DON'T SET THIS IN EVERY FRAME[/b], because physics may run in another thread and runs at a different granularity. Use [method _integrate_forces] as your process loop for precise control of the body state. </member> <member name="mass" type="float" setter="set_mass" getter="get_mass" brief=""> + The body mass. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" brief="" enum="RigidBody.Mode"> + The body mode from the MODE_* enum. Modes include: MODE_STATIC, MODE_KINEMATIC, MODE_RIGID, and MODE_CHARACTER. </member> <member name="sleeping" type="bool" setter="set_sleeping" getter="is_sleeping" brief=""> + The current 'sleeping' state of the Rigid body. </member> <member name="weight" type="float" setter="set_weight" getter="get_weight" brief=""> + The body weight given standard earth-weight (gravity 9.8). </member> </members> <signals> @@ -44435,17 +44524,18 @@ </class> <class name="Script" inherits="Resource" category="Core"> <brief_description> - Base class for scripts. + A class stored as a resource. </brief_description> <description> - Base class for scripts. Any script that is loaded becomes one of these resources, which can then create instances. + A class stored as a resource. The script exends the functionality of all objects that instance it. + The 'new' method of a script subclass creates a new instance. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <methods> <method name="can_instance" qualifiers="const"> <return type="bool"> </return> <description> - Return true if this script can be instance (ie not a library). + Returns true if the script can be instanced. </description> </method> <method name="get_node_type" qualifiers="const"> @@ -44458,7 +44548,7 @@ <return type="String"> </return> <description> - Return the script source code (if available). + Returns the script source code, or an empty string if source code is not available. </description> </method> <method name="has_script_signal" qualifiers="const"> @@ -44467,13 +44557,14 @@ <argument index="0" name="signal_name" type="String"> </argument> <description> + Returns true if the script, or a base class, defines a signal with the given name. </description> </method> <method name="has_source_code" qualifiers="const"> <return type="bool"> </return> <description> - Return true if the script contains source code. + Returns true if the script contains non-empty source code. </description> </method> <method name="instance_has" qualifiers="const"> @@ -44482,21 +44573,24 @@ <argument index="0" name="base_object" type="Object"> </argument> <description> - Return true if a given object uses an instance of this script. + Returns true if 'base_object' is an instance of this script. </description> </method> <method name="is_tool" qualifiers="const"> <return type="bool"> </return> <description> + Returns true if the script is a tool script. A tool script can run in the editor. </description> </method> <method name="reload"> <return type="int" enum="Error"> </return> <argument index="0" name="keep_state" type="bool" default="false"> + If true, preserve existing script instances and subclasses. </argument> <description> + Reloads the script's class implementation. Returns an error code. </description> </method> <method name="set_source_code"> @@ -44505,7 +44599,7 @@ <argument index="0" name="source" type="String"> </argument> <description> - Set the script source code. + Sets the script source code. Does not reload the class implementation. </description> </method> </methods> @@ -45473,16 +45567,17 @@ </class> <class name="Spatial" inherits="Node" category="Core"> <brief_description> - Base class for all 3D nodes. + Most basic 3D game object, parent of all 3D related nodes. </brief_description> <description> - Spatial is the base for every type of 3D [Node]. It contains a 3D [Transform] which can be set or get as local or global. If a Spatial [Node] has Spatial children, their transforms will be relative to the parent. + Most basic 3D game object, with a 3D [Transform] and visibility settings. All 3D physics nodes and sprites inherit from Spatial. Use Spatial as a parent node to move, scale, rotate and show/hide children in a 3D project. </description> <methods> <method name="get_gizmo" qualifiers="const"> <return type="SpatialGizmo"> </return> <description> + Return the SpatialGizmo for this node. Used for example in [EditorSpatialGizmo] as custom visualization and editing handles in Editor. </description> </method> <method name="get_global_transform" qualifiers="const"> @@ -45536,6 +45631,7 @@ <return type="World"> </return> <description> + Return current [World] resource this Spatial node is registered to. </description> </method> <method name="global_rotate"> @@ -45546,6 +45642,7 @@ <argument index="1" name="radians" type="float"> </argument> <description> + Rotate current node along normal [Vector3] by angle in radians in Global space. </description> </method> <method name="global_translate"> @@ -45554,42 +45651,49 @@ <argument index="0" name="offset" type="Vector3"> </argument> <description> + Move current node by [Vector3] offset in Global space. </description> </method> <method name="hide"> <return type="void"> </return> <description> + Disable rendering of this node. Change Spatial Visible property to false. </description> </method> <method name="is_local_transform_notification_enabled" qualifiers="const"> <return type="bool"> </return> <description> + Returns whether node sends notification that its local transformation changed. Spatial will not propagate this by default. </description> </method> <method name="is_set_as_toplevel" qualifiers="const"> <return type="bool"> </return> <description> + Returns whether this node is set as Toplevel, ignoring its parent node transformations. </description> </method> <method name="is_transform_notification_enabled" qualifiers="const"> <return type="bool"> </return> <description> + Returns whether node sends notification that its transformation changed. Spatial will not propagate this by default. </description> </method> <method name="is_visible" qualifiers="const"> <return type="bool"> </return> <description> + Returns whether this node is set to be visible. </description> </method> <method name="is_visible_in_tree" qualifiers="const"> <return type="bool"> </return> <description> + Returns whether this node is visible, taking into consideration that its parents visibility. </description> </method> <method name="look_at"> @@ -45600,6 +45704,7 @@ <argument index="1" name="up" type="Vector3"> </argument> <description> + Rotates itself to point into direction of target position. Operations take place in global space. </description> </method> <method name="look_at_from_pos"> @@ -45612,12 +45717,14 @@ <argument index="2" name="up" type="Vector3"> </argument> <description> + Moves itself to specified position and then rotates itself to point into direction of target position. Operations take place in global space. </description> </method> <method name="orthonormalize"> <return type="void"> </return> <description> + Reset this node transformations (like scale, skew and taper) preserving its rotation and translation. Performs orthonormalization on this node [Transform3D]. </description> </method> <method name="rotate"> @@ -45628,6 +45735,7 @@ <argument index="1" name="radians" type="float"> </argument> <description> + Rotates node in local space on given normal [Vector3] by angle in radians. </description> </method> <method name="rotate_x"> @@ -45636,6 +45744,7 @@ <argument index="0" name="radians" type="float"> </argument> <description> + Rotates node in local space on X axis by angle in radians. </description> </method> <method name="rotate_y"> @@ -45644,6 +45753,7 @@ <argument index="0" name="radians" type="float"> </argument> <description> + Rotates node in local space on Y axis by angle in radians. </description> </method> <method name="rotate_z"> @@ -45652,6 +45762,7 @@ <argument index="0" name="radians" type="float"> </argument> <description> + Rotates node in local space on Z axis by angle in radians. </description> </method> <method name="set_as_toplevel"> @@ -45660,6 +45771,7 @@ <argument index="0" name="enable" type="bool"> </argument> <description> + Makes this node ignore its parents tranformations. Node tranformations are only in global space. </description> </method> <method name="set_gizmo"> @@ -45668,6 +45780,7 @@ <argument index="0" name="gizmo" type="SpatialGizmo"> </argument> <description> + Set [SpatialGizmo] for this node. Used for example in [EditorSpatialGizmo] as custom visualization and editing handles in Editor. </description> </method> <method name="set_global_transform"> @@ -45676,13 +45789,14 @@ <argument index="0" name="global" type="Transform"> </argument> <description> - Set the transform globally, relative to worldspace. + Set the transform globally, relative to world space. </description> </method> <method name="set_identity"> <return type="void"> </return> <description> + Reset all tranformations for this node. Set its [Transform3D] to identity matrix. </description> </method> <method name="set_ignore_transform_notification"> @@ -45691,6 +45805,7 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> + Set whether this node ignores notification that its transformation changed. </description> </method> <method name="set_notify_local_transform"> @@ -45699,6 +45814,7 @@ <argument index="0" name="enable" type="bool"> </argument> <description> + Set whether this node sends notification that its local transformation changed. Spatial will not propagate this by default. </description> </method> <method name="set_notify_transform"> @@ -45707,6 +45823,7 @@ <argument index="0" name="enable" type="bool"> </argument> <description> + Set whether this node sends notification that its transformation changed. Spatial will not propagate this by default. </description> </method> <method name="set_rotation"> @@ -45733,6 +45850,7 @@ <argument index="0" name="scale" type="Vector3"> </argument> <description> + Set the scale. </description> </method> <method name="set_transform"> @@ -45764,6 +45882,7 @@ <return type="void"> </return> <description> + Enable rendering of this node. Change Spatial Visible property to false. </description> </method> <method name="to_global" qualifiers="const"> @@ -45772,6 +45891,7 @@ <argument index="0" name="local_point" type="Vector3"> </argument> <description> + Tranform [Vector3] from this node local space to world space. </description> </method> <method name="to_local" qualifiers="const"> @@ -45780,6 +45900,7 @@ <argument index="0" name="global_point" type="Vector3"> </argument> <description> + Tranform [Vector3] from world space to this node local space. </description> </method> <method name="translate"> @@ -45788,46 +45909,60 @@ <argument index="0" name="offset" type="Vector3"> </argument> <description> + Change node position by given offset [Vector3]. </description> </method> <method name="update_gizmo"> <return type="void"> </return> <description> + Update [SpatialGizmo] of this node. </description> </method> </methods> <members> <member name="global_transform" type="Transform" setter="set_global_transform" getter="get_global_transform" brief=""> + World space (global) [Transform] of this node. </member> <member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation" brief=""> + Local euler rotation in radians of this node. </member> <member name="rotation_deg" type="Vector3" setter="set_rotation_deg" getter="get_rotation_deg" brief=""> + Local euler rotation in degrees of this node. </member> <member name="scale" type="Vector3" setter="set_scale" getter="get_scale" brief=""> + Local scale of this node. </member> <member name="transform" type="Transform" setter="set_transform" getter="get_transform" brief=""> + Local space [Transform] of this node. </member> <member name="translation" type="Vector3" setter="set_translation" getter="get_translation" brief=""> + Local translation of this node. </member> <member name="visible" type="bool" setter="set_visible" getter="is_visible" brief=""> + Visibility of this node. Toggles if this node is rendered. </member> </members> <signals> <signal name="visibility_changed"> <description> + Emitted when node visibility changed. </description> </signal> </signals> <constants> <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="29" enum=""> - Spatial nodes receive this notification with their global transform changes. This means that either the current or a parent node changed its transform. + Spatial nodes receive this notification when their global transform changes. This means that either the current or a parent node changed its transform. + In order for NOTIFICATION_TRANSFORM_CHANGED to work user first needs to ask for it, with set_notify_transform(true). </constant> <constant name="NOTIFICATION_ENTER_WORLD" value="41" enum=""> + Spatial nodes receive this notification when they are registered to new [World] resource. </constant> <constant name="NOTIFICATION_EXIT_WORLD" value="42" enum=""> + Spatial nodes receive this notification when they are unregistered from current [World] resource. </constant> <constant name="NOTIFICATION_VISIBILITY_CHANGED" value="43" enum=""> + Spatial nodes receive this notification when their visibility changes. </constant> </constants> </class> @@ -46905,8 +47040,10 @@ </class> <class name="SphereMesh" inherits="PrimitiveMesh" category="Core"> <brief_description> + Class representing a spherical [PrimitiveMesh]. </brief_description> <description> + Class representing a spherical [PrimitiveMesh]. </description> <methods> <method name="get_height" qualifiers="const"> @@ -46982,14 +47119,19 @@ </methods> <members> <member name="height" type="float" setter="set_height" getter="get_height" brief=""> + Full height of the sphere. Defaults to 2.0. </member> <member name="is_hemisphere" type="bool" setter="set_is_hemisphere" getter="get_is_hemisphere" brief=""> + Determines whether a full sphere or a hemisphere is created. Attention: To get a regular hemisphere the height and radius of the sphere have to equal. Defaults to false. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" brief=""> + Number of radial segments on the sphere. Defaults to 64. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" brief=""> + Radius of sphere. Defaults to 1.0. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings" brief=""> + Number of segments along the height of the sphere. Defaults to 32. </member> </members> <constants> @@ -47989,12 +48131,16 @@ </methods> <members> <member name="bounce" type="float" setter="set_bounce" getter="get_bounce" brief=""> + The body bounciness. </member> <member name="constant_angular_velocity" type="Vector3" setter="set_constant_angular_velocity" getter="get_constant_angular_velocity" brief=""> + The constant angular velocity for the body. This does not rotate the body, but affects other bodies that touch it, as if it was in a state of rotation. </member> <member name="constant_linear_velocity" type="Vector3" setter="set_constant_linear_velocity" getter="get_constant_linear_velocity" brief=""> + The constant linear velocity for the body. This does not move the body, but affects other bodies that touch it, as if it was in a state of movement. </member> <member name="friction" type="float" setter="set_friction" getter="get_friction" brief=""> + The body friction, from 0 (frictionless) to 1 (full friction). </member> </members> <constants> @@ -53197,8 +53343,15 @@ </class> <class name="ToolButton" inherits="Button" category="Core"> <brief_description> + Flat button helper class. </brief_description> <description> + This is a helper class to generate a flat [Button] (see [method Button.set_flat]), creating a ToolButton is equivalent to: + + [codeblock] + var btn = Button.new() + btn.set_flat(true) + [/codeblock] </description> <methods> </methods> @@ -53403,10 +53556,10 @@ </class> <class name="Transform" category="Built-In Types"> <brief_description> - 3D Transformation. + 3D Transformation. 3x4 matrix. </brief_description> <description> - Transform is used to store translation, rotation and scaling transformations. It consists of a Basis "basis" and Vector3 "origin". Transform is used to represent transformations of objects in space, and as such, determine their position, orientation and scale. It is similar to a 3x4 matrix. + Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a [Basis] "basis" and an [Vector3] "origin". It is similar to a 3x4 matrix. </description> <methods> <method name="Transform"> @@ -53421,7 +53574,7 @@ <argument index="3" name="origin" type="Vector3"> </argument> <description> - Construct the Transform from four Vector3. Each axis corresponds to local basis vectors (some of which may be scaled). + Construct the Transform from four [Vector3]. Each axis corresponds to local basis vectors (some of which may be scaled). </description> </method> <method name="Transform"> @@ -53432,7 +53585,7 @@ <argument index="1" name="origin" type="Vector3"> </argument> <description> - Construct the Transform from a Basis and Vector3. + Construct the Transform from a [Basis] and [Vector3]. </description> </method> <method name="Transform"> @@ -53441,7 +53594,7 @@ <argument index="0" name="from" type="Transform2D"> </argument> <description> - Construct the Transform from a Transform2D. + Construct the Transform from a [Transform2D]. </description> </method> <method name="Transform"> @@ -53450,7 +53603,7 @@ <argument index="0" name="from" type="Quat"> </argument> <description> - Construct the Transform from a Quat. The origin will be Vector3(0, 0, 0). + Construct the Transform from a [Quat]. The origin will be Vector3(0, 0, 0). </description> </method> <method name="Transform"> @@ -53459,7 +53612,7 @@ <argument index="0" name="from" type="Basis"> </argument> <description> - Construct the Transform from a Basis. The origin will be Vector3(0, 0, 0). + Construct the Transform from a [Basis]. The origin will be Vector3(0, 0, 0). </description> </method> <method name="affine_inverse"> @@ -53477,13 +53630,14 @@ <argument index="1" name="weight" type="float"> </argument> <description> + Interpolate to other Transform by weight amount (0-1). </description> </method> <method name="inverse"> <return type="Transform"> </return> <description> - Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling). + Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use affine_inverse for transforms with scaling). </description> </method> <method name="looking_at"> @@ -53530,7 +53684,7 @@ <argument index="0" name="ofs" type="Vector3"> </argument> <description> - Translate the transform by the specified displacement. + Translate the transform by the specified offset. </description> </method> <method name="xform"> @@ -53554,10 +53708,10 @@ </methods> <members> <member name="basis" type="Basis" setter="" getter="" brief=""> - The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system travelling with the object. + The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> <member name="origin" type="Vector3" setter="" getter="" brief=""> - The origin of the transform. Which is the translation offset. + The translation offset of the transform. </member> </members> <constants> @@ -53565,10 +53719,10 @@ </class> <class name="Transform2D" category="Built-In Types"> <brief_description> - 3x2 Matrix for 2D transforms. + 2D Transformation. 3x2 matrix. </brief_description> <description> - 3x2 Matrix for 2D transforms. + Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a two [Vector2] x, y and [Vector2] "origin". It is similar to a 3x2 matrix. </description> <methods> <method name="Transform2D"> @@ -53577,6 +53731,7 @@ <argument index="0" name="from" type="Transform"> </argument> <description> + Constructs the [Transform2D] from a 3D [Transform]. </description> </method> <method name="Transform2D"> @@ -53589,6 +53744,7 @@ <argument index="2" name="origin" type="Vector2"> </argument> <description> + Constructs the [Transform2D] from 3 [Vector2] consisting of rows x, y and origin. </description> </method> <method name="Transform2D"> @@ -53599,13 +53755,14 @@ <argument index="1" name="pos" type="Vector2"> </argument> <description> + Constructs the [Transform2D] from rotation angle in radians and position [Vector2]. </description> </method> <method name="affine_inverse"> <return type="Transform2D"> </return> <description> - Return the inverse of the matrix. + Returns the inverse of the matrix. </description> </method> <method name="basis_xform"> @@ -53614,6 +53771,7 @@ <argument index="0" name="v" type="var"> </argument> <description> + Transforms the given vector "v" by this transform basis (no translation). </description> </method> <method name="basis_xform_inv"> @@ -53622,12 +53780,14 @@ <argument index="0" name="v" type="var"> </argument> <description> + Inverse-transforms vector "v" by this transform basis (no translation). </description> </method> <method name="get_origin"> <return type="Vector2"> </return> <description> + Return the origin [Vector2] (translation). </description> </method> <method name="get_rotation"> @@ -53641,6 +53801,7 @@ <return type="Vector2"> </return> <description> + Return the scale. </description> </method> <method name="interpolate_with"> @@ -53651,18 +53812,21 @@ <argument index="1" name="weight" type="float"> </argument> <description> + Interpolate to other Transform2D by weight amount (0-1). </description> </method> <method name="inverse"> <return type="Transform2D"> </return> <description> + Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use affine_inverse for transforms with scaling). </description> </method> <method name="orthonormalized"> <return type="Transform2D"> </return> <description> + Returns a transfrom with the basis orthogonal (90 degrees), and normalized axis vectors. </description> </method> <method name="rotated"> @@ -53671,6 +53835,7 @@ <argument index="0" name="phi" type="float"> </argument> <description> + Rotate the transform by phi. </description> </method> <method name="scaled"> @@ -53679,6 +53844,7 @@ <argument index="0" name="scale" type="Vector2"> </argument> <description> + Scale the transform by the specified 2D scaling factors. </description> </method> <method name="translated"> @@ -53687,6 +53853,7 @@ <argument index="0" name="offset" type="Vector2"> </argument> <description> + Translate the transform by the specified offset. </description> </method> <method name="xform"> @@ -53695,6 +53862,7 @@ <argument index="0" name="v" type="var"> </argument> <description> + Transforms the given vector "v" by this transform. </description> </method> <method name="xform_inv"> @@ -53703,15 +53871,19 @@ <argument index="0" name="v" type="var"> </argument> <description> + Inverse-transforms the given vector "v" by this transform. </description> </method> </methods> <members> <member name="origin" type="Vector2" setter="" getter="" brief=""> + The translation offset of the transform. </member> <member name="x" type="Vector2" setter="" getter="" brief=""> + The X axis of 2x2 basis matrix containing 2 [Vector2] as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> <member name="y" type="Vector2" setter="" getter="" brief=""> + The Y axis of 2x2 basis matrix containing 2 [Vector2] as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> </members> <constants> @@ -55584,8 +55756,10 @@ </class> <class name="Variant" category="Core"> <brief_description> + The most important data type in Godot. </brief_description> <description> + A Variant takes up only 20 bytes and can store almost any engine datatype inside of it. Variants are rarely used to hold information for long periods of time, instead they are used mainly for communication, editing, serialization and moving data around. </description> <methods> </methods> diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index b85a63a44a..6b8038c3ef 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -221,37 +221,24 @@ Error DirAccessUnix::change_dir(String p_dir) { if (prev_dir.parse_utf8(real_current_dir_name)) prev_dir = real_current_dir_name; //no utf8, maybe latin? - //print_line("directory we are changing out of (prev_dir): " + prev_dir); - // try_dir is the directory we are trying to change into String try_dir = ""; if (p_dir.is_rel_path()) { String next_dir = current_dir + "/" + p_dir; - //print_line("p_dir is relative: " + p_dir + " about to simplfy: " + next_dir); next_dir = next_dir.simplify_path(); try_dir = next_dir; } else { try_dir = p_dir; - //print_line("p_dir is absolute: " + p_dir); - } - - // if try_dir is nothing, it is not changing directory so change it to a "." otherwise chdir will fail - if (try_dir == "") { - try_dir = "."; } - //print_line("directory we are changing in to (try_dir): " + try_dir); - bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8 if (!worked) { - //print_line("directory does not exist"); return ERR_INVALID_PARAMETER; } // the directory exists, so set current_dir to try_dir current_dir = try_dir; chdir(prev_dir.utf8().get_data()); - //print_line("directory exists, setting current_dir to: " + current_dir); return OK; } @@ -319,11 +306,16 @@ size_t DirAccessUnix::get_space_left() { DirAccessUnix::DirAccessUnix() { dir_stream = 0; - current_dir = "."; _cisdir = false; /* determine drive count */ + // set current directory to an absolute path of the current directory + char real_current_dir_name[2048]; + getcwd(real_current_dir_name, 2048); + if (current_dir.parse_utf8(real_current_dir_name)) + current_dir = real_current_dir_name; + change_dir(current_dir); } diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 890c3d8091..54bf31cd62 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -489,6 +489,7 @@ DependencyErrorDialog::DependencyErrorDialog() { vb->add_margin_child(TTR("Scene failed to load due to missing dependencies:"), files, true); files->set_v_size_flags(SIZE_EXPAND_FILL); get_ok()->set_text(TTR("Open Anyway")); + get_cancel()->set_text(TTR("Done")); text = memnew(Label); vb->add_child(text); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 323a36154e..da53b06516 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -449,13 +449,15 @@ struct ProjectItem { String conf; uint64_t last_modified; bool favorite; + bool grayed; ProjectItem() {} - ProjectItem(const String &p_project, const String &p_path, const String &p_conf, uint64_t p_last_modified, bool p_favorite = false) { + ProjectItem(const String &p_project, const String &p_path, const String &p_conf, uint64_t p_last_modified, bool p_favorite = false, bool p_grayed = false) { project = p_project; path = p_path; conf = p_conf; last_modified = p_last_modified; favorite = p_favorite; + grayed = p_grayed; } _FORCE_INLINE_ bool operator<(const ProjectItem &l) const { return last_modified > l.last_modified; } _FORCE_INLINE_ bool operator==(const ProjectItem &l) const { return project == l.project; } @@ -737,6 +739,7 @@ void ProjectManager::_load_recent_projects() { String project = _name.get_slice("/", 1); String conf = path.plus_file("project.godot"); bool favorite = (_name.begins_with("favorite_projects/")) ? true : false; + bool grayed = false; uint64_t last_modified = 0; if (FileAccess::exists(conf)) { @@ -748,16 +751,15 @@ void ProjectManager::_load_recent_projects() { if (cache_modified > last_modified) last_modified = cache_modified; } - - ProjectItem item(project, path, conf, last_modified, favorite); - if (favorite) - favorite_projects.push_back(item); - else - projects.push_back(item); } else { - //project doesn't exist on disk but it's in the XML settings file - EditorSettings::get_singleton()->erase(_name); //remove it + grayed = true; } + + ProjectItem item(project, path, conf, last_modified, favorite, grayed); + if (favorite) + favorite_projects.push_back(item); + else + projects.push_back(item); } projects.sort(); @@ -782,14 +784,14 @@ void ProjectManager::_load_recent_projects() { String path = item.path; String conf = item.conf; bool is_favorite = item.favorite; + bool is_grayed = item.grayed; Ref<ConfigFile> cf = memnew(ConfigFile); - Error err = cf->load(conf); - ERR_CONTINUE(err != OK); + Error cf_err = cf->load(conf); String project_name = TTR("Unnamed Project"); - if (cf->has_section_key("application", "config/name")) { + if (cf_err == OK && cf->has_section_key("application", "config/name")) { project_name = static_cast<String>(cf->get_value("application", "config/name")).xml_unescape(); } @@ -797,7 +799,7 @@ void ProjectManager::_load_recent_projects() { continue; Ref<Texture> icon; - if (cf->has_section_key("application", "config/icon")) { + if (cf_err == OK && cf->has_section_key("application", "config/icon")) { String appicon = cf->get_value("application", "config/icon"); if (appicon != "") { Ref<Image> img; @@ -819,8 +821,10 @@ void ProjectManager::_load_recent_projects() { } String main_scene; - if (cf->has_section_key("application", "run/main_scene")) { + if (cf_err == OK && cf->has_section_key("application", "run/main_scene")) { main_scene = cf->get_value("application", "run/main_scene"); + } else { + main_scene = ""; } selected_list_copy.erase(project); @@ -848,6 +852,8 @@ void ProjectManager::_load_recent_projects() { hb->add_child(tf); VBoxContainer *vb = memnew(VBoxContainer); + if (is_grayed) + vb->set_modulate(Color(0.5, 0.5, 0.5)); vb->set_name("project"); vb->set_h_size_flags(SIZE_EXPAND_FILL); hb->add_child(vb); @@ -926,6 +932,13 @@ void ProjectManager::_open_project_confirm() { for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { const String &selected = E->key(); String path = EditorSettings::get_singleton()->get("projects/" + selected); + String conf = path + "/project.godot"; + if (!FileAccess::exists(conf)) { + dialog_error->set_text(TTR("Can't open project")); + dialog_error->popup_centered_minsize(); + return; + } + print_line("OPENING: " + path + " (" + selected + ")"); List<String> args; @@ -1397,6 +1410,9 @@ ProjectManager::ProjectManager() { run_error_diag = memnew(AcceptDialog); gui_base->add_child(run_error_diag); run_error_diag->set_title(TTR("Can't run project")); + + dialog_error = memnew(AcceptDialog); + gui_base->add_child(dialog_error); } ProjectManager::~ProjectManager() { diff --git a/editor/project_manager.h b/editor/project_manager.h index ecc01955ba..9fcf10a0d9 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -58,6 +58,7 @@ class ProjectManager : public Control { ConfirmationDialog *multi_run_ask; ConfirmationDialog *multi_scan_ask; AcceptDialog *run_error_diag; + AcceptDialog *dialog_error; NewProjectDialog *npdialog; ScrollContainer *scroll; VBoxContainer *scroll_childs; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index afdf48b314..4fe24a2eff 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -823,7 +823,14 @@ Node *SceneTreeDock::_duplicate(Node *p_node, Map<Node *, Node *> &duplimap) { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; String name = E->get().name; - node->set(name, p_node->get(name)); + Variant value = p_node->get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + node->set(name, value); } List<Node::GroupInfo> group_info; diff --git a/misc/dist/html/default.html b/misc/dist/html/default.html new file mode 100644 index 0000000000..9fae34f97e --- /dev/null +++ b/misc/dist/html/default.html @@ -0,0 +1,386 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> +<head> + <meta charset="utf-8" /> + <title></title> + <style type="text/css"> + + body { + margin: 0; + border: 0 none; + padding: 0; + text-align: center; + background-color: #222226; + font-family: 'Noto Sans', Arial, sans-serif; + } + + + /* Godot Engine default theme style + * ================================ */ + + .godot { + color: #e0e0e0; + background-color: #3b3943; + background-image: linear-gradient(to bottom, #403e48, #35333c); + border: 1px solid #45434e; + box-shadow: 0 0 1px 1px #2f2d35; + } + + button.godot { + font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ + padding: 1px 5px; + background-color: #37353f; + background-image: linear-gradient(to bottom, #413e49, #3a3842); + border: 1px solid #514f5d; + border-radius: 1px; + box-shadow: 0 0 1px 1px #2a2930; + } + + button.godot:hover { + color: #f0f0f0; + background-color: #44414e; + background-image: linear-gradient(to bottom, #494652, #423f4c); + border: 1px solid #5a5667; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:active { + color: #fff; + background-color: #3e3b46; + background-image: linear-gradient(to bottom, #36343d, #413e49); + border: 1px solid #4f4c59; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:disabled { + color: rgba(230, 230, 230, 0.2); + background-color: #3d3d3d; + background-image: linear-gradient(to bottom, #434343, #393939); + border: 1px solid #474747; + box-shadow: 0 0 1px 1px #2d2b33; + } + + + /* Canvas / wrapper + * ================ */ + + #container { + display: inline-block; /* scale with canvas */ + vertical-align: top; /* prevent extra height */ + position: relative; /* root for absolutely positioned overlay */ + margin: 0; + border: 0 none; + padding: 0; + background-color: #0c0c0c; + } + + #canvas { + display: block; + margin: 0 auto; + color: white; + } + + #canvas:focus { + outline: none; + } + + + /* Status display + * ============== */ + + #status { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + /* don't consume click events - make children visible explicitly */ + visibility: hidden; + } + + #status-progress { + width: 244px; + height: 7px; + background-color: #38363A; + border: 1px solid #444246; + padding: 1px; + box-shadow: 0 0 2px 1px #1B1C22; + border-radius: 2px; + visibility: visible; + } + + #status-progress-inner { + height: 100%; + width: 0; + box-sizing: border-box; + transition: width 0.5s linear; + background-color: #202020; + border: 1px solid #222223; + box-shadow: 0 0 1px 1px #27282E; + border-radius: 3px; + } + + #status-indeterminate { + visibility: visible; + position: relative; + } + + #status-indeterminate > div { + width: 3px; + height: 0; + border-style: solid; + border-width: 6px 2px 0 2px; + border-color: #2b2b2b transparent transparent transparent; + transform-origin: center 14px; + position: absolute; + } + + #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } + #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } + #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } + #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } + #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } + #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } + #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } + #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } + + #status-notice { + margin: 0 100px; + line-height: 1.3; + visibility: visible; + padding: 4px 6px; + visibility: visible; + } + + + /* Debug output + * ============ */ + + #output-panel { + display: none; + max-width: 700px; + font-size: small; + margin: 6px auto 0; + padding: 0 4px 4px; + text-align: left; + line-height: 2.2; + } + + #output-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + #output-container { + padding: 6px; + background-color: #2c2a32; + box-shadow: inset 0 0 1px 1px #232127; + color: #bbb; + } + + #output-scroll { + line-height: 1; + height: 12em; + overflow-y: scroll; + white-space: pre-wrap; + font-size: small; + font-family: "Lucida Console", Monaco, monospace; + } + </style> +$GODOT_HEAD_INCLUDE +</head> +<body> + <div id="container"> + <canvas id="canvas" oncontextmenu="event.preventDefault();" width="640" height="480"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + <div id="status"> + <div id='status-progress' style='display: none;' oncontextmenu="event.preventDefault();"><div id ='status-progress-inner'></div></div> + <div id='status-indeterminate' style='display: none;' oncontextmenu="event.preventDefault();"> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <div id="status-notice" class="godot" style='display: none;'></div> + </div> + </div> + <div id="output-panel" class="godot"> + <div id="output-header"> + Output: + <button id='output-clear' class='godot' type='button' autocomplete='off'>Clear</button> + </div> + <div id="output-container"><div id="output-scroll"></div></div> + </div> + + <script type="text/javascript" src="$GODOT_BASENAME.js"></script> + <script type="text/javascript">//<![CDATA[ + + var game = new Engine; + + (function() { + + const BASENAME = '$GODOT_BASENAME'; + const MEMORY_SIZE = $GODOT_TOTAL_MEMORY; + const DEBUG_ENABLED = $GODOT_DEBUG_ENABLED; + const INDETERMINATE_STATUS_STEP_MS = 100; + + var container = document.getElementById('container'); + var canvas = document.getElementById('canvas'); + var statusProgress = document.getElementById('status-progress'); + var statusProgressInner = document.getElementById('status-progress-inner'); + var statusIndeterminate = document.getElementById('status-indeterminate'); + var statusNotice = document.getElementById('status-notice'); + + var initializing = true; + var statusMode = 'hidden'; + var indeterminiateStatusAnimationId = 0; + + setStatusMode('indeterminate'); + game.setCanvas(canvas); + game.setAsmjsMemorySize(MEMORY_SIZE); + + function setStatusMode(mode) { + + if (statusMode === mode || !initializing) + return; + [statusProgress, statusIndeterminate, statusNotice].forEach(elem => { + elem.style.display = 'none'; + }); + if (indeterminiateStatusAnimationId !== 0) { + cancelAnimationFrame(indeterminiateStatusAnimationId); + indeterminiateStatusAnimationId = 0; + } + switch (mode) { + case 'progress': + statusProgress.style.display = 'block'; + break; + case 'indeterminate': + statusIndeterminate.style.display = 'block'; + indeterminiateStatusAnimationId = requestAnimationFrame(animateStatusIndeterminate); + break; + case 'notice': + statusNotice.style.display = 'block'; + break; + case 'hidden': + break; + default: + throw new Error("Invalid status mode"); + } + statusMode = mode; + } + + function animateStatusIndeterminate(ms) { + var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8); + if (statusIndeterminate.children[i].style.borderTopColor == '') { + Array.prototype.slice.call(statusIndeterminate.children).forEach(child => { + child.style.borderTopColor = ''; + }); + statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; + } + requestAnimationFrame(animateStatusIndeterminate); + } + + function setStatusNotice(text) { + + while (statusNotice.lastChild) { + statusNotice.removeChild(statusNotice.lastChild); + } + var lines = text.split('\n'); + lines.forEach((line, index) => { + statusNotice.appendChild(document.createTextNode(line)); + statusNotice.appendChild(document.createElement('br')); + }); + }; + + game.setProgressFunc((current, total) => { + + if (total > 0) { + statusProgressInner.style.width = current/total * 100 + '%'; + setStatusMode('progress'); + if (current === total) { + // wait for progress bar animation + setTimeout(() => { + setStatusMode('indeterminate'); + }, 500); + } + } else { + setStatusMode('indeterminate'); + } + }); + + if (DEBUG_ENABLED) { + var outputRoot = document.getElementById("output-panel"); + var outputScroll = document.getElementById("output-scroll"); + var OUTPUT_MSG_COUNT_MAX = 400; + + document.getElementById('output-clear').addEventListener('click', () => { + while (outputScroll.firstChild) { + outputScroll.firstChild.remove(); + } + }); + + outputRoot.style.display = 'block'; + + function print(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + if (text.length <= 0) return; + while (outputScroll.childElementCount >= OUTPUT_MSG_COUNT_MAX) { + outputScroll.firstChild.remove(); + } + var msg = document.createElement("div"); + if (String.prototype.trim.call(text).startsWith("**ERROR**")) { + msg.style.color = "#d44"; + } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { + msg.style.color = "#ccc000"; + } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { + msg.style.color = "#c6d"; + } + msg.textContent = text; + var scrollToBottom = outputScroll.scrollHeight - (outputScroll.clientHeight + outputScroll.scrollTop) < 10; + outputScroll.appendChild(msg); + if (scrollToBottom) { + outputScroll.scrollTop = outputScroll.scrollHeight; + } + }; + + function printError(text) { + print('**ERROR**' + ":", text); + } + + game.setStdoutFunc(text => { + print(text); + console.log(text); + }); + + game.setStderrFunc(text => { + printError(text); + console.warn(text); + }); + } + + game.start(BASENAME + '.pck').then(() => { + setStatusMode('hidden'); + initializing = false; + }, err => { + if (DEBUG_ENABLED) + printError(err.message); + setStatusNotice(err.message); + setStatusMode('notice'); + initializing = false; + }); + })(); + //]]></script> +</body> +</html> diff --git a/misc/dist/html_fs/godotfs.js b/misc/dist/html_fs/godotfs.js deleted file mode 100644 index 676ee689fb..0000000000 --- a/misc/dist/html_fs/godotfs.js +++ /dev/null @@ -1,151 +0,0 @@ - -var Module; -if (typeof Module === 'undefined') Module = eval('(function() { try { return Module || {} } catch(e) { return {} } })()'); -if (!Module.expectedDataFileDownloads) { - Module.expectedDataFileDownloads = 0; - Module.finishedDataFileDownloads = 0; -} -Module.expectedDataFileDownloads++; -(function() { - - const PACK_FILE_NAME = '$GODOT_PACK_NAME'; - const PACK_FILE_SIZE = $GODOT_PACK_SIZE; - function fetchRemotePackage(packageName, callback, errback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', packageName, true); - xhr.responseType = 'arraybuffer'; - xhr.onprogress = function(event) { - var url = packageName; - if (event.loaded && event.total) { - if (!xhr.addedTotal) { - xhr.addedTotal = true; - if (!Module.dataFileDownloads) Module.dataFileDownloads = {}; - Module.dataFileDownloads[url] = { - loaded: event.loaded, - total: event.total - }; - } else { - Module.dataFileDownloads[url].loaded = event.loaded; - } - var total = 0; - var loaded = 0; - var num = 0; - for (var download in Module.dataFileDownloads) { - var data = Module.dataFileDownloads[download]; - total += data.total; - loaded += data.loaded; - num++; - } - total = Math.ceil(total * Module.expectedDataFileDownloads/num); - if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')'); - } else if (!Module.dataFileDownloads) { - if (Module['setStatus']) Module['setStatus']('Downloading data...'); - } - }; - xhr.onload = function(event) { - var packageData = xhr.response; - callback(packageData); - }; - xhr.send(null); - }; - - function handleError(error) { - console.error('package error:', error); - }; - - var fetched = null, fetchedCallback = null; - fetchRemotePackage(PACK_FILE_NAME, function(data) { - if (fetchedCallback) { - fetchedCallback(data); - fetchedCallback = null; - } else { - fetched = data; - } - }, handleError); - - function runWithFS() { - -function assert(check, msg) { - if (!check) throw msg + new Error().stack; -} - - function DataRequest(start, end, crunched, audio) { - this.start = start; - this.end = end; - this.crunched = crunched; - this.audio = audio; - } - DataRequest.prototype = { - requests: {}, - open: function(mode, name) { - this.name = name; - this.requests[name] = this; - Module['addRunDependency']('fp ' + this.name); - }, - send: function() {}, - onload: function() { - var byteArray = this.byteArray.subarray(this.start, this.end); - - this.finish(byteArray); - - }, - finish: function(byteArray) { - var that = this; - Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() { - Module['removeRunDependency']('fp ' + that.name); - }, function() { - if (that.audio) { - Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) - } else { - Module.printErr('Preloading file ' + that.name + ' failed'); - } - }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change - this.requests[this.name] = null; - }, - }; - new DataRequest(0, PACK_FILE_SIZE, 0, 0).open('GET', '/' + PACK_FILE_NAME); - - var PACKAGE_PATH; - if (typeof window === 'object') { - PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); - } else { - // worker - PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); - } - var PACKAGE_NAME = PACK_FILE_NAME; - var REMOTE_PACKAGE_NAME = PACK_FILE_NAME; - var PACKAGE_UUID = 'b39761ce-0348-4959-9b16-302ed8e1592e'; - - function processPackageData(arrayBuffer) { - Module.finishedDataFileDownloads++; - assert(arrayBuffer, 'Loading data file failed.'); - var byteArray = new Uint8Array(arrayBuffer); - var curr; - - // Reuse the bytearray from the XHR as the source for file reads. - DataRequest.prototype.byteArray = byteArray; - DataRequest.prototype.requests['/' + PACK_FILE_NAME].onload(); - Module['removeRunDependency']('datafile_datapack'); - - }; - Module['addRunDependency']('datafile_datapack'); - - if (!Module.preloadResults) Module.preloadResults = {}; - - Module.preloadResults[PACKAGE_NAME] = {fromCache: false}; - if (fetched) { - processPackageData(fetched); - fetched = null; - } else { - fetchedCallback = processPackageData; - } - - } - if (Module['calledRun']) { - runWithFS(); - } else { - if (!Module['preRun']) Module['preRun'] = []; - Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it - } - -})(); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index d809109987..559e9fa455 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -248,7 +248,7 @@ void unregister_gdnative_types() { memdelete(GDNativeCallRegistry::singleton); #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() && discoverer != NULL) { memdelete(discoverer); } #endif diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp index f0cfdd6258..04752dc71a 100644 --- a/modules/gdscript/gd_functions.cpp +++ b/modules/gdscript/gd_functions.cpp @@ -1177,20 +1177,28 @@ void GDFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, VALIDATE_ARG_COUNT(1); switch (p_args[0]->get_type()) { + case Variant::STRING: { + + String d = *p_args[0]; + r_ret = d.length(); + } break; case Variant::DICTIONARY: { + Dictionary d = *p_args[0]; r_ret = d.size(); } break; case Variant::ARRAY: { + Array d = *p_args[0]; r_ret = d.size(); } break; case Variant::POOL_BYTE_ARRAY: { + PoolVector<uint8_t> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_INT_ARRAY: { + PoolVector<int> d = *p_args[0]; r_ret = d.size(); } break; @@ -1200,14 +1208,14 @@ void GDFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, r_ret = d.size(); } break; case Variant::POOL_STRING_ARRAY: { + PoolVector<String> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_VECTOR2_ARRAY: { + PoolVector<Vector2> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_VECTOR3_ARRAY: { diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index b804863ee1..e282041745 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,33 +20,32 @@ for x in javascript_files: javascript_objects.append(env_javascript.Object(x)) env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""]) -env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"']) # output file name without file extension basename = "godot" + env["PROGSUFFIX"] target_dir = env.Dir("#bin") -js_file = target_dir.File(basename + ".js") -implicit_targets = [js_file] zip_dir = target_dir.Dir('.javascript_zip') -zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"]) +zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html') +implicit_targets = [] if env['wasm'] == 'yes': - wasm_file = target_dir.File(basename+'.wasm') - implicit_targets.append(wasm_file) - zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file)) + wasm = target_dir.File(basename + '.wasm') + implicit_targets.append(wasm) + zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm)) + prejs = env.File('pre_wasm.js') else: - asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')] - zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')] implicit_targets.extend(asmjs_files) + zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + prejs = env.File('pre_asmjs.js') -# HTML file must be the first target in the list -html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0] -Depends(html_file, "godot_shell.html") +js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0]; +zip_files.append(InstallAs(zip_dir.File('godot.js'), js)) -# Emscripten hardcodes file names, so replace common base name with -# placeholder while leaving extension; also change `.html.mem` to just `.mem` -fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html') +postjs = env.File('engine.js') +env.Depends(js, [prejs, postjs]) +env.Append(LINKFLAGS=['--pre-js', prejs.path]) +env.Append(LINKFLAGS=['--post-js', postjs.path]) -zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html)) -Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") +Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 5f066f1b15..bea8f156af 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -100,6 +100,7 @@ def configure(env): ## Link flags + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"']) env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) if (env['wasm'] == 'yes'): @@ -112,6 +113,7 @@ def configure(env): else: env.Append(LINKFLAGS=['-s', 'ASM_JS=1']) env.Append(LINKFLAGS=['--separate-asm']) + env.Append(LINKFLAGS=['--memory-init-file', '1']) # TODO: Move that to opus module's config if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"): diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js new file mode 100644 index 0000000000..552f5a7e02 --- /dev/null +++ b/platform/javascript/engine.js @@ -0,0 +1,366 @@ + return Module; + }, +}; + +(function() { + var engine = Engine; + + var USING_WASM = engine.USING_WASM; + var DOWNLOAD_ATTEMPTS_MAX = 4; + + var basePath = null; + var engineLoadPromise = null; + + var loadingFiles = {}; + + function getBasePath(path) { + + if (path.endsWith('/')) + path = path.slice(0, -1); + if (path.lastIndexOf('.') > path.lastIndexOf('/')) + path = path.slice(0, path.lastIndexOf('.')); + return path; + } + + function getBaseName(path) { + + path = getBasePath(path); + return path.slice(path.lastIndexOf('/') + 1); + } + + Engine = function Engine() { + + this.rtenv = null; + + var gameInitPromise = null; + var unloadAfterInit = true; + var memorySize = 268435456; + + var progressFunc = null; + var pckProgressTracker = {}; + var lastProgress = { loaded: 0, total: 0 }; + + var canvas = null; + var stdout = null; + var stderr = null; + + this.initGame = function(mainPack) { + + if (!gameInitPromise) { + + if (mainPack === undefined) { + if (basePath !== null) { + mainPack = basePath + '.pck'; + } else { + return Promise.reject(new Error("No main pack to load specified")); + } + } + if (basePath === null) + basePath = getBasePath(mainPack); + + gameInitPromise = Engine.initEngine().then( + instantiate.bind(this) + ); + var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; }); + gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) { + // resolve with pck + return new Uint8Array(values[0]); + }); + if (unloadAfterInit) + gameInitPromise.then(Engine.unloadEngine); + requestAnimationFrame(animateProgress); + } + return gameInitPromise; + }; + + function instantiate(initializer) { + + var rtenvOpts = { + noInitialRun: true, + thisProgram: getBaseName(basePath), + engine: this, + }; + if (typeof stdout === 'function') + rtenvOpts.print = stdout; + if (typeof stderr === 'function') + rtenvOpts.printErr = stderr; + if (typeof WebAssembly === 'object' && initializer instanceof WebAssembly.Module) { + rtenvOpts.instantiateWasm = function(imports, onSuccess) { + WebAssembly.instantiate(initializer, imports).then(function(result) { + onSuccess(result); + }); + return {}; + }; + } else if (initializer.asm && initializer.mem) { + rtenvOpts.asm = initializer.asm; + rtenvOpts.memoryInitializerRequest = initializer.mem; + rtenvOpts.TOTAL_MEMORY = memorySize; + } else { + throw new Error("Invalid initializer"); + } + + return new Promise(function(resolve, reject) { + rtenvOpts.onRuntimeInitialized = resolve; + rtenvOpts.onAbort = reject; + rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); + }); + } + + this.start = function(mainPack) { + + return this.initGame(mainPack).then(synchronousStart.bind(this)); + }; + + function synchronousStart(pckView) { + // TODO don't expect canvas when runninng as cli tool + if (canvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = canvas; + } else { + var firstCanvas = document.getElementsByTagName('canvas')[0]; + if (firstCanvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = firstCanvas; + } else { + throw new Error("No canvas found"); + } + } + + var actualCanvas = this.rtenv.canvas; + var context = false; + try { + context = actualCanvas.getContext('webgl2') || actualCanvas.getContext('experimental-webgl2'); + } catch (e) {} + if (!context) { + throw new Error("WebGL 2 not available"); + } + + // canvas can grab focus on click + if (actualCanvas.tabIndex < 0) { + actualCanvas.tabIndex = 0; + } + // necessary to calculate cursor coordinates correctly + actualCanvas.style.padding = 0; + actualCanvas.style.borderWidth = 0; + actualCanvas.style.borderStyle = 'none'; + // until context restoration is implemented + actualCanvas.addEventListener('webglcontextlost', function(ev) { + alert("WebGL context lost, please reload the page"); + ev.preventDefault(); + }, false); + + this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); + gameInitPromise = null; + this.rtenv.callMain(); + } + + this.setProgressFunc = function(func) { + progressFunc = func; + }; + + function animateProgress() { + + var loaded = 0; + var total = 0; + var totalIsValid = true; + var progressIsFinal = true; + + [loadingFiles, pckProgressTracker].forEach(function(tracker) { + Object.keys(tracker).forEach(function(file) { + if (!tracker[file].final) + progressIsFinal = false; + if (!totalIsValid || tracker[file].total === 0) { + totalIsValid = false; + total = 0; + } else { + total += tracker[file].total; + } + loaded += tracker[file].loaded; + }); + }); + if (loaded !== lastProgress.loaded || total !== lastProgress.total) { + lastProgress.loaded = loaded; + lastProgress.total = total; + if (typeof progressFunc === 'function') + progressFunc(loaded, total); + } + if (!progressIsFinal) + requestAnimationFrame(animateProgress); + } + + this.setCanvas = function(elem) { + canvas = elem; + }; + + this.setAsmjsMemorySize = function(size) { + memorySize = size; + }; + + this.setUnloadAfterInit = function(enabled) { + + if (enabled && !unloadAfterInit && gameInitPromise) { + gameInitPromise.then(Engine.unloadEngine); + } + unloadAfterInit = enabled; + }; + + this.setStdoutFunc = function(func) { + + var print = function(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + func(text); + }; + if (this.rtenv) + this.rtenv.print = print; + stdout = print; + }; + + this.setStderrFunc = function(func) { + + var printErr = function(text) { + if (arguments.length > 1) + text = Array.prototype.slice.call(arguments).join(" "); + func(text); + }; + if (this.rtenv) + this.rtenv.printErr = printErr; + stderr = printErr; + }; + + + }; // Engine() + + Engine.RuntimeEnvironment = engine.RuntimeEnvironment; + + Engine.initEngine = function(newBasePath) { + + if (newBasePath !== undefined) basePath = getBasePath(newBasePath); + if (engineLoadPromise === null) { + if (USING_WASM) { + if (typeof WebAssembly !== 'object') + return Promise.reject(new Error("Browser doesn't support WebAssembly")); + // TODO cache/retrieve module to/from idb + engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { + return WebAssembly.compile(xhr.response); + }); + } else { + var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) { + return asmjsModulePromise(xhr.response); + }); + var memPromise = loadPromise(basePath + '.mem'); + engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) { + return { asm: values[0], mem: values[1] }; + }); + } + engineLoadPromise = engineLoadPromise.catch(function(err) { + engineLoadPromise = null; + throw err; + }); + } + return engineLoadPromise; + }; + + function asmjsModulePromise(module) { + var elem = document.createElement('script'); + var script = new Blob([ + 'Engine.asm = (function() { var Module = {};', + module, + 'return Module.asm; })();' + ]); + var url = URL.createObjectURL(script); + elem.src = url; + return new Promise(function(resolve, reject) { + elem.addEventListener('load', function() { + URL.revokeObjectURL(url); + var asm = Engine.asm; + Engine.asm = undefined; + setTimeout(function() { + // delay to reclaim compilation memory + resolve(asm); + }, 1); + }); + elem.addEventListener('error', function() { + URL.revokeObjectURL(url); + reject("asm.js faiilure"); + }); + document.body.appendChild(elem); + }); + } + + Engine.unloadEngine = function() { + engineLoadPromise = null; + }; + + function loadPromise(file, tracker) { + if (tracker === undefined) + tracker = loadingFiles; + return new Promise(function(resolve, reject) { + loadXHR(resolve, reject, file, tracker); + }); + } + + function loadXHR(resolve, reject, file, tracker) { + + var xhr = new XMLHttpRequest; + xhr.open('GET', file); + if (!file.endsWith('.js')) { + xhr.responseType = 'arraybuffer'; + } + ['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { + xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); + }); + xhr.send(); + } + + function onXHREvent(resolve, reject, file, tracker, ev) { + + if (this.status >= 400) { + + if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + reject(new Error("Failed loading file '" + file + "': " + this.statusText)); + this.abort(); + return; + } else { + loadXHR(resolve, reject, file); + } + } + + switch (ev.type) { + case 'loadstart': + if (tracker[file] === undefined) { + tracker[file] = { + total: ev.total, + loaded: ev.loaded, + attempts: 0, + final: false, + }; + } + break; + + case 'progress': + tracker[file].loaded = ev.loaded; + tracker[file].total = ev.total; + break; + + case 'load': + tracker[file].final = true; + resolve(this); + break; + + case 'error': + case 'timeout': + if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + tracker[file].final = true; + reject(new Error("Failed loading file '" + file + "'")); + } else { + loadXHR(resolve, reject, file); + } + break; + + case 'abort': + tracker[file].final = true; + reject(new Error("Loading file '" + file + "' was aborted.")); + break; + } + } +})(); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 5a161dd0ab..4a97bf4c32 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -100,8 +100,8 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - current_line = current_line.replace("$GODOT_TMEM", itos(memory_mb * 1024 * 1024)); - current_line = current_line.replace("$GODOT_BASE", p_name); + current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024)); + current_line = current_line.replace("$GODOT_BASENAME", p_name); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); str_export += current_line + "\n"; @@ -114,28 +114,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re } } -void EditorExportPlatformJavaScript::_fix_fsloader_js(Vector<uint8_t> &p_js, const String &p_pack_name, uint64_t p_pack_size) { - - String str_template = String::utf8(reinterpret_cast<const char *>(p_js.ptr()), p_js.size()); - String str_export; - Vector<String> lines = str_template.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].find("$GODOT_PACK_NAME") != -1) { - str_export += lines[i].replace("$GODOT_PACK_NAME", p_pack_name); - } else if (lines[i].find("$GODOT_PACK_SIZE") != -1) { - str_export += lines[i].replace("$GODOT_PACK_SIZE", itos(p_pack_size)); - } else { - str_export += lines[i] + "\n"; - } - } - - CharString cs = str_export.utf8(); - p_js.resize(cs.length()); - for (int i = 0; i < cs.length(); i++) { - p_js[i] = cs[i]; - } -} - void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { if (p_preset->get("texture_format/s3tc")) { @@ -286,10 +264,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug); file = p_path.get_file(); - } else if (file == "godotfs.js") { - - _fix_fsloader_js(data, pck_path.get_file(), pack_size); - file = p_path.get_file().get_basename() + "fs.js"; } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; diff --git a/platform/javascript/godot_shell.html b/platform/javascript/godot_shell.html deleted file mode 100644 index ee7399a129..0000000000 --- a/platform/javascript/godot_shell.html +++ /dev/null @@ -1,347 +0,0 @@ -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> -<head> - <meta charset="utf-8" /> - <title></title> - <style type="text/css"> - body { - margin: 0; - border: 0 none; - padding: 0; - text-align: center; - background-color: #222226; - font-family: 'Droid Sans', Arial, sans-serif; - } - - - /* Godot Engine default theme style - * ================================ */ - - .godot { - color: #e0e0e0; - background-color: #3b3943; - background-image: linear-gradient(to bottom, #403e48, #35333c); - border: 1px solid #45434e; - box-shadow: 0 0 1px 1px #2f2d35; - } - - button.godot { - font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ - padding: 1px 5px; - background-color: #37353f; - background-image: linear-gradient(to bottom, #413e49, #3a3842); - border: 1px solid #514f5d; - border-radius: 1px; - box-shadow: 0 0 1px 1px #2a2930; - } - - button.godot:hover { - color: #f0f0f0; - background-color: #44414e; - background-image: linear-gradient(to bottom, #494652, #423f4c); - border: 1px solid #5a5667; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:active { - color: #fff; - background-color: #3e3b46; - background-image: linear-gradient(to bottom, #36343d, #413e49); - border: 1px solid #4f4c59; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:disabled { - color: rgba(230, 230, 230, 0.2); - background-color: #3d3d3d; - background-image: linear-gradient(to bottom, #434343, #393939); - border: 1px solid #474747; - box-shadow: 0 0 1px 1px #2d2b33; - } - - - /* Canvas / wrapper - * ================ */ - - #container { - display: inline-block; /* scale with canvas */ - vertical-align: top; /* prevent extra height */ - position: relative; /* root for absolutely positioned overlay */ - margin: 0; - border: 0 none; - padding: 0; - background-color: #0c0c0c; - } - - #canvas { - display: block; - margin: 0 auto; - /* canvas must have border and padding set to zero to - * calculate cursor coordinates correctly */ - border: 0 none; - padding: 0; - color: white; - } - - #canvas:focus { - outline: none; - } - - - /* Status display - * ============== */ - - #status-container { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - /* don't consume click events - make children visible explicitly */ - visibility: hidden; - } - - #status { - line-height: 1.3; - cursor: pointer; - visibility: visible; - padding: 4px 6px; - } - - - /* Debug output - * ============ */ - - #output-panel { - display: none; - max-width: 700px; - font-size: small; - margin: 6px auto 0; - padding: 0 4px 4px; - text-align: left; - line-height: 2.2; - } - - #output-header { - display: flex; - justify-content: space-between; - align-items: center; - } - - #output-container { - padding: 6px; - background-color: #2c2a32; - box-shadow: inset 0 0 1px 1px #232127; - color: #bbb; - } - - #output-scroll { - line-height: 1; - height: 12em; - overflow-y: scroll; - white-space: pre-wrap; - font-size: small; - font-family: "Lucida Console", Monaco, monospace; - } - </style> -$GODOT_HEAD_INCLUDE -</head> -<body> - <div id="container"> - <canvas id="canvas" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();"> - HTML5 canvas appears to be unsupported in the current browser.<br /> - Please try updating or use a different browser. - </canvas> - <div id="status-container"> - <span id="status" class="godot" onclick="this.style.visibility='hidden';">Downloading page...</span> - </div> - </div> - <div id="output-panel" class="godot"> - <div id="output-header"> - Output: - <button class="godot" type="button" autocomplete="off" onclick="Presentation.clearOutput();">Clear</button> - </div> - <div id="output-container"><div id="output-scroll"></div></div> - </div> - - <!-- Scripts --> - <script type="text/javascript">//<![CDATA[ - var Presentation = (function() { - var statusElement = document.getElementById("status"); - var canvasElement = document.getElementById("canvas"); - - var presentation = { - postRun: [], - setStatusVisible: function setStatusVisible(visible) { - statusElement.style.visibility = (visible ? "visible" : "hidden"); - }, - setStatus: function setStatus(text) { - if (text.length === 0) { - // emscripten sets empty string as status after "Running..." - // per timeout, but another status may have been set by then - if (Presentation.setStatus.lastText === "Running...") - Presentation.setStatusVisible(false); - return; - } - Presentation.setStatus.lastText = text; - while (statusElement.lastChild) { - statusElement.removeChild(statusElement.lastChild); - } - var lines = text.split("\n"); - lines.forEach(function(line, index) { - statusElement.appendChild(document.createTextNode(line)); - statusElement.appendChild(document.createElement("br")); - }); - var closeNote = document.createElement("span"); - closeNote.style.fontSize = "small"; - closeNote.textContent = "click to close"; - statusElement.appendChild(closeNote); - Presentation.setStatusVisible(true); - }, - isWebGL2Available: function isWebGL2Available() { - var context; - try { - context = canvasElement.getContext("webgl2") || canvasElement.getContext("experimental-webgl2"); - } catch (e) {} - return !!context; - }, - }; - - window.onerror = function(event) { presentation.setStatus("Failure during start-up\nSee JavaScript console") }; - - if ($GODOT_DEBUG_ENABLED) { // debugging enabled - var outputRoot = document.getElementById("output-panel"); - var outputElement = document.getElementById("output-scroll"); - const maxOutputMessages = 400; - - presentation.setOutputVisible = function setOutputVisible(visible) { - outputRoot.style.display = (visible ? "block" : "none"); - }; - presentation.clearOutput = function clearOutput() { - while (outputElement.firstChild) { - outputElement.firstChild.remove(); - } - }; - - presentation.setOutputVisible(true); - - presentation.print = function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - if (text.length <= 0) return; - while (outputElement.childElementCount >= maxOutputMessages) { - outputElement.firstChild.remove(); - } - var msg = document.createElement("div"); - if (String.prototype.trim.call(text).startsWith("**ERROR**") - || String.prototype.trim.call(text).startsWith("**EXCEPTION**")) { - msg.style.color = "#d44"; - } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { - msg.style.color = "#ccc000"; - } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { - msg.style.color = "#c6d"; - } - msg.textContent = text; - var scrollToBottom = outputElement.scrollHeight - (outputElement.clientHeight + outputElement.scrollTop) < 10; - outputElement.appendChild(msg); - if (scrollToBottom) { - outputElement.scrollTop = outputElement.scrollHeight; - } - }; - - presentation.postRun.push(function() { - window.onerror = function(event) { presentation.print("**EXCEPTION**:", event) }; - }); - - } else { - presentation.postRun.push(function() { window.onerror = null; }); - } - - return presentation; - })(); - - // Emscripten interface - var Module = (function() { - const BASE_NAME = '$GODOT_BASE'; - var module = { - thisProgram: BASE_NAME, - wasmBinaryFile: BASE_NAME + '.wasm', - TOTAL_MEMORY: $GODOT_TMEM, - print: function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.log(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print(text); - } - }, - printErr: function printErr(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.error(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print("**ERROR**:", text) - } - }, - canvas: document.getElementById("canvas"), - setStatus: function setStatus(text) { - var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); - var now = Date.now(); - if (m) { - if (now - Date.now() < 30) // if this is a progress update, skip it if too soon - return; - text = m[1]; - } - if (typeof Presentation !== "undefined" && typeof Presentation.setStatus == "function") { - Presentation.setStatus(text); - } - } - }; - - // As a default initial behavior, pop up an alert when WebGL context is lost. To make your - // application robust, you may want to override this behavior before shipping! - // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 - module.canvas.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false); - - if (typeof Presentation !== "undefined" && Presentation.postRun instanceof Array) { - module.postRun = Presentation.postRun; - } - - return module; - })(); - - if (!Presentation.isWebGL2Available()) { - Presentation.setStatus("WebGL 2 appears to be unsupported.\nPlease update browser and drivers."); - Presentation.preventLoading = true; - } else { - Presentation.setStatus("Downloading..."); - } - - if (Presentation.preventLoading) { - // prevent *fs.js and Emscripten's SCRIPT placeholder from loading any files - Presentation._XHR_send = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.send = function() {}; - Presentation._Node_appendChild = Node.prototype.appendChild; - Node.prototype.appendChild = function(node) { - if (!(node instanceof HTMLScriptElement)) { - return Presentation._Node_appendChild.call(this, node); - } - } - } - //]]></script> - <script type="text/javascript" src="$GODOT_BASEfs.js"></script> -{{{ SCRIPT }}} - <script type="text/javascript"> - if (Presentation.preventLoading) { - XMLHttpRequest.prototype.send = Presentation._XHR_send; - Node.prototype.appendChild = Presentation._Node_appendChild; - } - </script> -</body> -</html> diff --git a/platform/javascript/pre_asmjs.js b/platform/javascript/pre_asmjs.js new file mode 100644 index 0000000000..3c497721b6 --- /dev/null +++ b/platform/javascript/pre_asmjs.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: false, + RuntimeEnvironment: function(Module) { diff --git a/platform/javascript/pre_wasm.js b/platform/javascript/pre_wasm.js new file mode 100644 index 0000000000..be4383c8c9 --- /dev/null +++ b/platform/javascript/pre_wasm.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: true, + RuntimeEnvironment: function(Module) { diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index caf313190b..966dd88a2c 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -31,7 +31,6 @@ #include "arvr_nodes.h" #include "core/os/input.h" #include "servers/arvr/arvr_interface.h" -#include "servers/arvr/arvr_positional_tracker.h" #include "servers/arvr_server.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -142,6 +141,7 @@ void ARVRController::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joystick_axis", "axis"), &ARVRController::get_joystick_axis); ClassDB::bind_method(D_METHOD("get_is_active"), &ARVRController::get_is_active); + ClassDB::bind_method(D_METHOD("get_hand"), &ARVRController::get_hand); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::INT, "button"))); ADD_SIGNAL(MethodInfo("button_release", PropertyInfo(Variant::INT, "button"))); @@ -204,6 +204,19 @@ bool ARVRController::get_is_active() const { return is_active; }; +ARVRPositionalTracker::TrackerHand ARVRController::get_hand() const { + // get our ARVRServer + ARVRServer *arvr_server = ARVRServer::get_singleton(); + ERR_FAIL_NULL_V(arvr_server, ARVRPositionalTracker::TRACKER_HAND_UNKNOWN); + + ARVRPositionalTracker *tracker = arvr_server->find_by_type_and_id(ARVRServer::TRACKER_CONTROLLER, controller_id); + if (tracker == NULL) { + return ARVRPositionalTracker::TRACKER_HAND_UNKNOWN; + }; + + return tracker->get_hand(); +}; + String ARVRController::get_configuration_warning() const { if (!is_visible() || !is_inside_tree()) return String(); diff --git a/scene/3d/arvr_nodes.h b/scene/3d/arvr_nodes.h index 4c14be71b5..5269ec0248 100644 --- a/scene/3d/arvr_nodes.h +++ b/scene/3d/arvr_nodes.h @@ -33,6 +33,7 @@ #include "scene/3d/camera.h" #include "scene/3d/spatial.h" +#include "servers/arvr/arvr_positional_tracker.h" /** @author Bastiaan Olij <mux213@gmail.com> @@ -84,6 +85,7 @@ public: float get_joystick_axis(int p_axis) const; bool get_is_active() const; + ARVRPositionalTracker::TrackerHand get_hand() const; String get_configuration_warning() const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index c3d9d97c5a..a543dba9cb 100755 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -2117,7 +2117,15 @@ Node *Node::_duplicate(int p_flags) const { if (!(p_flags & DUPLICATE_SCRIPTS) && name == "script/script") continue; - node->set(name, get(name)); + Variant value = get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + + node->set(name, value); } node->set_name(get_name()); @@ -2199,7 +2207,16 @@ void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; String name = E->get().name; - node->set(name, get(name)); + + Variant value = get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + + node->set(name, value); } node->set_name(get_name()); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index a187692bcb..abe9a00c3f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -67,8 +67,8 @@ RID Material::get_rid() const { } void Material::_validate_property(PropertyInfo &property) const { - if (!_can_do_next_pass() && property.name=="next_pass") { - property.usage=0; + if (!_can_do_next_pass() && property.name == "next_pass") { + property.usage = 0; } } @@ -80,7 +80,7 @@ void Material::_bind_methods() { ClassDB::bind_method(D_METHOD("set_render_priority", "priority"), &Material::set_render_priority); ClassDB::bind_method(D_METHOD("get_render_priority"), &Material::get_render_priority); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "render_priority", PROPERTY_HINT_RANGE, itos(RENDER_PRIORITY_MIN) + "," + itos(RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RENDER_PRIORITY_MIN) + "," + itos(RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "next_pass", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_next_pass", "get_next_pass"); BIND_CONSTANT(RENDER_PRIORITY_MAX); @@ -212,7 +212,7 @@ void ShaderMaterial::get_argument_options(const StringName &p_function, int p_id bool ShaderMaterial::_can_do_next_pass() const { - return shader.is_valid() && shader->get_mode()==Shader::MODE_SPATIAL; + return shader.is_valid() && shader->get_mode() == Shader::MODE_SPATIAL; } ShaderMaterial::ShaderMaterial() { diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index aa7827a61a..04efe88102 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -761,7 +761,7 @@ Array ArrayMesh::surface_get_arrays(int p_surface) const { Array ArrayMesh::surface_get_blend_shape_arrays(int p_surface) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - return Array(); + return VisualServer::get_singleton()->mesh_surface_get_blend_shape_arrays(mesh, p_surface); } int ArrayMesh::get_surface_count() const { @@ -1010,6 +1010,8 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("surface_get_material", "surf_idx"), &ArrayMesh::surface_get_material); ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); + ClassDB::bind_method(D_METHOD("surface_get_arrays", "surf_idx"), &ArrayMesh::surface_get_arrays); + ClassDB::bind_method(D_METHOD("surface_get_blend_shape_arrays", "surf_idx"), &ArrayMesh::surface_get_blend_shape_arrays); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 53c6eb2d89..f4edb258b6 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -120,6 +120,7 @@ public: virtual int surface_get_array_len(int p_idx) const = 0; virtual int surface_get_array_index_len(int p_idx) const = 0; virtual Array surface_get_arrays(int p_surface) const = 0; + virtual Array surface_get_blend_shape_arrays(int p_surface) const = 0; virtual uint32_t surface_get_format(int p_idx) const = 0; virtual PrimitiveType surface_get_primitive_type(int p_idx) const = 0; virtual Ref<Material> surface_get_material(int p_idx) const = 0; @@ -174,7 +175,7 @@ public: void add_surface(uint32_t p_format, PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const Rect3 &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<Rect3> &p_bone_aabbs = Vector<Rect3>()); Array surface_get_arrays(int p_surface) const; - virtual Array surface_get_blend_shape_arrays(int p_surface) const; + Array surface_get_blend_shape_arrays(int p_surface) const; void add_blend_shape(const StringName &p_name); int get_blend_shape_count() const; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index cfc1468533..52e080bdc8 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -105,6 +105,15 @@ Array PrimitiveMesh::surface_get_arrays(int p_surface) const { return VisualServer::get_singleton()->mesh_surface_get_arrays(mesh, 0); } +Array PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, 1, Array()); + if (pending_request) { + _update(); + } + + return Array(); +} + uint32_t PrimitiveMesh::surface_get_format(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, 0); if (pending_request) { @@ -119,6 +128,8 @@ Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { } Ref<Material> PrimitiveMesh::surface_get_material(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, NULL); + return material; } @@ -151,6 +162,8 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material", "material"), &PrimitiveMesh::set_material); ClassDB::bind_method(D_METHOD("get_material"), &PrimitiveMesh::get_material); + ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_material", "get_material"); } @@ -168,6 +181,10 @@ Ref<Material> PrimitiveMesh::get_material() const { return material; } +Array PrimitiveMesh::get_mesh_arrays() const { + return surface_get_arrays(0); +} + PrimitiveMesh::PrimitiveMesh() { // defaults mesh = VisualServer::get_singleton()->mesh_create(); diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 34fb75a196..38a5695883 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -67,6 +67,7 @@ public: virtual int surface_get_array_len(int p_idx) const; virtual int surface_get_array_index_len(int p_idx) const; virtual Array surface_get_arrays(int p_surface) const; + virtual Array surface_get_blend_shape_arrays(int p_surface) const; virtual uint32_t surface_get_format(int p_idx) const; virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const; virtual Ref<Material> surface_get_material(int p_idx) const; @@ -78,6 +79,8 @@ public: void set_material(const Ref<Material> &p_material); Ref<Material> get_material() const; + Array get_mesh_arrays() const; + PrimitiveMesh(); ~PrimitiveMesh(); }; diff --git a/servers/arvr/arvr_positional_tracker.cpp b/servers/arvr/arvr_positional_tracker.cpp index 539bac6703..4ecd7a3898 100644 --- a/servers/arvr/arvr_positional_tracker.cpp +++ b/servers/arvr/arvr_positional_tracker.cpp @@ -31,6 +31,10 @@ #include "core/os/input.h" void ARVRPositionalTracker::_bind_methods() { + BIND_ENUM_CONSTANT(TRACKER_HAND_UNKNOWN); + BIND_ENUM_CONSTANT(TRACKER_LEFT_HAND); + BIND_ENUM_CONSTANT(TRACKER_RIGHT_HAND); + // this class is read only from GDScript, so we only have access to getters.. ClassDB::bind_method(D_METHOD("get_type"), &ARVRPositionalTracker::get_type); ClassDB::bind_method(D_METHOD("get_name"), &ARVRPositionalTracker::get_name); @@ -39,6 +43,7 @@ void ARVRPositionalTracker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_orientation"), &ARVRPositionalTracker::get_orientation); ClassDB::bind_method(D_METHOD("get_tracks_position"), &ARVRPositionalTracker::get_tracks_position); ClassDB::bind_method(D_METHOD("get_position"), &ARVRPositionalTracker::get_position); + ClassDB::bind_method(D_METHOD("get_hand"), &ARVRPositionalTracker::get_hand); ClassDB::bind_method(D_METHOD("get_transform", "adjust_by_reference_frame"), &ARVRPositionalTracker::get_transform); // these functions we don't want to expose to normal users but do need to be callable from GDNative @@ -141,6 +146,14 @@ Vector3 ARVRPositionalTracker::get_rw_position() const { return rw_position; }; +ARVRPositionalTracker::TrackerHand ARVRPositionalTracker::get_hand() const { + return hand; +}; + +void ARVRPositionalTracker::set_hand(const ARVRPositionalTracker::TrackerHand p_hand) { + hand = p_hand; +}; + Transform ARVRPositionalTracker::get_transform(bool p_adjust_by_reference_frame) const { Transform new_transform; @@ -164,6 +177,7 @@ ARVRPositionalTracker::ARVRPositionalTracker() { tracker_id = 0; tracks_orientation = false; tracks_position = false; + hand = TRACKER_HAND_UNKNOWN; }; ARVRPositionalTracker::~ARVRPositionalTracker(){ diff --git a/servers/arvr/arvr_positional_tracker.h b/servers/arvr/arvr_positional_tracker.h index f91f862ba3..ff0c150f89 100644 --- a/servers/arvr/arvr_positional_tracker.h +++ b/servers/arvr/arvr_positional_tracker.h @@ -48,6 +48,13 @@ class ARVRPositionalTracker : public Object { GDCLASS(ARVRPositionalTracker, Object); _THREAD_SAFE_CLASS_ +public: + enum TrackerHand { + TRACKER_HAND_UNKNOWN, /* unknown or not applicable */ + TRACKER_LEFT_HAND, /* controller is the left hand controller */ + TRACKER_RIGHT_HAND /* controller is the right hand controller */ + }; + private: ARVRServer::TrackerType type; // type of tracker StringName name; // (unique) name of the tracker @@ -57,6 +64,7 @@ private: Basis orientation; // our orientation bool tracks_position; // do we track position? Vector3 rw_position; // our position "in the real world, so without world_scale applied" + TrackerHand hand; // if known, the hand this tracker is held in protected: static void _bind_methods(); @@ -77,6 +85,8 @@ public: Vector3 get_position() const; // get position with world_scale applied void set_rw_position(const Vector3 &p_rw_position); Vector3 get_rw_position() const; + ARVRPositionalTracker::TrackerHand get_hand() const; + void set_hand(const ARVRPositionalTracker::TrackerHand p_hand); Transform get_transform(bool p_adjust_by_reference_frame) const; @@ -84,4 +94,6 @@ public: ~ARVRPositionalTracker(); }; +VARIANT_ENUM_CAST(ARVRPositionalTracker::TrackerHand); + #endif diff --git a/servers/arvr_server.cpp b/servers/arvr_server.cpp index bac24f6438..3308e1cc07 100644 --- a/servers/arvr_server.cpp +++ b/servers/arvr_server.cpp @@ -68,8 +68,8 @@ void ARVRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("interface_added", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("interface_removed", PropertyInfo(Variant::STRING, "name"))); - ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"))); - ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING, "name"))); + ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id"))); }; real_t ARVRServer::get_world_scale() const { @@ -232,7 +232,7 @@ void ARVRServer::add_tracker(ARVRPositionalTracker *p_tracker) { ERR_FAIL_NULL(p_tracker); trackers.push_back(p_tracker); - emit_signal("tracker_added", p_tracker->get_name(), p_tracker->get_type()); + emit_signal("tracker_added", p_tracker->get_name(), p_tracker->get_type(), p_tracker->get_tracker_id()); }; void ARVRServer::remove_tracker(ARVRPositionalTracker *p_tracker) { @@ -250,7 +250,7 @@ void ARVRServer::remove_tracker(ARVRPositionalTracker *p_tracker) { ERR_FAIL_COND(idx == -1); - emit_signal("tracker_removed", p_tracker->get_name()); + emit_signal("tracker_removed", p_tracker->get_name(), p_tracker->get_type(), p_tracker->get_tracker_id()); trackers.remove(idx); }; diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 67b847d127..47a5f4c7f3 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1420,6 +1420,29 @@ Array VisualServer::mesh_surface_get_arrays(RID p_mesh, int p_surface) const { return _get_array_from_surface(format, vertex_data, vertex_len, index_data, index_len); } +Array VisualServer::mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_surface) const { + + Vector<PoolVector<uint8_t> > blend_shape_data = mesh_surface_get_blend_shapes(p_mesh, p_surface); + if (blend_shape_data.size() > 0) { + int vertex_len = mesh_surface_get_array_len(p_mesh, p_surface); + + PoolVector<uint8_t> index_data = mesh_surface_get_index_array(p_mesh, p_surface); + int index_len = mesh_surface_get_array_index_len(p_mesh, p_surface); + + uint32_t format = mesh_surface_get_format(p_mesh, p_surface); + + Array blend_shape_array; + blend_shape_array.resize(blend_shape_data.size()); + for (int i = 0; i < blend_shape_data.size(); i++) { + blend_shape_array.set(i, _get_array_from_surface(format, blend_shape_data[i], vertex_len, index_data, index_len)); + } + + return blend_shape_array; + } else { + return Array(); + } +} + void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("force_draw"), &VisualServer::draw); diff --git a/servers/visual_server.h b/servers/visual_server.h index 7494820287..72f36f6b65 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -267,6 +267,7 @@ public: virtual PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const = 0; virtual Array mesh_surface_get_arrays(RID p_mesh, int p_surface) const; + virtual Array mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_surface) const; virtual uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const = 0; virtual PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const = 0; |