ðŠ Hitsumabushi SDK æè¡ä»æ§æž
æ¬æžã®äœçœ®ã¥ã ç¶æ ã»èªè : ã²ãŒã ã¯ãªãšã€ã¿ãŒåãã®ã忢±SDKããŒãžã§ã³ã®çŸè¡å ¬éAPIã»éä¿¡ä»æ§ã§ããå°æ¥å¯Ÿå¿ãšæèšããæ©èœïŒSteamworks飿ºãªã©ïŒã¯æªå®è£ ã®èšç»ã§ãã åªå é äœ: æ¬æžãSDKå ¬éAPIã®æ£æ¬ïŒSSOTïŒãšããå éšèšèšæžãä»ã®èª¬ææãšççŸããå Žåã¯æ¬æžãåªå ããŸããSDKå®è£ ãšã®äžäžèŽã¯ä»æ§ãŸãã¯å®è£ ã®äžå ·åãšããŠç¢ºèªã»ä¿®æ£ããäžæ¹ãç¡æ¡ä»¶ã«æ£ãããã®ãšããªãã§ãã ããã
Hitsumabushi SDK ã¯ãåå¥ã®å¯ŸæŠWebã²ãŒã ã¢ã㪠(iframe) ãš ã²ã€ãŸã¶ã æ¬äœ (芪ãŠã£ã³ããŠ) ã®éã§ãéä¿¡ã»èšå®æ³šå
¥ã»å¯ŸæŠçµæã»OBSç¶æ
飿ºã»å°æ¥ã®Steamworks SDKäºæãç°¡åãã€å®å
šã«è¡ãããã®å
¬åŒJavaScript API/SDKã§ãã
æ¬æžã¯APIã»éä¿¡ãããã³ã«ã®å®å šãªãã¡ã¬ã³ã¹ã§ããã€ã³ã¹ããŒã«æé ãæå°æ§æããåãããŸã§ã®ãã¥ãŒããªã¢ã«ã¯ Getting Started ãåç §ããŠãã ããã
1. ã¢ãžã¥ãŒã«æäŸïŒå°å ¥æ¹æ³
å°å ¥ã®æé 㯠Getting Started ãæ£æ¬ã§ããããã§ã¯åç §ã«å¿ èŠãªäºå®ã ãæããŸãã
ã²ãŒã ã¯SDKã <script> ã¿ã°ã§èªã¿èŸŒã¿ãã°ããŒãã«ã® window.Hitsumabushi ãšããŠäœ¿ããŸãã
URL 㯠https://milightpartner.jp/sdk/<version>/hitsumabushi-sdk.js ã§ãçãåºå®ããŠèªã¿èŸŒã¿ãŸãã
<version>ã«ã¯ãã€ã³ã¹ããŒã«ããŠãã SDK ããã±ãŒãžã®çãå ¥ããŸãïŒnpx hitsumabushi initãçæããindex.htmlã«ã¯ããã®çãå ¥ã£ãŠããŸãïŒããã¹ãããã«ãšverifyã¯ãã®çã® SDK ã§åãã®ã§ã確ããã SDK ãšæ¬çªã§èªãŸãã SDK ãäžèŽããŸãã- çä»ãã®ãã¡ã€ã«ã¯äžåºŠå
¬éãããäžèº«ãå€ãããŸãããSDK ãæŽæ°ãããšãã¯ããã® URL ã®çãæžãæããŠãã ããããã¹ãããã«ãš
verifyãããã® URL ããèªã¿èŸŒãã SDK ã§ã²ãŒã ãåãããŸãã - çãªãã®
https://milightpartner.jp/sdk/hitsumabushi-sdk.jsãåŒãç¶ã䜿ããŸãããåžžã«ææ°ãè¿ããããSDK ã®å€æŽã§ã²ãŒã ã®åããå€ããããŸããverifyãâ warnã§å ±åããŸãã
â ïž ãã³ãã©ã䜿ã£ãESM importïŒ
import { Hitsumabushi } from '@milightpartner/hitsumabushi-sdk'ïŒã¯ã ã²ãŒã æ¬äœã®èªã¿èŸŒã¿æ¹ãšããŠã¯çŸåšãµããŒãããŠããŸããã ãã¹ãããã«ïŒcreateTestRoomïŒãšverifyã®èµ·å確èªã jsdom äžã§åããŠãããjsdom ã¯<script type="module">ãå®è¡ããªãããã ESMæ§æã®ã²ãŒã ã¯npm testãšverifyãéããŸãããnpmããã±ãŒãžèªäœã¯ESMã§ããã ãã㯠CLI ãš@milightpartner/hitsumabushi-sdk/testingïŒNodeäžã§åããã®ïŒã®ããã®ãã®ã§ãã
2. JavaScript API ãªãã¡ã¬ã³ã¹
Hitsumabushi.init(options)
SDKãåæåããããŒã¿ã«ãã¹ããž GAME_READY éç¥ãéä¿¡ããŸãã
åŒæ° options (ãªããžã§ã¯ã):
onCreateMatch(config)(Function): ãã®ã¯ã©ã€ã¢ã³ããæåã®å¯ŸæŠèšå®ãåãåã£ãæã«1åã ãåŒã°ããã³ãŒã«ããã¯ãDOMã®æ§ç¯ãã€ãã³ããªã¹ããŒã®ç»é²ãã¢ã»ããã®èªã¿èŸŒã¿ãªã©ãäžåºŠããã°æžãåŠçãããã«çœ®ããŸãããªãããã§ã¯åŒã°ããªããããããã§äœã£ããã®ãäœãçŽãããããšã¯ãããŸããã- 飿Šã®éäžããåå ããã¯ã©ã€ã¢ã³ãïŒåŸããå
¥å®€ãã芳æŠè
ãªã©ïŒã§ãããã®ã¯ã©ã€ã¢ã³ãã«ãšã£ãŠã®ååã§åŒã°ããŸãïŒ
config.matchNumberã1ãšã¯éããŸããïŒã
- 飿Šã®éäžããåå ããã¯ã©ã€ã¢ã³ãïŒåŸããå
¥å®€ãã芳æŠè
ãªã©ïŒã§ãããã®ã¯ã©ã€ã¢ã³ãã«ãšã£ãŠã®ååã§åŒã°ããŸãïŒ
onStartMatch(config)(Function): æ¯è©Šåã®éå§æã«åŒã°ããã³ãŒã«ããã¯ããªãããæãåŒã°ããŸãïŒååã¯onCreateMatchã®çŽåŸïŒãç€é¢ã»ã¹ã³ã¢ã»ã¿ã€ããŒã»çµæè¡šç€ºãªã©ã詊åããšã«ãªã»ãããå¿ èŠãªãã®ã ããããã«æžããŸãã- ãªã»ãã察象ããã®ã³ãŒã«ããã¯ã«éãŸãæ§é ã«ãªã£ãŠãããããããªãããæã®ãªã»ãããæžãå¿ããããšããäºæ ãèµ·ãã«ãããªã£ãŠããŸãã
onObsModeChange(isObsMode)(Function): OBS ã¢ãŒããåãæ¿ãã£ãæã«åŒã°ããã³ãŒã«ããã¯ã詊åã®éå§æã®ç¶æ ã¯config.isObsModeã§åãåã£ãŠãã ããïŒãã®ã³ãŒã«ããã¯ãåŒã°ãããšã¯éããŸããïŒãOBS ã¢ãŒãã§ã²ãŒã ããã¹ãããšã¯ å¶çŽ Â§8 ã«ãããŸããonStreamerModeChange(isStreamerMode)(Function): é ä¿¡è ã¢ãŒãïŒé ä¿¡è ããããŒã¿ã«ã®ç»é¢ããã®ãŸãŸé ä¿¡ããŠããããšã瀺ãã¢ãŒãïŒã®å€æŽæã«åŒã°ããã³ãŒã«ããã¯ãonGameData(payload, senderId)(Function): çžæãã¬ã€ã€ãŒãããªã¢ã«ã¿ã€ã éä¿¡ãã±ãããåä¿¡ããæã«åŒã°ããã³ãŒã«ããã¯ãpayload(Any): éä¿¡å ããéãããä»»æã®JSONããŒã¿ãsenderId(String | null): éä¿¡å ãã¬ã€ã€ãŒã®è«çIDïŒ'p1','p2','p3'çïŒã- ð¡ïž
senderIdã¯ã¯ã©ã€ã¢ã³ãããè©ç§°ã§ããªããããæçªãã¬ã€ã€ãŒã®äžèŽç¢ºèªã«å®å šã«äœ¿ããŸããé éå ã»ãšã³ãŒã»é åºä¿èšŒã»nullã«ãªãæ¡ä»¶ãå«ã詳现㯠§3 ã®GAME_DATA_RELAYïŒããŒã¿ã« â ã²ãŒã ïŒãåç §ããŠãã ããã
onMatchState(state)(Function): 詊åã®éäžããåå ããæïŒéäžå ¥å®€ãã芳æŠè ã»åæ¥ç¶ãããã¬ã€ã€ãŒïŒã«åŒã°ããã³ãŒã«ããã¯ãé²è¡åœ¹ãsetMatchStateã§ç»é²ããç¶æ ãæž¡ãããŸããonStartMatchã®çŽåŸã«åŒã°ããŸããonStartMatchã§ãªã»ããããäžã«åŸ©å ããŠãã ããã- 詊åéå§æããå±
ãã¯ã©ã€ã¢ã³ãã«ã¯åŒã°ããŸãããé²è¡åœ¹ãäžåºŠã
setMatchStateãåŒãã§ããªãå ŽåãåŒã°ããŸããã
onError(code, detail)(Function): éä¿¡ãæåŠãããæã«åŒã°ããã³ãŒã«ããã¯ãSDKãããŒã«ã«ã§åŒŸããå ŽåãšãããŒã¿ã«ãæåŠãè¿ããå Žåã®äž¡æ¹ãããã«å±ããŸããcode(String): 次ã®ããããã'PAYLOAD_TOO_LARGE'/'RATE_LIMIT_EXCEEDED'ïŒéä¿¡å¶çŽã®è¶ éïŒÂ§2sendGameDataã®ãéä¿¡å¶çŽãïŒã'SPECTATOR_BLOCKED'/'MATCH_OVER'ã'PROXY_SEND_DENIED'ïŒCPUã®ä»£çéä¿¡ã®æåŠïŒÂ§2sendGameDataAsïŒã'MATCH_STATE_DENIED'ïŒè©Šåç¶æ ã®ç»é²ã®æåŠïŒÂ§2setMatchStateïŒã'INVALID_GAME_OVER'ïŒè©Šåçµæãå¥çŽã«åããŠããïŒÂ§2sendGameOverïŒã
detail(Object):messageïŒèª¬ææïŒãããã³è©²åœããå Žåã¯byteLengthïŒå®æž¬ãµã€ãºãJSONåã§ããªãå Žåã¯-1ïŒãšlimitïŒè¶ éããäžéå€ïŒã- éä¿¡ã®æåŠã ããå¿
èŠãªã
sendGameDataã®æ»ãå€ã§å€å®ã§ããŸããonErrorã¯çç±ãšå®æž¬å€ãŸã§åãåãããå Žåã«äœ¿ã£ãŠãã ããã
Hitsumabushi.getConfig()
æåŸã« INIT_GAME ã§åãåã£ãèšå®ãªããžã§ã¯ããè¿ããŸãïŒåŒæ°ãªãïŒã
- æ»ãå€:
HitsumabushiGameConfig | nullãååã®INIT_GAMEåä¿¡åã¯ãinit()ãåŒãã åŸã§ãnullã§ãã - èšå®ã®ãã£ãŒã«ãã¯3ç¯ã®
INIT_GAMEãåç §ããŠãã ãããonCreateMatch(config)/onStartMatch(config)ã«æž¡ããããã®ãšåããªããžã§ã¯ãã§ãã³ããŒã§ã¯ãªãããã²ãŒã åŽã§å€æŽããªãã§ãã ããã - åæŠæãªã©ã«
INIT_GAMEãåä¿¡ãããšãä¿æããèšå®ã¯æ°ãããªããžã§ã¯ãã«çœ®ãæãããŸããå¿ èŠãªã¿ã€ãã³ã°ã§åååŸããŠãã ããã SET_OBS_MODE/SET_STREAMER_MODEã¯å€æŽéç¥ã®ã³ãŒã«ããã¯ãåŒã³åºãã ãã§ãä¿æããèšå®ãæžãæããŸãããã¢ãŒãã®ææ°ç¶æ ã¯onObsModeChange/onStreamerModeChangeã§ç®¡çããŠãã ããã
const config = Hitsumabushi.getConfig();
if (config) {
const localPlayer = config.players.find(player => player.id === config.localPlayerId);
console.log(localPlayer?.teamId ?? null);
}
Hitsumabushi.sendGameData(payload)
ãªã¢ã«ã¿ã€ã 察æŠäžãçžæãã¬ã€ã€ãŒã®ç»é¢ãžæäœå ¥åããªããžã§ã¯ã座æšïŒãã±ããïŒãéæã«äžç¶éä¿¡ããŸãã
payload(Object / Any): ã²ãŒã ç¬èªã§å®çŸ©ããä»»æã®JSONããŒã¿æ§é ã- æ»ãå€ (Boolean): éä¿¡ããå Žåã¯
trueãSDKãéä¿¡ãæ¢ããå Žåã¯falseã
if (!Hitsumabushi.sendGameData({ type: 'POS', x, y })) {
// äžéã«åŒµãä»ããŠãã â æ¬¡ãã¬ãŒã ã«ãŸãšãããå·®åã ãéãçã®çž®é
}
â ïž éä¿¡å¶çŽïŒã²ãŒã åŽãå®ãã¹ãäžéïŒ
ããŒã¿ã«ã®äžç¶åºç€ã¯ãsendGameData ã«ä»¥äžã®äžéã課ããŠããŸããäžéãè¶
ããåã¯äžç¶ãããç Žæ£ãããŸãã
| å¶çŽ | äžé | è¶ éæã®æå |
|---|---|---|
| 1ãã±ããã®ãã€ããŒããµã€ãº | 32KBïŒJSONååŸã®ãã€ãæ°ïŒ | SDKãéä¿¡åã«åŒŸããfalse ãè¿ã㊠onError('PAYLOAD_TOO_LARGE') ãåŒã¶ |
| 1ã¯ã©ã€ã¢ã³ãã®éä¿¡ã¬ãŒã | 60ãã±ãã/ç§ | SDKãéä¿¡åã«åŒŸããfalse ãè¿ã㊠onError('RATE_LIMIT_EXCEEDED') ãåŒã¶ |
SDKã¯Portalãžæããåã«ããŒã«ã«ã§å€å®ããŸãã ãã€ãŠã¯äžéè¶
éããµãŒããŒåŽã§ããæ€åºããããã¬ãŒãè¶
éã«è³ã£ãŠã¯ç¡èšã§ç Žæ£ããããããæ¬çªã§ã ããããŸã«åæããããããšãã圢ã§ããçŸããŸããã§ãããçŸåšã¯éçºäžã®ãã®å Žã§ false ãš onError ã§åãããŸãã
- ã¬ãŒãäžéã¯ã¯ã©ã€ã¢ã³ãåäœã§ãã60fpsã®ã²ãŒã ã§æ¯ãã¬ãŒã éä¿¡ãããšäžéã«åŒµãä»ããããã«å ããŠå
¥åã€ãã³ãïŒ
mousemove/touchmoveçïŒããšã«éä¿¡ãããšç¢ºå®ã«è¶ éããŸããæ¯ãã¬ãŒã éä¿¡ã¯é¿ããéä¿¡ééãéåŒããŠãã ããïŒäŸ: ç§20ã30åçšåºŠã«æããå·®åã®ã¿ãéãïŒã - ç€é¢å šäœã®ã¹ãããã·ã§ããçã§32KBã«è¿ã¥ãå Žåã¯ãå·®åã®ã¿ã®éä¿¡ã座æšã®äžžãïŒå°æ°ç¹ä»¥äžã®æ¡åæžïŒãæ€èšããŠãã ããã
- ããŒã«ã«å€å®ã®èšæž¬ãŠã£ã³ããŠã¯ãµãŒããŒãšåãåºå®ãŠã£ã³ããŠã§ãããéå§æå»ãŸã§ã¯åæããŠããŸãããæç¶çãªéãããã¯ç¢ºå®ã«æ€åºã§ããäžæ¹ãäžéããããã®1ãã±ããåäœã®å€å®ããµãŒããŒãšå®å
šã«äžèŽãããšã¯éããŸãããå€å®ãããæããå Žåããµã€ãºè¶
éã¯ãµãŒããŒãã
ERRORãè¿ãåãonErrorã«å±ããŸãããã¬ãŒãè¶ éã¯ãµãŒããŒãç¡èšã§ç Žæ£ããéç¥ã¯å±ããŸããã
å¶çŽå€ã®åç §æ¹æ³
å€ãããŒãã³ãŒããããHitsumabushi.limits ã§èªã¿åããŸãïŒNodeåŽã®ããŒã«ãã㯠@milightpartner/hitsumabushi-sdk/limits ã import ã§ããŸãïŒã
Hitsumabushi.limits;
// => { maxPayloadBytes: 32768, maxPacketsPerSecond: 60, rateWindowMs: 1000 }
ð èªåã®å¯ŸæŠãµãŒããŒã䜿ãããšãã§ããŸãã ãã®å Žåã¯
INIT_GAMEã®roomIdãèªåã® ãµãŒããŒã®éšå±ããŒã«äœ¿ããSDKãžã¯æ±ºçïŒsendGameOverïŒã ããå ±åããŸãããã®äžç¶ã䜿ããªã éžæè¢ããããšããæå³ã§ã䜿ãå Žåã«ããŒã¿ã«åŽã®è²»çšã¯ããããŸããã
Hitsumabushi.setMatchState(state)
çŸåšã®å¯ŸæŠç¶æ
ãããŒã¿ã«ã«ç»é²ããŸãã éäžåå ãã芳æŠè
ã忥ç¶ãããã¬ã€ã€ãŒãžã次ã®çæãåŸ
ããã«ç€é¢ã埩å
ããããã®ä»çµã¿ã§ããæ»ãå€ã¯ BooleanïŒsendGameData ãšåãïŒã
ç€é¢ã»æçªã»éç®åŸç¹ã®ããã«ã詊åäžã«ç©ã¿äžããç¶æ ãæã€ã²ãŒã ã¯äœ¿ã£ãŠãã ããã äžç¶ãã±ããã¯ãåå ããåŸã®éä¿¡ãããå±ããªãããã䜿ããªããšéäžåå è ã«ã¯æ¬¡ã®çæãŸã§ç©ºã®ç€é¢ã衚瀺ãããŸãã
// é²è¡åœ¹ããçæãç€é¢ã«åæ ããããã³ã«çŸåšã®ç€é¢ãç»é²ãã
function publishMatchState() {
if (Hitsumabushi.isGameMaster()) {
Hitsumabushi.setMatchState({ board, currentTurn });
}
}
// èªåã®çæã®ããšïŒplace å
ïŒãšãçžæã®çæãåä¿¡ããããšïŒonGameData å
ïŒã®äž¡æ¹ã§åŒã¶
-
â ïž é²è¡åœ¹ã¯ãçžæã®çæãåä¿¡ãããšãã«ãç»é²ãçŽããŠãã ããã ç»é²ã§ããã®ã¯é²è¡åœ¹ã ããªã®ã§ãèªåã®çæã®ãšãã ãç»é²ãããšãçžæã®æãæããç€é¢ãéäžåå è ã«é ãããŸãã
-
NÃNã°ãªããç€é¢ã®
boardãã³ãã¬ãŒãã«ã¯ãç»é²ïŒsetMatchStateïŒãšåŸ©å ïŒonMatchStateïŒã®æå°å®è£ ãå ¥ã£ãŠããŸãã -
state(Any): ä»»æã®JSONããŒã¿ïŒç€é¢ã»æçªã»æ®ãæéãªã©ïŒããµã€ãºäžéã¯sendGameDataãšåã32KBã§ãéä¿¡ã¬ãŒããåãäºç®ãå ±æããŸãã -
ð¡ïž é²è¡åœ¹ã®ã¿ç»é²ã§ããŸãã ãã以å€ã®ã¯ã©ã€ã¢ã³ãããã®ç»é²ã¯ããŒã¿ã«ãæåŠãã
onError('MATCH_STATE_DENIED')ãåŒã°ããŸããããã«ããã察æŠçžæãç€é¢ãæé ããŠéäžåå è ã«é ããçµè·¯ãå¡ãããŸãã -
ç»é²ããç¶æ ã¯æ¬¡ã®è©Šåãå§ãŸãæç¹ã§ç Žæ£ãããŸããåã®è©Šåã®ç€é¢ããªãããã®éäžåå è ãžé ãããããšã¯ãããŸããã
-
â ïž é²è¡åœ¹èªèº«ã¯æš©åšã¯ã©ã€ã¢ã³ãã§ãããããŒã¿ã«ã¯
stateã®äžèº«ãæ€èšŒããŸããïŒé²è¡åœ¹ãã¿ã€ããŒãCPUçæãæ åœããã®ãšåãä¿¡é Œã¬ãã«ã§ãïŒã
Hitsumabushi.sendGameOver(data)
察æŠãçµäºãåè
ã確å®ããæã«åŒã³åºããŸããããŒã¿ã«åŽã§ããªã³åå©çŽå¹éªã察æŠãªã¶ã«ããã€ã¢ãã°ãèªåããŒãã¡ã³ã衚ã®åã¡äžãããèªåå®è¡ãããŸãã
åè
ãšé äœã¯ããã®è©Šåã®åäœã§å ±åããŸããconfig.players ã« teamId ãããã°ããŒã æŠã®è©Šåã§ãåäœã¯ããŒã ã§ããç¡ããã°å人æŠã®è©Šåã§ãåäœã¯ãã¬ã€ã€ãŒã§ããããŒã æŠã«å¯Ÿå¿ããã²ãŒã ã§ããããŒã ãçµãŸãã«éã¹ã°å人æŠã®è©Šåã«ãªããŸãã
| 詊å | åè | rankings ã®äžèº« | 䜿ããªãé ç® |
|---|---|---|---|
å人æŠïŒteamId ãç¡ãïŒ | winnerPlayerId | ãã¬ã€ã€ãŒIDïŒ'p1' ãªã©ïŒ | winnerTeamId |
ããŒã æŠïŒteamId ãããïŒ | winnerTeamId | ããŒã IDïŒ'team_a' ãªã©ïŒ | winnerPlayerId |
data.winnerPlayerId(String | null): å人æŠã®è©Šåã®åè ã®ãã¬ã€ã€ãŒID ('p1','p2','p3'âŠ)ãåŒãåãæã¯nullïŒãŸãã¯çç¥ïŒãdata.winnerTeamId(String | null): ããŒã æŠã®è©Šåã®åè ã®ããŒã IDïŒconfig.players[].teamIdãšåãå€ãäŸ'team_a'ïŒãåŒãåãæã¯nullïŒãŸãã¯çç¥ïŒãdata.isTie(Boolean): åŒãåããã©ã°ã åŒãåãã§æ±ºçããå Žåã¯trueãæå®ããŸãïŒçç¥æfalseïŒãdata.rankings(String[]): é äœã衚ãããã®è©Šåã®åäœã®IDã®é åïŒ1äœããé ïŒãå人æŠãªã['p2', 'p1', 'p3']ãããŒã æŠãªã['team_b', 'team_a']ã3人ïŒ3ããŒã ïŒä»¥äžã®ã²ãŒã ãé äœå¶ã®ã²ãŒã ã§æå®ããŸãïŒçç¥æã¯ç©ºé åïŒãåè ãæå®ãããšãã¯ãåè ã1äœã«çœ®ããŸããdata.scores(Object): æçµã¹ã³ã¢ããã¬ã€ã€ãŒIDããšã®æ°å€ïŒäŸ{ p1: 5, p2: 3 }ïŒdata.stats(Object): ãããæéãææ°ãªã©ã®çµ±èšããŒã¿
// åæãã€ããå Žå
Hitsumabushi.sendGameOver({
winnerPlayerId: 'p1',
rankings: ['p1', 'p2'],
scores: { p1: 5, p2: 3 }
});
// ããŒã æŠã®è©Šåã§åæãã€ããå ŽåïŒåè
ãšé äœã¯ããŒã ã§å ±åãããscores ã¯ãã¬ã€ã€ãŒããšïŒ
Hitsumabushi.sendGameOver({
winnerTeamId: 'team_a',
rankings: ['team_a', 'team_b'],
scores: { p1: 5, p2: 3, p3: 2, p4: 1 }
});
// åŒãåãã®å ŽåïŒmanifest.json ã§ resultModel.allowTie: true ã宣èšããŠããã²ãŒã ïŒ
Hitsumabushi.sendGameOver({
isTie: true,
winnerPlayerId: null,
scores: { p1: 3, p2: 3 }
});
æ»ãå€ã¯ BooleanïŒsendGameData ãšåãïŒãéã£ãã trueãéããªãã£ãã false ãè¿ããŸãã
ð¡ïž SDK ã¯éãåã«ãçµæãäžã®å®çŸ©ãšãã®è©Šåã® config.players ã«åã£ãŠãããã確ãããŸãã ããŒã¿ã«ã¯çµæããã®å®çŸ©ã©ããã§ããåæã§çµæç»é¢ã»éç®æçžŸã»å€§äŒã®åã¡äžãããæ±ãããããåã£ãŠããªãçµæã¯éããŸããã
| 確ãããããš | éããªãäŸ |
|---|---|
winnerPlayerId ã¯å人æŠã®è©Šåã ãã§ãconfig.players ã«ãã id | 'p5'ïŒ4人ã®è©ŠåïŒã1ãããŒã æŠã§ã® 'p1' |
winnerTeamId ã¯ããŒã æŠã®è©Šåã ãã§ãconfig.players ã®ã©ããã® teamId | å人æŠã§ã® 'team_a'ã詊åã«ç¡ã teamIdïŒteam_a 察 team_b ã®è©Šåã§ã® 'team_c'ïŒ |
rankings ã¯ããã®è©Šåã®åäœã®IDïŒå人æŠã¯ãã¬ã€ã€ãŒã® idãããŒã æŠã¯ teamIdïŒã®é
åã§ãåãIDã2ååºãŠããªã | 'p1'ïŒé
åã§ãªãïŒã['p1', 'p1']ãããŒã æŠã§ã® ['p1', 'p2'] |
åè
ãš rankings ã®äž¡æ¹ããããšãã¯ãåè
ã rankings ã®1äœ | { winnerPlayerId: 'p2', rankings: ['p1', 'p2'] } |
isTie ã¯ãtrue ã false | 'true' |
scores ã¯ãããŒã config.players ã«ãã id ã§ãå€ãæéã®æ° | { team_a: 3 }ã{ p1: '5' } |
isTie: true ã®ãšãã¯ãwinnerPlayerId ãš winnerTeamId ãæå®ããªã | { isTie: true, winnerPlayerId: 'p1' } |
- çç¥ãããã£ãŒã«ãïŒ
undefinedãšnullïŒã¯ç¢ºããããäžã®çç¥æã®å€ã§éããŸããstatsã¯ç¢ºãããŸããïŒããŒã¿ã«ã¯äœ¿ããã«æšãŠãŸãïŒã - åã£ãŠããªããã°ã
falseãè¿ããonError('INVALID_GAME_OVER')ãåŒã³ãŸããdetail.messageã«ãã©ã®ãã£ãŒã«ããã©ãéãããå ¥ããŸãã - éããªãã£ããšãã¯
phaseã'playing'ã®ãŸãŸãªã®ã§ãçŽããŠéãçŽããŸãã詊åã¯ãçµæãéããããŸã§çµãããŸããã - 芳æŠè
ïŒã«ãŒã ãã¹ããé€ãïŒãåŒãã ãšããéããã
falseãè¿ããŠonError('SPECTATOR_BLOCKED')ãåŒã³ãŸãã
â ïž åŒãåãã winnerPlayerId ã®çç¥ã ãã§è¡šçŸããªãã§ãã ããã isTie: true ãç¡ãå ŽåãããŒã¿ã«ã¯ãã®è©ŠåãåŒãåãã§ã¯ãªã**ãåè
ãªãã**ãšããŠæ±ããŸãã倧äŒã¢ãŒãã®ç·åœããæŠã§ã¯ãäž¡è
ãšãåã¡ç¹ãå
¥ããïŒåŒãåãã®1ç¹ãå
¥ããŸããïŒãããŒãã¡ã³ãã§ã¯åã¡äžããäººãæ±ºãŸããªãããå詊åã«ãªããŸããmanifest.json ã§ resultModel.allowTie: true ã宣èšããã²ãŒã ã¯ãå¿
ã isTie ãæå®ããŠãã ããããªããã°ã©ã³ããªã¢ãŒãã«ãããŠã¯ isTie: true ã®å Žåãå
šå¡ã®ç²åŸãã€ã³ãã 0 ãšãªããŸãã
â ïž 3人ïŒ3ããŒã ïŒä»¥äžã®ã²ãŒã ã»é äœå¶ã®ã²ãŒã ã§ã¯ rankings ãæå®ããŠãã ããã rankings ãç¡ãå ŽåãããŒã¿ã«ã¯ã°ã©ã³ããªåœ¢åŒã®éèšã§åè
以å€ã®å
šå¡ã®ç²åŸãã€ã³ã㯠0 ãšãªããŸããresultModel.type: "ranked" ã宣èšããã²ãŒã ã§ã¯äºå®äžå¿
é ã§ãã
â ïž rankings ã§åŒãåãã衚çŸããªãã§ãã ããã rankings ã¯é åºä»ãã®é
åãªã®ã§ãåé äœãæžãææ®µããããŸããã['p1', 'p2'] ã¯ãp1 ã p2 ããäžäœããšããæå³ã«ãããªãããã2人ãåçããšã¯èªãŸããŸããã
åŒãåã㯠rankings ã§ã¯ãªã isTie: true ã§å ±åããŠãã ããïŒrankings ã¯çç¥ããŠæ§ããŸããïŒã
// â åçã®ã€ããã§ããp1 ã1äœãp2 ã2äœããšè§£éããã
Hitsumabushi.sendGameOver({ rankings: ['p1', 'p2'] });
// â
åŒãåã㯠isTie ã§å ±åãã
Hitsumabushi.sendGameOver({ isTie: true, winnerPlayerId: null });
ãªãã3人以äžã§äžéšã®é äœã ããåçã«ãªãã±ãŒã¹ïŒ1äœã確å®ãã2äœä»¥äžãåçãªã©ïŒã rankings ã§ã¯è¡šçŸã§ããŸããããã®å Žåã«é äœãç¡çã«æ±ºããŠäžŠã¹ããšãå®éãšã¯ç°ãªãé äœãããŒã¿ã«ã«èšé²ãããŸããè¡šçŸææ®µã¯çŸæç¹ã§çšæãããŠããªããããåçãèµ·ããããã²ãŒã 㯠resultModel.type ã®éžæãå«ããŠèšè𿮵éã§æ€èšããŠãã ããã
Hitsumabushi.unlockAchievement(achievementId)
å®çžŸã®è§£é€ãããŒã¿ã«ãžèŠæ±ããŸããWebç°å¢ã§ã¯ããŒã¿ã«ã®ãããžå®çžŸãå°æ¥ã®Steamç°å¢ã§ã¯ SteamUserStats.SetAchievement() ã«èªåããªããžãããèšèšã§ãã
ð§ çŸç¶ãã®APIã¯æªãµããŒãã§ãïŒ
UNLOCK_ACHIEVEMENTã®åãå£ãããŒã¿ã«åŽã«ååšããŸããïŒã åŒã³åºããŠããšã©ãŒã«ã¯ãªãããUNLOCK_ACHIEVEMENTã¡ãã»ãŒãžãéä¿¡ãããã ãã§äœãèµ·ãããŸããïŒå®çžŸã¯èšé²ããããUI衚瀺ããããŸããïŒããŸãmanifest.jsonã«ã¯å®çžŸã宣èšãããã£ãŒã«ããååšããªããããå®çžŸIDã»è¡šç€ºåã»ã¢ã€ã³ã³ãããŒã¿ã«ãžç»é²ããææ®µãçŸç¶ãããŸããã å°æ¥ã®å®è£ ã«åããŠåŒã³åºããæžããŠããããšã¯åé¡ãããŸããããå®çžŸã®è§£é€ãåæãšããã²ãŒã èšèšïŒå®çžŸãã²ãŒã é²è¡ãUIã®å¿ é èŠçŽ ã«ãªãèšèšïŒã¯çŸæç¹ã§ã¯æç«ããŸããã
Hitsumabushi.phase
çŸåšã®è©Šåã®é²è¡ç¶æ
ã衚ãããããã£ïŒã¡ãœããã§ã¯ãããŸããïŒã'waiting' | 'playing' | 'over' ã®ãããããè¿ããŸããã²ãŒã åŽã§ isGameOver ãã©ã°ãèªå管çããå¿
èŠã¯ãããŸããã
| å€ | æå³ |
|---|---|
'waiting' | ãŸã 察æŠèšå®ã泚å
¥ãããŠããªãïŒonCreateMatch ããåïŒ |
'playing' | 察æŠäžãéä¿¡ç³»APIã䜿ãã |
'over' | 詊åãæ±ºçãããæ¬¡ã®è©Šåãå§ãŸããŸã§ sendGameData ã¯é®æããã |
'over'ãžã®é·ç§»ã¯å šã¯ã©ã€ã¢ã³ãã§èµ·ããŸããèªåã§sendGameOver()ãåŒãã§éã£ãã¯ã©ã€ã¢ã³ãã¯ãã®æç¹ã§ããã以å€ã®ã¯ã©ã€ã¢ã³ãïŒæè åŽã»èгæŠè ïŒã¯ããŒã¿ã«ãçµæãæ€ç¥ããæç¹ã§é·ç§»ããŸããåæãå ±åããã®ã¯éåžž1ã¯ã©ã€ã¢ã³ãã ããªã®ã§ããã®äºçµè·¯ã®äž¡æ¹ãå¿ èŠã§ãã- 次ã®è©Šåã®
onStartMatchã§èªåçã«'playing'ãžæ»ããŸãããªã»ããåŠçãæžãå¿ èŠã¯ãããŸããã - ð¡ïž å®å
šã¬ãŒã:
'over'ã®éã«sendGameDataãåŒãã§ããSDKå éšã§éä¿¡ã鮿ãããŸãïŒèŠåãã°åºåïŒã決çåŸã«æ®ã£ãŠããã¿ã€ããŒãã«ãŒããçžæãžãã±ãããéã£ãŠããŸãäºæ ãé²ããŸãã
Hitsumabushi.isSpectator()
çŸåšã®ã¯ã©ã€ã¢ã³ããã芳æŠè
ïŒSpectatorïŒããã©ãããè¿ããŸãïŒå¯ŸæŠã¹ãããã«çåžããŠããªãå ŽåããŸã㯠config.isSpectator === true ã®å Žåã« trueïŒã
- 芳æŠè
ç»é¢ã§ã¯ç€é¢æäœã®ããã¯ïŒ
pointer-events: noneïŒããð 芳æŠäžãUIã®è¡šç€ºãè¡ã£ãŠãã ããã - ð¡ïž å®å
šã¬ãŒã: 芳æŠè
ç¶æ
ã®æã¯ã
sendGameData,sendGameOver,unlockAchievementãåŒã³åºããŠãSDKå éšã§éä¿¡ãèªåçã«é®æãããŸãïŒèŠåãã°åºåïŒãâ»ãã ãéšå±ç®¡çè ïŒRoom HostïŒã倧äŒã¢ãŒãã®é²è¡ç®¡çãªã©ã§èŠ³æŠæ ã«ããå ŽåãsendGameOverã¯é®æãããéä¿¡å¯èœã§ãã
Hitsumabushi.isRoomHost()
çŸåšã®ãã¬ã€ã€ãŒããéšå±ã®ç®¡çè ïŒäœæè ïŒããã©ãããè¿ããŸãã
- é
ä¿¡è
ã倧äŒäž»å¬è
ã芳æŠåžã«ããå Žåã§ã
trueã«ãªããŸãã
Hitsumabushi.isGameMaster()
çŸåšã®ãã¬ã€ã€ãŒãã詊åé²è¡åœ¹ïŒGame MasterïŒããã©ãããè¿ããŸããé²è¡åœ¹ã¯çåžããŠãããã¬ã€ã€ãŒã®ãã¡ãCPUã§ãªãæåã®1人ã§ãããŒã¿ã«ã決å®ã㊠config.gameMasterId ãšããŠå
šã¯ã©ã€ã¢ã³ãã«é
ããŸãïŒå
šå¡ãåãå€ãèŠãŸãïŒã
- â ïž éèŠ: éšå±ç®¡çè
ïŒRoom HostïŒã芳æŠåžã«åã£ãŠããå Žåã芳æŠè
ã¯ã²ãŒã å
ã®ã¿ã€ããŒãCPUåŠçãæ
åœã§ããŸããããã®ãããã¿ã€ããŒé²è¡ã»ã¿ãŒã³ç®¡çã»CPUçæããžãã¯ãªã©ã®ãã²ãŒã å
ã§1ç®æã ãåããããé²è¡åŠçãã¯ã
Hitsumabushi.isGameMaster()ã§ã¬ãŒãããŠãã ããã - ã¹ãããã®å é ãCPUã§ããé²è¡åœ¹ã¯äººéã®ã¯ã©ã€ã¢ã³ãã«ãªããŸãïŒCPUã¯èªã¿é£ã°ãããŸãïŒã
Hitsumabushi.sendGameDataAs(asPlayerId, payload)
CPUã¹ãããã®ä»£çãšããŠãã±ãããéä¿¡ããŸãã æ»ãå€ã¯ sendGameData ãšåã Boolean ã§ãã
allowCpuFill: true ã®ã²ãŒã ã§ã¯ãCPUã®æã¯é²è¡åœ¹ã®ãã©ãŠã¶ãèšç®ããŠéããŸããããã sendGameData ã§éããšãåä¿¡åŽã«ã¯é²è¡åœ¹èªèº«ã®IDã senderId ãšããŠå±ãããã§3 ã§æšå¥šããŠãã senderId === currentTurn ã®æ€èšŒãçŽ çŽã«æžããšCPUã®æãå
šãŠåŒŸãããŸãã
// é²è¡åœ¹ãCPUã®æãéã
Hitsumabushi.sendGameDataAs('p2', { type: 'MOVE', index: 40 });
// åä¿¡åŽã¯ãCPUã®æã人éã®æãåãæ€èšŒã§æ±ãã
onGameData: (payload, senderId) => {
if (senderId !== currentTurn) return; // 代çéä¿¡ã§ãããã§æ£ããéã
applyMove(payload.index);
}
asPlayerId(String): CPUã¹ãããã®è«çIDïŒconfig.playersã«çŸããIDããã®ãŸãŸæå®ããŸãïŒãIDã¯åžã®äœçœ®ã§æ±ºãŸããCPUã®åžã人éãšåã'p<n>'ã§ããã©ã®åžãCPUãã¯è©Šåããšã«å€ããã®ã§ãconfig.playersããisCpu: trueã®ãã®ãåŒããŠãã ããã- ð¡ïž ããŒã¿ã«ãèªå¯ããŸãã ãéä¿¡å
ãé²è¡åœ¹ã§ããããšããšã察象ã¹ãããã
isCpu: trueã§ããããšãã®äž¡æ¹ãæºããå Žåã®ã¿äžç¶ãããåä¿¡åŽã«ã¯senderIdããã®CPUã®IDãšããŠå±ããŸããã©ã¡ãããæºãããªãéä¿¡ã¯äžç¶ããããonError('PROXY_SEND_DENIED')ãåŒã°ããŸãã人éã®ã¹ããããžã®ãªãããŸãã¯ã§ããŸããã - éçºããŒãã¹ïŒ
npx hitsumabushi devïŒã¯CPUåžãåçŸããªãããããã®çµè·¯ã¯åžžã«æåŠãããŸããCPU察æŠã®å®æåã¯ããŒã¿ã«æ¬äœã§ç¢ºèªããŠãã ããã
3. éä¿¡ãããã³ã«ä»æ§ (window.postMessage)
SDKå éšã§ã¯ãããŒã¿ã«ãš iframe éã§ä»¥äžã® JSON ã¡ãã»ãŒãžãããåãããŠããŸãã
ð °ïž Game â Portal (ã²ãŒã ã¢ããªããããŒã¿ã«ãžéä¿¡)
1. GAME_READY (åæåå®äºéç¥)
{
"type": "GAME_READY"
}
2. GAME_OVER (å¯ŸæŠæ±ºç)
{
"type": "GAME_OVER",
"winnerPlayerId": "p1",
"winnerTeamId": null,
"rankings": ["p1", "p2"],
"isTie": false,
"scores": { "p1": 5, "p2": 2 },
"stats": {}
}
ããŒã æŠã®è©Šåã§ã¯ãåè
ã winnerTeamId ã«ãrankings ãããŒã IDã®äžŠã³ã«ãªããwinnerPlayerId 㯠null ã§ããåŒãåãã®å Žåã¯åè
ãã©ã¡ãã null ã§ãisTie: true ã«ãªããŸãã
{
"type": "GAME_OVER",
"winnerPlayerId": null,
"winnerTeamId": null,
"rankings": [],
"isTie": true,
"scores": { "p1": 3, "p2": 3 },
"stats": {}
}
3. GAME_DATA_RELAY (ãªã¢ã«ã¿ã€ã ããŒã¿äžç¶)
{
"type": "GAME_DATA_RELAY",
"payload": {
"puckX": 450,
"puckY": 300,
"vx": 8.5
}
}
â ïž payload 㯠32KB ãŸã§ãé信㯠60ãã±ãã/ç§ ãŸã§ïŒã¯ã©ã€ã¢ã³ãåäœïŒãè¶
éåã¯äžç¶ããããã¬ãŒãè¶
éã¯ç¡èšã§ç Žæ£ãããŸãã詳现㯠§2 Hitsumabushi.sendGameData(payload) ã®ãéä¿¡å¶çŽããåç
§ããŠãã ããã
CPUã®ä»£çéä¿¡ïŒÂ§2 sendGameDataAsïŒã®å Žå㯠asPlayerId ãæ·»ããããŸããããŒã¿ã«ããéä¿¡å
ãé²è¡åœ¹ ã〠察象ãCPUã¹ãããããæ€èšŒããããã§äžç¶ããŸãã
4. SET_MATCH_STATE (察æŠç¶æ
ã®ç»é²)
{
"type": "SET_MATCH_STATE",
"state": { "board": "...", "currentTurn": "p2" }
}
é²è¡åœ¹ãçŸåšã®å¯ŸæŠç¶æ
ãããŒã¿ã«ãžç»é²ããŸããä»ã¯ã©ã€ã¢ã³ããžã¯äžç¶ãããŸããïŒãµãŒããŒãä¿æããéäžåå ã»åæ¥ç¶ããã¯ã©ã€ã¢ã³ãã«ã ãåçãããŸãïŒãé²è¡åœ¹ä»¥å€ããã®ç»é²ã¯æåŠãããERROR(MATCH_STATE_DENIED) ãè¿ããŸãã
ð ±ïž Portal â Game (ããŒã¿ã«ããã²ãŒã ãžæ³šå ¥)
1. INIT_GAME (察æŠèšå®ã®æ³šå
¥)
{
"type": "INIT_GAME",
"roomId": "AB12",
"platform": "web",
"localPlayerId": "p1",
"isRoomHost": true,
"gameMasterId": "p1",
"isSpectator": false,
"isObsMode": false,
"isStreamerMode": false,
"matchNumber": 1,
"isRematch": false,
"seriesId": "AB12-S1",
"players": [
{ "id": "p1", "name": "Alice", "color": "#ffe600", "teamId": "team_c", "isCpu": false },
{ "id": "p2", "name": "Bob", "color": "#00f0ff", "teamId": "team_c", "isCpu": false },
{ "id": "p3", "name": "Carol", "color": "#39ff14", "teamId": "team_d", "isCpu": false },
{ "id": "p4", "name": "CPU 1", "color": "#94a3b8", "teamId": "team_d", "isCpu": true }
]
}
â»ããŒã æŠã®è©Šåã§ã¯åãã¬ã€ã€ãŒã« teamId ãä»ããŸãïŒåäººæŠæã¯ teamId: null ãŸãã¯çç¥ïŒãããŒã ã®åãæ¹ã¯äžã®ãplayers ã®èªã¿æ¹ããèŠãŠãã ãããCPUåå æã¯ isCpu: true ãšãªããŸãã
ð€ players ã®èªã¿æ¹
â ïž ãã¬ã€ã€ãŒã¯é
åã®äœçœ®ã§ã¯ãªã idïŒ'p1'..'pN'ïŒã§èå¥ããŠãã ãããplayers 㯠id ã®é ã«äžŠãã§ããŸããã
è«çIDã¯å¯ŸæŠã®åžã®äœçœ®ã§æ±ºãŸãã空åžã¯é
åã«å
¥ããŸãããããšãã°4åžã®ãã¡2çªç®ã®åžã空ããŠããã°ãé
å㯠[p1, p3, p4] ã«ãªããplayers[1].id 㯠'p2' ã§ã¯ãªã 'p3' ã§ãã
䞊ã³èªäœã¯åžã®é ã§ãå
šã¯ã©ã€ã¢ã³ããåãé åºã®é
åãåãåããŸãã ãã®ããæçªã®é åºãšããŠäœ¿ããŸããäº€äºæçªã®ã²ãŒã ã§å
æã決ãããªã players[0] ã§ååã§ãIDããœãŒããçŽãå¿
èŠã¯ãããŸããã
// 飿Šã§ã¯å
æã1ã€ãã€ãããïŒå
æãæå©ãªã²ãŒã ãå
¬å¹³ã«ããïŒ
var order = config.players.map(function (p) { return p.id; });
var shift = (config.matchNumber - 1) % order.length;
order = order.slice(shift).concat(order.slice(0, shift));
var firstPlayer = order[0];
â ïž color ã®äžææ§ã¯ä¿èšŒãããŠããŸããã color ã¯ãã¬ã€ã€ãŒèªèº«ãéžãã è²ã§ãéè€ãåŸãŸãïŒããŒã¿ã«åŽã®èª²é¡ãšã㊠#310 ã§è¿œè·¡ïŒãç³ãã³ããè²ã§åºå¥ããã²ãŒã ã¯ãcolor ããã®ãŸãŸäœ¿ãããã²ãŒã åŽã§ id ããè²ã決ããŠãã ããã
â ïž color ã¯ããŒã ã®è²ã§ã¯ãããŸããã ããŒã æŠã§ãåãã¬ã€ã€ãŒãéžãã è²ã®ãŸãŸãªã®ã§ãåãããŒã ã®ãã¬ã€ã€ãŒãå¥ã®è²ã«ãªããŸããããŒã ãè²ã§å¡ãåããã²ãŒã ã¯ãã²ãŒã åŽã§ teamId ããšã«åºå®ã®è²ã決ããŠãã ããã
â ïž ããŒã 㯠team_a / team_b ãšæ±ºãæã¡ãããplayers ã«ãã teamId ã§ãŸãšããŠãã ããã 詊åã«åºã teamId ã®çµã¿åããã¯æ±ºãŸã£ãŠããŸããã2ããŒã ã®è©Šåã§ã team_a ãš team_b ãšã¯éãããããšãã°ãã¢ã®å€§äŒã§ã¯ team_c 察 team_d ã team_a 察 team_c ã®è©Šåã«ãªããŸããteam_a 以å€ã team_b ã«çœ®ãæãããã'team_a' / 'team_b' ãšæ¯ã¹ãŠããŒã ãåããããããšããããã詊åã§ããŒã åãã厩ããŸããåè
ãšé äœãå ±åã§ããŸããïŒsendGameOver ã¯ãwinnerTeamId ã rankings ã«è©Šåã«ç¡ã teamId ããããšéããŸããïŒã
// teamId ããšã«ãã¬ã€ã€ãŒããŸãšããïŒããŒã ã®äžŠã³ã¯åžã®é ïŒ
var teams = {};
config.players.forEach(function (p) {
if (!teams[p.teamId]) teams[p.teamId] = [];
teams[p.teamId].push(p);
});
var teamIds = Object.keys(teams); // äŸ: ['team_c', 'team_d']
// åã£ãããŒã ïŒrankings ãéããªãé äœãïŒã¯ãåãåã£ã teamId ã®ãŸãŸå ±åããïŒteam_a / team_b ã«çœ®ãæããªãïŒ
Hitsumabushi.sendGameOver({ winnerTeamId: winningTeamId, scores: scores });
gameMasterId ã¯è©Šåé²è¡åœ¹ã®è«çIDïŒçåžäžã®æåã®éCPUãã¬ã€ã€ãŒïŒã§ããå
šã¯ã©ã€ã¢ã³ããåãå€ãåãåããŸããèªåãé²è¡åœ¹ãã©ãã㯠Hitsumabushi.isGameMaster() ã§å€å®ã§ããŸãã
ð 飿ŠïŒã·ãªãŒãºïŒã®æè
matchNumber(Number): çŸåšã®é£æŠã«ãããäœè©Šåç®ãïŒ1å§ãŸãïŒãisRematch(Boolean):matchNumber > 1ãšå矩ãåæŠãã©ããã®åå²ã«äœ¿ããŸããseriesId(String): åãé¡ã¶ãã»åãã²ãŒã ã§ç¶ããŠãã飿Šãèå¥ããå®å®IDã 飿Šã®éç®æçžŸãæã¡ããå Žåã¯ããã®IDãå€ãã£ãããªã»ããããŠãã ãããããŒã¿ã«åŽã¯çåžã¡ã³ããŒïŒäººã¯åæ¥ç¶ããŸããã§å®å®ããIDã§èå¥ãCPUã¯ã¹ãããïŒãšã²ãŒã IDã®çµã§å€å®ããŠãããããã²ãŒã åŽã§ãã¬ã€ã€ãŒåãªã©ããã·ã°ããã£ãèªäœããå¿ èŠã¯ãããŸããã- é¡ã¶ããã²ãŒã ãå€ããã°æ°ãã
seriesIdã«ãªããmatchNumber㯠1 ã«æ»ããŸãããããŒãžæ»ã£ãŠãåãé¡ã¶ãã§åéããå Žåã¯é£æŠãç¶ç¶ããŸãã - 察æŠãå§ãŸã£ãããšã«æ°ããåå è
ã察æŠã¹ããããžå²ã蟌ãããšã¯ãããŸããïŒè©Šåäžã®å
¥å®€ã¯èгæŠè
ã«ãªããŸãïŒããããã£ãŠé£æŠäžã«
seriesIdãäºæããå€ããããšã¯ãããŸããã
- é¡ã¶ããã²ãŒã ãå€ããã°æ°ãã
ð 芳æŠè
ã® INIT_GAME
察æŠã¹ãããã«çåžããŠããªãã¯ã©ã€ã¢ã³ãã«ã¯ãlocalPlayerId ã« null ãå
¥ããŸãïŒäŸ¿å®äžã®IDã¯æ¯ãããŸããïŒãplayers ã«ã¯çåžäžã®ãã¬ã€ã€ãŒã ããå
¥ãããã芳æŠè
èªèº«ã¯ players ã«çŸããŸããã
{
"type": "INIT_GAME",
"roomId": "AB12",
"platform": "web",
"localPlayerId": null,
"isRoomHost": false,
"gameMasterId": "p1",
"isSpectator": true,
"isObsMode": false,
"isStreamerMode": false,
"matchNumber": 1,
"isRematch": false,
"seriesId": "AB12-S1",
"players": [
{ "id": "p1", "name": "Alice", "color": "#00f0ff", "teamId": null, "isCpu": false },
{ "id": "p2", "name": "Bob", "color": "#ff007f", "teamId": null, "isCpu": false }
]
}
ãããã£ãŠ config.localPlayerId ã«ãã©ãŒã«ããã¯ãæžããªãã§ãã ãããlocalPlayerId || players[0].id ã®ãããªæžãæ¹ããããšã芳æŠè
ã p1 ãšããŠæäœã§ããŠããŸããŸããæäœã®ããã¯ã¯ Hitsumabushi.isSpectator() ã§è¡ã£ãŠãã ããïŒÂ§2ïŒã
ãªã localPlayerId: null ã¯èгæŠè
ã ããšã¯éããŸãããçåžããã«éšå±ãç«ãŠããã¹ãã¯è©Šåç®¡çæš©éãæã€ãã isSpectator: false ã®ãŸãŸ localPlayerId ã null ã«ãªããŸãïŒ#275ïŒããlocalPlayerId ãããããšããåæã«ããã³ãŒãã¯ããã®å Žåãå£ããŸãã
2. SET_OBS_MODE / SET_STREAMER_MODE
{ "type": "SET_OBS_MODE", "isObsMode": true }
3. ERROR (éä¿¡ã®æåŠ)
{ "type": "ERROR", "code": "PAYLOAD_TOO_LARGE", "message": "éä¿¡ããŒã¿ãµã€ãºãäžéïŒ32KBïŒãè¶
éããããç Žæ£ãããŸããã" }
ããŒã¿ã«ãã²ãŒã ã®éä¿¡ãæåŠããæã«æ³šå
¥ãããŸããSDKã¯ããã onError(code, detail) ã«æ©æž¡ãããŸããã²ãŒã èªèº«ã®éä¿¡ãåå ã§è¿ããã®ã ãã転éãããéšå±ã®äœæã»åå ã«é¢ããããŒã¿ã«åŽã®ãšã©ãŒã¯ã²ãŒã ã«ã¯å±ããŸããã
éåžžã¯SDKãPortalãžæããåã«ããŒã«ã«ã§åŒŸãããããã®ã¡ãã»ãŒãžã¯ããŒã«ã«å€å®ãããæããå Žåã®åŸæ®µã®ç¶²ã§ããã¬ãŒãè¶
éã¯ããæããŠããµãŒããŒãç¡èšã§ç Žæ£ããããããã®ã¡ãã»ãŒãžã¯å±ããŸããïŒÂ§2 sendGameData ã®ãéä¿¡å¶çŽããåç
§ïŒã
4. MATCH_OVER (詊åçµäºã®éç¥)
{ "type": "MATCH_OVER" }
ããŒã¿ã«ã詊åã®æ±ºçãæ€ç¥ããæã«ãã«ãŒã å
ã®å
šã¯ã©ã€ã¢ã³ãã®iframeãžæ³šå
¥ãããŸããSDKã¯ãããåã㊠Hitsumabushi.phase ã 'over' ã«ããŸããåæãå ±åããã®ã¯éåžž1ã¯ã©ã€ã¢ã³ãã ããªã®ã§ãæè
åŽãšèгæŠè
ã¯ãã®éç¥ã§ 'over' ã«å°éããŸããã²ãŒã åŽãçŽæ¥ãã³ããªã³ã°ããå¿
èŠã¯ãããŸããïŒHitsumabushi.phase ãåç
§ããŠãã ããïŒã
5. MATCH_STATE (éäžåå æã®ç¶æ
埩å
)
{
"type": "MATCH_STATE",
"state": { "board": "...", "currentTurn": "p2" }
}
詊åã®éäžããåå ããã¯ã©ã€ã¢ã³ãã«ã ããINIT_GAME ã®çŽåŸã«æ³šå
¥ãããŸãïŒéäžå
¥å®€ãã芳æŠè
ã»åæ¥ç¶ãããã¬ã€ã€ãŒïŒãé²è¡åœ¹ã setMatchState ã§ç»é²ããææ°ã®ç¶æ
ããã®ãŸãŸå
¥ããŸããSDKã¯ããã onMatchState(state) ã«æž¡ããŸãã
詊åéå§æããå± ãã¯ã©ã€ã¢ã³ãã«ã¯æ³šå ¥ãããŸããïŒäžç¶ãã±ããã§æ¢ã«åæããŠããããïŒã
6. GAME_DATA_RELAY (ãªã¢ã«ã¿ã€ã ããŒã¿äžç¶ãã±ããã®åä¿¡)
{
"type": "GAME_DATA_RELAY",
"payload": {
"action": "roll_dice"
},
"senderId": "p1"
}
payload(Any): éä¿¡å ã¯ã©ã€ã¢ã³ããHitsumabushi.sendGameData(payload)ã§éä¿¡ããããŒã¿ãsenderId(String | null): éä¿¡å ãã¬ã€ã€ãŒã®è«çIDïŒ'p1','p2'çïŒãçŸè¡ã®èªå¯ã¢ãã«ã§ã¯ã芳æŠè ã®éä¿¡ã¯ã¯ã©ã€ã¢ã³ãïŒSDKïŒã»ãµãŒããŒã®äž¡æ¹ã§é®æãããsenderIdã¯ãã²ã€ãµãŒããŒãéãäž»ã®ä»ã®è©Šåã§ã®è«çIDãä»ãããããonGameDataã«å®éã«å±ããã±ããã®senderIdã¯åžžã«å¯ŸæŠã¹ããããžçåžãããã¬ã€ã€ãŒã®è«çIDã«ãªããå®éçšã§nullã«ãªãããšã¯ãããŸããã åãšããŠã¯å°æ¥ã®æ¡åŒµã«åããŠnullãæ®ããŠããŸãããçŸç¶ããã«äŸåããåå²ãæžãå¿ èŠã¯ãããŸããã
ð¬ é éå ã»ãšã³ãŒã»é åº
- é éå : éä¿¡è 以å€ã®ãåãã«ãŒã ã«åå ããŠããå šã¯ã©ã€ã¢ã³ãã«é éãããŸãã察æŠã¹ãããã®ãã¬ã€ã€ãŒã ãã§ãªã芳æŠè ãå«ã¿ãŸãïŒèгæŠè ããã¬ã€ã€ãŒãšåãSocket.ioã«ãŒã ã«åå ããŠããããïŒãã芳æŠè ã«ã¯é ãããªãããšããåæã§ã²ãŒã ãå®è£ ããªãã§ãã ããã
- ãšã³ãŒ: éä¿¡è
èªèº«ã«ã¯é
éãããŸããïŒãµãŒããŒã¯
socket.to(roomId).emit(...)ã§äžç¶ãããããéä¿¡å ãœã±ããã¯é é察象ããèªåçã«é€å€ãããŸãïŒã - é åºä¿èšŒ: åäžéä¿¡è
ãéã£ããã±ããã¯ãå±ãé åºãä¿ãããŸãïŒWebSocketæ¥ç¶èªäœã®é åºä¿èšŒãšããã²ã€ãµãŒããŒïŒã²ã€ãµãïŒãåäžã€ã³ã¹ã¿ã³ã¹æ§æã§ããããšã«ãããŸãã
--max-instances 1â #188ïŒãç°ãªãéä¿¡è éã®ãã±ããã®çžå¯Ÿé åºã¯ä¿èšŒãããŸããïŒãµãŒããŒãžã®å°éé ã«äŸåãããããã¯ãŒã¯æ¡ä»¶ã§å ¥ãæ¿ããåŸãŸãïŒã - éäžåå ã¯ã©ã€ã¢ã³ã: ã«ãŒã ã«åå ããåã«éä¿¡ãããäžç¶ãã±ããã¯ãåŸããåå ããã¯ã©ã€ã¢ã³ãïŒéäžåå ã®èгæŠè ã»åæ¥ç¶ãããã¬ã€ã€ãŒïŒã«ã¯å±ããŸããããçŸåšã®å¯ŸæŠç¶æ ã埩å ãããåŠçã¯ãã®äžç¶ãã£ã³ãã«ã®ç¯å²å€ã§ãã²ãŒã èªèº«ã®è²¬åã§ãã
ð¡ïž senderId ã®ã»ãã¥ãªãã£è§£æ±ºã¬ã€ã€ãŒ
- ã²ãŒã iframe â ããŒã¿ã«èŠªãŠã£ã³ããŠ: ã²ãŒã åŽã¯
{ type: 'GAME_DATA_RELAY', payload }ã®ã¿ãéä¿¡ããã¯ã©ã€ã¢ã³ãããsenderIdãæå®ã»å€æŽããããšã¯ã§ããŸããã - ããŒã¿ã«èŠªãŠã£ã³ã㊠â ãã²ã€ãµãŒããŒïŒã²ã€ãµãïŒ: 芪ãŠã£ã³ããŠã¯ã«ãŒã IDãš
payloadã®ã¿ããµãŒããŒãžéä¿¡ããŸãã - ãµãŒããŒäžç¶: ãµãŒããŒïŒ
server/socketHandlers.jsïŒã¯ãéä¿¡å ã察æŠã¹ãããã®ãã¬ã€ã€ãŒã§ããããšã確èªããããã§ïŒèгæŠè ã»éšå€è ããã®éä¿¡ã¯ãµã€ã¬ã³ãã«ç Žæ£ïŒãéä¿¡å ã®æ¥ç¶ã座ã£ãŠããåžããè«çãã¬ã€ã€ãŒïŒ'p1','p2'çïŒã決ããsenderIdãšããŠä»ããŠã«ãŒã å ã®ä»ã¯ã©ã€ã¢ã³ããžè»¢éããŸãã - åä¿¡åŽããŒã¿ã«èŠªãŠã£ã³ã㊠â åä¿¡åŽã²ãŒã iframe: 芪ãŠã£ã³ããŠïŒ
GameViewerïŒããµãŒããŒã®ä»ããsenderIdãããã®ãŸãŸ iframe ãžæ³šå ¥ããŸãã - æ¹ãã鲿¢ã®ä¿èšŒ: iframeå
ã®JavaScriptããåœã®
senderIdãä»äººã«åœè£ ããŠéä¿¡ããããšã¯ç©ççã«äžå¯èœã§ãããã¹ãæš©åšã¢ãã«ã«ãããŠæçªãã¬ã€ã€ãŒã®äžèŽç¢ºèªïŒäžæ£æäœé²æ¢ïŒã«å®å šã«äœ¿çšã§ããŸãã - CPUã®ä»£çéä¿¡:
sendGameDataAs(asPlayerId, payload)ã®å Žåã®ã¿ãsenderIdã¯éä¿¡å ã§ã¯ãªãæå®ãããCPUã¹ãããã®IDã«ãªããŸãããµãŒããŒããéä¿¡å ãé²è¡åœ¹ ã〠察象ãisCpu: trueããæ€èšŒãããã±ããã ãããã®çµè·¯ãéãããã人éã®ã¹ããããžã®ãªãããŸãã«ã¯äœ¿ããŸãããåä¿¡åŽã¯ä»£çéä¿¡ãã©ãããåºå¥ããsenderId === currentTurnã§æ€èšŒã§ããŸãã
4. åæŠïŒãªãããïŒå¯Ÿå¿ã®å®è£ ã«ãŒã«
ããŒã¿ã«ã®çµæç»é¢ã§ãã¹ãããããäžåºŠå¯ŸæŠããéžæãããšãã²ãŒã ã¢ããªç»é¢ïŒiframeïŒããªããŒãããããšãªããå床 INIT_GAME ãé
ä¿¡ãã㊠onStartMatch(config) ãå®è¡ãããŸãã
ã©ã€ããµã€ã¯ã«ã2ã€ã«åãããŠããã®ã§ã眮ãå Žæããå®ãã°ãªã»ããæŒãã¯èµ·ããŸããã
| ã³ãŒã«ãã㯠| åŒã°ããã¿ã€ãã³ã° | ããã«æžããã® |
|---|---|---|
onCreateMatch(config) | ãã®ã¯ã©ã€ã¢ã³ãã§1åã ã | DOMæ§ç¯ãã€ãã³ããªã¹ããŒç»é²ãã¢ã»ããèªã¿èŸŒã¿ |
onStartMatch(config) | æ¯è©ŠåïŒãªãããå«ãïŒ | ç€é¢ã»ã¹ããŒãã»ã¿ã€ããŒã»çµæè¡šç€ºã®ãªã»ãã |
onStartMatch ã«æžãããã®ã¯æ¯è©Šåå¿
ãå®è¡ãããããã以äžã¯èªåçã«æºããããŸãïŒ
- ç€é¢ã»ã¹ããŒãã®åæå: é§ããªããžã§ã¯ãã®é
眮ããŒã¿ãæçªïŒ
currentTurn = 'p1'ïŒãåæå€ã«æ»ãã - DOMã®åæå: åã®è©Šåã§ä»äžãããèšå·ïŒâ/ÃçïŒãåæãšãã§ã¯ãçšCSSã¯ã©ã¹ããªã»ããããã
- ã¿ã€ããŒã®åæå: æ¢åã®
setInterval/setTimeoutãclearIntervalã§ç Žæ£ãã100%ããåã¹ã¿ãŒãããã
ã²ãŒã ãªãŒããŒãã©ã°ã®åæåã¯äžèŠã§ãã Hitsumabushi.phase ã¯SDKã管çããŠãããæ¬¡ã®è©Šåã® onStartMatch ã§èªåçã« 'playing' ãžæ»ããŸãïŒÂ§2 Hitsumabushi.phaseïŒã
Hitsumabushi.init({
onCreateMatch: (config) => {
buildBoardDom(); // äžåºŠã ãããªãããã§äœãçŽãããªã
bindCellClickHandlers();
},
onStartMatch: (config) => {
resetBoardState(); // æ¯è©Šåãããã«æžãã°æŒããªã
clearTimers();
renderTurn(config.players);
},
});
5. 察æŠçµäºåŸã®UIæŽæ°ã«ãŒã«ïŒçµæè¡šç€ºã®äžæžã鲿¢ïŒ
§6 ãããªãããæã«å¿ ããªã»ãããããã«ãŒã«ã§ããã®ã«å¯Ÿãããã®å¯Ÿç§°ã«ãããã®ããçµäºåŸã¯ä»ã®UIæŽæ°ãã¹ãçµæè¡šç€ºãäžæžãããªããã«ãŒã«ã§ãã
æ¯ã¿ãŒã³åŒã°ããå
±éã®ã¹ããŒã¿ã¹æŽæ°é¢æ°ïŒupdateStatus() çïŒãæã€èšèšã§ã¯ãåæç¢ºå®åŠçã®çŽåŸã«ãã®é¢æ°ãç¡æ¡ä»¶ã§åŒã°ãããPlayer 1 ã®åã¡ïŒãããPlayer 2 ã®çªã§ããã§äžæžãããããã°ãéåžžã«èžã¿ããããªã£ãŠããŸããç¹ã«ç€é¢ã²ãŒã ã§é »åºããŸãã
以äžã®ãããããå¿ ãå®è£ ããŠãã ããã
æ¹åŒA: å
šUIæŽæ°é¢æ°ã®å
é ã Hitsumabushi.phase ã§ã¬ãŒãããïŒæšå¥šïŒ
function updateStatus() {
if (Hitsumabushi.phase === 'over') return; // â çµäºåŸã¯æçªã¡ãã»ãŒãžãæããªã
statusEl.textContent = `${currentTurn === myPlayerId ? 'ããªã' : 'çžæ'}ã®çªã§ã`;
}
phase ã¯SDKã管çããŠããããããã©ã°ãèªåã§çšæããå¿
èŠãããªãããã§æ»ãå¿
èŠããããŸãããåæãå ±åããã¯ã©ã€ã¢ã³ã㯠sendGameOver() ã®æç¹ã§ããã以å€ã®ã¯ã©ã€ã¢ã³ãïŒæè
åŽã»èгæŠè
ïŒã¯ããŒã¿ã«ããã®éç¥ã§ 'over' ã«ãªããŸãã
æ¹åŒB: çµäºåŸã¯ä»ã®æç»ãã¹ãåŒã°ãªãå¶åŸ¡ãããŒã«ãã
function applyMove(cell) {
placeStone(cell);
const winner = judge();
if (winner) {
finishMatch(winner); // çµæè¡šç€ºã®ã¿ãè¡ãã以é updateStatus() ã¯åŒã°ãªã
return; // â ããã§å¿
ã return ãã
}
switchTurn();
updateStatus();
}
å®è£ æã®æ³šæ:
- ã¬ãŒãå¯Ÿè±¡ã¯æçªè¡šç€ºã ãã§ã¯ãããŸãããæ®ãæé衚瀺ãæäœãã³ãããã€ã©ã€ãã
requestAnimationFrameã«ãŒãå ã®æç»ãåã察象ã§ãã onGameDataã§çžæãããã±ãããå±ããå Žåãåæ§ã§ããçµäºåŸã«å±ããé å»¶ãã±ããã§ç€é¢ãè¡šç€ºãæŽæ°ããªããããåä¿¡åŽã§ãã¬ãŒãããŠãã ããïŒéä¿¡åŽã§ã¯phase === 'over'ã®ésendGameDataãSDKã«é®æããããããããããæ±ºçåŸã®ãã±ããã¯åºãŠãããŸããïŒã- èªåã®ã¯ã©ã€ã¢ã³ãããæ±ºçããããšå€æããç¬éãšãããŒã¿ã«ã®
MATCH_OVERéç¥ãå±ãç¬éã«ã¯å ããªå·®ããããŸããèªåã§çµæãæç»ããã¯ã©ã€ã¢ã³ãã¯ãæç»ãšåæã«ããŒã«ã«ã§ãå ¥åãæ¢ããŠãã ããïŒphaseã¯ãã®çŽåŸã«'over'ãžè¿œãã€ããŸãïŒã