本人严重拖延症,拖了好久才开始弄这个三国杀。
首先,先弄好cocos的相关界面与请求。
1.搭建webSocket连接脚本。
2.搭建登入,和注册界面。
首先,我们先搭建一个简单的webSocket连接
const { ccclass, property } = cc._decorator;
const serverAddress = "ws://localhost:8082";
@ccclass
// WebSocketClient.ts
export default class WebSocketClient {
private static _instance: WebSocketClient;
private socket: WebSocket | null = null;
private constructor() {
// 初始化WebSocket连接
this.socket = new WebSocket(serverAddress);
// 设置WebSocket事件监听
this.socket.onopen = this.onWebSocketOpen.bind(this);
this.socket.onmessage = this.onWebSocketMessage.bind(this);
this.socket.onclose = this.onWebSocketClose.bind(this);
this.socket.onerror = this.onWebSocketError.bind(this);
}
public static get instance(): WebSocketClient {
if (!this._instance) {
this._instance = new WebSocketClient();
}
return this._instance;
}
private onWebSocketOpen(event: Event) {
console.log("WebSocket连接已建立");
}
private onWebSocketMessage(event: MessageEvent) {
console.log("收到消息" + event.data)
}
private onWebSocketClose(event: CloseEvent) {
console.log("WebSocket连接已关闭");
}
private onWebSocketError(event: Event) {
console.error("WebSocket连接发生错误");
}
public sendPlayerMove(data: any) {
// 确保WebSocket连接已建立后再发送消息
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
console.log('发送数据:' + JSON.stringify(data))
this.socket.send(JSON.stringify(data));
} else {
console.log("WebSocket连接尚未建立,无法发送消息。");
}
}
public close() {
if (this.socket) {
this.socket.close();
}
}
}
然后,我们再搭建一个登入注册的脚本,并且把它挂载再Cocos的界面上
import WebSocketClient from "../socket";
const {ccclass, property} = cc._decorator;
// 这个接口的目的是,便于后端接收数据 定义的数据类型,不知道能不能叫做协议
export interface user{
code:number
userName:string
passWord:string
nickName?:string
}
@ccclass
export class login extends cc.Component {
@property(cc.EditBox)
userName: cc.EditBox = null;
@property(cc.EditBox)
passWord: cc.EditBox = null;
// 登入按鈕
@property(cc.Button)
btnLogin: cc.Button = null;
// 注册按钮
@property(cc.Button)
btnReg: cc.Button = null;
@property(cc.Node)
regBg: cc.Node = null;
@property(cc.Button)
yes: cc.Button = null;
@property(cc.Button)
no: cc.Button = null;
@property(cc.EditBox)
userName2: cc.EditBox = null;
@property(cc.EditBox)
passWord2: cc.EditBox = null;
@property(cc.EditBox)
nick: cc.EditBox = null;
private socket : WebSocketClient;
protected onLoad(): void {
this.regBg.active = false;
this.btnLogin.node.on(cc.Node.EventType.MOUSE_DOWN,this.goLogin,this);
this.btnReg.node.on(cc.Node.EventType.MOUSE_DOWN,()=>{
console.log("按下注册按钮")
this.regBg.active = true;
});
this.yes.node.on(cc.Node.EventType.MOUSE_DOWN,()=>{
const userInfo:user = {
userName: this.userName2.textLabel.string,
passWord: this.passWord2.textLabel.string,
nickName: this.nick.textLabel.string,
code: 1002
}
this.socket.sendPlayerMove(userInfo)
});
this.no.node.on(cc.Node.EventType.MOUSE_DOWN,()=>{
this.regBg.active = false;
},this);
this.socket = WebSocketClient.instance
}
//登入
goLogin(){
if(this.userName.textLabel.string.length<=0 || this.passWord.textLabel.string.length<=0){
return;
}
const userInfo:user = {
userName: this.userName.textLabel.string,
passWord: this.passWord.textLabel.string,
code: 1001
}
this.socket.sendPlayerMove(userInfo)
}
}
好,现在我们就搭建好了一个简单的前端的登入界面,和注册界面,这里,使用了一个简单的code标识,便于服务端进行判断,该请求到底要转发到哪里的。如,10001:登入,10002:注册等等,一开始先这样测试一下,后面再专门同一管理。
下面开始搭建服务端的代码:
目录
4.定义一个消息分发处理器,处理不同的code,进行分发到不同的对象进行处理
6.定义一个通道管理类,用来管理全部登入的玩家,和删除已经退出的玩家。
1.创建一个简单的SpringBoot项目,
相关的依赖,这网络上很多,就不多讲了
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.异步启动netty
这是SpringBoot 的启动类,我们异步启动netty
@Async
@EnableAsync
@SpringBootApplication
public class WebsocketTwoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(WebsocketTwoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
new MyNettyWebSocketApplication().bind();
}
}
这是netty启动类,网络上有很多,我这个是最简单的,我这里是直接用已有,没有进行对数据的编码和解码,这肯定是不行,但是对于登入和注册界面的问题不大,就先暂时先这样了,这里固定了监听窗口,8082,
public class MyNettyWebSocketApplication {
public void bind() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec()); // HTTP 协议解析,用于握手阶段
pipeline.addLast(new HttpObjectAggregator(65536)); // HTTP 协议解析,用于握手阶段
pipeline.addLast(new WebSocketServerCompressionHandler()); // WebSocket 数据压缩扩展
pipeline.addLast(new WebSocketServerProtocolHandler("/",null , true)); // WebSocket 握手、控制帧处理
pipeline.addLast(new JsonDecoder());
pipeline.addLast(new MyWebSocketServerHandler());
}
});
ChannelFuture f = b.bind(8082).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
3.定义一个接收消息管理器,处理消息
这里就是netty的一个消息处理器,channelReal0 就是当接收到数据的时候调用的方法,
下面的那个channelReal 就是为了不让它自动释放资源而进行重写的方法,本来我这里想的是继承
ChannelInboundHandlerAdapter 这个类就不会自动释放资源,现在继承的类就是这个类的子类,但是不明为什么,当我继承ChannelInboundHandlerAdapter 这个类的时候,它不能后调用我写的解码方法,也就是ByteToMessageDecoder 这个类 重写的方法,所以没办法就只能先将就一下,使用这个simple类,后面找到原因再修改回来
public class MyWebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
// 数据处理
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
if (frame instanceof TextWebSocketFrame) {
String request = ((TextWebSocketFrame) frame).text();
manager.getInstance().RspClienMsg(ctx,request);
}
}
//不用释放资源 除非用户退出
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.channelRead0(ctx, (WebSocketFrame)msg);
}
// @Override
// public void channelActive(ChannelHandlerContext ctx) throws Exception {
// super.channelActive(ctx);
// ctx.read();
// }
//
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
// if (msg instanceof JsonDecoder){
// TextWebSocketFrame text = (TextWebSocketFrame)msg;
// String request = text.text();
// manager.getInstance().sendClienMsg(ctx,msg.toString());
// }
// }
}
4.定义一个消息分发处理器,处理不同的code,进行分发到不同的对象进行处理
这个manger 类就是用来处理,我们消息的分发,这里我们定义了一个协议
public enum NetCode { Non(0), // 错误 userLogin(1001), // 用户登入 USERREGISTERED(1002); int code; NetCode(int code){ this.code = code; } public int getCode(){ return code; } public static NetCode getType(int code){ NetCode[] values = NetCode.values(); for (int i = 0; i < values.length; i++) { if(code == values[i].code){ return values[i]; } } return Non; } }
我们根据请求里面的code来进行请求的分发。
public class manager {
private static manager instance;
@Autowired
NetConnManager netConnManager;
static UserService userService;
public manager() {
userService = SpringBeanUtil.getBean(UserService.class);
}
public static manager getInstance() {
if (instance == null){
return instance = new manager();
}
return instance;
}
// 分发消息
public void RspClienMsg(ChannelHandlerContext ctx, String msg){
NetMessage message = JSON.parseObject(msg, NetMessage.class);
NetConn conn = getNetConn(ctx);
switch (NetCode.getType(message.code)){
case Non:
System.out.println("无效请求头");
ctx.channel().writeAndFlush(new TextWebSocketFrame( "无效请求头"));
break;
case userLogin:
// ctx.channel().writeAndFlush(new TextWebSocketFrame( "用户登入"));
userService.UserLogin(conn,message);
break;
case USERREGISTERED:
// ctx.channel().writeAndFlush(new TextWebSocketFrame( "用户登入"));
userService.registered(conn,message);
break;
}
}
NetConn getNetConn(ChannelHandlerContext ctx){
NetConn conn = NetConnManager.getNetConn(ctx);
if (conn == null){
conn = new NetConn(ctx);
}
return conn;
}
}
5.定义一个通道类,用来保存通道绑定该玩家相关的信息。
这里我们定义一个类来进行通道和用户之间的绑定,只有我们再登入的时候才会进行这个用户的绑定,这样我们就能找到用户进行对应的业务操作
public class NetConn {
public Player player;
ChannelHandlerContext ctx;
public NetConn(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void send(String msg) {
if (ctx != null) {
ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
}
}
}
6.定义一个通道管理类,用来管理全部登入的玩家,和删除已经退出的玩家。
刚刚我们定义了一个绑定的类,现在我们定义一个管理这个绑定的管理类,来进行查找对应的通道,判断用户的在线,以及将所用登入的用户都添加近我们的players里面,这样就有利于,我们后面进行的匹配,
public class NetConnManager {
static Map<ChannelHandlerContext, NetConn> ctxs = new HashMap<>();
static Map<Integer, NetConn> players = new HashMap<>();
public static NetConn getNetConn(ChannelHandlerContext ctx) {
return ctxs.get(ctx);
}
public static NetConn getNetConn(int id) {
return players.get(id);
}
public static void addNetConn(NetConn conn) {
ctxs.put(conn.ctx, conn);
players.put(conn.player.id, conn);
}
public static void remove(Player player){
NetConn netConn = players.remove(player.id);
if(netConn != null){
ctxs.remove(netConn.ctx);
}
}
}
7.定义一些对于的枚举类,和一些工具类
这是一个spring的工具类用来获取对应的bean
@Component
public class SpringBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringBeanUtil.applicationContext == null) {
SpringBeanUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
让后就是写 service 和dao 啦,这个就很简单的逻辑啦,这里,就不放代码了,有需要的朋友,我再发出来,简单说一下逻辑,登入,就是查询,用户,判断密码。注册,就是直接insert,就好啦,
数据库,就是很简单一个player表,id,username,password,nickname;
好啦就到这里啦,后面再慢慢完善,写这篇博客就是怕直接这个拖延,弄得一个项目要搞很久,来提醒一下自己,当然上面其实还有很多不懂的地方,希望大家多多指教