diff --git a/asset_dev/particles/plasma.xcf b/asset_dev/particles/plasma.xcf new file mode 100644 index 0000000..3f2cd1f Binary files /dev/null and b/asset_dev/particles/plasma.xcf differ diff --git a/assets/sprites/particles/plasma.png b/assets/sprites/particles/plasma.png new file mode 100644 index 0000000..bad026a --- /dev/null +++ b/assets/sprites/particles/plasma.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3312dc25a68246b8e7015132e895f4f0079f976fe8dc7dd9b003681dd3d408b +size 11900 diff --git a/assets/sprites/particles/plasma.png.import b/assets/sprites/particles/plasma.png.import new file mode 100644 index 0000000..6ad49d1 --- /dev/null +++ b/assets/sprites/particles/plasma.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c47bkx508biqr" +path.s3tc="res://.godot/imported/plasma.png-0d62f2e4ae083d296fe25ccf3ab68e80.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/sprites/particles/plasma.png" +dest_files=["res://.godot/imported/plasma.png-0d62f2e4ae083d296fe25ccf3ab68e80.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +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=0 diff --git a/src/equipment/balls/physics_ball/game_ball.gd b/src/equipment/balls/physics_ball/game_ball.gd index c99ab43..a3ab2e4 100644 --- a/src/equipment/balls/physics_ball/game_ball.gd +++ b/src/equipment/balls/physics_ball/game_ball.gd @@ -25,6 +25,7 @@ const IRON_DAMPING := 9999.0 ## Base damage inflicted on impact with a player @export var base_damage := 15.0 +var last_contact_normal: Vector3 var _zones: Array[BallZone] = [] @onready var normal_physics: PhysicsMaterial = preload( @@ -34,6 +35,8 @@ var _zones: Array[BallZone] = [] "res://src/equipment/balls/physics_ball/iron_physics.tres" ) +@onready var _debug_draw: Control = %DebugDraw + ## Called by a water area when this ball enters it func enter_water() -> void: @@ -54,6 +57,8 @@ func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: damping = _total_terrain_angular_damping() if damping <= TERRAIN_DAMPING_EPSILON: damping = rough_damping + last_contact_normal = state.get_contact_local_normal(0) + _debug_draw.queue_redraw() angular_damp = damping diff --git a/src/equipment/balls/physics_ball/physics_ball.tscn b/src/equipment/balls/physics_ball/physics_ball.tscn index f026442..ea64613 100644 --- a/src/equipment/balls/physics_ball/physics_ball.tscn +++ b/src/equipment/balls/physics_ball/physics_ball.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://dfttci386ohip"] +[gd_scene load_steps=9 format=3 uid="uid://dfttci386ohip"] [ext_resource type="Script" path="res://src/equipment/balls/physics_ball/game_ball.gd" id="1_iwh2u"] [ext_resource type="PhysicsMaterial" uid="uid://3bih72l068ic" path="res://src/equipment/balls/physics_ball/normal_physics.tres" id="1_l23pw"] @@ -27,9 +27,27 @@ rings = 6 [sub_resource type="SphereShape3D" id="SphereShape3D_0hvq6"] radius = 0.05 +[sub_resource type="GDScript" id="GDScript_p4v7o"] +resource_name = "debug_draw" +script/source = "extends Control + +const COLOR := Color(0, 1, 0) +const WIDTH := 4 + +@onready var physics_ball: GameBall = $\"..\" + +func _draw() -> void: + if physics_ball.last_contact_normal != null: + var camera := get_viewport().get_camera_3d() + var start := camera.unproject_position(physics_ball.global_transform.origin) + var end := camera.unproject_position(physics_ball.global_transform.origin + physics_ball.last_contact_normal) + draw_line(start, end, COLOR, WIDTH) +" + [node name="PhysicsBall" type="RigidBody3D"] mass = 0.05 physics_material_override = ExtResource("1_l23pw") +freeze = true continuous_cd = true contact_monitor = true max_contacts_reported = 1 @@ -44,3 +62,14 @@ mesh = SubResource("SphereMesh_y0d13") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] shape = SubResource("SphereShape3D_0hvq6") + +[node name="DebugDraw" type="Control" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = SubResource("GDScript_p4v7o") diff --git a/src/equipment/balls/plasma_ball/plasma_ball.gd b/src/equipment/balls/plasma_ball/plasma_ball.gd new file mode 100644 index 0000000..50316e0 --- /dev/null +++ b/src/equipment/balls/plasma_ball/plasma_ball.gd @@ -0,0 +1,16 @@ +extends GameBall +## The plasma ball sticks to the first surface it hits + +@onready var manual_sleep_timer: Timer = %ManualSleepTimer + + +func _on_body_entered(_body: Node) -> void: + print_debug("Plasma ball stuck to ", _body) + # Freeze physics as soon as we hit something + freeze = true + sleeping = true + manual_sleep_timer.start() + + +func _fire_sleep_signal() -> void: + sleeping_state_changed.emit() diff --git a/src/equipment/balls/plasma_ball/plasma_ball.tscn b/src/equipment/balls/plasma_ball/plasma_ball.tscn new file mode 100644 index 0000000..a0ae8de --- /dev/null +++ b/src/equipment/balls/plasma_ball/plasma_ball.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=11 format=3 uid="uid://dcqxlbsrubapk"] + +[ext_resource type="PackedScene" uid="uid://dfttci386ohip" path="res://src/equipment/balls/physics_ball/physics_ball.tscn" id="1_yh4fp"] +[ext_resource type="Texture2D" uid="uid://c47bkx508biqr" path="res://assets/sprites/particles/plasma.png" id="2_8fdyx"] +[ext_resource type="Script" path="res://src/equipment/balls/plasma_ball/plasma_ball.gd" id="2_pdts3"] + +[sub_resource type="Curve" id="Curve_kabhn"] +max_value = 2.0 +_data = [Vector2(0, 2), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="CurveTexture" id="CurveTexture_oxhrr"] +curve = SubResource("Curve_kabhn") + +[sub_resource type="Curve" id="Curve_77lhu"] +_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.249135, 1), 0.0, 0.0, 0, 0, Vector2(1, 0.598878), -0.922125, -0.922125, 0, 0] +point_count = 3 + +[sub_resource type="CurveTexture" id="CurveTexture_vfusk"] +curve = SubResource("Curve_77lhu") + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_uffe8"] +angle_min = -720.0 +angle_max = 720.0 +direction = Vector3(0, 1, 0) +spread = 10.0 +initial_velocity_max = 0.1 +gravity = Vector3(0, 2, 0) +scale_curve = SubResource("CurveTexture_vfusk") +emission_curve = SubResource("CurveTexture_oxhrr") + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7ptri"] +transparency = 1 +vertex_color_use_as_albedo = true +albedo_color = Color(0.48, 1, 1, 0.384314) +albedo_texture = ExtResource("2_8fdyx") +emission_enabled = true +emission = Color(0.42, 1, 1, 1) +billboard_mode = 3 +billboard_keep_scale = true +particles_anim_h_frames = 1 +particles_anim_v_frames = 1 +particles_anim_loop = false +grow_amount = -0.6 + +[sub_resource type="QuadMesh" id="QuadMesh_go8iw"] +material = SubResource("StandardMaterial3D_7ptri") +size = Vector2(0.4, 0.4) + +[node name="PlasmaBall" instance=ExtResource("1_yh4fp")] +script = ExtResource("2_pdts3") + +[node name="PlasmaFireEffect" type="GPUParticles3D" parent="BallMesh" index="0"] +amount = 20 +lifetime = 0.6 +process_material = SubResource("ParticleProcessMaterial_uffe8") +draw_pass_1 = SubResource("QuadMesh_go8iw") + +[node name="ManualSleepTimer" type="Timer" parent="." index="3"] +unique_name_in_owner = true +one_shot = true + +[connection signal="body_entered" from="." to="." method="_on_body_entered"] +[connection signal="timeout" from="ManualSleepTimer" to="." method="_fire_sleep_signal"] diff --git a/src/player/shot_setup/shot_setup.gd b/src/player/shot_setup/shot_setup.gd index 72957b4..8cad5e1 100644 --- a/src/player/shot_setup/shot_setup.gd +++ b/src/player/shot_setup/shot_setup.gd @@ -22,6 +22,7 @@ const ZOOM_MAX := 12.0 const ARROW_ACCELERATION := 8.0 const PLAYER_ACCELERATION := 2.0 +const FREE_CAM_GIMBAL_TWEEN_TIME := 0.2 const FREE_CAM_RETURN_TIME := 0.618 const BALL_RETURN_TIME := 0.618 const CAMERA_SNAP_TIME := 0.3 @@ -45,8 +46,6 @@ const WATER_DAMAGE := 10.0 @export var base_curve := 0.0 @export_category("Debug") -## When enabled, the game will pause and enter free cam mode when the ball has a collision. -@export var debug_ball_impact := false ## When enabled, ignore curve meter and hit a perfect shot every time. @export var perfect_aim := false ## Keep projection visible @@ -125,7 +124,8 @@ var _tracking_camera: OrbitalCamera @onready var shot_projection: ProjectileArc = %ShotProjection @onready var ball_point: Node3D = %BallPoint -@onready var physics_ball: GameBall = %PhysicsBall +# @onready var game_ball: GameBall = %PhysicsBall +@onready var game_ball: GameBall = %PlasmaBall @onready var drive_ref: RayCast3D = %DriveRef @onready var drive_arrow: Node3D = %DriveArrow @@ -143,8 +143,6 @@ var _tracking_camera: OrbitalCamera @onready var camera_distance := zoom.position.z: set = _set_camera_distance -@onready var phys_ball_scene := preload("res://src/equipment/balls/physics_ball/physics_ball.tscn") - @onready var world: World = get_tree().get_first_node_in_group(World.group) @onready var game: Game = get_tree().get_first_node_in_group(Game.group) @@ -229,15 +227,15 @@ func take_shot() -> void: Transform3D.IDENTITY.scaled(Vector3.ONE * impulse.length()).looking_at(impulse) ) - physics_ball.freeze = false - physics_ball.apply_central_impulse(impulse) + game_ball.freeze = false + game_ball.apply_central_impulse(impulse) ## Make the shot projection widget visible, with animated transition func _show_shot_projection() -> void: shot_projection.initial_speed = 1 shot_projection.basis = shot_ref.basis.orthonormalized() - var shot_speed := get_shot_impulse(1.0).length() / physics_ball.mass + var shot_speed := get_shot_impulse(1.0).length() / game_ball.mass var tween := get_tree().create_tween() tween.tween_property(shot_projection, "initial_speed", shot_speed, CAMERA_SNAP_TIME).set_trans( Tween.TRANS_QUAD @@ -251,6 +249,9 @@ func insert_free_cam() -> void: hud.hide_hud() _free_camera = FreeCamera.create(camera) add_sibling(_free_camera) + # Un-gimbal-lock ourselves: quickly tween Z rotation to 0 + var tween := get_tree().create_tween() + tween.tween_property(_free_camera, "rotation:z", 0, FREE_CAM_GIMBAL_TWEEN_TIME) control_disabled = true camera.current = false @@ -271,12 +272,12 @@ func return_free_cam() -> void: func return_ball() -> void: - physics_ball.freeze = true + game_ball.freeze = true var tween := get_tree().create_tween() ( tween . tween_property( - physics_ball, + game_ball, "global_transform", ball_point.global_transform, BALL_RETURN_TIME, @@ -287,14 +288,26 @@ func return_ball() -> void: func travel_to_ball() -> void: - physics_ball.freeze = true - global_position = physics_ball.global_position - physics_ball.global_transform = ball_point.global_transform + game_ball.freeze = true + global_position = game_ball.global_position + + # Re-orient to the ball's last contact normal if there is one. + # Normally this will just be Vector3.UP or something close to it. + var normal := game_ball.last_contact_normal + if normal == null: + normal = Vector3.UP + var up := Vector3.BACK + if not normal.cross(up): + up = Vector3.RIGHT + look_at(global_position + normal, up) + rotate_object_local(Vector3.RIGHT, -PI / 2) + + game_ball.global_transform = ball_point.global_transform func start_shot_track() -> void: if phase == Phase.SHOT: - _tracking_camera = OrbitalCamera.create(physics_ball) + _tracking_camera = OrbitalCamera.create(game_ball) _tracking_camera.rotation.y = randf_range(0.0, TAU) add_sibling(_tracking_camera) _tracking_camera.global_transform = ball_point.global_transform @@ -322,7 +335,7 @@ func _on_club_change(new_club_type: Club.Type) -> void: wedge_arrow.hide() iron_arrow.hide() putt_arrow.hide() - physics_ball.iron_ball = false + game_ball.iron_ball = false hud.club_selector.value = new_club_type # TODO club change animation character.hold_right(new_club.get_model()) @@ -339,7 +352,7 @@ func _on_club_change(new_club_type: Club.Type) -> void: Club.Type.IRON: shot_ref = iron_ref iron_arrow.show() - physics_ball.iron_ball = true + game_ball.iron_ball = true Club.Type.SPECIAL: # TODO figure this out shot_ref = drive_ref @@ -488,33 +501,20 @@ func _process(delta: float) -> void: phase = Phase.AIM -func _on_physics_ball_sleeping_state_changed() -> void: - if physics_ball.sleeping and phase == Phase.SHOT: +func _on_ball_sleeping_state_changed() -> void: + if game_ball.sleeping and phase == Phase.SHOT: end_shot_track() func _on_ball_entered_water() -> void: # Should only be possible during SHOT phase, but let's check just to be sure... if phase == Phase.SHOT: - physics_ball.freeze = true + game_ball.freeze = true hud.play_wasted_animation() player.life -= WATER_DAMAGE ball_return_timer.start(WASTED_BALL_RETURN_DELAY) -func _on_physics_ball_body_entered(_body: Node) -> void: - print_debug("BONK!") - if debug_ball_impact: - get_tree().paused = true - var snap_point: Node3D = camera - if _tracking_camera: - snap_point = _tracking_camera - _free_camera = FreeCamera.create(snap_point) - add_sibling(_free_camera) - control_disabled = true - camera.current = false - - func _on_ball_return_timer_timeout() -> void: return_ball() diff --git a/src/player/shot_setup/shot_setup.tscn b/src/player/shot_setup/shot_setup.tscn index f9e5d2a..ad0ad5d 100644 --- a/src/player/shot_setup/shot_setup.tscn +++ b/src/player/shot_setup/shot_setup.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=19 format=3 uid="uid://cy7t2tc4y3b4"] +[gd_scene load_steps=20 format=3 uid="uid://cy7t2tc4y3b4"] [ext_resource type="Script" path="res://src/player/shot_setup/shot_setup.gd" id="1_r6ei4"] [ext_resource type="PackedScene" uid="uid://dfttci386ohip" path="res://src/equipment/balls/physics_ball/physics_ball.tscn" id="2_1i5j5"] [ext_resource type="PackedScene" uid="uid://c2k88ns0h5ie1" path="res://src/ui/3d/arrow/arrow.tscn" id="2_s70wl"] +[ext_resource type="PackedScene" uid="uid://dcqxlbsrubapk" path="res://src/equipment/balls/plasma_ball/plasma_ball.tscn" id="3_8dte7"] [ext_resource type="PackedScene" uid="uid://1s3gywmoi20e" path="res://src/characters/player_characters/gfolf_girl/gfolf_girl.tscn" id="3_e4aur"] [ext_resource type="PackedScene" uid="uid://fht6j87o8ecr" path="res://src/ui/3d/projectile_arc/projectile_arc.tscn" id="4_ry2ho"] [ext_resource type="PackedScene" uid="uid://dbdul15c4oblg" path="res://src/ui/3d/projected_target.tscn" id="6_mynqj"] @@ -216,8 +217,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.08, 0) [node name="PhysicsBall" parent="BallPoint" instance=ExtResource("2_1i5j5")] unique_name_in_owner = true +process_mode = 4 transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0) -freeze = true +visible = false + +[node name="PlasmaBall" parent="BallPoint" instance=ExtResource("3_8dte7")] +unique_name_in_owner = true [node name="BallImpulseDebug" type="Node3D" parent="BallPoint"] unique_name_in_owner = true @@ -369,14 +374,15 @@ one_shot = true [node name="Hitbox" type="Area3D" parent="." node_paths=PackedStringArray("ignored_balls")] script = ExtResource("7_uh8kn") -ignored_balls = [NodePath("../BallPoint/PhysicsBall")] +ignored_balls = [NodePath("../BallPoint/PhysicsBall"), NodePath("../BallPoint/PlasmaBall")] [node name="CollisionShape3D" type="CollisionShape3D" parent="Hitbox"] shape = SubResource("SphereShape3D_xvvdi") -[connection signal="body_entered" from="BallPoint/PhysicsBall" to="." method="_on_physics_ball_body_entered"] [connection signal="entered_water" from="BallPoint/PhysicsBall" to="." method="_on_ball_entered_water"] -[connection signal="sleeping_state_changed" from="BallPoint/PhysicsBall" to="." method="_on_physics_ball_sleeping_state_changed"] +[connection signal="sleeping_state_changed" from="BallPoint/PhysicsBall" to="." method="_on_ball_sleeping_state_changed"] +[connection signal="entered_water" from="BallPoint/PlasmaBall" to="." method="_on_ball_entered_water"] +[connection signal="sleeping_state_changed" from="BallPoint/PlasmaBall" to="." method="_on_ball_sleeping_state_changed"] [connection signal="timeout" from="BallReturnTimer" to="." method="_on_ball_return_timer_timeout"] [connection signal="ball_collision" from="Hitbox" to="." method="_on_hitbox_ball_collision"] [connection signal="body_entered" from="Hitbox" to="Hitbox" method="_on_body_entered"] diff --git a/src/ui/shot_hud/shot_hud.tscn b/src/ui/shot_hud/shot_hud.tscn index 73c1f07..6071088 100644 --- a/src/ui/shot_hud/shot_hud.tscn +++ b/src/ui/shot_hud/shot_hud.tscn @@ -691,9 +691,9 @@ anchors_preset = -1 anchor_top = 1.0 anchor_right = 0.333 anchor_bottom = 1.0 -offset_top = 20.0 +offset_top = 18.6812 offset_right = 40.0 -offset_bottom = 118.0 +offset_bottom = 116.681 grow_vertical = 0 theme_override_constants/margin_left = 16 theme_override_constants/margin_bottom = 16