Django框架课-多人联机对战 (二)-编写同步函数

多人联机对战 (二)-编写同步函数

唯一编号

image

在三个用户的每个终端上都有三个玩家u1,u2,u3(即自己和其他玩家)

有一个问题需要考虑,比如这个事件,user1在自己的终端中向u2发射了火球。那么这个事件在user2的终端上怎么体现出来是u1打了自己,这个事件又在user3的终端上怎么体现是u1打了u2。怎么区分出来谁是谁呢?

还有,比如有u1发射了一个火球,u2发射了一个火球。这两个火球又怎么分别在三台终端上区分出来呢?怎么知道哪个是u1的火球,哪个是u2的火球呢?

解决方案就是给每一个游戏对象加一个唯一编号。

playground/ac_game_object/zbase.js

class AcGameObject{
    constructor() {
        ...
        this.uuid = this.create_uuid();
    }

    create_uuid() { // 创建元素唯一标识id(一个随机的16位数)
        let res = "";
        for(let i = 0; i < 16; i ++)
        {
            let x = parseInt(Math.floor(Math.random() * 10));
            res += x;
        }
        return res;
    }
	...
}

给每个元素创建了uuid后,仔细想下,还有一个问题,user1,user2,user3每个玩家的终端都会为每一个元素创建一个uuid。这样同一个元素可能有了至少三个uuid。

想为一个元素统一成一个uuid的办法就是,属于哪个玩家的元素,就用哪个玩家终端里所创建的uuid。比如user1创建了玩家u1,和发射了5个火球,这个u1对象和5个火球对象都是属于user1的,就统一成user1终端里为这些对象所创建的uuid。

统一逻辑相同的元素的uuid:告诉服务器,然后让服务器为这场对战里的每名玩家通信广播。

playground/zbase.js

class AcGamePlayground {
    ...
    show(mode) {
        ...
        if (mode === "single mode") {
            ...
        } else if (mode === "multi mode") {
            this.mps = new MultiPlayerSocket(this);
            this.mps.uuid = this.players[0].uuid;
            this.mps.ws.onopen = function() {
                outer.mps.send_create_player();
            };
        }
    }
    ...
}

playground/socket/multiplayer/zbase.js

class MultiPlayerSocket {
    ...
    send_create_player() {
        let outer = this;
        this.ws.send(JSON.stringify({
            'event': 'create_player',
            'uuid': outer.uuid,
        }));
    }
}

create_player

后端

一个房间最多容纳3人
settings.py

...
ROOM_CAPACITY = 3

写后端处理函数:

js发起请求后,服务端寻找房间,将房间内玩家信息返回给前端,前端再请求后端创建玩家,后端创建玩家后群发给组内所有用户,在各个用户的地图上创建玩家。

playground/zbase.js

..
show(mode){
	..
	else if(mode === "multi mode")
	{
		..
		this.mps.ws.onopen = function() {
			outer.mps.send_create_player(outer.root.settings.username, outer.root.settings.photo);
		};
	}
}
..

playground/socket/multiplayer/zbase.js

class MultiPlayerSocket {
    ..
    send_create_player() {
        this.ws.send(JSON.stringify({
            let outer = this;
        	this.ws.send(JSON.stringify({
            	'event':"create_player",
            	'uuid': outer.uuid,
            	'username': username,
            	'photo': photo,
        }));
    }
    ..
}

consumers/multiplayer/index.py

from channels.generic.websocket import AsyncWebsocketConsumer
import json
from django.conf import settings
from django.core.cache import cache


class MultiPlayer(AsyncWebsocketConsumer):
    async def connect(self):
        # 先去找到一个房间,找到了就存在room_name里
        self.room_name = None
        for i in range(1000): # 上限1k房间
            name = "room-%d" % (i)
            # 房间空或者人数未满
            if not cache.has_key(name) or len(cache.get(name)) < settings.ROOM_CAPACITY:
                self.room_name = name
                break
        if not self.room_name: # 没有可用房间直接返回
            return

        await self.accept()

        if not cache.has_key(self.room_name): # 如果房间不存在就新建一个房间,有效期1h
            cache.set(self.room_name,[],3600)

        # 建立了连接之后,服务器向本地发送找到的房间里已有的玩家信息 (注意建立连接时没有创建玩家,下面有专门的创建玩家事件)
        for player in cache.get(self.room_name): 
            await self.send(text_data=json.dumps({
                    'event': "create_player",
                    'uuid': player['uuid'],
                    'username': player['username'],
                    'photo': player['photo'],
                }))
        await self.channel_layer.group_add(self.room_name, self.channel_name)


    async def disconnect(self, close_code):
        print('disconnect')
        await self.channel_layer.group_discard(self.room_name, self.channel_name)

    async def create_player(self,data):
        players = cache.get(self.room_name) # 找到当前对局的所有玩家(当前的对局已经找到了:self.room_name)
        # 将当前玩家加到players里,再更新redis里的players
        players.append({
            'uuid': data['uuid'],
            'username': data['username'],
            'photo': data['photo'],
        })
        cache.set(self.room_name, players, 3600) # 更新这个房间存在时间为1h
        # 服务器群发消息
        await self.channel_layer.group_send(
            self.room_name, # group的名字
            {
                # 群发的信息
                'type': "group_create_player", # 群发消息后,group组内的人接受此消息的函数名
                'event': "create_player",
                'uuid': data['uuid'],
                'username': data['username'],
                'photo': data['photo'],
            }
        )
    async def group_create_player(self, data): # 接收到消息,发送给前端
        await self.send(text_data=json.dumps(data))

    async def receive(self, text_data):
        data = json.loads(text_data)
        event = data['event'] # 前端发送过来一个event
        if event == "create_player": 
            await self.create_player(data)

前端js

class MultiPlayerSocket {
    constructor(playground) {
        this.playground = playground;

        this.ws = new WebSocket("wss://app1117.acapp.acwing.com.cn/wss/multiplayer/");

        this.start();
    }
    start() {
        this.receive();
    }
    receive() {
        let outer = this;
        this.ws.onmessage = function(e) {
            let data = JSON.parse(e.data);
            let uuid = data.uuid;
            if (uuid === outer.uuid) return false;

            let event = data.event;
            if (event === "create_player") {
                outer.receive_create_player(uuid, data.username, data.photo);
            }
        };
    }
    send_create_player(username, photo) {
        let outer = this;
        this.ws.send(JSON.stringify({
            'event': 'create_player',
            'uuid': outer.uuid,
            'username': username,
            'photo': photo,
        }));
    }
    receive_create_player(uuid, username, photo) {
        let player = new Player(
            this.playground,
            this.playground.width / 2 / this.playground.scale,
            0.5,
            0.05,
            "white",
            0.15,
            "enemy",
            username,
            photo,
        );
        player.uuid = uuid;
        this.playground.players.push(player);
    }
}

redis 调试语句:
打开 shell 交互

python3 manage.py shell
然后用 py3 交互进行 cache 调试

from django.core.cache import cache

def clear():
    for key in cache.keys('*'):
        cache.delete(key)

cache.keys('*') # 查询当前 redis 中所有 key

cache.get('room-1') # 查询当前 redis 中 key 为 room-1 的值

到目前为止,便可以在不同的窗口渲染同一批玩家了

posted @ 2023-03-07 17:08  r涤生  阅读(63)  评论(0编辑  收藏  举报