关于热血传奇actor绘制的分析与思考

    代码内将每个actor的动作细分为  站立 行走 攻击 死亡 等等。

     而每个动作都包含8个方向的定义。指定如何从资源文件中 获取相应的纹理。 

    以下是方向的常量

  // Actor 方向常量
  DIR_UP        = 0;
  DIR_UPRIGHT   = 1;
  DIR_RIGHT     = 2;
  DIR_DOWNRIGHT = 3;
  DIR_DOWN      = 4;
  DIR_DOWNLEFT  = 5;
  DIR_LEFT      = 6;
  DIR_UPLEFT    = 7;
 以下是动作定义。

  TActionInfo = record
    start   : word;              // 开始帧
    frame   : word;              // 帧数
    skip    : word;              // 跳过的帧数
    ftime   : word;              // 每帧的延迟时间(毫秒)
    usetick : byte;              // (意义未知)
  end;

不管人类还是NPC或者是怪物,其当前动作 都可以使用TActionInfo 来指定 读取规则。

   而人类是包含以下动作的

  THumanAction = packed record //人物动作
    ActStand: TActionInfo; //1  站立
    ActWalk: TActionInfo; //8   行走
    ActRun: TActionInfo; //8     跑
    ActRushLeft: TActionInfo; // 左冲锋?
    ActRushRight: TActionInfo; //右冲锋
    ActWarMode: TActionInfo; 
    ActHit: TActionInfo; //攻击
    ActHeavyHit: TActionInfo; //重击
    ActBigHit: TActionInfo; //大击
    ActFireHitReady: TActionInfo; //烈火攻击准备?
    ActSpell: TActionInfo; //使用法术
    ActSitdown: TActionInfo; //1   //坐下
    ActStruck: TActionInfo; //3     //被攻击
    ActDie: TActionInfo; //4      //死亡
    ActSerieHit: array[0..18 - 1] of TActionInfo;  //连击动作

同时定义了一个全局人类读取规则


{人类动作定义} 
HumAction: THumanAction = (
     //       开始帧      有效帧    跳过帧   每帧延迟    (意义未知)
    ActStand: (start: 0; frame: 4; skip: 4; ftime: 200; usetick: 0); //0-63  站立
    ActWalk: (start: 64; frame: 6; skip: 2; ftime: 90; usetick: 2);  //64-127   行走
    ActRun: (start: 128; frame: 6; skip: 2; ftime: 120; usetick: 3);  //128- 191    跑步
    ActRushLeft: (start: 128; frame: 3; skip: 5; ftime: 120; usetick: 3);//野蛮冲撞的貌似
    ActRushRight: (start: 131; frame: 3; skip: 5; ftime: 120; usetick: 3);  //
    ActWarMode: (start: 192; frame: 1; skip: 0; ftime: 200; usetick: 0); //攻击后停滞的画面
    ActHit: (start: 200; frame: 6; skip: 2; ftime: 85; usetick: 0);
    ActHeavyHit: (start: 264; frame: 6; skip: 2; ftime: 90; usetick: 0);
    ActBigHit: (start: 328; frame: 8; skip: 0; ftime: 70; usetick: 0);
    ActFireHitReady: (start: 192; frame: 6; skip: 4; ftime: 70; usetick: 0);
    ActSpell: (start: 392; frame: 6; skip: 2; ftime: 60; usetick: 0);
    ActSitdown: (start: 456; frame: 2; skip: 0; ftime: 300; usetick: 0);
    ActStruck: (start: 472; frame: 3; skip: 5; ftime: 70; usetick: 0);
    ActDie: (start: 536; frame: 4; skip: 4; ftime: 120; usetick: 0);
    ActSerieHit:
    (  //连击
    (start: 0; frame: 6; skip: 4; ftime: 60; usetick: 0), //0
    (start: 80; frame: 8; skip: 2; ftime: 60; usetick: 0), //1
    (start: 160; frame: 15; skip: 5; ftime: 60; usetick: 0), //2
    (start: 320; frame: 6; skip: 4; ftime: 60; usetick: 0), //3
    (start: 400; frame: 13; skip: 7; ftime: 60; usetick: 0), //4
    (start: 560; frame: 10; skip: 0; ftime: 60; usetick: 0), //5
    (start: 640; frame: 6; skip: 4; ftime: 60; usetick: 0), //6
    (start: 720; frame: 6; skip: 4; ftime: 60; usetick: 0), //7
    (start: 800; frame: 8; skip: 2; ftime: 60; usetick: 0), //8
    (start: 880; frame: 10; skip: 0; ftime: 60; usetick: 0), //9
    (start: 960; frame: 10; skip: 0; ftime: 60; usetick: 0), //10
    (start: 1040; frame: 13; skip: 7; ftime: 60; usetick: 0), //11
    (start: 1200; frame: 6; skip: 4; ftime: 60; usetick: 0), //12
    (start: 1280; frame: 6; skip: 4; ftime: 60; usetick: 0), //13
    (start: 1360; frame: 9; skip: 1; ftime: 60; usetick: 0), //14
    (start: 1440; frame: 12; skip: 8; ftime: 60; usetick: 0), //15
    (start: 1600; frame: 12; skip: 8; ftime: 60; usetick: 0), //16
    (start: 1760; frame: 14; skip: 6; ftime: 60; usetick: 0) //17
    )
    );

而对于怪物来说。也定义一些基本的动作状态:

    TMonsterAction = packed record
    ActStand: TActionInfo; //1
    ActWalk: TActionInfo; //8
    ActAttack: TActionInfo; //6
    ActCritical: TActionInfo; //6 0x20 -
    ActStruck: TActionInfo; //3
    ActDie: TActionInfo; //4
    ActDeath: TActionInfo;

而关于怪物的读取规则就定义了许多。以配合读取不同的怪物资源。

       如:

MA9: TMonsterAction = (//4C03D4
    ActStand: (start: 0; frame: 1; skip: 7; ftime: 200; usetick: 0);
    ActWalk: (start: 64; frame: 6; skip: 2; ftime: 120; usetick: 3);
    ActAttack: (start: 64; frame: 6; skip: 2; ftime: 150; usetick: 0);
    ActCritical: (start: 0; frame: 0; skip: 0; ftime: 0; usetick: 0);
    ActStruck: (start: 64; frame: 6; skip: 2; ftime: 100; usetick: 0);
    ActDie: (start: 0; frame: 1; skip: 7; ftime: 140; usetick: 0);
    ActDeath: (start: 0; frame: 1; skip: 7; ftime: 0; usetick: 0);
    );

而对于NPC的读取规则定义 就是使用的怪物的定义。也就是说NPC的基本动作和怪物是一样的 只不过他们都是站立的 并不会行走,当然可以自己实现行走也是没问题的。

而actor的绘制,是通过TActor.DrawChr 这个函数来绘制的,而Actor实现的绘制只是身体的绘制并不包含武器 头发这种。

procedure TActor.DrawChr(dsurface: TTexture; dx, dy: Integer; blend: Boolean; boFlag: Boolean);//绘制玩家角色?
var
  idx, ax, ay: Integer;
  d: TTexture;
  ceff: TColorEffect;
  wimg: TGameImages;
begin
  d := nil; 
  if not CanDraw then Exit;
  if not (m_btDir in [0..7]) then Exit; //如果人物方向不在0.。7就退出
  if GetTickCount - m_dwLoadSurfaceTick > m_dwLoadSurfaceTime then begin
    m_dwLoadSurfaceTick := GetTickCount;
    LoadSurface;//此函数的作用就是载入纹理。
  end;

  ceff := GetDrawEffectValue; //获取当前绘制状态,是不是中毒 隐身 等等

  if m_BodySurface <> nil then begin 
    DrawEffSurface(dsurface,
      m_BodySurface,
      dx + m_nPx + m_nShiftX,
      dy + m_nPy + m_nShiftY,
      blend,
      ceff); //在Dsurface上绘制出来。而Dsurface是全局纹理。
  end;

  if m_boUseMagic and (m_CurMagic.EffectNumber > 0) then begin //如果使用魔法并且魔法效果号>0
    if m_nCurEffFrame in [0..m_nSpellFrame - 1] then begin  
      GetEffectBase(m_CurMagic.EffectNumber - 1, 0, wimg, idx);
      idx := idx + m_nCurEffFrame;
      if wimg <> nil then
        d := wimg.GetCachedImage(idx, ax, ay);
      if d <> nil then
        DrawBlend(dsurface,
          dx + ax + m_nShiftX,
          dy + ay + m_nShiftY,
          d); //混合绘制纹理
    end;
  end;
end;

以上函数是将纹理绘制出来而已。并没有写出如何按照规则读取出纹理。而读取纹理 其实是在Tactor.loadsurface

procedure TActor.LoadSurface;
var
  mimg: TGameImages;
begin
  mimg := g_WMonImages.Images[m_wAppearance]; //获取纹理图库 MonX.wil
  if mimg <> nil then begin     //如果纹理图库部位空
    if (not m_boReverseFrame) then begin      //GetOffset的函数是按照怪物的m_wAppearance(数据库内Appr) 返回图片的基地址。

      m_BodySurface := mimg.GetCachedImage(GetOffset(m_wAppearance) + m_nCurrentFrame, m_nPx, m_nPy);
    end else begin                            //m_nCurrentFrame就是t将动作计算成偏移图片的序号。
      m_BodySurface := mimg.GetCachedImage(
        GetOffset(m_wAppearance) + m_nEndFrame - (m_nCurrentFrame - m_nStartFrame),
        m_nPx, m_nPy);
    end;
  end;
end;

如上注释:m_nCurrentFrame就是将动作转换成偏移图片的序号。而将当前actor的动作转换成偏移序号的函数是TActor.CalcActorFrame

procedure TActor.CalcActorFrame;
var
  haircount: Integer;
begin
  m_boUseMagic := False;
  m_nCurrentFrame := -1;
  m_nBodyOffset := GetOffset(m_wAppearance);  //按照怪物的appr(外观编号)获得偏移的序号。
  m_Action := GetRaceByPM(m_btRace, m_wAppearance);//按照Actor的Race(动作编号)获得怪物的绘制规则。是一个TMonsterAction.
  if m_Action = nil then Exit;//如果这个动作没有定义则不执行。 一下就是如何将当前动作结合动作规则将其转换成图片序号的详细过程
  case m_nCurrentAction of
    SM_TURN: begin
        m_nStartFrame := m_Action.ActStand.start + m_btDir * (m_Action.ActStand.frame + m_Action.ActStand.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActStand.frame - 1;
        m_dwFrameTime := m_Action.ActStand.ftime;
        m_dwStartTime := GetTickCount;
        m_nDefFrameCount := m_Action.ActStand.frame;
        Shift(m_btDir, 0, 0, 1);
      end;
    SM_WALK, SM_RUSH, SM_RUSHKUNG, SM_BACKSTEP: begin
        m_nStartFrame := m_Action.ActWalk.start + m_btDir * (m_Action.ActWalk.frame + m_Action.ActWalk.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActWalk.frame - 1;
        m_dwFrameTime := m_Action.ActWalk.ftime;
        m_dwStartTime := GetTickCount;
        m_nMaxTick := m_Action.ActWalk.usetick;
        m_nCurTick := 0;
        m_nMoveStep := 1;

        if m_nCurrentAction = SM_BACKSTEP then
          Shift(GetBack(m_btDir), m_nMoveStep, 0, m_nEndFrame - m_nStartFrame + 1)
        else
          Shift(m_btDir, m_nMoveStep, 0, m_nEndFrame - m_nStartFrame + 1);
      end;

    SM_HIT: begin
        m_nStartFrame := m_Action.ActAttack.start + m_btDir * (m_Action.ActAttack.frame + m_Action.ActAttack.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActAttack.frame - 1;
        m_dwFrameTime := m_Action.ActAttack.ftime;
        m_dwStartTime := GetTickCount;
        //WarMode := TRUE;
        m_dwWarModeTime := GetTickCount;
        Shift(m_btDir, 0, 0, 1);
      end;
    SM_STRUCK: begin
        m_nStartFrame := m_Action.ActStruck.start + m_btDir * (m_Action.ActStruck.frame + m_Action.ActStruck.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActStruck.frame - 1;
        m_dwFrameTime := m_dwStruckFrameTime; //pm.ActStruck.ftime;
        m_dwStartTime := GetTickCount;
        Shift(m_btDir, 0, 0, 1);
      end;
    SM_DEATH: begin
        m_nStartFrame := m_Action.ActDie.start + m_btDir * (m_Action.ActDie.frame + m_Action.ActDie.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActDie.frame - 1;
        m_nStartFrame := m_nEndFrame;
        m_dwFrameTime := m_Action.ActDie.ftime;
        m_dwStartTime := GetTickCount;
      end;
    SM_NOWDEATH: begin
        m_nStartFrame := m_Action.ActDie.start + m_btDir * (m_Action.ActDie.frame + m_Action.ActDie.skip);
        m_nEndFrame := m_nStartFrame + m_Action.ActDie.frame - 1;
        m_dwFrameTime := m_Action.ActDie.ftime;
        m_dwStartTime := GetTickCount;
      end;
    SM_SKELETON: begin
        m_nStartFrame := m_Action.ActDeath.start + m_btDir;
        m_nEndFrame := m_nStartFrame + m_Action.ActDeath.frame - 1;
        m_dwFrameTime := m_Action.ActDeath.ftime;
        m_dwStartTime := GetTickCount;
      end;
  end;
end;

对于 NPC 和Hum 虽然有不同但也是按照此思路进行编码。

思考:对于绘制其实是将动作转换成相应的纹理将其绘制出来。而对于一个Actor只有BodySurface 也就是身体的纹理。而对于Hum 则有 武器 头发 翅膀 等效果。那么应该把将动作转换成纹理这个函数独立出来。 并不局限于某个类,将各种纹理 例如武器头发翅膀 这些按照一定规则独立出来。那么则可以以自由的方式组合某个Actor的纹理,可以拥有身体 武器  或者又拥有 头发 翅膀等等。

  这样方便实现变身这种功能。只需要改变动作类型以及选择使用哪些纹理 即可实现。

 ActionDrawObject. HumanDrawAction    MonDrawAction,  NpcDrawAction



你可能感兴趣的:(Delphi,pascal,热血传奇源码分析)