Django框架课-多人联机对战 (二)-编写同步函数
多人联机对战 (二)-编写同步函数
唯一编号
在三个用户的每个终端上都有三个玩家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 的值
到目前为止,便可以在不同的窗口渲染同一批玩家了