马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
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地区
- 一张地图包含若干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
|
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |