Merge branch 'main' of https://gitty.informatik.hs-mannheim.de/3002102/gae_wild_jam
commit
c587fe9e4f
Binary file not shown.
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cn7yee27ivj7b"
|
||||
path="res://.godot/imported/laser.wav-ff32fed33cb3ccbcd074381583780fec.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/music&sfx/sfx/laser.wav"
|
||||
dest_files=["res://.godot/imported/laser.wav-ff32fed33cb3ccbcd074381583780fec.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
|
|
@ -1,26 +1,28 @@
|
|||
[
|
||||
{
|
||||
"time_start": 0,
|
||||
"time_end": 60,
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 0, "count_at_end": 15, "min_interval": 0.5 }
|
||||
]
|
||||
"time_start": 0,
|
||||
"time_end": 60,
|
||||
"elite_enemy": "res://scenes/slime.tscn",
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 0, "count_at_end": 15, "min_interval": 0.5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"time_start": 60,
|
||||
"time_end": 180,
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 15, "count_at_end": 40, "min_interval": 0.3 },
|
||||
{ "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 0, "count_at_end": 10, "min_interval": 0.8 }
|
||||
]
|
||||
"time_start": 60,
|
||||
"time_end": 180,
|
||||
"elite_enemy": "res://scenes/blue_slime.tscn",
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 15, "count_at_end": 40, "min_interval": 0.3 },
|
||||
{ "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 0, "count_at_end": 10, "min_interval": 0.8 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"time_start": 180,
|
||||
"time_end": -1,
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 40, "count_at_end": 100, "min_interval": 0.2 },
|
||||
{ "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 10, "count_at_end": 60, "min_interval": 0.5 },
|
||||
{ "enemy": "res://scenes/fire_slime.tscn", "count_at_start": 0, "count_at_end": 40, "min_interval": 0.6 }
|
||||
]
|
||||
"time_start": 180,
|
||||
"time_end": -1,
|
||||
"entries": [
|
||||
{ "enemy": "res://scenes/slime.tscn", "count_at_start": 40, "count_at_end": 100, "min_interval": 0.2 },
|
||||
{ "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 10, "count_at_end": 60, "min_interval": 0.5 },
|
||||
{ "enemy": "res://scenes/fire_slime.tscn", "count_at_start": 0, "count_at_end": 40, "min_interval": 0.6 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -34,6 +34,33 @@ window/stretch/mode="viewport"
|
|||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
|
||||
[input]
|
||||
|
||||
move_left={
|
||||
"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":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, 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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"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":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, 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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"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":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, 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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"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":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, 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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
rendering_device/driver.windows="d3d12"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
[gd_scene format=3 uid="uid://d2mpjsuueg0bn"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdhx27edemfce" path="res://scripts/beam.gd" id="1_beam00"]
|
||||
[ext_resource type="Texture2D" uid="uid://dxox0vjihmukh" path="res://assets/Fire Pixel Bullet 16x16/All_Fire_Bullet_Pixel_16x16_05.png" id="2_hl8vi"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_daiji"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(96, 0, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bawts"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(112, 0, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_8wlve"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(128, 0, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bt3ye"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(144, 0, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a06w7"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(96, 16, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_5fvdj"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(112, 16, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_djxf5"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(128, 16, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_eeead"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(144, 16, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qfoqh"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(96, 32, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tuykp"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(112, 32, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2dcgn"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(128, 32, 16, 16)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_7703o"]
|
||||
atlas = ExtResource("2_hl8vi")
|
||||
region = Rect2(144, 32, 16, 16)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_beam0"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_daiji")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_bawts")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_8wlve")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_bt3ye")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"end",
|
||||
"speed": 10.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_a06w7")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_5fvdj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_djxf5")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_eeead")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"middle",
|
||||
"speed": 10.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qfoqh")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_tuykp")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_2dcgn")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_7703o")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"start",
|
||||
"speed": 10.0
|
||||
}]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_beam0"]
|
||||
size = Vector2(16, 8)
|
||||
|
||||
[node name="Beam" type="Area2D" unique_id=968931754]
|
||||
script = ExtResource("1_beam00")
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=100000001]
|
||||
texture_filter = 1
|
||||
sprite_frames = SubResource("SpriteFrames_beam0")
|
||||
animation = &"end"
|
||||
frame_progress = 0.02647079
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=100000002]
|
||||
shape = SubResource("RectangleShape2D_beam0")
|
||||
disabled = true
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
[gd_scene format=3 uid="uid://cchili0scene1"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/chili.gd" id="1_chili0"]
|
||||
[ext_resource type="Texture2D" uid="uid://d2pinnrigixnp" path="res://assets/16x16 Pixelart Food Icons/Pixel_Foods(ARTLİNE).png" id="2_chili0"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_chili"]
|
||||
atlas = ExtResource("2_chili0")
|
||||
region = Rect2(19, 37, 16, 16)
|
||||
|
||||
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_chili"]
|
||||
radius = 4.0
|
||||
height = 12.0
|
||||
|
||||
[node name="Chili" type="Area2D" unique_id=200000001]
|
||||
script = ExtResource("1_chili0")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=200000002]
|
||||
texture_filter = 1
|
||||
texture = SubResource("AtlasTexture_chili")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=200000003]
|
||||
shape = SubResource("CapsuleShape2D_chili")
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
[gd_scene format=3 uid="uid://cpe6aiuqiox0u"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dklt42vjjcks7" path="res://scripts/fire_slime.gd" id="1_88j2t"]
|
||||
[ext_resource type="Script" uid="uid://cjkaw7wqw4e30" path="res://scripts/drop_table.gd" id="dt_fslime"]
|
||||
[ext_resource type="PackedScene" uid="uid://cchili0scene1" path="res://scenes/chili.tscn" id="chili_fslime"]
|
||||
[ext_resource type="Texture2D" uid="uid://b1tyfy8ooudkc" path="res://assets/Slime3/With_shadow/Slime3_Death_with_shadow.png" id="2_ahfdi"]
|
||||
[ext_resource type="Texture2D" uid="uid://bbm1sv6hmc2j" path="res://assets/Slime3/With_shadow/Slime3_Hurt_with_shadow.png" id="3_kq38e"]
|
||||
[ext_resource type="Texture2D" uid="uid://bt07131sttb6e" path="res://assets/Slime3/With_shadow/Slime3_Walk_with_shadow.png" id="4_ret1g"]
|
||||
|
|
@ -732,9 +734,15 @@ radius = 7.071068
|
|||
[sub_resource type="CircleShape2D" id="CircleShape2D_odbmi"]
|
||||
radius = 8.062258
|
||||
|
||||
[sub_resource type="Resource" id="Resource_chili_drop"]
|
||||
script = ExtResource("dt_fslime")
|
||||
drop = ExtResource("chili_fslime")
|
||||
chance = 0.4
|
||||
|
||||
[node name="FireSlime" type="CharacterBody2D" unique_id=1827403107]
|
||||
script = ExtResource("1_88j2t")
|
||||
metadata/_custom_type_script = "uid://c0uv02nt5ocvg"
|
||||
drop_table = Array[ExtResource("dt_fslime")]([SubResource("Resource_chili_drop")])
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1151813585]
|
||||
texture_filter = 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene format=3 uid="uid://claser0spell1"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/laser.gd" id="1_laser0"]
|
||||
|
||||
[node name="Laser" type="Node2D"]
|
||||
script = ExtResource("1_laser0")
|
||||
|
|
@ -4,6 +4,7 @@ class_name DropsBase
|
|||
var witch
|
||||
var player
|
||||
var is_spawning = true
|
||||
var in_orbit: bool = false
|
||||
signal collected
|
||||
|
||||
var _collect_sfx = preload("res://assets/music&sfx/sfx/lesiakower-coin-collect-retro-8-bit-sound-effect-145251.mp3")
|
||||
|
|
@ -44,9 +45,14 @@ func _animate_spawn() -> void:
|
|||
is_spawning = false
|
||||
|
||||
func _on_body_entered(body: Node2D) -> void:
|
||||
if body == player and not is_spawning:
|
||||
collect()
|
||||
pass
|
||||
if body == player and not is_spawning and not in_orbit:
|
||||
var cauldron = witch.get_node("CauldronBar")
|
||||
if cauldron.is_brewing and player.fruit_queue.size() < 3:
|
||||
_sfx_player.pitch_scale = randf_range(0.85, 1.15)
|
||||
_sfx_player.play()
|
||||
player.add_to_queue(self)
|
||||
else:
|
||||
collect()
|
||||
|
||||
func collect():
|
||||
_sfx_player.pitch_scale = randf_range(0.85, 1.15)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ extends Resource
|
|||
class_name SpawnStage
|
||||
|
||||
@export var time_start: float = 0.0
|
||||
@export var time_end: float = -1.0 # -1 = forever
|
||||
@export var time_end: float = -1.0
|
||||
@export var elite_enemy: PackedScene = null
|
||||
@export var entries: Array[StageEntry]
|
||||
|
|
|
|||
|
|
@ -2,29 +2,31 @@ extends Node
|
|||
|
||||
const APPLE = 0
|
||||
const GRAPE = 1
|
||||
const CHILI = 2
|
||||
|
||||
const NONE = "NONE"
|
||||
const SHURIKEN = "SHURIKEN"
|
||||
const FIREBALL = "FIREBALL"
|
||||
const FIRE_SWIRL = "FIRE_SWIRL"
|
||||
const TORNADO = "TORNADO"
|
||||
const LASER = "LASER"
|
||||
|
||||
# Each spell's display recipe: sorted apples-first, grapes-last
|
||||
var recipes: Dictionary = {
|
||||
SHURIKEN: [APPLE, GRAPE, GRAPE],
|
||||
FIREBALL: [APPLE, APPLE, APPLE],
|
||||
FIRE_SWIRL: [APPLE, APPLE, GRAPE],
|
||||
TORNADO: [GRAPE, GRAPE, GRAPE],
|
||||
FIREBALL: [APPLE],
|
||||
TORNADO: [GRAPE],
|
||||
LASER: [CHILI],
|
||||
SHURIKEN: [APPLE, GRAPE],
|
||||
FIRE_SWIRL: [APPLE, CHILI],
|
||||
}
|
||||
|
||||
# Takes cauldron slot_states (uses texture indices: 2 = apple, 4 = grape)
|
||||
# and returns which spell that combination brews.
|
||||
func identify(cauldron_slots: Array) -> String:
|
||||
var apples = cauldron_slots.count(2)
|
||||
var grapes = cauldron_slots.count(4)
|
||||
var types: Array = []
|
||||
if cauldron_slots.any(func(x): return x == 2): types.append(APPLE)
|
||||
if cauldron_slots.any(func(x): return x == 4): types.append(GRAPE)
|
||||
if cauldron_slots.any(func(x): return x == 3): types.append(CHILI)
|
||||
|
||||
for spell_id in recipes:
|
||||
var r: Array = recipes[spell_id]
|
||||
if r.count(APPLE) == apples and r.count(GRAPE) == grapes:
|
||||
if recipes[spell_id] == types:
|
||||
return spell_id
|
||||
return NONE
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
extends Area2D
|
||||
|
||||
@export var beam_type := "middle"
|
||||
|
||||
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
|
||||
@onready var shape: CollisionShape2D = $CollisionShape2D
|
||||
|
||||
func _ready() -> void:
|
||||
if sprite.sprite_frames.has_animation(beam_type):
|
||||
sprite.play(beam_type)
|
||||
shape.disabled = beam_type != "middle"
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://bdhx27edemfce
|
||||
|
|
@ -5,6 +5,7 @@ func _ready() -> void:
|
|||
speed = 15.0
|
||||
max_hp = 25
|
||||
hp = max_hp
|
||||
damage = 5
|
||||
# $Area2D.body_entered.connect(_on_area_2d_body_entered)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ var progres_index = 0
|
|||
var is_brewing
|
||||
var brew_explosion = true
|
||||
@onready var witch = get_parent()
|
||||
@onready var player = get_node("/root/Game/Player")
|
||||
var explosion_scene = preload("res://scenes/explosion.tscn")
|
||||
var _ignite_sfx = preload("res://assets/music&sfx/sfx/data_pion-sfx9-fwoosh-324525.mp3")
|
||||
var _ignite_player: AudioStreamPlayer
|
||||
|
|
@ -87,6 +88,8 @@ func progres_bar(fruit):
|
|||
change_texture(progres_index, 2)
|
||||
if fruit is Grape:
|
||||
change_texture(progres_index, 4)
|
||||
if fruit is Chili:
|
||||
change_texture(progres_index, 3)
|
||||
_pop_slot(progres_index)
|
||||
progres_index += 1
|
||||
if progres_index == 3:
|
||||
|
|
@ -117,9 +120,11 @@ func brew(fruits):
|
|||
SpellLibrary.FIREBALL: witch.shoot_fireballs()
|
||||
SpellLibrary.FIRE_SWIRL: witch.shoot_fire_swirl()
|
||||
SpellLibrary.TORNADO: witch.shoot_tornado()
|
||||
SpellLibrary.LASER: witch.shoot_laser()
|
||||
reset_texture()
|
||||
is_brewing = false
|
||||
|
||||
player.flush_queue()
|
||||
|
||||
func get_unique_fruits() -> Array:
|
||||
var unique = []
|
||||
for fruit in slot_states:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
extends DropsBase
|
||||
class_name Chili
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://dinqfnri3co88
|
||||
|
|
@ -3,6 +3,13 @@ extends CanvasLayer
|
|||
@onready var _spawn_control = get_node("/root/Game/SpawnControl")
|
||||
@onready var _witch = get_node("/root/Game/Witch")
|
||||
@onready var _drop_manager = get_node("/root/Game/DropManager")
|
||||
@onready var _player = get_node("/root/Game/Player")
|
||||
|
||||
var _fruit_scenes: Dictionary = {
|
||||
"Apple": preload("res://scenes/apple.tscn"),
|
||||
"Grape": preload("res://scenes/grape.tscn"),
|
||||
"Chili": preload("res://scenes/chili.tscn"),
|
||||
}
|
||||
|
||||
var _spell_dispatch: Dictionary
|
||||
var _lvl_disable_btn: Button
|
||||
|
|
@ -14,6 +21,7 @@ func _ready() -> void:
|
|||
SpellLibrary.SHURIKEN: _witch.shoot_shuriken,
|
||||
SpellLibrary.FIRE_SWIRL: _witch.shoot_fire_swirl,
|
||||
SpellLibrary.TORNADO: _witch.shoot_tornado,
|
||||
SpellLibrary.LASER: _witch.shoot_laser,
|
||||
}
|
||||
_build_ui()
|
||||
hide()
|
||||
|
|
@ -72,6 +80,10 @@ func _build_ui() -> void:
|
|||
btn.pressed.connect(_skip_time.bind(float(secs)))
|
||||
hbox_time.add_child(btn)
|
||||
|
||||
_add_section(vbox, "FRUITS")
|
||||
for fruit_name in _fruit_scenes.keys():
|
||||
_add_button(vbox, fruit_name, _spawn_fruit.bind(fruit_name))
|
||||
|
||||
_add_section(vbox, "SPELLS")
|
||||
for spell_id in SpellLibrary.recipes.keys():
|
||||
if _spell_dispatch.has(spell_id):
|
||||
|
|
@ -99,6 +111,11 @@ func _add_button(parent: VBoxContainer, label: String, callback: Callable) -> vo
|
|||
btn.pressed.connect(callback)
|
||||
parent.add_child(btn)
|
||||
|
||||
func _spawn_fruit(fruit_name: String) -> void:
|
||||
var fruit = _fruit_scenes[fruit_name].instantiate()
|
||||
fruit.global_position = _player.global_position + Vector2(randf_range(-30, 30), randf_range(-30, 30))
|
||||
get_node("/root/Game").add_child(fruit)
|
||||
|
||||
func _kill_all_enemies() -> void:
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if is_instance_valid(enemy):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ func _ready() -> void:
|
|||
speed = 15.0
|
||||
max_hp = 50
|
||||
hp = max_hp
|
||||
damage = 10
|
||||
$Area2D.body_entered.connect(_on_area_2d_body_entered)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
extends Node2D
|
||||
|
||||
const BEAM_DURATION := 3.0
|
||||
const PRIMARY_TICK_DMG := 40
|
||||
const SPLASH_TICK_DMG := 3
|
||||
const TICK_INTERVAL := 0.5
|
||||
const SWEEP_RATE := 22.0
|
||||
|
||||
@export var segment_size := 16.0
|
||||
@export var start_offset := 24.0
|
||||
|
||||
@onready var perk_effects = get_node("/root/Game/PerkEffects")
|
||||
|
||||
var beam_seg := preload("res://scenes/beam.tscn")
|
||||
var target: Node2D = null
|
||||
var all_segs: Array = []
|
||||
var mid_segs: Array = []
|
||||
var tick_timer := 0.0
|
||||
var elapsed := 0.0
|
||||
var done := false
|
||||
var current_angle := 0.0
|
||||
var sweep_to := 0.0
|
||||
var sweeping := false
|
||||
|
||||
func _ready() -> void:
|
||||
target = get_highest_hp_enemy()
|
||||
if target == null:
|
||||
queue_free()
|
||||
return
|
||||
current_angle = global_position.direction_to(target.global_position).angle()
|
||||
rebuild_segments()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if done:
|
||||
return
|
||||
elapsed += delta
|
||||
tick_timer += delta
|
||||
|
||||
if sweeping:
|
||||
current_angle = lerp_angle(current_angle, sweep_to, 1.0 - exp(-SWEEP_RATE * delta))
|
||||
if abs(angle_difference(current_angle, sweep_to)) < 0.005:
|
||||
current_angle = sweep_to
|
||||
sweeping = false
|
||||
rebuild_segments()
|
||||
|
||||
if tick_timer >= TICK_INTERVAL:
|
||||
tick_timer -= TICK_INTERVAL
|
||||
do_damage_tick()
|
||||
if elapsed >= BEAM_DURATION:
|
||||
done = true
|
||||
cleanup()
|
||||
|
||||
func rebuild_segments() -> void:
|
||||
var dir := Vector2.from_angle(current_angle)
|
||||
var beam_start := global_position + dir * start_offset
|
||||
var dest_dist := global_position.distance_to(target.global_position)
|
||||
var beam_dist := dest_dist - start_offset
|
||||
var n_mid := int(max(0.0, beam_dist - segment_size) / segment_size)
|
||||
|
||||
var positions: Array = [beam_start]
|
||||
var types: Array = ["start"]
|
||||
for i in range(n_mid):
|
||||
positions.append(beam_start + dir * (segment_size * float(i + 1)))
|
||||
types.append("middle")
|
||||
positions.append(beam_start + dir * (segment_size * float(n_mid + 1)))
|
||||
types.append("end")
|
||||
|
||||
while all_segs.size() < positions.size():
|
||||
var seg = beam_seg.instantiate()
|
||||
get_parent().add_child(seg)
|
||||
all_segs.append(seg)
|
||||
while all_segs.size() > positions.size():
|
||||
var seg = all_segs.pop_back()
|
||||
if is_instance_valid(seg):
|
||||
seg.queue_free()
|
||||
|
||||
mid_segs.clear()
|
||||
for i in range(all_segs.size()):
|
||||
var seg = all_segs[i]
|
||||
if not is_instance_valid(seg):
|
||||
continue
|
||||
seg.global_position = positions[i]
|
||||
seg.rotation = current_angle
|
||||
if seg.beam_type != types[i]:
|
||||
seg.beam_type = types[i]
|
||||
seg.shape.disabled = types[i] != "middle"
|
||||
if seg.sprite.sprite_frames.has_animation(types[i]):
|
||||
seg.sprite.play(types[i])
|
||||
if types[i] == "middle":
|
||||
mid_segs.append(seg)
|
||||
|
||||
if all_segs.size() > 1 and is_instance_valid(all_segs[0]):
|
||||
var ref_frame: int = all_segs[0].sprite.frame
|
||||
var ref_progress: float = all_segs[0].sprite.frame_progress
|
||||
for i in range(1, all_segs.size()):
|
||||
var seg = all_segs[i]
|
||||
if is_instance_valid(seg):
|
||||
seg.sprite.frame = ref_frame
|
||||
seg.sprite.frame_progress = ref_progress
|
||||
|
||||
func retarget(new_target: Node2D) -> void:
|
||||
target = new_target
|
||||
sweep_to = global_position.direction_to(new_target.global_position).angle()
|
||||
sweeping = true
|
||||
|
||||
func do_damage_tick() -> void:
|
||||
if (not is_instance_valid(target) or target.is_dying) and perk_effects.laser_retarget_enabled:
|
||||
var new_target = get_highest_hp_enemy()
|
||||
if new_target != null:
|
||||
retarget(new_target)
|
||||
|
||||
if is_instance_valid(target) and not target.is_dying:
|
||||
target.take_damage(PRIMARY_TICK_DMG)
|
||||
|
||||
var hit: Array = []
|
||||
for seg in mid_segs:
|
||||
if not is_instance_valid(seg):
|
||||
continue
|
||||
for body in seg.get_overlapping_bodies():
|
||||
if body.is_in_group("enemies") and not body.is_dying \
|
||||
and body != target and not hit.has(body):
|
||||
body.take_damage(SPLASH_TICK_DMG)
|
||||
hit.append(body)
|
||||
|
||||
func cleanup() -> void:
|
||||
for seg in all_segs:
|
||||
if is_instance_valid(seg):
|
||||
seg.queue_free()
|
||||
queue_free()
|
||||
|
||||
func get_highest_hp_enemy() -> Node2D:
|
||||
var best: Node2D = null
|
||||
var best_hp: int = -1
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if not is_instance_valid(enemy) or enemy.is_dying:
|
||||
continue
|
||||
if enemy.hp > best_hp:
|
||||
best_hp = enemy.hp
|
||||
best = enemy
|
||||
return best
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://dp6b3imslv10d
|
||||
|
|
@ -7,6 +7,7 @@ var throwing_knife = preload("res://scenes/throwing_knive.tscn")
|
|||
var cauldron
|
||||
var available_perks: Array[Perk] = []
|
||||
var fireball_aoe_enabled = false
|
||||
var laser_retarget_enabled = false
|
||||
var throwing_knife_enabled = false
|
||||
var throwing_knife_cooldown: float = 2.0
|
||||
var throwing_knife_count: int = 1
|
||||
|
|
@ -15,12 +16,14 @@ var _knife_timer: float = 0.0
|
|||
var spellbook_scene = preload("res://scenes/spellbook.tscn")
|
||||
var spellbook_count: int = 0
|
||||
var spellbook_damage: int = 12
|
||||
const SPELLBOOK_MAX_DAMAGE: int = 40
|
||||
var spellbook_speed: float = 1.5
|
||||
const SPELLBOOK_RADIUS: float = 60.0
|
||||
var _spellbook_angle: float = 0.0
|
||||
var _spellbooks: Array = []
|
||||
|
||||
var _icon_knife = preload("res://assets/weapons/knvie.png")
|
||||
var _icon_laser: AtlasTexture
|
||||
var _icon_book = preload("res://assets/books_set_2/books_pentagram.png")
|
||||
var _icon_brew = preload("res://assets/books_set_2/books_health_potion.png")
|
||||
var _icon_shuriken: AtlasTexture
|
||||
|
|
@ -51,10 +54,23 @@ func _ready() -> void:
|
|||
_icon_player.atlas = preload("res://assets/Swordsman_lvl1/Without_shadow/Swordsman_lvl1_Idle_without_shadow.png")
|
||||
_icon_player.region = Rect2(0, 0, 64, 64)
|
||||
|
||||
_icon_laser = AtlasTexture.new()
|
||||
_icon_laser.atlas = preload("res://assets/Fire Pixel Bullet 16x16/All_Fire_Bullet_Pixel_16x16_05.png")
|
||||
_icon_laser.region = Rect2(96, 16, 16, 16)
|
||||
|
||||
var lrt = Perk.new()
|
||||
lrt.name = "Laser Lock-On"
|
||||
lrt.description = "Laser retargets to the next highest health enemy on kill"
|
||||
lrt.stats = _stat_toggle("Retarget")
|
||||
lrt.spell = SpellLibrary.LASER
|
||||
lrt.icon = _icon_laser
|
||||
lrt.effect = laser_retarget
|
||||
available_perks.append(lrt)
|
||||
|
||||
var dsh = Perk.new()
|
||||
dsh.name = "Double Shuriken"
|
||||
dsh.description = "Fire two shurikens at once"
|
||||
dsh.stats = _stat("Shurikens", "1", "2")
|
||||
dsh.description = "Fire two more shurikens at once"
|
||||
dsh.stats = _stat("Shurikens", str(witch.shuriken_count), str(witch.shuriken_count + 2))
|
||||
dsh.spell = SpellLibrary.SHURIKEN
|
||||
dsh.icon = _icon_shuriken
|
||||
dsh.effect = double_shuriken
|
||||
|
|
@ -69,6 +85,15 @@ func _ready() -> void:
|
|||
faoe.effect = fireball_aoe
|
||||
available_perks.append(faoe)
|
||||
|
||||
var fsp = Perk.new()
|
||||
fsp.name = "Fireball Spread"
|
||||
fsp.description = "Fireballs target more enemies"
|
||||
fsp.stats = _stat("Targets", str(witch.fireball_max_targets), str(witch.fireball_max_targets + 3))
|
||||
fsp.spell = SpellLibrary.FIREBALL
|
||||
fsp.icon = _icon_fireball
|
||||
fsp.effect = fireball_spread
|
||||
available_perks.append(fsp)
|
||||
|
||||
var bexp = Perk.new()
|
||||
bexp.name = "Brew Explosion"
|
||||
bexp.description = "Trigger an explosion on brew"
|
||||
|
|
@ -135,12 +160,18 @@ func _process(delta: float) -> void:
|
|||
var offset = (TAU / _spellbooks.size()) * i
|
||||
_spellbooks[i].global_position = witch.global_position + Vector2(cos(_spellbook_angle + offset), sin(_spellbook_angle + offset)) * SPELLBOOK_RADIUS
|
||||
|
||||
func laser_retarget():
|
||||
laser_retarget_enabled = true
|
||||
|
||||
func double_shuriken():
|
||||
witch.shuriken_count = 2
|
||||
witch.shuriken_count += 2
|
||||
|
||||
func fireball_aoe():
|
||||
fireball_aoe_enabled = true
|
||||
|
||||
func fireball_spread():
|
||||
witch.fireball_max_targets += 3
|
||||
|
||||
func brew_explosion():
|
||||
cauldron.brew_explosion = true
|
||||
|
||||
|
|
@ -237,16 +268,17 @@ func faster_orbit() -> void:
|
|||
available_perks.append(fo)
|
||||
|
||||
func book_damage() -> void:
|
||||
spellbook_damage += 4
|
||||
spellbook_damage = min(spellbook_damage + 4, SPELLBOOK_MAX_DAMAGE)
|
||||
for book in _spellbooks:
|
||||
book.damage = spellbook_damage
|
||||
var bd = Perk.new()
|
||||
bd.name = "Book Damage"
|
||||
bd.description = "Books hit harder"
|
||||
bd.stats = _stat("Damage", str(spellbook_damage), str(spellbook_damage + 4))
|
||||
bd.icon = _icon_book
|
||||
bd.effect = book_damage
|
||||
available_perks.append(bd)
|
||||
if spellbook_damage < SPELLBOOK_MAX_DAMAGE:
|
||||
var bd = Perk.new()
|
||||
bd.name = "Book Damage"
|
||||
bd.description = "Books hit harder"
|
||||
bd.stats = _stat("Damage", str(spellbook_damage), str(min(spellbook_damage + 4, SPELLBOOK_MAX_DAMAGE)))
|
||||
bd.icon = _icon_book
|
||||
bd.effect = book_damage
|
||||
available_perks.append(bd)
|
||||
|
||||
func _rebuild_spellbooks() -> void:
|
||||
for book in _spellbooks:
|
||||
|
|
|
|||
|
|
@ -6,11 +6,22 @@ var level = 1
|
|||
var speed = 60
|
||||
var strength = 3
|
||||
var attacks = false
|
||||
var fruit_queue: Array = []
|
||||
var orbit_angle: float = 0.0
|
||||
const ORBIT_RADIUS := 20.0
|
||||
const ORBIT_SPEED := 2.5
|
||||
|
||||
func _physics_process(delta):
|
||||
var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||
var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
velocity = direction * speed
|
||||
move_and_slide()
|
||||
orbit_angle += ORBIT_SPEED * delta
|
||||
for i in fruit_queue.size():
|
||||
var fruit = fruit_queue[i]
|
||||
if is_instance_valid(fruit):
|
||||
var angle = orbit_angle + (TAU / fruit_queue.size()) * i
|
||||
var target_pos = global_position + Vector2.from_angle(angle) * ORBIT_RADIUS
|
||||
fruit.global_position = fruit.global_position.lerp(target_pos, 1.0 - exp(-12.0 * delta))
|
||||
if attacks == true:
|
||||
return
|
||||
if direction == Vector2.ZERO:
|
||||
|
|
@ -76,6 +87,19 @@ func _on_attack_speed_timeout() -> void:
|
|||
|
||||
|
||||
|
||||
func add_to_queue(fruit) -> void:
|
||||
fruit.in_orbit = true
|
||||
fruit_queue.append(fruit)
|
||||
|
||||
func flush_queue() -> void:
|
||||
var to_flush = fruit_queue.duplicate()
|
||||
fruit_queue.clear()
|
||||
for fruit in to_flush:
|
||||
if is_instance_valid(fruit):
|
||||
fruit.in_orbit = false
|
||||
fruit.collect()
|
||||
await get_tree().create_timer(0.15).timeout
|
||||
|
||||
func _on_melee_area_body_entered(body: Node2D) -> void:
|
||||
if body.is_in_group("enemies"):
|
||||
if $AttackSpeed.is_stopped():
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ var elapsed_time: float = 0.0
|
|||
|
||||
const STAGES_JSON = "res://data/spawn_stages.json"
|
||||
|
||||
var stages: Array[SpawnStage] = []
|
||||
const ELITE_HP_MULT := 20
|
||||
const ELITE_SPD_MULT := 0.25
|
||||
const ELITE_SCALE := 3.0
|
||||
|
||||
# _state keys: Vector2i(stage_idx, entry_idx)
|
||||
# values: { "timer": float, "alive": int }
|
||||
var stages: Array[SpawnStage] = []
|
||||
var _state: Dictionary = {}
|
||||
var current_stage_idx: int = -1
|
||||
|
||||
func _ready() -> void:
|
||||
var camera: Camera2D = get_parent().get_node("Camera2D")
|
||||
|
|
@ -44,6 +46,8 @@ func _load_stages(path: String) -> void:
|
|||
var stage = SpawnStage.new()
|
||||
stage.time_start = float(sd["time_start"])
|
||||
stage.time_end = float(sd["time_end"])
|
||||
if sd.has("elite_enemy"):
|
||||
stage.elite_enemy = load(sd["elite_enemy"])
|
||||
for ed in sd["entries"]:
|
||||
var entry = StageEntry.new()
|
||||
entry.enemy = load(ed["enemy"])
|
||||
|
|
@ -72,9 +76,31 @@ func get_spawn_position() -> Vector2:
|
|||
spawn_y = randf_range(up_right.y, down_right.y)
|
||||
return Vector2(spawn_x, spawn_y)
|
||||
|
||||
func _active_stage_idx() -> int:
|
||||
for si in stages.size():
|
||||
var s: SpawnStage = stages[si]
|
||||
if elapsed_time >= s.time_start and (s.time_end == -1.0 or elapsed_time < s.time_end):
|
||||
return si
|
||||
return stages.size() - 1
|
||||
|
||||
func _spawn_elite(scene: PackedScene) -> void:
|
||||
var enemy = scene.instantiate()
|
||||
enemy.global_position = get_spawn_position()
|
||||
add_child(enemy)
|
||||
enemy.scale = Vector2(ELITE_SCALE, ELITE_SCALE)
|
||||
enemy.max_hp = enemy.max_hp * ELITE_HP_MULT
|
||||
enemy.hp = enemy.max_hp
|
||||
enemy.speed *= ELITE_SPD_MULT
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
elapsed_time += delta
|
||||
|
||||
var new_idx := _active_stage_idx()
|
||||
if new_idx != current_stage_idx:
|
||||
if current_stage_idx >= 0 and stages[current_stage_idx].elite_enemy != null:
|
||||
_spawn_elite(stages[current_stage_idx].elite_enemy)
|
||||
current_stage_idx = new_idx
|
||||
|
||||
for si in stages.size():
|
||||
var stage: SpawnStage = stages[si]
|
||||
if elapsed_time < stage.time_start:
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ var fireball = preload("res://scenes/fireball.tscn")
|
|||
var shuriken = preload("res://scenes/shuriken.tscn")
|
||||
var fire_swirl = preload("res://scenes/fire_swirl.tscn")
|
||||
var tornado = preload("res://scenes/tornado.tscn")
|
||||
var laser = preload("res://scenes/laser.tscn")
|
||||
var shuriken_count = 1
|
||||
var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav")
|
||||
var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav")
|
||||
var _laser_sfx = preload("res://assets/music&sfx/sfx/laser.wav")
|
||||
|
||||
var max_hp: int = 100
|
||||
var current_hp: int = 100
|
||||
var max_hp: int = 50
|
||||
var current_hp: int = 50
|
||||
var is_invincible: bool = false
|
||||
var fireball_max_targets: int = 5
|
||||
|
||||
const HP_BAR_WIDTH = 20
|
||||
const HP_BAR_HEIGHT = 3
|
||||
|
|
@ -51,7 +54,10 @@ func _on_collect(DropsBase):
|
|||
|
||||
func shoot_fireballs():
|
||||
var enemies = get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
enemies = enemies.filter(func(e): return is_instance_valid(e))
|
||||
enemies.sort_custom(func(a, b): return global_position.distance_to(a.global_position) < global_position.distance_to(b.global_position))
|
||||
var targets = enemies.slice(0, fireball_max_targets)
|
||||
for enemy in targets:
|
||||
if not is_instance_valid(enemy):
|
||||
continue
|
||||
var dir = global_position.direction_to(enemy.global_position)
|
||||
|
|
@ -99,6 +105,19 @@ func shoot_tornado():
|
|||
tw.global_position = target.global_position if target != null else global_position
|
||||
get_parent().add_child(tw)
|
||||
camera.shake(0.3, 0.8)
|
||||
|
||||
func shoot_laser():
|
||||
var ls = laser.instantiate()
|
||||
ls.global_position = global_position
|
||||
get_parent().add_child(ls)
|
||||
camera.shake(0.4, 1.2)
|
||||
var asp = AudioStreamPlayer.new()
|
||||
asp.stream = _laser_sfx
|
||||
asp.volume_db = 6
|
||||
asp.bus = "SFX"
|
||||
get_parent().add_child(asp)
|
||||
asp.play()
|
||||
asp.finished.connect(asp.queue_free)
|
||||
|
||||
func take_damage(amount: int) -> void:
|
||||
if is_invincible:
|
||||
|
|
@ -111,7 +130,7 @@ func take_damage(amount: int) -> void:
|
|||
get_tree().call_deferred("reload_current_scene")
|
||||
return
|
||||
is_invincible = true
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
is_invincible = false
|
||||
|
||||
func get_nearest_enemy(from: Vector2, filter: Callable = Callable()) -> Node:
|
||||
|
|
|
|||
Loading…
Reference in New Issue