【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(2)——服务端架构
创始人
2025-01-18 20:04:50
0

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 = true 		if item.IsStatic { 			item.RespawnLater(z.EventCh) 		} 		kind := item.Kind 		if kind.ID == TypeFirePotion.ID { 			// TODO 		} else if IsHealingItem(kind) { 			amount := 0 			switch kind.ID { 			case TypeFlask.ID: 				amount = 40 			case 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 := 999999 		minIndex := 0 		for 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 = pathLength 				minIndex = 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 = 0 		if mob.X != mob.OriginX || mob.Y != mob.OriginY { 			mob.X, mob.Y = mob.OriginX, mob.OriginY 			moveEvent := 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 int 	for pid, hate := range m.Haters { 		if hate > max { 			max = hate 			maxPid = 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 -= dmg 		if 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 = true 			if dropItem := m.DropItem(); dropItem != nil { 				z.Logger.Println("[DEBUG] m", m.ID, "DROP!", dropItem) 				dropItem.DespawnLater(z.EventCh) 				z.ItemsMap[dropItem.ID] = dropItem  				spawnItemEvent := 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

相关内容

热门资讯

专业讨论!德扑之星真破解套路(... 专业讨论!德扑之星真破解套路(辅助挂)软件透明挂(有挂了解)-哔哩哔哩;人气非常高,ai更新快且高清...
每日必看!智星德州菠萝外挂检测... 每日必看!智星德州菠萝外挂检测(辅助挂)软件透明挂(有挂教学)-哔哩哔哩1、玩家可以在智星德州菠萝外...
透视透明挂!轰趴十三水有后台(... 轰趴十三水有后台赢率提升策略‌;透视透明挂!轰趴十三水有后台(辅助挂)软件透明挂(有挂详情)-哔哩哔...
发现玩家!德扑ai助手软件(辅... 发现玩家!德扑ai助手软件(辅助挂)透视辅助(有挂教学)-哔哩哔哩;玩家在德扑ai助手软件中需先进行...
一分钟了解!x-poker辅助... 一分钟了解!x-poker辅助软件(辅助挂)辅助透视(有挂攻略)-哔哩哔哩1、每一步都需要思考,不同...
一分钟揭秘!德州最新辅助器(辅... 一分钟揭秘!德州最新辅助器(辅助挂)透视辅助(有挂攻略)-哔哩哔哩;德州最新辅助器最新版本免费下载安...
玩家攻略推荐!德州辅助(辅助挂... 玩家攻略推荐!德州辅助(辅助挂)辅助透视(有挂了解)-哔哩哔哩是由北京得德州辅助黑科技有限公司精心研...
揭秘真相!pokernow德州... 《揭秘真相!pokernow德州(辅助挂)辅助透视(有挂介绍)-哔哩哔哩》 pokernow德州软件...
五分钟了解!德州之星辅助器(辅... 五分钟了解!德州之星辅助器(辅助挂)辅助透视(有挂透明)-哔哩哔哩1、很好的工具软件,可以解锁游戏的...
推荐一款!pokermaste... 1、推荐一款!pokermaster有外挂(辅助挂)透视辅助(有挂教学)-哔哩哔哩;详细教程。2、p...