FC2ブログ
TOP > CATEGORY > ホムンクルスAI考察
BACK | TOP

リストマーク 一から始めるAI講座~第8回:デフォルトのAIを読み解こう・8~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
いよいよ、今回でデフォルトAIの読み解きも最終回です

デフォルトのAIを読み解くことで、
ホムが動く仕組みもだいぶわかってきたのではないでしょうか?
ラストも一気に行きましょう
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-8:攻撃、開始します!

まずは攻撃ですが、意外なほどシンプルです


function  OnATTACK_OBJECT_CMD (id)

  TraceAI ("OnATTACK_OBJECT_CMD")

  MySkill = 0
  MyEnemy = id
  MyState = CHASE_ST

end


要するに、対象を追跡する状態にするだけなんですね
(状態遷移図を思い出してください
 攻撃するためにはまず追跡でしたね)

では、スキルではどうでしょうか?


function  OnSKILL_OBJECT_CMD (level,skill,id)

  TraceAI ("OnSKILL_OBJECT_CMD")

  MySkillLevel = level
  MySkill = skill
  MyEnemy = id
  MyState = CHASE_ST

end


スキルも基本は同じです
ただ、「MySkill」に実行スキルを入れることで、
攻撃する時にスキルを使うようになるわけですね
(「OnATTACK_ST ()」のブロック4参照)

では、未実装ながら、
一応範囲攻撃スキルの場合も見てみましょう


function  OnSKILL_AREA_CMD (level,skill,x,y)

  TraceAI ("OnSKILL_AREA_CMD")

  Move (MyID,x,y)
  MyDestX = x
  MyDestY = y
  MySkillLevel = level
  MySkill = skill
  MyState = SKILL_AREA_CMD_ST
  
end


function OnSKILL_AREA_CMD_ST ()

  TraceAI ("OnSKILL_AREA_CMD_ST")

  local x , y = GetV (V_POSITION,MyID)
  if (GetDistance(x,y,MyDestX,MyDestY) <= GetV(V_SKILLATTACKRANGE,MyID,MySkill)) then  -- ポイントに到着
    SkillGround (MyID,MySkillLevel,MySkill,MyDestX,MyDestY) -- スキル実行
    MyState = IDLE_ST -- 待機状態に
    MySkill = 0
  end

end


単体スキルとの違いは、
まず指定されたところまで「Move」しているところです

そして、「OnSKILL_AREA_CMD_ST ()」で到着したかを判定し、
到着したところでスキル発動→待機となります


ざーっと見てきましたが、以上でデフォルトAIの解説を・・・

・・・と書くと、
「まだ解説してないコマンドがあるんじゃないの?」
と言われそうですが、実行方法が現状ないので、
割愛させていただきます


以上、デフォルトのAIを解説してきました
ホムの中身を知ることで、
また愛着が湧いてきたのではないでしょうか?

とはいえ、デフォルトのAIでは
育成にも手がかかるのが現実です
そこで第2章では、いよいよ本格的な改造に入ります


・・・と、その前に、次回は改造の前に必要な知識を

Close↑

[2006.03.25(Sat) 20:13] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第7回:デフォルトのAIを読み解こう・7~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
さて、デフォルトAIを読み解くのも後2回となりました

今回からコマンドの実行部分についてみていきますが、
基本は同じで、状態がどう遷移するか、です
早速見ていきましょう
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-7:用件、承ります

メインである「AI」関数や、待機状態である「OnIDLE_ST」関数を思い出してください
コマンドを実行するために、
「ProcessCommand (cmd)」という関数が使われていたはずです
まずはこれの中身を見ていきましょう


function  ProcessCommand (msg)

  if    (msg[1] == MOVE_CMD) then
    OnMOVE_CMD (msg[2],msg[3])
    TraceAI ("MOVE_CMD")
  elseif  (msg[1] == STOP_CMD) then
    OnSTOP_CMD ()
    TraceAI ("STOP_CMD")
  elseif  (msg[1] == ATTACK_OBJECT_CMD) then
    OnATTACK_OBJECT_CMD (msg[2])
    TraceAI ("ATTACK_OBJECT_CMD")
  elseif  (msg[1] == ATTACK_AREA_CMD) then
    OnATTACK_AREA_CMD (msg[2],msg[3])
    TraceAI ("ATTACK_AREA_CMD")
  elseif  (msg[1] == PATROL_CMD) then
    OnPATROL_CMD (msg[2],msg[3])
    TraceAI ("PATROL_CMD")
  elseif  (msg[1] == HOLD_CMD) then
    OnHOLD_CMD ()
    TraceAI ("HOLD_CMD")
  elseif  (msg[1] == SKILL_OBJECT_CMD) then
    OnSKILL_OBJECT_CMD (msg[2],msg[3],msg[4],msg[5])
    TraceAI ("SKILL_OBJECT_CMD")
  elseif  (msg[1] == SKILL_AREA_CMD) then
    OnSKILL_AREA_CMD (msg[2],msg[3],msg[4],msg[5])
    TraceAI ("SKILL_AREA_CMD")
  elseif  (msg[1] == FOLLOW_CMD) then
    OnFOLLOW_CMD ()
    TraceAI ("FOLLOW_CMD")
  end
