Initial commit
This commit is contained in:
commit
3b96451047
71 changed files with 2302 additions and 0 deletions
174
addons/walkabout/characters/character.gd
Normal file
174
addons/walkabout/characters/character.gd
Normal file
|
@ -0,0 +1,174 @@
|
|||
@tool
|
||||
@icon("character.svg")
|
||||
class_name WBCharacter
|
||||
extends CharacterBody2D
|
||||
## A character that can be controlled by events and behaviors and moves on a grid.
|
||||
|
||||
|
||||
## Emitted when the character begins moving.
|
||||
signal move_started()
|
||||
## Emitted when the character reaches its target position.
|
||||
signal move_finished()
|
||||
|
||||
|
||||
enum Dir {LEFT, RIGHT, UP, DOWN}
|
||||
|
||||
|
||||
const DIR_VECTORS: Dictionary[Dir, Vector2] = {
|
||||
Dir.LEFT: Vector2.LEFT,
|
||||
Dir.RIGHT: Vector2.RIGHT,
|
||||
Dir.UP: Vector2.UP,
|
||||
Dir.DOWN: Vector2.DOWN,
|
||||
}
|
||||
|
||||
|
||||
const DIR_ANIM_SUFFIXES: Dictionary[Dir, StringName] = {
|
||||
Dir.LEFT: &"_left",
|
||||
Dir.RIGHT: &"_right",
|
||||
Dir.UP: &"_up",
|
||||
Dir.DOWN: &"_down",
|
||||
}
|
||||
|
||||
|
||||
## Size of the grid the character is restricted to.
|
||||
@export var tile_size: float = 16.0
|
||||
## Speed the character walks at.
|
||||
@export var walk_speed: float = 4.0
|
||||
## Speed the character runs at.
|
||||
@export var run_speed: float = 8.0
|
||||
|
||||
## Direction the character is facing.
|
||||
@export var facing: Dir = Dir.DOWN
|
||||
|
||||
## Animation library for the character. [br]
|
||||
## At a minimum, the [code]idle_*[/code] animations are required. [br]
|
||||
## The following animations are used by default behavior: [br]
|
||||
## [code]idle_[left,right,up,down][/code] [br]
|
||||
## [code]walk_[left,right,up,down][/code] [br]
|
||||
## [code]run_[left,right,up,down][/code] [br]
|
||||
## [code]run_*[/code] will fallback to [code]walk_*[/code],
|
||||
## which will fallback to [code]idle_*[/code]. [br]
|
||||
## Addition custom animations may be provided to play on demand.
|
||||
@export var animations: SpriteFrames:
|
||||
set(value):
|
||||
animations = value
|
||||
sprite.sprite_frames = animations
|
||||
|
||||
## Texture drawing offset of the animated sprite.
|
||||
@export var sprite_offset: Vector2 = Vector2.ZERO:
|
||||
set(value):
|
||||
sprite_offset = value
|
||||
sprite.offset = sprite_offset
|
||||
|
||||
|
||||
## True when the character is moving.
|
||||
var moving: bool = false
|
||||
## Whether the character is running.
|
||||
var running: bool = false
|
||||
|
||||
## Tile position of the character on the grid.
|
||||
var tile_position: Vector2i:
|
||||
get():
|
||||
return pos_to_tile(global_position)
|
||||
|
||||
|
||||
var sprite: AnimatedSprite2D
|
||||
var _next_pos: Vector2
|
||||
var _playing_custom_animation: bool = false
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
sprite = AnimatedSprite2D.new()
|
||||
sprite.sprite_frames = animations
|
||||
add_child(sprite)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
global_position = closest_tile_center(global_position)
|
||||
for child in get_children():
|
||||
if child is CollisionShape2D or child is CollisionPolygon2D:
|
||||
return
|
||||
var col_shape := CollisionShape2D.new()
|
||||
col_shape.shape = RectangleShape2D.new()
|
||||
col_shape.shape.size = Vector2(tile_size - 2.0, tile_size - 2.0)
|
||||
add_child(col_shape)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if moving:
|
||||
var move_delta := (run_speed if running else walk_speed) * tile_size * delta
|
||||
global_position = global_position.move_toward(_next_pos, move_delta)
|
||||
if global_position == _next_pos:
|
||||
moving = false
|
||||
move_finished.emit()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if moving:
|
||||
_playing_custom_animation = false
|
||||
var anims: Array[StringName] = [
|
||||
&"walk" + DIR_ANIM_SUFFIXES[facing],
|
||||
&"idle" + DIR_ANIM_SUFFIXES[facing]
|
||||
]
|
||||
if running:
|
||||
anims.push_front(&"run" + DIR_ANIM_SUFFIXES[facing])
|
||||
_try_animations(anims)
|
||||
elif not _playing_custom_animation:
|
||||
_try_animations([&"idle" + DIR_ANIM_SUFFIXES[facing]])
|
||||
|
||||
|
||||
## Makes the character move one tile in the given direction. [br]
|
||||
## If [param ignore_collision] is true, the character will not perform collision checks.
|
||||
func start_move(dir: Dir, ignore_collision: bool = false) -> bool:
|
||||
if moving:
|
||||
return false
|
||||
|
||||
facing = dir
|
||||
|
||||
_next_pos = global_position + DIR_VECTORS[dir] * tile_size
|
||||
var col := move_and_collide(_next_pos - global_position, true)
|
||||
if col and not ignore_collision:
|
||||
return false
|
||||
|
||||
moving = true
|
||||
move_started.emit()
|
||||
return true
|
||||
|
||||
|
||||
## Plays a given custom animation from the animation set. [br]
|
||||
## If [param reset_after] is [constant true], the animation will return to
|
||||
## the default idle animation after it finishes.
|
||||
func play_custom_animation(anim: StringName, reset_after: bool = false) -> void:
|
||||
_try_animations([anim])
|
||||
_playing_custom_animation = true
|
||||
|
||||
if reset_after and not animations.get_animation_loop(anim):
|
||||
await sprite.animation_finished
|
||||
_playing_custom_animation = false
|
||||
|
||||
## Stops playing custom animation if one is currently playing.
|
||||
func end_custom_animation() -> void:
|
||||
_playing_custom_animation = false
|
||||
|
||||
|
||||
## Returns the closest tile center position to a given position in global coordinates.
|
||||
func closest_tile_center(pos: Vector2) -> Vector2:
|
||||
var tile := pos - Vector2(tile_size, tile_size) * 0.5
|
||||
tile = tile.snappedf(tile_size)
|
||||
tile += Vector2(tile_size, tile_size) * 0.5
|
||||
return tile
|
||||
|
||||
## Returns the tile coordinates of a given position in global coordinates.
|
||||
func pos_to_tile(pos: Vector2) -> Vector2i:
|
||||
return Vector2i((global_position / Vector2(tile_size, tile_size)).floor())
|
||||
|
||||
## Returns the center position of a given tile in global coordinates.
|
||||
func tile_center_pos(tile: Vector2i) -> Vector2:
|
||||
return (Vector2(tile) * Vector2(tile_size, tile_size)) + (Vector2(tile_size, tile_size) * 0.5)
|
||||
|
||||
|
||||
func _try_animations(anims: Array[StringName]) -> void:
|
||||
for anim in anims:
|
||||
if animations.has_animation(anim):
|
||||
sprite.play(anim)
|
||||
return
|
1
addons/walkabout/characters/character.gd.uid
Normal file
1
addons/walkabout/characters/character.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dx8lxwwkyrjin
|
63
addons/walkabout/characters/character.svg
Normal file
63
addons/walkabout/characters/character.svg
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="character.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
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="defs1">
|
||||
<linearGradient
|
||||
id="a"
|
||||
x2="0"
|
||||
y1="2"
|
||||
y2="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#ff8dbc"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset=".4"
|
||||
stop-color="#7260ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset=".6"
|
||||
stop-color="#7260ff"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#74c9ff"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="14.854978"
|
||||
inkscape:cx="0.67317502"
|
||||
inkscape:cy="10.434213"
|
||||
inkscape:window-width="1346"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="#8da5f3"
|
||||
d="m 6.4922281,1 a 1,1 0 0 0 -1,1 v 3 a 1,1 0 0 0 1,1 h 1 v 0.99 a 1,1 0 0 0 -0.316,0.062 l -2.051,0.684 -0.684,-2.051 a 1.0000706,1.0000706 0 0 0 -1.898,0.631 l 1,3 a 1,1 0 0 0 1.265,0.633 l 1.684,-0.56 v 0.61 c 0,0.041 0.019,0.076 0.024,0.116 l -4.579,3.052 a 1.0001245,1.0001245 0 1 0 1.11,1.664 l 5.056,-3.37 1.495,2.986 a 1,1 0 0 0 1.2100009,0.502 l 3,-1 a 1,1 0 1 0 -0.632,-1.897 l -2.178,0.725 -0.975001,-1.951 A 0.981,0.981 0 0 0 10.492229,9.999 V 9 h 1.382999 l 0.722,1.448 a 1.0006409,1.0006409 0 1 0 1.790001,-0.895 l -1,-2 A 1,1 0 0 0 12.492229,7 H 9.4922281 V 6 h 1.0000009 a 1,1 0 0 0 1,-1 V 2 a 1,1 0 0 0 -1,-1 z m 0,2 h 1 v 2 h -1 z"
|
||||
id="path1"
|
||||
style="fill:#20c997" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
37
addons/walkabout/characters/character.svg.import
Normal file
37
addons/walkabout/characters/character.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ufpt8ejikj1j"
|
||||
path="res://.godot/imported/character.svg-492c7305b4930c6092d7995fed4891a3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/walkabout/characters/character.svg"
|
||||
dest_files=["res://.godot/imported/character.svg-492c7305b4930c6092d7995fed4891a3.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
|
44
addons/walkabout/characters/character_behavior.gd
Normal file
44
addons/walkabout/characters/character_behavior.gd
Normal file
|
@ -0,0 +1,44 @@
|
|||
@tool
|
||||
@icon("character_behavior.svg")
|
||||
class_name WBCharacterBehavior
|
||||
extends Node
|
||||
## Controls/puppets a parent character.
|
||||
|
||||
|
||||
## Whether the behavior is currently active.
|
||||
@export var active: bool:
|
||||
set(value):
|
||||
var last_value := active
|
||||
active = value
|
||||
if active:
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
for child in get_parent().get_children():
|
||||
if child != self and child is WBCharacterBehavior:
|
||||
child.active = false
|
||||
if active:
|
||||
_activate()
|
||||
else:
|
||||
_deactivate()
|
||||
|
||||
|
||||
## The character being controlled.
|
||||
var character: WBCharacter:
|
||||
get():
|
||||
return get_parent() as WBCharacter
|
||||
|
||||
|
||||
## Enables the behavior.
|
||||
func enable() -> void:
|
||||
active = true
|
||||
|
||||
## Disables the behavior.
|
||||
func disable() -> void:
|
||||
active = false
|
||||
|
||||
|
||||
func _activate() -> void:
|
||||
pass
|
||||
|
||||
func _deactivate() -> void:
|
||||
pass
|
1
addons/walkabout/characters/character_behavior.gd.uid
Normal file
1
addons/walkabout/characters/character_behavior.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cgunv5ngogsky
|
66
addons/walkabout/characters/character_behavior.svg
Normal file
66
addons/walkabout/characters/character_behavior.svg
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="character_behavior.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
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="defs1">
|
||||
<linearGradient
|
||||
id="a"
|
||||
x2="0"
|
||||
y1="2"
|
||||
y2="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#ff8dbc"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset=".4"
|
||||
stop-color="#7260ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset=".6"
|
||||
stop-color="#7260ff"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#74c9ff"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="14.854978"
|
||||
inkscape:cx="0.67317502"
|
||||
inkscape:cy="10.434213"
|
||||
inkscape:window-width="1346"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#8da5f3"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2.28571"
|
||||
d="M 14.857145,14.857148 H 1.1428572 V 1.1428572 H 14.857145 Z"
|
||||
id="path1"
|
||||
style="stroke:#20c997;stroke-width:2;stroke-dasharray:none" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
37
addons/walkabout/characters/character_behavior.svg.import
Normal file
37
addons/walkabout/characters/character_behavior.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://lib4e4qnml8a"
|
||||
path="res://.godot/imported/character_behavior.svg-d7dbdf997daf0ba87cdd58e5db5e61e1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/walkabout/characters/character_behavior.svg"
|
||||
dest_files=["res://.godot/imported/character_behavior.svg-d7dbdf997daf0ba87cdd58e5db5e61e1.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
|
81
addons/walkabout/characters/path_behavior.gd
Normal file
81
addons/walkabout/characters/path_behavior.gd
Normal file
|
@ -0,0 +1,81 @@
|
|||
@tool
|
||||
@icon("path_behavior.svg")
|
||||
class_name WBPathBehavior
|
||||
extends WBCharacterBehavior
|
||||
## A behavior that makes a character walk along a specified path,
|
||||
## looping when they reach the end.
|
||||
|
||||
|
||||
## List of points to walk to.
|
||||
@export var points: Array[Vector2i]
|
||||
## Whether the character should be running.
|
||||
@export var run: bool = false
|
||||
|
||||
|
||||
var _current_point: int
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
add_child(DebugView.new(self))
|
||||
|
||||
|
||||
func _activate() -> void:
|
||||
_do_move()
|
||||
|
||||
|
||||
func _do_move() -> void:
|
||||
if not active:
|
||||
return
|
||||
|
||||
if character.tile_position == points[_current_point]:
|
||||
_current_point = posmod(_current_point + 1, points.size())
|
||||
|
||||
var point := points[_current_point]
|
||||
if character.tile_position.x < point.x:
|
||||
character.start_move(WBCharacter.Dir.RIGHT)
|
||||
elif character.tile_position.x > point.x:
|
||||
character.start_move(WBCharacter.Dir.LEFT)
|
||||
elif character.tile_position.y < point.y:
|
||||
character.start_move(WBCharacter.Dir.DOWN)
|
||||
elif character.tile_position.y > point.y:
|
||||
character.start_move(WBCharacter.Dir.UP)
|
||||
|
||||
if character.moving:
|
||||
await character.move_finished
|
||||
|
||||
_do_move()
|
||||
|
||||
|
||||
class DebugView extends Node2D:
|
||||
var behavior: WBPathBehavior
|
||||
|
||||
func _init(p_behavior: WBPathBehavior) -> void:
|
||||
behavior = p_behavior
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if Engine.get_frames_drawn() % 60 == 0:
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
if not behavior.character:
|
||||
return
|
||||
|
||||
var color := Color.MEDIUM_PURPLE
|
||||
var width := -1.0
|
||||
var selection := EditorInterface.get_selection()
|
||||
if behavior in selection.get_selected_nodes():
|
||||
color = Color.PURPLE
|
||||
width = 2.0
|
||||
|
||||
var last_point := to_local(behavior.character.closest_tile_center(behavior.character.global_position))
|
||||
for point in behavior.points + [behavior.points[0]]:
|
||||
var current_point := to_local(behavior.character.tile_center_pos(point))
|
||||
draw_line(last_point, Vector2(current_point.x, last_point.y), color, width)
|
||||
draw_line(Vector2(current_point.x, last_point.y), current_point, color, width)
|
||||
var arrow_dir := current_point.direction_to(Vector2(current_point.x, last_point.y))
|
||||
if arrow_dir.is_zero_approx():
|
||||
arrow_dir = current_point.direction_to(last_point)
|
||||
draw_line(current_point, current_point + arrow_dir.rotated(deg_to_rad(45.0)) * 8.0, color, width)
|
||||
draw_line(current_point, current_point + arrow_dir.rotated(deg_to_rad(-45.0)) * 8.0, color, width)
|
||||
last_point = current_point
|
1
addons/walkabout/characters/path_behavior.gd.uid
Normal file
1
addons/walkabout/characters/path_behavior.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://deanlc8wbefgm
|
70
addons/walkabout/characters/path_behavior.svg
Normal file
70
addons/walkabout/characters/path_behavior.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="path_behavior.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
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="defs1">
|
||||
<linearGradient
|
||||
id="a"
|
||||
x2="0"
|
||||
y1="2"
|
||||
y2="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#ff8dbc"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset=".4"
|
||||
stop-color="#7260ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset=".6"
|
||||
stop-color="#7260ff"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#74c9ff"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="14.854978"
|
||||
inkscape:cx="0.67317502"
|
||||
inkscape:cy="10.434213"
|
||||
inkscape:window-width="1346"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#8da5f3"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2.28571"
|
||||
d="M 14.857145,14.857148 H 1.1428572 V 1.1428572 H 14.857145 Z"
|
||||
id="path1"
|
||||
style="stroke:#20c997;stroke-width:2;stroke-dasharray:none" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#20c997;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 4.2410027,11.511293 4.1736852,4.3083202 8.5493228,4.7122252 8.2127353,7.7415128 11.915198,8.2127353 11.242023,12.11715 Z"
|
||||
id="path8" />
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
37
addons/walkabout/characters/path_behavior.svg.import
Normal file
37
addons/walkabout/characters/path_behavior.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3los038dn424"
|
||||
path="res://.godot/imported/path_behavior.svg-92b1066b81b0693c0689fa68366f00d0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/walkabout/characters/path_behavior.svg"
|
||||
dest_files=["res://.godot/imported/path_behavior.svg-92b1066b81b0693c0689fa68366f00d0.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
|
98
addons/walkabout/characters/player_character.gd
Normal file
98
addons/walkabout/characters/player_character.gd
Normal file
|
@ -0,0 +1,98 @@
|
|||
@tool
|
||||
@icon("player_character.svg")
|
||||
class_name WBPlayerCharacter
|
||||
extends WBCharacter
|
||||
## A character controlled directly by the player.
|
||||
|
||||
|
||||
## Whether the character will react to player input.
|
||||
@export var controllable: bool = true
|
||||
## Time to wait after turning before the character will start moving.
|
||||
@export var turn_walk_time: float = 0.25
|
||||
## Collision mask for interactive objects.
|
||||
## If a collision is detected, [method interact] will be called on the target object if it exists.
|
||||
@export_flags_2d_physics var interact_mask: int:
|
||||
set(value):
|
||||
interact_mask = value
|
||||
_interact_raycast.collision_mask = interact_mask
|
||||
|
||||
@export_group("Input Actions", "input_")
|
||||
@export var input_left: StringName = &"ui_left"
|
||||
@export var input_right: StringName = &"ui_right"
|
||||
@export var input_up: StringName = &"ui_up"
|
||||
@export var input_down: StringName = &"ui_down"
|
||||
@export var input_interact: StringName = &"ui_accept"
|
||||
@export var input_run: StringName = &"ui_cancel"
|
||||
|
||||
|
||||
var _interact_raycast: RayCast2D
|
||||
var _turn_cooldown: float = 0.0
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
super._init()
|
||||
|
||||
move_finished.connect(_check_move_input)
|
||||
|
||||
_interact_raycast = RayCast2D.new()
|
||||
_interact_raycast.enabled = false
|
||||
_interact_raycast.collide_with_areas = true
|
||||
_interact_raycast.hit_from_inside = true
|
||||
_interact_raycast.add_exception(self)
|
||||
add_child(_interact_raycast)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
_turn_cooldown -= delta
|
||||
running = Input.is_action_pressed(input_run)
|
||||
if not moving:
|
||||
_check_move_input(true)
|
||||
super._physics_process(delta)
|
||||
|
||||
|
||||
## Enables player control of the character.
|
||||
func enable_control() -> void:
|
||||
controllable = true
|
||||
|
||||
## Disables player control of the character.
|
||||
func disable_control() -> void:
|
||||
controllable = false
|
||||
|
||||
|
||||
func _check_move_input(check_turn_cooldown: bool = false) -> void:
|
||||
if not controllable:
|
||||
return
|
||||
|
||||
if Input.is_action_pressed(input_left):
|
||||
_try_move(Dir.LEFT, check_turn_cooldown)
|
||||
elif Input.is_action_pressed(input_right):
|
||||
_try_move(Dir.RIGHT, check_turn_cooldown)
|
||||
elif Input.is_action_pressed(input_up):
|
||||
_try_move(Dir.UP, check_turn_cooldown)
|
||||
elif Input.is_action_pressed(input_down):
|
||||
_try_move(Dir.DOWN, check_turn_cooldown)
|
||||
elif Input.is_action_just_pressed(input_interact):
|
||||
_try_interact()
|
||||
|
||||
|
||||
func _try_move(dir: Dir, check_turn_cooldown: bool) -> void:
|
||||
if facing != dir:
|
||||
_turn_cooldown = turn_walk_time
|
||||
if running:
|
||||
_turn_cooldown *= 0.5
|
||||
facing = dir
|
||||
if check_turn_cooldown and _turn_cooldown >= 0.0:
|
||||
return
|
||||
start_move(dir)
|
||||
|
||||
|
||||
func _try_interact() -> void:
|
||||
_interact_raycast.target_position = DIR_VECTORS[facing] * tile_size
|
||||
_interact_raycast.force_raycast_update()
|
||||
if _interact_raycast.is_colliding():
|
||||
var interactive := _interact_raycast.get_collider()
|
||||
if interactive.has_method(&"interact"):
|
||||
interactive.interact()
|
1
addons/walkabout/characters/player_character.gd.uid
Normal file
1
addons/walkabout/characters/player_character.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cbc7nngunyym7
|
68
addons/walkabout/characters/player_character.svg
Normal file
68
addons/walkabout/characters/player_character.svg
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="player_character.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
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="defs1">
|
||||
<linearGradient
|
||||
id="a"
|
||||
x2="0"
|
||||
y1="2"
|
||||
y2="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#ff8dbc"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset=".4"
|
||||
stop-color="#7260ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset=".6"
|
||||
stop-color="#7260ff"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#74c9ff"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="25.753231"
|
||||
inkscape:cx="4.5042892"
|
||||
inkscape:cy="6.6787737"
|
||||
inkscape:window-width="1346"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="#8da5f3"
|
||||
d="m 7.4922281,6.99 c -0.1079911,0.00346 -0.2147078,0.024398 -0.316,0.062 l -2.051,0.684 -0.684,-2.051 C 4.0205616,4.4196672 2.1225616,5.0506672 2.5432281,6.316 l 1,3 c 0.1744187,0.5242077 0.7408721,0.807658 1.265,0.633 l 1.684,-0.56 v 0.61 c 0,0.041 0.019,0.076 0.024,0.116 l -4.579,3.052 c -1.10956307,0.739954 4.369e-4,2.403954 1.11,1.664 l 5.056,-3.37 1.495,2.986 c 0.2223325,0.444998 0.7379489,0.658915 1.2100009,0.502 l 3,-1 c 1.302584,-0.402102 0.651473,-2.356463 -0.632,-1.897 l -2.178,0.725 -0.975001,-1.951 c 0.288394,-0.176434 0.465617,-0.488934 0.469001,-0.827 V 9 h 1.382999 l 0.722,1.448 c 0.596513,1.193792 2.386514,0.298792 1.790001,-0.895 l -1,-2 C 13.217804,7.2139484 12.871255,6.9998234 12.492229,7 H 9.4922281 Z"
|
||||
id="path1"
|
||||
style="fill:#20c997"
|
||||
sodipodi:nodetypes="ccccccccsccccccccccccccccccc" />
|
||||
<path
|
||||
style="fill:#20c997;fill-opacity:1;stroke:none;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 8.7367679,7.0282444 7.843676,4.89259 5.1644006,4.0383283 7.843676,2.951086 9.1250687,0.46596095 9.78518,3.1064064 12.969247,3.8830079 9.6686898,4.8537599 9.7463498,3.1452364 7.882506,3.0287462 l 0.03883,1.7861836 1.7861837,0.07766 z"
|
||||
id="path8" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
37
addons/walkabout/characters/player_character.svg.import
Normal file
37
addons/walkabout/characters/player_character.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdwrfpa2xn5cf"
|
||||
path="res://.godot/imported/player_character.svg-b26ca4ad6a1901b6ebacace37c911113.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/walkabout/characters/player_character.svg"
|
||||
dest_files=["res://.godot/imported/player_character.svg-b26ca4ad6a1901b6ebacace37c911113.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
|
78
addons/walkabout/characters/wander_behavior.gd
Normal file
78
addons/walkabout/characters/wander_behavior.gd
Normal file
|
@ -0,0 +1,78 @@
|
|||
@tool
|
||||
@icon("wander_behavior.svg")
|
||||
class_name WBWanderBehavior
|
||||
extends WBCharacterBehavior
|
||||
## A behavior that makes a character randomly wander around within a specified area.
|
||||
|
||||
|
||||
## Rectangle enclosing the tiles the character is allowed to move to.
|
||||
@export var territory: Rect2i
|
||||
## Minimum distance to travel in one "burst".
|
||||
@export var min_distance: int
|
||||
## Maximum distance to travel in one "burst".
|
||||
@export var max_distance: int
|
||||
## Minimum time to idle after finishing a "burst".
|
||||
@export_custom(0, "suffix:s") var min_idle: float
|
||||
## Maximum time to idle after finishing a "burst".
|
||||
@export_custom(0, "suffix:s") var max_idle: float
|
||||
## Whether the character should run.
|
||||
@export var run: bool = false
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
add_child(DebugView.new(self))
|
||||
|
||||
|
||||
func _activate() -> void:
|
||||
if character:
|
||||
_do_move()
|
||||
|
||||
|
||||
func _do_move() -> void:
|
||||
if not active:
|
||||
return
|
||||
|
||||
var remaining_distance: int = randi_range(min_distance, max_distance)
|
||||
var dir: WBCharacter.Dir = WBCharacter.Dir.values().pick_random()
|
||||
|
||||
while remaining_distance > 0:
|
||||
while not territory.has_point(character.tile_position + Vector2i(WBCharacter.DIR_VECTORS[dir])):
|
||||
dir = WBCharacter.Dir.values().pick_random()
|
||||
character.running = run
|
||||
character.start_move(dir)
|
||||
if character.moving:
|
||||
await character.move_finished
|
||||
remaining_distance -= 1
|
||||
|
||||
await create_tween().tween_interval(randf_range(min_idle, max_idle)).finished
|
||||
_do_move()
|
||||
|
||||
|
||||
class DebugView extends Node2D:
|
||||
var behavior: WBWanderBehavior
|
||||
|
||||
func _init(p_behavior: WBWanderBehavior) -> void:
|
||||
behavior = p_behavior
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if Engine.get_frames_drawn() % 60 == 0:
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
if not behavior.character:
|
||||
return
|
||||
|
||||
var position := to_local(Vector2(behavior.territory.position) * behavior.character.tile_size)
|
||||
var size := Vector2(behavior.territory.size) * behavior.character.tile_size
|
||||
|
||||
var color := Color.MEDIUM_PURPLE
|
||||
var width := -1.0
|
||||
var selection := EditorInterface.get_selection()
|
||||
if behavior in selection.get_selected_nodes():
|
||||
color = Color.PURPLE
|
||||
width = 2.0
|
||||
draw_rect(
|
||||
Rect2(position, size),
|
||||
color, false, width
|
||||
)
|
1
addons/walkabout/characters/wander_behavior.gd.uid
Normal file
1
addons/walkabout/characters/wander_behavior.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://xcfljtjwi1vt
|
86
addons/walkabout/characters/wander_behavior.svg
Normal file
86
addons/walkabout/characters/wander_behavior.svg
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wander_behavior.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
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="defs1">
|
||||
<linearGradient
|
||||
id="a"
|
||||
x2="0"
|
||||
y1="2"
|
||||
y2="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#ff8dbc"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset=".4"
|
||||
stop-color="#7260ff"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset=".6"
|
||||
stop-color="#7260ff"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#74c9ff"
|
||||
id="stop4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="14.854978"
|
||||
inkscape:cx="0.67317502"
|
||||
inkscape:cy="10.434213"
|
||||
inkscape:window-width="1346"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#8da5f3"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2.28571"
|
||||
d="M 14.857145,14.857148 H 1.1428572 V 1.1428572 H 14.857145 Z"
|
||||
id="path1"
|
||||
style="stroke:#20c997;stroke-width:2;stroke-dasharray:none" />
|
||||
<path
|
||||
style="fill:#20c997;fill-opacity:1;stroke:#20c997;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 3.9276284,7.9330316 5.599787,7.0386211 5.5608995,8.8663295 Z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#20c997;fill-opacity:1;stroke:#20c997;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 11.501205,7.9790821 9.8290464,8.8734926 9.8679344,7.0457842 Z"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#20c997;fill-opacity:1;stroke:#20c997;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 7.7568856,4.1887121 8.6512961,5.8608707 6.8235877,5.8219827 Z"
|
||||
id="path7"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#20c997;fill-opacity:1;stroke:#20c997;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 7.5941729,11.762289 6.6997624,10.09013 8.5274708,10.129018 Z"
|
||||
id="path8"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
37
addons/walkabout/characters/wander_behavior.svg.import
Normal file
37
addons/walkabout/characters/wander_behavior.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dcb3i7xdowdte"
|
||||
path="res://.godot/imported/wander_behavior.svg-cec028e106c64cd15d62b2fb10cf549b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/walkabout/characters/wander_behavior.svg"
|
||||
dest_files=["res://.godot/imported/wander_behavior.svg-cec028e106c64cd15d62b2fb10cf549b.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
|
Loading…
Add table
Add a link
Reference in a new issue