關於熱血傳奇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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章