【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(2)——服务端架构

1.架构选型

B/S架构:支持PC、平板、手机等多个平台

2.技术选型

(1)客户端web技术:

  • HTML5 Canvas:支持基于2D平铺的图形引擎

  • Web workers:允许在不减慢主页UI的情况下初始化大型世界地图。

  • localStorage:将您角色的进度将实时保存在其中

  • CSS3 Media Queries:使游戏可以自行调整大小并适应许多设备

  • HTML5 audio:你可以听到老鼠或骷髅死亡的声音

(2)后台

  • NodeJS(或golang)

  • DB:MongoDB(Metrics)

(3)通讯类型:websocket

(4)通讯协议:[type(int), ……]

3.服务架构类型

单体架构

4.数据结构

4.1 实体类型

实体分类

编号

类型

说明

Player

1

WARRIOR

战士

Mobs

2

RAT

老鼠

3

SKELETON

骷髅

4

GOBLIN

妖精(哥布林)

5

OGRE

食人魔

6

SPECTRE

幽灵、妖怪

7

CRAB

螃蟹

8

BAT

蝙蝠

9

WIZARD

巫师

10

EYE

11

SNAKE

12

SKELETON2

骷髅2

13

BOSS

14

DEATHKNIGHT

死亡骑士

防具(Armors)

20

FIREFOX

火狐

21

CLOTHARMOR

布衣

22

LEATHERARMOR

皮衣

23

MAILARMOR

铠甲

24

PLATEARMOR

鳞甲

25

REDARMOR

红衣

26

GOLDENARMOR

金色战甲

Objects

35

FLASK

烧瓶

36

BURGER

汉堡

37

CHEST

箱子

38

FIREPOTION

魔药

39

CAKE

蛋糕

NPCs

40

GUARD

卫兵

41

KING

国王

42

OCTOCAT

章鱼猫

43

VILLAGEGIRL

村民(女)

44

VILLAGER

村民(男)

45

PRIEST

牧师

46

SCIENTIST

科学家

47

AGENT

特工

48

RICK

干草堆

49

NYAN

50

SORCERER

男巫师

51

BEACHNPC

海滨NPC

52

FORESTNPC

森林NPC

53

DESERTNPC

沙漠NPC

54

LAVANPC

火山NPC

55

CODER

程序员

Weapons

60

SWORD1

剑1

61

SWORD2

剑2

62

REDSWORD

红剑

63

GOLDENSWORD

金剑

64

MORNINGSTAR

晨星

65

AXE

斧子

66

BLUESWORD

蓝剑

4.2 地图定义

字段

类型

初始值

范围

说明

width

int

172

地图宽

height

int

314

地图高

collisions

list[int]

碰撞点

doors

list[object]

doors.[].x

int

门x坐标

doors.[].y

int

门y坐标

doors.[].p

int

0/1

doors.[].tcx

int

doors.[].tcy

int

doors.[].to

string

u/d/l/r

门朝向

doors.[].tx

int

目标x

doors.[].ty

int

目标y

checkpoints

list[object]

checkpoints.[].id

int

checkpoints.[].x

int

checkpoints.[].y

int

checkpoints.[].w

int

checkpoints.[].h

int

checkpoints.[].s

int

0/1

roamingAreas

list[object]

移动区域

roamingAreas.[].id

int

roamingAreas.[].x

int

roamingAreas.[].y

int

roamingAreas.[].width

int

roamingAreas.[].height

int

roamingAreas.[].type

string

rat、crab、goblin……

怪物类型

roamingAreas.[].nb

int

数量

chestAreas

list[object]

箱子区域

chestAreas.[].x

int

chestAreas.[].y

int

chestAreas.[].w

int

chestAreas.[].h

int

chestAreas.[].i

list[int]

箱子中ItemList

chestAreas.[].tx

int

chestAreas.[].ty

int

staticChests

list[object]

静态箱子

staticChests.[].x

int

staticChests.[].y

int

staticChests.[].i

list[int]

箱子中ItemList

staticEntities

object

静态实体

staticEntities.key

int-string

staticEntities.value

string

rat、crab、goblin……

tilesize

int

16

瓦片大小

5.通讯协议

5.1 消息类型定义

客户端与服务器基于websocket连接进行数据收发,详细协议如下:

通讯类型

编号

消息类型

参数

含义

备注

服务端-->客户端

1

