feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import {_decorator} from 'cc';
|
||||
import {GAME_DEFINITIONS} from './game-definition.ts';
|
||||
@_decorator.ccclass('GameEntry_Game_Surface_3')
|
||||
export class TemplateGameEntry {
|
||||
static readonly definition=GAME_DEFINITIONS.Game_Surface_3;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ca0ce008-3ed5-4075-bec9-91aa79785ae7",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "584dbb85-4acc-4bea-9d9b-e0cbeb8741da",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Layer27_CreateRoom"
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class TemplateCreateRoomView extends CreateRoomPage {
|
||||
toggle.node.on(Toggle.EventType.TOGGLE,changed);this.releases.push(()=>toggle.node.off(Toggle.EventType.TOGGLE,changed));
|
||||
});
|
||||
this.groups.forEach(group=>group.allowSwitchOff=false);
|
||||
const bind=(button:Button,action:()=>void)=>{const fn=()=>{if(!this.busy&&this.context)try{action();}catch(error){context.reportError(error instanceof Error?error.message:String(error));}};
|
||||
const bind=(button:Button,action:()=>void)=>{const fn=()=>{if(!this.busy&&this.context)try{action();}catch(error){console.error('[TemplateCreateRoomView]',error);}};
|
||||
button.node.on(Button.EventType.CLICK,fn);this.releases.push(()=>button.node.off(Button.EventType.CLICK,fn));};
|
||||
bind(this.submitButton,()=>context.submit(this.rules.build()));
|
||||
this.cancelButtons.forEach(button=>{const cancel=()=>context.cancel();button.node.on(Button.EventType.CLICK,cancel);this.releases.push(()=>button.node.off(Button.EventType.CLICK,cancel));});this.setBusy(false);
|
||||
|
||||
@@ -1,39 +1,5 @@
|
||||
import {_decorator,Button,Component,Label} from 'cc';
|
||||
import type {PlatformGameSnapshot} from '../../framework/sdk/contracts/index.ts';
|
||||
import type {TemplateRoomActions,TemplateRoomPresentation} from './template-game.ts';
|
||||
const {ccclass,property}=_decorator;
|
||||
/** Template lobby-to-room handoff surface; concrete subgames replace this presentation. */
|
||||
import {_decorator} from 'cc';
|
||||
import {CommonRoomViewBase} from '../../scripts/platform-login/CommonRoomViewBase.ts';
|
||||
const {ccclass}=_decorator;
|
||||
@ccclass('TemplateRoomView')
|
||||
export class TemplateRoomView extends Component implements TemplateRoomPresentation {
|
||||
@property(Label) roomLabel:Label|null=null;
|
||||
@property(Label) playersLabel:Label|null=null;
|
||||
@property(Button) prepareButton:Button|null=null;
|
||||
@property(Button) exitButton:Button|null=null;
|
||||
private actions:TemplateRoomActions|null=null;
|
||||
private bound=false;
|
||||
private pending:'prepare'|'exit'|null=null;
|
||||
private timer:ReturnType<typeof setTimeout>|null=null;
|
||||
private snapshot:PlatformGameSnapshot|null=null;
|
||||
private readonly prepare=()=>this.invoke('prepare');
|
||||
private readonly exit=()=>this.invoke('exit');
|
||||
render(snapshot:PlatformGameSnapshot,actions:TemplateRoomActions):void{
|
||||
if(!this.roomLabel||!this.playersLabel||!this.prepareButton||!this.exitButton)throw Error('TemplateRoomView missing controls');
|
||||
this.actions=actions;this.snapshot=snapshot;
|
||||
if(!this.bound){this.prepareButton.node.on(Button.EventType.CLICK,this.prepare);this.exitButton.node.on(Button.EventType.CLICK,this.exit);this.bound=true;}
|
||||
this.roomLabel.string=`房间号:${snapshot.room.roomcode}`;
|
||||
this.playersLabel.string=snapshot.seats.map(p=>`${p.seat+1} ${p.nickname}${p.playerId===snapshot.self.playerId?'(我)':''} ${!p.online?'离线':p.ready?'已准备':'未准备'}`).join('\n');
|
||||
const self=snapshot.seats.find(p=>p.playerId===snapshot.self.playerId);
|
||||
if(self?.ready||snapshot.connection.phase!=='logged-in')this.clearPending();
|
||||
this.prepareButton.interactable=this.pending===null&&snapshot.connection.phase==='logged-in'&&snapshot.room.stage===0&&!self?.ready;
|
||||
this.exitButton.interactable=this.pending===null&&snapshot.connection.phase==='logged-in';
|
||||
}
|
||||
private invoke(kind:'prepare'|'exit'):void{
|
||||
if(!this.actions||this.pending)return;
|
||||
try{this.actions[kind]();this.pending=kind;this.prepareButton!.interactable=false;this.exitButton!.interactable=false;
|
||||
this.timer=setTimeout(()=>{this.clearPending();if(this.snapshot&&this.actions)this.render(this.snapshot,this.actions);this.node.emit('room-action-timeout');},15000);
|
||||
}catch(error){this.node.emit('room-action-error',error);}
|
||||
}
|
||||
private clearPending():void{if(this.timer!==null)clearTimeout(this.timer);this.timer=null;this.pending=null;}
|
||||
close():void{this.clearPending();if(this.bound){this.prepareButton?.node.off(Button.EventType.CLICK,this.prepare);this.exitButton?.node.off(Button.EventType.CLICK,this.exit);}this.bound=false;this.actions=null;}
|
||||
onDestroy():void{this.close();}
|
||||
}
|
||||
export class TemplateRoomView extends CommonRoomViewBase {}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** XML is the sole identity/version source. This config only locates it. */
|
||||
export const gameConfig = Object.freeze({versionResource:'Game_Surface_3/version',assetRoot:'games/template'});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "69288246-9bc5-4ec2-9afa-54574e136a4f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import {gameConfig} from './game-config.ts';
|
||||
import {createTemplateGame} from './template-game.ts';
|
||||
import {roomChatConfig} from './room-chat-config.ts';
|
||||
import type {GameDefinition} from '../../framework/sdk/contracts/game-definition.ts';
|
||||
export const GAME_DEFINITIONS = {
|
||||
Game_Surface_3:{
|
||||
config:gameConfig,key:'Game_Surface_3',route:'Game_Surface_3',
|
||||
createGame:function(this:GameDefinition,gameId:string|number){return createTemplateGame(gameId,this);},
|
||||
chat:roomChatConfig,roomMenu:{mainSceneButton:false,vipInfinite:false},
|
||||
createPageClass:'TemplateCreateRoomView',roomViewClass:'TemplateRoomView',
|
||||
resources:{createRoom:'games/template/Layer27_CreateRoom.prefab',room:'games/template/TemplateRoom.prefab',roomScene:'scenes/TemplateRoom.scene'},
|
||||
},
|
||||
} as const satisfies Readonly<Record<string,GameDefinition>>;
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e3fb84ba-d2b5-42f8-8f86-897f14cdceb0",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "ebfeb186-fb9e-403e-86f4-bce2d7f5411f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"isBundle": true,
|
||||
"bundleConfigID": "default",
|
||||
"bundleName": "version-Game_Surface_3",
|
||||
"priority": 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "00fe2433-722e-42a6-86b7-d646b79b2ca4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game>
|
||||
<agent id="1B2h0ccl205c390Y28m1Ajdplkuu4wgy" name="天盛网络"/>
|
||||
<game id="8x4l0rGjf026f60c48h0mbUAhK5vV16f" name="友乐游戏"/>
|
||||
<channel id="aouv0LotK0pYyQ0Pdrx0CsdcaezfzrcG" name="友乐互动游戏"/>
|
||||
<version value="1" name="1.1"/>
|
||||
</game>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "f3c5f3ca-cd7c-4571-9a64-830629c787e9",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "afdab568-f194-4b97-a3ea-bb55baaa8360",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,29 +1,39 @@
|
||||
import type {GameRoomProjection,GameRoomProjectionSource} from '../../framework/sdk/contracts/game-room-projection.ts';
|
||||
import type {GameEntry,GameHost,GameModule,PlatformGameSnapshot} from '../../framework/sdk/contracts/index.ts';
|
||||
import {TemplateRoomRules} from './template-room-rules.ts';
|
||||
import type {PlatformToGameEvent} from '../../framework/sdk/contracts/platform-events.ts';
|
||||
import {requireRoomCapabilities,type RoomCapabilities} from '../../framework/sdk/contracts/index.ts';
|
||||
export interface TemplateRoomActions {prepare():void;exit():void;}
|
||||
export interface TemplateRoomPresentation {render(snapshot:PlatformGameSnapshot,actions:TemplateRoomActions):void;close():void;}
|
||||
export interface TemplateRoomPresentation {render(snapshot:PlatformGameSnapshot,actions:TemplateRoomActions,projection?:GameRoomProjection|null):void;close():void;}
|
||||
/** Template hooks are intentionally empty, matching 02_SubGame_Input.js. No gameplay is inferred. */
|
||||
export function createTemplateGame(gameId:string|number){
|
||||
export function createTemplateGame(gameId:string|number,identity:Pick<GameEntry,'key'|'route'>){
|
||||
let active:TemplateModule|null=null;
|
||||
class TemplateModule implements GameModule {
|
||||
private host:GameHost|null=null;
|
||||
private capabilities:RoomCapabilities|null=null;
|
||||
readonly roomProjection:GameRoomProjectionSource|null=null;
|
||||
private unsubscribeProjection:(()=>void)|null=null;
|
||||
private unsubscribe:(()=>void)|null=null;
|
||||
private view:TemplateRoomPresentation|null=null;
|
||||
restored:unknown=null;
|
||||
attach(host:GameHost):void{
|
||||
if(this.host||active)throw Error('Template module already attached');
|
||||
this.host=host;active=this;this.unsubscribe=host.subscribe(()=>this.render());
|
||||
if(this.capabilities||active)throw Error('Template module already attached');
|
||||
this.capabilities=requireRoomCapabilities(host);active=this;this.unsubscribe=this.capabilities.room.subscribe(()=>this.render());
|
||||
this.unsubscribeProjection=this.roomProjection?.subscribe(()=>this.render())??null;
|
||||
}
|
||||
mount(view:TemplateRoomPresentation):void{this.view?.close();this.view=view;this.render();}
|
||||
private requireHost():GameHost{if(!this.host)throw Error('Template room session disposed');return this.host;}
|
||||
private render():void{this.view?.render(this.requireHost().getSnapshot(),{prepare:()=>this.requireHost().execute({type:'room.prepare'}),exit:()=>this.requireHost().execute({type:'room.exit'})});}
|
||||
handlePlatformEvent():void{this.render();}
|
||||
private requireCapabilities():RoomCapabilities{if(!this.capabilities)throw Error('Template room session disposed');return this.capabilities;}
|
||||
private render():void{this.view?.render(this.requireCapabilities().room.getSnapshot(),{prepare:()=>this.requireCapabilities().room.prepare(),exit:()=>this.requireCapabilities().room.requestExit()},this.roomProjection?.getSnapshot()??null);}
|
||||
handlePlatformEvent(event:PlatformToGameEvent):void{
|
||||
if(event.type==='room.dissolution-confirmed')throw Error('模板游戏解散结算界面尚未接入');
|
||||
this.render();
|
||||
}
|
||||
handleGameMessage():void{/* Original template _ReceiveData hook is empty. */}
|
||||
restore(deskinfo:unknown):void{this.requireHost();this.restored=deskinfo;}
|
||||
dispose():void{this.unsubscribe?.();this.unsubscribe=null;this.view?.close();this.view=null;this.host=null;this.restored=null;if(active===this)active=null;}
|
||||
restore(deskinfo:unknown):void{this.requireCapabilities();this.restored=deskinfo;}
|
||||
dispose():void{this.unsubscribeProjection?.();this.unsubscribeProjection=null;this.unsubscribe?.();this.unsubscribe=null;this.view?.close();this.view=null;this.capabilities=null;this.restored=null;if(active===this)active=null;}
|
||||
}
|
||||
const rules=new TemplateRoomRules();
|
||||
const entry:GameEntry={key:'Game_Surface_3',gameId,route:'Game_Surface_3',resolveSeatCount:roomtype=>rules.seatCount(roomtype),createModule:()=>new TemplateModule()};
|
||||
const entry:GameEntry={key:identity.key,gameId,route:identity.route,resolveSeatCount:roomtype=>rules.seatCount(roomtype),createModule:()=>new TemplateModule()};
|
||||
return {entry,mount(view:TemplateRoomPresentation){if(!active)throw Error('Template room not attached');active.mount(view);},
|
||||
get roomProjection(){return active?.roomProjection??null;},
|
||||
get restoredSnapshot():unknown{return active?.restored;}};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class TemplateRoomRules {
|
||||
if(!Number.isInteger(index)||index<0||index>1)throw Error('Invalid template option');
|
||||
this.selected[group]=index;
|
||||
}
|
||||
build():unknown[]{return [1,4,1,2,2,[1,1,[1,2000,10],null,null,1],[1,0,5]];}
|
||||
build():Roomtype[]{return [1,4,1,2,2,[1,1,[1,2000,10],null,null,1],[1,0,5]];}
|
||||
seatCount(roomtype: Roomtype):number{
|
||||
if(!Array.isArray(roomtype))throw Error('Template roomtype must be an array');
|
||||
// Desk.Create uses Game_Config.Max.PlayerCnt because the template defines no getMaxPlayerCount hook.
|
||||
|
||||
Reference in New Issue
Block a user