yes, they canjj st!
This commit is contained in:
parent
6b92473eeb
commit
a6421235f8
22 changed files with 510 additions and 14 deletions
12
systems/bullets/behaviors/bullet_behavior.gd
Normal file
12
systems/bullets/behaviors/bullet_behavior.gd
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@abstract
|
||||
class_name BulletBehavior
|
||||
extends Resource
|
||||
|
||||
|
||||
## Called when a bullet is spawned with this behavior in order to set up
|
||||
## behavior-specific state.
|
||||
@warning_ignore("unused_parameter")
|
||||
func init_bullet(bullet: Bullet) -> void: pass
|
||||
|
||||
## Called to process a tick of a bullet's movement.
|
||||
@abstract func process_bullet(bullet: Bullet, delta: float) -> void
|
||||
1
systems/bullets/behaviors/bullet_behavior.gd.uid
Normal file
1
systems/bullets/behaviors/bullet_behavior.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://djcajenyac4sd
|
||||
15
systems/bullets/behaviors/simple_linear_behavior.gd
Normal file
15
systems/bullets/behaviors/simple_linear_behavior.gd
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class_name SimpleLinearBehavior
|
||||
extends BulletBehavior
|
||||
## Makes bullets move in [member Bullet.direction], potentially accelerating.
|
||||
|
||||
|
||||
## Initial speed of the bullet when it is spawned.
|
||||
@export_custom(0, "suffix:px/s") var initial_speed: float = 0.0
|
||||
|
||||
## Rate at which the bullet will speed up.
|
||||
@export_custom(0, "suffix:px/s²") var acceleration: float = 0.0
|
||||
|
||||
|
||||
func process_bullet(bullet: Bullet, delta: float) -> void:
|
||||
var speed = initial_speed + acceleration * bullet.time_elapsed
|
||||
bullet.position += bullet.direction * speed * delta
|
||||
1
systems/bullets/behaviors/simple_linear_behavior.gd.uid
Normal file
1
systems/bullets/behaviors/simple_linear_behavior.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dntp60my5f65m
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
@tool
|
||||
@icon("bullet.svg")
|
||||
class_name Bullet
|
||||
extends Area2D
|
||||
|
||||
|
||||
static var _hitbox_shapes: Dictionary[Vector2i, RectangleShape2D] = {}
|
||||
## The number of bullets to allocate at startup.
|
||||
const INITIAL_ALLOCATED_BULLETS: int = 2000
|
||||
|
||||
|
||||
@export var texture: Texture2D:
|
||||
## Texture to draw for the bullet.
|
||||
@export var texture: Texture2D = null:
|
||||
set(value):
|
||||
texture = value
|
||||
queue_redraw()
|
||||
_update_visibility_notifier()
|
||||
|
||||
@export var hitbox_size: Vector2i:
|
||||
## Size of the bullet's collision box.
|
||||
@export var hitbox_size: Vector2i = Vector2i.ZERO:
|
||||
set(value):
|
||||
hitbox_size = value
|
||||
if not _hitbox_shapes.has(hitbox_size):
|
||||
|
|
@ -20,18 +25,100 @@ static var _hitbox_shapes: Dictionary[Vector2i, RectangleShape2D] = {}
|
|||
_hitbox_shapes[hitbox_size] = new_shape
|
||||
_hitbox.shape = _hitbox_shapes[hitbox_size]
|
||||
|
||||
## The direction that the bullet is travelling.
|
||||
@export_custom(0, "direction") var direction: Vector2 = Vector2.RIGHT
|
||||
|
||||
## If [code]true[/code], the bullet will always rotate to face toward [member direction].
|
||||
@export var face_direction: bool = false
|
||||
|
||||
|
||||
## The amount of time in seconds that the bullet has existed.
|
||||
var time_elapsed: float = 0.0
|
||||
|
||||
|
||||
static var _cached_bullets: Array[Bullet] = []
|
||||
static var _hitbox_shapes: Dictionary[Vector2i, RectangleShape2D] = {}
|
||||
|
||||
|
||||
var _hitbox: CollisionShape2D = CollisionShape2D.new()
|
||||
|
||||
|
||||
## Returns a new [Bullet], which may be sourced from the cached bullets.
|
||||
@warning_ignore("shadowed_variable")
|
||||
static func create(
|
||||
texture: Texture2D = null,
|
||||
hitbox_size: Vector2i = Vector2i.ZERO,
|
||||
direction: Vector2 = Vector2.RIGHT,
|
||||
face_direction: bool = false,
|
||||
) -> Bullet:
|
||||
var bullet: Bullet = _cached_bullets.pop_back()
|
||||
if not bullet:
|
||||
bullet = Bullet.new()
|
||||
|
||||
bullet.texture = texture
|
||||
bullet.hitbox_size = hitbox_size
|
||||
bullet.direction = direction
|
||||
bullet.face_direction = face_direction
|
||||
bullet.time_elapsed = 0.0
|
||||
|
||||
return bullet
|
||||
|
||||
|
||||
## Removes the bullet from the scene tree and returns it to the bullet cache to be
|
||||
## re-used later.
|
||||
func recycle() -> void:
|
||||
get_parent().remove_child(self)
|
||||
_cached_bullets.append(self)
|
||||
|
||||
|
||||
static func _static_init() -> void:
|
||||
for _i in INITIAL_ALLOCATED_BULLETS:
|
||||
_cached_bullets.append(Bullet.new())
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
monitoring = false
|
||||
add_to_group(&"bullets")
|
||||
_hitbox.debug_color.a = 0.0
|
||||
add_child(_hitbox)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_update_visibility_notifier()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
time_elapsed += delta
|
||||
|
||||
if face_direction:
|
||||
rotation = direction.angle()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
draw_texture(texture, -texture.get_size() * 0.5)
|
||||
if texture:
|
||||
draw_texture(texture, -texture.get_size() * 0.5)
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
return []
|
||||
|
||||
|
||||
# sets the canvas item up to notify when it leaves the screen
|
||||
# this essentially mimics `VisibleOnScreenNotifier` without an additional node
|
||||
func _update_visibility_notifier() -> void:
|
||||
if not texture or not is_inside_tree() or Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
# the visibility rect is set to twice the size of the texture to add a little margin
|
||||
# (func(): pass) is the cleanest way i could think of to have a callback that does nothing
|
||||
RenderingServer.canvas_item_set_visibility_notifier(
|
||||
get_canvas_item(), true,
|
||||
Rect2(-texture.get_size(), texture.get_size() * 2.0),
|
||||
(func(): pass), _on_screen_exited
|
||||
)
|
||||
|
||||
|
||||
# called when the bullet leaves the screen.
|
||||
func _on_screen_exited() -> void:
|
||||
recycle()
|
||||
|
|
|
|||
82
systems/bullets/bullet.svg
Normal file
82
systems/bullets/bullet.svg
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="bullet.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="10.995366"
|
||||
inkscape:cx="8.7309506"
|
||||
inkscape:cy="10.049688"
|
||||
inkscape:window-width="1358"
|
||||
inkscape:window-height="742"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:3.78"
|
||||
d="M 1.3983532,8.3266862 6.414829,13.926473 7.737001,12.526527 2.7594126,7.160064 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:3.78"
|
||||
d="M 3.4711505,6.74088 8.3320767,11.951793 13.465215,7.2075289 C 14.904049,5.69092 16.498433,3.3965627 14.554063,1.5299671 13.270778,-0.10330412 11.326407,0.36334478 9.1098249,2.0743908 Z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:3.78"
|
||||
d="M 3.2131098,11.837773 1.056944,14.71266 4.1432206,12.810161 Z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:3.78"
|
||||
d="M 4.8196647,13.40205 2.5789434,15.219994 5.4538311,14.247606 Z"
|
||||
id="path6" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:3.78"
|
||||
d="M 1.4797216,10.019829 0.42277761,12.979273 2.3252768,10.992217 Z"
|
||||
id="path7"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
43
systems/bullets/bullet.svg.import
Normal file
43
systems/bullets/bullet.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://73kflfycjlft"
|
||||
path="res://.godot/imported/bullet.svg-bd5de27a67ea13ec7922f62f013cca2b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://systems/bullets/bullet.svg"
|
||||
dest_files=["res://.godot/imported/bullet.svg-bd5de27a67ea13ec7922f62f013cca2b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
16
systems/bullets/bullet_preset.gd
Normal file
16
systems/bullets/bullet_preset.gd
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
class_name BulletPreset
|
||||
extends Resource
|
||||
|
||||
@export var behavior: BulletBehavior = null
|
||||
|
||||
@export var pattern: BulletSpawnPattern = null
|
||||
|
||||
@export var textures: Array[Texture2D] = []
|
||||
|
||||
@export var hitbox_size: Vector2i = Vector2i.ZERO
|
||||
|
||||
@export var face_direction: bool = false
|
||||
|
||||
@export var rounds: int = 1
|
||||
|
||||
@export var round_delay: float = 0.0
|
||||
1
systems/bullets/bullet_preset.gd.uid
Normal file
1
systems/bullets/bullet_preset.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://vus1a0flwtnm
|
||||
31
systems/bullets/bullet_set.gd
Normal file
31
systems/bullets/bullet_set.gd
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
class_name BulletSet
|
||||
extends Node2D
|
||||
|
||||
|
||||
@export var preset: BulletPreset
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for _n in preset.rounds:
|
||||
preset.pattern.spawn_bullets(self, preset)
|
||||
await get_tree().create_timer(preset.round_delay, false, true).timeout
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
for bullet in get_children():
|
||||
if bullet is Bullet:
|
||||
preset.behavior.process_bullet(bullet, delta)
|
||||
else:
|
||||
push_error("BulletSet does not support having non-bullet children. Removing child: ", bullet)
|
||||
bullet.queue_free()
|
||||
|
||||
|
||||
func add_bullet(bullet: Bullet) -> void:
|
||||
preset.behavior.init_bullet(bullet)
|
||||
add_child(bullet)
|
||||
|
||||
|
||||
func add_bullets(new_bullets: Array[Bullet]) -> void:
|
||||
for bullet in new_bullets:
|
||||
preset.behavior.init_bullet(bullet)
|
||||
add_child(bullet)
|
||||
1
systems/bullets/bullet_set.gd.uid
Normal file
1
systems/bullets/bullet_set.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cj2fj7snls8aa
|
||||
6
systems/bullets/spawn_patterns/bullet_spawn_pattern.gd
Normal file
6
systems/bullets/spawn_patterns/bullet_spawn_pattern.gd
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@abstract
|
||||
class_name BulletSpawnPattern
|
||||
extends Resource
|
||||
|
||||
|
||||
@abstract func spawn_bullets(bullet_set: BulletSet, preset: BulletPreset) -> void
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://bhy0mkwfsi5j8
|
||||
21
systems/bullets/spawn_patterns/ring_pattern.gd
Normal file
21
systems/bullets/spawn_patterns/ring_pattern.gd
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
class_name RingPattern
|
||||
extends BulletSpawnPattern
|
||||
|
||||
|
||||
@export var bullet_count: int
|
||||
@export var distance_offset: float
|
||||
@export_custom(0, "radians_as_degrees") var angle_offset: float
|
||||
@export_custom(0, "radians_as_degrees") var direction_rotation: float
|
||||
|
||||
|
||||
func spawn_bullets(bullet_set: BulletSet, preset: BulletPreset) -> void:
|
||||
for i in bullet_count:
|
||||
var angle = (float(i) / float(bullet_count)) * TAU + angle_offset
|
||||
var bullet = Bullet.create(
|
||||
preset.textures.pick_random(),
|
||||
preset.hitbox_size,
|
||||
Vector2.from_angle(angle + direction_rotation),
|
||||
preset.face_direction
|
||||
)
|
||||
bullet.position = Vector2.from_angle(angle) * distance_offset
|
||||
bullet_set.add_bullet(bullet)
|
||||
1
systems/bullets/spawn_patterns/ring_pattern.gd.uid
Normal file
1
systems/bullets/spawn_patterns/ring_pattern.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dtuc6qerbfset
|
||||
Loading…
Add table
Add a link
Reference in a new issue