赞
踩
这是一款多人在线游戏,其主要功能有:
1)玩家上线;
2)玩家移动;
3)世界聊天;
4)玩家下线;
AOI(Area Of Interest),即兴趣点区域。通过AOI算法,当一个玩家上线后,他只能被附近的玩家发现。
假设将一个地图分割成多份,每一份相当于上图中的一个单元格。当玩家上线后,该玩家会落入到上面的其中一个格子中。只有该格子的周围格子里面的玩家才可以看到该玩家。
举例1:当玩家在0号格子时候,他只能被0-1-5-6号格子内的玩家发现(如下图)。
举例2:当玩家在2号格子时候,他只能被1-2-3-6-7-8号格子内的玩家发现(如下图)。
举例3:当玩家在12号格子时候,他只能被6-7-8-11-12-13-16-17-18号格子内的玩家发现(如下图)。
假设地图是一个二维的空间,那么每个格子的坐标计算公式如下:
MsgId | Client | Server | 描述 |
---|---|---|---|
1 | - | SyncOnlinePid | 同步上线玩家ID |
2 | Talk | - | 聊天消息 |
3 | Move | - | 玩家移动消息 |
200 | - | Broadcast | 广播消息,Tp=1代表聊天,Tp=2代表向所有玩家(包括自己)广播坐标,Tp=4代表玩家移动 |
201 | - | SyncOfflinePid | 同步下线玩家ID |
202 | - | SyncPlayers | 同步周围人位置信息(包括自己) |
如果上面Server列有值,代表消息由Server端发起。同样地,如果Client列有值,代表消息由客户端发起。
第一步:新建一个Maven项目,并引入Netty和Protobuf依赖;
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
第二步:创建包结构;
org.netty.mmogame.codec: 存放编解码器;
org.netty.mmogame.mgr:存放游戏管理相关的类;
org.netty.mmogame.handler: 存放处理器;
org.netty.mmogame.pb: 存放protobuf协议文件;
org.netty.mmogame.client:存放游戏客户端的执行性文件;
第三步:创建启动类;
package org.netty.mmogame; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.concurrent.ImmediateEventExecutor; import org.netty.mmogame.codc.LittleEndianEncoder; import org.netty.mmogame.handler.PlayerOnlineHandler; /** * 启动类 */ public class MMOGameServer { public static void main(String[] args) throws InterruptedException { //1. 创建两个线程组,一个用于进行网络连接,另一个用于处理IO读写 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { //2. 创建一个ChannelGroup对象,用于存放所有Channel,一个Channel相当于一个客户端连接 ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); //3. 创建启动类 ServerBootstrap b = new ServerBootstrap(); //4. 配置启动信息 b.group(bossGroup, workGroup) // 配置NioServerSocketChannel .channel(NioServerSocketChannel.class) // 设置链接超时时间 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000) // 设置队列大小 .option(ChannelOption.SO_BACKLOG, 1024) // 通信不延迟 .childOption(ChannelOption.TCP_NODELAY, true) // 接收、发送缓存区大小 .childOption(ChannelOption.SO_RCVBUF, 1024 * 32) .childOption(ChannelOption.SO_SNDBUF, 1024 * 32) // 添加处理器到Pipeline中 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // TODO 往管道中添加处理器 } }); //5. 绑定端口并启动服务 ChannelFuture cf = b.bind(8999).sync(); System.out.println("MMO Server started, Listening port 8999."); //6. 同步阻塞关闭监听 cf.channel().closeFuture().sync(); } finally { //7.释放资源 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
syntax="proto3"; // Proto协议
option java_package = "org.netty.mmogame.pb"; // 包名
option java_outer_classname = "Msg"; // 类名
option csharp_namespace="Pb"; // 因为客户端unity3d是使用C#开发,所以需要给C#提供该选项
message SyncOnlinePid {
int32 Pid = 1;
}
message SyncOfflinePid {
int32 Pid = 1;
}
// 广播消息 message BroadCast { int32 Pid = 1; int32 Tp = 2; // Tp为1代表聊天,2代表玩家位置,4代表移动后的坐标信息更新 oneof Data { string Content = 3; Position P = 4; int32 ActionData = 5; } } // 玩家坐标 message Position { float X = 1; float Y = 2; float Z = 3; float V = 4; } // 聊天 message Talk { string content = 1; }
// 同步玩家
message SyncPlayers {
repeated Player ps = 1;
}
// 玩家
message Player {
int32 Pid = 1;
Position P = 2;
}
进入pb目录下执行如下命令即可。
cd ${PROJECT_PATH}/src/main/java/org/netty/mmogame/pb
protoc --java_out=../../../../ msg.proto
如果编译成功,会在pb目录下生成Msg.java文件。
该工具类提供了一些按照LittleEndian格式读写ByteBuf缓冲区内容的静态方法。
package org.netty.mmogame.codc; import io.netty.buffer.ByteBuf; public class LittleEndian { public static void put(ByteBuf buf, int v) { buf.writeByte(v); buf.writeByte(v >> 8); buf.writeByte(v >> 16); buf.writeByte(v >> 24); } public static int read(byte[] b) { return (int)b[0] | (int)b[1]<<8 | (int)b[2]<<16 | (int)b[3]<<24; } }
编码器实现服务器向客户端发送消息时候,将Message对象转换成字节数组。
编码规则:
1)前八个字节分别存放LittleEndian
格式的消息长度和消息ID;
2)后面位置存放消息的内容;
package org.netty.mmogame.codc; import com.google.protobuf.Message; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import org.netty.mmogame.pb.Msg; /** * 按照LittleEndian规则进行编码 */ public class LittleEndianEncoder extends MessageToByteEncoder<Message> { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf buf) throws Exception { byte[] data = msg.toByteArray(); LittleEndian.put(buf, data.length); if (msg instanceof Msg.SyncOnlinePid) { LittleEndian.put(buf, 1); } else if (msg instanceof Msg.BroadCast) { LittleEndian.put(buf, 200); } else if (msg instanceof Msg.SyncPlayers) { LittleEndian.put(buf, 202); } else if (msg instanceof Msg.SyncOfflinePid) { LittleEndian.put(buf, 201); } buf.writeBytes(data); } }
上面数字1代表同步上线玩家ID消息,200代表同步位置坐标消息,202代表将周围玩家坐标同步给当前玩家;201代表同步下线玩家ID。
解码器实现将客户端消息转换成Message对象。
解码规则:
1)从ByteBuf中读取前八个字节数据,然后按照LittleEndian格式进行处理后,得到消息长度和消息ID;
2)按照消息长度从ByteBuf中读取指定长度的消息内容;
3)最后将消息长度、消息ID、消息内容分别封装到ClientMessage对象中;
package org.netty.mmogame.codc; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import org.netty.mmogame.mgr.ClientMessage; import java.util.List; /** * 按照LittleEndian规则进行解码 */ public class LittleEndianDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { if (byteBuf.isReadable() && byteBuf.readableBytes() >= 8) { // 消息长度 byte[] headerBuf0 = new byte[4]; byteBuf.readBytes(headerBuf0); // 消息ID byte[] headerBuf1 = new byte[4]; byteBuf.readBytes(headerBuf1); // LittleEndian解析 int dataLen = (int) LittleEndian.read(headerBuf0); int msgId = (int) LittleEndian.read(headerBuf1); ClientMessage message = new ClientMessage(msgId, dataLen); if (dataLen > 0) { // 消息内容 byte[] dataBuf = new byte[dataLen]; byteBuf.readBytes(dataBuf); message.setData(dataBuf); } list.add(message); } } }
该类用于封装客户端发送过来的信息。
package org.netty.mmogame.mgr; import io.netty.util.CharsetUtil; /* 客户端发送的消息 */ public class ClientMessage { private int id; private int dataLen; private byte[] data; public ClientMessage() {} public ClientMessage(int id, int dataLen) { this.id = id; this.dataLen = dataLen; } // 这里省略了setter和getter方法。。。 @Override public String toString() { return "[dataLen = " + dataLen + ", msgId = " + id + ", data = " + new String(data, CharsetUtil.UTF_8) + "]"; } }
完成编解码器定义后,需要将添加到Pipeline中。
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 往管道中添加处理器
ch.pipeline().addLast(new LittleEndianEncoder());
ch.pipeline().addLast(new LittleEndianDecoder());
}
}
该模块实现了玩家上线的功能,其主要功能有:
1)同步玩家ID;
2)向所有玩家(包括自己)广播坐标;
3)向当前玩家同步其他玩家的坐标;
实现步骤:
第一步:新建一个玩家上线的处理类,并重写channelActive方法;
package org.netty.mmogame.handler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import org.netty.mmogame.mgr.AOIManager; import org.netty.mmogame.mgr.ClientMessage; import org.netty.mmogame.mgr.PlayerManager; import org.netty.mmogame.pb.Msg; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; /** * 玩家上线处理器 */ public class PlayerOnlineHandler extends SimpleChannelInboundHandler<ClientMessage> { private static int playerId = 1; // 全局的玩家ID,每次有客户端连接时候自动加1 private ChannelGroup channelGroup; public PlayerOnlineHandler(ChannelGroup channelGroup) { this.channelGroup = channelGroup; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Player connected, ip: " + ctx.channel().remoteAddress().toString().substring(1)); // 将当前channel添加到channelGroup中 channelGroup.add(ctx.channel()); // 构建同步玩家ID的消息 Msg.SyncOnlinePid syncPid = Msg.SyncOnlinePid.newBuilder().setPid(playerId).build(); // 发送消息给玩家 ctx.writeAndFlush(syncPid); // 生成随机坐标 Random random = new Random(); int posX = 160 + random.nextInt(10); int posY = 0; int posZ = 140 + random.nextInt(20); int posV = 0; // 向所有玩家(包括自己)广播坐标 Msg.BroadCast broadCastPosToPlayer = Msg.BroadCast.newBuilder() .setPid(playerId) .setTp(2) .setP(Msg.Position.newBuilder() .setX(posX) .setY(posY) .setZ(posZ) .setV(posV) .build()) .build(); channelGroup.writeAndFlush(broadCastPosToPlayer); // 向当前玩家同步其他玩家的坐标 Collection<Msg.Player> players = PlayerManager.getPlayers(); Msg.SyncPlayers syncPlayers = Msg.SyncPlayers.newBuilder().addAllPs(players).build(); channelGroup.writeAndFlush(syncPlayers); // 保存当前玩家坐标 Msg.Player player = Msg.Player.newBuilder() .setPid(playerId) .setP(Msg.Position.newBuilder() .setX(posX) .setY(posY) .setZ(posZ) .setV(posV) .build()) .build(); // 将Player保存起来 PlayerManager.addPlayer(ctx.channel(), player); // 将玩家ID添加到格子中 int gId = AOIManager.getGidByPos(posX, posZ); AOIManager.addPidToGrid(playerId, gId); System.out.println("SyncPid And BroadcastStartPos is finished, pid = " + playerId); // 玩家ID自增 playerId++; } @Override public void channelRead0(ChannelHandlerContext ctx, ClientMessage msg) throws Exception { ctx.fireChannelRead(msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); } }
定义完成后,将PlayerOnlineHandler添加到Pipeline中。
ch.pipeline().addLast(new PlayerOnlineHandler(channelGroup));
第二步:构建一个玩家管理工具类,用于管理所有的在线玩家;
package org.netty.mmogame.mgr; import io.netty.channel.ChannelId; import org.netty.mmogame.pb.Msg; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 玩家管理 */ public class PlayerManager { // 存放channelId和PlayerId的对应关系 private static Map<ChannelId, Msg.Player> players = new ConcurrentHashMap<>(); // 存放playerId和channel的对应关系 private static Map<Integer, Channel> channels = new ConcurrentHashMap<>(); // 添加玩家和channel public static void addPlayer(Channel channel, Msg.Player player) { if (channel != null && player != null) { players.put(channel.id(), player); channels.put(player.getPid(), channel); } } // 获取Channel对应的玩家 public static Msg.Player getPlayer(ChannelId channelId) { return map.get(channelId); } // 获取所有玩家 public static Collection<Msg.Player> getPlayers() { return map.values(); } }
第三步:构建一个格子类;
package org.netty.mmogame.mgr; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 格子 */ public class Grid { private int gid; // 格子ID private int minX; // 格子左边在x轴的坐标 private int maxX; // 格子右边在x轴的坐标 private int minY; // 格子左边在y轴的坐标 private int maxY; // 格子右边在y轴的坐标 private List<Integer> playerIds = new ArrayList<>(); // 格子内的玩家ID public Grid(int gid, int minX, int maxX, int minY, int maxY) { this.gid = gid; this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; } // 这里省略了setter和getter方法。。。 }
第四步:构建一个AOI管理类,用于管理地图上所有格子;
package org.netty.mmogame.mgr; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * AOI管理器 */ public class AOIManager { // AOI地图边界值 private static final int AOI_MIN_X = 85; // 左边界值 private static final int AOI_MAX_X = 410; // 右边界值 private static final int AOI_MIN_Y = 75; // 上边界值 private static final int AOI_MAX_Y = 400; // 下边界值 private static final int AOI_CNTS_X = 10; // X轴方向的格子数 private static final int AOI_CNTS_Y = 20; // Y轴方向的格子数 // 所有格子,key代表格子ID,value代表格子对象 private static Map<Integer, Grid> grids = new ConcurrentHashMap<>(); // 初始化AOI管理器 static { for (int y = 0; y < AOI_CNTS_Y; y++) { for (int x = 0; x < AOI_CNTS_X; x++) { int gid = y * AOI_CNTS_X + x; grids.put(gid, new Grid(gid, AOI_MIN_X + x * getGridWidth(), AOI_MIN_X + (x + 1) * getGridWidth(), AOI_MIN_Y + y * getGridLength(), AOI_MAX_Y + (y + 1) * getGridLength())); } } } // 获取格子x轴方向的宽度 private static int getGridWidth() { return (AOI_MAX_X - AOI_MIN_X) / AOI_CNTS_X; } // 获取格子y轴方向的长度 private static int getGridLength() { return (AOI_MAX_Y - AOI_MIN_Y) / AOI_CNTS_Y; } // 根据坐标获取所在格子的ID public static int getGidByPos(float x, float y) { // 根据坐标得到对应格子在x轴上的编号 int idx = ((int)x - AOI_MIN_X) / getGridWidth(); // 根据坐标得到对应格子在y轴上的编号 int idy = ((int)y - AOI_MIN_Y) / getGridLength(); // 计算出格子ID return idy * AOI_CNTS_X + idx; } // 添加一个PlayerID到一个格子中 public static void addPidToGrid(int pId, int gId) throws Exception { Grid grid = grids.get(gId); if (grid == null) { System.out.println("Grid not found, gId = " + gId); return; } grid.getPlayerIds().add(pId); } }
该模块实现了游戏消息的群发功能。
实现思路:
1)获取玩家发送过来的聊天消息;
2)构建广播聊天的消息;
3)通过ChannelGroup对象将广播消息发送给所有玩家;
实现步骤:
第一步:创建一个专门处理玩家聊天的Handler;
package org.netty.mmogame.handler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import org.netty.mmogame.mgr.ClientMessage; import org.netty.mmogame.mgr.PlayerManager; import org.netty.mmogame.pb.Msg; /** * 世界聊天处理器 */ public class WorldChatHandler extends SimpleChannelInboundHandler<ClientMessage> { private ChannelGroup channelGroup; public WorldChatHandler(ChannelGroup channelGroup) { this.channelGroup = channelGroup; } @Override public void channelRead0(ChannelHandlerContext ctx, ClientMessage msg) throws Exception { // msgId为2代表世界聊天 if (msg.getId() == 2) { Msg.Player player = PlayerManager.getPlayer(ctx.channel().id()); Msg.BroadCast broadcastMsg = Msg.BroadCast.newBuilder() .setPid(player.getPid()) .setTp(1) .setContent(new String(msg.getData(), CharsetUtil.UTF_8)) .build(); channelGroup.writeAndFlush(broadcastMsg); ReferenceCountUtil.release(msg); return; } ctx.fireChannelRead(msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); } }
第二步:将WorldChatHandler添加到管道中;
ch.pipeline().addLast(new WorldChatHandler(channelGroup));
该模块实现了玩家移动时候实时广播位置功能。
实现思路:
1)获取客户端发送过来的玩家位置信息,并解析成Msg.Position对象;
2)构建广播位置的消息;
3)获取九宫格内的玩家所对应的Channels;
4)遍历所有Channels,然后通过Channel将广播消息发送给附近玩家;
实现思路:
第一步:创建一个专门处理玩家移动的Handler;
package org.netty.mmogame.handler; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.util.ReferenceCountUtil; import org.netty.mmogame.mgr.ClientMessage; import org.netty.mmogame.mgr.PlayerManager; import org.netty.mmogame.pb.Msg; import javax.swing.*; import java.util.List; /** * 玩家移动处理器 */ public class MoveHandler extends SimpleChannelInboundHandler<ClientMessage> { private ChannelGroup channelGroup; public MoveHandler(ChannelGroup channelGroup) { this.channelGroup = channelGroup; } @Override public void channelRead0(ChannelHandlerContext ctx, ClientMessage msg) throws Exception { // msgId为3代表玩家移动 if (msg.getId() == 3) { // 将客户端发送的位置解析成MsgPosition对象 Msg.Position position = Msg.Position.parseFrom(msg.getData()); // 获取当前玩家ID Msg.Player player = PlayerManager.getPlayer(ctx.channel().id()); if (player != null) { System.out.println("Player position: [pid = " + player.getPid() + ", x = " + position.getX() + ", y = " + position.getY() + ", z = " + position.getZ() + ", v = " + position.getV() + "]"); // 向所有玩家(包括自己)广播坐标 Msg.BroadCast broadCastPosToPlayer = Msg.BroadCast.newBuilder() .setPid(player.getPid()) .setTp(4) // 4代表玩家移动 .setP(Msg.Position.newBuilder() .setX(position.getX()) .setY(position.getY()) .setZ(position.getZ()) .setV(position.getV()) .build()) .build(); // 获取当前玩家的周围玩家 List<Channel> channels = PlayerManager.getSurroundingPlayer(ctx.channel().id()); // 向九宫格内的玩家发送位置消息 for (Channel channel : channels) { channel.writeAndFlush(broadCastPosToPlayer); } } } ReferenceCountUtil.release(msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("Server exceptionCaught: " + cause); } }
第二步:在AOI管理类中定义两个方法;
// 根据格子ID获取九宫格内的格子集合 public static Collection<Integer> getSurroundingGridsByGid(int gId) { // 保存gId格子所在x轴方向上的格子ID List<Integer> gridsX = new ArrayList<>(); // 以上面x轴为基线,保存y轴方向上的格子ID List<Integer> gridsY = new ArrayList<>(); Grid grid = grids.get(gId); if (grid == null) { System.out.println("Grid in Aoi not found,gId = " + gId);; return null; } gridsX.add(gId); // 通过gid得到左边格子的x轴编号 int idx = gId % AOI_CNTS_X; if (idx > 0) { Grid leftGrid = grids.get(gId - 1); gridsX.add(leftGrid.getGid()); } // 通过gid得到右边格子的x轴编号 if (idx < AOI_CNTS_X - 1) { Grid rightGrid = grids.get(gId + 1); gridsX.add(rightGrid.getGid()); } for (int v : gridsX) { // 得到当前格子在y轴上的编号 int idY = v / AOI_CNTS_X; if (idY > 0) { Grid topGrid = grids.get(v - AOI_CNTS_X); gridsY.add(topGrid.getGid()); } if (idY < AOI_CNTS_Y - 1) { Grid bottomGrid = grids.get(v + AOI_CNTS_X); gridsY.add(bottomGrid.getGid()); } } gridsX.addAll(gridsY); return gridsX; } // 通过gID获取格子内所有PlayerID public static List<Integer> getPidsByGid(int gId) { Grid grid = grids.get(gId); if (grid == null) { System.out.println("Grid not found, gId = " + gId); return null; } return grid.getPlayerIds(); }
第三步:在玩家管理类中,定义获取周围玩家的方法;
// 获取周围玩家 public static List<Channel> getSurroundingPlayer(ChannelId channelId) { // 保存周围玩家,一个channel对应一个玩家 List<Channel> playerChannels = new ArrayList<>(); // 获取当前玩家 Msg.Player player = getPlayer(channelId); // 根据当前玩家坐标获取所在格子ID int gId = AOIManager.getGidByPos(player.getP().getX(), player.getP().getZ()); // 根据格子ID获取九宫格内的格子ID Collection<Integer> gridIds = AOIManager.getSurroundingGridsByGid(gId); if (gridIds != null && gridIds.size() > 0) { // 遍历九宫格内所有的格子ID,然后根据ID获取格子内所有玩家对应的channel for (int gridId : gridIds) { Collection<Integer> playerIds = AOIManager.getPidsByGid(gridId); if (playerIds != null && playerIds.size() > 0) { for (int playerId : playerIds) { Channel channel = channels.get(playerId); if (channel != null) { playerChannels.add(channel); } } } } } return playerChannels; }
第四步:将MoveHandler添加到Pipeline中;
ch.pipeline().addLast(new MoveHandler(channelGroup));
该模块实现了玩家下线的功能,其主要功能有:
1)通知客户端玩家下线;
2)删除玩家信息;
实现步骤:
第一步:在玩家管理类中添加删除玩家方法;
// 删除玩家
public static void RemovePlayer(ChannelId channelId, int playerId) {
players.remove(channelId);
channels.remove(playerId);
}
第二步:在AOI管理类中添加删除格子玩家的方法;
// 从格子中删除一个玩家ID
public static void RemovePidToGrid(int pId, int gId) throws Exception {
Grid grid = grids.get(gId);
if (grid == null) {
System.out.println("Grid not found, gId = " + gId);
return;
}
// 这里需要将Pid转换成Object类型,否则程序会认为pId是一个索引
grid.getPlayerIds().remove(new Integer(pId));
}
第三步:重写PlayerOnlineHandler的channelInactive方法,实现玩家下线的业务功能;
@Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { Msg.Player player = PlayerManager.getPlayer(ctx.channel().id()); if (player != null) { // 同步下线玩家ID Msg.SyncOfflinePid syncPid = Msg.SyncOfflinePid.newBuilder().setPid(player.getPid()).build(); channelGroup.writeAndFlush(syncPid); // 删除下线玩家 PlayerManager.RemovePlayer(ctx.channel().id(), player.getPid()); // 从格子中移除玩家 int posX = (int)player.getP().getX(); int posZ = (int)player.getP().getZ(); int gId = AOIManager.getGidByPos(posX, posZ); AOIManager.RemovePidToGrid(player.getPid(), gId); System.out.println("Player_" + player.getPid() + " disconnected."); } }
以上就是通过Netty框架实现的关于玩家上线、群聊、玩家移动、玩家下线的所有代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。