تخطيط العمل الموجه بهدف لذكاء اصطناعي أكثر دهاءً-ذكاءً-.
() translation by (you can also view the original English article)
تخطيط العمل الموجه بهدف هو نظام ذكاء اصطناعي سيعطى بسهولة ادواتك الخيارات والأدوات اللازمة لاتخاذ قرارات ذكية بدون الحاجة للحفاظ على آلة محدودة الحالات و كبيرة.
استعراض العرض التوضيحي
في هذا العرض التوضيحي، هناك أربع فئات من الشخصيات، جميعهم يستخدمون ادوات تعطل بعد استخدامها لفترة من الوقت:
- عامل تنقيب: يُنقّب عن معدن خام في الصخور. يحتاج إلى أداة للعمل.
- قاطع اشجار: يقطع الأشجار لإنتاج قطع اشجار. يحتاج إلى أداة للعمل.
- قاطع الأخشاب: يقطع الأشجار إلى خشب قابل للاستخدام. يحتاج إلى أداة للعمل.
- حداد: يطرق الأدوات الحديدية في المحددة. كل شخص يستخدم هذه الأدوات.
كل فئة ستعرف تلقائياً، باستخدام تخطيط العمل الموجه بهدف,ما هي الإجراءات التي يحتاجونها للوصول إلى أهدافهم. إذا تعطلت اداتهم، سوف يذهبون إلى كومة تزويد الادوات والتي واحدة منها ستكون صنعت بواسطة الحداد.
ما هو تخطيط العمل الموجه بهدف ؟
تخطيط العمل الموجه بهدف هو نظام ذكاء اصطناعي لعملاء يسمح لهم بتخطيط تسلسل من الإجراءات لتلبية هدف معين. تسلسل الإجراءات المعين يعتمد ليس فقط على الهدف، ولكن أيضا على الحالة الراهنة للعالم والاداة. وهذا يعني أنه إذا تم توفير نفس الهدف لادوات مختلفة أو لدول العالم، يمكنك الحصول على تسلسل مختلف تماما من الإجراءات.، مما يجعل الذكاء الاصطناعي أكثر ديناميكية وواقعية. دعونا ننظرالى مثال، كما هو مبين في العرض التوضيحي أعلاه.
لدينا اداة، قاطعة خشب، تأخذ قطع خشب الاشجار وتقطعهم إلى حطب. القاطعة يمكن تزويدها بالهدف MakeFirewood
، والإجراءات ChopLog
و GetAxe
و CollectBranches
الإجراءChopLog
الذي سوف يحول خشب الاشجارالى حطب، ولكن فقط إذا كان لدى قاطعة الخشب فأس. اجراء GetAxe
ستمنح قاطعة الخشب فأس. أخيرا، الاجراء CollectBranches
سوف ينتج الحطب كذلك، دون اشتراط فأس، لكن الحطب لن تكون عالية الجودة.
عندما نعطي الاداة هدف MakeFirewood
، نحصل على تسلسلين من الاجراءات المختلفة هذه :
- تحتاج حطب->
GetAxe
->ChopLog
= يصنع الحطب - تحتاج حطب->
CollectBranches
= يصنع الحطب
إذا كان يمكن للاداة الحصول على فأس، اذا يمكنها تقطيع قطع الاشجار لصنع حطب. ولكن ربما لا يمكنها ان تحصل على فأس؛ وقتها، يمكنهم فقط الذهاب وجمع الفروع. اي من هذه التسلسلات ستفي بهدف MakeFirewood
.
تخطيط العمل الموجه بهدف يمكنها اختيار التسلسل الأفضل استناداً إلى شروط مسبقة تتوفر. إذا لم يكن هناك فأس يدوية، اذا قاطعة الأخشاب تحتاج الى التقاط فروع. التقاط الفروع يمكن أن يستغرق وقتاً طويلاً حقاً ويسفر عن رداءة نوعية الحطب، اذا اننا لا نريد تشغيله طوال الوقت، فقط عندما يجب.
لمن -GOAP- تخطيط العمل الموجه بهدف
أنت، الآن، تعرف الآلات المحدودة الحالات (FSM) جيدا، لكن إذا لا، اذا القي نظرة على هذا البرنامج التعليمي الرائع.
ربما دخلت في حالات من التعقيد باستخدام ادوات آلاتك الكبيرة المحدودة FSM، حيث تحصل في نهاية المطاف إلى نقطة حيث لا تريد إضافة سلوكيات اواجراءات جديدة لأنها ستسبب الكثير من الآثار الجانبية والثغرات في مستوى الذكاء الاصطناعي.
GOAP تحول هذا:



الى هذا



بفصل الإجراءات عن بعضها البعض، يمكن أن نركز الآن على كل إجراء على حدى. هذا يجعل الكود البرمجية نمطي، وسهل للاختبار والصيانة. إذا كنت ترغب في إضافة في اجراء آخر، يمكنك القيام بذالك، ولا إجراءات أخرى قد يتعين عليك تغييرها. حاول القيام بذلك مع الآلات المحدودة الحالات !
أيضا، يمكنك إضافة أو إزالة اجراءات على الطاير لتغيير سلوك الاداة ولجعلهم أكثر ديناميكية. كما لو انك تملك غولاً وفجأة غضب واستعر؟ تعطي لهم إجراء جديد "هجوم غاضب" الذي يزول عند هدوءهم. ببساطة إضافة الإجراء إلى قائمة الإجراءات هو كل ما عليك القيام به؛ مخطط GOAP سوف يتولى الامور الباقية.
إذا وجدت ان لديك الآلات المحدودة الحالات معقدة جداً للادوات الخاصة بك، اذا يجب إعطاء GOAP محاولة. واحدة من االعلامات على ان الآلات المحدودة الحالات خاصتك تتعقد جدا هو عندما تكون كل حالة لها عدد وافر من عبارات if-else يجب ان تختبرهم جميعا للوصول الى الحالة التي تقود إلى التالي، وإضافة حالة جديدة يجعلك تتأوه على جميع الآثار التي من المحتمل أن تحصل لها.
إذا كان لديك اداة بسيطة جداً تقوم بأداء مهام واحد أو اثنين فقط، اذا قد تكون هنا GOAP ستكون عالية التكلفة بعض الشيء وستكون الآلات المحدودة الحالات FSM كافية. ومع ذلك، يجدر النظر في المفاهيم هنا ورؤية ما إذا كان سيكون سهل بما يكفي لتتمكن من توصيله مع اداتك.
الإجراءات
الإجراء هو الشيء الذي تفعله الاداة. عادة هو مجرد تشغيل رسوم متحركة وصوت، وتغيير قليل للحالة (على سبيل المثال، إضافة حطب). فتح باب هو اجراء مختلف (رسوم متحركة) اكثر من التقاط قلم رصاص. الإجراء مغلف، ولا ينبغي أن تقلق بشأن الإجراءات الأخرى.
لمساعدة GOAP في تحديد الإجراءات التي نريد أن نستخدمها،. كل إجراء يعطى تكلفة ,ولن يختارالاجراء صاحب التكلفة العالية على حساب المنخفضة . عندما نسلسل الإجراءات معا، نضيف التكاليف وثم نختار التسلسل صاحب الأقل تكلفة.
هيا نعيين بعض التكاليف للإجراءات:
- تكلفة
GetAxe
هي 2 - تكلفة
ChopLog
هي4 - تكلفة
CollectBranches
هي8
ننظر على تسلسل الإجراءات مرة أخرى ونضيف إجمالي التكاليف، سوف نرى ما هو أرخص تسلسل:
- يحتاج حطب-> (
GetAxe
(2 >ثمChopLog
(4 = اصنع حطب (المجموع: 6) - يحتاج الحطب-> (8)
CollectBranches
= يصنع حطب (المجموع: 8)
جلب فاس وتقطيع قطع الاشجار ينتج حطب الوقود بتكلفة أقل من 6، في حين جمع الفروع ينتج حطب بتكلفة أعلى من 8. لذا، اداتنا تختارجلب فاس وقطع الخشب.
ولكن لن يعمل هذا التسلسل طوال الوقت؟ الا اذا وضعنا شروط مسبقة تمكنه...
شروط مسبقة واثارها
الإجراءات لديها شروط مسبقة واثارها الناتجة . شرط مسبق هو الحالة المطلوبة للإجراء ليعمل، والآثار الناتجة هي التغيير إلى الحالة المخصصة بعد تشغيل الإجراء.
على سبيل المثال، يتطلب الإجراء ChopLog
أن يكون لدى الاداة فأساً يدوية. إذا لم تملك الاداة فأس، فإنها تحتاج إلى ايجاد اجراء آخر يستوفي الشرط المسبق بغية السماح بتشغيل الإجراء ChopLog
. لحسن الحظ، الإجراء GetAxe
يفعل ذلك – هذا هو أثرالاجراء.
مخطط
مخطط GOAP هو جزء من التعليمات البرمجية التي تنظر في الشروط المسبقة للإجراءات واثارها، وينشىء صفوف من الإجراءات التي سوف تتم هدف. وذاك الهدف تزوده الاداة، جنبا إلى جنب مع أي حالة عالمية، وقائمة بالإجراءات التي يمكن أن تؤديها الاداة. مع هذه المعلومات مخطط GOAP يمكنه ترتيب الإجراءات, النظرفي التي يمكن تشغيلها والتي لا يمكن وثم التقريراي الإجراءات التي هي الأفضل لأداءها. لحسن حظك، لقد كتبت هذا الكود، حتى لا تضطرلذلك.
لإعداد هذا، هيا نضيف شروط مسبقة والآثار الناتجة عنها لاجراءات قاطعة الخشب خاصتنا:
- تكلفة
GetAxe:هي 2
شروط مسبقة: " الفاس متوفرة"، "لا تملك بفاس". النتيجة : "تملك فاس". - تكلفة
ChopLog
: هي4- شروط مسبقة: "تمتلك فاس". النتيجة: "تصنع حطب" - تكلفة
CollectBranches
: هي 8- شروط مسبقة: (بلا). النتيجة: "صنع حطب".
مخطط GOAP الآن تمتلك المعلومات اللازمة لترتيب تسلسل الإجراءات الرامية لصنع الحطب (هدفنا).
نبدأ بتوفير "مخطط GOAP " مع الحالة الراهنة العالمية وحالة الاداة. هذه الحالة العالمية المُركّبة هي:
- "لا تملك فاس"
- "فاس متوفرة"
- "الشمس مشرقة"
بالنظر لاجراءاتنا الحالية المتاحة، الجزء الوحيد من الحالات ذات الصلة بها هي "لا تملك فاس" و"فاس متوفرة"؛ قد يتم استخدام الحالة الأخرى لادوات أخرى مع إجراءات أخرى.
حسنا، لدينا حالتنا العالمية الحالية واجراءتنا (مع شروطها المسبقة ونتائجها)، والهدف. دعونا نخطط!
1 |
GOAL: "make firewood" |
2 |
Current State: "doesn’t have an axe", "an axe is available" |
3 |
Can action ChopLog run? |
4 |
NO - requires precondition "has an axe" |
5 |
Cannot use it now, try another action. |
6 |
Can action GetAxe run? |
7 |
YES, preconditions "an axe is available" and "doesn’t have an axe" are true. |
8 |
PUSH action onto queue, update state with action’s effect |
9 |
New State |
10 |
"has an axe" |
11 |
Remove state "an axe is available" because we just took one. |
12 |
Can action ChopLog run? |
13 |
YES, precondition "has an axe" is true |
14 |
PUSH action onto queue, update state with action’s effect |
15 |
New State |
16 |
"has an axe", "makes firewood" |
17 |
We have reached our GOAL of "makes firewood" |
18 |
Action sequence: GetAxe -> ChopLog |
سيمر المخطط خلال الإجراءات الأخرى، أيضا، و لن يتوقف فقط عندما يجد حلاً للهدف. ماذا إذا كان لديه تسلسل آخر بتكلفة أقل؟ سيمر خلال جميع الإمكانيات لإيجاد أفضل حل.
عندما تخطط، فإنه تبني شجرة. في كل مرة يتم تطبيق إجراء، يبرز خارج قائمة الإجراءات المتاحة، اذا لا نملك سلسلة من الجراءات GetAxe
50 بالعودة للخلف. يتم تغيير الحالة وفقا لنتيجة هذا الإجراء.
الشجرة التي يبنيها المخطط تبدو مثل هذا:



يمكننا أن نرى أنها ستجد فعلا ثلاثة مسارات للهدف مع تكاليفها الإجمالية:
-
GetAxe
->-ChopLog(المجموع :6)
-
GetAxe
->CollectBranches (المجموع :10)
-
CollectBranches
(المجموع: 8)
بالرغم من ان GetAxe
-> CollectBranches
تعمل,ارخص مسار هو GetAxe
-> ChopLog. اذن هاذا المسار تعود اليه
كيف الشروط المسبقة، وآثارها فعلا يبدوان في الكود البرمجي؟ حسنا، هذا متروك لك، ولكن قد وجدت أنه من الأسهل أن تقوم بتخزينها كزوج مفاتح-قيمة، حيث ان المفتاح هو دائماً سلسلي والقيمة هي هدف او نوع بدائي (عائم، باحث، منطقي، أو ما شابه). في #سي، يمكن أن يبدو مثل هذا:
1 |
HashSet< KeyValuePair<string,object> > preconditions; |
2 |
|
3 |
HashSet< KeyValuePair<string,object> > effects; |
عندما يتم تنفيذ الإجراء، ماذا هذه الآثار تبدو فعلا وماذا يفعلون؟ حسنا، ليس عليهم ان يفعلوا شيء – انهم فقط يستخدموا للتخطيط، ولا يؤثروا على حالة الاداة الفعلية حتى يتم تشغيلهم بحق.
وهذا يستحق التأكيد: تخطيط الإجراءات ليس نفس تشغيلها. عندما تقوم الاداة بتنفيذ الإجراء GetAxe
، سيكون على الأرجح قرب كومة من الأدوات، تشغل رسوم متحركة تنحني لأسفل وتلتقط فاس، وثم تخزن الفاس في جعبتها الخلفية . وهذا يغيير الحالة الخاصة بالاداة. ولكن، أثناء تخطيط GOAP،تغير الحالة مؤقت فقط، حتى يمكن المخطط من معرفة الحل الأمثل.
الشروط المسبقة الإجرائية
في بعض الأحيان، الإجراءات بحاجة إلى القيام بالمزيد لتحديد ما إذا كان يمكن تشغيلها. على سبيل المثال، اجراء GetAxe
له الشرط المسبق "فاس متاح" الذي سوف يحتاج إلى البحث في عالميا، أو المتاخمة، لمعرفة ما إذا كان هناك فاس يمكن أن تاخذه الاداة. إنه يمكنها تحديد أنأقرب فاس بعيداً جداً أو خلف خطوط العدو، وفي هذه الحالة سيقول أنه لا يمكنها فعل هذا الاجراء. هذا الشرط المسبق اجرائي ويحتاج إلى تشغيل بعض التعليمات البرمجية؛ وليس عامل منطقي بسيط بحيث يمكننا تبديله.
ومن الواضح أن بعض هذه الشروط الإجرائية يمكن أن يستغرق بعض الوقت للتشغيل، وينبغي أن يقوم على شيء آخر غير الخيط المقدم، من الناحية المثالية كخيط خلفية أوكالكوروتينز (في الوحدة).
يمكن أن يكون لديك آثار إجرائية أيضا، إذا كنت ترغب في ذلك. وإذا كنت ترغب في عرض نتائج أكثر ديناميكية، يمكنك تغيير تكلفة الإجراءات على الطاير!
GOAP والحالة
نظام GOAP خاصتنا سيحتاج إلى العيش في آلة محدودة الحالات و صغيرة (FSM)، لسبب وحيد، في العديد من الألعاب، سوف تحتاج الإجراءات لتكون قريب من هدف من أجل العمل. ونحن في النهاية ننتهي بثلاث حالات:
عاطلة عن العمل
الانتقالية
تنفيذ اجراء
عند العطول عن العمل، الاداة ستعرف الهدف الذي يريدونه لاكمال الجزء المتعامل معه خارج GOAP . تتم معالجة هذا الجزء من خارج GOAP ؛ سوف تقول لك الإجراءات التي يمكنك تشغيلها لتنفيذ هذا الهدف. عندما يتم اختيار هدف يتم تمريره إلى "مخطط جوب"، جنبا إلى جنب مع العالم و حالة بدء الاداة ، والمخطط سسترج قائمة الإجراءات (إذا كان يمكن تحقيق هذا الهدف)
عندما ينتهي ذلك المخطط والاداة يكون لها قائمة من الإجراءات، فإنه ستحاول تنفيذ الإجراء الأول. سوف تحتاج جميع الإجراءات لمعرفة إذا كان يجب أن تكون في نطاق الهدف. إذا كذلك، فسوف تضغط الآلة محدودة الحالات على الحالة القادمة: MoveTo.
حالة MoveTo
ستخبر الاداة انها بحاجة للانتقال إلى هدف محدد. الاداة ستقوم بهذا النقل (ويشغل رسم متحرك يمشي)، وثم السماح للآلات محدودة الحالات ان تعرف عندما تكون داخل نطاق الهدف. هذه الحالة هي ثم تبرز، ويمكن تنفيذ الإجراء.
حالة PerformAction
ستشغل الإجراء التالي في قائمة الاجراءات التي يتم إرجاعها بواسطة "مخطط جوب". الإجراء يمكن أن يكون لحظي أو يبقى خلال العديد من الإطارات، ولكن عندما ينتهي يبرز وثم يتم تنفيذ الإجراء التالي (مرة أخرى، بعد التحقق من ما إذا كان هذا الإجراء التالي يجب تنفيذه ضمن نطاق الهدف).
هذا كله يتكرر حتى لا توجد إجراءات تحتاج للقيام بها، عند هذه النقطة نعود إلى حالة Idle
، احصل على هدف جديد، وخطط مرة أخرى.
مثال لكود برمجي حقيقي
لقد حان الوقت لنلقي نظرة على مثال حقيقي! لا تقلق؛ الامر ليس معقد، وقد قدمت نسخة عمل في وحدة و#سي لك لمحاولة الخروج. فقط سأتحدث عن ذلك بإيجاز هنا حيث يمكنك التعود على الهندسة المعمارية. تستخدم التعليمة البرمجية بعض الأمثلة للحطابة على نفس النحو الوارد أعلاه.
إذا كنت ترغب في معرفة المزيد،توجه هنا إلى التعليمة البرمجية: http://github.com/sploreg/goap
لدينا أربعة عمال:
- حداد: يحول خام الحديد إلى أدوات.
- قاطع الاشجار: يستخدم أداة لتقطيع الأشجار لإنتاج قطع من الاشجار.
- المنقّب: ينقّّب الصخوربأداة لإنتاج خام الحديد.
- قاطع الخشب: يستخدم أداة تقطع قطع الاشجار لإنتاج الحطب.
الأدوات تبلي بمرور الوقت، وسوف تحتاج إلى استبدال. لحسن الحظ، يصنع الحداد أدوات . ولكن خام الحديد مطلوب لصنع الأدوات؛ هذا حيث يأتي دور المنقّب في (الذي يحتاج أيضا أدوات). قاطع الخشب يحتاج إلى قطع اشجار، وتلك تأتي من قاطع الاشجار؛ كلاهما بحاجة إلى أدوات كذلك.
يتم تخزين أدوات وموارد الإمداد على كومات التزويد. ستجمع الادوات المواد أو الأدوات التي يحتاجونها من الأكوام، وأيضا ستسقط ناتجها عليهم.
الكود له ستة فصول ل جوب رئيسية:
-
GoapAgent
: تدرك حالة وتستخدم الآلات محدودة الحالات وGoapPlanner
للعمل. -
GoapAction
: الإجراءات التي يمكن أن تؤديها الادوات. -
GoapPlanner
: تخطط الإجراءات لGoapAgent
. -
FSM
: الآلة محدودة الحالات. - FSMState: حالة في الآلات محدودة الحالات.
-
IGoap
: الواجهة التي يستخدمها ممثلي عمالنا الحقيقيين .ترتبط باحداث ل -جوب-ول الآلات محدودة الحالات.
دعنا ننظر إلى فئة GoapAction
، نظرا لأنه هو الذي سوف تُفرّعه:
1 |
public abstract class GoapAction : MonoBehaviour { |
2 |
|
3 |
private HashSet<KeyValuePair<string,object>> preconditions; |
4 |
private HashSet<KeyValuePair<string,object>> effects; |
5 |
|
6 |
private bool inRange = false; |
7 |
|
8 |
/* The cost of performing the action.
|
9 |
* Figure out a weight that suits the action.
|
10 |
* Changing it will affect what actions are chosen during planning.*/
|
11 |
public float cost = 1f; |
12 |
|
13 |
/**
|
14 |
* An action often has to perform on an object. This is that object. Can be null. */
|
15 |
public GameObject target; |
16 |
|
17 |
public GoapAction() { |
18 |
preconditions = new HashSet<KeyValuePair<string, object>> (); |
19 |
effects = new HashSet<KeyValuePair<string, object>> (); |
20 |
}
|
21 |
|
22 |
public void doReset() { |
23 |
inRange = false; |
24 |
target = null; |
25 |
reset (); |
26 |
}
|
27 |
|
28 |
/**
|
29 |
* Reset any variables that need to be reset before planning happens again.
|
30 |
*/
|
31 |
public abstract void reset(); |
32 |
|
33 |
/**
|
34 |
* Is the action done?
|
35 |
*/
|
36 |
public abstract bool isDone(); |
37 |
|
38 |
/**
|
39 |
* Procedurally check if this action can run. Not all actions
|
40 |
* will need this, but some might.
|
41 |
*/
|
42 |
public abstract bool checkProceduralPrecondition(GameObject agent); |
43 |
|
44 |
/**
|
45 |
* Run the action.
|
46 |
* Returns True if the action performed successfully or false
|
47 |
* if something happened and it can no longer perform. In this case
|
48 |
* the action queue should clear out and the goal cannot be reached.
|
49 |
*/
|
50 |
public abstract bool perform(GameObject agent); |
51 |
|
52 |
/**
|
53 |
* Does this action need to be within range of a target game object?
|
54 |
* If not then the moveTo state will not need to run for this action.
|
55 |
*/
|
56 |
public abstract bool requiresInRange (); |
57 |
|
58 |
|
59 |
/**
|
60 |
* Are we in range of the target?
|
61 |
* The MoveTo state will set this and it gets reset each time this action is performed.
|
62 |
*/
|
63 |
public bool isInRange () { |
64 |
return inRange; |
65 |
}
|
66 |
|
67 |
public void setInRange(bool inRange) { |
68 |
this.inRange = inRange; |
69 |
}
|
70 |
|
71 |
|
72 |
public void addPrecondition(string key, object value) { |
73 |
preconditions.Add (new KeyValuePair<string, object>(key, value) ); |
74 |
}
|
75 |
|
76 |
|
77 |
public void removePrecondition(string key) { |
78 |
KeyValuePair<string, object> remove = default(KeyValuePair<string,object>); |
79 |
foreach (KeyValuePair<string, object> kvp in preconditions) { |
80 |
if (kvp.Key.Equals (key)) |
81 |
remove = kvp; |
82 |
}
|
83 |
if ( !default(KeyValuePair<string,object>).Equals(remove) ) |
84 |
preconditions.Remove (remove); |
85 |
}
|
86 |
|
87 |
|
88 |
public void addEffect(string key, object value) { |
89 |
effects.Add (new KeyValuePair<string, object>(key, value) ); |
90 |
}
|
91 |
|
92 |
|
93 |
public void removeEffect(string key) { |
94 |
KeyValuePair<string, object> remove = default(KeyValuePair<string,object>); |
95 |
foreach (KeyValuePair<string, object> kvp in effects) { |
96 |
if (kvp.Key.Equals (key)) |
97 |
remove = kvp; |
98 |
}
|
99 |
if ( !default(KeyValuePair<string,object>).Equals(remove) ) |
100 |
effects.Remove (remove); |
101 |
}
|
102 |
|
103 |
|
104 |
public HashSet<KeyValuePair<string, object>> Preconditions { |
105 |
get { |
106 |
return preconditions; |
107 |
}
|
108 |
}
|
109 |
|
110 |
public HashSet<KeyValuePair<string, object>> Effects { |
111 |
get { |
112 |
return effects; |
113 |
}
|
114 |
}
|
115 |
}
|
لا شيء موهم جداً هنا: فإنه يخزن الشروط المسبقة والآثار. فهو يعرف أيضا ما إذا كان يجب أن يكون في نطاق الهدف، وإذا كان الأمر كذلك، فان الآلات محدودة الحالات تعرف كيف تضغط MoveTo
عند الحاجة. هي تعرف متى تنتهي، أيضا؛ذلك محدد بواسطة فئة تنفيذ العمل .
هنا واحد من الإجراءات:
1 |
public class MineOreAction : GoapAction |
2 |
{
|
3 |
private bool mined = false; |
4 |
private IronRockComponent targetRock; // where we get the ore from |
5 |
|
6 |
private float startTime = 0; |
7 |
public float miningDuration = 2; // seconds |
8 |
|
9 |
public MineOreAction () { |
10 |
addPrecondition ("hasTool", true); // we need a tool to do this |
11 |
addPrecondition ("hasOre", false); // if we have ore we don't want more |
12 |
addEffect ("hasOre", true); |
13 |
}
|
14 |
|
15 |
|
16 |
public override void reset () |
17 |
{
|
18 |
mined = false; |
19 |
targetRock = null; |
20 |
startTime = 0; |
21 |
}
|
22 |
|
23 |
public override bool isDone () |
24 |
{
|
25 |
return mined; |
26 |
}
|
27 |
|
28 |
public override bool requiresInRange () |
29 |
{
|
30 |
return true; // yes we need to be near a rock |
31 |
}
|
32 |
|
33 |
public override bool checkProceduralPrecondition (GameObject agent) |
34 |
{
|
35 |
// find the nearest rock that we can mine
|
36 |
IronRockComponent[] rocks = FindObjectsOfType ( typeof(IronRockComponent) ) as IronRockComponent[]; |
37 |
IronRockComponent closest = null; |
38 |
float closestDist = 0; |
39 |
|
40 |
foreach (IronRockComponent rock in rocks) { |
41 |
if (closest == null) { |
42 |
// first one, so choose it for now
|
43 |
closest = rock; |
44 |
closestDist = (rock.gameObject.transform.position - agent.transform.position).magnitude; |
45 |
} else { |
46 |
// is this one closer than the last?
|
47 |
float dist = (rock.gameObject.transform.position - agent.transform.position).magnitude; |
48 |
if (dist < closestDist) { |
49 |
// we found a closer one, use it
|
50 |
closest = rock; |
51 |
closestDist = dist; |
52 |
}
|
53 |
}
|
54 |
}
|
55 |
targetRock = closest; |
56 |
target = targetRock.gameObject; |
57 |
|
58 |
return closest != null; |
59 |
}
|
60 |
|
61 |
public override bool perform (GameObject agent) |
62 |
{
|
63 |
if (startTime == 0) |
64 |
startTime = Time.time; |
65 |
|
66 |
if (Time.time - startTime > miningDuration) { |
67 |
// finished mining
|
68 |
BackpackComponent backpack = (BackpackComponent)agent.GetComponent(typeof(BackpackComponent)); |
69 |
backpack.numOre += 2; |
70 |
mined = true; |
71 |
ToolComponent tool = backpack.tool.GetComponent(typeof(ToolComponent)) as ToolComponent; |
72 |
tool.use(0.5f); |
73 |
if (tool.destroyed()) { |
74 |
Destroy(backpack.tool); |
75 |
backpack.tool = null; |
76 |
}
|
77 |
}
|
78 |
return true; |
79 |
}
|
80 |
|
81 |
}
|
الجزء الأكبر من الاجراء هو طريقة checkProceduralPreconditions
. تنظر لأقرب هدف لعبة مع IronRockComponent
، وتحفظ هذه الصخرة المستهدفة. ثم، عندما ينفذ، يحصل على تلك الصخرة الهدف وسيتم تنفيذ الإجراء عليها. عند إعادة استخدام الإجراء في التخطيط مرة أخرى، جميع حقولها مضبوطة بحيث يمكن حسابها مرة أخرى.
هذه هي كافة المكونات التي تم إضافتها إلى نطاق هدف Miner
في الوحدة:



من أجل ان تعمل اداتك، يجب إضافة المكونات التالية إليها:
-
GoapAgent
. - فئة تطبق
IGoap
(في المثال أعلاه، هيMiner.cs
). - بعض الإجراءات.
- جعبة خلفية (فقط لان الإجراءات تستخدمها؛ ولا علاقة لها بجوب).
هنا العرض التجريبي عمليا مرة أخرى:
كل عامل يذهب إلى الهدف الذي يحتاج لإنجاز عمله(شجرة، صخرة، تقطيع كتلة، أو أيا كان) يقوم بتنفيذ الإجراء، ويرجعوا غالباً إلى كومة التزويد لإسقاط بضائعهم. الحداد سوف ينتظر بعض الوقت حتى يكون هناك خام حديد في واحدة من أكوام الإمداد (يضيفها المنقّب). الحداد ثم يتلف ويصنع ادوات، وسيسقط الأدوات في كومة العرض الأقرب له.عندما تعطل أداة عامل سوف يتوجهوا إلى كومة الإمداد بالقرب من الحداد حيث الأدوات الجديدة.
يمكنك امتلاك التعليمة البرمجية والتطبيق الكامل هنا: http://github.com/sploreg/goap.