1c6c9b2a70
Add Phase A greybox vertical slice: third-person movement, forest path, checkpoints, story triggers, and light ability gate.
295 lines
12 KiB
C#
295 lines
12 KiB
C#
using UnityEngine;
|
|
|
|
namespace KirbyAdventures
|
|
{
|
|
/// <summary>
|
|
/// Procedural greybox for Phase A — Level_01_Forest.
|
|
/// </summary>
|
|
public static class Level01ForestBuilder
|
|
{
|
|
static Transform _root;
|
|
|
|
public static void Build()
|
|
{
|
|
if (GameObject.Find("Level_01_Forest") != null)
|
|
return;
|
|
|
|
_root = new GameObject("Level_01_Forest").transform;
|
|
|
|
EnsureLighting();
|
|
BuildGroundAndPath();
|
|
BuildJumpPlatforms();
|
|
var startPos = new Vector3(0f, 1.1f, 0f);
|
|
BuildCheckpoints();
|
|
BuildStoryTriggers();
|
|
BuildAbilityPedestal();
|
|
BuildVillageGate();
|
|
var player = BuildPlayer(startPos);
|
|
BuildCamera(player);
|
|
BuildKillPlane(player.transform);
|
|
|
|
CheckpointSystem.EnsureStart(startPos, Quaternion.identity);
|
|
CheckpointSystem.SetCheckpoint(startPos, Quaternion.identity, "start");
|
|
|
|
DialogueUI.ShowTemporary("Кирби очнулся среди деревьев. Тропа ведёт к деревне — и к себе.", 4f);
|
|
}
|
|
|
|
static void EnsureLighting()
|
|
{
|
|
// Keep scene light; add one if missing
|
|
if (Object.FindFirstObjectByType<Light>() == null)
|
|
{
|
|
var lightGo = new GameObject("Directional Light");
|
|
lightGo.transform.SetParent(_root);
|
|
var light = lightGo.AddComponent<Light>();
|
|
light.type = LightType.Directional;
|
|
light.color = new Color(1f, 0.95f, 0.85f);
|
|
light.intensity = 1.1f;
|
|
lightGo.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
|
|
}
|
|
|
|
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
|
|
RenderSettings.ambientSkyColor = new Color(0.45f, 0.55f, 0.65f);
|
|
RenderSettings.ambientEquatorColor = new Color(0.35f, 0.4f, 0.3f);
|
|
RenderSettings.ambientGroundColor = new Color(0.15f, 0.12f, 0.1f);
|
|
}
|
|
|
|
static void BuildGroundAndPath()
|
|
{
|
|
// Start platform
|
|
CreateBox("StartPlatform", new Vector3(0f, 0f, 0f), new Vector3(10f, 1f, 10f),
|
|
new Color(0.35f, 0.45f, 0.28f));
|
|
|
|
// Forest path stretches
|
|
CreateBox("Path_A", new Vector3(0f, 0f, 14f), new Vector3(6f, 1f, 16f),
|
|
new Color(0.32f, 0.42f, 0.26f));
|
|
CreateBox("Path_B", new Vector3(0f, 0f, 32f), new Vector3(5f, 1f, 12f),
|
|
new Color(0.3f, 0.4f, 0.24f));
|
|
|
|
// Side tree markers (cylinders)
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
float z = 4f + i * 5f;
|
|
float x = (i % 2 == 0) ? -5.5f : 5.5f;
|
|
var tree = CreatePrimitive(PrimitiveType.Cylinder, $"Tree_{i}",
|
|
new Vector3(x, 2f, z), new Vector3(0.6f, 2f, 0.6f),
|
|
new Color(0.2f, 0.28f, 0.15f));
|
|
tree.transform.SetParent(_root);
|
|
}
|
|
}
|
|
|
|
static void BuildJumpPlatforms()
|
|
{
|
|
// Gap then platforms
|
|
CreateBox("Pad_1", new Vector3(0f, 0.5f, 42f), new Vector3(3.5f, 1f, 3.5f),
|
|
new Color(0.45f, 0.38f, 0.28f));
|
|
CreateBox("Pad_2", new Vector3(3f, 1.2f, 48f), new Vector3(3f, 1f, 3f),
|
|
new Color(0.48f, 0.4f, 0.3f));
|
|
CreateBox("Pad_3", new Vector3(-2f, 2f, 54f), new Vector3(3f, 1f, 3f),
|
|
new Color(0.5f, 0.42f, 0.32f));
|
|
CreateBox("Pad_4", new Vector3(0f, 2.5f, 61f), new Vector3(4f, 1f, 4f),
|
|
new Color(0.4f, 0.45f, 0.3f));
|
|
|
|
// Landing after mid section
|
|
CreateBox("Path_C", new Vector3(0f, 0f, 72f), new Vector3(8f, 1f, 14f),
|
|
new Color(0.33f, 0.42f, 0.27f));
|
|
CreateBox("Path_D", new Vector3(0f, 0f, 90f), new Vector3(10f, 1f, 16f),
|
|
new Color(0.36f, 0.44f, 0.3f));
|
|
CreateBox("VillageApproach", new Vector3(0f, 0f, 108f), new Vector3(12f, 1f, 14f),
|
|
new Color(0.4f, 0.4f, 0.35f));
|
|
}
|
|
|
|
static void BuildCheckpoints()
|
|
{
|
|
CreateCheckpoint("CP_Start", new Vector3(0f, 1.5f, 2f), new Vector3(4f, 2f, 2f), "cp_start");
|
|
CreateCheckpoint("CP_Mid", new Vector3(0f, 3.5f, 61f), new Vector3(3f, 2f, 3f), "cp_mid");
|
|
CreateCheckpoint("CP_NearGate", new Vector3(0f, 1.5f, 100f), new Vector3(5f, 2f, 3f), "cp_gate");
|
|
}
|
|
|
|
static void CreateCheckpoint(string name, Vector3 pos, Vector3 size, string id)
|
|
{
|
|
var go = new GameObject(name);
|
|
go.transform.SetParent(_root);
|
|
go.transform.position = pos;
|
|
var box = go.AddComponent<BoxCollider>();
|
|
box.isTrigger = true;
|
|
box.size = size;
|
|
var cp = go.AddComponent<Checkpoint>();
|
|
cp.CheckpointId = id;
|
|
|
|
var marker = CreatePrimitive(PrimitiveType.Cube, name + "_Marker",
|
|
pos + Vector3.up * 0.5f, new Vector3(0.4f, 1.2f, 0.4f),
|
|
new Color(0.2f, 0.7f, 0.9f, 0.7f));
|
|
marker.transform.SetParent(go.transform);
|
|
Object.Destroy(marker.GetComponent<Collider>());
|
|
cp.SpawnPoint = go.transform;
|
|
}
|
|
|
|
static void BuildStoryTriggers()
|
|
{
|
|
CreateStory("Story_Fall", new Vector3(0f, 2f, 8f), new Vector3(6f, 3f, 3f),
|
|
"«Падение»\nЗемля приняла его мягко, будто лес давно ждал чёрного кота.", 5f);
|
|
CreateStory("Story_Road", new Vector3(0f, 2f, 28f), new Vector3(5f, 3f, 3f),
|
|
"«Путь в деревню»\nТропа узкая, но шаг за шагом — и чужой шум становится тише.", 5f);
|
|
CreateStory("Story_Source", new Vector3(0f, 3.5f, 74f), new Vector3(6f, 3f, 3f),
|
|
"«Внутренний источник»\nСвет не нужно искать в небе — он уже греет изнутри.", 5f);
|
|
}
|
|
|
|
static void CreateStory(string name, Vector3 pos, Vector3 size, string quote, float secs)
|
|
{
|
|
var go = new GameObject(name);
|
|
go.transform.SetParent(_root);
|
|
go.transform.position = pos;
|
|
var box = go.AddComponent<BoxCollider>();
|
|
box.isTrigger = true;
|
|
box.size = size;
|
|
var st = go.AddComponent<StoryTrigger>();
|
|
st.Quote = quote;
|
|
st.DisplaySeconds = secs;
|
|
}
|
|
|
|
static void BuildAbilityPedestal()
|
|
{
|
|
var pedestal = CreatePrimitive(PrimitiveType.Cylinder, "LightPedestal",
|
|
new Vector3(0f, 1.2f, 78f), new Vector3(1.4f, 0.6f, 1.4f),
|
|
new Color(0.55f, 0.5f, 0.75f));
|
|
pedestal.transform.SetParent(_root);
|
|
|
|
var orb = CreatePrimitive(PrimitiveType.Sphere, "LightOrb",
|
|
new Vector3(0f, 2.4f, 78f), Vector3.one * 0.7f,
|
|
new Color(1f, 0.95f, 0.55f));
|
|
orb.transform.SetParent(pedestal.transform);
|
|
Object.Destroy(orb.GetComponent<Collider>());
|
|
|
|
var trigger = new GameObject("AbilityUnlock");
|
|
trigger.transform.SetParent(_root);
|
|
trigger.transform.position = new Vector3(0f, 1.5f, 78f);
|
|
var col = trigger.AddComponent<BoxCollider>();
|
|
col.isTrigger = true;
|
|
col.size = new Vector3(3.5f, 3f, 3.5f);
|
|
trigger.AddComponent<AbilityUnlockPedestal>();
|
|
}
|
|
|
|
static void BuildVillageGate()
|
|
{
|
|
var posts = CreateBox("GatePosts", new Vector3(0f, 3f, 116f), new Vector3(8f, 6f, 1.2f),
|
|
new Color(0.35f, 0.28f, 0.22f));
|
|
// Hollow-ish: leave a door slab that moves
|
|
var door = CreateBox("GateDoor", new Vector3(0f, 2.5f, 116.2f), new Vector3(3.2f, 4.5f, 0.6f),
|
|
new Color(0.15f, 0.12f, 0.2f));
|
|
var gate = door.AddComponent<LightGate>();
|
|
gate.GateVisual = door.transform;
|
|
|
|
// Invisible hit proxy in front
|
|
var hit = new GameObject("GateLightTarget");
|
|
hit.transform.SetParent(_root);
|
|
hit.transform.position = new Vector3(0f, 2.5f, 115.5f);
|
|
var hc = hit.AddComponent<BoxCollider>();
|
|
hc.size = new Vector3(3.5f, 4.5f, 1.5f);
|
|
// forward hits to LightGate via child relationship
|
|
hit.transform.SetParent(door.transform);
|
|
// LightGate is on door; GetComponentInParent will find it
|
|
}
|
|
|
|
static GameObject BuildPlayer(Vector3 pos)
|
|
{
|
|
// Black capsule named Kirby
|
|
var body = CreatePrimitive(PrimitiveType.Capsule, "Kirby", pos, new Vector3(0.9f, 1f, 0.9f),
|
|
new Color(0.05f, 0.05f, 0.08f));
|
|
body.tag = "Player";
|
|
Object.Destroy(body.GetComponent<Collider>());
|
|
|
|
var cc = body.AddComponent<CharacterController>();
|
|
cc.height = 2f;
|
|
cc.radius = 0.45f;
|
|
cc.center = new Vector3(0f, 0f, 0f);
|
|
cc.skinWidth = 0.08f;
|
|
|
|
var yaw = new GameObject("YawPivot");
|
|
yaw.transform.SetParent(body.transform);
|
|
yaw.transform.localPosition = Vector3.zero;
|
|
|
|
var motor = body.AddComponent<PlayerMotor>();
|
|
motor.YawPivot = yaw.transform;
|
|
|
|
body.AddComponent<LightAbility>();
|
|
|
|
return body;
|
|
}
|
|
|
|
static void BuildCamera(GameObject player)
|
|
{
|
|
Camera cam = Camera.main;
|
|
if (cam == null)
|
|
{
|
|
var camGo = new GameObject("Main Camera");
|
|
cam = camGo.AddComponent<Camera>();
|
|
cam.tag = "MainCamera";
|
|
camGo.AddComponent<AudioListener>();
|
|
}
|
|
|
|
var follow = cam.GetComponent<SimpleCameraFollow>();
|
|
if (follow == null)
|
|
follow = cam.gameObject.AddComponent<SimpleCameraFollow>();
|
|
|
|
var motor = player.GetComponent<PlayerMotor>();
|
|
Transform yaw = player.transform.Find("YawPivot");
|
|
follow.Target = player.transform;
|
|
follow.YawPivot = yaw;
|
|
motor.CameraTransform = cam.transform;
|
|
motor.YawPivot = yaw;
|
|
|
|
cam.transform.position = player.transform.position + new Vector3(0f, 2.5f, -5.5f);
|
|
cam.transform.LookAt(player.transform.position + Vector3.up * 1.2f);
|
|
}
|
|
|
|
static void BuildKillPlane(Transform player)
|
|
{
|
|
var go = new GameObject("FallKillPlane");
|
|
go.transform.SetParent(_root);
|
|
var kill = go.AddComponent<FallKillPlane>();
|
|
kill.KillY = -10f;
|
|
kill.Bind(player);
|
|
}
|
|
|
|
static GameObject CreateBox(string name, Vector3 pos, Vector3 scale, Color color)
|
|
{
|
|
var go = CreatePrimitive(PrimitiveType.Cube, name, pos, scale, color);
|
|
go.transform.SetParent(_root);
|
|
return go;
|
|
}
|
|
|
|
static GameObject CreatePrimitive(PrimitiveType type, string name, Vector3 pos, Vector3 scale, Color color)
|
|
{
|
|
var go = GameObject.CreatePrimitive(type);
|
|
go.name = name;
|
|
go.transform.position = pos;
|
|
go.transform.localScale = scale;
|
|
var rend = go.GetComponent<Renderer>();
|
|
if (rend != null)
|
|
{
|
|
// URP Lit may exist after package import; fallback to Standard / Unlit
|
|
var shader = Shader.Find("Universal Render Pipeline/Lit");
|
|
if (shader == null) shader = Shader.Find("Standard");
|
|
if (shader == null) shader = Shader.Find("Sprites/Default");
|
|
if (shader != null)
|
|
{
|
|
var mat = new Material(shader);
|
|
if (mat.HasProperty("_BaseColor"))
|
|
mat.SetColor("_BaseColor", color);
|
|
else if (mat.HasProperty("_Color"))
|
|
mat.SetColor("_Color", color);
|
|
else
|
|
mat.color = color;
|
|
rend.material = mat;
|
|
}
|
|
else
|
|
{
|
|
rend.material.color = color;
|
|
}
|
|
}
|
|
return go;
|
|
}
|
|
}
|
|
}
|