end


「AI(myid)」関数とよく似ていますね
受け取ったコマンドに応じて、対応する関数を呼んでいるだけです
この中で今回は、移動と停止に関する部分を見ていきましょう

まずは「移動」コマンド(ALT+右クリック)です

-- 移動コマンド実行
function  OnMOVE_CMD (x,y)
  
  TraceAI ("OnMOVE_CMD")

  if ( x == MyDestX and y == MyDestY and MOTION_MOVE == GetV(V_MOTION,MyID)) then
    return    -- 目的地と現在地が同一の場合は、処理しない
  end

  local curX, curY = GetV (V_POSITION,MyID)
  if (math.abs(x-curX)+math.abs(y-curY) > 15) then    -- 目的地が一定距離以上なら (サーバーで遠距離は処理しないため)
    List.pushleft (ResCmdList,{MOVE_CMD,x,y})      -- 元の目的地への移動を予約する
    x = math.floor((x+curX)/2)              -- 中間地点へ移動する
    y = math.floor((y+curY)/2)              --
  end

  Move (MyID,x,y) -- 移動実施
  
  MyState = MOVE_CMD_ST -- 「移動コマンド実行中」状態
  MyDestX = x
  MyDestY = y
  MyEnemy = 0 -- ターゲット初期化
  MySkill = 0 -- スキル使用状態初期化

end


-- 移動コマンド実行中状態
function  OnMOVE_CMD_ST ()

  TraceAI ("OnMOVE_CMD_ST")

  local x, y = GetV (V_POSITION,MyID)
  if (x == MyDestX and y == MyDestY) then        -- DESTINATION_ARRIVED_IN
    MyState = IDLE_ST
  end
end


まず、「OnMOVE_CMD (x,y)」関数ですが、
前半の難しい部分はさておき、覚えるべき部分は下の数行です
(前半は、遠い距離を一気に移動できないので、
 移動コマンドを分割しています)

まず、移動を開始し、状態を「移動コマンド実行中」状態にします
そして、敵がいればそのターゲットを初期化しています

つまり、たとえ敵をターゲットしていても、
移動コマンドによって強制的に中断できることを意味します

で、そのコマンド実行中状態の関数「OnMOVE_CMD_ST ()」では、
移動が完了したかを確認し、
完了していれば「待機」状態に遷移させています


続いて「待機」コマンド(右クリックメニューから「待機」を選択)です


-- 待機コマンド実行
function  OnHOLD_CMD ()

  TraceAI ("OnHOLD_CMD")

  MyDestX = 0
  MyDestY = 0
  MyEnemy = 0
  MyState = HOLD_CMD_ST

end


-- 待機コマンド実行中状態
function OnHOLD_CMD_ST ()

  TraceAI ("OnHOLD_CMD_ST")
  
  if (MyEnemy ~= 0) then
    local d = GetDistance(MyEnemy,MyID)
    if (d ~= -1 and d <= GetV(V_ATTACKRANGE,MyID)) then
        Attack (MyID,MyEnemy)
    else
      MyEnemy = 0;
    end
    return
  end


  local  object = GetOwnerEnemy (MyID)
  if (object == 0) then              
    object = GetMyEnemy (MyID)
    if (object == 0) then            
      return
    end
  end

  MyEnemy = object

end


-- 停止コマンド実行
function  OnSTOP_CMD ()

  TraceAI ("OnSTOP_CMD")

  if (GetV(V_MOTION,MyID) ~= MOTION_STAND) then
    Move (MyID,GetV(V_POSITION,MyID))
  end
  MyState = IDLE_ST
  MyDestX = 0
  MyDestY = 0
  MyEnemy = 0
  MySkill = 0

end


まず、「待機コマンド」(「待機」一回目)ですが、
コマンド実行時は「待機コマンド実行中」状態に
変化させているだけです

その状態を示す関数、「OnHOLD_CMD_ST ()」ですが、
わけあってまず後半を見ていくと、
主人か自分に敵がいるかを判断しています

