Merge Kenny's City Builder Starter Kit

This commit is contained in:
Tony Bark 2023-12-14 01:41:21 -05:00
parent dd04a01651
commit 36edaaf17b
510 changed files with 2236 additions and 7925 deletions

156
scripts/3d/builder3d.gd Normal file
View file

@ -0,0 +1,156 @@
extends Node3D
@export var structures: Array[Structure] = []
var map:DataMap
var index:int = 0 # Index of structure being built
@export var selector:Node3D # The 'cursor'
@export var selector_container:Node3D # Node that holds a preview of the structure
@export var view_camera:Camera3D # Used for raycasting mouse
@export var gridmap:GridMap
@export var cash_display:Label
var plane:Plane # Used for raycasting mouse
func _ready():
map = DataMap.new()
plane = Plane(Vector3.UP, Vector3.ZERO)
# Create new MeshLibrary dynamically, can also be done in the editor
# See: https://docs.godotengine.org/en/stable/tutorials/3d/using_gridmaps.html
var mesh_library = MeshLibrary.new()
for structure in structures:
var id = mesh_library.get_last_unused_item_id()
mesh_library.create_item(id)
mesh_library.set_item_mesh(id, get_mesh(structure.model))
mesh_library.set_item_mesh_transform(id, Transform3D())
gridmap.mesh_library = mesh_library
update_structure()
update_cash()
func _process(delta):
# Controls
action_rotate() # Rotates selection 90 degrees
action_structure_toggle() # Toggles between structures
action_save() # Saving
action_load() # Loading
# Map position based on mouse
var world_position = plane.intersects_ray(
view_camera.project_ray_origin(get_viewport().get_mouse_position()),
view_camera.project_ray_normal(get_viewport().get_mouse_position()))
var gridmap_position = Vector3(round(world_position.x), 0, round(world_position.z))
selector.position = lerp(selector.position, gridmap_position, delta * 40)
action_build(gridmap_position)
action_demolish(gridmap_position)
# Retrieve the mesh from a PackedScene, used for dynamically creating a MeshLibrary
func get_mesh(packed_scene):
var scene_state:SceneState = packed_scene.get_state()
for i in range(scene_state.get_node_count()):
if(scene_state.get_node_type(i) == "MeshInstance3D"):
for j in scene_state.get_node_property_count(i):
var prop_name = scene_state.get_node_property_name(i, j)
if prop_name == "mesh":
var prop_value = scene_state.get_node_property_value(i, j)
return prop_value.duplicate()
# Build (place) a structure
func action_build(gridmap_position):
if Input.is_action_just_pressed("build"):
var previous_tile = gridmap.get_cell_item(gridmap_position)
gridmap.set_cell_item(gridmap_position, index, gridmap.get_orthogonal_index_from_basis(selector.basis))
if previous_tile != index:
map.cash -= structures[index].price
update_cash()
# Demolish (remove) a structure
func action_demolish(gridmap_position):
if Input.is_action_just_pressed("demolish"):
gridmap.set_cell_item(gridmap_position, -1)
# Rotates the 'cursor' 90 degrees
func action_rotate():
if Input.is_action_just_pressed("rotate"):
selector.rotate_y(deg_to_rad(90))
# Toggle between structures to build
func action_structure_toggle():
if Input.is_action_just_pressed("structure_next"):
index = wrap(index + 1, 0, structures.size())
if Input.is_action_just_pressed("structure_previous"):
index = wrap(index - 1, 0, structures.size())
update_structure()
# Update the structure visual in the 'cursor'
func update_structure():
# Clear previous structure preview in selector
for n in selector_container.get_children():
selector_container.remove_child(n)
# Create new structure preview in selector
var _model = structures[index].model.instantiate()
selector_container.add_child(_model)
_model.position.y += 0.25
func update_cash():
cash_display.text = "$" + str(map.cash)
# Saving/load
func action_save():
if Input.is_action_just_pressed("save"):
print("Saving map...")
map.structures.clear()
for cell in gridmap.get_used_cells():
var data_structure:DataStructure = DataStructure.new()
data_structure.position = Vector2i(cell.x, cell.z)
data_structure.orientation = gridmap.get_cell_item_orientation(cell)
data_structure.structure = gridmap.get_cell_item(cell)
map.structures.append(data_structure)
ResourceSaver.save(map, "user://map.res")
func action_load():
if Input.is_action_just_pressed("load"):
print("Loading map...")
gridmap.clear()
map = ResourceLoader.load("user://map.res")
if not map:
map = DataMap.new()
for cell in map.structures:
gridmap.set_cell_item(Vector3i(cell.position.x, 0, cell.position.y), cell.structure, cell.orientation)
update_cash()

5
scripts/3d/data_map.gd Normal file
View file

@ -0,0 +1,5 @@
extends Resource
class_name DataMap
@export var cash:int = 10000
@export var structures:Array[DataStructure]

View file

@ -0,0 +1,6 @@
extends Resource
class_name DataStructure
@export var position:Vector2i
@export var orientation:int
@export var structure:int

8
scripts/3d/structure.gd Normal file
View file

@ -0,0 +1,8 @@
extends Resource
class_name Structure
@export_subgroup("Model")
@export var model:PackedScene # Model of the structure
@export_subgroup("Gameplay")
@export var price:int # Price of the structure when building

49
scripts/3d/view.gd Normal file
View file

