M5-M7: сцены, лобби, экран игры и цикл матча
Обе сцены собраны через Code Mode, не руками. - loader: поднимает модули, прогревает арт карт, уходит в lobby - lobby: обе UI-ветки в одной сцене (по ARCHITECTURE.md), матч — это смена видимого корня, а не загрузка сцены - GameScreen рисует текущее состояние и доводит узлы твинами, а не проигрывает поток событий: узел карты живёт вместе со своей картой, поэтому "шестёрка ушла из руки на стол" не требует отдельного случая - один тап = ход целиком; логика вынесена в core/rules/intent.ts и покрыта тестами, в том числе проверкой, что она никогда не предложит ход, который правила отвергнут - арт карт переехал в assets/resources: .pac — это инструкция упаковки на сборке, а не рантайм-ассет, и без ссылок на кадры в сборку не попадало ничего. Теперь все 37 спрайтов в одной текстуре 2048x2048 - клики кнопок подписываются в коде, а не через clickEvents сцены - строки интерфейса собраны в одном файле Сборка web-mobile проходит чисто, все ссылки в сцене проверены. Вживую не запускалось: браузер в этом окружении не поднимает главный цикл движка. Подробности — в docs/TECHNICAL.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { _decorator, Component, Node } from 'cc';
|
||||
import { App } from '../app/App';
|
||||
import { LobbyScreen } from './lobby/LobbyScreen';
|
||||
import { GameScreen, MatchOutcome } from './game/GameScreen';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* Owns the two screens the lobby scene contains and moves between them.
|
||||
*
|
||||
* ARCHITECTURE.md puts the main game UI in the lobby scene, so a match is a
|
||||
* change of visible screen rather than a scene load: no reload cost between
|
||||
* games, and no way for a half-finished match to leak into a new one as long
|
||||
* as `GameScreen.teardown` is honest about clearing up.
|
||||
*/
|
||||
@ccclass('MainFlow')
|
||||
export class MainFlow extends Component {
|
||||
@property(LobbyScreen)
|
||||
lobby: LobbyScreen | null = null;
|
||||
|
||||
@property(GameScreen)
|
||||
game: GameScreen | null = null;
|
||||
|
||||
@property(Node)
|
||||
lobbyRoot: Node | null = null;
|
||||
|
||||
@property(Node)
|
||||
gameRoot: Node | null = null;
|
||||
|
||||
start(): void {
|
||||
if (this.lobby) {
|
||||
this.lobby.onStartRequested = () => this.startMatch();
|
||||
}
|
||||
if (this.game) {
|
||||
this.game.onFinished = (outcome) => this.finishMatch(outcome);
|
||||
this.game.onQuitRequested = () => this.abandonMatch();
|
||||
}
|
||||
this.showLobby();
|
||||
}
|
||||
|
||||
private showLobby(): void {
|
||||
setActive(this.gameRoot, false);
|
||||
setActive(this.lobbyRoot, true);
|
||||
this.lobby?.refresh();
|
||||
}
|
||||
|
||||
private startMatch(): void {
|
||||
setActive(this.lobbyRoot, false);
|
||||
setActive(this.gameRoot, true);
|
||||
this.game?.startNewMatch();
|
||||
}
|
||||
|
||||
private finishMatch(outcome: MatchOutcome): void {
|
||||
if (App.exists) {
|
||||
App.instance.recordMatch(outcome);
|
||||
}
|
||||
this.game?.teardown();
|
||||
this.showLobby();
|
||||
}
|
||||
|
||||
/** Leaving early still ends the match — and it counts as a loss. */
|
||||
private abandonMatch(): void {
|
||||
this.finishMatch('loss');
|
||||
}
|
||||
}
|
||||
|
||||
function setActive(node: Node | null, active: boolean): void {
|
||||
if (node) {
|
||||
node.active = active;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user