ここでは状態を変化させていませんので
もう一度「OnHOLD_CMD_ST ()」が呼ばれるわけですが、
もし敵がいた場合、攻撃範囲内なら攻撃しています
以下、他のコマンドが指示されるまでそのままです


続いて「停止コマンド」(コマンド二回目)ですが、
立ち状態以外、つまり「何か」をしているときは、
その行動を「Move」関数でキャンセルしています

さらに、「待機」状態に移行し、全ての行動を初期化しています
「待機」に状態が遷移したわけですから、
これ以降は状況に応じた行動をとっていくわけです
(公式HPの説明に合致)


さあ、いかがでしょうか?
もう自力でコードが読めるようになってきたのではないでしょうか?

次回はいよいよラスト、攻撃命令やスキル命令についてみていきます

Close↑

[2006.03.24(Fri) 16:58] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第6回:デフォルトのAIを読み解こう・6~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回までで状態に関する部分は一通り見てきたので、
今回からコマンド部分を・・・と思ったのですが、
一番肝心な「敵を決める部分」を見てなかったんですね

そこで今回は、ちょっと大変ですが、
ホムが敵を認識するコードを読み解いていきましょう
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-6:ホムンクルス的敵の探し方

さて、まずはちょっと大変ですが、
ケミを攻撃している敵を認識するロジックを見ていきましょう
これがわかれば、他はその応用です


function  GetOwnerEnemy (myid)
  local result = 0
  local owner = GetV (V_OWNER,myid) -- 主人
  local actors = GetActors () -- 周囲で動いているもの
  local enemys = {} -- 敵のリスト
  local index = 1
  local target -- 敵のターゲット
  for i,v in ipairs(actors) do -- 周囲の物ごとに判断
    if (v ~= owner and v ~= myid) then -- 自分でも主人でもない?
      target = GetV (V_TARGET,v) -- ターゲット取得
      if (target == owner) then -- ターゲットは主人?
        if (IsMonster(v) == 1) then -- それはモンスター?
          enemys[index] = v -- それは敵である
          index = index+1
        else -- モンスターでない場合
          local motion = GetV(V_MOTION,i) -- それの動きを取得
          if (motion == MOTION_ATTACK or motion == MOTION_ATTACK2) then -- 攻撃中?
            enemys[index] = v -- モンスターじゃないけど敵である
            index = index+1
          end
        end
      end
    end
  end
-- 一番近い「敵」を探す
  local min_dis = 100
  local dis
  for i,v in ipairs(enemys) do
    dis = GetDistance2 (myid,v)
    if (dis < min_dis) then -- 今見ている敵より近い位置にいる?
      result = v -- ターゲット変更
      min_dis = dis -- 敵との最小距離変更
    end
  end
  
  return result -- 敵決定
end


ちと大変ですね・・・
一つずつ見ていきましょう

まず最初に、周囲で動くもの全てを確認しています
これにはホム自身、主人、モンスターの他、
他のキャラなども含まれることに注意してください

次に、自分、主人以外であるかを確認し、
そのターゲットを確認しています

ターゲットが主人であり、しかもモンスターなら、
「敵」として認識しているわけですが、
ターゲットが主人でモンスターじゃないもの、とは何でしょう?

韓国でホムが実装された当時、
主人に支援する味方まで攻撃するバグがあった、というと、
なんとなくわかるのではないでしょうか?

そう、ターゲットするのは支援する味方の場合と、
対人戦の場合の「本当の敵」に分かれます
人が攻撃モーションであるかを確認しているのはこのためです

こうして、最初のループで、いくつかの「敵」候補が決まります
でも、ホムが攻撃できるのは一体だけです
そこで、次のループでは、一番近い敵を探しています

一体ずつ敵との距離を判断し、
今ターゲットしている敵より近ければ、
ターゲットを変更しています


さて、ここまで大丈夫でしょうか?

複雑なようですが、一つ一つでやっていることは
そんなに難しくありません
あせらずに理解してください

今回は一気にやってしまいましょう
続いて、ホムが敵を認識するロジックです


function  GetMyEnemy (myid)
  local result = 0

  local type = GetV (V_HOMUNTYPE,myid)
  if (type == LIF or type == LIF_H or type == AMISTR or type == AMISTR_H or type == LIF2 or type == LIF_H2 or type == AMISTR2 or type == AMISTR_H2) then
    result = GetMyEnemyA (myid)
  elseif (type == FILIR or type == FILIR_H or type == VANILMIRTH or type == VANILMIRTH_H or type == FILIR2 or type == FILIR_H2 or type == VANILMIRTH2 or type == VANILMIRTH_H2) then
    result = GetMyEnemyB (myid)
  end
  return result
