在基础三层架构的基础上再进行拆分, 将不同的功能进行抽离独立, 提高性能
无缝地图架构在进阶三层架构中, 地图的切换总是需要loading (DNF), 为了解决这个问题, 在无缝地图架构中, 由一组节点 (Node) 服务器来管理地图区域, 这个组就是 NodeMaster, 它来进行整体管理, 如果还有更大的就再又更大的 WorldMaster 来进行管理
玩家在地图上进行移动其实就是在 Node 服务器间进行移动, 比如从 A ----> B, 需要由 NodeMaster 把数据从 NodeA 复制到 NodeB 后, 再移除 NodeA 的数据
三、通信联机最大特点便是多玩家之间的交互, 保证每个玩家的数据和显示一致是必不可少的步骤, 在介绍同步方案之前, 我们先来了解一下如何实现两端的通信
长连接通信 (Socket.io)极度简陋的聊天室 Demo (React node)[1]
实现步骤:
前后端建立连接
前端发送消息至服务端
服务端收到消息后对当前所有用户进行广播
前端收到广播, 更新状态
// client
import React, { memo, useEffect, useState, useRef } from "react";
import { io } from "socket.io-client";
import { nanoid } from "nanoid";
import "./index.css";
const host = "192.168.0.108",
port = 3101;
const ChatRoom = () => {
const [socket, setSocket] = useState(io());
const [message, setMessage] = useState("");
const [content, setContent] = useState<
{
id: string;
message: string;
type?: string;
}[]
>([]);
const [userList, setUserList] = useState<string[]>([]);
const userInfo = useRef({ id: "", enterRoomTS: 0 });
const roomState = useRef({
content: [] as {
id: string;
message: string;
type?: string;
}[],
});
useEffect(() => {
// 初始化 Socket
initSocket();
// 初始化用户信息
userInfo.current = {
id: nanoid(),
enterRoomTS: Date.now(),
};
}, []);
useEffect(() => {
roomState.current.content = content;
}, [content]);
const initSocket = () => {
const socket = io(`ws://${host}:${port}`);
setSocket(socket);
// 建立连接
socket.on("connect", () => {
console.log("连接成功");
//用户加入
socket.emit("add user", userInfo.current);
});
//用户加入聊天室
socket.on("user joined", ({ id, userList }) => {
const newContent = [...roomState.current.content];
newContent.push({ id, message: `${id}加入`, type: "tip" });
setContent(newContent);
setUserList(userList);
});
//新消息
socket.on("new message", ({ id, message }) => {
const newContent = [...roomState.current.content];
newContent.push({ id, message });
setContent(newContent);
});
//用户离开聊天室
socket.on("user leave", function ({ id, userList }) {
const newContent = [...roomState.current.content];
newContent.push({ id, message: `${id}离开`, type: "tip" });
setContent(newContent);
setUserList(userList);
});
};
const handleEnterSend: React.KeyboardEventHandler<HTMLTextAreaElement> = (
e
) => {
if (e.key === "Enter") {
//客户端发送新消息
socket.emit("new message", {
id: userInfo.current.id,
message,
});
setMessage("");
e.preventDefault();
}
};
const handleButtonSend = () => {
//客户端发送新消息
socket.emit("new message", {
id: userInfo.current.id,
message,
});
setMessage("");
};
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> = (e) => {
const val = e.target.value ?? "";
setMessage(val);
};
const handleQuit = () => {
//断开连接
socket.disconnect();
};
return (
<div>
//...
</div>
);
};
export default memo(ChatRoom);
// server
import { Server } from "socket.io";
const host = "192.168.0.108",
port = 3101;
const io = new Server(port, { cors: true });
const sessionList = [];
io.on("connection", (socket) => {
console.log("socket connected successful");
//用户进入聊天室
socket.on("add user", ({ id }) => {
socket.id = id;
if (!sessionList.includes(id)) {
sessionList.push(id);
}
console.log(`${id} 已加入房间, 房间人数: ${sessionList.length}`);
console.log(JSON.stringify(sessionList));
io.emit("user joined", { id, userList: sessionList });
});
//发送的新消息
socket.on("new message", ({ id, message }) => {
io.emit("new message", { id, message });
});
socket.on("disconnect", () => {
sessionList.splice(sessionList.indexOf(socket.id), 1);
socket.broadcast.emit("user leave", {
id: socket.id,
userList: sessionList,
});
});
});
四、同步策略
现在大多游戏常用的两种同步技术方向分别是: 帧同步和状态同步
帧同步帧同步的方式服务端很简单, 只承担了操作转发的操作, 你给我了什么, 我就通知其他人你怎么了, 具体的执行是各个客户端拿到操作后自己执行
image.png
状态同步状态同步是客户端将操作告诉服务端, 然后服务端拿着操作进行计算, 最后把结果返给各个客户端, 然后客户端根据新数据进行渲染即可