[此文来源于互联网,牛C网只负责收集整理]
前段时间用Flash做了个网游的Demo,通讯用的是Socket。曾承诺写个教程,现在有空就把它写写吧。
先从FLASH说起。我要达到的效果是点击地面,人物就走到点击的地点。思路:一个鼠标监听器监听鼠标的点击事件,把X座标和Y座标传到角色,做为角色的目的地。角色每一帧都向这个目的地移动一点点。
role_mc为场景里的一个MovieClip

如图,角色在A点,要移动到B点去,方向就是AB。C点是下一帧角色将会出现的点,那向量AC就相当于角色的速度。将向量AC分解到两轴就得到x方向上的向量AE和y方向上的向量AD,这就是角色在这帧里需要移动的xy值了。角色的速度是已知的,要得到两个分解的向量,就只需要知道角度就行了。角度也可以根据A点和B点得到。我把角色面向右边时定为0度,则向上时为-90度,向下为90度,向左为180度。之所以这么定是为了计算方便。如Math.atan2(role_mc.y-role_mc._y, role_mc.x-role_mc._x)就可以直接到得角色面向的方向了(注意,Math.atan2得到的是以弧度为单位的数,不是角度)。所以把AS改写成:
[role.png]

[head.png]:

将两张图导入到FLASH的role_mc里。因为我是把身体和头部所有方向都做成一张图,所以要用遮照来只显示需要的那个。将第一帧做成第一个方向的站立,给帧起名stand_1,加AS:stop();后边若干帧做成行走的动画,给帧起名run_1,加AS:play();在行走动画的后边加上一帧空白关健帧,加AS:gotoAndPlay("run_1")。这就完成了一个方向。在这一段后边加上其他几个方向。我做的是方向1为向下,2为左下,3为左,4为左上,5为上。其他三个方向可以之后用AS将234方向做水平翻转达到。也许我说得不是很明白,看我的这个文件(teach1.fla)就知道了。做好这一步之后,就可以在moveRole函数里加些东西,让角色做出相应的动作。我们先画个图来看一看角色的angle值分别代表角色面向哪个方向。见下图
[2.gif]:

图中的红线就是每个方向的分隔线,把角度也列出来了,用弧度为单位是为了方便以后的计算。把AS也修改了一下就达到行走效果了。
前段时间用Flash做了个网游的Demo,通讯用的是Socket。曾承诺写个教程,现在有空就把它写写吧。
先从FLASH说起。我要达到的效果是点击地面,人物就走到点击的地点。思路:一个鼠标监听器监听鼠标的点击事件,把X座标和Y座标传到角色,做为角色的目的地。角色每一帧都向这个目的地移动一点点。
role_mc为场景里的一个MovieClip
role_mc.x = role_mc._x;
role_mc.y = role_mc._y;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = this.x-this._x>0 ? 1 : -1;
}
if (this.y != this._y) {
this._y = this.y-this._y>0 ? 1 : -1;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
}
};
}
试一下效果,发现角色会斜45度角移动到与目的地垂直或者水平后再垂直或水平运动。这是move函数里的算法没写好。我们现在来想想我想需要的是怎么样的移动。[1.gif]role_mc.y = role_mc._y;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = this.x-this._x>0 ? 1 : -1;
}
if (this.y != this._y) {
this._y = this.y-this._y>0 ? 1 : -1;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
}
};
}

如图,角色在A点,要移动到B点去,方向就是AB。C点是下一帧角色将会出现的点,那向量AC就相当于角色的速度。将向量AC分解到两轴就得到x方向上的向量AE和y方向上的向量AD,这就是角色在这帧里需要移动的xy值了。角色的速度是已知的,要得到两个分解的向量,就只需要知道角度就行了。角度也可以根据A点和B点得到。我把角色面向右边时定为0度,则向上时为-90度,向下为90度,向左为180度。之所以这么定是为了计算方便。如Math.atan2(role_mc.y-role_mc._y, role_mc.x-role_mc._x)就可以直接到得角色面向的方向了(注意,Math.atan2得到的是以弧度为单位的数,不是角度)。所以把AS改写成:
role_mc.x = role_mc._x;
role_mc.y = role_mc._y;
role_mc.speed = 5;
role_mc.angle = 0;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x; //当角色和目的地的距离小于角色的速度时,就不能再以速度计算出的位移,而直接移动到目的地,Y方向同理
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
}
};
}
移动看起来应该没什么大问题了。现在来做人物。把自己画的人物,或者是从游戏里抓图弄出来的人物处理好,我有两张从RO里弄出来的图。role_mc.y = role_mc._y;
role_mc.speed = 5;
role_mc.angle = 0;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x; //当角色和目的地的距离小于角色的速度时,就不能再以速度计算出的位移,而直接移动到目的地,Y方向同理
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
}
};
}
[role.png]