end


別な記事でも取り上げましたが、
ここでホムごとの先攻・非先攻を切り替えています

単純にタイプで「GetMyEnemyA (myid)」(非先攻)と
「GetMyEnemyB (myid)」(先攻)を分けているだけですので、
難しいことはないでしょう

問題は、「GetMyEnemyA (myid)」と「GetMyEnemyB (myid)」です
どうやって分けているのでしょうか?


-------------------------------------------
-- 非先攻型 GetMyEnemy
-------------------------------------------
function  GetMyEnemyA (myid)
  local result = 0
  local owner = GetV (V_OWNER,myid) -- 主人
  local actors = GetActors () -- 周囲のもの
  local enemys = {} -- 敵のリスト
  local index = 1
  local target -- ターゲット
  for i,v in ipairs(actors) do -- 周囲の物ごとに判断
    if (v ~= owner and v ~= myid) then -- 主人でも自分でもない?
      target = GetV (V_TARGET,v) -- そのターゲットを確認
      if (target == myid) then -- ターゲットは自分(=ホム)?
        enemys[index] = v -- それは敵である
        index = index+1
      end
    end
  end
-- 一番近い「敵」を探す
  local min_dis = 100
  local dis
  for i,v in ipairs(enemys) do
    dis = GetDistance2 (myid,v)
    if (dis < min_dis) then
      result = v
      min_dis = dis
    end
  end

  return result
end


まずは「GetMyEnemyA (myid)」です
一見難しそうですが、先ほどのケミの敵部分の
簡略化バージョンなのがわかるでしょう

違いは、敵のターゲットが自分であるかを判断しているところです
自分に向かってきていれば敵と判断します(=非先攻)


-------------------------------------------
-- 先攻型 GetMyEnemy
-------------------------------------------
function  GetMyEnemyB (myid)
  local result = 0
  local owner = GetV (V_OWNER,myid) -- 主人
  local actors = GetActors () -- 周囲のもの
  local enemys = {} -- 敵のリスト
  local index = 1
  local type -- 「もの」のタイプ
  for i,v in ipairs(actors) do -- 周囲の物ごとに判断
    if (v ~= owner and v ~= myid) then -- 主人でも自分でもない?
      if (1 == IsMonster(v))  then -- それはモンスター?
        enemys[index] = v -- それは敵である
        index = index+1
      end
    end
  end
-- 一番近い「敵」を探す
  local min_dis = 100
  local dis
  for i,v in ipairs(enemys) do
    dis = GetDistance2 (myid,v)
    if (dis < min_dis) then
      result = v
      min_dis = dis
    end
  end

  return result
end


さあ、今回のラスト、「GetMyEnemyB (myid)」です

「GetMyEnemyA (myid)」との違いは一点、
モンスターを全て敵とみなしているところです(=先攻)
「~A」にあった、ターゲットの確認をやっていません

逆を言えば、他人が殴っている敵であっても、
全て敵として認識してしまうので、横殴りの危険があります
まず直すとしたらここですかね・・・


・・・さて、今回はだいぶたくさん取り上げましたが、
いかがでしょうか?

ここまでを理解しておけば、
デフォルトAIの半分以上は読み解いたも同然です
後残すはコマンドの実行部分のみ

というわけで、次回は前回の予告のとおり、
コマンドごとの実行についてみていきます

Close↑

[2006.03.23(Thu) 20:30] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第5回:デフォルトのAIを読み解こう・5~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回までで、敵の認識・追跡まで見てきました
今回はいよいよ、敵の攻撃部分を見ていきましょう

・・・とはいえ、今までと基本は同じです
難しいことはありませんので、
慌てずにゆっくり読むことが大事です
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-5:戦え、ホムンクルス!

例によって今回も分割してみていきます
状態の変化に気をつけてください


function  OnATTACK_ST ()

  TraceAI ("OnATTACK_ST")
-------------------ブロック1-------------------
  if (true == IsOutOfSight(MyID,MyEnemy)) then  -- 敵を見失った?
    MyState = IDLE_ST
    TraceAI ("ATTACK_ST -> IDLE_ST")
    return
  end
-------------------ブロック2-------------------
  if (MOTION_DEAD == GetV(V_MOTION,MyEnemy)) then -- 敵を倒した?
    MyState = IDLE_ST
    TraceAI ("ATTACK_ST -> IDLE_ST")
    return
  end
-------------------ブロック3-------------------
  if (false == IsInAttackSight(MyID,MyEnemy)) then -- 敵が離れた?
    MyState = CHASE_ST
    MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
    Move (MyID,MyDestX,MyDestY) -- 移動
    TraceAI ("ATTACK_ST -> CHASE_ST : ENEMY_OUTATTACKSIGHT_IN")
    return
  end
