UE4局域网斗地主(三)
发表于2018-01-26
接上篇文章,游戏的创建和加入房间已经大概完成了,具体的扩展丰富,比如当前服务器列表之类的信息,UE4提供的sessionAPI都有相应的参数,就不一一解释了。那这个UE4局域网斗地主的实现就进入到最重要的部分,主要分三大块:发牌(包括抢地主),出牌(主要回合逻辑),牌型判断(算法)。其他的小细节在过程中一个个补充,在这里声明下,游戏大概的框架也是参照官方给的shootergame例子来的,因为是用了它的同步机制嘛。
大概思路简单概括:用它的RPC函数把主要的逻辑算法判断等交给服务器来计算,客户端只要傻瓜式的表现效果就OK。是的,就这么简单一句话包括了很多东西,理解起来简单,执行起来有点困难。前两篇文章已经把大概的基础知识和内容过一遍了,所以我就很直接的写了。
先完成发牌部分,这样的思路:
1.服务器发牌(并且随机一名玩家作为第一个抢地主的)-玩家客户端更新UI。
2.玩家提交抢地主数据-服务器存储计算结果。(循环)
3.服务器计算出抢地主结果-客户端更新UI。
大家发现,基本所有的逻辑计算都是服务器来完成,客户端都是做一些体力活:UI显示。因为是卡牌游戏,不存在什么移动,所有我就没有用playcontroller类,甚至player state也没用。有人就疑问了,那是怎么存储数据的?直接放pawn里同步不就完了,客户端通过Server RPC函数交给服务器计算,然后服务器上通过变量的同步来达到服务器的数据可以同步到客户端,然后在服务器上用multicast和client RPC函数来实现客户端上的执行。这里服务器和客户端的通信压力会显得非常大,特别是使用multicast,本人才疏学浅,不懂服务器,所以可能存在一些隐患。
下面是步骤流程的运行截图:
1.三个玩家进入房间,蓝色表示玩家头像的UI(简陋了点),下面是服务器,玩家ID为0,右上角是玩家1,下表为1,左上角是玩家2,下标为2。之前的几个步骤大概介绍下:玩家按照加入房间按顺序在服务器上给予ID,然后满三个玩家后各自出现准备按钮的UI。
2.如下图,每当一名玩家准备后,调用RPC函数,在服务器上执行,在服务器上更新这个玩家准备好的逻辑,然后做判断是否满足游戏玩家数,并且调用multicast更新这名玩家在所有客户端上准备好的UI显示结果。
3.所有玩家准备好后,服务器上发牌,并随机一位玩家作为第一个抢地主的人。注意:这些都在服务器上执行。然后调用Multicast,参数为随机的这个玩家下标和三个玩家对应的牌的数据。我的抢地主流程介绍下:一共两轮,第一轮是1,2,3倍叫分,第二轮是抢地主和放弃抢地主。第三轮积分大的抢到地主,如果一样,则按照顺序越前越大的规则。比如第一个抢地主下标为1,抢到最后只剩0和2,且两个叫的分一样大,那么2为地主,因为2在1的下手。
部分代码如下:
//发牌 bool AMyNewPlayer::HandOutCardsToPlayers_Server() { if (Role == ROLE_Authority&&m_server) { TArray<AActor*> _outActors; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyNewPlayer::StaticClass(), _outActors); if (_outActors.Num() > 0&&m_cardsServer.Num() >= 54) { m_cardsTop = GetCardsByIndexRange(0, 2); for (int i=0;i<_outActors.Num();i++) { AMyNewPlayer* t_player = (AMyNewPlayer*)_outActors[i]; if (t_player) { if (t_player->m_indexPlayer == 0) t_player->m_cardsClient = GetCardsByIndexRange(3, 19); else if (t_player->m_indexPlayer == 1) t_player->m_cardsClient =GetCardsByIndexRange(20, 36); else if(t_player->m_indexPlayer==2) t_player->m_cardsClient = GetCardsByIndexRange(37, 53); } } return true; } } return false; } //选地主 FProcessDisplayData_selectLandlord AMyNewPlayer::SelectLandlord() { FProcessDisplayData_selectLandlord t_res; if (Role == ROLE_Authority) { AMyCardGameGameMode* t_gameMode=(AMyCardGameGameMode*)(GetWorld()->GetAuthGameMode()); if (t_gameMode) { return t_gameMode->SelectLandlord(); } } return t_res; } FProcessDisplayData_selectLandlord AMyCardGameGameMode::SelectLandlord() { FProcessDisplayData_selectLandlord t_res; //first index in first round if (m_currentRoundingPlayerIndx == -1 && m_roundSelectLandlord == 0) { m_currentRoundingPlayerIndx = GetRandomTheFirstPlayerSelectLandlord(); t_firstRandomIndexOfRounding = m_currentRoundingPlayerIndx; t_res.Init(m_roundSelectLandlord, m_currentRoundingPlayerIndx); m_roundingPlayers = SortRoundingPlayersByFirstIndex(m_playingPlayers, m_currentRoundingPlayerIndx); t_firstIndexOfRounding = m_currentRoundingPlayerIndx; t_roundNumCurrent=1; t_roundNumTotal = m_roundingPlayers.Num(); return t_res; } //change round if (t_changeRound) { t_changeRound = false; FliterToGetRemainRoundPlayersWhoSelectLandlord(); //last round over if (m_roundSelectLandlord >= 2) { t_res.Init(m_roundSelectLandlord, m_currentRoundingPlayerIndx, true, m_currentRoundingPlayerIndx); m_landlordIndex = m_currentRoundingPlayerIndx; return t_res; } } //only one player has max integral if (m_roundingPlayers.Num() == 1 && m_roundSelectLandlord != 0) { t_res.Init(m_roundSelectLandlord, m_currentRoundingPlayerIndx, true, m_currentRoundingPlayerIndx); m_landlordIndex = m_currentRoundingPlayerIndx; return t_res; } //next player this round t_res.Init(m_roundSelectLandlord, m_currentRoundingPlayerIndx); return t_res; } //记录叫分 bool AMyNewPlayer::SetPlayerIntegralByDataFromPlayerSelect(int _index,int _integralTimes,bool _giveup) { if (Role < ROLE_Authority) { ServerSetPlayerIntegralByDataFromPlayerSelect(_index,_integralTimes,_giveup); } AMyCardGameGameMode* t_gamemode = (AMyCardGameGameMode*)(GetWorld()->GetAuthGameMode()); if (t_gamemode) { return t_gamemode->SetPlayerIntegralByDataFromPlayerSelect(_index,_integralTimes,_giveup); } return false; } bool AMyCardGameGameMode::SetPlayerIntegralByDataFromPlayerSelect(int _index, int _integralTimes, bool _giveup/* =false */) { //update integral if (_giveup) { RemoveIndexGrapping(_index); } else { if (_integralTimes > 0) { if (m_roundingPlayers.Contains(_index)) { if (m_integralsOfPlayers.Contains(_index)) { int t_num1 = m_integralsOfPlayers.FindChecked(_index); m_integralsOfPlayers.Remove(_index); m_integralsOfPlayers.Add(_index, t_num1*_integralTimes); if (t_num1*_integralTimes>m_integral) m_integral = t_num1*_integralTimes; } else { m_integralsOfPlayers.Add(_index, _integralTimes); if (_integralTimes > m_integral) m_integral = _integralTimes; } } } } //round control int t_remainNum = m_roundingPlayers.Num(); if (t_remainNum >1) { int t_nextIndex = GetNextIndexOfPlayerWhenRounding(); if (t_nextIndex >= 0) //is valid index? { //change round if (t_roundNumTotal == t_roundNumCurrent) { m_roundSelectLandlord++; t_changeRound = true; t_roundNumCurrent = 0; t_roundNumTotal = m_roundingPlayers.Num(); t_nextIndex = m_roundingPlayers[0]; t_firstIndexOfRounding = t_nextIndex; } m_currentRoundingPlayerIndx = t_nextIndex; t_roundNumCurrent++; } } else { //all players has not grap if (t_remainNum == 0) { m_roundingPlayers.Add(t_firstRandomIndexOfRounding); m_roundSelectLandlord++; } m_currentRoundingPlayerIndx = m_roundingPlayers[0]; } return true; }
部分蓝图:
4.下图为第二轮抢地主。
5.抢地主结束后,服务器记录数据,并且给予地主三张牌,然后同样的办法,在所有客户端上刷新UI数据。之后地主开始出牌。
//给地主牌 FPlayerOperationData AMyNewPlayer::GiveLandlordCardsToLandlord() { FPlayerOperationData t_res; if (Role == ROLE_Authority) { if (m_cardsTop.Num() > 0) { AMyCardGameGameMode* t_gamemode = (AMyCardGameGameMode*)GetWorld()->GetAuthGameMode(); if (t_gamemode) { int t_landlordNum = t_gamemode->m_landlordIndex; if (t_landlordNum >= 0) { AMyNewPlayer* t_landlord=GetPlayerByIndexOnServer(t_landlordNum); if (t_landlord) { for (int i=0;i<m_cardsTop.Num();i++) { t_landlord->m_cardsClient.Add(m_cardsTop[i]); } t_landlord->m_cardsClient=SortCards(t_landlord->m_cardsClient); t_res.Init(FPlayerOperationType::ReadyToOutCards, GetCardsUIDataByCards(t_landlord->m_cardsClient), t_landlordNum); } } } } } return t_res; }
对于局域网斗地主实现的过程有点快,但是在掌握了RPC机制的基础上,理解起来并不困难。代码和蓝图太多了,只展示了主要的流程。