广州自助网站设计平台,wordpress去掉版本号,php 图片上载 wordpress,什么是网络营销最基本最重要的工具#x1f61c;作 者#xff1a;是江迪呀✒️本文关键词#xff1a;websocket、网络、原理、多人游戏☀️每日 一言#xff1a;这世上有两种东西无法直视#xff0c;一是太阳#xff0c;二是人心#xff01; 一、我的游戏史
我最开始接触游戏要从一盘300游戏… 作 者是江迪呀✒️本文关键词websocket、网络、原理、多人游戏☀️每日 一言这世上有两种东西无法直视一是太阳二是人心 一、我的游戏史
我最开始接触游戏要从一盘300游戏的光碟说起那是家里买DVD送的《魂斗罗》、《超级马里奥》天天玩。自从买回来后我就经常和姐姐因为抢电视机使用权而大打出手。有次她把遥控器藏到了沙发的夹层里被我妈一屁股做成了两半我和我姐喜提一顿暴打。那顿是我挨得最狠的以至于现在回想起来屁股还条件反射的隐隐作痛。
后来我骗我妈说我要学习英语、练习打字以后成为祖国的栋梁之才让她给我买台小霸王学习机游戏机在我一哭二闹三上吊胡搅蛮缠的攻势下我妈妥协了。就此我接触到了FC游戏。现在还能记得我和朋友玩激龟快打满屋子的小朋友在看的场景。经常有家长在我家门口喊他家小孩吃饭。那时候我们县城里面有商店卖游戏卡小卡一张5块钱一张传奇卡25-40块不等所谓传奇卡就是角色扮演带有存档的游戏每天放学都要去商店去看看有没有新的游戏卡买不起就看下封面过过瘾。我记得我省吃俭用一个多月买了两张卡《哪吒传奇》和《重装机兵》那是真的上瘾没日没夜的玩。
再然后我接触到了手机游戏记得那时候有个软件叫做冒泡游戏我心目的中的Stream里面好多游戏太吸引我了。一个游戏一般都是几百KB最大也就是几MB不过那时候流量很贵1块钱1MB并且一个月只有30Mb。我姑父是收手机的我在他那里搞到了一部半智能手机牌子我现在还记得诺基亚N70那时候我打开游戏就会显示一杯冒着热气的咖啡我很喜欢这个图标因为看见它意味着我的游戏快加载完成了没想到十几年后我们会再次相遇哈哈哈哈。我当时玩了一款网游叫做《幻想三国》第一回接触网游简直惊呆了里面好多人都是其他玩的家这太有趣了。并且我能在我的手机上看到其他玩家能够看到他们的行为动作这太神奇了我也一直思考这到底是怎么实现的
最后是电脑游戏单机《侠盗飞车》、《植物大战僵尸》、《虐杀原型》网游《DNF》、《CF》、《LOL》、《梦幻西游》我都玩过。
不过那个疑问一直没有解决也一值留在我心中 —— 在网络游戏中是如何实时更新其他玩家行为的呢
二、解惑
在我进入大学后我选择了软件开发专业真巧再次遇到了那个冒着热气的咖啡图标这时我才知道它叫做——Java。我很认真的去学希望有一天能够做一款游戏
参加工作后我并没有如愿以偿我成为了一名Java开发程序员但是我在日常的开发的都是web应用接触到大多是HTTP请求它是种请求-响应协议模式。这个问题也还是想不明白难道每当其他玩家做一个动作都需要发送一次HTTP请求然后响应给其他玩家。这样未免效率也太低了吧如果一个服务器中有几千几万人那么服务器得承受多大压力呀一定不是这样的
直到我遇到了Websocket这是一种长连接而HTTP是一种短连接顿时这个问题我就想明白了。在此二者的区别我就不过多赘述了请看我的另一篇文章WebSocket详解以及应用
知道了这个知识后我终于能够大致明白了网络游戏的基本原理。原来网络游戏是由客户端和服务器端组成的客户端就是我们下载到电脑或者手机上的应用而服务器端就是把其他玩家连接起来的中转站还有一点需要说明的是网络游戏是分房间的这个房间就相当于一台服务器。首先在玩家登陆客户端并选择房间建立长连接后A玩家做出移动的动作随即会把这个动作指令上传给服务器然后服务器再将指令广播到房间中的其他玩家的客户端来操作A的角色这样就可以实现实时更新其他玩家行为。 三、简单实现
客户端和服务端在处理指令时方法必须是配套的。比如说有新的玩家连接到服务器那么服务器就应当向其它客户端广播创建一个新角色的指令客户端在接收到该指令后执行客户端创建角色的方法。 为了方便演示这里需要定义两个HTML来表示两个不同的客户端不同的玩家这两套客户端代码除了玩家的信息不一样其它完全一致
3.1 客户端实现步骤
我在这里客户端使用HTMLJQ实现
客户端——1代码
1创建画布
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleCanvas Game/titlestylecanvas {border: 1px solid black;}/stylescript srchttps://code.jquery.com/jquery-3.6.0.min.js/script
/head
body
canvas idgameCanvas width800 height800/canvas
/body
/html2设置1s60帧更新页面
const canvas document.getElementById(gameCanvas);
const ctx canvas.getContext(2d);
function clearCanvas() {ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function gameLoop() {clearCanvas();players.forEach(player {player.draw();});}setInterval(gameLoop, 1000 / 60);
//清除画布方法
function clearCanvas() {ctx.clearRect(0, 0, canvas.width, canvas.height);}3连接游戏服务器并处理指令
这里使用websocket链接游戏服务器 //连接服务器
const websocket new WebSocket(ws://192.168.31.136:7070/websocket?userId userId userName userName);//向服务器发送消息function sendMessage(userId,keyCode){const messageData {playerId: userId,keyCode: keyCode};websocket.send(JSON.stringify(messageData));}//接收服务器消息并根据不同的指令做出不同的动作websocket.onmessage event {const data JSON.parse(event.data);// 处理服务器发送过来的消息console.log(Received message:, data);//创建游戏对象if(data.type 1){console.log(玩家信息: data.players.length)for (let i 0; i data.players.length; i) {console.log(玩家idplayerOfIds);createPlayer(data.players[i].playerId,data.players[i].pointX, data.players[i].pointY, data.players[i].color);}}//销毁游戏对象if(data.type 2){console.log(玩家信息: data.players.length)for (let i 0; i data.players.length; i) {destroyPlayer(data.players[i].playerId)}}//移动游戏对象if(data.type 3){console.log(移动;玩家信息: data.players.length)for (let i 0; i data.players.length; i) {players.filter(player player.id data.players[i].playerId)[0].move(data.players[i].keyCode)}}};4创建玩家对象
//存放游戏对象
let players [];
//playerId在此写死正常情况下应该是用户登录获取的
const userId 1; // 用户的 id
const userName 逆风笑; // 用户的名称
//玩家对象
class Player {constructor(id,x, y, color) {this.id id;this.x x;this.y y;this.size 30;this.color color;}//绘制游戏角色方法draw() {ctx.fillStyle this.color;ctx.fillRect(this.x, this.y, this.size, this.size);}//游戏角色移动方法 move(keyCode) {switch (keyCode) {case 37: // Leftthis.x Math.max(0, this.x - 10);break;case 38: // Upthis.y Math.max(0, this.y - 10);break;case 39: // Rightthis.x Math.min(canvas.width - this.size, this.x 10);break;case 40: // Downthis.y Math.min(canvas.height - this.size, this.y 10);break;}this.draw();}}5客户端创建角色方法
//创建游戏对象方法
function createPlayer(id,x, y, color) {const player new Player(id,x, y, color);players.push(player);playerOfIds.push(id);return player;
}6客户端销毁角色方法
在玩家推出客户端后其它玩家的客户端应当销毁对应的角色。
//角色销毁
function destroyPlayer(playId){players players.filter(player player.id ! playId);
}客户端——2代码
客户端2的代码只有玩家信息不一致 const userId 2; // 用户的 idconst userName 逆风哭; // 用户的名称3.2 服务器端
服务器端使用Javawebsocket来实现
1引入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.1.2.RELEASE/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactIdversion2.3.7.RELEASE/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.11/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.75/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.16.16/version/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.6.3/version/dependency2创建服务器
Component
ServerEndpoint(/websocket)
Slf4j
public class Server {/*** 服务器玩家池* 解释这里使用 ConcurrentHashMap为了保证线程安全不会出现同一个玩家存在多条记录问题* 使用 static fina修饰 是为了保证 playerPool 全局唯一*/private static final ConcurrentHashMapString, Server playerPool new ConcurrentHashMap();/*** 存储玩家信息*/private static final ConcurrentHashMapString, Player playerInfo new ConcurrentHashMap();/*** 已经被创建了的玩家id*/private static ConcurrentHashMapString, Server createdPlayer new ConcurrentHashMap();private Session session;private Player player;/*** 连接成功后调用的方法*/OnOpenpublic void webSocketOpen(Session session) throws IOException {MapString, ListString requestParameterMap session.getRequestParameterMap();String userId requestParameterMap.get(userId).get(0);String userName requestParameterMap.get(userName).get(0);this.session session;if (!playerPool.containsKey(userId)) {int locationX getLocation(151);int locationY getLocation(151);String color PlayerColorEnum.getValueByCode(getLocation(1) 1);Player newPlayer new Player(userId, userName, locationX, locationY,color,null);playerPool.put(userId, this);this.player newPlayer;//存放玩家信息playerInfo.put(userId,newPlayer);}log.info(玩家:{}|{}连接了服务器, userId, userName);// 创建游戏对象this.createPlayer(userId);}/*** 接收到消息调用的方法*/OnMessagepublic void onMessage(String message, Session session) throws IOException, InterruptedException {log.info(用户:{},消息{}:,this.player.getPlayerId(),message);PlayerDTO playerDTO new PlayerDTO();Player player JSONObject.parseObject(message, Player.class);ListPlayer players new ArrayList();players.add(player);playerDTO.setPlayers(players);playerDTO.setType(OperationType.MOVE_OBJECT.getCode());String returnMessage JSONObject.toJSONString(playerDTO);//广播所有玩家for (String key : playerPool.keySet()) {synchronized (session){String playerId playerPool.get(key).player.getPlayerId();if(!playerId.equals(this.player.getPlayerId())){playerPool.get(key).session.getBasicRemote().sendText(returnMessage);}}}}/*** 关闭连接调用方法*/OnClosepublic void onClose() throws IOException {String playerId this.player.getPlayerId();log.info(玩家{}退出!, playerId);Player playerBaseInfo playerInfo.get(playerId);//移除玩家for (String key : playerPool.keySet()) {playerPool.remove(playerId);playerInfo.remove(playerId);createdPlayer.remove(playerId);}//通知客户端销毁对象destroyPlayer(playerBaseInfo);}/*** 出现错误时调用的方法*/OnErrorpublic void onError(Throwable error) {log.info(服务器错误,玩家id{},原因{},this.player.getPlayerId(),error.getMessage());}/*** 获取随即位置* param seed* return*/private int getLocation(Integer seed){Random random new Random();return random.nextInt(seed);}
}websocket配置
Configuration
public class ServerConfig {Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}
3创建玩家对象
玩家对象
Data
AllArgsConstructor
NoArgsConstructor
public class Player {/*** 玩家id*/private String playerId;/*** 玩家名称*/private String playerName;/*** 玩家生成的x坐标*/private Integer pointX;/*** 玩家生成的y坐标*/private Integer pointY;/*** 玩家生成颜色*/private String color;/*** 玩家动作指令*/private Integer keyCode;
}创建玩家对象返回给客户端DTO
Data
AllArgsConstructor
NoArgsConstructor
public class PlayerDTO {private Integer type;private ListPlayer players;
}玩家移动指令返回给客户端DTO
Data
AllArgsConstructor
NoArgsConstructor
public class PlayerMoveDTO {private Integer type;private ListPlayer players;
}
4动作指令
public enum OperationType {CREATE_OBJECT(1,创建游戏对象),DESTROY_OBJECT(2,销毁游戏对象),MOVE_OBJECT(3,移动游戏对象),;private Integer code;private String value;OperationType(Integer code, String value) {this.code code;this.value value;}public Integer getCode() {return code;}public String getValue() {return value;}
}5创建对象方法 /*** 创建对象方法* param playerId* throws IOException*/private void createPlayer(String playerId) throws IOException {if (!createdPlayer.containsKey(playerId)) {ListPlayer players new ArrayList();for (String key : playerInfo.keySet()) {Player playerBaseInfo playerInfo.get(key);players.add(playerBaseInfo);}PlayerDTO playerDTO new PlayerDTO();playerDTO.setType(OperationType.CREATE_OBJECT.getCode());playerDTO.setPlayers(players);String syncInfo JSONObject.toJSONString(playerDTO);for (String key :playerPool.keySet()) {playerPool.get(key).session.getBasicRemote().sendText(syncInfo);}// 存放createdPlayer.put(playerId, this);}}6销毁对象方法 /*** 销毁对象方法* param playerBaseInfo* throws IOException*/private void destroyPlayer(Player playerBaseInfo) throws IOException {PlayerDTO playerDTO new PlayerDTO();playerDTO.setType(OperationType.DESTROY_OBJECT.getCode());ListPlayer players new ArrayList();players.add(playerBaseInfo);playerDTO.setPlayers(players);String syncInfo JSONObject.toJSONString(playerDTO);for (String key :playerPool.keySet()) {playerPool.get(key).session.getBasicRemote().sendText(syncInfo);}}四、演示
4.1 客户端1登陆服务器 4.2 客户端2登陆服务器 4.3 客户端2移动 4.4 客户端1移动 4.5 客户端1退出 完结撒花
完整代码传送门
五、总结
以上就是我对网络游戏如何实现玩家实时同步的理解与实现我实现后心里也释然了哈哈哈真的好有趣 我希望大家也是不要失去好奇心遇到自己感兴趣的事情一定要多思考呀~
后来随着我经验的不断积累我又去了解了一下Java作为游戏服务器的相关内容发现Netty更适合做这个并且更容易入门比如《我的世界》一些现有的服务器就是使用Netty实现的。有空也实现下玩玩~