├── icon.png ├── smooth_icon.png ├── addons └── smoothing │ ├── smoothing.png │ ├── smoothing_2d.png │ ├── plugin.cfg │ ├── smoothing_plugin.gd │ ├── smoothing.png.import │ ├── smoothing_2d.png.import │ ├── LICENSE │ ├── smoothing.gd │ └── smoothing_2d.gd ├── default_env.tres ├── Target2D.gd ├── Target2D_flipped.gd ├── Root.gd ├── export_presets.cfg ├── icon.png.import ├── smooth_icon.png.import ├── project.godot ├── Target.gd ├── LICENSE ├── Root.tscn └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lawnjelly/smoothing-addon/HEAD/icon.png -------------------------------------------------------------------------------- /smooth_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lawnjelly/smoothing-addon/HEAD/smooth_icon.png -------------------------------------------------------------------------------- /addons/smoothing/smoothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lawnjelly/smoothing-addon/HEAD/addons/smoothing/smoothing.png -------------------------------------------------------------------------------- /addons/smoothing/smoothing_2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lawnjelly/smoothing-addon/HEAD/addons/smoothing/smoothing_2d.png -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /addons/smoothing/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Smoothing" 4 | description="Smoothing nodes for fixed timestep interpolation." 5 | author="Lawnjelly" 6 | version="1.2.1" 7 | script="smoothing_plugin.gd" 8 | -------------------------------------------------------------------------------- /Target2D.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const MOVE_DIST = 100 4 | var m_Dir = MOVE_DIST 5 | 6 | func _physics_process(_delta): 7 | var x = get_position().x 8 | 9 | if x > 1000: 10 | m_Dir = -MOVE_DIST 11 | if x < 0: 12 | m_Dir = MOVE_DIST 13 | 14 | x += m_Dir * _delta 15 | 16 | position.x = x 17 | 18 | rotate(_delta) 19 | 20 | var sc = x / 1000.0 21 | 22 | set_scale(Vector2(sc * 3, (1.0 - sc) * 3)) 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /Target2D_flipped.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var _x = 0 4 | var _right = true 5 | 6 | func _ready(): 7 | position = Vector2(200, 100) 8 | set_scale(Vector2(-1, 1)) 9 | 10 | 11 | func _physics_process(delta): 12 | 13 | if _right: 14 | _x += 100 15 | if _x >= 800: 16 | _right = false 17 | set_scale(Vector2(1, 1)) 18 | else: 19 | _x -= 100 20 | if _x <= 200: 21 | _right = true 22 | set_scale(Vector2(-1, 1)) 23 | 24 | position.x = _x 25 | 26 | pass 27 | -------------------------------------------------------------------------------- /Root.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | 4 | # Called every frame. 'delta' is the elapsed time since the previous frame. 5 | func _process(_delta): 6 | if Input.is_action_just_pressed("ui_cancel"): 7 | get_tree().quit() 8 | 9 | if Input.is_action_just_pressed("ui_select"): 10 | $Example3D/Target.translation = Vector3(0, 0, 0) 11 | $Example3D/Target/Smoothing.teleport() 12 | 13 | $Example2D/Target2D.position = Vector2(300, 300) 14 | $Example2D/Target2D/Smoothing2D.teleport() 15 | -------------------------------------------------------------------------------- /addons/smoothing/smoothing_plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | 5 | func _enter_tree(): 6 | # Initialization of the plugin goes here 7 | # Add the new type with a name, a parent type, a script and an icon 8 | add_custom_type("Smoothing", "Spatial", preload("smoothing.gd"), preload("smoothing.png")) 9 | add_custom_type("Smoothing2D", "Node2D", preload("smoothing_2d.gd"), preload("smoothing_2d.png")) 10 | pass 11 | 12 | 13 | func _exit_tree(): 14 | # Clean-up of the plugin goes here 15 | # Always remember to remove it from the engine when deactivated 16 | remove_custom_type("Smoothing") 17 | remove_custom_type("Smoothing2D") 18 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Linux/X11" 4 | platform="Linux/X11" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | export_path="/home/baron/Pel/Godot/Export/Tests/SmoothPlug/Smooth Plug.x86_64" 11 | patch_list=PoolStringArray( ) 12 | script_export_mode=1 13 | script_encryption_key="" 14 | 15 | [preset.0.options] 16 | 17 | texture_format/bptc=false 18 | texture_format/s3tc=true 19 | texture_format/etc=false 20 | texture_format/etc2=false 21 | texture_format/no_bptc_fallbacks=true 22 | binary_format/64_bits=true 23 | custom_template/release="/home/baron/Apps/Fork/godot/bin/godot.x11.opt.64" 24 | custom_template/debug="" 25 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /smooth_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/smooth_icon.png-11111123e491159fd0c2d41967dd2dbc.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://smooth_icon.png" 13 | dest_files=[ "res://.import/smooth_icon.png-11111123e491159fd0c2d41967dd2dbc.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /addons/smoothing/smoothing.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/smoothing.png-6b454a779e636eaa20b6c6ac618bf82a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/smoothing/smoothing.png" 13 | dest_files=[ "res://.import/smoothing.png-6b454a779e636eaa20b6c6ac618bf82a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /addons/smoothing/smoothing_2d.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/smoothing_2d.png-4942c58db397caab18506104d957cac1.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/smoothing/smoothing_2d.png" 13 | dest_files=[ "res://.import/smoothing_2d.png-4942c58db397caab18506104d957cac1.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | } 14 | 15 | [application] 16 | 17 | config/name="Smoothing" 18 | run/main_scene="res://Root.tscn" 19 | config/icon="res://smooth_icon.png" 20 | 21 | [editor_plugins] 22 | 23 | enabled=PoolStringArray( "smoothing" ) 24 | 25 | [physics] 26 | 27 | common/physics_fps=2 28 | common/physics_jitter_fix=0.0 29 | common/enable_object_picking=false 30 | 31 | [rendering] 32 | 33 | quality/directional_shadow/size=640 34 | quality/shadow_atlas/size=640 35 | quality/depth_prepass/enable=false 36 | environment/default_environment="res://default_env.tres" 37 | -------------------------------------------------------------------------------- /Target.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | var m_Dir = 1 4 | var m_Scale = Vector3(1, 1, 1) 5 | var m_Angle = 0.0 6 | 7 | 8 | func _physics_process(_delta): 9 | var tr = transform 10 | 11 | var x = tr.origin.x 12 | if x >= 5: 13 | m_Dir = -1 14 | if x <= -5: 15 | m_Dir = +1 16 | 17 | x += m_Dir * _delta 18 | tr.origin.x = x 19 | 20 | m_Angle += _delta 21 | if m_Angle > (PI*2): 22 | m_Angle -= PI*2 23 | 24 | var rotvec = Vector3(1, 0.5, 0.2) 25 | rotvec = rotvec.normalized() 26 | 27 | tr.basis = Basis(rotvec, m_Angle) 28 | 29 | m_Scale.x = rand_scale(m_Scale.x) 30 | m_Scale.y = rand_scale(m_Scale.y) 31 | m_Scale.z = rand_scale(m_Scale.z) 32 | 33 | #m_Scale = Vector3(0.5, 0.5, 0.5) 34 | #tr.basis = tr.basis.scaled(m_Scale) 35 | #scale_object_local(m_Scale) 36 | 37 | transform = tr 38 | 39 | pass 40 | 41 | func rand_scale(var v): 42 | v = v + (randf() - 0.5) * 0.2 43 | v = clamp(v, 0.3, 3.0) 44 | return v 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lawnjelly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/smoothing/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lawnjelly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Root.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=16 format=2] 2 | 3 | [ext_resource path="res://addons/smoothing/smoothing.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/smoothing/smoothing.png" type="Texture" id=2] 5 | [ext_resource path="res://Target.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/smoothing/smoothing_2d.gd" type="Script" id=4] 7 | [ext_resource path="res://icon.png" type="Texture" id=5] 8 | [ext_resource path="res://Target2D.gd" type="Script" id=6] 9 | [ext_resource path="res://Root.gd" type="Script" id=7] 10 | [ext_resource path="res://addons/smoothing/smoothing_2d.png" type="Texture" id=8] 11 | [ext_resource path="res://smooth_icon.png" type="Texture" id=9] 12 | [ext_resource path="res://Target2D_flipped.gd" type="Script" id=10] 13 | 14 | [sub_resource type="CubeMesh" id=3] 15 | 16 | [sub_resource type="SpatialMaterial" id=4] 17 | albedo_color = Color( 0.537255, 0.643137, 1, 1 ) 18 | 19 | [sub_resource type="CubeMesh" id=1] 20 | 21 | [sub_resource type="SpatialMaterial" id=2] 22 | albedo_color = Color( 1, 0, 0, 1 ) 23 | 24 | [sub_resource type="Environment" id=5] 25 | 26 | [node name="Root" type="Spatial"] 27 | script = ExtResource( 7 ) 28 | 29 | [node name="Example2D" type="Node2D" parent="."] 30 | 31 | [node name="Target2D" type="Node2D" parent="Example2D"] 32 | position = Vector2( 0, 300 ) 33 | scale = Vector2( 2, 2 ) 34 | script = ExtResource( 6 ) 35 | 36 | [node name="Sprite" type="Sprite" parent="Example2D/Target2D"] 37 | texture = ExtResource( 5 ) 38 | 39 | [node name="Smoothing2D" type="Node2D" parent="Example2D/Target2D"] 40 | position = Vector2( 0, -150 ) 41 | scale = Vector2( 0.5, 0.5 ) 42 | script = ExtResource( 4 ) 43 | __meta__ = { 44 | "_editor_icon": ExtResource( 8 ) 45 | } 46 | flags = 63 47 | 48 | [node name="Sprite_smoothed" type="Sprite" parent="Example2D/Target2D/Smoothing2D"] 49 | texture = ExtResource( 5 ) 50 | 51 | [node name="Target2D_flipped" type="Node2D" parent="Example2D"] 52 | script = ExtResource( 10 ) 53 | 54 | [node name="Smoothing2D" type="Node2D" parent="Example2D/Target2D_flipped"] 55 | script = ExtResource( 4 ) 56 | 57 | [node name="Sprite" type="Sprite" parent="Example2D/Target2D_flipped/Smoothing2D"] 58 | texture = ExtResource( 9 ) 59 | 60 | [node name="Example3D" type="Spatial" parent="."] 61 | 62 | [node name="Target" type="Spatial" parent="Example3D"] 63 | script = ExtResource( 3 ) 64 | 65 | [node name="MeshInstance" type="MeshInstance" parent="Example3D/Target"] 66 | mesh = SubResource( 3 ) 67 | material/0 = SubResource( 4 ) 68 | 69 | [node name="Smoothing" type="Spatial" parent="Example3D/Target"] 70 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0 ) 71 | script = ExtResource( 1 ) 72 | __meta__ = { 73 | "_editor_icon": ExtResource( 2 ) 74 | } 75 | 76 | [node name="MeshInstance_smoothed" type="MeshInstance" parent="Example3D/Target/Smoothing"] 77 | mesh = SubResource( 1 ) 78 | material/0 = SubResource( 2 ) 79 | 80 | [node name="Camera" type="Camera" parent="."] 81 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 6.77548 ) 82 | 83 | [node name="DirectionalLight" type="DirectionalLight" parent="."] 84 | 85 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 86 | environment = SubResource( 5 ) 87 | -------------------------------------------------------------------------------- /addons/smoothing/smoothing.gd: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Lawnjelly 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | extends Spatial 22 | 23 | export (NodePath) var target: NodePath setget set_target, get_target 24 | 25 | var _m_Target: Spatial 26 | 27 | var _m_trCurr: Transform 28 | var _m_trPrev: Transform 29 | 30 | const SF_ENABLED = 1 << 0 31 | const SF_TRANSLATE = 1 << 1 32 | const SF_BASIS = 1 << 2 33 | const SF_SLERP = 1 << 3 34 | const SF_INVISIBLE = 1 << 4 35 | 36 | export (int, FLAGS, "enabled", "translate", "basis", "slerp") var flags: int = SF_ENABLED | SF_TRANSLATE | SF_BASIS setget _set_flags, _get_flags 37 | 38 | ########################################################################################## 39 | # USER FUNCS 40 | 41 | 42 | # call this on e.g. starting a level, AFTER moving the target 43 | # so we can update both the previous and current values 44 | func teleport(): 45 | var temp_flags = flags 46 | _SetFlags(SF_TRANSLATE | SF_BASIS) 47 | 48 | _RefreshTransform() 49 | _m_trPrev = _m_trCurr 50 | 51 | # do one frame update to make sure all components are updated 52 | _process(0) 53 | 54 | # resume old flags 55 | flags = temp_flags 56 | 57 | 58 | func set_enabled(bEnable: bool): 59 | _ChangeFlags(SF_ENABLED, bEnable) 60 | _SetProcessing() 61 | 62 | 63 | func is_enabled(): 64 | return _TestFlags(SF_ENABLED) 65 | 66 | 67 | ########################################################################################## 68 | 69 | 70 | func _ready(): 71 | _m_trCurr = Transform() 72 | _m_trPrev = Transform() 73 | set_process_priority(100) 74 | set_as_toplevel(true) 75 | Engine.set_physics_jitter_fix(0.0) 76 | 77 | 78 | func set_target(new_value): 79 | target = new_value 80 | if is_inside_tree(): 81 | _FindTarget() 82 | 83 | 84 | func get_target(): 85 | return target 86 | 87 | 88 | func _set_flags(new_value): 89 | flags = new_value 90 | # we may have enabled or disabled 91 | _SetProcessing() 92 | 93 | 94 | func _get_flags(): 95 | return flags 96 | 97 | 98 | func _SetProcessing(): 99 | var bEnable = _TestFlags(SF_ENABLED) 100 | if _TestFlags(SF_INVISIBLE): 101 | bEnable = false 102 | 103 | set_process(bEnable) 104 | set_physics_process(bEnable) 105 | pass 106 | 107 | 108 | func _enter_tree(): 109 | # might have been moved 110 | _FindTarget() 111 | pass 112 | 113 | 114 | func _notification(what): 115 | match what: 116 | # invisible turns off processing 117 | NOTIFICATION_VISIBILITY_CHANGED: 118 | _ChangeFlags(SF_INVISIBLE, is_visible_in_tree() == false) 119 | _SetProcessing() 120 | 121 | 122 | func _RefreshTransform(): 123 | if _HasTarget() == false: 124 | return 125 | 126 | _m_trPrev = _m_trCurr 127 | _m_trCurr = _m_Target.global_transform 128 | 129 | func _FindTarget(): 130 | _m_Target = null 131 | 132 | # If no target has been assigned in the property, 133 | # default to using the parent as the target. 134 | if target.is_empty(): 135 | var parent = get_parent_spatial() 136 | if parent: 137 | _m_Target = parent 138 | return 139 | 140 | var targ = get_node(target) 141 | 142 | if ! targ: 143 | printerr("ERROR SmoothingNode : Target " + target + " not found") 144 | return 145 | 146 | if not targ is Spatial: 147 | printerr("ERROR SmoothingNode : Target " + target + " is not spatial") 148 | target = "" 149 | return 150 | 151 | # if we got to here targ is a spatial 152 | _m_Target = targ 153 | 154 | # do a final check 155 | # certain targets are disallowed 156 | if _m_Target == self: 157 | var msg = _m_Target.get_name() + " assigned to " + self.get_name() + "]" 158 | printerr("ERROR SmoothingNode : Target should not be self [", msg) 159 | 160 | # error message 161 | #OS.alert("Target cannot be a parent or grandparent in the scene tree.", "SmoothingNode") 162 | _m_Target = null 163 | target = "" 164 | return 165 | 166 | 167 | func _HasTarget() -> bool: 168 | if _m_Target == null: 169 | return false 170 | 171 | # has not been deleted? 172 | if is_instance_valid(_m_Target): 173 | return true 174 | 175 | _m_Target = null 176 | return false 177 | 178 | 179 | func _process(_delta): 180 | 181 | var f = Engine.get_physics_interpolation_fraction() 182 | var tr: Transform = Transform() 183 | 184 | # translate 185 | if _TestFlags(SF_TRANSLATE): 186 | var ptDiff = _m_trCurr.origin - _m_trPrev.origin 187 | tr.origin = _m_trPrev.origin + (ptDiff * f) 188 | 189 | # rotate 190 | if _TestFlags(SF_BASIS): 191 | if _TestFlags(SF_SLERP): 192 | tr.basis = _m_trPrev.basis.slerp(_m_trCurr.basis, f) 193 | else: 194 | tr.basis = _LerpBasis(_m_trPrev.basis, _m_trCurr.basis, f) 195 | 196 | transform = tr 197 | 198 | 199 | func _physics_process(_delta): 200 | _RefreshTransform() 201 | 202 | 203 | func _LerpBasis(from: Basis, to: Basis, f: float) -> Basis: 204 | var res: Basis = Basis() 205 | res.x = from.x.linear_interpolate(to.x, f) 206 | res.y = from.y.linear_interpolate(to.y, f) 207 | res.z = from.z.linear_interpolate(to.z, f) 208 | return res 209 | 210 | 211 | func _SetFlags(f): 212 | flags |= f 213 | 214 | 215 | func _ClearFlags(f): 216 | flags &= ~f 217 | 218 | 219 | func _TestFlags(f): 220 | return (flags & f) == f 221 | 222 | 223 | func _ChangeFlags(f, bSet): 224 | if bSet: 225 | _SetFlags(f) 226 | else: 227 | _ClearFlags(f) 228 | -------------------------------------------------------------------------------- /addons/smoothing/smoothing_2d.gd: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Lawnjelly 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | extends Node2D 22 | 23 | export (NodePath) var target: NodePath setget set_target, get_target 24 | 25 | var _m_Target: Node2D 26 | var _m_Flip: bool = false 27 | 28 | var _m_Trans_curr: Transform2D = Transform2D() 29 | var _m_Trans_prev: Transform2D = Transform2D() 30 | 31 | const SF_ENABLED = 1 << 0 32 | const SF_GLOBAL_IN = 1 << 1 33 | const SF_GLOBAL_OUT = 1 << 2 34 | const SF_TOP_LEVEL = 1 << 3 35 | const SF_INVISIBLE = 1 << 4 36 | 37 | export (int, FLAGS, "enabled", "global in", "global out", "top level") var flags: int = SF_ENABLED | SF_GLOBAL_IN | SF_GLOBAL_OUT setget _set_flags, _get_flags 38 | 39 | ########################################################################################## 40 | # USER FUNCS 41 | 42 | 43 | # call this on e.g. starting a level, AFTER moving the target 44 | # so we can update both the previous and current values 45 | func teleport(): 46 | 47 | _RefreshTransform() 48 | _m_Trans_prev = _m_Trans_curr 49 | 50 | # call frame upate to make sure all components of the node are set 51 | _process(0) 52 | 53 | func set_enabled(bEnable: bool): 54 | _ChangeFlags(SF_ENABLED, bEnable) 55 | _SetProcessing() 56 | 57 | 58 | func is_enabled(): 59 | return _TestFlags(SF_ENABLED) 60 | 61 | 62 | ########################################################################################## 63 | 64 | 65 | func _ready(): 66 | set_process_priority(100) 67 | Engine.set_physics_jitter_fix(0.0) 68 | set_as_toplevel(_TestFlags(SF_TOP_LEVEL)) 69 | 70 | 71 | func set_target(new_value): 72 | target = new_value 73 | if is_inside_tree(): 74 | _FindTarget() 75 | 76 | 77 | func get_target(): 78 | return target 79 | 80 | 81 | func _set_flags(new_value): 82 | flags = new_value 83 | # we may have enabled or disabled 84 | _SetProcessing() 85 | 86 | 87 | func _get_flags(): 88 | return flags 89 | 90 | 91 | func _SetProcessing(): 92 | var bEnable = _TestFlags(SF_ENABLED) 93 | if _TestFlags(SF_INVISIBLE): 94 | bEnable = false 95 | 96 | set_process(bEnable) 97 | set_physics_process(bEnable) 98 | set_as_toplevel(_TestFlags(SF_TOP_LEVEL)) 99 | 100 | 101 | func _enter_tree(): 102 | # might have been moved 103 | _FindTarget() 104 | 105 | 106 | func _notification(what): 107 | match what: 108 | # invisible turns off processing 109 | NOTIFICATION_VISIBILITY_CHANGED: 110 | _ChangeFlags(SF_INVISIBLE, is_visible_in_tree() == false) 111 | _SetProcessing() 112 | 113 | 114 | func _RefreshTransform(): 115 | 116 | if _HasTarget() == false: 117 | return 118 | 119 | _m_Trans_prev = _m_Trans_curr 120 | 121 | if _TestFlags(SF_GLOBAL_IN): 122 | _m_Trans_curr = _m_Target.get_global_transform() 123 | else: 124 | _m_Trans_curr = _m_Target.get_transform() 125 | 126 | _m_Flip = false 127 | 128 | # Ideally we would use determinant core function, as in commented line below, but we 129 | # need to workaround for backward compat. 130 | # if (_m_Trans_prev.determinant() < 0) != (_m_Trans_curr.determinant() < 0): 131 | 132 | if (_Determinant_Sign(_m_Trans_prev) != _Determinant_Sign(_m_Trans_curr)): 133 | _m_Flip = true 134 | 135 | 136 | func _Determinant_Sign(t:Transform2D)->bool: 137 | # Workaround Transform2D determinant function not being available 138 | # until 3.6 / 4.1. 139 | # We calculate determinant manually, slower but compatible to lower 140 | # godot versions. 141 | var d = (t.x.x * t.y.y) - (t.x.y * t.y.x) 142 | return d >= 0.0 143 | 144 | 145 | func _FindTarget(): 146 | _m_Target = null 147 | 148 | # If no target has been assigned in the property, 149 | # default to using the parent as the target. 150 | if target.is_empty(): 151 | var parent = get_parent() 152 | if parent and (parent is Node2D): 153 | _m_Target = parent 154 | return 155 | 156 | var targ = get_node(target) 157 | 158 | if ! targ: 159 | printerr("ERROR SmoothingNode2D : Target " + target + " not found") 160 | return 161 | 162 | if not targ is Node2D: 163 | printerr("ERROR SmoothingNode2D : Target " + target + " is not Node2D") 164 | target = "" 165 | return 166 | 167 | # if we got to here targ is correct type 168 | _m_Target = targ 169 | 170 | 171 | func _HasTarget() -> bool: 172 | if _m_Target == null: 173 | return false 174 | 175 | # has not been deleted? 176 | if is_instance_valid(_m_Target): 177 | return true 178 | 179 | _m_Target = null 180 | return false 181 | 182 | 183 | func _process(_delta): 184 | 185 | var f = Engine.get_physics_interpolation_fraction() 186 | 187 | var tr = Transform2D() 188 | tr.origin = lerp(_m_Trans_prev.origin, _m_Trans_curr.origin, f) 189 | tr.x = lerp(_m_Trans_prev.x, _m_Trans_curr.x, f) 190 | tr.y = lerp(_m_Trans_prev.y, _m_Trans_curr.y, f) 191 | 192 | # When a sprite flip is detected, turn off interpolation for that tick. 193 | if _m_Flip: 194 | tr = _m_Trans_curr 195 | 196 | if _TestFlags(SF_GLOBAL_OUT) and not _TestFlags(SF_TOP_LEVEL): 197 | set_global_transform(tr) 198 | else: 199 | set_transform(tr) 200 | 201 | func _physics_process(_delta): 202 | _RefreshTransform() 203 | 204 | 205 | 206 | func _SetFlags(f): 207 | flags |= f 208 | 209 | 210 | func _ClearFlags(f): 211 | flags &= ~f 212 | 213 | 214 | func _TestFlags(f): 215 | return (flags & f) == f 216 | 217 | 218 | func _ChangeFlags(f, bSet): 219 | if bSet: 220 | _SetFlags(f) 221 | else: 222 | _ClearFlags(f) 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smoothing-addon v 1.2.2 2 | Fixed timestep interpolation gdscript addon for Godot 3.2 (and later versions) 3 | 4 | ### Update 5 | As of Godot 3.5, 3D physics interpolation is build into the engine, and as of 3.6 (beta 4 onward) 2D physics interpolation is built in. New users are recommended to use core interpolation, rather than this addon. 6 | 7 | https://docs.godotengine.org/en/3.6/tutorials/physics/interpolation/index.html 8 | 9 | However, the addon is maintained for compatibility purposes with existing games, and previous versions of the engine. 10 | 11 | ## Introduction 12 | 13 | If you were wondering how to use that new function `Engine.get_physics_interpolation_fraction()` in 3.2, feel free to use this as is, or to get ideas from for your own version. 14 | 15 | _If you find bugs / have suggestions, please add an issue to the issue tracker and I will look into it!_ :) 16 | 17 | The smoothing addon adds 2 new nodes to Godot, 'Smoothing' (for 3d) and 'Smoothing2d' (for 2d). They allow for fixed timestep interpolation without writing any code. See here for an explanation of fixed timestep interpolation:
18 | https://www.gamedev.net/blogs/entry/2265460-fixing-your-timestep-and-evaluating-godot/ 19 |
20 | https://www.youtube.com/watch?v=lWhHBAcH4sM 21 | 22 | ## Installation 23 | 24 | This repository contains the addon (in the addons folder) and an example demo project. 25 | 26 | To use the addon in your own project: 27 | 1. Create a new project in Godot or use an existing project. 28 | 2. Copy the 'addons' folder from this repository to your Godot project folder. 29 | 3. Go to 'Project Settings' plugins tab. 30 | 4. Find the smoothing plugin and set status to 'Active'. 31 | 32 | ## Explanation 33 | In a game you would usually choose to create a Node2D, Spatial, RigidBody, Kinematic body etc node for a game object, which is affected by physics and / or AI and / or player input. This I will refer to as the PHYSICS REP (representation). 34 | 35 | The visual respresentation of this object (VISUAL REP) is often simply a child of this node, such as a MeshInstance, or Sprite. That way it inherits the transform of the parent physics rep. When you move the physics rep, the transform propagates to the child node, the visual rep, and it renders in the same place as the physics rep. In some games the visual rep can even be the same node as the physics rep (particularly when there is no actual physics). 36 | 37 | Usually transforms propagate from a parent to child. Fixed timestep interpolation works slightly differently - the VisualRep indirectly _follows_ the transform of the PhysicsRep, rather than being directly affected by it. 38 | 39 | In your gameplay programming, 99% of the time you would usually be mostly concerned with the position and rotation of the physics rep. Aside a few things like visual effects, the visual rep will follow the physics rep, and you don't need to worry about it. This also means that providing you drive your gameplay using `_physics_process` rather than `_process`, your gameplay will run the same no matter what machine you run it on! Fantastic. 40 | 41 | ### Note 42 | The 3D smoothing node automatically calls `set_as_toplevel()` when in global mode. This ensures that it only follows the selected target, rather than having the transform controlled directly by the parent. The default target to follow will however be the parent node, if a `Target` has not been assigned in the inspector. 43 | 44 | In 2D, *flips* are supported. That is, if you use negative scaling to flip a sprite, the interpolation will detect this and turn off for a tick to get an instantaneous flip, instead of having the sprite "turn inside out". 45 | 46 | ## Usage 47 | 48 | ### 3D 49 | 1. You would usually in a game choose to create a Spatial, RigidBody, Kinematic body etc node for your physics rep, and have a visual representation (e.g. a MeshInstance) as a child of this node. 50 | 2. Do this as normal so that you can see the object moving in the game. 51 | 3. Add the new 'Smoothing' node to the scene, as a child of your physics rep. 52 | 4. Drag the visual representation from being a child of the physics rep, to being a child of the Smoothing node. 53 | 5. That is mainly it! Just run the game and now hopefully the visual representation will follow the gameobject, but now with interpolation. You can test this is working by running at a low physics tick rate (physics_fps in project settings->physics). 54 | 55 | ### 2D 56 | The procedure for 2D is pretty much the same as with 3D except you would be using a node derived from Node2D as the physics rep (target) and the 'Smoothing2D' node should be used instead of 'Smoothing'. 57 | 58 | In 2D, for legacy support, the smoothing node can be set to `toplevel` if the property flag is enabled (this defaults to disabled). `toplevel` enables the transform of the smoothing node to be specified in true global space (which is more stable, and may play better with GPU snapping), and makes things simpler because the smoothing node can be a direct child of a target. 59 | 60 | You are recommended to try `toplevel` mode, however there are two downsides which may preclude its use: 61 | 1. Parent node visibility is not automatically propagated to `toplevel` nodes, thus you have to explicitly hide the smoothing node, rather than rely on hiding just the parent node. 62 | 2. Y-sorting does not work correctly for `toplevel` nodes. 63 | 64 | ### Following targets that are not the parent node 65 | When not using `toplevel` mode in 2D, and in other problematic situations you may see jitter. In this case you may want to use the smoothing node's ability to follow targets that are not parent nodes. You can do this by assigning a `Target` in the inspector panel for the Smoothing node. 66 | 67 | In this situation you are highly recommended to place the smoothing node on a separate branch in the scene tree, preferably inheriting no transform from a parent node (i.e. all the parents and grandparents will have zero translate, no rotate, and 1:1 scale). 68 | 69 | e.g. Instead of: 70 | ``` 71 | Root 72 | PhysicsRep 73 | VisualRep (child of PhysicsRep) 74 | ``` 75 | The relationship becomes: 76 | ``` 77 | Root 78 | PhysicsRep 79 | VisualRep (child of Root) 80 | ``` 81 | To enable interpolation instead of relying on the scenetree transforms being propagated to children, we specifically tell the VisualRep to follow the PhysicsRep. This way it can follow the position and rotation of the PhysicsRep WITHOUT being directly affected by the transform of the PhysicsRep. 82 | 83 | This may sound overly complicated, but because of the maths involved, it is usually essential to getting a good result. 84 | 85 | ### Teleporting 86 | There is one special case when using smoothing nodes - the case when you want a target to instantaneously move from one location to another (for instance respawning a player, or at level start) where you do not want interpolation from the previous position. In this special case, you should move the target node (by setting the translation or position), and then call the 'teleport' function in the Smoothing node. This ensures that interpolation will be switched off temporarily for the move. 87 | _Make sure to call `teleport` AFTER moving the target node, rather than before._ 88 | 89 | ### Other options 90 | As well as choosing the Target, in the inspector for the Smoothing nodes there are a set of flags. 91 | 92 | #### 3D 93 | 1. enabled - chooses whether the smoothing node is active 94 | 2. translate - interpolation will be done for the position 95 | 3. basis - interpolation will be done for rotation and scale 96 | 4. slerp - this will do quaternion slerping instead of the simpler basis lerping. Note that this only works with no scaling applied to the target. 97 | 98 | #### 2D 99 | 1. enabled - as above 100 | 5. global in - will read the global transform of the target instead of local 101 | 6. global out - will set the global transform of the smoothing node instead of local 102 | 103 | (Local mode may be more efficient but you must understand the difference between local and global transforms.) 104 | 105 | ### Notes 106 | 107 | * Processing will also be turned off automatically for the smoothing nodes if they are hidden (either directly or through a parent). 108 | * You can also set the target for the smoothing node from script, using the `set_target` function and passing a NodePath argument (e.g. `mynode.get_path()`). 109 | * The best way to debug / develop smoothing is to set physics ticks per second (`ProjectSettings->Physics->Common->Physics_FPS`) to a low value (e.g. 10). Once you have it working you can set it back up to high value if desired. 110 | * If you encounter problems smoothing a `Camera2D` node, try setting the `ProcessMode` to `Idle` instead of `Physics`. 111 | * In order to prevent an unneeded extra delay of one tick, it is important that smoothing nodes are processed _AFTER_ target nodes. This should now be automatically taken care as the addon internally uses `process_priority` to achieve this. Previously we required smoothing nodes to be placed lower in the scene tree than the target. This should hopefully no longer be the case. 112 | * Fixed timestep interpolation may not work well in 2D pixel snapped games. For further info see https://github.com/lawnjelly/godot-snapping-demo . 113 | 114 | There is no need for JitterFix (`Project Settings->Physics->Common->Physics Jitter Fix`) when using fixed timestep interpolation, indeed it may interfere with getting a good result. The addon now enforces this by setting `Engine.set_physics_jitter_fix` to 0 as smoothing nodes are created. 115 | 116 | ### Authors 117 | Lawnjelly, Calinou 118 | 119 | __This addon is also available as a c++ module (slight differences), see:__ 120 | https://github.com/lawnjelly/godot-smooth 121 | 122 | ### Addendum 123 | Physics Interpolation is now available in core Godot as of 3.6, and you are encouraged to use core interpolation rather than addons wherever available. 124 | It should be: 125 | * Easier to use. 126 | * Introduces new 2D mode to get around `toplevel` problems. 127 | * More accurate (particularly for pivots). 128 | * Faster. 129 | 130 | See the official documentation for details. 131 | --------------------------------------------------------------------------------