基于WebSocket通信的H5小游戏开发总结
创作时间:
作者:
@小白创作中心
基于WebSocket通信的H5小游戏开发总结
引用
CSDN
1.
https://blog.csdn.net/wangye135/article/details/136646040
本文将介绍一个基于WebSocket通信的H5小游戏项目,这是一个数字华容道游戏,支持单人和双人对战模式。文章将详细介绍项目的功能逻辑、代码实现以及开发经验总结。
1.项目介绍
数字华容道是一款经典的益智游戏,旨在挑战玩家的逻辑思维和空间想象能力。玩家需要通过移动数字方块的位置,按照特定的顺序将数字排列成正确的顺序,从而完成整个拼图。本项目不仅提供了离线单机版还提供双人PK功能,让您和好友在紧张刺激的氛围下一起头脑风暴。
2.项目逻辑
- 用户点击进入房间后,前端获取访问者的设备号,随机昵称和头像,并存储到本地,用做后续的用户识别。
- 房间内只能存在两个玩家,第一个进行房间的人默认成为房主,向后端发送请求,建立WebSocket通信,后端通过map将用户的唯一Id和WebSocket相绑定。房主点击邀请好友后获得邀请码,分享给好友。
- 好友通过房主分享的邀请码进行房间,生成昵称和头像并向后端发送请求,建立WebSocket通信,同样地后端通过map将好友Id和WebSocket连接进行绑定。数据结构见Client.go
- 此时后端通过roomId将房主和好友进行绑定,方便后期查找,进行各种数据的处理。数据结构见Hub.go
- 好友点击准备,房主点击开始游戏后,双方进入华容道游戏。
- 在游戏过程中,实时交换双方的游戏数据:步数和完成进度。
- 如果一方完成游戏,另一方被告知对方已完成,游戏结束。
- 特殊情况处理:
- 如果在游戏的过程中,一方主动退出游戏,则另一方直接胜利;
- 如果在游戏过程中,玩家出现弱网、断网的情况,则会进行3次短线重连的提示,超过三次后失败后,则直接算作退出房间,留在房间内的玩家胜利。
游戏设计示意图
3.代码实现
本部分只说明项目中比较重要的代码实现,其余代码实现请查看git仓库
3.1项目结构
项目接口分为两部分,http请求(httpLink包)和websocket通信(socket包)。
在router包指定路径和特定的请求处理器。
在pojo包中定义了常用的数据接口和websocket信息交互格式。
3.2WebSocket通信建立和跨域处理
func WsHandle(hub *pojo.HupCenter) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//将短连接 升级成 长连接-建立WebSocket通信
upgrade := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
conn, err := upgrade.Upgrade(w, r, nil)
if err != nil {
log.Println("conn错误", err)
return
}
fmt.Println("远程主机连接成功 IP为", conn.RemoteAddr())
//进行client的初始化操作
client := &pojo.Client{User: &pojo.User{}, Hub: &pojo.HupCenter{}} //非字段不要为nil
client.Hub = hub
client.User.UserConn = conn
client.User.HealthCheck = time.Now().Add(time.Duration(pkg.HeartCheckSecond) * time.Second) //健康时间
//计时器 : 如果用户规定秒内没有完成用户认证,则直接断开连接
time.AfterFunc(time.Duration(pkg.UserAuthSecond)*time.Second, func() {
if !client.User.UserCer {
fmt.Println("用户认证失败,关闭连接")
client.User.Close()
}
})
//接受信息 根据信息类型进行分别处理
go Controller(client)
}
}
3.3WebSocket游戏处理中枢
func Controller(client *pojo.Client) {
defer func() {
client.Hub.UnRegister <- client
}()
for {
_, p, err := client.User.UserConn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
//用户主动给关闭连接后的输出
log.Println("WebSocket closed")
} else {
//服务器主动断开走这个,从一个断开的连接中读取信息
log.Println("server.go conn.ReadMessage 读取信息错误", err)
}
return
}
var requestPkg pojo.RequestPkg
err = json.Unmarshal(p, &requestPkg)
if err != nil {
fmt.Println("websocket反序列化失败", err)
return
}
//2. 在信息中枢处根据消息类型进行特定的处理
switch requestPkg.Type {
case pojo.CertificationType:
//用户认证
client.CertificationProcess(requestPkg)
case pojo.CreateRoomType:
//创建房间号,并将创建者加入房间
fmt.Println("发起创建房间的请求")
client.CreateRoomProcess()
case pojo.JoinRoomType:
//1.加入房间的前提,先建立连接
//2.完成用户认证
//3.发送消息类型和房间号 Type uuid
//只有完成上述步骤,才可以加入房间
var data map[string]interface{}
err = json.Unmarshal([]byte(requestPkg.Data), &data)
if err != nil {
fmt.Println("解析 JSON 失败:", err)
return
}
uuidValue, ok := data["uuid"].(string)
if !ok {
fmt.Println("uuid 字段不存在或不是字符串类型")
return
}
client.JoinRoomProcess(uuidValue)
case pojo.RefreshScoreType:
//什么是否进行分数更新,前端判断 type:RefreshScoreType, data:step、step、score
//当用户的行为触发前端游戏机制的更新时,前端调用此接口,后端进行分数的转发 不需要做业务处理,直接转发即可
fmt.Println("游戏交换中数据", client)
client.RefreshScoreProcess(requestPkg)
case pojo.DiscontinueQuitType:
client.DiscontinueQuitProcess()
case pojo.GameOverType:
//游戏结束类型好像没有太大用,游戏结束的时候的提醒,通过分数更新就可以实现了
fmt.Println("GameOverType")
case pojo.HeartCheckType:
//开启一个协程遍历hub中的Client,进行健康检测,生命时间是否会过期,如果过期进行逻辑删除和关闭连接
if requestPkg.Data == "PING" {
client.HeartCheckProcess()
}
}
}
}
3.4维护建立连接的客户端
维护已经建立的客户端连接和进行客户端的之间的配对、查询。这里使用管道对全局唯一的map进行操作,防止出现多个协程操作同一个map。
package pojo
import (
"fmt"
"klotski/pkg"
"time"
)
type HupCenter struct {
ClientsMap map[string]map[string]*Client `json:"-"` //第一个string-roomId 第二个string-userId
Register chan *Client
UnRegister chan *Client
}
// NewHub 初始化一个hub
func NewHub() *HupCenter {
return &HupCenter{
ClientsMap: make(map[string]map[string]*Client),
Register: make(chan *Client, 1),
UnRegister: make(chan *Client, 1),
}
}
// Run 用户向hub中的逻辑注册、删除、心跳检测全方法
func (h *HupCenter) Run() {
checkTicker := time.NewTicker(time.Duration(pkg.HeartCheckSecond) * time.Second)
defer checkTicker.Stop()
for {
select {
case client := <-h.Register:
//先查询是否存在此一个roomId key
if myMap, ok := client.Hub.ClientsMap[client.User.RoomId]; ok { //有,加入房间
//检测人数
if len(myMap) == 1 {
myMap[client.User.UserId] = client
}
} else { //没有,创建房间
myMap := make(map[string]*Client)
myMap[client.User.UserId] = client //userId
client.Hub.ClientsMap[client.User.RoomId] = myMap //roomId
}
fmt.Println("有人加入房间:", client.Hub.ClientsMap)
case client := <-h.UnRegister:
client.User.Close()
if value, ok1 := client.Hub.ClientsMap[client.User.RoomId]; ok1 {
if _, ok2 := value[client.User.UserId]; ok2 {
delete(value, client.User.UserId)
}
}
if len(client.Hub.ClientsMap[client.User.RoomId]) == 0 {
delete(client.Hub.ClientsMap, client.User.RoomId)
}
case <-checkTicker.C:
for _, roomMap := range h.ClientsMap {
//fmt.Println(roomMap)
for _, client := range roomMap {
//fmt.Println(client)
//fmt.Println(client.User.HealthCheck)
if client.User.HealthCheck.Before(time.Now()) {
h.UnRegister <- client
}
}
}
fmt.Println(time.Now().Format(time.DateTime), h.ClientsMap)
}
}
}
// QueryOtherUser 读操作 -- 根据当前用户寻找另一位用户,返回user对象
func (h *HupCenter) QueryOtherUser(c *Client) *Client {
if roomMap, ok := h.ClientsMap[c.User.RoomId]; ok { //room
for userId, user := range roomMap {
if userId != c.User.UserId {
return user
}
}
}
return nil
}
4.项目收获
- 前期的设计要明确具体,提前构思好项目的整体交互、处理流程,不要把一切问题推迟到编码阶段解决,要学会使用工具,将自己的项目构思表达出来。
- WebSocket通信的应用场景。
- 如何设计WebSocket数据包和客户端、服务端的通信流程
- 将管道和协程熟练运用到自己的项目中
- 掌握go语言打包部署流程,使用Jenkin自动化部署,进行产品迭代
热门推荐
解密巴菲特的黑历史:从“无情清算者”到价值投资之王
无人机风速风向仪:助力气象预报更精准
灯光频闪对眼睛的影响及防护措施
中药调理肠胃的方法是什么
每次吃面条、吃饺子都爱放醋?爱吃醋的人,对身体健康好不好
仁爱与和谐:探寻中华文明的根基
手老是出汗,怎么办?
欧洲300年油画展在沪展出,呈现西方艺术演变之路
30岁了还不开窍?读了这3本书,助你人生逆袭!
猪腱子肉的功效与作用、禁忌和食用方法
如何计算市净率并进行分析?这种分析方法的局限性是什么?
1000个智能体创建首个「AI文明」,北大校友放弃MIT教职打造「西部世界」
女人爱不爱,“身体反应”最诚实
如何选购适合自己的粉饼 粉饼的颜色怎么选
多胖叫胖?体重多少才健康?一文读懂超重肥胖与体重管理
朝鲜族传统米糕:承载千年文化的美味佳肴
银行理财市场存续规模升至5年来新高 低风险产品占比超95%
逆袭高二数学困境:如何助力孩子突破偏科?七大策略可参考
厨刀锐化完全指南:从入门到精通
活性炭口罩和N95口罩的区别在哪?医用外科口罩又有什么不同?
逃离健身房的户外小白,开始逃回来了
绿茶能降低血尿酸吗
古装剧扬帆出海:优质国产剧与海外受欢迎?这些作品正在走出去
甲状腺疾病会遗传?是真的吗?
古代法律对诬告者的严惩与警示
张志磊称重287.5磅,将迎战卡巴耶尔,能否上演KO好戏?
古代书信格式:礼仪与规范的完美融合
固态盛行下,小白新手必知的机械硬盘价值全攻略
苏州公交地铁双融机制:从危机意识走向创新实践
网球肘的病因、症状、诊断、治疗及 4个缓解动作