【Zinx应用-MMO游戏案例-(10)模拟客户端AI模块】Golang轻量级并发服务器框架

Zinx源代码

github
https://github.com/aceld/zinx
gitee码云
https://gitee.com/Aceld/zinx


在线开发教程

【B站】
zinx视频教程-Golang轻量级TCP服务器框架-适合自学者

【YouTube】
zinx开发YouTube中国版


【Zinx教程目录】
完整教程电子版(在线高清)-下载
Zinx框架视频教程(框架篇)(完整版下载)链接在下面正文
Zinx框架视频教程(应用篇)(完整版下载)链接在下面正文
Zinx开发API文档
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
Zinx第七章-读写分离模型
Zinx第八章-消息队列及多任务
Zinx第九章-链接管理
Zinx第十章-连接属性设置


【Zinx应用案例-MMO多人在线游戏】
(1)案例介绍
(2)AOI兴趣点算法
(3)数据传输协议protocol buffer
(4)Proto3协议定义
(5)构建项目及用户上线
(6)世界聊天
(7)上线位置信息同步
(8)移动位置与AOI广播
(9)玩家下线
(10)模拟客户端AI模块


为了我们方便测试服务器的调试,这里提供了一个,模拟客户端的代码。

现在我们不用在windows上打开client.exe也能够访问并且测试我们的服务器.

完整代码如下:
mmo_game/client_AI_robot.go

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "github.com/golang/protobuf/proto"
    "io"
    "math/rand"
    "net"
    "os"
    "os/signal"
    "time"
    "zinx/zinx_app_demo/mmo_game/pb"
)

type Message struct {
    Len   uint32
    MsgId uint32
    Data  []byte
}

type TcpClient struct {
    conn     net.Conn
    X        float32
    Y        float32
    Z        float32
    V        float32
    Pid      int32
    isOnline chan bool
}

func (this *TcpClient) Unpack(headdata []byte) (head *Message, err error) {
    headbuf := bytes.NewReader(headdata)

    head = &Message{}

    // 读取Len
    if err = binary.Read(headbuf, binary.LittleEndian, &head.Len); err != nil {
        return nil, err
    }

    // 读取MsgId
    if err = binary.Read(headbuf, binary.LittleEndian, &head.MsgId); err != nil {
        return nil, err
    }

    // 封包太大
    //if head.Len > MaxPacketSize {
    //  return nil, packageTooBig
    //}

    return head, nil
}

func (this *TcpClient) Pack(msgId uint32, dataBytes []byte) (out []byte, err error) {
    outbuff := bytes.NewBuffer([]byte{})
    // 写Len
    if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil {
        return
    }
    // 写MsgId
    if err = binary.Write(outbuff, binary.LittleEndian, msgId); err != nil {
        return
    }

    //all pkg data
    if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil {
        return
    }

    out = outbuff.Bytes()

    return
}

func (this *TcpClient) SendMsg(msgID uint32, data proto.Message) {

    // 进行编码
    binary_data, err := proto.Marshal(data)
    if err != nil {
        fmt.Println(fmt.Sprintf("marshaling error:  %s", err))
        return
    }

    sendData, err := this.Pack(msgID, binary_data)
    if err == nil {
        this.conn.Write(sendData)
    } else {
        fmt.Println(err)
    }

    return
}

func (this *TcpClient) AIRobotAction() {
    //聊天或者移动

    //随机获得动作
    tp := rand.Intn(2)
    if tp == 0 {
        content := fmt.Sprintf("hello 我是player %d, 你是谁?", this.Pid)
        msg := &pb.Talk{
            Content: content,
        }
        this.SendMsg(2, msg)
    } else {
        //移动
        x := this.X
        z := this.Z

        randpos := rand.Intn(2)
        if randpos == 0 {
            x -= float32(rand.Intn(10))
            z -= float32(rand.Intn(10))
        } else {
            x += float32(rand.Intn(10))
            z += float32(rand.Intn(10))
        }

        //纠正坐标位置
        if x > 410 {
            x = 410
        } else if x < 85 {
            x = 85
        }

        if z > 400 {
            z = 400
        } else if z < 75 {
            z = 75
        }

        //移动方向角度
        randv := rand.Intn(2)
        v := this.V
        if randv == 0 {
            v = 25
        } else {
            v = 335
        }
        //封装Postsition消息
        msg := &pb.Position{
            X: x,
            Y: this.Y,
            Z: z,
            V: v,
        }

        fmt.Println(fmt.Sprintf("player ID: %d. Walking...", this.Pid))
        //发送移动MsgID:3的指令
        this.SendMsg(3, msg)
    }
}

