move source files to separate folders
This commit is contained in:
parent
e619b9a6df
commit
99b28ecfa3
23 changed files with 34 additions and 34 deletions
9
source/Building.gd
Normal file
9
source/Building.gd
Normal file
|
@ -0,0 +1,9 @@
|
|||
class_name Building
|
||||
extends TileMap
|
||||
|
||||
func _init():
|
||||
print("perse")
|
||||
|
||||
func _process(_delta):
|
||||
#position = get_viewport().get_mouse_position()
|
||||
pass
|
171
source/EventBus.gd
Normal file
171
source/EventBus.gd
Normal file
|
@ -0,0 +1,171 @@
|
|||
class_name EventBus
|
||||
extends Node
|
||||
|
||||
@onready var node_main:Main
|
||||
@onready var node_mainmenu:MainMenu
|
||||
@onready var node_game:Game
|
||||
@onready var node_camera:Camera
|
||||
@onready var node_uilayer:UILayer
|
||||
|
||||
|
||||
# The idea is for the user to be able to choose the map from GUI later
|
||||
var map_filenames:Array = [
|
||||
"res://maps/tampere_10x10km_1000px.png",
|
||||
"res://maps/tampere_10x10km_1024px.png",
|
||||
"res://maps/varkaus_256x256px_test.png",
|
||||
"res://maps/tampere_256px.png",
|
||||
"res://maps/tampere_10x10km_4096px.png"
|
||||
]
|
||||
var map_filename:String = map_filenames[1]
|
||||
var _world_generator:WorldGenerator
|
||||
|
||||
|
||||
func _process(_delta) -> void:
|
||||
while Input.is_action_pressed("camera_rotate_left_stepless"):
|
||||
node_camera.camera_rotate(-0.1)
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
while Input.is_action_pressed("camera_rotate_right_stepless"):
|
||||
node_camera.camera_rotate(0.1)
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
|
||||
|
||||
func _ready():
|
||||
node_main = get_parent()
|
||||
node_mainmenu = find_child("MainMenu")
|
||||
node_game = find_child("Game")
|
||||
node_camera = find_child("Camera")
|
||||
node_uilayer = find_child("UILayer")
|
||||
|
||||
|
||||
func set_ready():
|
||||
node_mainmenu.set_ready()
|
||||
|
||||
|
||||
func _unhandled_input(event) -> void:
|
||||
if !node_camera:
|
||||
return
|
||||
###################################
|
||||
# MAIN MENU #
|
||||
###################################
|
||||
|
||||
if event.is_action_pressed("open_main_menu"):
|
||||
# move mainmenu to current game camera position
|
||||
var mainmenu_pos = Globals.CAMERA_POSITION
|
||||
mainmenu_pos.x -= DisplayServer.window_get_size(0).x/2
|
||||
mainmenu_pos.y -= DisplayServer.window_get_size(0).y/2
|
||||
node_mainmenu.set_position(mainmenu_pos, false)
|
||||
|
||||
# show the menu
|
||||
node_mainmenu.set_visible(true)
|
||||
node_game.set_visible(false)
|
||||
node_uilayer.set_visible(false)
|
||||
node_main.pause_game()
|
||||
|
||||
###################################
|
||||
# GAME CAMERA #
|
||||
###################################
|
||||
|
||||
if event.is_action_pressed("camera_zoom_in"):
|
||||
node_camera.camera_zoom_in()
|
||||
if event.is_action_pressed("camera_zoom_out"):
|
||||
node_camera.camera_zoom_out()
|
||||
|
||||
if event.is_action_pressed("camera_rotate_left_fixed_step"):
|
||||
node_camera.camera_rotate(-45)
|
||||
if event.is_action_pressed("camera_rotate_right_fixed_step"):
|
||||
node_camera.camera_rotate(45)
|
||||
if event.is_action_pressed("camera_reset_rotation"):
|
||||
node_camera.camera_reset_rotation()
|
||||
|
||||
if event.is_action_pressed("take_screenshot"):
|
||||
node_camera.camera_take_screenshot()
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if !node_camera.get_camera_panning() and event.pressed:
|
||||
node_camera.set_camera_panning(true)
|
||||
if node_camera.get_camera_panning() and !event.pressed:
|
||||
node_camera.set_camera_panning(false)
|
||||
|
||||
if event is InputEventMouseMotion and node_camera.get_camera_panning():
|
||||
# rotate event.relative vector with camera rotation so camera moves to "correct" direction
|
||||
node_camera.camera_pan_position(event.relative.rotated(node_camera.get_camera_rotation()) * Globals.CAMERA_PAN_MULTI)
|
||||
|
||||
# prevent camera from going overboard
|
||||
node_camera.clamp_camera_position()
|
||||
|
||||
|
||||
func _on_mainmenu_button_pressed(button:int):
|
||||
match button:
|
||||
Globals.MAINMENU_NEW_GAME:
|
||||
start_new_game()
|
||||
|
||||
Globals.MAINMENU_LOAD_GAME:
|
||||
pass
|
||||
|
||||
Globals.MAINMENU_RESUME_GAME:
|
||||
resume_game()
|
||||
|
||||
Globals.MAINMENU_OPTIONS:
|
||||
pass
|
||||
|
||||
Globals.MAINMENU_CREDITS:
|
||||
pass
|
||||
|
||||
Globals.MAINMENU_QUIT_GAME:
|
||||
node_main.quit_game()
|
||||
|
||||
_:
|
||||
push_error("Error: Main: unknown signal at _on_mainmenu_button_pressed: ", button)
|
||||
|
||||
|
||||
func resume_game() -> void:
|
||||
# TODO save camera position before opening menu, restore camera position when closing menu
|
||||
node_main.unpause_game()
|
||||
node_mainmenu.set_visible(false)
|
||||
node_game.set_visible(true)
|
||||
node_uilayer.set_visible(true)
|
||||
|
||||
|
||||
func set_camera_position(pos:Vector2):
|
||||
node_camera.set_camera_position(pos)
|
||||
|
||||
|
||||
func start_new_game():
|
||||
# create a new world with worldgenerator
|
||||
_world_generator = WorldGenerator.new()
|
||||
if !_world_generator.generate_world(map_filename):
|
||||
push_error("World generation failed :-(")
|
||||
node_main.quit_game()
|
||||
|
||||
# after generating the world we know what limits we should set to camera
|
||||
node_camera.set_camera_limits()
|
||||
|
||||
node_mainmenu.find_child("Menu_NewGame").disabled = true
|
||||
node_mainmenu.find_child("Menu_ResumeGame").disabled = false
|
||||
|
||||
# unpause game and setup it
|
||||
node_main.unpause_game()
|
||||
node_game.set_ready()
|
||||
node_uilayer.set_ready()
|
||||
|
||||
# hide menu, display game & ui
|
||||
node_mainmenu.set_visible(false)
|
||||
node_game.set_visible(true)
|
||||
node_uilayer.set_visible(true)
|
||||
|
||||
var node_infolayer:InfoLayer
|
||||
node_infolayer = find_child("InfoLayer")
|
||||
node_infolayer.set_visible(true)
|
||||
|
||||
# set camera to center of the map
|
||||
node_camera.camera_reset_rotation()
|
||||
node_camera.set_camera_position(
|
||||
Vector2(Globals.map_size / 2.0 * Globals.TILE_SIZE_X,
|
||||
Globals.map_size / 2.0 * Globals.TILE_SIZE_Y)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
209
source/Globals.gd
Normal file
209
source/Globals.gd
Normal file
|
@ -0,0 +1,209 @@
|
|||
# File contains global variables or constants so they all are in one place instead
|
||||
# of a million files. So you can adjust them "easily" from one place if needed.
|
||||
|
||||
extends Node
|
||||
|
||||
var chunks_loaded:int = 0
|
||||
|
||||
###################################
|
||||
# VARIABLE GAME DATA, saved #
|
||||
###################################
|
||||
|
||||
# map size is based on input image x*y pixel size
|
||||
@export var map_size:int
|
||||
|
||||
# store terrain type (water, land, forest etc. for every map cell)
|
||||
@export var map_terrain_data:Array[PackedInt32Array] = [[]]
|
||||
|
||||
# preprocess and store exact tile for every map cell to speed up setting tiles
|
||||
@export var map_tile_data:Array[Array] = [[]]
|
||||
|
||||
# list of parcels the map is divided to
|
||||
@export var map_parcel_data:Array[Array] = [[]]
|
||||
|
||||
# current camera zoom level
|
||||
@export var CAMERA_ZOOM_LEVEL:float
|
||||
@export var CAMERA_POSITION:Vector2i
|
||||
|
||||
# minimap texture, used also as save game's imagetexture
|
||||
@export var minimap_texture:ImageTexture = null
|
||||
|
||||
|
||||
###################################
|
||||
# FILE PATHS #
|
||||
###################################
|
||||
|
||||
const SCENE_PATH:String = "res://scenes/"
|
||||
const ART_PATH:String = "res://art/"
|
||||
const SCRIPT_PATH:String = "res://scripts"
|
||||
|
||||
|
||||
###################################
|
||||
# MINIMAP SETTINGS #
|
||||
###################################
|
||||
|
||||
var minimap_colors:Dictionary = {
|
||||
Globals.TILE_WATER : Color8(42, 31, 255),
|
||||
Globals.TILE_TERRAIN: Color8(148, 113, 71),
|
||||
Globals.TILE_FOREST : Color8(0,123,19),
|
||||
"default": Color8(255,0,255),
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
# CHUNK AND TERRAIN SETTINGS #
|
||||
###################################
|
||||
|
||||
# world map chunk size
|
||||
const CHUNK_SIZE:Vector2i = Vector2i(32,32)
|
||||
|
||||
# tilemap tile types
|
||||
enum {TILE_WATER, TILE_TERRAIN, TILE_FOREST, TILE_BOG}
|
||||
|
||||
# parcel owner types
|
||||
enum {PARCEL_CITY, PARCEL_STATE, PARCEL_PRIVATE}
|
||||
|
||||
# tilemap layers
|
||||
enum {LAYER_TERRAIN, LAYER_BUILDINGS}
|
||||
const TILESET_TERRAIN:TileSet = preload("res://scenes/Chunk.tres")
|
||||
|
||||
###################################
|
||||
# CAMERA SETTINGS #
|
||||
###################################
|
||||
|
||||
# minimap camera marker sprite
|
||||
var camera_marker:Sprite2D
|
||||
|
||||
# GAME WINDOW DEFAULT SIZE
|
||||
const DEFAULT_X_RES:int = 1920
|
||||
const DEFAULT_Y_RES:int = 1080
|
||||
|
||||
# camera movement settings
|
||||
const CAMERA_MIN_ZOOM_LEVEL: float = 0.1
|
||||
const CAMERA_MAX_ZOOM_LEVEL: float = 2.0
|
||||
const CAMERA_ZOOM_FACTOR: float = 0.1
|
||||
const CAMERA_ZOOM_DURATION: float = 0.1
|
||||
const CAMERA_PAN_MULTI:float = 2.0
|
||||
|
||||
|
||||
###################################
|
||||
# UI ELEMENT SETTINGS #
|
||||
###################################
|
||||
|
||||
# NODE NAMES
|
||||
const WORLD_NODE:String = "World"
|
||||
const DEBUGINFO_NODE:String = "DebugInfo"
|
||||
const CONSTRUCTION_PANEL_NODE:String = "ConstructionPanel"
|
||||
|
||||
const GUI_BUILD_BUTTON_SIZE_X: int = 50
|
||||
const GUI_BUILD_BUTTON_SIZE_Y: int = 50
|
||||
const GUI_BUILD_BUTTON_SIZE: Vector2i = Vector2i(GUI_BUILD_BUTTON_SIZE_X,GUI_BUILD_BUTTON_SIZE_Y)
|
||||
|
||||
# maybe should use int for these instead for faster matching?
|
||||
# ^ yes TODO switch to enum
|
||||
const TYPE_RESIDENTIAL:String = "residential"
|
||||
const TYPE_COMMERCIAL:String = "commercial"
|
||||
const TYPE_INDUSTRIAL:String = "industrial"
|
||||
const TYPE_SERVICES:String = "services"
|
||||
const TYPE_SOCIAL:String = "social"
|
||||
const TYPE_POWERPLANT:String = "powerplant"
|
||||
const TYPE_ROADS:String = "roads"
|
||||
const TYPE_DEMOLISH:String = "demolish"
|
||||
|
||||
# Main menu buttons
|
||||
enum {
|
||||
MAINMENU_NEW_GAME,
|
||||
MAINMENU_LOAD_GAME,
|
||||
MAINMENU_RESUME_GAME,
|
||||
MAINMENU_OPTIONS,
|
||||
MAINMENU_CREDITS,
|
||||
MAINMENU_QUIT_GAME,
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
# WORLD GENERATION SETTINGS #
|
||||
###################################
|
||||
|
||||
# city map generation file should have black ground (0,0,0) and white water (1,1,1)
|
||||
const GROUND_TILE_COLOR_IN_MAP_FILE: Color = Color(0,0,0)
|
||||
const WATER_TILE_COLOR_IN_MAP_FILE: Color = Color(1,1,1)
|
||||
|
||||
# min and max sizes for a map so the map won't be unreasonably small or large
|
||||
const MAP_MIN_HEIGHT:int = 256
|
||||
const MAP_MAX_HEIGHT:int = 4096
|
||||
const MAP_MIN_WIDTH:int = 256
|
||||
const MAP_MAX_WIDTH:int = 4096
|
||||
|
||||
# tile size
|
||||
const TILE_SIZE_X:int = 16
|
||||
const TILE_SIZE_Y:int = 16
|
||||
|
||||
# tile dict to tilemap
|
||||
var td = {
|
||||
TILE_WATER: {
|
||||
"default": [Vector2i(1,0)]
|
||||
},
|
||||
TILE_TERRAIN: {
|
||||
"default": [Vector2i(0,0)],
|
||||
# 4 land tiles around water
|
||||
[1,1,1,1]: [Vector2i(0,0)],
|
||||
# 3 land tiles around water
|
||||
[1,1,1,0]: [Vector2i(0,0)],
|
||||
[1,1,0,1]: [Vector2i(0,0)],
|
||||
[1,0,1,1]: [Vector2i(0,0)],
|
||||
[0,1,1,1]: [Vector2i(0,0)],
|
||||
# 2 land tiles around water
|
||||
[1,1,0,0]: [Vector2i(11,0), Vector2i(12,0)],
|
||||
[0,1,1,0]: [Vector2i(7,0), Vector2i(8,0)],
|
||||
[0,0,1,1]: [Vector2i(19,0), Vector2i(20,0)],
|
||||
[1,0,0,1]: [Vector2i(15,0), Vector2i(16,0)],
|
||||
# 1 land tile around water
|
||||
[0,0,0,1]: [Vector2i(17,0), Vector2i(18,0)],
|
||||
[0,0,1,0]: [Vector2i(5,0), Vector2i(6,0)],
|
||||
[0,1,0,0]: [Vector2i(9,0), Vector2i(10,0)],
|
||||
[1,0,0,0]: [Vector2i(13,0), Vector2i(14,0)],
|
||||
},
|
||||
TILE_FOREST: {
|
||||
"default": [Vector2i(5,1)],
|
||||
# 4 forest tiles around land
|
||||
[2,2,2,2]: [Vector2i(5,1)],
|
||||
# 3 forest tiles around land
|
||||
[2,2,2,1]: [Vector2i(5,1)],
|
||||
[2,2,1,2]: [Vector2i(5,1)],
|
||||
[2,1,2,2]: [Vector2i(5,1)],
|
||||
[1,2,2,2]: [Vector2i(5,1)],
|
||||
# 2 forest tiles around land
|
||||
[2,2,1,1]: [Vector2i(28,0)],
|
||||
[1,2,2,1]: [Vector2i(26,0)],
|
||||
[1,1,2,2]: [Vector2i(24,0)],
|
||||
[2,1,1,2]: [Vector2i(22,0)],
|
||||
# 1 forest tile around land
|
||||
[1,1,1,2]: [Vector2i(23,0)],
|
||||
[1,1,2,1]: [Vector2i(25,0)],
|
||||
[1,2,1,1]: [Vector2i(27,0)],
|
||||
[2,1,1,1]: [Vector2i(29,0)],
|
||||
},
|
||||
TILE_BOG: {
|
||||
"key": [Vector2i(0,0)]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
# GAME ERORR MESSAGES #
|
||||
###################################
|
||||
|
||||
# error messages
|
||||
const ERROR_BUILDING_TYPE_NOT_SET:String = "Building type not set, while trying to place building."
|
||||
const ERROR_BUTTON_NOT_FOUND:String = "Button '%s' not found when trying to set it's properties in Control.gd!"
|
||||
const ERROR_FAILED_TO_LOAD_FILE:String = "Failed to load image with filename: '%s'"
|
||||
const ERROR_TILE_X_COORDS_OUT_OF_BOUNDS:String = "Trying to build outside the game area: '%s'. Cell should be between '%s-%s'"
|
||||
const ERROR_TILE_Y_COORDS_OUT_OF_BOUNDS:String = "Trying to build outside the game area: y is: '%s'. Cell should be between '%s-%s'"
|
||||
const ERROR_IMAGE_WIDTH_INCORRECT:String = "Provided map image width '%s' too small or too large. Width should be between: '%s-%s'"
|
||||
const ERROR_IMAGE_HEIGHT_INCORRECT:String = "Provided map image height '%s' too small or too large. Height should be between: '%s-%s'"
|
||||
const ERROR_MAKING_WORLD_INSTANCE:String = "Error while making an instance of World node."
|
||||
const ERROR_WHILE_GENERATING_MAP:String = "Error in generating the map. Game won't start."
|
||||
const ERROR_WORLD_TILEMAP_NODE_MISSING:String = "World TileMap node missing or name is wrong. Tried to load: '%s'"
|
||||
|
||||
|
28
source/Main.gd
Normal file
28
source/Main.gd
Normal file
|
@ -0,0 +1,28 @@
|
|||
# https://github.com/dfloer/SC2k-docs
|
||||
|
||||
class_name Main
|
||||
extends Node
|
||||
|
||||
var bus:EventBus
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pause_game()
|
||||
bus = find_child("EventBus")
|
||||
bus.set_ready()
|
||||
|
||||
|
||||
func pause_game() -> void:
|
||||
get_tree().paused = true
|
||||
|
||||
|
||||
func unpause_game() -> void:
|
||||
get_tree().paused = false
|
||||
|
||||
|
||||
func quit_game():
|
||||
get_tree().get_root().propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
|
327
source/WorldGenerator.gd
Normal file
327
source/WorldGenerator.gd
Normal file
|
@ -0,0 +1,327 @@
|
|||
class_name WorldGenerator
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# About biome generation with noise: https://www.redblobgames.com/maps/terrain-from-noise/
|
||||
# Trees with Poisson Disc: http://devmag.org.za/2009/05/03/poisson-disk-sampling/
|
||||
|
||||
|
||||
signal worldgenerator_function_called(message:String, runtime:float)
|
||||
|
||||
var image:Image = Image.new()
|
||||
|
||||
var directions:Array = [
|
||||
Vector2i(0,1), # south
|
||||
Vector2i(1,0), # east
|
||||
Vector2i(0,-1), # north
|
||||
Vector2i(-1,0) # west
|
||||
]
|
||||
|
||||
|
||||
func are_coords_valid(value:int, bounds:Vector2i, errmsg:String) -> bool:
|
||||
if bounds.x > value or value > bounds.y:
|
||||
errmsg = errmsg % [value, bounds.x, bounds.y]
|
||||
push_error(errmsg)
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func choose_randomly(list_of_entries):
|
||||
return list_of_entries[randi() % list_of_entries.size()]
|
||||
|
||||
|
||||
func choose_tile(tile:Vector2i, selected, surrounding) -> Array:
|
||||
var surrounding_tiles:Array = []
|
||||
|
||||
# determine which directions have land around the tile
|
||||
for dir in directions:
|
||||
# avoid index out of bounds
|
||||
if (tile.y+dir.y >= Globals.map_size) or (tile.x+dir.x >= Globals.map_size):
|
||||
surrounding_tiles.append(surrounding)
|
||||
elif Globals.map_terrain_data[tile.y+dir.y][tile.x+dir.x] == surrounding:
|
||||
surrounding_tiles.append(surrounding)
|
||||
else:
|
||||
surrounding_tiles.append(selected)
|
||||
|
||||
# this is because a tile can have more than 1 option
|
||||
var selected_tile = Globals.td[surrounding].get(surrounding_tiles)
|
||||
var tile_coords:Vector2i
|
||||
|
||||
if selected_tile == null:
|
||||
tile_coords = Globals.td[selected].get("default")[0]
|
||||
elif selected_tile.size() > 1:
|
||||
tile_coords = choose_randomly(selected_tile)
|
||||
else:
|
||||
tile_coords = selected_tile[0]
|
||||
|
||||
return [tile_coords, 0 if selected_tile else choose_randomly([0,1,2,3])]
|
||||
|
||||
|
||||
# Generates biomes, like forest and bog
|
||||
func generate_biomes() -> void:
|
||||
# generate a new noisemap which should emulate forest-looking areas
|
||||
var fnl:FastNoiseLite = FastNoiseLite.new()
|
||||
fnl.noise_type = FastNoiseLite.TYPE_SIMPLEX
|
||||
fnl.seed = 69 #randi()
|
||||
fnl.frequency = 0.01
|
||||
fnl.fractal_type = FastNoiseLite.FRACTAL_FBM
|
||||
fnl.fractal_octaves = 7
|
||||
fnl.fractal_lacunarity = 1.671
|
||||
fnl.fractal_gain = 0.947
|
||||
|
||||
var water_next_to_tile:bool = false
|
||||
|
||||
for y in Globals.map_terrain_data.size():
|
||||
for x in Globals.map_terrain_data[y].size():
|
||||
|
||||
# replace non-water with biomes
|
||||
if Globals.map_terrain_data[y][x] > 0:
|
||||
water_next_to_tile = false
|
||||
|
||||
# don't put forest next to water
|
||||
for dir in directions:
|
||||
if (y+dir.y >= Globals.map_size) or (x+dir.x >= Globals.map_size):
|
||||
continue
|
||||
if Globals.map_terrain_data[y+dir.y][x+dir.x] == Globals.TILE_WATER:
|
||||
water_next_to_tile = true
|
||||
|
||||
# if there's no water next to a land tile, it can be replaced with forest
|
||||
if !water_next_to_tile:
|
||||
var noise_sample = fnl.get_noise_2d(x, y)
|
||||
if noise_sample < 0.1:
|
||||
Globals.map_terrain_data[y][x] = Globals.TILE_FOREST
|
||||
# can add other tresholds here for other biomes
|
||||
|
||||
# TODO move to globals later
|
||||
var parcel_width = 16
|
||||
var parcel_height = 64
|
||||
|
||||
# forests are not generated yet so can just compare water and terrain
|
||||
func is_filled_with_water(coords:Vector2i) -> bool:
|
||||
var terrain_tile_count:int = 0
|
||||
|
||||
for y in range(coords.y, coords.y + parcel_height):
|
||||
for x in range(coords.x, coords.x + parcel_width):
|
||||
if Globals.map_terrain_data[y][x] == Globals.TILE_TERRAIN:
|
||||
terrain_tile_count += 1
|
||||
|
||||
# parcel is ok if it has at least one land
|
||||
if terrain_tile_count > 0:
|
||||
return false
|
||||
return true
|
||||
|
||||
func generate_parcels() -> void:
|
||||
# divide the land area Cadastres / Parcels
|
||||
# TODO better solution, this is something my skills were able to handle at proto stage
|
||||
# should replace with a real/better algo when I am skilled enough to do it
|
||||
Globals.map_parcel_data.resize(Globals.map_size / parcel_height)
|
||||
|
||||
for y in Globals.map_size / parcel_height:
|
||||
Globals.map_parcel_data[y].resize(Globals.map_size / parcel_width)
|
||||
for x in Globals.map_size / parcel_width:
|
||||
# ignore parcels full fo water
|
||||
if !is_filled_with_water(Vector2i(y,x)):
|
||||
# 0 = top left corner, 1 = bottom right corner, 2 = owner
|
||||
Globals.map_parcel_data[y][x] = [
|
||||
Vector2i(y * parcel_height, x * parcel_width),
|
||||
Vector2i(y * parcel_height + parcel_height, x * parcel_width + parcel_width),
|
||||
Globals.PARCEL_STATE
|
||||
]
|
||||
|
||||
#for row in Globals.map_parcel_data:
|
||||
# print(row)
|
||||
#for col in row:
|
||||
# print(Globals.map_parcel_data[row][col])
|
||||
|
||||
|
||||
func generate_world(filename) -> bool:
|
||||
# Try to load the image which we used to place water & ground to world map
|
||||
image = load(filename)
|
||||
if image == null:
|
||||
var errmsg:String = Globals.ERROR_FAILED_TO_LOAD_FILE
|
||||
push_error(errmsg % filename)
|
||||
return false
|
||||
|
||||
if (image.get_size().x / image.get_size().y) != 1:
|
||||
push_error("Error: image size was invalid in world generator")
|
||||
return false
|
||||
|
||||
var image_size:Vector2i = image.get_size()
|
||||
Globals.map_size = image_size.x
|
||||
|
||||
if !validate_mapgen_params():
|
||||
push_error("Error: invalid mapgen size parameters in world generator")
|
||||
return false
|
||||
|
||||
# idx 0: message sent to GUI, 1: function call, 2: optional args
|
||||
var worldgen_calls:Array[Array] = [
|
||||
["Reading image data", "read_image_pixel_data"],
|
||||
["Smoothing water", "smooth_land_features", Globals.TILE_WATER],
|
||||
["Generating parcels", "generate_parcels"],
|
||||
["Generating biomes", "generate_biomes"],
|
||||
["Smoothing forests", "smooth_land_features", Globals.TILE_FOREST],
|
||||
["Precalculating tilemap tiles", "select_tilemap_tiles"],
|
||||
]
|
||||
|
||||
# do this to send the generation stage and processing time to GUI
|
||||
var start:int;
|
||||
var end:int;
|
||||
|
||||
for function in worldgen_calls:
|
||||
start = Time.get_ticks_usec()
|
||||
if function.size() == 3:
|
||||
self.call(function[1], function[2])
|
||||
else:
|
||||
self.call(function[1])
|
||||
|
||||
end = Time.get_ticks_usec()
|
||||
emit_signal("worldgenerator_function_called", (end-start)/1000.0, function[0])
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func read_image_pixel_data() -> void:
|
||||
# initialize the array to have enough rows
|
||||
Globals.map_terrain_data.resize(Globals.map_size)
|
||||
Globals.map_tile_data.resize(Globals.map_size)
|
||||
|
||||
for y in Globals.map_size:
|
||||
#initialize the row to have enough columns
|
||||
Globals.map_terrain_data[y].resize(Globals.map_size)
|
||||
Globals.map_tile_data[y].resize(Globals.map_size)
|
||||
|
||||
for x in Globals.map_size:
|
||||
if image.get_pixel(x, y) == Globals.WATER_TILE_COLOR_IN_MAP_FILE:
|
||||
Globals.map_terrain_data[y][x] = Globals.TILE_WATER
|
||||
else:
|
||||
Globals.map_terrain_data[y][x] = Globals.TILE_TERRAIN
|
||||
|
||||
|
||||
func select_tilemap_tiles() -> void:
|
||||
for y in Globals.map_terrain_data.size():
|
||||
for x in Globals.map_terrain_data[y].size():
|
||||
# layer | position coords | tilemap id | coords of the tile at tilemap | alternative tile
|
||||
match Globals.map_terrain_data[y][x]:
|
||||
Globals.TILE_WATER: # water or shoreline
|
||||
Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_WATER, Globals.TILE_TERRAIN)
|
||||
|
||||
Globals.TILE_TERRAIN: #terrain or forest edge
|
||||
Globals.map_tile_data[y][x] = choose_tile(Vector2i(x,y), Globals.TILE_TERRAIN, Globals.TILE_FOREST)
|
||||
|
||||
Globals.TILE_FOREST:
|
||||
Globals.map_tile_data[y][x] = [Vector2i(5,1), choose_randomly([0,1,2,3])]
|
||||
|
||||
_: #default
|
||||
push_error("should be never here in worldgen!")
|
||||
pass
|
||||
|
||||
|
||||
# Fill water tiles, surrounded in 3-4 sides by land, with land.
|
||||
# Do it recursively with limit of n recursions!
|
||||
func smooth_land_features(tile_type:int) -> void:
|
||||
# TODO for testing avoid map borders to make it simpler to implement
|
||||
for y in range(1, Globals.map_size-1):
|
||||
for x in range(1, Globals.map_size-1):
|
||||
if Globals.map_terrain_data[y][x] != tile_type:
|
||||
continue
|
||||
|
||||
match tile_type:
|
||||
Globals.TILE_WATER:
|
||||
smooth_recursively(
|
||||
Vector2i(x, y),
|
||||
Globals.TILE_WATER,
|
||||
Globals.TILE_TERRAIN
|
||||
)
|
||||
Globals.TILE_FOREST:
|
||||
smooth_forest_recursively(
|
||||
Vector2i(x, y),
|
||||
Globals.TILE_FOREST,
|
||||
Globals.TILE_TERRAIN
|
||||
)
|
||||
|
||||
|
||||
# TEMP SPAGHETTI SOLUTION
|
||||
func smooth_forest_recursively(pos:Vector2i, selected:int, comp:int) -> void:
|
||||
# now we are supposed to be inspecting a tile with land
|
||||
var surrounding_tiles:Array = []
|
||||
|
||||
# determine which directions have land around the tile
|
||||
for dir in directions:
|
||||
# avoid out of bounds hack
|
||||
if (pos.y+dir.y >= Globals.map_size) or (pos.x+dir.x >= Globals.map_size):
|
||||
surrounding_tiles.append(comp)
|
||||
elif Globals.map_terrain_data[pos.y+dir.y][pos.x+dir.x] == comp:
|
||||
surrounding_tiles.append(comp)
|
||||
elif Globals.map_terrain_data[pos.y+dir.y][pos.x+dir.x] == selected:
|
||||
surrounding_tiles.append(selected)
|
||||
|
||||
match surrounding_tiles:
|
||||
[1,1,1,2]: #west
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.x -= 1
|
||||
[1,1,2,1]: #north
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.y -= 1
|
||||
[1,2,1,1]: #east
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.x += 1
|
||||
[2,1,1,1]: #south
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.y += 1
|
||||
[1,1,1,1]: # remove solo forests
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
return
|
||||
_:
|
||||
return
|
||||
|
||||
smooth_forest_recursively(pos, selected, comp)
|
||||
|
||||
|
||||
func smooth_recursively(pos:Vector2i, selected:int, comp:int) -> void:
|
||||
# now we are supposed to be inspecting a tile with land
|
||||
var surrounding_tiles:Array = []
|
||||
|
||||
# determine which directions have land around the tile
|
||||
for dir in directions:
|
||||
# avoid out of bounds hack
|
||||
if (pos.y+dir.y >= Globals.map_size) or (pos.x+dir.x >= Globals.map_size):
|
||||
surrounding_tiles.append(comp)
|
||||
elif Globals.map_terrain_data[pos.y+dir.y][pos.x+dir.x] == comp:
|
||||
surrounding_tiles.append(comp)
|
||||
elif Globals.map_terrain_data[pos.y+dir.y][pos.x+dir.x] == selected:
|
||||
surrounding_tiles.append(selected)
|
||||
|
||||
match surrounding_tiles:
|
||||
[1,1,1,0]: #west
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.x -= 1
|
||||
[1,1,0,1]: #north
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.y -= 1
|
||||
[1,0,1,1]: #east
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.x += 1
|
||||
[0,1,1,1]: #south
|
||||
Globals.map_terrain_data[pos.y][pos.x] = comp
|
||||
pos.y += 1
|
||||
_:
|
||||
return
|
||||
|
||||
smooth_recursively(pos, selected, comp)
|
||||
|
||||
|
||||
func validate_mapgen_params() -> bool:
|
||||
if !are_coords_valid(
|
||||
Globals.map_size,
|
||||
Vector2i(Globals.MAP_MIN_HEIGHT, Globals.MAP_MAX_HEIGHT),
|
||||
Globals.ERROR_IMAGE_HEIGHT_INCORRECT):
|
||||
return false
|
||||
|
||||
elif !are_coords_valid(
|
||||
Globals.map_size,
|
||||
Vector2i(Globals.MAP_MIN_WIDTH, Globals.MAP_MAX_WIDTH),
|
||||
Globals.ERROR_IMAGE_WIDTH_INCORRECT):
|
||||
return false
|
||||
|
||||
return true
|
132
source/camera/Camera.gd
Normal file
132
source/camera/Camera.gd
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Class handles the camera zoom and movement in the game
|
||||
class_name Camera
|
||||
extends Camera2D
|
||||
|
||||
signal camera_rotation_changed(new_rotation)
|
||||
signal camera_zoom_changed(new_zoom_factor)
|
||||
|
||||
var is_panning_camera:bool = false
|
||||
var tween:Tween
|
||||
|
||||
var chunk_in_px:Vector2i = Vector2i(
|
||||
Globals.CHUNK_SIZE.x * Globals.TILE_SIZE_X,
|
||||
Globals.CHUNK_SIZE.y * Globals.TILE_SIZE_Y)
|
||||
var game_res:Vector2i = DisplayServer.window_get_size(0)
|
||||
var x_min_limit:int = self.game_res.x/2 - chunk_in_px.x
|
||||
|
||||
|
||||
func _process(_delta) -> void:
|
||||
Globals.CAMERA_POSITION = self.get_camera_position()
|
||||
|
||||
|
||||
func get_camera_position():
|
||||
return self.position
|
||||
|
||||
|
||||
func get_camera_panning() -> bool:
|
||||
return self.is_panning_camera
|
||||
|
||||
|
||||
func get_camera_rotation():
|
||||
return self.rotation
|
||||
|
||||
|
||||
func set_camera_panning(value:bool) -> void:
|
||||
self.is_panning_camera = value
|
||||
|
||||
|
||||
func set_camera_position(pos: Vector2) -> void:
|
||||
self.position = pos
|
||||
print("camera pos set:", self.position)
|
||||
|
||||
|
||||
func set_camera_limits() -> void:
|
||||
if Globals.map_size < Globals.MAP_MIN_WIDTH:
|
||||
push_error("Camera: implausible map size '" + str(Globals.map_size) + "' while setting camera limits:")
|
||||
|
||||
# set camera bounds to map size, with chunk_in_px room to go over
|
||||
self.set_limit(SIDE_LEFT, -chunk_in_px.x)
|
||||
self.set_limit(SIDE_RIGHT, Globals.map_size*Globals.TILE_SIZE_X + chunk_in_px.x)
|
||||
self.set_limit(SIDE_TOP, -chunk_in_px.y)
|
||||
self.set_limit(SIDE_BOTTOM, Globals.map_size*Globals.TILE_SIZE_Y + chunk_in_px.y)
|
||||
|
||||
|
||||
func set_camera_zoom_level(value: float) -> void:
|
||||
# keep zoom level in bounds, return if zoom level was at min or max zoom
|
||||
var new_zoom_level = clamp(value, Globals.CAMERA_MIN_ZOOM_LEVEL, Globals.CAMERA_MAX_ZOOM_LEVEL)
|
||||
if new_zoom_level == Globals.CAMERA_ZOOM_LEVEL:
|
||||
return
|
||||
|
||||
Globals.CAMERA_ZOOM_LEVEL = new_zoom_level
|
||||
|
||||
#interpolate frames between zoom levels to make zooming look smoother
|
||||
tween = get_tree().create_tween()
|
||||
tween.tween_property(
|
||||
self,
|
||||
"zoom",
|
||||
Vector2(Globals.CAMERA_ZOOM_LEVEL, Globals.CAMERA_ZOOM_LEVEL),
|
||||
Globals.CAMERA_ZOOM_DURATION
|
||||
)
|
||||
|
||||
emit_signal("camera_zoom_changed", new_zoom_level)
|
||||
|
||||
|
||||
func clamp_camera_position() -> void:
|
||||
self.position.x = clamp(
|
||||
self.position.x,
|
||||
x_min_limit,
|
||||
Globals.map_size*Globals.TILE_SIZE_X - chunk_in_px.x
|
||||
);
|
||||
self.position.y = clamp(
|
||||
self.position.y,
|
||||
0,
|
||||
Globals.map_size*Globals.TILE_SIZE_Y
|
||||
);
|
||||
|
||||
|
||||
func camera_pan_position(value) -> void:
|
||||
self.position -= value
|
||||
|
||||
|
||||
func camera_zoom_in() -> void:
|
||||
set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL - Globals.CAMERA_ZOOM_FACTOR)
|
||||
|
||||
|
||||
func camera_zoom_out() -> void:
|
||||
set_camera_zoom_level(Globals.CAMERA_ZOOM_LEVEL + Globals.CAMERA_ZOOM_DURATION)
|
||||
|
||||
|
||||
func camera_reset_rotation() -> void:
|
||||
self.rotation_degrees = 0
|
||||
emit_signal("camera_rotation_changed", self.rotation)
|
||||
|
||||
|
||||
func camera_rotate(step:float) -> void:
|
||||
self.rotation_degrees += step
|
||||
emit_signal("camera_rotation_changed", self.rotation)
|
||||
|
||||
|
||||
func camera_take_screenshot() -> void:
|
||||
# Saves screenshot to user://
|
||||
# Windows: %APPDATA%\Godot\app_userdata\[project_name]
|
||||
# macOS: ~/Library/Application Support/Godot/app_userdata/[project_name]
|
||||
# Linux: ~/.local/share/godot/app_userdata/[project_name]
|
||||
|
||||
var user_path:String = "user://screenshots/"
|
||||
var moment:Dictionary = Time.get_datetime_dict_from_system()
|
||||
var time:String = "%s-%s-%s_%s_%s-%s" % [
|
||||
moment.get("year"),
|
||||
moment.get("month"),
|
||||
moment.get("day"),
|
||||
moment.get("hour"),
|
||||
moment.get("minute"),
|
||||
moment.get("second")
|
||||
]
|
||||
var path:String = user_path + "acsim_" + time + ".png"
|
||||
var captured_image:Image = get_viewport().get_texture().get_image()
|
||||
|
||||
captured_image.save_png(path)
|
||||
|
||||
|
||||
|
||||
|
55
source/game/Chunk.gd
Normal file
55
source/game/Chunk.gd
Normal file
|
@ -0,0 +1,55 @@
|
|||
class_name Chunk
|
||||
extends TileMap
|
||||
|
||||
var x:int = -1
|
||||
var y:int = -1
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _init(ypos:int, xpos:int):
|
||||
self.x = xpos
|
||||
self.y = ypos
|
||||
|
||||
#self.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
|
||||
self.cell_quadrant_size = 32
|
||||
|
||||
self.name = "Chunk [%d,%d]" % [x, y]
|
||||
self.set_tileset(Globals.TILESET_TERRAIN)
|
||||
self.position = Vector2i(
|
||||
x*Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X,
|
||||
y*Globals.CHUNK_SIZE.y*Globals.TILE_SIZE_Y
|
||||
)
|
||||
|
||||
|
||||
func _ready():
|
||||
generate_chunk()
|
||||
|
||||
|
||||
# draws borders around the chunk
|
||||
#func _draw():
|
||||
# self.draw_rect(
|
||||
# Rect2(
|
||||
# Vector2(0,0),
|
||||
# Vector2(
|
||||
# Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X,
|
||||
# Globals.CHUNK_SIZE.y*Globals.TILE_SIZE_Y)
|
||||
# ),
|
||||
# Color(0,0,0,0.5),
|
||||
# false
|
||||
# )
|
||||
|
||||
|
||||
func generate_chunk() -> void:
|
||||
for row in Globals.CHUNK_SIZE.y:
|
||||
for col in Globals.CHUNK_SIZE.x:
|
||||
var tile_data: Array = Globals.map_tile_data[row+y*Globals.CHUNK_SIZE.y][col+x*Globals.CHUNK_SIZE.x]
|
||||
# layer | tile coords at tilemap | tilemap id | coords of the tile at tileset | alternative tile
|
||||
self.set_cell(
|
||||
Globals.LAYER_TERRAIN,
|
||||
Vector2i(col, row),
|
||||
0,
|
||||
tile_data[0],
|
||||
tile_data[1]
|
||||
)
|
||||
|
||||
|
||||
|
159
source/game/ChunkHandler.gd
Normal file
159
source/game/ChunkHandler.gd
Normal file
|
@ -0,0 +1,159 @@
|
|||
class_name ChunkHandler
|
||||
extends Node2D
|
||||
|
||||
# one tilemap is one chunk
|
||||
# map consists of many chunks
|
||||
# chunks are loaded to view when needed
|
||||
# chunks are deleted after they are no longer needed (in view)
|
||||
|
||||
# This is done to speed up game loading and avoiding setting one large tilemap in one go
|
||||
# which is extremely slow in godot 4.0, 4096x4096 takes minutes to fill with set_cell() commands
|
||||
|
||||
|
||||
signal chunk_stats(chunks, removal_queue)
|
||||
|
||||
var chunks:Dictionary = {}
|
||||
var chunks_to_remove:Array[Chunk] = []
|
||||
var window_width:int = DisplayServer.window_get_size(0).x
|
||||
var distance:int = abs((window_width/(Globals.CHUNK_SIZE.x*Globals.TILE_SIZE_X)) / 2 +1 )
|
||||
|
||||
# for threading
|
||||
var chunk_queue:Array = []
|
||||
var mutex:Mutex
|
||||
var semaphore:Semaphore
|
||||
var thread:Thread
|
||||
var exit_thread:bool = false
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if !thread:
|
||||
return
|
||||
mutex.lock()
|
||||
exit_thread = true
|
||||
mutex.unlock()
|
||||
|
||||
semaphore.post()
|
||||
thread.wait_to_finish()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
self.name = "ChunkHandler"
|
||||
|
||||
|
||||
func clean_up_chunks():
|
||||
while true:
|
||||
if not chunks_to_remove.is_empty():
|
||||
mutex.lock()
|
||||
var chunk = chunks_to_remove.pop_front()
|
||||
mutex.unlock()
|
||||
chunk.queue_free()
|
||||
|
||||
await get_tree().create_timer(0.02).timeout
|
||||
|
||||
|
||||
func correction_factor(d) -> float:
|
||||
if Globals.CAMERA_ZOOM_LEVEL < 0.6:
|
||||
return d * 2.0
|
||||
elif Globals.CAMERA_ZOOM_LEVEL > 1.0:
|
||||
return d
|
||||
else:
|
||||
return d * ( 1 + 2 * (1-Globals.CAMERA_ZOOM_LEVEL) )
|
||||
|
||||
|
||||
func get_chunk(key:Vector2i):
|
||||
if self.chunks.has(key):
|
||||
return chunks.get(key)
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func load_chunk(y:int, x:int, key):
|
||||
var chunk = Chunk.new(y, x)
|
||||
call_deferred("add_child", chunk)
|
||||
|
||||
mutex.lock()
|
||||
chunks[key] = chunk
|
||||
mutex.unlock()
|
||||
|
||||
|
||||
func process_delay_chunks() -> void:
|
||||
while true:
|
||||
update_chunks()
|
||||
await get_tree().create_timer(0.05).timeout
|
||||
|
||||
|
||||
func process_delay_stats() -> void:
|
||||
# emit stats about chunk amounts every 0,5s
|
||||
while true:
|
||||
emit_signal("chunk_stats", self.chunks.size(), self.chunks_to_remove.size())
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
|
||||
func set_ready():
|
||||
mutex = Mutex.new()
|
||||
semaphore = Semaphore.new()
|
||||
exit_thread = false
|
||||
|
||||
if !thread:
|
||||
thread = Thread.new()
|
||||
|
||||
if !thread.is_started():
|
||||
thread.start(start_chunkgen, Thread.PRIORITY_NORMAL)
|
||||
clean_up_chunks()
|
||||
|
||||
process_delay_chunks()
|
||||
process_delay_stats()
|
||||
|
||||
|
||||
func start_chunkgen():
|
||||
while true:
|
||||
semaphore.wait()
|
||||
|
||||
mutex.lock()
|
||||
var should_exit = exit_thread # Protect with Mutex.
|
||||
mutex.unlock()
|
||||
|
||||
if should_exit:
|
||||
break
|
||||
|
||||
# work on emptying the generation queue
|
||||
if not chunk_queue.is_empty():
|
||||
mutex.lock()
|
||||
var vars = chunk_queue.pop_front()
|
||||
mutex.unlock()
|
||||
|
||||
load_chunk(vars[0].y, vars[0].x, vars[1])
|
||||
|
||||
|
||||
func update_chunks():
|
||||
var p_x:float = int(Globals.CAMERA_POSITION.x- Globals.CHUNK_SIZE.x) / Globals.TILE_SIZE_X / Globals.CHUNK_SIZE.x
|
||||
var p_y:float = int(Globals.CAMERA_POSITION.y- Globals.CHUNK_SIZE.y) / Globals.TILE_SIZE_Y / Globals.CHUNK_SIZE.y
|
||||
|
||||
# When updating chunks, adjust chunk rendering distance
|
||||
# based on current zoom level.
|
||||
var zoom_corrected:float = correction_factor(distance)
|
||||
|
||||
# iterate through all the chunks. if a chunk is in camera range,
|
||||
# and it exists, it should not be removed
|
||||
# if chunk should be in range and it doesn't exist, it will be
|
||||
# created by adding the coords to a work queue
|
||||
for y in Globals.map_size/Globals.CHUNK_SIZE.y:
|
||||
for x in Globals.map_size/Globals.CHUNK_SIZE.x:
|
||||
|
||||
var key = Vector2i(y,x)
|
||||
var chunk = null
|
||||
|
||||
if chunks.has(key):
|
||||
chunk = get_chunk(key)
|
||||
|
||||
if (abs(x - p_x) <= zoom_corrected && abs(y - p_y) <= zoom_corrected):
|
||||
if chunk == null:
|
||||
mutex.lock()
|
||||
chunk_queue.push_back([Vector2i(x, y), key])
|
||||
mutex.unlock()
|
||||
semaphore.post()
|
||||
|
||||
elif chunks.has(key):
|
||||
chunks_to_remove.append(chunks.get(key))
|
||||
chunks.erase(key)
|
||||
|
23
source/game/Game.gd
Normal file
23
source/game/Game.gd
Normal file
|
@ -0,0 +1,23 @@
|
|||
class_name Game
|
||||
extends Node2D
|
||||
|
||||
@onready var node_chunkhandler:ChunkHandler
|
||||
@onready var node_mapbackground:MapBackground
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
node_chunkhandler = find_child("ChunkHandler")
|
||||
node_mapbackground = find_child("MapBackground")
|
||||
|
||||
|
||||
# sets the minimap texture as map background to avoid jarring transitions
|
||||
func _on_minimap_set_map_background_texture(sprite, scaling:Vector2) -> void:
|
||||
self.set_map_background_texture(sprite, scaling)
|
||||
|
||||
|
||||
func set_ready() -> void:
|
||||
node_chunkhandler.set_ready()
|
||||
|
||||
|
||||
func set_map_background_texture(sprite, scaling:Vector2) -> void:
|
||||
node_mapbackground.set_map_background_texture(sprite, scaling)
|
8
source/game/MapBackground.gd
Normal file
8
source/game/MapBackground.gd
Normal file
|
@ -0,0 +1,8 @@
|
|||
class_name MapBackground
|
||||
extends Sprite2D
|
||||
|
||||
# sets the minimap texture as map background to avoid jarring transitions
|
||||
func set_map_background_texture(sprite, scaling:Vector2) -> void:
|
||||
self.texture = sprite
|
||||
self.scale = scaling
|
||||
|
12
source/game/Simulation.gd
Normal file
12
source/game/Simulation.gd
Normal file
|
@ -0,0 +1,12 @@
|
|||
class_name Simulation
|
||||
extends Node
|
||||
|
||||
|
||||
# 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):
|
||||
pass
|
23
source/infolayer/InfoLayer.gd
Normal file
23
source/infolayer/InfoLayer.gd
Normal file
|
@ -0,0 +1,23 @@
|
|||
class_name InfoLayer
|
||||
extends Node2D
|
||||
|
||||
# displays various info layers of the game
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
|
||||
|
||||
func _draw():
|
||||
for y in 16:
|
||||
for x in 64:
|
||||
draw_rect(Rect2(x*16*16, y*64*16, 16*16, 64*16), Color8(200,25,25,220), false, 4.0)
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta):
|
||||
self.position = Vector2(0,0)
|
||||
|
||||
|
||||
|
40
source/mainmenu/MainMenu.gd
Normal file
40
source/mainmenu/MainMenu.gd
Normal file
|
@ -0,0 +1,40 @@
|
|||
class_name MainMenu
|
||||
extends Control
|
||||
|
||||
signal button_pressed(button_name)
|
||||
|
||||
# Connect main menu to Main game
|
||||
|
||||
#func _process(delta):
|
||||
# print("aaa")
|
||||
|
||||
|
||||
func set_ready():
|
||||
self.connect("button_pressed", self.get_parent()._on_mainmenu_button_pressed, CONNECT_PERSIST)
|
||||
self.find_child("Menu_ResumeGame").disabled = true
|
||||
|
||||
|
||||
func _on_menu_new_game_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_NEW_GAME)
|
||||
|
||||
|
||||
func _on_menu_load_game_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_LOAD_GAME)
|
||||
|
||||
|
||||
func _on_menu_resume_game_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_RESUME_GAME)
|
||||
|
||||
|
||||
func _on_menu_options_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_OPTIONS)
|
||||
|
||||
|
||||
func _on_menu_credits_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_CREDITS)
|
||||
|
||||
|
||||
func _on_menu_exit_game_pressed():
|
||||
emit_signal("button_pressed", Globals.MAINMENU_QUIT_GAME)
|
||||
|
||||
|
34
source/uilayer/CameraMarker.gd
Normal file
34
source/uilayer/CameraMarker.gd
Normal file
|
@ -0,0 +1,34 @@
|
|||
class_name CameraMarker
|
||||
extends Sprite2D
|
||||
|
||||
var size_multiplier:float
|
||||
var w_s:Vector2
|
||||
|
||||
|
||||
# Draws a box represnting the camera view in minimap
|
||||
func _draw():
|
||||
draw_rect(Rect2(-w_s.x/2, -w_s.y/2, w_s.x, w_s.y), Color.GREEN, false, -3.0)
|
||||
|
||||
|
||||
# Rotates the box if camera is rotated
|
||||
func _on_camera_zoom_2d_camera_rotation_changed(new_rotation):
|
||||
self.rotation = new_rotation
|
||||
|
||||
|
||||
# Redraws the box to a different size if camera is zoomed
|
||||
func _on_camera_zoom_2d_camera_zoom_changed(new_zoom_factor):
|
||||
w_s = DisplayServer.window_get_size(0) / size_multiplier
|
||||
w_s.x /= new_zoom_factor
|
||||
w_s.y /= new_zoom_factor
|
||||
queue_redraw()
|
||||
|
||||
|
||||
# Sets the initial size of the camera box, after game is loaded
|
||||
func set_camera_marker() -> void:
|
||||
size_multiplier = Globals.map_size / 32
|
||||
w_s = DisplayServer.window_get_size(0) / size_multiplier
|
||||
|
||||
|
||||
func set_camera_marker_position(pos:Vector2) -> void:
|
||||
#print("marker pos: ", pos)
|
||||
self.position = pos
|
107
source/uilayer/Control.gd
Normal file
107
source/uilayer/Control.gd
Normal file
|
@ -0,0 +1,107 @@
|
|||
extends Control
|
||||
|
||||
# var view = get_node("../View")
|
||||
|
||||
signal button_pressed(button_name)
|
||||
@onready var debug_info = get_node("DebugContainer/" + Globals.DEBUGINFO_NODE)
|
||||
@onready var minimap:Minimap
|
||||
|
||||
var amount_of_chunks:int = 0
|
||||
var size_of_chunk_removal_queue:int = 0
|
||||
var update_debug_info:bool = false
|
||||
|
||||
|
||||
# name, position
|
||||
var buttons = {
|
||||
"button_residental": [Vector2(0,0), "R"],
|
||||
"button_commercial": [Vector2(50,0), "C"],
|
||||
"button_industrial": [Vector2(100,00), "I"],
|
||||
"button_roads": [Vector2(0,50), "Rd"],
|
||||
"button_demolish": [Vector2(50,50), "Dm"],
|
||||
"button_services": [Vector2(100,50), "Sv"],
|
||||
"button_social": [Vector2(150,50), "So"],
|
||||
}
|
||||
|
||||
func _on_chunk_handler_chunk_stats(chunks, removal_queue):
|
||||
self.amount_of_chunks = chunks
|
||||
self.size_of_chunk_removal_queue = removal_queue
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
create_buttons()
|
||||
minimap = Minimap.new()
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta):
|
||||
update_debug_info_func()
|
||||
|
||||
|
||||
# sends signals which View catches and places selected type of buildings
|
||||
func _on_button_residental_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_RESIDENTIAL)
|
||||
|
||||
|
||||
func _on_button_commercial_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_COMMERCIAL)
|
||||
|
||||
|
||||
func _on_button_industrial_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_INDUSTRIAL)
|
||||
|
||||
|
||||
func _on_button_roads_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_ROADS)
|
||||
|
||||
|
||||
func _on_button_demolish_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_DEMOLISH)
|
||||
|
||||
|
||||
func _on_button_services_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_SERVICES)
|
||||
|
||||
|
||||
func _on_button_social_pressed():
|
||||
emit_signal("button_pressed", Globals.TYPE_SOCIAL)
|
||||
|
||||
|
||||
func _on_main_worldgen_ready():
|
||||
self.set_process(true)
|
||||
update_debug_info = true
|
||||
|
||||
|
||||
# defines construction toolbar buttons
|
||||
func create_buttons():
|
||||
for button in buttons:
|
||||
var values = buttons[button]
|
||||
var node_path = get_node(Globals.CONSTRUCTION_PANEL_NODE + "/" + str(button))
|
||||
|
||||
if(!node_path):
|
||||
var errmsg = Globals.ERROR_BUTTON_NOT_FOUND % button
|
||||
push_error(errmsg)
|
||||
|
||||
node_path.set_size(Globals.GUI_BUILD_BUTTON_SIZE)
|
||||
node_path.set_position(values[0])
|
||||
node_path.set_anchor(SIDE_TOP, anchor_top)
|
||||
node_path.set_text(values[1])
|
||||
node_path.show()
|
||||
|
||||
func update_debug_info_func():
|
||||
debug_info.set_text(
|
||||
"Camera pos: " + str(Globals.CAMERA_POSITION) + "\n" +
|
||||
"Chunks: " + str(self.amount_of_chunks) + "\n" +
|
||||
"Chunk del: " + str(self.size_of_chunk_removal_queue)
|
||||
)
|
||||
# debug_info.set_text(
|
||||
# #str(get_viewport().get_mouse_position()) +"\n" +
|
||||
# "FPS " + str(Engine.get_frames_per_second()) + "\n" +
|
||||
# "Zoom lvl: " + str(Globals.CAMERA_ZOOM_LEVEL) + "\n" +
|
||||
# "Camera pos: " + str(Globals.CAMERA_POSITION) + "\n" +
|
||||
# "Camera pos: " + str(Globals.camera_marker.position) + "\n" +
|
||||
# "Chunks: " + str(self.amount_of_chunks) + "\n" +
|
||||
# "Chunk del: " + str(self.size_of_chunk_removal_queue),
|
||||
# )
|
||||
|
||||
|
90
source/uilayer/EntityPlacer.gd
Normal file
90
source/uilayer/EntityPlacer.gd
Normal file
|
@ -0,0 +1,90 @@
|
|||
class_name EntityPlacer
|
||||
extends Control
|
||||
|
||||
var has_placeable_building: bool = false
|
||||
var building
|
||||
var building_type: String
|
||||
var scene
|
||||
|
||||
func get_building_properties() -> Array:
|
||||
var tileset_id = 0 # default value
|
||||
var tilemap_tile_coords: Vector2i
|
||||
|
||||
if building_type == null:
|
||||
push_error(Globals.ERROR_BUILDING_TYPE_NOT_SET)
|
||||
return []
|
||||
|
||||
# layer | position coords | tileset id | coords of the tile at tilemap | alternative tile
|
||||
match building_type:
|
||||
Globals.TYPE_RESIDENTIAL:
|
||||
tilemap_tile_coords = Vector2i(0,0)
|
||||
tileset_id = 0
|
||||
Globals.TYPE_COMMERCIAL:
|
||||
tilemap_tile_coords = Vector2i(4,12)
|
||||
tileset_id = 1
|
||||
Globals.TYPE_INDUSTRIAL:
|
||||
tilemap_tile_coords = Vector2i(4,20)
|
||||
tileset_id = 1
|
||||
Globals.TYPE_ROADS:
|
||||
tilemap_tile_coords = Vector2i(14,2)
|
||||
tileset_id = 1
|
||||
Globals.TYPE_DEMOLISH:
|
||||
tilemap_tile_coords = Vector2i(4,4)
|
||||
tileset_id = 1
|
||||
Globals.TYPE_SERVICES:
|
||||
tilemap_tile_coords = Vector2i(4,8)
|
||||
tileset_id = 1
|
||||
Globals.TYPE_SOCIAL:
|
||||
tilemap_tile_coords = Vector2i(4,0)
|
||||
tileset_id = 1
|
||||
_: #default
|
||||
tilemap_tile_coords = Vector2i(16,16)
|
||||
tileset_id = 1
|
||||
|
||||
return [tileset_id, tilemap_tile_coords]
|
||||
|
||||
func _on_control_button_pressed(type):
|
||||
self.building_type = type
|
||||
|
||||
# create new building, in Building node it is attached to mouse cursor
|
||||
#var building_properties = get_building_properties()
|
||||
scene = load(Globals.SCENE_PATH + "Building.tscn")
|
||||
building = scene.instantiate()
|
||||
#building.set_cell(0, Vector2i(0,0), building_properties[0], building_properties[1], 0)
|
||||
#add_child(building)
|
||||
|
||||
has_placeable_building = true
|
||||
|
||||
#func _input(event):
|
||||
# # place the building
|
||||
# if event.is_action_pressed("place_building") and has_placeable_building:
|
||||
# has_placeable_building = false
|
||||
# place_building_to_map()
|
||||
#
|
||||
# # cancel placement
|
||||
# if event.is_action_pressed("cancel"):
|
||||
# if has_placeable_building:
|
||||
# pass
|
||||
|
||||
func calculate_grid_coordinates(map_position: Vector2) -> Vector2:
|
||||
return (map_position).floor()
|
||||
|
||||
#func place_building_to_map():
|
||||
# var building_properties = get_building_properties()
|
||||
# #var tile_on_mouse = local_to_map(get_global_mouse_position())
|
||||
#
|
||||
# if !Globals.are_coords_valid(
|
||||
# tile_on_mouse.y,
|
||||
# Vector2i(0, Globals.map_image_size.y),
|
||||
# Globals.ERROR_TILE_Y_COORDS_OUT_OF_BOUNDS
|
||||
# ):
|
||||
# return false
|
||||
# elif !Globals.are_coords_valid(
|
||||
# tile_on_mouse.x,
|
||||
# Vector2i(0, Globals.map_image_size.x),
|
||||
# Globals.ERROR_TILE_X_COORDS_OUT_OF_BOUNDS
|
||||
# ):
|
||||
# return false
|
||||
|
||||
# set_cell(Globals.LAYER_BUILDINGS, tile_on_mouse, building_properties[0], building_properties[1], 0)
|
||||
|
111
source/uilayer/Minimap.gd
Normal file
111
source/uilayer/Minimap.gd
Normal file
|
@ -0,0 +1,111 @@
|
|||
class_name Minimap
|
||||
extends Control
|
||||
|
||||
signal set_camera_position(pos:Vector2)
|
||||
signal set_map_background_texture(texture, scaling)
|
||||
|
||||
@onready var sprite:Sprite2D
|
||||
@onready var is_mouse_inside_minimap:bool = false
|
||||
@onready var position_multiplier:float
|
||||
@onready var area_size:Vector2
|
||||
@onready var node_camera_marker:CameraMarker
|
||||
var observe_mouse_inside_minimap:bool = false
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
Globals.minimap_texture = ImageTexture.new()
|
||||
|
||||
|
||||
func _draw():
|
||||
#self.draw_rect(Rect2i(Vector2i(1,1), Vector2i(514,514)), Color(0,0,0), false, 2.0)
|
||||
pass
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
if !is_mouse_inside_minimap and observe_mouse_inside_minimap:
|
||||
node_camera_marker.set_position(Vector2(
|
||||
Globals.CAMERA_POSITION.x / position_multiplier,
|
||||
Globals.CAMERA_POSITION.y / position_multiplier,
|
||||
))
|
||||
|
||||
|
||||
func _on_mouse_entered():
|
||||
is_mouse_inside_minimap = true
|
||||
|
||||
func _on_mouse_exited():
|
||||
is_mouse_inside_minimap = false
|
||||
|
||||
|
||||
func _unhandled_input(event) -> void:
|
||||
if is_mouse_inside_minimap:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
node_camera_marker.set_camera_marker_position(get_local_mouse_position())
|
||||
emit_signal(
|
||||
"set_camera_position",
|
||||
get_local_mouse_position() * position_multiplier
|
||||
)
|
||||
|
||||
|
||||
func generate_minimap() -> void:
|
||||
var image = Image.new()
|
||||
|
||||
image = Image.create(Globals.map_size, Globals.map_size, false, Image.FORMAT_RGBAF)
|
||||
image.resize(Globals.map_size, Globals.map_size)
|
||||
for y in Globals.map_size:
|
||||
for x in Globals.map_size:
|
||||
var color:Color
|
||||
|
||||
match Globals.map_terrain_data[y][x]:
|
||||
Globals.TILE_WATER:
|
||||
color = Globals.minimap_colors.get(Globals.TILE_WATER)
|
||||
Globals.TILE_TERRAIN:
|
||||
color = Globals.minimap_colors.get(Globals.TILE_TERRAIN)
|
||||
Globals.TILE_FOREST:
|
||||
color = Globals.minimap_colors.get(Globals.TILE_FOREST)
|
||||
_: #default
|
||||
color = Globals.minimap_colors.get("default")
|
||||
|
||||
image.set_pixel(x, y, color)
|
||||
|
||||
Globals.minimap_texture = ImageTexture.create_from_image(image)
|
||||
|
||||
|
||||
func set_camera_marker() -> void:
|
||||
node_camera_marker = self.find_child("CameraMarker")
|
||||
node_camera_marker.set_camera_marker()
|
||||
|
||||
|
||||
func set_minimap() -> void:
|
||||
self.sprite = self.find_child("MinimapSprite")
|
||||
self.sprite.texture = Globals.minimap_texture
|
||||
|
||||
# The size of a sprite is determined from its texture
|
||||
var texture_size = sprite.texture.get_size()
|
||||
|
||||
# Calculate which scale the sprite should have to match the size of the area
|
||||
var sx = area_size.x / texture_size.x
|
||||
var sy = area_size.y / texture_size.y
|
||||
|
||||
sprite.scale = Vector2(sx, sy)
|
||||
|
||||
emit_signal("set_map_background_texture", sprite.texture, Vector2(16, 16))
|
||||
|
||||
|
||||
func set_ready() -> void:
|
||||
# Assuming the area has a child CollisionShape2D with a RectangleShape resource
|
||||
self.set_process(true)
|
||||
observe_mouse_inside_minimap = true
|
||||
area_size = self.get_rect().size
|
||||
|
||||
position_multiplier = Globals.map_size / 32
|
||||
|
||||
self.generate_minimap()
|
||||
self.set_minimap()
|
||||
self.set_camera_marker()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
14
source/uilayer/UILayer.gd
Normal file
14
source/uilayer/UILayer.gd
Normal file
|
@ -0,0 +1,14 @@
|
|||
class_name UILayer
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var node_minimap:Minimap
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
node_minimap = find_child("Minimap")
|
||||
|
||||
|
||||
func set_ready() -> void:
|
||||
node_minimap.set_ready()
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue