尽管有很多人说它是拉几,但我从来都相信mir2从coderes到picres都是一个优秀的游戏,超越同类mmrpg很多,下面谈谈它的一个方面
:精灵移动
因为对这个的讨论涉及到地图格及地图单元,先在头脑中想象一下地图格,这里不打算讨论地图单元
这里仅讨论actor.pas中跟动作有关的函数,,验证它的优越之处,,
现在开始,,下面是从mir2res中提取的源程序片断,,正是它们构成了精灵移动的基础
{------------------------------------------------------------------------------}
// 常量定义
{------------------------------------------------------------------------------}
const
MAX_HUMANFRAME = 600; // 人类动画总帧数 [0..599]
MAX_HAIR = 3; // 头发种类数 [0..2]
MAX_WEAPON = 34; // 武器种类数 [0..34]
{------------------------------------------------------------------------------}
// 数据结构定义
{------------------------------------------------------------------------------}
type
// 动作定义
PActionInfo = ^TActionInfo;
TActionInfo = record
start : word; // 开始帧
frame : word; // 帧数
skip : word; // 跳过的帧数
ftime : word; // 每帧的延迟时间(毫秒)
end;
// 玩家的动作定义
PHumanAction = ^THumanAction;
THumanAction = record
ActStand : TActionInfo; //1
ActWalk : TActionInfo; //8
ActRun : TActionInfo; //8
ActRushLeft : TActionInfo;
ActRushRight : TActionInfo;
ActWarMode : TActionInfo; //1
ActHit : TActionInfo; //6
ActHeavyHit : TActionInfo; //6
ActBigHit : TActionInfo; //6
ActFireHitReady: TActionInfo; //6
ActSpell : TActionInfo; //6
ActSitdown : TActionInfo; //1
ActStruck : TActionInfo; //3
ActDie : TActionInfo; //4
end;
{------------------------------------------------------------------------------}
// TActor class 精灵类定义
{------------------------------------------------------------------------------}
TActor = class(TObject)
protected
FrameCount : Word; // 当前动作的总帧数
FrameStart : Word; // 当前动作的开始帧索引
FrameEnd : Word; // 当前动作的结束帧索引
FrameTime : Word; // 当前动作每帧等待的时间
FrameIndex : Word; // 当前帧索引
FrameTick : Cardinal; // 当前帧显示时的 GetTickCount
public
CurrentMap : TMirMap; // 当前人物所在地图
CurrentAction : Byte; // 当前动作
Direction : Byte; // 当前方向
Appearance : Word; // 外观显示(对 HumanActor 则相当于级别,初级,高级,,中级,,最高级-顶级)
MapX : Word; // 处于地图上的位置(以 MapPoint 为单位)
MapY : Word;
ShiftX : Integer; // 当前位移(以像素为单位, 用于 Walk, Run)
ShiftY : Integer;
MovX : Integer; // 上次 Walk, Run 的偏移值,,
MovY : Integer;
constructor Create(Map: TMirMap); virtual; abstract; //需要子类的重载
// 重新计算动画帧
procedure ReCalcFrames; virtual; abstract;
// 根据经过的时间处理下一帧
procedure ProcessFrame; virtual; abstract;
end;
{------------------------------------------------------------------------------}
// THumanActor class 角色精灵类定义
{------------------------------------------------------------------------------}
THumanActor = class(TActor)
private
protected
BodyOffset : Word; // 身体图片索引的主偏移
HairOffset : Word; // 头发图片索引的主偏移
WeaponOffset : Word; // 武器图片索引的主偏移
WeaponOrder : Byte; // 武器绘制顺序(是否先于身体绘制)
public
Sex : Byte; // 性别
Job : Byte; // 职业
Hair : Byte; // 发型
Weapon : Byte; // 武器
constructor Create(Map: TMirMap); override; //重载
procedure ReCalcFrames; override;
procedure ProcessFrame; override;
procedure Draw(Surface: IDirectDrawSurface7; X, Y, Width, Height: Integer);
function CalcNextDirection: Boolean;
// 移动至 MapPoint, 虽然 MapPoint 坐标定义为 Word, 这里必须用 Integer 代替
// 因为玩家可以点击至 Map 坐标负方向进行转向
procedure Walk(X, Y: Integer);
procedure Run(X, Y: Integer);
procedure Hit(X, Y: Integer);
end;
const
{------------------------------------------------------------------------------}
// 人物动作图片索引
{------------------------------------------------------------------------------}
// 以下二项数据组成 9 * 2 * 600 = 10800 幅图片
// Appearance = [0..8] [没穿衣服/初级/中级/高级(武/法/道)/最高级(武/法/道)]
// Sex = [0..1] [男/女]
// 每个 Appearance 包含 14 种 Action
// 不同 Direction 的 Action = Action 的起始帧 * 8 * Direction
// Action = [0 - 13] (Action 数量与总帧数无关, 并非每个 Action 都 8 帧)
// Direction = [0 - 7]
HUMANACTION_STAND = 0;
HUMANACTION_WALK = 1;
HUMANACTION_RUN = 2;
HUMANACTION_RUSHLEFT = 3;
HUMANACTION_RUSHRIGHT = 4;
HUMANACTION_WARMODE = 5;
HUMANACTION_HIT = 6;
HUMANACTION_HEAVYHIT = 7;
HUMANACTION_BIGHIT = 8;
HUMANACTION_FIREHITREADY = 9;
HUMANACTION_SPELL = 10;
HUMANACTION_SITDOWN = 11;
HUMANACTION_STRUCK = 12;
HUMANACTION_DIE = 13;
HUMANACTIONS: THumanAction =
(
// 开始帧 有效帧 跳过帧 每帧延迟(每帧经过的时间,单位:毫秒)
ActStand: (start: 0; frame: 4; skip: 4; ftime: 200);
ActWalk: (start: 64; frame: 6; skip: 2; ftime: 80);
ActRun: (start: 128; frame: 6; skip: 2; ftime: 90);
ActRushLeft: (start: 128; frame: 3; skip: 5; ftime: 120);
ActRushRight: (start: 131; frame: 3; skip: 5; ftime: 120);
ActWarMode: (start: 192; frame: 1; skip: 0; ftime: 200);
ActHit: (start: 200; frame: 6; skip: 2; ftime: 85);
ActHeavyHit: (start: 264; frame: 6; skip: 2; ftime: 90);
ActBigHit: (start: 328; frame: 8; skip: 0; ftime: 70);
ActFireHitReady: (start: 192; frame: 6; skip: 4; ftime: 70);
ActSpell: (start: 392; frame: 6; skip: 2; ftime: 60);
ActSitdown: (start: 456; frame: 2; skip: 0; ftime: 300);
ActStruck: (start: 472; frame: 3; skip: 5; ftime: 70);
ActDie: (start: 536; frame: 4; skip: 4; ftime: 120);
);
{------------------------------------------------------------------------------}
// 武器绘制顺序 (是否先于身体绘制: 0是/1否)
// WEAPONORDERS: array [Sex, FrameIndex] of Byte
{------------------------------------------------------------------------------}
WEAPONORDERS: array[0..1, 0..MAX_HUMANFRAME - 1] of Byte =
(
(
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,1,1,1,1,
0,0,0,0,1,1,1,1, 0,0,0,0,1,1,1,1,
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,1,
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,1,1,1,1,1,1, 0,0,1,1,1,0,0,1,
0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,1,
//
0,1,1,1,0,0,0,0,
//
1,1,1,0,0,0,1,1, 1,1,1,0,0,0,0,0, 1,1,1,0,0,0,0,0,
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0, 1,1,1,1,0,0,1,1,
//
0,1,1,0,0,0,1,1, 0,1,1,0,0,0,1,1, 1,1,1,0,0,0,0,0,
1,1,1,0,0,1,1,1, 1,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,1,
0,0,0,1,1,1,0,0, 0,1,1,1,1,0,1,1,
//
1,1,0,1,0,0,0,0, 1,1,0,0,0,0,0,0, 1,1,1,1,1,0,0,0,
1,1,0,0,1,0,0,0, 1,1,1,0,0,0,0,1, 0,1,1,0,0,0,0,0,
0,0,0,0,1,1,1,0, 1,1,1,1,1,0,0,0,
//
0,0,0,0,0,0,1,1, 0,0,0,0,0,0,1,1, 0,0,0,0,0,0,1,1,
1,0,0,0,0,1,1,1, 1,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,1,
0,0,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
//
0,0,1,0,1,1,1,1, 1,1,0,0,0,1,0,0,
//
0,0,0,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
//
0,0,1,1,1,1,1,1, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1
),
//以上为男600,,下面开始女600
(
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,1,1,1,1,
0,0,0,0,1,1,1,1, 0,0,0,0,1,1,1,1,
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,1,
//
0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,1,1,1,1,1,1, 0,0,1,1,1,0,0,1,
0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,1,
//
1,1,1,1,0,0,0,0,
//
1,1,1,0,0,0,1,1, 1,1,1,0,0,0,0,0, 1,1,1,0,0,0,0,0,
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0, 1,1,1,1,0,0,1,1,
//
0,1,1,0,0,0,1,1, 0,1,1,0,0,0,1,1, 1,1,1,0,0,0,0,0,
1,1,1,0,0,1,1,1, 1,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,1,
0,0,0,1,1,1,0,0, 0,1,1,1,1,0,1,1,
//
1,1,0,1,0,0,0,0, 1,1,0,0,0,0,0,0, 1,1,1,1,1,0,0,0,
1,1,0,0,1,0,0,0, 1,1,1,0,0,0,0,1, 0,1,1,0,0,0,0,0,
0,0,0,0,1,1,1,0, 1,1,1,1,1,0,0,0,
//
0,0,0,0,0,0,1,1, 0,0,0,0,0,0,1,1, 0,0,0,0,0,0,1,1,
1,0,0,0,0,1,1,1, 1,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,1,
0,0,1,1,0,0,1,1, 0,0,0,1,0,0,1,1,
//
0,0,1,0,1,1,1,1, 1,1,0,0,0,1,0,0,
//
0,0,0,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
//
0,0,1,1,1,1,1,1, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1, 0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1,
0,0,0,1,1,1,1,1, 0,0,0,1,1,1,1,1
)
);
implementation
{ THumanActor }
procedure THumanActor.Draw(Surface: IDirectDrawSurface7; X, Y, Width, Height: Integer);
var
Index: Word;
begin
// 绘制武器,允许的范围 [0..33] (WeaponOrder = 0)
if (WeaponOrder = 0) and (Weapon > 0) and (Weapon < MAX_WEAPON) then
G_WilWeapon.DrawEx(WeaponOffset + FrameIndex, Surface, X, Y,
CLIENT_WIDTH, CLIENT_HEIGHT, True);
// 绘制身体
G_WilHuman.DrawEx(BodyOffset + FrameIndex, Surface, X, Y,
CLIENT_WIDTH, CLIENT_HEIGHT, True);
// 绘制头发,允许的范围 [0..2]
if (Hair > 0) and (Hair < MAX_HAIR) then
G_WilHair.DrawEx(HairOffset + FrameIndex, Surface, X, Y,
CLIENT_WIDTH, CLIENT_HEIGHT, True);
// 绘制武器,允许的范围 [0..33] (WeaponOrder = 1)
if (WeaponOrder = 1) and (Weapon > 0) and (Weapon < MAX_WEAPON) then
G_WilWeapon.DrawEx(WeaponOffset + FrameIndex, Surface, X, Y,
CLIENT_WIDTH, CLIENT_HEIGHT, True);
end;
procedure THumanActor.ProcessFrame;
var
CurrTick: Cardinal;
NewIndex: Integer; //重要的是把握它的实际用意,,下面会谈到
begin
inherited;
CurrTick := GetTickCount;
case CurrentAction of //这里计算shiftx,,y,,注意,这里只是计算,,而不实际实现人物在地图上的位移效果,
//shiftx,,y是地图单元用的,,精灵移动好像根本不用到它,,故下面几行不讨论
HUMANACTION_WALK:
begin
ShiftX := MovX * MAPUNIT_WIDTH * Integer((CurrTick - FrameTick)) div (FrameTime * FrameCount);
ShiftY := MovY * MAPUNIT_HEIGHT * Integer((CurrTick - FrameTick)) div (FrameTime * FrameCount);
end;
HUMANACTION_RUN:
begin
ShiftX := MovX * 2 * MAPUNIT_WIDTH * Integer((CurrTick - FrameTick)) div (FrameTime * FrameCount);
ShiftY := MovY * 2 * MAPUNIT_HEIGHT * Integer((CurrTick - FrameTick)) div (FrameTime * FrameCount);
end;
end;
//ProcessFrame对精灵移动的实际作用真正从下面这行开始
NewIndex := FrameStart + (CurrTick - FrameTick) div (FrameTime); //经过的时间除以每帧经过的时间,,就可以得出理论上应该过多少帧
if NewIndex = FrameIndex then Exit; // 判断是否需要计算帧,如果理论与实际相符,此函数不发挥作用,如果不符,就要经过这个函数的作用作一点处理
FrameIndex := NewIndex; //处理的第一步:把frameindex拉回正确位置,,为什么要这么做:mir2是一个网络游戏,由于本地和网络的延迟,,
// TODO: 可能 GetTickCount 会在 49 天内循环回来,,mir2的B/S模型中,gettickcount是本地的计算(毫秒级的timer)而不是服务端的计算
if FrameIndex < FrameStart then FrameIndex := FrameStart; //处理的第二步,,注意:在这之前frameindex经过了frameindex=newindex的作用,不是原来的frameindex了,
if FrameIndex > FrameEnd then
begin
case CurrentAction of
HUMANACTION_WALK:
begin
Inc(MapX, MovX);
Inc(MapY, MovY);
end;
HUMANACTION_RUN:
begin
Inc(MapX, MovX * 2); //inc,,按movx*2递增mapx
Inc(MapY, MovY * 2);
end;
end;
CurrentAction := HUMANACTION_STAND; //最终的动作总是停下来
ReCalcFrames; //停下来就是动作改变,就要重新计算帧,,,等待下一次的processframe过程
end;
// 计算武器绘制顺序
WeaponOrder := WEAPONORDERS[Sex, FrameIndex];
end;
constructor THumanActor.Create(Map: TMirMap);
begin
inherited; //继承父类TActor的create过程
Appearance := 7;
Job := 1;
Sex := 1;
Hair := 2;
Direction := 5;
Weapon := 31;
CurrentAction := HUMANACTION_STAND; //一开始被create出来,,动作当然是stand
MapX := 300;
MapY := 300;
end;
procedure THumanActor.ReCalcFrames;
var
InfoPtr: PActionInfo;
begin
inherited;
case CurrentAction of //每一个动作额定对应一个系列的动作图片(开始于多少帧,每帧多少)及图片信息(如每帧持久多少)
//这个可以说是所有属性的重置,,重置工作的初始过程:为infoprt这个指针变量赋值
0: InfoPtr := @HUMANACTIONS.ActStand;
1: InfoPtr := @HUMANACTIONS.ActWalk;
2: InfoPtr := @HUMANACTIONS.ActRun;
3: InfoPtr := @HUMANACTIONS.ActRushLeft;
4: InfoPtr := @HUMANACTIONS.ActRushRight;
5: InfoPtr := @HUMANACTIONS.ActWarMode;
6: InfoPtr := @HUMANACTIONS.ActHit;
7: InfoPtr := @HUMANACTIONS.ActHeavyHit;
8: InfoPtr := @HUMANACTIONS.ActBigHit;
9: InfoPtr := @HUMANACTIONS.ActFireHitReady;
10: InfoPtr := @HUMANACTIONS.ActSpell;
11: InfoPtr := @HUMANACTIONS.ActSitdown;
12: InfoPtr := @HUMANACTIONS.ActStruck;
13: InfoPtr := @HUMANACTIONS.ActDie;
else
raise Exception.CreateFmt('Unknown Human Action : %d', [CurrentAction]);
end;
// 身体图片偏移值
BodyOffset := MAX_HUMANFRAME * (Appearance * 2 + Sex) + Direction * 8;
// 头发图片偏移值
if (Hair > 0) and (Hair < MAX_HAIR) then
HairOffset := MAX_HUMANFRAME * (Hair * 2 + Sex) + Direction * 8;
// 武器图片偏移值
if (Weapon > 0) and (Weapon < MAX_WEAPON) then
WeaponOffset := MAX_HUMANFRAME * (Weapon * 2 + Sex) + Direction * 8;
//为infoprt赋值后这里就是具体的重置过程了
FrameCount := InfoPtr^.frame;
FrameStart := InfoPtr^.start;
FrameEnd := FrameStart + FrameCount - 1; //这是什么用意,,最后一帧为什么不显示?
FrameTime := InfoPtr^.ftime;
FrameIndex := FrameStart; //从头开始,,注意frameindex,是thumactor的成员
FrameTick := GetTickCount;
ShiftX := 0; //x,,y上的位移都置0,,跟movx,,,movy有关,在procseeframe中
ShiftY := 0; //
end;
function THumanActor.CalcNextDirection: Boolean; //计算下一个方向为什么要返回boolean??walk(),run()中会看到作判断用
var
NewDir: Byte;
begin //这里比较容易理解吧:坐标系统跟数学坐标不一样
if (MovX = -1) and (MovY = -1) then NewDir := DIR_UPLEFT else
if (MovX = -1) and (MovY = 0) then NewDir := DIR_LEFT else
if (MovX = -1) and (MovY = 1) then NewDir := DIR_DOWNLEFT else
if (MovX = 0) and (MovY = -1) then NewDir := DIR_UP else
if (MovX = 0) and (MovY = 1) then NewDir := DIR_DOWN else
if (MovX = 1) and (MovY = -1) then NewDir := DIR_UPRIGHT else
if (MovX = 1) and (MovY = 0) then NewDir := DIR_RIGHT else
if (MovX = 1) and (MovY = 1) then NewDir := DIR_DOWNRIGHT else
raise Exception.Create('CalcNextDirection');
Result := NewDir <> Direction; //比较计算出的新方向与当前方向,,看是不是相等,,到此为止实际上函数的作用已经完成了,但是还要加入一个附加处理
if Result then Direction := NewDir; //附加处理:如果不等(为真),,那么置当前dir为newdir
end;
procedure THumanActor.Walk(X, Y: Integer); //x,,y为目标位置
begin
if CurrentAction <> HUMANACTION_STAND then Exit; //走之前一定要是站立状态
MovX := Min(1, Max(-1, X - MapX)); //movx,,movy是上次 Walk, Run Action 的偏移值,这个语句值得深究,我们来推导一下
if min(1,max())= 取max then,,max只能取0,,负数,,因此在max内部,,要么为0,不能是-1(为0时,此时x-mapx=-1,x=mapx-1),,要么为负(此时x-mapx<-1,,x<mapx-1)
也即x<=mapx-1
实际意义就是用户点了(这里只讨论x方向)人物当前x坐标前一个方向
if min(1,max())= 取1 then, , max内部只能取2,,或2以上(max不能是1),,因此x-mapx是,,即x>=mapx+2,,此时x在水平方向总是要比mapx大二个或二个地图格以上
即(实际意义):用户在水平向上点击了比mapx大二个或二个的地图格以上的地图位置,,此时movx被计算出来,为>=2
以上讨论水平方向,这里讨论垂直方向,二个方向共同作用,,确定movx,movy,,注意到在上面的过程中,min()只能取1,,或>=2的数值max(),,
下面讨论竖直方向的情况,同一个道理
MovY := Min(1, Max(-1, Y - MapY));
三种情况,,,1,,y=mapy 2,,y<mapy 3,,y>=mapy+2
设想一下,y仅仅>mapy,,而不规定y>=mapy+2时,比如y=mapy+1时也就是用户点击人物当前位置竖直方向再往下一个伴置,此时
y>mapy,,但y不>=mapy+2,(至少要3个地图格),此时不满足上面三种情况的任何一种情况
if (MovX = 0) and (MovY = 0) then Exit; 用户点了站立的位置,,相当于不走,exit,,即walk()函数不发挥作用
if CalcNextDirection then ReCalcFrames; //如果方向改变了,,那么也要重新计算帧
if not CurrentMap.CanMove(MapX + MovX, MapY + MovY) then Exit; //如果地图不能走当然也就不能继续walk()了
CurrentAction := HUMANACTION_WALK;
ReCalcFrames; //动作由站立改为走也要重新计算帧,,也就是说,,recalcframes,,,的影响因素有二个1,动作,,2方向
end;
procedure THumanActor.Run(X, Y: Integer); x,,y为目标位置
begin
if CurrentAction <> HUMANACTION_STAND then Exit; //跑之前一定要是站立状态
MovX := Min(1, Max(-1, X - MapX));
MovY := Min(1, Max(-1, Y - MapY));
if (MovX = 0) and (MovY = 0) then Exit; //用户点了站立的位置,,相当于不跑
if CalcNextDirection then ReCalcFrames;
if not CurrentMap.CanMove(MapX + MovX, MapY + MovY) then Exit;
if not CurrentMap.CanMove(MapX + MovX * 2, MapY + MovY * 2) then
begin
Walk(X, Y);
Exit; //这里为什么不要recalcframe,,因为包括在walk(x,y)中了
end;
CurrentAction := HUMANACTION_RUN; //这句语句的上面计算run产生的地图位移,下面就是产生动画效果
ReCalcFrames;
end;
procedure THumanActor.Hit(X, Y: Integer);
begin
if CurrentAction <> HUMANACTION_STAND then Exit;
MovX := Min(1, Max(-1, X - MapX));
MovY := Min(1, Max(-1, Y - MapY));
if (MovX = 0) and (MovY = 0) then Exit;
CalcNextDirection;
CurrentAction := HUMANACTION_HIT;
ReCalcFrames;
end;
end.
声明:本文系互联网搜索而收集整理,不以盈利性为目的,文字、图文资料源于互联网且共享于互联网。
如有侵权,请联系 yao4fvip#qq.com (#改@) 删除。
如有侵权,请联系 yao4fvip#qq.com (#改@) 删除。