白桦的天空

第一次的心动,永远的心痛!
posts - 316, comments - 202, trackbacks - 1, articles - 16
  首页 :: 新随笔 :: 联系 :: 管理

公告

用XMLSocket实现多人连网对战的即时战略游戏(一)

Posted on 2005-04-08 11:26 白桦的天空 阅读(...) 评论(...)  编辑 收藏
曾经有人问过我:“能不能实现flash之间的对联?”
  我回答:“很遗憾,不能直接对联。”
  也曾经有人问我:“能不能用flash直接读取文件?”
  我也回答:“不能。”

  难道说我们没有了web服务器,就真的没有实现flash对联的方法了吗?
  难道说没有了web服务器和fso,就真的不能读写文件了吗?

  非也!!!因为flash为我们提供了XMLSocket。

  什么是socket呢?举个例子说吧,socket就象是一个两端带有插头的一根电线,我们只需要将这个插头往两边插座上面那么一插,嘿嘿,网络就这样通了。所以,我们实现flash对联的思路也随之而来:我们先自己编写个Server端(很遗憾的是flash中的XMLSocket对象并不能用来写server端,但是没有关系,用vc++写起来一样简单),然后用flash写个client端,这样,我们的client端就可以和server端进行通讯了;如果同时有几个flash的client端需要通讯,那么先将他们的数据发送到server端,然后再从server端发送到别的flash上,这样就实现了多个flash之间的对联了。很显然,在这里server端起的是个数据中转站的作用。

  有了socket,读写文件也变得简单的多,我们直接把要写入文件的东西发送到server端,考过2级的人都知道,用c语言控制文件的读写是多么的简单,而我们的server端正好是用vc++在写,难道我们实现不了读写文件吗?

  其实话说回来,web服务器本身就是一个很强大的server端,但是它不合我们的需要,因为它太复杂,太不够灵活,所以,我们要自己写。

  OK,废话就说这么多,以后的时间里,我们一起来编写一个可以实现多机对联的即时战略游戏,顺便领略XMLSocket的风采。

 一、即时战斗模型

  帝国里面的好东东真是不少,正当我为使用什么东西作为这篇XMLSocket教程的范例的时候,我发现了个好东西,那就是在帝国的“下载区”的“在线游戏代码”中的由turbine编写的“即时战斗模型”,麻烦大家“当”一个下来先:

http://www.flashempire.com/efe_flas/flasdown.php?id=109&name=rtsgame

  这个代码的确不错,它实现了对象的选取、运动和卷屏这些基本的东西,下面,我们用少量的篇幅对这个代码进行分析:

  在这个游戏代码的主场景中,总共有三个层,最上面的一个曾用来写代码,但是只定义了几个函数,代码如下(我分别加了注释,请仔细看):

//初始化播放器的属性,禁止缩放和显示菜单


fscommand("showmenu", "false");
fscommand("allowscale", "false");
var x ;
var y;

//隐藏鼠标,并用拖动一个mc来实现置换鼠标

Mouse.hide();
startDrag(_root.newmouse, true);

//向上卷屏的函数,由于卷屏时地图和飞船保持相对静止
//所以这个函数用了两个mc类型的参数,在调用的时候,
//将飞船和地图的mc作为实参。

function move_up(mc1,mc2) {
    mc1._y -= 5;
    mc2._y -= 5;
}

//向下卷屏的函数

function move_down(mc1,mc2) {
    mc1._y+= 5;
    mc2._y+= 5;
}

//向左卷屏的函数
function move_right(mc1,mc2) {
    mc1._x+=5;
    mc2._x+=5;
}

//向右卷屏的函数

function move_left(mc1,mc2) {
    mc1._x -= 5;
    mc2._x -= 5;
}

  主场景的最下面一层放的是地图,但是大家肯定注意到了在这一层不仅有一个实体名为map的mc,还有一个名为bounds的mc,那这个叫bounds的mc究竟是干什么的呢?其实,它才是实现卷屏的关键:大家仔细看就会发现,这个bounds比主场景就小那么一点点,它的作用就是当我们鼠标的位置在这个bounds之外的时候,就开始卷屏。

  第二层是关键,它上面有三个mc,一个名为square_x的框框,它的作用就是为了实现我们鼠标在场景内拖动时出现一个选取框的效果;还有一个mc是飞船;而重点则在那个名为control的空mc上,因为大部分的代码都在这里实现:

onClipEvent (mouseDown) {
    time = getTimer()-down_time;
    if (time>0 && time<300) {
        with (_root.ship) {
            gotoAndStop(1);
        }
    }
    down_time = getTimer();
    _root.MouseDown = true;

//记录鼠标按下的起点,并且复制square_x这个mc,为实现鼠标拖动时
//出现选取框打下基础:

    X1 = _root._xmouse;
    Y1 = _root._ymouse;
    duplicateMovieClip(_root.square_x, "shape", 1000);
}

