Compare commits

..

2 Commits

Author SHA1 Message Date
CoDeayant
2891086cb4 "Убрал лишние файлы" 2024-12-07 15:43:10 +05:00
CoDeayant
b99f5ec797 Добавил персонажа и движение на курсор 2024-12-07 15:26:00 +05:00
99 changed files with 6760 additions and 1079 deletions

9
LICENSE.md Normal file
View File

@ -0,0 +1,9 @@
# MIT License
Copyright 2022 Gennady "Don Tnowe" Krupenyov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# Sprite Painter
Actually, it's not just sprites it paints.
Image editor right within Godot 4!
Just press the colorful button above the viewport to edit a Node's texture, or an image file in the project folder!
- **Box and Wand selections**, which mask effects of drawing tools. The underlying pixels can be dragged and dropped elsewhere;
- 1-pixel **pencil** - can erase if secondary color is transparent;
- Round **brush** with **eraser** and smooth option;
- A classic **bucket** tool with a **gradient** variant;
- **Line** drawing;
- A number of **shapes** to shape your graphics;
- And a bit of **scripting** support.
Other features:
- Quick operations on the image: flip, rotate, and resize;
- Press Alt to pick color from editor window;
- Grid overlay for Sprites with Frames and Maps with Tiles;
- Region overlays for Atlastextures and Spriteframes;
- Full Undo support.
#
Made by Don Tnowe in 2022.
[My Website](https://redbladegames.netlify.app)
[My Twitter](https://twitter.com/don_tnowe)
[My Itch](https://don-tnowe.itch.io)
Copying and Modification is allowed in accordance to the MIT license, full text is included.
Some shader code is from [https://iquilezles.org/articles](https://iquilezles.org/articles), released under the MIT License.
Icon made in Sprite Painter [*tm*]

16
Scenes/player.gd Normal file
View File

@ -0,0 +1,16 @@
extends CharacterBody2D
var speed = 300
var click_position = Vector2()
var target_position = Vector2()
func _ready():
click_position = position
func _physics_process(delta):
if Input.is_action_just_pressed("left_click"):
click_position = get_global_mouse_position()
if (position.distance_to(click_position)) >3:
target_position = (click_position - position).normalized()
velocity = target_position*speed
move_and_slide()

17
Scenes/player.tscn Normal file
View File

@ -0,0 +1,17 @@
[gd_scene load_steps=4 format=3 uid="uid://dtttpb73i7qt2"]
[ext_resource type="Texture2D" uid="uid://blmo4uqveqgtu" path="res://assets/Amogus.png" id="1_27e8i"]
[ext_resource type="Script" path="res://Scenes/player.gd" id="1_xdifv"]
[sub_resource type="CircleShape2D" id="CircleShape2D_ounmr"]
radius = 3.16228
[node name="Player" type="CharacterBody2D"]
script = ExtResource("1_xdifv")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("1_27e8i")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 1)
shape = SubResource("CircleShape2D_ounmr")

View File

@ -1,3 +0,0 @@
extends Button
# Called when the node enters the

View File

@ -1,32 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://ceju5kxboqm1r"]
[ext_resource type="Script" path="res://UI/construct.gd" id="1_g0ew0"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Construct" type="Button" parent="."]
layout_mode = 0
offset_right = 8.0
offset_bottom = 8.0
text = "Конструкция"
script = ExtResource("1_g0ew0")
[node name="Destroy" type="Button" parent="."]
layout_mode = 0
offset_top = 40.0
offset_right = 92.0
offset_bottom = 71.0
text = "Демонтаж"
[node name="Road" type="Button" parent="."]
layout_mode = 0
offset_top = 80.0
offset_right = 66.0
offset_bottom = 111.0
text = "Дорога"

View File

@ -1,7 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dyvpxcmnnre1i"]
[ext_resource type="Texture2D" uid="uid://dq0at84j2c0av" path="res://assets/selection.png" id="1_1haqg"]
[node name="Selection" type="Sprite2D"]
z_index = 1024
texture = ExtResource("1_1haqg")

View File

@ -0,0 +1,9 @@
# MIT License
Copyright 2022 Gennady "Don Tnowe" Krupenyov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,16 @@
@tool
extends Button
@export var icon_name := "Node"
# set(v):
# _set_icon_name(v)
func _set_icon_name(v):
icon_name = v
if has_theme_icon(v, "EditorIcons"):
icon = get_theme_icon(v, "EditorIcons")
func _ready():
_set_icon_name(icon_name)

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1038.4a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6z" fill="none" stroke="#5fb2ff" stroke-linejoin="round" stroke-width="2" transform="translate(0 -1036.4)"/></svg>

After

Width:  |  Height:  |  Size: 273 B

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cpxsxp2vr8kc6"
path="res://.godot/imported/circle_shape_2d.svg-82c4246092358c78c496ed3aeadc38e1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/circle_shape_2d.svg"
dest_files=["res://.godot/imported/circle_shape_2d.svg-82c4246092358c78c496ed3aeadc38e1.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://rvkxkfn8dol2"]
[sub_resource type="Image" id="Image_fmgcp"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 225, 225, 225, 51, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 50, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 112, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 112, 224, 224, 224, 254, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 17, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 4, 255, 255, 255, 4, 255, 255, 255, 4, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 160, 224, 224, 224, 194, 224, 224, 224, 32, 224, 224, 224, 8, 224, 224, 224, 104, 255, 255, 255, 6, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 224, 224, 224, 96, 224, 224, 224, 192, 224, 224, 224, 255, 224, 224, 224, 232, 227, 227, 227, 9, 224, 224, 224, 107, 224, 224, 224, 255, 226, 226, 226, 35, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 112, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 254, 225, 225, 225, 100, 255, 255, 255, 3, 224, 224, 224, 211, 224, 224, 224, 201, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 225, 225, 225, 75, 255, 255, 255, 0, 225, 225, 225, 124, 224, 224, 224, 255, 226, 226, 226, 79, 255, 255, 255, 0, 231, 231, 231, 21, 225, 225, 225, 67, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 234, 234, 234, 12, 224, 224, 224, 139, 224, 224, 224, 255, 224, 224, 224, 153, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 51, 226, 226, 226, 77, 226, 226, 226, 86, 224, 224, 224, 123, 224, 224, 224, 178, 224, 224, 224, 238, 224, 224, 224, 254, 224, 224, 224, 140, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 228, 228, 228, 37, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 211, 224, 224, 224, 153, 228, 228, 228, 47, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 42, 226, 226, 226, 52, 226, 226, 226, 44, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_fmgcp")

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c7nymhfcdr3hl"
path="res://.godot/imported/diags.png-8f1f755b2561632401e027e528c97db7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/diags.png"
dest_files=["res://.godot/imported/diags.png-8f1f755b2561632401e027e528c97db7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="DiamondShape2D.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="24.306796"
inkscape:cx="0.90509668"
inkscape:cy="4.8134687"
inkscape:window-width="1920"
inkscape:window-height="1057"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<rect
fill="none"
height="8.3526468"
rx="1.1832915e-05"
stroke="#5fb2ff"
stroke-linejoin="round"
stroke-miterlimit="10"
stroke-width="2.00051"
width="8.3526468"
x="-4.1989856"
y="7.0055852"
id="rect2"
transform="matrix(0.71834136,-0.6956908,0.71834136,0.6956908,0,0)" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drjoeyt7e6kwg"
path="res://.godot/imported/diamond_shape_2d.svg-c7409b07122604b74671fded8f79cf9b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/diamond_shape_2d.svg"
dest_files=["res://.godot/imported/diamond_shape_2d.svg-c7409b07122604b74671fded8f79cf9b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://b5v27x2n2je75"]
[sub_resource type="Image" id="Image_my53x"]
data = {
"data": PackedByteArray(225, 225, 225, 17, 225, 225, 225, 68, 225, 225, 225, 68, 225, 225, 225, 68, 224, 224, 224, 24, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 16, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 142, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 146, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 181, 225, 225, 225, 142, 224, 224, 224, 255, 224, 224, 224, 139, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 144, 224, 224, 224, 255, 224, 224, 224, 138, 224, 224, 224, 184, 224, 224, 224, 64, 233, 233, 233, 23, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 123, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 225, 225, 225, 134, 255, 255, 255, 0, 255, 255, 255, 7, 233, 233, 233, 23, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 144, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 132, 224, 224, 224, 139, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 184, 224, 224, 224, 146, 224, 224, 224, 255, 225, 225, 225, 134, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 255, 225, 225, 225, 142, 224, 224, 224, 186, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 138, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 142, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 186, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 16, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 233, 233, 233, 23, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 16),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_my53x")

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://diqj6upxhiiko"]
[sub_resource type="Image" id="Image_buorn"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 184, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 255, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 7, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 255, 225, 225, 225, 183, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 7, 224, 224, 224, 184, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 183, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_buorn")

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="HexShape2D.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="45.254834"
inkscape:cx="5.2149125"
inkscape:cy="8.9051261"
inkscape:window-width="1920"
inkscape:window-height="1057"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="rect2"
style="fill:none;stroke:#5fb2ff;stroke-width:2.00051;stroke-linejoin:round;stroke-miterlimit:10"
transform="matrix(0.71834136,-0.6956908,0.71834136,0.6956908,0,0)"
d="m -1.5727512,5.4706195 5.7264006,1.5349657 1.19e-5,1.18e-5 1.5124976,5.703909 -4.4560086,4.456008 -5.409124,-1.807282 -1.19e-5,-1.2e-5 -1.8297623,-5.4316038 z"
sodipodi:nodetypes="ccccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://43n8i7bhg4he"
path="res://.godot/imported/hex_shape_2d.svg-33b4a57aed136855fbb5d791f29eb5bb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/hex_shape_2d.svg"
dest_files=["res://.godot/imported/hex_shape_2d.svg-33b4a57aed136855fbb5d791f29eb5bb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://dnnjnka3gsci7"]
[sub_resource type="Image" id="Image_i608h"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 42, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 226, 224, 224, 224, 255, 224, 224, 224, 164, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 168, 224, 224, 224, 255, 224, 224, 224, 225, 255, 255, 255, 6, 224, 224, 224, 41, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 40, 255, 255, 255, 0, 224, 224, 224, 165, 224, 224, 224, 254, 226, 226, 226, 35, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 38, 224, 224, 224, 255, 224, 224, 224, 163, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 31, 225, 225, 225, 17, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 255, 255, 255, 0, 237, 237, 237, 14, 225, 225, 225, 68, 224, 224, 224, 224, 224, 224, 224, 180, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 224, 224, 224, 24, 225, 225, 225, 182, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 245, 225, 225, 225, 84, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 226, 226, 226, 35, 224, 224, 224, 80, 224, 224, 224, 80, 224, 224, 224, 80, 224, 224, 224, 80, 226, 226, 226, 35, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 166, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 39, 224, 224, 224, 255, 224, 224, 224, 165, 255, 255, 255, 0, 225, 225, 225, 42, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 40, 255, 255, 255, 6, 224, 224, 224, 225, 224, 224, 224, 255, 224, 224, 224, 165, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 225, 225, 225, 166, 224, 224, 224, 255, 224, 224, 224, 224, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 40, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 40, 255, 255, 255, 6, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_i608h")

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><rect fill="none" height="8" rx=".000017" stroke="#5fb2ff" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" width="12" x="2" y="4"/></svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cfon2trqh4c3k"
path="res://.godot/imported/rect_shape_2d.svg-d16c8b9085065dfd954a4786aa34e9fe.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/rect_shape_2d.svg"
dest_files=["res://.godot/imported/rect_shape_2d.svg-d16c8b9085065dfd954a4786aa34e9fe.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://jpxduo4o6vce"]
[sub_resource type="Image" id="Image_f5vrx"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 4, 224, 224, 224, 65, 224, 224, 224, 112, 224, 224, 224, 90, 230, 230, 230, 30, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 40, 224, 224, 224, 204, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 248, 224, 224, 224, 120, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 10, 224, 224, 224, 220, 224, 224, 224, 230, 225, 225, 225, 83, 238, 238, 238, 15, 229, 229, 229, 38, 224, 224, 224, 164, 224, 224, 224, 255, 224, 224, 224, 98, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 113, 224, 224, 224, 254, 228, 228, 228, 55, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 224, 224, 224, 236, 255, 255, 255, 1, 255, 255, 255, 1, 255, 255, 255, 4, 225, 225, 225, 169, 225, 225, 225, 216, 255, 255, 255, 4, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 89, 224, 224, 224, 255, 229, 229, 229, 38, 227, 227, 227, 9, 224, 224, 224, 209, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 214, 232, 232, 232, 11, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 91, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 229, 229, 229, 39, 224, 224, 224, 244, 224, 224, 224, 246, 226, 226, 226, 44, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 187, 224, 224, 224, 235, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 90, 224, 224, 224, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 224, 224, 224, 40, 225, 225, 225, 167, 224, 224, 224, 255, 226, 226, 226, 95, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 247, 224, 224, 224, 115, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 30, 224, 224, 224, 89, 228, 228, 228, 28, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_f5vrx")

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://hc35i3cvcupp"]
[sub_resource type="Image" id="Image_ts5bs"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 31, 225, 225, 225, 91, 224, 224, 224, 113, 224, 224, 224, 66, 255, 255, 255, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 224, 224, 224, 121, 224, 224, 224, 250, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 205, 224, 224, 224, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 98, 224, 224, 224, 255, 224, 224, 224, 164, 224, 224, 224, 40, 224, 224, 224, 16, 225, 225, 225, 83, 224, 224, 224, 230, 224, 224, 224, 220, 232, 232, 232, 11, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 2, 224, 224, 224, 236, 224, 224, 224, 186, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 53, 224, 224, 224, 254, 224, 224, 224, 115, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 36, 224, 224, 224, 255, 225, 225, 225, 93, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 4, 226, 226, 226, 214, 226, 226, 226, 171, 255, 255, 255, 4, 255, 255, 255, 0, 226, 226, 226, 35, 224, 224, 224, 255, 226, 226, 226, 94, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 232, 232, 232, 11, 224, 224, 224, 214, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 207, 224, 224, 224, 8, 255, 255, 255, 1, 224, 224, 224, 234, 224, 224, 224, 189, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 43, 224, 224, 224, 245, 224, 224, 224, 242, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 95, 224, 224, 224, 255, 224, 224, 224, 168, 224, 224, 224, 41, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 97, 224, 224, 224, 88, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 225, 225, 225, 116, 224, 224, 224, 248, 224, 224, 224, 255, 225, 225, 225, 68, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 228, 228, 228, 28, 224, 224, 224, 88, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_ts5bs")

View File

@ -0,0 +1,13 @@
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://ro5r4gstjow0"]
[sub_resource type="Image" id="Image_4da7w"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 35, 224, 224, 224, 65, 224, 224, 224, 66, 225, 225, 225, 67, 227, 227, 227, 54, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 229, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 50, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 34, 224, 224, 224, 64, 224, 224, 224, 195, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 19, 224, 224, 224, 224, 225, 225, 225, 191, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 225, 225, 225, 75, 224, 224, 224, 74, 255, 255, 255, 1, 227, 227, 227, 18, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 76, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 72, 255, 255, 255, 0, 225, 225, 225, 34, 224, 224, 224, 226, 224, 224, 224, 33, 226, 226, 226, 35, 224, 224, 224, 229, 225, 225, 225, 34, 255, 255, 255, 0, 225, 225, 225, 75, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 72, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 229, 229, 229, 19, 255, 255, 255, 1, 224, 224, 224, 72, 224, 224, 224, 72, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 195, 224, 224, 224, 224, 227, 227, 227, 18, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 191, 224, 224, 224, 64, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 226, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 50, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[resource]
image = SubResource("Image_4da7w")

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="TriangleShape2D.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="24.306796"
inkscape:cx="0.90509668"
inkscape:cy="4.8134687"
inkscape:window-width="1920"
inkscape:window-height="1057"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
id="rect2"
style="fill:none;stroke:#5fb2ff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10"
d="m 2.000017,2 c 0,0 4.7752562,4.7752562 11.999983,11.999983 0,9e-6 -8e-6,1.7e-5 -1.7e-5,1.7e-5 H 2.000017 C 2.0000076,14 2,13.999992 2,13.999983 V 2.000017 c 0,-9.4e-6 8.305003,8.304969 1.7e-5,-1.7e-5 z"
sodipodi:nodetypes="ccssssc" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://yunqetk64l50"
path="res://.godot/imported/triangle_shape_2d.svg-215872cca079d243eb964f3debc05ac8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/sprite_painter/graphics/triangle_shape_2d.svg"
dest_files=["res://.godot/imported/triangle_shape_2d.svg-215872cca079d243eb964f3debc05ac8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,38 @@
extends ImageScript
func _get_param_list():
return [
[
"Hue",
SCRIPT_PARAM_INT,
0,
[0, 360]
],
[
"Saturation",
SCRIPT_PARAM_INT,
0,
[-100, +100]
],
[
"Value",
SCRIPT_PARAM_INT,
0,
[-100, +100]
],
]
func _get_image(new_image, selection):
var pix : Color
for i in new_image.get_width():
for j in new_image.get_width():
if !selection.get_bit(i, j): continue
pix = new_image.get_pixel(i, j)
pix.h += get_param("Hue") / 360.0
pix.s *= 1.0 + get_param("Saturation") * 0.01
pix.v *= 1.0 + get_param("Value") * 0.01
new_image.set_pixel(i, j, pix)
return new_image

View File

@ -0,0 +1,52 @@
extends ImageScript
func _get_param_list():
var default_curve = Curve.new()
default_curve.add_point(Vector2.ZERO, 1, 1)
default_curve.add_point(Vector2.ONE, 1, 1)
return [
[
"Red",
SCRIPT_PARAM_RESOURCE,
default_curve.duplicate(),
"Curve",
],
[
"Green",
SCRIPT_PARAM_RESOURCE,
default_curve.duplicate(),
"Curve",
],
[
"Blue",
SCRIPT_PARAM_RESOURCE,
default_curve.duplicate(),
"Curve",
],
[
"Alpha",
SCRIPT_PARAM_RESOURCE,
default_curve.duplicate(),
"Curve",
],
]
func _get_image(new_image, selection):
var r_curve = get_param("Red")
var g_curve = get_param("Green")
var b_curve = get_param("Blue")
var a_curve = get_param("Alpha")
var pix : Color
for i in new_image.get_width():
for j in new_image.get_width():
if !selection.get_bit(i, j): continue
pix = new_image.get_pixel(i, j)
pix.r = r_curve.sample(pix.r)
pix.g = g_curve.sample(pix.g)
pix.b = b_curve.sample(pix.b)
pix.a = a_curve.sample(pix.a)
new_image.set_pixel(i, j, pix)
return new_image

View File

@ -0,0 +1,68 @@
extends ImageScript
func _get_param_list():
return [
[
"Palette Texture",
SCRIPT_PARAM_RESOURCE,
# Does not work well on gradients: doesn't consider Interp Mode and Width
# so with no proper spatial partitioning takes an eternity
# GradientTexture1D.new(),
null,
"Texture2D"
],
]
func _get_image(new_image, selection):
if get_param("Palette Texture") == null:
return new_image
var palette_img = get_param("Palette Texture").get_image()
if palette_img == null:
return new_image
var palette = {}
var pix : Color
for i in palette_img.get_width():
for j in palette_img.get_height():
pix = palette_img.get_pixel(i, j)
if pix.a > 0.01:
palette[Color(pix, 1.0)] = true
var image_width = new_image.get_width()
var image_color_mapping = {}
var nearest_color : Color
for i in new_image.get_width():
for j in new_image.get_height():
pix = new_image.get_pixel(i, j)
if image_color_mapping.has(pix):
if selection.get_bit(i, j):
new_image.set_pixel(i, j, Color(image_color_mapping[pix], pix.a))
elif selection.get_bit(i, j):
# I'll rewrite this into a proper spatial algo access later,
# bruteforce will do for now
nearest_color = Color(pick_nearest(pix, palette), 1.0)
image_color_mapping[Color(pix, 1.0)] = nearest_color
new_image.set_pixel(i, j, Color(nearest_color, pix.a))
return new_image
func pick_nearest(to : Color, from : Dictionary):
var nearest_dist := INF
var nearest : Color
var cur_dist : float
for x in from:
cur_dist = (
(x.r - to.r) * (x.r - to.r)
+ (x.g - to.g) * (x.g - to.g)
+ (x.b - to.b) * (x.b - to.b)
)
if cur_dist < nearest_dist:
nearest = x
nearest_dist = cur_dist
return nearest

View File

@ -0,0 +1,87 @@
extends ImageScript
var sample_modes = [
[Vector2(0, 1), Vector2(1, 0), Vector2(-1, 0), Vector2(0, -1)],
range(8).map(func(i):return Vector2(cos(i * PI * 0.25), sin(i * PI * 0.25))),
range(16).map(func(i):return Vector2(cos(i * PI * 0.125), sin(i * PI * 0.125))),
# The following modes sample two rings around the pixel,
# resulting in a more precise thick outline around thin objects
range(16).map(func(i):return ceil((i + 1) * 0.125) * 0.5 * Vector2(cos(i * PI * 0.25), sin(i * PI * 0.25))),
range(32).map(func(i):return ceil((i + 1) * 0.0625) * 0.5 * Vector2(cos(i * PI * 0.125), sin(i * PI * 0.125))),
]
func _get_param_list():
return [
[
"Width",
SCRIPT_PARAM_INT,
1,
[1, 150]
],
[
"Color",
SCRIPT_PARAM_COLOR,
Color.BLACK
],
[
"Samples",
SCRIPT_PARAM_ENUM,
0,
["4 (Naive)", "8", "16", "4+4 (Wide Line)", "8+8 (Wide Line Ultra)"]
],
[
"Mode",
SCRIPT_PARAM_ENUM,
0,
["Outline + Image", "Just Outline"]
],
]
func _get_image(new_image, selection):
var line_color = get_param("Color")
var width = get_param("Width")
var line_only = get_param("Mode") == 1
var samples = sample_modes[get_param("Samples")]
var image_size = new_image.get_size()
var new_new_image = Image.create_from_data(
image_size.x,
image_size.y,
false,
new_image.get_format(),
new_image.get_data()
)
var pix : Color
var pix_outline_alpha := 0.0
for i in new_image.get_width():
for j in new_image.get_height():
if !selection.get_bit(i, j): continue
pix = new_image.get_pixel(i, j)
if pix.a == 1.0:
if line_only:
new_new_image.set_pixel(i, j, Color.TRANSPARENT)
continue
pix_outline_alpha = 0.0
for x in samples:
if pix_outline_alpha >= line_color.a: break
if ImageFillTools.is_out_of_bounds(Vector2(i, j) + x * width, image_size):
continue
pix_outline_alpha = max(pix_outline_alpha, min(
line_color.a,
new_image.get_pixelv(Vector2(i, j) + x * width).a
))
if pix_outline_alpha > 0.0:
if line_only:
pix = Color(line_color, pix_outline_alpha)
else:
pix = Color(line_color, pix_outline_alpha).blend(pix)
new_new_image.set_pixel(i, j, pix)
return new_new_image

View File

@ -0,0 +1,140 @@
extends ImageScript
const ROOT2DIV2 = 0.7071
func _get_param_list():
var default_curve = Curve.new()
default_curve.add_point(Vector2(0.0, 0.5), 1, 1, Curve.TANGENT_LINEAR, Curve.TANGENT_LINEAR)
default_curve.add_point(Vector2(1.0, 0.5), 1, 1, Curve.TANGENT_LINEAR, Curve.TANGENT_LINEAR)
return [
[
"Bevel",
SCRIPT_PARAM_INT,
1,
[1, 150]
],
[
"Bevel Distance",
SCRIPT_PARAM_ENUM,
0,
["Circle", "Square", "Diamond"]
],
]
func _get_image(new_image, selection):
var image_size : Vector2i = new_image.get_size()
var closest_edge : Array[Vector2] = []
var entropy := {}
closest_edge.resize(image_size.x * image_size.y)
var pix : Color
var vec : Vector2
for i in image_size.x:
for j in image_size.y:
pix = new_image.get_pixel(i, j)
if pix.a == 0.0:
closest_edge[i + j * image_size.x] = Vector2.ZERO
else:
vec = Vector2(0, 0)
if i == 0: vec.x -= 1
if j == 0: vec.y -= 1
if i == image_size.x - 1: vec.x += 1
if j == image_size.y - 1: vec.y += 1
if vec == Vector2.ZERO:
vec = Vector2(-INF, -INF)
entropy[Vector2(i, j)] = 5
closest_edge[i + j * image_size.x] = vec
var neighbors = []
var neighbor_pos = []
var iters = 0
var closest_edge_new : Array[Vector2]
while entropy.size() > 0:
iters += 1
closest_edge_new = closest_edge.duplicate()
for pos in entropy.keys():
neighbor_pos = [
pos + Vector2.RIGHT,
pos + Vector2.DOWN,
pos + Vector2.LEFT,
pos + Vector2.UP,
]
var any_neighbor_defined = false
for x in neighbor_pos:
if closest_edge[x.x + x.y * image_size.x] != Vector2(-INF, -INF):
any_neighbor_defined = true
break
if any_neighbor_defined:
entropy.erase(pos)
neighbors = [
closest_edge[pos.x + 1 + pos.y * image_size.x]
if !entropy.has(pos + Vector2.RIGHT) else Vector2(-INF, -INF),
closest_edge[pos.x + (pos.y + 1) * image_size.x]
if !entropy.has(pos + Vector2.DOWN) else Vector2(-INF, -INF),
closest_edge[pos.x - 1 + pos.y * image_size.x]
if !entropy.has(pos + Vector2.LEFT) else Vector2(-INF, -INF),
closest_edge[pos.x + (pos.y - 1) * image_size.x]
if !entropy.has(pos + Vector2.UP) else Vector2(-INF, -INF),
]
closest_edge_new[pos.x + pos.y * image_size.x] = get_closest_edge(neighbors)
closest_edge = closest_edge_new
var bevel = get_param("Bevel")
var dist_func = [
# Three different distance calculations have their own artifacts..
func (x): return x.length_squared() > bevel * 2.0, # Circle
func (x): return max(abs(x.x), abs(x.y)) > bevel, # Square
func (x): return abs(x.x) + abs(x.y) > bevel * 2.0, # Diamond
][get_param("Bevel Distance")]
var vec3 : Vector3
var vec_len : float
var vec_dir : Vector2
for i in image_size.x:
for j in image_size.y:
vec = closest_edge[i + j * image_size.x]
if vec == Vector2.ZERO || dist_func.call(vec):
new_image.set_pixel(i, j, Color(0.5, 0.5, 1.0))
continue
vec_len = vec.length()
vec_dir = vec / vec_len
vec3 = Vector3.BACK.rotated(
Vector3(-vec_dir.y, vec_dir.x, 0.0),
PI * 0.25
)
vec3 = vec3 * Vector3(0.5, -0.5, 1.0) + Vector3(0.5, 0.5, 0.0)
new_image.set_pixel(i, j, Color(vec3.x, vec3.y, vec3.z))
return new_image
func get_closest_edge(neighbor_vecs):
var lengths = [
neighbor_vecs[0].length_squared(),
neighbor_vecs[1].length_squared(),
neighbor_vecs[2].length_squared(),
neighbor_vecs[3].length_squared(),
]
var closest_length = INF
for i in 4:
if lengths[i] < closest_length:
closest_length = lengths[i]
var result = Vector2.ZERO
var neighbor_dirs = [Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, Vector2.UP]
var coeff = 1.0
for i in 4:
if lengths[i] == closest_length:
result += neighbor_vecs[i] + neighbor_dirs[i]
return result

View File

@ -0,0 +1,9 @@
[plugin]
name="Sprite Painter"
description="Paints Sprites.
An image editor built right into Godot 4."
author="Don Tnowe"
version="0.1"
script="plugin.gd"

View File

@ -0,0 +1,181 @@
@tool
extends EditorPlugin
const can_edit_properties := [
"texture",
"tile_set",
"frames", # AnimatedSprite
"texture_normal", # TextureButton
"texture_progress", # Self explanatory
]
const can_edit_types := [
"CompressedTexture2D",
"AtlasTexture",
"SpriteFrames",
"TileSet",
]
var editor_view : Control
var editor_2d_vp : Control
var editor_3d_vp : Control
var enable_buttons : Dictionary
var sploinky := Control.new()
var sploinky3 := Control.new()
var undo_redo : EditorUndoRedoManager
var overlay_enabled := false
func _enter_tree() -> void:
# Sometimes crashes on startup. Until I find why, let this be here
await get_tree().create_timer(2.0)
var ui := get_editor_interface()
undo_redo = get_undo_redo()
editor_view = load(get_script().resource_path.get_base_dir() + "/src/main.tscn").instantiate()
editor_view.editor_interface = ui
editor_view.editor_plugin = self
editor_view.undo_redo = undo_redo
_connect_editor_viewports()
_add_enable_button(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU)
_add_enable_button(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU)
main_screen_changed.connect(_on_main_screen_changed)
ui.get_selection().selection_changed.connect(_on_selection_changed)
ui.get_base_control().add_child(editor_view)
make_visible(false)
func _forward_canvas_gui_input(event):
if !overlay_enabled:
return false
return editor_view.handle_input(event)
func _connect_editor_viewports():
var mainscreen = get_editor_interface().get_editor_main_screen()
mainscreen.resized.connect(_on_editor_resized.bind(mainscreen))
for x in mainscreen.get_children():
if x.get_class() == "CanvasItemEditor":
editor_2d_vp = x
editor_view.editor_2d_vp = x
x.add_child(sploinky)
x.move_child(sploinky, 1)
if x.get_class() == "Node3DEditor":
editor_3d_vp = x
editor_view.editor_3d_vp = x
x.add_child(sploinky3)
x.move_child(sploinky3, 1)
call_deferred("_on_editor_resized", mainscreen)
func _add_enable_button(container_id):
var enable_button = Button.new()
add_control_to_container(container_id, enable_button)
enable_button.add_theme_stylebox_override("normal", StyleBoxEmpty.new())
enable_button.add_theme_stylebox_override("hover", StyleBoxEmpty.new())
enable_button.add_theme_stylebox_override("pressed", StyleBoxEmpty.new())
enable_button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
enable_button.icon = enable_button.get_theme_icon("StyleBoxTexture", "EditorIcons")
enable_button.text = "Edit Texture..."
enable_button.pressed.connect(_on_enable_pressed)
enable_button.hide()
enable_buttons[container_id] = enable_button
func _exit_tree() -> void:
make_visible(false)
editor_view.queue_free()
sploinky.queue_free()
sploinky3.queue_free()
for x in enable_buttons.values():
x.queue_free()
func make_visible(visible):
if !is_instance_valid(editor_view): return
if editor_view.visible == visible: return
editor_view.visible = visible
editor_2d_vp.get_child(0).visible = !visible
editor_3d_vp.get_child(0).visible = !visible
sploinky.custom_minimum_size.y = editor_2d_vp.get_child(0).size.y
sploinky3.custom_minimum_size.y = editor_3d_vp.get_child(0).size.y
sploinky.visible = visible
sploinky3.visible = visible
if visible:
editor_view.edit_object(editor_view.edited_object)
else:
editor_view.save_changes()
if editor_view.unsaved_image_paths.size() == 0:
return
# print("Saved images: " + str(editor_view.unsaved_image_paths))
get_editor_interface()\
.get_resource_filesystem()\
# I am totally done with this thing freezing the editor forever,
# Yes it is more efficient, but stability matters more
# .reimport_files(editor_view.unsaved_image_paths)
.scan()
editor_view.unsaved_image_paths.clear()
func _edit(object):
editor_view.edit_object(object)
for x in enable_buttons.values():
x.show()
if overlay_enabled:
make_visible(true)
func _handles(object):
for x in can_edit_properties:
if x in object:
return true
for x in can_edit_types:
if ClassDB.is_parent_class(object.get_class(), x):
return true
return false
func _on_enable_pressed():
overlay_enabled = !overlay_enabled
make_visible(overlay_enabled)
func _on_main_screen_changed(screen):
overlay_enabled = false
make_visible(false)
func _on_selection_changed():
var sel = get_editor_interface().get_selection().get_selected_nodes()
if sel.size() == 0 || !_handles(sel[-1]):
for x in enable_buttons.values():
x.hide()
overlay_enabled = false
make_visible(false)
else:
for x in enable_buttons.values():
x.show()
func _on_editor_resized(mainscreen : Control):
editor_view.size = mainscreen.size
editor_view.global_position = mainscreen.global_position

View File

@ -0,0 +1,60 @@
[gd_resource type="Shader" format=3 uid="uid://bpswjmm8i01cg"]
[resource]
code = "shader_type canvas_item;
render_mode blend_premul_alpha;
uniform sampler2D gradient;
uniform vec2 from;
uniform vec2 delta;
uniform int type;
float angle(vec2 dir) {
return atan(dir.x / dir.y) / PI * 0.5 + (dir.y < 0.0 ? 0.75 : 0.25);
}
void fragment() {
if (texture(TEXTURE, UV).r < 0.5) discard;
float pos;
switch (type)
{
case 0: // Line
// Stolen again from:
// https://iquilezles.org/articles/forgot-the-exact-link
vec2 delta_rotated = vec2(-delta.y, delta.x);
vec2 diff = UV - from;
float h = dot(diff, delta_rotated) / dot(delta_rotated, delta_rotated);
if (dot(diff, delta) > 0.0)
pos = length(diff - h * delta_rotated) / length(delta);
break;
case 1: // Line Mirrored
vec2 delta_rotated = vec2(-delta.y, delta.x);
float h = dot(UV - from, delta_rotated) / dot(delta_rotated, delta_rotated);
pos = length(UV - from - h * delta_rotated) / length(delta);
break;
case 2: // Radial
pos = length(UV - from) / length(delta);
break;
case 3: // Conic
vec2 dir = (UV - from);
pos = mod(angle(dir) - angle(delta), 1.0);
break;
case 4: // Bounds
vec2 abs_delta = abs(delta);
vec2 abs_from = from;
if (delta.x < 0.0) abs_from.x -= abs_delta.x;
if (delta.y < 0.0) abs_from.y -= abs_delta.y;
pos = min(
min(UV.x - abs_from.x, abs_delta.x - UV.x + abs_from.x),
min(UV.y - abs_from.y, abs_delta.y - UV.y + abs_from.y)
) / min(abs_delta.x, abs_delta.y) * 2.0;
break;
}
COLOR = texture(gradient, vec2(clamp(pos, 0.0, 0.999999), 0.5));
}
"

View File

@ -0,0 +1,38 @@
[gd_resource type="Shader" format=3 uid="uid://c84r6o3a8gfo0"]
[resource]
code = "shader_type canvas_item;
render_mode blend_premul_alpha;
uniform vec4 color;
uniform vec2 origin;
uniform vec2 delta;
uniform float width;
uniform bool enable_aa;
float get_sdf(vec2 pos) {
if (delta == vec2(0.0)) return float(-1.0);
float h = clamp(dot(pos, delta) / dot(delta, delta), 0.0, 1.0);
return length(pos - h * delta);
}
vec4 sdf_to_color(float sdf) {
if (enable_aa)
return vec4(color.rgb, mix(
0.0,
color.a,
(width * 0.5 - sdf) * 2.0
));
else
return vec4(color.rgb, 1.0 - floor(sdf / (width * 0.5)));
}
void fragment() {
COLOR = sdf_to_color(get_sdf(
floor(UV / TEXTURE_PIXEL_SIZE) - origin
));
}
"

View File

@ -0,0 +1,127 @@
[gd_resource type="Shader" format=3 uid="uid://clpby1t4s1pvy"]
[resource]
code = "shader_type canvas_item;
render_mode blend_premul_alpha;
uniform int shape_index;
// if you are reading this file and know
// what hint_color is called in 4.0
// please open an issue or pull request
uniform vec4 color_border;
uniform vec4 color_fill;
uniform vec2 origin;
uniform vec2 shape_size;
uniform float border_width;
uniform vec2 drag_delta;
uniform bool enable_aa;
float get_sdf(vec2 pos, vec2 size) {
size -= vec2(1.0);
if (size == vec2(0.0)) return -1.0;
switch(shape_index) {
case 0: // Rect
return min(
min(pos.x, size.x - pos.x),
min(pos.y, size.y - pos.y)
);
case 1: // Ellipse
// Stolen from:
// https://iquilezles.org/articles/ellipsedist/
vec2 extents = size * 0.5;
vec2 posf = abs(vec2(pos) - extents);
vec2 q = extents * (posf - extents);
vec2 cs = normalize(q.x < q.y ? vec2(0.01, 1) : vec2(1, 0.01));
for (int i = 0; i < 3; i++) {
vec2 u = extents * vec2(+cs.x, cs.y);
vec2 v = extents * vec2(-cs.y, cs.x);
float a = dot(posf - u, v);
float c = dot(posf - u, u) + dot(v, v);
float b = sqrt(c * c - a * a);
cs = vec2(cs.x * b - cs.y * a, cs.y * b + cs.x * a) / c;
}
float d = length(posf - extents * cs);
return dot(posf / extents, posf / extents) > 1.0 ? -d : d;
case 2: // RA Triangle
float aspect = size.x / size.y;
float distance_to_diag = 0.0;
if ((drag_delta.x < 0.0) == (drag_delta.y < 0.0))
// Main diag
if (drag_delta.x < drag_delta.y)
// Bottom filled
distance_to_diag = (pos.x - pos.y * aspect);
else
// Top filled
// !!! incorrest sdf
distance_to_diag = (pos.y * aspect - pos.x);
else
// Secondary diag
if (drag_delta.x < -drag_delta.y)
// Bottom filled
// !!! incorrest sdf
distance_to_diag = (pos.y * aspect - size.x + pos.x);
else
// Top filled
distance_to_diag = (size.x - pos.x - pos.y * aspect);
float rect_dist = min(
min(pos.x, size.x - pos.x),
min(pos.y, size.y - pos.y)
);
return min(distance_to_diag / aspect, rect_dist);
case 3: // Diamond
vec2 from_center = abs(vec2(pos * 2.0) - size) * 0.5;
from_center.y *= size.x / size.y;
return size.x * 0.5 - (from_center.x + from_center.y);
case 4: // Hex
if ((drag_delta.x < 0.0) == (drag_delta.y < 0.0)) {
pos = pos.yx;
size = size.yx;
}
vec2 diamond_from_center = abs(pos * 2.0 - size) * vec2(0.25, 0.5);
return size.x * 0.5 - max(
diamond_from_center.x + diamond_from_center.y * size.x / size.y,
abs(pos.x - size.x * 0.5)
);
return -1.0;
}
vec4 sdf_to_color(float sdf) {
// Debug
// return vec4(sdf * 0.1, -sdf * 0.1, 0.0, 1.0);
if (sdf < -0.5) {
return vec4(color_border.rgb, 0.0);
}
if (sdf < 0.0 && enable_aa) {
sdf = (0.5 + sdf) * 2.0;
return mix(vec4(color_border.rgb, 0.0), color_border, sdf * sdf);
}
if (sdf <= border_width - 0.5) {
return color_border;
}
if (sdf < border_width && enable_aa) {
sdf = -(sdf - border_width + 0.5) * 2.0;
return mix(color_fill, color_border, 1.0 - sdf * sdf);
}
return color_fill;
}
void fragment() {
COLOR = sdf_to_color(get_sdf(
floor(UV / TEXTURE_PIXEL_SIZE) - origin,
shape_size
));
}
"

View File

@ -0,0 +1,369 @@
@tool
class_name EditingTool
extends VBoxContainer
enum {
TOOL_PROP_BOOL,
TOOL_PROP_INT,
TOOL_PROP_FLOAT,
TOOL_PROP_ENUM,
TOOL_PROP_ICON_ENUM,
TOOL_PROP_ICON_FLAGS,
TOOL_PROP_RESOURCE,
TOOL_PROP_FOLDER_SCAN,
TOOL_PROP_COLOR,
}
enum {
OPERATION_REPLACE,
OPERATION_ADD,
OPERATION_SUBTRACT,
OPERATION_INTERSECTION,
OPERATION_XOR,
}
@export var tool_name := "Box Selection"
@export_multiline var tool_desc := ""
@export var preview_shader : ShaderMaterial
@export_enum("None", "When Drawing", "When Active") var image_hide_mode := 0
var selection : BitMap
var _last_grid : GridContainer
var _hotkey_adjustment_hook : Callable
func _enter_tree():
if !visibility_changed.is_connected(_on_visibility_changed):
visibility_changed.connect(_on_visibility_changed)
set_process_shortcut_input(false)
func add_name():
var label = Label.new()
label.text = tool_name
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
add_child(label)
add_separator()
label = Label.new()
label.autowrap_mode = TextServer.AUTOWRAP_WORD
label.self_modulate.a = 0.75
label.text = tool_desc
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
if tool_desc == "": label.hide()
add_child(label)
_last_grid = null
func add_separator():
var sep = ColorRect.new()
sep.custom_minimum_size = Vector2(0, 2)
sep.color = get_theme_color("accent_color", "Editor")
sep.size_flags_horizontal = SIZE_EXPAND_FILL
add_child(sep)
_last_grid = null
func start_property_grid():
var grid = GridContainer.new()
grid.columns = 2
_last_grid = grid
add_child(grid)
return grid
func add_property(
property_name,
default_value,
setter : Callable,
type : int,
hint : Variant = null,
hotkey_adjustment = false,
):
var parent = _last_grid
if _last_grid == null:
parent = HBoxContainer.new()
add_child(parent)
var label = Label.new()
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
label.text = property_name
parent.add_child(label)
var editor
match type:
TOOL_PROP_BOOL:
editor = CheckBox.new()
editor.button_pressed = default_value
editor.text = "On"
editor.toggled.connect(setter)
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
editor.button_pressed = !editor.button_pressed
setter.call(editor.button_pressed)
TOOL_PROP_INT, TOOL_PROP_FLOAT:
editor = EditorSpinSlider.new()
if hint == null:
editor.max_value = INF
editor.min_value = -INF
else:
editor.min_value = hint[0]
editor.max_value = hint[1]
editor.hide_slider = false
editor.value = default_value
editor.step = 0.01 if type == TOOL_PROP_FLOAT else 1.0
editor.custom_minimum_size.x = 64.0
editor.value_changed.connect(setter)
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
editor.value += editor.step * x
setter.call(editor.value)
TOOL_PROP_ENUM:
if hint.size() != 2:
editor = OptionButton.new()
for x in hint:
editor.add_item(x)
editor.item_selected.connect(setter)
editor.select(default_value)
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
editor.select(posmod((editor.get_selected_id() + x), hint.size()))
setter.call(editor.get_selected_id())
else:
editor = Button.new()
editor.text = hint[default_value]
editor.pressed.connect(func():
var x = 1 if editor.text == hint[0] else 0
editor.text = hint[x]
setter.call(x)
)
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
var new_value = 1 if editor.text == hint[0] else 0
editor.text = hint[new_value]
setter.call(new_value)
TOOL_PROP_ICON_ENUM, TOOL_PROP_ICON_FLAGS:
editor = HBoxContainer.new()
var icons = hint if hint is Array else hint.keys()
var tooltips = {} if hint is Array else hint
var button
var bgroup = ButtonGroup.new()
for x in icons:
button = load("res://addons/sprite_painter/editor_icon_button.gd").new()
button.tooltip_text = tooltips.get(x)
button.toggle_mode = true
button.add_theme_stylebox_override("pressed", button.get_theme_stylebox("focus", "Button"))
button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
if x is Texture:
button.set_deferred("icon", x)
else:
button._set_icon_name(x)
editor.add_child(button)
if type == TOOL_PROP_ICON_ENUM:
button.button_group = bgroup
button.toggled.connect(func(toggled):
if !toggled: return
setter.call(button.get_index())
)
button.button_pressed = default_value == button.get_index()
else:
button.toggled.connect(func(toggled):
default_value[button.get_index()] = toggled
setter.call(default_value)
)
button.button_pressed = default_value[button.get_index()]
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
var new_value : int
for i in editor.get_child_count():
if editor.get_child(i).button_pressed:
new_value = posmod(i + x, hint.size())
editor.get_child(new_value).button_pressed = true
editor.get_child(i).button_pressed = false
break
# setter.call(new_value)
TOOL_PROP_RESOURCE:
editor = EditorResourcePicker.new()
editor.resource_changed.connect(func(x):
setter.call(x)
)
editor.edited_resource = default_value
if !hint is Array || hint.size() > 0:
editor.base_type = hint if hint is String else hint[0]
var plugin_root = get_parent()
while !plugin_root is Window:
plugin_root = plugin_root.get_parent()
if plugin_root is SpritePainterRoot:
editor.resource_selected.connect(func(x, inspected):
plugin_root.editor_interface.edit_resource(x)
)
break
TOOL_PROP_FOLDER_SCAN:
editor = OptionButton.new()
var folder = (hint if hint is String else hint[0]).trim_suffix("/") + "/"
var filenames = DirAccess.get_files_at(folder)
for x in filenames:
editor.add_item(x.get_basename())
editor.item_selected.connect(func(x):
setter.call(load(folder + filenames[x]))
)
if default_value is int:
editor.select(default_value)
elif default_value is String:
default_value = default_value.get_file()
var found_index = filenames.find(default_value)
editor.select(found_index)
if hotkey_adjustment:
_hotkey_adjustment_hook = func(x):
editor.select(posmod((editor.get_selected_id() + x), hint.size()))
setter.call(load(folder + filenames[editor.get_selected_id()]))
TOOL_PROP_COLOR:
editor = ColorPickerButton.new()
editor.color = default_value
editor.color_changed.connect(setter)
editor.size_flags_horizontal = SIZE_EXPAND_FILL
parent.add_child(editor)
return editor
func add_text_display():
var textbox = LineEdit.new()
textbox.editable = false
textbox.size_flags_vertical = SIZE_FILL
textbox.expand_to_text_length = true
add_child(textbox)
_last_grid = null
return textbox
func add_button_panel(labels : Array[String]):
var container = HFlowContainer.new()
for x in labels:
var new_button = Button.new()
new_button.text = x
new_button.size_flags_horizontal = SIZE_EXPAND_FILL
container.add_child(new_button)
add_child(container)
_last_grid = null
return container
func is_out_of_bounds(pos : Vector2i, rect_size : Vector2i):
return (
pos.x < 0 || pos.y < 0
|| pos.x >= rect_size.x || pos.y >= rect_size.y
)
func get_rect_from_drag(start_pos, end_pos, squarify : bool = false):
var rect = Rect2i(start_pos, end_pos - start_pos)
if !squarify:
return rect.abs().grow_individual(0, 0, 1, 1)
var max_side = maxi(abs(rect.size.x), abs(rect.size.y))
var square = Rect2i(rect.position, Vector2i(max_side, max_side))
if rect.size.x < 0:
square.position.x -= square.size.x
if rect.size.y < 0:
square.position.y -= square.size.y
return square
func is_selection_empty():
return selection.get_true_bit_count() == selection.get_size().x * selection.get_size().y
func set_image_pixel(image : Image, x : int, y : int, color : Color):
if !is_out_of_bounds(Vector2i(x, y), image.get_size()):
if selection.get_bit(x, y):
image.set_pixel(x, y, color)
func set_image_pixelv(image : Image, pos : Vector2i, color : Color):
if !is_out_of_bounds(pos, image.get_size()):
if selection.get_bitv(pos):
image.set_pixelv(pos, color)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
printerr("Not implemented: mouse_pressed! (" + get_script().resource_path.get_file() + ")")
func get_affected_rect() -> Rect2i:
printerr("Not implemented: get_affected_rect! (" + get_script().resource_path.get_file() + ")")
return Rect2i()
func mouse_moved(event : InputEventMouseMotion):
printerr("Not implemented: mouse_moved! (" + get_script().resource_path.get_file() + ")")
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
printerr("Not implemented: draw_preview! (" + get_script().resource_path.get_file() + ")")
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
pass
func draw_crosshair(image_view : CanvasItem, mouse_position : Vector2i, line_length : int, color : Color):
image_view.draw_rect(Rect2i(mouse_position + Vector2i(0, 4), Vector2(1, +line_length)).abs(), color)
image_view.draw_rect(Rect2i(mouse_position - Vector2i(0, 3), Vector2(1, -line_length)).abs(), color)
image_view.draw_rect(Rect2i(mouse_position + Vector2i(4, 0), Vector2(+line_length, 1)).abs(), color)
image_view.draw_rect(Rect2i(mouse_position - Vector2i(3, 0), Vector2(-line_length, 1)).abs(), color)
func _shortcut_input(event : InputEvent):
if _hotkey_adjustment_hook.is_null(): return
if !event is InputEventKey: return
if !event.pressed: return
if event.keycode != KEY_BRACKETLEFT:
_hotkey_adjustment_hook.call(+1)
if event.keycode != KEY_BRACKETRIGHT:
_hotkey_adjustment_hook.call(-1)
func _on_visibility_changed():
set_process_shortcut_input(visible)

View File

@ -0,0 +1,264 @@
@tool
extends EditingTool
enum {
BRUSH_DRAW,
BRUSH_ERASE,
BRUSH_CLONE,
BRUSH_SHADING,
BRUSH_NORMALMAP,
}
@export_enum("Draw", "Erase", "Clone", "Shading", "Normal Map") var brush_type := 0
@export var chunk_size := Vector2i(256, 256)
@export var max_brush_size := 150
@export var max_brush_min_size := 150
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
var brushsize := 5
var brushminsize := 5
var brush_offset := Vector2(0.5, 0.5)
var hardness := 1.0
var opacity := 1.0
var pen_flags := [true, false, false]
var drawing := false
var drawing_color1 := Color()
var drawing_color2 := Color()
var last_edits_chunks := {}
var last_edits_textures := {}
var last_affected_rect := Rect2i()
func _ready():
add_name()
start_property_grid()
add_property("Size", brushsize,
func (x):
brushsize = x
brush_offset = Vector2(0.5, 0.5) * float(int(x) % 2),
TOOL_PROP_INT,
[1, max_brush_size],
true
)
add_property("Min Size", brushminsize,
func (x):
brushminsize = x,
TOOL_PROP_INT,
[1, max_brush_min_size],
true
)
add_property("Hardness", hardness * 100,
func (x): hardness = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
add_property("Strength", opacity * 100,
func (x): opacity = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
var pressure_options := {
"ToolScale" : "Size",
"Gradient" : "Opacity",
}
if brush_type == BRUSH_DRAW:
pressure_options["CanvasItemMaterial"] = "Tint"
add_property("Pen Pressure", pen_flags,
func (x): pen_flags = x,
TOOL_PROP_ICON_FLAGS,
pressure_options
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
drawing = event.pressed
drawing_color1 = color1
drawing_color2 = color2
if drawing:
start_drawing(image, event.position)
else:
match brush_type:
BRUSH_ERASE:
apply_eraser(image)
_:
apply_brush(image)
func start_drawing(image, start_pos):
last_edits_chunks.clear()
# Break the image up into tiles - small images are faster to edit.
for i in ceil(float(image.get_width()) / chunk_size.x):
for j in ceil(float(image.get_height()) / chunk_size.y):
last_edits_chunks[Vector2i(i, j) * chunk_size] = Image.create(
chunk_size.x, chunk_size.y,
false, image.get_format()
)
for k in last_edits_chunks:
last_edits_textures[k] = ImageTexture.create_from_image(last_edits_chunks[k])
# Copy the image to the tiles. Worse opacity handling,
# but with more work can make eraser editing more performant and previewable.
# last_edits_chunks[k].blit_rect(image, Rect2i(k, chunk_size), Vector2i.ZERO)
last_affected_rect = Rect2i(start_pos, Vector2i.ZERO)
func apply_brush(image):
for k in last_edits_chunks:
image.blend_rect(
last_edits_chunks[k],
Rect2i(Vector2i.ZERO, chunk_size),
k
)
func apply_eraser(image):
# Cutting off a smaller image does not increase performance.
# Must find another way - erasing is very slow.
# var new_image = Image.create(last_affected_rect.size.x + 1, last_affected_rect.size.y + 1, false, image.get_format())
# new_image.blit_rect(new_image, last_affected_rect, Vector2i.ZERO)
var pos : Vector2i
for k in last_edits_chunks:
var chunk = last_edits_chunks[k]
var height = mini(image.get_height() - k.y, chunk.get_height())
for i in mini(image.get_width() - k.x, chunk.get_width()):
for j in height:
# pos = k - last_affected_rect.position + Vector2i(i, j)
pos = Vector2i(i + k.x, j + k.y)
chunk.set_pixel(
i, j,
image.get_pixelv(pos) - chunk.get_pixel(i, j)
)
image.blit_rect(last_edits_chunks[k], Rect2i(Vector2i.ZERO, chunk_size), k)
func get_affected_rect():
return last_affected_rect.grow_individual(0, 0, 1, 1)
func mouse_moved(event : InputEventMouseMotion):
if !drawing: return
if event.button_mask & MOUSE_BUTTON_MASK_LEFT != 0.0:
stroke(event.position, event.position - event.relative, event.pressure)
else:
stroke(event.position, event.position - event.relative, 1.0)
func stroke(stroke_start : Vector2, stroke_end : Vector2, pressure : float):
var rect = Rect2i(stroke_start, Vector2.ZERO)\
.expand(stroke_end)\
.grow(brushsize * 0.5 + 1)
rect = Rect2i(rect.position / chunk_size, rect.end / chunk_size)
var key : Vector2i
var keyf : Vector2
for i in rect.size.x + 1:
for j in rect.size.y + 1:
key = (rect.position + Vector2i(i, j)) * chunk_size
keyf = Vector2(key)
if !last_edits_chunks.has(key): continue
paint(
last_edits_chunks[key],
stroke_end - keyf,
stroke_start - keyf,
key,
pressure
)
func paint(on_image : Image, stroke_start : Vector2, stroke_end : Vector2, chunk_position : Vector2i, pressure : float):
var unsolid_radius := (brushsize * 0.5) * (1.0 - hardness)
var radius := lerpf(brushminsize, brushsize, pressure if pen_flags[0] else 1.0) * 0.5
var solid_radius := radius - unsolid_radius
var color : Color
if brush_type == BRUSH_ERASE:
color = Color.BLACK
elif pen_flags[2]:
color = lerp(drawing_color2, drawing_color1, pressure)
else:
color = drawing_color1
color.a *= opacity
if pen_flags[1]:
color.a *= pressure
var new_rect := Rect2i(stroke_start, Vector2i.ZERO)\
.expand(stroke_end)\
.grow(radius + 2)\
.intersection(Rect2i(Vector2i.ZERO, on_image.get_size()))
if new_rect.size == Vector2i.ZERO:
return
last_affected_rect = last_affected_rect\
.expand(Vector2i(new_rect.position) + chunk_position)\
.expand(Vector2i(new_rect.end) + chunk_position)
stroke_start = stroke_start.floor() + Vector2(0.5, 0.5)
stroke_end = stroke_end.floor() + Vector2(0.5, 0.5)
var cur_pos
for i in new_rect.size.x:
for j in new_rect.size.y:
cur_pos = new_rect.position + Vector2i(i, j)
if is_out_of_bounds(cur_pos + chunk_position, selection.get_size()):
continue
if !selection.get_bitv(cur_pos + chunk_position):
continue
on_image.set_pixelv(cur_pos, get_new_pixel(
on_image, color,
stroke_start, stroke_end, Vector2(cur_pos) + brush_offset,
radius, solid_radius
))
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
var old_color = on_image.get_pixelv(cur_pos)
var distance = Geometry2D.get_closest_point_to_segment(
cur_pos, stroke_start, stroke_end
).distance_to(cur_pos)
if distance <= solid_radius:
var blended = old_color.blend(color)
blended.a = max(old_color.a, color.a)
return blended
elif distance <= radius:
var blended := old_color.blend(color)
distance = (distance - solid_radius) / (radius - solid_radius)
# Possible better handling of variable pressure,
# but creates artifacts when zig-zagging
blended.a = max(old_color.a, color.a * (1.0 - distance * distance))
# This one also creates artifacts, but this is during normal brush usage.
# blended.a = lerp(old_color.a, color.a, (1.0 - distance * distance))
return blended
else:
return old_color
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if drawing:
for k in last_edits_chunks:
last_edits_textures[k].update(last_edits_chunks[k])
image_view.draw_texture(last_edits_textures[k], k)
var circle_center = Vector2(mouse_position + Vector2i.ONE) - brush_offset
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 0.1, PI * 0.9, 32, crosshair_color, 1.0)
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 1.1, PI * 1.9, 32, crosshair_color, 1.0)
# With region set to (0, 0, 0, 0), hides the image.
# image_view.region_enabled = drawing

View File

@ -0,0 +1,81 @@
@tool
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
@export var clone_preview_color := Color(0.5, 0.5, 0.5, 0.75)
var replace_alpha := false
var clone_offset := Vector2.ZERO
var clone_offset_editing := false
var source_image : Image
var clone_offset_view : Control
func _ready():
super._ready()
clone_offset_view = add_text_display()
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
source_image = image
if event.button_index == MOUSE_BUTTON_RIGHT:
clone_offset_editing = event.pressed
clone_offset = clone_offset.floor()
else:
super.mouse_pressed(event, image, color1, color2)
func mouse_moved(event : InputEventMouseMotion):
if clone_offset_editing:
clone_offset -= event.relative
clone_offset_view.text = "Offset: " + str(clone_offset.floor())
super.mouse_moved(event)
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
var old_color := on_image.get_pixelv(cur_pos)
var cloned_color := source_image.get_pixel(
fposmod(cur_pos.x + clone_offset.x, source_image.get_width()),
fposmod(cur_pos.y + clone_offset.y, source_image.get_height())
)
var distance := Geometry2D.get_closest_point_to_segment(
cur_pos, stroke_start, stroke_end
).distance_to(cur_pos)
if distance <= solid_radius:
var blended = old_color.blend(cloned_color)
blended.a = max(old_color.a, cloned_color.a)
return blended
elif distance <= radius:
var blended = old_color.blend(cloned_color)
distance = (distance - solid_radius) / (radius - solid_radius)
# blended.a = max(old_color.a, cloned_color.a * (1.0 - distance * distance))
blended.a = lerp(old_color.a, cloned_color.a, (1.0 - distance * distance))
return blended
else:
return old_color
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
super.draw_preview(image_view, mouse_position)
if clone_offset == Vector2.ZERO:
image_view.draw_string(
get_theme_font("main", "EditorFonts"),
mouse_position,
"Hold Right Mouse and drag to change offset.",
HORIZONTAL_ALIGNMENT_CENTER
)
var circle_center := Vector2(mouse_position + Vector2i.ONE + Vector2i(clone_offset)) - brush_offset
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 0.6, PI * 1.4, 32, clone_preview_color, 1.0)
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 1.6, PI * 2.4, 32, clone_preview_color, 1.0)

View File

@ -0,0 +1,78 @@
@tool
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
@export var tilt_preview_scale := 4.0
var shrink := 1.0
var mouse_tilt_editing := false
var mouse_tilt := Vector2.ZERO
var tilt : Vector2
var tilt_length : float
func _ready():
super._ready()
add_property("Edge Shrink", shrink * 100,
func (x): shrink = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
if event.button_index == MOUSE_BUTTON_RIGHT:
mouse_tilt_editing = event.pressed
mouse_tilt = mouse_tilt.limit_length(1.0)
else:
super.mouse_pressed(event, image, color1, color2)
func mouse_moved(event : InputEventMouseMotion):
if mouse_tilt_editing:
mouse_tilt += event.relative / (brushsize * tilt_preview_scale)
tilt = mouse_tilt.limit_length(1.0)
return
if event.tilt != Vector2.ZERO:
tilt = Vector2(event.tilt.x, -event.tilt.y)
var joy_tilt = Vector2(Input.get_joy_axis(0, JOY_AXIS_LEFT_X), Input.get_joy_axis(0, JOY_AXIS_LEFT_Y))
if joy_tilt != Vector2.ZERO:
tilt = joy_tilt
tilt_length = tilt.length() * 0.707214
var tilt_basis = Basis.from_euler(Vector3(tilt.x * PI * 0.499, tilt.y * PI * 0.499, 0.0))
var normal = Vector3.BACK * tilt_basis
drawing_color1 = Color(normal.y * 0.5 + 0.5, normal.x * 0.5 + 0.5, normal.z, 1.0)
super.mouse_moved(event)
func get_new_pixel(on_image, color, stroke_start, stroke_end, cur_pos, radius, solid_radius):
return super.get_new_pixel(
on_image, on_image.get_pixelv(cur_pos).blend(Color(color, color.a)), stroke_start, stroke_end, cur_pos,
radius * (1.0 - tilt_length * shrink), solid_radius * (1.0 - tilt_length * shrink)
)
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
var circle_center = Vector2(mouse_position + Vector2i.ONE) - brush_offset
image_view.draw_line(
circle_center,
circle_center + tilt * brushsize * tilt_preview_scale,
crosshair_color,
1.01
)
if mouse_tilt_editing:
image_view.draw_arc(circle_center - mouse_tilt * brushsize * tilt_preview_scale, brushsize * tilt_preview_scale + 0.5, PI * 0.6, PI * 1.4, 32, crosshair_color, 1.0)
image_view.draw_arc(circle_center - mouse_tilt * brushsize * tilt_preview_scale, brushsize * tilt_preview_scale + 0.5, PI * 1.6, PI * 2.4, 32, crosshair_color, 1.0)
super.draw_preview(image_view, mouse_position)

View File

@ -0,0 +1,46 @@
@tool
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
var fill_mode := 0
var tolerance := 0.0
var allowed_pixels = BitMap.new()
var image_size := Vector2i()
func _ready():
super._ready()
add_property("Fill Mode", fill_mode,
func (x): fill_mode = x,
TOOL_PROP_ENUM,
[&"Contiguous", &"Global"]
)
add_property("Tolerance", tolerance * 100,
func (x): tolerance = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
image_size = image.get_size()
if event.pressed:
allowed_pixels.create(image.get_size())
ImageFillTools.fill_on_image(
image, allowed_pixels, event.position,
tolerance, fill_mode == 0, selection
)
super.mouse_pressed(event, image, color1, color2)
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
if !allowed_pixels.get_bitv(cur_pos):
return on_image.get_pixelv(cur_pos)
return super.get_new_pixel(on_image, color, stroke_start, stroke_end, cur_pos, radius, solid_radius)

View File

@ -0,0 +1,85 @@
@tool
extends EditingTool
var fill_mode := 0
var tolerance := 0.0
var drawing := false
var drawing_color := Color.BLACK
var image : Image
var start_color := Color.TRANSPARENT
var affected_pixels := BitMap.new()
var last_affected_rect := Rect2i()
func _ready():
add_name()
start_property_grid()
add_property("Fill Mode", fill_mode,
func (x): fill_mode = x,
TOOL_PROP_ENUM,
[&"Contiguous", &"Global"]
)
add_property("Tolerance", tolerance * 100,
func (x): tolerance = x * 0.01,
TOOL_PROP_INT,
[0, 100],
true
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
drawing = event.pressed
drawing_color = Color.BLACK.blend(color1)
start_color = image.get_pixelv(event.position)
self.image = image
if drawing:
affected_pixels.create(image.get_size())
fill(event.position)
else:
for i in image.get_width():
for j in image.get_height():
if affected_pixels.get_bit(i, j):
set_image_pixel(image, i, j, color1)
func get_affected_rect():
return last_affected_rect.grow_individual(0, 0, 1, 1)
func mouse_moved(event : InputEventMouseMotion):
if !drawing: return
if is_out_of_bounds(event.position, image.get_size()):
affected_pixels.create(affected_pixels.get_size())
return
var cur_color := image.get_pixelv(event.position)
if cur_color == start_color:
return
start_color = cur_color
fill(event.position)
func fill(start_pos : Vector2):
last_affected_rect = ImageFillTools.fill_on_image(
image,
affected_pixels,
start_pos,
tolerance,
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT),
selection
)
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if !drawing: return
image_view.draw_rect(Rect2(mouse_position, Vector2.ONE), Color.WHITE)
ImageFillTools.draw_bitmap(image_view, affected_pixels, drawing_color)

View File

@ -0,0 +1,186 @@
@tool
extends EditingTool
enum {
GRADIENT_LINEAR,
GRADIENT_LINEAR_MIRRORED,
GRADIENT_RADIAL,
GRADIENT_CONIC,
GRADIENT_BOUNDING_BOX,
}
@export var line_color = Color.GRAY
@export var line_aligned_color = Color.GREEN
@export var point_grab_area = Vector2(5, 5)
@export var point_grab_color = Color.GRAY
@export var shader_preview : NodePath
@export var shader_viewport_texture : Texture2D
var gradient_type := 0
var fill_mode := 0
var tolerance := 1.0
var drawing := false
var default_gradient := Gradient.new()
var custom_gradient : Gradient = null
var default_color2 := Color.TRANSPARENT
var affected_pixels := BitMap.new()
var affected_pixels_tex : ImageTexture
var points := [Vector2(-INF, -INF), Vector2(-INF, -INF)]
var point_grabbed := -1
var last_affected_rect := Rect2i()
func _ready():
default_gradient.add_point(1.0, Color.WHITE)
add_name()
start_property_grid()
add_property("Type", gradient_type,
func (x): gradient_type = x,
TOOL_PROP_ICON_ENUM,
{
"Line": "Linear",
"Hsize": "Linear Mirrored",
"Node": "Radial",
"ToolRotate": "Conic",
"Groups": "Bounding Box",
},
true
)
add_property("Gradient", custom_gradient,
func (x): custom_gradient = x,
TOOL_PROP_RESOURCE,
["Gradient"]
)
add_property("Fill Mode", fill_mode,
func (x): fill_mode = x,
TOOL_PROP_ENUM,
[&"Contiguous", &"Global"]
)
add_property("Tolerance", tolerance * 100,
func (x): tolerance = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
drawing = event.pressed
default_gradient.set_color(0, color1)
default_gradient.set_color(1, color2)
var cur_color := image.get_pixelv(event.position)
if drawing:
if try_grab_point(event):
return
if is_out_of_bounds(event.position, image.get_size()):
affected_pixels.create(affected_pixels.get_size())
return
points[0] = event.position.floor()
points[1] = event.position.floor()
affected_pixels.create(image.get_size())
fill(event.position, image)
var mask = affected_pixels.convert_to_image()
mask.clear_mipmaps()
affected_pixels_tex = ImageTexture.create_from_image(mask)
update_preview_shader()
point_grabbed = 1
else:
point_grabbed = -1
if points[0] == points[1]:
points[0] = Vector2(-INF, -INF)
points[1] = Vector2(-INF, -INF)
return
var result_image = shader_viewport_texture.get_image()
for i in result_image.get_width():
for j in result_image.get_height():
if affected_pixels.get_bit(i, j):
set_image_pixel(image, i, j, result_image.get_pixel(i, j))
func try_grab_point(event : InputEventMouse) -> bool:
for i in points.size():
if Rect2(points[i] - point_grab_area * 0.5, point_grab_area).has_point(event.position):
point_grabbed = i
return true
return false
func get_affected_rect():
return last_affected_rect.grow_individual(0, 0, 1, 1)
func mouse_moved(event : InputEventMouseMotion):
if point_grabbed == -1: return
if Input.is_key_pressed(KEY_SHIFT):
var origin = points[1 - point_grabbed]
var angle_rounded = snappedf(origin.angle_to_point(event.position), PI * 0.25)
var distance = (event.position - origin).length()
points[point_grabbed] = (origin + Vector2(
distance * cos(angle_rounded),
distance * sin(angle_rounded)
)).floor()
else:
points[point_grabbed] = event.position.floor()
update_preview_shader()
func update_preview_shader():
var preview_node = get_node(shader_preview)
var g_tex = preview_node.material.get_shader_parameter("gradient")
g_tex.gradient = default_gradient if custom_gradient == null else custom_gradient
preview_node.texture = affected_pixels_tex
preview_shader.set_shader_parameter("gradient", g_tex)
preview_shader.set_shader_parameter("type", gradient_type)
preview_shader.set_shader_parameter("from", points[0] / Vector2(affected_pixels.get_size()))
preview_shader.set_shader_parameter("delta", (points[1] - points[0]) / Vector2(affected_pixels.get_size()))
func fill(start_pos : Vector2, image : Image):
last_affected_rect = ImageFillTools.fill_on_image(
image,
affected_pixels,
start_pos,
tolerance,
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT),
selection
)
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
if points[0] == Vector2(-INF, -INF):
image_view.hide()
return
image_view.texture = affected_pixels_tex
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if points[0] == Vector2(-INF, -INF):
return
for i in points.size():
image_view.draw_line(points[0], points[1], line_color, 1.1)
image_view.draw_rect(
Rect2(points[i] - point_grab_area * 0.5, point_grab_area),
point_grab_color, false, 2
)
func _on_visibility_changed():
points = [Vector2(-INF, -INF), Vector2(-INF, -INF)]
super._on_visibility_changed()

View File

@ -0,0 +1,49 @@
@tool
extends "./tool_shape.gd"
func _ready():
add_name()
start_property_grid()
add_property("Width", line_width,
func (x): line_width = x,
TOOL_PROP_INT,
[0, 250]
)
add_property("Flags", [erase_mode, aa],
func (x):
print(x)
erase_mode = x[0]
aa = x[1],
TOOL_PROP_ICON_FLAGS,
{"Eraser" : "Erase Mode", "CurveTexture" : "Anti-Aliasing"}
)
func mouse_moved(event : InputEventMouseMotion):
if !drawing: return
if Input.is_key_pressed(KEY_SHIFT):
var angle_rounded = snappedf((Vector2(start_pos)).angle_to_point(event.position), PI * 0.25)
var distance = (event.position - Vector2(start_pos)).length()
end_pos = (Vector2(start_pos) + Vector2(
distance * cos(angle_rounded),
distance * sin(angle_rounded)
)).round()
else:
end_pos = event.position.floor()
func get_affected_rect():
return super.get_affected_rect().grow(line_width)
func update_preview_cheddar():
var rect = super.get_affected_rect()
preview_shader.set_shader_parameter("color", color_line if !erase_mode else Color.BLACK.blend(color_line));
preview_shader.set_shader_parameter("origin", Vector2(start_pos) + Vector2(0.5, 0.5));
preview_shader.set_shader_parameter("width", line_width);
preview_shader.set_shader_parameter("enable_aa", aa);
if end_pos == start_pos: return # The SDF for this case evaluates to being inside; abort
preview_shader.set_shader_parameter("delta", end_pos - start_pos);

View File

@ -0,0 +1,98 @@
@tool
extends EditingTool
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
@export var crosshair_size := 3
@export var crosshair_size_ruler := 32
var ruler_mode := false
var jaggies_removal := true
var drawing := false
var drawing_color := Color()
var drawing_positions : Array[Vector2]
var image_size := Vector2()
var last_affected_rect := Rect2i()
func _ready():
add_name()
start_property_grid()
add_property("Guide Lines", ruler_mode,
func (x): ruler_mode = x,
TOOL_PROP_BOOL,
null,
true
)
add_property("Remove Jaggies", jaggies_removal,
func (x): jaggies_removal = x,
TOOL_PROP_BOOL
)
drawing_positions = []
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
drawing = event.pressed
drawing_color = Color.BLACK.blend(color1)
image_size = image.get_size()
if drawing:
drawing_positions.clear()
last_affected_rect = Rect2i(event.position, Vector2i.ZERO)
_add_point(event.position)
else:
for x in drawing_positions:
set_image_pixelv(image, x, color1)
func get_affected_rect():
return last_affected_rect.grow_individual(0, 0, 1, 1)
func mouse_moved(event : InputEventMouseMotion):
if !drawing: return
var pt_count = max(abs(event.relative.x), abs(event.relative.y))
var lerp_step = 1 / pt_count
for i in pt_count:
_add_point(event.position + Vector2.ONE - event.relative * i * lerp_step - Vector2.ONE)
func _add_point(pt : Vector2):
pt = pt.floor()
if drawing_positions.size() >= 1 && drawing_positions[-1] == pt:
return
if pt.x < 0 || pt.y < 0 || pt.x >= image_size.x || pt.y >= image_size.y:
return
if jaggies_removal && drawing_positions.size() > 2:
var diff1 = (drawing_positions[-1] - drawing_positions[-2]).abs()
var diff2 = (pt - drawing_positions[-1]).abs()
if diff1 != diff2 && diff1.x + diff1.y + diff2.x + diff2.y == 2:
drawing_positions.pop_back()
drawing_positions.append(pt)
last_affected_rect = last_affected_rect.expand(pt)
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if drawing:
for x in drawing_positions:
image_view.draw_rect(Rect2(x, Vector2.ONE), drawing_color)
if !ruler_mode:
draw_crosshair(image_view, mouse_position, crosshair_size, crosshair_color)
else:
draw_crosshair(image_view, mouse_position, crosshair_size_ruler, crosshair_color)
var diag_distance := 8
var posf := Vector2(mouse_position) + Vector2(0.5, 0.5)
image_view.draw_line(posf + Vector2(-1, +1) * diag_distance, posf + Vector2(-1, +1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
image_view.draw_line(posf + Vector2(+1, -1) * diag_distance, posf + Vector2(+1, -1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
image_view.draw_line(posf + Vector2(+1, +1) * diag_distance, posf + Vector2(+1, +1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
image_view.draw_line(posf + Vector2(-1, -1) * diag_distance, posf + Vector2(-1, -1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)

View File

@ -0,0 +1,138 @@
@tool
extends EditingTool
@export var workspace : NodePath
var live_update := true
var timer : Timer
var param_grid : Control
var script_instance : ImageScript
var original_image : Image
var result_image_tex : ImageTexture
func _ready():
add_name()
var button = add_property("Script",
"",
load_script,
TOOL_PROP_FOLDER_SCAN,
"res://addons/sprite_painter/image_scripts"
)
button.text = "Choose script... "
button.fit_to_longest_item = false
param_grid = start_property_grid()
add_separator()
add_property("Live Update",
live_update,
func(x):
live_update = x
if x: update_script(true),
TOOL_PROP_BOOL
)
var buttons = add_button_panel(["Apply", "Reset"]).get_children()
buttons[0].pressed.connect(func():
var ws = get_node(workspace)
var old_image = ws.edited_image
ws.replace_image(old_image, result_image_tex.get_image())
ws.image_replaced.emit(old_image, ws.edited_image)
)
buttons[1].pressed.connect(func(): load_script(script_instance.get_script()))
# Updates are expensive if images are changed on the CPU.
# Update only sometimes to reduce lag
timer = Timer.new()
timer.wait_time = 2.0
timer.one_shot = true
timer.timeout.connect(_on_timer_timeout)
add_child(timer)
func load_script(script : Script):
script_instance = script.new()
param_grid.free()
param_grid = start_property_grid()
param_grid.get_parent().move_child(param_grid, 3)
param_grid.get_parent().get_child(2).hide()
for x in script_instance._get_param_list():
add_property(
x[0], # Name
x[2], # Value
func(y):
script_instance._params[x[0]] = y
update_script()
if y is Resource && !y.changed.is_connected(update_script):
y.changed.connect(update_script),
x[1], # Type
x[3] if x.size() > 3 else null # Hint (if any)
)
script_instance._params[x[0]] = x[2]
if x[2] is Resource && !x[2].changed.is_connected(update_script):
x[2].changed.connect(update_script)
script_instance._ready(original_image)
update_script()
func update_script(automatic : bool = false):
if !live_update: return
if timer.time_left != 0.0: return
var new_image = Image.create_from_data(
original_image.get_width(),
original_image.get_height(),
false,
original_image.get_format(),
original_image.get_data()
)
result_image_tex = ImageTexture.create_from_image(
script_instance._get_image(new_image, selection)
)
if !automatic:
timer.start()
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
original_image = image
func get_affected_rect() -> Rect2i:
return Rect2i()
func mouse_moved(event : InputEventMouseMotion):
original_image = get_node(workspace).edited_image
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
pass
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
image_view.texture = result_image_tex
func _on_visibility_changed():
super._on_visibility_changed()
if is_visible_in_tree() && script_instance != null:
script_instance._ready(original_image)
update_script(true)
func _on_timer_timeout():
update_script(true)
func _on_workspace_image_replaced(old_image, new_image):
original_image = new_image
if is_visible_in_tree() && script_instance != null:
script_instance._ready(original_image)
update_script(true)

View File

@ -0,0 +1,219 @@
@tool
extends EditingTool
@export var preview_color := Color("1e90ff7f")
@export var crosshair_size := 3
enum {
DRAG_LEAVE_TRANSPARENT,
DRAG_LEAVE_SECONDARY,
DRAG_SELECTION_ONLY,
DRAG_NONE,
}
var operation := 0
var dragging_mode := DRAG_LEAVE_TRANSPARENT
var mouse_down := false
var selection_dragging := false
var image : Image
var draw_start := Vector2i()
var draw_end := Vector2i()
var image_size := Vector2i()
func _ready():
add_name()
start_property_grid()
add_selection_common_properties()
add_selection_button_panel()
func add_selection_common_properties():
add_property("Operation", operation,
func (x): operation = x,
TOOL_PROP_ENUM,
["Replace", "Add (Ctrl)", "Subtract (Right-click)", "Intersection", "Invert"],
true
)
add_property("Drag Mode", dragging_mode,
func (x): dragging_mode = x,
TOOL_PROP_ENUM,
["Transparent BG", "Secondary Color BG", "Move Selection Only", "Never Drag"]
)
func add_selection_button_panel():
var selection_view = $"%SelectionView"
var workspace = $"%Workspace"
add_separator()
var buttons = add_button_panel(
["Deselect", "Erase", "Invert"]
).get_children()
buttons[0].pressed.connect(func():
selection.set_bit_rect(Rect2i(Vector2i.ZERO, selection.get_size()), true)
selection_view.queue_redraw()
)
# This fragment of code is worth framing IMO.
buttons[1].pressed.connect(func():
var bg_color = Color.TRANSPARENT
if dragging_mode == DRAG_LEAVE_SECONDARY:
bg_color = get_parent().current_color2
workspace.make_image_edit(
func():
for i in selection.get_size().x:
for j in selection.get_size().y:
if selection.get_bit(i, j):
image.set_pixel(i, j, bg_color),
Rect2i(Vector2i.ZERO, selection.get_size())
)
)
buttons[2].pressed.connect(func():
for i in selection.get_size().x:
for j in selection.get_size().y:
selection.set_bit(i, j, !selection.get_bit(i, j))
selection_view.queue_redraw()
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
var subtract = Input.is_key_pressed(KEY_ALT) || event.button_index == MOUSE_BUTTON_RIGHT
var add = Input.is_key_pressed(KEY_CTRL) || Input.is_key_pressed(KEY_META)
self.image = image
image_size = image.get_size()
mouse_down = event.pressed
if mouse_down:
draw_start = event.position
draw_end = draw_start
selection_dragging = (
dragging_mode != DRAG_NONE
&& !subtract && !add
&& selection.get_bitv(event.position)
&& !is_selection_empty()
)
elif selection_dragging:
move_selected_pixels(
image,
Vector2i(draw_end - draw_start),
add,
color2
)
else:
apply_selection(add, subtract)
func apply_selection(add_modifier, subtract_modifier):
var rect = get_selection_rect()
if operation == OPERATION_REPLACE || is_selection_empty():
if !add_modifier && !subtract_modifier:
selection.set_bit_rect(Rect2i(Vector2i.ZERO, image_size), false)
selection.set_bit_rect(rect, !subtract_modifier)
match operation:
OPERATION_ADD:
selection.set_bit_rect(rect, !subtract_modifier)
OPERATION_SUBTRACT:
selection.set_bit_rect(rect, subtract_modifier)
OPERATION_INTERSECTION, OPERATION_XOR:
var intersect = operation == OPERATION_INTERSECTION
var result_bit
for i in image_size.x:
for j in image_size.y:
result_bit = (
selection.get_bit(i, j)
&& rect.has_point(Vector2i(i, j))
) if intersect else (
selection.get_bit(i, j)
!= rect.has_point(Vector2i(i, j))
)
selection.set_bit(i, j, result_bit != subtract_modifier)
func move_selected_pixels(image, drag_vec, copy, back_color):
var old_pixels = []
var old_sel = BitMap.new()
var move_image = dragging_mode != DRAG_SELECTION_ONLY
old_sel.create(image_size)
old_pixels.resize(image_size.x * image_size.y)
if dragging_mode == DRAG_LEAVE_TRANSPARENT:
back_color = Color.TRANSPARENT
# Go through source pixels
var source_selected := false
for i in image_size.x:
for j in image_size.y:
source_selected = selection.get_bit(i, j)
old_sel.set_bit(i, j, source_selected)
selection.set_bit(i, j, false)
old_pixels[i + j * image_size.x] = image.get_pixel(i, j)
if !copy && source_selected && move_image:
image.set_pixel(i, j, back_color)
# Paste into destination pixels
var dest_pixel : Color
var dest_pos : Vector2i
for i in image_size.x:
for j in image_size.y:
dest_pos = Vector2i(i, j) - drag_vec
if (is_out_of_bounds(dest_pos, image_size) || !old_sel.get_bitv(dest_pos)):
continue
selection.set_bit(i, j, true)
dest_pixel = old_pixels[dest_pos.x + dest_pos.y * image_size.x]
if move_image:
image.set_pixel(i, j, image.get_pixel(i, j).blend(dest_pixel))
func get_affected_rect():
if selection_dragging:
# Can be anything!
return Rect2i(Vector2i.ZERO, image_size)
else:
return Rect2()
func get_selection_rect():
if draw_start == draw_end: return Rect2i()
return get_rect_from_drag(draw_start, draw_end, Input.is_key_pressed(KEY_SHIFT))
func mouse_moved(event : InputEventMouseMotion):
draw_end = event.position
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if mouse_down:
if selection_dragging:
ImageFillTools.draw_bitmap(
image_view,
selection,
preview_color,
mouse_position - draw_start
)
else:
draw_selection_preview(image_view, mouse_position)
return
draw_crosshair(image_view, mouse_position, crosshair_size, preview_color)
func draw_selection_preview(image_view : CanvasItem, mouse_position : Vector2i):
image_view.draw_rect(get_selection_rect(), preview_color)

View File

@ -0,0 +1,155 @@
@tool
extends EditingTool
enum {
SHAPE_RECTANGLE,
SHAPE_ELLIPSE,
SHAPE_TRIANGLE,
SHAPE_DIAMOND,
SHAPE_HEXAGON,
}
const I_SIN120 = sin(PI * 0.66666) # Used for making hexagons have equal qdges
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
@export var crosshair_size := 3
@export var shader_viewport_texture : Texture2D
var shape := 0
var fill_mode := 0
var line_width := 1
var erase_mode := false
var aa := false
var drawing := false
var color_line : Color = Color.BLACK
var color_fill : Color = Color.WHITE
var image_size := Vector2()
var start_pos := Vector2i()
var end_pos := Vector2i()
func _ready():
var icon_folder = "res://addons/sprite_painter/graphics/"
add_name()
start_property_grid()
add_property("Shape", shape,
func (x): shape = x,
TOOL_PROP_ICON_ENUM,
{
load(icon_folder + "rect_shape_2d.svg") : "Rectangle",
load(icon_folder + "circle_shape_2d.svg") : "Ellipse",
load(icon_folder + "triangle_shape_2d.svg") : "Triangle",
load(icon_folder + "diamond_shape_2d.svg") : "Diamond",
load(icon_folder + "hex_shape_2d.svg") : "Hexagon",
},
true
)
add_property("Fill Color", fill_mode,
func (x): fill_mode = x,
TOOL_PROP_ENUM,
["Primary", "Secondary", "None (outline only)"]
)
add_property("Border Width", line_width,
func (x): line_width = x,
TOOL_PROP_INT,
[0, 20]
)
add_property("Flags", [erase_mode, aa],
func (x):
erase_mode = x[0]
aa = x[1],
TOOL_PROP_ICON_FLAGS,
{"Eraser" : "Erase Mode", "CurveTexture" : "Anti-Aliasing"}
)
func mouse_pressed(
event : InputEventMouseButton,
image : Image,
color1 : Color = Color.BLACK,
color2 : Color = Color.WHITE,
):
drawing = event.pressed
color_line = color1
color_fill = [color1, color2, Color.TRANSPARENT][fill_mode]
image_size = image.get_size()
if drawing:
start_pos = Vector2i(event.position)
end_pos = Vector2i(event.position)
else:
var rect = get_affected_rect()
var new_image = shader_viewport_texture.get_image()
var cur_pos : Vector2i
var cur_pixel : Color
for i in rect.size.x:
for j in rect.size.y:
cur_pos = rect.position + Vector2i(i, j)
if is_out_of_bounds(cur_pos, image_size):
continue
cur_pixel = image.get_pixelv(cur_pos)
if !erase_mode:
set_image_pixelv(image, cur_pos,
cur_pixel.blend(new_image.get_pixelv(cur_pos))
)
else:
set_image_pixelv(image, cur_pos, Color(
cur_pixel,
(cur_pixel.a - new_image.get_pixelv(cur_pos).a)
))
func get_affected_rect():
var squarify = Input.is_key_pressed(KEY_SHIFT)
var rect = get_rect_from_drag(start_pos, end_pos, squarify)
if shape == SHAPE_HEXAGON && squarify:
if (start_pos.x < end_pos.x) == (start_pos.y < end_pos.y):
rect.size.y *= I_SIN120
else:
rect.size.x *= I_SIN120
return rect
func mouse_moved(event : InputEventMouseMotion):
if !drawing: return
end_pos = event.position
func update_preview_cheddar():
var rect = get_affected_rect()
preview_shader.set_shader_parameter("shape_index", shape);
preview_shader.set_shader_parameter("color_border", color_line if !erase_mode else Color.BLACK.blend(color_line));
preview_shader.set_shader_parameter("color_fill", color_fill);
preview_shader.set_shader_parameter("origin", rect.position);
preview_shader.set_shader_parameter("shape_size", rect.size);
preview_shader.set_shader_parameter("border_width", line_width);
preview_shader.set_shader_parameter("drag_delta", start_pos - end_pos);
preview_shader.set_shader_parameter("enable_aa", aa);
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
image_view.texture = null
if !drawing:
image_view.hide()
else:
if image_view.texture == null || image_view.texture.get_size() != image_size:
image_view.texture = ImageTexture.create_from_image(Image.create(
image_size.x,
image_size.y,
false,
Image.FORMAT_L8 # Doesn't matter, won't read from it
))
update_preview_cheddar()
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if !drawing:
draw_crosshair(image_view, mouse_position, crosshair_size, crosshair_color)

View File

@ -0,0 +1,73 @@
@tool
extends "./tool_select.gd"
var selection_operations = [
func(s, d): return d,
func(s, d): return s || d,
func(s, d): return s && !d,
func(s, d): return s && d,
func(s, d): return s != d,
]
var fill_mode := 0
var tolerance := 0.0
func _ready():
add_name()
start_property_grid()
add_selection_common_properties()
add_property("Fill Mode", fill_mode,
func (x): fill_mode = x,
TOOL_PROP_ENUM,
[&"Contiguous", &"Global"]
)
add_property("Tolerance", tolerance * 100,
func (x): tolerance = x * 0.01,
TOOL_PROP_INT,
[0, 100]
)
add_selection_button_panel()
func apply_selection(add_modifier, subtract_modifier):
var affected_pixels = BitMap.new()
affected_pixels.create(image_size)
var affected_rect = ImageFillTools.fill_on_image(
image,
affected_pixels,
draw_end,
tolerance,
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT)
)
affected_rect.size += Vector2i.ONE
var used_op_index = operation
if operation == OPERATION_REPLACE || is_selection_empty():
if !add_modifier && !subtract_modifier:
selection.set_bit_rect(Rect2i(Vector2i.ZERO, image_size), false)
if add_modifier:
used_op_index = OPERATION_ADD
var used_op = selection_operations[used_op_index]
var cur_pos : Vector2i
for i in affected_rect.size.x:
for j in affected_rect.size.y:
cur_pos = Vector2i(i, j) + affected_rect.position
if !affected_pixels.get_bitv(cur_pos): continue
selection.set_bitv(cur_pos, used_op.call(
selection.get_bitv(cur_pos), true
) != subtract_modifier)
func draw_selection_preview(image_view : CanvasItem, mouse_position : Vector2i):
var affected_pixels = BitMap.new()
affected_pixels.create(image_size)
var affected_rect = ImageFillTools.fill_on_image(
image,
affected_pixels,
draw_end,
tolerance,
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT)
)
ImageFillTools.draw_bitmap(image_view, affected_pixels, preview_color)

View File

@ -0,0 +1,113 @@
@tool
class_name ImageFillTools
static func fill_on_image(
image : Image,
result_into_mask : BitMap,
start_pos : Vector2i,
tolerance : float = 0.0,
fill_contiguous : bool = true,
selection : BitMap = null,
) -> Rect2i:
var affected_rect : Rect2i
result_into_mask.create(image.get_size())
if fill_contiguous:
affected_rect = flood_fill(image, result_into_mask, start_pos, tolerance, selection)
else:
affected_rect = global_fill(image, result_into_mask, start_pos, tolerance)
return affected_rect
static func flood_fill(
image : Image,
result_into_mask : BitMap,
start_pos : Vector2i,
tolerance : float = 0.0,
selection : BitMap = null
) -> Rect2i:
var start_color = image.get_pixelv(start_pos)
var affected_rect = Rect2i(start_pos, Vector2.ZERO)
var q = [start_pos]
while q.size() > 0:
var x = q.pop_front()
result_into_mask.set_bitv(x, true)
for pos in [x + Vector2i.RIGHT, x + Vector2i.DOWN, x + Vector2i.LEFT, x + Vector2i.UP]:
if (
is_out_of_bounds(pos, image.get_size())
|| result_into_mask.get_bitv(pos)
|| (selection != null && !selection.get_bitv(pos))
):
continue
if tolerance == 1.0 || get_color_distance_squared(start_color, image.get_pixelv(pos)) <= tolerance:
affected_rect = affected_rect.expand(pos)
result_into_mask.set_bitv(pos, true)
q.append(pos)
return affected_rect
static func global_fill(
image : Image,
result_into_mask : BitMap,
start_pos : Vector2i,
tolerance : float = 0.0
) -> Rect2i:
var start_color = image.get_pixelv(start_pos)
var affected_rect = Rect2i(start_pos, Vector2.ZERO)
for i in image.get_width():
for j in image.get_height():
if tolerance == 1.0 || get_color_distance_squared(start_color, image.get_pixel(i, j)) <= tolerance:
affected_rect = affected_rect.expand(Vector2i(i, j))
result_into_mask.set_bit(i, j, true)
return affected_rect
static func get_color_distance_squared(a : Color, b : Color) -> float:
if a.a + b.a == 0.0: return 0.0
return (
(a.r - b.r) * (a.r - b.r)
+ (a.g - b.g) * (a.g - b.g)
+ (a.b - b.b) * (a.b - b.b)
+ (a.a - b.a) * (a.a - b.a)
) * 0.33333
static func draw_bitmap(on_node : CanvasItem, bitmap : BitMap, color : Color, offset : Vector2 = Vector2(0, 0)):
var map_size = bitmap.get_size()
var draw_next = false
var rect_height = 0
var draw_pos : Vector2
for i in map_size.x:
for j in map_size.y:
if bitmap.get_bit(i, j) != draw_next || j == 0:
if rect_height >= 1 && draw_next:
draw_pos = Vector2(i, j - rect_height)
if j == 0:
draw_pos = Vector2(i - 1, map_size.y - rect_height)
on_node.draw_rect(Rect2(
draw_pos + offset,
Vector2(1, rect_height)
), color)
rect_height = 0
rect_height += 1
draw_next = bitmap.get_bit(i, j)
on_node.draw_rect(Rect2(
offset + Vector2(map_size.x - 1, map_size.y - rect_height),
Vector2(1, rect_height)
), color)
static func is_out_of_bounds(pos : Vector2i, rect_size : Vector2i) -> bool:
return (
pos.x < 0 || pos.y < 0
|| pos.x >= rect_size.x || pos.y >= rect_size.y
)

View File

@ -0,0 +1,168 @@
@tool
extends MarginContainer
signal color_changed(new_color, is_primary)
@export var workspace : NodePath
@export var picker_shortcut : Shortcut
@onready var color1_button = $Container/Box/Box/Box/Control/Color1
@onready var color2_button = $Container/Box/Box/Box/Control/Color2
@onready var palette = $Container/Box/Box/Palette
@onready var which_color_picked = $Container/Box/Box/WhichColor
@onready var color_picker_container = $Control/Picker
@onready var color_picker = $Control/Picker/Margins/Box/ColorPicker
@onready var color_picker_tool_button = $Container/Box/Box/Box/Control/Control.get_child(0)
var color1 := Color.WHITE
var color2 := Color.TRANSPARENT
var color_picker_primary := true
var color_picker_picking := false
var color_picker_picking_disable_with_click := false
var color_picker_pick_from_image := false
var screen_image : Image
func _ready():
if get_viewport() is SubViewport: return
hide()
size = Vector2.ZERO
show()
position = Vector2(0, get_parent().size.y - get_minimum_size().y)
set_picked_primary(true, false)
set_color(true, color1)
set_color(false, color2)
_on_visibility_changed()
_yoink_color_picker_tool_button()
hide()
show()
color_picker_container.hide()
color_picker_container.size = Vector2.ZERO
await get_tree().process_frame
color_picker_container.global_position = (
global_position
+ get_minimum_size()
+ Vector2(16, -color_picker_container.size.y)
)
func set_color(is_primary, color):
if is_primary:
color1 = color
color1_button.self_modulate = color
else:
color2 = color
color2_button.self_modulate = color
if color_picker_primary == is_primary:
color_picker.color = color
color_changed.emit(color, is_primary)
func set_picked_primary(is_primary, toggle_picker_shown = true):
if toggle_picker_shown:
color_picker_container.visible = !color_picker_container.visible
color_picker_primary = is_primary
color_picker.color = color1 if is_primary else color2
if !color_picker_container.visible:
which_color_picked.text = "Color"
elif is_primary:
which_color_picked.text = "Primary"
else:
which_color_picked.text = "Secondary"
func set_color_screen_picking(picking, hold):
color_picker_picking_disable_with_click = !hold
color_picker_picking = picking
get_node(workspace).input_disabled = picking
if picking:
screen_image = get_viewport().get_texture().get_image()
func _input(event):
if picker_shortcut.matches_event(event) && !event.is_echo():
# Only pick primary color (for consistent feel if picker not open)
set_color_screen_picking(event.is_pressed(), true)
return
if event is InputEventMouseButton && color_picker_picking:
if !event.pressed: return
if color_picker_picking_disable_with_click:
set_color_screen_picking(false, false)
if color_picker_pick_from_image:
var img_view = get_node(workspace).image_view
var imagespace_event = img_view.event_vp_to_image(event)
var color = get_node(workspace).edited_image.get_pixelv(imagespace_event.position)
set_color(event.button_index == MOUSE_BUTTON_LEFT, color)
else:
set_color(event.button_index == MOUSE_BUTTON_LEFT, screen_image.get_pixelv(event.position))
func _yoink_color_picker_tool_button():
var child = color_picker.get_child(1, true);
if child == null:
return
for x in child.get_children(true):
if !x is Button:
continue
x.free()
break
func _on_color_picker_color_changed(color):
set_color(color_picker_primary, color)
func _on_color_1_pressed():
set_picked_primary(true, !color_picker_container.visible || color_picker_primary)
func _on_color_2_pressed():
set_picked_primary(false, !color_picker_container.visible || !color_picker_primary)
func _on_swap_pressed():
var swap = color1
set_color(true, color2)
set_color(false, swap)
set_picked_primary(color_picker_primary, false)
func _on_open_picker_toggled(button_pressed):
color_picker_container.visible = button_pressed
set_picked_primary(color_picker_primary)
func _on_palette_toggled(button_pressed):
palette.visible = button_pressed
func _on_visibility_changed():
set_process_input(is_visible_in_tree())
func _on_picker_header_gui_input(event):
if event is InputEventMouseMotion:
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
color_picker_container.position += event.relative
func _on_color_pick_pressed():
set_color_screen_picking(!color_picker_picking, false)
func _on_pick_from_image_toggled(button_pressed):
color_picker_pick_from_image = button_pressed

View File

@ -0,0 +1,530 @@
[gd_scene load_steps=19 format=3 uid="uid://be21fmb246ubn"]
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/color_picker.gd" id="1_ae0lg"]
[ext_resource type="Script" path="res://addons/sprite_painter/editor_icon_button.gd" id="1_srmpw"]
[sub_resource type="InputEventKey" id="InputEventKey_l6hbd"]
resource_name = "Alt"
keycode = 4194328
[sub_resource type="InputEventKey" id="InputEventKey_wy4cg"]
resource_name = "I"
[sub_resource type="Shortcut" id="Shortcut_76p2c"]
events = [SubResource("InputEventKey_l6hbd"), SubResource("InputEventKey_wy4cg")]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o2ioo"]
draw_center = false
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
shadow_color = Color(0.8, 0.8, 0.8, 1)
shadow_size = 4
anti_aliasing = false
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eeewi"]
bg_color = Color(1, 1, 1, 1)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_70jsv"]
bg_color = Color(1, 1, 1, 1)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(1.5, 1.5, 1.5, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gjl5v"]
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cicb3"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5mbmh"]
bg_color = Color(1, 1, 1, 1)
draw_center = false
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qpcds"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xa6r5"]
content_margin_left = 5.0
content_margin_top = 3.0
content_margin_right = 5.0
content_margin_bottom = 3.0
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fjbou"]
[sub_resource type="InputEventKey" id="InputEventKey_0j7g6"]
keycode = 88
[sub_resource type="Shortcut" id="Shortcut_8hnrs"]
resource_name = "X"
events = [SubResource("InputEventKey_0j7g6")]
[sub_resource type="Image" id="Image_670mm"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_qaodq"]
image = SubResource("Image_670mm")
[node name="ColorSettings" type="MarginContainer"]
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_top = -110.0
offset_right = 80.0
grow_vertical = 0
mouse_filter = 0
script = ExtResource("1_ae0lg")
picker_shortcut = SubResource("Shortcut_76p2c")
[node name="Panel2" type="Panel" parent="."]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
metadata/_edit_lock_ = true
[node name="Panel3" type="Panel" parent="."]
layout_mode = 2
size_flags_vertical = 3
metadata/_edit_lock_ = true
[node name="Container" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
metadata/_edit_lock_ = true
[node name="Box" type="HBoxContainer" parent="Container"]
layout_mode = 2
[node name="Box" type="VBoxContainer" parent="Container/Box"]
layout_mode = 2
alignment = 2
[node name="WhichColor" type="Label" parent="Container/Box/Box"]
layout_mode = 2
text = "Color"
[node name="Box" type="HBoxContainer" parent="Container/Box/Box"]
layout_mode = 2
[node name="Control" type="Control" parent="Container/Box/Box/Box"]
custom_minimum_size = Vector2(64, 64)
layout_mode = 2
[node name="Color2" type="Button" parent="Container/Box/Box/Box/Control"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -37.0
offset_top = -37.0
grow_horizontal = 0
grow_vertical = 0
tooltip_text = "(Right Click)
Secondary Color
Click to open the Color Picker."
theme_override_styles/normal = SubResource("StyleBoxFlat_eeewi")
theme_override_styles/hover = SubResource("StyleBoxFlat_70jsv")
theme_override_styles/pressed = SubResource("StyleBoxFlat_gjl5v")
theme_override_styles/focus = SubResource("StyleBoxEmpty_cicb3")
[node name="Frame" type="Button" parent="Container/Box/Box/Box/Control/Color2"]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/hover = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/pressed = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/focus = SubResource("StyleBoxEmpty_qpcds")
[node name="Color1" type="Button" parent="Container/Box/Box/Box/Control"]
self_modulate = Color(0.498039, 0.498039, 0.498039, 1)
layout_mode = 0
offset_right = 37.0
offset_bottom = 37.0
tooltip_text = "(Left Click)
Primary Color
Click to open the Color Picker."
theme_override_styles/normal = SubResource("StyleBoxFlat_eeewi")
theme_override_styles/hover = SubResource("StyleBoxFlat_70jsv")
theme_override_styles/pressed = SubResource("StyleBoxFlat_gjl5v")
theme_override_styles/focus = SubResource("StyleBoxEmpty_cicb3")
[node name="Frame2" type="Button" parent="Container/Box/Box/Box/Control/Color1"]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/hover = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/pressed = SubResource("StyleBoxFlat_5mbmh")
theme_override_styles/focus = SubResource("StyleBoxEmpty_qpcds")
[node name="Control2" type="CenterContainer" parent="Container/Box/Box/Box/Control"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 38.0
offset_bottom = -37.0
grow_horizontal = 2
grow_vertical = 2
[node name="Swap" type="Button" parent="Container/Box/Box/Box/Control/Control2"]
layout_mode = 2
tooltip_text = "(X)
Swap Primary/Secondary Colors
The Screen Color Picker (Alt) will always change the Primary (Left Mouse Button) color."
theme_override_styles/normal = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/hover = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/focus = SubResource("StyleBoxEmpty_fjbou")
shortcut = SubResource("Shortcut_8hnrs")
shortcut_in_tooltip = false
icon = SubResource("ImageTexture_qaodq")
flat = true
icon_alignment = 1
script = ExtResource("1_srmpw")
icon_name = "Loop"
[node name="Control" type="CenterContainer" parent="Container/Box/Box/Box/Control"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 38.0
offset_right = -38.0
grow_horizontal = 2
grow_vertical = 2
[node name="Pick" type="Button" parent="Container/Box/Box/Box/Control/Control"]
layout_mode = 2
tooltip_text = "(Alt)
Color Sampler
Can pick color from the editor screen, or the image. Change this in the Color Picker accessed by clicking a color here."
theme_override_styles/normal = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/hover = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/pressed = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/disabled = SubResource("StyleBoxEmpty_xa6r5")
theme_override_styles/focus = SubResource("StyleBoxEmpty_fjbou")
icon = SubResource("ImageTexture_qaodq")
flat = true
icon_alignment = 1
script = ExtResource("1_srmpw")
icon_name = "ColorPick"
[node name="Palette" type="VBoxContainer" parent="Container/Box/Box"]
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="Box" type="HBoxContainer" parent="Container/Box/Box/Palette"]
layout_mode = 2
[node name="Label" type="Label" parent="Container/Box/Box/Palette/Box"]
layout_mode = 2
size_flags_horizontal = 3
text = "Palette"
[node name="FromImage" type="Button" parent="Container/Box/Box/Palette/Box"]
layout_mode = 2
tooltip_text = "Get from image / selection"
icon = SubResource("ImageTexture_qaodq")
script = ExtResource("1_srmpw")
icon_name = "CanvasLayer"
[node name="Palette" type="VFlowContainer" parent="Container/Box/Box/Palette"]
custom_minimum_size = Vector2(0, 96)
layout_mode = 2
[node name="ColorRect" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect2" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect3" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect4" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect5" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect6" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect7" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect8" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect9" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect10" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect11" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect12" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect27" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect21" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect22" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect23" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect24" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect25" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect26" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect13" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect14" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect15" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect16" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect17" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect18" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect19" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="ColorRect20" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_horizontal = 3
[node name="Palette2" type="Button" parent="Container/Box/Box"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
toggle_mode = true
icon = SubResource("ImageTexture_qaodq")
icon_alignment = 1
script = ExtResource("1_srmpw")
icon_name = "Grid"
[node name="Control" type="Control" parent="."]
layout_mode = 2
mouse_filter = 2
metadata/_edit_lock_ = true
[node name="Picker" type="MarginContainer" parent="Control"]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 2.0
offset_top = -40.0
offset_right = 308.0
grow_horizontal = 0
grow_vertical = 0
metadata/_edit_group_ = true
[node name="Panel4" type="Panel" parent="Control/Picker"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
metadata/_edit_lock_ = true
[node name="Panel5" type="Panel" parent="Control/Picker"]
layout_mode = 2
size_flags_vertical = 3
metadata/_edit_lock_ = true
[node name="Margins" type="MarginContainer" parent="Control/Picker"]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="Box" type="VBoxContainer" parent="Control/Picker/Margins"]
layout_mode = 2
[node name="Header" type="HBoxContainer" parent="Control/Picker/Margins/Box"]
layout_mode = 2
[node name="Button" type="Button" parent="Control/Picker/Margins/Box/Header"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
icon = SubResource("ImageTexture_qaodq")
icon_alignment = 1
script = ExtResource("1_srmpw")
icon_name = "ToolMove"
[node name="Button2" type="Button" parent="Control/Picker/Margins/Box/Header"]
layout_mode = 2
tooltip_text = "Toggle color picking from image
If off, the Picker Tool (Alt) will pick the color from the editor's viewport."
toggle_mode = true
icon = SubResource("ImageTexture_qaodq")
script = ExtResource("1_srmpw")
icon_name = "SubViewport"
[node name="Button3" type="Button" parent="Control/Picker/Margins/Box/Header"]
layout_mode = 2
icon = SubResource("ImageTexture_qaodq")
script = ExtResource("1_srmpw")
icon_name = "Close"
[node name="ColorPicker" type="ColorPicker" parent="Control/Picker/Margins/Box"]
layout_mode = 2
color = Color(0.498039, 0.498039, 0.498039, 1)
color_mode = 1
sampler_visible = false
metadata/_edit_lock_ = true
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color2" to="." method="_on_color_2_pressed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color2/Frame" to="." method="_on_color_2_pressed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color1" to="." method="_on_color_1_pressed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color1/Frame2" to="." method="_on_color_2_pressed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Control2/Swap" to="." method="_on_swap_pressed"]
[connection signal="pressed" from="Container/Box/Box/Box/Control/Control/Pick" to="." method="_on_color_pick_pressed"]
[connection signal="toggled" from="Container/Box/Box/Palette2" to="." method="_on_palette_toggled"]
[connection signal="gui_input" from="Control/Picker/Margins/Box/Header/Button" to="." method="_on_picker_header_gui_input"]
[connection signal="toggled" from="Control/Picker/Margins/Box/Header/Button2" to="." method="_on_pick_from_image_toggled"]
[connection signal="pressed" from="Control/Picker/Margins/Box/Header/Button3" to="Control/Picker" method="hide"]
[connection signal="color_changed" from="Control/Picker/Margins/Box/ColorPicker" to="." method="_on_color_picker_color_changed"]

View File

@ -0,0 +1,145 @@
@tool
extends Control
signal tool_changed(tool_node)
@export var toolbar : NodePath
@export var toolbar_end : NodePath
@export var image_view : NodePath
var by_button := {}
var by_shortcut := {}
var current_tool : Control
var current_tool_shortcut_list := []
var current_color1 := Color.WHITE
var current_color2 := Color.WHITE
func _ready():
if get_viewport() is SubViewport: return
var end_at = get_node(toolbar_end)
for x in get_node(toolbar).get_children():
if x == end_at: break
if !x is BaseButton: continue
_setup_tool_button(x)
func _setup_tool_button(button : BaseButton):
var tool_node = get_node_or_null(NodePath(button.name))
by_button[button] = tool_node
button.disabled = false
if tool_node == null:
button.disabled = true
return
if button.shortcut != null:
var buttons_with_sc = by_shortcut.get(button.shortcut, [])
buttons_with_sc.append(button)
button.pressed.connect(_on_button_shortcut_pressed.bind(
button,
buttons_with_sc
))
if buttons_with_sc.size() >= 2:
button.set_deferred("shortcut", null)
by_shortcut[button.shortcut] = buttons_with_sc
if current_tool == null:
_on_button_shortcut_pressed(button, buttons_with_sc)
button.tooltip_text = "%s (%s)" % [
tool_node.tool_name,
(button.shortcut.get_as_text() + " + ")\
.repeat(by_shortcut[button.shortcut].size())\
.trim_suffix(" + "),
]
tool_node.hide()
button.toggled.connect(_on_tool_button_toggled.bind(tool_node))
if current_tool == null:
_on_tool_button_toggled(true, tool_node)
func _on_button_shortcut_pressed(button : BaseButton, list : Array):
if button.shortcut == null: return
var pressed_in_list = list.find(button)
if current_tool_shortcut_list != list:
# Next time the shortcut is pressed, it will select the first from the list
if current_tool_shortcut_list.size() > 1:
var sc
for x in current_tool_shortcut_list:
if x.shortcut != null:
sc = x.shortcut
x.shortcut = null
current_tool_shortcut_list[0].shortcut = sc
list[(pressed_in_list + 1) % list.size()].shortcut = button.shortcut
if list.size() > 1:
button.shortcut = null
current_tool_shortcut_list = list
func _on_tool_button_toggled(toggled : bool, tool_node : EditingTool):
if !toggled: return
if current_tool != null:
current_tool.hide()
tool_node.show()
current_tool = tool_node
get_node(image_view).self_modulate.a = 1.0 if tool_node.image_hide_mode != 2 else 0.0
tool_changed.emit(tool_node)
func handle_image_input(event, image, selection) -> bool:
if current_tool == null: return false
if event is InputEventMouseMotion:
current_tool.selection = selection # Needed because Image Script tool does not normally use clicks
# I better just fetch it from the Workspace but also ehhhh incapsulation
current_tool.mouse_moved(event)
return true
elif event is InputEventMouseButton:
current_tool.selection = selection
current_tool.mouse_pressed(
event,
image,
current_color1 if event.button_index == MOUSE_BUTTON_LEFT else current_color2,
current_color1 if event.button_index != MOUSE_BUTTON_LEFT else current_color2
)
if current_tool.image_hide_mode != 2:
get_node(image_view).self_modulate.a = 1.0
if event.pressed && current_tool.image_hide_mode == 1:
get_node(image_view).self_modulate.a = 0.0
return true
return false
func get_affected_rect() -> Rect2i:
return current_tool.get_affected_rect()
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
if current_tool == null: return
current_tool.draw_preview(image_view, mouse_position)
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
if current_tool == null: return
image_view.material = current_tool.preview_shader
current_tool.draw_shader_preview(image_view, mouse_position)
func _on_color_settings_color_changed(new_color, is_primary):
if is_primary:
current_color1 = new_color
else:
current_color2 = new_color

View File

@ -0,0 +1,69 @@
@tool
extends Node
@export var label : NodePath
@export var frame_data_textbox : NodePath
var plugin_root
var editor_dock
var anim_list
var frame_list
var edited_object
var current_anim
var current_frame
func connect_plugin(plugin_root_node):
plugin_root = plugin_root_node
func try_edit(object):
get_node(label).hide()
get_node(frame_data_textbox).hide()
if object is AnimatedSprite2D || object is AnimatedSprite3D:
try_edit(object.frames)
return
if object is SpriteFrames:
edited_object = object
call_deferred("update_spriteframes")
func try_connect_bottom_dock(dock : Control):
if dock.get_class() == "SpriteFramesEditor":
editor_dock = dock
anim_list = dock.get_child(0).get_child(1).get_child(0).get_child(1)
anim_list\
.item_selected.connect(_on_anim_selected)
frame_list = dock.get_child(1).get_child(1).get_child(0).get_child(1)
frame_list\
.item_clicked.connect(_on_frame_selected)
func update_spriteframes():
current_anim = anim_list.get_selected().get_text(0)
current_frame = frame_list.get_selected_items()[0]
get_node(label).show()
get_node(frame_data_textbox).show()
get_node(frame_data_textbox).text = "%s : frame %s" % [current_anim, current_frame]
var frame_tex = edited_object.get_frame(current_anim, current_frame)
if frame_tex is AtlasTexture:
plugin_root.edit_subresource(
frame_tex.atlas.resource_path,
frame_tex.region.size,
frame_tex.region.position,
true
)
else:
plugin_root.edit_file(frame_tex.resource_path)
func _on_frame_selected(index, at_pos, mouse_button_index):
update_spriteframes()
func _on_anim_selected():
update_spriteframes()

View File

@ -0,0 +1,48 @@
@tool
extends Node
var plugin_root
var editor_dock
var source_list
var edited_object
func connect_plugin(plugin_root_node):
plugin_root = plugin_root_node
func try_edit(object):
if object is TileMap:
try_edit(object)
return
if object is TileSet:
edited_object = object
call_deferred("update_tileset")
func try_connect_bottom_dock(dock : Control):
if dock.get_class() == "TileSetEditor":
editor_dock = dock
source_list = dock.get_child(1).get_child(0).get_child(0)
source_list\
.item_clicked.connect(_on_source_selected)
func update_tileset():
var selected_id = source_list.get_selected_items()[0]
var sauce = edited_object.get_source(selected_id)
if !(sauce is TileSetAtlasSource):
return
plugin_root.edit_subresource(
sauce.get_texture().resource_path,
sauce.texture_region_size + sauce.separation,
sauce.margins,
false
)
func _on_source_selected(index, at_pos, mouse_button_index):
update_tileset()

View File

@ -0,0 +1,85 @@
@tool
extends MarginContainer
@onready var type_handlers = $"TypeHandlers".get_children()
@onready var cursor_pos_info = $"Container/Box/CursorPos"
var editor_plugin : EditorPlugin
var plugin_root : Node
var input_start := Vector2()
var input_end := Vector2()
var input_pressed := false
var edited_object : Object
func _ready():
hide()
readjust_size()
show()
plugin_root = self
while !plugin_root is Window:
plugin_root = plugin_root.get_parent()
if !plugin_root is SpritePainterRoot:
continue
editor_plugin = plugin_root.editor_plugin
plugin_root.object_selected.connect(_on_plugin_object_selected)
for x in type_handlers:
x.connect_plugin(plugin_root)
break
var c = Control.new()
editor_plugin.add_control_to_bottom_panel(c, "AAA")
for x in c.get_parent().get_children():
for y in type_handlers:
y.try_connect_bottom_dock(x)
editor_plugin.remove_control_from_bottom_panel(c)
_on_plugin_object_selected(plugin_root.edited_object)
func handle_image_input(event):
if event is InputEventMouseMotion:
if input_pressed:
input_end = event.position.floor()
else:
input_start = event.position.floor()
elif event is InputEventMouseButton:
input_pressed = event.pressed
input_end = event.position.floor()
if input_pressed:
cursor_pos_info.text = "Cursor: %s -> %s (size %s)" % [
input_start,
input_end,
(input_end - input_start).abs() + Vector2.ONE
]
else:
cursor_pos_info.text = "Cursor: %s" % input_start
return false
func _on_plugin_object_selected(obj):
if obj == null: return
for x in type_handlers:
x.try_edit(obj)
edited_object = obj
readjust_size()
func readjust_size():
size = Vector2.ZERO
position = Vector2(get_parent().size) - get_minimum_size() #get_parent().size can be a Vector2i
func _on_visibility_changed():
pass

View File

@ -0,0 +1,172 @@
[gd_scene load_steps=6 format=3 uid="uid://dcc6kewvuomhc"]
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context_settings.gd" id="1_7q6dr"]
[ext_resource type="Script" path="res://addons/sprite_painter/editor_icon_button.gd" id="2_l84pr"]
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context/context_spriteframes.gd" id="3_jtsht"]
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context/context_tileset.gd" id="4_mhrcf"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o2ioo"]
draw_center = false
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
shadow_color = Color(0.8, 0.8, 0.8, 1)
shadow_size = 4
anti_aliasing = false
[node name="ContextSettings" type="MarginContainer"]
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -647.0
offset_top = -363.0
offset_right = -331.0
offset_bottom = -312.0
grow_horizontal = 0
grow_vertical = 0
mouse_filter = 0
script = ExtResource("1_7q6dr")
[node name="Panel2" type="Panel" parent="."]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
metadata/_edit_lock_ = true
[node name="Panel3" type="Panel" parent="."]
layout_mode = 2
size_flags_vertical = 3
metadata/_edit_lock_ = true
[node name="Container" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
metadata/_edit_lock_ = true
[node name="Box" type="VBoxContainer" parent="Container"]
layout_mode = 2
[node name="Grid" type="GridContainer" parent="Container/Box"]
layout_mode = 2
columns = 2
[node name="Anim" type="Label" parent="Container/Box/Grid"]
visible = false
layout_mode = 2
text = "Animation"
[node name="FrameData" type="LineEdit" parent="Container/Box/Grid"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
text = "Idle, Frame 8"
editable = false
expand_to_text_length = true
[node name="Label2" type="Label" parent="Container/Box/Grid"]
visible = false
layout_mode = 2
text = "Helpers"
[node name="Box" type="HBoxContainer" parent="Container/Box/Grid"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="ThemeIconButton" type="Button" parent="Container/Box/Grid/Box"]
layout_mode = 2
script = ExtResource("2_l84pr")
icon_name = "GuiVisibilityVisible"
[node name="Button" type="Button" parent="Container/Box/Grid/Box"]
layout_mode = 2
size_flags_horizontal = 3
text = "Edit"
[node name="CursorPos" type="LineEdit" parent="Container/Box"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
text = "Cursor: (0, 0) -> (0, 0) (size (0, 0))"
editable = false
expand_to_text_length = true
[node name="Control" type="Control" parent="."]
layout_mode = 2
mouse_filter = 2
metadata/_edit_lock_ = true
[node name="Extra" type="MarginContainer" parent="Control"]
visible = false
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -704.0
offset_top = -143.0
offset_right = -398.0
offset_bottom = -103.0
grow_horizontal = 0
grow_vertical = 0
metadata/_edit_group_ = true
[node name="Panel4" type="Panel" parent="Control/Extra"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
metadata/_edit_lock_ = true
[node name="Panel5" type="Panel" parent="Control/Extra"]
layout_mode = 2
size_flags_vertical = 3
metadata/_edit_lock_ = true
[node name="Margins" type="MarginContainer" parent="Control/Extra"]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="Box" type="VBoxContainer" parent="Control/Extra/Margins"]
layout_mode = 2
[node name="Header" type="HBoxContainer" parent="Control/Extra/Margins/Box"]
layout_mode = 2
[node name="Button" type="Button" parent="Control/Extra/Margins/Box/Header"]
layout_mode = 2
size_flags_horizontal = 3
disabled = true
icon_alignment = 1
script = ExtResource("2_l84pr")
icon_name = "ToolMove"
[node name="Button2" type="Button" parent="Control/Extra/Margins/Box/Header"]
layout_mode = 2
script = ExtResource("2_l84pr")
icon_name = "Close"
[node name="TypeHandlers" type="Node" parent="."]
[node name="Spriteframes" type="Node" parent="TypeHandlers"]
script = ExtResource("3_jtsht")
label = NodePath("../../Container/Box/Grid/Anim")
frame_data_textbox = NodePath("../../Container/Box/Grid/FrameData")
[node name="Tileset" type="Node" parent="TypeHandlers"]
script = ExtResource("4_mhrcf")
[connection signal="gui_input" from="Control/Extra/Margins/Box/Header/Button" to="." method="_on_picker_header_gui_input"]
[connection signal="pressed" from="Control/Extra/Margins/Box/Header/Button2" to="Control/Extra" method="hide"]

View File

@ -0,0 +1,54 @@
class_name ImageScript
extends RefCounted
## Base class for scripts that process images through Sprite painter.
##
## Extend this script and add the new file to `res://addons/sprite_painter/image_scripts/`
## to make it usable in Sprite Painter.
enum {
SCRIPT_PARAM_BOOL, ## Checkbox property. No hints.
SCRIPT_PARAM_INT, ## Integer number property. Hints: [MINVALUE, MAXVALUE]
SCRIPT_PARAM_FLOAT, ## Floating-point number property. Hints: [MINVALUE, MAXVALUE]
SCRIPT_PARAM_ENUM, ## Enumeration property. Hint is an array of names for the created OptionButton.
SCRIPT_PARAM_ICON_ENUM, ## Enumeration property. Hint is an dictionary of {ICON : TOOLTIP} pairs. ICON can be a theme icon name or a loaded Texture.
SCRIPT_PARAM_ICON_FLAGS, ## Array of Bools property. Hint is an dictionary of {ICON : TOOLTIP} pairs. ICON can be a theme icon or a loaded Texture.
SCRIPT_PARAM_RESOURCE, ## Resource property. Hint is the base type of accepted Resources.
SCRIPT_PARAM_FILE, ## Resource property. Allows choosing a file from a folder set in a hint.
SCRIPT_PARAM_COLOR, ## Color property. No hints.
}
var _params = {}
## Returns a parameter set through the GUI.
##
## Key must be same as passed in `_get_param_list()`.
func get_param(key : String) -> Variant:
return _params[key]
## Called when the script is loaded: when switching scripts, opening an image, or resetting parameters.
func _ready(image : Image):
pass
## Called to preview or apply the script. Must return the result, which can be the same image object.
func _get_image(new_image : Image, selection : BitMap) -> Image:
return new_image
## Must return a list of parameters. Each parameter contains:
##
## - Name
##
## - Type
##
## - Default
##
## - Type Hint, for which refer to ImageScript's class.
func _get_param_list():
return [
[
"_get_param_list() Not overriden!",
SCRIPT_PARAM_ENUM,
0,
["Refer to the ImageScript class for more info."]
],
]

View File

@ -0,0 +1,202 @@
@tool
extends MenuButton
enum {
OPTION_ROTATE_CW,
OPTION_ROTATE_CCW,
OPTION_FLIP_H,
OPTION_FLIP_V,
OPTION_CROP,
OPTION_BORDER,
OPTION_RESIZE,
}
@export var workspace : NodePath
@onready var width_edit = $"Dialog/Grid/Width"
@onready var height_edit = $"Dialog/Grid/Height"
var original_size := Vector2i()
var expand_dir := Vector2i()
var percent_view := false
var stretch := false
var interp := 0
func _ready():
var dir_buttons = $"Dialog/Grid/Box/Grid".get_children()
$"Dialog/Grid/Box/Stretch".pressed.connect(_on_stretch_pressed)
for i in 9:
dir_buttons[i].pressed.connect(func():
_on_anchor_dir_pressed(Vector2i(1 - i % 3, 1 - i / 3))
)
var p = get_popup()
p.id_pressed.connect(_on_id_pressed)
p.hide_on_item_selection = false
if !get_viewport() is Window: return
p.set_item_icon(OPTION_ROTATE_CW, get_theme_icon("RotateRight", "EditorIcons"))
p.set_item_icon(OPTION_ROTATE_CCW, get_theme_icon("RotateLeft", "EditorIcons"))
p.set_item_icon(OPTION_FLIP_H, get_theme_icon("MoveRight", "EditorIcons"))
p.set_item_icon(OPTION_FLIP_V, get_theme_icon("MoveDown", "EditorIcons"))
# One of them is not real.
# p.set_item_icon(OPTION_FLIP_H, get_theme_icon("Hsize", "EditorIcons"))
# p.set_item_icon(OPTION_FLIP_V, get_theme_icon("VSize", "EditorIcons"))
p.set_item_icon(OPTION_CROP + 1, get_theme_icon("MeshTexture", "EditorIcons"))
p.set_item_icon(OPTION_BORDER + 1, get_theme_icon("ToolMove", "EditorIcons"))
p.set_item_icon(OPTION_RESIZE + 1, get_theme_icon("DistractionFree", "EditorIcons"))
func _on_id_pressed(id):
var old_image = get_node(workspace).edited_image
original_size = old_image.get_size()
if id == OPTION_CROP:
crop(old_image, get_node(workspace).edited_image_selection)
return
elif id == OPTION_RESIZE:
$"Dialog".popup_centered()
update_entered_size(original_size)
return
elif id == OPTION_BORDER:
var new_image = get_node(workspace).get_resized(
old_image,
original_size + Vector2i(2, 2),
Vector2i(0, 0),
-1
)
submit_changed_image(new_image)
return
var new_image = Image.create(original_size.x, original_size.y, false, old_image.get_format())
new_image.blit_rect(old_image, Rect2(Vector2i.ZERO, original_size), Vector2i.ZERO)
match id:
OPTION_ROTATE_CW:
new_image.rotate_90(CLOCKWISE)
OPTION_ROTATE_CCW:
new_image.rotate_90(COUNTERCLOCKWISE)
OPTION_FLIP_H:
new_image.flip_x()
OPTION_FLIP_V:
new_image.flip_y()
submit_changed_image(new_image)
func crop(old_image, selection):
var result_rect = Rect2i(0, 0, 0, 0)
var found_start = false
original_size = old_image.get_size()
var sel_bits = selection.get_true_bit_count()
var crop_selection = sel_bits > 2 && sel_bits < original_size.x * original_size.y
for i in original_size.x:
for j in original_size.y:
if crop_selection:
if !selection.get_bit(i, j):
continue
elif old_image.get_pixel(i, j).a < 0.02:
continue
if !found_start:
found_start = true
result_rect.position = Vector2i(i, j)
else:
result_rect = result_rect.expand(Vector2i(i, j))
result_rect.size += Vector2i.ONE
if result_rect.size == Vector2i.ONE:
return
var new_image = Image.create(
result_rect.size.x,
result_rect.size.y,
false,
old_image.get_format()
)
new_image.blit_rect(old_image, result_rect, Vector2i.ZERO)
submit_changed_image(new_image)
func get_entered_size():
if percent_view:
return Vector2i(
original_size.x * width_edit.value * 0.01,
original_size.y * height_edit.value * 0.01
)
else:
return Vector2i(
width_edit.value,
height_edit.value
)
func update_entered_size(new_size):
if percent_view:
width_edit.prefix = ""
width_edit.value = new_size.x * 100.0 / original_size.x
width_edit.suffix = "%"
width_edit.step = 0.01
height_edit.prefix = ""
height_edit.value = new_size.y * 100.0 / original_size.y
height_edit.suffix = "%"
height_edit.step = 0.01
else:
width_edit.prefix = str(original_size.x) + " -> "
width_edit.value = new_size.x
width_edit.suffix = "px"
width_edit.step = 1
height_edit.prefix = str(original_size.y) + " -> "
height_edit.value = new_size.y
height_edit.suffix = "px"
height_edit.step = 1
func submit_changed_image(new_image):
var ws = get_node(workspace)
ws.image_replaced.emit(ws.edited_image, new_image)
ws.update_texture(new_image)
func _on_dialog_confirmed():
var ws = get_node(workspace)
var new_image = ws.get_resized(
ws.edited_image,
get_entered_size(),
expand_dir,
interp if stretch else -1
)
submit_changed_image(new_image)
func _on_anchor_dir_pressed(direction):
$"Dialog/Grid/Interpolation".disabled = true
stretch = false
expand_dir = direction
func _on_stretch_pressed():
$"Dialog/Grid/Interpolation".disabled = false
stretch = true
func _on_interpolation_item_selected(index):
interp = index
func _on_percent_toggled(v):
var old_entered_size = get_entered_size()
percent_view = v
update_entered_size(old_entered_size)

View File

@ -0,0 +1,72 @@
@tool
extends Node2D
@export var grid_color := Color.WHITE
@export var grid_line_width := 1.0
@export var region_color := Color.WHITE
@export var region_line_width := 1.0
@export var region_outer_color := Color.BLACK
var image_size := Vector2i()
var grid_size := Vector2i()
var grid_offset := Vector2i()
var is_region := false
func _draw():
if grid_size == Vector2i.ZERO: return
if is_region:
draw_rect(Rect2(
Vector2.ZERO,
Vector2(image_size.x, grid_offset.y)
), region_outer_color)
draw_rect(Rect2(
Vector2(0, grid_offset.y),
Vector2(grid_offset.x, grid_size.y)
), region_outer_color)
draw_rect(Rect2(
Vector2(grid_offset.x + grid_size.x, grid_offset.y),
Vector2(image_size.x - grid_offset.x - grid_size.x, grid_size.y)
), region_outer_color)
draw_rect(Rect2(
Vector2(0, grid_offset.y + grid_size.y),
Vector2(image_size.x, image_size.y - grid_offset.y - grid_size.y)
), region_outer_color)
draw_rect(Rect2(
grid_offset + Vector2i(-region_line_width, -region_line_width),
Vector2(grid_size.x + region_line_width * 2, region_line_width)
), region_color)
draw_rect(Rect2(
grid_offset + Vector2i(-region_line_width, 0),
Vector2(region_line_width, grid_size.y)
), region_color)
draw_rect(Rect2(
grid_offset + Vector2i(grid_size.x, 0),
Vector2(region_line_width, grid_size.y)
), region_color)
draw_rect(Rect2(
grid_offset + Vector2i(-region_line_width, grid_size.y),
Vector2(grid_size.x + region_line_width * 2, region_line_width)
), region_color)
return
var lines = []
var line_count = Vector2i(
float(image_size.x - grid_offset.x % grid_size.x) / grid_size.x,
float(image_size.y - grid_offset.y % grid_size.y) / grid_size.y
)
lines.resize(line_count.x * 2)
for i in line_count.x:
lines[i * 2] = Vector2((i + 1) * grid_size.x + grid_offset.x, 0)
lines[i * 2 + 1] = lines[i * 2] + Vector2(0, image_size.y)
draw_multiline(lines, grid_color, grid_line_width)
lines.resize(line_count.y * 2)
for i in line_count.y:
lines[i * 2] = Vector2(0, (i + 1) * grid_size.y + grid_offset.y)
lines[i * 2 + 1] = lines[i * 2] + Vector2(image_size.x, 0)
draw_multiline(lines, grid_color, grid_line_width)

View File

@ -0,0 +1,39 @@
@tool
extends Node2D
@export var selection_color := Color(0.5, 0.5, 0.5, 0.5)
var selection : BitMap
func _ready():
get_node("../../..").image_changed.connect(_on_image_changed)
_on_visibility_changed()
func _process(delta):
if Engine.get_process_frames() % 10 != 0: return
self_modulate.a = abs(1.0 - fmod(Time.get_ticks_msec() * 0.0005, 2.0))
func _draw():
if selection == null: return
var sel_size = selection.get_size()
if selection.get_true_bit_count() == sel_size.x * sel_size.y:
return
ImageFillTools.draw_bitmap(self, selection, selection_color)
func _on_visibility_changed():
if !get_viewport() is Window:
set_process(false)
self_modulate.a = 0.75
return
set_process(is_visible_in_tree())
func _on_image_changed(image, rect_changed):
if selection != null:
queue_redraw()

View File

@ -0,0 +1,15 @@
@tool
extends Sprite2D
@export var draw_handler : NodePath
@export var draw_method := "draw_preview"
var mouse_pos : Vector2
func _draw():
get_node(draw_handler).call(draw_method, self, mouse_pos)
func _on_tool_switcher_tool_changed(tool_node):
texture = null

View File

@ -0,0 +1,92 @@
@tool
extends Sprite2D
@export var draw_handler : NodePath
@onready var border_rect = $"../ImageBorder"
@onready var resize_result = $"../ResizeResult"
@onready var preview = $"ToolView"
@onready var preview_shader = $"SubViewport/ShaderView"
@onready var resize_rect = border_rect.get_node("Resize")
var camera_pos := Vector2.ZERO
var mouse_pos := Vector2.ZERO
func zoom(by : Vector2):
scale *= by
camera_pos *= by
update_position()
func translate(by : Vector2):
camera_pos += by
update_position()
func update_position():
update_position_local()
resize_rect.rect_scale = global_scale
$"SubViewport".size = texture.get_size()
func reset_position():
camera_pos = Vector2.ZERO
scale = Vector2.ONE
update_position()
func update_position_local():
position = get_parent().size * 0.5 + camera_pos - texture.get_size() * 0.5
centered = false
update_texture_view_rect()
func update_position_overlay(edited_node):
position = edited_node.global_position - edited_node.get_viewport().get_visible_rect().position
centered = false
scale = edited_node.global_scale if edited_node is Node2D else edited_node.scale
# TODO: fetch source's actual on-screen position.
update_texture_view_rect()
func update_texture_view_rect():
border_rect.size = scale * (texture.get_size() if texture != null else Vector2.ZERO)
border_rect.position = position
if centered:
border_rect.position -= border_rect.size * 0.5
func _on_resize_preview_changed(current_delta, expand_direction):
resize_result.hide()
var old_size = texture.get_size()
var delta_one_axis = round(current_delta.x if expand_direction.x != 0 else current_delta.y)
var old_size_one_axis = old_size.x if expand_direction.x != 0 else old_size.y
resize_result.text = "%s%spx (%.1f%s)\n-> %s\n" % [
"+" if delta_one_axis >= 0.5 else "",
delta_one_axis,
(1.0 + delta_one_axis / float(old_size_one_axis)) * 100,
"%",
(old_size + current_delta).round(),
]
resize_result.visible = expand_direction != Vector2i(0, 0)
resize_result.global_position = get_global_mouse_position() - resize_result.size * 0.5
func event_vp_to_image(from_event : InputEventMouse, unsafe : bool = false) -> InputEventMouse:
if !unsafe:
from_event = from_event.duplicate()
from_event.position = (from_event.position - global_position) / scale
if from_event is InputEventMouseMotion:
from_event.relative /= scale
preview.mouse_pos = from_event.position
preview.show()
preview.queue_redraw()
preview_shader.mouse_pos = from_event.position
preview_shader.show()
preview_shader.queue_redraw()
return from_event

View File

@ -0,0 +1,51 @@
@tool
extends Control
signal preview_changed(current_delta, expand_direction)
signal value_changed(delta, expand_direction)
@export var delta_color := Color(1.0, 0.25, 0.0, 0.75)
@export var sensitivity := 1.0
var dragging := false
var size_delta := Vector2()
var rect_scale := Vector2.ONE
var resize_direction := Vector2()
func _ready():
$"X-".gui_input.connect(_on_child_gui_input.bind(Vector2.LEFT))
$"X+".gui_input.connect(_on_child_gui_input.bind(Vector2.RIGHT))
$"Y-".gui_input.connect(_on_child_gui_input.bind(Vector2.UP))
$"Y+".gui_input.connect(_on_child_gui_input.bind(Vector2.DOWN))
func _draw():
var anchor = (resize_direction + Vector2.ONE) * 0.5
draw_rect(Rect2(size * anchor.floor(), (
Vector2(resize_direction.x * round(size_delta.x) * rect_scale.x, size.y)
if size_delta.x != 0.0 else
Vector2(size.x, resize_direction.y * round(size_delta.y) * rect_scale.y)
)), delta_color)
func _on_child_gui_input(event, direction):
if event is InputEventMouseMotion:
if !dragging: return
size_delta += event.relative * direction * sensitivity / rect_scale
preview_changed.emit(size_delta, Vector2i(direction))
if event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_LEFT:
dragging = event.pressed
resize_direction = direction
if !event.pressed:
value_changed.emit(size_delta, Vector2i(direction))
preview_changed.emit(Vector2.ZERO, Vector2i.ZERO)
size_delta = Vector2.ZERO
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
else:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
queue_redraw()

View File

@ -0,0 +1,89 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://addons/sprite_painter/src/image_view/resize_rect.gd" id="1_eudu3"]
[ext_resource type="Texture2D" uid="uid://c7nymhfcdr3hl" path="res://addons/sprite_painter/graphics/diags.png" id="1_v44m5"]
[node name="Resize" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_eudu3")
metadata/_edit_lock_ = true
[node name="X-" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -48.0
offset_top = -16.0
offset_right = -16.0
offset_bottom = 16.0
grow_vertical = 2
mouse_filter = 0
mouse_default_cursor_shape = 10
texture = ExtResource("1_v44m5")
ignore_texture_size = true
stretch_mode = 1
metadata/_edit_lock_ = true
[node name="X+" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = 16.0
offset_top = -16.0
offset_right = 48.0
offset_bottom = 16.0
grow_horizontal = 0
grow_vertical = 2
mouse_filter = 0
mouse_default_cursor_shape = 10
texture = ExtResource("1_v44m5")
ignore_texture_size = true
stretch_mode = 1
metadata/_edit_lock_ = true
[node name="Y-" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -16.0
offset_top = -48.0
offset_right = 16.0
offset_bottom = -16.0
grow_horizontal = 2
mouse_filter = 0
mouse_default_cursor_shape = 9
texture = ExtResource("1_v44m5")
ignore_texture_size = true
stretch_mode = 1
metadata/_edit_lock_ = true
[node name="Y+" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -16.0
offset_top = 16.0
offset_right = 16.0
offset_bottom = 48.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 0
mouse_default_cursor_shape = 9
texture = ExtResource("1_v44m5")
ignore_texture_size = true
stretch_mode = 1
metadata/_edit_lock_ = true

View File

@ -0,0 +1,204 @@
@tool
class_name SpritePainterRoot
extends Control
signal object_selected(object)
@onready var workspace = $"%Workspace"
var editor_interface : EditorInterface
var editor_plugin : EditorPlugin
var editor_2d_vp : Control
var editor_3d_vp : Control
var viewport_ignored := true
var edited_object : Object
var unsaved_image_paths : Array[String] = []
var undo_redo : EditorUndoRedoManager
func _ready():
workspace.pre_image_changed.connect(_on_workspace_pre_image_changed)
workspace.image_changed.connect(_on_workspace_image_changed)
workspace.image_replaced.connect(_on_workspace_image_replaced)
# var plugin_undoredo = editor_plugin.get_undo_redo()
# var own_history_id = plugin_undoredo.get_object_history_id(self)
# undo_redo = plugin_undoredo.get_history_undo_redo(own_history_id)
# undo_redo = editor_plugin.get_undo_redo()
func edit_object(obj : Object):
if obj is Node:
edit_node(obj)
edited_object = obj
object_selected.emit(obj)
elif obj is CompressedTexture2D:
edit_file(obj.resource_path)
edited_object = obj
object_selected.emit(obj)
elif obj is AtlasTexture:
var region = obj.region
edit_subresource(obj.atlas.resource_path, region.size, region.position, true)
edited_object = obj
object_selected.emit(obj)
else:
object_selected.emit(obj)
func edit_node(node : Node):
if node == null:
return
mouse_filter = MOUSE_FILTER_STOP if viewport_ignored else MOUSE_FILTER_IGNORE
if "texture" in node:
call_deferred("edit_subresource", node.texture.resource_path)
func edit_subresource(
filepath : String,
grid_size : Vector2i = Vector2i.ZERO,
grid_offset : Vector2i = Vector2i.ZERO,
is_region : bool = false
):
if unsaved_image_paths.find(StringName(filepath)) == -1:
unsaved_image_paths.append(StringName(filepath))
if !is_visible_in_tree(): return
workspace.edit_texture(filepath)
if edited_object is Sprite2D || edited_object is Sprite3D:
update_grid_from_sprite(edited_object)
else:
workspace.set_view_grid(grid_size, grid_offset, is_region)
func update_grid_from_sprite(node : CanvasItem):
var tex_size = Vector2(node.texture.get_size())
var frame_size = tex_size / Vector2(node.hframes, node.vframes)
var region_offset = Vector2i.ZERO
if node.region_enabled:
frame_size = node.region_rect.size
region_offset = node.region_rect.position
workspace.set_view_grid(frame_size, region_offset, node.region_enabled)
func edit_file(filepath : String):
unsaved_image_paths.append(filepath)
workspace.edit_texture(filepath)
workspace.set_view_grid(Vector2i.ZERO, Vector2i.ZERO, false)
func _gui_input(event):
handle_input(event)
func handle_input(event):
return workspace.handle_input(event)
static func print_hierarchy(root : Node, indent : String = ""):
for x in root.get_children(true):
print(x.name.indent(indent) + " : " + x.get_class())
print_hierarchy(x, indent + "- ")
static func save_scene(root : Node, filename = "saved_scn.tscn"):
for x in root.get_children(true):
pack_to_owner(root)
var packed = PackedScene.new()
packed.pack(root)
packed.resource_path = "res://" + filename
ResourceSaver.save(packed)
static func pack_to_owner(root : Node, keep_scene_nodes : bool = true, new_owner : Node = root):
for x in root.get_children(true):
# if keep_scene_nodes && root.file_path != "":
# new_owner = root
x.owner = new_owner
pack_to_owner(x, keep_scene_nodes, new_owner)
func _on_close_pressed():
workspace.rollback_changes()
editor_plugin._on_enable_pressed()
func _on_save_pressed():
editor_plugin._on_enable_pressed()
func _on_workspace_pre_image_changed(image : Image, rect):
if rect.size.x == 0 || rect.size.y == 0:
return
undo_redo.create_action("Edit image (start: %s, end: %s)" % [rect.position, rect.end])
var saved_image = copy_image_rect(image, rect)
undo_redo.add_undo_method(self, "paste_image_rect", saved_image, image, rect.position)
# undo_redo.add_undo_method(func edit_undo():
# paste_image_rect(saved_image, image, rect.position)
# )
func _on_workspace_image_changed(image : Image, rect):
if rect.size.x == 0 || rect.size.y == 0:
return
var saved_image = copy_image_rect(image, rect)
undo_redo.add_do_method(self, "paste_image_rect", saved_image, image, rect.position)
# undo_redo.add_do_method(func edit_do():
# paste_image_rect(saved_image, image, rect.position)
# )
undo_redo.commit_action()
func copy_image_rect(from, rect) -> Image:
var new_image = Image.create(rect.size.x, rect.size.y, false, from.get_format())
new_image.blit_rect(from, rect, Vector2.ZERO)
return new_image
func paste_image_rect(from, to, destination = Vector2.ZERO):
to.blit_rect(from, Rect2(Vector2.ZERO, from.get_size()), destination)
save_changes(to)
func save_changes(image = null):
if image == null:
image = workspace.edited_image
if image == null: return
var err = image.save_png(workspace.edited_image_path.get_basename() + ".png")
if err != OK: printerr(err)
workspace.update_texture(image)
func _on_workspace_image_replaced(old_image, new_image):
undo_redo.create_action("Resize image (%s -> %s)" % [old_image.get_size(), new_image.get_size()])
undo_redo.add_undo_method(self, "save_changes",
copy_image_rect(old_image, Rect2i(Vector2i.ZERO, old_image.get_size()))
)
undo_redo.add_do_method(self, "save_changes",
copy_image_rect(new_image, Rect2i(Vector2i.ZERO, new_image.get_size()))
)
# undo_redo.add_undo_method(func edit_undo():
# workspace.edited_image = copy_image_rect(
# old_image,
# Rect2i(Vector2i.ZERO, old_image.get_size())
# )
# )
# undo_redo.add_do_method(func edit_do():
# workspace.edited_image = copy_image_rect(
# new_image,
# Rect2i(Vector2i.ZERO, new_image.get_size())
# )
# )
undo_redo.commit_action()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
@tool
extends Control
signal pre_image_changed(image, rect_changed)
signal image_changed(image, rect_changed)
signal image_replaced(old_image, new_image)
@onready var image_view = $"%EditedImageView"
@onready var selection_view = $"%SelectionView"
@onready var grid_view = $"%GridView"
@onready var tool_manager = $"%ToolSwitcher"
@onready var context_settings = $"%ContextSettings"
var cur_scale := 1.0
var mouse_button := -1
var input_disabled := false
var dragging := false
var rollback_to_image : Image
var edited_image : Image
var edited_image_path : String
var edited_image_selection := BitMap.new()
func _ready():
if !get_viewport() is Window: return
# Recolor all floating panels to make buttons more visible
var style = get_theme_stylebox("panel", "Panel").duplicate()
style.bg_color = get_theme_color("base_color", "Editor")
theme = Theme.new()
theme.set_stylebox("panel", "Panel", style)
func handle_input(event) -> bool:
if input_disabled: return true
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT || event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
dragging = true
mouse_button = event.button_index
return pass_event_to_tool(event)
elif mouse_button == event.button_index:
dragging = false
mouse_button = -1
var rect = tool_manager.get_affected_rect()
make_image_edit(func(): pass_event_to_tool(event), rect)
return true
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
image_view.zoom(Vector2.ONE * 1.05)
return true
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
image_view.zoom(Vector2.ONE / 1.05)
return true
if event.button_index == MOUSE_BUTTON_MIDDLE:
return true
if event is InputEventMouseMotion:
if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE) || Input.is_key_pressed(KEY_SPACE):
image_view.translate(event.relative)
grab_focus()
return pass_event_to_tool(event)
if event is InputEventKey:
var ctrl = (event.get_keycode_with_modifiers() & (KEY_MASK_CMD_OR_CTRL)) != 0
match event.keycode:
KEY_A, KEY_D:
if ctrl:
clear_selection()
return ctrl
return false
return false
func make_image_edit(edit : Callable, affected_rect : Rect2i):
pre_image_changed.emit(edited_image, affected_rect)
edit.call()
image_changed.emit(edited_image, affected_rect)
if edited_image_selection.get_true_bit_count() == 0:
clear_selection()
image_view.texture.update(edited_image)
func pass_event_to_tool(event) -> bool:
event.position = event.global_position
var image_space_event = image_view.event_vp_to_image(event)
if context_settings.handle_image_input(image_space_event):
return true
return tool_manager.handle_image_input(
image_space_event,
edited_image,
edited_image_selection
)
func edit_texture(tex_path : String):
var new_image = Image.load_from_file(tex_path)
if new_image.get_format() == Image.FORMAT_RGB8:
# If no alpha, add alpha.
var image_bytes := new_image.get_data()
var new_image_bytes := PackedByteArray()
new_image_bytes.resize(image_bytes.size() / 3 * 4)
for i in image_bytes.size() / 3:
new_image_bytes[i * 4 + 0] = image_bytes[i * 3 + 0]
new_image_bytes[i * 4 + 1] = image_bytes[i * 3 + 1]
new_image_bytes[i * 4 + 2] = image_bytes[i * 3 + 2]
new_image_bytes[i * 4 + 3] = 255
new_image.set_data(new_image.get_width(), new_image.get_height(), new_image.has_mipmaps(), Image.FORMAT_RGBA8, new_image_bytes)
rollback_to_image = Image.create_from_data(new_image.get_width(), new_image.get_height(), new_image.has_mipmaps(), new_image.get_format(), new_image.get_data())
image_view.texture = ImageTexture.create_from_image(new_image)
image_view.reset_position()
replace_image(edited_image, new_image)
image_view.call_deferred("update_position")
edited_image_path = tex_path
func rollback_changes():
image_view.texture = ImageTexture.create_from_image(rollback_to_image)
replace_image(edited_image, rollback_to_image)
func set_view_grid(grid_size, grid_offset, is_region):
grid_view.image_size = edited_image.get_size()
grid_view.grid_size = grid_size
grid_view.grid_offset = grid_offset
grid_view.is_region = is_region
grid_view.queue_redraw()
func update_texture(new_image):
if new_image.get_size() != edited_image.get_size():
image_view.texture = ImageTexture.create_from_image(new_image)
image_view.update_position()
image_view.queue_redraw()
replace_image(edited_image, new_image)
image_view.texture.update(new_image)
edited_image = new_image
grid_view.image_size = new_image.get_size()
grid_view.queue_redraw()
func get_resized(old_image, new_size, expand_direction, stretch_format = -1) -> Image:
var old_size = old_image.get_size()
if stretch_format != -1:
var new_image = Image.create(old_size.x, old_size.y, false, old_image.get_format())
new_image.blit_rect(
old_image,
Rect2i(Vector2.ZERO, old_image.get_size()), Vector2i.ZERO
)
new_image.resize(new_size.x, new_size.y, stretch_format)
return new_image
else:
var new_image = Image.create(new_size.x, new_size.y, false, old_image.get_format())
var anchors = (Vector2i.ONE - expand_direction) * 0.5
new_image.blit_rect(
old_image,
Rect2i(Vector2i.ZERO, old_image.get_size()),
Vector2i(
(new_size.x - old_size.x) * anchors.x,
(new_size.y - old_size.y) * anchors.y,
)
)
return new_image
func replace_image(old_image, new_image):
var new_size = new_image.get_size()
edited_image = new_image
edited_image_selection.create(new_size)
clear_selection()
selection_view.selection = edited_image_selection
func clear_selection():
edited_image_selection.set_bit_rect(Rect2i(Vector2i.ZERO, edited_image_selection.get_size()), true)
func _on_resize_value_changed(
delta, expand_direction,
stretch_format = Image.INTERPOLATE_NEAREST if Input.is_key_pressed(KEY_SHIFT) else -1
):
var old_size = edited_image.get_size()
var new_size = old_size + Vector2i(delta.round())
var new_image = get_resized(edited_image, new_size, expand_direction, stretch_format)
image_replaced.emit(edited_image, new_image)
update_texture(new_image)

BIN
assets/Amogus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

View File

@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dqkc6jqfc6bxp"
path="res://.godot/imported/moonbase1.png-3dab277b7e316bd64644cfdb9a2e6cd4.ctex"
uid="uid://blmo4uqveqgtu"
path="res://.godot/imported/Amogus.png-c83f0e9d5398aea3025fb80985b16828.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/moonbase1.png"
dest_files=["res://.godot/imported/moonbase1.png-3dab277b7e316bd64644cfdb9a2e6cd4.ctex"]
source_file="res://assets/Amogus.png"
dest_files=["res://.godot/imported/Amogus.png-c83f0e9d5398aea3025fb80985b16828.ctex"]
[params]

BIN
assets/Sprite-0001.aseprite Normal file

Binary file not shown.

BIN
assets/coridors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

View File

@ -1,146 +0,0 @@
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../export/windows/game.exe"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=0
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
binary_format/architecture="x86_64"
codesign/enable=false
codesign/timestamp=true
codesign/timestamp_server_url=""
codesign/digest_algorithm=1
codesign/description=""
codesign/custom_options=PackedStringArray()
application/modify_resources=false
application/icon=""
application/console_wrapper_icon=""
application/icon_interpolation=4
application/file_version=""
application/product_version=""
application/company_name=""
application/product_name=""
application/file_description=""
application/copyright=""
application/trademarks=""
application/export_angle=0
application/export_d3d12=0
application/d3d12_agility_sdk_multiarch=true
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
$settings = New-ScheduledTaskSettingsSet
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
Start-ScheduledTask -TaskName godot_remote_debug
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
Remove-Item -Recurse -Force '{temp_dir}'"
[preset.1]
name="Web"
platform="Web"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../export/web/game.html"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.1.options]
custom_template/debug=""
custom_template/release=""
variant/extensions_support=false
variant/thread_support=false
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
html/export_icon=true
html/custom_html_shell=""
html/head_include=""
html/canvas_resize_policy=2
html/focus_canvas_on_start=true
html/experimental_virtual_keyboard=false
progressive_web_app/enabled=false
progressive_web_app/ensure_cross_origin_isolation_headers=true
progressive_web_app/offline_page=""
progressive_web_app/display=1
progressive_web_app/orientation=0
progressive_web_app/icon_144x144=""
progressive_web_app/icon_180x180=""
progressive_web_app/icon_512x512=""
progressive_web_app/background_color=Color(0, 0, 0, 1)
[preset.2]
name="Linux"
platform="Linux"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../export/linux/game.x86_64"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.2.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
binary_format/architecture="x86_64"
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash
export DISPLAY=:0
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
\"{temp_dir}/{exe_name}\" {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
rm -rf \"{temp_dir}\""

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bhpnk3olpquth"
path="res://.godot/imported/moonnauts.png-9888b6b5f38f2c677f48207439089193.ctex"
uid="uid://b7n80wgfsnaml"
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/moonnauts.png"
dest_files=["res://.godot/imported/moonnauts.png-9888b6b5f38f2c677f48207439089193.ctex"]
source_file="res://icon.png"
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
[params]

0
images/.gdignore Normal file
View File

BIN
images/spritepainter1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
images/spritepainter2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
images/spritepainter3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -1,56 +0,0 @@
class_name NPC
extends Node2D
var id_path: Array[Vector2i]
var target: Vector2i
var speed: float = 1
var oxygen: float = 100
func is_walking() -> bool:
return not id_path.is_empty()
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
randomize()
$Sprite2D.set_nation(randi()%$Sprite2D.Nations.max)
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
if id_path.is_empty(): return
var target_pos = Vector2(id_path.front()*World.tile_size)
global_position = global_position.move_toward(target_pos, speed)
if global_position.x < target_pos.x: $Sprite2D.flip_h = true
elif global_position.x > target_pos.x: $Sprite2D.flip_h = false
if global_position == target_pos:
id_path.pop_front()
set_target(target)
if $"../../TileMap".check_indoors(global_position/16):
set_spacesuit(false)
else:
set_spacesuit(true)
func set_spacesuit(suit: bool) -> void:
$Sprite2D.set_spacesuit(suit)
if suit: speed = 0.2
else: speed = 1
func set_target(pos: Vector2i):
var new_id_path = $"../../TileMap".astar_grid.get_id_path(
$"../../TileMap/ground".local_to_map(global_position+$"../../TileMap/ground".global_position),
target
).slice(1)
if not new_id_path.is_empty():
id_path = new_id_path
target = pos
else:
id_path = []
target = pos

View File

@ -1,21 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://cxueg5xm4buqk"]
[ext_resource type="Script" path="res://npc/npc.gd" id="1_m82ir"]
[ext_resource type="Texture2D" uid="uid://bhpnk3olpquth" path="res://assets/moonnauts.png" id="1_vgeae"]
[ext_resource type="Script" path="res://npc/sprite_2d.gd" id="2_jtl2q"]
[sub_resource type="AtlasTexture" id="AtlasTexture_2v0ml"]
resource_local_to_scene = true
atlas = ExtResource("1_vgeae")
region = Rect2(0, 0, 16, 16)
[node name="npc" type="Node2D"]
z_index = 32
z_as_relative = false
script = ExtResource("1_m82ir")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture_filter = 1
texture = SubResource("AtlasTexture_2v0ml")
centered = false
script = ExtResource("2_jtl2q")

View File

@ -1,27 +0,0 @@
extends Sprite2D
enum Nations {
ru = 0,
arab = 1,
chi = 2,
max,
}
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func set_spacesuit(suit: bool) -> void:
var atlas: AtlasTexture = texture
if suit: atlas.region.position.y = 0
else: atlas.region.position.y = 16
func set_nation(nation: Nations) -> void:
var atlas: AtlasTexture = texture
atlas.region.position.x = 16*nation

View File

@ -11,17 +11,16 @@ config_version=5
[application]
config/name="TestProject"
run/main_scene="res://world/world.tscn"
run/main_scene="res://world.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility")
config/icon="res://assets/icon.svg"
[display]
window/size/viewport_width=640
window/size/viewport_height=480
window/size/mode=2
window/size/viewport_width=1024
window/size/viewport_height=1024
window/stretch/mode="canvas_items"
window/stretch/aspect="keep_height"
window/stretch/aspect="keep_width"
[dotnet]
@ -29,7 +28,7 @@ project/assembly_name="TestProject"
[editor_plugins]
enabled=PackedStringArray()
enabled=PackedStringArray("res://addons/sprite_painter/plugin.cfg")
[input]
@ -38,41 +37,6 @@ left_click={
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
left_mouse={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(92, 19),"global_position":Vector2(101, 65),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
]
}
right_mouse={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(277, 13),"global_position":Vector2(286, 59),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
]
}
zoom_in={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":61,"unicode":61,"location":0,"echo":false,"script":null)
]
}
zoom_out={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":45,"unicode":45,"location":0,"echo":false,"script":null)
]
}
1={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
]
}
2={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null)
]
}
3={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
]
}
[rendering]

BIN
test/diags2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

View File

@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dq0at84j2c0av"
path="res://.godot/imported/selection.png-fb5449805f2ef00ca05e408b4d47d602.ctex"
uid="uid://cejv38d7lmges"
path="res://.godot/imported/diags2.png-3ad0e4c68a2af359ead886b6bff1377e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/selection.png"
dest_files=["res://.godot/imported/selection.png-fb5449805f2ef00ca05e408b4d47d602.ctex"]
source_file="res://test/diags2.png"
dest_files=["res://.godot/imported/diags2.png-3ad0e4c68a2af359ead886b6bff1377e.ctex"]
[params]

1
test/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

37
test/icon.svg.import Normal file
View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bjmypnfjkbve6"
path="res://.godot/imported/icon.svg-9ffd99e4b8a55380f3055552bd81b639.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://test/icon.svg"
dest_files=["res://.godot/imported/icon.svg-9ffd99e4b8a55380f3055552bd81b639.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

28
test/node_2d.tscn Normal file
View File

@ -0,0 +1,28 @@
[gd_scene load_steps=4 format=3 uid="uid://dq1nb7p2ts0ok"]
[ext_resource type="Texture2D" uid="uid://c7nymhfcdr3hl" path="res://addons/sprite_painter/graphics/diags.png" id="1_hgr16"]
[ext_resource type="Texture2D" uid="uid://b7n80wgfsnaml" path="res://icon.png" id="1_uj0gm"]
[ext_resource type="Texture2D" uid="uid://cejv38d7lmges" path="res://test/diags2.png" id="2_lxinl"]
[node name="Node2D" type="Node2D"]
[node name="Polygon2D2" type="Polygon2D" parent="."]
texture_repeat = 2
position = Vector2(-537, 434)
texture = ExtResource("1_hgr16")
polygon = PackedVector2Array(302, 105, -2, 293, 224, 391, 330, 537, 478, 327, 567, 292, 640, 199, 842, 581, 852, 669, 1112, 443, 904, 241, 868, 107, 665.661, 13.9704, 520, -53, 418, 52)
[node name="a" type="Sprite2D" parent="."]
position = Vector2(411, 128)
texture = ExtResource("2_lxinl")
centered = false
[node name="Icon2" type="Sprite2D" parent="."]
position = Vector2(43, 131)
texture = ExtResource("1_uj0gm")
centered = false
[node name="Icon4" type="Sprite2D" parent="."]
position = Vector2(281, 520)
texture = ExtResource("1_uj0gm")
centered = false

76
tile_map.gd Normal file
View File

@ -0,0 +1,76 @@
extends Node2D
enum StructType {
EMPTY_ROOM, # 0. Пустой модуль
RESIDENTIAL_ROOM, # 1. Жилой Модуль
LABORATORY_ROOM, # 2. Лабораторный Модуль
ENERGY_ROOM, # 3. Энергетический Модуль
FARM_ROOM, # 4. Фермерский Модуль
LIFE_SUPPORT_ROOM, # 5. Модуль Жизнеобеспечения
CONTROL_ROOM, # 6. Модуль Управления
OBSERVATORY_ROOM, # 7. Модуль Обсерватории
MEDICAL_ROOM, # 8. Медицинский Модуль
GEOLOGICAL_ROOM, # 9. Геологический Модуль
RESOURCE_EXTRACTION_ROOM, # 10. Модуль Добычи и Переработки Ресурсов
COMMUNICATIONS_ROOM # 11. Модуль Связи и Навигации
}
enum struct_fields {
SOURCE_ID,
SIZE,
POS_ATLAS,
LAYER
}
@onready var structs = {
StructType.EMPTY_ROOM: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(5,5),
struct_fields.POS_ATLAS: Vector2i(0,0),
struct_fields.LAYER: $buildings/buildings0
}
}
var layers: Array[TileMapLayer]
var layers_dict: Dictionary
func _list_childrens(node: Node2D):
for child in node.get_children():
if child is TileMapLayer:
layers.append(child)
layers_dict.get_or_add(child.name, child)
elif child is Node2D:
_list_childrens(child)
pass
func _ready() -> void:
layers = []
_list_childrens(self)
func get_maxZ(tile_pos: Vector2i) -> int:
for layer in layers:
if layer.get_cell_source_id(tile_pos) != -1:
return layer.z_index
return -1
func place_struct(pos: Vector2i, type: StructType):
#Is place free check
var struct_size = structs[type][struct_fields.SIZE]
var layer = structs[type][struct_fields.LAYER]
var source_id = structs[type][struct_fields.SOURCE_ID]
var pos_atlas = structs[type][struct_fields.POS_ATLAS]
var half_size = Vector2i(struct_size.x / 2, struct_size.y / 2)
for x in range(struct_size.x):
for y in range(struct_size.y):
var offset = Vector2i(x, y)
var tile_pos = pos+offset-half_size
if get_maxZ(tile_pos) >= layer.z_index:
return
for x in range(struct_size.x):
for y in range(struct_size.y):
var offset = Vector2i(x, y)
var tile_pos = pos+offset-half_size
layer.set_cell(tile_pos, source_id, Vector2i(x,y)+pos_atlas)

26
world.gd Normal file
View File

@ -0,0 +1,26 @@
extends Node2D
# Переменная для хранения ссылки на TileMap
@onready var buildings: TileMapLayer = $TileMap/buildings/buildings0
@onready var roads: TileMapLayer = $TileMap/roads
@onready var ground: TileMapLayer = $TileMap/ground
var is_drawing_road: bool = false # Флаг для отслеживания зажатия кнопки мыши
var is_drawing_mountian: bool = false # Флаг для отслеживания зажатия кнопки мыши
func _ready():
pass
func _input(event):
if event is InputEventMouseButton:
# Проверяем нажатие или отпускание левой кнопки мыши
if event.button_index == MOUSE_BUTTON_LEFT:
is_drawing_road = event.pressed # Устанавливаем флаг, если кнопка нажата
elif event.button_index == MOUSE_BUTTON_RIGHT:
$TileMap.place_struct(ground.local_to_map(get_global_mouse_position()-$TileMap.global_position), $TileMap.StructType.EMPTY_ROOM)
if event is InputEventMouseMotion and is_drawing_road:
# Получаем позицию мыши в мировых координатах
var current_tile:Vector2 = ground.local_to_map(get_global_mouse_position()-$TileMap.global_position)
if buildings.get_cell_source_id(current_tile) == -1:
roads.set_cells_terrain_connect([current_tile], 0, 2, true)

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
extends Node2D
const NPC_SCENE = preload("res://npc/npc.tscn")
var npcs: Array[NPC]
func add_npc(pos: Vector2i):
var npc: NPC = NPC_SCENE.instantiate()
npc.translate(pos*World.tile_size)
npcs.append(npc)
add_child(npc)
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
randomize()
add_npc(Vector2i(11, 8))
add_npc(Vector2i(9, 9))
add_npc(Vector2i(12, 11))
func set_npc_target(npc: NPC, target: Vector2i):
npc.set_target(target)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
for npc in npcs:
if not npc.is_walking():
set_npc_target(npc, Vector2i(randi()%64,randi()%64))
pass

View File

@ -1,252 +0,0 @@
class_name MyTileMap
extends Node2D
enum StructType {
EMPTY_ROOM, # 0. Пустой модуль
RESIDENTIAL_ROOM, # 1. Жилой Модуль
LABORATORY_ROOM, # 2. Лабораторный Модуль
ENERGY_ROOM, # 3. Энергетический Модуль
FARM_ROOM, # 4. Фермерский Модуль
LIFE_SUPPORT_ROOM, # 5. Модуль Жизнеобеспечения
CONTROL_ROOM, # 6. Модуль Управления
OBSERVATORY_ROOM, # 7. Модуль Обсерватории
MEDICAL_ROOM, # 8. Медицинский Модуль
GEOLOGICAL_ROOM, # 9. Геологический Модуль
RESOURCE_EXTRACTION_ROOM, # 10. Модуль Добычи и Переработки Ресурсов
COMMUNICATIONS_ROOM, # 11. Модуль Связи и Навигации_
V_WAY, H_WAY, WAY_UP, WAY_DOWN, WAY_LEFT, WAY_RIGHT,
}
enum struct_fields {
SOURCE_ID,
SIZE,
POS_ATLAS,
LAYER
}
@onready var structs = {
StructType.EMPTY_ROOM: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(5,5),
struct_fields.POS_ATLAS: Vector2i(0,0),
struct_fields.LAYER: $buildings/buildings0
},
StructType.V_WAY: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(3,1),
struct_fields.POS_ATLAS: Vector2i(0,6),
struct_fields.LAYER: $buildings/buildings1
},
StructType.H_WAY: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(1,3),
struct_fields.POS_ATLAS: Vector2i(4,5),
struct_fields.LAYER: $buildings/buildings1
},
StructType.WAY_UP: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(3,1),
struct_fields.POS_ATLAS: Vector2i(0,5),
struct_fields.LAYER: $buildings/buildings1
},
StructType.WAY_DOWN: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(3,1),
struct_fields.POS_ATLAS: Vector2i(0,7),
struct_fields.LAYER: $buildings/buildings1
},
StructType.WAY_LEFT: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(1,3),
struct_fields.POS_ATLAS: Vector2i(3,5),
struct_fields.LAYER: $buildings/buildings1
},
StructType.WAY_RIGHT: {
struct_fields.SOURCE_ID: 1,
struct_fields.SIZE: Vector2i(1,3),
struct_fields.POS_ATLAS: Vector2i(5,5),
struct_fields.LAYER: $buildings/buildings1
},
}
var layers: Array[TileMapLayer]
var layers_dict: Dictionary
var astar_grid: AStarGrid2D
func _list_childrens(node: Node2D):
for child in node.get_children():
if child is TileMapLayer:
layers.append(child)
layers_dict.get_or_add(child.name, child)
elif child is Node2D:
_list_childrens(child)
pass
func _ready() -> void:
layers = []
_list_childrens(self)
astar_grid = AStarGrid2D.new()
astar_grid.region = layers_dict["ground"].get_used_rect()
astar_grid.cell_size = Vector2i(16, 16)
astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
astar_grid.update()
for x in $ground.get_used_rect().size.x:
for y in $ground.get_used_rect().size.y:
var tile_pos = Vector2i(x,y)
update_astar_tile(tile_pos)
func get_maxZ(tile_pos: Vector2i) -> int:
var max_z: int = -1024
for layer: TileMapLayer in layers:
if layer.get_cell_source_id(tile_pos) != -1 and layer.z_index > max_z:
max_z = layer.z_index
return max_z
func get_toplayer(pos: Vector2i) -> TileMapLayer:
var toplayer: TileMapLayer = null
for layer: TileMapLayer in layers:
if layer.get_cell_tile_data(pos) != null:
if toplayer == null: toplayer = layer
elif layer.z_index > toplayer.z_index: toplayer = layer
return toplayer
func place_road(pos: Vector2i) -> bool:
var layer: TileMapLayer = layers_dict["roads"]
if get_maxZ(pos) >= layer.z_index:
return false
layer.set_cells_terrain_connect([pos], 0, 2, true)
update_astar_tile(pos)
return true
func erase_road(pos: Vector2i) -> bool:
var layer: TileMapLayer = layers_dict["roads"]
layer.erase_cell(pos)
update_astar_tile(pos)
return true
func place_way(pos1: Vector2i, pos2: Vector2i) -> bool:
if pos1.x == pos2.x and pos1.y == pos2.y: return false
if pos1.x != pos2.x and pos1.y != pos2.y: return false
var tiledata1: TileData = layers_dict["buildings0"].get_cell_tile_data(pos1)
var tiledata2: TileData = layers_dict["buildings0"].get_cell_tile_data(pos2)
if tiledata1 == null or tiledata2 == null: return false
if not (tiledata1.get_custom_data("is_center") and tiledata1.get_custom_data("struct_name")=="EMPTY_ROOM"): return false
if not (tiledata2.get_custom_data("is_center") and tiledata2.get_custom_data("struct_name")=="EMPTY_ROOM"): return false
#Check place is free
if pos1.x != pos2.x:
if abs(pos1.x-pos2.x)<=5: return false
for x in range(min(pos1.x,pos2.x)+2, max(pos1.x,pos2.x)-1):
if get_maxZ(Vector2i(x, pos1.y)) >= structs[StructType.H_WAY][struct_fields.LAYER].z_index:
return false
if pos1.y != pos2.y:
if abs(pos1.y-pos2.y)<=5: return false
for y in range(min(pos1.y,pos2.y)+2, max(pos1.y,pos2.y)-1):
if get_maxZ(Vector2i(pos1.x, y)) >= structs[StructType.V_WAY][struct_fields.LAYER].z_index:
return false
if pos1.x != pos2.x:
place_struct(Vector2i(min(pos1.x,pos2.x)+2, pos1.y), StructType.WAY_LEFT)
place_struct(Vector2i(max(pos1.x,pos2.x)-2, pos1.y), StructType.WAY_RIGHT)
for x in range(min(pos1.x,pos2.x)+3, max(pos1.x,pos2.x)-2):
place_struct(Vector2i(x, pos1.y), StructType.H_WAY)
if pos1.y != pos2.y:
place_struct(Vector2i(pos1.x, min(pos1.y,pos2.y)+2), StructType.WAY_UP)
place_struct(Vector2i(pos1.x, max(pos1.y,pos2.y)-2), StructType.WAY_DOWN)
for y in range(min(pos1.y,pos2.y)+3, max(pos1.y,pos2.y)-2):
place_struct(Vector2i(pos1.x, y), StructType.V_WAY)
return true
func destroy_building(pos: Vector2i, only_ways: bool = false):
for layer in layers:
var tiledata: TileData = layer.get_cell_tile_data(pos)
if tiledata != null and tiledata.get_custom_data("is_center"):
if tiledata.get_custom_data("struct_name") == "H_WAY":
remove_struct(pos)
destroy_building(pos+Vector2i.LEFT)
destroy_building(pos+Vector2i.RIGHT)
elif tiledata.get_custom_data("struct_name") == "V_WAY":
remove_struct(pos)
destroy_building(pos+Vector2i.UP)
destroy_building(pos+Vector2i.DOWN)
elif not only_ways and tiledata.get_custom_data("struct_name") == "EMPTY_ROOM":
remove_struct(pos)
destroy_building(pos+Vector2i.LEFT*3, true)
destroy_building(pos+Vector2i.RIGHT*3, true)
destroy_building(pos+Vector2i.UP*3, true)
destroy_building(pos+Vector2i.DOWN*3, true)
elif not only_ways:
remove_struct(pos)
return
func remove_struct(pos: Vector2i):
for layer in layers:
var tiledata: TileData = layer.get_cell_tile_data(pos)
if tiledata != null and tiledata.get_custom_data("is_center"):
var struct_size = tiledata.get_custom_data("struct_size")
var half_size = Vector2i(struct_size.x / 2, struct_size.y / 2)
for x in range(struct_size.x):
for y in range(struct_size.y):
var offset = Vector2i(x, y)
var tile_pos = pos+offset-half_size
layer.erase_cell(tile_pos)
update_astar_tile(tile_pos)
return
func place_struct(pos: Vector2i, type: StructType) -> bool:
if not structs.has(type):
return false
var struct_size = structs[type][struct_fields.SIZE]
var layer = structs[type][struct_fields.LAYER]
var source_id = structs[type][struct_fields.SOURCE_ID]
var pos_atlas = structs[type][struct_fields.POS_ATLAS]
var half_size = Vector2i(struct_size.x / 2, struct_size.y / 2)
#Is place free check
for x in range(struct_size.x):
for y in range(struct_size.y):
var offset = Vector2i(x, y)
var tile_pos = pos+offset-half_size
if get_maxZ(tile_pos) >= layer.z_index:
return false
for x in range(struct_size.x):
for y in range(struct_size.y):
var offset = Vector2i(x, y)
var tile_pos = pos+offset-half_size
layer.set_cell(tile_pos, source_id, Vector2i(x,y)+pos_atlas)
update_astar_tile(tile_pos)
return true
func is_walkable(pos: Vector2i) -> bool:
var state: bool = true
for layer in layers:
var tiledata: TileData = layer.get_cell_tile_data(pos)
if tiledata != null and not tiledata.get_custom_data("walkable"):
state = false
break
return state
func weight(pos: Vector2i) -> int:
var layer = get_toplayer(pos)
var tiledata: TileData = layer.get_cell_tile_data(pos)
if tiledata != null: return tiledata.get_custom_data("astar_weight")
else: return 5
func update_astar_tile(pos: Vector2i):
astar_grid.set_point_solid(pos, not is_walkable(pos))
astar_grid.set_point_weight_scale(pos, weight(pos))
func check_indoors(pos: Vector2i) -> bool:
var layer = get_toplayer(pos)
var tiledata: TileData = layer.get_cell_tile_data(pos)
if tiledata != null: return tiledata.get_custom_data("indoors")
else: return false

View File

@ -1,123 +0,0 @@
class_name World
extends Node2D
const tile_size: int = 16
# Переменная для хранения ссылки на TileMap
@onready var ground: TileMapLayer = $TileMap/ground
@onready var tileMap: Node2D = $TileMap
const SELECTION_SCENE = preload("res://UI/selection/selection.tscn")
var selected_instrument: instruments
var selected_tile: Vector2i
var selected_tile2: Vector2i
var selected: bool
var selected2: bool
var selection: Node
var selection2: Node
var lmb: bool
var rmb: bool
func _ready():
Engine.physics_ticks_per_second = 60
selection = SELECTION_SCENE.instantiate()
selection2 = SELECTION_SCENE.instantiate()
add_child(selection)
add_child(selection2)
change_instrument(instruments.NULL)
enum instruments {
NULL,
DESTROY,
CONSTRUCT,
BUILD_ROAD
}
func select_tile(pos: Vector2i) -> Vector2i:
var toplayer: TileMapLayer = tileMap.get_toplayer(pos)
var tiledata: TileData = toplayer.get_cell_tile_data(pos)
if tiledata == null: return pos
while tiledata.get_custom_data("center_direction") != Vector2i.ZERO:
pos+=tiledata.get_custom_data("center_direction")
tiledata = toplayer.get_cell_tile_data(pos)
return pos
func _physics_process(delta: float) -> void:
if Input.is_action_just_released("left_mouse") and lmb:
selected_tile = select_tile(ground.local_to_map(get_global_mouse_position()-tileMap.global_position))
selected = true
if(selected_tile==selected_tile2):
selected_tile2 = Vector2i(-128,-128)
selected2 = false
print(tileMap.is_walkable(selected_tile))
if Input.is_action_just_released("right_mouse") and rmb:
selected_tile2 = select_tile(ground.local_to_map(get_global_mouse_position()-tileMap.global_position))
if(selected_tile!=selected_tile2): selected2 = true
else:
selected_tile2 = Vector2i(-128,-128)
selected2 = false
if Input.is_action_just_pressed("ui_cancel"):
change_instrument(instruments.NULL)
match selected_instrument:
instruments.NULL:
pass
instruments.DESTROY:
if Input.is_action_just_pressed("ui_accept") and selected:
tileMap.destroy_building(selected_tile)
instruments.CONSTRUCT:
if Input.is_action_just_pressed("ui_accept") and selected and selected2:
tileMap.place_way(selected_tile, selected_tile2)
elif Input.is_action_just_pressed("ui_accept") and selected:
tileMap.place_struct(selected_tile, tileMap.StructType.EMPTY_ROOM)
instruments.BUILD_ROAD:
selected_tile = ground.local_to_map(get_global_mouse_position()-tileMap.global_position)
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
tileMap.place_road(selected_tile)
elif Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
tileMap.erase_road(selected_tile)
selection.offset = ground.map_to_local(selected_tile)+tileMap.global_position
selection2.offset = ground.map_to_local(selected_tile2)+tileMap.global_position
func change_instrument(new: instruments):
selected_tile = Vector2i(-128, -128)
selected_tile2 = Vector2i(-128, -128)
selected = false
selected2 = false
selected_instrument = new
match selected_instrument:
instruments.NULL:
lmb = true
rmb = false
instruments.DESTROY:
lmb = true
rmb = false
instruments.CONSTRUCT:
lmb = true
rmb = true
instruments.BUILD_ROAD:
lmb = false
rmb = false
func _process(delta: float) -> void:
if Input.is_action_pressed("ui_right"):
$Camera2D.translate(Vector2.RIGHT*320*delta/$Camera2D.zoom)
if Input.is_action_pressed("ui_left"):
$Camera2D.translate(Vector2.LEFT*320*delta/$Camera2D.zoom)
if Input.is_action_pressed("ui_up"):
$Camera2D.translate(Vector2.UP*320*delta/$Camera2D.zoom)
if Input.is_action_pressed("ui_down"):
$Camera2D.translate(Vector2.DOWN*320*delta/$Camera2D.zoom)
if Input.is_action_just_pressed("zoom_in"):
$Camera2D.zoom+=Vector2.ONE*0.25
if Input.is_action_just_pressed("zoom_out"):
$Camera2D.zoom-=Vector2.ONE*0.25
if Input.is_action_just_pressed("1"): change_instrument(instruments.CONSTRUCT)
if Input.is_action_just_pressed("2"): change_instrument(instruments.DESTROY)
if Input.is_action_just_pressed("3"): change_instrument(instruments.BUILD_ROAD)