Files
OTG_2/Assets/Editor/Lab2SceneBuilder.cs
2026-06-04 17:37:58 +03:00

565 lines
24 KiB
C#

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public static class Lab2SceneBuilder
{
private const string ScriptsFolder = "Assets/_Scripts";
private const string DataFolder = "Assets/_Data";
private const string PrefabsFolder = "Assets/_Prefabs";
private const string ScenesFolder = "Assets/_Scenes";
private const string MaterialsFolder = "Assets/_Materials";
private const string FontsFolder = "Assets/_Fonts";
private const string EditorFolder = "Assets/Editor";
private const string ScenePath = "Assets/_Scenes/WeaponTest.unity";
[MenuItem("Tools/Technical Game Design/Lab2/Create Weapon System Scene")]
public static void CreateWeaponSystemScene()
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
EditorUtility.DisplayDialog("Lab 2", "Stop Play Mode before generating the scene.", "OK");
return;
}
CreateFolders();
Font uiFont = CreateOrLoadUbuntuFont();
Material floorMaterial = CreateMaterial("Floor_Mat", new Color(0.35f, 0.38f, 0.34f));
Material wallMaterial = CreateMaterial("Wall_Mat", new Color(0.48f, 0.50f, 0.52f));
Material targetMaterial = CreateMaterial("Target_Mat", new Color(0.85f, 0.15f, 0.12f));
Material pistolMaterial = CreateMaterial("Pistol_Mat", new Color(0.05f, 0.11f, 0.25f));
Material rifleMaterial = CreateMaterial("Rifle_Mat", new Color(0.20f, 0.35f, 0.27f));
Material shotgunMaterial = CreateMaterial("Shotgun_Mat", new Color(0.45f, 0.18f, 0.10f));
Material smgMaterial = CreateMaterial("SMG_Mat", new Color(0.35f, 0.35f, 0.38f));
Material muzzleMaterial = CreateMaterial("MuzzleFlash_Mat", new Color(1f, 0.62f, 0.08f));
Material hitMaterial = CreateMaterial("HitEffect_Mat", new Color(1f, 0.95f, 0.25f));
GameObject muzzleFlashPrefab = CreateEffectPrefab("MuzzleFlashPrefab", muzzleMaterial, new Vector3(0.16f, 0.16f, 0.16f));
GameObject hitEffectPrefab = CreateEffectPrefab("HitEffectPrefab", hitMaterial, new Vector3(0.12f, 0.12f, 0.12f));
WeaponData pistolData = CreateWeaponData(
"PistolData",
"Pistol",
15f,
0.25f,
8,
40,
0.02f,
1.2f,
1.5f,
1,
80f,
false,
pistolMaterial.color,
"Accurate semi-automatic weapon for medium range.",
muzzleFlashPrefab,
hitEffectPrefab);
WeaponData rifleData = CreateWeaponData(
"RifleData",
"Rifle",
10f,
0.1f,
30,
90,
0.06f,
2f,
2f,
1,
100f,
true,
rifleMaterial.color,
"Automatic rifle with high fire rate and moderate spread.",
muzzleFlashPrefab,
hitEffectPrefab);
WeaponData shotgunData = CreateWeaponData(
"ShotgunData",
"Shotgun",
8f,
0.8f,
5,
25,
0.18f,
4f,
2.5f,
8,
45f,
false,
shotgunMaterial.color,
"Close-range shotgun that fires several pellets per shot.",
muzzleFlashPrefab,
hitEffectPrefab);
CreateWeaponData(
"SMGData",
"SMG",
7f,
0.075f,
24,
120,
0.08f,
1.6f,
1.8f,
1,
75f,
true,
smgMaterial.color,
"Additional data-only weapon example. It is not active in the scene.",
muzzleFlashPrefab,
hitEffectPrefab);
CreateWeaponPrefabTemplate("Pistol", pistolData, pistolMaterial);
CreateWeaponPrefabTemplate("Rifle", rifleData, rifleMaterial);
CreateWeaponPrefabTemplate("Shotgun", shotgunData, shotgunMaterial);
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
Scene scene = SceneManager.GetActiveScene();
CreateLighting();
CreateEnvironment(floorMaterial, wallMaterial);
WeaponHUD hud = CreateUI(uiFont, out Text ammoText, out Text weaponNameText, out Text reloadText);
GameObject player = CreatePlayer(out Camera playerCamera, out Transform weaponHolder);
Weapon pistol = CreateSceneWeapon("Pistol", pistolData, pistolMaterial, weaponHolder, playerCamera, hud, ammoText, weaponNameText, reloadText, new Vector3(0f, 0f, 0f));
Weapon rifle = CreateSceneWeapon("Rifle", rifleData, rifleMaterial, weaponHolder, playerCamera, hud, ammoText, weaponNameText, reloadText, new Vector3(0f, -0.02f, 0.02f));
Weapon shotgun = CreateSceneWeapon("Shotgun", shotgunData, shotgunMaterial, weaponHolder, playerCamera, hud, ammoText, weaponNameText, reloadText, new Vector3(0f, -0.02f, 0.02f));
WeaponSwitcher switcher = player.AddComponent<WeaponSwitcher>();
switcher.weapons = new[] { pistol, rifle, shotgun };
switcher.currentIndex = 0;
switcher.weaponNameText = weaponNameText;
switcher.hud = hud;
pistol.gameObject.SetActive(true);
rifle.gameObject.SetActive(false);
shotgun.gameObject.SetActive(false);
CreateTargets(targetMaterial, uiFont);
EditorSceneManager.SaveScene(scene, ScenePath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
string resultMessage = "Weapon system scene was generated: " + ScenePath;
if (Application.isBatchMode)
Debug.Log(resultMessage);
else
EditorUtility.DisplayDialog("Lab 2", resultMessage, "OK");
}
private static void CreateFolders()
{
EnsureFolder(ScriptsFolder);
EnsureFolder(DataFolder);
EnsureFolder(PrefabsFolder);
EnsureFolder(ScenesFolder);
EnsureFolder(MaterialsFolder);
EnsureFolder(FontsFolder);
EnsureFolder(EditorFolder);
}
private static void EnsureFolder(string path)
{
if (AssetDatabase.IsValidFolder(path))
return;
string parent = System.IO.Path.GetDirectoryName(path)?.Replace("\\", "/");
string name = System.IO.Path.GetFileName(path);
if (string.IsNullOrEmpty(parent))
parent = "Assets";
AssetDatabase.CreateFolder(parent, name);
}
private static Material CreateMaterial(string assetName, Color color)
{
string path = MaterialsFolder + "/" + assetName + ".mat";
Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material == null)
{
Shader shader = Shader.Find("Standard");
material = new Material(shader);
AssetDatabase.CreateAsset(material, path);
}
material.color = color;
EditorUtility.SetDirty(material);
return material;
}
private static Font CreateOrLoadUbuntuFont()
{
const string fontAssetPath = FontsFolder + "/Ubuntu-Regular.ttf";
Font projectFont = AssetDatabase.LoadAssetAtPath<Font>(fontAssetPath);
if (projectFont != null)
return projectFont;
string[] ubuntuFontCandidates =
{
"/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf",
"/usr/share/fonts/truetype/ubuntu/Ubuntu-Regular.ttf",
"/usr/share/fonts/TTF/Ubuntu-R.ttf",
"/home/ruby/.local/share/fonts/Ubuntu-R.ttf",
"/home/ruby/.local/share/fonts/Ubuntu-Regular.ttf"
};
foreach (string sourcePath in ubuntuFontCandidates)
{
if (!System.IO.File.Exists(sourcePath))
continue;
FileUtil.CopyFileOrDirectory(sourcePath, fontAssetPath);
AssetDatabase.ImportAsset(fontAssetPath, ImportAssetOptions.ForceUpdate);
projectFont = AssetDatabase.LoadAssetAtPath<Font>(fontAssetPath);
if (projectFont != null)
return projectFont;
}
Font osUbuntuFont = Font.CreateDynamicFontFromOSFont("Ubuntu", 18);
if (osUbuntuFont != null)
return osUbuntuFont;
return Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
}
private static GameObject CreateEffectPrefab(string name, Material material, Vector3 scale)
{
string path = PrefabsFolder + "/" + name + ".prefab";
GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Sphere);
temp.name = name;
temp.transform.localScale = scale;
Collider collider = temp.GetComponent<Collider>();
if (collider != null)
Object.DestroyImmediate(collider);
Renderer renderer = temp.GetComponent<Renderer>();
renderer.sharedMaterial = material;
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(temp, path);
Object.DestroyImmediate(temp);
return prefab;
}
private static WeaponData CreateWeaponData(
string assetName,
string weaponName,
float damage,
float fireRate,
int magazineSize,
int reserveAmmo,
float spread,
float recoilForce,
float reloadTime,
int pelletsPerShot,
float range,
bool automatic,
Color weaponColor,
string description,
GameObject muzzleFlashPrefab,
GameObject hitEffectPrefab)
{
string path = DataFolder + "/" + assetName + ".asset";
WeaponData data = AssetDatabase.LoadAssetAtPath<WeaponData>(path);
if (data == null)
{
data = ScriptableObject.CreateInstance<WeaponData>();
AssetDatabase.CreateAsset(data, path);
}
data.weaponName = weaponName;
data.damage = damage;
data.fireRate = fireRate;
data.magazineSize = magazineSize;
data.totalReserveAmmo = reserveAmmo;
data.spread = spread;
data.recoilForce = recoilForce;
data.reloadTime = reloadTime;
data.pelletsPerShot = pelletsPerShot;
data.range = range;
data.automatic = automatic;
data.weaponColor = weaponColor;
data.description = description;
data.muzzleFlashPrefab = muzzleFlashPrefab;
data.hitEffectPrefab = hitEffectPrefab;
EditorUtility.SetDirty(data);
return data;
}
private static void CreateWeaponPrefabTemplate(string name, WeaponData data, Material material)
{
GameObject root = BuildWeaponModel(name, data, material);
Weapon weapon = root.AddComponent<Weapon>();
weapon.data = data;
weapon.firePoint = root.transform.Find("FirePoint");
PrefabUtility.SaveAsPrefabAsset(root, PrefabsFolder + "/" + name + ".prefab");
Object.DestroyImmediate(root);
}
private static void CreateLighting()
{
GameObject lightObject = new GameObject("Directional Light");
Light light = lightObject.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 1.2f;
lightObject.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
RenderSettings.ambientLight = new Color(0.45f, 0.45f, 0.45f);
}
private static void CreateEnvironment(Material floorMaterial, Material wallMaterial)
{
GameObject floor = GameObject.CreatePrimitive(PrimitiveType.Plane);
floor.name = "Floor";
floor.transform.localScale = new Vector3(4f, 1f, 4f);
floor.GetComponent<Renderer>().sharedMaterial = floorMaterial;
CreateWall("Back Wall", new Vector3(0f, 2f, 18f), new Vector3(22f, 4f, 0.35f), wallMaterial);
CreateWall("Left Wall", new Vector3(-11f, 2f, 6f), new Vector3(0.35f, 4f, 24f), wallMaterial);
CreateWall("Right Wall", new Vector3(11f, 2f, 6f), new Vector3(0.35f, 4f, 24f), wallMaterial);
CreateWall("Low Cover", new Vector3(0f, 0.75f, 7f), new Vector3(4f, 1.5f, 0.4f), wallMaterial);
}
private static void CreateWall(string name, Vector3 position, Vector3 scale, Material material)
{
GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
wall.name = name;
wall.transform.position = position;
wall.transform.localScale = scale;
wall.GetComponent<Renderer>().sharedMaterial = material;
}
private static GameObject CreatePlayer(out Camera playerCamera, out Transform weaponHolder)
{
GameObject player = new GameObject("Player");
player.transform.position = new Vector3(0f, 1.1f, -6f);
CharacterController controller = player.AddComponent<CharacterController>();
controller.height = 1.8f;
controller.radius = 0.35f;
controller.center = new Vector3(0f, 0.9f, 0f);
SimpleFPSController fpsController = player.AddComponent<SimpleFPSController>();
GameObject cameraObject = new GameObject("Main Camera");
cameraObject.tag = "MainCamera";
cameraObject.transform.SetParent(player.transform);
cameraObject.transform.localPosition = new Vector3(0f, 1.6f, 0f);
cameraObject.transform.localRotation = Quaternion.identity;
playerCamera = cameraObject.AddComponent<Camera>();
playerCamera.nearClipPlane = 0.01f;
playerCamera.fieldOfView = 70f;
cameraObject.AddComponent<AudioListener>();
fpsController.playerCamera = playerCamera;
GameObject holder = new GameObject("WeaponHolder");
holder.transform.SetParent(cameraObject.transform);
holder.transform.localPosition = new Vector3(0.38f, -0.34f, 0.72f);
holder.transform.localRotation = Quaternion.Euler(0f, -2f, 0f);
weaponHolder = holder.transform;
return player;
}
private static WeaponHUD CreateUI(Font uiFont, out Text ammoText, out Text weaponNameText, out Text reloadText)
{
GameObject canvasObject = new GameObject("HUD Canvas");
Canvas canvas = canvasObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
CanvasScaler scaler = canvasObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.matchWidthOrHeight = 0.5f;
canvasObject.AddComponent<GraphicRaycaster>();
WeaponHUD hud = canvasObject.AddComponent<WeaponHUD>();
hud.labTitleText = CreateText(canvasObject.transform, "Lab Title", "Лабораторная работа №2 — Data-Driven система оружия", 30, TextAnchor.UpperLeft, new Vector2(24f, -18f), new Vector2(760f, 48f), new Vector2(0f, 1f), new Vector2(0f, 1f), uiFont);
weaponNameText = CreateText(canvasObject.transform, "Weapon Name", "Weapon: Pistol", 26, TextAnchor.UpperLeft, new Vector2(24f, -78f), new Vector2(360f, 38f), new Vector2(0f, 1f), new Vector2(0f, 1f), uiFont);
ammoText = CreateText(canvasObject.transform, "Ammo", "Ammo: 8 / 40", 26, TextAnchor.UpperLeft, new Vector2(24f, -118f), new Vector2(320f, 38f), new Vector2(0f, 1f), new Vector2(0f, 1f), uiFont);
reloadText = CreateText(canvasObject.transform, "Reload Status", "Ready", 24, TextAnchor.UpperLeft, new Vector2(24f, -158f), new Vector2(320f, 34f), new Vector2(0f, 1f), new Vector2(0f, 1f), uiFont);
hud.hintsText = CreateText(canvasObject.transform, "Controls Hint", "ЛКМ — стрелять\nR — перезарядка\n1/2/3 или колесо мыши — смена оружия\nWASD + мышь — движение", 21, TextAnchor.UpperLeft, new Vector2(24f, 152f), new Vector2(520f, 150f), new Vector2(0f, 0f), new Vector2(0f, 0f), uiFont);
hud.statsText = CreateText(canvasObject.transform, "Weapon Stats", string.Empty, 22, TextAnchor.UpperRight, new Vector2(-24f, -78f), new Vector2(330f, 190f), new Vector2(1f, 1f), new Vector2(1f, 1f), uiFont);
hud.messageText = CreateText(canvasObject.transform, "Message", string.Empty, 24, TextAnchor.MiddleCenter, new Vector2(0f, -210f), new Vector2(560f, 44f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), uiFont);
hud.dataDrivenText = CreateText(canvasObject.transform, "Data Driven Note", "Параметры оружия хранятся в ScriptableObject-ассетах в папке Assets/_Data. Для добавления нового оружия достаточно создать новый WeaponData и назначить его объекту Weapon.", 19, TextAnchor.LowerRight, new Vector2(-24f, 22f), new Vector2(720f, 82f), new Vector2(1f, 0f), new Vector2(1f, 0f), uiFont);
hud.weaponNameText = weaponNameText;
hud.ammoText = ammoText;
hud.reloadText = reloadText;
hud.SetReloading(false);
return hud;
}
private static Text CreateText(Transform parent, string name, string value, int fontSize, TextAnchor alignment, Vector2 anchoredPosition, Vector2 size, Vector2 anchorMin, Vector2 anchorMax, Font uiFont)
{
GameObject textObject = new GameObject(name);
textObject.transform.SetParent(parent, false);
RectTransform rect = textObject.AddComponent<RectTransform>();
rect.anchorMin = anchorMin;
rect.anchorMax = anchorMax;
rect.pivot = new Vector2(anchorMin.x == anchorMax.x ? anchorMin.x : 0.5f, anchorMin.y == anchorMax.y ? anchorMin.y : 0.5f);
rect.anchoredPosition = anchoredPosition;
rect.sizeDelta = size;
Text text = textObject.AddComponent<Text>();
text.text = value;
text.font = uiFont;
text.fontSize = fontSize;
text.alignment = alignment;
text.color = Color.white;
text.horizontalOverflow = HorizontalWrapMode.Wrap;
text.verticalOverflow = VerticalWrapMode.Overflow;
Outline outline = textObject.AddComponent<Outline>();
outline.effectColor = new Color(0f, 0f, 0f, 0.85f);
outline.effectDistance = new Vector2(1.5f, -1.5f);
return text;
}
private static Weapon CreateSceneWeapon(
string name,
WeaponData data,
Material material,
Transform parent,
Camera playerCamera,
WeaponHUD hud,
Text ammoText,
Text weaponNameText,
Text reloadText,
Vector3 localOffset)
{
GameObject weaponObject = BuildWeaponModel(name, data, material);
weaponObject.transform.SetParent(parent, false);
weaponObject.transform.localPosition = localOffset;
weaponObject.transform.localRotation = Quaternion.identity;
Weapon weapon = weaponObject.AddComponent<Weapon>();
weapon.data = data;
weapon.playerCamera = playerCamera;
weapon.hud = hud;
weapon.ammoText = ammoText;
weapon.weaponNameText = weaponNameText;
weapon.reloadText = reloadText;
weapon.firePoint = weaponObject.transform.Find("FirePoint");
weaponObject.AddComponent<AudioSource>().spatialBlend = 0f;
return weapon;
}
private static GameObject BuildWeaponModel(string name, WeaponData data, Material material)
{
GameObject root = new GameObject(name);
if (name == "Pistol")
{
AddPart(root.transform, "Slide", new Vector3(0f, 0f, 0.18f), new Vector3(0.18f, 0.12f, 0.55f), material);
AddPart(root.transform, "Grip", new Vector3(0f, -0.16f, -0.02f), new Vector3(0.14f, 0.34f, 0.16f), material);
AddPart(root.transform, "Barrel", new Vector3(0f, 0.02f, 0.52f), new Vector3(0.08f, 0.08f, 0.24f), material);
CreateFirePoint(root.transform, new Vector3(0f, 0.02f, 0.66f));
}
else if (name == "Rifle")
{
AddPart(root.transform, "Body", new Vector3(0f, 0f, 0.3f), new Vector3(0.18f, 0.14f, 0.95f), material);
AddPart(root.transform, "Stock", new Vector3(0f, 0f, -0.28f), new Vector3(0.2f, 0.16f, 0.34f), material);
AddPart(root.transform, "Grip", new Vector3(0f, -0.18f, 0.05f), new Vector3(0.12f, 0.34f, 0.14f), material);
AddPart(root.transform, "Barrel", new Vector3(0f, 0.02f, 0.92f), new Vector3(0.07f, 0.07f, 0.45f), material);
CreateFirePoint(root.transform, new Vector3(0f, 0.02f, 1.16f));
}
else
{
AddPart(root.transform, "Wide Body", new Vector3(0f, 0f, 0.25f), new Vector3(0.24f, 0.16f, 0.86f), material);
AddPart(root.transform, "Pump", new Vector3(0f, -0.08f, 0.54f), new Vector3(0.28f, 0.12f, 0.28f), material);
AddPart(root.transform, "Grip", new Vector3(0f, -0.20f, -0.04f), new Vector3(0.13f, 0.36f, 0.16f), material);
AddPart(root.transform, "Short Barrel", new Vector3(0f, 0.04f, 0.88f), new Vector3(0.14f, 0.10f, 0.36f), material);
CreateFirePoint(root.transform, new Vector3(0f, 0.04f, 1.08f));
}
root.transform.localRotation = Quaternion.identity;
return root;
}
private static void AddPart(Transform parent, string name, Vector3 localPosition, Vector3 localScale, Material material)
{
GameObject part = GameObject.CreatePrimitive(PrimitiveType.Cube);
part.name = name;
part.transform.SetParent(parent, false);
part.transform.localPosition = localPosition;
part.transform.localScale = localScale;
Collider collider = part.GetComponent<Collider>();
if (collider != null)
Object.DestroyImmediate(collider);
part.GetComponent<Renderer>().sharedMaterial = material;
}
private static void CreateFirePoint(Transform parent, Vector3 localPosition)
{
GameObject firePoint = new GameObject("FirePoint");
firePoint.transform.SetParent(parent, false);
firePoint.transform.localPosition = localPosition;
firePoint.transform.localRotation = Quaternion.identity;
}
private static void CreateTargets(Material targetMaterial, Font uiFont)
{
List<Vector3> positions = new List<Vector3>
{
new Vector3(-3.5f, 1f, 5f),
new Vector3(0f, 1f, 8f),
new Vector3(3.5f, 1f, 11f),
new Vector3(-5.5f, 1f, 14f),
new Vector3(5.5f, 1f, 16f)
};
for (int i = 0; i < positions.Count; i++)
CreateTarget("EnemyTarget_" + (i + 1), positions[i], targetMaterial, uiFont);
}
private static void CreateTarget(string name, Vector3 position, Material material, Font uiFont)
{
GameObject target = GameObject.CreatePrimitive(PrimitiveType.Cube);
target.name = name;
target.transform.position = position;
target.transform.localScale = new Vector3(1.2f, 2f, 0.35f);
target.GetComponent<Renderer>().sharedMaterial = material;
EnemyTarget enemyTarget = target.AddComponent<EnemyTarget>();
enemyTarget.maxHealth = 100f;
enemyTarget.targetRenderer = target.GetComponent<Renderer>();
GameObject canvasObject = new GameObject("Health Canvas");
canvasObject.transform.SetParent(target.transform, false);
canvasObject.transform.localPosition = new Vector3(0f, 1.25f, 0f);
canvasObject.transform.localScale = Vector3.one * 0.012f;
Canvas canvas = canvasObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
RectTransform canvasRect = canvasObject.GetComponent<RectTransform>();
canvasRect.sizeDelta = new Vector2(220f, 50f);
Text healthText = CreateText(canvasObject.transform, "Health Text", "HP: 100 / 100", 24, TextAnchor.MiddleCenter, Vector2.zero, new Vector2(220f, 50f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), uiFont);
enemyTarget.healthText = healthText;
}
}