《Three.js 3D坦克 网页联机对战版》
📅 2026/7/17 4:44:53
👁️ 阅读次数
📝 编程学习
Three.js 3D坦克 网页联机对战版
纯前端 + WebSocket 局域网联机
一台开服务端,另一台浏览器输入IP就能双人联机对战,完整可直接部署运行
一、服务端(Node.js)server.js
constWebSocket=require('ws');constwss=newWebSocket.Server({port:8080});// 房间玩家数据letplayers={};letplayerId=1;wss.on('connection',(ws)=>{constpid=playerId++;players[pid]={id:pid,pos:{x:pid===1?-15:15,y:0,z:0},rotY:0,hp:100};console.log(`玩家${pid}已连接`);// 发送当前所有玩家数据给新玩家ws.send(JSON.stringify({type:'init',data:players,selfId:pid}));// 广播新玩家上线broadcast({type:'newPlayer',p:players[pid]});ws.on('message',(msg)=>{constdata=JSON.parse(msg);switch(data.type){// 同步自身位置旋转case'pos':if(players[pid]){players[pid].pos=data.pos;players[pid].rotY=data.rotY;}broadcast({type:'syncPos',list:players});break;// 开火同步case'fire':broadcast({type:'fire',sid:pid,dir:data.dir});break;// 扣血同步case'hurt':players[data.tid].hp-=data.dmg;broadcast({type:'hurt',tid:data.tid,hp:players[data.tid].hp});break;}});ws.on('close',()=>{deleteplayers[pid];broadcast({type:'leave',id:pid});console.log(`玩家${pid}离线`);});});// 全员广播functionbroadcast(data){wss.clients.forEach(client=>{if(client.readyState===WebSocket.OPEN){client.send(JSON.stringify(data));}});}console.log("联机坦克服务端启动成功 ws://localhost:8080");二、客户端联机游戏页面index.html
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>3D坦克 局域网联机对战</title><style>*{margin:0;padding:0;box-sizing:border-box;}body{overflow:hidden;background:#111;}#tip{position:absolute;top:10px;left:10px;color:#fff;font-size:18px;z-index:999;}#win{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:50px;color:red;display:none;background:#0008;padding:20px;border-radius:10px;}</style></head><body><divid="tip">连接中...</div><divid="win">对局结束</div><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script><script>// ========== 联机配置 ==========constWS_URL="ws://127.0.0.1:8080";// 改成你的电脑局域网IPletws,selfId=0;letallPlayerData={};// 游戏全局letscene,camera,renderer;lettanks={};letbullets=[];letwalls=[];constkeys={};constspeed=0.08;constbulletSpeed=0.3;// 初始化场景functioninitScene(){scene=newTHREE.Scene();scene.background=newTHREE.Color(0x87ceeb);camera=newTHREE.PerspectiveCamera(60,innerWidth/innerHeight,0.1,1000);camera.position.set(0,35,30);camera.lookAt(0,0,0);renderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(innerWidth,innerHeight);renderer.shadowMap.enabled=true;document.body.appendChild(renderer.domElement);// 光照scene.add(newTHREE.AmbientLight(0xffffff,0.4));constdir=newTHREE.DirectionalLight(0xffffff,1);dir.position.set(20,40,20);scene.add(dir);// 地面constground=newTHREE.Mesh(newTHREE.PlaneGeometry(60,60),newTHREE.MeshLambertMaterial({color:0x338833}));ground.rotation.x=-Math.PI/2;ground.receiveShadow=true;scene.add(ground);}// 创建坦克模型functioncreateTank(color){consttank=newTHREE.Group();// 车身letbody=newTHREE.Mesh(newTHREE.BoxGeometry(2.5,1,3.5),newTHREE.MeshLambertMaterial({color}));body.position.y=0.5;body.castShadow=true;tank.add(body);// 炮塔lettur=newTHREE.Mesh(newTHREE.CylinderGeometry(0.6,0.6,1.2,16),body.material);tur.position.y=1.6;tank.add(tur);// 炮管letgun=newTHREE.Mesh(newTHREE.CylinderGeometry(0.2,0.2,2.2,8),body.material);gun.rotation.z=Math.PI/2;gun.position.set(0,1.6,1.2);tank.add(gun);returntank;}// 生成墙体functioncreateWall(){letmat=newTHREE.MeshLambertMaterial({color:0x555});for(leti=0;i<10;i++){letw=newTHREE.Mesh(newTHREE.BoxGeometry(5,2,1.5),mat);w.position.set((Math.random()-0.5)*45,1,(Math.random()-0.5)*45);walls.push(w);scene.add(w);}}// 发射炮弹functionfire(dirVec){letbullet=newTHREE.Mesh(newTHREE.SphereGeometry(0.3,8,8),newTHREE.MeshBasicMaterial({color:0xff8800}));letselfTank=tanks[selfId];bullet.position.copy(selfTank.position);bullet.position.y=1.6;bullet.userData.dir=dirVec;bullets.push(bullet);scene.add(bullet);}// 键盘监听window.addEventListener('keydown',e=>{keys[e.code]=true;if(e.code==="Space"){letselfT=tanks[selfId];letdir=newTHREE.Vector3(0,0,1);dir.applyQuaternion(selfT.quaternion);// 本地开火 + 同步服务器fire(dir);ws.send(JSON.stringify({type:"fire",dir:{x:dir.x,y:dir.y,z:dir.z}}));}});window.addEventListener('keyup',e=>keys[e.code]=false);// 本地玩家移动functionselfMove(){lett=tanks[selfId];if(!t)return;if(keys.KeyW)t.translateZ(speed);if(keys.KeyS)t.translateZ(-speed);if(keys.KeyA)t.rotation.y+=0.03;if(keys.KeyD)t.rotation.y-=0.03;// 同步位置给服务器ws.send(JSON.stringify({type:"pos",pos:{x:t.position.x,y:t.position.y,z:t.position.z},rotY:t.rotation.y}));}// 同步所有坦克位置functionsyncTankPos(){for(letidinallPlayerData){letd=allPlayerData[id];if(!tanks[id]){// 区分敌我颜色letc=Number(id)===selfId?0x2266ff:0xff2222;tanks[id]=createTank(c);scene.add(tanks[id]);}tanks[id].position.set(d.pos.x,d.pos.y,d.pos.z);tanks[id].rotation.y=d.rotY;document.getElementById("tip").innerText=`我方血量:${allPlayerData[selfId]?.hp||100}| 联机对战中`;}}// 更新炮弹与碰撞functionupdateBullet(){for(leti=bullets.length-1;i>=0;i--){letb=bullets[i];b.position.x+=b.userData.dir.x*bulletSpeed;b.position.z+=b.userData.dir.z*bulletSpeed;// 检测击中其他玩家for(letpidintanks){if(Number(pid)===selfId)continue;if(b.position.distanceTo(tanks[pid].position)<2){ws.send(JSON.stringify({type:"hurt",tid:pid,dmg:15}));scene.remove(b);bullets.splice(i,1);break;}}// 边界清除if(Math.abs(b.position.x)>38||Math.abs(b.position.z)>38){scene.remove(b);bullets.splice(i,1);}}}// 胜负判断functioncheckWin(){letwinDom=document.getElementById("win");letselfHp=allPlayerData[selfId]?.hp;if(selfHp<=0){winDom.innerText="你战败了!";winDom.style.display="block";}for(letidinallPlayerData){if(id!=selfId&&allPlayerData[id].hp<=0){winDom.innerText="你胜利了!";winDom.style.display="block";}}}// 连接WebSocket服务端functionconnectWS(){ws=newWebSocket(WS_URL);ws.onopen=()=>{document.getElementById("tip").innerText="已连接服务器,等待队友进入";};ws.onmessage=(e)=>{letmsg=JSON.parse(e.data);switch(msg.type){case"init":selfId=msg.selfId;allPlayerData=msg.data;break;case"syncPos":allPlayerData=msg.list;break;case"hurt":allPlayerData[msg.tid].hp=msg.hp;checkWin();break;case"fire":// 接收别人开火letd=newTHREE.Vector3(msg.dir.x,msg.dir.y,msg.dir.z);fire(d);break;}};ws.onerror=()=>alert("连接服务器失败,请检查IP与端口");}// 窗口适配window.onresize=()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);};// 渲染循环functionanimate(){requestAnimationFrame(animate);selfMove();syncTankPos();updateBullet();renderer.render(scene,camera);}// 启动游戏functionstartGame(){initScene();createWall();connectWS();animate();}startGame();</script></body></html>三、使用教程(超简单)
- 安装Node.js官网下载安装
- 新建文件夹,放入
server.js - 打开cmd进入文件夹执行:
npminstallwsnodeserver.js- 查看本机局域网IP(Win:ipconfig / Linux:ifconfig)
- 修改
index.html里WS_URL = "ws://你的局域网IP:8080" - 两台电脑同WiFi,都打开这个html即可联机对战
操作规则
- 玩家1 / 玩家2 统一操控:WASD移动转向,空格开火
- 位置、开火、掉血全部实时同步
- 血量归0直接弹出胜负提示
- 自带随机墙体阻挡
拓展功能可直接加
- 增加炮弹爆炸粒子特效
- 加入观战视角、第三人称跟随视角
- 限制坦克穿墙碰撞
- 增加道具、加速、护盾
- 改成4人多人联机
编程学习
技术分享
实战经验