[head.png]:

将两张图导入到FLASH的role_mc里。因为我是把身体和头部所有方向都做成一张图,所以要用遮照来只显示需要的那个。将第一帧做成第一个方向的站立,给帧起名stand_1,加AS:stop();后边若干帧做成行走的动画,给帧起名run_1,加AS:play();在行走动画的后边加上一帧空白关健帧,加AS:gotoAndPlay("run_1")。这就完成了一个方向。在这一段后边加上其他几个方向。我做的是方向1为向下,2为左下,3为左,4为左上,5为上。其他三个方向可以之后用AS将234方向做水平翻转达到。也许我说得不是很明白,看我的这个文件(teach1.fla)就知道了。做好这一步之后,就可以在moveRole函数里加些东西,让角色做出相应的动作。我们先画个图来看一看角色的angle值分别代表角色面向哪个方向。见下图
[2.gif]:

图中的红线就是每个方向的分隔线,把角度也列出来了,用弧度为单位是为了方便以后的计算。把AS也修改了一下就达到行走效果了。
role_mc.x = role_mc._x;
role_mc.y = role_mc._y;
role_mc.speed = 5;
role_mc.angle = 0;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.dire = 1 Math.round((role.angle Math.PI)/(Math.PI/4)); //把角色面向的角度的值从-Math.PI到Math.PI的范围变为1到8的自然数,分别代码八个方向
role.dire = role.dire>6 ? role.dire-6 : role.dire 2; //把八个方向处理一下,和图上代表的方向符合
if (role.dire>5) {
//如果方向为789中的一个,就水平翻转MC
role.dire = role.dire == 6 ? 4 : role.dire == 7 ? 3 : 2; //方向6对应方向4,7对应3,8对应2
role._xscale = -100;
} else {
role._xscale = 100;
}
role.gotoAndPlay("run_" role.dire);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x;
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
this.gotoAndStop("stand_" this.dire);
}
};
}
大体样子已经出来了。现在我们来想想,其他的玩家要怎么办?当收到服务器有新玩家登陆这里的时候,FLASH要添加一个新的角色在画面上。我们可以做一个创造角色的函数,收到有新人登陆时就添加一个。而自己登陆的时候也可以用这个函数来创建自己。其他玩家移动,也是从服务器收到是谁要移动到哪的信息,FLASH只要调用moveRole函数就可以了。现在先做一个创建角色的函数。role_mc不需要再放在场景里,在库里给他一个链接名role,就可以方便用attachMovie命令调用了。role_mc.y = role_mc._y;
role_mc.speed = 5;
role_mc.angle = 0;
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
moveRole(role_mc, _xmouse, _ymouse);
};
Mouse.addListener(mouseListener);
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.dire = 1 Math.round((role.angle Math.PI)/(Math.PI/4)); //把角色面向的角度的值从-Math.PI到Math.PI的范围变为1到8的自然数,分别代码八个方向
role.dire = role.dire>6 ? role.dire-6 : role.dire 2; //把八个方向处理一下,和图上代表的方向符合
if (role.dire>5) {
//如果方向为789中的一个,就水平翻转MC
role.dire = role.dire == 6 ? 4 : role.dire == 7 ? 3 : 2; //方向6对应方向4,7对应3,8对应2
role._xscale = -100;
} else {
role._xscale = 100;
}
role.gotoAndPlay("run_" role.dire);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x;
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
this.gotoAndStop("stand_" this.dire);
}
};
}
//创建鼠标监听器
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
for (var i = 0; i<allRoles.length; i ) {
if (allRoles[i].ctrl) {
moveRole(allRoles[i], _xmouse, _ymouse);
break;
}
}
};
Mouse.addListener(mouseListener);
//创建角色
var allRoles:Array = new Array();
allRoles[allRoles.length] = createRole(Math.random(), true, 5, Stage.width/2, Stage.height/2);
function createRole() {
var roleObj:MovieClip = this.attachMovie("role", "role_mc", this.getNextHighestDepth());
roleObj.id = arguments[0]; //区别每一个角色的ID,可以从数据库中得到值。在这个教程里就直接用一个随机数代替
roleObj.ctrl = arguments[1]; //标识该角色是否为由玩家控制的角色
roleObj.speed = arguments[2];
roleObj._x = arguments[3];
roleObj._y = arguments[4];
roleObj.x = roleObj._x;
roleObj.y = roleObj._y;
return roleObj;
}
//角色移动
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.dire = 1 Math.round((role.angle Math.PI)/(Math.PI/4));
role.dire = role.dire>6 ? role.dire-6 : role.dire 2;
if (role.dire>5) {
role.dire = role.dire == 6 ? 4 : role.dire == 7 ? 3 : 2;
role._xscale = -100;
} else {
role._xscale = 100;
}
role.gotoAndPlay("run_" role.dire);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x;
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
this.gotoAndStop("stand_" this.dire);
}
};
}
var mouseListener:Object = new Object();
mouseListener.onMouseDown = function() {
for (var i = 0; i<allRoles.length; i ) {
if (allRoles[i].ctrl) {
moveRole(allRoles[i], _xmouse, _ymouse);
break;
}
}
};
Mouse.addListener(mouseListener);
//创建角色
var allRoles:Array = new Array();
allRoles[allRoles.length] = createRole(Math.random(), true, 5, Stage.width/2, Stage.height/2);
function createRole() {
var roleObj:MovieClip = this.attachMovie("role", "role_mc", this.getNextHighestDepth());
roleObj.id = arguments[0]; //区别每一个角色的ID,可以从数据库中得到值。在这个教程里就直接用一个随机数代替
roleObj.ctrl = arguments[1]; //标识该角色是否为由玩家控制的角色
roleObj.speed = arguments[2];
roleObj._x = arguments[3];
roleObj._y = arguments[4];
roleObj.x = roleObj._x;
roleObj.y = roleObj._y;
return roleObj;
}
//角色移动
function moveRole(role:MovieClip, x:Number, y:Number) {
role.x = x;
role.y = y;
role.angle = Math.atan2(role.y-role._y, role.x-role._x);
role.dire = 1 Math.round((role.angle Math.PI)/(Math.PI/4));
role.dire = role.dire>6 ? role.dire-6 : role.dire 2;
if (role.dire>5) {
role.dire = role.dire == 6 ? 4 : role.dire == 7 ? 3 : 2;
role._xscale = -100;
} else {
role._xscale = 100;
}
role.gotoAndPlay("run_" role.dire);
role.onEnterFrame = function() {
if (this.x != this._x) {
this._x = Math.abs(this.x-this._x)>Math.abs(this.speed*Math.cos(this.angle)) ? this.speed*Math.cos(this.angle) : this.x-this._x;
}
if (this.y != this._y) {
this._y = Math.abs(this.y-this._y)>Math.abs(this.speed*Math.sin(this.angle)) ? this.speed*Math.sin(this.angle) : this.y-this._y;
}
if (this.x == this._x && this.y == this._y) {
delete this.onEnterFrame;
this.gotoAndStop("stand_" this.dire);
}
};
}
teach1.fla:点击这里下载源文件
接下来是后台服务器的制作。我用的是Microsoft Visual Studio .NET 2003。语言为VB。(图和相关文字不能放在一起-_- 大家麻烦点,上下对着看吧。)
首先建一个空项目,命名为server(图3)。在这个项目的“解决方案资源管理器”上右击项目,选择“添加(D)”-“添加新项(W)”,添加一个类,命名为server.vb(图4)。在server.vb里写上socket的代码。我是从微软件的网站上的例子上修改来的,也没什么可说的。