WELCOME

id,name,x,y,hp

欢迎信息

4

MOVE

id,x,y

移动信息

双向消息

5

LOOTMOVE

id,item

朝向ITEM移动捡取

双向消息

7

ATTACK

attacker,target

攻击信息

双向消息

2

SPAWN

id,kind,x,y

再生信息

3

DESPAWN

id

取消再生

SPAWN_BATCH

批量再生

10

HEALTH

points,[isRegen]

健康信息

11

CHAT

id,text

聊天信息

双向消息

13

EQUIP

id,itemKind

装备信息

14

DROP

mobId,id,kind,playersInvolved

掉落信息

15

TELEPORT

id,x,y

传送信息

16

DAMAGE

id,dmg

伤害信息

17

POPULATION

worldPlayers,totalPlayers

人口数量信息

19

LIST

列表信息

22

DESTROY

id

销毁信息

18

KILL

mobKind

杀死信息

23

HP

maxHP

生命信息

24

BLINK

id

闪烁

客户端-->服务端

0

HELLO

player.name,

招呼

4

MOVE

x,y

移动

双向消息

5

LOOTMOVE

x,y,item.id

移动捡取

双向消息

6

AGGRO

mob.id

7

ATTACK

mob.id

攻击

双向消息

8

HIT

mob.id

开始攻击

9

HURT

mob.id

伤害

11

CHAT

text

聊天

双向消息

12

LOOT

item.id

捡取

15

TELEPORT

x,y

传送

双向消息

20

WHO

ids

信息查询

21

ZONE

-

区域切换

玩家从一个区域走到另外区域

25

OPEN

chest.id

打开箱子

26

CHECK

id

确认

5.2 协议交互流程

6.类图

  • 一个世界包含一张地图【静态】

    • 一张地图包含若干ChestArea区域

      • 一个ChestArea区域包含若干Item对象

    • 一张地图包含若干MobArea区域

    • 一张地图包含若干CheckPoint

  • 一个世界包含若干Zone【动态】

    • 一个Zone包含若干NPC对象

    • 一个Zone包含若干Mob对象

    • 一个Zone包含若干Item对象

    • 一个Zone包含若干Player对象

7.线程模型

7.1 协程创建

  • 创建一个世界广播服务协程

  • 根据地图的区域个数,每个区域创建一个协程

  • 每个接入用户创建一个Handler协程,每个Handler协程创建一个PlayerHandleLoop协程

7.2 协程通信

(1)Handler协程与PlayerHandleLoop协程通过带缓冲PacketChan通信

(2)Player读取解析PacketChan中的消息,逻辑处理后投递到所属区域对象的zone.EventCh

(3)Player对象调用世界对象,将消息投递到world.BroadcastCh进行世界消息发送(如人数)

(4)世界对象解析world.BroadcastCh中的消息,遍历所有区域对象,将消息投递到zone.EventCh

(5)区域对象读取解析zone.EventCh中的消息,逻辑处理后调用Player对象send方法进行消息发送

8.游戏详细处理逻辑分析

8.1地图加载

(1)通过json Unmarshal进行decode到Map结构体。

(2)根据地图宽高和区域宽高,计算出区域个数

(3)其中Map.collitions表示碰撞的点,结合地图宽高,初始化碰撞二维表

(4)初始化checkpoint Map,checkpoint ID作为KEY。其中checkpoint.S为1的表示为起始区域

8.2.物品掉落

	TypeCrab.ID: &MobProperty{Drops: map[string]int{"flask":        50,"axe":          20,"leatherarmor": 10,"firepotion":   5,},HP:          60,ArmorLevel:  2,WeaponLevel: 1,},

Drops表示:flask:50%,axe:20%,leatherarmor:%10,firepotion:5%,不掉落5%

算法:随机一个[0~99]的值,累计求和,判断是否在Drops区间,如果在则掉落对应物品,否则不掉落。

8.3.物品捡取

