typeLobbyMatchstruct{}typeLobbyMatchStatestruct{presencesmap[string]runtime.PresenceemptyTicksint}func(m*LobbyMatch)MatchInit(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,paramsmap[string]interface{})(interface{},int,string){state:=&LobbyMatchState{emptyTicks:0,presences:map[string]runtime.Presence{},}tickRate:=1// 1 tick per second = 1 MatchLoop func invocations per second
label:=""returnstate,tickRate,label}func(m*LobbyMatch)MatchJoin(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,dispatcherruntime.MatchDispatcher,tickint64,stateinterface{},presences[]runtime.Presence)interface{}{lobbyState,ok:=state.(*LobbyMatchState)if!ok{logger.Error("state not a valid lobby state object")returnnil}fori:=0;i<len(presences);i++{lobbyState.presences[presences[i].GetSessionId()]=presences[i]}returnlobbyState}func(m*LobbyMatch)MatchLeave(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,dispatcherruntime.MatchDispatcher,tickint64,stateinterface{},presences[]runtime.Presence)interface{}{lobbyState,ok:=state.(*LobbyMatchState)if!ok{logger.Error("state not a valid lobby state object")returnnil}fori:=0;i<len(presences);i++{delete(lobbyState.presences,presences[i].GetSessionId())}returnlobbyState}func(m*LobbyMatch)MatchLoop(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,dispatcherruntime.MatchDispatcher,tickint64,stateinterface{},messages[]runtime.MatchData)interface{}{lobbyState,ok:=state.(*LobbyMatchState)if!ok{logger.Error("state not a valid lobby state object")returnnil}// If we have no presences in the match according to the match state, increment the empty ticks count
iflen(lobbyState.presences)==0{lobbyState.emptyTicks++}// If the match has been empty for more than 100 ticks, end the match by returning nil
iflobbyState.emptyTicks>100{returnnil}returnlobbyState}
constmatchInit=function(ctx: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,params:{[key: string]:string}):{state: nkruntime.MatchState,tickRate: number,label: string}{return{state:{presences:{},emptyTicks: 0},tickRate: 1,// 1 tick per second = 1 MatchLoop func invocations per second
label:''};};constmatchJoin=function(ctx: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,dispatcher: nkruntime.MatchDispatcher,tick: number,state: nkruntime.MatchState,presences: nkruntime.Presence[]):{state: nkruntime.MatchState}|null{presences.forEach(function(p){state.presences[p.sessionId]=p;});return{state};}constmatchLeave=function(ctx: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,dispatcher: nkruntime.MatchDispatcher,tick: number,state: nkruntime.MatchState,presences: nkruntime.Presence[]):{state: nkruntime.MatchState}|null{presences.forEach(function(p){delete(state.presences[p.sessionId]);});return{state};}constmatchLoop=function(ctx: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,dispatcher: nkruntime.MatchDispatcher,tick: number,state: nkruntime.MatchState,messages: nkruntime.MatchMessage[]):{state: nkruntime.MatchState}|null{// If we have no presences in the match according to the match state, increment the empty ticks count
if(state.presences.length===0){state.emptyTicks++;}// If the match has been empty for more than 100 ticks, end the match by returning null
if(state.emptyTicks>100){returnnull;}return{state};}
localM={}functionM.match_init(context,initial_state)localstate={presences={},empty_ticks=0}localtick_rate=1-- 1 tick per second = 1 MatchLoop func invocations per secondlocallabel=""returnstate,tick_rate,labelendfunctionM.match_join(context,dispatcher,tick,state,presences)for_,presenceinipairs(presences)dostate.presences[presence.session_id]=presenceendreturnstateendfunctionM.match_leave(context,dispatcher,tick,state,presences)for_,presenceinipairs(presences)dostate.presences[presence.session_id]=nilendreturnstateendfunctionM.match_loop(context,dispatcher,tick,state,messages)-- Get the count of presences in the matchlocaltotalPresences=0fork,vinpairs(state.presences)dototalPresences=totalPresences+1end-- If we have no presences in the match according to the match state, increment the empty ticks countiftotalPresences==0thenstate.empty_ticks=state.empty_ticks+1end-- If the match has been empty for more than 100 ticks, end the match by returning nilifstate.empty_ticks>100thenreturnnilendreturnstateend
比赛无法从外部停止,只有在生命周期函数之一返回nil状态时才会结束。
必须注册比赛处理程序才能使用它。
Server
1
2
3
4
5
6
7
8
funcInitModule(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,initializerruntime.Initializer)error{iferr:=initializer.RegisterMatch("lobby",func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule)(runtime.Match,error){return&LobbyMatch{},nil});err!=nil{logger.Error("unable to register: %v",err)returnerr}}
-- the name must be the same as the match handler file (e.g. lobby.lua)nk.register_matchmaker_matched(function(context,matched_users)localmatch_id,err=nk.match_create("lobby",{invited=matched_users})returnmatch_idend)
constMATCH_START_OPCODE=7matchStartData:=&map[string]interface{}{"started":true,"roundTimer":100,}data,err:=json.Marshal(matchStartData)iferr!=nil{logger.Error("error marshaling match start data",err)returnnil}reliable:=truedispatcher.BroadcastMessage(MATCH_START_OPCODE,data,nil,nil,reliable)
socket.onmatchdata=(result)=>{varcontent=result.data;switch(result.op_code){case101:console.log("A custom opcode.");break;default:console.log("User %o sent %o",result.presence.user_id,content);}};
Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Use whatever decoder for your message contents.varenc=System.Text.Encoding.UTF8;socket.ReceivedMatchState+=newState=>{varcontent=enc.GetString(newState.State);switch(newState.OpCode){case101:Console.WriteLine("A custom opcode.");break;default:Console.WriteLine("User '{0}'' sent '{1}'",newState.UserPresence.Username,content);}};
Client
1
2
3
4
5
6
7
8
9
10
11
12
rtListener->setMatchDataCallback([](constNMatchData&data){switch(data.opCode){case101:std::cout<<"A custom opcode."<<std::endl;break;default:std::cout<<"User "<<data.presence.userId<<" sent "<<data.data<<std::endl;break;}});
Client
1
2
3
4
5
6
SocketListenerlistener=newAbstractSocketListener(){@OverridepublicvoidonMatchData(finalMatchDatamatchData){System.out.format("Received match data %s with opcode %d",matchData.getData(),matchData.getOpCode());}};
Client
1
2
3
4
5
6
func_ready():# First, setup the socket as explained in the authentication section.socket.connect("received_match_state",self,"_on_match_state")func_on_match_state(p_state:NakamaRTAPI.MatchData):print("Received match state with opcode %s, data %s"%[p_state.op_code,parse_json(p_state.data)])
funcCreateMatchRPC(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,payloadstring)(string,error){params:=make(map[string]interface{})iferr:=json.Unmarshal([]byte(payload),¶ms);err!=nil{return"",err}modulename:="pingpong"// Name with which match handler was registered in InitModule, see example above.
ifmatchId,err:=nk.MatchCreate(ctx,modulename,params);err!=nil{return"",err}else{returnmatchId,nil}}// Register as RPC function, this call should be in InitModule.
iferr:=initializer.RegisterRpc("create_match_rpc",CreateMatchRPC);err!=nil{logger.Error("Unable to register: %v",err)returnerr}
localnk=require("nakama")localfunctionmakematch(context,matched_users)-- print matched usersfor_,userinipairs(matched_users)dolocalpresence=user.presencenk.logger_info(("Matched user '%s' named '%s'"):format(presence.user_id,presence.username))fork,vinpairs(user.properties)donk.logger_info(("Matched on '%s' value '%s'"):format(k,v))endendlocalmodulename="pingpong"localsetupstate={invited=matched_users}localmatchid=nk.match_create(modulename,setupstate)returnmatchidendnk.register_matchmaker_matched(makematch)
funcMakeMatch(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,entries[]runtime.MatchmakerEntry)(string,error){for_,e:=rangeentries{logger.Info("Matched user '%s' named '%s'",e.GetPresence().GetUserId(),e.GetPresence().GetUsername())fork,v:=rangee.GetProperties(){logger.Info("Matched on '%s' value '%v'",k,v)}}matchId,err:=nk.MatchCreate(ctx,"pingpong",map[string]interface{}{"invited":entries})iferr!=nil{return"",err}returnmatchId,nil}// Register as matchmaker matched hook, this call should be in InitModule.
iferr:=initializer.RegisterMatchmakerMatched(MakeMatch);err!=nil{logger.Error("Unable to register: %v",err)returnerr}
functionmatchmakerMatched(context: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,matches: nkruntime.MatchmakerResult[]):string{matches.forEach(function(match){logger.info("Matched user '%s' named '%s'",match.presence.userId,match.presence.username);Object.keys(match.properties).forEach(function(key){logger.info("Matched on '%s' value '%v'",key,match.properties[key])});});try{constmatchId=nk.matchCreate("pingpong",{invited: matches});returnmatchId;}catch(err){logger.error(err);throw(err);}}// ...
initializer.registerMatchmakerMatched(matchmakerMatched);
// Define an op code for sending a new match id to remaining presences
constnewMatchOpCode=999func(m*LobbyMatch)MatchTerminate(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,dispatcherruntime.MatchDispatcher,tickint64,stateinterface{},graceSecondsint)interface{}{logger.Debug("match will terminate in %d seconds",graceSeconds)varmatchIdstring// Find an existing match for the remaining connected presences to join
limit:=1authoritative:=truelabel:=""minSize:=2maxSize:=4query:="*"availableMatches,err:=nk.MatchList(ctx,limit,authoritative,label,minSize,maxSize,query)iferr!=nil{logger.Error("error listing matches",err)returnnil}iflen(availableMatches)>0{matchId=availableMatches[0].MatchId}else{// No available matches, create a new match instead
matchId,err=nk.MatchCreate(ctx,"match",nil)iferr!=nil{logger.Error("error creating match",err)returnnil}}// Broadcast the new match id to all remaining connected presences
data:=map[string]string{matchId:matchId,}dataJson,err:=json.Marshal(data)iferr!=nil{logger.Error("error marshaling new match message")returnnil}dispatcher.BroadcastMessage(newMatchOpCode,dataJson,nil,nil,true)returnstate}
// Define an op code for sending a new match id to remaining presences
constnewMatchOpCode=999;constmatchTerminate=function(ctx: nkruntime.Context,logger: nkruntime.Logger,nk: nkruntime.Nakama,dispatcher: nkruntime.MatchDispatcher,tick: number,state: nkruntime.MatchState,graceSeconds: number):{state: nkruntime.MatchState}|null{logger.debug(`Match will terminate in ${graceSeconds} seconds.`);letmatchId=null;// Find an existing match for the remaining connected presences to join
constlimit=1;constauthoritative=true;constlabel="";constminSize=2;constmaxSize=4;constquery="*";constavailableMatches=nk.matchList(limit,authoritative,label,minSize,maxSize,query);if(availableMatches.length>0){matchId=availableMatches[0].matchId;}else{// No available matches, create a new match instead
matchId=nk.matchCreate("match",{invited: state.presences});}// Broadcast the new match id to all remaining connected presences
dispatcher.broadcastMessage(newMatchOpCode,JSON.stringify({matchId}),null,null,true);return{state};}
-- Define an op code for sending a new match id to remaining presenceslocalnew_match_op_code=999functionM.match_terminate(context,dispatcher,tick,state,grace_seconds)localmessage="Server shutting down in "..grace_seconds.." seconds"localmatch_id-- Find an existing match for the remaining connected presences to joinlocallimit=1;localauthoritative=true;locallabel="";localmin_size=2;localmax_size=4;localquery="*";localavailable_matches=nk.match_list(limit,authoritative,label,min_size,max_size,query);if#available_matches>0thenmatch_id=available_matches[0].match_id;else-- No available matches, create a new match insteadmatch_id=nk.match_create("match",{invited=state.presences});end-- Broadcast the new match id to all remaining connected presencesdispatcher.broadcast_message(new_match_op_code,nk.json_encode({["matchId"]=match_id}))returnstateend
typeLobbyMatchStatestruct{presencesmap[string]runtime.Presencestartedbool}func(m*LobbyMatch)MatchInit(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,paramsmap[string]interface{})(interface{},int,string){state:=&LobbyMatchState{presences:map[string]runtime.Presence{},started:false,}tickRate:=1label:=""returnstate,tickRate,label}func(m*LobbyMatch)MatchLoop(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,nkruntime.NakamaModule,dispatcherruntime.MatchDispatcher,tickint64,stateinterface{},messages[]runtime.MatchData)interface{}{lobbyState,ok:=state.(*LobbyMatchState)if!ok{logger.Error("state not a valid lobby state object")}if(len(lobbyState.presences)>2){lobbyState.started=true;}returnlobbyState}
localM={}functionM.match_init(context,initial_state)localstate={presences={},started=false}localtick_rate=1locallabel=""returnstate,tick_rate,labelendfunctionM.match_loop(context,dispatcher,tick,state,messages)-- Get the count of presences in the matchlocaltotalPresences=0fork,vinpairs(state.presences)dototalPresences=totalPresences+1endiftotalPresences>2thenstate.started=trueendreturnstateend