Simplify action bar slots to label-only buttons
- Remove the drawn slot icons (research/build/ship/menu/reserved) and their RGB ghost copies; keep the GlitchText split on labels and panel outline only. - Center button text vertically and shrink the bar height from 92 to 56 px now that there is no icon row. - Drop iconSize/iconY config and the drawIcon helper, and update README/comments to match the label-only design.
This commit is contained in:
parent
2b37be7494
commit
adc0389c1e
|
|
@ -146,7 +146,7 @@ layers now, with the rules and panels to come next:
|
|||
Six evenly spaced slots: **Research, Build, Ship, ·, ·, Menu**. The slots
|
||||
fire `onAction` and are otherwise inert; two slots are reserved. Dressed
|
||||
with the menu's CRT language — scanlines over the strip and the GlitchText
|
||||
RGB pull-apart on icons, labels, and the panel outline during bursts. All
|
||||
RGB pull-apart on labels and the panel outline during bursts. All
|
||||
layout, palette, and motion (boot flicker, rail comet, glitch bursts, RGB
|
||||
split, scanlines) live in the config.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
{
|
||||
"_comment": "COMMAND DECK — the cyberpunk bar across the bottom of the screen (rendered by js/ui/ActionBar.js). Six evenly spaced slots: `buttons` is the ORDERED list — give a slot an `id` (the action it will fire) + `label` + `accent`, or leave both `null` for a reserved slot. Buttons currently fire `onAction` (see GameScene); the behavior behind them comes next. Everything visual is procedural (no image assets) and tunable here: layout in `height`/`margin`/`padding`/`button`, palette in `colors`, CRT scanlines in `scanline`, motion in `animation` (boot flicker, the rail comet, the glitch bursts, the RGB pull-apart).",
|
||||
"enabled": true,
|
||||
"height": 92,
|
||||
"height": 56,
|
||||
"margin": { "left": 20, "right": 20, "bottom": 12 },
|
||||
"padding": 16,
|
||||
"button": {
|
||||
"widthFactor": 0.72,
|
||||
"maxWidth": 190,
|
||||
"notch": 10,
|
||||
"iconSize": 26,
|
||||
"fontSize": 14,
|
||||
"letterSpacing": 2.5
|
||||
},
|
||||
|
|
@ -39,7 +38,7 @@
|
|||
"animation": {
|
||||
"bootStagger": 70,
|
||||
"rgb": {
|
||||
"_comment": "RGB pull-apart on icons/labels/panel (the menu title's GlitchText technique): idleOffset px of constant chromatic fringe, burstOffset extra px at full burst, idleAlpha/burstAlpha ghost strength.",
|
||||
"_comment": "RGB pull-apart on labels/panel (the menu title's GlitchText technique): idleOffset px of constant chromatic fringe, burstOffset extra px at full burst, idleAlpha/burstAlpha ghost strength.",
|
||||
"idleOffset": 1.8,
|
||||
"burstOffset": 6,
|
||||
"idleAlpha": 0.4,
|
||||
|
|
|
|||
|
|
@ -6,17 +6,16 @@ import { canvasTexture } from '../utils/Textures.js';
|
|||
import { CyberShape } from './CyberShape.js';
|
||||
|
||||
const FONT_FALLBACK = "'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
|
||||
const DEG = Math.PI / 180;
|
||||
|
||||
/**
|
||||
* COMMAND DECK — the cyberpunk action bar across the bottom of the screen:
|
||||
* a cut-corner console panel with an energy rail, six evenly spaced slot
|
||||
* buttons (drawn icons, RGB-split labels, per-slot accent colors), reserved
|
||||
* "standby" slots, a rail comet that streaks past on a loop, periodic glitch
|
||||
* buttons (label-only, per-slot accent rail on top, RGB-split labels),
|
||||
* reserved "standby" slots, a rail comet that streaks past on a loop, periodic glitch
|
||||
* bursts (slice bars + label jitter), and a boot flicker-in.
|
||||
* Dressed with the menu's CRT language: scanlines over the whole bar (the
|
||||
* same 1px-dark-row tile as CyberOverlay) and the GlitchText RGB pull-apart
|
||||
* on everything — icons, labels, and the panel outline itself on bursts.
|
||||
* on labels and the panel outline itself on bursts.
|
||||
*
|
||||
* Everything is procedural — no image assets — and fully config-driven
|
||||
* (data/actionbar.json): slots, labels, accents, palette, and every
|
||||
|
|
@ -69,9 +68,7 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
slotBorder: toColor(ab.colors?.slotBorder, 0x22405f),
|
||||
reserved: toColor(ab.colors?.reserved, 0x3d4c74),
|
||||
inkCss: toCss(ab.colors?.text ?? '#eaf6ff'),
|
||||
iconY: -12,
|
||||
labelY: 16,
|
||||
iconSize: bc.iconSize ?? 26,
|
||||
labelY: 0, // label-only buttons: the text is centred vertically
|
||||
};
|
||||
// The menu's CRT scanline tuning (data/theme.json) is the default, so
|
||||
// the bar matches the menu unless actionbar.json overrides it.
|
||||
|
|
@ -212,8 +209,6 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
const letterSpacing = bc.letterSpacing ?? 2.5;
|
||||
const cyan = toCss('#00e5ff');
|
||||
const magenta = toCss('#ff2d6f');
|
||||
const iconSize = this.style.iconSize;
|
||||
const iconY = this.style.iconY;
|
||||
|
||||
this.slots = buttons.map((b, i) => {
|
||||
const live = typeof b.id === 'string' && b.id.length > 0;
|
||||
|
|
@ -244,31 +239,7 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
_scaleTw: null,
|
||||
};
|
||||
s.panel = scene.add.graphics().setScrollFactor(0);
|
||||
// RGB-split icon ghosts (the GlitchText technique on the menu title):
|
||||
// additive magenta/cyan copies of the drawn icon, moved per frame.
|
||||
s.iconGhostM = scene
|
||||
.add.graphics()
|
||||
.setPosition(0, iconY)
|
||||
.setScrollFactor(0)
|
||||
.setBlendMode(Phaser.BlendModes.ADD)
|
||||
.setAlpha(0);
|
||||
s.iconGhostC = scene
|
||||
.add.graphics()
|
||||
.setPosition(0, iconY)
|
||||
.setScrollFactor(0)
|
||||
.setBlendMode(Phaser.BlendModes.ADD)
|
||||
.setAlpha(0);
|
||||
s.icon = scene.add.graphics().setPosition(0, iconY).setScrollFactor(0);
|
||||
slot.add([s.panel, s.iconGhostM, s.iconGhostC, s.icon]);
|
||||
|
||||
// Drawn icon (accent for live slots, dim for reserved sockets) —
|
||||
// live slots also get their ghost copies in the split colors.
|
||||
const iconKind = live ? b.id : 'reserved';
|
||||
drawIcon(s.icon, iconKind, iconSize, live ? s.accent : this.style.reserved);
|
||||
if (live) {
|
||||
drawIcon(s.iconGhostM, iconKind, iconSize, 0xff2d6f);
|
||||
drawIcon(s.iconGhostC, iconKind, iconSize, 0x00e5ff);
|
||||
}
|
||||
slot.add(s.panel);
|
||||
|
||||
if (live) {
|
||||
const labelText = String(b.label ?? '').toUpperCase();
|
||||
|
|
@ -312,8 +283,8 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
s.panel.on('pointerout', () => this.setHover(s, false));
|
||||
s.panel.on('pointerdown', () => this.press(s));
|
||||
} else {
|
||||
// Reserved socket: a standby dot that breathes in update().
|
||||
s.resDot = scene.add.circle(0, iconY, 1.6, this.style.reserved, 0.35).setScrollFactor(0);
|
||||
// Reserved socket: a standby dot (centred) that breathes in update().
|
||||
s.resDot = scene.add.circle(0, 0, 1.6, this.style.reserved, 0.35).setScrollFactor(0);
|
||||
slot.add(s.resDot);
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +480,7 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
|
||||
/**
|
||||
* Fire a glitch burst right now: slice bars across the deck, and the RGB
|
||||
* split (level → 1, decays in update) on icons, labels, and the panel.
|
||||
* split (level → 1, decays in update) on labels and the panel.
|
||||
*/
|
||||
triggerBurst(duration = 300, intensity = 1) {
|
||||
const now = this.lastTime ?? this.scene.time.now;
|
||||
|
|
@ -620,7 +591,7 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
const flick = 0.6 + 0.4 * Math.sin(t * 3.3);
|
||||
const baseA = Math.min(1, rgb.idleAlpha * flick + L * rgb.burstAlpha);
|
||||
|
||||
// Slots: the menu's RGB pull-apart on icon + label — magenta left,
|
||||
// Slots: the menu's RGB pull-apart on the label — magenta left,
|
||||
// cyan right (GlitchText's exact offsets), always a faint fringe at
|
||||
// idle, pulled apart + jittered during bursts; per-slot phase so the
|
||||
// six buttons don't shimmer in lockstep.
|
||||
|
|
@ -636,8 +607,6 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
|||
const a = s.hoverOn ? Math.min(1, baseA + 0.25) : baseA;
|
||||
s.ghostMagenta.setAlpha(a).setPosition(-off + jx, this.style.labelY + jy * 0.5);
|
||||
s.ghostCyan.setAlpha(a * 0.95).setPosition(off * 0.85, this.style.labelY - off * 0.3 + jy);
|
||||
s.iconGhostM.setAlpha(a * 0.8).setPosition(-off + jx, this.style.iconY + jy * 0.5);
|
||||
s.iconGhostC.setAlpha(a * 0.75).setPosition(off * 0.85, this.style.iconY - off * 0.3 + jy);
|
||||
}
|
||||
|
||||
// The bar itself: the panel outline pulled apart in magenta/cyan while
|
||||
|
|
@ -835,85 +804,3 @@ function drawBarBody(ctx, W, H, P, c) {
|
|||
corner(x - 3, y + h + 3, 1, -1);
|
||||
corner(x + w + 3, y + h + 3, -1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The drawn slot icons — small, geometric, in the console's language.
|
||||
* Each icon is drawn centered on the icon Graphics' origin.
|
||||
*/
|
||||
function drawIcon(g, id, size, color) {
|
||||
const r = size * 0.44;
|
||||
const glow = (lw) => g.lineStyle(lw, color, 0.25);
|
||||
|
||||
switch (id) {
|
||||
case 'research': {
|
||||
// Orbit: a ring with a lit trail, a satellite riding it, a core.
|
||||
glow(4);
|
||||
g.strokeCircle(0, 0, r);
|
||||
g.lineStyle(1.5, color, 0.95);
|
||||
g.strokeCircle(0, 0, r);
|
||||
g.lineStyle(2, color, 0.5); // trail behind the satellite
|
||||
g.beginPath();
|
||||
g.arc(0, 0, r, -110 * DEG, -25 * DEG);
|
||||
g.stroke();
|
||||
const sa = -35 * DEG;
|
||||
g.fillStyle(color, 1);
|
||||
g.fillCircle(Math.cos(sa) * r, Math.sin(sa) * r, 2.6);
|
||||
g.fillStyle(color, 0.9);
|
||||
g.fillCircle(0, 0, 1.7);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'build': {
|
||||
// Isometric cube — construction.
|
||||
const v = [0, -30, 30, 90, 150, 210].map((deg) => ({
|
||||
x: Math.cos(deg * DEG) * r,
|
||||
y: Math.sin(deg * DEG) * r,
|
||||
}));
|
||||
glow(4);
|
||||
g.strokePoints(v, true);
|
||||
g.lineStyle(1.5, color, 0.95);
|
||||
g.strokePoints(v, true);
|
||||
g.lineStyle(1.5, color, 0.8); // inner edges of the cube
|
||||
g.lineBetween(0, 0, v[0].x, v[0].y);
|
||||
g.lineBetween(0, 0, v[2].x, v[2].y);
|
||||
g.lineBetween(0, 0, v[4].x, v[4].y);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ship': {
|
||||
// Chevron arrow, nose up.
|
||||
const pts = [
|
||||
{ x: 0, y: -r },
|
||||
{ x: r * 0.78, y: r * 0.62 },
|
||||
{ x: 0, y: r * 0.18 },
|
||||
{ x: -r * 0.78, y: r * 0.62 },
|
||||
];
|
||||
glow(4);
|
||||
g.strokePoints(pts, true);
|
||||
g.lineStyle(1.5, color, 0.95);
|
||||
g.strokePoints(pts, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'menu': {
|
||||
// Signal bars — slightly ragged widths, left-aligned.
|
||||
g.fillStyle(color, 0.9);
|
||||
g.fillRect(-r, -r * 0.75, r * 2, 2.4);
|
||||
g.fillRect(-r, -1.2, r * 1.5, 2.4);
|
||||
g.fillRect(-r, r * 0.75 - 2.4, r * 1.85, 2.4);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Reserved socket: a dim diamond — a slot waiting for a command.
|
||||
const d = [
|
||||
{ x: 0, y: -7 },
|
||||
{ x: 7, y: 0 },
|
||||
{ x: 0, y: 7 },
|
||||
{ x: -7, y: 0 },
|
||||
];
|
||||
g.lineStyle(1.2, color, 0.45);
|
||||
g.strokePoints(d, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue