generated from krampus/template-godot4
Added multiline (wide-angle) spray type
This commit is contained in:
parent
2cf7010c92
commit
22984897e2
BIN
asset_dev/ui/reticle.xcf
Normal file
BIN
asset_dev/ui/reticle.xcf
Normal file
Binary file not shown.
BIN
assets/ui/hud/reticle.png
(Stored with Git LFS)
Normal file
BIN
assets/ui/hud/reticle.png
(Stored with Git LFS)
Normal file
Binary file not shown.
35
assets/ui/hud/reticle.png.import
Normal file
35
assets/ui/hud/reticle.png.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bdnurvg7xe6k7"
|
||||
path.s3tc="res://.godot/imported/reticle.png-0a9f831dae57a5cb1ef00786d2ed0e86.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/ui/hud/reticle.png"
|
||||
dest_files=["res://.godot/imported/reticle.png-0a9f831dae57a5cb1ef00786d2ed0e86.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
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=0
|
@ -141,7 +141,7 @@ environment = SubResource("Environment_cc548")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.16, 0)
|
||||
light_energy = 32.0
|
||||
light_energy = 8.0
|
||||
shadow_enabled = true
|
||||
omni_range = 20.0
|
||||
|
||||
@ -190,7 +190,7 @@ surface_material_override/0 = SubResource("StandardMaterial3D_ygm4w")
|
||||
shape = SubResource("ConcavePolygonShape3D_0qjrr")
|
||||
|
||||
[node name="Cylinder" parent="Geometry" instance=ExtResource("1_a67lu")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6, -8.312)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.33738, 6, -7.18125)
|
||||
mask_dim = 4096
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Geometry/Cylinder"]
|
||||
|
@ -90,3 +90,8 @@ fire={
|
||||
"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)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
3d_render/layer_1="World"
|
||||
3d_render/layer_2="HUD"
|
||||
|
24
src/equipment/point_spray/point_spray.gd
Normal file
24
src/equipment/point_spray/point_spray.gd
Normal file
@ -0,0 +1,24 @@
|
||||
class_name PointSpray extends Spray
|
||||
## Simple single-point spraygun
|
||||
|
||||
@export var spray_scale := 16.0
|
||||
|
||||
@onready var raycast: RayCast3D = %RayCast3D
|
||||
@onready var spray_effect: MeshInstance3D = %SprayEffect
|
||||
|
||||
|
||||
func fire() -> void:
|
||||
if raycast.is_colliding():
|
||||
var collider := raycast.get_collider()
|
||||
if collider is GunkBody:
|
||||
var point := raycast.get_collision_point()
|
||||
var point_scale := sqrt(point.distance_to(global_position)) * spray_scale
|
||||
(collider as GunkBody).paint_continuous(
|
||||
point, raycast.get_collision_normal(), point_scale
|
||||
)
|
||||
|
||||
spray_effect.visible = true
|
||||
|
||||
|
||||
func idle() -> void:
|
||||
spray_effect.visible = false
|
35
src/equipment/point_spray/point_spray.tscn
Normal file
35
src/equipment/point_spray/point_spray.tscn
Normal file
@ -0,0 +1,35 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://cc102xko0u6yj"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/equipment/point_spray/point_spray.gd" id="1_2yl2v"]
|
||||
[ext_resource type="Texture2D" uid="uid://bdnurvg7xe6k7" path="res://assets/ui/hud/reticle.png" id="1_f2scl"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ng43h"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_color = Color(0, 1, 0.301961, 0.254902)
|
||||
|
||||
[sub_resource type="PrismMesh" id="PrismMesh_ow0jh"]
|
||||
material = SubResource("StandardMaterial3D_ng43h")
|
||||
size = Vector3(0.2, 2, 0.2)
|
||||
|
||||
[node name="PointSpray" type="Node3D"]
|
||||
script = ExtResource("1_2yl2v")
|
||||
|
||||
[node name="RayCast3D" type="RayCast3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
target_position = Vector3(0, 0, -2)
|
||||
|
||||
[node name="SprayEffect" type="MeshInstance3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, -4.47035e-08, -1, 0, 1, -4.47035e-08, 0, 0, -1)
|
||||
visible = false
|
||||
layers = 2
|
||||
mesh = SubResource("PrismMesh_ow0jh")
|
||||
skeleton = NodePath("../../../..")
|
||||
|
||||
[node name="Decal" type="Decal" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.47035e-08, -1, 0, 1, -4.47035e-08, 0, 0, -1)
|
||||
size = Vector3(0.2, 2, 0.2)
|
||||
texture_albedo = ExtResource("1_f2scl")
|
||||
cull_mask = 1048573
|
12
src/equipment/spray.gd
Normal file
12
src/equipment/spray.gd
Normal file
@ -0,0 +1,12 @@
|
||||
class_name Spray extends Node3D
|
||||
## Abstract base class for spraygun types
|
||||
|
||||
|
||||
## Called each frame that this spray is being fired.
|
||||
func fire() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Called each frame that this spray is not being fired.
|
||||
func idle() -> void:
|
||||
pass
|
39
src/equipment/wide_spray/wide_spray.gd
Normal file
39
src/equipment/wide_spray/wide_spray.gd
Normal file
@ -0,0 +1,39 @@
|
||||
class_name WideSpray extends Spray
|
||||
## Wide spray pattern
|
||||
|
||||
const SPRAYCAST_GROUP := "SprayCast"
|
||||
|
||||
@export var spray_scale := 16.0
|
||||
|
||||
@onready var spray_casts: Node3D = %SprayCasts
|
||||
@onready var spray_effect: MeshInstance3D = %SprayEffect
|
||||
|
||||
|
||||
func fire() -> void:
|
||||
var prev_target: GunkBody = null
|
||||
var prev_point: Vector3
|
||||
var prev_normal: Vector3
|
||||
|
||||
for raycast: RayCast3D in spray_casts.get_children():
|
||||
if raycast.is_colliding():
|
||||
var target := raycast.get_collider() as GunkBody
|
||||
if target:
|
||||
var point := raycast.get_collision_point()
|
||||
var normal := raycast.get_collision_normal()
|
||||
|
||||
# Always paint at least a dot, to cap the ends of the line
|
||||
target.paint_dot(point, normal, spray_scale)
|
||||
if target == prev_target:
|
||||
# Continue the multiline if possible
|
||||
target.add_to_multiline(prev_point, prev_normal, point, normal, spray_scale * 2)
|
||||
prev_point = point
|
||||
prev_normal = normal
|
||||
prev_target = target
|
||||
prev_point = point
|
||||
prev_normal = normal
|
||||
|
||||
spray_effect.visible = true
|
||||
|
||||
|
||||
func idle() -> void:
|
||||
spray_effect.visible = false
|
56
src/equipment/wide_spray/wide_spray.tscn
Normal file
56
src/equipment/wide_spray/wide_spray.tscn
Normal file
@ -0,0 +1,56 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://d2hnxr5l6w2x4"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/equipment/wide_spray/wide_spray.gd" id="1_ggkto"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cdyoo"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_color = Color(0, 1, 0.301961, 0.254902)
|
||||
|
||||
[sub_resource type="PrismMesh" id="PrismMesh_vh2mt"]
|
||||
material = SubResource("StandardMaterial3D_cdyoo")
|
||||
size = Vector3(1, 2, 0.2)
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_c3qhn"]
|
||||
load_path = "res://.godot/imported/reticle.png-0a9f831dae57a5cb1ef00786d2ed0e86.s3tc.ctex"
|
||||
|
||||
[node name="WideSpray" type="Node3D"]
|
||||
script = ExtResource("1_ggkto")
|
||||
|
||||
[node name="SprayCasts" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="RayCast3D" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(-0.5, 0, -2)
|
||||
|
||||
[node name="RayCast3D2" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(-0.333, 0, -2)
|
||||
|
||||
[node name="RayCast3D3" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(-0.167, 0, -2)
|
||||
|
||||
[node name="RayCast3D4" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(0, 0, -2)
|
||||
|
||||
[node name="RayCast3D5" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(0.167, 0, -2)
|
||||
|
||||
[node name="RayCast3D6" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(0.333, 0, -2)
|
||||
|
||||
[node name="RayCast3D7" type="RayCast3D" parent="SprayCasts"]
|
||||
target_position = Vector3(0.5, 0, -2)
|
||||
|
||||
[node name="SprayEffect" type="MeshInstance3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, -4.47035e-08, -1, 0, 1, -4.47035e-08, 0, 0, -1)
|
||||
layers = 2
|
||||
mesh = SubResource("PrismMesh_vh2mt")
|
||||
skeleton = NodePath("../../../..")
|
||||
|
||||
[node name="Decal" type="Decal" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.47035e-08, -1, 0, 1, -4.47035e-08, 0, 0, -1)
|
||||
size = Vector3(0.2, 2, 0.2)
|
||||
texture_albedo = SubResource("CompressedTexture2D_c3qhn")
|
||||
cull_mask = 1048573
|
@ -7,8 +7,6 @@ const JUMP_FORCE := 4.5
|
||||
const GROUND_FRICTION := 0.3
|
||||
const AIR_FRICTION := 0.03
|
||||
|
||||
const SPRAY_SCALE := 16
|
||||
|
||||
var gravity: Vector3 = (
|
||||
ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
||||
@ -16,9 +14,6 @@ var gravity: Vector3 = (
|
||||
|
||||
@onready var camera_pivot: Node3D = %CameraPivot
|
||||
@onready var spray_muzzle: Marker3D = %SprayMuzzle
|
||||
@onready var raycast: RayCast3D = %RayCast3D
|
||||
|
||||
@onready var spray_effect: MeshInstance3D = %SprayEffect
|
||||
|
||||
|
||||
func get_speed() -> float:
|
||||
@ -35,27 +30,15 @@ func get_friction() -> float:
|
||||
return AIR_FRICTION
|
||||
|
||||
|
||||
func _scale_spray_point(point: Vector3) -> float:
|
||||
return point.distance_to(spray_muzzle.global_position) * SPRAY_SCALE
|
||||
|
||||
|
||||
func fire_spray() -> void:
|
||||
if raycast.is_colliding():
|
||||
var collider := raycast.get_collider()
|
||||
if collider is GunkBody:
|
||||
var point := raycast.get_collision_point()
|
||||
var point_scale := point.distance_to(spray_muzzle.global_position) * SPRAY_SCALE
|
||||
(collider as GunkBody).paint_continuous(
|
||||
point, raycast.get_collision_normal(), point_scale
|
||||
)
|
||||
func get_spray() -> Spray:
|
||||
return spray_muzzle.get_child(0)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if Input.is_action_pressed("fire"):
|
||||
fire_spray()
|
||||
spray_effect.visible = true
|
||||
get_spray().fire()
|
||||
else:
|
||||
spray_effect.visible = false
|
||||
get_spray().idle()
|
||||
|
||||
# Gravity
|
||||
if not is_on_floor():
|
||||
|
@ -1,21 +1,12 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://bwe2jdmvinhqd"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://bwe2jdmvinhqd"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/player/player.gd" id="1_npueo"]
|
||||
[ext_resource type="Script" path="res://src/player/camera_controller.gd" id="2_veeqv"]
|
||||
[ext_resource type="PackedScene" uid="uid://cc102xko0u6yj" path="res://src/equipment/point_spray/point_spray.tscn" id="3_8n2h0"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_ua7a2"]
|
||||
size = Vector3(0.05, 0.05, 0.3)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ng43h"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
albedo_color = Color(0, 1, 0.301961, 0.254902)
|
||||
|
||||
[sub_resource type="PrismMesh" id="PrismMesh_ow0jh"]
|
||||
material = SubResource("StandardMaterial3D_ng43h")
|
||||
size = Vector3(0.2, 2, 0.2)
|
||||
|
||||
[sub_resource type="QuadMesh" id="QuadMesh_cn0yq"]
|
||||
size = Vector2(1, 0.05)
|
||||
|
||||
@ -32,21 +23,14 @@ script = ExtResource("2_veeqv")
|
||||
|
||||
[node name="SprayNozzle" type="MeshInstance3D" parent="CameraPivot"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, -0.1, -0.1)
|
||||
layers = 2
|
||||
mesh = SubResource("BoxMesh_ua7a2")
|
||||
|
||||
[node name="SprayMuzzle" type="Marker3D" parent="CameraPivot/SprayNozzle"]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 0.997564, -0.0697565, 0, 0.0697565, 0.997564, 0, 0, -0.15)
|
||||
|
||||
[node name="RayCast3D" type="RayCast3D" parent="CameraPivot/SprayNozzle/SprayMuzzle" groups=["SprayCast"]]
|
||||
unique_name_in_owner = true
|
||||
target_position = Vector3(0, 0, -2)
|
||||
|
||||
[node name="SprayEffect" type="MeshInstance3D" parent="CameraPivot/SprayNozzle/SprayMuzzle"]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, -1)
|
||||
mesh = SubResource("PrismMesh_ow0jh")
|
||||
skeleton = NodePath("../../..")
|
||||
[node name="PointSpray" parent="CameraPivot/SprayNozzle/SprayMuzzle" instance=ExtResource("3_8n2h0")]
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot"]
|
||||
current = true
|
||||
|
@ -1,7 +1,7 @@
|
||||
class_name GunkBody extends StaticBody3D
|
||||
## StaticBody3D with an associated "gunkable" mesh.
|
||||
|
||||
const CONTINUITY_LIMIT := 64
|
||||
const CONTINUITY_LIMIT := 128
|
||||
const BUFFER_LIMIT := 3
|
||||
const FACE_EPSILON := 0.4
|
||||
const MASK_COLOR := Color.RED
|
||||
@ -11,8 +11,10 @@ const MASK_COLOR := Color.RED
|
||||
var meshtool := MeshDataTool.new()
|
||||
|
||||
var _polyline_buffer: Array[Vector2] = []
|
||||
var _continuous := false
|
||||
var _painted_this_frame := false
|
||||
var _continued_paint_this_frame := false
|
||||
|
||||
var _multiline_buffer := PackedVector2Array()
|
||||
var _multiline_width := 1.0
|
||||
|
||||
@onready var mesh_instance: MeshInstance3D = $MeshInstance3D
|
||||
@onready var mesh: ArrayMesh = mesh_instance.mesh
|
||||
@ -28,7 +30,7 @@ func _ready() -> void:
|
||||
meshtool.create_from_surface(mesh, 0)
|
||||
|
||||
|
||||
## Transform cartesian coordinates to barycentric wrt the given triangle
|
||||
## Transform cartesian coordinates to barycentric wrt the given triangle.
|
||||
func _barycentric(p: Vector3, a: Vector3, b: Vector3, c: Vector3) -> Vector3:
|
||||
var v0 := b - a
|
||||
var v1 := c - a
|
||||
@ -45,11 +47,15 @@ func _barycentric(p: Vector3, a: Vector3, b: Vector3, c: Vector3) -> Vector3:
|
||||
return Vector3(u, v, w)
|
||||
|
||||
|
||||
## Is the given point on the planar triangle defined by v1, v2, and v3?
|
||||
func _is_in_triangle(point: Vector3, v1: Vector3, v2: Vector3, v3: Vector3) -> bool:
|
||||
var bc := _barycentric(point, v1, v2, v3)
|
||||
return (bc.x > 0 and bc.x < 1) and (bc.y > 0 and bc.y < 1) and (bc.z > 0 and bc.z < 1)
|
||||
|
||||
|
||||
## Get the index of the mesh face on which the given point+normal lies.
|
||||
##
|
||||
## Returns -1 if the given point+normal does not lie on a mesh face within tolerance.
|
||||
func _get_face(point: Vector3, normal: Vector3) -> int:
|
||||
for i in range(meshtool.get_face_count()):
|
||||
if meshtool.get_face_normal(i).distance_squared_to(normal) > FACE_EPSILON:
|
||||
@ -63,6 +69,9 @@ func _get_face(point: Vector3, normal: Vector3) -> int:
|
||||
return -1
|
||||
|
||||
|
||||
## UV-unwrap a point+normal in 3D space onto the UV space of this mesh.
|
||||
##
|
||||
## Returns Vector2.INF if the given point+normal does not lie on this mesh within tolerance.
|
||||
func _get_uv(point: Vector3, normal: Vector3) -> Vector2:
|
||||
var face := _get_face(point, normal)
|
||||
if face < 0:
|
||||
@ -80,8 +89,27 @@ func _get_uv(point: Vector3, normal: Vector3) -> Vector2:
|
||||
return (uv1 * bc.x) + (uv2 * bc.y) + (uv3 * bc.z)
|
||||
|
||||
|
||||
## UV-unwrap a point+normal in 3D space onto the pixel space of this mesh's texture.
|
||||
##
|
||||
## Returns Vector2.INF if the given point+normal does not lie on this mesh within tolerance.
|
||||
func _get_px(point: Vector3, normal: Vector3) -> Vector2:
|
||||
return _get_uv(point * global_transform, normal * global_basis) * mask_control.size
|
||||
|
||||
|
||||
## Paint a dot on the gunk mask.
|
||||
func paint_dot(point: Vector3, normal: Vector3, radius: float) -> void:
|
||||
var px := _get_px(point, normal)
|
||||
if px == Vector2.INF:
|
||||
return
|
||||
|
||||
mask_control.queue_draw(
|
||||
func() -> void: mask_control.draw_circle(px, radius, MASK_COLOR, true, -1, true)
|
||||
)
|
||||
|
||||
|
||||
## Paint a continuous line on the gunk mask if called on successive frames.
|
||||
func paint_continuous(point: Vector3, normal: Vector3, width: float) -> void:
|
||||
var px := _get_uv(point * global_transform, normal * global_basis) * mask_control.size
|
||||
var px := _get_px(point, normal)
|
||||
if _polyline_buffer and px.distance_to(_polyline_buffer[0]) <= CONTINUITY_LIMIT:
|
||||
_polyline_buffer.push_front(px)
|
||||
if len(_polyline_buffer) > BUFFER_LIMIT:
|
||||
@ -95,10 +123,34 @@ func paint_continuous(point: Vector3, normal: Vector3, width: float) -> void:
|
||||
mask_control.queue_draw(
|
||||
func() -> void: mask_control.draw_circle(px, width, MASK_COLOR, true, -1, true)
|
||||
)
|
||||
_painted_this_frame = true
|
||||
_continued_paint_this_frame = true
|
||||
|
||||
|
||||
## Add a segment to the multiline to paint this frame.
|
||||
func add_to_multiline(
|
||||
point_a: Vector3, normal_a: Vector3, point_b: Vector3, normal_b: Vector3, width: float
|
||||
) -> void:
|
||||
var px_a := _get_px(point_a, normal_a)
|
||||
var px_b := _get_px(point_b, normal_b)
|
||||
if px_a == Vector2.INF or px_b == Vector2.INF or px_a.distance_to(px_b) > CONTINUITY_LIMIT:
|
||||
return
|
||||
_multiline_buffer.append(px_a)
|
||||
_multiline_buffer.append(px_b)
|
||||
_multiline_width = width
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not _painted_this_frame:
|
||||
# If paint_continuous wasn't called last frame, stop the current polyline.
|
||||
if not _continued_paint_this_frame:
|
||||
_polyline_buffer = []
|
||||
_painted_this_frame = false
|
||||
_continued_paint_this_frame = false
|
||||
|
||||
# If we've buffered a multiline in the last frame, draw it & clear the buffer.
|
||||
# The width of the multiline will just be the width of the last call.
|
||||
if _multiline_buffer:
|
||||
var points := _multiline_buffer.duplicate()
|
||||
var width := _multiline_width
|
||||
mask_control.queue_draw(
|
||||
func() -> void: mask_control.draw_multiline(points, MASK_COLOR, width, true)
|
||||
)
|
||||
_multiline_buffer = PackedVector2Array()
|
||||
|
Loading…
x
Reference in New Issue
Block a user