-------------------ブロック4-------------------
  if (MySkill == 0) then -- スキルを使う?
    Attack (MyID,MyEnemy) -- 普通に攻撃
  else
    SkillObject (MyID,MySkillLevel,MySkill,MyEnemy) -- スキルで攻撃
    MySkill = 0
  end
  TraceAI ("ATTACK_ST -> ATTACK_ST : ENERGY_RECHARGED_IN")
  return

end


if文で分けると、4つのブロックに分かれていますね

第2回でやった図をもう一度思い出してください
「攻撃」状態からは、4つの条件の矢印が出ていたはずです
まさにそれとこのブロックが対応しているわけです


○ブロック1

 敵を見失ったかの判定をしています
 見失った場合、「待機」状態に遷移しています

○ブロック2

 対象の動作を確認し、「死亡」状態、
 つまり倒したなら、「待機」に戻しています

○ブロック3

 まず、対象が攻撃範囲にいるかを判定し、
 離れていた場合は「追跡」状態に遷移しています

 (ここでなぜか「移動」を実行していますが・・・
  AIの骨組みからは外れている気がします
  対応を高速化するためでしょうか?)

○ブロック4

 ここでやっと、実際の攻撃に入ります

 まず、スキル使用命令が出ているかを確認しています
 スキルを使わないなら普通に攻撃、使う場合はスキルを使う、
 ただそれだけで、非常にシンプルです


繰り返しになりますが、
こうやって状態ごとに動作関数を分離することで、
個々のコードをわかりやすくしています

「攻撃」するまでには多くの条件や動作が必要になりますが、
それまでの判定や動作は、前回までに見てきたコードに分離されており、
ここでは単純に攻撃のことだけを書いているわけですね


さて、今回までで遷移図にあった状態のコードはすべて見てきました
命令を出さない状態のホムがどんな動作をするのか、
これでわかったのではないでしょうか?

次回はいよいよ、コマンドに応じた動きを見ていきます

Close↑

[2006.03.22(Wed) 17:04] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第4回:デフォルトのAIを読み解こう・4~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回は待機中のホムがどんな場合に行動を開始するかを見ていきました
そこで今回は、ホムが実際にどう移動し、
次のアクションを起こすのかを見ていきます
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-4:走れ、ホムンクルス!

まず、基本となる「追尾」状態のコードを見ていきましょう


function  OnFOLLOW_ST ()

  TraceAI ("OnFOLLOW_ST")

  if (GetDistanceFromOwner(MyID) <= 3) then  -- 主人に追いついた?
    MyState = IDLE_ST
    TraceAI ("FOLLOW_ST -> IDLE_ST")
    return;
  elseif (GetV(V_MOTION,MyID) == MOTION_STAND) then
    MoveToOwner (MyID)  -- 主人に向かって移動
    TraceAI ("FOLLOW_ST -> FOLLOW_ST")
    return;
  end

end


構造は実にシンプルです

まず、主人との距離を調べ、
3マス以内なら状態を「待機」に戻しています(移動完了)

一方、まだ距離が離れている場合、
「MoveToOwner (MyID)」関数で実際に移動しています
ここで初めて実際に「移動」しているのがポイントです


さて、これをふまえて、敵を「追跡」する場合を見てみましょう


function  OnCHASE_ST ()

  TraceAI ("OnCHASE_ST")
-------------------ブロック1-------------------
  if (true == IsOutOfSight(MyID,MyEnemy)) then  -- 敵を見失った?
    MyState = IDLE_ST
    MyEnemy = 0
    MyDestX, MyDestY = 0,0
    TraceAI ("CHASE_ST -> IDLE_ST : ENEMY_OUTSIGHT_IN")
    return
  end
-------------------ブロック2-------------------
  if (true == IsInAttackSight(MyID,MyEnemy)) then -- 射程範囲?
    MyState = ATTACK_ST
    TraceAI ("CHASE_ST -> ATTACK_ST : ENEMY_INATTACKSIGHT_IN")
    return
  end
-------------------ブロック3-------------------
  local x, y = GetV (V_POSITION,MyEnemy)
  if (MyDestX ~= x or MyDestY ~= y) then      -- 敵と離れている?
    MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
    Move (MyID,MyDestX,MyDestY)  -- 敵の位置に移動
    TraceAI ("CHASE_ST -> CHASE_ST : DESTCHANGED_IN")
    return
  end

end


例によって、if文を元に3つのブロックに分けてみます

