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

リストマーク AI講座・上級編~第1回:コマンド入力を実装する~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
今回からは不定期に、私が改造を加えたAIを紹介していきます

第1回はホムAIスレからヒントを得て、
「コマンド入力」でホムの動きを制御するやり方を紹介します

これを使うことで、一つのAIで
先攻・非先攻の切り替えが可能になったりします
ぜひ応用して試してみてください
スポンサーサイト



Open↓

  ~3章~ ちょっと難しい改造

 3-1:スイッチを入れてみよう

今回から改造内容が複雑になるので、
先にAIを公開しておきます

AI 2006/04/25版

保存して、「AI.lua」に名前を変えればそのまま動きます

さて、具体的にどのように「コマンド」を実装するか、ですが、
「右左右」のように、ホムが特定の動きをしたとき、
スイッチが入るようにしてみます

例えば、ホムを以下のように動かした時、
何らかのスイッチが切り替わるようにするわけです

 □□□   □□□   □□□ 
 □●■ → ■●□ → □●■ 
 □□□   □□□   □□□ 
●:主人 ■:移動先



具体的な判別方法ですが、
主人の座標とホムの移動先を比較すれば、
「コマンド」であるかを判別できます

具体的なコードは以下の通りです


------------------------------------------
-- mode flag
------------------------------------------
MODE_ACTIVE = 0   -- 先攻モード
MODE_NONACTIVE = 1   -- 非先攻モード
MyActiveMode = MODE_NONACTIVE   -- 先攻モードフラグ
ActiveModeCounter = 0   -- コマンドカウンター

------------------------------------------

function  OnMOVE_CMD (x,y)
  
  TraceAI ("OnMOVE_CMD")

       (中略)

  Move (MyID,x,y)  
  
  MyState = MOVE_CMD_ST
  MyDestX = x
  MyDestY = y
  MyEnemy = 0
  MySkill = 0

  CheckMoveCommand()

end


function CheckMoveCommand()

  local ownerX, ownerY
  ownerX, ownerY = GetV (V_POSITION,GetV(V_OWNER,MyID)) -- 主人の座標

-- 先攻・非先攻切り替え
  if (MyDestY == ownerY) then
    if (ActiveModeCounter == 0 and MyDestX == ownerX+1) then -- 右
      ActiveModeCounter = 1
    elseif (ActiveModeCounter == 1 and MyDestX == ownerX-1) then -- 左
      ActiveModeCounter = 2
    elseif (ActiveModeCounter == 2 and MyDestX == ownerX+1) then -- 右
      ActiveModeCounter = 0
      if (MyActiveMode == MODE_NONACTIVE) then -- 先攻切り替え
        MyActiveMode = MODE_ACTIVE
      else
        MyActiveMode = MODE_NONACTIVE
      end
    else
      ActiveModeCounter = 0
    end
  else
    ActiveModeCounter = 0
  end

end


CheckMoveCommandという関数がスイッチを判定する核となるところです
デフォルトAIのOnMOVE_CMD関数の最後にこの判定スイッチを入れることで、
移動がコマンドに沿ったものであるかを判断できるわけです

続いて核となるCheckMoveCommandですが、
まず主人の座標を取得しています

その上で、移動先が主人の右ならカウント+1、
カウント1で主人の左ならカウント2・・・のようにして、
「右左右」の動きが完成したところで、MyActiveModeフラグを入れ替えています

で、実際にMyActiveModeフラグをどう使っているかというと・・・


function  GetMyEnemy (myid)

     (中略)

-- 他の敵を探す
  if (get_flg == 0 and MyActiveMode == MODE_ACTIVE) then
  -- 自分を攻撃する敵がいない? & 自分が先攻モード?
    local max_dis = 10 -- 索敵範囲
    local target_dis
    for i,v in ipairs(actors) do -- 敵を探す
      if (v ~= owner and v ~= myid) then
        target_dis = GetDistance2 (myid,v) -- 距離計算
        if (target_dis < max_dis) then -- 近いところにいる?
          if (1 == IsMonster(v))  then
            target = GetV (V_TARGET,v) -- 対象のターゲット確認
            if (target == 0) then -- ターゲットがいない
              enemys[index] = v
              index = index+1
              get_flg = 1
            end
          end
        end
      end
    end
  end

     (後略)

end


・・・このように、索敵時にフラグの判定を入れることで、
先攻・非先攻を切り替えることが可能が可能です


どうでしょうか
このように動きをコマンドスイッチとすることで、
一つのAIで複数の動きをさせることが可能になります

ちなみに、先ほど公開したAIには、
もう一つのスイッチが実装されています
コードをよーく読んで、何ができるのかを調べてみてください

それでは、またいつか

Close↑

[2006.04.25(Tue) 16:41] ホムンクルスAI考察Trackback(0) | Comments(2) 見る▼
↑TOPへ


COMMENT