/*
    处理一个回执业务
*/
func (this *TcpClient) DoMsg(msg *Message) {
    //处理消息
    //fmt.Println(fmt.Sprintf("msg id :%d, data len: %d", msg.MsgId, msg.Len))
    if msg.MsgId == 1 {
        //服务器回执给客户端 分配ID

        //解析proto
        syncpid := &pb.SyncPid{}
        proto.Unmarshal(msg.Data, syncpid)

        //给当前客户端ID进行赋值
        this.Pid = syncpid.Pid
    } else if msg.MsgId == 200 {
        //服务器回执客户端广播数据

        //解析proto
        bdata := &pb.BroadCast{}
        proto.Unmarshal(msg.Data, bdata)

        //初次玩家上线 广播位置消息
        if bdata.Tp == 2 && bdata.Pid == this.Pid {
            //本人
            //更新客户端坐标
            this.X = bdata.GetP().X
            this.Y = bdata.GetP().Y
            this.Z = bdata.GetP().Z
            this.V = bdata.GetP().V
            fmt.Println(fmt.Sprintf("player ID: %d online.. at(%f,%f,%f,%f)", bdata.Pid, this.X, this.Y, this.Z, this.V))

            //玩家已经成功上线
            this.isOnline <- true

        } else if bdata.Tp == 1 {
            fmt.Println(fmt.Sprintf("世界聊天,玩家%d说的话是: %s", bdata.Pid, bdata.GetContent()))
        }
    }
}

func (this *TcpClient) Start() {
    go func() {
        for {
            //read per head data
            headdata := make([]byte, 8)

            if _, err := io.ReadFull(this.conn, headdata); err != nil {
                fmt.Println(err)
                return
            }
            pkgHead, err := this.Unpack(headdata)
            if err != nil {
                return
            }
            //data
            if pkgHead.Len > 0 {
                pkgHead.Data = make([]byte, pkgHead.Len)
                if _, err := io.ReadFull(this.conn, pkgHead.Data); err != nil {
                    return
                }
            }

            //处理服务器回执业务
            this.DoMsg(pkgHead)
        }
    }()

    select {
    case <-this.isOnline:
        //自动AI业务
        go func() {
            for {
                this.AIRobotAction()
                time.Sleep(3 * time.Second)
            }
        }()
    }
}

func NewTcpClient(ip string, port int) *TcpClient {
    addrStr := fmt.Sprintf("%s:%d", ip, port)
    conn, err := net.Dial("tcp", addrStr)
    if err == nil {
        client := &TcpClient{
            conn:     conn,
            Pid:      0,
            X:        0,
            Y:        0,
            Z:        0,
            V:        0,
            isOnline: make(chan bool),
        }
        return client
    } else {
        panic(err)
    }
}

func main() {
    for i := 0; i < 1000; i++ {
        client := NewTcpClient("127.0.0.1", 8999)
        client.Start()
        time.Sleep(1 * time.Second)
    }

    // close
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, os.Kill)
    sig := <-c
    fmt.Println("=======", sig)
}

这里实际上一个TcpClient就是一个模拟的客户端,包含与服务器建立的链接conn、还有模拟玩家的基本信息(ID和坐标等)

TcpClient提供了Start()的启动方法,Start()中,其中读写的业务用一个单独的goroutine去装载。

AIRobotAction()是一个简单的AI自动化方法,他会随机的选择移动或者聊天。这样一个模拟客户端上线之后,就不会傻傻的站立在那里了。

我们也可以通过在main()中,更改循环变量i来进行循环建立多个客户端进行压力测试

func main() {
    for i := 0; i < 1000; i++ {
        client := NewTcpClient("127.0.0.1", 8999)
        client.Start()
        time.Sleep(1 * time.Second)
    }

    // close
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, os.Kill)
    sig := <-c
    fmt.Println("=======", sig)
}
【Zinx应用-MMO游戏案例-(10)模拟客户端AI模块】Golang轻量级并发服务器框架_第1张图片
mmo_ai_test.png

关于作者:

作者:Aceld(刘丹冰)
号:IT无崖子

mail: [email protected]
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld

原创声明:未经作者允许请勿转载, 如果转载请注明出处

你可能感兴趣的:(【Zinx应用-MMO游戏案例-(10)模拟客户端AI模块】Golang轻量级并发服务器框架)