├── .import ├── .gdignore ├── icon.png-487276ed1e3a0c39cad0279d744ee560.md5 ├── menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.md5 ├── icon.png-487276ed1e3a0c39cad0279d744ee560.stex ├── circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.md5 ├── menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.etc2.stex ├── menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.s3tc.stex ├── circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.etc2.stex └── circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.s3tc.stex ├── icon.png ├── menu-partymode ├── menu-item.png ├── menu-item.png.import ├── MenuItem_Party.gd ├── MainMenu.gd ├── MenuItem_Party.tscn └── MainMenu.tscn ├── menu-minimal ├── circular-menu-item.png ├── circular-menu-item.png.import ├── MainMenu.tscn ├── MenuItem.tscn ├── MainMenu.gd └── MenuItem.gd ├── fonts ├── JetBrainsMono-2.242 │ ├── JetBrainsMonoNL-Regular.ttf │ ├── README.md │ ├── AUTHORS.txt │ └── OFL.txt └── mono-120.tres ├── default_env.tres ├── sample-scenes ├── Button.gd ├── RedScene.tscn ├── BlueScene.tscn └── GreenScene.tscn ├── icon.png.import ├── LICENSE ├── project.godot └── README.md /.import/.gdignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/icon.png -------------------------------------------------------------------------------- /menu-partymode/menu-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/menu-partymode/menu-item.png -------------------------------------------------------------------------------- /menu-minimal/circular-menu-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/menu-minimal/circular-menu-item.png -------------------------------------------------------------------------------- /.import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5: -------------------------------------------------------------------------------- 1 | source_md5="8aa71e5cb6f0e0b3348170f97df3424c" 2 | dest_md5="b4cfd32e9e3664928b8abd67d8641f37" 3 | 4 | -------------------------------------------------------------------------------- /.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.md5: -------------------------------------------------------------------------------- 1 | source_md5="aff114e7746b69e8a6b6fd9e69c0e45e" 2 | dest_md5="7c101f9785af02cbfd904c827f2f3d67" 3 | 4 | -------------------------------------------------------------------------------- /.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex -------------------------------------------------------------------------------- /fonts/JetBrainsMono-2.242/JetBrainsMonoNL-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/fonts/JetBrainsMono-2.242/JetBrainsMonoNL-Regular.ttf -------------------------------------------------------------------------------- /.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.md5: -------------------------------------------------------------------------------- 1 | source_md5="5ca47e89a94a67a95bb1fbac9bfd3f0f" 2 | dest_md5="787afaad0351157826de2d8a85c077e1" 3 | 4 | -------------------------------------------------------------------------------- /fonts/JetBrainsMono-2.242/README.md: -------------------------------------------------------------------------------- 1 | # JetBrains Mono 2 | 3 | Get the latest release here: 4 | → [www.jetbrains.com/lp/mono/](https://www.jetbrains.com/lp/mono/) 5 | -------------------------------------------------------------------------------- /.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.etc2.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.etc2.stex -------------------------------------------------------------------------------- /.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.s3tc.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.s3tc.stex -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" format=2] 2 | 3 | [resource] 4 | background_mode = 1 5 | background_color = Color( 0.0588235, 0.121569, 0.14902, 1 ) 6 | glow_enabled = true 7 | -------------------------------------------------------------------------------- /.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.etc2.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.etc2.stex -------------------------------------------------------------------------------- /.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.s3tc.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dueddel/godot-3d-ui-demo/HEAD/.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.s3tc.stex -------------------------------------------------------------------------------- /fonts/mono-120.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://fonts/JetBrainsMono-2.242/JetBrainsMonoNL-Regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 120 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /sample-scenes/Button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | 4 | var menu_scene: PackedScene = load("res://menu-minimal/MainMenu.tscn") 5 | 6 | 7 | func _on_Button_pressed() -> void: 8 | # warning-ignore:return_value_discarded 9 | get_tree().change_scene_to(menu_scene) 10 | -------------------------------------------------------------------------------- /fonts/JetBrainsMono-2.242/AUTHORS.txt: -------------------------------------------------------------------------------- 1 | # This is the official list of project authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS.txt file. 3 | # See the latter for an explanation. 4 | # 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | 8 | JetBrains <> 9 | Philipp Nurullin 10 | Konstantin Bulenkov 11 | -------------------------------------------------------------------------------- /sample-scenes/RedScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://sample-scenes/Button.gd" type="Script" id=1] 4 | 5 | [node name="RedScene" type="ColorRect"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | color = Color( 0.447059, 0.203922, 0.180392, 1 ) 9 | 10 | [node name="Button" type="Button" parent="."] 11 | anchor_left = 0.5 12 | anchor_top = 0.5 13 | anchor_right = 0.5 14 | anchor_bottom = 0.5 15 | margin_left = -132.5 16 | margin_top = -10.0 17 | margin_right = 132.5 18 | margin_bottom = 10.0 19 | text = "Bring me back to the minimal 3D menu" 20 | script = ExtResource( 1 ) 21 | 22 | [connection signal="pressed" from="Button" to="Button" method="_on_Button_pressed"] 23 | -------------------------------------------------------------------------------- /sample-scenes/BlueScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://sample-scenes/Button.gd" type="Script" id=1] 4 | 5 | [node name="BlueScene" type="ColorRect"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | color = Color( 0.160784, 0.282353, 0.403922, 1 ) 9 | 10 | [node name="Button" type="Button" parent="."] 11 | anchor_left = 0.5 12 | anchor_top = 0.5 13 | anchor_right = 0.5 14 | anchor_bottom = 0.5 15 | margin_left = -132.5 16 | margin_top = -10.0 17 | margin_right = 132.5 18 | margin_bottom = 10.0 19 | text = "Bring me back to the minimal 3D menu" 20 | script = ExtResource( 1 ) 21 | 22 | [connection signal="pressed" from="Button" to="Button" method="_on_Button_pressed"] 23 | -------------------------------------------------------------------------------- /sample-scenes/GreenScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://sample-scenes/Button.gd" type="Script" id=1] 4 | 5 | [node name="GreenScene" type="ColorRect"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | color = Color( 0.160784, 0.403922, 0.164706, 1 ) 9 | 10 | [node name="Button" type="Button" parent="."] 11 | anchor_left = 0.5 12 | anchor_top = 0.5 13 | anchor_right = 0.5 14 | anchor_bottom = 0.5 15 | margin_left = -132.5 16 | margin_top = -10.0 17 | margin_right = 132.5 18 | margin_bottom = 10.0 19 | text = "Bring me back to the minimal 3D menu" 20 | script = ExtResource( 1 ) 21 | 22 | [connection signal="pressed" from="Button" to="Button" method="_on_Button_pressed"] 23 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 dueddel (Arvid Schönberg) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /menu-partymode/menu-item.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path.s3tc="res://.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.s3tc.stex" 6 | path.etc2="res://.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.etc2.stex" 7 | metadata={ 8 | "imported_formats": [ "s3tc", "etc2" ], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://menu-partymode/menu-item.png" 15 | dest_files=[ "res://.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.s3tc.stex", "res://.import/menu-item.png-7b130679b2ecb5ec760c0fccd0d1102d.etc2.stex" ] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/lossy_quality=0.7 21 | compress/hdr_mode=0 22 | compress/bptc_ldr=0 23 | compress/normal_map=0 24 | flags/repeat=true 25 | flags/filter=true 26 | flags/mipmaps=true 27 | flags/anisotropic=false 28 | flags/srgb=1 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/HDR_as_SRGB=false 32 | process/invert_color=false 33 | process/normal_map_invert_y=false 34 | stream=false 35 | size_limit=0 36 | detect_3d=false 37 | svg/scale=1.0 38 | -------------------------------------------------------------------------------- /menu-minimal/circular-menu-item.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path.s3tc="res://.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.s3tc.stex" 6 | path.etc2="res://.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.etc2.stex" 7 | metadata={ 8 | "imported_formats": [ "s3tc", "etc2" ], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://menu-minimal/circular-menu-item.png" 15 | dest_files=[ "res://.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.s3tc.stex", "res://.import/circular-menu-item.png-0a8bcb49f063af3ad46c241936f5ad36.etc2.stex" ] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/lossy_quality=0.7 21 | compress/hdr_mode=0 22 | compress/bptc_ldr=0 23 | compress/normal_map=0 24 | flags/repeat=true 25 | flags/filter=true 26 | flags/mipmaps=true 27 | flags/anisotropic=false 28 | flags/srgb=1 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/HDR_as_SRGB=false 32 | process/invert_color=false 33 | process/normal_map_invert_y=false 34 | stream=false 35 | size_limit=0 36 | detect_3d=false 37 | svg/scale=1.0 38 | -------------------------------------------------------------------------------- /menu-minimal/MainMenu.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://menu-minimal/MenuItem.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://menu-minimal/MainMenu.gd" type="Script" id=2] 5 | [ext_resource path="res://sample-scenes/RedScene.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://sample-scenes/GreenScene.tscn" type="PackedScene" id=4] 7 | [ext_resource path="res://sample-scenes/BlueScene.tscn" type="PackedScene" id=5] 8 | 9 | [node name="MainMenu" type="Spatial"] 10 | script = ExtResource( 2 ) 11 | 12 | [node name="CamPivot" type="Position3D" parent="."] 13 | 14 | [node name="Camera" type="Camera" parent="CamPivot"] 15 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5 ) 16 | fov = 30.0 17 | 18 | [node name="MenuItems" type="Spatial" parent="."] 19 | 20 | [node name="Red" parent="MenuItems" instance=ExtResource( 1 )] 21 | transform = Transform( 0.965926, 0, 0.258819, 0, 1, 0, -0.258819, 0, 0.965926, -1, 0, 0 ) 22 | button_label = "Red" 23 | scene_to_load = ExtResource( 3 ) 24 | label_modulate_normal = Color( 3, 0, 0, 1 ) 25 | 26 | [node name="Green" parent="MenuItems" instance=ExtResource( 1 )] 27 | button_label = "Green" 28 | scene_to_load = ExtResource( 4 ) 29 | label_modulate_normal = Color( 0, 2, 0, 1 ) 30 | 31 | [node name="Blue" parent="MenuItems" instance=ExtResource( 1 )] 32 | transform = Transform( 0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 1, 0, 0 ) 33 | button_label = "Blue" 34 | scene_to_load = ExtResource( 5 ) 35 | label_modulate_normal = Color( 0.5, 0.5, 3, 1 ) 36 | -------------------------------------------------------------------------------- /menu-minimal/MenuItem.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://menu-minimal/circular-menu-item.png" type="Texture" id=1] 4 | [ext_resource path="res://menu-minimal/MenuItem.gd" type="Script" id=2] 5 | [ext_resource path="res://fonts/mono-120.tres" type="DynamicFont" id=3] 6 | 7 | [sub_resource type="CylinderShape" id=5] 8 | radius = 0.4 9 | height = 0.01 10 | 11 | [sub_resource type="ViewportTexture" id=6] 12 | viewport_path = NodePath("Label/Viewport") 13 | 14 | [node name="MenuItem" type="Spatial"] 15 | script = ExtResource( 2 ) 16 | 17 | [node name="MouseCollider" type="StaticBody" parent="."] 18 | collision_layer = 4096 19 | collision_mask = 0 20 | 21 | [node name="CollisionShape" type="CollisionShape" parent="MouseCollider"] 22 | transform = Transform( 1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0 ) 23 | shape = SubResource( 5 ) 24 | 25 | [node name="Button" type="Sprite3D" parent="."] 26 | layers = 32768 27 | modulate = Color( 2.2, 2.2, 2.2, 1 ) 28 | pixel_size = 0.0008 29 | texture = ExtResource( 1 ) 30 | region_enabled = true 31 | region_rect = Rect2( 5, 5, 1000, 1000 ) 32 | 33 | [node name="Tween" type="Tween" parent="."] 34 | 35 | [node name="Label" type="Sprite3D" parent="."] 36 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.2 ) 37 | flip_v = true 38 | pixel_size = 0.0007 39 | texture = SubResource( 6 ) 40 | 41 | [node name="Viewport" type="Viewport" parent="Label"] 42 | size = Vector2( 1000, 1000 ) 43 | transparent_bg = true 44 | 45 | [node name="Label" type="Label" parent="Label/Viewport"] 46 | anchor_right = 1.0 47 | anchor_bottom = 1.0 48 | custom_fonts/font = ExtResource( 3 ) 49 | text = "Button 50 | Label" 51 | align = 1 52 | valign = 1 53 | -------------------------------------------------------------------------------- /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=4 10 | 11 | _global_script_classes=[ { 12 | "base": "Spatial", 13 | "class": "MenuItem", 14 | "language": "GDScript", 15 | "path": "res://menu-minimal/MenuItem.gd" 16 | }, { 17 | "base": "Spatial", 18 | "class": "MenuItem_Party", 19 | "language": "GDScript", 20 | "path": "res://menu-partymode/MenuItem_Party.gd" 21 | } ] 22 | _global_script_class_icons={ 23 | "MenuItem": "", 24 | "MenuItem_Party": "" 25 | } 26 | 27 | [application] 28 | 29 | config/name="3D UI Demo" 30 | run/main_scene="res://menu-minimal/MainMenu.tscn" 31 | config/icon="res://icon.png" 32 | 33 | [input] 34 | 35 | party_mode={ 36 | "deadzone": 0.5, 37 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":32,"unicode":0,"echo":false,"script":null) 38 | ] 39 | } 40 | mouse_left_click={ 41 | "deadzone": 0.5, 42 | "events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) 43 | ] 44 | } 45 | 46 | [physics] 47 | 48 | common/enable_pause_aware_picking=true 49 | 50 | [rendering] 51 | 52 | environment/default_environment="res://default_env.tres" 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D UI Demo – Godot 2 | 3 | This is a prototype implementation of a 3D menu in Godot. Feel free to use it for learning purpose or as a base for your own 3D UI. 4 | 5 | It's the scene setup and source codes as promised in the comment section of this Reddit post: 6 | [→ I had so much fun building a prototype for a game's main menu in 3D instead of using Godot's Control nodes!](https://www.reddit.com/r/godot/comments/wbrvhj/i_had_so_much_fun_building_a_prototype_for_a/) 7 | 8 | 9 | ## Where to start? 10 | 11 | First of all start Godot and load the project, of course. 12 | 13 | 14 | ### Minimal 15 | 16 | Have a look into the [menu-minimal/](./menu-minimal/) folder and open the [`MainMenu.tscn`](./menu-minimal/MainMenu.tscn) file. 17 | This is a most basic sample scene to achieve a 3D menu as seen in the Reddit post linked above. 18 | 19 | I recommend to use that one as a base for your own menu, don't use the party mode sources. 20 | 21 | Speaking of which… 22 | 23 | 24 | ### Party 25 | 26 | To see how I've implemented the "party mode" as seen in the video of said Reddit post head to the [menu-partymode/](./menu-partymode/) folder and open the [`MainMenu.tscn`](./menu-partymode/MainMenu.tscn) file in it. 27 | 28 | Basically the party mode sources are a direct copy of the minimal directory where I simply added a bit extra stuff. 29 | 30 | 31 | #### Activate party mode 32 | 33 | Activating the party mode is mapped to the space bar key. 34 | 35 | 36 | #### By no means perfect 37 | 38 | Please know that this whole project was a fun idea without the aspiration of building something neat and elegant. Especially the party-mode related code is put together roughly and explicitly without any sophisticated design priniciples of software development in mind. 39 | Long story short, it's quick and dirty at one or another place. So be warned. 😉 40 | 41 | 42 | #### No sound 43 | 44 | Be informed that I had to remove all audio files from the project that I originally used in the video on Reddit. I removed them due to licensing. 45 | The background music and sound effects came from some previous [→ Humble Bundle](https://www.humblebundle.com/software) offers as well as from the [→ Universal SFX](https://imphenzia.com/universal-sound-fx) pack made by Imphenzia. 46 | I can use these sound files commercially and everything, but I am not allowed to redistribute them in a free repository like this one on GitHub. 47 | 48 | That's why I removed all sounds from the party. You can simply reenable them by uncommenting the appropriate lines in [`MainMenu.gd`](./menu-partymode/MainMenu.gd) and [`MenuItem_Party.gd`](./menu-partymode/MenuItem_Party.gd) and adding your own sounds to the [menu-partymode/](./menu-partymode/) folder. 49 | 50 | 51 | ## License 52 | 53 | This project and all its files, unless stated differently, are licensed under the terms of the **MIT license** (also known as **Expat** or **X11** license). See also the project's [license file](./LICENSE). 54 | 55 | Keep in mind that the used [font](./fonts/JetBrainsMono-2.242/), for instance, is licensed under **SIL Open Font License Version 1.1**. (By the way that font is really awesome!) 56 | 57 | Godot itself which has been used to create this project is also licensed under **MIT license**, see also [→ the official website of Godot](https://godotengine.org/license). 58 | -------------------------------------------------------------------------------- /menu-minimal/MainMenu.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | 4 | const CAM_MOVEMENT = 0.015 5 | const CAM_MOVEMENT_SMOOTHNESS = 0.05 6 | const RAY_LEN = 10 7 | const MENU_ITEMS_MASK_BIT = 4096 # layer 13, bit 12 8 | 9 | 10 | onready var cam_pivot := $CamPivot 11 | onready var cam := $CamPivot/Camera 12 | var active_item : MenuItem_Party 13 | 14 | 15 | func _unhandled_input(event: InputEvent) -> void: 16 | # on clicking a menu item 17 | if active_item and event.is_action_released("mouse_left_click"): 18 | # do something with it 19 | active_item.click() 20 | # don't forget to reset the mouse cursor 21 | Input.set_default_cursor_shape(Input.CURSOR_ARROW) 22 | # you might implement changing the scene here which is 23 | # currently implemented in the item itself, but … 24 | # you COULD do it here instead if you only wanted to ;-) 25 | # --> see also method documentation of MenuItem.click() 26 | 27 | 28 | # warning-ignore:unused_argument 29 | func _process(delta: float) -> void: 30 | # determine viewport size and mouse position for usage below 31 | var vp_size := get_viewport().get_visible_rect().size 32 | var mouse_pos = get_viewport().get_mouse_position() 33 | # avoid having mouse cursor coordinates outside the viewport 34 | mouse_pos.x = clamp(mouse_pos.x, 0, vp_size.x) 35 | mouse_pos.y = clamp(mouse_pos.y, 0, vp_size.y) 36 | 37 | # use mouse cursor position to do our actual menu stuff 38 | rotate_cam(mouse_pos, vp_size) 39 | control_menu_items(mouse_pos) 40 | 41 | 42 | # 43 | # Rotates the scene's camera around its pivot point 44 | # depending on given mouse position and viewport size. 45 | # 46 | func rotate_cam(mouse_pos: Vector2, vp_size: Vector2) -> void: 47 | # get mouse position as seen from viewport center to use it for rotation calculation 48 | mouse_pos -= vp_size / 2 49 | 50 | # rotate camera around its pivot depending on mouse position 51 | cam_pivot.rotation_degrees.x = lerp(cam_pivot.rotation_degrees.x, mouse_pos.y * CAM_MOVEMENT, CAM_MOVEMENT_SMOOTHNESS) 52 | cam_pivot.rotation_degrees.y = lerp(cam_pivot.rotation_degrees.y, mouse_pos.x * CAM_MOVEMENT, CAM_MOVEMENT_SMOOTHNESS) 53 | 54 | 55 | # 56 | # Determines "mouse-over"/"mouse-out" for the menu items 57 | # and activates their hovered respectively unhovered states. 58 | # 59 | func control_menu_items(mouse_pos: Vector2) -> void: 60 | # get menu item that the mouse cursor is over right now 61 | var item := get_hovered_menu_item(mouse_pos) 62 | 63 | # check if menu item isn't hovered any longer 64 | if active_item and active_item != item: 65 | active_item.unhover() 66 | active_item = null 67 | Input.set_default_cursor_shape(Input.CURSOR_ARROW) 68 | 69 | # item is hovered and it's a new one that hasn't been hovered before 70 | if item and active_item != item: 71 | item.hover() 72 | active_item = item 73 | Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND) 74 | 75 | 76 | # 77 | # Returns the menu item that's "under" the mouse cursor. 78 | # 79 | # Casts a ray from mouse cursor through camera to the 3D scene 80 | # to determine the menu item being hovered by the mouse cursor. 81 | # 82 | func get_hovered_menu_item(mouse_pos: Vector2) -> MenuItem_Party: 83 | var ray_start : Vector3 = cam.project_ray_origin(mouse_pos) 84 | var ray_end : Vector3 = ray_start + cam.project_ray_normal(mouse_pos) * RAY_LEN 85 | var space_state : PhysicsDirectSpaceState = get_world().direct_space_state 86 | var result := space_state.intersect_ray(ray_start, ray_end, [], MENU_ITEMS_MASK_BIT) 87 | 88 | return result.collider.get_parent() if not result.empty() else null 89 | -------------------------------------------------------------------------------- /menu-minimal/MenuItem.gd: -------------------------------------------------------------------------------- 1 | class_name MenuItem 2 | extends Spatial 3 | 4 | 5 | const TRANSITION_DURATION = 0.2 6 | const ROTATION_DURATION = TRANSITION_DURATION * 5 7 | 8 | 9 | export(String, MULTILINE) var button_label: String 10 | export var scene_to_load: PackedScene 11 | 12 | export var button_rotate_unhover := false 13 | export var button_rotate_hover := true 14 | export var button_modulate_normal := Color(2.0, 2.0, 2.0, 1) 15 | export var button_modulate_hover := Color(2.2, 2.2, 2.2, 1) 16 | export var button_scale_normal := Vector3(1.0, 1.0, 1.0) 17 | export var button_scale_hover := Vector3(1.1, 1.1, 1.0) 18 | export var label_modulate_normal := Color(1.8, 1.8, 1.8, 1) 19 | export var label_modulate_hover := Color(2.0, 2.0, 2.0, 1) 20 | export var label_scale_normal := Vector3(1.0, 1.0, 1.0) 21 | export var label_scale_hover := Vector3(1.3, 1.3, 1.0) 22 | 23 | onready var button := $Button 24 | onready var label := $Label 25 | onready var tween := $Tween 26 | 27 | var last_clicked: Sprite3D 28 | 29 | 30 | func _ready() -> void: 31 | button.modulate = button_modulate_normal 32 | button.scale = button_scale_normal 33 | label.modulate = label_modulate_normal 34 | label.scale = label_scale_normal 35 | label.get_child(0).get_child(0).text = button_label 36 | 37 | 38 | func hover() -> void: 39 | transition_to( 40 | button_rotate_hover, 41 | button_modulate_hover, 42 | button_scale_hover, 43 | label_modulate_hover, 44 | label_scale_hover 45 | ) 46 | 47 | 48 | func unhover() -> void: 49 | transition_to( 50 | button_rotate_unhover, 51 | button_modulate_normal, 52 | button_scale_normal, 53 | label_modulate_normal, 54 | label_scale_normal 55 | ) 56 | 57 | 58 | func transition_to(button_rotate: bool, button_modulate: Color, button_scale: Vector3, label_modulate: Color, label_scale: Vector3) -> void: 59 | if button_rotate: 60 | var button_rotation := Vector3(0, 0, stepify(button.rotation_degrees.z - 180, 180)) 61 | tween.interpolate_property(button, "rotation_degrees", button.rotation_degrees, button_rotation, ROTATION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 62 | add_to_tween(button, button_modulate, button_scale) 63 | add_to_tween(label, label_modulate, label_scale) 64 | # warning-ignore:return_value_discarded 65 | tween.start() 66 | 67 | 68 | func add_to_tween(sprite: Sprite3D, sprite_modulate: Color, sprite_scale: Vector3) -> void: 69 | # warning-ignore:return_value_discarded 70 | tween.interpolate_property(sprite, "scale", sprite.scale, sprite_scale, TRANSITION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 71 | # warning-ignore:return_value_discarded 72 | tween.interpolate_property(sprite, "modulate", sprite.modulate, sprite_modulate, TRANSITION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 73 | 74 | 75 | # 76 | # Does awesome stuff whenever this menu item is clicked. 77 | # 78 | func click() -> void: 79 | # this is where click sounds could be played 80 | # or some nice button animation is started 81 | # or whatever else you wanna make happen … 82 | 83 | # for example let's have an exported field 84 | # of type PackedScene (see above in line 10) 85 | # and use that one to start your game or to 86 | # load another submenu scene or whatever 87 | if scene_to_load: 88 | get_tree().change_scene_to(scene_to_load) 89 | 90 | # or let's simply print the menu item's name 91 | # just because we can and because real devs 92 | # don't use the built-in Debugger … Hell, no! 93 | # (I hope you get my sarcasm in that statement 94 | # 'cause debugging is awesome! Thus do it!) 95 | print("menu item for '%s' has been clicked" % name) 96 | -------------------------------------------------------------------------------- /menu-partymode/MenuItem_Party.gd: -------------------------------------------------------------------------------- 1 | class_name MenuItem_Party 2 | extends Spatial 3 | 4 | 5 | const TRANSITION_DURATION = 0.2 6 | const ROTATION_DURATION = TRANSITION_DURATION * 5 7 | 8 | 9 | export(String, MULTILINE) var button_label: String 10 | export var button_rotate_unhover := false 11 | export var button_rotate_hover := true 12 | export var button_modulate_normal := Color(2.0, 2.0, 2.0, 1) 13 | export var button_modulate_hover := Color(2.2, 2.2, 2.2, 1) 14 | export var button_scale_normal := Vector3(1.0, 1.0, 1.0) 15 | export var button_scale_hover := Vector3(1.1, 1.1, 1.0) 16 | export var label_modulate_normal := Color(1.8, 1.8, 1.8, 1) 17 | export var label_modulate_hover := Color(2.0, 2.0, 2.0, 1) 18 | export var label_scale_normal := Vector3(1.0, 1.0, 1.0) 19 | export var label_scale_hover := Vector3(1.3, 1.3, 1.0) 20 | 21 | 22 | onready var button := $Button 23 | onready var label := $Label 24 | onready var tween := $Tween 25 | 26 | var last_clicked: Sprite3D 27 | 28 | 29 | func _ready() -> void: 30 | $Label/Viewport/Label.text = button_label 31 | button.modulate = button_modulate_normal 32 | button.scale = button_scale_normal 33 | label.modulate = label_modulate_normal 34 | label.scale = label_scale_normal 35 | 36 | 37 | func hover() -> void: 38 | # if button_rotate_hover: 39 | # $AudioStreamPlayer.stream = load("res://menu-partymode/hover.wav") 40 | # $AudioStreamPlayer.play() 41 | transition_to( 42 | button_rotate_hover, 43 | button_modulate_hover, 44 | button_scale_hover, 45 | label_modulate_hover, 46 | label_scale_hover 47 | ) 48 | 49 | 50 | func unhover() -> void: 51 | transition_to( 52 | button_rotate_unhover, 53 | button_modulate_normal, 54 | button_scale_normal, 55 | label_modulate_normal, 56 | label_scale_normal 57 | ) 58 | 59 | 60 | func transition_to(button_rotate: bool, button_modulate: Color, button_scale: Vector3, label_modulate: Color, label_scale: Vector3) -> void: 61 | if button_rotate: 62 | var button_rotation := Vector3(0, 0, stepify(button.rotation_degrees.z - 180, 180)) 63 | tween.interpolate_property(button, "rotation_degrees", button.rotation_degrees, button_rotation, ROTATION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 64 | add_to_tween(button, button_modulate, button_scale) 65 | add_to_tween(label, label_modulate, label_scale) 66 | # warning-ignore:return_value_discarded 67 | tween.start() 68 | 69 | 70 | func add_to_tween(sprite: Sprite3D, sprite_modulate: Color, sprite_scale: Vector3) -> void: 71 | # warning-ignore:return_value_discarded 72 | tween.interpolate_property(sprite, "scale", sprite.scale, sprite_scale, TRANSITION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 73 | # warning-ignore:return_value_discarded 74 | tween.interpolate_property(sprite, "modulate", sprite.modulate, sprite_modulate, TRANSITION_DURATION, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 75 | 76 | 77 | func click() -> void: 78 | # randomly choose a click label 79 | var clicks := $ClickLabels.get_children() 80 | randomize() 81 | clicks.shuffle() 82 | var clicked: Sprite3D = clicks.pop_front() 83 | if clicked == last_clicked: 84 | clicked = clicks.pop_front() 85 | 86 | # show and fade out again 87 | clicked.opacity = 1 88 | clicked.show() 89 | var t: Tween = clicked.get_child(0) 90 | t.interpolate_property(clicked, "opacity", clicked.opacity, 0.0, .2, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 91 | t.start() 92 | 93 | last_clicked = clicked 94 | 95 | # if button_rotate_hover: 96 | # $AudioStreamPlayer.stream = load("res://menu-partymode/click.wav") 97 | # else: 98 | # $AudioStreamPlayer.stream = load("res://menu-partymode/clicknormal.wav") 99 | # $AudioStreamPlayer.play() 100 | -------------------------------------------------------------------------------- /fonts/JetBrainsMono-2.242/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /menu-partymode/MainMenu.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | 4 | const RAY_LEN = 10 5 | const MENU_ITEMS_MASK_BIT = 4096 # layer 13, bit 12 6 | 7 | 8 | onready var cam_pivot := $CamPivot 9 | onready var cam := $CamPivot/Camera 10 | var active_item : MenuItem_Party 11 | 12 | 13 | func _ready() -> void: 14 | OS.set_window_title("Why bothering with UI nodes if you could build a game's menu in 3D?") 15 | # $Party/AudioStreamPlayer3.stream = load("res://menu-partymode/bgm.wav") 16 | # $Party/AudioStreamPlayer3.play() 17 | 18 | 19 | func _unhandled_input(event: InputEvent) -> void: 20 | if active_item and event.is_action_released("mouse_left_click"): 21 | active_item.click() 22 | Input.set_default_cursor_shape(Input.CURSOR_ARROW) 23 | return 24 | 25 | if event.is_action_pressed("party_mode"): 26 | $Party.visible = not $Party.visible 27 | $Partycles.emitting = $Party.visible 28 | for item in $MenuItems.get_children(): 29 | if $Party.visible: 30 | party(item) 31 | else: 32 | unparty(item) 33 | if $Party.visible: 34 | # $Party/AudioStreamPlayer.stream = load("res://menu-partymode/party.wav") 35 | # $Party/AudioStreamPlayer.play() 36 | # $Party/AudioStreamPlayer2.stream = load("res://menu-partymode/party2.wav") 37 | # $Party/AudioStreamPlayer2.play() 38 | # $Party/AudioStreamPlayer3.stream = load("res://menu-partymode/party3.wav") 39 | # $Party/AudioStreamPlayer3.play() 40 | $Unparty.hide() 41 | OS.set_window_title("WHOOP! WHOOOOP!!") 42 | else: 43 | # $Party/AudioStreamPlayer.stream = load("res://menu-partymode/unparty.wav") 44 | # $Party/AudioStreamPlayer.play() 45 | # $Party/AudioStreamPlayer2.stream = load("res://menu-partymode/unparty2.wav") 46 | # $Party/AudioStreamPlayer2.play() 47 | # $Party/AudioStreamPlayer3.stream = load("res://menu-partymode/unparty3.wav") 48 | # $Party/AudioStreamPlayer3.play() 49 | $Unparty.show() 50 | var t: Tween = $Unparty/Tween 51 | # warning-ignore:return_value_discarded 52 | t.interpolate_property($Unparty, "opacity", $Unparty.opacity, 0.0, 4, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT, 2) 53 | # warning-ignore:return_value_discarded 54 | t.start() 55 | OS.set_window_title("Kinda boring out of a sudden, heh?") 56 | 57 | 58 | func party(item: MenuItem_Party) -> void: 59 | item.button_rotate_hover = true 60 | item.button_modulate_normal = Color(2.0, 2.0, 1.0, 1) 61 | item.button_modulate_hover = Color(3, 3, 0.5, 1) 62 | item.button_scale_normal = Vector3(1.0, 1.0, 1.0) 63 | item.button_scale_hover = Vector3(1.1, 1.1, 1.0) 64 | item.label_modulate_normal = Color(1.8, 1.8, 1.8, 1) 65 | item.label_modulate_hover = Color(4, 4, 4, 1) 66 | item.label_scale_normal = Vector3(1.0, 1.0, 1.0) 67 | item.label_scale_hover = Vector3(1.3, 1.3, 1.0) 68 | item.button.modulate = item.button_modulate_normal 69 | 70 | func unparty(item: MenuItem_Party) -> void: 71 | item.button_rotate_hover = false 72 | item.button_modulate_normal = Color(2.0, 2.0, 2.0, 1) 73 | item.button_modulate_hover = Color(2.2, 2.2, 2.2, 1) 74 | item.button_scale_normal = Vector3(1.0, 1.0, 1.0) 75 | item.button_scale_hover = Vector3(1.1, 1.1, 1.0) 76 | item.label_modulate_normal = Color(1.8, 1.8, 1.8, 1) 77 | item.label_modulate_hover = Color(2.0, 2.0, 2.0, 1) 78 | item.label_scale_normal = Vector3(1.0, 1.0, 1.0) 79 | item.label_scale_hover = Vector3(1.3, 1.3, 1.0) 80 | item.button.modulate = item.button_modulate_normal 81 | 82 | 83 | # warning-ignore:unused_argument 84 | func _process(delta: float) -> void: 85 | # determine viewport size and mouse position 86 | var vp_size := get_viewport().get_visible_rect().size 87 | var mouse_pos = get_viewport().get_mouse_position() 88 | mouse_pos.x = clamp(mouse_pos.x, 0, vp_size.x) 89 | mouse_pos.y = clamp(mouse_pos.y, 0, vp_size.y) 90 | 91 | rotate_cam(mouse_pos, vp_size) 92 | control_menu_items(mouse_pos) 93 | 94 | 95 | func rotate_cam(mouse_pos: Vector2, vp_size: Vector2) -> void: 96 | # get mouse position as seen from viewport center 97 | mouse_pos -= vp_size / 2 98 | 99 | # rotate camera around its pivot depending on mouse position 100 | cam_pivot.rotation_degrees.x = lerp(cam_pivot.rotation_degrees.x, mouse_pos.y * 0.015, 0.05) 101 | cam_pivot.rotation_degrees.y = lerp(cam_pivot.rotation_degrees.y, mouse_pos.x * 0.015, 0.05) 102 | 103 | 104 | func control_menu_items(mouse_pos: Vector2) -> void: 105 | var item := get_hovered_menu_item(mouse_pos) 106 | 107 | if active_item and active_item != item: 108 | active_item.unhover() 109 | active_item = null 110 | Input.set_default_cursor_shape(Input.CURSOR_ARROW) 111 | 112 | if item: 113 | if active_item != item: 114 | item.hover() 115 | active_item = item 116 | Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND) 117 | 118 | 119 | func get_hovered_menu_item(mouse_pos: Vector2) -> MenuItem_Party: 120 | var ray_start : Vector3 = cam.project_ray_origin(mouse_pos) 121 | var ray_end : Vector3 = ray_start + cam.project_ray_normal(mouse_pos) * RAY_LEN 122 | var space_state : PhysicsDirectSpaceState = get_world().direct_space_state 123 | var result := space_state.intersect_ray(ray_start, ray_end, [], MENU_ITEMS_MASK_BIT) 124 | 125 | return result.collider.get_parent() if not result.empty() else null 126 | -------------------------------------------------------------------------------- /menu-partymode/MenuItem_Party.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://menu-partymode/menu-item.png" type="Texture" id=1] 4 | [ext_resource path="res://fonts/mono-120.tres" type="DynamicFont" id=2] 5 | [ext_resource path="res://menu-partymode/MenuItem_Party.gd" type="Script" id=3] 6 | 7 | [sub_resource type="BoxShape" id=1] 8 | extents = Vector3( 0.39, 0.39, 0.001 ) 9 | 10 | [sub_resource type="ViewportTexture" id=2] 11 | viewport_path = NodePath("Label/Viewport") 12 | 13 | [sub_resource type="ViewportTexture" id=3] 14 | viewport_path = NodePath("ClickedViewport") 15 | 16 | [node name="MenuItem_Party" type="Spatial"] 17 | script = ExtResource( 3 ) 18 | 19 | [node name="MouseCollider" type="StaticBody" parent="."] 20 | collision_layer = 4096 21 | collision_mask = 0 22 | 23 | [node name="CollisionShape" type="CollisionShape" parent="MouseCollider"] 24 | shape = SubResource( 1 ) 25 | 26 | [node name="Button" type="Sprite3D" parent="."] 27 | layers = 32768 28 | modulate = Color( 2.2, 2.2, 2.2, 1 ) 29 | pixel_size = 0.0008 30 | texture = ExtResource( 1 ) 31 | region_enabled = true 32 | region_rect = Rect2( 5, 5, 1000, 1000 ) 33 | 34 | [node name="Label" type="Sprite3D" parent="."] 35 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.2 ) 36 | flip_v = true 37 | pixel_size = 0.0007 38 | texture = SubResource( 2 ) 39 | 40 | [node name="Viewport" type="Viewport" parent="Label"] 41 | size = Vector2( 1000, 1000 ) 42 | transparent_bg = true 43 | 44 | [node name="Label" type="Label" parent="Label/Viewport"] 45 | anchor_right = 1.0 46 | anchor_bottom = 1.0 47 | custom_fonts/font = ExtResource( 2 ) 48 | text = "Button 49 | Label" 50 | align = 1 51 | valign = 1 52 | 53 | [node name="ClickLabels" type="Spatial" parent="."] 54 | 55 | [node name="ClickLabel" type="Sprite3D" parent="ClickLabels"] 56 | transform = Transform( 0.908501, -0.417882, 0, 0.417882, 0.908501, 0, 0, 0, 1, 0.271302, 0.180445, 0.1 ) 57 | visible = false 58 | flip_v = true 59 | modulate = Color( 3, 0, 0, 1 ) 60 | pixel_size = 0.001 61 | texture = SubResource( 3 ) 62 | 63 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel"] 64 | 65 | [node name="ClickLabel2" type="Sprite3D" parent="ClickLabels"] 66 | transform = Transform( 0.960692, 0.277617, 0, -0.277617, 0.960692, 0, 0, 0, 1, -0.24257, -0.254349, 0.1 ) 67 | visible = false 68 | flip_v = true 69 | modulate = Color( 3, 0, 0, 1 ) 70 | pixel_size = 0.001 71 | texture = SubResource( 3 ) 72 | 73 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel2"] 74 | 75 | [node name="ClickLabel3" type="Sprite3D" parent="ClickLabels"] 76 | transform = Transform( 0.807136, -0.590366, 0, 0.590366, 0.807136, 0, 0, 0, 1, -0.33825, 0.0489828, 0.0999999 ) 77 | visible = false 78 | flip_v = true 79 | modulate = Color( 3, 0, 0, 1 ) 80 | pixel_size = 0.001 81 | texture = SubResource( 3 ) 82 | 83 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel3"] 84 | 85 | [node name="ClickLabel4" type="Sprite3D" parent="ClickLabels"] 86 | transform = Transform( 0.916447, -0.400157, 0, 0.400157, 0.916447, 0, 0, 0, 1, 0.253378, -0.281365, 0.1 ) 87 | visible = false 88 | flip_v = true 89 | modulate = Color( 3, 0, 0, 1 ) 90 | pixel_size = 0.001 91 | texture = SubResource( 3 ) 92 | 93 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel4"] 94 | 95 | [node name="ClickLabel5" type="Sprite3D" parent="ClickLabels"] 96 | transform = Transform( 0.95401, 0.299774, 0, -0.299774, 0.95401, 0, 0, 0, 1, -0.162844, 0.261167, 0.1 ) 97 | visible = false 98 | flip_v = true 99 | modulate = Color( 3, 0, 0, 1 ) 100 | pixel_size = 0.001 101 | texture = SubResource( 3 ) 102 | 103 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel5"] 104 | 105 | [node name="ClickLabel6" type="Sprite3D" parent="ClickLabels"] 106 | transform = Transform( 0.95401, 0.299774, 0, -0.299774, 0.95401, 0, 0, 0, 1, 0.386674, -0.0188523, 0.1 ) 107 | visible = false 108 | flip_v = true 109 | modulate = Color( 3, 0, 0, 1 ) 110 | pixel_size = 0.001 111 | texture = SubResource( 3 ) 112 | 113 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel6"] 114 | 115 | [node name="ClickLabel7" type="Sprite3D" parent="ClickLabels"] 116 | transform = Transform( 0.84067, 0.541547, 0, -0.541547, 0.84067, 0, 0, 0, 1, 0.0466986, 0.333314, 0.1 ) 117 | visible = false 118 | flip_v = true 119 | modulate = Color( 3, 0, 0, 1 ) 120 | pixel_size = 0.001 121 | texture = SubResource( 3 ) 122 | 123 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel7"] 124 | 125 | [node name="ClickLabel8" type="Sprite3D" parent="ClickLabels"] 126 | transform = Transform( 0.976642, -0.214871, 0, 0.214871, 0.976642, 0, 0, 0, 1, -0.0200453, -0.321644, 0.1 ) 127 | visible = false 128 | flip_v = true 129 | modulate = Color( 3, 0, 0, 1 ) 130 | pixel_size = 0.001 131 | texture = SubResource( 3 ) 132 | 133 | [node name="Tween" type="Tween" parent="ClickLabels/ClickLabel8"] 134 | 135 | [node name="ClickedViewport" type="Viewport" parent="."] 136 | size = Vector2( 1000, 1000 ) 137 | transparent_bg = true 138 | 139 | [node name="Label" type="Label" parent="ClickedViewport"] 140 | anchor_right = 1.0 141 | anchor_bottom = 1.0 142 | custom_fonts/font = ExtResource( 2 ) 143 | text = "Click!" 144 | align = 1 145 | valign = 1 146 | 147 | [node name="Tween" type="Tween" parent="."] 148 | 149 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] 150 | -------------------------------------------------------------------------------- /menu-partymode/MainMenu.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=12 format=2] 2 | 3 | [ext_resource path="res://menu-partymode/MenuItem_Party.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://menu-partymode/MainMenu.gd" type="Script" id=2] 5 | [ext_resource path="res://fonts/mono-120.tres" type="DynamicFont" id=3] 6 | 7 | [sub_resource type="ViewportTexture" id=20] 8 | viewport_path = NodePath("GameTitle/Viewport") 9 | 10 | [sub_resource type="ViewportTexture" id=21] 11 | viewport_path = NodePath("Party/Viewport") 12 | 13 | [sub_resource type="Animation" id=12] 14 | length = 0.001 15 | tracks/0/type = "value" 16 | tracks/0/path = NodePath(".:modulate") 17 | tracks/0/interp = 1 18 | tracks/0/loop_wrap = true 19 | tracks/0/imported = false 20 | tracks/0/enabled = true 21 | tracks/0/keys = { 22 | "times": PoolRealArray( 0 ), 23 | "transitions": PoolRealArray( 1 ), 24 | "update": 0, 25 | "values": [ Color( 5, 0, 0, 1 ) ] 26 | } 27 | 28 | [sub_resource type="Animation" id=13] 29 | resource_name = "party" 30 | length = 0.8 31 | loop = true 32 | step = 0.02 33 | tracks/0/type = "value" 34 | tracks/0/path = NodePath(".:modulate") 35 | tracks/0/interp = 1 36 | tracks/0/loop_wrap = true 37 | tracks/0/imported = false 38 | tracks/0/enabled = true 39 | tracks/0/keys = { 40 | "times": PoolRealArray( 0, 0.2, 0.3 ), 41 | "transitions": PoolRealArray( 1, 1, 1 ), 42 | "update": 0, 43 | "values": [ Color( 5, 0, 0, 1 ), Color( 0, 0.43, 3, 1 ), Color( 1.38, 1.2, 0, 1 ) ] 44 | } 45 | 46 | [sub_resource type="ViewportTexture" id=22] 47 | viewport_path = NodePath("Unparty/Viewport") 48 | 49 | [sub_resource type="SpatialMaterial" id=19] 50 | emission_enabled = true 51 | emission = Color( 1, 2, 1.5, 1 ) 52 | emission_energy = 1.0 53 | emission_operator = 0 54 | emission_on_uv2 = false 55 | 56 | [sub_resource type="PlaneMesh" id=18] 57 | material = SubResource( 19 ) 58 | 59 | [sub_resource type="Curve" id=15] 60 | _data = [ Vector2( 0, 0 ), 0.0, 8.02138, 0, 0, Vector2( 0.125789, 1 ), 0.0, 0.0, 0, 0, Vector2( 1, 0.288629 ), 0.0, 0.0, 0, 0 ] 61 | 62 | [node name="MainMenu" type="Spatial"] 63 | script = ExtResource( 2 ) 64 | 65 | [node name="CamPivot" type="Position3D" parent="."] 66 | 67 | [node name="Camera" type="Camera" parent="CamPivot"] 68 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5 ) 69 | fov = 30.0 70 | 71 | [node name="GameTitle" type="Sprite3D" parent="."] 72 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.02768, -3 ) 73 | flip_v = true 74 | pixel_size = 0.003 75 | texture = SubResource( 20 ) 76 | 77 | [node name="Viewport" type="Viewport" parent="GameTitle"] 78 | size = Vector2( 2000, 2000 ) 79 | transparent_bg = true 80 | 81 | [node name="Label" type="Label" parent="GameTitle/Viewport"] 82 | anchor_right = 1.0 83 | anchor_bottom = 1.0 84 | custom_fonts/font = ExtResource( 3 ) 85 | text = "Generic Game Title" 86 | align = 1 87 | valign = 1 88 | 89 | [node name="Party" type="Sprite3D" parent="."] 90 | transform = Transform( 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 0, 0, 1, 0.0456954, 0.642578, -1.32992 ) 91 | visible = false 92 | flip_v = true 93 | modulate = Color( 5, 0, 0, 1 ) 94 | pixel_size = 0.002 95 | texture = SubResource( 21 ) 96 | 97 | [node name="Viewport" type="Viewport" parent="Party"] 98 | size = Vector2( 2000, 2000 ) 99 | transparent_bg = true 100 | 101 | [node name="Label" type="Label" parent="Party/Viewport"] 102 | anchor_right = 1.0 103 | anchor_bottom = 1.0 104 | custom_fonts/font = ExtResource( 3 ) 105 | text = "Party Mode!" 106 | align = 1 107 | valign = 1 108 | 109 | [node name="AnimationPlayer" type="AnimationPlayer" parent="Party"] 110 | autoplay = "party" 111 | anims/RESET = SubResource( 12 ) 112 | anims/party = SubResource( 13 ) 113 | 114 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="Party"] 115 | 116 | [node name="AudioStreamPlayer2" type="AudioStreamPlayer" parent="Party"] 117 | 118 | [node name="AudioStreamPlayer3" type="AudioStreamPlayer" parent="Party"] 119 | 120 | [node name="Unparty" type="Sprite3D" parent="."] 121 | transform = Transform( 0.997047, 0.0767886, 0, -0.0767886, 0.997047, 0, 0, 0, 1, 0.0456954, -0.915102, -0.0389571 ) 122 | visible = false 123 | flip_v = true 124 | modulate = Color( 5, 0, 0, 1 ) 125 | pixel_size = 0.0015 126 | texture = SubResource( 22 ) 127 | 128 | [node name="Viewport" type="Viewport" parent="Unparty"] 129 | size = Vector2( 2000, 2000 ) 130 | transparent_bg = true 131 | 132 | [node name="Label" type="Label" parent="Unparty/Viewport"] 133 | anchor_right = 1.0 134 | anchor_bottom = 1.0 135 | custom_fonts/font = ExtResource( 3 ) 136 | text = "Boring Mode Again… :-(" 137 | align = 1 138 | valign = 1 139 | 140 | [node name="Tween" type="Tween" parent="Unparty"] 141 | 142 | [node name="Partycles" type="CPUParticles" parent="."] 143 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -1 ) 144 | emitting = false 145 | amount = 50 146 | lifetime = 0.8 147 | mesh = SubResource( 18 ) 148 | emission_shape = 2 149 | emission_box_extents = Vector3( 4, 1, 1 ) 150 | flag_align_y = true 151 | flag_rotate_y = true 152 | direction = Vector3( 1, 1, 1 ) 153 | initial_velocity = 2.0 154 | initial_velocity_random = 1.0 155 | angular_velocity = 30.0 156 | angular_velocity_random = 1.0 157 | radial_accel = 5.0 158 | damping = 5.0 159 | angle = 10.0 160 | angle_random = 1.0 161 | scale_amount = 0.01 162 | scale_amount_random = 0.1 163 | scale_amount_curve = SubResource( 15 ) 164 | 165 | [node name="MenuItems" type="Spatial" parent="."] 166 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.307838, 0 ) 167 | 168 | [node name="Item1" parent="MenuItems" instance=ExtResource( 1 )] 169 | transform = Transform( 0.965926, 0, 0.258819, 0, 1, 0, -0.258819, 0, 0.965926, -1.5, 0, 0 ) 170 | button_label = "Cool 171 | Menu 172 | Item" 173 | button_rotate_hover = false 174 | 175 | [node name="Item2" parent="MenuItems" instance=ExtResource( 1 )] 176 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0 ) 177 | button_label = "An Awesome 178 | Thing Here!" 179 | button_rotate_hover = false 180 | 181 | [node name="Item3" parent="MenuItems" instance=ExtResource( 1 )] 182 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0 ) 183 | button_label = "And 184 | Here!" 185 | button_rotate_hover = false 186 | 187 | [node name="Item4" parent="MenuItems" instance=ExtResource( 1 )] 188 | transform = Transform( 0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 1.5, 0, 0 ) 189 | button_label = "Yet 190 | Another 191 | One" 192 | button_rotate_hover = false 193 | --------------------------------------------------------------------------------