onClipEvent (mouseUp) {

//判断鼠标拖动的范围有没有经过飞船,如果有的话,飞船的mc
//跳到第二帧,显示出被选中的状态
    if (_root.squareBounds.xMax>_root.shipBounds.xMax && 
_root.squareBounds.yMax>_root.shipBounds.yMax && 
_root.squareBounds.xMin<_root.shipBounds.xMin && 
_root.squareBounds.yMin<_root.shipBounds.yMin) {
        with (_root.ship) {
            gotoAndStop(2);
        }
    }
    _root.MouseDown = false;

//选取框消失
    removeMovieClip(_root.shape);
}

onClipEvent (enterFrame) {

//以下的代码分别是为了记录飞船的大小和位置、选取框
//的大小和位置、地图的大小和位置等等,主要用到了
//getBounds()函数,这个函数我们后面讲解:

    _root.x = _root.ship._x;
    _root.y = _root.ship._y;
    _root.shipBounds = _root.ship.getBounds(_root);
    _root.mapBounds = _root.map.getBounds(_root);
    _root.boundsBounds = _root.bounds.getBounds(_root);
    X2 = _root._xmouse;
    Y2 = _root._ymouse;
    if (_root.MouseDown) {

//鼠标拖动时画出选取框
        _root.squareBounds = _root.shape.getBounds(_root);
        with (_root.shape) {
            _x = ((X2-X1)/2)+X1;
            _y = ((Y2-Y1)/2)+Y1;
            _xscale = X1-X2;
            _yscale = Y1-Y2;
        }
    }

//如果鼠标在屏幕的最左边,则向左卷屏,并将鼠标显示为向左的双箭头

    if ( _root._xmouse>_root.boundsBounds.xMax) {
        if (_root.mapBounds.xMax>495) {
            _root.mapmove = true;
            _root.move_left(_root.map, _root.ship);
            _root.newmouse.gotoAndStop(2);
        }
    }

//如果鼠标在屏幕的最右边,则向右卷屏,并将鼠标显示为向右的双箭头

    if (_root._xmouse<_root.boundsBounds.xMin) {
        if (_root.mapBounds.xMin<5) {
            _root.mapmove = true;
            _root.move_right(_root.map, _root.ship);
            _root.newmouse.gotoAndStop(3);
        }
    }

//如果鼠标在屏幕的最上边,则向上卷屏,并将鼠标显示为向上的双箭头

    if (_root._ymouse>_root.boundsBounds.yMax) {
        if (_root.mapBounds.yMax>395) {
            _root.mapmove = true;
            _root.move_up(_root.map, _root.ship);
            _root.newmouse.gotoAndStop(5);
        }
    }

//如果鼠标在屏幕的最下边,则向下卷屏,并将鼠标显示为向下的双箭头
    if (_root._ymouse<_root.boundsBounds.yMin) {
        if (_root.mapBounds.yMin<5) {
            _root.mapmove = true;
            _root.move_down(_root.map, _root.ship);
            _root.newmouse.gotoAndStop(4);
        }
    }

  //如果鼠标在bounds这个mc的范围内,则不卷屏,还原鼠标样式

    if (_root._xmouse<_root.boundsBounds.xMax && 
_root._xmouse>_root.boundsBounds.xMin 
&&_root._ymouse<_root.boundsBounds.yMax 
&&_root._ymouse>_root.boundsBounds.yMin) {
        _root.newmouse.gotoAndStop(1);
    }
}

  正如前面所讲,卷屏的实现是通过判断鼠标与bounds的位置关系所决定的,其中用到了getBounds()函数,这个函数的用法如下:

clipBounds = clip.getBounds(_root);

clipBounds是这个函数返回的对象,它具有 xMin、xMax、yMin 和 yMax属性,比如下面将另一个影片剪辑实例 clip2 与 clip 并排放置:

clip2._x = clipBounds.xMax;

  到这里,主场景中重要的代码就讲完了,但是还有一个重要的地方,那就是ship剪辑中,我们双击ship剪辑打开,找到第二帧中的那个小圈圈,上面的代码如下:

onClipEvent (load) {
    n_x = _root.x;
    n_y = _root.y;
    PI = 180/Math.PI;
}

onClipEvent (enterFrame) {
    if (_root.MouseDown) {
        _root.mapmove = false;
        n_x = _root._xmouse;
        n_y = _root._ymouse;
        m_x = n_x-_parent._x;
        m_y = n_y-_parent._y;
        _root.ship.photo._rotation = PI*Math.atan2(m_y, m_x)+90;
    }
    if (_root.mapmove) {
        setProperty(_parent, _x, _parent._x);
        setProperty(_parent, _y, _parent._y);
    } else {
        setProperty(_parent, _x, _parent._x+(n_x-_parent._x)/50);
        setProperty(_parent, _y, _parent._y+(n_y-_parent._y)/50);
    }
}


  它实现的功能就是让我们的飞船跟着我们的鼠标移动和转向,这个实现很简单,所以我不多讲了。

  然而,这里正是我们实现连网游戏的重点地方,因为我们要发给服务器的数据都在这里:我们将我们自己的坐标发到服务器,同时从服务器接受其它人控制的飞船的坐标,这就是我们的思路。