虽然从目前的角度来说,这个教程在不断的前进着。但从寻找资源来说,进展困难,我本来想让10.0的教程的内容为实现一个玩家在地图中行走,但资源找来找去,只找到了相对合适的地图资源,人物的资源一直没有找到,因此就先实现了地图资源。
考虑到目前客户端j2me的局限,我决定j2me的jar中,仅留有代码,所有的地图资源等,都通过服务器下载。因此服务器端必须对这一点进行支持。也因此增加了两个命令。而代码的更新,更大的体现在客户端。
先看下目前客户端的截图吧:
上述截图的资源完全来自开源的daimonin,我本身不会进行美工的工作内容,如果可以的话,就不用这么辛苦的找资源了。
为了实现资源的下载,在客户端,我修改了底层传输支持的Netstream的机制:
int stillbytes = 0;
if ( ( stillbytes = is.available() ) > 0 ) {
byte[] buf = new byte[stillbytes];
is.read( buf );
int buf_index = 0;
while (stillbytes > 0) {
int read_size = stillbytes;
if ( stillbytes + iReceiveEndindex > RECEIVEBUFLENGTH ) {
System.out.println("buf too small stillbytes=" + stillbytes + "receiveendindex" + iReceiveEndindex);
read_size = RECEIVEBUFLENGTH - iReceiveEndindex;
}
System.arraycopy( buf, buf_index, iReceivebuf, iReceiveEndindex, read_size );
iReceiveEndindex += read_size;
buf_index += read_size;
stillbytes -= read_size;
for ( ; iReceiveEndindex >= 8; ) {
int curindex = 0;
int alength = bytetoint( iReceivebuf, curindex );
curindex += 4;
int cmd = bytetoint( iReceivebuf, curindex );
curindex += 4;
alength -= 4;
System.out.println("get");
for ( int i = 0; i < alength+8; i++ ) {
int high = 0;
int low = 0;
high = ((iReceivebuf[i] & 0xf0) >> 4);
low = (iReceivebuf[i] & 0x0f);
System.out.print(" " + DIGITS[high]+DIGITS[low] );
}
System.out.println("");
if ( curindex + alength > RECEIVEBUFLENGTH ) {
System.out.println("error data");
} else if ( curindex + alength > iReceiveEndindex ) {
System.out.println("wait data" + curindex + "::"+alength+"::"+iReceiveEndindex);
break;
} else {
Packet packet = new Packet( alength, cmd, iReceivebuf, curindex );
curindex += alength;
System.arraycopy( iReceivebuf, curindex, iReceivebuf, 0, RECEIVEBUFLENGTH - curindex );
iReceiveEndindex -= curindex;
iPacketArray.addElement( packet );
}
}
}
{
buf = null;
};
}
中间有一段打印接收到数据的16进制的代码,可以屏蔽掉,现在保留主要是为了方便调试用。
由于使用TCP/IP进行的传输,从底层硬件来讲,不会出现漏掉某个数据包,但如果程序接收算法不对的话,则可能会漏掉。我在测试的时候,服务器最多每次传输1024字节,但客户端每次却可能接收超过4096字节,因此,必须按照规则自己分包。
对于daimonin的地图编辑器,虽然写了个插件来导出资源,但有些内容还是不清楚如何导出,比如每个地图元件的详细信息。而客户端中,我将所有的动画都在播放,然后场景中有一个门的开关动画,就一直在那里开合开合的播放。对于动画的导出,我用erlang写了一个简单的导出脚本:
%% @doc 对动画中每一帧的文件名生成列表.
animation([]) ->
[];
animation([Name|Rest]) ->
L = length(Name),
[<>, Name] ++ animation(Rest).
%% @doc 根据参数写动画文件,Name1为动画名称,Face为面数,List为帧列表。均不需要添加data/的路径,客户端请求数据的时候没有data/的前缀,文件读取自动添加.
write_ani(Name1, Face, List) ->
Name = "data/"++Name1,
case file:open(Name, [write, binary, raw]) of
{ok, S} ->
Length = length(List),
Data1 = list_to_binary([<>, <>, animation(List)]),
CRC32 = erlang:crc32(Data1),
Data = list_to_binary([<>, Data1]),
case file:pwrite(S, 0, Data) of
ok ->
map:register_obj([{"data/"++File, "data/"++File, file}|| File <- List]);
{error, Reason} ->
{error, Reason}
end
end.
上出代码转换动画信息,并将动画中的每个元件注册到数据库中,因为只有注册到数据库中的元件才被允许传给客户端。具体的用法可以看map:test_load。
现在服务器的启动,因为需要做太多的设置,因此我也做了个辅助测试的,调用start-dev.sh启动之后,可以调用server:start_test来启动5个服务器的节点,启动3个属于test1服务器,2个属于test2服务器。接下来调用map:test_load(),可以完成相关的地图设置。这些调用,只用在第一次编译之后调用一次即可,以后不用再重新设置。接下来还是找资源紧要。
最后附上目前的服务器结构图: