diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..4399061 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build PNGTuber-Plus", + "type": "shell", + "command": "cd c:\\DEV\\Sync\\PNGTuber-Plus && .\\build-windows.bat", + "group": "build" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 7e973a1..87fa924 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![Build Status](https://github.com/litruv/PNGTuber-Plus/workflows/Build%20Windows/badge.svg) ### Recent Updates by Litruv +- **Opacity Controls**: Sprite opacity slider, with hierarchical "affect children" option that also preserves clip mask behavior - **GitHub Actions**: Automated Windows builds on every commit - **StreamDeck Integration**: Add buttons to enable the streamdeck plugin that was built in - **Rotation Wobble**: New sprite rotation effects with frequency and amplitude controls diff --git a/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll b/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll new file mode 100644 index 0000000..60e5fe6 Binary files /dev/null and b/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll differ diff --git a/bin/~libgdexample.windows.template_debug.x86_64.dll b/bin/~libgdexample.windows.template_debug.x86_64.dll new file mode 100644 index 0000000..c28b9ad Binary files /dev/null and b/bin/~libgdexample.windows.template_debug.x86_64.dll differ diff --git a/main_scenes/main.gd b/main_scenes/main.gd index 34441a6..56a7e63 100644 --- a/main_scenes/main.gd +++ b/main_scenes/main.gd @@ -153,17 +153,8 @@ func _process(delta): followShadow() func followShadow(): - shadow.visible = is_instance_valid(Global.heldSprite) - if !shadow.visible: - return - - shadow.global_position = Global.heldSprite.sprite.global_position + Vector2(6,6) - shadow.global_rotation = Global.heldSprite.sprite.global_rotation - shadow.offset = Global.heldSprite.sprite.offset - - shadow.texture = Global.heldSprite.sprite.texture - shadow.hframes = Global.heldSprite.sprite.hframes - shadow.frame = Global.heldSprite.sprite.frame + # Shadow disabled for selected sprites + shadow.visible = false func isFileSystemOpen(): @@ -362,6 +353,10 @@ func _on_load_dialog_file_selected(path): sprite.clipped = data[item]["clipped"] if data[item].has("toggle"): sprite.toggle = data[item]["toggle"] + if data[item].has("spriteOpacity"): + sprite.spriteOpacity = data[item]["spriteOpacity"] + if data[item].has("affectChildrenOpacity"): + sprite.affectChildrenOpacity = data[item]["affectChildrenOpacity"] origin.add_child(sprite) sprite.position = str_to_var(data[item]["pos"]) @@ -424,6 +419,9 @@ func _on_save_dialog_file_selected(path): data[id]["toggle"] = child.toggle + data[id]["spriteOpacity"] = child.spriteOpacity + data[id]["affectChildrenOpacity"] = child.affectChildrenOpacity + id += 1 Saving.settings["lastAvatar"] = path @@ -500,6 +498,9 @@ func _on_duplicate_button_pressed(): sprite.costumeLayers = Global.heldSprite.costumeLayers + sprite.spriteOpacity = Global.heldSprite.spriteOpacity + sprite.affectChildrenOpacity = Global.heldSprite.affectChildrenOpacity + origin.add_child(sprite) sprite.position = Global.heldSprite.position + Vector2(16,16) @@ -547,7 +548,7 @@ func moveSpriteMenu(delta): var size = get_viewport().get_visible_rect().size - var windowLength = 1400 + var windowLength = 1500 $ViewerArrows/Arrows.position.y = size.y - 25 @@ -673,4 +674,3 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed): if i >= 0: changeCostume(i+1) - diff --git a/ui_scenes/selectedSprite/opacity.gdshader b/ui_scenes/selectedSprite/opacity.gdshader new file mode 100644 index 0000000..8767a05 --- /dev/null +++ b/ui_scenes/selectedSprite/opacity.gdshader @@ -0,0 +1,8 @@ +shader_type canvas_item; + +uniform float opacity : hint_range(0.0, 1.0) = 1.0; + +void fragment() { + COLOR = texture(TEXTURE, UV); + COLOR.a *= opacity; +} diff --git a/ui_scenes/selectedSprite/opacity.gdshader.uid b/ui_scenes/selectedSprite/opacity.gdshader.uid new file mode 100644 index 0000000..0ccddcf --- /dev/null +++ b/ui_scenes/selectedSprite/opacity.gdshader.uid @@ -0,0 +1 @@ +uid://byxs44vljsg2f diff --git a/ui_scenes/selectedSprite/spriteObject.gd b/ui_scenes/selectedSprite/spriteObject.gd index b0686b6..0353874 100644 --- a/ui_scenes/selectedSprite/spriteObject.gd +++ b/ui_scenes/selectedSprite/spriteObject.gd @@ -28,6 +28,9 @@ var imageSize = Vector2.ZERO @onready var wob = $WobbleOrigin @onready var outlineScene = preload("res://ui_scenes/selectedSprite/outline.tscn") +@onready var opacityShader = preload("res://ui_scenes/selectedSprite/opacity.gdshader") + +var opacityMaterial = null #Visuals var mouseOffset = Vector2.ZERO @@ -83,6 +86,10 @@ var clipped = false var tick = 0 +#Opacity +var spriteOpacity = 1.0 +var affectChildrenOpacity = false + #Vis toggle var toggle = "null" @@ -265,7 +272,24 @@ func talkBlink(): var faded = 0.2 * int(Global.main.editMode) var value = (showOnTalk + (showOnBlink*3)) + (int(Global.speaking)*10) + (int(Global.blink)*20) var yes = [0,10,20,30,1,21,12,32,3,13,4,15,26,36,27,38].has(int(value)) - sprite.self_modulate.a = max(int(yes),faded) + var baseAlpha = max(int(yes), faded) + + # Only handle talk/blink/edit visibility with modulate + # Opacity slider effects are handled by shader + sprite.self_modulate.a = 1.0 # Always keep original texture alpha for clipping + sprite.modulate.a = baseAlpha # Only apply talk/blink/edit visibility + +func calculateParentOpacityMultiplier(): + var multiplier = 1.0 + var currentParent = parentSprite + + # Traverse up the entire parent hierarchy + while currentParent != null: + if currentParent.affectChildrenOpacity: + multiplier *= currentParent.spriteOpacity + currentParent = currentParent.parentSprite + + return multiplier func delete(): queue_free() @@ -403,6 +427,39 @@ func getAllLinkedSprites(): linkedSprites.append(node) return linkedSprites +func updateOpacity(): + # Force a refresh of opacity for this sprite and its children + talkBlink() + updateShaderOpacity() + + # Update all child sprites in the entire hierarchy if this sprite affects children + if affectChildrenOpacity: + updateChildrenOpacityRecursively() + +func updateShaderOpacity(): + # Calculate total opacity from user settings and parent hierarchy + var totalOpacity = spriteOpacity * calculateParentOpacityMultiplier() + + if totalOpacity >= 1.0: + # No shader needed for 100% opacity + sprite.material = null + else: + # Create or update shader material for opacity less than 100% + if opacityMaterial == null: + opacityMaterial = ShaderMaterial.new() + opacityMaterial.shader = opacityShader + + opacityMaterial.set_shader_parameter("opacity", totalOpacity) + sprite.material = opacityMaterial + +func updateChildrenOpacityRecursively(): + var linkedSprites = getAllLinkedSprites() + for child in linkedSprites: + child.talkBlink() + child.updateShaderOpacity() + # Always update the entire hierarchy below this child + child.updateChildrenOpacityRecursively() + func visToggle(keys): if keys.has(toggle): $WobbleOrigin/DragOrigin.visible = !$WobbleOrigin/DragOrigin.visible diff --git a/ui_scenes/spriteEditMenu/sprite_viewer.gd b/ui_scenes/spriteEditMenu/sprite_viewer.gd index fe4d250..787bd6d 100644 --- a/ui_scenes/spriteEditMenu/sprite_viewer.gd +++ b/ui_scenes/spriteEditMenu/sprite_viewer.gd @@ -73,6 +73,10 @@ func setImage(): $Animation/animFramesLabel.text = "sprite frames: " + str(Global.heldSprite.frames) $Animation/animFrames.value = Global.heldSprite.frames + $Opacity/opacityLabel.text = "opacity: " + str(int(Global.heldSprite.spriteOpacity * 100)) + "%" + $Opacity/opacitySlider.value = Global.heldSprite.spriteOpacity + $Opacity/affectChildrenCheck.button_pressed = Global.heldSprite.affectChildrenOpacity + $VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\"" changeRotLimit() @@ -369,3 +373,12 @@ func _on_set_toggle_pressed(): var key = keys[0] Global.heldSprite.toggle = key $VisToggle/setToggle/Label.text = "toggle: \"" + Global.heldSprite.toggle + "\"" + +func _on_opacity_slider_value_changed(value): + $Opacity/opacityLabel.text = "opacity: " + str(int(value * 100)) + "%" + Global.heldSprite.spriteOpacity = value + Global.heldSprite.updateOpacity() + +func _on_affect_children_check_toggled(button_pressed): + Global.heldSprite.affectChildrenOpacity = button_pressed + Global.heldSprite.updateOpacity() diff --git a/ui_scenes/spriteEditMenu/sprite_viewer.tscn b/ui_scenes/spriteEditMenu/sprite_viewer.tscn index f3b4a4b..eeb014f 100644 --- a/ui_scenes/spriteEditMenu/sprite_viewer.tscn +++ b/ui_scenes/spriteEditMenu/sprite_viewer.tscn @@ -29,7 +29,7 @@ [ext_resource type="Texture2D" uid="uid://gflmeocxfnrq" path="res://ui_scenes/settings/deleteHotkey.png" id="27_fgu6g"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_21kva"] -size = Vector2(245, 1341) +size = Vector2(245, 1455) [sub_resource type="ShaderMaterial" id="ShaderMaterial_hxmah"] shader = ExtResource("3_ky0lu") @@ -14285,11 +14285,12 @@ script = ExtResource("1_hp0e3") z_index = -2 [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] -position = Vector2(122.5, 670.5) +position = Vector2(122.5, 727.5) shape = SubResource("RectangleShape2D_21kva") [node name="Border" type="Sprite2D" parent="."] -scale = Vector2(1, 1.16) +position = Vector2(0, 2.22901) +scale = Vector2(1, 1.37547) texture = ExtResource("1_b54o8") centered = false offset = Vector2(-9, -10.3448) @@ -14349,13 +14350,13 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.362) fov = 36.5 [node name="Position" type="Node2D" parent="."] -position = Vector2(0, 10) +position = Vector2(0, 20) [node name="fileTitle" type="Label" parent="Position"] offset_left = 10.0 -offset_top = 134.0 +offset_top = 127.0 offset_right = 236.0 -offset_bottom = 154.0 +offset_bottom = 147.0 text = "user://greenThing/head1.png" label_settings = SubResource("LabelSettings_6favr") @@ -14381,7 +14382,7 @@ offset_bottom = 227.0 text = "layer : 0" [node name="Buttons" type="Node2D" parent="."] -position = Vector2(0, 27) +position = Vector2(0, 141) [node name="Speaking" type="Sprite2D" parent="Buttons"] position = Vector2(42, 523) @@ -14468,7 +14469,7 @@ scrollable = false [node name="WobbleControl" type="Node2D" parent="."] z_index = 1 -position = Vector2(-3, 378) +position = Vector2(-3, 486) [node name="animationBox" type="Sprite2D" parent="WobbleControl"] position = Vector2(125, 384.5) @@ -14612,7 +14613,7 @@ scrollable = false [node name="RotationalLimits" type="Node2D" parent="."] z_index = -1 -position = Vector2(0, 325) +position = Vector2(0, 435) [node name="RotBack" type="Sprite2D" parent="RotationalLimits"] clip_children = 2 @@ -14725,8 +14726,43 @@ offset_bottom = 1068.0 theme = SubResource("Theme_mrbyn") scrollable = false +[node name="Opacity" type="Node2D" parent="."] +position = Vector2(-2, -611) + +[node name="opacityLabel" type="Label" parent="Opacity"] +offset_left = 11.0 +offset_top = 1090.0 +offset_right = 159.0 +offset_bottom = 1116.0 +text = "opacity: 100%" + +[node name="opacitySlider" type="HSlider" parent="Opacity"] +offset_left = 10.0 +offset_top = 1111.0 +offset_right = 233.0 +offset_bottom = 1131.0 +theme = SubResource("Theme_mrbyn") +max_value = 1.0 +step = 0.01 +value = 1.0 +scrollable = false + +[node name="affectChildrenLabel" type="Label" parent="Opacity"] +offset_left = 11.0 +offset_top = 1140.0 +offset_right = 159.0 +offset_bottom = 1166.0 +text = "affect children:" + +[node name="affectChildrenCheck" type="CheckBox" parent="Opacity"] +offset_left = 140.0 +offset_top = 1140.0 +offset_right = 200.0 +offset_bottom = 1166.0 +theme = SubResource("Theme_mrbyn") + [node name="Layers" type="Node2D" parent="."] -position = Vector2(0, 327) +position = Vector2(0, 443) [node name="Layer1" type="Sprite2D" parent="Layers"] position = Vector2(23, 878) @@ -14863,7 +14899,7 @@ position = Vector2(23, 878) texture = ExtResource("20_px0dt") [node name="VisToggle" type="Node2D" parent="."] -position = Vector2(11, 1289) +position = Vector2(11, 1404) [node name="setToggle" type="Button" parent="VisToggle"] custom_minimum_size = Vector2(0, 37.125) @@ -14929,6 +14965,8 @@ centered = false [connection signal="value_changed" from="RotationalLimits/rotLimitMax" to="." method="_on_rot_limit_max_value_changed"] [connection signal="value_changed" from="Animation/animFrames" to="." method="_on_anim_frames_value_changed"] [connection signal="value_changed" from="Animation/animSpeed" to="." method="_on_anim_speed_value_changed"] +[connection signal="value_changed" from="Opacity/opacitySlider" to="." method="_on_opacity_slider_value_changed"] +[connection signal="toggled" from="Opacity/affectChildrenCheck" to="." method="_on_affect_children_check_toggled"] [connection signal="pressed" from="Layers/Layer1/layerButton1" to="." method="_on_layer_button_1_pressed"] [connection signal="pressed" from="Layers/Layer2/layerButton2" to="." method="_on_layer_button_2_pressed"] [connection signal="pressed" from="Layers/Layer3/layerButton3" to="." method="_on_layer_button_3_pressed"]