mirror of
https://github.com/tonytins/citylimits
synced 2025-06-25 09:24:44 -04:00
Merge Kenny's City Builder Starter Kit
This commit is contained in:
parent
dd04a01651
commit
36edaaf17b
510 changed files with 2236 additions and 7925 deletions
156
scripts/3d/builder3d.gd
Normal file
156
scripts/3d/builder3d.gd
Normal 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
5
scripts/3d/data_map.gd
Normal file
|
@ -0,0 +1,5 @@
|
|||
extends Resource
|
||||
class_name DataMap
|
||||
|
||||
@export var cash:int = 10000
|
||||
@export var structures:Array[DataStructure]
|
6
scripts/3d/data_structure.gd
Normal file
6
scripts/3d/data_structure.gd
Normal 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
8
scripts/3d/structure.gd
Normal 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
49
scripts/3d/view.gd
Normal 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
21
scripts/builder.gd
Normal 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)
|
|
@ -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()
|
||||
|
|
@ -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()
|
Loading…
Add table
Add a link
Reference in a new issue