Gamemaker studio2经验(2)——TCP联机

分类栏目:gamemaker教程

290

问题概述

众所周知gamemaker是一款制作2d游戏的优秀引擎,但是落后的弱联网机制始终是一个坑。所幸在gms2中,yoyogames集团加入了TCP的联机机制,这也为gm系列引擎制作联网游戏带来了希冀。

下面用一个最简单的“红蓝球游戏”作为我们的联机用例。用例的基本描述如下:

进入游戏先选择host和join,host方以红球身份进入房间,join方以蓝球身份进入房间。双方都以WSAD为移动方式,并且双方在屏幕上需要看得到对方的球被操控移动。

 

代码实现

首先声明一下,如果点击host,那么给全局变量global.net赋值1,如果点击join则赋值2,表示现在玩家是以什么样的身份进入游戏。

之后host方进入一个房间,这个房间有一个obj_server,join方进入另一个房间,这个房间有一个obj_joiner。

 

obj_server

创建

第一行表示创建一个新的tcp连接,括号中第一个参量是tcp的写法,第二个参量是端口(自定),第三个参量是客户端数量的上限(尽量不超过1000)

sendmes表示服务器要发送的数据内容,刚开始不发送的时候先清零

timer是一个计时器,规定每5步发送一次数据

 

network_create_server(network_socket_tcp,250,10);

sendmes=0;

timer=5;

步事件

这里面需要介绍一下buffer。buffer是一种缓冲区,可以起到优化空间的效果,而游戏引擎里也将buffer作为发送数据的载体,因此我们定义一个新的缓冲区buf。

发送的数据sendmes赋值为红球(即host方)的x值+y值×1000,因为我这里的地图坐标上限是999,因此这样一个六位数可以把x坐标和y坐标都储存下来,最后发送一个数字就可以了。

当计时器timer减到0并且判定网络连接已经连接成功了(global.connect==1),我们开始处理发送数据的事情。(具体怎么判定网络连接成功见后文)

首先要使用buffer_create()函数申请一个buffer(类似malloc)。第一个参量是缓冲区的容量,这个可以尽量大一点;第二个参量是buffer的类型,这个可以参考下表

 

类型 描述

buffer_fixed A buffer of a fixed size in bytes. The size is set when the buffer is created and cannot be changed again.

buffer_grow A buffer that will grow dynamically as data is added. You create it with an initial size (which should be an approximation of the size of the data expected to be stored), and then it will expand to accept further data that overflows this initial size.

buffer_wrap A buffer where the data will wrap. When the data being added reaches the limit of the buffer size, the overwrite will be placed back at the start of the buffer, and further writing will continue from that point.

buffer_fast This is a special “stripped down” buffer that is extremely fast to read/write to. However it can only be used with buffer_u8 data types, and must be 1 byte aligned. (Information on data types and byte alignment can be found further down this page).

第三个参量是数据排列字节数,详情可见gms2中万能的F1说明,这里不再赘述。

申请完缓冲区后,便可以使用buffer_write()函数向缓冲区中写入数据。第一个参量为声明的缓冲区,第二个参量为数据类型(具体写哪个数据类型可以F1自己查),第三个便是要发送的数据内容。

写完后,使用network_send_packet()函数发送你的数据包,第一个参量是接收对象,第二个参量是写好的缓冲区,第三个参量是总尺寸,这个写buffer_get_size()是绝对没错的。

发送完后,你的缓冲区就没有意义了,因此需要用buffer_delete()函数释放空间。

 

var buf;

sendmes=global.redx+global.redy*1000;

if(timer>0)timer-=1;

if(global.connect==1&&timer<=0)

{

buf=buffer_create(10000,buffer_fixed,4);

buffer_write(buf,buffer_u32,sendmes);

network_send_packet(client,buf,buffer_get_size(buf));

buffer_delete(buf);

timer=5;

}

 

绘制事件

打印出getmes的内容,这里纯粹是为了方便debug。

 

draw_text(5,5,getmes);

 

Async-Networking

