Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ProjectileEffect base class and basic functionality #2

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions projectiles/double_laser.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class_name EffectDoubleLaser
extends ProjectileEffect

var Projectile : PackedScene = preload("res://units/projectile.tscn")
campenr marked this conversation as resolved.
Show resolved Hide resolved

@export var laser_offset = Vector2(0, -50)
const is_double_laser = true

func check_not_double_laser(effect: PackedScene) -> bool:
# TODO:: Filter on type? This implementation is Bad
if "is_double_laser" in effect:
return not effect.is_double_laser

# TODO:: Hacky? but const on the node does not work for packed scenes
return effect.resource_path != "res://projectiles/double_laser.tscn"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Non-blocking: can we type check the effect? I guess we're loading the scene, but we could probably type check the parent node of the scene to be able to check against a custom class_name? 🤔


func modify_creation(owner: Node, projectile_effects: Array, transform: Transform2D):
var p = Projectile.instantiate()
# TODO:: Stacking double-lasers? would mean we only want to filter 1 out of this list.
var non_recursive_effects = projectile_effects.filter(check_not_double_laser)
var doubled_transform = transform
# TODO:: Do we want to offset the laser, or do we want to angle out of the same point of origin?
doubled_transform.origin += laser_offset
p.spawn(owner, non_recursive_effects, doubled_transform)
6 changes: 6 additions & 0 deletions projectiles/double_laser.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dgp68vbl25onx"]

[ext_resource type="Script" path="res://projectiles/double_laser.gd" id="1_wqupk"]

[node name="DoubleLaser" type="Node"]
script = ExtResource("1_wqupk")
7 changes: 7 additions & 0 deletions projectiles/fast_laser.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://dj6jn0ia62yba"]

[ext_resource type="Script" path="res://projectiles/projectile_effect.gd" id="1_jlt11"]

[node name="FastLaser" type="Node"]
script = ExtResource("1_jlt11")
speed_modifier = 3
2 changes: 2 additions & 0 deletions projectiles/huge_laser.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class_name HugeLaser
extends ProjectileEffect
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍷 🧀

This is not actually needed with the current implementation.

It WILL be needed if we want to use the Node class type instead of a PackedScene for effects management. I.e. you can't do HugeLaser.new() unless you define the class_name in a script.
But if you load a PackedScene from the scene path, this is totally superfluous boilerplate.

7 changes: 7 additions & 0 deletions projectiles/huge_laser.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://i6l7jkbauvmf"]

[ext_resource type="Script" path="res://projectiles/huge_laser.gd" id="1_ouvu5"]

[node name="HugeLaser" type="Node"]
script = ExtResource("1_ouvu5")
scale_modifier = 10
71 changes: 71 additions & 0 deletions projectiles/projectile_effect.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
class_name ProjectileEffect
extends Node
# A ProjectileEffect is a set of post-processors meant to be run on every instance of a Projectile.
# This by itself does nothing but is meant to be extended by various types of projectile effect.
# Each effect will compound on the others. Each function will be called by the Projectile scene,
# in order of Effect Priority
# TODO:: Effect Priority as an enum? (to discourage "priority: 9999999")

# Each modifier is a combination of a double value and a modification type.
campenr marked this conversation as resolved.
Show resolved Hide resolved
# This is to indicate if a modification is additive or multiplicitive.
# The default for all modifiers is MULTIPLICITIVE : 1, ultimately resulting in no effect.
# (To subtract and divide, use negative values and decimals < 1)
enum MODIFICATION_TYPE {
ADD,
MULTIPLY
}
@export var effect_priority = -1
@export var speed_modifier_type = MODIFICATION_TYPE.MULTIPLY
@export var speed_modifier = 1
@export var damage_modifier_type = MODIFICATION_TYPE.MULTIPLY
@export var damage_modifier = 1
@export var scale_modifier_type = MODIFICATION_TYPE.MULTIPLY
@export var scale_modifier = 1


