diff --git a/Scenes/Dart.tscn b/Scenes/Dart.tscn index 62e1a53..d765ddb 100644 --- a/Scenes/Dart.tscn +++ b/Scenes/Dart.tscn @@ -1,3 +1,23 @@ [gd_scene format=3 uid="uid://bswbcehkkqbk0"] -[node name="Dart" type="Node3D"] +[ext_resource type="Script" uid="uid://8kk531f88oqw" path="res://Scenes/DartPhysics.gd" id="1_s1kro"] +[ext_resource type="PackedScene" uid="uid://cjq45anerhxnk" path="res://models/Dart.glb" id="2_mlvfh"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xxti7"] +radius = 0.27441406 +height = 2.3862305 + +[node name="Dart" type="Node3D" unique_id=367982453] + +[node name="RigidBody3D" type="RigidBody3D" parent="." unique_id=1993396351] +transform = Transform3D(1, 0, 0, 0, 1.0000001, 0, 0, 0, 1.0000001, 0, 0, 0) +center_of_mass_mode = 1 +script = ExtResource("1_s1kro") +angle_of_plane = -25.0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D" unique_id=1030334431] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.10033989, 0) +shape = SubResource("CapsuleShape3D_xxti7") + +[node name="Dart2" parent="RigidBody3D" unique_id=1655536183 instance=ExtResource("2_mlvfh")] +transform = Transform3D(0.4, 0, 0, 0, 0.39999995, 0, 0, 0, 0.39999995, 0, -1.4526575, 0.015561234) diff --git a/Scenes/DartPhysics.gd b/Scenes/DartPhysics.gd new file mode 100644 index 0000000..73563cc --- /dev/null +++ b/Scenes/DartPhysics.gd @@ -0,0 +1,161 @@ +extends RigidBody3D + +class_name Dart + +#states +var in_flight: bool = false +var dragging: bool = false + +#drag +var drag_plane: Plane +var drag_offset: Vector3 = Vector3.ZERO +var target_pos: Vector3 = Vector3.ZERO + +# velocity tracking for release throw +var position_history: Array[Vector3] = [] +var time_history: Array[float] = [] +const HISTORY_LENGTH = 6 + +# drag "stiffness" tuning +@export var drag_follow_speed: float = 20.0 # how fast it chases the mouse +@export var max_drag_speed: float = 42.0 # clamp so it doesn't go insane +@export_range(-180,180) var angle_of_plane: float = 15.0 +@export var throw_force_multiplier: float = 0.8 # scale raw throw velocity down if it's too fast +@export var max_throw_speed: float = 42.0 # hard cap so fast flicks don't kill the arc +@export var flight_gravity_scale: float = 4.0 # higher = more visible arc +@export var nose_align_speed: float = 1.0 # how fast dart rotates to face velocity +@export var dart_forward_axis: Vector3 = Vector3.FORWARD + +func _ready(): + freeze_mode = RigidBody3D.FREEZE_MODE_STATIC + freeze = true + +func start_drag(camera: Camera3D, mouse_pos: Vector2, hit_point: Vector3): + dragging = true + in_flight = false + freeze = false + + # keep it physics-active — don't freeze + gravity_scale = 0.0 # optional: ignore gravity while held so it doesn't sag + linear_damp = 5.0 # optional: a bit of damping for stability while dragged + angular_damp = 10.0 + + #drag_plane = Plane(camera.global_transform.basis.z, hit_point) + var base_normal = camera.global_transform.basis.z + var tilt = base_normal.rotated(camera.global_transform.basis.x, deg_to_rad(angle_of_plane)) # tilt adjustable around camera's right axis + drag_plane = Plane(tilt.normalized(), hit_point) + drag_offset = global_position - hit_point + + target_pos = global_position #fixes a bug where dart drops until mouse is moved. + + position_history.clear() + time_history.clear() + +func update_drag(camera: Camera3D, mouse_pos: Vector2): + if not dragging: + return + + var ray_origin = camera.project_ray_origin(mouse_pos) + var ray_dir = camera.project_ray_normal(mouse_pos) + + var intersection = drag_plane.intersects_ray(ray_origin, ray_dir) + if intersection: + target_pos = intersection + drag_offset + +func _align_dart_to_velocity(delta: float): + if linear_velocity.length() < 0.5: + return + + var vel_dir = linear_velocity.normalized() + var current_forward = global_transform.basis * dart_forward_axis + var rotation_axis = current_forward.cross(vel_dir) + + if rotation_axis.length() < 0.001: + return + + rotation_axis = rotation_axis.normalized() + var angle = current_forward.angle_to(vel_dir) + var rotate_amount = min(angle, nose_align_speed * delta) + + global_transform.basis = global_transform.basis.rotated(rotation_axis, rotate_amount) + global_transform.basis = global_transform.basis.orthonormalized() + +func _physics_process(delta): + if dragging: + # chase the target position with velocity instead of teleporting + var to_target = target_pos - global_position + var desired_velocity = to_target * drag_follow_speed + + if desired_velocity.length() > max_drag_speed: + desired_velocity = desired_velocity.normalized() * max_drag_speed + + linear_velocity = desired_velocity + angular_velocity = angular_velocity.lerp(Vector3.ZERO, 0.2) # optional: damp spin while dragging + + # track actual position for momentum calc on release + position_history.append(global_position) + time_history.append(Time.get_ticks_msec() / 1000.0) + if position_history.size() > HISTORY_LENGTH: + position_history.pop_front() + time_history.pop_front() + elif in_flight: + # Debug: confirm Y velocity is dropping (becoming negative) each frame + print("local +X world dir: ", global_transform.basis.x) + print("local +Y world dir: ", global_transform.basis.y) + print("local +Z world dir: ", global_transform.basis.z) + print("velocity dir: ", linear_velocity.normalized()) + + # Align dart nose to face velocity direction (tip points the way it's flying) + if linear_velocity.length() > 0.5: + _align_dart_to_velocity(delta) + else: + in_flight = false + angular_velocity = Vector3.ZERO + # If your dart tip points along +Z, use global_position + linear_velocity + # If it points along -Z, negate the direction below + var look_target = global_position + linear_velocity.normalized() + var up_ref = Vector3.UP + + # Avoid gimbal lock when flying straight up/down + if abs(linear_velocity.normalized().dot(Vector3.UP)) > 0.99: + up_ref = Vector3.FORWARD + + var target_basis = Basis.looking_at(linear_velocity.normalized(), up_ref) + global_transform.basis = global_transform.basis.slerp( + target_basis, nose_align_speed * delta + ) + +func release_drag(): + if not dragging: + return + dragging = false + in_flight = true + + gravity_scale = flight_gravity_scale + angular_damp = 0.0 + linear_damp = 0.0 # reset to whatever your normal dart damping is + + var velocity = calculate_throw_velocity() * throw_force_multiplier + if velocity.length() > max_throw_speed: + velocity = velocity.normalized() * max_throw_speed + linear_velocity = velocity + # angular_velocity left as-is from physics, or compute your own spin here + + print("throw speed: %.2f" % velocity.length()) + print("gravity_scale: ", gravity_scale) + print("linear_damp: ", linear_damp) + +func calculate_throw_velocity() -> Vector3: + if position_history.size() < 2: + return linear_velocity # fallback to whatever physics already had + + var start_pos = position_history[0] + var end_pos = position_history[position_history.size() - 1] + var start_time = time_history[0] + var end_time = time_history[time_history.size() - 1] + + var dt = end_time - start_time + if dt <= 0.0: + return linear_velocity + + return (end_pos - start_pos) / dt diff --git a/Scenes/DartPhysics.gd.uid b/Scenes/DartPhysics.gd.uid new file mode 100644 index 0000000..c6d8fdc --- /dev/null +++ b/Scenes/DartPhysics.gd.uid @@ -0,0 +1 @@ +uid://8kk531f88oqw diff --git a/Scenes/camera_3d.gd b/Scenes/camera_3d.gd new file mode 100644 index 0000000..c9c8ddf --- /dev/null +++ b/Scenes/camera_3d.gd @@ -0,0 +1,27 @@ +extends Camera3D + +var held_dart: Dart = null + +func _unhandled_input(event): + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: + if event.pressed: + try_grab_dart(event.position) + else: + if held_dart: + held_dart.release_drag() + held_dart = null + elif event is InputEventMouseMotion and held_dart: + held_dart.update_drag(self, event.position) + +func try_grab_dart(mouse_pos: Vector2): + var ray_origin = project_ray_origin(mouse_pos) + var ray_dir = project_ray_normal(mouse_pos) + var ray_end = ray_origin + ray_dir * 1000.0 + + var space_state = get_world_3d().direct_space_state + var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end) + var result = space_state.intersect_ray(query) + + if result and result.collider is Dart: + held_dart = result.collider + held_dart.start_drag(self, mouse_pos, result.position) diff --git a/Scenes/camera_3d.gd.uid b/Scenes/camera_3d.gd.uid new file mode 100644 index 0000000..7ad7d4b --- /dev/null +++ b/Scenes/camera_3d.gd.uid @@ -0,0 +1 @@ +uid://ds3uiri3oc05v diff --git a/Scenes/main.gd b/Scenes/main.gd new file mode 100644 index 0000000..7b1663a --- /dev/null +++ b/Scenes/main.gd @@ -0,0 +1,5 @@ +extends Node3D + +func _unhandled_input(event): + if event.is_action_pressed("restart"): + get_tree().reload_current_scene() diff --git a/Scenes/main.gd.uid b/Scenes/main.gd.uid new file mode 100644 index 0000000..cb2871d --- /dev/null +++ b/Scenes/main.gd.uid @@ -0,0 +1 @@ +uid://geqmah8kbko0 diff --git a/Scenes/main.tscn b/Scenes/main.tscn new file mode 100644 index 0000000..17c6be3 --- /dev/null +++ b/Scenes/main.tscn @@ -0,0 +1,40 @@ +[gd_scene format=3 uid="uid://doh7p84eej78c"] + +[ext_resource type="Script" uid="uid://geqmah8kbko0" path="res://Scenes/main.gd" id="1_8gbba"] +[ext_resource type="Script" uid="uid://ds3uiri3oc05v" path="res://Scenes/camera_3d.gd" id="1_bo1nx"] +[ext_resource type="PackedScene" uid="uid://bswbcehkkqbk0" path="res://Scenes/Dart.tscn" id="1_jjgbg"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_qu08v"] +size = Vector3(1, 0.01, 1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6q475"] +albedo_color = Color(0, 0.42745098, 0.05490196, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_xxti7"] +material = SubResource("StandardMaterial3D_6q475") +size = Vector3(1, 0.01, 1) + +[node name="Main" type="Node3D" unique_id=819165214] +script = ExtResource("1_8gbba") + +[node name="Camera3D" type="Camera3D" parent="." unique_id=1570426839] +transform = Transform3D(-1, 2.4096988e-08, -8.4036174e-08, 0, 0.9612618, 0.2756374, 8.742278e-08, 0.2756374, -0.9612618, 0, 5.0674033, -6.36445) +fov = 90.0 +script = ExtResource("1_bo1nx") + +[node name="Dart" parent="." unique_id=367982453 instance=ExtResource("1_jjgbg")] +transform = Transform3D(1, 0, 0, 0, 0.8363807, -0.5481493, 0, 0.5481492, 0.8363808, 0, 4.3246593, 0) + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1891469178] +transform = Transform3D(-0.789482, 0.24223976, -0.56394875, -0.56595325, 0.06825608, 0.821607, 0.23751883, 0.9678125, 0.083209395, -46.816067, 15.942318, -50.370865) + +[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=889230389] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 63.39778) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=229129629] +transform = Transform3D(100, 0, 0, 0, 100, 0, 0, 0, 100, 0, 0, 0) +shape = SubResource("BoxShape3D_qu08v") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D/CollisionShape3D" unique_id=849495197] +mesh = SubResource("BoxMesh_xxti7") +skeleton = NodePath("") diff --git a/Scripts/camera_3d.gd b/Scripts/camera_3d.gd new file mode 100644 index 0000000..6052b06 --- /dev/null +++ b/Scripts/camera_3d.gd @@ -0,0 +1,15 @@ +extends Camera3D + +const RAY_LENGTH = 1000 + +func ray_cast(): + var space_state = get_world_3d().direct_space_state + var cam = $"." + var mousepos = get_viewport().get_mouse_position() + var origin = cam.project_ray_origin(mousepos) + var end = origin + cam.project_ray_normal(mousepos) * RAY_LENGTH + var query = PhysicsRayQueryParameters3D.create(origin,end) + query.collide_with_areas = true + var result = space_state.intersect_ray(query) + result = result.get("collider") + return result diff --git a/Scripts/camera_3d.gd.uid b/Scripts/camera_3d.gd.uid new file mode 100644 index 0000000..27e151f --- /dev/null +++ b/Scripts/camera_3d.gd.uid @@ -0,0 +1 @@ +uid://ckbdoamrm313u diff --git a/Scripts/dart.gd b/Scripts/dart.gd new file mode 100644 index 0000000..121daee --- /dev/null +++ b/Scripts/dart.gd @@ -0,0 +1,50 @@ +extends Node3D + +var draggingCollider +var mousePosition +var doDrag = false + +func _input(event): + var intersect + + if event is InputEventMouse: + intersect = get_mouse_intersect(event.position) + if intersect: mousePosition = intersect.position + #snap on collider + #if intersect: mousePosition = intersect.collider.global_position + + if event is InputEventMouseButton: + var leftButtonPressed = event.button_index == MOUSE_BUTTON_LEFT && event.pressed + var leftButtonReleased = event.button_index == MOUSE_BUTTON_LEFT && !event.pressed + + if leftButtonReleased: + doDrag = false + drag_and_drop(intersect) + elif leftButtonPressed: + doDrag = true + drag_and_drop(intersect) + + +func _process(delta): + if draggingCollider: + draggingCollider.global_position = mousePosition + +func drag_and_drop(intersect): + var canMove = intersect.collider in get_tree().get_nodes_in_group("Dart") + if !draggingCollider && doDrag && canMove: + draggingCollider = intersect.collider + elif draggingCollider: + draggingCollider = null + +func get_mouse_intersect(mousePosition): + var currentCamera = get_viewport().get_camera_3d() + var params = PhysicsRayQueryParameters3D.new() + + params.from = currentCamera.project_ray_origin(mousePosition) + params.to = currentCamera.project_position(mousePosition, 1000) + if draggingCollider: params.exclude = [draggingCollider] + + var worldspace = get_world_3d().direct_space_state + var result = worldspace.intersect_ray(params) + + return result diff --git a/Scripts/dart.gd.uid b/Scripts/dart.gd.uid new file mode 100644 index 0000000..3b13ace --- /dev/null +++ b/Scripts/dart.gd.uid @@ -0,0 +1 @@ +uid://sueo33rxr07e diff --git a/Scripts/mesh_instance_3d.gd b/Scripts/mesh_instance_3d.gd new file mode 100644 index 0000000..f4ebe81 --- /dev/null +++ b/Scripts/mesh_instance_3d.gd @@ -0,0 +1,16 @@ +extends MeshInstance3D + +@onready var camera = get_node("Camera3D") +@onready var area = get_node("../../../Dart") + +func _input(event): + if Input.is_action_just_pressed("leftClick"): + print(camera.ray_cast().position) + + if camera.ray_cast() != null and Input.is_action_pressed("midleclic"): + var mousepos = get_viewport().get_mouse_position() + var origin = camera.projectray_origin(mousepos) + var end = camera.project_ray_normal(mousepos) + var depth = origin.distence_to(area.global_position) + var final_position = origin + end * depth + area.global_position=final_position diff --git a/Scripts/mesh_instance_3d.gd.uid b/Scripts/mesh_instance_3d.gd.uid new file mode 100644 index 0000000..5bd89f0 --- /dev/null +++ b/Scripts/mesh_instance_3d.gd.uid @@ -0,0 +1 @@ +uid://cc5yy1yc3j2uu diff --git a/models/Dart.glb b/models/Dart.glb new file mode 100644 index 0000000..30d5418 Binary files /dev/null and b/models/Dart.glb differ diff --git a/models/Dart.glb.import b/models/Dart.glb.import new file mode 100644 index 0000000..f8bf431 --- /dev/null +++ b/models/Dart.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://cjq45anerhxnk" +path="res://.godot/imported/Dart.glb-e68d17366fac688e4c81cf4cc62e1175.scn" + +[deps] + +source_file="res://models/Dart.glb" +dest_files=["res://.godot/imported/Dart.glb-e68d17366fac688e4c81cf4cc62e1175.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/models/Dart_Dart-Default.png b/models/Dart_Dart-Default.png new file mode 100644 index 0000000..bfbaad5 Binary files /dev/null and b/models/Dart_Dart-Default.png differ diff --git a/models/Dart_Dart-Default.png.import b/models/Dart_Dart-Default.png.import new file mode 100644 index 0000000..19becea --- /dev/null +++ b/models/Dart_Dart-Default.png.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://62njxkpga6e8" +path.s3tc="res://.godot/imported/Dart_Dart-Default.png-ffb5dce2e6bbf58fe2b00949ee100702.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} +generator_parameters={ +"md5": "9c4718c8d2830abe8cc9971ef621b46a" +} + +[deps] + +source_file="res://models/Dart_Dart-Default.png" +dest_files=["res://.godot/imported/Dart_Dart-Default.png-ffb5dce2e6bbf58fe2b00949ee100702.s3tc.ctex"] + +[params] + +compress/mode=2 +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=true +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=0 diff --git a/models/Dart_Wings-Default.png b/models/Dart_Wings-Default.png new file mode 100644 index 0000000..6c07cdc Binary files /dev/null and b/models/Dart_Wings-Default.png differ diff --git a/models/Dart_Wings-Default.png.import b/models/Dart_Wings-Default.png.import new file mode 100644 index 0000000..9f407bf --- /dev/null +++ b/models/Dart_Wings-Default.png.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7bsi4ury2lve" +path.s3tc="res://.godot/imported/Dart_Wings-Default.png-dcf030c3d00e4d9573d0a3e9c88fff0a.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} +generator_parameters={ +"md5": "52ee9a89e7c6ed5f537103a41115551e" +} + +[deps] + +source_file="res://models/Dart_Wings-Default.png" +dest_files=["res://.godot/imported/Dart_Wings-Default.png-dcf030c3d00e4d9573d0a3e9c88fff0a.s3tc.ctex"] + +[params] + +compress/mode=2 +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=true +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=0 diff --git a/project.godot b/project.godot index d43d6b5..0dde64d 100644 --- a/project.godot +++ b/project.godot @@ -23,10 +23,33 @@ config/icon="res://icon.svg" ModifierCatalog="*uid://btm4dwci50xmu" +[display] + +window/stretch/mode="viewport" +window/stretch/aspect="expand" + [filesystem] import/blender/enabled=false +[global_group] + +Dart="All throwable darts" + +[input] + +leftClick={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} +restart={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + [rendering] renderer/rendering_method="mobile"