Async本质上就是一种需要特殊条件才能触发的步事件,例如Networking就是一种触发条件,顾名思义就是“只要连接上网络了就触发”。当客户端成功连接上这里的服务器后,我们的服务器就会进入到这里,开始处理连接成功以后的琐事。

首先,我们需要再引入一个新概念:“map”。

我们理解map为一个外交官,当两国进行交涉后,外交官将会得到很多信息,比如他国的地理位置、他国的国际代号、他国接下来想做些什么等等等等。

这时候,我们想要从外交官那里套得信息,不能简单地问:“嘿,外交官,你知道关于X国的什么信息了”,这样太笼统。正确的问法是:“嘿,外交官,X国的地理位置在哪?”“嘿,外交官,X国的国际代号是什么?”“嘿,外交官,X国现在想对我国做些什么?”

这里面map就如同外交官一样,当服务器与客户端成功连接,那么map就会身为服务器的外交官与客户端进行交涉,之后他会得到若干种信息,分别是:

 

信息类型 含义

type 客户端的动机(X国想做什么?)

socket 客户端的id(X国的代号是什么?)

ip 客户端的地理位置(X国的地理位置是什么?)

buffer 客户端发来的数据(X国想跟我们说什么?)

这个应该很好懂,不多赘述。接下来开始写代码:

首先我们要赋予map这个使命,让他=async_load

然后!我们可以通过ds_map_find_value(map,“type”)来获取客户端的动机(动机可以有很多,比如联网,比如发送数据,比如失去连接)

这时候把他的动机赋值给一个新变量type,然后接下来检测:如果动机是连接上了(type==network_type_connect),那么我们就让global.connect=1,此时client和ip分别运用ds_map_find_value()获取客户端的id和地址。

如果动机是接受到客户端发来的数据了,那么我们就创建一个新的缓冲区,来接受发来的缓冲区,并使用buffer_read()函数来提取缓冲区的数据,放到getmes中。之后呢,x值和y值就可以由接收到的6位数来确定了。

 

var map=async_load;

var type=ds_map_find_value(map,"type");

if (type==network_type_connect)

{

global.connect = 1;                             //表示连接上了

client=ds_map_find_value(map,"socket");         //client表示接受的客户id

ip=ds_map_find_value(map,"ip");

}

if (type==network_type_data)

{

buffer_create(10000,buffer_fixed,4);

buf=ds_map_find_value(map,"buffer");

getmes=buffer_read(buf,buffer_s32);

global.bluex=getmes mod 1000;

global.bluey=getmes / 1000;

}

 

obj_joiner

对于客户端,首先我们要定义一个socket(即id),表示我们创建了一个tcp类型的接入口,然后通过network_connect()函数来连接服务器。第一个参量是刚创建的socket,第二个参量是服务器的地址,第三个参量是服务器的端口。

 

创建

socket=network_create_socket(network_socket_tcp);

network_connect(socket,"XXXXX",250);

步事件

这里面的内容和obj_server异曲同工(几乎一样),不再赘述

 

var buf;

sendmes=global.bluex+global.bluey*1000;

if(global.connect==1)

{

buf=buffer_create(10000,buffer_fixed,4);

buffer_write(buf,buffer_u32,sendmes);

network_send_packet(server,buf,buffer_get_size(buf));

buffer_delete(buf);

}

绘制事件

同上

 

draw_text(5,5,getmes);

Async-Networking

对于客户端而言,核心服务器永远只有一个,因此一旦连接成功,我们不需要判定type是否为network_type_connect便可以开始连接后的操作。

 

map=async_load;

type=ds_map_find_value(map,"type");

server=ds_map_find_value(map,"id");         //server表示服务器的id

global.connect = 1;                             //表示连接上了

if (type==network_type_data)

{

buffer_create(10000,buffer_fixed,4);

buf=ds_map_find_value(map,"buffer");

getmes=buffer_read(buf,buffer_s32);

global.redx=getmes mod 1000;

global.redy=getmes / 1000;

}

 

之后服务器和客户端每帧都会收到一个getmes,确定完x和y后便将小球按这个坐标进行定位即可。