222 lines
7.4 KiB
GDScript
222 lines
7.4 KiB
GDScript
@tool
|
|
extends Node3D
|
|
class_name ArenaGenerator
|
|
|
|
@export var generate: bool = false:
|
|
set(value):
|
|
if value:
|
|
if not is_inside_tree():
|
|
call_deferred("_generate_tactical_arena")
|
|
else:
|
|
_generate_tactical_arena()
|
|
generate = false
|
|
|
|
@export var clear: bool = false:
|
|
set(value):
|
|
if value:
|
|
_clear_arena()
|
|
clear = false
|
|
|
|
@export var sector_size = 24.0
|
|
@export var step_height = 3.0
|
|
@export var wall_height = 12.0
|
|
|
|
@export var floor_color = Color(0.2, 0.22, 0.25)
|
|
@export var wall_color = Color(0.3, 0.4, 0.5)
|
|
@export var obstacle_color = Color(0.6, 0.35, 0.15)
|
|
@export var ramp_color = Color(0.7, 0.4, 0.2)
|
|
|
|
func _clear_arena() -> void:
|
|
var children = get_children()
|
|
for c in children:
|
|
remove_child(c)
|
|
c.free()
|
|
|
|
func _generate_tactical_arena() -> void:
|
|
_clear_arena()
|
|
|
|
var cols = 3 # Left, Mid, Right
|
|
var rows = 5 # Spawn A, Conn A, Mid, Conn B, Spawn B
|
|
|
|
# Generate elevations (symmetric for fairness)
|
|
var elevations = []
|
|
for x in range(cols):
|
|
elevations.append([])
|
|
for z in range(rows):
|
|
elevations[x].append(0)
|
|
|
|
# Randomize half, mirror other half
|
|
for x in range(cols):
|
|
for z in range(3): # 0, 1, 2
|
|
elevations[x][z] = randi_range(0, 2)
|
|
|
|
# Mirror to the other side (point symmetry)
|
|
# Or mirror horizontally (Spawn A == Spawn B setup)
|
|
# A standard mirror across mid:
|
|
for x in range(cols):
|
|
elevations[x][3] = elevations[cols - 1 - x][1]
|
|
elevations[x][4] = elevations[cols - 1 - x][0]
|
|
|
|
# Build Sector Floors
|
|
var _sec_size = float(sector_size if sector_size != null else 24.0)
|
|
var _step_h = float(step_height if step_height != null else 3.0)
|
|
var _wall_h = float(wall_height if wall_height != null else 12.0)
|
|
|
|
var _f_col = floor_color if floor_color != null else Color(0.2, 0.22, 0.25)
|
|
var _w_col = wall_color if wall_color != null else Color(0.3, 0.4, 0.5)
|
|
|
|
var hw = (cols * _sec_size) / 2.0
|
|
var hl = (rows * _sec_size) / 2.0
|
|
|
|
# Global origin is center of grid
|
|
for x in range(cols):
|
|
for z in range(rows):
|
|
var cx = -hw + x * _sec_size + _sec_size / 2.0
|
|
var cz = -hl + z * _sec_size + _sec_size / 2.0
|
|
var y = float(elevations[x][z]) * _step_h
|
|
|
|
# Make floors incredibly thick (40 units) so they act as solid pillars all the way down
|
|
# This prevents players from walking underneath higher sectors and falling into the void
|
|
_create_box("Sector_%d_%d" % [x, z], Vector3(cx, y - 20.0, cz), Vector3(_sec_size, 40.0, _sec_size), _f_col)
|
|
|
|
# Add cover in sector (except spawns z=0 and z=4)
|
|
if z > 0 and z < 4:
|
|
_generate_cover("Cover_%d_%d" % [x, z], Vector3(cx, y, cz))
|
|
|
|
# Ramps between Z adjacent (North/South)
|
|
if z < rows - 1:
|
|
var diff = elevations[x][z+1] - elevations[x][z]
|
|
if diff != 0:
|
|
_create_ramp_z("RampZ_%d_%d" % [x, z], cx, cz + _sec_size / 2.0, y, diff)
|
|
|
|
# Ramps/Walls between X adjacent (East/West)
|
|
if x < cols - 1:
|
|
var is_connector = randf() > 0.4 # 60% chance to be an open connector
|
|
var diff = elevations[x+1][z] - elevations[x][z]
|
|
|
|
if is_connector:
|
|
if diff != 0:
|
|
_create_ramp_x("RampX_%d_%d" % [x, z], cx + _sec_size / 2.0, cz, y, diff)
|
|
else:
|
|
# Solid wall
|
|
var max_y = max(elevations[x][z], elevations[x+1][z]) * _step_h
|
|
_create_box("InnerWall_%d_%d" % [x, z], Vector3(cx + _sec_size / 2.0, max_y + _wall_h/2, cz), Vector3(1.0, _wall_h, _sec_size), _w_col)
|
|
|
|
# Outer Walls
|
|
_create_box("Wall_North", Vector3(0, _wall_h * 0.5, -hl), Vector3(hw * 2, _wall_h, 1.0), _w_col)
|
|
_create_box("Wall_South", Vector3(0, _wall_h * 0.5, hl), Vector3(hw * 2, _wall_h, 1.0), _w_col)
|
|
_create_box("Wall_East", Vector3(hw, _wall_h * 0.5, 0), Vector3(1.0, _wall_h, hl * 2), _w_col)
|
|
_create_box("Wall_West", Vector3(-hw, _wall_h * 0.5, 0), Vector3(1.0, _wall_h, hl * 2), _w_col)
|
|
|
|
|
|
func _generate_cover(base_name: String, center: Vector3) -> void:
|
|
var _sec_size = float(sector_size if sector_size != null else 24.0)
|
|
var _o_col = obstacle_color if obstacle_color != null else Color(0.6, 0.35, 0.15)
|
|
# Generate 1-3 blocks of cover
|
|
var count = randi_range(1, 3)
|
|
for i in range(count):
|
|
var ox = randf_range(-_sec_size*0.3, _sec_size*0.3)
|
|
var oz = randf_range(-_sec_size*0.3, _sec_size*0.3)
|
|
var sx = randf_range(2.0, 6.0)
|
|
var sy = randf_range(1.0, 4.0)
|
|
var sz = randf_range(2.0, 6.0)
|
|
_create_box("%s_%d" % [base_name, i], center + Vector3(ox, sy * 0.5, oz), Vector3(sx, sy, sz), _o_col)
|
|
|
|
func _create_ramp_z(node_name: String, cx: float, cz: float, base_y: float, diff_levels: int) -> void:
|
|
var _step_h = float(step_height if step_height != null else 3.0)
|
|
var _sec_size = float(sector_size if sector_size != null else 24.0)
|
|
var _r_col = ramp_color if ramp_color != null else Color(0.7, 0.4, 0.2)
|
|
# Ramp along Z axis (North/South)
|
|
var h = abs(diff_levels) * _step_h
|
|
var length = _sec_size * 0.4 # Ramp takes up 40% of sector depth
|
|
var center_y = base_y + h / 2.0
|
|
|
|
var dir = sign(diff_levels)
|
|
var rot_x = 0
|
|
if dir > 0:
|
|
rot_x = rad_to_deg(atan2(h, length))
|
|
else:
|
|
rot_x = -rad_to_deg(atan2(h, length))
|
|
|
|
var true_length = sqrt(length*length + h*h)
|
|
_create_ramp(node_name, Vector3(cx, center_y, cz), Vector3(_sec_size * 0.5, 0.5, true_length), Vector3(rot_x, 0, 0), _r_col)
|
|
|
|
func _create_ramp_x(node_name: String, cx: float, cz: float, base_y: float, diff_levels: int) -> void:
|
|
var _step_h = float(step_height if step_height != null else 3.0)
|
|
var _sec_size = float(sector_size if sector_size != null else 24.0)
|
|
var _r_col = ramp_color if ramp_color != null else Color(0.7, 0.4, 0.2)
|
|
# Ramp along X axis (East/West)
|
|
var h = abs(diff_levels) * _step_h
|
|
var length = _sec_size * 0.4
|
|
var center_y = base_y + h / 2.0
|
|
|
|
var dir = sign(diff_levels)
|
|
var rot_z = 0
|
|
if dir > 0:
|
|
rot_z = -rad_to_deg(atan2(h, length))
|
|
else:
|
|
rot_z = rad_to_deg(atan2(h, length))
|
|
|
|
var true_length = sqrt(length*length + h*h)
|
|
_create_ramp(node_name, Vector3(cx, center_y, cz), Vector3(true_length, 0.5, _sec_size * 0.5), Vector3(0, 0, rot_z), _r_col)
|
|
|
|
|
|
func _create_box(node_name: String, pos: Vector3, size: Vector3, color: Color) -> void:
|
|
var body := StaticBody3D.new()
|
|
body.name = node_name
|
|
body.position = pos
|
|
|
|
var shape := CollisionShape3D.new()
|
|
shape.name = "CollisionShape3D"
|
|
shape.shape = BoxShape3D.new()
|
|
shape.shape.size = size
|
|
body.add_child(shape)
|
|
|
|
var mesh := MeshInstance3D.new()
|
|
mesh.name = "MeshInstance3D"
|
|
mesh.mesh = BoxMesh.new()
|
|
mesh.mesh.size = size
|
|
var mat := StandardMaterial3D.new()
|
|
mat.albedo_color = color
|
|
mat.roughness = 0.8
|
|
mesh.mesh.surface_set_material(0, mat)
|
|
body.add_child(mesh)
|
|
|
|
add_child(body)
|
|
if Engine.is_editor_hint():
|
|
var scene_root = get_tree().edited_scene_root
|
|
if scene_root:
|
|
body.owner = scene_root
|
|
shape.owner = scene_root
|
|
mesh.owner = scene_root
|
|
|
|
func _create_ramp(node_name: String, pos: Vector3, size: Vector3, rot_deg: Vector3, color: Color) -> void:
|
|
var body := StaticBody3D.new()
|
|
body.name = node_name
|
|
body.position = pos
|
|
body.rotation_degrees = rot_deg
|
|
|
|
var shape := CollisionShape3D.new()
|
|
shape.name = "CollisionShape3D"
|
|
shape.shape = BoxShape3D.new()
|
|
shape.shape.size = size
|
|
body.add_child(shape)
|
|
|
|
var mesh := MeshInstance3D.new()
|
|
mesh.name = "MeshInstance3D"
|
|
mesh.mesh = BoxMesh.new()
|
|
mesh.mesh.size = size
|
|
var mat := StandardMaterial3D.new()
|
|
mat.albedo_color = color
|
|
mat.roughness = 0.8
|
|
mesh.mesh.surface_set_material(0, mat)
|
|
body.add_child(mesh)
|
|
|
|
add_child(body)
|
|
if Engine.is_editor_hint():
|
|
var scene_root = get_tree().edited_scene_root
|
|
if scene_root:
|
|
body.owner = scene_root
|
|
shape.owner = scene_root
|
|
mesh.owner = scene_root
|