用ai制作剪刀石头布游戏
这篇文章是一个模板,展示了如何利用人工智能或计算机视觉制作多人游戏,比如“石头、剪刀、布”,这些游戏涉及手部和身体的动作。
包含的多人游戏:
代码库目前包含三个完整的双人游戏:
-
石头、剪刀、布
-
对视比赛
-
007(对峙或阻挡、重新装填、射击和霰弹枪)——如何玩
关于双人游戏的代码
代码文件非常简短,你可以随意修改它们,制作新的游戏,或者学习 WebSockets 的工作原理。它具有最基本的匹配功能,以及可共享的链接。
拥有相同链接的用户将进入同一个“房间”。
技术非常简单,它是一个非常基础的 Express JS 服务器,包含三个多人游戏文件夹。游戏使用 WebRTC 和原生 WebSockets 实现实时连接。人工智能视觉模型由 Roboflow 提供。
眨眼和“石头、剪刀、布”模型已经在 Roboflow Universe 上制作完成。
用人工智能制作“石头、剪刀、布”:如何从零开始制作游戏
我开始自己训练其中一个模型,通过拍摄一段视频并标注约200帧用于目标检测。
这个过程听起来比实际操作要难。你只需上传视频,然后围绕手部拖动方框,并写下你希望识别的游戏动作。可能需要通过尝试类似的手势动作来调整。
当我意识到一些手势动作非常相似时,我尝试通过增加更多图像来区分它们,使模型更容易识别玩家的一个动作而不是另一个动作。
例如,在“007”游戏中,玩家通常会将手直直地举起来重新装填,这与射击动作(也是指向前方)看起来非常相似。因此,我尝试在重新装填动作中包含更多手臂的部分,以便模型不会轻易混淆这两种动作。
我还包含了各种姿势,以及不同的衣服、房间和朋友,以使模型尽可能灵活。
你可以选择大致围绕手部绘制方框进行标注,也可以花费更多时间仅对手部和手指进行分割标注。
我发现,即使一开始花费更多时间对手部和手指进行分割标注也是值得的。经过一段时间后,智能标注工具会变得非常擅长为你完成这项工作。
在线游戏中的视频流
网络摄像头通过 WebRTC 以点对点的方式通过 stunprotocol 和谷歌服务器进行流式传输。这是一种确保你能够在复杂网络环境中实现点对点连接的方法。
你不需要理解它是如何工作的,所有功能都在 common/common.js 文件中:
1 | // 调用 start(true) 将开始流式传输你的网络摄像头functionstart(isCaller){ peerConnection =newRTCPeerConnection(peerConnectionConfig); peerConnection.onicecandidate= gotIceCandidate; peerConnection.ontrack= gotRemoteStream; for(const track of localStream.getTracks()){ peerConnection.addTrack(track, localStream); } if(isCaller){ peerConnection.createOffer().then(createdDescription).catch(errorHandler); }}// 你的设备 uuid 被发送到服务器,以便它可以将你分配到一个房间functiongotIceCandidate(event){ if(event.candidate!=null){ serverConnection.send(JSON.stringify({'ice': event.candidate,'uuid': uuid})); }} |
let clients = {};let gameStates = {};
1 |
|
if (clients[ws.room].length>=2){ ws.send(“房间已满”); ws.close();return;}clients[ws.room].push(ws);if(game ===‘rps’){ gameStates[ws.room]={…defaultGameState_rps };if(clients[ws.room].length===2){ gameStates[ws.room].id_p1= clients[ws.room][0].uuid; gameStates[ws.room].id_p2= clients[ws.room][1].uuid; roundLoop_rps(gameStates, ws, clients);}}
1 |
|
function roundLoop_rps(gameStates, ws, clients){ gameStates[ws.room].remoteVideoVisible=false; broadcast_game_rps(…); setTimeout(()=>{ gameStates[ws.room].gameStateText=“1”; broadcast_game_rps(…); setTimeout(()=>{ gameStates[ws.room].gameStateText=“1, 2”; broadcast_game_rps(…); setTimeout(()=>{ gameStates[ws.room].gameStateText=“1, 2, Go!”; gameStates[ws.room].is_resolving=true; gameStates[ws.room].remoteVideoVisible=true; handleRound_rps(gameStates, ws, clients); broadcast_game_rps(…); if(!gameStates[ws.room].end_game){ setTimeout(()=>{ roundLoop_rps(gameStates, ws, clients); gameStates[ws.room].gameStateText=“”; gameStates[ws.room].is_resolving=false; broadcast_game_rps(…); },2000); } broadcast_game_rps(…); },2000); },2000); },2000);}
1 |
|
function broadcast_game_rps(data, room, clients){ clients[room].forEach(client=>{ if(client.readyState===WebSocket.OPEN){ let gameData =JSON.parse(data); // 根据玩家的 UUID 设置“你”和“对方”字段。 if(client.uuid=== gameData.gameState.id_p1){ … }else{ … client.send(JSON.stringify(gameData),{binary:false}); }});}
1 |
|
function handleRound_rps(gameStates, ws, clients){if(gameStates[ws.room].move_p1==“ROCK”){ if(gameStates[ws.room].move_p2==“SCISSORS”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }elseif(gameStates[ws.room].move_p2==“PAPER”){ gameStates[ws.room].health_p1= gameStates[ws.room].health_p1-1; }elseif(gameStates[ws.room].move_p2==“NOTHING”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }}elseif(gameStates[ws.room].move_p1==“PAPER”){ if(gameStates[ws.room].move_p2==“ROCK”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }elseif(gameStates[ws.room].move_p2==“SCISSORS”){ gameStates[ws.room].health_p1= gameStates[ws.room].health_p1-1; }elseif(gameStates[ws.room].move_p2==“NOTHING”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }}elseif(gameStates[ws.room].move_p1==“SCISSORS”){ if(gameStates[ws.room].move_p2==“PAPER”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }elseif(gameStates[ws.room].move_p2==“ROCK”){ gameStates[ws.room].health_p1= gameStates[ws.room].health_p1-1; }elseif(gameStates[ws.room].move_p2==“NOTHING”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }}else{ gameStates[ws.room].health_p1= gameStates[ws.room].health_p1-1; if(gameStates[ws.room].move_p2==“NOTHING”){ gameStates[ws.room].health_p2= gameStates[ws.room].health_p2-1; }}if(gameStates[ws.room].health_p1<=0|| gameStates[ws.room].health_p2<=0){ gameStates[ws.room].end_game=true; if(gameStates[ws.room].health_p1>0){ gameStates[ws.room].winnerText_p1=“WINNER!” gameStates[ws.room].winnerText_p2=“DEFEAT!” }elseif(gameStates[ws.room].health_p2>0){ gameStates[ws.room].winnerText_p1=“DEFEAT!” gameStates[ws.room].winnerText_p2=“WINNER!” }else{ gameStates[ws.room].winnerText_p1=“DRAW!” gameStates[ws.room].winnerText_p2=“DRAW!” } broadcast_game_rps(…);}}
我将两个玩家的默认生命值都设置为 3。当其中一个玩家的生命值降到 0 以下时,gameStates[ws.room].end_game 被设置为 True,游戏结束。
