├── icon.png ├── prefabs ├── EnemyNPC.scn └── base_projectile.tscn ├── godot_modules └── dungeonmap │ ├── SCsub │ ├── config.py │ ├── register_types.cpp │ ├── register_types.h │ ├── dungeon_map_mesh_builder.h │ ├── dungeon_map_builder.h │ ├── dungeon_map_debug_renderer.h │ ├── dungeon_map_builder.cpp │ ├── dungeon_map_debug_renderer.cpp │ ├── dungeon_map.h │ ├── dungeon_map_mesh_builder.cpp │ └── dungeon_map.cpp ├── screenshots ├── dungeon_wave_1.png └── dungeon_wave_2.png ├── scripts ├── FPSLabel.gd ├── FloorMesh.gd ├── main.gd ├── Navigation.gd ├── base_projectile.gd ├── EnemySpawner.gd ├── EnemyNPC.gd ├── LevelGenMapUI.gd └── Player.gd ├── .gitignore ├── icon.png.import ├── export_presets.cfg ├── LICENSE ├── readme.md ├── project.godot ├── default_env.tres ├── game_env.tres └── scenes └── world.tscn /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorholt/dungeonwave/HEAD/icon.png -------------------------------------------------------------------------------- /prefabs/EnemyNPC.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorholt/dungeonwave/HEAD/prefabs/EnemyNPC.scn -------------------------------------------------------------------------------- /godot_modules/dungeonmap/SCsub: -------------------------------------------------------------------------------- 1 | Import('env') 2 | 3 | env.add_source_files(env.modules_sources, '*.cpp') -------------------------------------------------------------------------------- /screenshots/dungeon_wave_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorholt/dungeonwave/HEAD/screenshots/dungeon_wave_1.png -------------------------------------------------------------------------------- /screenshots/dungeon_wave_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorholt/dungeonwave/HEAD/screenshots/dungeon_wave_2.png -------------------------------------------------------------------------------- /godot_modules/dungeonmap/config.py: -------------------------------------------------------------------------------- 1 | def can_build(platform): 2 | return True 3 | 4 | def configure(env): 5 | pass -------------------------------------------------------------------------------- /scripts/FPSLabel.gd: -------------------------------------------------------------------------------- 1 | extends Label 2 | 3 | func _process(delta): 4 | text = "FPS: " + String(Performance.get_monitor(Performance.TIME_FPS)) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.app 2 | *.app/ 3 | Godot.app 4 | .DS_Store 5 | 6 | *.dll 7 | *.o 8 | *.obj 9 | *.so 10 | *.dylib 11 | *.exp 12 | *.lib 13 | 14 | .vscode 15 | .vscode/ 16 | 17 | __pycache__ 18 | __pycache__/ 19 | 20 | .import/* -------------------------------------------------------------------------------- /scripts/FloorMesh.gd: -------------------------------------------------------------------------------- 1 | extends MeshInstance 2 | 3 | onready var level_map = get_parent().get_node('GUI/LevelGenMapUI') 4 | 5 | func _ready(): 6 | level_map.connect("level_image_generation_complete", self, "_on_start_mesh_generation") 7 | 8 | func _on_start_mesh_generation(): 9 | get_parent().get_node("GUI/GameStatusLabel").set_text("Finished") 10 | 11 | -------------------------------------------------------------------------------- /scripts/main.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | var capture_mouse = true 4 | 5 | func _ready(): 6 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 7 | 8 | func _process(delta): 9 | if Input.is_action_just_pressed("ui_cancel"): 10 | if capture_mouse: 11 | Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) 12 | capture_mouse = false 13 | else: 14 | Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 15 | capture_mouse = true 16 | # get_tree().quit() 17 | if Input.is_action_just_pressed("restart"): 18 | get_tree().reload_current_scene() 19 | -------------------------------------------------------------------------------- /scripts/Navigation.gd: -------------------------------------------------------------------------------- 1 | extends Navigation 2 | 3 | onready var dungeon_map = get_parent().get_node("DungeonMap") 4 | onready var level_gen_ui = get_node("../GUI/LevelGenMapUI") 5 | 6 | func _ready(): 7 | dungeon_map.add_navigation_meshes(self) 8 | level_gen_ui.connect("level_image_generating", self, "_generating_map") 9 | level_gen_ui.connect("level_image_generation_complete", self, "_generate_map_complete") 10 | 11 | func _generating_map(): 12 | dungeon_map.remove_navigation_meshes(self) 13 | 14 | func _generate_map_complete(): 15 | dungeon_map.add_navigation_meshes(self) -------------------------------------------------------------------------------- /scripts/base_projectile.gd: -------------------------------------------------------------------------------- 1 | extends RigidBody 2 | 3 | # class member variables go here, for example: 4 | var timer = 0 5 | var damage = 10 6 | 7 | func _ready(): 8 | set_meta("projectile", true) 9 | 10 | func _physics_process(delta): 11 | # Check if we've collided. 12 | #if contacts_reported > 0: 13 | # var bodies = get_colliding_bodies() 14 | #for c in bodies: 15 | # emit_signal("projectile_hit_event", self, c) 16 | 17 | # Check if we need to delete the projectile. 18 | timer += delta 19 | if timer > 500: 20 | timer = 0 21 | queue_free() 22 | timer += 1 23 | -------------------------------------------------------------------------------- /godot_modules/dungeonmap/register_types.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 GameSencha, LLC 3 | * @author Victor Holt 4 | * @date 2/17/2018 5 | */ 6 | #include "register_types.h" 7 | #include "dungeon_map.h" 8 | #include "dungeon_map_debug_renderer.h" 9 | #ifndef _3D_DISABLED 10 | #include "class_db.h" 11 | #endif 12 | 13 | void register_dungeonmap_types() 14 | { 15 | #ifndef _3D_DISABLED 16 | ClassDB::register_class(); 17 | ClassDB::register_class(); 18 | #ifdef TOOLS_ENABLED 19 | #endif 20 | #endif 21 | } 22 | 23 | void unregister_dungeonmap_types() 24 | { 25 | 26 | } -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://icon.png" 10 | source_md5="ae7e641067601e2184afcade49abd283" 11 | 12 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 13 | dest_md5="84511021bbc8c9d37c7f0f4d181de883" 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/normal_map=0 21 | flags/repeat=0 22 | flags/filter=true 23 | flags/mipmaps=false 24 | flags/anisotropic=false 25 | flags/srgb=2 26 | process/fix_alpha_border=true 27 | process/premult_alpha=false 28 | process/HDR_as_SRGB=false 29 | stream=false 30 | size_limit=0 31 | detect_3d=true 32 | svg/scale=1.0 33 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Windows Desktop" 4 | platform="Windows Desktop" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | patch_list=PoolStringArray( ) 11 | 12 | [preset.0.options] 13 | 14 | texture_format/s3tc=true 15 | texture_format/etc=false 16 | texture_format/etc2=false 17 | binary_format/64_bits=true 18 | custom_template/release="D:/Projects/guilden-mobile/godot/templates/windows_64_release.exe" 19 | custom_template/debug="D:/Projects/guilden-mobile/godot/templates/windows_64_debug.exe" 20 | application/icon="" 21 | application/file_version="" 22 | application/product_version="" 23 | application/company_name="" 24 | application/product_name="" 25 | application/file_description="" 26 | application/copyright="" 27 | application/trademarks="" 28 | -------------------------------------------------------------------------------- /scripts/EnemySpawner.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | # Dictionary of all enemies spawned. 4 | var npc_dictionary = Dictionary() 5 | onready var npc_enemy_prefab = preload("res://prefabs/EnemyNPC.scn") 6 | onready var dungeon_map = get_parent().get_node("DungeonMap") 7 | onready var level_gen_ui = get_node("../GUI/LevelGenMapUI") 8 | 9 | 10 | func _ready(): 11 | level_gen_ui.connect("level_image_generation_complete", self, "_generate_map_complete") 12 | _load_enemies() 13 | 14 | func _generate_map_complete(): 15 | _load_enemies() 16 | 17 | func _load_enemies(): 18 | if npc_dictionary.size() > 0: 19 | for i in range(20): 20 | npc_dictionary[i].queue_free() 21 | npc_dictionary.clear() 22 | 23 | for i in range(20): 24 | var rand_loc = dungeon_map.get_random_map_location() 25 | var npc_instance = npc_enemy_prefab.instance() 26 | npc_instance.translation = Vector3(rand_loc.x, 0.51, rand_loc.y) 27 | add_child(npc_instance) 28 | npc_dictionary[i] = npc_instance -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /godot_modules/dungeonmap/register_types.h: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author: Victor Holt 3 | * The MIT License (MIT) 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | **********************************************************/ 24 | 25 | void register_dungeonmap_types(); 26 | void unregister_dungeonmap_types(); -------------------------------------------------------------------------------- /scripts/EnemyNPC.gd: -------------------------------------------------------------------------------- 1 | extends RigidBody 2 | 3 | var has_target = false 4 | var health = 100 5 | var dead = false 6 | 7 | func _ready(): 8 | pass 9 | 10 | func _physics_process(delta): 11 | _check_hit() 12 | _patrol(delta) 13 | 14 | func _check_hit(): 15 | if dead: 16 | return 17 | 18 | if contacts_reported > 0: 19 | var bodies = get_colliding_bodies() 20 | for c in bodies: 21 | if c.has_meta("projectile"): 22 | # Free up the projectile. 23 | c.queue_free() 24 | 25 | # Not the best way to handle this.... 26 | hide() 27 | translation = Vector3(-9999,-9999,-9999) 28 | dead = true 29 | return 30 | 31 | func _patrol(delta): 32 | if dead: 33 | return 34 | 35 | var navigation = get_node("../../Navigation") 36 | var player = get_node("../../Player") 37 | var path_to_player = PoolVector3Array() 38 | 39 | if player and translation.distance_to(player.translation) > 30: 40 | return 41 | 42 | if navigation and player: 43 | path_to_player = navigation.get_simple_path(translation, player.translation, false) 44 | if path_to_player.size() == 0: 45 | return 46 | has_target = true 47 | 48 | if !has_target: 49 | return 50 | 51 | var next_pos = Vector3() 52 | next_pos = translation.linear_interpolate(path_to_player[path_to_player.size() - 1], 0.5 * delta) 53 | translation = next_pos 54 | translation.y = 0.51 55 | 56 | #func _projectile_hit_event(projectile, body): 57 | # print("I am checking hit event") 58 | # if body == self: 59 | # print("I've been hit with " + String(projectile.damage) + " damage") 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | A Godot game I did over the weekend to demonstrate how you can create custom modules and tie those into NavigationMeshes. 4 | 5 | Includes my DungeonMap and DungeonMapDebugRenderer custom nodes. 6 | 7 | # Requirements 8 | 9 | **Requires you to build your own CUSTOM version of Godot with the dungeonmap module.** 10 | 11 | # Video/Screenshots 12 | 13 | Video: https://www.youtube.com/watch?v=INOjQGeNRaA 14 | 15 | ![Dungeon Map Module](./screenshots/dungeon_wave_1.png) 16 | ![Dungeon Map Module](./screenshots/dungeon_wave_2.png) 17 | 18 | # Credits 19 | 20 | Jeremy Bullock (FPS Camera Code) 21 | https://www.youtube.com/channel/UCwJw2-V5S1TkBjLQ3_Ws54g 22 | 23 | # License 24 | 25 | The MIT License (MIT) 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a 28 | copy of this software and associated documentation files (the "Software"), 29 | to deal in the Software without restriction, including without limitation 30 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 31 | and/or sell copies of the Software, and to permit persons to whom the 32 | Software is furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in 35 | all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 40 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 43 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /prefabs/base_projectile.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scripts/base_projectile.gd" type="Script" id=1] 4 | 5 | [sub_resource type="CapsuleMesh" id=1] 6 | 7 | radius = 1.0 8 | mid_height = 1.0 9 | radial_segments = 64 10 | rings = 8 11 | 12 | [sub_resource type="CapsuleShape" id=2] 13 | 14 | radius = 1.0 15 | height = 1.0 16 | 17 | [node name="Spatial" type="RigidBody" index="0"] 18 | 19 | input_ray_pickable = true 20 | input_capture_on_drag = false 21 | collision_layer = 2 22 | collision_mask = 3 23 | mode = 0 24 | mass = 0.5 25 | friction = 1.0 26 | bounce = 0.0 27 | gravity_scale = 1.0 28 | custom_integrator = false 29 | continuous_cd = true 30 | contacts_reported = 1 31 | contact_monitor = true 32 | sleeping = false 33 | can_sleep = true 34 | axis_lock_linear_x = false 35 | axis_lock_linear_y = false 36 | axis_lock_linear_z = false 37 | axis_lock_angular_x = false 38 | axis_lock_angular_y = false 39 | axis_lock_angular_z = false 40 | linear_velocity = Vector3( 0, 0, 0 ) 41 | linear_damp = -1.0 42 | angular_velocity = Vector3( 0, 0, 0 ) 43 | angular_damp = -1.0 44 | script = ExtResource( 1 ) 45 | _sections_unfolded = [ "Axis Lock", "Collision", "Linear", "Transform" ] 46 | 47 | [node name="MeshInstance" type="MeshInstance" parent="." index="0"] 48 | 49 | transform = Transform( 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.15, 0, 0, 0 ) 50 | layers = 1 51 | material_override = null 52 | cast_shadow = 1 53 | extra_cull_margin = 0.0 54 | use_in_baked_light = false 55 | lod_min_distance = 0.0 56 | lod_min_hysteresis = 0.0 57 | lod_max_distance = 0.0 58 | lod_max_hysteresis = 0.0 59 | mesh = SubResource( 1 ) 60 | skeleton = NodePath("..") 61 | material/0 = null 62 | _sections_unfolded = [ "Transform" ] 63 | 64 | [node name="CollisionShape" type="CollisionShape" parent="." index="1"] 65 | 66 | transform = Transform( 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.2, 0, 0, 0 ) 67 | shape = SubResource( 2 ) 68 | disabled = false 69 | _sections_unfolded = [ "Transform" ] 70 | 71 | 72 | -------------------------------------------------------------------------------- /scripts/LevelGenMapUI.gd: -------------------------------------------------------------------------------- 1 | extends TextureRect 2 | 3 | signal level_image_generating 4 | signal level_image_generation_complete 5 | 6 | onready var dungeon_map = get_parent().get_parent().get_node("DungeonMap") 7 | onready var generate_button = get_parent().get_node("GenerateLevelBtn") 8 | onready var player = get_parent().get_parent().get_node("Player") 9 | onready var location_label = get_parent().get_node("LocationLabel") 10 | 11 | var is_generating = false 12 | var last_pos = Vector2() 13 | 14 | func _ready(): 15 | set_texture(dungeon_map.get_map_texture()) 16 | generate_button.connect("pressed", self, "_generate_map") 17 | 18 | # Update the label with the seed. 19 | get_parent().get_node("GameStatusLabel").set_text("Seed: " + String(dungeon_map.get_dungeon_seed())) 20 | 21 | func _process(delta): 22 | update_position() 23 | 24 | func update_position(): 25 | if !player: 26 | return 27 | 28 | var new_pos = Vector2(floor(player.translation.x), floor(player.translation.z)) 29 | var map_image = dungeon_map.get_map_image() 30 | 31 | var snap_pos = dungeon_map.get_tile_position(new_pos) 32 | snap_pos.y *= -1 33 | location_label.text = "Location: " + String(snap_pos) 34 | 35 | # Update our map 36 | if new_pos != last_pos: 37 | if dungeon_map.is_valid_position(last_pos): 38 | dungeon_map.set_map_tile_color(dungeon_map.get_tile_position(last_pos), Color(1,1,1)) 39 | 40 | if dungeon_map.is_valid_position(new_pos): 41 | dungeon_map.set_map_tile_color(dungeon_map.get_tile_position(new_pos), Color(1,0,0)) 42 | last_pos = new_pos 43 | 44 | func _generate_map(): 45 | if is_generating: 46 | return 47 | 48 | # Send signal that we're generating. 49 | emit_signal("level_image_generating") 50 | 51 | # Reset the focus. 52 | generate_button.set_focus_mode(Control.FOCUS_NONE) 53 | 54 | # Update the seed. 55 | dungeon_map.set_dungeon_seed(randi() % 2000000000) 56 | get_parent().get_node("GameStatusLabel").set_text("Seed: " + String(dungeon_map.get_dungeon_seed())) 57 | 58 | # Update generating flag and reset the map. 59 | is_generating = true 60 | 61 | # Update the status label. 62 | dungeon_map.mark_dirty() 63 | dungeon_map.generate_map_image() 64 | dungeon_map.apply() 65 | 66 | is_generating = false 67 | 68 | # Update the map texture. 69 | set_texture(dungeon_map.get_map_texture()) 70 | 71 | # Set the new player location. 72 | if (player): 73 | var rand_map_loc = dungeon_map.get_random_map_location() 74 | player.translation = Vector3(rand_map_loc.x, 0, rand_map_loc.y) 75 | player.translation.y += 0.05 76 | 77 | # Emit our signal to generate the level mesh. 78 | emit_signal("level_image_generation_complete") 79 | 80 | -------------------------------------------------------------------------------- /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=3 10 | 11 | [application] 12 | 13 | config/name="DungeonWave" 14 | run/main_scene="res://scenes/world.tscn" 15 | run/low_processor_mode=true 16 | config/icon="res://icon.png" 17 | 18 | [display] 19 | 20 | window/size/width=1600 21 | window/size/height=900 22 | 23 | [input] 24 | 25 | move_left=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) 26 | ] 27 | move_right=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null) 28 | ] 29 | move_forward=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null) 30 | ] 31 | move_backward=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null) 32 | ] 33 | move_sprint=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null) 34 | ] 35 | jump=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null) 36 | ] 37 | restart=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":82,"unicode":0,"echo":false,"script":null) 38 | ] 39 | fire=[ 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) 40 | ] 41 | 42 | [physics] 43 | 44 | common/physics_fps=120 45 | 3d/physics_engine="Bullet" 46 | 47 | [rendering] 48 | 49 | quality/shadows/filter_mode=0 50 | quality/subsurface_scattering/quality=2 51 | environment/default_environment="res://default_env.tres" 52 | -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_mesh_builder.h: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author: Victor Holt 3 | * The MIT License (MIT) 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | **********************************************************/ 24 | 25 | #ifndef DUNGEON_MAP_MESH_BUILDER_H 26 | #define DUNGEON_MAP_MESH_BUILDER_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | class DungeonMap; 33 | 34 | class DungeonMapMeshBuilder 35 | { 36 | public: 37 | // Calculating the uv for the tile meshes. 38 | enum UVType 39 | { 40 | UV_XY = 0, 41 | UV_XZ, 42 | UV_YZ, 43 | UV_ZY 44 | }; 45 | 46 | // Creates a tile mesh based on the given tile id. 47 | static void add_mesh_tile(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, int height, bool inverse = false); 48 | // Creates a tile mesh based on the given tile id. 49 | static void add_mesh_tile(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, bool inverse = false); 50 | 51 | // Creates a tile mesh based on the given tile id. 52 | static void add_mesh_tile_edge(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, int height, bool inverse = false); 53 | // Creates a tile mesh based on the given tile id. 54 | static void add_mesh_tile_edge(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, bool inverse = false); 55 | 56 | // Creates a mesh from a given aabb. 57 | static RID create_mesh_from_aabb(const AABB& aabb); 58 | // Creates mesh lines from a given aabb. 59 | static RID create_mesh_lines_from_aabb(const AABB& aabb); 60 | 61 | // Returns a uv coordinate for a vertex. 62 | static Vector2 get_uv(UVType uvType, const Vector3& vertex, float scale = 1.0f); 63 | }; 64 | 65 | #endif -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | radiance_size = 4 6 | sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) 7 | sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) 8 | sky_curve = 0.25 9 | sky_energy = 1.0 10 | ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) 11 | ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) 12 | ground_curve = 0.01 13 | ground_energy = 1.0 14 | sun_color = Color( 1, 1, 1, 1 ) 15 | sun_latitude = 35.0 16 | sun_longitude = 0.0 17 | sun_angle_min = 1.0 18 | sun_angle_max = 100.0 19 | sun_curve = 0.05 20 | sun_energy = 16.0 21 | texture_size = 2 22 | 23 | [resource] 24 | 25 | background_mode = 2 26 | background_sky = SubResource( 1 ) 27 | background_sky_custom_fov = 0.0 28 | background_color = Color( 0, 0, 0, 1 ) 29 | background_energy = 1.0 30 | background_canvas_max_layer = 0 31 | ambient_light_color = Color( 0, 0, 0, 1 ) 32 | ambient_light_energy = 1.0 33 | ambient_light_sky_contribution = 1.0 34 | fog_enabled = false 35 | fog_color = Color( 0.5, 0.6, 0.7, 1 ) 36 | fog_sun_color = Color( 1, 0.9, 0.7, 1 ) 37 | fog_sun_amount = 0.0 38 | fog_depth_enabled = true 39 | fog_depth_begin = 10.0 40 | fog_depth_curve = 1.0 41 | fog_transmit_enabled = false 42 | fog_transmit_curve = 1.0 43 | fog_height_enabled = false 44 | fog_height_min = 0.0 45 | fog_height_max = 100.0 46 | fog_height_curve = 1.0 47 | tonemap_mode = 0 48 | tonemap_exposure = 1.0 49 | tonemap_white = 1.0 50 | auto_exposure_enabled = false 51 | auto_exposure_scale = 0.4 52 | auto_exposure_min_luma = 0.05 53 | auto_exposure_max_luma = 8.0 54 | auto_exposure_speed = 0.5 55 | ss_reflections_enabled = false 56 | ss_reflections_max_steps = 64 57 | ss_reflections_fade_in = 0.15 58 | ss_reflections_fade_out = 2.0 59 | ss_reflections_depth_tolerance = 0.2 60 | ss_reflections_roughness = true 61 | ssao_enabled = false 62 | ssao_radius = 1.0 63 | ssao_intensity = 1.0 64 | ssao_radius2 = 0.0 65 | ssao_intensity2 = 1.0 66 | ssao_bias = 0.01 67 | ssao_light_affect = 0.0 68 | ssao_color = Color( 0, 0, 0, 1 ) 69 | ssao_quality = 0 70 | ssao_blur = 3 71 | ssao_edge_sharpness = 4.0 72 | dof_blur_far_enabled = false 73 | dof_blur_far_distance = 10.0 74 | dof_blur_far_transition = 5.0 75 | dof_blur_far_amount = 0.1 76 | dof_blur_far_quality = 1 77 | dof_blur_near_enabled = false 78 | dof_blur_near_distance = 2.0 79 | dof_blur_near_transition = 1.0 80 | dof_blur_near_amount = 0.1 81 | dof_blur_near_quality = 1 82 | glow_enabled = false 83 | glow_levels/1 = false 84 | glow_levels/2 = false 85 | glow_levels/3 = true 86 | glow_levels/4 = false 87 | glow_levels/5 = true 88 | glow_levels/6 = false 89 | glow_levels/7 = false 90 | glow_intensity = 0.8 91 | glow_strength = 1.0 92 | glow_bloom = 0.0 93 | glow_blend_mode = 2 94 | glow_hdr_threshold = 1.0 95 | glow_hdr_scale = 2.0 96 | glow_bicubic_upscale = false 97 | adjustment_enabled = false 98 | adjustment_brightness = 1.0 99 | adjustment_contrast = 1.0 100 | adjustment_saturation = 1.0 101 | 102 | -------------------------------------------------------------------------------- /game_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | radiance_size = 4 6 | sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) 7 | sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) 8 | sky_curve = 0.25 9 | sky_energy = 1.0 10 | ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) 11 | ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) 12 | ground_curve = 0.01 13 | ground_energy = 1.0 14 | sun_color = Color( 1, 1, 1, 1 ) 15 | sun_latitude = 35.0 16 | sun_longitude = 0.0 17 | sun_angle_min = 1.0 18 | sun_angle_max = 100.0 19 | sun_curve = 0.05 20 | sun_energy = 16.0 21 | texture_size = 2 22 | _sections_unfolded = [ "Sky" ] 23 | 24 | [resource] 25 | 26 | background_mode = 2 27 | background_sky = SubResource( 1 ) 28 | background_sky_custom_fov = 0.0 29 | background_color = Color( 0, 0, 0, 1 ) 30 | background_energy = 1.0 31 | background_canvas_max_layer = 0 32 | ambient_light_color = Color( 0.0390625, 0.0390625, 0.0390625, 1 ) 33 | ambient_light_energy = 2.0 34 | ambient_light_sky_contribution = 0.0 35 | fog_enabled = true 36 | fog_color = Color( 0.5, 0.6, 0.7, 1 ) 37 | fog_sun_color = Color( 1, 0.9, 0.7, 1 ) 38 | fog_sun_amount = 0.0 39 | fog_depth_enabled = true 40 | fog_depth_begin = 50.0 41 | fog_depth_curve = 0.933033 42 | fog_transmit_enabled = false 43 | fog_transmit_curve = 1.0 44 | fog_height_enabled = false 45 | fog_height_min = 0.0 46 | fog_height_max = 100.0 47 | fog_height_curve = 1.0 48 | tonemap_mode = 0 49 | tonemap_exposure = 1.0 50 | tonemap_white = 1.0 51 | auto_exposure_enabled = false 52 | auto_exposure_scale = 0.4 53 | auto_exposure_min_luma = 0.05 54 | auto_exposure_max_luma = 8.0 55 | auto_exposure_speed = 0.5 56 | ss_reflections_enabled = false 57 | ss_reflections_max_steps = 64 58 | ss_reflections_fade_in = 0.15 59 | ss_reflections_fade_out = 2.0 60 | ss_reflections_depth_tolerance = 0.2 61 | ss_reflections_roughness = true 62 | ssao_enabled = false 63 | ssao_radius = 2.0 64 | ssao_intensity = 1.0 65 | ssao_radius2 = 0.0 66 | ssao_intensity2 = 1.0 67 | ssao_bias = 0.01 68 | ssao_light_affect = 0.0 69 | ssao_color = Color( 0, 0, 0, 1 ) 70 | ssao_quality = 0 71 | ssao_blur = 1 72 | ssao_edge_sharpness = 1.0 73 | dof_blur_far_enabled = false 74 | dof_blur_far_distance = 50.0 75 | dof_blur_far_transition = 5.0 76 | dof_blur_far_amount = 0.1 77 | dof_blur_far_quality = 1 78 | dof_blur_near_enabled = false 79 | dof_blur_near_distance = 2.0 80 | dof_blur_near_transition = 1.0 81 | dof_blur_near_amount = 0.1 82 | dof_blur_near_quality = 1 83 | glow_enabled = false 84 | glow_levels/1 = false 85 | glow_levels/2 = false 86 | glow_levels/3 = true 87 | glow_levels/4 = false 88 | glow_levels/5 = true 89 | glow_levels/6 = false 90 | glow_levels/7 = false 91 | glow_intensity = 0.8 92 | glow_strength = 1.0 93 | glow_bloom = 0.0 94 | glow_blend_mode = 2 95 | glow_hdr_threshold = 1.0 96 | glow_hdr_scale = 2.0 97 | glow_bicubic_upscale = false 98 | adjustment_enabled = false 99 | adjustment_brightness = 1.0 100 | adjustment_contrast = 1.0 101 | adjustment_saturation = 1.0 102 | _sections_unfolded = [ "Ambient Light", "Background", "Fog", "Tonemap" ] 103 | 104 | -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_builder.h: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author: Victor Holt 3 | * The MIT License (MIT) 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | **********************************************************/ 24 | 25 | #ifndef DUNGEON_MAP_BUILDER_H 26 | #define DUNGEON_MAP_BUILDER_H 27 | #include 28 | #include 29 | #include 30 | 31 | class DungeonMapBuilder 32 | { 33 | public: 34 | struct DungeonParams 35 | { 36 | // Seed for generating maps. 37 | // long seed = 345432342352; 38 | long seed = 1023321012; 39 | // The dungeon size. 40 | int dungeon_size = 256; 41 | // Number of floors placed. 42 | int floors_placed = 0; 43 | // Size of a single floor tile. 44 | int floor_size = 8; 45 | // Max floors that can be placed. 46 | int max_floors = 1500; 47 | // Current direction for the floor placement. 48 | int dir = 1; 49 | // Random starting x position. 50 | int rand_x = 0; 51 | // Random starting y position. 52 | int rand_y = 0; 53 | // Lower-bound position. 54 | Vector2 lower_bound; 55 | // Upper-bound position. 56 | Vector2 upper_bound; 57 | // Current x position. 58 | int current_x = 0; 59 | // Current y position. 60 | int current_y = 0; 61 | // Flag for whether or not the dungeon is generating. 62 | bool is_generating = false; 63 | }; 64 | 65 | private: 66 | // Image of the map. 67 | Ref map_image; 68 | // Texture for the map. 69 | Ref map_texture; 70 | // Flag for whether or not the dungeon is dirty. 71 | bool dirty = true; 72 | 73 | // Build the floors. 74 | void _build_floors(); 75 | // Place a single floor. 76 | void _place_floor(); 77 | 78 | // Check if we need to update the bounds. 79 | void _update_bounds(); 80 | 81 | public: 82 | // Dungeon generating parameters. 83 | DungeonParams params; 84 | 85 | // Constructor. 86 | DungeonMapBuilder(); 87 | // Destructor. 88 | virtual ~DungeonMapBuilder(); 89 | 90 | // Create the map image. 91 | void create_map_image(); 92 | // Clears the map image. 93 | void clear_map_image(); 94 | 95 | // Generates the map image. 96 | void generate_map_image(); 97 | 98 | // Set the map image color for a specific tile. 99 | void set_map_tile_color(const Vector2& tile_id, Color color); 100 | 101 | // Returns the map image. 102 | _FORCE_INLINE_ Ref get_map_image() const { return map_image; } 103 | // Returns the map texture. 104 | _FORCE_INLINE_ Ref get_map_texture() const { return map_texture; } 105 | }; 106 | 107 | #endif -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_debug_renderer.h: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author: Victor Holt 3 | * The MIT License (MIT) 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | **********************************************************/ 24 | 25 | #ifndef DUNGEON_MAP_DEBUG_RENDERER_H 26 | #define DUNGEON_MAP_DEBUG_RENDERER_H 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "dungeon_map.h" 37 | 38 | class DungeonMapDebugRenderer : public Spatial 39 | { 40 | GDCLASS(DungeonMapDebugRenderer, Spatial); 41 | OBJ_CATEGORY("3D Visual Nodes"); 42 | 43 | /// Parent node for the debug renderer. 44 | DungeonMap* parent = NULL; 45 | 46 | /// Region view instances. 47 | Map region_instances; 48 | /// Region debug meshes. 49 | Map region_meshes; 50 | 51 | /// Tile view instances. 52 | Map tile_instances; 53 | /// Tile debug meshes. 54 | Map tile_meshes; 55 | 56 | /// Debug material instance for the region. 57 | Ref debug_region_material; 58 | /// Debug material instance for the tiles. 59 | Ref debug_tile_material; 60 | 61 | /// Flag to display all regions. 62 | bool region_display_all = false; 63 | /// Flag to display all tiles. 64 | bool tile_display_all = false; 65 | 66 | protected: 67 | // Updates the visibility of the node. 68 | void _update_visibility(); 69 | // Updates the grid transformation. 70 | void _update_transform(); 71 | 72 | // Handles notifications for the node. 73 | void _notification(int p_what); 74 | // Set property from the editor. 75 | bool _set(const StringName &p_name, const Variant &p_value); 76 | // Retrieve property from the editor. 77 | bool _get(const StringName &p_name, Variant &r_ret) const; 78 | // Retrieves the properly list for the editor. 79 | void _get_property_list(List *p_list) const; 80 | // Binds methods for the terrain. 81 | static void _bind_methods(); 82 | 83 | public: 84 | // Constructor. 85 | DungeonMapDebugRenderer(); 86 | // Destructor. 87 | ~DungeonMapDebugRenderer(); 88 | 89 | // Displays a region's debug aabb. 90 | void region_display(const Vector2& region_id); 91 | // Display's a tile's debug aabb. 92 | void tile_display(const Vector2& tile_id); 93 | // Displays a tile's aabb and the neighbor's aabb. 94 | void tile_display_neighbors(const Vector2& tile_id); 95 | 96 | // Sets whether or not to display all region debug meshes. 97 | void set_region_display_all(bool display); 98 | // Sets whether or not to display all tile debug meshes. 99 | void set_tile_display_all(bool display); 100 | 101 | // Clears and updates the debug renderer instances/meshes. 102 | void update(); 103 | // Clears the debug renderer instances/meshes. 104 | void clear(); 105 | }; 106 | 107 | #endif -------------------------------------------------------------------------------- /scripts/Player.gd: -------------------------------------------------------------------------------- 1 | extends KinematicBody 2 | 3 | # Camera variables 4 | var camera_angle = 0 5 | var mouse_sensitivity = 0.3 6 | 7 | # Movement variables 8 | var velocity = Vector3() 9 | var direction = Vector3() 10 | 11 | # Fly constants 12 | const FLY_SPEED = 40 13 | const FLY_ACCEL = 4 14 | 15 | # Walk variables/constants 16 | var gravity = -9.8 * 3 17 | const MAX_SPEED = 8 18 | const MAX_RUNNING_SPEED = 20 19 | const ACCEL = 2 20 | const DEACCEL = 20 21 | 22 | # Jumping variables/constants 23 | var jump_height = 15 24 | 25 | onready var dungeon_map = get_parent().get_node("DungeonMap") 26 | 27 | func _ready(): 28 | # Set the new player location. 29 | var rand_map_loc = dungeon_map.get_random_map_location() 30 | translation = Vector3(rand_map_loc.x, 0, rand_map_loc.y) 31 | translation.y += 0.05 32 | 33 | func _physics_process(delta): 34 | # Ensure we don't process any input while the mouse 35 | # is not captured. 36 | if !get_parent().capture_mouse: 37 | return 38 | 39 | walk(delta) 40 | # fly(delta) 41 | 42 | fire(delta) 43 | 44 | func _input(event): 45 | # Ensure we don't process any input while the mouse 46 | # is not captured. 47 | if !get_parent().capture_mouse: 48 | return 49 | 50 | if event is InputEventMouseMotion: 51 | $Head.rotate_y(deg2rad(-event.relative.x * mouse_sensitivity)) 52 | 53 | # How far we've moved up/down 54 | var change = -event.relative.y * mouse_sensitivity 55 | if (change + camera_angle) < 90 && (change + camera_angle) > -90: 56 | $Head/Camera.rotate_x(deg2rad(change)) 57 | camera_angle += change 58 | 59 | func fly(delta): 60 | # Reset our direction for the player. 61 | direction = Vector3() 62 | 63 | # Get the rotation of the camera. 64 | var aim = $Head/Camera.get_global_transform().basis 65 | 66 | # Handle player movement. 67 | if Input.is_action_pressed("move_forward"): 68 | direction -= aim.z 69 | if Input.is_action_pressed("move_backward"): 70 | direction += aim.z 71 | if Input.is_action_pressed("move_left"): 72 | direction -= aim.x 73 | if Input.is_action_pressed("move_right"): 74 | direction += aim.x 75 | 76 | # Normalize our direction so that we move the same speed no matter 77 | # the direction we want to go. 78 | direction = direction.normalized() 79 | 80 | # How far will we go at max speed. 81 | var target = direction * FLY_SPEED 82 | 83 | # Accel to the max speed. Calculate a portion of the distance to go. 84 | velocity = velocity.linear_interpolate(target, FLY_ACCEL * delta) 85 | 86 | # Move our player. 87 | move_and_slide(velocity) 88 | 89 | func walk(delta): 90 | # Reset our direction for the player. 91 | direction = Vector3() 92 | 93 | # Get the rotation of the camera. 94 | var aim = $Head/Camera.get_global_transform().basis 95 | 96 | # Handle player movement. 97 | if Input.is_action_pressed("move_forward"): 98 | direction -= aim.z 99 | if Input.is_action_pressed("move_backward"): 100 | direction += aim.z 101 | if Input.is_action_pressed("move_left"): 102 | direction -= aim.x 103 | if Input.is_action_pressed("move_right"): 104 | direction += aim.x 105 | 106 | # Normalize our direction so that we move the same speed no matter 107 | # the direction we want to go. 108 | direction = direction.normalized() 109 | 110 | # Apply our gravity. 111 | velocity.y += gravity * delta 112 | 113 | var temp_velocity = velocity 114 | temp_velocity.y = 0 115 | 116 | # Check if we should run/walk. 117 | var speed = MAX_SPEED 118 | if Input.is_action_pressed("move_sprint"): 119 | speed = MAX_RUNNING_SPEED 120 | 121 | # How far will we go at max speed. 122 | var target = direction * speed 123 | 124 | var acceleration = DEACCEL 125 | # If two vectors are going in the same general direction, 126 | # their dot product will be positive. 127 | if direction.dot(temp_velocity) > 0: 128 | acceleration = ACCEL 129 | 130 | # Accel to the max speed. Calculate a portion of the distance to go. 131 | temp_velocity = temp_velocity.linear_interpolate(target, acceleration * delta) 132 | velocity.x = temp_velocity.x 133 | velocity.z = temp_velocity.z 134 | 135 | # Move our player. 136 | velocity = move_and_slide(velocity, Vector3(0, 1, 0)) 137 | 138 | if Input.is_action_just_pressed("jump"): 139 | velocity.y = jump_height 140 | 141 | func fire(delta): 142 | if !Input.is_action_just_pressed("fire"): 143 | return 144 | 145 | # Create a projectile based on our current weapon. 146 | var projectile = preload("res://prefabs/base_projectile.tscn").instance() 147 | projectile.set_transform($Head/Camera.get_global_transform()) 148 | get_parent().add_child(projectile) 149 | 150 | var velocity = $Head/Camera.get_global_transform().basis[2].normalized() * Vector3(-1,-1,-1) 151 | projectile.set_linear_velocity(velocity * 20) 152 | projectile.add_collision_exception_with(self) -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_builder.cpp: -------------------------------------------------------------------------------- 1 | #include "dungeon_map_builder.h" 2 | 3 | #include 4 | #include 5 | 6 | void DungeonMapBuilder::generate_map_image() 7 | { 8 | Math::seed(params.seed); 9 | 10 | // Reset our params. 11 | params.floors_placed = 0; 12 | params.lower_bound = Vector2(0, 0); 13 | params.upper_bound = Vector2(0, 0); 14 | 15 | // Clear the image. 16 | if (!map_image.is_valid()) { 17 | create_map_image(); 18 | } else { 19 | clear_map_image(); 20 | } 21 | 22 | _build_floors(); 23 | 24 | // Update our map texture. 25 | map_texture->set_data(map_image); 26 | } 27 | 28 | void DungeonMapBuilder::create_map_image() 29 | { 30 | if (map_image.is_valid()) { 31 | map_image.unref(); 32 | } 33 | if (map_texture.is_valid()) { 34 | map_texture.unref(); 35 | } 36 | 37 | Ref img = memnew(Image( 38 | params.dungeon_size, 39 | params.dungeon_size, 40 | false, 41 | Image::FORMAT_RGB8 42 | )); 43 | 44 | map_image = img; 45 | 46 | Ref tex = memnew(ImageTexture); 47 | tex->create_from_image(map_image); 48 | map_texture = tex; 49 | } 50 | 51 | void DungeonMapBuilder::clear_map_image() 52 | { 53 | if (!map_image.is_valid()) { 54 | return; 55 | } 56 | 57 | int w = map_image->get_width(); 58 | int h = map_image->get_height(); 59 | 60 | map_image->lock(); 61 | for (int x = 0; x < w; x++) { 62 | for (int y = 0; y < h; y++) { 63 | map_image->set_pixel(x, y, Color(0, 0, 0)); 64 | } 65 | } 66 | map_image->unlock(); 67 | 68 | // Update the texture. 69 | map_texture->set_data(map_image); 70 | } 71 | 72 | void DungeonMapBuilder::set_map_tile_color(const Vector2& tile_id, Color color) 73 | { 74 | map_image->lock(); 75 | for (int x = 0; x < params.floor_size; x++) { 76 | for (int y = 0; y < params.floor_size; y++) { 77 | map_image->set_pixel((int)tile_id.x + x, (int)(tile_id.y * -1.0f) + y, color); 78 | } 79 | } 80 | map_image->unlock(); 81 | map_texture->set_data(map_image); 82 | } 83 | 84 | void DungeonMapBuilder::_build_floors() 85 | { 86 | if (params.is_generating) { 87 | return; 88 | } 89 | params.is_generating = true; 90 | 91 | int w = map_image->get_width(); 92 | int h = map_image->get_height(); 93 | 94 | // Set the initial position. 95 | //Math::randomize(); 96 | params.rand_x = Math::random(0, params.dungeon_size - params.floor_size); 97 | params.rand_y = Math::random(0, params.dungeon_size - params.floor_size); 98 | 99 | params.current_x = params.rand_x - (params.rand_x % params.floor_size); 100 | params.current_y = params.rand_y - (params.rand_y % params.floor_size); 101 | params.lower_bound.x = params.current_x; 102 | params.upper_bound.y = params.current_y; 103 | 104 | // Update our bounds so that the initial start is what 105 | // we'd expect it to be. 106 | _update_bounds(); 107 | 108 | // Create our floors for the dungeon. 109 | while (params.floors_placed < params.max_floors) { 110 | _place_floor(); 111 | } 112 | 113 | // Update the status to let us know that we're finished. 114 | params.is_generating = false; 115 | } 116 | 117 | void DungeonMapBuilder::_place_floor() 118 | { 119 | // Add our floor tile to the image. 120 | map_image->lock(); 121 | for (int x = 0; x < params.floor_size; x++) { 122 | for (int y = 0; y < params.floor_size; y++) { 123 | map_image->set_pixel(params.current_x + x, params.current_y + y, Color(1, 1, 1)); 124 | } 125 | } 126 | map_image->unlock(); 127 | 128 | // Increment the number of floors. 129 | params.floors_placed += 1; 130 | 131 | // Determine the next location for the floor tile. 132 | params.dir = Math::random(0, 5); 133 | switch (params.dir) { 134 | case 1: 135 | params.current_x += params.floor_size; 136 | break; 137 | case 2: 138 | params.current_y += params.floor_size; 139 | break; 140 | case 3: 141 | params.current_x -= params.floor_size; 142 | break; 143 | case 4: 144 | params.current_y -= params.floor_size; 145 | break; 146 | } 147 | 148 | _update_bounds(); 149 | } 150 | 151 | void DungeonMapBuilder::_update_bounds() 152 | { 153 | // Check if we are outside of the room 154 | if (params.current_x < params.floor_size) 155 | params.current_x = params.floor_size; 156 | if (params.current_x > int(params.dungeon_size) - (params.floor_size * 2)) 157 | params.current_x = int(params.dungeon_size) - (params.floor_size * 2); 158 | 159 | if (params.current_y < params.floor_size) 160 | params.current_y = params.floor_size; 161 | if (params.current_y > int(params.dungeon_size) - (params.floor_size * 2)) 162 | params.current_y = int(params.dungeon_size) - (params.floor_size * 2); 163 | 164 | // Store the top-left and bottom-rigth corners of the map. 165 | if (params.current_x < params.lower_bound.x) 166 | params.lower_bound.x = params.current_x; 167 | if (params.current_x > params.upper_bound.x) 168 | params.upper_bound.x = params.current_x; 169 | if (params.current_y < params.lower_bound.y) 170 | params.lower_bound.y = params.current_y; 171 | if (params.current_y > params.upper_bound.y) 172 | params.upper_bound.y = params.current_y; 173 | } 174 | 175 | DungeonMapBuilder::DungeonMapBuilder() 176 | { 177 | //set_notify_transform(true); 178 | } 179 | 180 | DungeonMapBuilder::~DungeonMapBuilder() 181 | { 182 | // clear(); 183 | } -------------------------------------------------------------------------------- /scenes/world.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://scripts/main.gd" type="Script" id=1] 4 | [ext_resource path="res://game_env.tres" type="Environment" id=2] 5 | [ext_resource path="res://scripts/FPSLabel.gd" type="Script" id=3] 6 | [ext_resource path="res://scripts/LevelGenMapUI.gd" type="Script" id=4] 7 | [ext_resource path="res://scripts/EnemySpawner.gd" type="Script" id=5] 8 | [ext_resource path="res://scripts/Navigation.gd" type="Script" id=6] 9 | [ext_resource path="res://scripts/Player.gd" type="Script" id=7] 10 | 11 | [sub_resource type="CapsuleShape" id=1] 12 | 13 | radius = 0.6 14 | height = 0.5 15 | 16 | [node name="World" type="Spatial" index="0"] 17 | 18 | script = ExtResource( 1 ) 19 | 20 | [node name="WorldEnvironment" type="WorldEnvironment" parent="." index="0"] 21 | 22 | environment = ExtResource( 2 ) 23 | 24 | [node name="DirectionalLight" type="DirectionalLight" parent="." index="1"] 25 | 26 | transform = Transform( 1, 0, 0, 0, 0.106348, 0.994329, 0, -0.994329, 0.106348, 0, 23.2781, 0 ) 27 | layers = 1 28 | light_color = Color( 1, 1, 1, 1 ) 29 | light_energy = 0.5 30 | light_indirect_energy = 1.0 31 | light_negative = false 32 | light_specular = 0.5 33 | light_bake_mode = 1 34 | light_cull_mask = -1 35 | shadow_enabled = false 36 | shadow_color = Color( 0, 0, 0, 1 ) 37 | shadow_bias = 0.1 38 | shadow_contact = 0.0 39 | shadow_reverse_cull_face = false 40 | editor_only = false 41 | directional_shadow_mode = 2 42 | directional_shadow_split_1 = 0.1 43 | directional_shadow_split_2 = 0.2 44 | directional_shadow_split_3 = 0.5 45 | directional_shadow_blend_splits = false 46 | directional_shadow_normal_bias = 0.8 47 | directional_shadow_bias_split_scale = 0.25 48 | directional_shadow_depth_range = 0 49 | directional_shadow_max_distance = 200.0 50 | 51 | [node name="GUI" type="Control" parent="." index="2"] 52 | 53 | anchor_left = 0.0 54 | anchor_top = 0.0 55 | anchor_right = 1.0 56 | anchor_bottom = 1.0 57 | margin_left = -1.0 58 | margin_right = -1.0 59 | rect_pivot_offset = Vector2( 0, 0 ) 60 | mouse_filter = 0 61 | mouse_default_cursor_shape = 0 62 | size_flags_horizontal = 1 63 | size_flags_vertical = 1 64 | 65 | [node name="FPSLabel" type="Label" parent="GUI" index="0"] 66 | 67 | anchor_left = 0.0 68 | anchor_top = 0.0 69 | anchor_right = 0.0 70 | anchor_bottom = 0.0 71 | margin_left = 1457.0 72 | margin_top = 4.0 73 | margin_right = 1573.0 74 | margin_bottom = 25.0 75 | rect_pivot_offset = Vector2( 0, 0 ) 76 | mouse_filter = 2 77 | mouse_default_cursor_shape = 0 78 | size_flags_horizontal = 1 79 | size_flags_vertical = 4 80 | text = "FPS" 81 | percent_visible = 1.0 82 | lines_skipped = 0 83 | max_lines_visible = -1 84 | script = ExtResource( 3 ) 85 | _sections_unfolded = [ "Anchor", "Margin" ] 86 | 87 | [node name="LocationLabel" type="Label" parent="GUI" index="1"] 88 | 89 | anchor_left = 0.0 90 | anchor_top = 0.0 91 | anchor_right = 0.0 92 | anchor_bottom = 0.0 93 | margin_left = 1458.0 94 | margin_top = 24.0 95 | margin_right = 1574.0 96 | margin_bottom = 41.0 97 | rect_pivot_offset = Vector2( 0, 0 ) 98 | mouse_filter = 2 99 | mouse_default_cursor_shape = 0 100 | size_flags_horizontal = 1 101 | size_flags_vertical = 4 102 | text = "Location" 103 | percent_visible = 1.0 104 | lines_skipped = 0 105 | max_lines_visible = -1 106 | 107 | [node name="GameStatusLabel" type="Label" parent="GUI" index="2"] 108 | 109 | anchor_left = 0.0 110 | anchor_top = 0.0 111 | anchor_right = 0.0 112 | anchor_bottom = 0.0 113 | margin_left = 1457.0 114 | margin_top = 47.0 115 | margin_right = 1576.0 116 | margin_bottom = 61.0 117 | rect_pivot_offset = Vector2( 0, 0 ) 118 | mouse_filter = 2 119 | mouse_default_cursor_shape = 0 120 | size_flags_horizontal = 1 121 | size_flags_vertical = 4 122 | text = "Generating Level" 123 | percent_visible = 1.0 124 | lines_skipped = 0 125 | max_lines_visible = -1 126 | 127 | [node name="LevelGenMapUI" type="TextureRect" parent="GUI" index="3"] 128 | 129 | anchor_left = 0.0 130 | anchor_top = 0.0 131 | anchor_right = 0.0 132 | anchor_bottom = 0.0 133 | margin_right = 256.0 134 | margin_bottom = 256.0 135 | rect_pivot_offset = Vector2( 0, 0 ) 136 | mouse_filter = 1 137 | mouse_default_cursor_shape = 0 138 | size_flags_horizontal = 1 139 | size_flags_vertical = 1 140 | stretch_mode = 3 141 | script = ExtResource( 4 ) 142 | _sections_unfolded = [ "Margin" ] 143 | 144 | [node name="GenerateLevelBtn" type="Button" parent="GUI" index="4"] 145 | 146 | anchor_left = 0.0 147 | anchor_top = 0.0 148 | anchor_right = 0.0 149 | anchor_bottom = 0.0 150 | margin_left = 1456.0 151 | margin_top = 72.0 152 | margin_right = 1561.0 153 | margin_bottom = 92.0 154 | rect_pivot_offset = Vector2( 0, 0 ) 155 | focus_mode = 2 156 | mouse_filter = 0 157 | mouse_default_cursor_shape = 0 158 | size_flags_horizontal = 1 159 | size_flags_vertical = 1 160 | toggle_mode = false 161 | enabled_focus_mode = 2 162 | shortcut = null 163 | group = null 164 | text = "Generate" 165 | flat = false 166 | align = 1 167 | _sections_unfolded = [ "Theme" ] 168 | 169 | [node name="TargetLabel" type="Label" parent="GUI" index="5"] 170 | 171 | anchor_left = 0.5 172 | anchor_top = 0.5 173 | anchor_right = 0.5 174 | anchor_bottom = 0.5 175 | rect_scale = Vector2( 1.5, 1.5 ) 176 | rect_pivot_offset = Vector2( 0, 0 ) 177 | mouse_filter = 2 178 | mouse_default_cursor_shape = 0 179 | size_flags_horizontal = 1 180 | size_flags_vertical = 4 181 | text = "+" 182 | align = 1 183 | valign = 1 184 | percent_visible = 1.0 185 | lines_skipped = 0 186 | max_lines_visible = -1 187 | _sections_unfolded = [ "Anchor", "Grow Direction", "Margin", "Rect", "Size Flags" ] 188 | 189 | [node name="Label" type="Label" parent="GUI" index="6"] 190 | 191 | anchor_left = 0.5 192 | anchor_top = 0.0 193 | anchor_right = 0.5 194 | anchor_bottom = 0.0 195 | margin_left = -92.5 196 | margin_top = 20.0 197 | margin_right = 92.5 198 | margin_bottom = 16.0 199 | rect_pivot_offset = Vector2( 0, 0 ) 200 | mouse_filter = 2 201 | mouse_default_cursor_shape = 0 202 | size_flags_horizontal = 1 203 | size_flags_vertical = 4 204 | text = "Press `Esc` To Free Mouse" 205 | uppercase = true 206 | percent_visible = 1.0 207 | lines_skipped = 0 208 | max_lines_visible = -1 209 | _sections_unfolded = [ "Margin" ] 210 | 211 | [node name="EnemySpawner" type="Node" parent="." index="3"] 212 | 213 | script = ExtResource( 5 ) 214 | 215 | [node name="DungeonMap" type="DungeonMap" parent="." index="4"] 216 | 217 | material/floor = null 218 | material/wall = null 219 | material/water = null 220 | _sections_unfolded = [ "material" ] 221 | 222 | [node name="Navigation" type="Navigation" parent="." index="5"] 223 | 224 | up_vector = Vector3( 0, 1, 0 ) 225 | script = ExtResource( 6 ) 226 | _sections_unfolded = [ "Transform" ] 227 | 228 | [node name="Player" type="KinematicBody" parent="." index="6"] 229 | 230 | editor/display_folded = true 231 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 11.6247, 0.996533, -10.5223 ) 232 | input_ray_pickable = true 233 | input_capture_on_drag = false 234 | collision_layer = 3 235 | collision_mask = 3 236 | axis_lock_linear_x = false 237 | axis_lock_linear_y = false 238 | axis_lock_linear_z = false 239 | axis_lock_angular_x = false 240 | axis_lock_angular_y = false 241 | axis_lock_angular_z = false 242 | collision/safe_margin = 0.001 243 | script = ExtResource( 7 ) 244 | _sections_unfolded = [ "Axis Lock", "Collision", "Transform", "collision" ] 245 | 246 | [node name="CapsuleShape" type="CollisionShape" parent="Player" index="0"] 247 | 248 | transform = Transform( 1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0 ) 249 | shape = SubResource( 1 ) 250 | disabled = false 251 | _sections_unfolded = [ "Transform" ] 252 | 253 | [node name="Head" type="Spatial" parent="Player" index="1"] 254 | 255 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0 ) 256 | _sections_unfolded = [ "Transform" ] 257 | 258 | [node name="Camera" type="Camera" parent="Player/Head" index="0"] 259 | 260 | keep_aspect = 1 261 | cull_mask = 1048575 262 | environment = null 263 | h_offset = 0.0 264 | v_offset = 0.0 265 | doppler_tracking = 0 266 | projection = 0 267 | current = false 268 | fov = 70.0 269 | size = 1.0 270 | near = 0.05 271 | far = 500.0 272 | _sections_unfolded = [ "Transform" ] 273 | 274 | 275 | -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_debug_renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "dungeon_map_debug_renderer.h" 2 | #include "dungeon_map_mesh_builder.h" 3 | 4 | void DungeonMapDebugRenderer::region_display(const Vector2& region_id) 5 | { 6 | 7 | } 8 | 9 | void DungeonMapDebugRenderer::tile_display(const Vector2& tile_id) 10 | { 11 | 12 | } 13 | 14 | void DungeonMapDebugRenderer::tile_display_neighbors(const Vector2& tile_id) 15 | { 16 | 17 | } 18 | 19 | void DungeonMapDebugRenderer::set_region_display_all(bool display) 20 | { 21 | if (!is_inside_tree()) return; 22 | 23 | region_display_all = display; 24 | for (Map::Element* e = region_instances.front(); e; e = e->next()) { 25 | if (e->get().is_valid()) { 26 | VS::get_singleton()->instance_set_visible(e->get(), display); 27 | } 28 | } 29 | } 30 | 31 | void DungeonMapDebugRenderer::set_tile_display_all(bool display) 32 | { 33 | if (!is_inside_tree()) return; 34 | 35 | tile_display_all = display; 36 | for (Map::Element* e = tile_instances.front(); e; e = e->next()) { 37 | if (e->get().is_valid()) { 38 | VS::get_singleton()->instance_set_visible(e->get(), display); 39 | } 40 | } 41 | } 42 | 43 | void DungeonMapDebugRenderer::update() 44 | { 45 | if (!is_inside_tree()) 46 | return; 47 | 48 | // Check our parent node. 49 | if (get_parent() && get_parent()->is_class("DungeonMap")) { 50 | parent = (DungeonMap*)get_parent(); 51 | } else { 52 | return; 53 | } 54 | 55 | // Clear previous instances/meshes. 56 | clear(); 57 | 58 | // Create the debug material for the region. 59 | debug_region_material.instance(); 60 | debug_region_material->set_albedo(Color(0.0f, 1.0f, 0.0f, 0.2f)); 61 | debug_region_material->set_on_top_of_alpha(); 62 | debug_region_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true); 63 | debug_region_material->set_line_width(3.0); 64 | debug_region_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); 65 | 66 | // Create the debug material for the tiles. 67 | debug_tile_material.instance(); 68 | debug_tile_material->set_albedo(Color(1.0f, 1.0f, 0.0f, 0.75f)); 69 | debug_tile_material->set_on_top_of_alpha(); 70 | debug_tile_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true); 71 | debug_tile_material->set_line_width(3.0); 72 | debug_tile_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); 73 | 74 | // Build instances/meshes. 75 | for (Map::Element* e = parent->get_regions().front(); e; e = e->next()) { 76 | auto region = e->get(); 77 | 78 | // Create the debug render aabb instance. 79 | RID mesh_instance = DungeonMapMeshBuilder::create_mesh_from_aabb(region->aabb); 80 | VS::get_singleton()->mesh_surface_set_material( 81 | mesh_instance, 82 | 0, 83 | debug_region_material->get_rid() 84 | ); 85 | RID instance = VS::get_singleton()->instance_create2(mesh_instance, get_world()->get_scenario()); 86 | 87 | VS::get_singleton()->instance_set_transform(instance, region->transform); 88 | VS::get_singleton()->instance_set_visible(instance, false); 89 | 90 | region_instances[region->id] = instance; 91 | region_meshes[region->id] = mesh_instance; 92 | } 93 | 94 | // Build instances/meshes. 95 | for (Map::Element* e = parent->get_tiles().front(); e; e = e->next()) { 96 | auto tile = e->get(); 97 | 98 | // Create the debug render aabb instance. 99 | RID mesh_instance = DungeonMapMeshBuilder::create_mesh_lines_from_aabb(tile->aabb); 100 | VS::get_singleton()->mesh_surface_set_material( 101 | mesh_instance, 102 | 0, 103 | debug_tile_material->get_rid() 104 | ); 105 | RID instance = VS::get_singleton()->instance_create2(mesh_instance, get_world()->get_scenario()); 106 | 107 | VS::get_singleton()->instance_set_transform(instance, tile->transform); 108 | VS::get_singleton()->instance_set_visible(instance, false); 109 | 110 | tile_instances[tile->id] = instance; 111 | tile_meshes[tile->id] = mesh_instance; 112 | } 113 | } 114 | 115 | void DungeonMapDebugRenderer::clear() 116 | { 117 | // Clear materials. 118 | if (debug_region_material.is_valid()) { 119 | debug_region_material.unref(); 120 | } 121 | if (debug_tile_material.is_valid()) { 122 | debug_tile_material.unref(); 123 | } 124 | 125 | // Delete cells. 126 | for (Map::Element* e = tile_instances.front(); e; e = e->next()) { 127 | RID instance = e->get(); 128 | RID mesh = tile_meshes[e->key()]; 129 | 130 | if (mesh.is_valid()) { 131 | VS::get_singleton()->free(mesh); 132 | } 133 | 134 | if (instance.is_valid()) { 135 | VS::get_singleton()->free(instance); 136 | } 137 | } 138 | tile_meshes.clear(); 139 | tile_instances.clear(); 140 | 141 | // Delete regions. 142 | for (Map::Element* e = region_instances.front(); e; e = e->next()) { 143 | RID instance = e->get(); 144 | RID mesh = region_meshes[e->key()]; 145 | 146 | if (mesh.is_valid()) { 147 | VS::get_singleton()->free(mesh); 148 | } 149 | 150 | if (instance.is_valid()) { 151 | VS::get_singleton()->free(instance); 152 | } 153 | } 154 | region_meshes.clear(); 155 | region_instances.clear(); 156 | } 157 | 158 | void DungeonMapDebugRenderer::_update_visibility() 159 | { 160 | if (!is_inside_tree()) 161 | return; 162 | 163 | if (region_display_all) { 164 | set_region_display_all(is_visible()); 165 | } 166 | 167 | if (tile_display_all) { 168 | set_tile_display_all(is_visible()); 169 | } 170 | 171 | _change_notify("visible"); 172 | } 173 | 174 | void DungeonMapDebugRenderer::_update_transform() 175 | { 176 | 177 | } 178 | 179 | void DungeonMapDebugRenderer::_notification(int p_what) 180 | { 181 | switch (p_what) { 182 | case NOTIFICATION_ENTER_WORLD: { 183 | update(); 184 | } break; 185 | 186 | case NOTIFICATION_EXIT_WORLD: { 187 | clear(); 188 | } break; 189 | 190 | case NOTIFICATION_VISIBILITY_CHANGED: { 191 | _update_visibility(); 192 | } break; 193 | 194 | case NOTIFICATION_TRANSFORM_CHANGED: { 195 | _update_transform(); 196 | } break; 197 | } 198 | } 199 | 200 | bool DungeonMapDebugRenderer::_set(const StringName &p_name, const Variant &p_value) 201 | { 202 | if (p_name == "display_regions") { 203 | set_region_display_all((bool)p_value); 204 | return true; 205 | } 206 | if (p_name == "display_tile") { 207 | set_tile_display_all((bool)p_value); 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | bool DungeonMapDebugRenderer::_get(const StringName &p_name, Variant &r_ret) const 214 | { 215 | if (p_name == "display_regions") { 216 | r_ret = region_display_all; 217 | return true; 218 | } 219 | if (p_name == "display_tile") { 220 | r_ret = tile_display_all; 221 | return true; 222 | } 223 | return false; 224 | } 225 | 226 | void DungeonMapDebugRenderer::_get_property_list(List *p_list) const 227 | { 228 | p_list->push_back(PropertyInfo(Variant::BOOL, "display_regions", PROPERTY_HINT_NONE, "bool")); 229 | p_list->push_back(PropertyInfo(Variant::BOOL, "display_tile", PROPERTY_HINT_NONE, "bool")); 230 | } 231 | 232 | void DungeonMapDebugRenderer::_bind_methods() 233 | { 234 | ClassDB::bind_method(D_METHOD("region_display", "region_id"), &DungeonMapDebugRenderer::region_display); 235 | ClassDB::bind_method(D_METHOD("tile_display", "tile_id"), &DungeonMapDebugRenderer::tile_display); 236 | ClassDB::bind_method(D_METHOD("tile_display_neighbors", "tile_id"), &DungeonMapDebugRenderer::tile_display_neighbors); 237 | 238 | ClassDB::bind_method(D_METHOD("set_region_display_all", "display"), &DungeonMapDebugRenderer::set_region_display_all); 239 | ClassDB::bind_method(D_METHOD("set_tile_display_all", "display"), &DungeonMapDebugRenderer::set_tile_display_all); 240 | 241 | ClassDB::bind_method(D_METHOD("update"), &DungeonMapDebugRenderer::update); 242 | ClassDB::bind_method(D_METHOD("clear"), &DungeonMapDebugRenderer::clear); 243 | } 244 | 245 | DungeonMapDebugRenderer::DungeonMapDebugRenderer() 246 | { 247 | 248 | } 249 | 250 | DungeonMapDebugRenderer::~DungeonMapDebugRenderer() 251 | { 252 | 253 | } -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map.h: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author: Victor Holt 3 | * The MIT License (MIT) 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the "Software"), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all 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 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | * 23 | **********************************************************/ 24 | 25 | #ifndef DUNGEON_MAP_H 26 | #define DUNGEON_MAP_H 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "dungeon_map_builder.h" 44 | 45 | class DungeonMap : public Spatial 46 | { 47 | GDCLASS(DungeonMap, Spatial); 48 | OBJ_CATEGORY("3D Visual Nodes"); 49 | 50 | public: 51 | // Types of tiles that can be generated. 52 | enum TileType 53 | { 54 | EMPTY = 0, 55 | FLOOR, 56 | WALL, 57 | WATER, 58 | MAX_TILE_TYPES 59 | }; 60 | 61 | // Tile neighbors 62 | enum Neighbor 63 | { 64 | NEIGHBOR_NORTH = 0, 65 | NEIGHBOR_EAST, 66 | NEIGHBOR_SOUTH, 67 | NEIGHBOR_WEST, 68 | 69 | NEIGHBOR_NORTH_EAST, 70 | NEIGHBOR_NORTH_WEST, 71 | NEIGHBOR_SOUTH_EAST, 72 | NEIGHBOR_SOUTH_WEST, 73 | 74 | MAX_TILE_NEIGHBORS 75 | }; 76 | 77 | // Regions are groups of tiles. 78 | struct Region 79 | { 80 | // Position of the region. 81 | Vector2 id; 82 | // Tiles in the region. 83 | Vector tiles; 84 | // AABB for the region. 85 | AABB aabb; 86 | 87 | // Region mesh instance. 88 | RID instance; 89 | // Region edge mesh instance. 90 | RID edge_instance; 91 | 92 | // Reference to the land mesh. 93 | Ref mesh; 94 | // Reference to the edge mesh. 95 | Ref edge_mesh; 96 | // Reference to the navigation mesh. 97 | Ref nav_mesh; 98 | // NavigationMesh id when added to the Navigation node. 99 | int nav_mesh_id = 0; 100 | 101 | // Reference to the collision body. 102 | StaticBody* collision_body = NULL; 103 | // Reference to the collision shape. 104 | CollisionShape* collision_shape = NULL; 105 | 106 | // Reference to the collision body. 107 | StaticBody* edge_collision_body = NULL; 108 | // Reference to the collision shape. 109 | CollisionShape* edge_collision_shape = NULL; 110 | 111 | // Transform for the region. 112 | Transform transform; 113 | 114 | // Whether or not the region needs to be regenerated. 115 | bool dirty = true; 116 | }; 117 | 118 | // Tile that makes up a level. 119 | struct Tile 120 | { 121 | // Position of the tile. 122 | Vector2 id; 123 | // Region id for the tile. 124 | Vector2 region_id; 125 | // Local position relative to the region. 126 | Vector2 local_position; 127 | // Type of tile when generating the mesh. 128 | TileType type = TileType::EMPTY; 129 | // Tile Neighbors 130 | Vector neighbors; 131 | 132 | // AABB for the tile. 133 | AABB aabb; 134 | 135 | // Height of the cell. 136 | int height = 0; 137 | 138 | // Transform for the tile. 139 | Transform transform; 140 | 141 | // Has a north tile neighbor. 142 | bool has_north_tile = false; 143 | // Has a east tile neighbor. 144 | bool has_east_tile = false; 145 | // Has a south tile neighbor. 146 | bool has_south_tile = false; 147 | // Has a west tile neighbor. 148 | bool has_west_tile = false; 149 | // Has a north-east tile neighbor. 150 | bool has_north_east_tile = false; 151 | // Has a north-west tile neighbor. 152 | bool has_north_west_tile = false; 153 | // Has a south-east tile neighbor. 154 | bool has_south_east_tile = false; 155 | // Has a south-west tile neighbor. 156 | bool has_south_west_tile = false; 157 | }; 158 | 159 | private: 160 | // Reference to the map builder. 161 | DungeonMapBuilder map_builder; 162 | 163 | // Regions of the map. 164 | Map regions; 165 | // Tiles of the map. 166 | Map tiles; 167 | // List of valid tiles for the current map. 168 | Vector valid_tiles; 169 | 170 | // Reference to the navigation node. 171 | Node* navigation_node = NULL; 172 | 173 | // Number of regions (region_size * region_size). 174 | int region_size = 8; 175 | // Tiles per region. 176 | int tiles_per_region = 4; 177 | // Celling height. 178 | int ceiling_height = 8; 179 | 180 | // Flag for whether or not the dungeon is dirty. 181 | bool dirty = true; 182 | 183 | // Build the regions/tiles. 184 | void _build_regions(); 185 | // Build the tiles. 186 | void _build_tiles(const Vector2& region_id); 187 | // Update the tile neighbors. 188 | void _update_tile_neighbors(); 189 | 190 | // Builds the region meshes. 191 | void _build_region_meshes(); 192 | // Builds the region mesh edges. 193 | void _build_region_mesh_edges(); 194 | 195 | // Create a collision mesh from a given mesh. 196 | void _create_collision(StaticBody*& collision_body, CollisionShape*& collision_shape, Ref mesh, const Transform& transform); 197 | 198 | // Create the collisions for a region. 199 | void _create_region_collision(const Vector2& region_id); 200 | 201 | // Create the edge collisions for a region. 202 | void _create_region_edge_collision(const Vector2& region_id); 203 | 204 | // Updates the visibility of the node. 205 | void _update_visibility(); 206 | // Updates the grid transformation. 207 | void _update_transform(); 208 | // Updates the material for the terrain. 209 | void _update_material(); 210 | 211 | protected: 212 | // Handles notifications for the node. 213 | void _notification(int p_what); 214 | // Set property from the editor. 215 | bool _set(const StringName &p_name, const Variant &p_value); 216 | // Retrieve property from the editor. 217 | bool _get(const StringName &p_name, Variant &r_ret) const; 218 | // Retrieves the properly list for the editor. 219 | void _get_property_list(List *p_list) const; 220 | // Binds methods for the terrain. 221 | static void _bind_methods(); 222 | 223 | public: 224 | // Constructor. 225 | DungeonMap(); 226 | // Destructor. 227 | virtual ~DungeonMap(); 228 | 229 | // Applies all changes to the dungeon. 230 | void apply(); 231 | // Clears the dungeon objects. 232 | void clear(); 233 | 234 | // Generates the map image. 235 | void generate_map_image(); 236 | 237 | // Attempts to return a region based on the given coordinates/id. 238 | Region* find_region(const Vector2& region_id); 239 | // Attempts to return a tile based on the given coordinates/id. 240 | Tile* find_tile(const Vector2& tile_id); 241 | 242 | // Returns a random map location (based on the initial seed). 243 | Vector2 get_random_map_location(); 244 | 245 | // Returns the "snapped" tile position given a position. 246 | Vector2 get_tile_position(const Vector2& position); 247 | 248 | // Checks if a position is valid. 249 | bool is_valid_position(const Vector2& position); 250 | 251 | // Set the map image color for a specific tile. 252 | void set_map_tile_color(const Vector2& tile_id, Color color); 253 | 254 | // Adds the navigation meshes. 255 | void add_navigation_meshes(Node* navigation); 256 | // Removes the navigation meshes. 257 | void remove_navigation_meshes(Node* navigation); 258 | 259 | // Returns the navigation meshes. 260 | Array get_nav_meshes() const; 261 | 262 | // Marks the terrain as dirty. 263 | _FORCE_INLINE_ void mark_dirty() { dirty = true; } 264 | // Check if the terrain is dirty. 265 | _FORCE_INLINE_ bool is_dirty() { return dirty; } 266 | 267 | // Returns the map image. 268 | _FORCE_INLINE_ Ref get_map_image() const { return map_builder.get_map_image(); } 269 | // Returns the map texture. 270 | _FORCE_INLINE_ Ref get_map_texture() const { return map_builder.get_map_texture(); } 271 | 272 | // Sets the size of the region. 273 | _FORCE_INLINE_ void set_region_size(int size) { mark_dirty(); region_size = size; } 274 | // Returns the size of the region. 275 | _FORCE_INLINE_ int get_region_size() const { return region_size; } 276 | // Sets the tiles per region. 277 | _FORCE_INLINE_ void set_tiles_per_region(int tiles) { mark_dirty(); tiles_per_region = tiles; } 278 | // Returns the tile per region. 279 | _FORCE_INLINE_ int get_tiles_per_region() const { return tiles_per_region; } 280 | 281 | // Set the ceiling height. 282 | _FORCE_INLINE_ void set_ceiling_height(int height) { ceiling_height = height; } 283 | // Returns the ceiling height. 284 | _FORCE_INLINE_ int get_ceiling_height() const { return ceiling_height; } 285 | 286 | // Returns the dungeon map builder params. 287 | _FORCE_INLINE_ const DungeonMapBuilder::DungeonParams& get_dungeon_params() const { return map_builder.params; } 288 | 289 | // Sets the dungeon seed. 290 | _FORCE_INLINE_ void set_dungeon_seed(int64_t seed) { mark_dirty(); map_builder.params.seed = seed; } 291 | // Returns the dungeon seed. 292 | _FORCE_INLINE_ int64_t get_dungeon_seed() const { return map_builder.params.seed; } 293 | 294 | // Returns all regions in the map. 295 | _FORCE_INLINE_ const Map& get_regions() const { return regions; } 296 | // Returns all tiles in the map. 297 | _FORCE_INLINE_ const Map& get_tiles() const { return tiles; } 298 | }; 299 | 300 | #endif -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map_mesh_builder.cpp: -------------------------------------------------------------------------------- 1 | #include "dungeon_map_mesh_builder.h" 2 | #include "dungeon_map.h" 3 | 4 | #include 5 | #include 6 | 7 | // Using OpenGL (right-handed coord system) convention where -Z is forward... 8 | 9 | void DungeonMapMeshBuilder::add_mesh_tile(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, int height, bool inverse) 10 | { 11 | DungeonMap::Tile* tile = dungeon_map->find_tile(tile_id); 12 | if (!tile) { 13 | return; 14 | } 15 | float scale = dungeon_map->get_dungeon_params().floor_size; 16 | Vector2 origin = Vector2(tile->aabb.position.x, tile->aabb.position.z); 17 | Vector3 position = tile->aabb.position; 18 | 19 | if (height == -99999) { 20 | height = tile->height; 21 | } 22 | 23 | if (!inverse) { 24 | // Triangle 1 25 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y), scale)); 26 | tool->add_vertex(Vector3(origin.x, height, origin.y)); 27 | 28 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y + scale), scale)); 29 | tool->add_vertex(Vector3(origin.x, height, origin.y - scale)); 30 | 31 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y), scale)); 32 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y)); 33 | 34 | // Triangle 2 35 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y), scale)); 36 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y)); 37 | 38 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y + scale), scale)); 39 | tool->add_vertex(Vector3(origin.x, height, origin.y - scale)); 40 | 41 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y + scale), scale)); 42 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y - scale)); 43 | } else { 44 | // Triangle 1 45 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y), scale)); 46 | tool->add_vertex(Vector3(origin.x, height, origin.y)); 47 | 48 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y), scale)); 49 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y)); 50 | 51 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y + scale), scale)); 52 | tool->add_vertex(Vector3(origin.x, height, origin.y - scale)); 53 | 54 | // Triangle 2 55 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y), scale)); 56 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y)); 57 | 58 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x + scale, height, position.y + scale), scale)); 59 | tool->add_vertex(Vector3(origin.x + scale, height, origin.y - scale)); 60 | 61 | tool->add_uv(get_uv(UVType::UV_XZ, Vector3(position.x, height, position.y + scale), scale)); 62 | tool->add_vertex(Vector3(origin.x, height, origin.y - scale)); 63 | } 64 | } 65 | 66 | void DungeonMapMeshBuilder::add_mesh_tile(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, bool inverse) 67 | { 68 | add_mesh_tile(dungeon_map, tool, tile_id, -99999, inverse); 69 | } 70 | 71 | void DungeonMapMeshBuilder::add_mesh_tile_edge(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, int height, bool inverse) 72 | { 73 | DungeonMap::Tile* tile = dungeon_map->find_tile(tile_id); 74 | if (!tile || tile->type == DungeonMap::TileType::EMPTY) { 75 | return; 76 | } 77 | 78 | Vector2 origin = Vector2(tile->aabb.position.x, tile->aabb.position.z); 79 | Vector3 position = tile->aabb.position; 80 | if (height == -99999) { 81 | height = tile->height; 82 | } 83 | float length = dungeon_map->get_dungeon_params().floor_size; 84 | float edge_slant = 0.0f; 85 | 86 | if (!tile->has_north_tile || ((DungeonMap::Tile*)tile->neighbors[(uint8_t)DungeonMap::NEIGHBOR_NORTH])->type == DungeonMap::EMPTY) { 87 | Vector3 v01 = Vector3(origin.x - edge_slant, 0.0f, origin.y - length - edge_slant); 88 | Vector3 v02 = Vector3(origin.x + length, height, origin.y - length); 89 | Vector3 v03 = Vector3(origin.x, height, origin.y - length); 90 | 91 | Vector3 v11 = Vector3(origin.x - edge_slant, 0.0f, origin.y - length - edge_slant); 92 | Vector3 v12 = Vector3(origin.x + length + edge_slant, 0.0f, origin.y - length - edge_slant); 93 | Vector3 v13 = Vector3(origin.x + length, height, origin.y - length); 94 | 95 | // Check if we need to inverse the mesh. 96 | if (inverse) { 97 | Vector3 tmp = v02; 98 | v02 = v03; 99 | v03 = tmp; 100 | 101 | tmp = v12; 102 | v12 = v13; 103 | v13 = tmp; 104 | } 105 | 106 | // Triangle #1 107 | tool->add_uv(get_uv(UVType::UV_XY, v01)); 108 | tool->add_vertex(v01); 109 | 110 | tool->add_uv(get_uv(UVType::UV_XY, v02)); 111 | tool->add_vertex(v02); 112 | 113 | tool->add_uv(get_uv(UVType::UV_XY, v03)); 114 | tool->add_vertex(v03); 115 | 116 | // Triangle #2 117 | tool->add_uv(get_uv(UVType::UV_XY, v11)); 118 | tool->add_vertex(v11); 119 | 120 | tool->add_uv(get_uv(UVType::UV_XY, v12)); 121 | tool->add_vertex(v12); 122 | 123 | tool->add_uv(get_uv(UVType::UV_XY, v13)); 124 | tool->add_vertex(v13); 125 | } 126 | 127 | if (!tile->has_east_tile || ((DungeonMap::Tile*)tile->neighbors[(uint8_t)DungeonMap::NEIGHBOR_EAST])->type == DungeonMap::EMPTY) { 128 | Vector3 v01 = Vector3(origin.x + length + edge_slant, 0.0f, origin.y + edge_slant); 129 | Vector3 v02 = Vector3(origin.x + length, height, origin.y); 130 | Vector3 v03 = Vector3(origin.x + length, height, origin.y - length); 131 | 132 | Vector3 v11 = Vector3(origin.x + length + edge_slant, 0.0f, origin.y + edge_slant); 133 | Vector3 v12 = Vector3(origin.x + length, height, origin.y - length); 134 | Vector3 v13 = Vector3(origin.x + length + edge_slant, 0.0f, origin.y - length - edge_slant); 135 | 136 | // Check if we need to inverse the mesh. 137 | if (inverse) { 138 | Vector3 tmp = v02; 139 | v02 = v03; 140 | v03 = tmp; 141 | 142 | tmp = v12; 143 | v12 = v13; 144 | v13 = tmp; 145 | } 146 | 147 | // Triangle #1 148 | tool->add_uv(get_uv(UVType::UV_ZY, v01)); 149 | tool->add_vertex(v01); 150 | 151 | tool->add_uv(get_uv(UVType::UV_ZY, v02)); 152 | tool->add_vertex(v02); 153 | 154 | tool->add_uv(get_uv(UVType::UV_ZY, v03)); 155 | tool->add_vertex(v03); 156 | 157 | // Triangle #2 158 | tool->add_uv(get_uv(UVType::UV_ZY, v11)); 159 | tool->add_vertex(v11); 160 | 161 | tool->add_uv(get_uv(UVType::UV_ZY, v12)); 162 | tool->add_vertex(v12); 163 | 164 | tool->add_uv(get_uv(UVType::UV_ZY, v13)); 165 | tool->add_vertex(v13); 166 | } 167 | 168 | if (!tile->has_south_tile || ((DungeonMap::Tile*)tile->neighbors[(uint8_t)DungeonMap::NEIGHBOR_SOUTH])->type == DungeonMap::EMPTY) { 169 | Vector3 v01 = Vector3(origin.x - edge_slant, 0.0f, origin.y + edge_slant); 170 | Vector3 v02 = Vector3(origin.x, height, origin.y); 171 | Vector3 v03 = Vector3(origin.x + length, height, origin.y); 172 | 173 | Vector3 v11 = Vector3(origin.x - edge_slant, 0.0f, origin.y + edge_slant); 174 | Vector3 v12 = Vector3(origin.x + length, height, origin.y); 175 | Vector3 v13 = Vector3(origin.x + length + edge_slant, 0.0f, origin.y + edge_slant); 176 | 177 | // Check if we need to inverse the mesh. 178 | if (inverse) { 179 | Vector3 tmp = v02; 180 | v02 = v03; 181 | v03 = tmp; 182 | 183 | tmp = v12; 184 | v12 = v13; 185 | v13 = tmp; 186 | } 187 | 188 | // Triangle #1 189 | tool->add_uv(get_uv(UVType::UV_XY, v01)); 190 | tool->add_vertex(v01); 191 | 192 | tool->add_uv(get_uv(UVType::UV_XY, v02)); 193 | tool->add_vertex(v02); 194 | 195 | tool->add_uv(get_uv(UVType::UV_XY, v03)); 196 | tool->add_vertex(v03); 197 | 198 | // Triangle #2 199 | tool->add_uv(get_uv(UVType::UV_XY, v11)); 200 | tool->add_vertex(v11); 201 | 202 | tool->add_uv(get_uv(UVType::UV_XY, v12)); 203 | tool->add_vertex(v12); 204 | 205 | tool->add_uv(get_uv(UVType::UV_XY, v13)); 206 | tool->add_vertex(v13); 207 | } 208 | 209 | if (!tile->has_west_tile || ((DungeonMap::Tile*)tile->neighbors[(uint8_t)DungeonMap::NEIGHBOR_WEST])->type == DungeonMap::EMPTY) { 210 | Vector3 v01 = Vector3(origin.x - edge_slant, 0.0f, origin.y + edge_slant); 211 | Vector3 v02 = Vector3(origin.x, height, origin.y - length); 212 | Vector3 v03 = Vector3(origin.x, height, origin.y); 213 | 214 | Vector3 v11 = Vector3(origin.x - edge_slant, 0.0f, origin.y + edge_slant); 215 | Vector3 v12 = Vector3(origin.x - edge_slant, 0.0f, origin.y - length - edge_slant); 216 | Vector3 v13 = Vector3(origin.x, height, origin.y - length); 217 | 218 | // Check if we need to inverse the mesh. 219 | if (inverse) { 220 | Vector3 tmp = v02; 221 | v02 = v03; 222 | v03 = tmp; 223 | 224 | tmp = v12; 225 | v12 = v13; 226 | v13 = tmp; 227 | } 228 | 229 | // Triangle #1 230 | tool->add_uv(get_uv(UVType::UV_ZY, v01)); 231 | tool->add_vertex(v01); 232 | 233 | tool->add_uv(get_uv(UVType::UV_ZY, v02)); 234 | tool->add_vertex(v02); 235 | 236 | tool->add_uv(get_uv(UVType::UV_ZY, v03)); 237 | tool->add_vertex(v03); 238 | 239 | // Triangle #2 240 | tool->add_uv(get_uv(UVType::UV_ZY, v11)); 241 | tool->add_vertex(v11); 242 | 243 | tool->add_uv(get_uv(UVType::UV_ZY, v12)); 244 | tool->add_vertex(v12); 245 | 246 | tool->add_uv(get_uv(UVType::UV_ZY, v13)); 247 | tool->add_vertex(v13); 248 | } 249 | } 250 | 251 | void DungeonMapMeshBuilder::add_mesh_tile_edge(DungeonMap* dungeon_map, SurfaceTool* tool, const Vector2& tile_id, bool inverse) 252 | { 253 | add_mesh_tile_edge(dungeon_map, tool, tile_id, -99999, inverse); 254 | } 255 | 256 | RID DungeonMapMeshBuilder::create_mesh_from_aabb(const AABB& aabb) 257 | { 258 | // Create the debug render aabb instance. 259 | RID mesh = VS::get_singleton()->mesh_create(); 260 | Vector3 pos = aabb.position; 261 | Vector3 size = aabb.size; 262 | 263 | SurfaceTool st; 264 | st.begin(Mesh::PRIMITIVE_TRIANGLES); 265 | 266 | // Front 267 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 268 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 269 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 270 | 271 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 272 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 273 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 274 | 275 | // Back 276 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 277 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 278 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 279 | 280 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 281 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 282 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 283 | 284 | // Left 285 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 286 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 287 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 288 | 289 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 290 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 291 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 292 | 293 | // Right 294 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 295 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 296 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 297 | 298 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 299 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 300 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 301 | 302 | // Top 303 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 304 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 305 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 306 | 307 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 308 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 309 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 310 | 311 | // Bottom 312 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 313 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 314 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 315 | 316 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 317 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 318 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 319 | 320 | st.index(); 321 | Ref arrMesh = st.commit(); 322 | 323 | VS::get_singleton()->mesh_add_surface_from_arrays( 324 | mesh, 325 | VisualServer::PRIMITIVE_TRIANGLES, 326 | arrMesh->surface_get_arrays(0) 327 | ); 328 | 329 | return mesh; 330 | } 331 | 332 | RID DungeonMapMeshBuilder::create_mesh_lines_from_aabb(const AABB& aabb) 333 | { 334 | RID mesh = VS::get_singleton()->mesh_create(); 335 | Vector3 pos = aabb.position; 336 | Vector3 size = aabb.size; 337 | SurfaceTool st; 338 | st.begin(Mesh::PRIMITIVE_LINES); 339 | 340 | // Front 341 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 342 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 343 | 344 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 345 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 346 | 347 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 348 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 349 | 350 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 351 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 352 | 353 | // Back 354 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 355 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 356 | 357 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 358 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 359 | 360 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 361 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 362 | 363 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 364 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 365 | 366 | // Left 367 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 368 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 369 | 370 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z)); 371 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 372 | 373 | st.add_vertex(Vector3(pos.x, pos.y + size.y, pos.z - size.z)); 374 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 375 | 376 | st.add_vertex(Vector3(pos.x, pos.y, pos.z - size.z)); 377 | st.add_vertex(Vector3(pos.x, pos.y, pos.z)); 378 | 379 | // Right 380 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 381 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 382 | 383 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z)); 384 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 385 | 386 | st.add_vertex(Vector3(pos.x + size.x, pos.y + size.y, pos.z - size.z)); 387 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 388 | 389 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z - size.z)); 390 | st.add_vertex(Vector3(pos.x + size.x, pos.y, pos.z)); 391 | 392 | st.index(); 393 | Ref arrMesh = st.commit(); 394 | 395 | VS::get_singleton()->mesh_add_surface_from_arrays( 396 | mesh, 397 | VisualServer::PRIMITIVE_LINES, 398 | arrMesh->surface_get_arrays(0) 399 | ); 400 | 401 | arrMesh.unref(); 402 | return mesh; 403 | } 404 | 405 | Vector2 DungeonMapMeshBuilder::get_uv(UVType uvType, const Vector3& vertex, float scale) 406 | { 407 | Vector2 ret; 408 | if (uvType == UVType::UV_XY) { 409 | ret = Vector2(vertex.x / scale, vertex.y / scale); 410 | } else if (uvType == UVType::UV_XZ) { 411 | ret = Vector2(vertex.x / scale, vertex.z / scale); 412 | } else if (uvType == UVType::UV_YZ) { 413 | ret = Vector2(vertex.y / scale, vertex.z / scale); 414 | } else if (uvType == UVType::UV_ZY) { 415 | ret = Vector2(vertex.z / scale, vertex.y / scale); 416 | } 417 | return ret; 418 | } -------------------------------------------------------------------------------- /godot_modules/dungeonmap/dungeon_map.cpp: -------------------------------------------------------------------------------- 1 | #include "dungeon_map.h" 2 | #include "dungeon_map_mesh_builder.h" 3 | 4 | #include 5 | #include 6 | 7 | void DungeonMap::apply() 8 | { 9 | if (!is_inside_tree()) { 10 | return; 11 | } 12 | 13 | if (!is_dirty()) { 14 | return; 15 | } 16 | 17 | // Clear the previous data. 18 | clear(); 19 | 20 | _build_regions(); 21 | _update_tile_neighbors(); 22 | _build_region_meshes(); 23 | _build_region_mesh_edges(); 24 | 25 | // Update all regions as not dirty. 26 | for (Map::Element* e = regions.front(); e; e = e->next()) { 27 | Region* region = e->get(); 28 | region->dirty = false; 29 | } 30 | dirty = false; 31 | 32 | // Attempt to apply the nav mesh. 33 | // Node* node = get_parent()->get_node(NodePath("Navigation")); 34 | // if (navigation_node) { 35 | // add_navigation_meshes(navigation_node); 36 | // } 37 | 38 | emit_signal("dungeon_map_apply_completed"); 39 | } 40 | 41 | void DungeonMap::clear() 42 | { 43 | valid_tiles.clear(); 44 | for (Map::Element* e = tiles.front(); e; e = e->next()) { 45 | Tile* tile = e->get(); 46 | memdelete(tile); 47 | } 48 | tiles.clear(); 49 | 50 | // Attempt to remove the nav meshes. 51 | if (navigation_node) { 52 | remove_navigation_meshes(navigation_node); 53 | } 54 | navigation_node = NULL; 55 | 56 | // Delete all regions in our dictionary. 57 | for (Map::Element* e = regions.front(); e; e = e->next()) { 58 | Region* region = e->get(); 59 | 60 | // Free meshes. 61 | if (region->mesh.is_valid()) { 62 | region->mesh.unref(); 63 | } 64 | if (region->edge_mesh.is_valid()) { 65 | region->edge_mesh.unref(); 66 | } 67 | 68 | // Free instance. 69 | if (region->instance.is_valid()) { 70 | VS::get_singleton()->free(region->instance); 71 | } 72 | 73 | if (region->edge_instance.is_valid()) { 74 | VS::get_singleton()->free(region->edge_instance); 75 | } 76 | 77 | // Delete collision. 78 | if (region->collision_shape) { 79 | region->collision_shape->queue_delete(); 80 | } 81 | if (region->collision_body) { 82 | // region->collision_body->set_owner(NULL); 83 | // remove_child(region->collision_body); 84 | // PhysicsServer::get_singleton()->free(region->collision_body->get_rid()); 85 | region->collision_body->queue_delete(); 86 | } 87 | 88 | if (region->edge_collision_shape) { 89 | region->edge_collision_shape->queue_delete(); 90 | } 91 | if (region->edge_collision_body) { 92 | // remove_child(region->edge_collision_body); 93 | // region->edge_collision_body->set_owner(NULL); 94 | // PhysicsServer::get_singleton()->free(region->edge_collision_body->get_rid()); 95 | region->edge_collision_body->queue_delete(); 96 | } 97 | 98 | region->tiles.clear(); 99 | 100 | if (region->nav_mesh.is_valid()) { 101 | region->nav_mesh.unref(); 102 | } 103 | memdelete(region); 104 | } 105 | regions.clear(); 106 | dirty = true; 107 | } 108 | 109 | void DungeonMap::generate_map_image() 110 | { 111 | if (!is_inside_tree()) { 112 | return; 113 | } 114 | 115 | // Create the map image/texture. 116 | map_builder.params.dungeon_size = region_size * map_builder.params.floor_size * tiles_per_region; 117 | print_line(String("Dungeon Size = ") + itos(map_builder.params.dungeon_size)); 118 | 119 | map_builder.create_map_image(); 120 | map_builder.generate_map_image(); 121 | emit_signal("dungeon_map_image_generated"); 122 | } 123 | 124 | DungeonMap::Region* DungeonMap::find_region(const Vector2& region_id) 125 | { 126 | if (!regions.has(region_id)) { 127 | return NULL; 128 | } 129 | return regions[region_id]; 130 | } 131 | 132 | DungeonMap::Tile* DungeonMap::find_tile(const Vector2& tile_id) 133 | { 134 | if (!tiles.has(tile_id)) { 135 | return NULL; 136 | } 137 | return tiles[tile_id]; 138 | } 139 | 140 | Vector2 DungeonMap::get_random_map_location() 141 | { 142 | if (valid_tiles.size() == 0) { 143 | print_line("No valid tiles to spawn on!!!"); 144 | return Vector2(); 145 | } 146 | 147 | Vector2 position = valid_tiles[Math::rand() % valid_tiles.size()]; 148 | Tile* tile = find_tile(position); 149 | float floor_size_half = (float)(get_dungeon_params().floor_size) / 2.0f; 150 | if (!tile) { 151 | return Vector2(); 152 | } 153 | 154 | return Vector2( 155 | position.x + floor_size_half, 156 | position.y - floor_size_half 157 | ); 158 | } 159 | 160 | Vector2 DungeonMap::get_tile_position(const Vector2& position) 161 | { 162 | return Vector2( 163 | Math::floor(position.x - ((int)position.x % get_dungeon_params().floor_size)), 164 | Math::floor(position.y - ((int)position.y % get_dungeon_params().floor_size)) 165 | ); 166 | } 167 | 168 | bool DungeonMap::is_valid_position(const Vector2& position) 169 | { 170 | Tile* tile = find_tile(get_tile_position(position)); 171 | if (!tile) return false; 172 | 173 | return tile->type != EMPTY; 174 | } 175 | 176 | void DungeonMap::set_map_tile_color(const Vector2& tile_id, Color color) 177 | { 178 | map_builder.set_map_tile_color(tile_id, color); 179 | } 180 | 181 | void DungeonMap::add_navigation_meshes(Node* navigation) 182 | { 183 | if (!is_inside_tree()) 184 | return; 185 | 186 | if (navigation && !navigation->is_class("Navigation")) { 187 | return; 188 | } 189 | 190 | navigation_node = navigation; 191 | for (auto e = regions.front(); e; e = e->next()) { 192 | auto region = e->get(); 193 | if (region->nav_mesh.is_valid()) { 194 | region->nav_mesh_id = ((Navigation*)(navigation))->navmesh_add(region->nav_mesh, region->transform, navigation); 195 | // print_line(String("Added navmesh id: ") + itos(region->nav_mesh_id) + String(" with polygon count: ") + itos(region->nav_mesh->get_polygon_count())); 196 | } 197 | } 198 | } 199 | 200 | void DungeonMap::remove_navigation_meshes(Node* navigation) 201 | { 202 | if (!is_inside_tree()) 203 | return; 204 | 205 | if (navigation && !navigation->is_class("Navigation")) { 206 | return; 207 | } 208 | 209 | for (auto e = regions.front(); e; e = e->next()) { 210 | auto region = e->get(); 211 | if (region->nav_mesh.is_valid()) { 212 | ((Navigation*)(navigation))->navmesh_remove(region->nav_mesh_id); 213 | region->nav_mesh_id = 0; 214 | } 215 | } 216 | } 217 | 218 | Array DungeonMap::get_nav_meshes() const 219 | { 220 | Array nav_meshes; 221 | if (!is_inside_tree()) 222 | return nav_meshes; 223 | 224 | for (auto e = regions.front(); e; e = e->next()) { 225 | auto region = e->get(); 226 | if (region->nav_mesh.is_valid()) { 227 | nav_meshes.push_back(region->nav_mesh); 228 | } 229 | } 230 | return nav_meshes; 231 | } 232 | 233 | void DungeonMap::_build_regions() 234 | { 235 | int tile_size = map_builder.params.floor_size; 236 | int region_width = tiles_per_region * tile_size; 237 | 238 | // Build out our region and the cells. 239 | for (int x = 0; x < region_size; x++) { 240 | for (int y = 0; y < region_size; y++) { 241 | Vector2 id = Vector2(x, y); 242 | if (regions.has(id)) 243 | continue; 244 | 245 | Region* region = memnew(Region); 246 | region->dirty = true; 247 | region->aabb = AABB( 248 | Vector3( 249 | x * region_width, 250 | 0.0f, 251 | y * region_width * -1.0f 252 | ), 253 | Vector3( 254 | region_width, 255 | 2, 256 | region_width 257 | ) 258 | ); 259 | region->id = Vector2(region->aabb.position.x, region->aabb.position.z); 260 | 261 | region->transform.translated(region->aabb.position); 262 | regions[region->id] = region; 263 | _build_tiles(region->id); 264 | } 265 | } 266 | } 267 | 268 | void DungeonMap::_build_tiles(const Vector2& region_id) 269 | { 270 | // Build out the tiles per region. 271 | Ref map_image = get_map_image(); 272 | int tile_size = map_builder.params.floor_size; 273 | int region_width = tile_size * tile_size; 274 | 275 | for (int x = 0; x < tiles_per_region; x++) { 276 | for (int y = 0; y < tiles_per_region; y++) { 277 | if (!regions.has(region_id)) { 278 | continue; 279 | } 280 | 281 | // Create our tile. 282 | Region* region = regions[region_id]; 283 | Tile* tile = memnew(Tile); 284 | tile->local_position = Vector2(x, y); 285 | tile->region_id = region_id; 286 | tile->aabb = AABB( 287 | Vector3( 288 | (x * tile_size) + region->aabb.position.x, 289 | 0.0f, 290 | (y * tile_size * -1.0f) + region->aabb.position.z 291 | ), 292 | Vector3( 293 | tile_size, 294 | tile->height, 295 | tile_size 296 | ) 297 | ); 298 | tile->id = Vector2(tile->aabb.position.x, tile->aabb.position.z); 299 | 300 | int px = (int)Math::floor(tile->id.x); 301 | int py = (int)Math::floor(tile->id.y * -1.0f); 302 | 303 | map_image->lock(); 304 | Color pixel = map_image->get_pixel(px, py); 305 | map_image->unlock(); 306 | 307 | if (pixel != Color(0, 0, 0)) { 308 | tile->type = FLOOR; 309 | valid_tiles.push_back(tile->id); 310 | } else { 311 | tile->height = -1; 312 | } 313 | 314 | // Save our tile. 315 | tile->transform.translated(tile->aabb.position); 316 | tiles[tile->id] = tile; 317 | 318 | // Add tile to the region. Check for empty so 319 | // we know how to build the mesh. 320 | if (tile->type != EMPTY) { 321 | region->tiles.push_back(tile->id); 322 | } 323 | } 324 | } 325 | } 326 | 327 | void DungeonMap::_update_tile_neighbors() 328 | { 329 | // Update the neighbors. 330 | for (Map::Element* e = tiles.front(); e; e = e->next()) { 331 | Tile* tile = e->get(); 332 | if (tile->neighbors.size() == 0) { 333 | tile->neighbors.resize((uint8_t)MAX_TILE_NEIGHBORS); 334 | } 335 | 336 | int tile_size = map_builder.params.floor_size; 337 | Vector2 tile_id = tile->id; 338 | Vector2 north_pos = Vector2(tile_id.x, tile_id.y - tile_size); 339 | Vector2 east_pos = Vector2(tile_id.x + tile_size, tile_id.y); 340 | Vector2 south_pos = Vector2(tile_id.x, tile_id.y + tile_size); 341 | Vector2 west_pos = Vector2(tile_id.x - tile_size, tile_id.y); 342 | 343 | Vector2 north_east_pos = Vector2(tile_id.x + tile_size, tile_id.y - tile_size); 344 | Vector2 north_west_pos = Vector2(tile_id.x - tile_size, tile_id.y - tile_size); 345 | Vector2 south_east_pos = Vector2(tile_id.x + tile_size, tile_id.y + tile_size); 346 | Vector2 south_west_pos = Vector2(tile_id.x - tile_size, tile_id.y + tile_size); 347 | 348 | // Set the north tile. 349 | Tile* nTile = find_tile(north_pos); 350 | if (nTile) { 351 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH] = nTile; 352 | tile->has_north_tile = true; 353 | } else { 354 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH] = NULL; 355 | } 356 | 357 | // Set the east cell. 358 | nTile = find_tile(east_pos); 359 | if (nTile) { 360 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_EAST] = nTile; 361 | tile->has_east_tile = true; 362 | } else { 363 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_EAST] = NULL; 364 | } 365 | 366 | // Set the south cell. 367 | nTile = find_tile(south_pos); 368 | if (nTile) { 369 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH] = nTile; 370 | tile->has_south_tile = true; 371 | } else { 372 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH] = NULL; 373 | } 374 | 375 | // Set the west cell. 376 | nTile = find_tile(west_pos); 377 | if (nTile) { 378 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_WEST] = nTile; 379 | tile->has_west_tile = true; 380 | } else { 381 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_WEST] = NULL; 382 | } 383 | 384 | // Set the north-east cell. 385 | nTile = find_tile(north_east_pos); 386 | if (nTile) { 387 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH_EAST] = nTile; 388 | tile->has_north_east_tile = true; 389 | } else { 390 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH_EAST] = NULL; 391 | } 392 | 393 | // Set the north-west cell. 394 | nTile = find_tile(north_west_pos); 395 | if (nTile) { 396 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH_WEST] = nTile; 397 | tile->has_north_west_tile = true; 398 | } else { 399 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_NORTH_WEST] = NULL; 400 | } 401 | 402 | // Set the south-east cell. 403 | nTile = find_tile(south_east_pos); 404 | if (nTile) { 405 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH_EAST] = nTile; 406 | tile->has_south_east_tile = true; 407 | } else { 408 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH_EAST] = NULL; 409 | } 410 | 411 | // Set the south-west cell. 412 | nTile = find_tile(south_west_pos); 413 | if (nTile) { 414 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH_WEST] = nTile; 415 | tile->has_south_west_tile = true; 416 | } else { 417 | tile->neighbors[(uint8_t)Neighbor::NEIGHBOR_SOUTH_WEST] = NULL; 418 | } 419 | } 420 | } 421 | 422 | void DungeonMap::_build_region_meshes() 423 | { 424 | for (Map::Element* e = regions.front(); e; e = e->next()) { 425 | Region* region = e->get(); 426 | if (region->tiles.size() == 0) continue; 427 | 428 | SurfaceTool tool; 429 | tool.begin(Mesh::PRIMITIVE_TRIANGLES); 430 | for (int i = 0; i < region->tiles.size(); i++) { 431 | DungeonMapMeshBuilder::add_mesh_tile(this, &tool, region->tiles[i], false); 432 | DungeonMapMeshBuilder::add_mesh_tile(this, &tool, region->tiles[i], ceiling_height, true); 433 | } 434 | tool.generate_normals(); 435 | tool.index(); 436 | region->mesh = tool.commit(); 437 | 438 | // Create the mesh instance. 439 | region->instance = VS::get_singleton()->instance_create2(region->mesh->get_rid(), get_world()->get_scenario()); 440 | VS::get_singleton()->instance_set_transform(region->instance, region->transform); 441 | VS::get_singleton()->instance_set_visible(region->instance, true); 442 | 443 | // Create the collision shape. 444 | _create_region_collision(region->id); 445 | 446 | // Create the navigation mesh. 447 | Ref nav_mesh = memnew(NavigationMesh); 448 | region->nav_mesh = nav_mesh; 449 | region->nav_mesh->create_from_mesh(region->mesh); 450 | } 451 | } 452 | 453 | void DungeonMap::_build_region_mesh_edges() 454 | { 455 | for (Map::Element* e = regions.front(); e; e = e->next()) { 456 | Region* region = e->get(); 457 | if (region->tiles.size() == 0) continue; 458 | 459 | SurfaceTool tool; 460 | tool.begin(Mesh::PRIMITIVE_TRIANGLES); 461 | for (int i = 0; i < region->tiles.size(); i++) { 462 | DungeonMapMeshBuilder::add_mesh_tile_edge(this, &tool, region->tiles[i], ceiling_height, true); 463 | } 464 | tool.generate_normals(); 465 | tool.index(); 466 | region->edge_mesh = tool.commit(); 467 | 468 | // Create the edge mesh instance. 469 | region->edge_instance = VS::get_singleton()->instance_create2(region->edge_mesh->get_rid(), get_world()->get_scenario()); 470 | VS::get_singleton()->instance_set_transform(region->edge_instance, region->transform); 471 | VS::get_singleton()->instance_set_visible(region->edge_instance, true); 472 | 473 | // Create the collision shape. 474 | _create_region_edge_collision(region->id); 475 | } 476 | } 477 | 478 | void DungeonMap::_create_collision(StaticBody*& collision_body, CollisionShape*& collision_shape, Ref mesh, const Transform& transform) 479 | { 480 | if (mesh.is_null()) 481 | return; 482 | 483 | Ref shape = mesh->create_trimesh_shape(); 484 | if (shape.is_null()) 485 | return; 486 | 487 | collision_body = memnew(StaticBody); 488 | collision_shape = memnew(CollisionShape); 489 | collision_shape->set_shape(shape); 490 | collision_body->add_child(collision_shape); 491 | collision_shape->set_owner(collision_body); 492 | 493 | // Build the collision. 494 | // String("region_") + region->id + "_col" 495 | collision_body->set_name("collision_body"); 496 | collision_body->set_transform(transform); 497 | 498 | add_child(collision_body); 499 | collision_body->set_owner(this); 500 | } 501 | 502 | void DungeonMap::_create_region_collision(const Vector2& region_id) 503 | { 504 | Region* region = find_region(region_id); 505 | if (!region || !region->dirty) return; 506 | 507 | if (region->mesh.is_null()) 508 | return; 509 | 510 | if (region->collision_body != NULL) { 511 | region->collision_body->remove_child(region->collision_shape); 512 | remove_child(region->collision_body); 513 | region->collision_body->set_owner(NULL); 514 | 515 | memdelete(region->collision_shape); 516 | memdelete(region->collision_body); 517 | } 518 | 519 | _create_collision(region->collision_body, region->collision_shape, region->mesh, region->transform); 520 | } 521 | 522 | void DungeonMap::_create_region_edge_collision(const Vector2& region_id) 523 | { 524 | Region* region = find_region(region_id); 525 | if (!region || !region->dirty) return; 526 | 527 | if (region->edge_mesh.is_null()) 528 | return; 529 | 530 | if (region->edge_collision_body != NULL) { 531 | region->edge_collision_body->remove_child(region->edge_collision_shape); 532 | remove_child(region->edge_collision_body); 533 | region->edge_collision_body->set_owner(NULL); 534 | 535 | memdelete(region->edge_collision_shape); 536 | memdelete(region->edge_collision_body); 537 | } 538 | 539 | _create_collision(region->edge_collision_body, region->edge_collision_shape, region->edge_mesh, region->transform); 540 | } 541 | 542 | void DungeonMap::_update_visibility() 543 | { 544 | if (!is_inside_tree()) 545 | return; 546 | 547 | for (auto e = regions.front(); e; e = e->next()) { 548 | auto region = e->get(); 549 | 550 | if (region->instance.is_valid()) { 551 | VS::get_singleton()->instance_set_visible(region->instance, is_visible()); 552 | } 553 | if (region->edge_mesh.is_valid()) { 554 | VS::get_singleton()->instance_set_visible(region->edge_instance, is_visible()); 555 | } 556 | } 557 | _change_notify("visible"); 558 | } 559 | 560 | void DungeonMap::_update_transform() 561 | { 562 | 563 | } 564 | 565 | void DungeonMap::_update_material() 566 | { 567 | 568 | } 569 | 570 | void DungeonMap::_notification(int p_what) 571 | { 572 | switch (p_what) { 573 | case NOTIFICATION_ENTER_WORLD: { 574 | print_line("DungeonMap::entering world"); 575 | generate_map_image(); 576 | apply(); 577 | } break; 578 | 579 | case NOTIFICATION_EXIT_WORLD: { 580 | clear(); 581 | } break; 582 | 583 | case NOTIFICATION_VISIBILITY_CHANGED: { 584 | _update_visibility(); 585 | } break; 586 | 587 | case NOTIFICATION_TRANSFORM_CHANGED: { 588 | _update_transform(); 589 | } break; 590 | } 591 | } 592 | 593 | bool DungeonMap::_set(const StringName &p_name, const Variant &p_value) 594 | { 595 | return false; 596 | } 597 | 598 | bool DungeonMap::_get(const StringName &p_name, Variant &r_ret) const 599 | { 600 | return false; 601 | } 602 | 603 | void DungeonMap::_get_property_list(List *p_list) const 604 | { 605 | p_list->push_back(PropertyInfo(Variant::OBJECT, "material/floor", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); 606 | p_list->push_back(PropertyInfo(Variant::OBJECT, "material/wall", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); 607 | p_list->push_back(PropertyInfo(Variant::OBJECT, "material/water", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); 608 | } 609 | 610 | void DungeonMap::_bind_methods() 611 | { 612 | ClassDB::bind_method(D_METHOD("apply"), &DungeonMap::apply); 613 | ClassDB::bind_method(D_METHOD("clear"), &DungeonMap::clear); 614 | 615 | ClassDB::bind_method(D_METHOD("mark_dirty"), &DungeonMap::mark_dirty); 616 | ClassDB::bind_method(D_METHOD("is_dirty"), &DungeonMap::is_dirty); 617 | 618 | ClassDB::bind_method(D_METHOD("generate_map_image"), &DungeonMap::generate_map_image); 619 | 620 | ClassDB::bind_method(D_METHOD("get_nav_meshes"), &DungeonMap::get_nav_meshes); 621 | ClassDB::bind_method(D_METHOD("add_navigation_meshes", "navigation"), &DungeonMap::add_navigation_meshes); 622 | ClassDB::bind_method(D_METHOD("remove_navigation_meshes", "navigation"), &DungeonMap::remove_navigation_meshes); 623 | 624 | ClassDB::bind_method(D_METHOD("get_map_image"), &DungeonMap::get_map_image); 625 | ClassDB::bind_method(D_METHOD("get_map_texture"), &DungeonMap::get_map_texture); 626 | 627 | ClassDB::bind_method(D_METHOD("set_region_size", "size"), &DungeonMap::set_region_size); 628 | ClassDB::bind_method(D_METHOD("get_region_size"), &DungeonMap::get_region_size); 629 | ClassDB::bind_method(D_METHOD("set_tiles_per_region", "tiles"), &DungeonMap::set_tiles_per_region); 630 | ClassDB::bind_method(D_METHOD("get_tiles_per_region"), &DungeonMap::get_tiles_per_region); 631 | ClassDB::bind_method(D_METHOD("set_ceiling_height", "height"), &DungeonMap::set_ceiling_height); 632 | ClassDB::bind_method(D_METHOD("get_ceiling_height"), &DungeonMap::get_ceiling_height); 633 | 634 | ClassDB::bind_method(D_METHOD("set_dungeon_seed", "seed"), &DungeonMap::set_dungeon_seed); 635 | ClassDB::bind_method(D_METHOD("get_dungeon_seed"), &DungeonMap::get_dungeon_seed); 636 | 637 | ClassDB::bind_method(D_METHOD("get_random_map_location"), &DungeonMap::get_random_map_location); 638 | ClassDB::bind_method(D_METHOD("get_tile_position", "position"), &DungeonMap::get_tile_position); 639 | ClassDB::bind_method(D_METHOD("is_valid_position", "position"), &DungeonMap::is_valid_position); 640 | 641 | ClassDB::bind_method(D_METHOD("set_map_tile_color", "tile_id", "color"), &DungeonMap::set_map_tile_color); 642 | 643 | ADD_SIGNAL(MethodInfo("dungeon_map_image_generated")); 644 | ADD_SIGNAL(MethodInfo("dungeon_map_apply_completed")); 645 | } 646 | 647 | DungeonMap::DungeonMap() 648 | { 649 | //set_notify_transform(true); 650 | } 651 | 652 | DungeonMap::~DungeonMap() 653 | { 654 | // clear(); 655 | } --------------------------------------------------------------------------------