├── .gitattributes ├── card.gd ├── hand_rotation_curve.tres ├── .gitignore ├── game.gd ├── hand_y_curve.tres ├── project.godot ├── icon.svg ├── icon.svg.import ├── LICENSE ├── hand.gd ├── card.tscn └── game.tscn /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /card.gd: -------------------------------------------------------------------------------- 1 | class_name Card 2 | extends Panel 3 | 4 | const SIZE := Vector2(100, 140) 5 | 6 | @export var text: String 7 | @onready var label: Label = $Label 8 | 9 | 10 | func _ready() -> void: 11 | label.text = text 12 | -------------------------------------------------------------------------------- /hand_rotation_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://dwkwcluylhkq0"] 2 | 3 | [resource] 4 | min_value = -1.0 5 | _data = [Vector2(0, -1), 0.0, 2.19231, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(1, 1), 2.41154, 0.0, 0, 0] 6 | point_count = 3 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | -------------------------------------------------------------------------------- /game.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | @onready var hand: Hand = $Hand 4 | 5 | 6 | func _on_draw_card_button_pressed() -> void: 7 | hand.draw() 8 | 9 | 10 | func _on_reset_button_pressed() -> void: 11 | get_tree().reload_current_scene() 12 | 13 | 14 | func _on_discard_card_button_pressed() -> void: 15 | hand.discard() 16 | -------------------------------------------------------------------------------- /hand_y_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://dtskme7prx4g5"] 2 | 3 | [resource] 4 | bake_resolution = 101 5 | _data = [Vector2(0, 0), 0.0, 2.43699, 0, 0, Vector2(0.2, 0.5), 2.70134, 2.70134, 0, 0, Vector2(0.5, 1), 0.0, 0.0, 0, 0, Vector2(0.8, 0.5), -3.0, -3.26374, 0, 0, Vector2(1, 0), -2.36703, 0.0, 0, 0] 6 | point_count = 5 7 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Card 2D Fanning" 14 | run/main_scene="res://game.tscn" 15 | config/features=PackedStringArray("4.2", "Mobile") 16 | config/icon="res://icon.svg" 17 | 18 | [rendering] 19 | 20 | renderer/rendering_method="mobile" 21 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d2a16exh5b6ji" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Adam Gulacsi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hand.gd: -------------------------------------------------------------------------------- 1 | class_name Hand 2 | extends ColorRect 3 | 4 | const CARD = preload("res://card.tscn") 5 | 6 | @export var hand_curve: Curve 7 | @export var rotation_curve: Curve 8 | 9 | @export var max_rotation_degrees := 10 10 | @export var x_sep := 20 11 | @export var y_min := 50 12 | @export var y_max := -50 13 | 14 | 15 | func draw() -> void: 16 | var new_card = CARD.instantiate() 17 | new_card.text = "Card %s" % (get_child_count() + 1) 18 | add_child(new_card) 19 | _update_cards() 20 | 21 | 22 | func discard() -> void: 23 | if get_child_count() < 1: 24 | return 25 | 26 | var child := get_child(-1) 27 | child.reparent(get_tree().root) 28 | child.queue_free() 29 | _update_cards() 30 | 31 | 32 | func _update_cards() -> void: 33 | var cards := get_child_count() 34 | var all_cards_size := Card.SIZE.x * cards + x_sep * (cards - 1) 35 | var final_x_sep = x_sep 36 | 37 | if all_cards_size > size.x: 38 | final_x_sep = (size.x - Card.SIZE.x * cards) / (cards - 1) 39 | all_cards_size = size.x 40 | 41 | var offset := (size.x - all_cards_size) / 2 42 | 43 | for i in cards: 44 | var card := get_child(i) 45 | var y_multiplier := hand_curve.sample(1.0 / (cards-1) * i) 46 | var rot_multiplier := rotation_curve.sample(1.0 / (cards-1) * i) 47 | 48 | if cards == 1: 49 | y_multiplier = 0.0 50 | rot_multiplier = 0.0 51 | 52 | var final_x: float = offset + Card.SIZE.x * i + final_x_sep * i 53 | var final_y: float = y_min + y_max * y_multiplier 54 | 55 | card.position = Vector2(final_x, final_y) 56 | card.rotation_degrees = max_rotation_degrees * rot_multiplier 57 | -------------------------------------------------------------------------------- /card.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://bo6b3uggsca8i"] 2 | 3 | [ext_resource type="Script" path="res://card.gd" id="1_4sblk"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5cuqc"] 6 | bg_color = Color(0.469645, 0.211248, 0.0426612, 1) 7 | border_width_left = 5 8 | border_width_top = 5 9 | border_width_right = 5 10 | border_width_bottom = 5 11 | border_color = Color(0.970424, 0.604054, 0.483844, 1) 12 | corner_radius_top_left = 3 13 | corner_radius_top_right = 3 14 | corner_radius_bottom_right = 3 15 | corner_radius_bottom_left = 3 16 | shadow_size = 5 17 | shadow_offset = Vector2(4, 4) 18 | 19 | [sub_resource type="LabelSettings" id="LabelSettings_blxak"] 20 | font_size = 24 21 | shadow_size = 3 22 | shadow_color = Color(0, 0, 0, 0.6) 23 | 24 | [node name="Card" type="Panel"] 25 | custom_minimum_size = Vector2(100, 140) 26 | anchors_preset = 15 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | offset_right = -1052.0 30 | offset_bottom = -508.0 31 | grow_horizontal = 2 32 | grow_vertical = 2 33 | pivot_offset = Vector2(50, 140) 34 | theme_override_styles/panel = SubResource("StyleBoxFlat_5cuqc") 35 | script = ExtResource("1_4sblk") 36 | text = "Card 3" 37 | 38 | [node name="Label" type="Label" parent="."] 39 | layout_mode = 1 40 | anchors_preset = 8 41 | anchor_left = 0.5 42 | anchor_top = 0.5 43 | anchor_right = 0.5 44 | anchor_bottom = 0.5 45 | offset_left = -20.0 46 | offset_top = -11.5 47 | offset_right = 20.0 48 | offset_bottom = 11.5 49 | grow_horizontal = 2 50 | grow_vertical = 2 51 | text = "Card 1" 52 | label_settings = SubResource("LabelSettings_blxak") 53 | horizontal_alignment = 1 54 | vertical_alignment = 1 55 | -------------------------------------------------------------------------------- /game.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://tphn86kaqmon"] 2 | 3 | [ext_resource type="Script" path="res://game.gd" id="1_0rb8f"] 4 | [ext_resource type="Script" path="res://hand.gd" id="2_drdp6"] 5 | [ext_resource type="Curve" uid="uid://dtskme7prx4g5" path="res://hand_y_curve.tres" id="3_jwe8m"] 6 | [ext_resource type="Curve" uid="uid://dwkwcluylhkq0" path="res://hand_rotation_curve.tres" id="4_lbysu"] 7 | 8 | [node name="Game" type="Control"] 9 | layout_mode = 3 10 | anchors_preset = 15 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | grow_horizontal = 2 14 | grow_vertical = 2 15 | script = ExtResource("1_0rb8f") 16 | 17 | [node name="Background" type="ColorRect" parent="."] 18 | layout_mode = 1 19 | anchors_preset = 15 20 | anchor_right = 1.0 21 | anchor_bottom = 1.0 22 | grow_horizontal = 2 23 | grow_vertical = 2 24 | color = Color(0.12393, 0.429359, 0.458081, 1) 25 | 26 | [node name="Hand" type="ColorRect" parent="."] 27 | custom_minimum_size = Vector2(800, 140) 28 | layout_mode = 1 29 | anchors_preset = 7 30 | anchor_left = 0.5 31 | anchor_top = 1.0 32 | anchor_right = 0.5 33 | anchor_bottom = 1.0 34 | offset_left = -400.0 35 | offset_top = -140.0 36 | offset_right = 400.0 37 | grow_horizontal = 2 38 | grow_vertical = 0 39 | color = Color(1, 1, 1, 0.168627) 40 | script = ExtResource("2_drdp6") 41 | hand_curve = ExtResource("3_jwe8m") 42 | rotation_curve = ExtResource("4_lbysu") 43 | max_rotation_degrees = 5 44 | x_sep = -10 45 | y_min = 0 46 | y_max = -15 47 | 48 | [node name="DrawCardButton" type="Button" parent="."] 49 | layout_mode = 0 50 | offset_left = 43.0 51 | offset_top = 36.0 52 | offset_right = 133.0 53 | offset_bottom = 67.0 54 | text = "Draw Card" 55 | 56 | [node name="ResetButton" type="Button" parent="."] 57 | layout_mode = 1 58 | anchors_preset = 5 59 | anchor_left = 0.5 60 | anchor_right = 0.5 61 | offset_left = -45.0 62 | offset_top = 38.0 63 | offset_right = 45.0 64 | offset_bottom = 69.0 65 | grow_horizontal = 2 66 | text = "Reset" 67 | 68 | [node name="DiscardCardButton" type="Button" parent="."] 69 | layout_mode = 1 70 | anchors_preset = 1 71 | anchor_left = 1.0 72 | anchor_right = 1.0 73 | offset_left = -146.0 74 | offset_top = 39.0 75 | offset_right = -39.0 76 | offset_bottom = 70.0 77 | grow_horizontal = 0 78 | text = "Discard Card" 79 | 80 | [connection signal="pressed" from="DrawCardButton" to="." method="_on_draw_card_button_pressed"] 81 | [connection signal="pressed" from="ResetButton" to="." method="_on_reset_button_pressed"] 82 | [connection signal="pressed" from="DiscardCardButton" to="." method="_on_discard_card_button_pressed"] 83 | --------------------------------------------------------------------------------