func (z *Zone) onLoot(e *Event) {itemID := e.Data[0].(int)p := z.PlayersMap[e.PlayerID]if p == nil {return}if item := z.ItemsMap[itemID]; item != nil {despawnEvent := AquireEvent(EventDespawn, itemID)z.broadcastZone(despawnEvent)item.IsDestroy = trueif item.IsStatic {item.RespawnLater(z.EventCh)}kind := item.Kindif kind.ID == TypeFirePotion.ID {// TODO} else if IsHealingItem(kind) {amount := 0switch kind.ID {case TypeFlask.ID:amount = 40case TypeBurger.ID:amount = 100}if amount > 0 && !p.HasFullHealth() {p.ReginHealthBy(amount)healthEvent := AquireEvent(EventHealth, p.HP)_ = p.send(healthEvent)}} else if IsArmor(kind) || IsWeapon(kind) {equipEvent := AquireEvent(EventEquip, p.ID, kind.ID)z.broadcastZone(equipEvent)if IsArmor(kind) {p.equipArmor(kind.ID)p.updateHP()HPEvent := AquireEvent(EventHP, p.MaxHP)_ = p.send(HPEvent)} else {p.equipWeapon(kind.ID)}}}
}

捡取流程:

通过EventDespawn消息广播消失;

  • 如果是静态物品,则触发定时重刷;

  • 如果是药品,则触发补血;

  • 如果是防具,则广播装备并根据当前防具类型更新当前用户血条;

  • 如果是武器广播装备的同时并装备。

8.4.mob跟随

func (m *Mob) ChaseTarget(zoneID string, mp *Map, targetX, targetY int) {zid := mp.GetGroupIDFromPosition(targetX, targetY)if zoneID != zid {m.X, m.Y = targetX, targetY} else {pointsAround := make([][2]int, 0)for _, p := range [][2]int{[2]int{targetX, targetY + 1},[2]int{targetX + 1, targetY},[2]int{targetX, targetY - 1},[2]int{targetX - 1, targetY},} { // 沿着玩家上下左右,找到若干个有效的点作为目标if mp.IsValidPosition(p[0], p[1]) && zoneID == mp.GetGroupIDFromPosition(p[0], p[1]) {pointsAround = append(pointsAround, p)}}minLen := 999999minIndex := 0for i, p := range pointsAround { // 基于有效点,找到其中mob到玩家有效点的一个最小距离pathLength := (m.X-p[0])*(m.X-p[0]) + (m.Y-p[1])*(m.Y-p[1])if pathLength <= minLen {minLen = pathLengthminIndex = i}}m.X, m.Y = pointsAround[minIndex][0], pointsAround[minIndex][1]}
}

算法:先找玩家周围有效点,然后从中计算选取一个最短路径点,最短路径通过:(x1-x2)(x1-x2) + (y1-y2)(y1-y2)粗略算出。更新当前mob的X、Y。

8.5.mob平静期处理

func (z *Zone) onMobCalm(e *Event) {mobID := e.Data[0].(int)if mob := z.MobsMap[mobID]; mob != nil {z.Logger.Println("[DEBUG] Mob", mob, "Calm Down")mob.RecoveryHP()for k := range mob.Haters {delete(mob.Haters, k)}mob.TargetID = 0if mob.X != mob.OriginX || mob.Y != mob.OriginY {mob.X, mob.Y = mob.OriginX, mob.OriginYmoveEvent := AquireEvent(EventMove, mob.ID, mob.X, mob.Y)z.broadcastZone(moveEvent)}mob.TargetID = 0}
}

平静期到时(如果有玩家HIT攻击此mob时,平静期会被重置),mob恢复体力,清除所有Haters,当前位置不在原始位置则移动到原始位置并广播。

8.6.多人同时攻击

func (m *Mob) AddHate(playerID, damage int) {m.Haters[playerID] += damage
}func (m *Mob) ChooseMobTarget() int {var max, maxPid intfor pid, hate := range m.Haters {if hate > max {max = hatemaxPid = pid}}if max <= 0 {return -1}return maxPid
}func (z *Zone) onMobAttacked(m *Mob, p *Player) {m.ResetHateLater(z.EventCh)dmg := DamageFormula(p.WeaponLevel, m.ArmorLevel)if dmg > 0 {m.HP -= dmgif m.HP > 0 {dmgEvent := AquireEvent(EventDamage, m.ID, dmg)_ = p.send(dmgEvent)m.AddHate(p.ID, dmg)if maxHateTarget := m.ChooseMobTarget(); maxHateTarget > 0 {if maxHateTarget != m.TargetID {m.TargetID = maxHateTarget}attackEvent := AquireEvent(EventAttack, m.ID, m.TargetID)z.broadcastZone(attackEvent)}} else {z.Logger.Println("[DEBUG] m", m.ID, "DEAD!")m.IsDead = trueif dropItem := m.DropItem(); dropItem != nil {z.Logger.Println("[DEBUG] m", m.ID, "DROP!", dropItem)dropItem.DespawnLater(z.EventCh)z.ItemsMap[dropItem.ID] = dropItemspawnItemEvent := AquireEvent(EventSpawn, dropItem.Pack()...)z.broadcastZone(spawnItemEvent)}z.Logger.Println("[DEBUG] m", m.ID, "DESPAWN LATER!")m.RespawnLater(z.EventCh)despawnEvent := AquireEvent(EventDespawn, m.ID)z.broadcastZone(despawnEvent)killEvent := AquireEvent(EventKill, m.Kind.ID)_ = p.send(killEvent)z.Logger.Println("[DEBUG] m", m.ID, "DESPAWN!")}}
}

所有玩家及伤害累积基于当前被攻击的mob的Haters列表,mob选择一个累积伤害最大的玩家进行攻击

9.代码还需完善点

  • ChestArea、MobArea、StaticChest支持

  • DO、PO拆分

  • 多世界支持

  • 排队与负载支持

  • 账号接入

  • NPC寻路算法增强

  • 任务与活动

  • 数据持久化

  • 机器人压测脚本

  • 性能metrics监控

  • ……

10.三方框架

语言

框架

c

skynet

c++

kbengine/TrinityCore

golang

leaf

rust

veloren

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2978653.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

为何申请图形商标的通过率比文字商标低!

最近有朋友找普推知产老杨说要申请图形商标&#xff0c;从全国的平均通过率来看&#xff0c;图形商标的通过率要比文字低一些&#xff0c;因为图形商标存在许多不确定因素。 文字在检索时只要不在盲查期&#xff0c;基本都可以搜索出来&#xff0c;是相同还是高近差不多就可以检…

符号内链接文件作用域:static限制在本模块

示例&#xff1a; /*** brief how about symbol-inner? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>static…

基于K-means和FCM算法的合成纹理图像及SAR图像的分割

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

使用Docker搭建本地Nexus私有仓库

0-1开始Java语言编程之路 一、Ubuntu下Java语言环境搭建 二、Ubuntu下Docker环境安装 三、使用Docker搭建本地Nexus Maven私有仓库 四、Ubuntu下使用VisualStudioCode进行Java开发 你需要Nexus Java应用编译构建的一种主流方式就是通过Maven, Maven可以很方便的管理Java应用的…

wsl2 中docker安装完毕后无法正常启动

wsl2 中docker安装完毕后无法正常启动 1、背景2、目标3、环境4、原因4、操作5.1、查看配置5.2、 切换配置5.3、启动docker5.4、验证docker 1、背景 在win10中安装wsl2体验linux操作系统&#xff0c;按照docker官网步骤安装&#xff0c;安装完毕后面提示 $ docker ps Cannot co…

每日一题 — 二分查找

704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 朴素二分查找模板&#xff1a; while(.......){//防止溢出int mid left(right - left)/2;if(........){right mid-1;}else if(......){left mid1;}else{return mid;}} 代码&#xff1a; public int search(int[] num…

【前端】项目中如何快速构建vue脚手架

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、vue脚手架介绍二、构建vue脚手架三、总结 前言 随着开发语言的普及&#xff0c;越来越多的开发框架需要搭建ue脚手架来进行敏捷开发&#xff0c;本文主要介…

软考高项(已通过,E类人才)-学习笔记材料梳理汇总

软考高项&#xff0c;即软考高级信息系统项目管理师&#xff0c;全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试中的高级水平测试。适用于从事计算机应用技术、软件、网络、信息系统和信息服务等领域的专业人员&#xff0c;以及各级企业管理人员和从事项目…

MySQL学习笔记5——函数和索引

函数和索引 一、函数1、数学函数2、字符串函数3、条件判断函数 二、索引1、索引是什么2、单字段索引3、组合索引4、总结 一、函数 MySQL提供了很多功能强大&#xff0c;而且使用起来非常方便的函数&#xff0c;包括数学函数、字符串处理函数和条件判断函数等。 1、数学函数 …

Spring Cloud面试篇

面试篇-nacos面试题 1. springboot常见组件 注册中心组件&#xff1a;Eureka、Nacos 负载均衡组件&#xff1a;Ribbon 远程调用组件&#xff1a;OpenFeign 网关组件&#xff1a;Zuul、Gateway 服务保护组件&#xff1a;Hystrix、Sentinel 服务配置管理组件&#xff1a;SpringCl…

大模型的研究新方向:混合专家模型(MoE)

大模型的发展已经到了一个瓶颈期,包括被业内所诟病的罔顾事实而产生的“幻觉”问题、深层次的逻辑理解能力、数学推理能力等,想要解决这些问题就不得不继续增加模型的复杂度。随着不同应用场景的实际需求,大模型的参数会变得越来越大,复杂性和规模不断的增加,尤其是在多模…

18.基础乐理-音阶是什么、有什么用

音阶&#xff1a; 将调式中的音&#xff0c;从以主音开始到以主音结束&#xff0c;由低到高&#xff08;叫做上行&#xff08;xing&#xff09;&#xff09;&#xff0c;或由高到低&#xff08;叫做下行&#xff08;xing&#xff09;&#xff09;&#xff0c;以阶梯状排列起来&…

Bingbong的回文路径

Here 利用回文串&#xff0c;从左往右与从右往左的hash值相同来判断从左往右&#xff0c;例&#xff1a;从右往左&#xff0c;例&#xff1a;由于在树上&#xff0c;考虑建两颗树&#xff0c;一颗根为最高位&#xff08;up&#xff09;&#xff0c;一棵根为最低位&#xff08;…

Rust 使用结构体组织相关联的数据

目录 结构体的定义和实例化 使用字段初始化简写语法使用结构体更新语法从其他实例创建实例使用没有命名字段的元组结构体来创建不同的类型没有任何字段的类单元结构体结构体示例程序 通过派生 trait 增加实用功能方法语法 定义方法带有更多参数的方法关联函数多个 impl 块本文有…

大厂常见算法50题-反转链表

专栏持续更新50道算法题&#xff0c;都是大厂高频算法题&#xff0c;建议关注。 文章目录 解法参考链接题目解法一 双指针解法二 递归解法三 妖魔化的双指针总结 解法参考链接 题目 解法一 双指针 定义两个指针&#xff1a; pre 和 cur。pre 在前 cur 在后。每次让 pre的 nex…

Day4 商品管理

Day4 商品管理 这里会总结构建项目过程中遇到的问题&#xff0c;以及一些个人思考&#xff01;&#xff01; 学习方法&#xff1a; 1 github源码 文档 官网 2 内容复现 &#xff0c;实际操作 项目源码同步更新到github 欢迎大家star~ 后期会更新并上传前端项目 编写品牌服务 …

在线预约订房酒店小程序源码系统 带完整的安装代码包以及=安装部署教程

传统的酒店预订方式往往依赖于电话、邮件或者到店咨询&#xff0c;这种方式不仅效率低下&#xff0c;而且容易造成信息不准确、沟通不畅等问题。随着智能手机的普及和移动互联网的发展&#xff0c;用户对于随时随地、方便快捷地进行酒店预订的需求日益增强。小编给大家分享一款…

[MySQL]运算符

1. 算术运算符 (1). 算术运算符 : , -, *, / 或 DIV, % 或MOD. (2). 例 : (3). 注 : DUAL是伪表.可以看到4/2结果为小数&#xff0c;并不会截断小数部分.(可能与其他语言不同&#xff0c;比如java中&#xff0c;两个操作数如果是整数&#xff0c;则计算得到的也是整数&…

羊大师:夏季羊奶的好处有哪些?

夏季羊奶的好处主要包括以下几点 1.增强免疫力&#xff1a;羊奶中的钙元素丰富&#xff0c;能有效为身体补充营养物质&#xff0c;增强自身的免疫能力。羊奶还富含上皮细胞生长因子&#xff08;EGF&#xff09;&#xff0c;对人体鼻腔、咽喉、血管、肠胃等黏膜有良好的修复作用…

Qt 跨平台开发的一丢丢总结

Qt 跨平台开发 文章目录 Qt 跨平台开发摘要第一 \ & /第二 神奇{不能换行显示第三 预处理宏 关键字&#xff1a; Qt、 win、 linux、 lib、 MSVC 摘要 最近一直在琢磨Qt跨平台开发的问题&#xff0c;缘由有以下几个&#xff0c; 首先第一个&#xff0c;我们目前开发…