400网站源码分享,2020网站源码

今天给各位分享400网站源码分享的知识,其中也会对2020网站源码进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

使用MultipeerConnectivity框架在SwiftUI4中创建“石头剪刀布”游戏-第2部分

在第1部分中,我们创建了一个框架RPSMultipeerSession,仅使用多点连接框架直接从一个设备与另一个设备进行通信,根本没有使用后端服务器。

在这一部分中,我们将完成这些方法的实现并开始构建UI!

首先,我们需要一种方法将我们的行动发送给我们的对手。在RPSMultipeerSession类内部,在deinit()之后,我们将放置这个方法:

funcsend(move:Move){\nif!session.connectedPeers.isEmpty{\nlog.info(&34;)\ndo{\ntrysession.send(move.rawValue.data(using:.utf8)!,toPeers:session.connectedPeers,with:.reliable)\n}catch{\nlog.error(&34;)\n}\n}\n}

首先,我们通过检查session.connectedPeers是否为空来确保我们的对手已连接。如果它不是空的,我们有一个对手连接并等待接收我们的动作。我们尝试发送使用session.send()提供的移动并捕获任何抛出的异常。

通过实现send方法,我们可以继续完成委托。

在MCSessionDelegate内部,有一个switch语句来处理状态已更改的对等方的状态。如果对等方已断开连接,我们应该将配对变量设为false并开始寻找另一个对手。如果对等点已连接,我们将配对变量设置为true并停止寻找对等点。如果发生了其他事情,很可能对等点当前正在连接,因此我们的配对变量应该为假。

实现如下所示:

