客户端向leafserver传输玩家所在位置,数据是json格式,使用大端序len+data,len占两个字节,可以在leafserver中配置。gs中没有逻辑判断只是广播玩家位置消息,对方玩家会因一定延迟导致卡顿,所以使用了while循环确保能够收到对方玩家的逻辑帧。另外在step里边直接同步逻辑实际上也是不妥的,但这里只作为学习,不用计较。
资源地址https://download.csdn.net/download/Jailman/68047268
to be continued,不断完善中……

python使用socket通信的示例脚本

#!/usr/bin/python
#coding=utf8

import socket
import struct
import time
import json

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
client.connect(("127.0.0.1", 3563))

while True:

    msg_data = b'''{"PLAYER_POSITION": {"pos_x": 300.13233, "pos_y": 200.13233}}'''

    # 计算len+data长度,len占2个字节,可设置
    msg_len = len(msg_data)

    # 将数据长度转换为大端序
    big_endian_head = struct.pack(">H", msg_len)
    # 构造数据
    fin_data = bytes(big_endian_head) + bytes(msg_data)

    print("SND MSG LEN: " + str(len(fin_data)))
    print("SND MSG: " + msg_data)  

    # 发送数据
    client.send(fin_data)

    # 接收数据
    info = client.recv(1024)
    # 解析数据长度
    rcv_msg_len = struct.unpack(">H",info[:2])[0]
    rsv_msg = str(info[2:rcv_msg_len+2])
    print("RCV MSG LEN: " + str(rcv_msg_len))
    print("RCV MSG: " + rsv_msg)


    position = json.loads(rsv_msg)
    pos_x = position["PLAYER_POSITION"]["Pos_x"]
    pos_y = position["PLAYER_POSITION"]["Pos_y"]

    with open("client_location", "a") as f:
        f.write("player_position: {} {}\n".format(str(pos_x), str(pos_y)))

    time.sleep(0.1)

完全的网游雏形,客户端使用while粗暴的解决补帧逻辑(可以联想一下外挂的原理),缺点就是对方掉线我方直接卡死!
cocos2d客户端1

#!/usr/bin/python
#coding=utf8

from pyglet.window import key
from cocos import actions, layer, sprite, scene
from cocos.director import director
from cocos.actions import *
import socket
import struct
import json



class Frame_Sync(actions.Move):
    def __init__(self, player_id, client, local):
        super(Frame_Sync, self).__init__()
        self.player_id = player_id
        self.client = client
        self.local = local
    def step(self, dt):
        super(Frame_Sync, self).step(dt)

        if self.local:
            # Set the object's velocity. 
            velocity_x = 200 * (keyboard[key.RIGHT] - keyboard[key.LEFT])
            velocity_y = 200 * (keyboard[key.UP] - keyboard[key.DOWN])
            
            if self.target.position[0] > 960:
                self.target.velocity = (-100, velocity_y)
            elif self.target.position[0] < 0:
                self.target.velocity = (100, velocity_y)
            elif self.target.position[1] < 0:
                self.target.velocity = (velocity_x, 100)
            elif self.target.position[1] > 640:
                self.target.velocity = (velocity_x, -100)
            else:
                self.target.velocity = (velocity_x, velocity_y)

            # print(self.target.position)
            msg_data = b'{"PLAYER_STATUS": {"id": %s, "pos_x": %s, "pos_y": %s}}' % (self.player_id, self.target.position[0], self.target.position[1])
            # 计算len+data长度,len占2个字节,可设置
            msg_len = len(msg_data)

            # 将数据长度转换为大端序
            big_endian_head = struct.pack(">H", msg_len)
            # 构造数据
            fin_data = bytes(big_endian_head) + bytes(msg_data)
            print("SND MSG LEN: " + str(len(fin_data)))
            print("SND MSG: " + msg_data)  
            # 发送数据
            self.client.send(fin_data)
        else:
            # tick计数,确保无法收到对方的动作后退出
            tick = 0
            # while循环类似补帧的作用,可以确保对方玩家动作连续
            while True:
                # 接收数据
                info = self.client.recv(1024)
                # 解析数据长度
                rcv_msg_len = struct.unpack(">H",info[:2])[0]
                rsv_msg = str(info[2:rcv_msg_len+2])
                print("RCV MSG LEN: " + str(rcv_msg_len))
                print("RCV MSG: " + rsv_msg)

                position = json.loads(rsv_msg)
                id = position["PLAYER_STATUS"]["Id"]
                pos_x = position["PLAYER_STATUS"]["Pos_x"]
                pos_y = position["PLAYER_STATUS"]["Pos_y"]
                if id == self.player_id:
                    self.target.position = (pos_x, pos_y)
                    break
                tick += 1
                if tick > 100:
                    break
            



