mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 14:22:26 +08:00
寻路服务器
This commit is contained in:
47
pathfinding/app/app.go
Normal file
47
pathfinding/app/app.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hk4e/common/config"
|
||||
"hk4e/common/mq"
|
||||
"hk4e/pathfinding/handle"
|
||||
"hk4e/pkg/logger"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, configFile string) error {
|
||||
config.InitConfig(configFile)
|
||||
|
||||
logger.InitLogger("pathfinding")
|
||||
logger.Warn("pathfinding start")
|
||||
|
||||
messageQueue := mq.NewMessageQueue(mq.PATHFINDING, "1")
|
||||
defer messageQueue.Close()
|
||||
|
||||
_ = handle.NewHandle(messageQueue)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case s := <-c:
|
||||
logger.Warn("get a signal %s", s.String())
|
||||
switch s {
|
||||
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
|
||||
logger.Warn("pathfinding exit")
|
||||
time.Sleep(time.Second)
|
||||
return nil
|
||||
case syscall.SIGHUP:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
pathfinding/handle/handle.go
Normal file
70
pathfinding/handle/handle.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"hk4e/common/mq"
|
||||
"hk4e/pathfinding/world"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/protocol/cmd"
|
||||
|
||||
pb "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Handle struct {
|
||||
worldStatic *world.WorldStatic
|
||||
messageQueue *mq.MessageQueue
|
||||
}
|
||||
|
||||
func NewHandle(messageQueue *mq.MessageQueue) (r *Handle) {
|
||||
r = new(Handle)
|
||||
r.worldStatic = world.NewWorldStatic()
|
||||
r.worldStatic.InitTerrain()
|
||||
r.messageQueue = messageQueue
|
||||
go r.run()
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *Handle) run() {
|
||||
for i := 0; i < 4; i++ {
|
||||
go func() {
|
||||
for {
|
||||
netMsg := <-h.messageQueue.GetNetMsg()
|
||||
if netMsg.MsgType != mq.MsgTypeGame {
|
||||
continue
|
||||
}
|
||||
if netMsg.EventId != mq.NormalMsg {
|
||||
continue
|
||||
}
|
||||
gameMsg := netMsg.GameMsg
|
||||
switch gameMsg.CmdId {
|
||||
case cmd.QueryPathReq:
|
||||
h.QueryPath(gameMsg.UserId, gameMsg.PayloadMessage)
|
||||
case cmd.ObstacleModifyNotify:
|
||||
h.ObstacleModifyNotify(gameMsg.UserId, gameMsg.PayloadMessage)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// SendMsg 发送消息给客户端
|
||||
func (h *Handle) SendMsg(cmdId uint16, userId uint32, payloadMsg pb.Message) {
|
||||
if userId < 100000000 || payloadMsg == nil {
|
||||
return
|
||||
}
|
||||
gameMsg := new(mq.GameMsg)
|
||||
gameMsg.UserId = userId
|
||||
gameMsg.CmdId = cmdId
|
||||
gameMsg.ClientSeq = 0
|
||||
// 在这里直接序列化成二进制数据 防止发送的消息内包含各种游戏数据指针 而造成并发读写的问题
|
||||
payloadMessageData, err := pb.Marshal(payloadMsg)
|
||||
if err != nil {
|
||||
logger.Error("parse payload msg to bin error: %v", err)
|
||||
return
|
||||
}
|
||||
gameMsg.PayloadMessageData = payloadMessageData
|
||||
h.messageQueue.SendToGate("1", &mq.NetMsg{
|
||||
MsgType: mq.MsgTypeGame,
|
||||
EventId: mq.NormalMsg,
|
||||
GameMsg: gameMsg,
|
||||
})
|
||||
}
|
||||
74
pathfinding/handle/query_path.go
Normal file
74
pathfinding/handle/query_path.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"hk4e/pathfinding/pfalg"
|
||||
"hk4e/pkg/logger"
|
||||
"hk4e/protocol/cmd"
|
||||
"hk4e/protocol/proto"
|
||||
|
||||
pb "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func (h *Handle) ConvPbVecToMeshVec(pbVec *proto.Vector) pfalg.MeshVector {
|
||||
return pfalg.MeshVector{
|
||||
X: int16(pbVec.X),
|
||||
Y: int16(pbVec.Y),
|
||||
Z: int16(pbVec.Z),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) ConvMeshVecToPbVec(meshVec pfalg.MeshVector) *proto.Vector {
|
||||
return &proto.Vector{
|
||||
X: float32(meshVec.X),
|
||||
Y: float32(meshVec.Y),
|
||||
Z: float32(meshVec.Z),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) ConvPbVecListToMeshVecList(pbVecList []*proto.Vector) []pfalg.MeshVector {
|
||||
ret := make([]pfalg.MeshVector, 0)
|
||||
for _, pbVec := range pbVecList {
|
||||
ret = append(ret, h.ConvPbVecToMeshVec(pbVec))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (h *Handle) ConvMeshVecListToPbVecList(meshVecList []pfalg.MeshVector) []*proto.Vector {
|
||||
ret := make([]*proto.Vector, 0)
|
||||
for _, meshVec := range meshVecList {
|
||||
ret = append(ret, h.ConvMeshVecToPbVec(meshVec))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (h *Handle) QueryPath(userId uint32, payloadMsg pb.Message) {
|
||||
req := payloadMsg.(*proto.QueryPathReq)
|
||||
logger.Debug("query path req: %v, uid: %v", req, userId)
|
||||
var ok = false
|
||||
var path []pfalg.MeshVector = nil
|
||||
for _, destinationPos := range req.DestinationPos {
|
||||
ok, path = h.worldStatic.Pathfinding(h.ConvPbVecToMeshVec(req.SourcePos), h.ConvPbVecToMeshVec(destinationPos))
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
queryPathRsp := &proto.QueryPathRsp{
|
||||
QueryId: req.QueryId,
|
||||
QueryStatus: proto.QueryPathRsp_PATH_STATUS_TYPE_FAIL,
|
||||
}
|
||||
h.SendMsg(cmd.QueryPathRsp, userId, queryPathRsp)
|
||||
return
|
||||
}
|
||||
queryPathRsp := &proto.QueryPathRsp{
|
||||
QueryId: req.QueryId,
|
||||
QueryStatus: proto.QueryPathRsp_PATH_STATUS_TYPE_SUCC,
|
||||
Corners: h.ConvMeshVecListToPbVecList(path),
|
||||
}
|
||||
h.SendMsg(cmd.QueryPathRsp, userId, queryPathRsp)
|
||||
}
|
||||
|
||||
func (h *Handle) ObstacleModifyNotify(userId uint32, payloadMsg pb.Message) {
|
||||
req := payloadMsg.(*proto.ObstacleModifyNotify)
|
||||
logger.Debug("obstacle modify req: %v, uid: %v", req, userId)
|
||||
}
|
||||
200
pathfinding/pfalg/bfs_pathfinding.go
Normal file
200
pathfinding/pfalg/bfs_pathfinding.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package pfalg
|
||||
|
||||
import (
|
||||
"hk4e/pkg/alg"
|
||||
)
|
||||
|
||||
const (
|
||||
NODE_NONE = iota
|
||||
NODE_START
|
||||
NODE_END
|
||||
NODE_BLOCK
|
||||
)
|
||||
|
||||
type PathNode struct {
|
||||
x int16
|
||||
y int16
|
||||
z int16
|
||||
visit bool
|
||||
state int
|
||||
parent *PathNode
|
||||
}
|
||||
|
||||
type BFS struct {
|
||||
gMap map[int16]map[int16]map[int16]*PathNode
|
||||
startPathNode *PathNode
|
||||
endPathNode *PathNode
|
||||
}
|
||||
|
||||
func NewBFS() (r *BFS) {
|
||||
r = new(BFS)
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *BFS) InitMap(terrain map[MeshVector]bool, start MeshVector, end MeshVector, extR int16) {
|
||||
xLen := end.X - start.X
|
||||
yLen := end.Y - start.Y
|
||||
zLen := end.Z - start.Z
|
||||
dx := int16(1)
|
||||
dy := int16(1)
|
||||
dz := int16(1)
|
||||
if xLen < 0 {
|
||||
dx = -1
|
||||
xLen *= -1
|
||||
}
|
||||
if yLen < 0 {
|
||||
dy = -1
|
||||
yLen *= -1
|
||||
}
|
||||
if zLen < 0 {
|
||||
dz = -1
|
||||
zLen *= -1
|
||||
}
|
||||
b.gMap = make(map[int16]map[int16]map[int16]*PathNode)
|
||||
for x := start.X - extR*dx; x != end.X+extR*dx; x += dx {
|
||||
b.gMap[x] = make(map[int16]map[int16]*PathNode)
|
||||
for y := start.Y - extR*dy; y != end.Y+extR*dy; y += dy {
|
||||
b.gMap[x][y] = make(map[int16]*PathNode)
|
||||
for z := start.Z - extR*dz; z != end.Z+extR*dz; z += dz {
|
||||
state := -1
|
||||
if x == start.X && y == start.Y && z == start.Z {
|
||||
state = NODE_START
|
||||
} else if x == end.X && y == end.Y && z == end.Z {
|
||||
state = NODE_END
|
||||
} else {
|
||||
_, exist := terrain[MeshVector{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
}]
|
||||
if exist {
|
||||
state = NODE_NONE
|
||||
} else {
|
||||
state = NODE_BLOCK
|
||||
}
|
||||
}
|
||||
node := &PathNode{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
visit: false,
|
||||
state: state,
|
||||
parent: nil,
|
||||
}
|
||||
b.gMap[x][y][z] = node
|
||||
if node.state == NODE_START {
|
||||
b.startPathNode = node
|
||||
} else if node.state == NODE_END {
|
||||
b.endPathNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BFS) GetNeighbor(node *PathNode) []*PathNode {
|
||||
neighborList := make([]*PathNode, 0)
|
||||
dir := [][3]int16{
|
||||
//
|
||||
{1, 0, 0},
|
||||
{-1, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, -1, 0},
|
||||
{0, 0, 1},
|
||||
{0, 0, -1},
|
||||
//
|
||||
{1, 1, 0},
|
||||
{-1, 1, 0},
|
||||
{-1, -1, 0},
|
||||
{1, -1, 0},
|
||||
//
|
||||
{1, 0, 1},
|
||||
{-1, 0, 1},
|
||||
{-1, 0, -1},
|
||||
{1, 0, -1},
|
||||
//
|
||||
{0, 1, 1},
|
||||
{0, -1, 1},
|
||||
{0, -1, -1},
|
||||
{0, 1, -1},
|
||||
//
|
||||
{1, 1, 1},
|
||||
{1, 1, -1},
|
||||
{1, -1, 1},
|
||||
{1, -1, -1},
|
||||
{-1, 1, 1},
|
||||
{-1, 1, -1},
|
||||
{-1, -1, 1},
|
||||
{-1, -1, -1},
|
||||
}
|
||||
for _, v := range dir {
|
||||
x := node.x + v[0]
|
||||
y := node.y + v[1]
|
||||
z := node.z + v[2]
|
||||
if _, exist := b.gMap[x]; !exist {
|
||||
continue
|
||||
}
|
||||
if _, exist := b.gMap[x][y]; !exist {
|
||||
continue
|
||||
}
|
||||
if _, exist := b.gMap[x][y][z]; !exist {
|
||||
continue
|
||||
}
|
||||
neighborNode := b.gMap[x][y][z]
|
||||
neighborList = append(neighborList, neighborNode)
|
||||
}
|
||||
return neighborList
|
||||
}
|
||||
|
||||
func (b *BFS) GetPath() []*PathNode {
|
||||
path := make([]*PathNode, 0)
|
||||
if b.endPathNode.parent == nil {
|
||||
return nil
|
||||
}
|
||||
node := b.endPathNode
|
||||
for {
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
path = append(path, node)
|
||||
node = node.parent
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (b *BFS) Pathfinding() []MeshVector {
|
||||
queue := alg.NewALQueue[*PathNode]()
|
||||
b.startPathNode.visit = true
|
||||
queue.EnQueue(b.startPathNode)
|
||||
for queue.Len() > 0 {
|
||||
head := queue.DeQueue()
|
||||
neighborList := b.GetNeighbor(head)
|
||||
for _, neighbor := range neighborList {
|
||||
if !neighbor.visit && neighbor.state != NODE_BLOCK {
|
||||
neighbor.visit = true
|
||||
neighbor.parent = head
|
||||
queue.EnQueue(neighbor)
|
||||
if neighbor.state == NODE_END {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
path := b.GetPath()
|
||||
if path == nil {
|
||||
return nil
|
||||
}
|
||||
pathVectorList := make([]MeshVector, 0)
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
node := path[i]
|
||||
pathVectorList = append(pathVectorList, MeshVector{
|
||||
X: node.x,
|
||||
Y: node.y,
|
||||
Z: node.z,
|
||||
})
|
||||
}
|
||||
return pathVectorList
|
||||
}
|
||||
7
pathfinding/pfalg/mesh_vector.go
Normal file
7
pathfinding/pfalg/mesh_vector.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package pfalg
|
||||
|
||||
type MeshVector struct {
|
||||
X int16
|
||||
Y int16
|
||||
Z int16
|
||||
}
|
||||
88
pathfinding/world/world_static.go
Normal file
88
pathfinding/world/world_static.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"os"
|
||||
|
||||
"hk4e/pathfinding/pfalg"
|
||||
"hk4e/pkg/logger"
|
||||
)
|
||||
|
||||
type WorldStatic struct {
|
||||
// x y z -> if terrain exist
|
||||
terrain map[pfalg.MeshVector]bool
|
||||
}
|
||||
|
||||
func NewWorldStatic() (r *WorldStatic) {
|
||||
r = new(WorldStatic)
|
||||
r.terrain = make(map[pfalg.MeshVector]bool)
|
||||
return r
|
||||
}
|
||||
|
||||
func (w *WorldStatic) InitTerrain() bool {
|
||||
data, err := os.ReadFile("./world_terrain.bin")
|
||||
if err != nil {
|
||||
logger.Error("read world terrain file error: %v", err)
|
||||
return false
|
||||
}
|
||||
decoder := gob.NewDecoder(bytes.NewReader(data))
|
||||
err = decoder.Decode(&w.terrain)
|
||||
if err != nil {
|
||||
logger.Error("unmarshal world terrain data error: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldStatic) SaveTerrain() bool {
|
||||
var buffer bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buffer)
|
||||
err := encoder.Encode(w.terrain)
|
||||
if err != nil {
|
||||
logger.Error("marshal world terrain data error: %v", err)
|
||||
return false
|
||||
}
|
||||
err = os.WriteFile("./world_terrain.bin", buffer.Bytes(), 0644)
|
||||
if err != nil {
|
||||
logger.Error("write world terrain file error: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldStatic) GetTerrain(x int16, y int16, z int16) (exist bool) {
|
||||
pos := pfalg.MeshVector{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
}
|
||||
exist = w.terrain[pos]
|
||||
return exist
|
||||
}
|
||||
|
||||
func (w *WorldStatic) SetTerrain(x int16, y int16, z int16) {
|
||||
pos := pfalg.MeshVector{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
}
|
||||
w.terrain[pos] = true
|
||||
}
|
||||
|
||||
func (w *WorldStatic) Pathfinding(startPos pfalg.MeshVector, endPos pfalg.MeshVector) (bool, []pfalg.MeshVector) {
|
||||
bfs := pfalg.NewBFS()
|
||||
bfs.InitMap(
|
||||
w.terrain,
|
||||
startPos,
|
||||
endPos,
|
||||
100,
|
||||
)
|
||||
pathVectorList := bfs.Pathfinding()
|
||||
if pathVectorList == nil {
|
||||
logger.Error("could not find path")
|
||||
return false, nil
|
||||
}
|
||||
logger.Debug("find path success, path: %v", pathVectorList)
|
||||
return true, pathVectorList
|
||||
}
|
||||
Reference in New Issue
Block a user