なーゅりさーん((=’Å’=)) by KaMuRi
ばかっぽぃケドブログ書くことにしたんですー((=’Å’=))
リンクだけでもはらしてもらってぇぇすかぁ??(′・ω・`)モフ

すでに昨日のうちに・・・ by パロット
Blogの存在は確認済みです^^
こちらからリンクしましたので、
そちらもご自由に^^

コメントを閉じる▲

リストマーク 一から始めるAI講座~最終回?:AIの限界~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
AI講座の次のネタとして、
鳥の「フリットムーブ」を一定時間ごとに使う、
というのを考えていたのですが・・・

ロジック自体はできてます
しかし、問題はスキルIDの取得なんですよ

いろいろ試行錯誤したり、調べてみた結果、
「ターゲットの存在しないスキルはID取得不能」
というのが結論のようです


そんなわけで、私が考えていたAIネタは尽きたわけですが、
AIの可能性は、アイデア次第でまだまだ広がっていきます

もし「こんなことがしてみたいけど実装がわからない」
といったネタがありましたら、
コメントをもらえれば実装を試しますので、ぜひお願いします


それでは、また会う日まで・・・
[2006.04.15(Sat) 17:50] ホムンクルスAI考察Trackback(0) | Comments(11) 見る▼
↑TOPへ


COMMENT

おつかれさまです。 by えび
スキルについては、いろいろと違う情報サイトを見て値がわかりました。
分からないことや、まだ調べてないことなんですが、
ホムが死んでしまった後は、AIによりスキルが使えないんでしょうか。
例えばですが、死んでしまったとき、ログアウト・ケミがリザといった自動機能などですが。
いろいろなところを調べてきます。
おつかれさまでした。

by えび
今私は、GetTick()の使用と、敵フィッシング行為を勉強中です。
敵を、フィッシングすることで、主人の近くで敵を倒すということができれば、スタックする可能性、MHへの危険性が減ると思うんです。
ただ、効率はへると思いますが・・・
敵IDの消失は、なんとなく分かるのですが、
見つけた敵を攻撃する瞬間がわからないのです。
どこにあるのでしょうか・・・という質問です。
また、自分でも見つけるのを頑張っていきます。

by パロット
前者に関してはグレーな範囲ですので、
方法を見つけたとしても、自己責任でお願いします
特に、所属Gに迷惑をかけないように

後者に関しては、「OnATTACK_ST」関数内で
主人に戻るようにすればできるかなと
「攻撃する瞬間」=「Attack()」ですね

横殴りするみたいです・・・ by えび
いつもは、下水で狩りしていたので
横殴りとか、12回目に書いてあるのがそのままなんですが、
もしかして、索敵以外にも、追跡モードのとき全てに、攻撃対象確認を入れないといけないんでしょうか。
今、たまたまプロボックかけた状態のポリンが近くを通ったんですが、
横殴りしてしまったのですorz

by パロット
どのように修正されたのかわかりませんが、
GetMyEnemy関数が丸ごと置き換わっていれば、
各状態ではその関数を呼んでいるだけですので、
横殴りは発生しないはずです

「上級編」のエントリに、
実際に私が使っているAIを置いてますので、
比較してみてはいかがでしょうか

by えび
ありがとうございます。
試してみます。
早急なご返事感謝します。

by えび
比較してみましたところ、
私のものは、「画面外から来た一度も攻撃をしていないトレインしている状態のモンスター」
にのみ、横殴りが発生してしまうらしいです。
これは、プロボックをした状態のモンスターを一度も危害を加えずにトレインしている という場合において、横殴りが発生しやすいということなんですが、
今から、比較してみます。
それでは、ありがとうございました。

よくわかりません。 by えび
すみません。上の見本のがノンアクから始まることを忘れていて、そのまま実験してました。
私の同様、上のコメントどおり、
「一度も、攻撃を食らうことなくトレインしている人が、画面外からくると」
という場合、横殴りしますね・・・
これは、プロ南で遊んでいる最中に、よくポリンが倒しに行ってしまったので気づいたのですが・・・
モンスターのターゲットが確定されるのは、モンスター自身が攻撃を加える瞬間だというのが、結果としてでました。

by パロット
なるほど・・・

現象から推測するに、
以下のコードで問題(?)があるようですね

 target = GetV(V_TARGET,v) -- 対象のターゲット確認
 if (target == 0) then -- ターゲットがいない

ここでモンスターがターゲットを持っているかを判定していますが、
この「ターゲット」はAttack関数の引数に取っているIDを指すようです

ということは、モンスターが攻撃状態でないと、
「タゲを持っている」ことを認識できないので、
攻撃対象にしてしまう、と

どうしてもこの現象を回避したいのであれば、
モンスターのモーションも取得して、
それが「待機」状態のものだけ対象にすれば、
たぶん回避できます
(=移動しているものは対象にしない)

ただし、タゲを持っていなくても、
移動中のモンスターを対象にしなくなりますが・・・

by パロット
付け加えると・・・

>モンスターのターゲットが確定されるのは、モンスター自身が攻撃を加える瞬間だ

おっしゃるとおり、ということですね

コメントを閉じる▲

リストマーク 一から始めるAI講座~第14回:スキルを使ってみよう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回はTraceAI関数の使い方を中心に、
スキルのIDを調べる方法を見ていきました

今回はそのIDを元に、自動でスキルを使う方法を見ていきましょう
・・・といっても、これもデフォルトAIの応用ですけどね
Open↓

  ~2章~ 攻撃パターンを改造する

 2-5:スキルでGO!

前回調べたスキルID、実は鳥のムーンライトのものでした
コードに一見無意味な数字を埋め込むのはよろしくないので、
定数を定義してしまいましょう


------------------------------------------
-- skill id
------------------------------------------
S_MOONLIT = 8009
------------------------------------------


こうなれば、後は簡単
スキルを使うための関数は用意されていましたね

 SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)

OnATTACK_ST関数でのこの記述は、
クライアントから送られた情報を元に決まっていましたが、
このスキルとレベルを書き換えれば、
自由にスキルを使えることになります

そこで、以下のようにしてみました


function  OnATTACK_ST ()

  TraceAI ("OnATTACK_ST")
  
  if (true == IsOutOfSight(MyID,MyEnemy)) then  -- ENEMY_OUTSIGHT_IN
    MyState = IDLE_ST
    TraceAI ("ATTACK_ST -> IDLE_ST")
    return
  end

  if (MOTION_DEAD == GetV(V_MOTION,MyEnemy)) then -- ENEMY_DEAD_IN
    MyState = IDLE_ST
    TraceAI ("ATTACK_ST -> IDLE_ST")
    return
  end

-- 主人を見失ったか?
  local max_dis = 10 -- 主人との最大距離
  local dis = GetDistanceFromOwner(MyID)
  if (dis > max_dis) then  -- 主人から離れた?
    MoveToOwner (MyID) -- 主人のそばへ
    MyState = FOLLOW_ST -- 追尾状態へ
    MyEnemy = 0 -- 敵を初期化
    TraceAI ("CHASE_ST -> FOLLOW_ST : MASTER_OUTSIGHT_IN")
    return
  end
    
  if (false == IsInAttackSight(MyID,MyEnemy)) then -- ENEMY_OUTATTACKSIGHT_IN
    MyState = CHASE_ST
    MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
    Move (MyID,MyDestX,MyDestY)
    TraceAI ("ATTACK_ST -> CHASE_ST : ENEMY_OUTATTACKSIGHT_IN")
    return
  end
  
  if (MySkill == 0) then
    SkillObject (MyID,5,S_MOONLIT,MyEnemy)
    Attack (MyID,MyEnemy)
  else
    SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)
    MySkill = 0
  end
  TraceAI ("ATTACK_ST -> ATTACK_ST : ENERGY_RECHARGED_IN")
  return


end


一行加えるだけで、攻撃時に必ずスキルを使うようになります
ついでに、以前に改造した、離れすぎないようにする修正を、
攻撃時にも加えておきました

しかし、これでは問題があります
このままではSPが枯渇するまでスキルを使い続け、
いざと言う時にスキルが使えなくなります

そこで、SPを使いすぎないように修正してみました


  if (MySkill == 0) then
    local my_sp
    my_sp = GetV (V_SP,MyID)/GetV (V_MAXSP,MyID)
    if (my_sp > 0.5) then

      SkillObject (MyID,5,S_MOONLIT,MyEnemy)
    else
      Attack (MyID,MyEnemy)
    end
  else
    SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)
    MySkill = 0
  end


ポイントはmy_spです
現在のSPと最大SPを計算し、
50%以上残っている時だけスキルを使うようにしました

これでだいぶましになりましたが、まだ問題があります

例えば、HP600の敵に対しスキルで500与えたとすると、
HPの残りは100ですが、SPが残っていれば、
またスキルを使ってしまいます

こういう場合は通常攻撃して欲しいので、さらに修正してみました


  if (MySkill == 0) then
    local my_sp
    local ememy_hp
    my_sp = GetV (V_SP,MyID)/GetV (V_MAXSP,MyID)
    ememy_hp = GetV (V_HP,MyEnemy)/GetV (V_MAXHP,MyEnemy)
    if (my_sp > 0.5 and ememy_hp > 0.5) then
      SkillObject (MyID,5,S_MOONLIT,MyEnemy)
    else
      Attack (MyID,MyEnemy)
    end
  else
    SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)
    MySkill = 0
  end


SPの計算と同じ原理で、敵のHPを計算し、
相手のHPが半分以上残っている時だけスキルを使うようにしました


以上、スキルの使い方を見てきましたが、
今回の修正はあくまで鳥の場合であって、
他のホムには使えません

ですが、以前にも書いたように、
大事なのは考え方のプロセスであって、
他のホムのスキルについても同じように改造できるはずです
自分のホムにあった形で、いろいろ試してみてください

次回は別な形でスキルの使い方をみていきます

Close↑

[2006.04.10(Mon) 09:44] ホムンクルスAI考察Trackback(0) | Comments(0) 見る▼
↑TOPへ


リストマーク 一から始めるAI講座~第13回:スキルのIDを調べよう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回まででホムンクルスの基本的な動きは修正できたと思います
今回からは応用編として、
スキルをAIに組み込む方法を考えていきます

その前準備として、今回はTraceAI関数の使い方を見ていきましょう
Open↓

  ~2章~ 攻撃パターンを改造する

 2-4:お前の動きは見切った!

コードの中になんとなく埋め込まれている「TraceAI」関数、
いまいち意味がわからない、という方も多いことでしょう

ではまず、ROでホムを呼び出して、
以下のコマンドを打ち込みます

 /traceai

しばらくホムを動かしたら、もう一度同じコマンドを打ち込みます
これでデバッグログが出力されたことになります

そうしたら一度ROを終了して、
ROのフォルダを開いてみましょう
「TraceAI.txt」というファイルができていませんか?

これを開いてみると・・・


2006鰍 4杉 7析 8獣 50歳 16段 OnIDLE_ST
2006鰍 4杉 7析 8獣 50歳 16段 IDLE_ST -> CHASE_ST : ATTACKED_IN
2006鰍 4杉 7析 8獣 50歳 16段 OnCHASE_ST
2006鰍 4杉 7析 8獣 50歳 16段 CHASE_ST -> ATTACK_ST : ENEMY_INATTACKSIGHT_IN
2006鰍 4杉 7析 8獣 50歳 16段 OnATTACK_ST
2006鰍 4杉 7析 8獣 50歳 16段 ATTACK_ST -> IDLE_ST
2006鰍 4杉 7析 8獣 50歳 16段 OnIDLE_ST
2006鰍 4杉 7析 8獣 50歳 16段 IDLE_ST -> CHASE_ST : ATTACKED_IN


・・・私の環境だと明らかに文字化けしてますが、
大事なのは右側です

コードに埋め込まれた文字列がずらっと並んでますね?
ホムはこの順番でAIを動いたことがわかります

さて、TraceAIの使い方がわかったところで、
問題はスキルのIDです
どうすればよいのでしょうか?

そもそも、TraceAI関数の引数は「文字」であって、
「数字」ではありません
他の言語のつもりで以下のように書くと、エラーになります


function  OnSKILL_OBJECT_CMD (level,skill,id)

  TraceAI ("OnSKILL_OBJECT_CMD")

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

-- 間違った書き方
  TraceAI ("SKILL_ID = " + skill) -- エラー

end


結論から言うと、Luaでは文字列の連結に、
ピリオド二つ「..」を使います
よって、正しくは以下のようになります


function  OnSKILL_OBJECT_CMD (level,skill,id)

  TraceAI ("OnSKILL_OBJECT_CMD")

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

-- 正しい書き方
  TraceAI ("SKILL_ID = "..skill) -- エラーにならない

end


この状態でROを起動し、「/traceai」を打ち込んだ後、
適当なスキルを使ってみましょう


2006鰍 4杉 7析 8獣 50歳 19段 OnSKILL_OBJECT_CMD
2006鰍 4杉 7析 8獣 50歳 19段 SKILL_ID = 8009
2006鰍 4杉 7析 8獣 50歳 19段 SKILL_OBJECT_CMD
2006鰍 4杉 7析 8獣 50歳 19段 OnCHASE_ST
2006鰍 4杉 7析 8獣 50歳 19段 CHASE_ST -> CHASE_ST : DESTCHANGED_IN


お、何か数字が出てきましたね
この数字、同じスキルであれば、何回使っても変わりません
つまり、スキルに固有の値=IDであるとわかります


どうでしょう、TraceAI関数の使い方はわかったでしょうか?
これを使えば、AIの中でやり取りされるあらゆる値を、
デバッグログに吐き出すことができます

次回は判明したIDを使って、
攻撃時にスキルを使うように改造してみましょう

Close↑

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


リストマーク 一から始めるAI講座~第12回:横殴りを避けよう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
いよいよお待ちかね、横殴り防止の改造です
結果からいえば、改造はたった3行で終わります

しかし、この連載の目的は、自分で改造できるように
スキルを身につけてもらうことですから、
きちっと解説を加えた上でソースを出しますね
Open↓

  ~2章~ 攻撃パターンを改造する

 2-3:横殴りはダメよ♪

AIに「横殴りするな」とは書けませんので、
Luaで書けるように噛み砕く必要があります
が、ヒントはすでにデフォルトAIの中にあるのです

「横殴り」とは「他者が戦闘中の敵を殴る」ことですから、
「他者が戦闘している」ことを調べればいいことになります

思い出してください
主人の敵を調べる部分や、自分の敵を調べる部分で、
対象のターゲットを調べていたはずです

 target = GetV (V_TARGET,v)

これですね
ということは、誰かがターゲットしている敵は
以下のように書けます

 GetV (V_TARGET,v)~=0

裏を返せば、殴っていいのは、
「誰もターゲットしてない敵」ですね
よって、以下のようになります


function  GetMyEnemy (myid)
  local result = 0
  local owner = GetV (V_OWNER,myid)
  local actors = GetActors ()
  local enemys = {}
  local index = 1
  local target
  local get_flg = 0 -- 敵発見フラグ

-- 自分を攻撃する敵を探す
  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
        get_flg = 1 -- 敵発見
      end
    end
  end

-- 他の敵を探す
  if (get_flg == 0) then -- 自分を攻撃する敵がいない?
    local max_dis = 10 -- 索敵範囲
    local target_dis
    for i,v in ipairs(actors) do -- 敵を探す
      if (v ~= owner and v ~= myid) then
        target_dis = GetDistance2 (myid,v) -- 距離計算
        if (target_dis < max_dis) then -- 近いところにいる?
          if (1 == IsMonster(v))  then
            target = GetV (V_TARGET,v) -- 対象のターゲット確認
            if (target == 0) then -- ターゲットがいない
              enemys[index] = v
              index = index+1
              get_flg = 1
            end
          end
        end
      end
    end
  end

-- 一番近い敵を探す
  if (get_flg == 1) then -- 敵がいる
    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
  end

  return result
end


どうでしょう
たった3行なのがわかりますね?

最初にも書きましたが、結果は非常に単純です
でも、そこに至るプロセスを知ることで、
自分で改造する時の力になります


ちょっと横道にそれますが、
プログラミングにおいてはこのプロセスが非常に大事で・・・

 問題提起→解決策発見→仕様策定→実装

・・・こんな感じでしょうか?
今回の場合でいえば・・・

  横殴りしたくない
 →誰もターゲットしてない敵を狙う
 →GetV (V_TARGET,v)==0だけ敵とする
 →実装

・・・こんな感じですね


さて、ここまでの改造で、ホムの基本的な動きは
ほぼ問題なくなったと思います

次回は一歩踏み込んで、スキルについてのお話を
TraceAIの使い方にも触れる予定です

Close↑

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


リストマーク 一から始めるAI講座~第11回:スタックを避けてみよう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回は本格的な改造に入りましたが、いかがでしょうか?
まだそんなに難しくないと思います

本連載は、役に立つAIを作るというよりも、
自分で改造できるスキルを身につけてもらうことを目指しています
自分でどんどん改良して、良いホムライフを
Open↓

  ~2章~ 攻撃パターンを改造する

 2-2:無茶しないで戻りなさい

前回、索敵範囲を指定することで、
無差別な攻撃を止めさせることができました

しかし、問題は全て解決したわけではありません
具体的にまだ残っている問題は以下の通りです

 ・ターゲットした敵が離れていくと、際限なく追いかける
 ・壁の向こうの敵をターゲットしたまま動かなくなる

実際、私も前回のAIを使い、下水で狩りをしていますが、
ノンアクティブのゴキが離れるとずんずん追いかけて離れてしまったり、
壁の反対側を狙ったまま、主人が離れても動かなかったりします

せっかく主人から離れすぎないようにしたのに、
これでは中途半端ですね
そこで今回は、追跡部分に手を入れてみましょう


function  OnCHASE_ST ()

  TraceAI ("OnCHASE_ST")

-- 敵を見失ったか?
  if (true == IsOutOfSight(MyID,MyEnemy)) then  -- ENEMY_OUTSIGHT_IN
    MyState = IDLE_ST
    MyEnemy = 0
    MyDestX, MyDestY = 0,0
    TraceAI ("CHASE_ST -> IDLE_ST : ENEMY_OUTSIGHT_IN")
    return
  end

-- 主人を見失ったか?
  local max_dis = 10 -- 主人との最大距離
  local dis = GetDistanceFromOwner(MyID)
  if (dis > max_dis) then  -- 主人から離れた?
    MoveToOwner (MyID) -- 主人のそばへ
    MyState = FOLLOW_ST -- 追尾状態へ
    MyEnemy = 0 -- 敵を初期化
    TraceAI ("CHASE_ST -> FOLLOW_ST : MASTER_OUTSIGHT_IN")
    return
  end


  if (true == IsInAttackSight(MyID,MyEnemy)) then -- ENEMY_INATTACKSIGHT_IN
    MyState = ATTACK_ST
    TraceAI ("CHASE_ST -> ATTACK_ST : ENEMY_INATTACKSIGHT_IN")
    return
  end

  local x, y = GetV (V_POSITION,MyEnemy)
  if (MyDestX ~= x or MyDestY ~= y) then      -- DESTCHANGED_IN
    MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
    Move (MyID,MyDestX,MyDestY)
    TraceAI ("CHASE_ST -> CHASE_ST : DESTCHANGED_IN")
    return
  end

end


手を加えたのは第2ブロックです
考え方は前回と同じで、追跡中に最大距離より離れたら、
追跡をやめて主人の元に戻るようにしています

これならば、離れていく敵をを追いかけても途中で戻りますし、
壁の向こうをターゲットしても、主人が離れればついてきます
前回同様、数値を変えれば活動範囲を変えられるのがポイントです


今回で二回目ですが、どうでしょうか?
あくまで元の骨組みを利用しつつ、
少し改良を加えるだけで、動きが良くなることがわかるでしょう

次回はある意味最大の問題、
「横殴り」に対処しようと思いますが・・・
ちょっと時間がかかるかも^^;

Close↑

[2006.03.31(Fri) 19:10] ホムンクルスAI考察Trackback(0) | Comments(3) 見る▼
↑TOPへ


COMMENT

by えび
この機能によって、画面外に抜けることが少なくなりました。ありがとうございます。しかしまだ画面外にでてしまうことがあります。そこで索敵範囲を8にしてみました。が、それでも画面外に抜けてしまいますね。
がなり運が悪いですorz

by パロット
>まだ画面外にでてしまうことがあります
追跡時だけでなく、あらゆる「状態」において、
主人との距離をチェックするようにすると対応できると思います

例えば、攻撃中の部分に同じコードを入れることで、
攻撃中であっても、主人が離れると帰還を優先するようになります

by えび
ご返事ありがとうございます。
なるほどです。またいろいろと追加してみます。

コメントを閉じる▲

リストマーク 一から始めるAI講座~第10回:~改造を始めよう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
このAI講座も10回目にして、ついに具体的な改造に入ります

とはいえ、一から書くのは大変ですから、
デフォルトのAIを流用しつつ、
目的を達成するように書き直していくのが基本となります
恐れずにチャレンジしてみてください
Open↓

  ~2章~ 攻撃パターンを改造する

 2-1:そんな遠くまで行かないで・・・

皆さんは普段、先攻・非先攻のどちらを使っているでしょうか?
弱い敵を大量に倒したい時は先攻が便利だと思いますが、
使っていると、いろいろ問題が出てきますよね・・・

 ・はるか遠くの敵を追いかけてしまう
 ・攻撃されているのに、他の敵を攻撃する(敵を増やしてしまう)

もう一度、GetMyEnemyB関数を思い出しましょう
あの関数は、画面内全てのモンスターを認識するために、
画面の端にいる敵まで追いかけてしまうわけです

かといって、GetMyEnemyA関数は、
自分を攻撃する敵だけ攻撃するものの、
ノンアクティブへの攻撃は指示が必要で面倒です

そこで、GetMyEnemy関数をこんな風に書き換えてみました


function  GetMyEnemy (myid)
  local result = 0
  local owner = GetV (V_OWNER,myid)
  local actors = GetActors ()
  local enemys = {}
  local index = 1
  local target
  local get_flg = 0 -- 敵発見フラグ

-- 自分を攻撃する敵を探す
  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
        get_flg = 1 -- 敵発見
      end
    end
  end

-- 他の敵を探す
  if (get_flg == 0) then -- 自分を攻撃する敵がいない?
    local max_dis = 10 -- 索敵範囲
    local target_dis
    for i,v in ipairs(actors) do -- 敵を探す
      if (v ~= owner and v ~= myid) then
        target_dis = GetDistance2 (myid,v) -- 距離計算
        if (target_dis < max_dis) then -- 近いところにいる?
          if (1 == IsMonster(v))  then
            enemys[index] = v
            index = index+1
            get_flg = 1
          end
        end
      end
    end
  end

-- 一番近い敵を探す
  if (get_flg == 1) then -- 敵がいる
    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
  end

  return result
end

※タブを変換してますので、このままコピーできません
 全角2つをタブに置換して貼り付けてください


基本的にはA+Bといった感じですが、
順番に見ていきましょう

まず、前者の問題ですが、GetMyEnemyAで敵が見つかったらそちら、
見つからなかったらGetMyEnemyBを実行する、
という感じにしたいわけです

そこで、「get_flg」という変数を導入し、
最初のブロック=自分を攻撃する敵が見つかったらフラグを立て、
次の索敵を行わないようにしています

続いて第2ブロックですが、
ここは最初のif文により、第1ブロックで敵が見つからなかった場合、
つまり自分が攻撃されてない時だけ実行されます

ポイントは「max_dis」という定数を導入したことです

GetMyEnemyB同様、全てのIDを検査するのですが、
ここでホムと対象との距離を計算し、
max_disより小さい時だけ、モンスターであるかの判定を行っています

つまり、max_disを大きくすれば索敵範囲が広がり、
小さくするとごく近くの敵しか狙わないようになるわけです
これにより、第2の問題が解決します

最後のブロックはほとんどいじってませんが、
効率化のため、敵がいるときだけループに入るように修正しました


いかがでしょうか?
基本はGetMyEnemyA/Bなのに、ほんの数行加えるだけで、
非常にホムの動きが良くなります

次回もホムの攻撃部分に修正を加えていく予定ですが、
「こんな動きをさせたい!」という要望がありましたら、
コメントをお寄せください

Close↑

[2006.03.28(Tue) 05:31] ホムンクルスAI考察Trackback(0) | Comments(3) 見る▼
↑TOPへ


COMMENT

A+Bについて by えび
この機能を導入したところ
受身型になってしまい、
ホムが攻撃しなくなりました。
主人が攻撃されているとき、自分が攻撃されているときは攻撃してくれるのですがね。

もしかして、AとBを全部消してこれだけにするのがまずかったのですか?

A+Bについて by えび
0のところに1が入っていたようで直りました。
しかし、索敵時ゴキのようにルートモンスターだった場合、
敵移動とともに索敵をする場面があるため、結局画面外にでることがありました。ただたんに私の運が悪いんでしょうね・・・
最初から、いろいろと解説をありがとうございます。とても分かりやすく参考になります。

by パロット
コメントありがとうございます^^

> 受身型
この講座を最後まで見ていくと、
「コマンド」で切り替え可能にできます
参考にしてみてください

> 際限なく追いかける
それがこの次の回の修正対象になりますね

コメントを閉じる▲

リストマーク 一から始めるAI講座~第9回:定義された関数を知ろう~ 

【重要】新しいBlogに移転していますあるネットゲーマーの日常
前回まではデフォルトのAIを読み解いていきました
その時は全体の流れを重視したために、
細かい関数の解説はあえてしませんでした

しかし、これから具体的に改造を加えるにあたり、
関数を使い方を知らないとお話になりません

そこで今回は、具体的な改造の前に、
ホムAIで使える関数を見ていきましょう
Open↓

  ~間章~ 定義された関数一覧

 A-1:クライアント定義関数

まずは、マニュアルにもある、
クライアントで定義された関数を見ていきます


1) MoveToOwner (id)
 id : ホムンクルスの id
 戻り値 : なし
 機能 : ホムンクルスを召喚者の近くに移動させる

主人の元にホムを帰還させる関数です
ホムの主人はクライアントが知っているので、
主人のIDは指定する必要がありません


2) Move (id, x, y)
 id : ホムンクルスの id
 x : 目的地X 座標
 y : 目的地Y 座標
 戻り値 : なし
 機能 : ホムンクルスを目的地に移動させる

ホムを指定座標に移動させます
座標はMAP内の絶対座標なので、
画面がスクロールしても関係ありません


3) Attack (id1, id2)
 id1 : ホムンクルスの id
 id2 : 攻撃対象の id
 戻り値 : なし
 機能 : ホムンクルスに id2 を攻撃させる

ホムに攻撃させます
攻撃範囲内に対象がいないと意味が無いので、
まずは追跡と、攻撃範囲判定が必要です


4) GetV (V_***, id)
 V_*** : 対象の情報を表す定数値
 id : 対象のid
 戻り値 : V_***によって異なる
 ※例えば、V_POSITION の場合は x、y 座標、V_HP の場合は HP
 機能 : id の情報(V_***)を得る。情報を表す定数値は Util.lua に定義されている

さまざまな情報を取得するのに使います
使える定数は以下の通りです

V_OWNER = 0 -- 召喚者のID
V_POSITION = 1 -- 座標 (x、y)
V_TYPE = 2 -- タイプ(未実装)
V_MOTION = 3 -- 現在の命令
V_ATTACKRANGE = 4 -- 物理攻撃範囲(未実装。現在は1 セルに固定)
V_TARGET = 5 -- 攻撃、スキル使用対象のID
V_SKILLATTACKRANGE = 6 -- スキル攻撃範囲(未実装)
V_HOMUNTYPE = 7 -- ホムンクルスの種類
V_HP = 8 -- HP (ホムンクルスと召喚者)
V_SP = 9 -- SP (ホムンクルスと召喚者)
V_MAXHP = 10 -- 最大 HP (ホムンクルスと召喚者)
V_MAXSP = 11 -- 最大 HP (ホムンクルスと召喚者)


5) GetActors ()
 戻り値 : id を返還(LUA の table 形式に返還される)
 機能 : キャラクターの視界内のキャラクター、NPC、モンスター、
     アイテム、スキルなどの id を取得する

他の関数を見ればわかるように、
動作には対象のIDが必要です
この関数は画面内の全てのIDを取得しますが、
それが何であるかの区別は別に必要です


6) GetTick ()
 戻り値 : 1/1000 秒単位の数字
 機能 : コンピューターの現在の時間を取得。
     この値は、コンピューター起動時に 0 からカウントし、
     1/1000 秒ごとに 1 ずつの増加する

時間を取得する関数です
一定時間ごとに何かをさせたい、スキルの持続時間は・・・など、
改造の際には良く使うことになるでしょう


7) GetMsg (id)
 id : ホムンクルスの id
 戻り値 : ROクライアントから伝達したメッセージ(LUA の table 形式に返還される
 機能 : 使用者の直接的な命令等をスクリプトで伝達する

プレイヤーが出した命令を取得します
対象のID、スキルのIDなどが配列になって格納されます


8) GetResMsg (id)
 id : ホムンクルスの id
 戻り値 : RO クライアントから伝達した予約メッセージ(LUA の table 形式に返還される)
 機能 : 使用者の直接的な予約命令等をスクリプトで伝達する

予約メッセージというのがどういうものかわかりません・・・
デフォルトの骨組みを流用する限り、
考えなくても良いでしょう


10) SkillObject (id, level, skill, target)
 id : ホムンクルスの id
 level : レベル(skill に対応)
 skill : スキルの id
 target : 対象の id
 戻り値 : なし
 機能 : ホムンクルスが、対象(target)に対して level 値に対応したスキル(skill)を使用

ホムにスキルを使わせます
射程内に対象がいないと意味が無いので、追跡等が必須です
スキルのIDはTraceAI関数(後述)で調べましょう


11) SkillGround (id、level、skill、x、y)
 id : ホムンクルスの id
 level : レベル(skill に対応)
 skill : スキルの id
 x : x 座標
 y : y 座標
 戻り値 : なし
 機能 : ホムンクルスが、x、y 座標に対して level 値に対応したスキル(skill)を使用

現在は未実装ですが、範囲攻撃スキルを使用する関数です


12) IsMonster (id)
 id : 対象のid
 戻り値 : id にあたるのがモンスターなら 1 を、そうでなければ 0 を返還する。
 機能 : モンスターを判別する

GetActors関数で取得したIDが、
モンスターであるのかを判別するのに使います


13) TraceAI (string)
 string : TraceAI.txt ファイルに記録される内容。文字列
 戻り値 : なし
 機能 : 実行中のスクリプトの現在状態を記録する

この説明ではわかりづらいですよね
要するに、デバック文を出力する関数です

ゲーム中に「/traceai」と打ち込むと、
実行中のAIがTraceAI関数を通過する際に、
デバックログに引数の文字列を出力します

プログラムは目に見えないものですが、
必要なところにデバック文を入れておくことで、
プログラムがどこを通ったのか、IDが何だったのか、
そういった情報を後から確認できます


 A-2:Util定義関数

Util.luaに定義された関数です
こちらも重要なものが多いです


1) リスト系関数
 List.new ():新規リスト生成
 List.pushleft (list, value):左から値を挿入
 List.pushright (list, value):右から値を挿入
 List.popleft (list):左から値を取り出す
 List.popright (list):右から値を取り出す
 List.clear (list):リストをクリアする
 List.size (list):リストのサイズを取得する

情報処理関係の試験勉強をした方ならご存知だと思いますが、
ここではわからない方向けの説明を

例えば、値を格納する管があったとすると・・・

pushleft
  ----------------------
 ○→   ○○○    
  ----------------------

pushright
  ----------------------
     ○○○○   ←○
  ----------------------

popleft
  ----------------------
  ←○  ○○○○   
  ----------------------

popright
  ----------------------
      ○○○  ○→
  ----------------------

・・・なんとなくわかりますかね?


2) GetDistance (x1,y1,x2,y2)

二点間の距離を計算します(整数)
ベクトルのサイズを計算してますので、
ホムの距離感覚は円形に広がっていることになります


3) GetDistance2 (id1, id2)

2つの対象の間の距離を計算します
どちらかが画面外にいる場合は-1が返ります


4) GetOwnerPosition (id)

主人の座標を返します


5) GetDistanceFromOwner (id)

主人との距離を計算します
どちらかが画面外にいる場合、-1が返ります


6) IsOutOfSight (id1,id2)

2つの対象が「見えない位置」にいるかを判断します
画面外にいるか、20より離れているとtrue
20以内にいる場合はfalseを返します


7) IsInAttackSight (id1,id2)

id1の対象がid2の対象を攻撃可能であるか、
射程内にいるかを返します


以上、関数を見てきました
これでAI改造の準備はばっちりです

最後に、第1回でも書きましたが、
「USER_AI」フォルダの準備をしておきましょう
次回からはここにあるAIを改造していきます

Close↑

[2006.03.26(Sun) 12:25] ホムンクルスAI考察Trackback(0) | Comments(1) 見る▼
↑TOPへ


COMMENT

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

コメントを閉じる▲

TOP | NEXT

プロフィール

Categories

Recent Entries

Recent Comments

Archives

AdSense

Calander

ブログ内検索

RO Search

Links

Copylights

Amazon