From 1dadffeac479eb31f38481a1d864d96c0cc504c2 Mon Sep 17 00:00:00 2001 From: Tabby <41929769+tabby-cat-nya@users.noreply.github.com> Date: Sun, 29 Jun 2025 16:25:51 +1000 Subject: [PATCH] its coming together --- 2dWorld.tscn | 77 ++++++++--- .../README.md | 9 ++ .../icon.png | Bin 0 -> 282 bytes .../icon.png.import | 34 +++++ .../plugin.cfg | 6 + .../plugin.gd | 9 ++ .../plugin.gd.uid | 1 + .../td_camera_2d.gd | 129 ++++++++++++++++++ .../td_camera_2d.gd.uid | 1 + cyclone.gd | 35 ++++- project.godot | 17 ++- 11 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 addons/Fantail-Interactive.top_down_camera/README.md create mode 100644 addons/Fantail-Interactive.top_down_camera/icon.png create mode 100644 addons/Fantail-Interactive.top_down_camera/icon.png.import create mode 100644 addons/Fantail-Interactive.top_down_camera/plugin.cfg create mode 100644 addons/Fantail-Interactive.top_down_camera/plugin.gd create mode 100644 addons/Fantail-Interactive.top_down_camera/plugin.gd.uid create mode 100644 addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd create mode 100644 addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd.uid diff --git a/2dWorld.tscn b/2dWorld.tscn index 1c6d174..cbe3436 100644 --- a/2dWorld.tscn +++ b/2dWorld.tscn @@ -1,27 +1,44 @@ -[gd_scene load_steps=4 format=3 uid="uid://biu528mgexdyp"] +[gd_scene load_steps=7 format=3 uid="uid://biu528mgexdyp"] [ext_resource type="Texture2D" uid="uid://cdw1vlidvg2yt" path="res://Sprites/Cyclone.png" id="1_1wkcu"] [ext_resource type="Texture2D" uid="uid://b48ofysofsffi" path="res://icon.svg" id="1_2uw02"] [ext_resource type="Script" uid="uid://u353j4q7l26d" path="res://cyclone.gd" id="1_xr6w1"] +[ext_resource type="Script" uid="uid://b1w10iwwhm5bj" path="res://addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd" id="4_u230a"] + +[sub_resource type="CircleShape2D" id="CircleShape2D_xr6w1"] +radius = 32.0 + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_xr6w1"] +size = Vector2(674, 28) [node name="2dWorld" type="Node2D"] [node name="WorldMap" type="Sprite2D" parent="."] +modulate = Color(1.8049e-07, 0.391665, 0.150329, 1) position = Vector2(589, 319) scale = Vector2(5, 5) texture = ExtResource("1_2uw02") -[node name="Cyclone" type="Node2D" parent="." node_paths=PackedStringArray("sprite", "label", "wind_speed_label")] +[node name="Cyclone" type="RigidBody2D" parent="." node_paths=PackedStringArray("sprite", "label", "wind_speed_label", "line")] position = Vector2(572, 229) +gravity_scale = 0.0 +lock_rotation = true script = ExtResource("1_xr6w1") sprite = NodePath("Sprite2D") label = NodePath("cLabel") wind_speed_label = NodePath("wsLabel") +line = NodePath("Line2D") [node name="Sprite2D" type="Sprite2D" parent="Cyclone"] scale = Vector2(0.2, 0.2) texture = ExtResource("1_1wkcu") +[node name="Line2D" type="Line2D" parent="Cyclone"] +position = Vector2(-572, -229) +width = 2.0 +default_color = Color(0.656747, 0, 0.0830602, 1) +joint_mode = 2 + [node name="cLabel" type="Label" parent="Cyclone"] offset_left = -21.0 offset_top = -11.0 @@ -42,32 +59,50 @@ text = "1" horizontal_alignment = 1 vertical_alignment = 1 -[node name="Timer" type="Timer" parent="Cyclone"] +[node name="changeTimer" type="Timer" parent="Cyclone"] wait_time = 5.0 autostart = true +[node name="CollisionShape2D" type="CollisionShape2D" parent="Cyclone"] +shape = SubResource("CircleShape2D_xr6w1") + +[node name="newPointTimer" type="Timer" parent="Cyclone"] +wait_time = 0.5 +autostart = true + [node name="Background" type="CanvasLayer" parent="."] layer = -1 -[node name="UI" type="Control" parent="Background"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 +[node name="wall" type="StaticBody2D" parent="."] +position = Vector2(581, -12) -[node name="VBoxContainer" type="VBoxContainer" parent="Background/UI"] -layout_mode = 0 -offset_right = 40.0 -offset_bottom = 40.0 +[node name="CollisionShape2D" type="CollisionShape2D" parent="wall"] +shape = SubResource("RectangleShape2D_xr6w1") -[node name="LoadImage" type="Button" parent="Background/UI/VBoxContainer"] -layout_mode = 2 -text = "Load Image" +[node name="wall2" type="StaticBody2D" parent="."] +position = Vector2(592, 647) -[node name="Spawn" type="Button" parent="Background/UI/VBoxContainer"] -layout_mode = 2 -text = "Spawn Cyclone" +[node name="CollisionShape2D" type="CollisionShape2D" parent="wall2"] +shape = SubResource("RectangleShape2D_xr6w1") -[connection signal="timeout" from="Cyclone/Timer" to="Cyclone" method="_on_timer_timeout"] +[node name="wall3" type="StaticBody2D" parent="."] +position = Vector2(259, 317) +rotation = 1.5708 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="wall3"] +shape = SubResource("RectangleShape2D_xr6w1") + +[node name="wall4" type="StaticBody2D" parent="."] +position = Vector2(909, 326) +rotation = 1.5708 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="wall4"] +shape = SubResource("RectangleShape2D_xr6w1") + +[node name="TDCamera2D" type="Camera2D" parent="."] +position = Vector2(579, 330) +script = ExtResource("4_u230a") +metadata/_custom_type_script = "uid://b1w10iwwhm5bj" + +[connection signal="timeout" from="Cyclone/changeTimer" to="Cyclone" method="_on_timer_timeout"] +[connection signal="timeout" from="Cyclone/newPointTimer" to="Cyclone" method="_on_new_point_timer_timeout"] diff --git a/addons/Fantail-Interactive.top_down_camera/README.md b/addons/Fantail-Interactive.top_down_camera/README.md new file mode 100644 index 0000000..69ef2d6 --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/README.md @@ -0,0 +1,9 @@ +# Top Down Camera +This addon aims to provide a general purposed 2D camera for use in top-down games where the mouse is used to move the pan and zoom the camera. Ideal for strategy/simulation games + +# Installation +Install from the AssetLib or download archive from [Github releases](https://github.com/TimCoraxAudio/top_down_camera/releases) and extract `addons` directory to +your project directory. Activate plugin in `Project` → `Project Settings` → `Plugins` + +# Usage +The node comes with (hopefully) plenty of built-in documentation. Please reach out if something is missing diff --git a/addons/Fantail-Interactive.top_down_camera/icon.png b/addons/Fantail-Interactive.top_down_camera/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3f29f26a952b276cee6b325ac3fabaca4b6c73c1 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}V1Q4EE0F&G|NqRH{~K2QpR^6c zn6&D1-?C4=OF#E6{n7(sfY?1tKKB9
4u`A6&Sk5yvj9-TfCoFT@o!8VI!v(g?d&{MOOb4>`6>dr{H sRH?LzEiK^8f*)DtPHRKBdydMR2X5k$Y&X2K7-%?yr>mdKI;Vst03Ka?5&!@I literal 0 HcmV?d00001 diff --git a/addons/Fantail-Interactive.top_down_camera/icon.png.import b/addons/Fantail-Interactive.top_down_camera/icon.png.import new file mode 100644 index 0000000..5e47fd2 --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://can5lubu4sv0o" +path="res://.godot/imported/icon.png-51d88dd58ca6134a790389f43b8f17f3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/Fantail-Interactive.top_down_camera/icon.png" +dest_files=["res://.godot/imported/icon.png-51d88dd58ca6134a790389f43b8f17f3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +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=1 diff --git a/addons/Fantail-Interactive.top_down_camera/plugin.cfg b/addons/Fantail-Interactive.top_down_camera/plugin.cfg new file mode 100644 index 0000000..6f1f2d9 --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/plugin.cfg @@ -0,0 +1,6 @@ +[plugin] +name="Top Down Camera" +description="A top-down 2D camera." +author="Fantail Interactive" +version="1.0" +script="plugin.gd" diff --git a/addons/Fantail-Interactive.top_down_camera/plugin.gd b/addons/Fantail-Interactive.top_down_camera/plugin.gd new file mode 100644 index 0000000..20533a0 --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/plugin.gd @@ -0,0 +1,9 @@ +@tool +extends EditorPlugin + + +func _enter_tree(): + add_custom_type("TDCamera2D", "Camera2D", preload("td_camera_2d.gd"), null) + +func _exit_tree(): + remove_custom_type("TDCamera2D") diff --git a/addons/Fantail-Interactive.top_down_camera/plugin.gd.uid b/addons/Fantail-Interactive.top_down_camera/plugin.gd.uid new file mode 100644 index 0000000..826375d --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/plugin.gd.uid @@ -0,0 +1 @@ +uid://c7paptfc4q1ft diff --git a/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd b/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd new file mode 100644 index 0000000..6a7b17d --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd @@ -0,0 +1,129 @@ +extends Camera2D + +## Scroll by using ui direction actions +@export var keyboard_pan := true +@export_group("Keyboard Settings") +@export var move_left_action := "ui_left" +@export var move_right_action := "ui_right" +@export var move_up_action := "ui_up" +@export var move_down_action := "ui_down" +@export_group("") +## Scroll by clicking right mouse button +@export var mouse_drag_pan := true +@export_group("Drag Settings") +@export var drag_action := "drag_camera" +@export_group("") +## Scroll by moving mouse to the window edge +@export var mouse_edge_pan := false +@export var use_mouse_wheel := true +@export_group("Zoom Settings") +@export var min_zoom := 2.0 +@export var max_zoom := 0.5 +@export var zoom_speed := 0.1 +@export var zoom_in_action := "zoom_in" +@export var zoom_out_action := "zoom_out" +@export_group("") +## How close should the mouse cursor be to the window edge (in pixels) before it starts +## to pan the camera +@export var edge_margin := 50 +## Camera speed (pixels/s) +@export var pan_speed := 450 + +var prev_mouse_pos := Vector2.ZERO +var dragging := false +var move_left := false +var move_right := false +var move_up := false +var move_down := false + + +func _ready() -> void: + # We want the camera to move when we set position + set_drag_horizontal_enabled(false) + set_drag_vertical_enabled(false) + +## Check for events detected by _unhandled_input and handle them +func _process(delta: float) -> void: + var motion := Vector2.ZERO + if keyboard_pan: + if move_left: + motion += Vector2.LEFT + if move_up: + motion += Vector2.UP + if move_right: + motion += Vector2.RIGHT + if move_down: + motion += Vector2.DOWN + + var mouse_pos := get_local_mouse_position() + # Pan camera when the mouse has been moved to the window edges + if mouse_edge_pan: + var rect := get_viewport().get_visible_rect() + var margin_rect = rect.grow(-edge_margin) + margin_rect.position -= rect.size / 2 + if not margin_rect.has_point(mouse_pos): + motion += margin_rect.get_center().direction_to(mouse_pos) + + motion *= (pan_speed * (1 / zoom.x)) * delta / Engine.time_scale + + if mouse_drag_pan and dragging: + motion = prev_mouse_pos - mouse_pos + + # Update position of the camera. + position += motion + clamp_position_to_limits() + + if use_mouse_wheel: + if Input.is_action_just_released(zoom_in_action): + zoom += Vector2(zoom_speed, zoom_speed) + if Input.is_action_just_released(zoom_out_action): + zoom -= Vector2(zoom_speed, zoom_speed) + + zoom.x = clampf(zoom.x, max_zoom, min_zoom) + zoom.y = zoom.x + + prev_mouse_pos = get_local_mouse_position() + +## Check for any unhandled input events and take note of them +## +## We are taking press/release events and turning them into a flag of whether the +## button is being held down +## +## NOTE: This is done here to ensure that we only consume input events if no other node does. +## i.e. the camera won't move if the player is moving through menus +func _unhandled_input(event: InputEvent) -> void: + # TODO: Change to input action + #print(event) + if event.is_action(drag_action): + dragging = event.is_pressed() + return + + # Transform a key change event into a "pressed" flag. + # We do it this way because we're checking against an InputEvent, not Input + if event.is_action_pressed(move_left_action): + move_left = true + if event.is_action_pressed(move_up_action): + move_up = true + if event.is_action_pressed(move_right_action): + move_right = true + if event.is_action_pressed(move_down_action): + move_down = true + if event.is_action_released(move_left_action): + move_left = false + if event.is_action_released(move_up_action): + move_up = false + if event.is_action_released(move_right_action): + move_right = false + if event.is_action_released(move_down_action): + move_down = false + +## Clamp the camera position to within the camera limits +## +## This fixes an issue where the camera can get "stuck" when in corners. I assume this +## is due to stopping all movement when a limit is reached +func clamp_position_to_limits() -> void: + var view_size := get_viewport().get_visible_rect ().size * get_zoom().x + var half_width := view_size.x / 2.0 + var half_height := view_size.y / 2.0 + position.x = clampf(position.x, limit_left + half_width, limit_right - half_width) + position.y = clampf(position.y, limit_top + half_height, limit_bottom - half_height) diff --git a/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd.uid b/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd.uid new file mode 100644 index 0000000..36d0441 --- /dev/null +++ b/addons/Fantail-Interactive.top_down_camera/td_camera_2d.gd.uid @@ -0,0 +1 @@ +uid://b1w10iwwhm5bj diff --git a/cyclone.gd b/cyclone.gd index de27aef..a135ba4 100644 --- a/cyclone.gd +++ b/cyclone.gd @@ -1,13 +1,16 @@ -extends Node2D +extends RigidBody2D @export var sprite : Sprite2D @export var label : Label @export var wind_speed_label : Label +@export var line : Line2D var wind_speed : float = 30 #var move_speed : float = 20 var wind_acceleration : float = 0.2 var wind_change : float = 0.2 +var wind_penalty : float = 0 +var sprite_spin : float = 40 var move_acceleration : Vector2 = Vector2(0,0) var move_change : Vector2 = Vector2(0,0) @@ -21,20 +24,28 @@ func _ready() -> void: # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: label.text = get_category() + sprite.self_modulate = Color(1,1,1, clamp(wind_speed/20,0,1) ) wind_speed_label.text = str(round(wind_speed*10)/10) - sprite.rotation_degrees += 40 * delta + sprite.rotation_degrees += sprite_spin * delta if wind_speed <= 0: end_cyclone() wind_acceleration += wind_change * delta wind_acceleration = clampf(wind_acceleration, -5, 5) wind_speed += wind_acceleration * delta + if wind_speed > 140: + wind_penalty = (wind_speed-140)/140 * delta * 150 + wind_speed -= wind_penalty * delta move_acceleration += move_change * delta move_acceleration.x = clampf(move_acceleration.x, -5, 5) move_acceleration.y = clampf(move_acceleration.y, -5, 5) - velocity += move_acceleration * delta - velocity.x = clampf(velocity.x, -3, 3) - velocity.y = clampf(velocity.y, -3, 3) - position += velocity * delta + apply_force(move_acceleration * delta * 10) + #linear_velocity += move_acceleration * delta + #linear_velocity.x = clampf(velocity.x, -3, 3) + #linear_velocity.y = clampf(velocity.y, -3, 3) + #position += velocity * delta + sprite_spin = clampf((wind_speed/250) * 180, 0, 9999) + + line.global_position = Vector2(0,0) func do_something(): @@ -43,24 +54,36 @@ func do_something(): pass func end_cyclone(): + line.reparent(get_tree().get_root()) queue_free() func get_category() -> String: if wind_speed >= 250: + sprite.modulate = Color.DEEP_PINK return "5" elif wind_speed >= 210: + sprite.modulate = Color.RED return "4" elif wind_speed >= 178: + sprite.modulate = Color.ORANGE_RED return "3" elif wind_speed >= 154: + sprite.modulate = Color.ORANGE return "2" elif wind_speed >= 119: + sprite.modulate = Color.YELLOW return "1" elif wind_speed >= 63: + sprite.modulate = Color.LIME_GREEN return "TS" else: + sprite.modulate = Color.DODGER_BLUE return "TD" func _on_timer_timeout() -> void: do_something() + + +func _on_new_point_timer_timeout() -> void: + line.add_point(global_position) diff --git a/project.godot b/project.godot index e2a1105..3c115f2 100644 --- a/project.godot +++ b/project.godot @@ -17,7 +17,7 @@ config/icon="res://icon.svg" [editor_plugins] -enabled=PackedStringArray("res://addons/Free fly camera/plugin.cfg") +enabled=PackedStringArray("res://addons/Fantail-Interactive.top_down_camera/plugin.cfg") [input] @@ -61,6 +61,21 @@ move_down={ "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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +drag_camera={ +"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":2,"position":Vector2(143, 27),"global_position":Vector2(152, 75),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} +zoom_in={ +"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":8,"position":Vector2(443, 28),"global_position":Vector2(452, 76),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} +zoom_out={ +"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":16,"position":Vector2(129, 16),"global_position":Vector2(138, 64),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} [rendering]