# Modify the logic that adds the projectile as a child to the scene.
# This can be used to change spawn locations, quantities, etc.
func modify_creation(owner: Node, projectile_effects: Array, transform: Transform2D):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍷 🧀

I'm pretty sure the resulting type here is "Packed Scene", which currently cannot be typed better

I flip-flopped between the actual node type (ProjectileEffect) and PackedScenes and some other ideas so many times that changing every type annotation was incredibly cumbersome, so I've opted to leave this as just Array until we flesh things out and settle on something that works and won't be changed.

There's also no support for Type Variables -- I figured having a Globals script that contains this type so we can just say var EffectType = Array[PackedScene] and use that so that making a change would be trivial, but can't find a way to do it with the current gdscript version.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Some interesting thoughts in this forum post on accessing more info about types when using PackedScene. A custom resource type is an interesting approach. Nothing for now though, just food for collective thought.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classic me. No link, and now I can't find it again 🤦

pass

func _calculate_vector_modification(
base_value: Vector2,
modifier_value: int,
modifier_type: MODIFICATION_TYPE
):
# TODO:: How does godot mutability work? we do NOT want to modify the original modifier
var local_modifier = base_value
if modifier_type == MODIFICATION_TYPE.MULTIPLY:
local_modifier *= modifier_value
elif modifier_type == MODIFICATION_TYPE.ADD:
local_modifier += modifier_value
return local_modifier

# Modify the physics logic of the projectile. This function will be called
# during the _physics_process.
# Uses exported variables by default, and should only be overridden if you're
# doing something really interesting.
func modify_physics(physics_vector: Vector2) -> Vector2:
return _calculate_vector_modification(physics_vector, speed_modifier, speed_modifier_type)


# Simple changes to the appearance using built-in features.
# By default, modifies the scale of the projectile.
func modify_appearance(scale: Vector2) -> Vector2:
return _calculate_vector_modification(owner.scale, scale_modifier, scale_modifier_type)


# To change the sprite itself, elements of the animation, or otherwise add effects
# Hard changes, rather than additions, will be subject to effect_priority.
func modify_animation():
pass


# Called when the node enters the scene tree for the first time.
func _enter_tree():
var projectile = get_owner()
if projectile:
# Projectile scenes are instantiated before being added to the tree
# This function will effectively be called twice, but some operations
# require the parent node to be present first.
projectile.scale_projectile(modify_appearance(projectile.scale))
6 changes: 6 additions & 0 deletions projectiles/projectile_effect.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bwxewmel1e8si"]

[ext_resource type="Script" path="res://projectiles/projectile_effect.gd" id="1_eosdr"]

[node name="ProjectileEffect" type="Node"]
script = ExtResource("1_eosdr")
3 changes: 3 additions & 0 deletions units/enemy.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ func _physics_process(delta):

func _on_visible_on_screen_notifier_2d_screen_exited():
queue_free()

func _on_area_entered(area):
queue_free()
4 changes: 3 additions & 1 deletion units/enemy.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=4 format=3 uid="uid://4fjgeuv7aun7"]
[gd_scene load_steps=4 format=3 uid="uid://bdsjg08y1h6yh"]

[ext_resource type="Script" path="res://units/enemy.gd" id="1_6h8a0"]
[ext_resource type="Texture2D" uid="uid://dqkvsqmwue64p" path="res://units/PNG_Parts&Spriter_Animation/Ship1/Ship1.png" id="1_ndkjl"]
Expand All @@ -13,11 +13,13 @@ script = ExtResource("1_6h8a0")
texture = ExtResource("1_ndkjl")

[node name="Area2D" type="Area2D" parent="."]
collision_mask = 3