最初のブロックでは、敵を見失ったかの判定をしています
(倒された、画面外に逃げた・・・等)
この場合、状態を「待機」に変えて、追跡を終了しています

次のブロックでは、敵が攻撃可能範囲にいるかを調べています
範囲内であれば、状態を「攻撃」に変更しますが、
ここで攻撃しているわけでないことに注意してください

最後のブロックでは、敵との位置関係を調べ、
敵の位置に移動しています
ここで初めて移動していることがポイントですね


前回もさんざん書いたように、状態の変化が主なポイントであり、
「追跡」「追尾」どちらの場合も、
「移動」以外のアクションを起こしていないことに注意してください

「攻撃」のような別なアクションは、
その状態のところで定義されているのです


だいぶコードにも慣れてきたでしょうか?
ポイントは、無理に細かな内容をつめようとせず、
大雑把に「何をしているのか?」をつかむことです

次回はいよいよ、どうやって攻撃しているのか、を見ていきます

Close↑

[2006.03.21(Tue) 20:06] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第3回:デフォルトのAIを読み解こう・3~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回は状態の遷移について見ていきましたが、
今回からは、各状態で何をしているのか、
具体的に見ていくことにします

何もコマンドを与えられていないホムは、
一見ただぼーっとしているように見えます
しかし、実際には常に敵を見張り、臨戦態勢にあるのです

今回は待機状態のホムンクルスが何をしているのか、
具体的なコードから見ていくことにしましょう
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-3:ホムンクルスは眠らない


第1回の復習をしましょう
AI(myid)関数の中身、700行目あたりをみると・・・


  if (MyState == IDLE_ST) then
    OnIDLE_ST ()

・・・つまり、OnIDLE_ST ()が
待機状態の動きをを定義していることになりますね

では、実際に240行目付近を見てみましょう
ポイントは前回やったように、
待機状態からどの状態に遷移しているか、です


function  OnIDLE_ST ()
  
  TraceAI ("OnIDLE_ST")
-------------------ブロック1-------------------
  local cmd = List.popleft(ResCmdList)
  if (cmd ~= nil) then    
    ProcessCommand (cmd)  -- 予約コマンド処理
    return
  end
-------------------ブロック2-------------------
  local  object = GetOwnerEnemy (MyID)
  if (object ~= 0) then              -- MYOWNER_ATTACKED_IN
    MyState = CHASE_ST
    MyEnemy = object
    TraceAI ("IDLE_ST -> CHASE_ST : MYOWNER_ATTACKED_IN")
    return
  end
-------------------ブロック3-------------------
  object = GetMyEnemy (MyID)
  if (object ~= 0) then              -- ATTACKED_IN
    MyState = CHASE_ST
    MyEnemy = object
    TraceAI ("IDLE_ST -> CHASE_ST : ATTACKED_IN")
    return
  end
-------------------ブロック4-------------------
  local distance = GetDistanceFromOwner(MyID)
  if ( distance > 3 or distance == -1) then    -- MYOWNER_OUTSIGNT_IN
    MyState = FOLLOW_ST
    TraceAI ("IDLE_ST -> FOLLOW_ST")
    return;
  end

end


また長いコードですね^^;
今回も分割して考えましょう

if~endで分割していくと、
大きく分けて4つのブロックがあるのがわかりますね
一つずつブロックの動きを見ていきましょう


○ブロック1

 AI(myid)関数を見直してください
 最初にコマンドの予約をしていました
 その予約コマンドをここで実行しています

○ブロック2

 GetOwnerEnemy (MyID)という関数名からもわかるように、
 ケミを攻撃している敵がいるかを判断しています

 もしいた場合、「MyState = CHASE_ST」という文の通り、
 「待機→追跡」に状態を変えています

 GetOwnerEnemy (MyID)の中身については、
 また次回以降に詳しく見ていきましょう

○ブロック3

 ブロック2と同じですね
 GetMyEnemy (MyID)という関数名の通り、
 自分を攻撃している敵を判断しています

 ここでもポイントは状態の遷移
 「待機→追跡」に状態を変えていますね

○ブロック4

 一言でいうと、主人であるケミとの距離を調べ、
 離れていたら追いかけています

 「GetDistanceFromOwner(MyID)」で距離を調べ、
 3マスより離れているか、画面内に見当たらない場合、
 「追尾」状態に遷移しています


まとめると、待機中のホムは以下の行動をとっています

 1.予約されたコマンドの実行
 2.主人を攻撃する敵の「判定」(追跡、ではない)
 3.自分を攻撃する敵の「判定」(追跡、ではない)
 4.主人との距離の「判定」(追尾、ではない)

あえて「○○、ではない」としつこく書きましたが、
ここがポイントです