首先建一个空项目,命名为server(图3)。在这个项目的“解决方案资源管理器”上右击项目,选择“添加(D)”-“添加新项(W)”,添加一个类,命名为server.vb(图4)。在server.vb里写上socket的代码。我是从微软件的网站上的例子上修改来的,也没什么可说的。
Imports System.Net
Imports System.Net.Sockets
Namespace ibaiy
Public Class Server
Private lisenSocket As Socket
Private allRoles As New Hashtable
Private rolesPosition As New Hashtable
’在这个例子中我不打算用数据库了,所以建一个hashtable来存在线用户的资料。如果要存入数据库,只需要加上登陆后验证用户名密码,读数据库资料,再存到这个hashtable里。用户做了操作时再把hashtable里的数据处理后再写入数据库就可以了。这里的操作不难,但比较麻烦,所以我偷下懒
’启动服务的主函数
Public Sub startServer(ByVal port As Integer)
lisenSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
Dim ipen As IPHostEntry = Dns.Resolve(Dns.GetHostName)
Dim endpoint = New IPEndPoint(ipen.AddressList(0).Any, port)
lisenSocket.Bind(endpoint)
lisenSocket.Listen(1000)
lisenSocket.BeginAccept(AddressOf Me.Listen_Callback, lisenSocket)
End Sub
Public Sub Listen_Callback(ByVal result As IAsyncResult)
Dim s As Socket = CType(result.AsyncState, Socket)
Dim so2 As New StateObject
so2.workSocket = s.EndAccept(result)
so2.workSocket.BeginReceive(so2.buffer, 0, so2.buffer.Length, 0, AddressOf Me.Read_Callback, so2)
s.BeginAccept(AddressOf Me.Listen_Callback, s)
End Sub
Public Sub Read_Callback(ByVal result As IAsyncResult)
Dim so As StateObject = CType(result.AsyncState, StateObject)
so.len = so.workSocket.EndReceive(result)
opMsg(so)
so.buffer.Clear(so.buffer, 0, so.buffer.Length)
Try
so.workSocket.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, AddressOf Me.Read_Callback, so)
Catch
End Try
End Sub
’这个函数就是处理服务器收到客户端消息的处理函数,一会详细说明
Public Sub opMsg(ByVal so As StateObject)
End Sub
End Class
End Namespace
在server项目里像添加server.vb一样再添加一个StateObject.vb。这是为每个联接的SOCKET一定的buffer,索性做了一个新的类。写以下代码:Imports System.Net.Sockets
Namespace ibaiy
Public Class Server
Private lisenSocket As Socket
Private allRoles As New Hashtable
Private rolesPosition As New Hashtable
’在这个例子中我不打算用数据库了,所以建一个hashtable来存在线用户的资料。如果要存入数据库,只需要加上登陆后验证用户名密码,读数据库资料,再存到这个hashtable里。用户做了操作时再把hashtable里的数据处理后再写入数据库就可以了。这里的操作不难,但比较麻烦,所以我偷下懒
’启动服务的主函数
Public Sub startServer(ByVal port As Integer)
lisenSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
Dim ipen As IPHostEntry = Dns.Resolve(Dns.GetHostName)
Dim endpoint = New IPEndPoint(ipen.AddressList(0).Any, port)
lisenSocket.Bind(endpoint)
lisenSocket.Listen(1000)
lisenSocket.BeginAccept(AddressOf Me.Listen_Callback, lisenSocket)
End Sub
Public Sub Listen_Callback(ByVal result As IAsyncResult)
Dim s As Socket = CType(result.AsyncState, Socket)
Dim so2 As New StateObject
so2.workSocket = s.EndAccept(result)
so2.workSocket.BeginReceive(so2.buffer, 0, so2.buffer.Length, 0, AddressOf Me.Read_Callback, so2)
s.BeginAccept(AddressOf Me.Listen_Callback, s)
End Sub
Public Sub Read_Callback(ByVal result As IAsyncResult)
Dim so As StateObject = CType(result.AsyncState, StateObject)
so.len = so.workSocket.EndReceive(result)
opMsg(so)
so.buffer.Clear(so.buffer, 0, so.buffer.Length)
Try
so.workSocket.BeginReceive(so.buffer, 0, so.buffer.Length, SocketFlags.None, AddressOf Me.Read_Callback, so)
Catch
End Try
End Sub
’这个函数就是处理服务器收到客户端消息的处理函数,一会详细说明
Public Sub opMsg(ByVal so As StateObject)
End Sub
End Class
End Namespace
Imports System.Net.Sockets
Namespace ibaiy
Public Class StateObject
Public buffer(1024) As Byte
Public workSocket As Socket
Public len As Integer
End Class
End Namespace
Namespace ibaiy
Public Class StateObject
Public buffer(1024) As Byte
Public workSocket As Socket
Public len As Integer
End Class
End Namespace


作者:gdgzboy@牛C网
地址:http://www.niuc.net/post/1968/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
牛C网推荐您再看看以下日志:
有关Flash AS3编程的一些总结
Flash制作的XML图片加载示例(带分页效果)
Flash AS3生成单独“双击事件”
Flash问答-一个简单的字母顺序变化效果
Adobe Flex 2 入门教程之RIA概述
用Flash按钮弹出窗口
汇总知识-Flash中常用的几个JS语句
Flash片头加载完美的loading的制作
Flash动画内部加连接的一点小技巧
Flash使用bitmapData打造随机凹凸拼图效果
有关Flash AS3编程的一些总结
Flash制作的XML图片加载示例(带分页效果)
Flash AS3生成单独“双击事件”
Flash问答-一个简单的字母顺序变化效果
Adobe Flex 2 入门教程之RIA概述
用Flash按钮弹出窗口
汇总知识-Flash中常用的几个JS语句
Flash片头加载完美的loading的制作
Flash动画内部加连接的一点小技巧
Flash使用bitmapData打造随机凹凸拼图效果
Flash游戏中导弹追踪的算法
Flash处理外部XML文档数据





