a482a9c4ed
SLICE A - rooms become places: ExpeditionBiome duplicated to sub-slot 1 (X+1500) - ground, 205 decor children, rim cliffs, ambience; collider ring + 3 landmark colliders duplicated in the subscene; NEW in-room cover at BOTH slots (4 collidable rocks each: Environment-layer box colliders in the subscene + matching Synty rock meshes in Game.unity, placed clear of the landing point, portal spot, spawn ring and boss spawn). Half of all rooms previously played on a literal void. SLICE C - owned FX wired: VFXConfig gains Portal + EnemySpawn slots (FX_Portal_Round_01 / FX_Dust_Big_01 from PolygonParticleFX); the portal beacon prefers the authored effect (procedural pillar stays as fallback); enemies get a spawn-emerge cue on the ghost add-edge (primed-scan guard skips the connect flood). SLICE D (teaching) - the two undiscoverable verbs are now taught: dash on the Move step, class ability + attack on the Rooms step (scheme-aware glyphs); How-to-Play Controls adds Class ability + Dash rows and fixes the wrong RT glyph; Loop step 1 mentions class pick + prep; the stale '50 starting Ore' now interpolates Tuning.StartingOre. 456/456 EditMode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
147 lines
7.3 KiB
C#
147 lines
7.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ProjectM.Simulation;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace ProjectM.Client
|
|
{
|
|
/// <summary>
|
|
/// The replayable "How to Play" reference card (UI Toolkit), built on <see cref="MenuUi"/> like
|
|
/// <see cref="SettingsScreen"/> and reachable from BOTH the main menu and the in-game pause overlay. Tabbed,
|
|
/// one glanceable page each: Controls (for the chosen class), The Loop (the annotated win-condition diagram —
|
|
/// the single highest-value page, since the inverted goal "clear expeditions to win" is the #1 new-player
|
|
/// confusion), Build & Economy, Threats, Win/Lose. Static + stateless: <see cref="Build"/> returns a fresh
|
|
/// full-screen panel each call; the caller owns its lifetime (RemoveFromHierarchy on close).
|
|
/// </summary>
|
|
public static class HowToPlayPanel
|
|
{
|
|
static readonly string[] Tabs = { "Controls", "The Loop", "Build & Economy", "Threats", "Win / Lose" };
|
|
|
|
public static VisualElement Build(Action onClose)
|
|
{
|
|
var root = MenuUi.FullScreenRoot(true);
|
|
var card = MenuUi.Card("HOW TO PLAY");
|
|
card.style.minWidth = 640;
|
|
card.style.maxWidth = 780;
|
|
root.Add(card);
|
|
|
|
var tabBar = new VisualElement();
|
|
tabBar.style.flexDirection = FlexDirection.Row;
|
|
tabBar.style.justifyContent = Justify.Center;
|
|
tabBar.style.marginBottom = 12;
|
|
card.Add(tabBar);
|
|
|
|
var content = new VisualElement();
|
|
content.style.minHeight = 230;
|
|
card.Add(content);
|
|
|
|
var tabButtons = new List<Button>();
|
|
void Show(int idx)
|
|
{
|
|
content.Clear();
|
|
BuildTab(content, idx);
|
|
for (int i = 0; i < tabButtons.Count; i++)
|
|
tabButtons[i].style.backgroundColor = i == idx
|
|
? new Color(0.16f, 0.34f, 0.42f, 1f)
|
|
: new Color(0.16f, 0.20f, 0.27f, 1f);
|
|
}
|
|
|
|
for (int i = 0; i < Tabs.Length; i++)
|
|
{
|
|
int idx = i;
|
|
var b = MenuUi.Button(Tabs[i], () => Show(idx));
|
|
b.style.height = 32; b.style.fontSize = 13;
|
|
b.style.marginLeft = 3; b.style.marginRight = 3;
|
|
b.style.flexGrow = 1;
|
|
tabButtons.Add(b);
|
|
tabBar.Add(b);
|
|
}
|
|
|
|
card.Add(MenuUi.Button("Back", () => onClose?.Invoke()));
|
|
Show(0);
|
|
return root;
|
|
}
|
|
|
|
static void BuildTab(VisualElement c, int idx)
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0: // Controls (chosen class)
|
|
bool ranger = WorldLauncher.SelectedClass == (byte)CharacterId.Ranger;
|
|
Head(c, ranger ? "CLASS: Ranger (ranged anchor)" : "CLASS: Warrior (melee anchor)");
|
|
Body(c, "Move — WASD / Left Stick");
|
|
Body(c, "Aim — Mouse cursor / Right Stick");
|
|
Body(c, "Attack — LMB / X (3-hit melee combo — the finisher hits hard)");
|
|
Body(c, ranger ? "Class ability — RMB / LT (aimed power shot)" : "Class ability — RMB / LT (cone slam with knockback)");
|
|
Body(c, "Dash — LShift / B (brief invulnerability — dodge THROUGH attacks)");
|
|
Body(c, "Build menu — Tab / Y");
|
|
Body(c, "Place / Cancel (in build) — LMB / RMB (A / B on gamepad)");
|
|
Body(c, "Deposit at base — G");
|
|
Body(c, "Ready up (launch a run) — T");
|
|
Body(c, "Boons / route map / shop — mouse click");
|
|
|
|
Body(c, "Inventory — I");
|
|
Body(c, "Pause — Esc");
|
|
break;
|
|
case 1: // The Loop
|
|
Head(c, "THE LOOP — expedition RUNS are how you win");
|
|
Body(c, "1. BASE — pick your CLASS + buy PREP buffs (this run only); build Turrets + a Fabricator; spend Aether on PERMANENT class upgrades.");
|
|
Body(c, "2. READY UP [T] — when everyone is ready, the party launches into a multi-room run together.");
|
|
Body(c, "3. RUN — clear each room, pick 1 of 3 BOONS (this run only), choose your path on the map, mine the crystals.");
|
|
Body(c, "4. BOSS — fell it and the party returns with the haul: +1 on the Engine meter. A wipe banks nothing.");
|
|
Body(c, "5. SIEGE — a completed run provokes retaliation at base. Defend the Core!");
|
|
Body(c, "6. WIN — fill the Engine meter, then hold the final siege.");
|
|
break;
|
|
case 2: // Build & Economy
|
|
Head(c, "RESOURCES & BUILDING");
|
|
Body(c, "Ore — main currency. Attack the glowing crystals INSIDE runs and haul them home (they're scarce — spend well).");
|
|
Body(c, "Turret (40 Ore) — auto-fires at enemies. Needs Charge as ammo.");
|
|
Body(c, "Fabricator (30 Ore) — converts Ore → Charge so turrets keep firing.");
|
|
Body(c, "Wall (Biomass) — a cheap barrier that blocks enemies.");
|
|
Body(c, "Aether — rare; fuels PERMANENT class upgrades at the base between runs.");
|
|
Body(c, "Open Build with Tab (Y), pick a piece, click a green tile to place it.");
|
|
break;
|
|
case 3: // Threats
|
|
Head(c, "THREATS");
|
|
Body(c, "Husks assault the base during a Siege — keep them off the Engine Core.");
|
|
Body(c, "A Husk that reaches the Core drains it. Lose the Core in the final siege and the run ends.");
|
|
Body(c, "Runs escalate room by room — elites guard the deep paths, a boss guards the end.");
|
|
Body(c, "Enemy variety scales the deeper you push.");
|
|
break;
|
|
default: // Win / Lose
|
|
Head(c, "WIN / LOSE");
|
|
Body(c, "WIN — clear expeditions to fill the Engine meter, then hold the final siege.");
|
|
Body(c, "LOSE — the Engine Core falls during the final siege.");
|
|
Body(c, "A Core breach mid-run is only a setback: resources lost, the Core recovers in Calm.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Head(VisualElement c, string t)
|
|
{
|
|
var l = new Label(t);
|
|
l.style.color = MenuUi.Accent;
|
|
l.style.fontSize = 16;
|
|
l.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
l.style.marginBottom = 8;
|
|
l.style.whiteSpace = WhiteSpace.Normal;
|
|
var th = HudTheme.Get();
|
|
if (th != null) th.ApplyDisplay(l.style);
|
|
c.Add(l);
|
|
}
|
|
|
|
static void Body(VisualElement c, string t)
|
|
{
|
|
var l = new Label(t);
|
|
l.style.color = MenuUi.TextCol;
|
|
l.style.fontSize = 14;
|
|
l.style.marginBottom = 5;
|
|
l.style.whiteSpace = WhiteSpace.Normal;
|
|
var th = HudTheme.Get();
|
|
if (th != null) th.ApplyBody(l.style);
|
|
c.Add(l);
|
|
}
|
|
}
|
|
}
|