@ -0,0 +1,49 @@
extends Node3D
var camera_position:Vector3
var camera_rotation:Vector3
@onready var camera = $Camera
func _ready():
camera_rotation = rotation_degrees # Initial rotation
pass
func _process(delta):
# Set position and rotation to targets
position = position.lerp(camera_position, delta * 8)
rotation_degrees = rotation_degrees.lerp(camera_rotation, delta * 6)
handle_input(delta)
# Handle input
func handle_input(_delta):
# Rotation
var input := Vector3.ZERO
input.x = Input.get_axis("camera_left", "camera_right")
input.z = Input.get_axis("camera_forward", "camera_back")
input = input.rotated(Vector3.UP, rotation.y).normalized()
camera_position += input / 4
# Back to center
if Input.is_action_pressed("camera_center"):
camera_position = Vector3()
func _input(event):
# Rotate camera using mouse (hold 'middle' mouse button)
if event is InputEventMouseMotion:
if Input.is_action_pressed("camera_rotate"):
camera_rotation += Vector3(0, -event.relative.x / 10, 0)

21
scripts/builder.gd Normal file
View file

@ -0,0 +1,21 @@
extends Node2D
@export var structures: Array[Structure] = []
var map:DataMap
var index:int = 0 # Index of structure being built
@export var selector:Node2D # The 'cursor'
@export var selector_container:Node2D # Node that holds a preview of the structure
@export var view_camera:Camera2D # Used for raycasting mouse
@export var tile_map:TileMap
@export var cash_display:Label
func _ready():
map = DataMap.new()
func update_cash():
cash_display.text = "$" + str(map.cash)

View file

@ -1,22 +0,0 @@
extends Node2D
var can_place: bool = true
var is_placed: bool
@onready var level = get_node("/root/main/Level")
var current_item
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
global_position = get_global_mouse_position()
if (current_item != null and can_place and Input.is_action_just_pressed("mb_left")):
var new_item = current_item.instantiate()
level.add_child(new_item)
new_item.global_position = get_global_mouse_position()

View file

@ -1,150 +0,0 @@
extends Control
@export var load_locations: Array[String]
@export var export_locations: Array[String]
@onready var tree: Tree = $Tree
@onready var text: TextEdit = $Text
@onready var license_manager: LicenseManager = $LicenseManager
@onready var op_locations: OptionButton = $op_locations
var root: TreeItem
var engine: TreeItem
var game: TreeItem
var licenses: TreeItem
var copyright: String
var location_index: int = 0
# key = identifier
# value = TreeItem
var licenses_dict = {}
func _ready() -> void:
if not DirAccess.dir_exists_absolute("res://licenses/license_links/"):
DirAccess.make_dir_recursive_absolute("res://licenses/license_links/")
refresh_after_location_change()
reload_license_manager()
func refresh_after_location_change():
text.clear()
if load_locations.size() == 0:
load_locations.append('res://licenses')
export_locations.clear()
export_locations.append('user://licenses/game/')
location_index = 0
op_locations.clear()
for i in load_locations.size():
op_locations.add_item(load_locations[i])
func reload_license_manager():
text.clear()
license_manager.exclude_engine = location_index > 0
tree.clear()
license_manager.load_dir = load_locations[location_index]
license_manager.export_dir = export_locations[location_index]
license_manager.load_license_information()
copyright = license_manager.get_combined_copyright()
root = tree.create_item()
var combined = tree.create_item(root)
combined.set_text(0, "All Components")
combined.set_meta('mode', 'combined')
game = tree.create_item()
var _name = 'Game' if location_index == 0 else 'Mod'
if _name == 'Game' and ProjectSettings.has_setting('application/config/name'):
_name = ProjectSettings.get_setting('application/config/name')
game.set_text(0, _name)
game.set_meta('mode', 'parent')
if not license_manager.exclude_engine:
engine = tree.create_item()
engine.set_text(0, 'Godot Engine')
engine.set_meta('mode', 'parent')
licenses = tree.create_item()
licenses.set_text(0, 'Licenses')
licenses.set_meta('mode', 'parent')
var item: TreeItem
var used_licenses = {}
for parent_component in license_manager.license_links.by_parent:
for link in license_manager.license_links.by_parent[parent_component].values():
if link is LicenseLink:
match parent_component:
"Game":
item = tree.create_item(game)
"Godot Engine":
item = tree.create_item(engine)
var valid_ids = license_manager.get_all_valid_licenses(link)
for id in valid_ids:
used_licenses[id] = valid_ids[id]
item.set_text(0, link.componet_name)
item.set_meta('mode', 'link')
item.set_meta('link', link)
for license in used_licenses.values():
if license is License:
item = tree.create_item(licenses)
item.set_text(0, license.identifier)
item.set_meta('mode', 'license')
item.set_meta('license', license)
licenses_dict[license.identifier] = item
func _on_tree_item_selected() -> void:
var item = tree.get_selected()
var mode = item.get_meta('mode')
match mode:
'combined':
text.text = copyright
'parent':
pass
'link':
var link = item.get_meta('link') as LicenseLink
text.text = link.to_formatted_string(link.component_of == 'Godot Engine')
'license':
var license = item.get_meta('license') as License
text.text = license.terms
func _on_tree_item_activated() -> void:
var item = tree.get_selected()
var mode = item.get_meta('mode')
match mode:
'parent':
item.collapsed = not item.collapsed
'link':
var link = item.get_meta('link') as LicenseLink
for id in link.license_identifiers:
if licenses_dict.has(id):
var to = licenses_dict[id]
to.select(0)
tree.scroll_to_item(to)
break
func _on_btn_open_data_dir_pressed() -> void:
OS.shell_open(OS.get_user_data_dir())
func _on_button_pressed() -> void:
license_manager.export()
func _on_op_locations_item_selected(index: int) -> void:
location_index = index
reload_license_manager()