# Main class

def run_scene():
    global keyboard # Declare this as global so it can be accessed within class methods.

    # Initialize the window.
    director.init(width=960, height=640, autoscale=False, resizable=True)

    # Create a layer and add a sprite to it.
    player_layer = layer.Layer()
    bg = sprite.Sprite('images/bg1.jpeg')
    me = sprite.Sprite('images/ufo1.png')
    him = sprite.Sprite('images/ufo2.png')
    player_layer.add(bg)
    player_layer.add(me)
    player_layer.add(him)

    # Set initial position and velocity.
    bg.position = (480, 320)
    me.position = (400, 100)
    me.velocity = (0, 0)
    him.position = (400, 100)
    him.velocity = (0, 0)

    # Set the sprite's movement class.
    my_player_id = 1
    local = True
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    client.connect(("127.0.0.1", 3563))
    me.do(Frame_Sync(my_player_id, client, local))
    his_player_id = 2
    local = False
    him.do(Frame_Sync(his_player_id, client, local))


    # me.do(RotateBy(180, duration=3))

    # Create a scene and set its initial layer.
    main_scene = scene.Scene(player_layer)

    # Attach a KeyStateHandler to the keyboard object.
    keyboard = key.KeyStateHandler()
    director.window.push_handlers(keyboard)

    # Play the scene in the window.
    director.run(main_scene)


if __name__ == '__main__':
    run_scene()








# 背景音乐下载
# https://www.sucaibar.com/yinxiao/chunyinyue.html


# 安装模块
# pip install pyglet==1.4.10
# pip install cocos2d


# cocos2d python samples
# https://github.com/los-cocos/cocos/tree/master/samples
# tutorials
# https://github.com/liamrahav/cocos2d-python-tutorials/tree/master/basics

客户端2

#!/usr/bin/python
#coding=utf8

from pyglet.window import key
from cocos import actions, layer, sprite, scene
from cocos.director import director
from cocos.actions import *
import socket
import struct
import json
        

class Frame_Sync(actions.Move):
    def __init__(self, player_id, client, local):
        super(Frame_Sync, self).__init__()
        self.player_id = player_id
        self.client = client
        self.local = local
    def step(self, dt):
        super(Frame_Sync, self).step(dt)

        if self.local:
            # Set the object's velocity. 
            velocity_x = 200 * (keyboard[key.RIGHT] - keyboard[key.LEFT])
            velocity_y = 200 * (keyboard[key.UP] - keyboard[key.DOWN])
            
            if self.target.position[0] > 960:
                self.target.velocity = (-100, velocity_y)
            elif self.target.position[0] < 0:
                self.target.velocity = (100, velocity_y)
            elif self.target.position[1] < 0:
                self.target.velocity = (velocity_x, 100)
            elif self.target.position[1] > 640:
                self.target.velocity = (velocity_x, -100)
            else:
                self.target.velocity = (velocity_x, velocity_y)

            # print(self.target.position)
            msg_data = b'{"PLAYER_STATUS": {"id": %s, "pos_x": %s, "pos_y": %s}}' % (self.player_id, self.target.position[0], self.target.position[1])
            # 计算len+data长度,len占2个字节,可设置
            msg_len = len(msg_data)

            # 将数据长度转换为大端序
            big_endian_head = struct.pack(">H", msg_len)
            # 构造数据
            fin_data = bytes(big_endian_head) + bytes(msg_data)
            print("SND MSG LEN: " + str(len(fin_data)))
            print("SND MSG: " + msg_data)  
            # 发送数据
            self.client.send(fin_data)
        else:
            # tick计数,确保无法收到对方的动作后退出
            tick = 0
            # while循环类似补帧的作用,可以确保对方玩家动作连续
            while True:
                # 接收数据
                info = self.client.recv(1024)
                # 解析数据长度
                rcv_msg_len = struct.unpack(">H",info[:2])[0]
                rsv_msg = str(info[2:rcv_msg_len+2])
                print("RCV MSG LEN: " + str(rcv_msg_len))
                print("RCV MSG: " + rsv_msg)

                position = json.loads(rsv_msg)
                id = position["PLAYER_STATUS"]["Id"]
                pos_x = position["PLAYER_STATUS"]["Pos_x"]
                pos_y = position["PLAYER_STATUS"]["Pos_y"]
                if id == self.player_id:
                    self.target.position = (pos_x, pos_y)
                    break
                tick += 1
                if tick > 100:
                    break

# Main class