[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
position = Vector2(0, -0.75)
shape = SubResource("RectangleShape2D_mvxux")

[node name="VisibleOnScreenNotifier2D" type="VisibleOnScreenNotifier2D" parent="."]

[connection signal="area_entered" from="Area2D" to="." method="_on_area_entered"]
[connection signal="screen_exited" from="VisibleOnScreenNotifier2D" to="." method="_on_visible_on_screen_notifier_2d_screen_exited"]
17 changes: 12 additions & 5 deletions units/player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ const HORIZONTAL_BUFFER = 75

@export var Projectile : PackedScene
@onready var projectile_spawner = $ProjectileSpawner
var DoubleLaserEffect : PackedScene = preload("res://projectiles/double_laser.tscn")
var HugeLaserEffect : PackedScene = preload("res://projectiles/huge_laser.tscn")
var FastLaserEffect : PackedScene = preload("res://projectiles/fast_laser.tscn")

# Called when the node enters the scene tree for the first time.
func _ready():
Projectile = load("res://units/projectile.tscn")

func fire():
print('fired!')
var p = Projectile.instantiate()
# Effects must be instantiated for each projectile.
# Finding a way to manage "which effects we need to instantiate" and
# "Which effects are actually on this projectile" is key
var effects = [DoubleLaserEffect, HugeLaserEffect, FastLaserEffect]
p.spawn(owner, effects, transform)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
Expand All @@ -30,8 +41,4 @@ func _physics_process(delta):
position.x += HORIZONTAL_SPEED * delta

if Input.is_action_just_pressed("fire"):
print('fired!')
var p = Projectile.instantiate()
owner.add_child(p)
p.transform = projectile_spawner.global_transform

fire()
campenr marked this conversation as resolved.
Show resolved Hide resolved
38 changes: 35 additions & 3 deletions units/projectile.gd
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
extends Area2D

const SPEED = 750
var projectile_effects: Array = []


func _physics_process(delta):
position += transform.x * SPEED * delta
func spawn(owner: Node, spawn_with_projectile_effects: Array, parent_transform: Transform2D):
# Execute any spawn modification effects for the projectile
print("spawned!")
#projectile_effects = spawn_with_projectile_effects
var local_transform = parent_transform
for effect in spawn_with_projectile_effects:
var instance = effect.instantiate()
projectile_effects.append(instance)
add_child(instance)
instance.set_owner(self)
instance.modify_creation(owner, spawn_with_projectile_effects, local_transform)
# Spawn the default projectile
# TODO:: Projectile spawn modification may want to prevent the default from spawning
# how do?
owner.add_child(self)
projectile_effects = projectile_effects
transform = local_transform

func scale_projectile(new_scale: Vector2):
$Sprite2D.scale = new_scale
$CollisionShape2D.scale = new_scale
scale = new_scale

func _ready():
if scale != Vector2.ONE:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍷 🧀

This is a hack to allow the "scale" you set on the Projectile node itself to impact the actual scale of the base projectile.
Without this, setting "Transform -> Scale" on the base projectile node will change the size of the projectile in the 2D visual UI, but at runtime it will be completely ignored. See this godot pr, also linked in the commit message directly.

scale_projectile(scale)

func _physics_process(delta):
var position_modifier = transform.x * SPEED * delta
for effect in projectile_effects:
campenr marked this conversation as resolved.
Show resolved Hide resolved
position_modifier = effect.modify_physics(position_modifier)
set_position(position + position_modifier)

func _on_area_entered(area):
# TODO: apply effects on enemy hit. For now just despawn.
queue_free()
area.owner.queue_free()

func _integrate_forces(delta):
scale = Vector2(100, 100)
1 change: 1 addition & 0 deletions units/projectile.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ radius = 4.0

[node name="Projectile" type="Area2D"]
position = Vector2(68, 1)
collision_layer = 2
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I kinda like the idea that projectiles can hit themselves / the player... but that'll take some thinking. For now 👍 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a terrible idea, though the huge double lasers overlapped hitboxes, so they would just immediately disappear

script = ExtResource("1_t6pyo")

[node name="Sprite2D" type="Sprite2D" parent="."]
Expand Down