From c2e3fa2ac2f99f69e95be1bd5c0c059a7f1bb278 Mon Sep 17 00:00:00 2001 From: yuki Date: Mon, 17 Nov 2025 21:45:45 -0300 Subject: [PATCH] implement basic power hit --- project.godot | 10 ++++ scenes/ball/ball.gd | 10 ++-- scenes/classes/hit.gd | 3 ++ scenes/classes/hit.gd.uid | 1 + scenes/classes/hit.tscn | 14 ++++++ scenes/classes/state.gd | 2 +- scenes/hit/power_hit.tscn | 8 +++- scenes/hit/{hit.tscn => small_hit.tscn} | 11 +++-- scenes/main.tscn | 4 ++ scenes/player/player.gd | 17 +++++++ scenes/player/player.tscn | 64 ++++++++++++++++++++++++- scenes/player/states/hitting.gd | 59 ++++++++++++++--------- scenes/player/states/idle.gd | 6 +-- scenes/player/states/running.gd | 2 +- 14 files changed, 173 insertions(+), 38 deletions(-) create mode 100644 scenes/classes/hit.gd create mode 100644 scenes/classes/hit.gd.uid create mode 100644 scenes/classes/hit.tscn rename scenes/hit/{hit.tscn => small_hit.tscn} (58%) diff --git a/project.godot b/project.godot index c771bef..1ba113e 100644 --- a/project.godot +++ b/project.godot @@ -111,6 +111,16 @@ p2_hit_right={ "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":4194441,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +p1_power_hit={ +"deadzone": 0.2, +"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":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null) +] +} +p2_power_hit={ +"deadzone": 0.2, +"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":4194439,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [layer_names] diff --git a/scenes/ball/ball.gd b/scenes/ball/ball.gd index edfbf14..0a06b4a 100644 --- a/scenes/ball/ball.gd +++ b/scenes/ball/ball.gd @@ -17,9 +17,9 @@ func _ready() -> void: launch() -func launch(angle: float = 0) -> void: +func launch(angle: float = 0, speed_override: float = 0) -> void: angle = randf_range(-PI/3, PI/3) + PI * float(randi()%2) if angle == 0 else angle - linear_velocity = Vector2(cos(angle), sin(angle)) * randf_range(launch_speed.x, launch_speed.y) + linear_velocity = Vector2(cos(angle), sin(angle)) * (randf_range(launch_speed.x, launch_speed.y) if speed_override == 0 else speed_override) var length: float = linear_velocity.length()/100 play_animation(length) decelerate_to_stop(length*2) @@ -45,15 +45,17 @@ func decelerate_to_stop(duration: float = 0.3) -> void: # Call this after colli ) -func _on_hit(hitbox: Area2D) -> void: +func _on_hit(hitbox: Hit) -> void: print("area detected") if not is_hit: if hitbox.is_in_group("hit"): + assert(hitbox.power_hit != null, "hit doesnt have correct metadata, is it a hit?") print("ball not hit yet") var player: Player = hitbox.get_parent() var timer: Timer = player.hit_timer var angle: float = randf_range(3*PI/4, PI/4) * (-1 if player.id == 1 else 1) - launch(angle) + var speed: float = randf_range(300, 350) if hitbox.power_hit else 0.0 # o.o + launch(angle, speed) print("ball hit") if not timer.is_connected("timeout", _on_hit_end): timer.connect("timeout", _on_hit_end) diff --git a/scenes/classes/hit.gd b/scenes/classes/hit.gd new file mode 100644 index 0000000..a78572d --- /dev/null +++ b/scenes/classes/hit.gd @@ -0,0 +1,3 @@ +class_name Hit extends Area2D + +@export var power_hit: bool = false diff --git a/scenes/classes/hit.gd.uid b/scenes/classes/hit.gd.uid new file mode 100644 index 0000000..69e065d --- /dev/null +++ b/scenes/classes/hit.gd.uid @@ -0,0 +1 @@ +uid://b440ri3521uio diff --git a/scenes/classes/hit.tscn b/scenes/classes/hit.tscn new file mode 100644 index 0000000..c0c9104 --- /dev/null +++ b/scenes/classes/hit.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://d04legmj3c42r"] + +[ext_resource type="Script" uid="uid://b440ri3521uio" path="res://scenes/classes/hit.gd" id="1_udkr8"] + +[node name="Hit2D" type="Area2D" groups=["hit"]] +collision_layer = 64 +collision_mask = 0 +script = ExtResource("1_udkr8") + +[node name="Sprite2D" type="Sprite2D" parent="."] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +rotation = 1.5707964 +debug_color = Color(0.6205005, 0.42311785, 0.8432588, 0.41960785) diff --git a/scenes/classes/state.gd b/scenes/classes/state.gd index 9648ee7..20ba1f1 100644 --- a/scenes/classes/state.gd +++ b/scenes/classes/state.gd @@ -4,7 +4,7 @@ ## Emitted when the state finishes and wants to transition to another state. @warning_ignore("unused_signal") -signal finished(next_state_path: String, data: Dictionary[String, Variant]) +signal finished(next_state_path: String, data: Dictionary) ## For 'ghost frame' update fix diff --git a/scenes/hit/power_hit.tscn b/scenes/hit/power_hit.tscn index 871fd11..ff49b0c 100644 --- a/scenes/hit/power_hit.tscn +++ b/scenes/hit/power_hit.tscn @@ -1,13 +1,17 @@ -[gd_scene load_steps=3 format=3 uid="uid://d0c57c37o75b0"] +[gd_scene load_steps=4 format=3 uid="uid://d0c57c37o75b0"] [ext_resource type="Texture2D" uid="uid://d3qem75thpant" path="res://scenes/hit/assets/power_hit.png" id="1_g005q"] +[ext_resource type="Script" uid="uid://b440ri3521uio" path="res://scenes/classes/hit.gd" id="1_kwsj5"] [sub_resource type="CircleShape2D" id="CircleShape2D_g005q"] radius = 21.023796 -[node name="Hit" type="Area2D" groups=["hit"]] +[node name="PowerHit2D" type="Area2D" groups=["hit"]] collision_layer = 64 collision_mask = 0 +script = ExtResource("1_kwsj5") +power_hit = true +metadata/_custom_type_script = "uid://b440ri3521uio" metadata/power_hit = true [node name="Sprite2D" type="Sprite2D" parent="."] diff --git a/scenes/hit/hit.tscn b/scenes/hit/small_hit.tscn similarity index 58% rename from scenes/hit/hit.tscn rename to scenes/hit/small_hit.tscn index 1aacafe..cf6a61f 100644 --- a/scenes/hit/hit.tscn +++ b/scenes/hit/small_hit.tscn @@ -1,19 +1,22 @@ -[gd_scene load_steps=3 format=3 uid="uid://px67runjx6ex"] +[gd_scene load_steps=4 format=3 uid="uid://px67runjx6ex"] -[ext_resource type="Texture2D" uid="uid://c6a62gvw7218s" path="res://scenes/hit/assets/hit.png" id="1_wjo4f"] +[ext_resource type="Script" uid="uid://b440ri3521uio" path="res://scenes/classes/hit.gd" id="1_dk3vy"] +[ext_resource type="Texture2D" uid="uid://c6a62gvw7218s" path="res://scenes/hit/assets/hit.png" id="1_rmj1p"] [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_fxeki"] radius = 11.0 height = 24.0 -[node name="Hit" type="Area2D" groups=["hit"]] +[node name="SmallHit2D" type="Area2D" groups=["hit"]] collision_layer = 64 collision_mask = 0 +script = ExtResource("1_dk3vy") +metadata/_custom_type_script = "uid://b440ri3521uio" metadata/power_hit = false [node name="Sprite2D" type="Sprite2D" parent="."] position = Vector2(7, -4) -texture = ExtResource("1_wjo4f") +texture = ExtResource("1_rmj1p") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] position = Vector2(4, 1) diff --git a/scenes/main.tscn b/scenes/main.tscn index b0d486b..a2cf892 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -20,14 +20,18 @@ metadata/_edit_lock_ = true [node name="Player1" parent="." instance=ExtResource("2_r0du0")] position = Vector2(141, 184) +id = 1 +max_speed = 90.0 [node name="Player2" parent="." instance=ExtResource("2_r0du0")] position = Vector2(167, 77) id = 2 +max_speed = 90.0 [node name="Ball" parent="." instance=ExtResource("3_r0du0")] position = Vector2(162, 115) launch_speed = Vector2(150, 300) +speed_mult = null [node name="LevelCamera2D" parent="." instance=ExtResource("4_jyhfs")] ball_weight = 0.4 diff --git a/scenes/player/player.gd b/scenes/player/player.gd index 1322a4a..53fd882 100644 --- a/scenes/player/player.gd +++ b/scenes/player/player.gd @@ -9,19 +9,36 @@ const DEADZONE: float = 0.1 @onready var anim_player: AnimationPlayer = $AnimationPlayer @onready var state_machine: StateMachine = $StateMachine @onready var hit_timer: Timer = $HitTimer +@onready var power_timer: Timer = $PowerTimer var anim_dir: String var def_dir: String +var power_bar: float func _ready() -> void: assert(id == 1 or id == 2, "id ("+str(id)+") is invalid") anim_dir = 'up' if id == 1 else 'down' def_dir = 'idle_up' if id == 1 else 'idle_down' + power_bar = 0 hit_timer.connect("timeout", $StateMachine/Hitting._on_hit_end) + power_timer.connect("timeout", func() -> void: + print(power_bar) + ) func get_movement_vector() -> Vector2: var x_mov: float = Input.get_action_strength('p'+str(id)+'_move_right') - Input.get_action_strength('p'+str(id)+'_move_left') var y_mov: float = Input.get_action_strength('p'+str(id)+'_move_down') - Input.get_action_strength('p'+str(id)+'_move_up') return Vector2(x_mov, y_mov) + + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("p"+str(id)+"_power_hit") and power_bar == 100: + power_bar = 0 + state_machine.state.finished.emit(PlayerState.HITTING, {"hit": "power"}) + + +func _process(_delta: float) -> void: + if Input.is_action_pressed("p"+str(id)+"_hit_left") and Input.is_action_pressed("p"+str(id)+"_hit_right"): + if power_bar < 100: power_bar += 1 diff --git a/scenes/player/player.tscn b/scenes/player/player.tscn index f95e21d..beba800 100644 --- a/scenes/player/player.tscn +++ b/scenes/player/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=38 format=3 uid="uid://642nm6c4mpxx"] +[gd_scene load_steps=40 format=3 uid="uid://642nm6c4mpxx"] [ext_resource type="Script" uid="uid://rdqmsvofiots" path="res://scenes/player/player.gd" id="1_v6fml"] [ext_resource type="Texture2D" uid="uid://b8ptokcqwpdud" path="res://scenes/player/saffron.png" id="2_3li8b"] @@ -363,11 +363,69 @@ tracks/1/keys = { "values": [0] } +[sub_resource type="Animation" id="Animation_myrg7"] +resource_name = "hit_power_up" +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("AnimatedSprite2D:animation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [&"idle_up"] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("AnimatedSprite2D:frame") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [2] +} + +[sub_resource type="Animation" id="Animation_kvlxm"] +resource_name = "hit_power_down" +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("AnimatedSprite2D:animation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [&"idle_down"] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("AnimatedSprite2D:frame") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [2] +} + [sub_resource type="AnimationLibrary" id="AnimationLibrary_ugbui"] _data = { &"RESET": SubResource("Animation_gx1jg"), &"hit_left_down": SubResource("Animation_fcs02"), &"hit_left_up": SubResource("Animation_h17s1"), +&"hit_power_down": SubResource("Animation_kvlxm"), +&"hit_power_up": SubResource("Animation_myrg7"), &"hit_right_down": SubResource("Animation_ugbui"), &"hit_right_up": SubResource("Animation_je7p5"), &"idle_down": SubResource("Animation_fm80t"), @@ -585,3 +643,7 @@ metadata/_custom_type_script = "uid://b2sr7p80gdjii" [node name="HitTimer" type="Timer" parent="."] wait_time = 0.5 one_shot = true + +[node name="PowerTimer" type="Timer" parent="."] +wait_time = 0.847 +autostart = true diff --git a/scenes/player/states/hitting.gd b/scenes/player/states/hitting.gd index a5634c4..190bb57 100644 --- a/scenes/player/states/hitting.gd +++ b/scenes/player/states/hitting.gd @@ -6,12 +6,14 @@ var hit_type: String var hit_node_ref: WeakRef -func _enter(_previous_state_path: String, _data: Dictionary[String, Variant] = {}) -> void: +func _enter(_previous_state_path: String, _data: Dictionary = {}) -> void: print("entering hitting") player.velocity = Vector2.ZERO player.move_and_slide() assert( - _data.get("hit") == "left" or _data.get("hit") == "right", + _data.get("hit") == "left" or + _data.get("hit") == "right" or + _data.get("hit") == "power", "Invalid hit_type ("+str(_data.get("hit"))+")." ) print("previous state path: "+_previous_state_path) @@ -28,28 +30,41 @@ func _enter(_previous_state_path: String, _data: Dictionary[String, Variant] = { player.anim_player.current_animation == "hit_left_up" or player.anim_player.current_animation == "hit_left_down" or player.anim_player.current_animation == "hit_right_up" or - player.anim_player.current_animation == "hit_right_down", + player.anim_player.current_animation == "hit_right_down" or + player.anim_player.current_animation == "hit_power_up" or + player.anim_player.current_animation == "hit_power_down", "invalid animation ("+player.anim_player.current_animation+")" ) - var sprite_texture: Texture2D = player.sprite.sprite_frames.get_frame_texture('up', 1) - var hit_node: Area2D = preload("res://scenes/hit/hit.tscn").instantiate() + var sprite_texture: Texture2D = player.sprite.sprite_frames.get_frame_texture(player.sprite.animation, 1) + var hit_node: Hit - # flip entire node horizontally if spawning left - # flip vertically if player 2 - hit_node.scale = Vector2( - -1 if hit_type == "left" else 1, - 1 if player.id == 1 else -1 - ) - player.add_child(hit_node) - - # set position according to frame width and height - @warning_ignore("integer_division") - hit_node.global_position = player.global_position + Vector2( - (sprite_texture.get_width()/2)*(-1 if hit_type == "left" else 1), - (sprite_texture.get_height()*-1) if player.id == 1 else 4 - ) + if hit_type == "left" or hit_type == "right": + hit_node = preload("res://scenes/hit/small_hit.tscn").instantiate() + # flip entire node horizontally if spawning left + # flip vertically if player 2 + hit_node.scale = Vector2( + -1 if hit_type == "left" else 1, + 1 if player.id == 1 else -1 + ) + + player.add_child(hit_node) + + # set position according to frame width and height + @warning_ignore("integer_division") + hit_node.global_position = player.global_position + Vector2( + (sprite_texture.get_width()/2)*(-1 if hit_type == "left" else 1), + (sprite_texture.get_height()*-1) if player.id == 1 else 4 + ) + elif hit_type == "power": + hit_node = preload("res://scenes/hit/power_hit.tscn").instantiate() + + player.add_child(hit_node) + + @warning_ignore("integer_division") + hit_node.global_position = player.global_position + Vector2(0,(sprite_texture.get_height()/2)*-1) + assert(hit_node != null, "hit_node is null") hit_node_ref = weakref(hit_node) # cooldown @@ -59,7 +74,7 @@ func _on_hit_end() -> void: print("cooldown end") assert(hit_node_ref != null, "hit node is null") - var hit_node: Area2D = hit_node_ref.get_ref() + var hit_node: Hit = hit_node_ref.get_ref() if hit_node and is_instance_valid(hit_node): hit_node.queue_free() @@ -72,11 +87,11 @@ func _on_hit_end() -> void: print("transitioning to idling") finished.emit(IDLE) return - elif Input.is_action_pressed('p'+str(player.id)+'_hit_left'): + elif Input.is_action_just_pressed('p'+str(player.id)+'_hit_left'): print("transitioning to hitting left") finished.emit(HITTING, {"hit": "left"}) return - elif Input.is_action_pressed('p'+str(player.id)+'_hit_right'): + elif Input.is_action_just_pressed('p'+str(player.id)+'_hit_right'): print("transitioning to hitting right") finished.emit(HITTING, {"hit": "right"}) return diff --git a/scenes/player/states/idle.gd b/scenes/player/states/idle.gd index 991de77..8bfc98c 100644 --- a/scenes/player/states/idle.gd +++ b/scenes/player/states/idle.gd @@ -1,7 +1,7 @@ extends PlayerState -func _enter(_previous_state_path: String, _data: Dictionary[String, Variant] = {}) -> void: +func _enter(_previous_state_path: String, _data: Dictionary = {}) -> void: print("entering idle") player.velocity = Vector2.ZERO player.move_and_slide() @@ -21,9 +21,9 @@ func _state_physics_update(_delta: float) -> void: if movement_vector.length() > player.DEADZONE: finished.emit(RUNNING) return - elif Input.is_action_pressed('p'+str(player.id)+'_hit_left'): + elif Input.is_action_just_pressed('p'+str(player.id)+'_hit_left'): finished.emit(HITTING, {"hit": "left"}) return - elif Input.is_action_pressed('p'+str(player.id)+'_hit_right'): + elif Input.is_action_just_pressed('p'+str(player.id)+'_hit_right'): finished.emit(HITTING, {"hit": "right"}) return diff --git a/scenes/player/states/running.gd b/scenes/player/states/running.gd index 68f9261..d32bf82 100644 --- a/scenes/player/states/running.gd +++ b/scenes/player/states/running.gd @@ -1,7 +1,7 @@ extends PlayerState -func _enter(_previous_state_path: String, _data: Dictionary[String, Variant] = {}) -> void: +func _enter(_previous_state_path: String, _data: Dictionary = {}) -> void: print("entering running") player.move_and_slide()