def run_scene():
    global keyboard # Declare this as global so it can be accessed within class methods.

    # Initialize the window.
    director.init(width=960, height=640, autoscale=False, resizable=True)

    # Create a layer and add a sprite to it.
    player_layer = layer.Layer()
    bg = sprite.Sprite('images/bg1.jpeg')
    me = sprite.Sprite('images/ufo2.png')
    him = sprite.Sprite('images/ufo1.png')
    player_layer.add(bg)
    player_layer.add(me)
    player_layer.add(him)

    # Set initial position and velocity.
    bg.position = (480, 320)
    me.position = (400, 100)
    me.velocity = (0, 0)
    him.position = (400, 100)
    him.velocity = (0, 0)

    # Set the sprite's movement class.
    my_player_id = 2
    local = True
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    client.connect(("127.0.0.1", 3563))
    me.do(Frame_Sync(my_player_id, client, local))
    his_player_id = 1
    local = False
    him.do(Frame_Sync(his_player_id, client, local))


    # me.do(RotateBy(180, duration=3))

    # Create a scene and set its initial layer.
    main_scene = scene.Scene(player_layer)

    # Attach a KeyStateHandler to the keyboard object.
    keyboard = key.KeyStateHandler()
    director.window.push_handlers(keyboard)

    # Play the scene in the window.
    director.run(main_scene)


if __name__ == '__main__':
    run_scene()








# 背景音乐下载
# https://www.sucaibar.com/yinxiao/chunyinyue.html


# 安装模块
# pip install pyglet==1.4.10
# pip install cocos2d


# cocos2d python samples
# https://github.com/los-cocos/cocos/tree/master/samples
# tutorials
# https://github.com/liamrahav/cocos2d-python-tutorials/tree/master/basics

两个客户端均同leafserver通信,并得到所有的游戏广播消息
leafserver修改
msg/mgs.go

package msg

import (
	"github.com/name5566/leaf/network/json"
)

// 使用默认的 JSON 消息处理器(默认还提供了 protobuf 消息处理器)
var Processor = json.NewProcessor()

func init() {
	// 这里我们注册了一个 JSON 消息 PLAYER_STATUS
	Processor.Register(&PLAYER_STATUS{})
}

// 一个结构体定义了一个 JSON 消息的格式
// 消息名为 PLAYER_STATUS
type PLAYER_STATUS struct {
	Id   int
	Pos_x float32
	Pos_y float32
}

gate/router.go

package gate

import (
	"server/game"
	"server/msg"
)

func init() {
	// 这里指定消息 PLAYER_STATUS 路由到 game 模块
	// 模块间使用 ChanRPC 通讯,消息路由也不例外
	msg.Processor.SetRouter(&msg.PLAYER_STATUS{}, game.ChanRPC)
}

game/internal/chanrpc.go广播消息

package internal

import (
	"github.com/name5566/leaf/gate"
)

var agents = make(map[gate.Agent]struct{})

func init() {
	skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)
	skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)
}

func rpcNewAgent(args []interface{}) {
	a := args[0].(gate.Agent)
	// _ = a
	agents[a] = struct{}{}
}

func rpcCloseAgent(args []interface{}) {
	a := args[0].(gate.Agent)
	// _ = a
	delete(agents, a)
}

game/internal/handler.go

package internal

import (
	"github.com/name5566/leaf/log"
	// "github.com/name5566/leaf/gate"
	"reflect"
	"server/msg"
)

func init() {
	// 向当前模块(game 模块)注册 PLAYER_STATUS 消息的消息处理函数 handleplayerstatus
	handler(&msg.PLAYER_STATUS{}, handleplayerstatus)
}

func handler(m interface{}, h interface{}) {
	skeleton.RegisterChanRPC(reflect.TypeOf(m), h)
}

func handleplayerstatus(args []interface{}) {
	// 收到的 PLAYER_POSITION 消息
	m := args[0].(*msg.PLAYER_STATUS)
	// 消息的发送者
	// a := args[1].(gate.Agent)

	// 输出收到的消息的内容
	log.Debug("PLAYER_STATUS: %v,%v,%v", m.Id, m.Pos_x, m.Pos_y)

	// 给发送者回应一个 PLAYER_POSITION 消息
	// a.WriteMsg(&msg.PLAYER_POSITION{
	// 	Pos_x: m.Pos_x,
	// 	Pos_y: m.Pos_y,
	// })

	for a := range agents {
		a.WriteMsg(&msg.PLAYER_STATUS{
			Id:   m.Id,
			Pos_x: m.Pos_x,
			Pos_y: m.Pos_y,
		})
	}
}

效果图
在这里插入图片描述

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