あくまでここでやっているのは「状態の遷移」だけであり、
具体的にどう追いかけるか、といった処理は、
各状態の関数に分離されているのです


なぜ、前回わざわざ状態の遷移について見たのか、
今回の話からなんとなくわかったのではないでしょうか?

各状態の関数では、処理とともに状態の遷移を行っており、
これによって処理を分離し、コードが絡まらないようにしているのです
そういった意味で、このデフォルトAIはよくできていると思います


次回も状態のコードについて見ていきましょう

Close↑

[2006.03.17(Fri) 20:41] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第2回:デフォルトのAIを読み解こう・2~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回はメイン関数的な部分を読み解くことで、
デフォルトAIの大雑把な骨組み
(大げさに言えば「フレームワーク」)を見ていきました

しかし、この「骨組み」を理解するのに、最も重要な要素、
「状態」については軽く触れただけでした

今回はちょっとコードから離れまして、
ホム制御の要となる、「状態遷移」について見ていきます
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-2:ホムンクルスの状態遷移

まず、マニュアルを開きましょう
こんな図はありませんでしたか?
状態遷移図
これを「状態遷移図」と言いますが、
情報処理関係の資格をお持ちの方なら、特に解説は不要でしょう
というより、今回の話自体が不要でしょう

この図、デフォルトのAIを読み解く上で、
まさに鍵となる図なのですが、読み方を解説しつつ、
例を挙げてみます


まず、黒丸がスタートで、
「休息」のように四角で囲まれた部分が「状態」を表します
そして、各状態を矢印が結び、その矢印をたどる「条件」が書かれています

では、例として「ケミが攻撃された場合」を見てみましょう

スタートは「休息」です
ここから右矢印に沿って「追跡」状態になり、
ホムは敵に向かって接近していきます

ここで敵が動いたとすると、
丸い矢印に沿って「追跡」を続行します
丸い回帰矢印は、その状態を続ける条件を示しています

「追跡」の結果、攻撃範囲内に入ると、
下矢印に沿って「攻撃」状態に移行します
一度の攻撃では倒れなかった場合、「攻撃」を続行します

ここで、敵が離れてしまった場合、
今度は上矢印に沿って「追跡」を行い、
追いついたところで「攻撃」します

最後に、敵が消滅したところで、
左上矢印に沿って「休息」状態になります

文章で書くとわかりづらいので、まとめると・・・

 1.ケミが攻撃されたことを認識
 2.「休息」から「追跡」状態に移行
 3.追いつくまで「追跡」続行
 4.追いついたら「攻撃」開始
 5.倒すまで「攻撃」続行
 6.離れてしまったら「追跡」→「攻撃」を行う
 7.敵を倒したら「休息」に戻る

・・・こんな感じです

つまり、「ホムが攻撃する」というだけで、
これだけの状態遷移が発生していることになります


このように、ホムはさまざまな状態をとり、
ある条件で状態を遷移させていきます
その繰り返しによって、ホムは操作されているのです

実は、デフォルトAIの一番最初に、
ホムの取りうる状態が定義されています

-----------------------------
-- state
-----------------------------
IDLE_ST          = 0
FOLLOW_ST          = 1
CHASE_ST          = 2
ATTACK_ST          = 3
MOVE_CMD_ST          = 4
STOP_CMD_ST          = 5
ATTACK_OBJECT_CMD_ST      = 6
ATTACK_AREA_CMD_ST      = 7
PATROL_CMD_ST        = 8
HOLD_CMD_ST          = 9
SKILL_OBJECT_CMD_ST      = 10
SKILL_AREA_CMD_ST        = 11
FOLLOW_CMD_ST        = 12
----------------------------

この13の状態を、さまざまな条件で遷移して、
ホムは動いていくわけです


今回の話は少し難しかったでしょうか?

これを理解しておくと、次回以降、
各状態でのコードを見たとき、意味がわかりやすくなります
大雑把でいいので、頭に入れておくと良いでしょう


次回は具体的な状態ごとのコードを読み解いていきます

Close↑

[2006.03.16(Thu) 14:57] ホムンクルスAI考察Trackback(0) | Comments(1) 見る▼
↑TOPへ


COMMENT

管理人のみ閲覧できます by -

コメントを閉じる▲

リストマーク 一から始めるAI講座~第1回:デフォルトのAIを読み解こう・1~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
ホムンクルスのAIは自分の好きなように書き換えることで、
自由に動作させることができます

とはいえ、スクリプトは「Lua」という言語で書かれており、
そもそも一般になじみがない上、
プログラムを書いたことのない人には全くもって意味不明です

