Add practice range: shooting, AK47, intro cinematic, dressed warehouse scene
- PortalTestDestination practice range: Valorant-style hitscan rifle (recoil, spray/bloom, reload, controller support), debug HUD, practice-dummy targets with head/body hitboxes, hit/death/respawn animations, procedural SFX/FX - AK47 model (Quaternius, CC0) held via Animator IK, with fire/reload/equip anims - Intro cinematic (camera shots, title card, target beam-in, GO hand-off) - Scene dressing editor pass: CC0 textures, props, warehouse roof, lighting and post-processing; custom stylized skybox shader - CharacterMovement: camera recoil, shoulder offset, cinematic control lock - Unity CLI pipeline package; plus in-progress chat/Rive/player changes Asset credits: Assets/Models/Props/PROPS_CREDITS.txt (3 CC-BY 3.0 props), Assets/Textures/Range/TEXTURES_LICENSE.txt, Assets/Resources/*/..._LICENSE.txt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c674d76549
commit
5fc9ebf1e6
@@ -0,0 +1,224 @@
|
||||
using Mirror;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace ProceduralEditor
|
||||
{
|
||||
// Rebuilds PortalTestDestination.unity as a small greybox shooter-map prototype:
|
||||
// a walled arena with a central street flanked by two rows of walkable buildings,
|
||||
// in the style of a tactical-shooter blockout pass.
|
||||
public static class SetupPortalTestDestinationScene
|
||||
{
|
||||
const string ScenePath = "Assets/Scenes/PortalTestDestination.unity";
|
||||
const string MaterialsFolder = "Assets/Materials";
|
||||
const string SkyMaterialPath = "Assets/Materials/RangeSky.mat";
|
||||
|
||||
static readonly Color FloorColor = new Color(0.55f, 0.55f, 0.55f);
|
||||
static readonly Color WallColor = new Color(0.28f, 0.28f, 0.3f);
|
||||
static readonly Color BoxColor = new Color(0.76f, 0.58f, 0.34f);
|
||||
|
||||
[MenuItem("Tools/Scene/Setup Portal Test Destination Shooter Map")]
|
||||
public static void Setup()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
Debug.LogError("Setup Portal Test Destination: exit Play Mode first — EditorSceneManager.OpenScene() can't run during Play Mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
||||
|
||||
// Clear out the old studio/pedestal content (Geometry, Lighting rig, post-process
|
||||
// volume, probe volume data, static lighting sky) but keep Main Camera and reconfigure it.
|
||||
foreach (var existingRoot in scene.GetRootGameObjects())
|
||||
{
|
||||
if (existingRoot.name == "Main Camera") continue;
|
||||
Object.DestroyImmediate(existingRoot);
|
||||
}
|
||||
|
||||
var floorMat = GetOrCreateMaterial("ShooterProto_Floor", FloorColor);
|
||||
var wallMat = GetOrCreateMaterial("ShooterProto_Wall", WallColor);
|
||||
var boxMat = GetOrCreateMaterial("ShooterProto_Box", BoxColor);
|
||||
|
||||
SetupCamera();
|
||||
SetupLighting();
|
||||
|
||||
var root = new GameObject("ShooterMapPrototype");
|
||||
|
||||
CreateBlock(root.transform, "Floor", new Vector3(0, -0.25f, 0), new Vector3(44, 0.5f, 34), floorMat);
|
||||
|
||||
// Perimeter walls (floor spans x:[-22,22], z:[-17,17])
|
||||
CreateBlock(root.transform, "Wall_North", new Vector3(0, 2, 17.5f), new Vector3(46, 4, 1), wallMat);
|
||||
CreateBlock(root.transform, "Wall_South", new Vector3(0, 2, -17.5f), new Vector3(46, 4, 1), wallMat);
|
||||
CreateBlock(root.transform, "Wall_East", new Vector3(22.5f, 2, 0), new Vector3(1, 4, 34), wallMat);
|
||||
CreateBlock(root.transform, "Wall_West", new Vector3(-22.5f, 2, 0), new Vector3(1, 4, 34), wallMat);
|
||||
|
||||
var buildingsRoot = new GameObject("Buildings").transform;
|
||||
buildingsRoot.SetParent(root.transform);
|
||||
|
||||
// A central street (x: -4..4) runs the full length of the map between two spawns.
|
||||
// Three buildings line each side of it, each walkable with a door facing the street
|
||||
// and a door facing the back alley, so players can cut through or around them.
|
||||
const float buildingWidth = 6f;
|
||||
const float buildingDepth = 6f;
|
||||
const float wallHeight = 3f;
|
||||
const float wallThickness = 0.5f;
|
||||
const float doorWidth = 2.2f;
|
||||
|
||||
float[] rowZ = { -9f, 0f, 9f };
|
||||
for (int i = 0; i < rowZ.Length; i++)
|
||||
{
|
||||
// Left row: door faces east (street), back door faces west (outer alley)
|
||||
CreateBuilding(buildingsRoot, $"Building_L{i + 1}", new Vector3(-13, 0, rowZ[i]),
|
||||
buildingWidth, buildingDepth, wallHeight, wallThickness,
|
||||
doorEast: true, doorWest: true, doorWidth, wallMat);
|
||||
|
||||
// Right row: mirrored
|
||||
CreateBuilding(buildingsRoot, $"Building_R{i + 1}", new Vector3(13, 0, rowZ[i]),
|
||||
buildingWidth, buildingDepth, wallHeight, wallThickness,
|
||||
doorEast: true, doorWest: true, doorWidth, wallMat);
|
||||
}
|
||||
|
||||
// A couple of symmetric crates in the open street for players crossing between
|
||||
// the two building rows to duck behind.
|
||||
var streetCoverRoot = new GameObject("StreetCover").transform;
|
||||
streetCoverRoot.SetParent(root.transform);
|
||||
CreateBlock(streetCoverRoot, "StreetCover_1", new Vector3(-2, 1, -4), new Vector3(2, 2, 2), boxMat);
|
||||
CreateBlock(streetCoverRoot, "StreetCover_2", new Vector3(2, 1, 4), new Vector3(2, 2, 2), boxMat);
|
||||
|
||||
SetupSpawnPoint("SpawnPoint_A", new Vector3(0, 0, -14));
|
||||
SetupSpawnPoint("SpawnPoint_B", new Vector3(0, 0, 14));
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
Debug.Log("Portal Test Destination rebuilt as a shooter-map prototype. Review it in the Scene view, then save (Ctrl+S).");
|
||||
}
|
||||
|
||||
static void SetupCamera()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam == null)
|
||||
{
|
||||
var go = new GameObject("Main Camera", typeof(Camera), typeof(AudioListener));
|
||||
go.tag = "MainCamera";
|
||||
cam = go.GetComponent<Camera>();
|
||||
}
|
||||
|
||||
// Overview framing for the editor / before a local player's camera gets reparented
|
||||
// onto them at runtime (see PlayerScript.OnStartLocalPlayer).
|
||||
cam.transform.position = new Vector3(0, 14, -26);
|
||||
cam.transform.rotation = Quaternion.Euler(24f, 0f, 0f);
|
||||
cam.fieldOfView = 60f;
|
||||
cam.clearFlags = CameraClearFlags.Skybox;
|
||||
cam.nearClipPlane = 0.1f;
|
||||
cam.farClipPlane = 200f;
|
||||
|
||||
if (cam.GetComponent<AudioListener>() == null)
|
||||
cam.gameObject.AddComponent<AudioListener>();
|
||||
}
|
||||
|
||||
static void SetupLighting()
|
||||
{
|
||||
// Low dusk sun in the north-west; the RangeSky skybox draws its disc from this light.
|
||||
var go = new GameObject("Directional Light", typeof(Light));
|
||||
var light = go.GetComponent<Light>();
|
||||
light.type = LightType.Directional;
|
||||
go.transform.rotation = Quaternion.Euler(20f, 120f, 0f);
|
||||
light.color = new Color(1f, 0.82f, 0.62f);
|
||||
light.intensity = 1.3f;
|
||||
light.shadows = LightShadows.Soft;
|
||||
RenderSettings.sun = light;
|
||||
|
||||
var sky = AssetDatabase.LoadAssetAtPath<Material>(SkyMaterialPath);
|
||||
if (sky != null) RenderSettings.skybox = sky;
|
||||
else Debug.LogWarning($"Setup Portal Test Destination: {SkyMaterialPath} not found; keeping the current skybox.");
|
||||
|
||||
// Ambient and fog pick up the sky's palette (indigo above, warm horizon).
|
||||
RenderSettings.ambientMode = AmbientMode.Trilight;
|
||||
RenderSettings.ambientSkyColor = new Color(0.42f, 0.40f, 0.62f);
|
||||
RenderSettings.ambientEquatorColor = new Color(0.62f, 0.45f, 0.42f);
|
||||
RenderSettings.ambientGroundColor = new Color(0.16f, 0.14f, 0.18f);
|
||||
RenderSettings.fog = true;
|
||||
RenderSettings.fogMode = FogMode.Linear;
|
||||
RenderSettings.fogColor = new Color(0.55f, 0.42f, 0.48f);
|
||||
RenderSettings.fogStartDistance = 30f;
|
||||
RenderSettings.fogEndDistance = 110f;
|
||||
DynamicGI.UpdateEnvironment();
|
||||
}
|
||||
|
||||
static void SetupSpawnPoint(string name, Vector3 position)
|
||||
{
|
||||
var go = new GameObject(name, typeof(NetworkStartPosition));
|
||||
go.transform.position = position;
|
||||
}
|
||||
|
||||
// Builds a walkable, roofless room: solid North/South walls and East/West walls that
|
||||
// can each optionally have a centered door gap.
|
||||
static void CreateBuilding(Transform parent, string name, Vector3 center, float width, float depth,
|
||||
float wallHeight, float wallThickness, bool doorEast, bool doorWest, float doorWidth, Material material)
|
||||
{
|
||||
var buildingRoot = new GameObject(name).transform;
|
||||
buildingRoot.SetParent(parent, false);
|
||||
buildingRoot.localPosition = center;
|
||||
|
||||
float halfWidth = width / 2f;
|
||||
float halfDepth = depth / 2f;
|
||||
float wallY = wallHeight / 2f;
|
||||
|
||||
// North/South walls run along X; extend by wallThickness so they close the corners
|
||||
// against the East/West walls instead of leaving a gap.
|
||||
CreateBlock(buildingRoot, "Wall_N", new Vector3(0, wallY, halfDepth), new Vector3(width + wallThickness, wallHeight, wallThickness), material);
|
||||
CreateBlock(buildingRoot, "Wall_S", new Vector3(0, wallY, -halfDepth), new Vector3(width + wallThickness, wallHeight, wallThickness), material);
|
||||
|
||||
CreateSideWall(buildingRoot, "Wall_E", new Vector3(halfWidth, wallY, 0), depth, wallThickness, wallHeight, doorEast, doorWidth, material);
|
||||
CreateSideWall(buildingRoot, "Wall_W", new Vector3(-halfWidth, wallY, 0), depth, wallThickness, wallHeight, doorWest, doorWidth, material);
|
||||
}
|
||||
|
||||
// An East/West wall runs along Z; a door splits it into two segments with a gap at the center.
|
||||
static void CreateSideWall(Transform parent, string name, Vector3 center, float depth, float thickness,
|
||||
float height, bool hasDoor, float doorWidth, Material material)
|
||||
{
|
||||
if (!hasDoor)
|
||||
{
|
||||
CreateBlock(parent, name, center, new Vector3(thickness, height, depth), material);
|
||||
return;
|
||||
}
|
||||
|
||||
float segmentLength = (depth - doorWidth) / 2f;
|
||||
float segmentOffset = doorWidth / 2f + segmentLength / 2f;
|
||||
CreateBlock(parent, name + "_A", center + new Vector3(0, 0, -segmentOffset), new Vector3(thickness, height, segmentLength), material);
|
||||
CreateBlock(parent, name + "_B", center + new Vector3(0, 0, segmentOffset), new Vector3(thickness, height, segmentLength), material);
|
||||
}
|
||||
|
||||
static void CreateBlock(Transform parent, string name, Vector3 position, Vector3 size, Material material)
|
||||
{
|
||||
var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
go.name = name;
|
||||
go.transform.SetParent(parent, false);
|
||||
go.transform.localPosition = position;
|
||||
go.transform.localScale = size;
|
||||
go.GetComponent<MeshRenderer>().sharedMaterial = material;
|
||||
}
|
||||
|
||||
static Material GetOrCreateMaterial(string name, Color color)
|
||||
{
|
||||
string path = $"{MaterialsFolder}/{name}.mat";
|
||||
var existing = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.color = color;
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (!AssetDatabase.IsValidFolder(MaterialsFolder))
|
||||
AssetDatabase.CreateFolder("Assets", "Materials");
|
||||
|
||||
var shader = Shader.Find("Universal Render Pipeline/Lit");
|
||||
var mat = new Material(shader) { color = color };
|
||||
AssetDatabase.CreateAsset(mat, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user