switchstate{\ncaseMCSessionState.notConnected:\n//Peerdisconnected\nDispatchQueue.main.async{\nself.paired=false\n}\n//Peerdisconnected,startacceptinginvitaionsagain\nserviceAdvertiser.startAdvertisingPeer()\nbreak\ncaseMCSessionState.connected:\n//Peerconnected\nDispatchQueue.main.async{\nself.paired=true\n}\n//Wearepaired,stopacceptinginvitations\nserviceAdvertiser.stopAdvertisingPeer()\nbreak\ndefault:\n//Peerconnectingorsomethingelse\nDispatchQueue.main.async{\nself.paired=false\n}\nbreak\n}

由于paired是一个已发布的变量,我们将有视图监听我们需要在主线程上进行更改的更改,因此使用了DispatchQueue.main.async。

还是在MCSessionDelegate中,didReceivedata:方法是下一个。当我们收到来自对手的消息时,我们应该告诉视图我们收到了一个动作以及那个动作是什么。实现如下所示:

funcsession(_session:MCSession,didReceivedata:Data,fromPeerpeerID:MCPeerID){\nifletstring=String(data:data,encoding:.utf8),letmove=Move(rawValue:string){\nlog.info(&34;)\n//Wereceivedamovefromtheopponent,telltheGameView\nDispatchQueue.main.async{\nself.receivedMove=move\n}\n}else{\nlog.info(&34;)\n}\n}

在这里,我们确保可以根据收到的数据创建Move,如果可以,我们会在主线程上更新receivedMove的值。

该委托的其余部分可以保持原样。

在MCNearbyServiceAdvertiserDelegate内部,有一个方法需要完成。这是didReceiveInvitationFromPeer之一,实现如下所示:

funcadvertiser(_advertiser:MCNearbyServiceAdvertiser,didReceiveInvitationFromPeerpeerID:MCPeerID,withContextcontext:Data?,invitationHandler:@escaping(Bool,MCSession?)->Void){\nlog.info(&34;)\n\nDispatchQueue.main.async{\n//TellPairViewtoshowtheinvitationalert\nself.recvdInvite=true\n//GivePairViewthepeerIDofthepeerwhoinvitedus\nself.recvdInviteFrom=peerID\n//GivePairViewthe`invitationHandler`soitcanaccept/denytheinvitation\nself.invitationHandler=invitationHandler\n}\n}

当我们收到来自其他玩家的邀请时,我们希望让我们的视图知道,以便它可以提示我们的用户接受或拒绝邀请。我们告诉我们的视图我们收到了一个邀请,邀请我们并给它一个邀请处理程序来响应其他玩家。

接下来我们将完成MCNearbyServiceBrowserDelegate。当浏览器找到对等点时,我们希望将其添加到availablePeers中,以便我们的视图可以将其显示给我们的用户。实现如下所示:

funcbrowser(_browser:MCNearbyServiceBrowser,foundPeerpeerID:MCPeerID,withDiscoveryInfoinfo:[String:String]?){\nlog.info(&34;)\n//Addthepeertothelistofavailablepeers\nDispatchQueue.main.async{\nself.availablePeers.append(peerID)\n}\n}

我们只需将peerID附加到主线程上的availablePeers即可。容易,对吧?

最后一点丢失的代码是在lostPeer浏览器方法中。当一个对等点丢失时,它应该从availablePeers中删除,如下所示:

funcbrowser(_browser:MCNearbyServiceBrowser,lostPeerpeerID:MCPeerID){\nlog.info(&34;)\n//Removelostpeerfromlistofavailablepeers\nDispatchQueue.main.async{\nself.availablePeers.removeAll(where:{\n$0==peerID\n})\n}\n}

这样就结束了我们的RPSMultipeerSession!现在我们继续创建UI并处理这些数据。

在UI领域,我们需要创建一个起始视图以允许我们的用户设置他/她的用户名。这可以通过多种方式完成,但是一旦收到文本,我们应该使用NavigationLink或类似的方法进入配对屏幕。

NavigationLink(destination:PairView(rpsSession:RPSMultipeerSession(username:username))){\nImage(systemName:&34;)\n.foregroundColor(Color(.gray))\n}

我就是这样做的,将用户名作为PairView的参数传递给RPSMultipeerSession。

如果我们的会话对象的pair属性为false,PairView将显示一个按钮列表,其中包含附近也在PairView上的玩家的用户名。当用户单击其中一个按钮时,将向该用户发送邀请,并且将显示提示玩家接受或拒绝邀请的警报。看起来是这样的:

//\n//PairView.swift\n//RPS\n//\n//CreatedbyJoeDiragion7/29/22.\n//\n\nimportSwiftUI\nimportos\n\nstructPairView:View{\n@StateObjectvarrpsSession:RPSMultipeerSession\nvarlogger=Logger()\n\nvarbody:someView{\nif(!rpsSession.paired){\nHStack{\nList(rpsSession.availablePeers,id:\\.self){peerin\nButton(peer.displayName){\nrpsSession.serviceBrowser.invitePeer(peer,to:rpsSession.session,withContext:nil,timeout:30)\n}\n}\n}\n.alert(&34;ERR&34;,isPresented:$rpsSession.recvdInvite){\nButton(&34;){\nif(rpsSession.invitationHandler!=nil){\nrpsSession.invitationHandler!(true,rpsSession.session)\n}\n}\nButton(&34;){\nif(rpsSession.invitationHandler!=nil){\nrpsSession.invitationHandler!(false,nil)\n}\n}\n}\n}else{\nGameView(rpsSession:rpsSession)\n}\n}\n}

在列表中创建的按钮,当按下时,将调用我们的serviceBrowser上的invitePeer以向其他玩家发送邀请。该方法由serviceBrowser提供,我们没有实现。

警报会侦听会话对象中recvdInvite布尔值的更改。如果用户选择“接受邀请”按钮,我们将调用邀请处理程序并使用真值和当前会话。否则,我们将邀请处理程序传递给false并且不打扰会话。

当我们或其他玩家收到并接受邀请时,将调用MCSessionDelegate的“peerdidChange”方法,状态为.connected。如果您返回查看该方法,您会看到我们将paired设置为true,这会将屏幕上的视图更改为GameView并传递我们的rpsSession。

这将我们带到了GameView。

由于这不是一个SwiftUI教程,而是更多关于如何使用MultipeerConnectivity的方法,所以我不会深入探讨我的布局的细节,因为它非常罗嗦。

相反,我将分解逻辑部分并展示我是如何完成接收和发送动作的。

布局由一个VStack组成,其中包含对手的移动(计时器倒计时时的思想泡泡或计时器到期时对手发送的移动),一个从10开始倒计时的Text,我们当前的移动,以及一个包含3个按钮的HStack(石头、纸和剪刀)。

下面是GameView的完整实现:

//\n//GameView.swift\n//RPS\n//\n//CreatedbyJoeDiragion7/29/22.\n//\n\nimportSwiftUI\n\nenumResult{\ncasewin,loss,tie\n}\n\nstructGameView:View{\n@StateObjectvarrpsSession:RPSMultipeerSession\n\n@StatevartimeLeft=10\n@Statevartimer=Timer.publish(every:1,on:.main,in:.common).autoconnect()\n\n@StatevarcurrentMove:Move=.unknown\n@StatevaropponentMove:Move=.unknown\n@StatevarshowResult:Bool=false\n@Statevarresult:Result=.tie\n@StatevarresultMessage:String=&34;\n\nvarbody:someView{\nZStack{\nVStack(alignment:.center){\n//Opponent-??\nImage(opponentMove.description)\n.resizable()\n.scaledToFit()\n.frame(width:100)\n.padding(.top)\n.padding()\n\n//Timer-10\nText(&34;)\n.font(.system(size:30))\n.onReceive(timer){inputin\nif(timeLeft>0){\ntimeLeft-=1\n}else{\ntimeLeft=10\ntimer.upstream.connect().cancel()\n//Calltimer.upstream.connect()torestartthetimer\nswitchrpsSession.receivedMove{\ncase.rock:\nopponentMove=.rock\nbreak\ncase.paper:\nopponentMove=.paper\nbreak\ncase.scissors:\nopponentMove=.scissors\nbreak\ndefault:\n//TODO:Invalid,bigredXorsomethingidk\nopponentMove=.unknown\nbreak\n}\n//TODO:Showwinning/losingscreenandrestartbutton\nresult=score(opponentMove:opponentMove,ourMove:currentMove)\nif(result==.win){\nresultMessage=&34;\n}elseif(result==.loss){\nresultMessage=&34;\n}else{\nresultMessage=&39;satie!&34;Rock&34;Paper&34;Scissors&34;Wouldyouliketoplayagain?&34;Yes&34;No”){\nrpsSession.session.disconnect()\n}\n}.zIndex(1)\n.frame(width:400,height:500)\n.background(Color.white)\n.cornerRadius(12)\n}\n}\n}\n\nfuncscore(opponentMove:Move,ourMove:Move)->Result{\nswitchopponentMove{\ncase.rock:\nifourMove==.scissors{\nreturn.loss\n}elseifourMove==.paper{\nreturn.win\n}else{\nreturn.tie\n}\ncase.paper:\nifourMove==.rock{\nreturn.loss\n}elseifourMove==.scissors{\nreturn.win\n}else{\nreturn.tie\n}\ncase.scissors:\nifourMove==.paper{\nreturn.loss\n}elseifourMove==.rock{\nreturn.win\n}else{\nreturn.tie\n}\ndefault:\n//Invalidmovesomewhere\nreturn.tie\n}\n}\n}

您可以看到计时器每秒更新一次,当它达到零时,检查我们是否收到对手的得分并宣布获胜者的动作。每次按下移动按钮时,我们都会设置currentMove,然后更新当前移动的图像,并将移动发送给我们的对手。

这个实现很好。它做了我想要的,最后我们可以在SwiftUI4中使用MPC,而不需要任何UIKit垃圾(抱歉)。但这并不完美。除了错误处理、丑陋的UI元素、在拒绝邀请方面与用户缺乏沟通、没有实现重启或离开游戏的方法等明显问题之外,我实现的主要问题是计时器。计时器通常会不同步,因为接收到邀请的设备上的计时器将在发送邀请的设备上的计时器之前启动几分之一秒(毕竟它知道邀请首先被接受)。我有一些想法来解决这个问题。

我相信最好的解决方案是让开始游戏的设备(接受邀请的设备)在另一台设备切换到游戏视图后将计时器流式传输到另一台设备。基本上,一旦显示游戏视图,第二个设备就会向第一个设备发送一条消息,通知它开始流式传输计时器。

这可能会更好,但它也可能有同样的问题,而且似乎有点矫枉过正。考虑到玩家无论如何都不会看着彼此的屏幕,计时器的延迟并不是什么大问题,否则有什么意义呢?

但为了好玩,我会坐下来尝试让我的流实现工作。了解未来项目如何通过MPC流式传输数据可能是值得的。当我解决完这个问题后,我将在此处发布后续内容,并计划制作一个完整的分步youtube教程。

感谢您阅读并留下一些评论,如果您有一些建议或注意到我错过的东西!

关注七爪网,获取更多APP/小程序/网站源码资源!

END,本文到此结束,如果可以帮助到大家,还望关注本站哦!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平