diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md index 4877f2f..64af032 100644 --- a/.github/ISSUE_TEMPLATE/task.md +++ b/.github/ISSUE_TEMPLATE/task.md @@ -1,6 +1,6 @@ --- name: Task -about: Small ARPG bakeoff task +about: Small Color-Eater Crown production task title: "" labels: task assignees: "" @@ -16,4 +16,3 @@ assignees: "" - [ ] No existing game identity is copied ## Notes - diff --git a/.github/workflows/repo-check.yml b/.github/workflows/repo-check.yml index 0da9571..d8b8e90 100644 --- a/.github/workflows/repo-check.yml +++ b/.github/workflows/repo-check.yml @@ -26,5 +26,5 @@ jobs: - name: Reject obvious copied-reference wording run: | ! grep -RInE "clone Hades|copy Hades|copy V Rising|copy Diablo|copy Path of Exile|copy Final Fantasy|copy Dragon Quest" . \ - --exclude-dir=.git - + --exclude-dir=.git \ + --exclude=repo-check.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..759ad97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Unity generated folders +/Library/ +/Temp/ +/Logs/ +/UserSettings/ +/MemoryCaptures/ +/Recordings/ + +# Local OS/editor files +.DS_Store +*.swp +*.swo + +# Local analysis outputs +/local-evidence/ + +# Unreal generated folders +/Unreal/**/Binaries/ +/Unreal/**/DerivedDataCache/ +/Unreal/**/Intermediate/ +/Unreal/**/Saved/ +/Unreal/**/.vs/ diff --git a/Assets/Design/AnimationList.md b/Assets/Design/AnimationList.md new file mode 100644 index 0000000..6d6e6b4 --- /dev/null +++ b/Assets/Design/AnimationList.md @@ -0,0 +1,92 @@ +# AnimationList: 夜番の砦 + +Status: active animation checklist for the first vertical slice. + +## Player Core + +- Idle +- Walk +- Run +- Dash +- Jump +- Land +- DodgeStep +- Guard +- Parry +- ParrySuccess +- HitSmall +- HitLarge +- Knockdown +- GetUp +- Death +- Respawn + +## Weapon Handling + +- PickupWeapon +- DropWeapon +- SwapWeapon +- ThrowWeapon +- PullEmbeddedWeapon +- WeaponBreakReaction +- UseItem +- RaiseTorch + +## Weapon Attacks + +- SwordLight1 +- SwordLight2 +- SwordLight3 +- SwordHeavy +- SpearThrust +- SpearHeavyPierce +- SpearThrow +- HammerLight +- HammerHeavySlam +- BowAim +- BowShoot +- TorchSwing +- ShieldGuard +- ShieldBash + +## Enemies + +Each first enemy requires: + +- Idle +- Patrol +- Notice +- Approach +- Attack +- HeavyAttack or SpecialAttack +- HitSmall +- HitLarge +- PostureBreak +- WeaponDrop +- Death + +## Garm + +- EntranceHowl +- PhaseOneIdle +- Walk +- Bite +- Leap +- Charge +- TailSweep +- Roar +- LegSpearEmbedded +- HeadStagger +- TailBreak +- PhaseTransition +- PhaseTwoChainAttack +- WeakPointOpenedByThrow +- Defeat + +## Quality Notes + +- Weapon pickup/swap must be short and readable. +- Heavy weapon motion must show weight. +- Dagger/sword/spear/hammer cannot share the same body timing. +- Embedded weapon pull needs a satisfying tug and enemy reaction. +- Feet must not slide during attacks or dodges. diff --git a/Assets/Design/ArtBible.md b/Assets/Design/ArtBible.md new file mode 100644 index 0000000..1de4a73 --- /dev/null +++ b/Assets/Design/ArtBible.md @@ -0,0 +1,102 @@ +# ArtBible: 夜番の砦 + +Status: active visual direction. + +## Art Promise + +The screen must immediately read as: + +- a night kingdom +- one hunter outside the walls +- monsters on the road +- weapons on racks, bodies, carts, and the ground +- a huge beast fight that uses those weapons + +Darkness is the premise, not the palette. Avoid a flat gray image. + +## Palette + +- torch orange +- moon blue +- blood red +- iron highlights +- leather brown +- forest green +- wet black soil +- fort-fire amber +- old stone blue-gray only as a supporting color + +## Player + +The nightwatch fighter needs a practical heroic silhouette: + +- leather armor +- cloth cloak +- shoulder lantern +- gloves, boots, belt, pouches +- waist dagger +- back weapon +- visible weapon holders +- readable silhouette for sword, spear, hammer, bow, torch, and shield + +The character should be cool and usable, not chosen-one ornate. + +## Weapons + +Each weapon type needs readable silhouettes and durability states: + +- fresh +- normal +- chipped +- near-broken +- broken + +Weapon models must be large enough to read during play. Near-broken weapons need +visible cracks or loose bindings so the player understands why throwing them is +valuable. + +## Wolf Forest + +First area visual kit: + +- moonlit tree trunks +- dark green foliage +- low ground mist +- hunter shack +- broken cart +- fallen trees +- stream crossing +- torch stands +- oil jars +- weapon racks +- animal bones +- claw marks +- warm fort light visible behind the start + +## Enemies + +- Small wolf: fast, lean, low silhouette. +- Horned beast: clear horn and lowered charge posture. +- Goblin: small weapon carrier; weapon must be visible. +- Shield goblin: readable shield front and vulnerable back. + +## Garm + +`大狼ガルム` must be memorable in one screenshot: + +- huge wolf body +- bright eyes +- scarred hide +- long tail for break target +- readable legs for spear embed +- head and jaw large enough for hammer stagger read +- moon backlight and torch rim + +## Visual Rejects + +- gray forest with no warm light +- default capsule player +- weapons too small to identify +- boss with no visible part targets +- UI that looks like debug text +- generic asset-store forest with no weapon-rack/combat staging diff --git a/Assets/Design/AudioBible.md b/Assets/Design/AudioBible.md new file mode 100644 index 0000000..83768c9 --- /dev/null +++ b/Assets/Design/AudioBible.md @@ -0,0 +1,93 @@ +# AudioBible: 夜番の砦 + +Status: active audio direction. + +## Music Identity + +Dark, tense, and adventurous. Avoid slow ambient-only music. The player should +feel that they are going out at night to hunt monsters, not walking through a +static horror scene. + +Useful instruments: + +- strings +- low drums +- flute +- horn +- metal percussion +- fire beds +- fort warning horns + +## First Vertical Slice Music + +- title theme +- Nightwatch Fort +- Wolf Forest exploration +- Wolf Forest combat layer +- Garm boss theme +- boss defeat +- morning return + +## Player SFX + +- footsteps: dirt, wood, stone, shallow water +- leather and cloak movement +- weapon draw +- pickup +- drop +- throw +- embed +- pull out +- weapon chip +- near-break warning +- break +- dodge +- guard +- parry +- hit reactions +- death + +## Weapon SFX + +- sword hit +- spear thrust +- spear embed +- hammer slam +- bow release +- arrow impact +- shield block +- torch flame +- torch hit + +## Enemy SFX + +- wolf growl +- wolf leap +- horned beast hoof/charge +- goblin voice +- shield scrape +- Garm howl +- Garm charge +- Garm part break + +## Environment SFX + +- night wind +- forest leaves +- torch fire +- stream +- distant fort horn +- creaking hunter shack +- oil ignition +- distant monster calls + +## UI SFX + +- pickup prompt +- weapon selection +- durability warning +- posture break +- part break +- lock-on +- menu confirm/cancel +- save diff --git a/Assets/Design/BossDesign.md b/Assets/Design/BossDesign.md new file mode 100644 index 0000000..e8dc46b --- /dev/null +++ b/Assets/Design/BossDesign.md @@ -0,0 +1,88 @@ +# BossDesign: 大狼ガルム + +Status: active first boss brief. + +## Product Role + +Garm proves that `夜番の砦` is not just a sword combat game. The fight must show +that local weapons change tactics: + +- spear stops charge +- hammer creates head stagger +- bow can tag a weak point +- large shield can survive a charge +- near-broken weapon throw opens a weak-point window +- tail break changes the moveset + +## Visual Read + +- huge wolf +- glowing eyes +- large head and jaws +- long tail +- scarred hide +- readable legs +- moonlit back silhouette +- torch rim from arena supplies + +## Arena + +The den should contain: + +- weapon rack before entry +- two throwing spears +- one great hammer +- one large shield +- arrow bundle +- torch stand +- broken cart with extra weapon +- small heal + +## Intro + +1. Player enters den. +2. Forest noise drops. +3. Garm steps into moonlight. +4. It howls. +5. Weapon rack and local supplies are still readable before control returns. + +## Phase 1 + +- bite +- leap +- charge +- tail sweep +- roar + +Counterplay: + +- dodge leap +- spear into leg during charge +- hammer head after missed bite +- shield charge if timed well + +## Phase 2 + +Triggered at 50% HP or after a major part break. + +- faster charge chain +- double leap +- stronger roar +- wider tail sweep unless tail was broken + +## Part Breaks + +- Leg: slows charge temporarily when spear embeds. +- Head: hammer stagger opens large damage window. +- Tail: weakens spin/tail attacks. + +## Defeat + +Garm collapses, the forest wind calms, torches stop flickering wildly, and the +sky begins to brighten toward morning. + +## Rewards + +- Garm fang material +- strong spear or wolf-blade prototype +- next request unlocked at the fort diff --git a/Assets/Design/EnemyDesign.md b/Assets/Design/EnemyDesign.md new file mode 100644 index 0000000..f9e12f6 --- /dev/null +++ b/Assets/Design/EnemyDesign.md @@ -0,0 +1,58 @@ +# EnemyDesign: 夜番の砦 + +Status: active enemy list for `狼森`. + +## Enemy Rules + +- The player should understand the enemy role from silhouette. +- Every attack needs a readable wind-up. +- Enemies should interact with weapons: dropping them, being interrupted by + them, or exposing why a weapon type matters. +- Enemies need HP and posture, not just HP. + +## SmallWolf + +Role: fast pressure. + +- attacks in short lunges +- weak to quick sword or dagger-style attacks +- can be staggered by torch +- should teach dodge and lock-on + +## HornedBeast + +Role: charge-stopper tutorial. + +- lowers head before charge +- spear hit or spear throw can stop charge +- horn can be broken later +- posture damage is high when interrupted + +## Goblin + +Role: weapon economy enemy. + +- visibly carries a weapon +- drops a usable weapon on death +- may pick up a nearby weapon in later versions +- teaches that enemy weapons are resources + +## ShieldGoblin + +Role: frontal defense. + +- hard from the front +- exposes back after attack +- posture can be broken by hammer/shield pressure +- later variants support axe shield break + +## Large Target Prototype + +Before full Garm, create a large beast test target that supports: + +- embedded spear point +- posture break +- part health +- weapon pull-out reaction + +This prevents building the boss before the weapon-hook mechanics work. diff --git a/Assets/Design/GameDesign.md b/Assets/Design/GameDesign.md new file mode 100644 index 0000000..4e72c14 --- /dev/null +++ b/Assets/Design/GameDesign.md @@ -0,0 +1,102 @@ +# GameDesign: 夜番の砦 + +Status: active Unity design source for the first vertical slice. + +## Core Loop + +1. Start at `夜番の砦`. +2. Choose two carry-in weapons from a rack. +3. Enter `狼森` at night. +4. Fight enemies while weapon durability falls. +5. Pick up, swap, drop, throw, and recover local weapons. +6. Open a shortcut with terrain or weapon interaction. +7. Resupply before the boss den. +8. Fight `大狼ガルム` using spear embed, hammer stagger, shield guard, bow shot, + and broken-weapon throw. +9. Defeat the boss and show morning returning. +10. Return to the fort for upgrades and next request. + +## Vertical Slice Scope + +Scene targets: + +- `Title` +- `NightwatchFort` +- `WolfForest` +- `Boss_Garm` + +Player abilities: + +- movement +- camera +- lock-on +- jump +- dodge +- normal attack +- heavy attack +- guard +- parry +- pickup weapon +- drop weapon +- throw weapon +- weapon swap +- torch use +- item use +- interact + +Core systems: + +- HP +- stamina +- two equipped weapon slots +- weapon pickup prompt +- weapon durability state +- weapon throw +- weapon embed +- weapon pull +- enemy HP +- enemy posture +- enemy weapon drop +- boss part break +- boss phase transition + +## First Weapons + +| Weapon | Role | +| --- | --- | +| One-handed sword | baseline fast weapon, guard/parry capable | +| Spear | reach, throw, charge stop, Garm leg embed | +| Great hammer | posture damage, armor/head stagger | +| Bow | weak-point poke, ranged pressure | +| Torch | light, frighten small night monsters, ignite oil | +| Large shield | charge stop, guard, shove, shield break | + +## First Enemies + +- `SmallWolf`: fast pack pressure, teaches dodge and quick weapon use. +- `HornedBeast`: charging enemy, teaches spear stop. +- `Goblin`: weapon carrier, drops usable weapons. +- `ShieldGoblin`: teaches axe/hammer/shield-breaking logic later; first slice + can expose posture and back-angle counterplay. + +## First Boss + +`大狼ガルム` + +- phase 1: bite, leap, charge, tail sweep +- phase 2: faster chain attacks and roar pressure +- spear in leg slows charge windows +- hammer to head creates large stagger +- tail break weakens spin attack +- near-broken weapon throw opens a short weak-point window + +## Done For First Playable + +- One playable route from fort gate into Wolf Forest and to Garm's den. +- At least four local weapons can be picked up, dropped, thrown, damaged, and + recovered. +- At least one enemy drops a usable weapon. +- One large target supports embedded weapon and pull-out interaction. +- HUD communicates HP, stamina, equipped weapons, durability, enemy HP, posture, + pickup prompt, and boss parts. +- Capture can show weapon pickup/swap/throw within 30 seconds. diff --git a/Assets/Design/ProjectStructure.md b/Assets/Design/ProjectStructure.md new file mode 100644 index 0000000..5d141be --- /dev/null +++ b/Assets/Design/ProjectStructure.md @@ -0,0 +1,89 @@ +# ProjectStructure: 夜番の砦 + +Status: active Unity project layout. + +Unity version is pinned by `ProjectSettings/ProjectVersion.txt`: +`6000.3.18f1`. + +The first generated scene entry point is: + +`Nightwatch Fortress > Build Wolf Forest Foundation Scene` + +It writes `Assets/Scenes/WolfForestFoundation.unity`. + +## Target Folder Layout + +```text +Assets/ + Art/ + Characters/ + Player/ + Enemies/ + Bosses/ + NPC/ + Environment/ + NightwatchFort/ + WolfForest/ + IronMine/ + SwampFort/ + MoonCastle/ + CapitalWall/ + Props/ + WeaponRacks/ + Torches/ + Carts/ + OilJars/ + Weapons/ + Sword/ + Spear/ + Hammer/ + Bow/ + Shield/ + Torch/ + Materials/ + Textures/ + VFX/ + Audio/ + Music/ + SFX/ + Ambience/ + Animations/ + Player/ + Enemies/ + Bosses/ + Weapons/ + Scripts/ + Core/ + Player/ + Combat/ + Weapons/ + Durability/ + Enemies/ + Bosses/ + UI/ + Save/ + Audio/ + VFX/ + Scenes/ + Title/ + NightwatchFort/ + WolfForest/ + Boss_Garm/ + UI/ + HUD/ + Menus/ + Icons/ + Design/ + GameDesign.md + ArtBible.md + AnimationList.md + AudioBible.md + BossDesign.md + EnemyDesign.md +``` + +## Creation Rule + +Create concrete folders when the first real asset or script lands in that area. +Do not commit empty production folders just to satisfy the tree. This keeps GUID +and placeholder churn down. diff --git a/Assets/Editor.meta b/Assets/Editor.meta new file mode 100644 index 0000000..3c2d3a8 --- /dev/null +++ b/Assets/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da0db9d9971c4040995412789d00ea80 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Nightwatch.meta b/Assets/Editor/Nightwatch.meta new file mode 100644 index 0000000..b4f5408 --- /dev/null +++ b/Assets/Editor/Nightwatch.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a30bdf88fb2b49cfb660db3037c1ea5f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs b/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs new file mode 100644 index 0000000..c354901 --- /dev/null +++ b/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs @@ -0,0 +1,471 @@ +using NightwatchFortress.Cameras; +using NightwatchFortress.Enemies; +using NightwatchFortress.Player; +using NightwatchFortress.UI; +using NightwatchFortress.Weapons; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace NightwatchFortress.EditorTools +{ + public static class WolfForestFoundationSceneBuilder + { + public const string ScenePath = "Assets/Scenes/WolfForestFoundation.unity"; + + [MenuItem("Nightwatch Fortress/Build Wolf Forest Foundation Scene")] + public static void BuildScene() + { + EnsureFolder("Assets/Scenes"); + Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); + scene.name = "WolfForestFoundation"; + + ConfigureLighting(); + Transform root = new GameObject("夜番の砦 - 狼森 foundation").transform; + BuildWolfForest(root); + + GameObject player = CreatePlayer(new Vector3(0f, 0.08f, -5.8f)); + PlayerVitals playerVitals = player.GetComponent(); + EnemyBase wolf = CreateEnemy("小型狼 - fast pressure", new Vector3(-1.35f, 0.05f, -1.6f), 58, 48f, new Color(0.16f, 0.18f, 0.15f, 1f), null); + AddPursuitAI(wolf, playerVitals, 3.8f, 1.15f, 1.32f, 7, 0.92f); + WeaponInstance goblinDrop = CreateWeapon(WeaponType.Spear, WeaponDurabilityState.Chipped, new Vector3(4f, 0.4f, -1.2f), Quaternion.Euler(90f, 0f, 0f), false); + goblinDrop.gameObject.SetActive(false); + EnemyBase goblin = CreateEnemy("武器持ち小鬼 - drops spear", new Vector3(1.45f, 0.05f, -0.5f), 72, 62f, new Color(0.23f, 0.18f, 0.12f, 1f), goblinDrop); + AddPursuitAI(goblin, playerVitals, 2.25f, 1.45f, 1.65f, 10, 1.18f); + AddCarriedWeapon(goblin.transform, WeaponType.Spear, new Vector3(0.36f, 1.0f, -0.12f), Quaternion.Euler(62f, 0f, -20f)); + EnemyBase garm = CreateGarmTarget(new Vector3(0f, 0.2f, 2.8f)); + + Camera camera = CreateCamera(player.transform); + var hud = camera.gameObject.AddComponent(); + hud.Bind(player.GetComponent(), player.GetComponent()); + hud.ObserveEnemy(garm != null ? garm : wolf); + + EditorSceneManager.MarkSceneDirty(scene); + EditorSceneManager.SaveScene(scene, ScenePath); + AssetDatabase.SaveAssets(); + Debug.Log($"Built Nightwatch Fortress Wolf Forest foundation scene at {ScenePath}"); + } + + private static void ConfigureLighting() + { + RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight; + RenderSettings.ambientSkyColor = new Color(0.06f, 0.09f, 0.14f, 1f); + RenderSettings.ambientEquatorColor = new Color(0.035f, 0.045f, 0.04f, 1f); + RenderSettings.ambientGroundColor = new Color(0.012f, 0.014f, 0.012f, 1f); + RenderSettings.fog = true; + RenderSettings.fogColor = new Color(0.035f, 0.045f, 0.052f, 1f); + RenderSettings.fogMode = FogMode.ExponentialSquared; + RenderSettings.fogDensity = 0.014f; + } + + private static void BuildWolfForest(Transform root) + { + Material ground = CreateMaterial("wet forest ground", new Color(0.035f, 0.050f, 0.032f, 1f), 0f, 0.72f); + Material grass = CreateMaterial("dark green moss", new Color(0.055f, 0.16f, 0.07f, 1f), 0f, 0.8f); + Material bark = CreateMaterial("moonlit bark", new Color(0.12f, 0.08f, 0.052f, 1f), 0f, 0.62f); + Material leaves = CreateMaterial("deep forest leaves", new Color(0.025f, 0.20f, 0.09f, 1f), 0f, 0.8f); + Material wood = CreateMaterial("broken hunter wood", new Color(0.22f, 0.13f, 0.07f, 1f), 0f, 0.66f); + Material moonStone = CreateMaterial("cold stream stone", new Color(0.16f, 0.20f, 0.22f, 1f), 0.05f, 0.32f); + Material torchFlame = CreateMaterial("torch flame", new Color(1f, 0.34f, 0.08f, 1f), 0f, 0.2f, new Color(2f, 0.48f, 0.08f, 1f)); + Material mist = CreateMaterial("low blue night mist", new Color(0.08f, 0.13f, 0.16f, 1f), 0f, 0.92f); + + Primitive(root, "uneven moonlit forest floor", PrimitiveType.Cube, new Vector3(0f, -0.05f, 0f), new Vector3(16f, 0.1f, 15f), ground); + Primitive(root, "shallow blue stream", PrimitiveType.Cube, new Vector3(-3.2f, 0.005f, 0.8f), new Vector3(1.2f, 0.035f, 9.5f), CreateMaterial("cold stream water", new Color(0.04f, 0.18f, 0.28f, 0.68f), 0f, 0.12f, new Color(0.0f, 0.08f, 0.12f, 1f))); + Primitive(root, "fallen tree shortcut blocker", PrimitiveType.Cylinder, new Vector3(2.9f, 0.45f, 0.8f), new Vector3(0.36f, 2.8f, 0.36f), wood).transform.rotation = Quaternion.Euler(0f, 0f, 82f); + Primitive(root, "hunter shack silhouette", PrimitiveType.Cube, new Vector3(5.2f, 0.95f, 3.8f), new Vector3(2.4f, 1.9f, 2.1f), wood); + Primitive(root, "hunter shack dark roof", PrimitiveType.Cube, new Vector3(5.2f, 2.1f, 3.8f), new Vector3(2.75f, 0.28f, 2.45f), CreateMaterial("black wet roof", new Color(0.035f, 0.028f, 0.024f, 1f), 0f, 0.52f)); + DecorativePrimitive(root, "foreground trunk left framing player", PrimitiveType.Cylinder, new Vector3(-6.7f, 1.7f, -4.7f), new Vector3(0.54f, 1.7f, 0.54f), bark); + DecorativePrimitive(root, "foreground trunk right framing weapons", PrimitiveType.Cylinder, new Vector3(6.35f, 1.55f, -4.15f), new Vector3(0.46f, 1.55f, 0.46f), bark); + DecorativePrimitive(root, "low ground mist band near player", PrimitiveType.Cube, new Vector3(0f, 0.12f, -3.15f), new Vector3(8.8f, 0.035f, 0.42f), mist); + DecorativePrimitive(root, "low ground mist band near stream", PrimitiveType.Cube, new Vector3(-1.3f, 0.13f, -0.55f), new Vector3(6.4f, 0.035f, 0.38f), mist); + DecorativePrimitive(root, "low ground mist band behind boss", PrimitiveType.Cube, new Vector3(1.0f, 0.13f, 2.0f), new Vector3(7.8f, 0.035f, 0.44f), mist); + + for (int i = 0; i < 22; i++) + { + float x = Mathf.Sin(i * 1.73f) * 7f; + float z = Mathf.Cos(i * 2.19f) * 6.2f; + if (Mathf.Abs(x) < 1.4f && z < -2.2f) + { + x += 2.4f; + } + + Primitive(root, $"readable tree trunk {i}", PrimitiveType.Cylinder, new Vector3(x, 1.1f, z), new Vector3(0.28f, 1.1f + (i % 3) * 0.24f, 0.28f), bark); + Primitive(root, $"moonlit leaf mass {i}", PrimitiveType.Sphere, new Vector3(x, 2.45f + (i % 2) * 0.25f, z), new Vector3(1.0f, 0.72f, 1.0f), leaves); + } + + for (int i = 0; i < 14; i++) + { + float x = -6.8f + i * 1.05f; + float z = -4.3f + Mathf.Sin(i * 1.9f) * 0.6f; + Primitive(root, $"moss and grass clump {i}", PrimitiveType.Cube, new Vector3(x, 0.045f, z), new Vector3(0.65f, 0.06f, 0.22f), grass); + } + + CreateTorch(root, new Vector3(-1.9f, 0f, -5.2f), torchFlame, wood); + CreateTorch(root, new Vector3(2.0f, 0f, -3.6f), torchFlame, wood); + CreateWeaponRack(root, new Vector3(-3.8f, 0f, -4.9f), wood); + + DecorativePrimitive(root, "pickup weapon amber marker", PrimitiveType.Cylinder, new Vector3(-0.85f, 0.035f, -5.05f), new Vector3(0.48f, 0.012f, 0.48f), CreateMaterial("pickup amber marker", new Color(1f, 0.48f, 0.08f, 1f), 0f, 0.4f, new Color(1.2f, 0.35f, 0.06f, 1f))); + CreateGroundWeapon(WeaponType.Spear, WeaponDurabilityState.Normal, new Vector3(-0.85f, 0.42f, -5.05f), Quaternion.Euler(90f, 0f, -8f)); + CreateGroundWeapon(WeaponType.Spear, WeaponDurabilityState.Normal, new Vector3(-3.95f, 0.42f, -4.78f), Quaternion.Euler(90f, 0f, 8f)); + CreateGroundWeapon(WeaponType.GreatHammer, WeaponDurabilityState.Chipped, new Vector3(-3.25f, 0.42f, -4.88f), Quaternion.Euler(90f, 0f, -15f)); + CreateGroundWeapon(WeaponType.Torch, WeaponDurabilityState.Normal, new Vector3(-4.55f, 0.42f, -4.7f), Quaternion.Euler(85f, 0f, 22f)); + CreateGroundWeapon(WeaponType.LargeShield, WeaponDurabilityState.Normal, new Vector3(2.3f, 0.28f, -1.2f), Quaternion.Euler(90f, 15f, 0f)); + + Light moon = new GameObject("cool blue moon key light").AddComponent(); + moon.type = LightType.Directional; + moon.color = new Color(0.42f, 0.56f, 1f, 1f); + moon.intensity = 1.15f; + moon.shadows = LightShadows.Soft; + moon.transform.rotation = Quaternion.Euler(48f, -24f, 10f); + + CreatePointLight("warm fort light behind start", new Vector3(0f, 2.1f, -7.2f), new Color(1f, 0.45f, 0.12f, 1f), 3.8f, 8f); + CreatePointLight("cold stream rim", new Vector3(-3.2f, 0.6f, 0.2f), new Color(0.1f, 0.36f, 0.85f, 1f), 1.2f, 6f); + + _ = moonStone; + } + + private static GameObject CreatePlayer(Vector3 position) + { + Material leather = CreateMaterial("nightwatch leather armor", new Color(0.12f, 0.075f, 0.045f, 1f), 0.05f, 0.48f); + Material cloak = CreateMaterial("dark cloth cloak", new Color(0.025f, 0.033f, 0.04f, 1f), 0f, 0.7f); + Material lantern = CreateMaterial("shoulder lantern amber", new Color(1f, 0.54f, 0.12f, 1f), 0.15f, 0.22f, new Color(1.8f, 0.55f, 0.12f, 1f)); + Material metal = CreateMaterial("worn iron", new Color(0.44f, 0.45f, 0.42f, 1f), 0.65f, 0.28f); + + GameObject root = new GameObject("PLAYER - Nightwatch hunter"); + root.transform.position = position; + root.AddComponent(); + var vitals = root.AddComponent(); + var inventory = root.AddComponent(); + root.AddComponent(); + root.AddComponent(); + + Transform visual = new GameObject("visual silhouette - leather cloak lantern").transform; + visual.SetParent(root.transform, false); + Primitive(visual, "leather torso", PrimitiveType.Capsule, new Vector3(0f, 0.95f, 0f), new Vector3(0.58f, 0.86f, 0.42f), leather); + Primitive(visual, "cloak back panel", PrimitiveType.Cube, new Vector3(0f, 0.86f, -0.27f), new Vector3(0.72f, 1.2f, 0.1f), cloak); + Primitive(visual, "head", PrimitiveType.Sphere, new Vector3(0f, 1.68f, 0f), new Vector3(0.32f, 0.36f, 0.3f), CreateMaterial("cool skin shadow", new Color(0.62f, 0.52f, 0.44f, 1f), 0f, 0.48f)); + Primitive(visual, "right weapon arm", PrimitiveType.Capsule, new Vector3(0.43f, 1.08f, 0.18f), new Vector3(0.15f, 0.5f, 0.15f), leather).transform.rotation = Quaternion.Euler(48f, 0f, 24f); + Primitive(visual, "left balance arm", PrimitiveType.Capsule, new Vector3(-0.38f, 1.05f, -0.02f), new Vector3(0.13f, 0.42f, 0.13f), leather).transform.rotation = Quaternion.Euler(55f, 0f, -32f); + Primitive(visual, "left boot leg", PrimitiveType.Capsule, new Vector3(-0.18f, 0.32f, 0.02f), new Vector3(0.17f, 0.38f, 0.17f), leather); + Primitive(visual, "right boot leg", PrimitiveType.Capsule, new Vector3(0.18f, 0.32f, 0.02f), new Vector3(0.17f, 0.38f, 0.17f), leather); + Primitive(visual, "shoulder lantern", PrimitiveType.Cube, new Vector3(-0.44f, 1.36f, 0.12f), new Vector3(0.18f, 0.24f, 0.16f), lantern); + Primitive(visual, "lantern metal cage", PrimitiveType.Cube, new Vector3(-0.44f, 1.36f, 0.12f), new Vector3(0.24f, 0.04f, 0.22f), metal); + Primitive(visual, "waist dagger", PrimitiveType.Cube, new Vector3(0.36f, 0.88f, -0.08f), new Vector3(0.08f, 0.42f, 0.05f), metal).transform.rotation = Quaternion.Euler(0f, 0f, -28f); + CreatePointLight("player lantern light", position + new Vector3(-0.42f, 1.45f, 0.12f), new Color(1f, 0.5f, 0.12f, 1f), 2.0f, 3.8f); + Primitive(visual, "weapon holder belt", PrimitiveType.Cube, new Vector3(0f, 0.95f, -0.32f), new Vector3(0.82f, 0.08f, 0.08f), metal); + + WeaponInstance sword = CreateWeapon(WeaponType.OneHandedSword, WeaponDurabilityState.Normal, position, Quaternion.identity, false); + WeaponInstance spear = CreateWeapon(WeaponType.Spear, WeaponDurabilityState.Chipped, position, Quaternion.identity, false); + inventory.SetInitialWeapons(sword, spear); + _ = vitals; + return root; + } + + private static EnemyBase CreateEnemy(string name, Vector3 position, int hp, float posture, Color color, WeaponInstance dropPrefab) + { + GameObject root = new GameObject(name); + root.transform.position = position; + Renderer renderer = Primitive(root.transform, "enemy body", PrimitiveType.Capsule, new Vector3(0f, 0.72f, 0f), new Vector3(0.62f, 0.74f, 0.62f), CreateMaterial($"{name} material", color, 0f, 0.62f)).GetComponent(); + ParticleSystem particles = root.AddComponent(); + var main = particles.main; + main.playOnAwake = false; + main.loop = false; + main.startLifetime = 0.18f; + main.startSpeed = 3.2f; + main.startSize = 0.08f; + main.startColor = new Color(0.7f, 0.03f, 0.02f, 1f); + + EnemyBase enemy = root.AddComponent(); + enemy.Configure(name, hp, posture, renderer, particles, dropPrefab); + return enemy; + } + + private static void AddPursuitAI(EnemyBase enemy, PlayerVitals target, float speed, float stopDistance, float attackRange, int damage, float cooldown) + { + EnemyPursuitAI ai = enemy.gameObject.AddComponent(); + ai.Configure(target, speed, stopDistance, attackRange, damage, cooldown); + } + + private static EnemyBase CreateGarmTarget(Vector3 position) + { + Material hide = CreateMaterial("Garm prototype dark hide", new Color(0.055f, 0.06f, 0.065f, 1f), 0f, 0.62f); + Material eye = CreateMaterial("Garm eye amber", new Color(1f, 0.42f, 0.05f, 1f), 0f, 0.2f, new Color(2f, 0.65f, 0.1f, 1f)); + Material scar = CreateMaterial("Garm fresh red scar", new Color(0.72f, 0.045f, 0.03f, 1f), 0f, 0.44f, new Color(0.55f, 0.02f, 0.01f, 1f)); + GameObject root = new GameObject("GARM PROTOTYPE - large embed target"); + root.transform.position = position; + Renderer renderer = Primitive(root.transform, "large wolf body", PrimitiveType.Capsule, new Vector3(0f, 0.95f, 0f), new Vector3(1.35f, 1.0f, 2.05f), hide).GetComponent(); + Primitive(root.transform, "large wolf head", PrimitiveType.Sphere, new Vector3(0f, 1.25f, -1.25f), new Vector3(0.75f, 0.55f, 0.6f), hide); + Primitive(root.transform, "long wolf muzzle", PrimitiveType.Cube, new Vector3(0f, 1.12f, -1.84f), new Vector3(0.42f, 0.22f, 0.42f), hide); + Primitive(root.transform, "front left leg silhouette", PrimitiveType.Capsule, new Vector3(-0.48f, 0.45f, -0.62f), new Vector3(0.24f, 0.55f, 0.24f), hide); + Primitive(root.transform, "front right leg silhouette", PrimitiveType.Capsule, new Vector3(0.48f, 0.45f, -0.62f), new Vector3(0.24f, 0.55f, 0.24f), hide); + Primitive(root.transform, "rear left leg silhouette", PrimitiveType.Capsule, new Vector3(-0.5f, 0.45f, 0.72f), new Vector3(0.25f, 0.55f, 0.25f), hide); + Primitive(root.transform, "rear right leg silhouette", PrimitiveType.Capsule, new Vector3(0.5f, 0.45f, 0.72f), new Vector3(0.25f, 0.55f, 0.25f), hide); + Primitive(root.transform, "glowing eye left", PrimitiveType.Sphere, new Vector3(-0.22f, 1.34f, -1.78f), new Vector3(0.08f, 0.08f, 0.08f), eye); + Primitive(root.transform, "glowing eye right", PrimitiveType.Sphere, new Vector3(0.22f, 1.34f, -1.78f), new Vector3(0.08f, 0.08f, 0.08f), eye); + DecorativePrimitive(root.transform, "red slash scar across muzzle", PrimitiveType.Cube, new Vector3(0f, 1.34f, -1.91f), new Vector3(0.62f, 0.035f, 0.035f), scar).transform.rotation = Quaternion.Euler(0f, 0f, -16f); + DecorativePrimitive(root.transform, "red body scar left", PrimitiveType.Cube, new Vector3(-0.52f, 1.1f, -0.15f), new Vector3(0.055f, 0.46f, 0.04f), scar).transform.rotation = Quaternion.Euler(18f, 0f, 28f); + DecorativePrimitive(root.transform, "red body scar right", PrimitiveType.Cube, new Vector3(0.54f, 1.02f, 0.4f), new Vector3(0.055f, 0.5f, 0.04f), scar).transform.rotation = Quaternion.Euler(-12f, 0f, -24f); + Primitive(root.transform, "breakable tail read", PrimitiveType.Cylinder, new Vector3(0f, 1.05f, 1.42f), new Vector3(0.16f, 1.25f, 0.16f), hide).transform.rotation = Quaternion.Euler(72f, 0f, 0f); + GameObject legTarget = Primitive(root.transform, "leg embed target - spear stops charge", PrimitiveType.Sphere, new Vector3(-0.55f, 0.62f, -0.35f), new Vector3(0.28f, 0.28f, 0.28f), scar); + legTarget.AddComponent(); + CreatePointLight("Garm moon rim light", position + new Vector3(0f, 2.2f, 2.0f), new Color(0.28f, 0.48f, 1f, 1f), 2.8f, 5.5f); + + ParticleSystem particles = root.AddComponent(); + var main = particles.main; + main.playOnAwake = false; + main.loop = false; + main.startLifetime = 0.22f; + main.startSpeed = 3.4f; + main.startSize = 0.1f; + main.startColor = new Color(0.8f, 0.04f, 0.02f, 1f); + + EnemyBase enemy = root.AddComponent(); + enemy.Configure("大狼ガルム - 槍を脚へ刺せ", 260, 180f, renderer, particles, null); + return enemy; + } + + private static void CreateWeaponRack(Transform parent, Vector3 position, Material wood) + { + Primitive(parent, "weapon rack cross beam", PrimitiveType.Cube, position + new Vector3(0f, 0.82f, 0f), new Vector3(1.8f, 0.12f, 0.12f), wood); + Primitive(parent, "weapon rack left leg", PrimitiveType.Cube, position + new Vector3(-0.82f, 0.42f, 0f), new Vector3(0.12f, 0.84f, 0.12f), wood); + Primitive(parent, "weapon rack right leg", PrimitiveType.Cube, position + new Vector3(0.82f, 0.42f, 0f), new Vector3(0.12f, 0.84f, 0.12f), wood); + } + + private static void CreateTorch(Transform parent, Vector3 position, Material flame, Material wood) + { + Primitive(parent, "torch stand", PrimitiveType.Cylinder, position + new Vector3(0f, 0.62f, 0f), new Vector3(0.08f, 0.62f, 0.08f), wood); + Primitive(parent, "torch flame", PrimitiveType.Sphere, position + new Vector3(0f, 1.3f, 0f), new Vector3(0.17f, 0.26f, 0.17f), flame); + CreatePointLight("torch warm pool", position + new Vector3(0f, 1.5f, 0f), new Color(1f, 0.38f, 0.09f, 1f), 2.2f, 4.8f); + } + + private static WeaponInstance CreateGroundWeapon(WeaponType type, WeaponDurabilityState state, Vector3 position, Quaternion rotation) + { + return CreateWeapon(type, state, position, rotation, true); + } + + private static WeaponInstance CreateWeapon(WeaponType type, WeaponDurabilityState state, Vector3 position, Quaternion rotation, bool onGround) + { + GameObject root = new GameObject($"{WeaponUtility.GetDisplayName(type)} - {WeaponUtility.GetDurabilityName(state)}"); + root.transform.position = position; + root.transform.rotation = rotation; + + Material material = CreateMaterial($"{type} material", WeaponUtility.GetWeaponColor(type), type == WeaponType.Torch ? 0f : 0.55f, 0.35f, type == WeaponType.Torch ? new Color(1.4f, 0.35f, 0.08f, 1f) : (Color?)null); + CreateWeaponVisual(root.transform, type, material); + var rigidbody = root.AddComponent(); + rigidbody.useGravity = onGround; + rigidbody.isKinematic = !onGround; + BoxCollider collider = root.AddComponent(); + collider.size = GetWeaponColliderSize(type); + var weapon = root.AddComponent(); + weapon.Configure(type, state, GetWeaponDamage(type), GetWeaponPosture(type)); + root.AddComponent(); + root.AddComponent(); + return weapon; + } + + private static void AddCarriedWeapon(Transform parent, WeaponType type, Vector3 localPosition, Quaternion localRotation) + { + Transform visual = new GameObject($"visible carried {WeaponUtility.GetDisplayName(type)}").transform; + visual.SetParent(parent, false); + visual.localPosition = localPosition; + visual.localRotation = localRotation; + Material material = CreateMaterial($"carried {type} material", WeaponUtility.GetWeaponColor(type), 0.45f, 0.36f); + CreateWeaponVisual(visual, type, material); + } + + private static void CreateWeaponVisual(Transform parent, WeaponType type, Material mainMaterial) + { + Material darkGrip = CreateMaterial($"{type} dark grip", new Color(0.11f, 0.075f, 0.045f, 1f), 0f, 0.58f); + Material iron = CreateMaterial($"{type} bright edge", new Color(0.72f, 0.72f, 0.66f, 1f), 0.72f, 0.22f); + Material flame = CreateMaterial($"{type} live flame", new Color(1f, 0.42f, 0.08f, 1f), 0f, 0.25f, new Color(1.6f, 0.38f, 0.08f, 1f)); + + switch (type) + { + case WeaponType.Spear: + DecorativePrimitive(parent, "spear long shaft", PrimitiveType.Cylinder, Vector3.zero, new Vector3(0.045f, 0.92f, 0.045f), darkGrip); + DecorativePrimitive(parent, "spear bright head", PrimitiveType.Cube, new Vector3(0f, 0.58f, 0f), new Vector3(0.16f, 0.24f, 0.16f), iron); + DecorativePrimitive(parent, "spear red wrap", PrimitiveType.Cube, new Vector3(0f, -0.18f, 0f), new Vector3(0.12f, 0.08f, 0.12f), mainMaterial); + return; + case WeaponType.GreatHammer: + DecorativePrimitive(parent, "hammer long handle", PrimitiveType.Cylinder, new Vector3(0f, -0.12f, 0f), new Vector3(0.06f, 0.82f, 0.06f), darkGrip); + DecorativePrimitive(parent, "hammer heavy head", PrimitiveType.Cube, new Vector3(0f, 0.48f, 0f), new Vector3(0.62f, 0.25f, 0.34f), mainMaterial); + DecorativePrimitive(parent, "hammer iron cap", PrimitiveType.Cube, new Vector3(0f, 0.48f, 0f), new Vector3(0.68f, 0.06f, 0.39f), iron); + return; + case WeaponType.Bow: + DecorativePrimitive(parent, "bow curved body", PrimitiveType.Cylinder, Vector3.zero, new Vector3(0.045f, 0.78f, 0.045f), mainMaterial); + DecorativePrimitive(parent, "bow string", PrimitiveType.Cube, new Vector3(0.18f, 0f, 0f), new Vector3(0.018f, 1.35f, 0.018f), iron); + DecorativePrimitive(parent, "bow grip", PrimitiveType.Cube, Vector3.zero, new Vector3(0.16f, 0.16f, 0.12f), darkGrip); + return; + case WeaponType.Torch: + DecorativePrimitive(parent, "torch wood shaft", PrimitiveType.Cylinder, new Vector3(0f, -0.08f, 0f), new Vector3(0.055f, 0.62f, 0.055f), darkGrip); + DecorativePrimitive(parent, "torch flame head", PrimitiveType.Sphere, new Vector3(0f, 0.45f, 0f), new Vector3(0.17f, 0.24f, 0.17f), flame); + return; + case WeaponType.LargeShield: + DecorativePrimitive(parent, "shield broad face", PrimitiveType.Cube, Vector3.zero, new Vector3(0.66f, 0.12f, 0.86f), mainMaterial); + DecorativePrimitive(parent, "shield center boss", PrimitiveType.Sphere, new Vector3(0f, 0.08f, 0f), new Vector3(0.18f, 0.1f, 0.18f), iron); + DecorativePrimitive(parent, "shield iron rim", PrimitiveType.Cube, new Vector3(0f, 0.09f, 0f), new Vector3(0.75f, 0.04f, 0.95f), iron); + return; + case WeaponType.OneHandedSword: + DecorativePrimitive(parent, "sword bright blade", PrimitiveType.Cube, new Vector3(0f, 0.26f, 0f), new Vector3(0.08f, 0.84f, 0.04f), iron); + DecorativePrimitive(parent, "sword crossguard", PrimitiveType.Cube, new Vector3(0f, -0.2f, 0f), new Vector3(0.42f, 0.055f, 0.09f), mainMaterial); + DecorativePrimitive(parent, "sword grip", PrimitiveType.Cylinder, new Vector3(0f, -0.42f, 0f), new Vector3(0.055f, 0.22f, 0.055f), darkGrip); + return; + case WeaponType.Dagger: + DecorativePrimitive(parent, "dagger bright blade", PrimitiveType.Cube, new Vector3(0f, 0.14f, 0f), new Vector3(0.075f, 0.42f, 0.035f), iron); + DecorativePrimitive(parent, "dagger dark grip", PrimitiveType.Cylinder, new Vector3(0f, -0.2f, 0f), new Vector3(0.05f, 0.18f, 0.05f), darkGrip); + return; + default: + DecorativePrimitive(parent, "axe handle", PrimitiveType.Cylinder, Vector3.zero, new Vector3(0.055f, 0.68f, 0.055f), darkGrip); + DecorativePrimitive(parent, "axe blade wedge", PrimitiveType.Cube, new Vector3(0.18f, 0.25f, 0f), new Vector3(0.34f, 0.24f, 0.08f), iron); + DecorativePrimitive(parent, "axe red wrap", PrimitiveType.Cube, new Vector3(0f, -0.1f, 0f), new Vector3(0.11f, 0.08f, 0.11f), mainMaterial); + return; + } + } + + private static Vector3 GetWeaponColliderSize(WeaponType type) + { + switch (type) + { + case WeaponType.Spear: + return new Vector3(0.28f, 1.55f, 0.28f); + case WeaponType.GreatHammer: + return new Vector3(0.84f, 1.25f, 0.56f); + case WeaponType.LargeShield: + return new Vector3(0.9f, 0.28f, 1.08f); + case WeaponType.Bow: + return new Vector3(0.42f, 1.38f, 0.24f); + default: + return new Vector3(0.42f, 1.05f, 0.32f); + } + } + + private static int GetWeaponDamage(WeaponType type) + { + switch (type) + { + case WeaponType.GreatHammer: + return 30; + case WeaponType.Spear: + return 20; + case WeaponType.Bow: + return 16; + case WeaponType.LargeShield: + return 12; + case WeaponType.Torch: + return 10; + default: + return 18; + } + } + + private static float GetWeaponPosture(WeaponType type) + { + switch (type) + { + case WeaponType.GreatHammer: + return 34f; + case WeaponType.LargeShield: + return 24f; + case WeaponType.Spear: + return 18f; + default: + return 14f; + } + } + + private static Camera CreateCamera(Transform target) + { + GameObject cameraObject = new GameObject("Main Camera - readable night forest"); + Camera camera = cameraObject.AddComponent(); + camera.tag = "MainCamera"; + camera.fieldOfView = 42f; + camera.nearClipPlane = 0.1f; + camera.farClipPlane = 90f; + camera.backgroundColor = new Color(0.012f, 0.016f, 0.024f, 1f); + camera.clearFlags = CameraClearFlags.SolidColor; + var follow = cameraObject.AddComponent(); + follow.SetTarget(target); + target.GetComponent().SetCamera(camera.transform); + return camera; + } + + private static GameObject Primitive(Transform parent, string name, PrimitiveType type, Vector3 localPosition, Vector3 localScale, Material material) + { + GameObject item = GameObject.CreatePrimitive(type); + item.name = name; + item.transform.SetParent(parent, false); + item.transform.localPosition = localPosition; + item.transform.localScale = localScale; + item.GetComponent().material = material; + return item; + } + + private static GameObject DecorativePrimitive(Transform parent, string name, PrimitiveType type, Vector3 localPosition, Vector3 localScale, Material material) + { + GameObject item = Primitive(parent, name, type, localPosition, localScale, material); + Collider collider = item.GetComponent(); + if (collider != null) + { + Object.DestroyImmediate(collider); + } + + return item; + } + + private static Material CreateMaterial(string name, Color color, float metallic, float smoothness) + { + return CreateMaterial(name, color, metallic, smoothness, null); + } + + private static Material CreateMaterial(string name, Color color, float metallic, float smoothness, Color? emission) + { + var material = new Material(Shader.Find("Standard")) + { + name = name, + color = color + }; + material.SetFloat("_Metallic", metallic); + material.SetFloat("_Glossiness", 1f - smoothness); + if (emission.HasValue) + { + material.EnableKeyword("_EMISSION"); + material.SetColor("_EmissionColor", emission.Value); + } + + return material; + } + + private static void CreatePointLight(string name, Vector3 position, Color color, float intensity, float range) + { + Light light = new GameObject(name).AddComponent(); + light.type = LightType.Point; + light.transform.position = position; + light.color = color; + light.intensity = intensity; + light.range = range; + light.shadows = LightShadows.Soft; + } + + private static void EnsureFolder(string path) + { + if (AssetDatabase.IsValidFolder(path)) + { + return; + } + + string[] parts = path.Split('/'); + string current = parts[0]; + for (int i = 1; i < parts.Length; i++) + { + string next = $"{current}/{parts[i]}"; + if (!AssetDatabase.IsValidFolder(next)) + { + AssetDatabase.CreateFolder(current, parts[i]); + } + + current = next; + } + } + } +} diff --git a/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs.meta b/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs.meta new file mode 100644 index 0000000..e7f3805 --- /dev/null +++ b/Assets/Editor/Nightwatch/WolfForestFoundationSceneBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 563360f00e6042c19208801d1e1ae510 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts.meta b/Assets/Scripts.meta new file mode 100644 index 0000000..f66cbd7 --- /dev/null +++ b/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3045376f6f4f4ec3a9f1e365a7e78e20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Cameras.meta b/Assets/Scripts/Cameras.meta new file mode 100644 index 0000000..e4cbbce --- /dev/null +++ b/Assets/Scripts/Cameras.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e19630b99bf146a19a499aa2e7361c79 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs b/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs new file mode 100644 index 0000000..b57bff1 --- /dev/null +++ b/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs @@ -0,0 +1,41 @@ +using UnityEngine; + +namespace NightwatchFortress.Cameras +{ + public class ThirdPersonFollowCamera : MonoBehaviour + { + [SerializeField] private Transform target; + [SerializeField] private Vector3 offset = new Vector3(0f, 5.8f, -7.4f); + [SerializeField] private float followSharpness = 8.5f; + [SerializeField] private float lookHeight = 1.15f; + + public void SetTarget(Transform targetTransform) + { + target = targetTransform; + SnapToTarget(); + } + + private void LateUpdate() + { + if (target == null) + { + return; + } + + Vector3 desired = target.position + offset; + transform.position = Vector3.Lerp(transform.position, desired, 1f - Mathf.Exp(-followSharpness * Time.deltaTime)); + transform.rotation = Quaternion.LookRotation((target.position + Vector3.up * lookHeight) - transform.position, Vector3.up); + } + + private void SnapToTarget() + { + if (target == null) + { + return; + } + + transform.position = target.position + offset; + transform.rotation = Quaternion.LookRotation((target.position + Vector3.up * lookHeight) - transform.position, Vector3.up); + } + } +} diff --git a/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs.meta b/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs.meta new file mode 100644 index 0000000..c17c6db --- /dev/null +++ b/Assets/Scripts/Cameras/ThirdPersonFollowCamera.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 645f2e6cd1704d81844182fc71c8f026 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Combat.meta b/Assets/Scripts/Combat.meta new file mode 100644 index 0000000..91e4d3e --- /dev/null +++ b/Assets/Scripts/Combat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ed54c73d21e48ef97a13cc1387279dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Combat/CombatHit.cs b/Assets/Scripts/Combat/CombatHit.cs new file mode 100644 index 0000000..ce40b22 --- /dev/null +++ b/Assets/Scripts/Combat/CombatHit.cs @@ -0,0 +1,27 @@ +using NightwatchFortress.Weapons; +using UnityEngine; + +namespace NightwatchFortress.Combat +{ + public readonly struct CombatHit + { + public readonly int Damage; + public readonly float PostureDamage; + public readonly WeaponType WeaponType; + public readonly Vector3 Point; + public readonly Vector3 Direction; + public readonly float Knockback; + public readonly bool FromThrownWeapon; + + public CombatHit(int damage, float postureDamage, WeaponType weaponType, Vector3 point, Vector3 direction, float knockback, bool fromThrownWeapon) + { + Damage = damage; + PostureDamage = postureDamage; + WeaponType = weaponType; + Point = point; + Direction = direction.sqrMagnitude > 0.001f ? direction.normalized : Vector3.forward; + Knockback = knockback; + FromThrownWeapon = fromThrownWeapon; + } + } +} diff --git a/Assets/Scripts/Combat/CombatHit.cs.meta b/Assets/Scripts/Combat/CombatHit.cs.meta new file mode 100644 index 0000000..494f526 --- /dev/null +++ b/Assets/Scripts/Combat/CombatHit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c5a9ce245494489b7ac31da1d113c3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Enemies.meta b/Assets/Scripts/Enemies.meta new file mode 100644 index 0000000..6bbb6e1 --- /dev/null +++ b/Assets/Scripts/Enemies.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 43a8bf413dfa4cb8a547c6905b822daa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Enemies/EnemyBase.cs b/Assets/Scripts/Enemies/EnemyBase.cs new file mode 100644 index 0000000..58dff03 --- /dev/null +++ b/Assets/Scripts/Enemies/EnemyBase.cs @@ -0,0 +1,118 @@ +using NightwatchFortress.Combat; +using NightwatchFortress.Weapons; +using UnityEngine; + +namespace NightwatchFortress.Enemies +{ + [DisallowMultipleComponent] + public class EnemyBase : MonoBehaviour + { + [SerializeField] private string enemyName = "小型狼"; + [SerializeField] private int maxHp = 80; + [SerializeField] private float maxPosture = 70f; + [SerializeField] private WeaponInstance weaponDropPrefab; + [SerializeField] private Renderer bodyRenderer; + [SerializeField] private ParticleSystem hitParticles; + + private int hp; + private float posture; + private bool postureBroken; + private bool dead; + + public string EnemyName => enemyName; + public int Hp => hp; + public int MaxHp => maxHp; + public float Posture => posture; + public float MaxPosture => maxPosture; + public bool IsPostureBroken => postureBroken; + public bool IsDead => dead; + + private void Awake() + { + hp = maxHp; + posture = maxPosture; + if (bodyRenderer == null) + { + bodyRenderer = GetComponentInChildren(); + } + } + + public void Configure(string displayName, int hpValue, float postureValue, Renderer visibleRenderer, ParticleSystem particles, WeaponInstance dropPrefab) + { + enemyName = displayName; + maxHp = Mathf.Max(1, hpValue); + maxPosture = Mathf.Max(1f, postureValue); + bodyRenderer = visibleRenderer; + hitParticles = particles; + weaponDropPrefab = dropPrefab; + hp = maxHp; + posture = maxPosture; + postureBroken = false; + dead = false; + } + + public void TakeHit(CombatHit hit) + { + if (dead) + { + return; + } + + hp = Mathf.Max(0, hp - Mathf.Max(0, hit.Damage)); + posture = Mathf.Max(0f, posture - Mathf.Max(0f, hit.PostureDamage)); + if (posture <= 0f) + { + postureBroken = true; + } + + if (hit.Knockback > 0f) + { + Vector3 flatDirection = new Vector3(hit.Direction.x, 0f, hit.Direction.z); + if (flatDirection.sqrMagnitude > 0.001f) + { + transform.position += flatDirection.normalized * hit.Knockback; + } + } + + FlashHit(hit.WeaponType); + if (hp <= 0) + { + Die(hit.Direction); + } + } + + private void FlashHit(WeaponType weaponType) + { + if (bodyRenderer != null) + { + bodyRenderer.material.color = Color.Lerp(bodyRenderer.material.color, WeaponUtility.GetWeaponColor(weaponType), 0.45f); + } + + if (hitParticles != null) + { + var main = hitParticles.main; + main.startColor = WeaponUtility.GetWeaponColor(weaponType); + hitParticles.Play(true); + } + } + + private void Die(Vector3 impulseDirection) + { + dead = true; + DropWeapon(impulseDirection); + gameObject.SetActive(false); + } + + private void DropWeapon(Vector3 impulseDirection) + { + if (weaponDropPrefab == null) + { + return; + } + + WeaponInstance drop = Instantiate(weaponDropPrefab, transform.position + Vector3.up * 0.5f, Quaternion.identity); + drop.gameObject.SetActive(true); + drop.Drop(transform.position + Vector3.up * 0.5f, (impulseDirection.normalized * 1.5f) + Vector3.up * 1.2f); + } + } +} diff --git a/Assets/Scripts/Enemies/EnemyBase.cs.meta b/Assets/Scripts/Enemies/EnemyBase.cs.meta new file mode 100644 index 0000000..57f3ac6 --- /dev/null +++ b/Assets/Scripts/Enemies/EnemyBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e0d9b2a00ec4933bf1f2e06c8af61a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Enemies/EnemyPursuitAI.cs b/Assets/Scripts/Enemies/EnemyPursuitAI.cs new file mode 100644 index 0000000..bc239e3 --- /dev/null +++ b/Assets/Scripts/Enemies/EnemyPursuitAI.cs @@ -0,0 +1,68 @@ +using NightwatchFortress.Player; +using UnityEngine; + +namespace NightwatchFortress.Enemies +{ + [RequireComponent(typeof(EnemyBase))] + public class EnemyPursuitAI : MonoBehaviour + { + [SerializeField] private PlayerVitals target; + [SerializeField] private float moveSpeed = 2.6f; + [SerializeField] private float stoppingDistance = 1.25f; + [SerializeField] private float attackRange = 1.35f; + [SerializeField] private float attackCooldown = 1.05f; + [SerializeField] private int attackDamage = 8; + [SerializeField] private float rotationSpeed = 12f; + + private EnemyBase enemy; + private float cooldownTimer; + + public bool HasTarget => target != null && !target.IsDead; + + private void Awake() + { + enemy = GetComponent(); + } + + private void Update() + { + if (enemy == null || enemy.IsDead || !HasTarget) + { + return; + } + + cooldownTimer = Mathf.Max(0f, cooldownTimer - Time.deltaTime); + Vector3 offset = target.transform.position - transform.position; + offset.y = 0f; + float distance = offset.magnitude; + if (distance <= 0.001f) + { + return; + } + + Vector3 direction = offset / distance; + transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction, Vector3.up), rotationSpeed * Time.deltaTime); + + if (distance > stoppingDistance) + { + transform.position += direction * (moveSpeed * Time.deltaTime); + } + + if (distance <= attackRange && cooldownTimer <= 0f) + { + target.TakeDamage(attackDamage); + cooldownTimer = attackCooldown; + } + } + + public void Configure(PlayerVitals targetVitals, float speed, float stopDistance, float range, int damage, float cooldown) + { + target = targetVitals; + moveSpeed = Mathf.Max(0f, speed); + stoppingDistance = Mathf.Max(0.1f, stopDistance); + attackRange = Mathf.Max(stoppingDistance, range); + attackDamage = Mathf.Max(1, damage); + attackCooldown = Mathf.Max(0.1f, cooldown); + } + } +} diff --git a/Assets/Scripts/Enemies/EnemyPursuitAI.cs.meta b/Assets/Scripts/Enemies/EnemyPursuitAI.cs.meta new file mode 100644 index 0000000..49b62c6 --- /dev/null +++ b/Assets/Scripts/Enemies/EnemyPursuitAI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7335f976f9ec4e0aa2bb6b5908320480 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player.meta b/Assets/Scripts/Player.meta new file mode 100644 index 0000000..52166c0 --- /dev/null +++ b/Assets/Scripts/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e75d36bdcf04dc7b2e7fa096fa6d776 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player/PlayerCombat.cs b/Assets/Scripts/Player/PlayerCombat.cs new file mode 100644 index 0000000..c7d49ce --- /dev/null +++ b/Assets/Scripts/Player/PlayerCombat.cs @@ -0,0 +1,165 @@ +using System.Collections; +using NightwatchFortress.Combat; +using NightwatchFortress.Enemies; +using NightwatchFortress.Weapons; +using UnityEngine; + +namespace NightwatchFortress.Player +{ + [RequireComponent(typeof(PlayerController))] + [RequireComponent(typeof(PlayerVitals))] + [RequireComponent(typeof(WeaponInventory))] + public class PlayerCombat : MonoBehaviour + { + [SerializeField] private float attackRadius = 0.9f; + [SerializeField] private float attackReach = 1.15f; + [SerializeField] private float lightAttackCost = 10f; + [SerializeField] private float heavyAttackCost = 24f; + [SerializeField] private float lightCooldown = 0.32f; + [SerializeField] private float heavyCooldown = 0.62f; + [SerializeField] private float throwCooldown = 0.38f; + [SerializeField] private LayerMask hittableLayers = ~0; + + private PlayerController controller; + private PlayerVitals vitals; + private WeaponInventory inventory; + private float cooldownTimer; + private Coroutine hitStopRoutine; + + public float Cooldown01 => Mathf.Clamp01(cooldownTimer / heavyCooldown); + + private void Awake() + { + controller = GetComponent(); + vitals = GetComponent(); + inventory = GetComponent(); + } + + private void Update() + { + cooldownTimer = Mathf.Max(0f, cooldownTimer - Time.deltaTime); + + if (Input.GetKeyDown(KeyCode.Tab)) + { + inventory.SwapActiveSlot(); + } + + if (Input.GetKeyDown(KeyCode.E)) + { + if (!inventory.TryPullEmbeddedNearby()) + { + inventory.TryPickupNearest(); + } + } + + if (Input.GetKeyDown(KeyCode.R)) + { + inventory.DropActive(); + } + + if (cooldownTimer > 0f || vitals.IsDead) + { + return; + } + + if (Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.J)) + { + Strike(false); + } + else if (Input.GetMouseButtonDown(1) || Input.GetKeyDown(KeyCode.U)) + { + Strike(true); + } + else if (Input.GetKeyDown(KeyCode.F)) + { + ThrowActive(); + } + } + + public void Strike(bool heavy) + { + WeaponInstance weapon = inventory.ActiveWeapon; + if (weapon == null || weapon.IsBroken) + { + return; + } + + float staminaCost = heavy ? heavyAttackCost : lightAttackCost; + if (!vitals.SpendStamina(staminaCost)) + { + return; + } + + Vector3 center = transform.position + Vector3.up * 0.88f + controller.Facing * attackReach; + Collider[] hits = Physics.OverlapSphere(center, attackRadius, hittableLayers, QueryTriggerInteraction.Ignore); + bool connected = false; + foreach (Collider hit in hits) + { + EnemyBase enemy = hit.GetComponentInParent(); + if (enemy == null || enemy.IsDead) + { + continue; + } + + float damageMultiplier = heavy ? 1.48f : 1f; + float postureMultiplier = heavy ? 1.7f : 1f; + Vector3 direction = enemy.transform.position - transform.position; + enemy.TakeHit(new CombatHit( + Mathf.RoundToInt(weapon.Damage * damageMultiplier), + weapon.PostureDamage * postureMultiplier, + weapon.WeaponType, + center, + direction, + heavy ? 0.28f : 0.13f, + false)); + connected = true; + } + + weapon.ApplyUseWear(heavy ? 0.10f : 0.055f); + cooldownTimer = heavy ? heavyCooldown : lightCooldown; + if (connected) + { + StartHitStop(heavy ? 0.075f : 0.04f); + } + } + + public void ThrowActive() + { + WeaponInstance weapon = inventory.ActiveWeapon; + if (weapon == null || !weapon.CanThrow) + { + return; + } + + if (!vitals.SpendStamina(14f)) + { + return; + } + + if (inventory.ThrowActive(controller.Facing)) + { + cooldownTimer = throwCooldown; + StartHitStop(0.025f); + } + } + + private void StartHitStop(float duration) + { + if (hitStopRoutine != null) + { + StopCoroutine(hitStopRoutine); + } + + hitStopRoutine = StartCoroutine(HitStop(duration)); + } + + private IEnumerator HitStop(float duration) + { + float previous = Time.timeScale; + Time.timeScale = 0.1f; + yield return new WaitForSecondsRealtime(duration); + Time.timeScale = previous; + hitStopRoutine = null; + } + } +} diff --git a/Assets/Scripts/Player/PlayerCombat.cs.meta b/Assets/Scripts/Player/PlayerCombat.cs.meta new file mode 100644 index 0000000..8e15e31 --- /dev/null +++ b/Assets/Scripts/Player/PlayerCombat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92d41416da294db3aa959a1cccb8fa13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player/PlayerController.cs b/Assets/Scripts/Player/PlayerController.cs new file mode 100644 index 0000000..1cf4f94 --- /dev/null +++ b/Assets/Scripts/Player/PlayerController.cs @@ -0,0 +1,133 @@ +using UnityEngine; + +namespace NightwatchFortress.Player +{ + [RequireComponent(typeof(CharacterController))] + [RequireComponent(typeof(PlayerVitals))] + public class PlayerController : MonoBehaviour + { + [SerializeField] private Transform cameraTransform; + [SerializeField] private float moveSpeed = 5.5f; + [SerializeField] private float rotationSpeed = 15f; + [SerializeField] private float jumpSpeed = 5.8f; + [SerializeField] private float gravity = -22f; + [SerializeField] private float dodgeSpeed = 12.5f; + [SerializeField] private float dodgeDuration = 0.22f; + [SerializeField] private float dodgeStaminaCost = 18f; + + private CharacterController characterController; + private PlayerVitals vitals; + private Vector3 velocity; + private Vector3 moveInput; + private Vector3 facing = Vector3.forward; + private Vector3 dodgeDirection; + private float dodgeTimer; + + public Vector3 Facing => facing; + public bool IsDodging => dodgeTimer > 0f; + + private void Awake() + { + characterController = GetComponent(); + vitals = GetComponent(); + characterController.radius = 0.36f; + characterController.height = 1.82f; + characterController.center = new Vector3(0f, 0.91f, 0f); + + if (cameraTransform == null && UnityEngine.Camera.main != null) + { + cameraTransform = UnityEngine.Camera.main.transform; + } + } + + private void Update() + { + float deltaTime = Time.deltaTime; + ReadMoveInput(); + + if (dodgeTimer > 0f) + { + dodgeTimer -= deltaTime; + characterController.Move(dodgeDirection * (dodgeSpeed * deltaTime)); + ApplyGravity(deltaTime); + return; + } + + if ((Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.LeftShift)) && vitals.SpendStamina(dodgeStaminaCost)) + { + StartDodge(); + } + + Move(deltaTime); + ApplyGravity(deltaTime); + } + + public void SetCamera(Transform followCamera) + { + cameraTransform = followCamera; + } + + private void ReadMoveInput() + { + float horizontal = Input.GetAxisRaw("Horizontal"); + float vertical = Input.GetAxisRaw("Vertical"); + moveInput = new Vector3(horizontal, 0f, vertical); + if (moveInput.sqrMagnitude > 1f) + { + moveInput.Normalize(); + } + } + + private void Move(float deltaTime) + { + Vector3 worldMove = CameraRelative(moveInput); + if (worldMove.sqrMagnitude > 0.01f) + { + facing = Vector3.Slerp(facing, worldMove.normalized, rotationSpeed * deltaTime).normalized; + transform.rotation = Quaternion.LookRotation(facing, Vector3.up); + characterController.Move(worldMove * (moveSpeed * deltaTime)); + } + + if (characterController.isGrounded && velocity.y < 0f) + { + velocity.y = -1f; + } + + if (Input.GetKeyDown(KeyCode.C) && characterController.isGrounded) + { + velocity.y = jumpSpeed; + } + } + + private void StartDodge() + { + Vector3 worldMove = CameraRelative(moveInput); + dodgeDirection = worldMove.sqrMagnitude > 0.01f ? worldMove.normalized : facing; + facing = dodgeDirection; + transform.rotation = Quaternion.LookRotation(facing, Vector3.up); + dodgeTimer = dodgeDuration; + } + + private void ApplyGravity(float deltaTime) + { + velocity.y += gravity * deltaTime; + characterController.Move(velocity * deltaTime); + } + + private Vector3 CameraRelative(Vector3 input) + { + if (cameraTransform == null || input.sqrMagnitude < 0.001f) + { + return input; + } + + Vector3 forward = cameraTransform.forward; + forward.y = 0f; + forward.Normalize(); + Vector3 right = cameraTransform.right; + right.y = 0f; + right.Normalize(); + return (right * input.x) + (forward * input.z); + } + } +} diff --git a/Assets/Scripts/Player/PlayerController.cs.meta b/Assets/Scripts/Player/PlayerController.cs.meta new file mode 100644 index 0000000..b8c3832 --- /dev/null +++ b/Assets/Scripts/Player/PlayerController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6eb5d77d8982499cb2b75f465f854e25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player/PlayerVitals.cs b/Assets/Scripts/Player/PlayerVitals.cs new file mode 100644 index 0000000..de5910c --- /dev/null +++ b/Assets/Scripts/Player/PlayerVitals.cs @@ -0,0 +1,69 @@ +using UnityEngine; + +namespace NightwatchFortress.Player +{ + public class PlayerVitals : MonoBehaviour + { + [SerializeField] private int maxHp = 120; + [SerializeField] private int hp = 120; + [SerializeField] private float maxStamina = 100f; + [SerializeField] private float stamina = 100f; + [SerializeField] private float staminaRegenPerSecond = 22f; + + public int Hp => hp; + public int MaxHp => maxHp; + public float Stamina => stamina; + public float MaxStamina => maxStamina; + public bool IsDead => hp <= 0; + + private void Awake() + { + hp = Mathf.Clamp(hp, 1, maxHp); + stamina = Mathf.Clamp(stamina, 0f, maxStamina); + } + + private void Update() + { + if (!IsDead) + { + stamina = Mathf.Min(maxStamina, stamina + staminaRegenPerSecond * Time.deltaTime); + } + } + + public bool SpendStamina(float amount) + { + if (amount <= 0f) + { + return true; + } + + if (stamina < amount) + { + return false; + } + + stamina -= amount; + return true; + } + + public void Heal(int amount) + { + if (amount <= 0 || IsDead) + { + return; + } + + hp = Mathf.Min(maxHp, hp + amount); + } + + public void TakeDamage(int amount) + { + if (amount <= 0 || IsDead) + { + return; + } + + hp = Mathf.Max(0, hp - amount); + } + } +} diff --git a/Assets/Scripts/Player/PlayerVitals.cs.meta b/Assets/Scripts/Player/PlayerVitals.cs.meta new file mode 100644 index 0000000..c4564c7 --- /dev/null +++ b/Assets/Scripts/Player/PlayerVitals.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c998bbd38a64f62b21896d38385a08a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI.meta b/Assets/Scripts/UI.meta new file mode 100644 index 0000000..672a4fb --- /dev/null +++ b/Assets/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b2e2f2d51ed45e99ab7063e0f5713b5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI/HUDController.cs b/Assets/Scripts/UI/HUDController.cs new file mode 100644 index 0000000..a89f366 --- /dev/null +++ b/Assets/Scripts/UI/HUDController.cs @@ -0,0 +1,150 @@ +using NightwatchFortress.Enemies; +using NightwatchFortress.Player; +using NightwatchFortress.Weapons; +using UnityEngine; + +namespace NightwatchFortress.UI +{ + public class HUDController : MonoBehaviour + { + [SerializeField] private PlayerVitals playerVitals; + [SerializeField] private WeaponInventory weaponInventory; + [SerializeField] private EnemyBase observedEnemy; + [SerializeField] private string currentObjective = "狼森を進み、現地武器で夜獣を討つ"; + + private GUIStyle labelStyle; + private GUIStyle smallStyle; + + public void Bind(PlayerVitals vitals, WeaponInventory inventory) + { + playerVitals = vitals; + weaponInventory = inventory; + } + + public void ObserveEnemy(EnemyBase enemy) + { + observedEnemy = enemy; + } + + private void OnGUI() + { + EnsureStyles(); + DrawVitals(); + DrawWeapons(); + DrawPickupPrompt(); + DrawEnemy(); + DrawObjective(); + } + + private void EnsureStyles() + { + if (labelStyle != null) + { + return; + } + + labelStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 18, + fontStyle = FontStyle.Bold, + normal = { textColor = new Color(0.95f, 0.9f, 0.78f, 1f) } + }; + + smallStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 13, + normal = { textColor = new Color(0.82f, 0.78f, 0.68f, 1f) } + }; + } + + private void DrawVitals() + { + Rect frame = new Rect(22f, 18f, 330f, 86f); + DrawPanel(frame, new Color(0.045f, 0.035f, 0.026f, 0.9f), new Color(0.85f, 0.45f, 0.16f, 0.9f)); + GUI.Label(new Rect(frame.x + 16f, frame.y + 8f, 250f, 22f), "夜番", labelStyle); + float hp01 = playerVitals != null ? Mathf.Clamp01((float)playerVitals.Hp / playerVitals.MaxHp) : 1f; + float stamina01 = playerVitals != null ? Mathf.Clamp01(playerVitals.Stamina / playerVitals.MaxStamina) : 1f; + DrawBar(new Rect(frame.x + 16f, frame.y + 40f, 292f, 12f), hp01, new Color(0.68f, 0.03f, 0.025f, 1f), Color.black); + DrawBar(new Rect(frame.x + 16f, frame.y + 60f, 292f, 10f), stamina01, new Color(0.55f, 0.78f, 0.32f, 1f), Color.black); + } + + private void DrawWeapons() + { + Rect frame = new Rect(22f, Screen.height - 126f, 392f, 96f); + DrawPanel(frame, new Color(0.034f, 0.034f, 0.04f, 0.92f), new Color(0.38f, 0.43f, 0.45f, 0.95f)); + GUI.Label(new Rect(frame.x + 16f, frame.y + 8f, 180f, 22f), "現地武器", labelStyle); + + DrawWeaponSlot(new Rect(frame.x + 16f, frame.y + 42f, 172f, 36f), weaponInventory != null ? weaponInventory.Primary : null, weaponInventory != null && weaponInventory.ActiveSlot == 0); + DrawWeaponSlot(new Rect(frame.x + 204f, frame.y + 42f, 172f, 36f), weaponInventory != null ? weaponInventory.Secondary : null, weaponInventory != null && weaponInventory.ActiveSlot == 1); + } + + private void DrawWeaponSlot(Rect rect, WeaponInstance weapon, bool active) + { + Color border = active ? new Color(1f, 0.68f, 0.18f, 1f) : new Color(0.32f, 0.31f, 0.28f, 1f); + DrawPanel(rect, new Color(0.10f, 0.09f, 0.08f, 0.94f), border); + string text = weapon == null ? "空き" : $"{weapon.DisplayName} / {WeaponUtility.GetDurabilityName(weapon.DurabilityState)}"; + GUI.Label(new Rect(rect.x + 9f, rect.y + 8f, rect.width - 16f, 20f), text, smallStyle); + } + + private void DrawPickupPrompt() + { + if (weaponInventory == null) + { + return; + } + + WeaponPickup pickup = weaponInventory.FindNearestPickup(); + if (pickup == null) + { + return; + } + + Rect frame = new Rect((Screen.width - 320f) * 0.5f, Screen.height - 184f, 320f, 42f); + DrawPanel(frame, new Color(0.05f, 0.035f, 0.02f, 0.88f), new Color(1f, 0.62f, 0.18f, 0.95f)); + GUI.Label(new Rect(frame.x + 14f, frame.y + 10f, 292f, 20f), $"E: 拾う {pickup.GetPrompt()}", labelStyle); + } + + private void DrawEnemy() + { + if (observedEnemy == null || observedEnemy.IsDead) + { + return; + } + + Rect frame = new Rect((Screen.width - 430f) * 0.5f, 22f, 430f, 58f); + DrawPanel(frame, new Color(0.035f, 0.025f, 0.022f, 0.86f), new Color(0.62f, 0.14f, 0.12f, 0.9f)); + GUI.Label(new Rect(frame.x + 16f, frame.y + 7f, 280f, 20f), observedEnemy.EnemyName, labelStyle); + DrawBar(new Rect(frame.x + 16f, frame.y + 34f, 190f, 10f), Mathf.Clamp01((float)observedEnemy.Hp / observedEnemy.MaxHp), new Color(0.7f, 0.04f, 0.035f, 1f), Color.black); + DrawBar(new Rect(frame.x + 224f, frame.y + 34f, 190f, 10f), Mathf.Clamp01(observedEnemy.Posture / observedEnemy.MaxPosture), new Color(0.86f, 0.69f, 0.28f, 1f), Color.black); + } + + private void DrawObjective() + { + Rect frame = new Rect(Screen.width - 394f, 18f, 366f, 54f); + DrawPanel(frame, new Color(0.025f, 0.028f, 0.035f, 0.76f), new Color(0.15f, 0.32f, 0.54f, 0.8f)); + GUI.Label(new Rect(frame.x + 14f, frame.y + 8f, 338f, 20f), "目的", smallStyle); + GUI.Label(new Rect(frame.x + 14f, frame.y + 26f, 338f, 20f), currentObjective, smallStyle); + } + + private void DrawBar(Rect rect, float value, Color fill, Color back) + { + GUI.color = back; + GUI.DrawTexture(rect, Texture2D.whiteTexture); + GUI.color = fill; + GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width * Mathf.Clamp01(value), rect.height), Texture2D.whiteTexture); + GUI.color = Color.white; + } + + private void DrawPanel(Rect rect, Color fill, Color border) + { + GUI.color = fill; + GUI.DrawTexture(rect, Texture2D.whiteTexture); + GUI.color = border; + GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, 2f), Texture2D.whiteTexture); + GUI.DrawTexture(new Rect(rect.x, rect.yMax - 2f, rect.width, 2f), Texture2D.whiteTexture); + GUI.DrawTexture(new Rect(rect.x, rect.y, 2f, rect.height), Texture2D.whiteTexture); + GUI.DrawTexture(new Rect(rect.xMax - 2f, rect.y, 2f, rect.height), Texture2D.whiteTexture); + GUI.color = Color.white; + } + } +} diff --git a/Assets/Scripts/UI/HUDController.cs.meta b/Assets/Scripts/UI/HUDController.cs.meta new file mode 100644 index 0000000..c15fd1c --- /dev/null +++ b/Assets/Scripts/UI/HUDController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 20af36db5e1749c48ad2413c0c2dd8f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons.meta b/Assets/Scripts/Weapons.meta new file mode 100644 index 0000000..d400652 --- /dev/null +++ b/Assets/Scripts/Weapons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a98707a9d9c8420bb9e2d7d31e81603f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs b/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs new file mode 100644 index 0000000..52b7a40 --- /dev/null +++ b/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs @@ -0,0 +1,58 @@ +using NightwatchFortress.Combat; +using NightwatchFortress.Enemies; +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + public class EmbeddedWeaponTarget : MonoBehaviour + { + [SerializeField] private string partName = "脚"; + [SerializeField] private float pullPostureDamage = 30f; + [SerializeField] private Transform embedRoot; + [SerializeField] private WeaponInstance embeddedWeapon; + + public string PartName => partName; + public WeaponInstance EmbeddedWeapon => embeddedWeapon; + public bool HasEmbeddedWeapon => embeddedWeapon != null; + public float PullPostureDamage => pullPostureDamage; + + private void Awake() + { + if (embedRoot == null) + { + embedRoot = transform; + } + } + + public bool TryEmbed(WeaponInstance weapon, Vector3 hitPoint, Vector3 direction) + { + if (weapon == null || !weapon.CanEmbed || embeddedWeapon != null) + { + return false; + } + + embeddedWeapon = weapon; + weapon.Embed(embedRoot, hitPoint, direction); + return true; + } + + public WeaponInstance PullWeapon(Vector3 dropPosition) + { + WeaponInstance pulled = embeddedWeapon; + if (pulled == null) + { + return null; + } + + embeddedWeapon = null; + EnemyBase enemy = GetComponentInParent(); + if (enemy != null) + { + enemy.TakeHit(new CombatHit(0, pullPostureDamage, pulled.WeaponType, transform.position, transform.forward, 0f, false)); + } + + pulled.PullFree(dropPosition); + return pulled; + } + } +} diff --git a/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs.meta b/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs.meta new file mode 100644 index 0000000..de6873e --- /dev/null +++ b/Assets/Scripts/Weapons/EmbeddedWeaponTarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9da1375dbe7548c69c41fb16d9d43a55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/ThrownWeaponImpact.cs b/Assets/Scripts/Weapons/ThrownWeaponImpact.cs new file mode 100644 index 0000000..0ffe89d --- /dev/null +++ b/Assets/Scripts/Weapons/ThrownWeaponImpact.cs @@ -0,0 +1,55 @@ +using NightwatchFortress.Combat; +using NightwatchFortress.Enemies; +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + [RequireComponent(typeof(WeaponInstance))] + public class ThrownWeaponImpact : MonoBehaviour + { + [SerializeField] private float minimumImpactSpeed = 2.6f; + [SerializeField] private float embedSpeedThreshold = 5.0f; + [SerializeField] private float knockback = 0.24f; + + private WeaponInstance weapon; + private Rigidbody body; + + private void Awake() + { + weapon = GetComponent(); + body = GetComponent(); + } + + private void OnCollisionEnter(Collision collision) + { + if (weapon == null || weapon.IsEquipped || weapon.IsEmbedded || body == null) + { + return; + } + + Vector3 linearVelocity = body.linearVelocity; + float speed = linearVelocity.magnitude; + if (speed < minimumImpactSpeed) + { + return; + } + + Vector3 point = collision.contactCount > 0 ? collision.GetContact(0).point : transform.position; + Vector3 direction = linearVelocity.sqrMagnitude > 0.001f ? linearVelocity.normalized : transform.forward; + + EnemyBase enemy = collision.collider.GetComponentInParent(); + if (enemy != null) + { + enemy.TakeHit(new CombatHit(weapon.GetThrowDamage(), weapon.PostureDamage * 1.4f, weapon.WeaponType, point, direction, knockback, true)); + } + + EmbeddedWeaponTarget target = collision.collider.GetComponentInParent(); + if (target != null && speed >= embedSpeedThreshold && target.TryEmbed(weapon, point, direction)) + { + return; + } + + weapon.ApplyUseWear(0.18f); + } + } +} diff --git a/Assets/Scripts/Weapons/ThrownWeaponImpact.cs.meta b/Assets/Scripts/Weapons/ThrownWeaponImpact.cs.meta new file mode 100644 index 0000000..609aa21 --- /dev/null +++ b/Assets/Scripts/Weapons/ThrownWeaponImpact.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 86f25737cc834fc1bc86239aa59b7277 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponDurabilityState.cs b/Assets/Scripts/Weapons/WeaponDurabilityState.cs new file mode 100644 index 0000000..5fb3815 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponDurabilityState.cs @@ -0,0 +1,11 @@ +namespace NightwatchFortress.Weapons +{ + public enum WeaponDurabilityState + { + Fresh = 0, + Normal = 1, + Chipped = 2, + NearBroken = 3, + Broken = 4 + } +} diff --git a/Assets/Scripts/Weapons/WeaponDurabilityState.cs.meta b/Assets/Scripts/Weapons/WeaponDurabilityState.cs.meta new file mode 100644 index 0000000..211b83e --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponDurabilityState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70979302be3641919d4df0d336bffcb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponInstance.cs b/Assets/Scripts/Weapons/WeaponInstance.cs new file mode 100644 index 0000000..c38126b --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponInstance.cs @@ -0,0 +1,251 @@ +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + [DisallowMultipleComponent] + public class WeaponInstance : MonoBehaviour + { + [SerializeField] private WeaponType weaponType = WeaponType.OneHandedSword; + [SerializeField] private WeaponDurabilityState durabilityState = WeaponDurabilityState.Normal; + [SerializeField] private float durability = 1f; + [SerializeField] private int damage = 18; + [SerializeField] private float postureDamage = 12f; + [SerializeField] private float throwDamageMultiplier = 1.4f; + [SerializeField] private bool embedded; + [SerializeField] private bool equipped; + [SerializeField] private Renderer visualRenderer; + [SerializeField] private Rigidbody body; + [SerializeField] private Collider pickupCollider; + [SerializeField] private Collider[] weaponColliders; + + public WeaponType WeaponType => weaponType; + public WeaponDurabilityState DurabilityState => durabilityState; + public float Durability01 => durability; + public int Damage => Mathf.RoundToInt(damage * GetDurabilityDamageFactor()); + public float PostureDamage => postureDamage * GetDurabilityPostureFactor(); + public bool IsEmbedded => embedded; + public bool IsEquipped => equipped; + public bool IsBroken => durabilityState == WeaponDurabilityState.Broken; + public bool CanThrow => WeaponUtility.CanThrow(weaponType) && !IsBroken; + public bool CanEmbed => WeaponUtility.CanEmbed(weaponType) && !IsBroken; + public string DisplayName => WeaponUtility.GetDisplayName(weaponType); + + private void Awake() + { + CacheComponents(); + ApplyVisuals(); + } + + public void Configure(WeaponType type, WeaponDurabilityState state, int baseDamage, float basePostureDamage) + { + weaponType = type; + durabilityState = state; + durability = StateToDurability(state); + damage = Mathf.Max(1, baseDamage); + postureDamage = Mathf.Max(1f, basePostureDamage); + CacheComponents(); + ApplyVisuals(); + } + + public void SetEquipped(Transform parent, Vector3 localPosition, Quaternion localRotation) + { + embedded = false; + equipped = true; + transform.SetParent(parent, false); + transform.localPosition = localPosition; + transform.localRotation = localRotation; + SetPhysics(false); + } + + public void Drop(Vector3 position, Vector3 impulse) + { + embedded = false; + equipped = false; + transform.SetParent(null, true); + transform.position = position; + SetPhysics(true); + if (body != null) + { + body.linearVelocity = impulse; + } + } + + public void Throw(Vector3 origin, Vector3 direction, float speed) + { + embedded = false; + equipped = false; + transform.SetParent(null, true); + transform.position = origin; + transform.rotation = Quaternion.LookRotation(direction.normalized, Vector3.up); + SetPhysics(true); + if (body != null) + { + body.linearVelocity = direction.normalized * speed; + body.angularVelocity = Vector3.Cross(Vector3.up, direction.normalized) * 8f; + } + } + + public void Embed(Transform target, Vector3 worldPosition, Vector3 direction) + { + embedded = true; + equipped = false; + transform.SetParent(target, true); + transform.position = worldPosition; + transform.rotation = Quaternion.LookRotation(direction.normalized, Vector3.up); + SetPhysics(false); + } + + public void PullFree(Vector3 dropPosition) + { + embedded = false; + transform.SetParent(null, true); + Drop(dropPosition, Vector3.up * 1.6f); + } + + public void ApplyUseWear(float amount) + { + if (IsBroken || amount <= 0f) + { + return; + } + + durability = Mathf.Clamp01(durability - amount); + durabilityState = DurabilityToState(durability); + ApplyVisuals(); + } + + public int GetThrowDamage() + { + float nearBrokenBonus = durabilityState == WeaponDurabilityState.NearBroken ? 1.65f : 1f; + return Mathf.RoundToInt(Damage * throwDamageMultiplier * nearBrokenBonus); + } + + private void CacheComponents() + { + if (visualRenderer == null) + { + visualRenderer = GetComponentInChildren(); + } + + if (body == null) + { + body = GetComponent(); + } + + if (pickupCollider == null) + { + pickupCollider = GetComponent(); + } + + if (weaponColliders == null || weaponColliders.Length == 0) + { + weaponColliders = GetComponentsInChildren(true); + } + } + + private void SetPhysics(bool active) + { + CacheComponents(); + if (body != null) + { + body.isKinematic = !active; + body.useGravity = active; + } + + if (weaponColliders != null) + { + for (int i = 0; i < weaponColliders.Length; i++) + { + if (weaponColliders[i] == null) + { + continue; + } + + weaponColliders[i].enabled = active; + weaponColliders[i].isTrigger = false; + } + } + } + + private void ApplyVisuals() + { + CacheComponents(); + if (visualRenderer == null) + { + return; + } + + Color baseColor = WeaponUtility.GetWeaponColor(weaponType); + Color wearColor = Color.Lerp(baseColor, new Color(0.12f, 0.10f, 0.09f, 1f), 1f - durability); + visualRenderer.material.color = durabilityState == WeaponDurabilityState.Broken + ? new Color(0.08f, 0.08f, 0.08f, 1f) + : wearColor; + } + + private float GetDurabilityDamageFactor() + { + switch (durabilityState) + { + case WeaponDurabilityState.Fresh: + return 1.12f; + case WeaponDurabilityState.Chipped: + return 0.82f; + case WeaponDurabilityState.NearBroken: + return 0.58f; + case WeaponDurabilityState.Broken: + return 0.12f; + default: + return 1f; + } + } + + private float GetDurabilityPostureFactor() + { + return durabilityState == WeaponDurabilityState.Broken ? 0.15f : 1f; + } + + private static float StateToDurability(WeaponDurabilityState state) + { + switch (state) + { + case WeaponDurabilityState.Fresh: + return 1f; + case WeaponDurabilityState.Normal: + return 0.78f; + case WeaponDurabilityState.Chipped: + return 0.48f; + case WeaponDurabilityState.NearBroken: + return 0.22f; + case WeaponDurabilityState.Broken: + return 0f; + default: + return 0.78f; + } + } + + private static WeaponDurabilityState DurabilityToState(float value) + { + if (value <= 0.02f) + { + return WeaponDurabilityState.Broken; + } + + if (value <= 0.28f) + { + return WeaponDurabilityState.NearBroken; + } + + if (value <= 0.58f) + { + return WeaponDurabilityState.Chipped; + } + + if (value < 0.92f) + { + return WeaponDurabilityState.Normal; + } + + return WeaponDurabilityState.Fresh; + } + } +} diff --git a/Assets/Scripts/Weapons/WeaponInstance.cs.meta b/Assets/Scripts/Weapons/WeaponInstance.cs.meta new file mode 100644 index 0000000..fc61999 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponInstance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f81016d56e124b7b84b4c2cc9f1b873d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponInventory.cs b/Assets/Scripts/Weapons/WeaponInventory.cs new file mode 100644 index 0000000..ce0ffeb --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponInventory.cs @@ -0,0 +1,214 @@ +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + public class WeaponInventory : MonoBehaviour + { + [SerializeField] private Transform mainHand; + [SerializeField] private Transform offHand; + [SerializeField] private WeaponInstance primary; + [SerializeField] private WeaponInstance secondary; + [SerializeField] private int activeSlot; + [SerializeField] private float pickupRadius = 1.65f; + [SerializeField] private float throwSpeed = 14f; + [SerializeField] private LayerMask pickupMask = ~0; + + public WeaponInstance ActiveWeapon => activeSlot == 0 ? primary : secondary; + public WeaponInstance Primary => primary; + public WeaponInstance Secondary => secondary; + public int ActiveSlot => activeSlot; + public float PickupRadius => pickupRadius; + + private void Awake() + { + EnsureHolders(); + EquipExisting(primary, 0); + EquipExisting(secondary, 1); + } + + public void EnsureHolders() + { + if (mainHand == null) + { + mainHand = new GameObject("main weapon holder").transform; + mainHand.SetParent(transform, false); + mainHand.localPosition = new Vector3(0.42f, 1.05f, 0.45f); + } + + if (offHand == null) + { + offHand = new GameObject("secondary weapon holder").transform; + offHand.SetParent(transform, false); + offHand.localPosition = new Vector3(-0.48f, 1.2f, -0.25f); + } + } + + public void SetInitialWeapons(WeaponInstance first, WeaponInstance second) + { + primary = first; + secondary = second; + activeSlot = 0; + EnsureHolders(); + EquipExisting(primary, 0); + EquipExisting(secondary, 1); + } + + public void SwapActiveSlot() + { + activeSlot = activeSlot == 0 ? 1 : 0; + EquipExisting(primary, 0); + EquipExisting(secondary, 1); + } + + public bool TryPickupNearest() + { + WeaponPickup pickup = FindNearestPickup(); + if (pickup == null || pickup.Weapon == null) + { + return false; + } + + Pickup(pickup.Weapon); + return true; + } + + public WeaponPickup FindNearestPickup() + { + Collider[] hits = Physics.OverlapSphere(transform.position + Vector3.up * 0.6f, pickupRadius, pickupMask, QueryTriggerInteraction.Collide); + WeaponPickup best = null; + float bestDistance = float.MaxValue; + foreach (Collider hit in hits) + { + WeaponPickup pickup = hit.GetComponentInParent(); + if (pickup == null || pickup.Weapon == null || pickup.Weapon.IsEquipped || pickup.Weapon.IsEmbedded) + { + continue; + } + + float distance = (pickup.transform.position - transform.position).sqrMagnitude; + if (distance < bestDistance) + { + best = pickup; + bestDistance = distance; + } + } + + return best; + } + + public void Pickup(WeaponInstance weapon) + { + if (weapon == null) + { + return; + } + + if (ActiveWeapon != null) + { + DropActive(); + } + + if (activeSlot == 0) + { + primary = weapon; + } + else + { + secondary = weapon; + } + + EquipExisting(weapon, activeSlot); + } + + public bool DropActive() + { + WeaponInstance weapon = ActiveWeapon; + if (weapon == null) + { + return false; + } + + Vector3 dropPosition = transform.position + transform.forward * 0.85f + Vector3.up * 0.55f; + weapon.Drop(dropPosition, (transform.forward * 2f) + Vector3.up * 1.2f); + ClearActive(); + return true; + } + + public bool ThrowActive(Vector3 direction) + { + WeaponInstance weapon = ActiveWeapon; + if (weapon == null || !weapon.CanThrow) + { + return false; + } + + Vector3 origin = transform.position + Vector3.up * 1.2f + direction.normalized * 0.65f; + weapon.Throw(origin, direction.normalized, throwSpeed); + weapon.ApplyUseWear(weapon.DurabilityState == WeaponDurabilityState.NearBroken ? 0.28f : 0.16f); + ClearActive(); + return true; + } + + public bool TryPullEmbeddedNearby() + { + Collider[] hits = Physics.OverlapSphere(transform.position + Vector3.up * 0.8f, pickupRadius, pickupMask, QueryTriggerInteraction.Collide); + EmbeddedWeaponTarget best = null; + float bestDistance = float.MaxValue; + foreach (Collider hit in hits) + { + EmbeddedWeaponTarget target = hit.GetComponentInParent(); + if (target == null || !target.HasEmbeddedWeapon) + { + continue; + } + + float distance = (target.transform.position - transform.position).sqrMagnitude; + if (distance < bestDistance) + { + best = target; + bestDistance = distance; + } + } + + if (best == null) + { + return false; + } + + WeaponInstance pulled = best.PullWeapon(transform.position + transform.forward * 0.8f + Vector3.up * 0.4f); + if (pulled != null) + { + Pickup(pulled); + return true; + } + + return false; + } + + private void EquipExisting(WeaponInstance weapon, int slot) + { + if (weapon == null) + { + return; + } + + Transform holder = slot == activeSlot ? mainHand : offHand; + Vector3 localPosition = slot == activeSlot ? Vector3.zero : Vector3.zero; + Quaternion localRotation = slot == activeSlot ? Quaternion.identity : Quaternion.Euler(0f, -25f, 55f); + weapon.SetEquipped(holder, localPosition, localRotation); + weapon.gameObject.SetActive(true); + } + + private void ClearActive() + { + if (activeSlot == 0) + { + primary = null; + } + else + { + secondary = null; + } + } + } +} diff --git a/Assets/Scripts/Weapons/WeaponInventory.cs.meta b/Assets/Scripts/Weapons/WeaponInventory.cs.meta new file mode 100644 index 0000000..1df5b7e --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponInventory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6987ff869dab4c4e8a5e72818d12b901 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponPickup.cs b/Assets/Scripts/Weapons/WeaponPickup.cs new file mode 100644 index 0000000..4a46d22 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponPickup.cs @@ -0,0 +1,39 @@ +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + [DisallowMultipleComponent] + [RequireComponent(typeof(WeaponInstance))] + public class WeaponPickup : MonoBehaviour + { + [SerializeField] private float promptRadius = 1.55f; + [SerializeField] private bool rackWeapon = false; + + private WeaponInstance weapon; + + public WeaponInstance Weapon => weapon; + public float PromptRadius => promptRadius; + public bool IsRackWeapon => rackWeapon; + + private void Awake() + { + weapon = GetComponent(); + } + + public string GetPrompt() + { + if (weapon == null) + { + weapon = GetComponent(); + } + + return $"{weapon.DisplayName} / {WeaponUtility.GetDurabilityName(weapon.DurabilityState)}"; + } + + private void OnDrawGizmosSelected() + { + Gizmos.color = rackWeapon ? new Color(1f, 0.65f, 0.16f, 1f) : new Color(0.45f, 0.75f, 1f, 1f); + Gizmos.DrawWireSphere(transform.position, promptRadius); + } + } +} diff --git a/Assets/Scripts/Weapons/WeaponPickup.cs.meta b/Assets/Scripts/Weapons/WeaponPickup.cs.meta new file mode 100644 index 0000000..8682d19 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponPickup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfb0db76e0e3440dadcb198ac2f22f27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponType.cs b/Assets/Scripts/Weapons/WeaponType.cs new file mode 100644 index 0000000..1435daf --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponType.cs @@ -0,0 +1,16 @@ +namespace NightwatchFortress.Weapons +{ + public enum WeaponType + { + None = 0, + OneHandedSword = 1, + Spear = 2, + GreatHammer = 3, + Bow = 4, + Torch = 5, + LargeShield = 6, + Dagger = 7, + OneHandedAxe = 8, + Greatsword = 9 + } +} diff --git a/Assets/Scripts/Weapons/WeaponType.cs.meta b/Assets/Scripts/Weapons/WeaponType.cs.meta new file mode 100644 index 0000000..3cf6314 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79cd41a760354d31b561655d44c7802f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Weapons/WeaponUtility.cs b/Assets/Scripts/Weapons/WeaponUtility.cs new file mode 100644 index 0000000..d5a9772 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponUtility.cs @@ -0,0 +1,103 @@ +using UnityEngine; + +namespace NightwatchFortress.Weapons +{ + public static class WeaponUtility + { + public static string GetDisplayName(WeaponType type) + { + switch (type) + { + case WeaponType.OneHandedSword: + return "片手剣"; + case WeaponType.Spear: + return "槍"; + case WeaponType.GreatHammer: + return "大槌"; + case WeaponType.Bow: + return "弓"; + case WeaponType.Torch: + return "松明"; + case WeaponType.LargeShield: + return "大盾"; + case WeaponType.Dagger: + return "短剣"; + case WeaponType.OneHandedAxe: + return "片手斧"; + case WeaponType.Greatsword: + return "大剣"; + default: + return "素手"; + } + } + + public static string GetDurabilityName(WeaponDurabilityState state) + { + switch (state) + { + case WeaponDurabilityState.Fresh: + return "新品"; + case WeaponDurabilityState.Normal: + return "通常"; + case WeaponDurabilityState.Chipped: + return "欠け"; + case WeaponDurabilityState.NearBroken: + return "壊れかけ"; + case WeaponDurabilityState.Broken: + return "破損"; + default: + return "-"; + } + } + + public static Color GetWeaponColor(WeaponType type) + { + switch (type) + { + case WeaponType.Spear: + return new Color(0.68f, 0.72f, 0.58f, 1f); + case WeaponType.GreatHammer: + return new Color(0.44f, 0.42f, 0.38f, 1f); + case WeaponType.Bow: + return new Color(0.48f, 0.28f, 0.12f, 1f); + case WeaponType.Torch: + return new Color(1f, 0.32f, 0.08f, 1f); + case WeaponType.LargeShield: + return new Color(0.22f, 0.26f, 0.30f, 1f); + default: + return new Color(0.72f, 0.74f, 0.72f, 1f); + } + } + + public static bool CanThrow(WeaponType type) + { + return type == WeaponType.OneHandedSword || + type == WeaponType.Spear || + type == WeaponType.Dagger || + type == WeaponType.OneHandedAxe || + type == WeaponType.Torch; + } + + public static bool CanEmbed(WeaponType type) + { + return type == WeaponType.Spear || + type == WeaponType.Dagger || + type == WeaponType.OneHandedAxe || + type == WeaponType.OneHandedSword; + } + + public static bool CanGuard(WeaponType type) + { + return type == WeaponType.OneHandedSword || + type == WeaponType.LargeShield || + type == WeaponType.Greatsword; + } + + public static bool CanParry(WeaponType type) + { + return type == WeaponType.OneHandedSword || + type == WeaponType.Dagger || + type == WeaponType.LargeShield; + } + } +} diff --git a/Assets/Scripts/Weapons/WeaponUtility.cs.meta b/Assets/Scripts/Weapons/WeaponUtility.cs.meta new file mode 100644 index 0000000..5aef762 --- /dev/null +++ b/Assets/Scripts/Weapons/WeaponUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89e9e97097d54f5385bf418c93511f23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json new file mode 100644 index 0000000..ade6af3 --- /dev/null +++ b/Packages/manifest.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.ui": "1.0.0" + } +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json new file mode 100644 index 0000000..ab28fe5 --- /dev/null +++ b/Packages/packages-lock.json @@ -0,0 +1,54 @@ +{ + "dependencies": { + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + } + } +} diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..50b4625 --- /dev/null +++ b/ProjectSettings/AudioManager.asset @@ -0,0 +1,23 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_EnableOutputSuspension: 1 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 0 + m_AudioFoundation: 0 + m_OutputChannelLayout: 2 + m_OutputSamplingRate: 48000 diff --git a/ProjectSettings/ClusterInputManager.asset b/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..e7886b2 --- /dev/null +++ b/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..3c102d1 --- /dev/null +++ b/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,45 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 23 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_DefaultMaxDepenetrationVelocity: 10 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0.1 + m_ClothInterCollisionStiffness: 0.2 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_SimulationMode: 0 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_InvokeCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ClothGravity: {x: 0, y: -9.81, z: 0} + m_ContactPairsMode: 0 + m_BroadphaseType: 2 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 256, y: 256, z: 256} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_ImprovedPatchFriction: 0 + m_GenerateOnTriggerStayEvents: 1 + m_SolverType: 0 + m_DefaultMaxAngularSpeed: 50 + m_ScratchBufferChunkCount: 4 + m_CurrentBackendId: 4072204805 + m_FastMotionThreshold: 3.4028235e+38 + m_SceneBuffersReleaseInterval: 0 + m_ReleaseSceneBuffers: 0 + m_LogVerbosity: 3 + m_IncrementalStaticBroadphase: 1 diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..7df81c6 --- /dev/null +++ b/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: [] + m_configObjects: {} + m_UseUCBPForAssetBundles: 0 diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..cdbdc12 --- /dev/null +++ b/ProjectSettings/EditorSettings.asset @@ -0,0 +1,50 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 15 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 1 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerCacheSize: 10 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;asmref;rsp;java;cpp;c;mm;m;h + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 + m_AsyncShaderCompilation: 1 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 0 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 1 + m_DisableCookiesInLightmapper: 0 + m_ShadowmaskStitching: 1 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0 + m_ReferencedClipsExactNaming: 1 + m_ForceAssetUnloadAndGCOnSceneLoad: 1 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..9c8f1b0 --- /dev/null +++ b/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,61 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 16 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 0} + m_VideoShadersIncludeMode: 2 + m_AlwaysIncludedShaders: [] + m_PreloadedShaders: [] + m_PreloadShadersBatchTimeLimit: -1 + m_SpritesDefaultMaterial: {fileID: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_BrgStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_RenderPipelineGlobalSettingsMap: {} + m_ShaderBuildSettings: + keywordDeclarationOverrides: [] + m_LightsUseLinearIntensity: 0 + m_LightsUseColorTemperature: 0 + m_LogWhenShaderIsCompiled: 0 + m_LightProbeOutsideHullStrategy: 1 + m_CameraRelativeLightCulling: 0 + m_CameraRelativeShadowCulling: 0 diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..8068b20 --- /dev/null +++ b/ProjectSettings/InputManager.asset @@ -0,0 +1,296 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + m_UsePhysicalKeys: 1 diff --git a/ProjectSettings/MemorySettings.asset b/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..5b5face --- /dev/null +++ b/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/ProjectSettings/MultiplayerManager.asset b/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 0000000..c19bcd7 --- /dev/null +++ b/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_EnablePlayModeLocalDeployment: 0 + m_EnablePlayModeRemoteDeployment: 0 + m_StrippingTypes: {} diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..2e2e369 --- /dev/null +++ b/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,93 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid diff --git a/ProjectSettings/Physics2DSettings.asset b/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..14f419f --- /dev/null +++ b/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,57 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_BounceThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_ContactThreshold: 0 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_SimulationMode: 0 + m_SimulationLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_MaxSubStepCount: 4 + m_MinSubStepFPS: 30 + m_UseSubStepping: 0 + m_UseSubStepContacts: 0 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 1 + m_AutoSyncTransforms: 0 + m_GizmoOptions: 10 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_PhysicsLowLevelSettings: {fileID: 0} diff --git a/ProjectSettings/PresetManager.asset b/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..67a94da --- /dev/null +++ b/ProjectSettings/PresetManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..073b06b --- /dev/null +++ b/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,933 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 28 + productGUID: ff4cd8427dd2a428683fd5ff58b544d9 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: RollNuts + productName: Nightwatch Fortress + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.12156863, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1920 + defaultScreenHeight: 1080 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 1 + androidDisplayOptions: 1 + androidBlitType: 0 + androidResizeableActivity: 1 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + androidApplicationEntry: 2 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 0 + meshDeformation: 0 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + metalUseMetalDisplayLink: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 + switchNVNGraphicsFirmwareMemory: 32 + switchGraphicsJobsSyncAfterKick: 1 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 0 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.4 + androidMinAspectRatio: 1 + applicationIdentifier: {} + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 0 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 25 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + AndroidPreferredDataLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + androidSplitApplicationBinary: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 15.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 15.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 00000000000000000000000000000000 + templatePackageId: + templateDefaultScene: + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidAllowedArchitectures: -1 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + androidAppCategory: 3 + useAndroidAppCategory: 1 + androidAppCategoryOther: + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 200 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: tvOS + m_Icons: + - m_Textures: [] + m_Width: 1280 + m_Height: 768 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 800 + m_Height: 480 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 400 + m_Height: 240 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 4640 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 2320 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 3840 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 1920 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + - m_BuildTarget: VisionOS + m_Icons: + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 0 + m_SubKind: + m_BuildTargetBatching: [] + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: [] + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: [] + m_BuildTargetVRSettings: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + VisionOS: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: [] + m_BuildTargetGroupHDRCubemapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 12.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 1 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: i8FUqAa7sGXvbgInZs09bWAgjePSzH7r + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 + webEnableSubmoduleStrippingCompatibility: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: {} + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: NightwatchFortress + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: Nightwatch Fortress + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 0 + embeddedLinuxEnableGamepadInput: 0 + hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: + apiCompatibilityLevel: 6 + captureStartupLogs: {} + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] + androidVulkanDeviceFilterListAsset: {fileID: 0} + d3d12DeviceFilterListAsset: {fileID: 0} + allowedHttpConnections: 3 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..3c6a0d3 --- /dev/null +++ b/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 6000.3.18f1 +m_EditorVersionWithRevision: 6000.3.18f1 (5ebeb53e4c07) diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..64f8aba --- /dev/null +++ b/ProjectSettings/QualitySettings.asset @@ -0,0 +1,347 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 5 + m_QualitySettings: + - serializedVersion: 5 + name: Very Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 1 + globalTextureMipmapLimit: 1 + textureMipmapLimitSettings: [] + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 25 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 0.3 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + - serializedVersion: 5 + name: Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 25 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 0.4 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 16 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + - serializedVersion: 5 + name: Medium + pixelLightCount: 1 + shadows: 1 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 1 + realtimeGICPUUsage: 25 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 0.7 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 64 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + - serializedVersion: 5 + name: High + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 1 + realtimeGICPUUsage: 50 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 1 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + - serializedVersion: 5 + name: Very High + pixelLightCount: 3 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 70 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 4 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 1 + realtimeGICPUUsage: 50 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 1.5 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 1024 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + - serializedVersion: 5 + name: Ultra + pixelLightCount: 4 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 4 + shadowDistance: 150 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 255 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 0 + adaptiveVsync: 0 + vSyncCount: 1 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 2 + meshLodThreshold: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4096 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 0} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: [] + m_TextureMipmapLimitGroupNames: [] + m_PerPlatformDefaultQuality: + Android: 2 + EmbeddedLinux: 5 + GameCoreScarlett: 5 + GameCoreXboxOne: 5 + Kepler: 5 + LinuxHeadlessSimulation: 5 + Nintendo Switch: 5 + Nintendo Switch 2: 5 + PS4: 5 + PS5: 5 + QNX: 5 + Server: 5 + Standalone: 5 + VisionOS: 5 + WebGL: 3 + Windows Store Apps: 5 + XboxOne: 5 + iPhone: 2 + tvOS: 2 diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..eb5d9ae --- /dev/null +++ b/ProjectSettings/TagManager.asset @@ -0,0 +1,45 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 3 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + m_RenderingLayers: + - Default diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..2e23a1f --- /dev/null +++ b/ProjectSettings/TimeManager.asset @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + Fixed Timestep: + m_Count: 2822399 + m_Rate: + m_Denominator: 1 + m_Numerator: 141120000 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..5ef5698 --- /dev/null +++ b/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,40 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + InsightsSettings: + m_EngineDiagnosticsEnabled: 0 + m_Enabled: 0 + CrashReportingSettings: + serializedVersion: 2 + m_EventUrl: https://perf-events.cloud.unity3d.com + m_EnableCloudDiagnosticsReporting: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/ProjectSettings/VFXManager.asset b/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..56783bb --- /dev/null +++ b/ProjectSettings/VFXManager.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_PrefixSumShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_EmptyShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + m_MaxScrubTime: 30 + m_MaxCapacity: 100000000 + m_CompiledVersion: 0 + m_RuntimeVersion: 0 + m_RuntimeResources: {fileID: 0} + m_BatchEmptyLifetime: 300 diff --git a/ProjectSettings/VersionControlSettings.asset b/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..979fd8e --- /dev/null +++ b/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_TrackPackagesOutsideProject: 0 diff --git a/README.md b/README.md index 6eb8f8c..4d27968 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,75 @@ -# ARPG Bakeoff +# 夜番の砦 Production -Public planning and review repo for a top-down dark fantasy ARPG / roguelite -prototype. +Public planning and review repo for a Steam-facing 3D solo dark fantasy action +RPG. This repository exists to keep the next work clean: - no inherited Unity mock implementation - no reuse of the first-person Unreal hotel project +- no placeholder-only visual proof - no new Maya/Meshy/API spend before written gates pass - every meaningful step happens through a pull request -- Veripsa Core can coordinate task slicing and program structure in PR comments +- review comments can coordinate task slicing and merge order ## Current Objective -Find the smallest path to a commercial-looking 30-second prototype: +Build the smallest commercial-quality vertical slice for `夜番の砦`: -1. readable playable hero -2. top-down combat camera -3. diagonal movement and attack flow -4. enemy density ramp -5. reward/build choice -6. screenshot and short-capture evidence +1. one readable player character, a practical `夜番` with leather armor, cloak, + lantern, weapon holders, gloves, boots, and a clear adventurer silhouette +2. third-person camera behind and above the player, readable for enemies, + weapons, footing, dodge direction, and pickups +3. fast, satisfying solo ARPG combat with attack, heavy attack, dodge, jump, + guard, parry, lock-on, item use, and interaction +4. the core hook: picking up, dropping, throwing, breaking, and swapping local + weapons during combat +5. weapon states: fresh, normal, chipped, near-broken, broken +6. weapon racks, dropped enemy weapons, embedded weapons, and boss-room weapon + supplies +7. first vertical-slice weapons: one-handed sword, spear, great hammer, bow, + torch, and large shield hooks +8. first area: `狼森`, with moonlit forest, hunter shack, fallen trees, stream, + weapon racks, torch stands, shortcut, and boss den +9. first enemies: small wolf, horned beast, goblin, shield goblin +10. first boss: `大狼ガルム`, with spear-into-leg stop, head stagger, tail + break, phase change, and defeat sequence +11. screenshots and 30-second capture that sell "one person hunting monsters at + night while surviving by changing weapons on the ground" + +The active product definition is +[`docs/product/game-definition-nightwatch-fortress.md`](docs/product/game-definition-nightwatch-fortress.md). ## Ground Rules -- Do not copy existing game characters, UI, logos, costumes, silhouettes, or - palettes. -- Use market references only for quality bars, readability, pacing, and store - presentation patterns. -- Prefer CC0/free assets before paid or API-generated assets. -- Use Maya as an inspection, cleanup, conversion, or rigging tool, not as a - reason to spend on throwaway assets. -- Keep generated assets isolated with provenance notes. +- Do not copy existing game characters, UI, logos, costumes, silhouettes, named + systems, bosses, maps, or weapon designs. +- Use references only for quality bars, readability, pacing, combat feel, color + language, and store presentation patterns. +- Prefer commercially legal free or already-owned assets before paid or + API-generated assets. +- Use Maya as an inspection, cleanup, conversion, rigging, or export tool, not + as a reason to spend on throwaway assets. +- Keep generated assets isolated with provenance notes and approval evidence. +- Screenshots and video cannot be accepted if they show gray boxes, default + floors, default skies, mannequin-only characters, debug UI, or unapproved + asset-store pileups. + +## Unity Project + +`ProjectSettings/ProjectVersion.txt` is present and pins the project to Unity +`6000.3.18f1`. + +`Packages/manifest.json` currently contains built-in Unity modules only. The +approval memo is +[`docs/production/package-approval-unity-foundation.md`](docs/production/package-approval-unity-foundation.md). + +The current playable foundation is generated inside Unity from: + +`Nightwatch Fortress > Build Wolf Forest Foundation Scene` +This creates `Assets/Scenes/WolfForestFoundation.unity`, a code-generated +moonlit `狼森` slice with player movement, local weapons, pickup/drop/throw, +basic enemy health/posture, weapon drops, an embedded-weapon boss target, HUD, +camera, lighting, torches, weapon rack, stream, hunter shack, and forest set +dressing. diff --git a/Unreal/NightwatchFortress/Config/DefaultEngine.ini b/Unreal/NightwatchFortress/Config/DefaultEngine.ini new file mode 100644 index 0000000..10464f0 --- /dev/null +++ b/Unreal/NightwatchFortress/Config/DefaultEngine.ini @@ -0,0 +1,17 @@ +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Game/Maps/WolfForestVisualSpike +GameDefaultMap=/Game/Maps/WolfForestVisualSpike +GlobalDefaultGameMode=/Script/Engine.GameModeBase + +[/Script/Engine.RendererSettings] +r.DefaultFeature.AutoExposure=False +r.DefaultFeature.MotionBlur=False +r.DefaultFeature.Bloom=True +r.DefaultFeature.AmbientOcclusion=True +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.Shadow.Virtual.Enable=1 + +[/Script/PythonScriptPlugin.PythonScriptPluginSettings] +bDeveloperMode=True +bRemoteExecution=False diff --git a/Unreal/NightwatchFortress/Config/DefaultGame.ini b/Unreal/NightwatchFortress/Config/DefaultGame.ini new file mode 100644 index 0000000..acf673d --- /dev/null +++ b/Unreal/NightwatchFortress/Config/DefaultGame.ini @@ -0,0 +1,6 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=02B08D4F48C74E6AB98F1F9EF58D5D5A +ProjectName=Nightwatch Fortress +CompanyName=RollNuts +Description=Solo 3D dark fantasy ARPG focused on picking up, throwing, breaking, and swapping local weapons during night hunts. +ProjectDisplayedTitle=NSLOCTEXT("[/Script/EngineSettings]", "NightwatchFortressTitle", "Nightwatch Fortress") diff --git a/Unreal/NightwatchFortress/Content/Maps/WolfForestVisualSpike.umap b/Unreal/NightwatchFortress/Content/Maps/WolfForestVisualSpike.umap new file mode 100644 index 0000000..5b66919 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Maps/WolfForestVisualSpike.umap differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_AmberPickupAndFire.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_AmberPickupAndFire.uasset new file mode 100644 index 0000000..40d90f5 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_AmberPickupAndFire.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_BrokenHunterWood.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_BrokenHunterWood.uasset new file mode 100644 index 0000000..de8c68d Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_BrokenHunterWood.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdMoonGlow.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdMoonGlow.uasset new file mode 100644 index 0000000..7840f41 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdMoonGlow.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdStreamWater.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdStreamWater.uasset new file mode 100644 index 0000000..9b52d0e Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_ColdStreamWater.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DarkCloak.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DarkCloak.uasset new file mode 100644 index 0000000..211f87d Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DarkCloak.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DeepForestLeaves.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DeepForestLeaves.uasset new file mode 100644 index 0000000..0999d7b Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_DeepForestLeaves.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmAmberEye.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmAmberEye.uasset new file mode 100644 index 0000000..c073d77 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmAmberEye.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmDarkHide.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmDarkHide.uasset new file mode 100644 index 0000000..a21c212 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmDarkHide.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmRedScar.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmRedScar.uasset new file mode 100644 index 0000000..8bcbe99 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_GarmRedScar.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_MoonlitBark.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_MoonlitBark.uasset new file mode 100644 index 0000000..9a05566 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_MoonlitBark.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_NightwatchLeather.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_NightwatchLeather.uasset new file mode 100644 index 0000000..a755a5a Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_NightwatchLeather.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WetForestGround.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WetForestGround.uasset new file mode 100644 index 0000000..dd2bbeb Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WetForestGround.uasset differ diff --git a/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WornIronEdge.uasset b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WornIronEdge.uasset new file mode 100644 index 0000000..5618945 Binary files /dev/null and b/Unreal/NightwatchFortress/Content/Nightwatch/Materials/M_WornIronEdge.uasset differ diff --git a/Unreal/NightwatchFortress/NightwatchFortress.uproject b/Unreal/NightwatchFortress/NightwatchFortress.uproject new file mode 100644 index 0000000..4f695b8 --- /dev/null +++ b/Unreal/NightwatchFortress/NightwatchFortress.uproject @@ -0,0 +1,25 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.8", + "Category": "Games", + "Description": "Nightwatch Fortress Unreal visual spike for a Steam-facing solo dark fantasy ARPG.", + "Modules": [], + "Plugins": [ + { + "Name": "PythonScriptPlugin", + "Enabled": true + }, + { + "Name": "EditorScriptingUtilities", + "Enabled": true + }, + { + "Name": "ModelingToolsEditorMode", + "Enabled": true + }, + { + "Name": "PCG", + "Enabled": false + } + ] +} diff --git a/Unreal/NightwatchFortress/Scripts/build_wolf_forest_visual_spike.py b/Unreal/NightwatchFortress/Scripts/build_wolf_forest_visual_spike.py new file mode 100644 index 0000000..958706d --- /dev/null +++ b/Unreal/NightwatchFortress/Scripts/build_wolf_forest_visual_spike.py @@ -0,0 +1,210 @@ +""" +Builds a first visual spike map for Nightwatch Fortress. + +Run from the project root with: + +"/Users/murakaminaoya/Epic Games/UE_5.8/Engine/Binaries/Mac/UnrealEditor-Cmd" \ + "/Users/murakaminaoya/Products/arpg-bakeoff/Unreal/NightwatchFortress/NightwatchFortress.uproject" \ + -run=pythonscript -script="/Users/murakaminaoya/Products/arpg-bakeoff/Unreal/NightwatchFortress/Scripts/build_wolf_forest_visual_spike.py" \ + -unattended -nop4 -nosplash + +The script intentionally uses built-in engine meshes only. Its job is not final +art; it is to prove whether Unreal can deliver a stronger night-forest read +than the Unity primitive scene before paid/API asset spend. +""" + +import math +import unreal + + +MAP_PATH = "/Game/Maps/WolfForestVisualSpike" + + +def load_asset(path): + asset = unreal.EditorAssetLibrary.load_asset(path) + if asset is None: + raise RuntimeError(f"Missing engine asset: {path}") + return asset + + +CUBE = load_asset("/Engine/BasicShapes/Cube.Cube") +SPHERE = load_asset("/Engine/BasicShapes/Sphere.Sphere") +CYLINDER = load_asset("/Engine/BasicShapes/Cylinder.Cylinder") +CONE = load_asset("/Engine/BasicShapes/Cone.Cone") + + +def make_material(name, color, emissive=None, roughness=0.65, metallic=0.0): + package_path = "/Game/Nightwatch/Materials" + unreal.EditorAssetLibrary.make_directory(package_path) + asset_path = f"{package_path}/{name}" + existing = unreal.EditorAssetLibrary.load_asset(asset_path) + if existing: + return existing + + tools = unreal.AssetToolsHelpers.get_asset_tools() + material = tools.create_asset(name, package_path, unreal.Material, unreal.MaterialFactoryNew()) + material.set_editor_property("two_sided", True) + + base = unreal.MaterialEditingLibrary.create_material_expression(material, unreal.MaterialExpressionConstant4Vector, -420, -80) + base.constant = color + unreal.MaterialEditingLibrary.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR) + + rough = unreal.MaterialEditingLibrary.create_material_expression(material, unreal.MaterialExpressionConstant, -420, 120) + rough.r = roughness + unreal.MaterialEditingLibrary.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) + + metal = unreal.MaterialEditingLibrary.create_material_expression(material, unreal.MaterialExpressionConstant, -420, 220) + metal.r = metallic + unreal.MaterialEditingLibrary.connect_material_property(metal, "", unreal.MaterialProperty.MP_METALLIC) + + if emissive is not None: + emission = unreal.MaterialEditingLibrary.create_material_expression(material, unreal.MaterialExpressionConstant4Vector, -420, 20) + emission.constant = emissive + unreal.MaterialEditingLibrary.connect_material_property(emission, "", unreal.MaterialProperty.MP_EMISSIVE_COLOR) + + unreal.MaterialEditingLibrary.recompile_material(material) + unreal.EditorAssetLibrary.save_asset(asset_path) + return material + + +MAT = { + "ground": make_material("M_WetForestGround", unreal.LinearColor(0.025, 0.034, 0.025, 1.0), roughness=0.28), + "water": make_material("M_ColdStreamWater", unreal.LinearColor(0.02, 0.12, 0.18, 0.72), unreal.LinearColor(0.0, 0.05, 0.08, 1.0), roughness=0.18), + "bark": make_material("M_MoonlitBark", unreal.LinearColor(0.12, 0.07, 0.04, 1.0), roughness=0.7), + "leaves": make_material("M_DeepForestLeaves", unreal.LinearColor(0.015, 0.12, 0.045, 1.0), roughness=0.8), + "wood": make_material("M_BrokenHunterWood", unreal.LinearColor(0.21, 0.12, 0.055, 1.0), roughness=0.72), + "iron": make_material("M_WornIronEdge", unreal.LinearColor(0.62, 0.60, 0.55, 1.0), roughness=0.28, metallic=0.65), + "amber": make_material("M_AmberPickupAndFire", unreal.LinearColor(1.0, 0.38, 0.08, 1.0), unreal.LinearColor(3.2, 0.8, 0.08, 1.0), roughness=0.2), + "moon": make_material("M_ColdMoonGlow", unreal.LinearColor(0.48, 0.58, 0.85, 1.0), unreal.LinearColor(1.0, 1.25, 2.4, 1.0), roughness=0.12), + "leather": make_material("M_NightwatchLeather", unreal.LinearColor(0.115, 0.065, 0.038, 1.0), roughness=0.44), + "cloak": make_material("M_DarkCloak", unreal.LinearColor(0.018, 0.023, 0.032, 1.0), roughness=0.74), + "wolf": make_material("M_GarmDarkHide", unreal.LinearColor(0.025, 0.03, 0.032, 1.0), roughness=0.68), + "scar": make_material("M_GarmRedScar", unreal.LinearColor(0.72, 0.025, 0.015, 1.0), unreal.LinearColor(0.85, 0.02, 0.01, 1.0), roughness=0.38), + "eye": make_material("M_GarmAmberEye", unreal.LinearColor(1.0, 0.42, 0.04, 1.0), unreal.LinearColor(3.0, 0.7, 0.08, 1.0), roughness=0.18), +} + + +def actor(name, mesh, loc, scale, mat, rot=(0, 0, 0)): + # Avoid spawn_actor_from_object here. In UE 5.8 commandlet/nullrhi runs it + # routes through the placement subsystem and can crash without a viewport. + spawned = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(*loc), unreal.Rotator(*rot)) + spawned.set_actor_label(name) + spawned.set_actor_scale3d(unreal.Vector(*scale)) + comp = spawned.get_component_by_class(unreal.StaticMeshComponent) + if comp: + comp.set_static_mesh(mesh) + if mat: + comp.set_material(0, mat) + return spawned + + +def point_light(name, loc, color, intensity, radius): + light = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.PointLight, unreal.Vector(*loc)) + light.set_actor_label(name) + comp = light.get_component_by_class(unreal.PointLightComponent) + comp.set_editor_property("light_color", color) + comp.set_editor_property("intensity", intensity) + comp.set_editor_property("attenuation_radius", radius) + comp.set_editor_property("cast_shadows", True) + return light + + +def get_editor_world(): + editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem) + return editor_subsystem.get_editor_world() + + +def build_level(): + unreal.EditorLoadingAndSavingUtils.new_blank_map(False) + + actor("wet black forest floor", CUBE, (0, 0, -8), (18, 16, 0.12), MAT["ground"]) + actor("cold stream crossing", CUBE, (-330, 40, -2), (1.35, 9.8, 0.04), MAT["water"]) + actor("full moon disc", SPHERE, (-520, 560, 420), (0.95, 0.08, 0.95), MAT["moon"], rot=(0, 0, 0)) + actor("fallen tree shortcut", CYLINDER, (300, 100, 58), (0.42, 0.42, 3.25), MAT["wood"], rot=(0, 82, 0)) + + # Hunter shack silhouette. + actor("hunter shack dark body", CUBE, (520, 380, 95), (2.5, 2.1, 1.85), MAT["wood"]) + actor("hunter shack broken roof", CUBE, (520, 380, 222), (3.0, 2.55, 0.32), MAT["cloak"]) + actor("hunter shack broken rafter left", CYLINDER, (450, 350, 285), (0.08, 0.08, 1.35), MAT["wood"], rot=(54, -42, 0)) + actor("hunter shack broken rafter right", CYLINDER, (595, 350, 275), (0.08, 0.08, 1.15), MAT["wood"], rot=(50, 38, 0)) + point_light("hunter shack warm interior", (500, 280, 105), unreal.Color(255, 94, 18), 1700, 430) + + # Forest framing and treeline. + for i in range(34): + x = math.sin(i * 1.73) * 760 + y = math.cos(i * 2.19) * 690 + if abs(x) < 180 and y < -260: + x += 280 + height = 1.2 + (i % 4) * 0.26 + actor(f"tree trunk {i:02d}", CYLINDER, (x, y, 92 * height), (0.32, 0.32, height), MAT["bark"]) + actor(f"dark pine mass {i:02d}", CONE, (x, y, 250 + (i % 2) * 34), (1.0, 1.0, 1.4), MAT["leaves"]) + + # Wet path stones and stream banks. + for i in range(13): + y = -565 + i * 78 + x = math.sin(i * 1.4) * 36 + actor(f"wet stepping stone {i:02d}", CYLINDER, (x, y, 5), (0.42 + (i % 3) * 0.08, 0.28, 0.035), MAT["iron"], rot=(0, i * 17, 0)) + + # Player silhouette. + actor("PLAYER leather torso", SPHERE, (0, -575, 95), (0.45, 0.32, 0.9), MAT["leather"]) + actor("PLAYER dark split cloak", CUBE, (0, -610, 72), (0.72, 0.12, 1.18), MAT["cloak"]) + actor("PLAYER head and hood", SPHERE, (0, -575, 178), (0.34, 0.32, 0.38), MAT["cloak"]) + actor("PLAYER right sword arm", CYLINDER, (48, -535, 108), (0.12, 0.12, 0.62), MAT["leather"], rot=(42, 22, 0)) + actor("PLAYER shoulder lantern", CUBE, (-46, -538, 142), (0.18, 0.18, 0.24), MAT["amber"]) + point_light("player shoulder lantern light", (-54, -535, 150), unreal.Color(255, 126, 25), 1150, 360) + + # Weapon rack and immediate pickup. + actor("right weapon rack beam", CUBE, (330, -455, 86), (1.9, 0.14, 0.12), MAT["wood"]) + actor("right weapon rack left leg", CUBE, (246, -455, 42), (0.13, 0.13, 0.9), MAT["wood"]) + actor("right weapon rack right leg", CUBE, (414, -455, 42), (0.13, 0.13, 0.9), MAT["wood"]) + actor("pickup amber floor marker", CYLINDER, (-92, -505, 3), (0.55, 0.55, 0.012), MAT["amber"]) + actor("pickup spear shaft", CYLINDER, (-86, -505, 48), (0.045, 0.045, 1.55), MAT["wood"], rot=(90, -8, 0)) + actor("pickup spear head", CONE, (-124, -505, 91), (0.18, 0.18, 0.32), MAT["iron"], rot=(90, -8, 0)) + actor("rack great hammer head", CUBE, (332, -455, 132), (0.78, 0.32, 0.42), MAT["iron"]) + actor("rack shield face", CYLINDER, (414, -455, 70), (0.58, 0.58, 0.11), MAT["wood"], rot=(90, 0, 0)) + point_light("pickup weapon warm read", (-90, -505, 68), unreal.Color(255, 150, 35), 650, 220) + + # Enemies and Garm. + actor("small wolf pressure enemy", SPHERE, (180, -60, 48), (0.45, 0.32, 0.36), MAT["wolf"]) + actor("weapon goblin silhouette", SPHERE, (-150, -110, 72), (0.34, 0.28, 0.65), MAT["leather"]) + actor("goblin visible carried spear", CYLINDER, (-122, -110, 118), (0.035, 0.035, 1.15), MAT["wood"], rot=(62, -20, 0)) + + actor("GARM massive body", SPHERE, (0, 278, 138), (1.55, 2.4, 1.1), MAT["wolf"]) + actor("GARM long muzzle head", SPHERE, (0, 150, 156), (0.72, 0.58, 0.52), MAT["wolf"]) + actor("GARM muzzle block", CUBE, (0, 92, 136), (0.44, 0.42, 0.22), MAT["wolf"]) + for x in (-54, 54): + actor(f"GARM amber eye {x}", SPHERE, (x, 88, 168), (0.08, 0.08, 0.08), MAT["eye"]) + for x, y in [(-54, 212), (54, 212), (-58, 340), (58, 340)]: + actor(f"GARM readable leg {x} {y}", CYLINDER, (x, y, 58), (0.22, 0.22, 0.7), MAT["wolf"]) + actor("GARM spear target red wound", SPHERE, (-58, 210, 82), (0.24, 0.24, 0.24), MAT["scar"]) + actor("GARM embedded spear silhouette", CYLINDER, (-72, 205, 110), (0.035, 0.035, 0.9), MAT["iron"], rot=(62, -18, 0)) + actor("GARM red muzzle scar", CUBE, (0, 84, 175), (0.62, 0.035, 0.035), MAT["scar"], rot=(0, 0, -16)) + for i, y in enumerate((210, 270, 335)): + actor(f"GARM back fur ridge {i}", CONE, (0, y, 235), (0.18, 0.18, 0.44), MAT["wolf"], rot=(-10 + i * 8, 0, 0)) + point_light("Garm cold moon rim", (0, 460, 250), unreal.Color(88, 126, 255), 2600, 620) + + # Lighting and camera. + sun = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.DirectionalLight, unreal.Vector(0, 0, 600)) + sun.set_actor_label("cold blue moon directional") + sun_comp = sun.get_component_by_class(unreal.DirectionalLightComponent) + sun_comp.set_editor_property("light_color", unreal.Color(120, 150, 255)) + sun_comp.set_editor_property("intensity", 2.2) + sun.set_actor_rotation(unreal.Rotator(-48, -24, 0), False) + point_light("left torch pool", (-190, -520, 145), unreal.Color(255, 92, 16), 1800, 420) + point_light("right torch pool", (485, -420, 150), unreal.Color(255, 92, 16), 1900, 460) + + camera = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.CineCameraActor, unreal.Vector(0, -980, 360)) + camera.set_actor_label("CineCamera visual target third person") + direction = unreal.Vector(0, -45, 104) - camera.get_actor_location() + camera.set_actor_rotation(direction.rotator(), False) + cam_comp = camera.get_cine_camera_component() + cam_comp.set_editor_property("current_focal_length", 26.0) + # Keep the camera as a scene actor. Do not call viewport camera APIs here; + # commandlet runs do not have a level viewport. + + unreal.EditorAssetLibrary.make_directory("/Game/Maps") + unreal.EditorLoadingAndSavingUtils.save_map(get_editor_world(), MAP_PATH) + unreal.log(f"Built {MAP_PATH}") + + +build_level() diff --git a/Unreal/NightwatchFortress/Scripts/open_nightwatch_unreal_editor.sh b/Unreal/NightwatchFortress/Scripts/open_nightwatch_unreal_editor.sh new file mode 100755 index 0000000..5c7efc2 --- /dev/null +++ b/Unreal/NightwatchFortress/Scripts/open_nightwatch_unreal_editor.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +UE_ROOT="${UE_ROOT:-/Users/murakaminaoya/Epic Games/UE_5.8}" +UE_APP="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app" +PROJECT="$PROJECT_ROOT/Unreal/NightwatchFortress/NightwatchFortress.uproject" +MAP_PATH="${NIGHTWATCH_MAP:-/Game/Maps/WolfForestVisualSpike}" + +if [[ ! -d "$UE_APP" ]]; then + echo "UnrealEditor.app not found: $UE_APP" >&2 + exit 1 +fi + +if [[ ! -f "$PROJECT" ]]; then + echo "Nightwatch Unreal project not found: $PROJECT" >&2 + exit 1 +fi + +echo "Opening Nightwatch Fortress in Unreal Editor..." +echo "Project: $PROJECT" +echo "Map: $MAP_PATH" + +open -n "$UE_APP" --args "$PROJECT" "$MAP_PATH" -nosplash "$@" diff --git a/Unreal/NightwatchFortress/Scripts/run_wolf_forest_visual_spike.sh b/Unreal/NightwatchFortress/Scripts/run_wolf_forest_visual_spike.sh new file mode 100755 index 0000000..0c2c996 --- /dev/null +++ b/Unreal/NightwatchFortress/Scripts/run_wolf_forest_visual_spike.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +UE_ROOT="${UE_ROOT:-/Users/murakaminaoya/Epic Games/UE_5.8}" +UE_CMD="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor-Cmd" +PROJECT="$PROJECT_ROOT/Unreal/NightwatchFortress/NightwatchFortress.uproject" +BUILDER="$SCRIPT_DIR/build_wolf_forest_visual_spike.py" +LOG="$PROJECT_ROOT/local-evidence/nightwatch_unreal_spike_abs.log" + +cleanup_unreal_autoconfig() { + local config="$PROJECT_ROOT/Unreal/NightwatchFortress/Config/DefaultEngine.ini" + local tmp="$config.tmp.$$" + + if [[ ! -f "$config" ]]; then + return + fi + + awk ' + /^\[\/Script\/AndroidFileServerEditor\.AndroidFileServerRuntimeSettings\]$/ { skip = 1; next } + /^\[/ { skip = 0 } + !skip { print } + ' "$config" > "$tmp" + + mv "$tmp" "$config" + perl -0pi -e 's/\n+\z/\n/' "$config" +} + +if [[ ! -x "$UE_CMD" ]]; then + echo "UnrealEditor-Cmd not found or not executable: $UE_CMD" >&2 + exit 1 +fi + +if [[ ! -f "$PROJECT" ]]; then + echo "Nightwatch Unreal project not found: $PROJECT" >&2 + exit 1 +fi + +if [[ ! -f "$BUILDER" ]]; then + echo "Wolf Forest builder script not found: $BUILDER" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$LOG")" +cd "$UE_ROOT/Engine/Binaries/Mac" + +echo "Running Unreal Wolf Forest visual spike..." +echo "Project: $PROJECT" +echo "Builder: $BUILDER" +echo "Log: $LOG" + +status=0 +"$UE_CMD" "$PROJECT" \ + -run=pythonscript \ + -script="$BUILDER" \ + -unattended \ + -nop4 \ + -nosplash \ + -nosound \ + -nullrhi \ + -stdout \ + -FullStdOutLogOutput \ + -abslog="$LOG" \ + "$@" || status=$? + +cleanup_unreal_autoconfig +exit "$status" diff --git a/docs/art/nightwatch/wolf-forest-visual-target-v1.png b/docs/art/nightwatch/wolf-forest-visual-target-v1.png new file mode 100644 index 0000000..1e60475 Binary files /dev/null and b/docs/art/nightwatch/wolf-forest-visual-target-v1.png differ diff --git a/docs/art/nightwatch/wolf-forest-visual-target.md b/docs/art/nightwatch/wolf-forest-visual-target.md new file mode 100644 index 0000000..4993197 --- /dev/null +++ b/docs/art/nightwatch/wolf-forest-visual-target.md @@ -0,0 +1,68 @@ +# Wolf Forest Visual Target + +Reference image: + +![Wolf Forest visual target](wolf-forest-visual-target-v1.png) + +## Purpose + +This is the current visual bar for the first `夜番の砦` vertical slice. The +Unity scene builder and later authored assets should move toward this read: + +- lone night hunter in practical leather, cloak, boots, gloves, lantern, and + weapon holders +- weapon pickup visible near the player, not hidden at the side of the scene +- weapon rack and alternate weapons visible in the first combat space +- dark forest with blue moonlight, warm torch/lantern light, low mist, wet + ground, fallen tree, stream, and hunter shack silhouette +- large original night wolf readable at game-camera distance, with legs, muzzle, + amber eyes, red scars, and a spear-target leg cue +- small enemy pressure in the midground without cluttering the screenshot + +## Reject Criteria + +- gray box floor, default sky, or mannequin-only character +- dark image where silhouettes, weapons, or pickups cannot be read +- weapon shapes that only read as cylinders +- wolf boss that reads as a shapeless dark blob +- UI text carrying the whole concept because the image itself is unclear +- copied silhouettes, armor, monsters, UI, logos, or weapon designs from any + existing game + +## Generation Prompt + +```text +Create a polished in-game screenshot target for the original game "夜番の砦" / +"Nightwatch Fortress". The image must clearly communicate the hook: a lone night +watch hunter survives by picking up and swapping weapons found on the ground +while fighting a huge night wolf in a moonlit forest. + +Scene/backdrop: dark but readable moonlit forest clearing called Wolf Forest, +wet ground, shallow stream, hunter shack silhouette, fallen tree, warm torch +pools, cold blue moonlight, thin ground mist, practical fantasy atmosphere. + +Subject: one original lone hunter in leather armor, cloth cloak, boots, gloves, +belt pouches, shoulder lantern, practical weapon holders; no existing IP +resemblance. The hunter is mid-action near a glowing pickup spear on the ground, +with a sword in hand and a large hammer and shield visible on a nearby weapon +rack. + +Enemy: an original giant black night wolf, readable legs, long muzzle, amber +eyes, red scars, a clear vulnerable front leg target where a spear can be +embedded. Add one small wolf and one goblin-like weapon carrier in the +midground, but keep the composition readable. + +Composition: third-person game camera behind and slightly above the player, +16:9 wide screenshot composition, player in lower third, weapon pickup clearly +visible, Garm looming in the upper/middle distance, UI-free except subtle +game-feel framing; no text, no logo, no watermark. + +Style: commercial early prototype quality, stylized low-to-mid poly 3D game art +with authored lighting and materials, not gray boxes, not a default engine +scene, not placeholder primitives, strong silhouettes, readable combat spacing. + +Negative constraints: do not copy Dark Souls, Elden Ring, Monster Hunter, +Diablo, Hades, V Rising, or any known character/monster design; no text; no UI +labels; no logo; no watermark; no over-dark unreadable image; no generic +asset-store pileup. +``` diff --git a/docs/art/threadlight/threadlight-gameplay-mock-v1.png b/docs/art/threadlight/threadlight-gameplay-mock-v1.png new file mode 100644 index 0000000..8f66a64 Binary files /dev/null and b/docs/art/threadlight/threadlight-gameplay-mock-v1.png differ diff --git a/docs/art/threadlight/threadlight-gameplay-mock-v2-1280x800.png b/docs/art/threadlight/threadlight-gameplay-mock-v2-1280x800.png new file mode 100644 index 0000000..af04023 Binary files /dev/null and b/docs/art/threadlight/threadlight-gameplay-mock-v2-1280x800.png differ diff --git a/docs/art/threadlight/threadlight-gameplay-mock-v2.png b/docs/art/threadlight/threadlight-gameplay-mock-v2.png new file mode 100644 index 0000000..2fc3b24 Binary files /dev/null and b/docs/art/threadlight/threadlight-gameplay-mock-v2.png differ diff --git a/docs/art/threadlight/threadlight-loom-caravan-v1.png b/docs/art/threadlight/threadlight-loom-caravan-v1.png new file mode 100644 index 0000000..6f447ac Binary files /dev/null and b/docs/art/threadlight/threadlight-loom-caravan-v1.png differ diff --git a/docs/art/threadlight/threadlight-trailer-beatboard-v1.png b/docs/art/threadlight/threadlight-trailer-beatboard-v1.png new file mode 100644 index 0000000..3428d88 Binary files /dev/null and b/docs/art/threadlight/threadlight-trailer-beatboard-v1.png differ diff --git a/docs/art/threadlight/threadlight-trailer-beatboard-v2.png b/docs/art/threadlight/threadlight-trailer-beatboard-v2.png new file mode 100644 index 0000000..59c9e94 Binary files /dev/null and b/docs/art/threadlight/threadlight-trailer-beatboard-v2.png differ diff --git a/docs/art/threadlight/threadlight-world-contact-sheet-v1.png b/docs/art/threadlight/threadlight-world-contact-sheet-v1.png new file mode 100644 index 0000000..1f6d26a Binary files /dev/null and b/docs/art/threadlight/threadlight-world-contact-sheet-v1.png differ diff --git a/docs/art/threadlight/threadlight-world-contact-sheet-v2.png b/docs/art/threadlight/threadlight-world-contact-sheet-v2.png new file mode 100644 index 0000000..488395f Binary files /dev/null and b/docs/art/threadlight/threadlight-world-contact-sheet-v2.png differ diff --git a/docs/art/threadlight/threadlight-world-elements-v1.png b/docs/art/threadlight/threadlight-world-elements-v1.png new file mode 100644 index 0000000..a428cfc Binary files /dev/null and b/docs/art/threadlight/threadlight-world-elements-v1.png differ diff --git a/docs/art/threadlight/trailer-beatboard.md b/docs/art/threadlight/trailer-beatboard.md new file mode 100644 index 0000000..0e48d39 --- /dev/null +++ b/docs/art/threadlight/trailer-beatboard.md @@ -0,0 +1,72 @@ +# Threadlight 30-Second Trailer Beatboard + +Status: superseded historical trailer planning proof. +Date: 2026-06-30. + +This beatboard is retained as historical research only. It must not drive the +current `夜番の砦` trailer. The active trailer direction is defined by +`docs/research/visual-audio-benchmark.md` and +`docs/capture/30-second-acceptance.md`. + +## Final Sheet + +- `threadlight-trailer-beatboard-v2.png` + +Use v2 for review. v1 is retained only as evidence of the visual QA pass. + +## Beat Sequence + +| Time | Beat | Screen Promise | +| --- | --- | --- | +| `0-3s` | Lantern finds the route. | The player is a surveyor and the world rule reads before UI. | +| `3-7s` | Light reveals threat. | Lantern pools reveal light-moths and cloth hazards. | +| `7-12s` | Thread binds action. | The route line becomes the combat verb: bind, anchor, cut, repair. | +| `12-16s` | Threat pushes back. | Enemies attack the route, not just the player body. | +| `16-21s` | Return to caravan. | A rescued memory changes the hub, creating repeat-run desire. | +| `21-26s` | Boss landmark wakes. | Loom-gate becomes the product-face threat. | +| `26-30s` | Readable combat peak. | End on the cleanest rule-readable combat frame before title card. | + +## Visual QA + +The first beatboard draft had a weak opening shot: the surveyor was cropped too +low and the first three seconds did not clearly sell the player role. + +v2 fixes that by: + +- keeping the full surveyor silhouette in the first panel +- preserving the lantern-spool tool read +- showing the thread route and threat line in the same frame +- adding the QA change directly to the production notes + +## Trailer Rules + +- Keep HUD hidden for the first 20 seconds. +- Do not show inventory, build menus, or loot screens until the world promise + is established. +- Alternate world rule, threat, repair, hub payoff, boss landmark, and peak + combat. +- Do not start with lore text. +- Do not start with a weapon close-up. +- Do not show a generic dark fantasy combat montage. + +## Audio Spine + +Use a small recurring motif instead of generic orchestration: + +1. taut thread pluck +2. lantern glass click +3. soft bell warning +4. loom wood knock +5. memory chime + +The first trailer arrangement should be built around that sequence. + +## Next Production Gate + +The beatboard supports only one low-cost 3D preview target: + +1. threadlight route tool +2. light-moth enemy + +Do not start a full hero Meshy/Maya pass yet. The route tool should prove the +gameplay rule first. diff --git a/docs/art/threadlight/world-contact-sheet.md b/docs/art/threadlight/world-contact-sheet.md new file mode 100644 index 0000000..01df16c --- /dev/null +++ b/docs/art/threadlight/world-contact-sheet.md @@ -0,0 +1,126 @@ +# Threadlight World Contact Sheet + +Status: superseded historical visual research. +Date: 2026-06-30. + +This sheet is retained as visual research and part-library evidence only. It is +not the active product direction. The active product is `夜番の砦` in +`docs/product/game-definition-nightwatch-fortress.md`. + +The useful lesson is still valid: do not sell only a weapon silhouette. Sell a +role, a world, and a screen that looks commercial. + +## Final Sheet + +- `threadlight-world-contact-sheet-v2.png` + +Use this sheet for review. It includes: + +- 1280x800 gameplay mock +- Loom Caravan hub +- world asset language +- acceptance gate +- visual QA change notes + +## Generated Assets + +| File | Role | Verdict | +| --- | --- | --- | +| `threadlight-gameplay-mock-v1.png` | First gameplay direction attempt. | Preserved for comparison; too armored and monster-heavy. | +| `threadlight-gameplay-mock-v2.png` | Improved gameplay direction source. | Adopted; clearer surveyor role and route-control rule. | +| `threadlight-gameplay-mock-v2-1280x800.png` | Exact 1280x800 gameplay mock crop. | Main game-screen proof image. | +| `threadlight-loom-caravan-v1.png` | Hub concept. | Adopted; strong non-combat return desire. | +| `threadlight-world-elements-v1.png` | Asset language concept sheet. | Adopted as part library; surveyor still needs less armor in future passes. | +| `threadlight-world-contact-sheet-v1.png` | First assembled sheet. | Rejected for layout; caption spacing looked mock-like. | +| `threadlight-world-contact-sheet-v2.png` | Final assembled sheet. | Adopted; fixes caption spacing and records visual QA. | + +## Acceptance Check + +Pass: + +- World rule reads without UI: light reveals, thread binds, route repairs. +- Player reads more as a surveyor/route worker than a weapon-first knight. +- Hub shows a repeat-run desire: rescued memories and caravan growth. +- The first credible 3D targets are route tool or light-moth, not a polished + full hero. + +Still risky: + +- The gameplay mock has strong commercial finish, but the banner threat and + knot hound still lean dark-monster. The next pass should make enemies more + cloth/relic/ecology driven. +- The world-elements sheet's surveyor is still more armored than the target + field-worker silhouette. Treat it as part reference, not final protagonist. + +## Visual QA Change + +The first gameplay mock looked too close to rugged dark fantasy combat: + +- armored protagonist +- larger monster read +- less visible field-worker role + +The improved mock changed the player signal: + +- field coat +- apron and route sash +- shoulder/hand lantern +- visible physical thread-spool tool +- readable golden thread route + +The first contact-sheet assembly also had caption overlap. The final sheet was +rebuilt with more vertical spacing so the presentation does not read as a rough +placeholder. + +## Prompt Record + +Generation used the built-in `image_gen` tool. Do not reuse external game names +or franchise references in production prompts. + +Gameplay v2 prompt summary: + +```text +Improved top-down gameplay mock for Threadlight Pilgrimage. Main character +must look like a field surveyor and route-mender first, not a knight or +assassin. Rain-dark cloth street, wet stone, stitched seams, glowing route +marks, loom-gate landmark. Small lantern-thread surveyor with field coat, +work apron, route cloth sash, shoulder lantern, map cloth roll, measuring pins, +physical spool-lantern tool, and short utility blade. Small light-moths, +banner husk, knot hound. No UI, no text, no spiked armor, no giant weapon +focus. +``` + +Hub prompt summary: + +```text +Loom Caravan hub: warm travelling loom wagon at night with rain outside, +cloth awnings, lantern racks, map cloth, NPC bunks, cooking area, repair +bench, thread spools, memory lights in glass jars. Emotional return space, +not a loot menu. +``` + +World-elements prompt summary: + +```text +Six-panel concept sheet: surveyor, threadlight route tool, light-moth, +banner husk, bell-root pulse enemy, and loom-gate keeper. Premium stylized +concept board with no text labels, no UI, and clear Threadlight world rules. +``` + +## Next Gate + +Create a 30-second trailer beatboard from this sheet: + +1. lantern lights a thread route +2. enemy attacks the route +3. surveyor binds or repairs the route +4. hub receives rescued memory +5. loom-gate boss landmark wakes + +After that, choose only one low-cost 3D preview target: + +1. threadlight route tool +2. light-moth enemy + +Do not spend Meshy/Maya credits on a full hero until the gameplay rule survives +an actual Unity or Unreal camera test. diff --git a/docs/assets/asset-ledger-template.csv b/docs/assets/asset-ledger-template.csv new file mode 100644 index 0000000..ddd5c60 --- /dev/null +++ b/docs/assets/asset-ledger-template.csv @@ -0,0 +1,3 @@ +asset_id,source_name,source_url,author,license,acquired_date,format,intended_use,modifications,attribution_required,redistribution_notes,ai_tool_input_allowed,status +sample_arpg_001,Quaternius,https://quaternius.com/,Quaternius,CC0,2026-06-29,FBX;OBJ,環境プロップ試作,ポリゴン簡素化/UV再整理,false,再配布不可(元素材のそのまま再配布は行わない),false,pending-review +sample_arpg_002,Kenney,https://www.kenney.nl/,Kenney,CC0,2026-06-29,PNG;SVG;FBX,UIベース素材,配色調整/コライダ追加,false,原素材の再配布回避、改変後アセットのみ共有,true,pending-review diff --git a/docs/assets/free-cc0-shortlist.md b/docs/assets/free-cc0-shortlist.md new file mode 100644 index 0000000..5d04c8d --- /dev/null +++ b/docs/assets/free-cc0-shortlist.md @@ -0,0 +1,50 @@ +# 無料・商用向け素材ソース(CC0ショートリスト) + +対象日: 2026-06-29(調査時点) + +## 対象ソース運用メモ + +| Source | 商用可 | 改変可 | クレジット | 主な形式 | 注意点 | +|---|---|---|---|---|---| +| Quaternius | ○(CC0) | ○(CC0) | 通常不要。著作情報の明示は任意 | FBX / OBJ / GLTF / Blender / PNG / JPG | CC0前提で配布。作者名と同梱素材URLを取得ログに残す。 | +| Kenney | ○(基本CC0) | ○(CC0) | 通常不要。任意で明示可 | PNG / SVG / PNGシート / OBJ / FBX / Blender系 | 無料素材中心だが、ページごとに公開条件が同一か確認する。 | +| Poly Haven | ○(CC0) | ○(CC0) | 不要。原則は帰属不要 | EXR / PNG / JPG / HDR / BLEND / FBX / GLTF / USDZ | 写真/モデルの派生再配布時も、商用利用自体は可。 | +| Fab / Megascans | ○(Fab standard利用条件) | 通常可 | 多くは不要。個別要件あり | FBX / GLTF / USD / OBJ / PNG / EXR | 素材そのものの再配布や抽出利用に制限。AI学習向け投入制限の有無を毎回確認。 | +| Unity Asset Store | ○(EULA範囲) | ○(EULA範囲) | 指定がある場合のみ必須 | UnityPackage / FBX / OBJ / PNG / WAV | 「販売/再配布禁止」「Unityプロジェクト外での公開再配布」を厳密確認。 | +| Sketchfab | ○(CC0/CC-BY等を選別) | ライセンス条件次第 | CC-BY系は必須、CC0は不要 | GLB / GLTF / FBX / OBJ / STL | Creative Commons以外や NC・ND・SA混在、Editorial表記は除外。 | +| OpenGameArt | 条件付き(素材ごと) | 条件付き | ライセンス次第(CC-BY系は必要) | PNG / WAV / OGG / FLAC / SVG / OBJ / FBX | GPL/SAなどを混在掲載するため素材ごとに個別確認。 | +| Mixamo | ○(提供時の契約範囲) | ○(用途内) | 原則不要。必要時は権利情報を保持 | FBX / FBX/BVH(Rig/Anim) / PNG | 生キャラクター/モーションをそのまま配布しない。人物再利用の範囲を別途確認。 | + +## ライセンス除外ルール + +以下を含む素材は原則採用しない: + +- NC / 非営利限定の条項 +- ND / 改変禁止 +- SA / 派生の再配布条件が厳格なもの +- GPL / CC-BY-SA(ARPG本体をクローズな販売物とする前提では原則不採用) +- Editorial / 参考用の限定利用 +- Trial(体験版)/教育版(商用案件での再利用不可) + +上記どれかがある場合は、商用実装前に`asset-ledger-template.csv`に「除外理由」を明記し、採用候補に入れない。 + +## 今回ARPGで最初に試す種類(優先順) + +1. **環境基盤(最優先)** + - Poly Haven: PBRテクスチャ/HDRI/岩肌 + - Quaternius: 低ポリ環境プロップ + - Kenney: 基本UI/エフェクト資産 +2. **キャラクター系(早期検証)** + - Mixamo: 基本モーション、リギング参照 + - Quaternius: 敵/小型生物 +3. **装備・小道具・UI(量産候補)** + - Kenney: 武器UI・インターフェース + - Sketchfab: 探索的に特定モチーフを追加(CC0/CC-BYのみ) +4. **最終補完(必要時)** + - Fab/Megascans: 岩盤・地形素材の高品質拡張 + +## 判定メモ(採用時の最低記録) + +- ソースURL、作成者、ライセンス条文該当箇所、取得日、形式、改変内容、再配布可否を`docs/assets/asset-ledger-template.csv`に記録 +- ライセンスが1件でも曖昧なら、実装前レビューまで `pending-review` 状態で固定 +- すべての判定基準は `NC/ND/SA/GPL/Editorial/Trial/教育版`除外規則と衝突しないこと diff --git a/docs/briefs/final-frame-brief.md b/docs/briefs/final-frame-brief.md new file mode 100644 index 0000000..23fb131 --- /dev/null +++ b/docs/briefs/final-frame-brief.md @@ -0,0 +1,47 @@ +# Final Frame Brief: 夜番の砦 + +Status: active commercial screenshot target for the first vertical slice. + +## Target Frame + +The screenshot should read as: + +`A lone nightwatch hunter fights a huge wolf in a moonlit forest, surrounded by weapons that can be picked up, thrown, broken, and pulled from the beast.` + +## Must Show + +- leather-armored nightwatch player with lantern and visible current weapon +- Wolf Forest environment: moonlight, torches, trees, mist, hunter debris +- weapon rack or fallen weapons in frame +- at least one embedded spear or thrown weapon in the enemy/boss +- clear weapon pickup/durability HUD +- enemy posture or boss part-break UI +- warm torch light plus cool moonlight + +## Reject If + +- it looks like a generic sword duel +- no weapon pickup/swap/throw is visible +- the scene is just gray forest fog +- the player silhouette reads as a default mannequin +- weapons are too small to identify +- UI is debug text + +## 30-Second Trailer Beat + +1. `0-3s`: fort gate at night, weapon rack, hunter takes two weapons. +2. `3-7s`: Wolf Forest movement, torch and moonlight. +3. `7-11s`: first enemy, weapon durability drops. +4. `11-15s`: player picks up dropped spear or axe and swaps instantly. +5. `15-19s`: thrown weapon sticks into an enemy. +6. `19-23s`: Garm appears, spear stops a charge. +7. `23-27s`: hammer head stagger or tail part break. +8. `27-30s`: near-broken weapon throw opens weak point, title frame. + +## Screenshot Checklist + +- [ ] Reads as solo night-hunter ARPG at thumbnail size. +- [ ] Shows local weapons as the central hook. +- [ ] Uses dark but readable lighting. +- [ ] Shows at least one warm and one cool light source. +- [ ] Avoids copied IP silhouettes, UI, logos, and boss designs. diff --git a/docs/capture/30-second-acceptance.md b/docs/capture/30-second-acceptance.md new file mode 100644 index 0000000..8eb2f4a --- /dev/null +++ b/docs/capture/30-second-acceptance.md @@ -0,0 +1,52 @@ +# 30-Second Acceptance: 夜番の砦 + +Status: acceptance checklist for first Steam-facing gameplay capture. + +A valid capture must prove the game is about local weapons, not just sword +combat. + +## Required Beats + +1. Player begins near a weapon rack or visible ground weapons. +2. Player moves through readable Wolf Forest lighting. +3. Enemy attack telegraph is visible. +4. Player attacks with one weapon. +5. Weapon durability or damage state is visible. +6. Player picks up or swaps to another weapon during combat. +7. Player throws a weapon. +8. Thrown weapon sticks into enemy, boss, or terrain. +9. Player recovers or pulls a weapon. +10. Boss/large enemy part or posture reaction appears. + +## Visual Requirements + +- HP and stamina are visible. +- Current and sub weapon are visible. +- Durability state is visible. +- Pickup prompt is visible when near a weapon. +- Weapon silhouettes are readable. +- Warm torch light and cool moonlight both appear. +- No gray-box/default-floor look. + +## Audio Requirements + +Even with temporary audio, the direction must be clear: + +- pickup +- drop +- throw +- embed +- pull out +- weapon hit +- weapon break or near-break warning +- monster reaction +- torch/fire or night ambience + +## Fail Conditions + +- 30 seconds of only sword attacks. +- No local weapon pickup or throw. +- Weapons too small to read. +- Forest too dark or monochrome. +- HUD looks like unstyled debug text. +- Boss/large enemy interaction does not show weapon-specific value. diff --git a/docs/character/2d-to-3d-character-workflow.md b/docs/character/2d-to-3d-character-workflow.md new file mode 100644 index 0000000..9105a8a --- /dev/null +++ b/docs/character/2d-to-3d-character-workflow.md @@ -0,0 +1,55 @@ +# 2D To 3D Character Workflow: 夜番の砦 + +Status: active workflow for first-slice characters and weapons. + +## Order + +1. Player silhouette with all held-weapon states. +2. First weapons with durability variants. +3. Small wolf and horned beast. +4. Goblin and shield goblin. +5. Garm boss part-target sheet. +6. 3D blockouts. +7. In-engine readability screenshots. + +## 2D Sheet Requirements + +For the player: + +- front/side/back +- leather armor and cloak +- shoulder lantern +- weapon holders +- hand positions for sword, spear, hammer, bow, shield, torch +- scale strip showing player beside first weapons + +For weapons: + +- clean +- chipped +- near-broken +- broken +- thrown +- embedded + +For Garm: + +- full body +- leg embed target +- head stagger target +- tail break target +- phase posture comparison + +## 3D Gate + +Do not model polish before the gameplay read is clear. A blockout must prove: + +- weapon scale reads in third-person camera +- pickup objects are visible on the ground +- embedded weapons are visible from gameplay distance +- Garm part targets are readable + +## Engine Evidence + +Each character/weapon milestone needs a screenshot or contact sheet from Unity, +not only a standalone render. diff --git a/docs/character/character-pattern-cards.md b/docs/character/character-pattern-cards.md new file mode 100644 index 0000000..3e193ca --- /dev/null +++ b/docs/character/character-pattern-cards.md @@ -0,0 +1,51 @@ +# Character Pattern Cards: 夜番の砦 + +Status: active character direction. + +## Player Card: Nightwatch Hunter + +| Field | Direction | +| --- | --- | +| Role | Ordinary but skilled night hunter defending villages and forts. | +| Silhouette | Leather armor, cloak, shoulder lantern, visible weapon holders. | +| Read | Practical, cool, not royal, not chosen-one ornate. | +| Key Props | Waist dagger, back weapon, belt hooks, torch loop, bow/quiver option. | +| Animation | Fast pickup/swap, grounded weapon weight, readable guard/parry. | + +Required silhouette reads: + +- one-handed sword +- spear +- great hammer +- bow +- large shield +- torch plus one-handed weapon + +## Enemy Cards + +### Small Wolf + +Fast low silhouette, clear leap posture, pack pressure. + +### Horned Beast + +Lowered head charge posture, readable horn target, spear-stop teach. + +### Goblin + +Weapon carrier. The held/dropped weapon must be visible. + +### Shield Goblin + +Large shield read from the front, vulnerable side/back read. + +## Boss Card: 大狼ガルム + +Huge wolf, moonlit body, glowing eyes, readable legs, large head, breakable tail. +Every screenshot should show at least one reason a weapon type matters. + +## Reuse From Old Concepts + +Older generated character sheets are not active direction. Only reuse abstract +lessons such as silhouette clarity, readable weapon scale, and dark-light +contrast. Do not reuse old character identities as current product targets. diff --git a/docs/character/concepts/concept-comparison-contact-sheet.png b/docs/character/concepts/concept-comparison-contact-sheet.png new file mode 100644 index 0000000..08414e3 Binary files /dev/null and b/docs/character/concepts/concept-comparison-contact-sheet.png differ diff --git a/docs/character/concepts/concept-silhouette-strip.png b/docs/character/concepts/concept-silhouette-strip.png new file mode 100644 index 0000000..67896be Binary files /dev/null and b/docs/character/concepts/concept-silhouette-strip.png differ diff --git a/docs/character/concepts/f01-ash-veil-halberd-matron-sheet.png b/docs/character/concepts/f01-ash-veil-halberd-matron-sheet.png new file mode 100644 index 0000000..f21903b Binary files /dev/null and b/docs/character/concepts/f01-ash-veil-halberd-matron-sheet.png differ diff --git a/docs/character/concepts/f02-glass-moth-duelist-sheet.png b/docs/character/concepts/f02-glass-moth-duelist-sheet.png new file mode 100644 index 0000000..5f4e329 Binary files /dev/null and b/docs/character/concepts/f02-glass-moth-duelist-sheet.png differ diff --git a/docs/character/concepts/f03-bell-root-hex-huntress-sheet.png b/docs/character/concepts/f03-bell-root-hex-huntress-sheet.png new file mode 100644 index 0000000..121d6c2 Binary files /dev/null and b/docs/character/concepts/f03-bell-root-hex-huntress-sheet.png differ diff --git a/docs/character/concepts/generation-log.md b/docs/character/concepts/generation-log.md new file mode 100644 index 0000000..9f6ad3b --- /dev/null +++ b/docs/character/concepts/generation-log.md @@ -0,0 +1,59 @@ +# Character Concept Generation Log + +Status: record of generated 2D concept sheets before any Meshy/Maya spend. + +Tool path: + +- Built-in `image_gen` tool. +- Outputs copied from `$CODEX_HOME/generated_images/...` into + `docs/character/concepts/`. + +## IP Safety Notes + +The user provided a hooded reference image for mood. It was not used as a direct +edit target. The derived H09 direction kept only broad, generic ideas: + +- hooded anonymity +- ritual weapon weight +- occult pilgrim mood +- heavy beads/charms as silhouette support + +Explicitly rejected from H09: + +- key-shaped weapon +- star charm +- black zipper coat +- school-uniform silhouette +- childlike proportions +- recognizable franchise character design + +## Generated Sheets + +| File | Prompt Direction | Current Verdict | +| --- | --- | --- | +| `h07-white-charcoal-judgement-axe-sheet.png` | Oversized-arm execution axe hero. | Reuse as boss/greatsword impact reference; too rugged for first hero. | +| `h02-black-iron-pincer-knight-sheet.png` | Asymmetric pincer knight with shears. | Keep as fortress elite or boss mass reference. | +| `h08-navy-lantern-threadbinder-sheet.png` | Lantern and thick thread-loop trap hero. | Historical only; do not revive Threadlight as product direction. | +| `h09-veiled-oath-relic-duelist-sheet.png` | Hooded relic duelist with broad reliquary greatblade. | Part library: hood/cloak weight and readable relic mass. | +| `f01-ash-veil-halberd-matron-sheet.png` | Female halberd warrior with ash veil crest. | Part library: pale veil/crest and polearm readability. | +| `f02-glass-moth-duelist-sheet.png` | Female glass-moth rogue with crescent daggers. | Keep with caution: useful dodge silhouette, risky ninja/fairy read. | +| `f03-bell-root-hex-huntress-sheet.png` | Female bell-root occult hunter/summoner. | Strong alternate enemy/area motif; not current protagonist. | +| `concept-comparison-contact-sheet.png` | Combined overview of all generated sheets. | Used for visual triage. | +| `concept-silhouette-strip.png` | Cropped top silhouette strips in grayscale. | Used for tiny-readability triage. | + +## Next Gate + +Before Meshy preview: + +1. Accept `docs/product/game-definition-nightwatch-fortress.md`. +2. Create nightwatch player, held-weapon silhouette, sword, spear, hammer, bow, + shield, torch, small wolf, horned beast, goblin, shield goblin, and Garm + concept sheets. +3. Run 64-128 px thumbnail and third-person gameplay crop tests. +4. Choose no more than two assets that prove the first Steam screenshot. +5. Prefer weapon rack, visible durability states, and Garm embed/part-break + targets before broad area decoration if they prove the local-weapon hook + faster. +6. Run only Meshy preview/draft first. +7. Do not refine, HD texture, rig, or animate until the preview survives the + game-camera and IP-safety test. diff --git a/docs/character/concepts/h02-black-iron-pincer-knight-sheet.png b/docs/character/concepts/h02-black-iron-pincer-knight-sheet.png new file mode 100644 index 0000000..a7a4b2d Binary files /dev/null and b/docs/character/concepts/h02-black-iron-pincer-knight-sheet.png differ diff --git a/docs/character/concepts/h07-white-charcoal-judgement-axe-sheet.png b/docs/character/concepts/h07-white-charcoal-judgement-axe-sheet.png new file mode 100644 index 0000000..2cc8f13 Binary files /dev/null and b/docs/character/concepts/h07-white-charcoal-judgement-axe-sheet.png differ diff --git a/docs/character/concepts/h08-navy-lantern-threadbinder-sheet.png b/docs/character/concepts/h08-navy-lantern-threadbinder-sheet.png new file mode 100644 index 0000000..8e6a64f Binary files /dev/null and b/docs/character/concepts/h08-navy-lantern-threadbinder-sheet.png differ diff --git a/docs/character/concepts/h09-veiled-oath-relic-duelist-sheet.png b/docs/character/concepts/h09-veiled-oath-relic-duelist-sheet.png new file mode 100644 index 0000000..5a038d8 Binary files /dev/null and b/docs/character/concepts/h09-veiled-oath-relic-duelist-sheet.png differ diff --git a/docs/phase1/nightwatch-weapon-foundation-smoke.md b/docs/phase1/nightwatch-weapon-foundation-smoke.md new file mode 100644 index 0000000..b24115c --- /dev/null +++ b/docs/phase1/nightwatch-weapon-foundation-smoke.md @@ -0,0 +1,56 @@ +# Nightwatch Weapon Foundation Smoke + +Scope: first Unity foundation for `夜番の砦`. + +## Build Scene + +1. Open the project with Unity `6000.3.18f1`. +2. Run `Nightwatch Fortress > Build Wolf Forest Foundation Scene`. +3. Open `Assets/Scenes/WolfForestFoundation.unity`. +4. Press Play. + +## Expected First-Screen Read + +- The scene reads as a moonlit forest, not a gray test floor. +- The player silhouette has leather armor, cloak, lantern, and weapon holder + cues. +- Warm torch pools, cold moonlight, stream color, hunter shack, fallen tree, + moss clumps, and dense tree shapes are visible. +- A weapon rack and loose weapons are visible near the start. +- A large wolf-shaped embed target is visible deeper in the arena, with a red + leg target that communicates spear-stopping intent. + +## Controls + +- `WASD`: move +- Mouse: camera +- `Space`: jump +- `Left Shift`: dodge +- `Tab`: swap weapon slots +- `E`: pull embedded weapon if near one, otherwise pick up nearest weapon +- `R`: drop active weapon +- `F`: throw active weapon +- `J` or left mouse: light attack +- `U` or right mouse: heavy attack + +## Pass Criteria + +- Player moves, jumps, dodges, and faces camera-relative directions. +- HUD shows HP, stamina, active weapon, sub weapon, durability, and nearest + pickup. +- Weapon pickup, slot swap, drop, and throw happen without exceptions. +- Small wolf and weapon goblin approach the player and can reduce HP if ignored. +- Thrown spear or sword can damage the small enemy and can embed into the large + wolf leg target. +- Embedded weapon can be pulled free with `E`. +- Enemy death can spawn a dropped spear. +- Screenshot communicates the hook: a lone hunter surviving in a night forest + by changing weapons found on the ground. + +## Not Yet Complete + +- This is not the finished `狼森` vertical slice. +- Enemy AI, boss phases, animation clips, authored models, audio, save data, and + final UI art are still production tasks. +- Do not merge future feature work that regresses the current visual read into + gray boxes, default floor, default sky, or debug-only UI. diff --git a/docs/product/game-definition-color-eater-crown.md b/docs/product/game-definition-color-eater-crown.md new file mode 100644 index 0000000..24f2ee1 --- /dev/null +++ b/docs/product/game-definition-color-eater-crown.md @@ -0,0 +1,420 @@ +# Game Definition: 色喰いの王冠 + +Status: historical, superseded. +Date: 2026-06-30. +English working title: `The Color-Eater Crown`. + +Active product source of truth: +[`docs/product/game-definition-nightwatch-fortress.md`](game-definition-nightwatch-fortress.md). + +This document is preserved as design history only. Do not use it as the current +implementation target. + +## One-Line Pitch + +A lone color mortician enters a poisonous royal dark fantasy kingdom, tears color +from enemies, stores up to three colors in a glass arm organ, reshapes the sword +with those colors, opens color-locked paths, strips bosses down to their true +weakness, and hunts the cursed crown that devoured the kingdom. + +## Product Target + +Steam commercial-quality 3D solo dark fantasy action RPG. + +This is not: + +- a cooperative game +- an online-first game +- a hack-and-slash loot game +- an open world +- a gray fantasy prototype +- a pure Soulslike defense-first game + +The game should feel: + +- dark but vivid +- beautiful but unpleasant +- courtly but monstrous +- sword-based, but mechanically centered on stealing color + +## World + +The setting is `彩冠王国オルフェリア`. + +Orphelia was once the most beautiful kingdom in the world: black marble, gold +leaf, red carpets, stained glass, wet wood, old oil paintings, silk masks, +greenhouses, court music, judges, dancers, painters, magicians, and knights. + +Color is not cosmetic in this world. Color is soul heat, memory, emotion, and +magic. + +| Color | Meaning | +| --- | --- | +| Red | anger, life, blood, heat | +| Blue | memory, silence, water, mirror | +| Green | growth, poison, vines, decay | +| Gold | authority, contract, light, weight | +| Purple | dream, madness, illusion, shadow | +| Black | desire, hunger, abyss | +| White | loss, blankness, mourning | + +The king gathered the kingdom's colors into the crown. When he wore it, he +became a color-eating monster. Nobles became masked beasts, citizens became +colorless husks, and paintings began to move like living things. + +The kingdom is dark, but not gray: wet black marble reflects red, blue stained +glass paints corridors, gold decoration rots, green glasshouses pulse like meat, +and purple moonlight stains the palace. + +## Protagonist + +Default name: `リュシアン`. + +Role: `彩葬師`, a mortician who calms violent color and mourns the colorless +dead. + +Visual pillars: + +- slim swordfighter +- black formal combat clothing +- one-sided long mantle +- brush-like short dagger at the waist +- narrow black-silver sword on the back +- glass color organ in the left arm, `彩槽` +- eyes, left arm, sword, and mantle lining change color in combat + +Core equipment: + +- `彩剣`: black-silver rapier that changes shape when color is equipped +- `彩槽`: stores up to three colors +- `葬筆`: short blade/magic brush used to paint seals, doors, floor marks, and + paintings +- mantle: dodge, air control, fall reduction, color-change silhouette + +## Core System: Color Drain + +Every enemy has a color core and a color layer separate from HP. + +Flow: + +1. Damage an enemy to削る its color layer. +2. Break the color layer. +3. Enter color-drain-ready state. +4. Press the color drain input at close range. +5. A short execution animation pulls color as liquid/ribbon light into the left + arm. +6. The enemy loses color and collapses. + +Color drain success: + +- restores a small amount of HP +- adds one color to the `彩槽` +- grants short invulnerability +- emits a color shockwave +- awards color material + +Color storage: + +- maximum three colors +- same-color stacking strengthens that color +- mixed colors unlock combo techniques later +- color skills consume color gauge +- gauge recovers through attacks, color drain, and color crystals + +## Colors + +### Red + +Theme: life, anger, blood, heat. + +- combat: close damage, HP steal, rapid attacks +- weapon: red curved blade +- VFX: blood-like slashes, sparks, red particles +- skills: `紅裂き`, `血華`, `命喰い` +- exploration: cut red seals and open vascular doors + +### Blue + +Theme: memory, silence, water, mirror. + +- combat: counter, ranged slash, time slow +- weapon: thin transparent crystal blade +- VFX: mirror shards, ripples, cold light +- skills: `水鏡返し`, `記憶刃`, `静止線` +- exploration: reveal memory ghosts and restore vanished platforms + +### Green + +Theme: growth, poison, vines, decay. + +- combat: poison, bind, setup, recovery support +- weapon: thorns and vines around the blade +- VFX: green liquid, spores, thorns, glowing pollen +- skills: `棘縫い`, `毒庭`, `再生芽` +- exploration: grow dead plants and open root-blocked paths + +### Gold + +Theme: authority, contract, light, weight. + +- combat: heavy attacks, guard break, shield, stun +- weapon: gold greatsword or heavy ceremonial sword +- VFX: gold leaf, seals, hard light, contract marks +- skills: `王印砕き`, `契約盾`, `断罪落とし` +- exploration: open royal crest doors and restart mechanisms + +### Purple + +Theme: dream, madness, illusion, shadow. + +- combat: short teleport, clone, confusion, back attack +- weapon: long thin shadow blade +- VFX: afterimages, butterflies, smoke, moonlight +- skills: `夢渡り`, `影替え`, `月裏刺し` +- exploration: pass illusion walls and enter dream rooms + +### Black + +Theme: desire, hunger, abyss. + +- combat: high-risk high-power +- weapon: black spatial tear +- VFX: black liquid, distortion, pull +- skills: `色喰い`, `黒渦`, `飢刃` +- exploration: open black rifts and hidden shortcuts + +### White + +Theme: loss, blankness, mourning. + +- combat: cleanse, remove buffs, special boss mechanics +- weapon: white bone-porcelain blade +- VFX: white powder, paper, silent light +- skills: `弔い`, `白断ち`, `空白化` +- exploration: restore lost text, portraits, and NPC memories + +## Combat + +Required actions: + +- movement +- camera control +- lock-on +- normal attack +- heavy attack +- dodge +- jump +- guard or parry +- color skill +- color switch +- color drain +- interact + +Combat feel: + +- normal attack chains up to three hits +- heavy attacks damage color layers strongly +- dodge changes with held colors, especially purple and blue +- parry strips color layers and blue strengthens the counter +- enemies need HP and color-layer reactions +- hit stop, color-specific SFX, color splash, camera impulse, and screen color + flash support impact + +## Exploration + +Every major path includes color gates. + +| Color | Exploration Gate | +| --- | --- | +| Red | vascular doors, red seal threads, pulsing walls | +| Blue | memory platforms, vanished bridges, past NPC ghosts | +| Green | vine elevators, dead trees, poison marshes | +| Gold | royal doors, contract devices, scales | +| Purple | illusion walls, dream rooms, moonlight stairs | +| Black | rifts, hidden passages, hungry paintings | +| White | unreadable inscriptions, lost portraits, cursed NPCs | + +Color is a resource, so players decide whether to spend it on combat or +exploration. + +## Structure + +Overall structure: semi-open royal city centered around `無色のアトリエ`. + +Hub functions: + +- save +- equipment changes +- color skill upgrades +- NPC conversations +- travel through paintings +- color return after boss defeats +- appearance state review +- music gallery +- bestiary/archive + +Major areas: + +1. `赤絨毯の劇場`: red, blood, stage, applause, execution, theatre +2. `青硝子の記憶図書館`: blue, memory, mirror, water, books, silence +3. `緑毒の温室宮`: green, growth, poison, noble garden, flesh plants +4. `金箔の裁判宮`: gold, authority, contract, trial, chains, scales +5. `紫月の夢宮`: purple, dream, madness, masquerade, moon, illusion +6. `色喰いの王冠`: all colors, black, white, king, hunger, beauty, end + +## Vertical Slice + +The first complete vertical slice is `赤絨毯の劇場`. + +It must include: + +- title screen +- player model and core movement +- jump, dodge, lock-on, normal combo, heavy attack, parry +- color drain +- red, blue, and purple abilities +- HP, color gauge, and three color slots +- death and revive +- three enemies: masked actor, red dancer, stage executioner +- boss: `緋幕の公爵夫人` +- red seal thread, blue memory platform, purple illusion wall +- shortcut +- save point +- return to `無色のアトリエ` +- title music, exploration music, combat layer, boss music +- core SFX, VFX, UI, animation, and post process + +## Boss: 緋幕の公爵夫人 + +Visual: + +- giant red dress +- white mask +- long thin arms +- countless applauding hands on her back +- dress hem becomes blades +- red trail on the stage floor + +Start: + +- player enters center stage +- masked audience applauds +- curtain rises +- Duchess bows +- waltz turns into battle music + +Phase 1: + +- triple dance slash +- red fan slash +- spinning dress attack +- thrust lunge +- applause shockwave +- color core flashes after attacks + +Phase 2 at 50% HP: + +- rotating stage +- falling curtain blades +- more hands on her back +- attack speed up +- red clone dance +- all-direction applause shockwave + +Strategy: + +- draining red strips dress defense +- blue helps read rotating-stage timing +- purple helps reach her back + +Reward: + +- red skill `血華` +- key deeper into the theatre +- Duchess memory fragment + +## UI + +HUD: + +- HP top-left +- state icons top-left +- three `彩槽` slots bottom-left +- color gauge as circular or liquid fill +- current color skill bottom-right +- color drain marker center +- lock-on marker center +- enemy HP and color layer +- boss HP, boss color layer, and boss part lock-on + +Menus: + +- equipment +- status +- color skill trees +- items +- map +- bestiary/archive +- settings +- return to atelier + +## Art Direction + +Core phrase: `鮮やかな闇`. + +The game must look dark, vivid, luxurious, wet, poisonous, and courtly. + +Materials: + +- black marble +- gold leaf +- red carpet +- blue stained glass +- green greenhouse glass +- purple velvet +- white porcelain +- wet wood +- old oil painting +- candle wax +- masks +- silk +- rusted ceremonial blades +- colored liquid + +Accepted evidence must not look gray, generic, or placeholder. + +## Audio Direction + +Do not rely on generic orchestration. Use court music, distorted waltz, +harpsichord, string quartet, low choir, broken music box, glass sounds, metal +sounds, and deep percussion. + +First vertical slice music: + +- title theme +- red theatre exploration +- red theatre combat layer +- red duchess boss +- color drain success accent +- boss victory accent + +## Done Criteria For Vertical Slice + +- title screen appears +- new game starts +- player can fight enemies +- enemies have HP and color layer +- color drain works +- stored color changes weapon/skills +- red theatre can be explored +- color gates open paths +- three enemy types work +- Red Duchess boss works with phase transition +- boss defeat grants reward +- atelier return works +- skill upgrade exists +- HP, color slots, enemy HP, boss HP, and color layer UI are visible +- music and SFX play +- player, enemies, and boss have dedicated animations +- color VFX are visible +- screenshots communicate "color-draining dark fantasy ARPG" diff --git a/docs/product/game-definition-nightwatch-fortress.md b/docs/product/game-definition-nightwatch-fortress.md new file mode 100644 index 0000000..fde4e2b --- /dev/null +++ b/docs/product/game-definition-nightwatch-fortress.md @@ -0,0 +1,430 @@ +# Game Definition: 夜番の砦 + +Status: active product source of truth. + +English working title: `Nightwatch Fortress` + +This document supersedes the active `色喰いの王冠` direction. Historical concepts +remain in the repository only as reference history; the current game is a simpler, +more immediately readable solo 3D dark fantasy ARPG centered on changing weapons +found on the battlefield. + +## One-Line Pitch + +A solo night-hunter ARPG where the player survives monster-infested roads by +quickly picking up, throwing, breaking, swapping, and reusing weapons found in the +field. + +## Product Target + +Make a Steam-facing commercial game, not a prototype. The first target is a +complete vertical slice of `狼森` that can show the hook within 30 seconds: + +- a lone nightwatch fighter enters a dangerous forest at night +- weapons lie on racks, bodies, carts, and the ground +- enemies drop usable weapons +- weapons wear down, chip, break, and become throwable +- thrown weapons stick into enemies or terrain +- a boss can be stopped, staggered, or weakened by using the right weapon type +- the scene reads as a dark but colorful night fantasy, not gray fog + +## Genre Boundaries + +This is: + +- 3D solo dark fantasy action RPG +- third-person camera behind and above the player +- focused on local weapons and readable action +- short-to-medium commercial scope + +This is not: + +- online co-op +- open world +- loot-rarity hack-and-slash +- lore-heavy opaque fantasy +- pure survival craft +- a slow defensive Souls clone +- a gray-box weapon sandbox + +## Core Hook: Local Weapons + +The game is about `現地武器`. + +The player brings two weapons from the fort, but cannot rely on them for the +whole stage. During battle, weapons chip, fall, break, get thrown, stick into +enemies, and are replaced by weapons found in the level. + +Required weapon actions: + +- pick up +- swap +- drop +- throw +- recover from the ground +- pull from an enemy or boss +- use while damaged +- break + +Required weapon states: + +- fresh +- normal +- chipped +- near-broken +- broken + +Near-broken weapons should become attractive as throws. A broken weapon should +not remain a viable main weapon. + +## World + +The kingdom is ordinary by day. At night, monsters appear in forests, mines, +forts, old castles, swamps, and near the capital walls. Villagers close gates, +forts ring bells, soldiers light fires, and the nightwatch goes outside. + +The protagonist is not royal or chosen. They are a night hunter who can fight +where ordinary people cannot. The story should stay clear: + +1. a village or fort is threatened +2. the nightwatch goes out +3. enemies are killed on the road +4. weapons are picked up and replaced +5. a monster nest is reached +6. a large night beast is defeated +7. morning returns + +## Player + +Default role: `夜番の剣士`. + +Visual direction: + +- leather armor +- cloth cloak +- small shoulder lantern +- short blade at the waist +- initial weapon on the back +- gloves, boots, belts, and weapon holders +- practical and cool, not overdesigned +- dark enough for night fantasy, but not lifeless + +The silhouette must clearly change with: + +- one-handed weapon +- two-handed weapon +- spear +- large shield +- bow +- torch + +## Controls + +- move +- camera +- lock-on +- normal attack +- heavy attack +- dodge +- jump +- guard +- parry +- pick up weapon +- drop weapon +- throw weapon +- swap weapon +- use torch +- use item +- interact + +## Weapon Types + +### One-Handed Sword + +Fast, stable, easy to guard and parry with. Baseline weapon. + +### Greatsword + +Wide and heavy. Strong for large parts, but has recovery. + +### One-Handed Axe + +High damage, good against shields, throwable. + +### Great Hammer + +Slow and heavy. Strong posture damage, armor breaking, ground shock. + +### Spear + +Long reach, thrusts, charge stopping, throwable. Can stick into boss legs, +wings, or mouth to interrupt movement. + +### Dagger + +Fast, good after dodge, easy to throw, strong from behind. + +### Bow + +Ranged weak-point tool. Uses limited arrows: fire, poison, heavy arrows. + +### Large Shield + +Best guard tool, can stop charges, push enemies, shield bash, and break under +pressure. + +### Torch + +Lights dark areas, frightens small night monsters, ignites oil, pairs with +one-handed weapons, and can hit weakly. + +## Combat + +Combat should be lighter and more responsive than a heavy Soulslike. The player +should feel that changing weapons is fast and useful, not a menu chore. + +Required combat systems: + +- weapon-specific light attacks +- weapon-specific heavy attacks +- dodge step +- guard +- parry +- posture damage +- weapon durability +- weapon throw +- weapon embed +- weapon pull +- enemy weapon drop +- readable enemy telegraph +- hit stop +- weapon-specific sound +- blood, sparks, wood chips, bone chips, shell chips +- small camera shake on heavy hits + +## Boss And Part Break Rules + +Large enemies and bosses have parts such as head, arms, legs, wings, tail, horn, +armor, and belly. Part breaks must change the fight. + +Boss arenas must include local supplies: + +- weapon rack +- broken weapons +- throwing spears +- oil jars +- torch +- large shield +- arrow bundle +- small healing item + +Boss fights should require choosing, throwing, embedding, breaking, and pulling +weapons. They must not be just dodge-and-hit loops. + +## Core Loop + +1. Accept request at a village or fort. +2. Choose two carry-in weapons from a weapon rack. +3. Enter the night area. +4. Fight enemies. +5. Replace damaged weapons with local weapons. +6. Open shortcuts. +7. Defeat a medium enemy and resupply. +8. Prepare at a boss-room rack. +9. Fight a giant night beast. +10. Defeat the boss and see morning return. +11. Return to the fort. +12. Upgrade, choose the next request, and leave again. + +## Areas + +### 1. 狼森 + +First vertical-slice area. + +Art: + +- dark forest +- moonlight +- mist +- hunter shack +- fallen trees +- stream +- torch stands +- weapon rack + +Enemies: + +- small wolf +- horned beast +- goblin +- shield goblin + +Gimmicks: + +- torch lights the path +- oil jars can be ignited +- fallen trees open shortcuts +- spear stops charging beasts + +Boss: `大狼ガルム` + +- giant wolf +- fast charge, bite, roar, leap +- spear in leg slows movement +- great hammer to head creates large stagger +- tail cut weakens spin attack + +### 2. 鉄鉱山 + +Mine, scaffold, minecart, furnace, workshop. Hammers and axes matter. + +Boss: `炉喰いの巨人` + +### 3. 沼の砦 + +Sunken fort, swamp, planks, poison, broken tower. Shields and torches matter. + +Boss: `沼鎧の騎士` + +### 4. 月見の古城 + +Blue moon castle, walls, chapel, armory, rooftops. Bows and agile weapons matter. + +Boss: `月角の魔女獣` + +### 5. 王都外壁 + +Burning walls, broken gate, refugees' traces, armory, siege tools. + +Boss: `夜王獣` + +The final boss must make the player use the whole weapon language: spear to stop +legs, greatsword on arms, bow to head, shield against charge, and a near-broken +weapon throw to open the final weak point. + +## First Vertical Slice + +The first complete slice is `狼森`. + +Required player features: + +- movement +- jump +- dodge +- lock-on +- one-handed sword +- spear +- great hammer +- bow +- torch +- weapon pickup +- weapon drop +- weapon throw +- weapon durability +- parry +- guard +- HP +- stamina +- death +- respawn + +Required enemies: + +- small wolf +- horned beast +- goblin +- shield goblin + +Required boss: + +- `大狼ガルム` +- intro +- phase 1 +- phase 2 +- spear sticks into leg +- head stagger +- tail part break +- defeat +- dedicated music target + +Required area: + +- Wolf Forest route +- hunter shack +- fallen tree shortcut +- stream +- weapon rack +- torch stand +- boss den + +Required UI: + +- HP +- stamina +- equipped weapon +- sub weapon +- weapon durability +- pickup prompt +- enemy HP +- enemy posture +- boss HP +- part break display +- lock-on +- pause/settings + +## Art Direction + +The image should immediately say: + +- night kingdom +- lone monster hunter +- local weapons on the ground +- huge beast combat + +Use: + +- village lights +- torches +- moonlight +- blood red +- iron highlights +- forest green +- blue castle night air +- warm fort fires + +Do not make the screen gray and unreadable. Darkness is the premise, not the +palette. + +## Sound Direction + +Music should carry night tension, adventure, and combat excitement. Use strings, +low drums, flute, horn, metal percussion, fire, and fort warning horns. + +Sound must sell the weapon hook: + +- pick up +- drop +- throw +- embed +- pull out +- chip +- near-break warning +- break +- weapon-specific hit sounds + +## Completion Definition + +The first vertical slice is not complete until: + +- the game starts from a title or playable entry +- the player can enter Wolf Forest +- weapons can be picked up, swapped, dropped, thrown, damaged, and broken +- enemies can drop usable weapons +- at least one weapon can stick into a large enemy or boss +- embedded weapons can be pulled out +- part break changes boss behavior +- Garm can be defeated +- morning return is shown +- the player can return to the fort +- screenshots communicate the weapon-swapping night-hunter ARPG immediately diff --git a/docs/product/game-definition-relic-runebound.md b/docs/product/game-definition-relic-runebound.md new file mode 100644 index 0000000..522ac1e --- /dev/null +++ b/docs/product/game-definition-relic-runebound.md @@ -0,0 +1,15 @@ +# Game Definition: Relic Runebound + +Status: superseded historical direction. +Date: 2026-06-30. +Japanese title: `聖櫃の巡礼隊` + +This cooperative reliquary-carrying concept is no longer the active product +target. The active product source of truth is +`docs/product/game-definition-nightwatch-fortress.md`. + +Do not use this file to justify cooperative play, reliquary transport, shared +tool roles, same-screen multiplayer, or top-down cooperative production work. +Any useful pieces may only be reused as isolated part-library inspiration after +they pass the current `夜番の砦` art, combat, local-weapon-system, commercial +screenshot, and IP-safety gates. diff --git a/docs/product/game-definition-solo-fantasy-arpg.md b/docs/product/game-definition-solo-fantasy-arpg.md new file mode 100644 index 0000000..99553bf --- /dev/null +++ b/docs/product/game-definition-solo-fantasy-arpg.md @@ -0,0 +1,14 @@ +# Game Definition: Solo Fantasy ARPG + +Status: superseded historical direction. +Date: 2026-06-30. + +This generic solo swordfighter concept is no longer the active product target. +The active product source of truth is +`docs/product/game-definition-nightwatch-fortress.md`. + +Do not use this file to justify generic swordfighter-only structure, ordinary +five-area boss ARPG scope, or colorless fantasy production work. Any useful +pieces may only be reused as isolated part-library inspiration after they pass +the current `夜番の砦` local-weapon, art, combat, commercial screenshot, and +IP-safety gates. diff --git a/docs/product/game-definition-threadlight.md b/docs/product/game-definition-threadlight.md new file mode 100644 index 0000000..870f48f --- /dev/null +++ b/docs/product/game-definition-threadlight.md @@ -0,0 +1,284 @@ +# Game Definition: Threadlight Pilgrimage + +Status: superseded historical direction. +Date: 2026-06-30. + +This document is retained as visual and research history only. Do not use it as +the current product source of truth. The active product is `夜番の砦`, +defined in `docs/product/game-definition-nightwatch-fortress.md`. + +## One-Line Pitch + +Enter frayed provinces as a lantern-thread surveyor, read the traces of a +broken land, bind its monsters and routes with threadlight, and bring lost +memories back to a growing pilgrimage caravan. + +## What The Player Buys + +The player is not buying a sword, axe, or armor set. + +The player is buying the fantasy of becoming a careful, brave field worker in a +beautifully damaged world: + +- reading danger before it kills them +- restoring paths others can no longer cross +- rescuing memories, names, and people from places that have come apart +- returning to a caravan that becomes warmer and stranger after each expedition +- mastering action combat because they understand the land, not because they + found a bigger weapon + +## Genre + +Top-down action roguelite / compact ARPG. + +The first commercial prototype should prove: + +- readable movement, dodge, and melee timing +- one exploration tool that changes combat and navigation +- short expeditions with retreat/deeper risk +- one hub that visibly grows +- one boss whose attacks are explained by the region's world rule + +## Player Role + +The player is a `Threadlight Surveyor`. + +Surveyors belong to a travelling repair pilgrimage. They enter provinces where +roads, names, memories, and buildings have frayed into physical thread. Their +job is not conquest. Their job is to make a route safe enough that others can +return. + +The hero should read as profession first, warrior second: + +- lantern or thread tool +- field coat, mantle, sash, or work apron +- measuring pins, spools, charms, chalk, map cloth, and route tags +- one practical weapon that also supports the job +- clear top-down value mark: pale lantern, white thread, red route cloth, or + blue-black coat + +Avoid hero-first signals: + +- chosen-one crown +- generic holy knight +- huge spiked armor +- skull overload +- weapon collector silhouette +- black-on-black body with no job identity + +## World Premise + +The world entered a `Long Night`. Some provinces did not simply fall into ruin; +they came unstitched. + +Memory became thread. Grief became knots. Roads now loop back on themselves. +Old flags, wedding veils, work clothes, banners, curtains, and burial cloth +wake as hostile relics. Lamps and bells are the safest way to mark a true path. + +People survive by travelling in pilgrimage caravans built around looms, +lantern racks, map cloth, kitchens, sleeping shelves, and memory archives. + +## Hub + +The hub is a mobile `Loom Caravan`. + +It is not a menu room. It is the emotional anchor. + +Hub upgrades should be visible: + +- lantern shelf fills with recovered lights +- NPC bunks appear and become lived-in +- map cloth expands across the wall +- repaired tools hang from hooks +- cooking area becomes warmer +- memory fragments become small exhibits +- failed expeditions leave stains, torn cloth, or missing markers + +This is how runs gain emotional weight beyond loot. + +## Core Loop + +1. Prepare at the Loom Caravan. +2. Choose a frayed province and one route objective. +3. Enter a compact top-down expedition. +4. Read traces: loose threads, lamp color, footprints, corpse direction, bell + rhythm, cloth movement, floor seams. +5. Use the threadlight tool to reveal, bind, anchor, or cut a route. +6. Fight enemies whose attacks come from the same local rule. +7. Decide to retreat with rescued memory or push deeper for a route seal. +8. Return to the caravan and see the world, NPCs, or tools change. + +## First Prototype Slice + +The first 30-second slice should not try to show the whole game. + +It should show: + +- a surveyor entering a rain-dark cloth street +- the threadlight lantern revealing a hidden route seam +- one enemy that eats light +- one enemy that tangles the player's route line +- one elite with a clear bell or banner telegraph +- a short fight with hit stop, knockback, and readable danger colors +- one rescued memory returned to a visible caravan object +- one boss tease: a giant loom gate or bell tower begins moving + +## Visual Pillars + +Use these before character detail: + +1. `Thread and cloth`: route lines, torn banners, veils, knots, sashes, ropes, + stitching, repair tags. +2. `Lantern light`: warm safety, pale memory light, reflected wet stone, + readable player mark. +3. `Soft dark fantasy`: dangerous and melancholy, not rugged armor brutality. +4. `Profession silhouettes`: surveyor, weaver, bell keeper, route guard, + lantern carrier, map stitcher. +5. `Repair over conquest`: visual payoff is opening, mending, lighting, and + returning, not only killing. + +Primary palette: + +- ink blue +- black cloth +- wet stone gray +- warm lantern gold +- pale memory white +- oxblood route cloth +- moss green + +Avoid: + +- one-note slate/dark-blue UI +- generic brown dungeon +- all-black hero on all-black floor +- purple-blue magic as the main palette +- sand/beige fantasy ruins + +## Character Direction + +Existing generated sheets are not final protagonist direction. Preserve them as +parts: + +- H09: hooded field-worker mood, relic weight, top-down cloak mass. +- F01: pale veil/crest and long polearm readability, but convert armor into + ritual field uniform. +- F03: bell-root motif and occult ecology, useful for NPC, elite, or region + boss language. +- H08: lantern/thread action language, but make it a physical survey tool + instead of thin VFX loops. +- H02/H07: reserve for enemy, guard, or boss silhouettes, not first hero. +- F02: hold for redesign; current ninja/fairy signal is off-direction. + +New character prompts should lead with job and world: + +- rain surveyor with lantern-thread spool +- route mender with map cloth and short blade +- bell keeper with cracked bronze mantle +- caravan stitcher with field apron and measuring pins +- ash veil route guard with practical polearm + +Do not lead with: + +- dark fantasy warrior +- magic swordsman +- execution axe hero +- armored valkyrie +- rogue assassin + +## Enemy Ecology + +Enemies are not demons for their own sake. They are frayed memories and hostile +relic behavior. + +First enemy set: + +- `Light-Moth`: small fast creature that dims safe lantern zones. +- `Knot Hound`: low runner that pulls thread routes into hazards. +- `Banner Husk`: cloth soldier that hides attack direction until lit. +- `Bell Root`: stationary pulse enemy that changes floor rhythm. +- `Loom Gate Keeper`: first boss; a doorway/loom/bell machine that controls the + route network. + +Each enemy must teach the world rule: + +- light reveals +- thread binds +- knots trap +- bells warn +- repaired routes create safety + +## Store Page Promise + +The first store-facing image should show: + +- a small readable surveyor with a warm lantern +- a visible thread route crossing a dark floor +- a hostile relic or cloth creature pulling the route apart +- a large landmark: loom gate, bell tower, sunken wedding hall, or stitched + street +- a hint of the caravan or rescued memory, so the game is not only combat + +First trailer structure: + +1. `0-3s`: lantern lights a thread route in a dark street. +2. `3-7s`: the route reveals danger before an enemy strikes. +3. `7-12s`: combat with a white hit core, warm thread trail, and knockback. +4. `12-16s`: retreat/deeper choice at a fraying doorway. +5. `16-21s`: caravan return, recovered memory changes a visible object. +6. `21-26s`: boss landmark wakes. +7. `26-30s`: high-readability combat beat, title card. + +## Audio Direction + +The sound should be dark but not generic. + +Signature materials: + +- taut thread plucks +- loom wood knocks +- lantern glass clicks +- soft bells +- rain on cloth +- low strings +- dry hand percussion +- breath-like choir fragments + +Combat mix: + +- player attacks: short cloth/metal motion plus clear contact +- enemy tells: bells, thread tension, breath, low scrape +- route tool: satisfying pluck, glow, and latch +- reward: small memory chime, not long fanfare + +## Production Gate + +Before any new Meshy/API spend: + +1. Create a world contact sheet for Threadlight Pilgrimage. +2. Create a 1280x800 gameplay mock with surveyor, thread route, enemy, hub + payoff, and boss tease. +3. Create one environment style tile. +4. Create a 30-second trailer beatboard. +5. Only then choose up to two assets for 3D preview. + +Preferred first 3D targets: + +1. threadlight route tool +2. light-moth enemy +3. surveyor body blockout + +Do not start with a fully polished hero model. + +## Rejection Rules + +Reject any direction that: + +- can only be explained as "dark fantasy ARPG" +- looks like weapon concept art without a world rule +- makes every character rugged, armored, and grim +- has no hub or non-combat desire +- cannot show the game promise in one screenshot +- requires paid generation to look convincing +- would still work if the thread, lantern, caravan, and repair ideas were + removed diff --git a/docs/production/asset-ledger-format.md b/docs/production/asset-ledger-format.md new file mode 100644 index 0000000..9e61440 --- /dev/null +++ b/docs/production/asset-ledger-format.md @@ -0,0 +1,47 @@ +# Asset Ledger Format + +Status: required schema for all visible assets. + +Every asset used in a screenshot, capture, build, PR, or public artifact needs a +ledger row before acceptance. + +## Required Columns + +| Column | Required Meaning | +| --- | --- | +| asset_path | Engine/repo path or planned import path. | +| asset_name | Human-readable asset name. | +| asset_type | Character, enemy, environment, VFX, UI, audio, animation, texture, etc. | +| source | URL, marketplace page, local source, generated task, or owned library. | +| creator_vendor | Creator, vendor, or generator/service. | +| license | Exact license or store EULA. | +| commercial_use | Yes/no/unknown plus proof note. | +| modification_rights | Whether editing, retopo, recolor, remix, or rigging is allowed. | +| redistribution_limits | Any restrictions on shipping, source sharing, or resale. | +| proof_path | Receipt, screenshot, license snapshot, prompt record, or terms note path. | +| generated_provenance | Prompt/model/service/date if AI-generated; otherwise `n/a`. | +| ip_similarity_review | Reviewer note confirming no known IP resemblance risk. | +| reviewer | Person/agent approving use. | +| approval_date | Date approved. | +| capture_evidence | Screenshot/video path where the asset appears, if accepted visually. | +| status | proposed, approved, rejected, removed, needs-review. | + +## Acceptance Rule + +No asset can appear in accepted Steam-facing screenshots or captures while its +status is `proposed`, `needs-review`, or `unknown commercial_use`. + +## Generated Asset Rule + +Generated assets require: + +- prompt record +- source service/API name +- generation date +- output file path +- manual cleanup notes +- IP similarity review +- proof that the asset appears in the planned first-screen or trailer evidence + +Do not spend generated credits on filler assets that can be solved with legal +free/owned sources or simple manual modeling. diff --git a/docs/production/engine-bakeoff-ownership.md b/docs/production/engine-bakeoff-ownership.md new file mode 100644 index 0000000..b1adeef --- /dev/null +++ b/docs/production/engine-bakeoff-ownership.md @@ -0,0 +1,32 @@ +# Engine Bakeoff Ownership + +Status: PR ownership map for `夜番の砦`. + +## PR Order + +1. Product Direction: active Nightwatch Fortress bible and docs. +2. Asset Gate: legal source list and asset ledger updates. +3. Unity Setup: ProjectSettings, package manifest, first scene folders. +4. Player Core: movement, camera, dodge, jump, lock-on hook. +5. Weapon Core: weapon data, durability, pickup/drop/swap. +6. Throw/Embed: thrown weapons, sticking, pull-out interaction. +7. Combat Core: attacks, heavy attacks, guard, parry, posture, hit stop. +8. Wolf Forest: dressed first route and weapon racks. +9. Enemies: wolf, horned beast, goblin, shield goblin. +10. Garm: boss arena, part breaks, spear stop, hammer stagger. +11. Capture: 30-second proof and contact sheet. +12. Polish: lighting, HUD, SFX, VFX, input feel. + +## Merge Rule + +Merge product and system foundation PRs before visual polish PRs. Do not merge a +visual scene that proves the wrong game hook. The first implementation must +show local weapons, not just sword combat. + +## Review Focus + +- Does this PR make `現地武器` more true? +- Can the player see and use weapons on the ground? +- Does the screen read as night fantasy but not gray? +- Are asset/license notes present for any imported media? +- Is there screenshot or playtest evidence when visuals/gameplay change? diff --git a/docs/production/engine-bakeoff.md b/docs/production/engine-bakeoff.md new file mode 100644 index 0000000..d210a33 --- /dev/null +++ b/docs/production/engine-bakeoff.md @@ -0,0 +1,76 @@ +# Engine Bakeoff + +Status: reopened for Unreal visual spike after `夜番の砦` visual target review. + +## Current Decision + +Do not commit to Unity as the production engine until Unreal has been tested +against the same `狼森` first-screen target. + +Reason: + +- Unity already has a useful local-weapon gameplay scaffold in this repo. +- The product's selling constraint is visual credibility: night lighting, wet + material response, fog, a heroic hunter silhouette, readable weapons, and a + giant wolf boss must sell the game in one screenshot. +- Unreal 5.8 is installed locally and is likely stronger for this visual target. +- The next decision must be based on rendered evidence, not engine habit. + +See: + +`docs/production/unreal-visual-spike.md` + +## Required First Proof + +Do not count the engine setup as meaningful until a playable scene proves: + +- player movement +- weapon pickup +- weapon swap +- weapon throw +- weapon durability state +- thrown weapon embedding in a large target +- weapon pull/recover +- enemy posture or part reaction +- HUD for HP, stamina, weapon slots, durability, pickup prompt + +## Visual Gate + +The first scene must be dressed enough to read as `狼森`: + +- moonlight +- torch light +- trees +- hunter debris +- weapon rack +- ground weapons +- no default floor/sky/graybox acceptance + +## Unity Version Rule + +For the Unity scaffold, `ProjectSettings/ProjectVersion.txt` is the source of +truth and currently pins Unity `6000.3.18f1`. + +## Unreal Version Rule + +For the Unreal spike, the local engine is: + +`/Users/murakaminaoya/Epic Games/UE_5.8` + +Run the current Unreal proof with: + +```bash +bash Unreal/NightwatchFortress/Scripts/run_wolf_forest_visual_spike.sh +``` + +If the commandlet stalls before producing a log, open the project manually: + +```bash +bash Unreal/NightwatchFortress/Scripts/open_nightwatch_unreal_editor.sh +``` + +## Package Rule + +`Packages/manifest.json` is the package source of truth. No package beyond the +manifest may be added without an approval memo covering official docs, license, +alternatives, and removal method. diff --git a/docs/production/maya-meshy-gate.md b/docs/production/maya-meshy-gate.md new file mode 100644 index 0000000..61059aa --- /dev/null +++ b/docs/production/maya-meshy-gate.md @@ -0,0 +1,42 @@ +# Maya / Meshy Gate + +Status: spending and asset-generation gate for `夜番の砦`. + +## Principle + +Maya and Meshy are useful only when they help prove the local-weapon screen. Do +not spend credits or modeling time on generic fantasy decoration before weapon +pickup/swap/throw/embed works. + +## First Approved Asset Targets, After Separate Approval + +1. Nightwatch player with lantern, weapon holders, and held-weapon silhouettes. +2. First weapon set: sword, spear, hammer, bow, shield, torch with durability + variants. +3. Weapon rack and broken cart kit. +4. Garm boss blockout with leg/head/tail part targets. +5. Wolf Forest hero props: torch stand, hunter shack fragments, fallen weapons. + +## Rejection Rules + +Reject generated assets if: + +- they look like generic fantasy kitbash +- weapon silhouettes are too small or unclear +- no durability states are modeled +- Garm has no readable embed/part-break targets +- asset provenance or commercial-use license is missing +- the asset does not help screenshots communicate local weapon combat + +## Maya Use + +Use Maya for: + +- cleanup +- retopo/blockout inspection +- pivot/origin correction +- scale checks +- weapon hand-grip alignment +- export verification + +Do not use Maya as justification for keeping assets that do not fit the product. diff --git a/docs/production/package-approval-unity-foundation.md b/docs/production/package-approval-unity-foundation.md new file mode 100644 index 0000000..9809c1f --- /dev/null +++ b/docs/production/package-approval-unity-foundation.md @@ -0,0 +1,54 @@ +# Unity Foundation Package Approval + +Status: approved for the first `夜番の砦` Unity foundation PR. + +## Scope + +This memo approves only the built-in Unity modules listed in +`Packages/manifest.json`. + +Approved modules: + +- `com.unity.modules.animation` +- `com.unity.modules.assetbundle` +- `com.unity.modules.audio` +- `com.unity.modules.imageconversion` +- `com.unity.modules.imgui` +- `com.unity.modules.physics` +- `com.unity.modules.screencapture` +- `com.unity.modules.ui` + +No registry package, Asset Store package, third-party plugin, paid asset, or +generated Meshy/Maya asset is approved by this memo. + +## Reason + +The first playable foundation needs only Unity's built-in modules for character +movement, physics overlap checks, IMGUI smoke HUD, audio hooks, and screenshot +capture. Keeping the manifest to built-in modules protects the local-weapon loop +from package sprawl before it proves itself. + +## Official References To Verify Before Changing + +- Unity Manual: Project manifest files, `Packages/manifest.json` + - https://docs.unity3d.com/Manual/upm-manifestPrj.html +- Unity Manual: Unity Package Manager concepts + - https://docs.unity3d.com/Manual/upm-concepts.html +- Unity legal terms + - https://unity.com/legal/terms-of-service + +## Alternatives Considered + +- Add Cinemachine, Input System, URP, Shader Graph, VFX Graph, or Test Framework + now. + - Rejected for this PR. Each can help later, but each needs a separate + approval memo and a concrete implementation need. +- Keep `Packages/manifest.json` absent. + - Rejected. AGENTS.md treats the manifest as the package source of truth. + +## Removal Method + +To remove this approval, delete this memo, remove the unneeded module entries +from `Packages/manifest.json`, then let Unity regenerate +`Packages/packages-lock.json` on the next project open. Do not delete modules +that active scripts or scenes compile against. diff --git a/docs/production/task-split.md b/docs/production/task-split.md new file mode 100644 index 0000000..b97bbe3 --- /dev/null +++ b/docs/production/task-split.md @@ -0,0 +1,114 @@ +# Task Split + +Status: first task map for `夜番の砦` commercial vertical-slice production. + +Active source of truth: +`docs/product/game-definition-nightwatch-fortress.md`. + +## Milestone 0: Repository And Review Gate + +- [ ] M0-T01: Confirm branch protection and required `docs-gate` check. +- [ ] M0-T02: Convert this plan into issue backlog. +- [ ] M0-T03: Define PR order for product bible, asset gate, Unity project, + player, weapon system, Wolf Forest, enemies, Garm, capture, and polish. +- [ ] M0-T04: Maintain required asset ledger format. +- [ ] M0-T05: Close or supersede PRs and branches targeting older product + directions before merging new implementation. + +## Milestone 1: Product And Visual Direction + +- [x] M1-T00: Set `夜番の砦` as the active product direction. +- [x] M1-T01: Define the local-weapon night-hunter ARPG product bible. +- [ ] M1-T02: Create the first commercial final-frame brief: nightwatch player, + Wolf Forest, weapon rack, thrown spear, torch, and Garm silhouette. +- [ ] M1-T03: Build a commercially legal asset shortlist for player, weapons, + Wolf Forest kit, enemies, Garm, VFX, UI, audio, and animation. +- [ ] M1-T04: Create a visual mood board with licensed sources and rejection + notes for "night fantasy but not gray". +- [ ] M1-T05: Define the 30-second local-weapon capture checklist. +- [ ] M1-T06: Create player weapon-silhouette sheets. +- [ ] M1-T07: Create Wolf Forest vertical-slice layout brief. +- [ ] M1-T08: Create Garm boss brief. + +## Milestone 2: Engine Spike And Scene Setup + +- [ ] M2-T01: Add Unity project settings and package manifest, documenting Unity + version and package approvals. +- [x] M2-T01B: Add Unreal visual-spike project, launch runners, and run the + `狼森` first-screen builder against UE 5.8. +- [ ] M2-T02: Add `Assets/Design` source docs for game, art, animation, audio, + enemy, boss, and weapon design. +- [ ] M2-T03: Add scene folders for Title, NightwatchFort, WolfForest, and + Boss_Garm. +- [ ] M2-T04: Add initial Wolf Forest scene with dressed commercial art + direction placeholders, not gray boxes. +- [ ] M2-T05: Add title-screen art target evidence. +- [ ] M2-T06: Capture first screenshot evidence and improve at least one + mock-looking area before completion. + +## Milestone 3: Player And Local Weapon System + +- [ ] M3-T01: Implement `PlayerController`: movement, jump, dodge, camera, + lock-on hooks. +- [ ] M3-T02: Implement `PlayerCombat`: normal attack, heavy attack, guard, + parry, hitboxes, hit stop hooks. +- [ ] M3-T03: Implement `WeaponInstance`: type, durability state, damage, + posture damage, throw rules, embed rules. +- [ ] M3-T04: Implement `WeaponInventory`: two equipped slots, pickup, swap, + drop, throw, recover. +- [ ] M3-T05: Implement `WeaponPickup`: ground/rack prompt, comparison text, + fast pickup flow. +- [ ] M3-T06: Implement durability: fresh, normal, chipped, near-broken, broken. +- [ ] M3-T07: Implement first weapon actions: sword, spear, great hammer, bow, + torch, and shield hooks. +- [ ] M3-T08: Capture playable evidence showing pickup, swap, throw, break, and + recover within 30 seconds. + +## Milestone 4: Enemies And Wolf Forest + +- [ ] M4-T01: Implement small wolf: quick lunge, pack pressure, hit/death. +- [ ] M4-T02: Implement horned beast: telegraphed charge stopped by spear. +- [ ] M4-T03: Implement goblin: carries and drops usable weapon. +- [ ] M4-T04: Implement shield goblin: frontal defense, posture/back counterplay. +- [ ] M4-T05: Implement Wolf Forest route: fort exit, hunter shack, stream, + fallen tree shortcut, weapon rack, torch stand, boss den gate. +- [ ] M4-T06: Add HUD: HP, stamina, equipped weapon, sub weapon, durability, + pickup prompt, enemy HP, posture, lock-on. +- [ ] M4-T07: Capture a 10-second playable Wolf Forest proof. + +## Milestone 5: Garm Boss Proof + +- [ ] M5-T01: Build Garm arena with weapon rack, throwing spears, hammer, shield, + arrow bundle, torch, broken cart, and small heal. +- [ ] M5-T02: Add `大狼ガルム`: entrance, phase 1, phase 2, boss HP, part HP, + posture, defeat. +- [ ] M5-T03: Add spear leg embed and pull-out reaction. +- [ ] M5-T04: Add hammer head stagger. +- [ ] M5-T05: Add tail break that weakens spin/tail attacks. +- [ ] M5-T06: Add near-broken weapon throw weak-point window. +- [ ] M5-T07: Add boss music and morning-return defeat beat. +- [ ] M5-T08: Capture 30-second Steam-facing gameplay and five store-screenshot + candidates. + +## Milestone 6: Commercial Completion Roadmap + +- [ ] M6-T01: Build `鉄鉱山`. +- [ ] M6-T02: Build `沼の砦`. +- [ ] M6-T03: Build `月見の古城`. +- [ ] M6-T04: Build `王都外壁`. +- [ ] M6-T05: Build final `夜王獣` encounter. +- [ ] M6-T06: Save/settings/localization/achievements/performance checklist. +- [ ] M6-T07: Steam page asset checklist. + +## Work Rules + +- One PR should target one task or one tightly coupled task pair. +- Do not add a new engine project and a large asset pack in the same PR. +- Do not mix market research, engine setup, gameplay, and art import in one PR. +- Every visible asset PR needs source/license notes. +- Every visual PR needs screenshot evidence. +- Every gameplay PR needs a playtest note or capture. +- If the screen looks gray, generic, or prototype-like, fix presentation before + adding scope. +- Never implement this as a generic sword game first and local weapons later; + weapon pickup/swap/throw must appear in the first playable loop. diff --git a/docs/production/unreal-visual-spike.md b/docs/production/unreal-visual-spike.md new file mode 100644 index 0000000..95de0e4 --- /dev/null +++ b/docs/production/unreal-visual-spike.md @@ -0,0 +1,128 @@ +# Unreal Visual Spike + +Status: proposed and scaffolded after the engine reassessment on 2026-06-30. + +## Decision + +For `夜番の砦`, Unreal Engine deserves a direct visual spike before more Unity +implementation work. + +Reason: the current product goal is a Steam-facing 3D dark fantasy ARPG where a +single screenshot must sell night lighting, wet materials, fog, a heroic hunter +silhouette, readable weapon pickups, and a huge wolf boss. Unreal's default +renderer, lighting workflow, material response, camera tools, and editor +environment are better aligned with that visual target than continuing to push a +code-generated Unity primitive scene. + +This is not approval to throw away the Unity work. The Unity branch remains a +useful gameplay-system scaffold. The next decision should be based on rendered +evidence from the same `狼森` beat in Unreal. + +## Local Engine + +Detected engine: + +`/Users/murakaminaoya/Epic Games/UE_5.8` + +Editor executable: + +`/Users/murakaminaoya/Epic Games/UE_5.8/Engine/Binaries/Mac/UnrealEditor` + +Command executable: + +`/Users/murakaminaoya/Epic Games/UE_5.8/Engine/Binaries/Mac/UnrealEditor-Cmd` + +## Spike Art Target + +Use: + +`docs/art/nightwatch/wolf-forest-visual-target-v1.png` + +The Unreal spike must prove the same first-read: + +- lone leather-clad night hunter +- visible pickup spear near the player +- weapon rack with alternate weapons +- moonlit wet forest clearing with warm torches, stream, shack, fog, and trees +- large readable original night wolf with leg target and embedded spear cue +- small enemy pressure without clutter + +## Project + +Project file: + +`Unreal/NightwatchFortress/NightwatchFortress.uproject` + +Scene builder: + +`Unreal/NightwatchFortress/Scripts/build_wolf_forest_visual_spike.py` + +CLI runner: + +```bash +bash Unreal/NightwatchFortress/Scripts/run_wolf_forest_visual_spike.sh +``` + +Manual editor runner: + +```bash +bash Unreal/NightwatchFortress/Scripts/open_nightwatch_unreal_editor.sh +``` + +The CLI runner uses `UnrealEditor-Cmd`, writes its absolute log to +`local-evidence/nightwatch_unreal_spike_abs.log`, and passes extra arguments +through to Unreal. The manual editor runner opens `/Game/Maps/WolfForestVisualSpike` +by default so the first visual pass can be inspected immediately. + +## Pass Gate + +Unreal becomes the primary production engine only if the spike produces a +captured image that beats the current Unity generated scene on: + +- lighting depth +- wet ground/material response +- night forest atmosphere +- player and weapon readability +- Garm silhouette +- time-to-polished-screenshot + +If the spike cannot be generated or captured locally, the decision remains open +and no Unity work should be deleted. + +## Current Verification Status + +The project scaffold, launch runners, Python scene builder, generated materials, +and `/Game/Maps/WolfForestVisualSpike` map asset are present. + +Observed from this Codex shell: + +- `UnrealEditor-Cmd -help -stdout -FullStdOutLogOutput` did not return within + 60 seconds and was interrupted. +- Running the project with `-run=pythonscript` also did not create a log or + generated map before interruption. +- Re-running from the engine binary directory with `-abslog`, `-nosound`, and + `-nullrhi` still did not produce a project log before interruption. +- Older local UE logs under the same UE 5.8 install show that the Python + commandlet can reach script execution on this machine. That makes the current + blocker a launch/project-startup issue, not a reason to reject Unreal. +- The launch runner now reaches Python, builds `/Game/Maps/WolfForestVisualSpike`, + and exits with `Success - 0 error(s), 0 warning(s)`. +- A macOS `screencapture` attempt after opening the editor produced a black + 3840x2160 image, so it is not accepted as visual evidence. + +This means the Unreal setup is no longer blocked at commandlet execution. +The engine decision is still not complete. The next required step is to open the +generated map in the local Unreal Editor UI and capture the resulting viewport +from an actual visible editor/game window. + +## Next Visual Proof + +Do not count this spike as passed until one of these exists: + +- a viewport screenshot of `/Game/Maps/WolfForestVisualSpike` +- a render/capture generated from the map +- a failure log that explains why UE 5.8 cannot load this project locally + +The screenshot must be judged against `docs/art/nightwatch/wolf-forest-visual-target-v1.png`. +If it still reads as primitive placeholder art, fix at least one visible issue +before asking for the engine decision. diff --git a/docs/research/commercial-expression-benchmark.md b/docs/research/commercial-expression-benchmark.md new file mode 100644 index 0000000..4b381ac --- /dev/null +++ b/docs/research/commercial-expression-benchmark.md @@ -0,0 +1,49 @@ +# Commercial Expression Benchmark: 夜番の砦 + +Status: market-facing expression research for `夜番の砦`. + +## Store Promise + +Lead with direct language: + +- Hunt monsters at night. +- Pick up whatever weapon you can find. +- Throw broken weapons into beasts. +- Pull spears from giant bosses. +- Survive until morning. + +Avoid explaining dense lore first. The weapon action sells the game. + +## Best First Screenshots + +1. Nightwatch at fort weapon rack before leaving. +2. Wolf Forest fight with visible ground weapons. +3. Spear embedded in a charging beast. +4. Garm boss with tail/head/leg part targets. +5. Morning return after boss defeat. + +## Trailer Rhythm + +Alternate: + +- preparation at fort +- traversal through night +- weapon pickup +- weapon throw +- enemy reaction +- boss part interaction +- victory/morning + +Do not spend the first 30 seconds on menu, lore, or empty walking. + +## UI Expression + +The UI should be practical and rugged: + +- iron frame +- leather strap +- small weapon silhouettes +- durability chips/cracks +- torch/lantern accent light + +Avoid ornate magic UI unless it supports the nightwatch/weapon fantasy. diff --git a/docs/research/free-asset-sources.md b/docs/research/free-asset-sources.md new file mode 100644 index 0000000..bdabccf --- /dev/null +++ b/docs/research/free-asset-sources.md @@ -0,0 +1,46 @@ +# Free Asset Source Notes + +Status: research input for PR review. + +## Principle + +Maya is a cleanup, inspection, conversion, and rigging tool. It is not the +primary free asset source. Autodesk education/trial terms are not a safe source +for commercial game assets. + +Prefer CC0 or explicit commercial-use sources before paid packs or API +generation. + +## First Safe Sources + +| Source | License Posture | Useful For | +| --- | --- | --- | +| Quaternius | CC0, commercial use and modification allowed. | Low-poly enemies, props, weapons, environment blockouts. | +| Kenney | CC0, commercial use and modification allowed. | UI, simple props, prototype packs, environment kits. | +| Poly Haven | CC0, commercial use and modification allowed. | HDRIs, PBR textures, rocks, ground, wood, metal. | +| Fab / Megascans | Fab Standard terms; commercial use generally allowed, asset resale/extraction forbidden. | Rocks, rubble, terrain, ruins, high-quality materials. | +| Unity Asset Store free assets | Unity Asset Store EULA plus individual restrictions. | Fast Unity prototyping, VFX, controllers, dungeon kits. | +| Sketchfab | Only CC0 or CC-BY candidates. Avoid NC, ND, and Editorial. | Specific gaps: altar, statue, tomb, creature, weapon. | +| OpenGameArt | License varies. Prefer CC0/OGA-BY. Avoid GPL/SA for this repo. | Backup source for icons, textures, small props. | +| Mixamo | Useful for animation/rigging tests; verify current Adobe terms before production use. | Humanoid motion tests. | + +## Reject Conditions + +- CC-NC +- CC-ND +- Editorial only +- education/research/trial-only assets +- GPL/CC-BY-SA art in closed commercial game scope +- brands, logos, real IP, recognizable characters +- third-party assets uploaded into AI tools for regeneration unless the license + explicitly permits that use + +## First Practical Test + +1. Build a CC0-only candidate list from Quaternius, Kenney, and Poly Haven. +2. Use Blender as the conversion hub. +3. Use Maya only for inspection, rig cleanup, scale/pivot fixes, or FBX review. +4. Record source URL, author, license, download date, format, and modification + note for every asset. +5. Use Meshy only after the free/CC0 route fails a written acceptance gate. + diff --git a/docs/research/game-character-production-pipeline.md b/docs/research/game-character-production-pipeline.md new file mode 100644 index 0000000..d155d19 --- /dev/null +++ b/docs/research/game-character-production-pipeline.md @@ -0,0 +1,63 @@ +# Game Character Production Pipeline: 夜番の砦 + +Status: production note for the first commercial-looking characters. + +## Character Priority + +1. Nightwatch player +2. Weapon silhouettes and hand placement +3. Small wolf +4. Horned beast +5. Goblin +6. Shield goblin +7. Garm boss + +Do not start with decorative NPCs. The first asset work must support combat and +weapon readability. + +## Player Sheet Requirements + +- front, side, back +- leather armor +- cloth cloak +- shoulder lantern +- waist dagger +- back weapon +- weapon holders +- hand grips for sword, spear, hammer, bow, shield, torch +- silhouette comparison for each held weapon + +## Weapon Sheet Requirements + +For each first-slice weapon: + +- clean state +- chipped state +- near-broken state +- broken state +- thrown/embedded orientation +- pickup icon silhouette + +## Boss Sheet Requirements + +Garm needs: + +- full body +- head weak/stagger target +- leg spear embed target +- tail break target +- phase 1 and phase 2 posture +- size comparison against player + +## 2D To 3D Notes + +Maya/Meshy usage must be gated by an approved asset brief. Generate or model +only assets that prove the local-weapon screen: + +- player with weapon holders +- weapon rack kit +- first weapons +- Garm target parts + +No paid/API spending for generic fantasy decoration before the weapon loop is +working. diff --git a/docs/research/market-fit.md b/docs/research/market-fit.md new file mode 100644 index 0000000..575912a --- /dev/null +++ b/docs/research/market-fit.md @@ -0,0 +1,54 @@ +# Market Fit: 夜番の砦 + +Status: research input for the active `夜番の砦` direction. + +## Positioning + +The commercial hook is simple enough for a Steam capsule/trailer: + +`A solo nightwatch hunter survives by changing weapons found on the battlefield.` + +This is clearer than a lore-heavy premise. The player immediately understands: + +- night is dangerous +- monsters threaten villages and forts +- weapons on the ground matter +- the boss can be fought differently depending on what is picked up + +## Reference Structures, Not Copies + +Use external games only for structural learning: + +- third-person action readability +- fast pickup/swap UX +- weapon impact and durability communication +- boss part-break telegraphing +- readable night lighting +- short trailer pacing + +Do not copy characters, monsters, UI, icons, maps, logos, named systems, or boss +designs. + +## Store-Page Differentiators + +- combat built around local weapons, not a fixed build +- weapons can be thrown, embedded, pulled, and broken +- boss parts react to specific weapon types +- simple night-hunter fantasy that needs little explanation +- dark fantasy that uses torch orange, moon blue, forest green, blood red, and + iron highlights instead of flat gray + +## First Trailer Promise + +Within 30 seconds show: + +1. weapon rack choice +2. night forest +3. weapon break/chip +4. pickup/swap under pressure +5. thrown spear embeds +6. Garm charge interrupted +7. part break or posture collapse + +If the first capture does not show weapon swapping, the project is missing its +own hook. diff --git a/docs/research/video-analysis-tooling.md b/docs/research/video-analysis-tooling.md new file mode 100644 index 0000000..a566d4a --- /dev/null +++ b/docs/research/video-analysis-tooling.md @@ -0,0 +1,46 @@ +# Video Analysis Tooling: 夜番の砦 + +Status: capture-analysis checklist for weapon-swap gameplay. + +## Measurements + +For every candidate gameplay clip, record: + +- first readable weapon rack frame +- first pickup/swap frame +- first durability-state frame +- first weapon throw frame +- first embed frame +- first pull/recover frame +- first boss part reaction frame +- first frame where the hook is understandable without narration + +## Reject Clip If + +- the weapon on the ground is not visible +- pickup prompt is unreadable +- weapon swap takes too long +- thrown weapon does not read as physical +- embed/pull does not have a clear reaction +- boss part break is hidden by camera or particles +- lighting hides weapons or enemies + +## Useful Free Tooling + +- OBS for capture +- ffmpeg for frame extraction +- ImageMagick or Python/Pillow for contact sheets +- Unity `ScreenCapture` for deterministic screenshots + +## Contact Sheet Requirement + +Every visual PR should include a contact sheet with at least: + +1. player idle/readability +2. weapon rack +3. pickup prompt +4. weapon throw +5. embed/recover +6. enemy posture/part reaction +7. boss or large target +8. improved frame after removing the most mock-looking area diff --git a/docs/research/visual-audio-benchmark.md b/docs/research/visual-audio-benchmark.md new file mode 100644 index 0000000..06b3b23 --- /dev/null +++ b/docs/research/visual-audio-benchmark.md @@ -0,0 +1,56 @@ +# Visual And Audio Benchmark: 夜番の砦 + +Status: research input for the active `夜番の砦` direction. + +## Visual Benchmark + +Good frame: + +- one clear hunter silhouette +- weapons visible on body, ground, or boss +- warm torch light and cool moonlight +- readable enemy telegraph +- forest/fort identity in the background +- UI communicates weapon state without clutter + +Bad frame: + +- generic sword duel +- gray forest fog +- no pickup/throw object visible +- boss has no readable parts +- player looks like a placeholder mannequin + +## Color And Lighting + +Use contrast: + +- torch orange against moon blue +- blood red against forest green +- iron highlights against dark leather +- warm fort lights behind the starting area + +Do not use darkness as a substitute for art direction. + +## Audio Benchmark + +The weapon hook needs unique sounds: + +- weapon pickup: fast and satisfying +- drop: physical clatter +- throw: strong release +- embed: heavy stick/impact +- pull: friction and enemy pain +- durability warning: tense but not annoying +- break: decisive + +Music should support danger and momentum, not slow the action into pure horror. + +## Capture Benchmark + +Every captured clip should answer: + +`Why did picking a different weapon matter here?` + +If the answer is unclear, tune level layout, enemy behavior, UI, or weapon +feedback before adding new areas.