そんな人でもAIを自由に書けるように、
今回から連載をしてみたいと思います


・・・と、その前に下準備を

クライアントフォルダに「AI」フォルダができていると思いますが、
この下に「USER_AI」というフォルダを作ってください
ここにAIを置くと、「/hoai」コマンドで切り替えが可能になります

これをやっておかないと、
アップデートでAIが書き換わってしまうので注意を
Open↓

  ~1章~ デフォルトのAIを読み解く

 1-1:メイン関数「function AI(myid)」

マニュアルには目を通しましたか?
意味がわからなかった、という方も多いでしょう

とりあえず、C言語でいうところの「main()」、
つまり、最初に実行されるのがどこかというと、
680行目付近にある「function AI(myid)」という関数です


function AI(myid)
-------------------ブロック1-------------------
  MyID = myid
  local msg  = GetMsg (myid) -- コマンド
  local rmsg  = GetResMsg (myid) -- 予約コマンド

  
  if msg[1] == NONE_CMD then -- コマンドが入力された?
    if rmsg[1] ~= NONE_CMD then -- 予約コマンドがある?
      if List.size(ResCmdList) < 10 then -- 予約コマンドは10個以下?
        List.pushright (ResCmdList,rmsg) -- 予約コマンド追加
      end
    end
  else
    List.clear (ResCmdList) -- 予約コマンドクリア
    ProcessCommand (msg) -- コマンド実行
  end
-------------------ブロック2-------------------
    
  -- 状態処理
  if (MyState == IDLE_ST) then
    OnIDLE_ST ()
  elseif (MyState == CHASE_ST) then
    OnCHASE_ST ()
  elseif (MyState == ATTACK_ST) then
    OnATTACK_ST ()
  elseif (MyState == FOLLOW_ST) then
    OnFOLLOW_ST ()
  elseif (MyState == MOVE_CMD_ST) then
    OnMOVE_CMD_ST ()
  elseif (MyState == STOP_CMD_ST) then
    OnSTOP_CMD_ST ()
  elseif (MyState == ATTACK_OBJECT_CMD_ST) then
    OnATTACK_OBJECT_CMD_ST ()
  elseif (MyState == ATTACK_AREA_CMD_ST) then
    OnATTACK_AREA_CMD_ST ()
  elseif (MyState == PATROL_CMD_ST) then
    OnPATROL_CMD_ST ()
  elseif (MyState == HOLD_CMD_ST) then
    OnHOLD_CMD_ST ()
  elseif (MyState == SKILL_OBJECT_CMD_ST) then
    OnSKILL_OBJECT_CMD_ST ()
  elseif (MyState == SKILL_AREA_CMD_ST) then
    OnSKILL_AREA_CMD_ST ()
  elseif (MyState == FOLLOW_CMD_ST) then
    OnFOLLOW_CMD_ST ()
  end

end


さて、いきなりわけがわかりませんね^^;

大きく分けて、この関数は2つのブロックに分かれています
前半はコマンドの処理、後半は状態に応じた処理を定義しています

まず前半ですが・・・一言で言えばこれだけです

 「入力されたコマンドを実行する」

予約だなんだとありますが、要するにコマンドを実行するところです
(ProcessCommand (msg)関数については次回以降に)

後半は、ホムの状態に応じた処理を実行するところです
一つ抜き出してみると・・・

  elseif (MyState == FOLLOW_ST) then
    OnFOLLOW_ST ()

これは「もし、ホムが何かを追いかけていたら、
OnFOLLOW_ST()を実行してください」というだけです

他の状態についても、「もし、○○という状態なら、
△という関数を実行してください」と列挙しているに過ぎません


この「状態」については、次回に詳しく見ていきますが、
非常にシンプルな作りになっていることがわかります


以上、AI(myid)関数についてみてきましたが、
覚えておくべきことはただ一つです

 「function AI(myid)はいじらなくていい」

・・・以上!!


次回は前述の通り、ちょっと本筋から離れて、
ホムの状態遷移について見ていきます

Close↑

[2006.03.15(Wed) 17:12] ホムンクルスAI考察Trackback(0) | Comments(2) 見る▼
↑TOPへ


COMMENT

by ファルティス
私もホム持ちなのでありがたいっ。
なんとなく概要がわかりました。

ウチのバミルも非先行にしようかな。

by パロット
おお、ホム持ちでしたか^^

今日も解説を追加しましたので、
読んでみてくださいね

コメントを閉じる▲

BACK | TOP

プロフィール

Categories

Recent Entries

Recent Comments

Archives

AdSense

Calander

ブログ内検索

RO Search

Links

Copylights

Amazon