<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های عباس زنگارکی فراهانی</title>
        <link>https://virgool.io/feed/@m_337640</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-17 02:35:48</pubDate>
        <image>
            <url>https://static.virgool.io/images/default-avatar.jpg</url>
            <title>عباس زنگارکی فراهانی</title>
            <link>https://virgool.io/@m_337640</link>
        </image>

                    <item>
                <title>مسابقه بازی سازی با گودو</title>
                <link>https://virgool.io/@m_337640/%D9%85%D8%B3%D8%A7%D8%A8%D9%82%D9%87-%D8%A8%D8%A7%D8%B2%DB%8C-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%A8%D8%A7-%DA%AF%D9%88%D8%AF%D9%88-nf7c58ee2x97</link>
                <description>شرایط:- موضوع: آزاد!- جایزه‌های برنده: در عکس بالا نوشته‌شده‌اند.- مهلت ارسال: یک هفته، از ۲۴ تا ۳۰ مهر [۱۳۹۹].- راهنما: جهت ثبت‌نام، یکی از دکمه‌های زیر این پیام (در نظرسنجی!) را بزنید.بازی را با #مسابقه_۲ و اسم سازنده(ها) در گروه گودوت ایران (@GodotIran) ارسال کنید. [لطفا غیر از این مورد از آن هشتگ استفاده نکنید]- خروجی‌های قابل‌قبول: اندروید، گنو/لینوکس، ویندوز و تحت وب.- در صورت استفاده از مقداری محتوای آماده، با توضیح اعلام کنید.- ✌</description>
                <category>عباس زنگارکی فراهانی</category>
                <author>عباس زنگارکی فراهانی</author>
                <pubDate>Mon, 12 Oct 2020 16:36:24 +0330</pubDate>
            </item>
                    <item>
                <title>اموزش ساخت بازی با گودو(گودوت) قسمت دوم</title>
                <link>https://virgool.io/@m_337640/%D8%A7%D9%85%D9%88%D8%B2%D8%B4-%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D8%A7%D8%B2%DB%8C-%D8%A8%D8%A7-%DA%AF%D9%88%D8%AF%D9%88%DA%AF%D9%88%D8%AF%D9%88%D8%AA-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-tvdplawucyef</link>
                <description>خب ابتدا اجازه بدین نکاتی رو خدمتتون عرض کنماول اینکه سورس کد هایی که قرار داده میشه سینتکسش درست نیس ایندنت ها رعایت نشده لطفا به سایت اصلی گودو مراجعه کنید تا مشکلی نداشته باشیندوم اینکه انجمن فارسی گودو رو هنوز بعضیا خبری ندارن ازش  میتونین در کانال تلگرام انجمن عضو شید @GodotIranخب بریم سر اصل مطلبساخت یک سیستم برای مدیریت انیمیشن ها در ابتدا ما به روشی برای کنترل انیمیشن های در حال تغییر نیاز داریم. Player.tscn را باز کرده و گره AnimationPlayer را انتخاب کنید (Player -&gt; Rotation_Helper -&gt; Model -&gt; Animation_Player).یک اسکریپت جدید به نام AnimationPlayer_Manager.gd ایجاد کرده و آن را به AnimationPlayer پیوست کنید. کد زیر را به AnimationPlayer_Manager.gd اضافه کنید:extends AnimationPlayer # Structure -&gt; Animation name :[Connecting Animation states] var states = { &quot;Idle_unarmed&quot;:[&quot;Knife_equip&quot;, &quot;Pistol_equip&quot;, &quot;Rifle_equip&quot;, &quot;Idle_unarmed&quot;], &quot;Pistol_equip&quot;:[&quot;Pistol_idle&quot;], &quot;Pistol_fire&quot;:[&quot;Pistol_idle&quot;], &quot;Pistol_idle&quot;:[&quot;Pistol_fire&quot;, &quot;Pistol_reload&quot;, &quot;Pistol_unequip&quot;, &quot;Pistol_idle&quot;], &quot;Pistol_reload&quot;:[&quot;Pistol_idle&quot;], &quot;Pistol_unequip&quot;:[&quot;Idle_unarmed&quot;], &quot;Rifle_equip&quot;:[&quot;Rifle_idle&quot;], &quot;Rifle_fire&quot;:[&quot;Rifle_idle&quot;], &quot;Rifle_idle&quot;:[&quot;Rifle_fire&quot;, &quot;Rifle_reload&quot;, &quot;Rifle_unequip&quot;, &quot;Rifle_idle&quot;], &quot;Rifle_reload&quot;:[&quot;Rifle_idle&quot;], &quot;Rifle_unequip&quot;:[&quot;Idle_unarmed&quot;], &quot;Knife_equip&quot;:[&quot;Knife_idle&quot;], &quot;Knife_fire&quot;:[&quot;Knife_idle&quot;], &quot;Knife_idle&quot;:[&quot;Knife_fire&quot;, &quot;Knife_unequip&quot;, &quot;Knife_idle&quot;], &quot;Knife_unequip&quot;:[&quot;Idle_unarmed&quot;], } var animation_speeds = { &quot;Idle_unarmed&quot;:1, &quot;Pistol_equip&quot;:1.4, &quot;Pistol_fire&quot;:1.8, &quot;Pistol_idle&quot;:1, &quot;Pistol_reload&quot;:1, &quot;Pistol_unequip&quot;:1.4, &quot;Rifle_equip&quot;:2, &quot;Rifle_fire&quot;:6, &quot;Rifle_idle&quot;:1, &quot;Rifle_reload&quot;:1.45, &quot;Rifle_unequip&quot;:2, &quot;Knife_equip&quot;:1, &quot;Knife_fire&quot;:1.35, &quot;Knife_idle&quot;:1, &quot;Knife_unequip&quot;:1, } var current_state = null var callback_function = null func _ready(): set_animation(&quot;Idle_unarmed&quot;) connect(&quot;animation_finished&quot;, self, &quot;animation_ended&quot;) func set_animation(animation_name): if animation_name == current_state: print (&quot;AnimationPlayer_Manager.gd -- WARNING: animation is already &quot;, animation_name) return true if has_animation(animation_name): if current_state != null: var possible_animations = states[current_state] if animation_name in possible_animations: current_state = animation_name play(animation_name, -1, animation_speeds[animation_name]) return true else: print (&quot;AnimationPlayer_Manager.gd -- WARNING: Cannot change to &quot;, animation_name, &quot; from &quot;, current_state) return false else: current_state = animation_name play(animation_name, -1, animation_speeds[animation_name]) return true return false func animation_ended(anim_name): # UNARMED transitions if current_state == &quot;Idle_unarmed&quot;: pass # KNIFE transitions elif current_state == &quot;Knife_equip&quot;: set_animation(&quot;Knife_idle&quot;) elif current_state == &quot;Knife_idle&quot;: pass elif current_state == &quot;Knife_fire&quot;: set_animation(&quot;Knife_idle&quot;) elif current_state == &quot;Knife_unequip&quot;: set_animation(&quot;Idle_unarmed&quot;) # PISTOL transitions elif current_state == &quot;Pistol_equip&quot;: set_animation(&quot;Pistol_idle&quot;) elif current_state == &quot;Pistol_idle&quot;: pass elif current_state == &quot;Pistol_fire&quot;: set_animation(&quot;Pistol_idle&quot;) elif current_state == &quot;Pistol_unequip&quot;: set_animation(&quot;Idle_unarmed&quot;) elif current_state == &quot;Pistol_reload&quot;: set_animation(&quot;Pistol_idle&quot;) # RIFLE transitions elif current_state == &quot;Rifle_equip&quot;: set_animation(&quot;Rifle_idle&quot;) elif current_state == &quot;Rifle_idle&quot;: pass; elif current_state == &quot;Rifle_fire&quot;: set_animation(&quot;Rifle_idle&quot;) elif current_state == &quot;Rifle_unequip&quot;: set_animation(&quot;Idle_unarmed&quot;) elif current_state == &quot;Rifle_reload&quot;: set_animation(&quot;Rifle_idle&quot;) func animation_callback(): if callback_function == null: print (&quot;AnimationPlayer_Manager.gd -- WARNING: No callback function for the animation to call!&quot;) else: callback_function.call_func()بیایید بررسی کنیم که این اسکریپت چه کاری انجام می دهد، بیایید با متغیرهای کلاس این اسکریپت شروع کنیم: state: اجرای حالت های انیمیشن ما. (توضیحات بیشتر در پایین*) animation_speeds: سرعت پخش انیمیشن(توضیح بیشتر در پایین*) current_state: متغیری برای نگهداری وضعیت انیمیشنی که در حال حاضر در آن هستیم. (توضیح بیشتر در پایین*)callback_function: متغیری برای نگهداری عملکرد callback. (توضیحات بیشتر در زیر) اگر با ماشین های حالت آشنایی دارید ، ممکن است متوجه شده باشید که حالت ها مانند یک ماشین اولیه ساختار دارند. در اینجا تقریباً نحوه تنظیم انها وجود دارد:state*تمام انیمیشن ها (حالت هایی) را که می توانیم به آن انتقال دهیم را در خود جای داده است. به عنوان مثال ، اگر در حال حاضر در حالت Idle_unarmed هستیم ، فقط می توانیم به Knife_equip ، Pistol_ Equip ، Rifle_ Equip و Idle_unarmed منتقل شویم. اگر بخواهیم به حالتی منتقل شویم که در حالات انتقال احتمالی، قرار نگرفته است ، یک پیام هشدار دریافت می کنیم و انیمیشن تغییر نمی کند. ما همچنین می توانیم به طور خودکار از برخی از حالت ها به حالت های دیگر برویم ، همانطور که در زیر در animation_ended توضیح داده خواهد شدanimation_speeds*سرعت پخش هر انیمیشن است. برخی از انیمیشن ها کمی کند هستند و در تلاشیم تا همه چیز روان به نظر برسد ، باید آنها را با سرعت بیشتری اجرا کنیم.نکته: توجه داشته باشید که همه انیمیشن های شلیک سریعتر از سرعت نرمال خود هستند. این را به خاطر بسپارید! current_state*نام حالت انیمیشنی را که در حال حاضر در آن هستیم نگه خواهد داشت. سرانجام ، callback_function یک FuncRef خواهد بود که توسط player برای تخم ریزی(spawning ) گلوله در قاب مناسب انیمیشن ارائه می شود. FuncRef به ما اجازه می دهد تا یک تابع را به عنوان آرگومان منتقل کنیم ، در واقع به ما اجازه می دهد تا یک تابع را از اسکریپت دیگر فراخوانی کنیم.اطلاعات بیشتر از FuncRef:https://docs.godotengine.org/en/stable/classes/class_funcref.html#class-funcrefحالا بیاییدready _ را بررسی کنیم.ابتدا با استفاده از تابع set_animation ، انیمیشن خود را روی Idle_unarmed تنظیم می کنیم ، بنابراین مطمئناً در آن انیمیشن شروع می کنیم. بعد سیگنال animation_finished را به این اسکریپت متصل می کنیم و آن را به فراخوانی animation_ended اختصاص می دهیم. این بدان معنی است که هر زمان که یک انیمیشن تمام می شود ، animation_ended فراخوانی می شود. بیایید به set_animation نگاه کنیم. set_animation اگر حالت انیمیشنی که در حال حاضر در آن هستیم ، نام حالت انیمیشن گذشته را در state داشته باشد ، سپس به آن انیمیشن تغییر خواهیم کرد.در مرحله اول ، بررسی می کنیم که آیا نام انیمیشن وارد شده همان نام انیمیشن در حال پخش است یا خیر. اگر آنها یکسان باشند ، ما یک هشدار برای کنسول می نویسیم و trueبرمی گردانیم. ثانیا ، می بینیم که AnimationPlayer دارای انیمیشن با نام animation_name با استفاده از has_animation است. اگر اینگونه نباشد ، false برمیگردانیم. ثالثاً ، بررسی می کنیم که آیا current_state تنظیم شده است یا خیر. اگر ما حالتی در state_state داشته باشیم ، تمام حالت های ممکن را که می توانیم به آنها انتقال دهیم بدست می آوریم. اگر نام انیمیشن در لیست انتقالهای احتمالی باشد ، ما current_state را روی انیمیشن pass (animation_name) تنظیم می کنیم ، به AnimationPlayer می گوییم که انیمیشن را با زمان ترکیبی1- با سرعت تعیین شده در animation_speed پخش کند و true برگردد.(به علت اینکه مطالب یکم نامفهومه سعی کنید کد جلوتون باشه و با توجه بهش پیش برین)Blend time مدت زمانی است که می توان دو انیمیشن را با هم مخلوط کرد. با قرار دادن مقدار 1 ، انیمیشن جدید فوراً پخش می شود و بر هر انیمیشنی که قبلاً پخش شده است ، غلبه می کند. اگر مقدار 1 را قرار دهید ، برای یک ثانیه انیمیشن جدید با افزایش قدرت پخش می شود و قبل از پخش انیمیشن جدید ، دو انیمیشن را برای یک ثانیه با هم مخلوط می کند. این منجر به ،انتقال روان بین انیمیشن ها می شود ، که وقتی از یک انیمیشن متحرک به یک انیمیشن در حال اجرا تغییر می کنید عالی به نظر می رسد. ما زمان ترکیبی را روی 1- تنظیم می کنیم زیرا می خواهیم فوراً انیمیشن ها را تغییر دهیم.حالا اجازه دهید به animation_ended نگاه کنیم. animation_ended تابعی است که با اجرای انیمیشن توسط AnimationPlayer فراخوانی می شود. برای برخی از حالت های انیمیشن ، ممکن است لازم باشد که به پایان برسیم و به حالت دیگری برویم. برای مقابله با این مسئله ، هر حالت انیمیشن احتمالی را بررسی می کنیم. در صورت نیاز ، به حالت دیگری خواهیم رفت.هشداراگر از مدل های متحرک خود استفاده می کنید ، مطمئن شوید که هیچ یک از انیمیشن ها حلقه ای تنظیم نشده باشند. وقتی انیمیشن ها به انتهای انیمیشن می رسند و در آستانه حلقه قرار دارند ، انیمیشن های تمام شده انیمیشن را ارسال نمی کنند توجه داشته باشید انتقال در animation_ended در حالت ایده آل بخشی از داده ها در state است ، اما در تلاش برای درک آسان تر آموزش ، ما کد انتقال هر حالت را در animation_ended سخت خواهیم کرد.سرانجام ، animation_callback این عملکرد با استفاده از اهنگ روش تماس( call method track ) در انیمیشن های ما فراخوانی می شود. اگر یک FuncRef به callback_function اختصاص داده شده باشد ، آنرا فراخوانی کرده ایم. اگر FuncRef به callback_function اختصاص داده نشده باشد ، یک هشدار را برای کنسول چاپ می کنیم. نکته:برای اطمینان از عدم بروز مشکل در زمان اجرا ، Testing_Area.tscn را اجرا کنید.  وقتی بازی اجرا شود به نظر نمی رسد چیزی تغییر کرده باشد ، همه چیز به درستی کار می کند. آماده شدن انیمیشن هااکنون که یک Animation manager فعال داریم ، باید آن را از اسکریپت player خود فراخوانی کنیم. هرچند قبل از آن ، ما باید برخی از آهنگ های برگشت تماس انیمیشن ( animation callback tracks )را در انیمیشن های شلیک شده خود تنظیم کنیم(we need to set some animation callback tracks in our firing animations). اگر Player.tscn را ندارید ، آن را باز کنید و به گره AnimationPlayer بروید (Player -&gt; Rotation_Helper -&gt; Model -&gt; Animation_Player).ما باید یک آهنگ روش تماس( call method track to three of our animations: The firing ) را به سه انیمیشن خود ضمیمه کنیم:انیمیشن شلیک برای تپانچه ، تفنگ و چاقو. بیایید با تپانچه شروع کنیم. روی لیست کشویی انیمیشن کلیک کنید و &quot;Pistol_fire&quot; را انتخاب کنید. اکنون به پایین لیست آهنگ های انیمیشن بروید. مورد آخر این لیست باید Armature / Skeleton: Left_UpperPointer باشد. اکنون بالای لیست ، روی دکمه &quot;افزودن آهنگ&quot; ، در سمت چپ خط زمان کلیک کنیدبا این کار با پنجره ای با چند انتخاب روبرو می شوید. ما می خواهیم آهنگ روش فراخوانی اضافه کنیم ، بنابراین روی گزینه ای که روی &quot; call method track &quot; نوشته شده است کلیک کنید. با این کار پنجره ای باز می شود که کل درخت گره را نشان می دهد. به گره AnimationPlayer بروید ، آن را انتخاب کنید و OK را فشار دهید.اکنون در پایین لیست آهنگ های انیمیشن ، یک قطعه سبز خواهید داشت که روی آن &quot;AnimationPlayer&quot; نوشته شده است. حال باید نقطه ای را اضافه کنیم که می خواهیم عملکرد پاسخگویی خود را فراخوانی کنیم. جدول زمانی را جابه جا کنید تا به نقطه ای برسید که گلوله شروع به شلیک کند. توجه داشته باشید جدول زمانی پنجره ای است که تمام نقاط انیمیشن ما در آن ذخیره شده است. هر یک از نقاط کوچک نشان دهنده یک نقطه از داده های انیمیشن است. برای پیش نمایش انیمیشن &quot;Pistol_fire&quot; ، گره دوربین زیر Rotator Helper را انتخاب کرده و کادر &quot;Preview&quot; را در گوشه بالا سمت چپ زیر چشم انداز علامت بزنید. اسکراب کردن جدول زمانی به معنای حرکت خودمان از طریق انیمیشن است. بنابراین وقتی می گوییم &quot;جدول زمانی را جابه جاکنید تا به یک نقطه برسید&quot; ، منظور ما حرکت از طریق پنجره انیمیشن تا رسیدن به نقطه روی جدول زمانی است. همچنین ،  فلاش پوزه بند ، تابشی از نور است که با شلیک گلوله از پوزه خارج می شود. از پوزه نیز گاهی به عنوان لوله تفنگ یاد می شود( Also, the muzzle of a gun is the end point where the bullet comes out. The muzzle flash is the flash of light that escapes the muzzle when a bullet is fired. The muzzle is also sometimes referred to as the barrel of the gun). نکته برای کنترل دقیق تر هنگام جابه جایی جدول زمانی ، Ctrl را فشار دهید و برای بزرگنمایی با چرخ ماوس به جلو بروید. پیمایش به عقب بزرگنمایی می شود همچنین می توانید با تغییر مقدار در مرحله (ها) به مقدار پایین / بالاتر ، چگونگی ضربه محکم و ناگهانی جدول زمانی را تغییر دهید. پس از رسیدن به نقطه ای که دوست دارید ، روی ردیف &quot;Animation Player&quot; کلیک راست کرده و Insert Key را فشار دهید. در قسمت نام خالی ، animation_callback را وارد کرده و Enter را فشار دهید.اکنون وقتی در حال پخش این انیمیشن هستیم ، آهنگ روش تماس در آن نقطه خاص از انیمیشن فعال می شود. بیایید روند انیمیشن های شلیک با تفنگ و چاقو را تکرار کنیم! توجه داشته باشید از آنجا که فرآیند دقیقاً همان تپانچه است ، روند کار باید در عمق کمی کمتر توضیح داده شود. اگر گم شدید مراحل را از بالا دنبال کنید! دقیقاً یکسان است ، فقط در یک انیمیشن متفاوت. از لیست کشویی انیمیشن به انیمیشن &quot;Rifle_fire&quot; بروید. پس از رسیدن به انتهای لیست آهنگ انیمیشن ، با کلیک روی دکمه &quot;افزودن آهنگ&quot; در بالای لیست ، مسیر روش تماس را اضافه کنید. نقطه ای را که پوزه بند شروع به چشمک زدن می کند پیدا کنید و کلیک راست کنید و Insert Key را فشار دهید تا یک نقطه رهگیری روش تماس در آن موقعیت در مسیر اضافه شود. &quot;animation_callback&quot; را در قسمت نام پنجره بازشو تایپ کنید و Enter را فشار دهید. اکنون باید مسیر روش تماس را روی انیمیشن چاقو اعمال کنیم. انیمیشن &quot;Knife_fire&quot; را انتخاب کنید و به پایین قسمت های انیمیشن بروید. روی دکمه &quot;افزودن آهنگ&quot; در بالای لیست کلیک کنید و یک مسیر روش اضافه کنید. سپس یک نقطه در حدود یک سوم اول انیمیشن پیدا کنید تا نقطه روش تماس انیمیشن را در آن قرار دهید.در واقع ما چاقو را شلیک نخواهیم کرد و انیمیشن به جای شلیک ، یک انیمیشن خیره کننده است( We will not actually be firing the knife, and the animation is a stabbing animation rather than a firing one).برای این آموزش ما از منطق شلیک اسلحه برای چاقو خود استفاده مجدد می کنیم ، بنابراین انیمیشن به سبک سازگار با سایر انیمیشن ها نامگذاری شده است. از آنجا بر روی جدول زمانی کلیک راست کرده و &quot;Insert Key&quot; را کلیک کنید. &quot;animation_callback&quot; را در قسمت نام قرار داده و Enter را فشار دهید. نکته حتماً کار خود را ذخیره کنید!با انجام این کار ، ما تقریباً آماده هستیم تا توانایی شلیک کردن به اسکریپت player اضافه کنیم!ما باید یک صحنه بسازیم:صحنه برای گلوله ما.ایجاد صحنه گلوله روش های مختلفی برای کنترل گلوله های اسلحه در بازی های ویدیویی وجود دارد. در این مجموعه آموزشی ، ما به بررسی دو روش رایج خواهیم پرداخت:اشیا، و برنامه های raycastاین شی ای خواهد بود که در جهان سفر می کند و کد برخورد خود را کنترل می کند. در این روش ما یک شی گلوله را در جهتی که اسلحه ما روبرو است ایجاد و تخم ریزی می کنیم و سپس به جلو حرکت می کند. این روش چندین مزیت دارد. اولین مورد لازم نیست که گلوله ها را در دستگاه پخش خود ذخیره کنیم ، و گلوله به خودی خود با ارسال سیگنال ها وقتی که به جسم برخورد میکند از بین میرودمزیت دیگر این است که ما می توانیم حرکت گلوله پیچیده تری داشته باشیم. اگر می خواهیم با گذشت زمان گلوله  اندکی سقوط کند ، می توانیم اسکریپت کنترل کننده گلوله را به آرامی به سمت زمین فشار دهیم. استفاده از یک شی همچنین باعث می شود گلوله برای رسیدن به هدف خود طول بکشد ، هر لحظه که به آن اشاره شود فوراً برخورد نمی کند. این احساس واقع بینانه تر است زیرا هیچ چیز در زندگی واقعی بلافاصله از یک نقطه به نقطه دیگر حرکت نمی کند. یکی از معایب بزرگ عملکرد است. در حالی که داشتن هر گلوله مسیرهای خود را محاسبه می کند و برخورد خود را انجام می دهد ، امکان انعطاف پذیری زیادی را فراهم می کند ، اما به عملکرد آن بستگی دارد. با استفاده از این روش ما در هر مرحله حرکت هر گلوله را محاسبه می کنیم و اگرچه این ممکن است برای چند ده گلوله مشکلی ایجاد نکند ، اما در صورت داشتن بالقوه چند صد گلوله می تواند به یک مشکل بزرگ تبدیل شود.  اندازه تیرها نمونه برجسته ای هستند زیرا در بسیاری از تیراندازان اول شخص ، تیر ها در موقعیت هدف خود بلافاصله منفجر نمی شوند. شما همچنین می توانید گلوله ها را بارها و بارها مثل نارنجک به عنوان اشیا پیدا کنید زیرا به طور کلی قبل از منفجر شدن در سراسر جهان پرش می کنند.یکی دیگر از معایب استفاده از گلوله ها شبکه سازی است. اشیا باید موقعیت ها (حداقل) را با تمام کلاینت هایی که به سرور متصل هستند همگام سازی کنند. در حالی که ما هیچ نوع شبکه ای را اجرا نمی کنیم، این نکته ای است که باید هنگام ایجاد تیرانداز اول شخص خود به خاطر داشته باشید ، مخصوصاً اگر قصد دارید به نوعی شبکه در آینده اضافه کنید . راه دیگر برای رسیدگی به برخوردهای گلوله ای که ما در آن بررسی خواهیم کرد ، پخش مستقیم از شبکه است. این روش در اسلحه هایی که گلوله های متحرک سریعی دارند که به مرور زمان مسیر حرکت آنها به ندرت تغییر می کند بسیار رایج است. به جای ایجاد یک جسم گلوله و ارسال آن از طریق فضا ، ما در عوض یک پرتو را از لوله تفنگ به جلو می فرستیم. منشا raycast را روی موقعیت شروع گلوله قرار می دهیم و براساس طول می توانیم میزان برد گلوله را در فضا تنظیم کنیم.یک مزیت بزرگ این روش سبک بودن عملکرد آن است. محاسبه ارسال چند صد اشعه از طریق فضا برای کامپیوتر بسیار آسان تر از ارسال چند صد گلوله است. مزیت دیگر این است که ما می توانیم بلافاصله بفهمیم که آیا وقتی چیزی را خواسته ایم به آن ضربه زده ایم یا نه. برای شبکه سازی این مهم است زیرا ما نیازی به همگام سازی حرکات گلوله از طریق اینترنت نداریم ، فقط لازم است که raycast را بزنید یا خیر. هرچند پخش تلویزیونی معایبی دارد. یک نقطه ضعف مهم این است که ما نمی توانیم به راحتی یک پرتو را در چیزی غیر از یک خط قرار دهیم. این بدان معناست که ما فقط می توانیم در یک خط مستقیم شلیک کنیم هرچقدر طول پرتوی ما طولانی باشد. شما می توانید با ریختن چندین اشعه در موقعیت های مختلف ، توهم حرکت گلوله را ایجاد کنید ، اما نه تنها اجرای آن در کد دشوار است ، بلکه از نظر عملکرد نیز سنگین تر است. نقطه ضعف دیگر این است که ما نمی توانیم گلوله را ببینیم. با استفاده از اشیا گلوله در صورت مشاهده ، می توانیم حرکت گلوله در فضا را ببینیم ، اما چون انفجارها بلافاصله اتفاق می افتد ، روش مناسبی برای نشان دادن گلوله ها نداریم. می توانید از مبدا raycast تا نقطه برخورد یک خط بکشید ، و این یکی از روش های محبوب نمایش raycast است. روش دیگر به راحتی کشیدن raycast نیست ، زیرا از نظر تئوری گلوله ها خیلی سریع حرکت می کنند ، چشم ما به هر حال نمی تواند آن را ببیند.بیایید شی گلوله را تنظیم کنیم. Bullet_Scene.tscnرا باز کنید. صحنه شامل گره Spatialبه نام گلوله است،یک MeshInstance و یکCollisionShape.یک اسکریپت جدید به نام Bullet_script.gd ایجاد کنید و آن را به Bullet Spatial پیوست کنید. ما می خواهیم کل شی گلوله را در منتقل کنیم. ما از منطقه(Area) برای بررسی اینکه آیا با چیزی برخورد کرده ایم یا نه استفاده خواهیم کردتوجه داشته باشید چرا ما از Area استفاده می کنیم و از RigidBody استفاده نمی کنیم؟دلیل اصلی استفاده نکردن از RigidBody این است که نمی خواهیم گلوله با گره های دیگر RigidBody تعامل داشته باشد. با استفاده از Area اطمینان حاصل می کنیم که هیچ یک از گره ها، از جمله گلوله های دیگر ، مشکل نخواهند داشت. دلیل دیگر صرفاً سهولت در تشخیص برخورد با یک منطقه است! این اسکریپتی است که گلوله ما را کنترل خواهد کرد:extends Spatial varBULLET_SPEED = 70var BULLET_DAMAGE = 15 const KILL_TIMER = 4var timer = 0var hit_something = falsefunc _ready(): $Area.connect(&quot;body_entered&quot;, self, &quot;collided&quot;)func _physics_process(delta):var forward_dir = global_transform.basis.z.normalized() global_translate(forward_dir * BULLET_SPEED * delta) timer += deltaif timer &gt;= KILL_TIMER: queue_free()func collided(body):if hit_something == false:if body.has_method(&quot;bullet_hit&quot;): body.bullet_hit(BULLET_DAMAGE, global_transform)hit_something = truequeue_free()ابتدا چند متغیر کلاس تعریف می کنیم:BULLET_SPEED: سرعتی که گلوله حرکت می کند. BULLET_DAMAGE: خسارتی که گلوله به هر چیزی که با آن برخورد کند ، وارد خواهد کرد. KILL_TIMER: چه مدت گلوله می تواند بدون اصابت به چیزی دوام بیاورد. timer: برای ردیابی مدت زنده بودن گلوله. hit_something: بولینی برای ردیابی اینکه آیا چیزی را زده ایم یا نه.به استثنای timer و hit_something همه این متغیرها نحوه تعامل گلوله با جهان را تغییر می دهند. توجه داشته باشید دلیل استفاده ما از تایمر کشتن این است که موردی نداریم که گلوله ای برای همیشه سفر کند. با استفاده از یک تایمر kill می توان اطمینان حاصل کرد که هیچ گلوله ای برای همیشه سفر نمی کند و منابع را مصرف نمی کند.نکته همانطور که در قسمت 1 ، همه متغیرهای کلاس بزرگ را چند زوج داریم. دلیل این امر همان دلیلی است که در قسمت 1 آورده شده است: ما می خواهیم با این متغیرها مانند ثابت رفتار کنیم ، اما می خواهیم بتوانیم آنها را تغییر دهیم. در این صورت بعداً باید آسیب و سرعت این گلوله ها را تغییر دهیم ، بنابراین باید متغیر باشند و ثابت نباشند.در _ready قبلاً سیگنال body_entered منطقه را روی خود تنظیم کردیم تا هنگام ورود بدن به ناحیه ، عملکرد برخورد را فراخوانی کند. _physics_proces ،محور Z محلی گلوله را بدست می آورد. اگر به صحنه در حالت محلی نگاه کنید ، متوجه خواهید شد که گلوله به سمت محور محلی مثبت Z رو به رو است. در مرحله بعدی ، ما کل گلوله را با توجه به جهت رو به جلو هدایت می کنیم ، در سرعت و زمان دلتا را ضرب می کنیم. بعد از آن زمان دلتا را به تایمر خود اضافه می کنیم و بررسی می کنیم که آیا تایمر به مقداری بزرگتر از ثابت KILL_TIME رسیده است. اگر داشته باشد ، از queue_free برای آزاد کردن گلوله استفاده می کنیم. در برخورد ما بررسی می کنیم که آیا هنوز چیزی را زده ایم یا نه. بخاطر داشته باشید که برخورد فقط زمانی وارد می شود که شی ای وارد گره Area شود. اگر گلوله قبلاً با چیزی برخورد نکرده باشد ، سپس بررسی می کنیم که بدنی که گلوله با آن برخورد کرده عملکردی به نام bullet_hit دارد یا خیر. اگر اینگونه باشد ، ما آن را صدا می کنیم و آسیب گلوله و تغییر شکل گلوله را تاثیر می دهیم تا بتوانیم چرخش و موقعیت گلوله را بدست آوریم.در برخورد ، بدن می تواند یک StaticBody ، RigidBody یا KinematicBody باشد ما متغیر hit_something Bullet را روی true قرار می دهیم زیرا صرف نظر از اینکه بدنی که گلوله با آن برخورد کرده است عملکرد bullet_hit را دارد یا خیر ، به چیزی برخورد کرده است و بنابراین باید مطمئن شویم که گلوله به چیز دیگری برخورد نمی کند. سپس گلوله را با استفاده از queue_free حذف می کنیم.نکته شاید از خود بپرسید که چرا گلوله را با استفاده از queue_free به محض برخورد به چیزی حذف می کنیم ، حتی یک متغیر hit_something داریم. دلیل اینکه ما باید ردیابی کنیم که آیا چیزی را مورد اصابت قرار داده ایم یا نه این است که queue_free بلافاصله گره را حذف نمی کند ، بنابراین گلوله می تواند با جسم دیگری برخورد کند قبل از اینکه گودو فرصتی برای آزاد شدن آن پیدا کند. با ردیابی اینکه گلوله به چیزی اصابت کرده است ، می توانیم مطمئن شویم که گلوله فقط به یک جسم برخورد خواهد کرد. قبل از اینکه دوباره برنامه نویسی player را شروع کنیم ، بیایید به Player.tscn بیندازیم. دوباره Player.tscn را باز کنید. Rotation_Helper را گسترش دهید میبینید که دو گره وجود دارد:Gun_Fire_Points و Gun_Aim_Point.Gun_aim_point نقطه ای است که گلوله ها به سمت آن نشانه می روند. توجه کنید که چگونه با مرکز صفحه صف کشیده شده و یک فاصله به جلو در محور Z کشیده شده است. Gun_aim_point به عنوان نقطه ای عمل می کند که گلوله ها به طور حتم با آن برخورد می کنند.یک نمونه مش نامرئی برای اشکال زدایی وجود دارد. مش یک کره کوچک است که نشان می دهد گلوله ها به کدام هدف برخورد میکنند. Gun_Fire_Points را باز کنید و سه گره spatial دیگر پیدا خواهید کرد ، یکی برای هر سلاح. Rifle_Point را باز کنید و یک گره Raycast پیدا خواهید کرد. این جایی است که ما ماشین های انفجاری گلوله های تفنگ خود را ارسال خواهیم کرد. طول raycast تعیین می کند که گلوله های ما تا کجا مسافت را طی کنند. ما از گره Raycast برای کنترل گلوله تفنگ استفاده می کنیم زیرا می خواهیم تعداد زیادی گلوله را سریع شلیک کنیم. اگر از اشیا گلوله استفاده کنیم ، کاملاً ممکن است در ماشین های قدیمی به مشکلات عملکردی برخورد کنیم. توجه داشته باشید اگر فکر می کنید موقعیت نقاط از کجا آمده است ، موقعیت های خشن انتهای هر سلاح است.با رفتن به AnimationPlayer ، انتخاب یکی از انیمیشن های شلیک شده و مرور زمان بندی ، می توانید این موضوع را مشاهده کنید.نکته raycast برای هر سلاح باید بیشتر در انتهای هر سلاح قرار بگیرد. Knife_Point را باز کنید و یک گره Area پیدا خواهید کرد. ما از یک چاقو برای استفاده از یک ناحیه استفاده می کنیم زیرا فقط از تمام اجسام نزدیک خود مراقبت می کنیم و چاقوی ما به فضا شلیک نمیشود. اگر ما در حال ساخت چاقوی پرتاب کننده بودیم ، به احتمال زیاد یک شی گلوله را تخم ریزی می کنیم که شبیه چاقو است. در آخر ، ما Pistol_Point داریم. این همان نقطه ای است که ما اشیا گلوله خود را ایجاد می کنیم. ما در اینجا به هیچ گره اضافی نیاز نداریم ، زیرا گلوله تمام تشخیص برخورد خود را کنترل می کند. اکنون که دیدیم چگونه دیگر سلاح های خود را کنترل خواهیم کرد و گلوله ها را در کجا تخم ریزی خواهیم کرد ، بیایید کار کردن آنها را شروع کنیم.در صورت تمایل می توانید به گره های HUD نیز نگاه کنید. هیچ چیز فانتزی در آنجا وجود ندارد و غیر از استفاده از یک برچسب ، ما هیچ یک از این گره ها را لمس نخواهیم کرد. رابط های طراحی را با گره های کنترلControl برای آموزش استفاده از گره های GUI بررسی کنید. ایجاد اولین سلاح بیایید کد مربوط به هر یک از سلاح های خود را بنویسیم ، با شروع از تپانچه. Pistol_Point (Player -&gt; Rotation_Helper -&gt; Gun_Fire_Points -&gt; Pistol_Point)را انتخاب کرده و اسکریپت جدیدی به نام Weapon_Pistol.gd ایجاد کنید. کد زیر را به Weapon_Pistol.gdاضافه کنید:extends Spatial const DAMAGE = 15 const IDLE_ANIM_NAME = &quot;Pistol_idle&quot; const FIRE_ANIM_NAME = &quot;Pistol_fire&quot; var is_weapon_enabled = false var bullet_scene = preload(&quot;Bullet_Scene.tscn&quot;) var player_node = null func _ready(): pass func fire_weapon(): var clone = bullet_scene.instance() var scene_root = get_tree().root.get_children()[0] scene_root.add_child(clone) clone.global_transform = self.global_transform clone.scale = Vector3(4, 4, 4) clone.BULLET_DAMAGE = DAMAGE func equip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: is_weapon_enabled = true return true if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: player_node.animation_manager.set_animation(&quot;Pistol_equip&quot;) return false func unequip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: if player_node.animation_manager.current_state != &quot;Pistol_unequip&quot;: player_node.animation_manager.set_animation(&quot;Pistol_unequip&quot;) if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: is_weapon_enabled = false return true else: return falseابتدا برخی متغیرهای کلاس را که در اسکریپت به آنها نیاز داریم تعریف می کنیم: DAMAGE: میزان خسارت یک گلوله. IDLE_ANIM_NAME: نام انیمیشن بیکار تپانچه است. FIRE_ANIM_NAME: نام انیمیشن آتش تپانچه است. is_weapon_enabled: متغیری برای بررسی استفاده یا فعال بودن این سلاح. bullet_scene: صحنه گلوله ای که قبلاً روی آن کار کردیم. player_node: متغیری برای نگه داشتن Player.gd. دلیل اینکه بیشتر این متغیرها را تعریف می کنیم این است که می توانیم از آنها در Player.gd استفاده کنیم. هر کدام از سلاح هایی که خواهیم ساخت ، همه این متغیرها را خواهد داشت (منهای bullet_scene) بنابراین ما یک رابط ثابت برای تعامل در Player.gd داریم. با استفاده از متغیرها / توابع مشابه در هر سلاح ، می توانیم بدون نیاز به دانستن اینکه از کدام سلاح استفاده می کنیم ، با آنها تعامل داشته باشیم ، این باعث می شود کد ما بسیار مدولارتر شود زیرا ما می توانیم اسلحه را بدون نیاز به تغییر بسیاری از کد در Player.gd اضافه کنیم. و کار خواهد کرد. ما می توانیم همه کدها را در Player.gd بنویسیم ، اما پس از آن با افزودن سلاح ، مدیریت Player.gd دشوارتر می شود. با استفاده از یک طراحی مدولار با یک رابط سازگار ، می توانیم Player.gd را زیبا و مرتب نگه داریم ، در حالی که افزودن / حذف / اصلاح سلاح را نیز آسان تر می کنیم.در _ ready به سادگی از آن عبور می کنیم. یک نکته قابل توجه است ، فرضیه ای که در برخی موارد Player.gd را پر خواهیم کرد. ما می خواهیم فرض کنیم که Player.gd قبل از فراخوانی هر یک از توابع موجود در Weapon_Pistol.gd ، خود را از دست خواهد داد. اگرچه این می تواند به موقعیت هایی منجر شود که بازیکن خود را رد نمی کند (زیرا ما فراموش می کنیم) ، برای بازیابی بازیکن باید یک رشته طولانی از تماسهای get_parent داشته باشیم تا درخت صحنه را رد کنیم. این خیلی زیبا به نظر نمی رسد (get_parent (). get_parent (). get_parent ()) و غیره و فرضاینکه بخاطر بسپاریم که خود را به هر سلاحی در Player.gd منتقل می کنیم نسبتاً ایمن است. بعد بیایید به fire_weapon نگاه کنیم:اولین کاری که ما انجام می دهیم این است که صحنه گلوله ای را که قبلاً ساخته بودیم ، نمونه کنیم. نکته با استفاده از صحنه ، ما یک گره جدید ایجاد می کنیم که تمام گره (های) صحنه را که در آن صحنه نصب کرده ایم نگه می دارد و به طور موثر آن صحنه را شبیه سازی می کند. سپس ما یک کلون به اولین گره کودک ریشه صحنه ای که هم اکنون در آن هستیم ، اضافه می کنیم. با این کار ، ما آن را فرزند گره اصلی صحنه بارگیری شده فعلی می کنیم. به عبارت دیگر ، ما در حال بارگذاری کلون به عنوان فرزند گره اول (هرچه در بالای درخت صحنه است) در صحنه بارگذاری شده / باز شده در حال حاضر هستیم. اگر صحنه بارگیری شده / باز شده در حال آزمایش Testing_Area.tscn است ، ما می توانیم کلون خود را به عنوان فرزند Testing_Area ، گره اصلی در آن صحنه ، اضافه کنیم.در مرحله بعدی ، تغییر شکل کلی کلون را به شکل تبدیل جهانی Pistol_Point قرار می دهیم. دلیل این کار ما این است که گلوله در انتهای تپانچه تخم ریزی می شود. با کلیک روی AnimationPlayer و پیمایش در Pistol_fire می توانید ببینید که Pistol_Point در انتهای تپانچه قرار گرفته است. بعد آن را با ضریب 4 مقیاس بندی می کنیم زیرا صحنه گلوله به طور پیش فرض کمی کوچک است. سپس میزان خسارت گلوله (BULLET_DAMAGE) را روی میزان خسارت یک گلوله تپانچه تنظیم می کنیم (DAMAGE). حالا بیایید به equip_weapon نگاهی بیندازیم:اولین کاری که ما انجام می دهیم بررسی این است که آیا مدیر انیمیشن در انیمیشن بیکار تپانچه idle قرار دارد یا خیر. اگر در انیمیشن بیکار تپانچه هستیم ، تنظیم می کنیم is_weapon_enabled به true زیرا اسلحه با موفقیت تجهیز شده است. از آنجا که می دانیم انیمیشن تجهیز تپانچه unequip ما به طور خودکار به انیمیشن بیکار تپانچهidle منتقل می شود ، اگر در انیمیشن بیکار تپانچه هستیم ، تپانچه باید اجرای انیمیشن تجهیز را تمام کرده باشد.ما می دانیم که این انیمیشن ها انتقال می یابند برای همین ما کد را برای انتقال آنها در Animation_Manager.gd نوشتیم بعد بررسی می کنیم که آیا پخش کننده در وضعیت انیمیشن Idle_unarmed است. از آنجا که تمام انیمیشن های تکنیکی به این حالت می روند و از آنجا که می توان هر اسلحه ای را از این حالت مجهز کرد ، اگر بازیکن در حالت Idle_unarmed باشد ، ما انیمیشن ها را به Pistol_ Equip تغییر می دهیم. از آنجا که می دانیم Pistol_ Equip به Pistol_idle منتقل می شود ، دیگر نیازی به پردازش اضافی برای تجهیز سلاح نداریم ، اما چون هنوز قادر به تجهیز تپانچه نبودیم ، false بر میگردانیم. در آخر ، بیایید به unequip_weapon نگاهی بیندازیم: unequip_weapon مانند equip_weapon است ، اما در عوض ما برعکس چیزها را بررسی می کنیم. ابتدا بررسی می کنیم که آیا player در وضعیت انیمیشن بیکارidle است یا خیر. سپس بررسی می کنیم که player در انیمیشن Pistol_unequip نیست. اگر player در انیمیشن Pistol_unequip نیست ، می خواهیم انیمیشن pistol_unequip را اجرا کنیم. توجه داشته باشید ممکن است از خود بپرسید که چرا ما بررسی می کنیم که آیا بازیکن در انیمیشن بیکار تپانچه حضور دارد یا خیر ، و سپس مطمئن می شویم که بازیکن بلافاصله از تکنیک خارج نیست. دلیل بررسی اضافی این است که قبل از اینکه فرصتی برای پردازش set_animation داشته باشیم ، می توانیم در موارد نادر دوبار با unequip_weapon تماس بگیریم ، بنابراین برای اطمینان از پخش انیمیشن unquip ، این چک اضافی را اضافه می کنیم. در مرحله بعدی بررسی می کنیم که آیا پخش کننده در Idle_unarmed قرار دارد یا خیر ، این همان وضعیت انیمیشنی است که از Pistol_unequip به آن منتقل خواهیم شد. اگر بازیکن در Idle_unarmed باشد ، از آنجا که دیگر از این سلاح استفاده نمی کنیم ، is_weapon_enabled را روی false تنظیم می کنیم و به دلیل اینکه تپانچه را با موفقیت از عوض کردیم ، true بر میگردانیم. اگر بازیکن در Idle_unarmed نباشد ، ما false برمیگردانیم زیرا هنوز با موفقیت تپانچه را مجهز نکرده ایم.ایجاد دو سلاح دیگر اکنون که همه کدی را که برای تپانچه مورد نیاز است در اختیار ما قرار داد ، بیایید کد اسلحه و چاقو را در قسمت بعدی اضافه کنیمRifle_Point (Player -&gt; Rotation_Helper -&gt; Gun_Fire_Points -&gt; Rifle_Point) را انتخاب کنید و یک اسکریپت جدید بنام Weapon_Rifle.gd ایجاد کنید ، سپس موارد زیر را اضافه کنید:extends Spatial const DAMAGE = 4 const IDLE_ANIM_NAME = &quot;Rifle_idle&quot; const FIRE_ANIM_NAME = &quot;Rifle_fire&quot; var is_weapon_enabled = false var player_node = null func _ready(): pass func fire_weapon(): var ray = $Ray_Cast ray.force_raycast_update() if ray.is_colliding(): var body = ray.get_collider() if body == player_node: pass elif body.has_method(&quot;bullet_hit&quot;): body.bullet_hit(DAMAGE, ray.global_transform) func equip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: is_weapon_enabled = true return true if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: player_node.animation_manager.set_animation(&quot;Rifle_equip&quot;) return false func unequip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: if player_node.animation_manager.current_state != &quot;Rifle_unequip&quot;: player_node.animation_manager.set_animation(&quot;Rifle_unequip&quot;) if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: is_weapon_enabled = false return true return falseبیشتر این موارد دقیقاً همان Weapon_Pistol.gd است ، بنابراین ما فقط قصد داریم به موارد تغییر یافته نگاه کنیم: fire_weaponاولین کاری که ما می کنیم این است که گره Raycast را بدست آوریم ، که فرزند Rifle_Point است. بعد Raycast را مجبور می کنیم با استفاده از force_raycast_update به روز شود. این کار Raycast را مجبور می کند وقتی که آن را فراخوانی می کنیم ، برخورد را تشخیص دهد ، به این معنی که با جهان فیزیک سه بعدی یک بررسی تصادف کامل قاب دریافت می کنیم. سپس بررسی می کنیم که آیا Raycast با چیزی برخورد کرده است یا خیر. اگر Raycast با چیزی برخورد کرده باشد ، ابتدا بدنه برخوردی را که با آن برخورد کرده است بدست می آوریم. این می تواند StaticBody ، RigidBody یا KinematicBody باشد. در مرحله بعدی می خواهیم اطمینان حاصل کنیم که بدنی که با آن برخورد کرده ایم بازیکن نیست ، زیرا ما (احتمالاً) نمی خواهیم به بازیکن توانایی شلیک به پا را بدهیم. اگر بدن player نیست ، سپس بررسی می کنیم که آیا عملکردی به نام bullet_hit دارد یا خیر. اگر این اتفاق بیفتد ، ما آن را صدا می کنیم و میزان خسارت این گلوله را وارد می کنیم (DAMAGE) ، و تغییر شکل کلی Raycast ، بنابراین می توانیم تشخیص دهیم که گلوله از کدام جهت به وجود آمده است. اکنون تنها کاری که باید انجام دهیم نوشتن کد چاقو است. Knife_Point (Player -&gt; Rotation_Helper -&gt; Gun_Fire_Points -&gt; Knife_Point) را انتخاب کرده و اسکریپت جدیدی به نام Weapon_Knife.gd ایجاد کرده و موارد زیر را اضافه کنید:extends Spatial const DAMAGE = 40 const IDLE_ANIM_NAME = &quot;Knife_idle&quot; const FIRE_ANIM_NAME = &quot;Knife_fire&quot; var is_weapon_enabled = false var player_node = null func _ready(): pass func fire_weapon(): var area = $Area var bodies = area.get_overlapping_bodies() for body in bodies: if body == player_node: continue if body.has_method(&quot;bullet_hit&quot;): body.bullet_hit(DAMAGE, area.global_transform) func equip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: is_weapon_enabled = true return true if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: player_node.animation_manager.set_animation(&quot;Knife_equip&quot;) return false func unequip_weapon(): if player_node.animation_manager.current_state == IDLE_ANIM_NAME: player_node.animation_manager.set_animation(&quot;Knife_unequip&quot;) if player_node.animation_manager.current_state == &quot;Idle_unarmed&quot;: is_weapon_enabled = false return true return falseهمانند Weapon_Rifle.gd ، تنها تفاوت ها در fire_weapon است ، بنابراین بیایید به آن نگاه کنیم:اولین کاری که ما می کنیم این است که گره کودک Area از Knife_Point را بدست آوریم. بعد می خواهیم تمام بدنه های برخورد را با استفاده از get_overlapping_bodies در داخل منطقه Area بدست آوریم. با این کار لیستی از هر جسمی که منطقه Area را لمس می کند باز می گردد. ما می خواهیم از طریق هر یک از آن اجساد عبور کنیم. ابتدا بررسی می کنیم که بدن بازیکن نیست ، زیرا نمی خواهیم به بازیکن اجازه دهیم خود را خنجر بزند. اگر بدن بازیکن است ، ما از ادامه استفاده می کنیم بنابراین می پریم و بدن بعدی را نگاه می کنیم. اگر به بدن بعدی پرش نکرده ایم ، سپس بررسی می کنیم که آیا بدن عملکرد  bullet_hit را دارد یا خیر. اگر این اتفاق بیفتد ، ما می توانیم مقدار خسارتی را که یک ضربه چاقو متحمل می شود (DAMAGE) و تحول جهانی منطقه وارد کنیم. توجه داشته باشید در حالی که می توانستیم مکانی خشن برای محل اصابت دقیق چاقو محاسبه کنیم ، اما ما نمی خواهیم این کار را انجام دهیم زیرا استفاده از موقعیت منطقهArea به اندازه کافی خوب کار می کند و زمان اضافی لازم برای محاسبه موقعیت خشن برای هر بدن ارزش تلاش را ندارد.کار کردن سلاح ها بیایید شروع به کار کردن سلاح ها در Player.gd کنیم.ابتدا بیایید متغیرهای کلاس مورد نیاز برای سلاح ها را اضافه کنیم:# Place before _readyvar animation_manager var current_weapon_name = &quot;UNARMED&quot; var weapons = {&quot;UNARMED&quot;:null, &quot;KNIFE&quot;:null, &quot;PISTOL&quot;:null, &quot;RIFLE&quot;:null} const WEAPON_NUMBER_TO_NAME = {0:&quot;UNARMED&quot;, 1:&quot;KNIFE&quot;, 2:&quot;PISTOL&quot;, 3:&quot;RIFLE&quot;} const WEAPON_NAME_TO_NUMBER = {&quot;UNARMED&quot;:0, &quot;KNIFE&quot;:1, &quot;PISTOL&quot;:2, &quot;RIFLE&quot;:3} var changing_weapon = false var changing_weapon_name = &quot;UNARMED&quot; var health = 100 var UI_status_labelاجازه دهید بررسی کنیم که این متغیرهای جدید چه کاری انجام می دهند: animation_manager: این گره AnimationPlayer و اسکریپت آن را که قبلاً نوشتیم در خود نگه می دارد. current_weapon_name: نام سلاحی که ما در حال حاضر از آن استفاده می کنیم. این چهار مقدار ممکن دارد: UNARMED ، KNIFE ، PISTOL ، و RIFLEarms: دیکشنری که تمام گره های سلاح را در خود جای می دهد. WEAPON_NUMBER_TO_NAME: ما از این برای تغییر سلاح استفاده خواهیم کرد. WEAPON_NAME_TO_NUMBER:به ما امکان می دهد نام اسلحه را به شماره آن تبدیل کنیم. ما از این برای تغییر سلاح استفاده خواهیم کرد. change_weapon: بولینی برای ردیابی اینکه آیا ما اسلحه / سلاح را تغییر می دهیم یا نه. change_weapon_name: نام سلاحی است که می خواهیم به آن تغییر دهیم. health: بازیکن ما چقدر سلامتی دارد. در این قسمت از آموزش ما از آن استفاده نخواهیم کرد. UI_status_label:برچسبی برای نشان دادن میزان سلامتی ما ، و مقدار مهمات. در مرحله بعدی باید چند مورد را در _ready اضافه کنیم:func _ready(): camera = $Rotation_Helper/Camera rotation_helper = $Rotation_Helper animation_manager = $Rotation_Helper/Model/Animation_Player animation_manager.callback_function = funcref(self, &quot;fire_bullet&quot;) Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) weapons[&quot;KNIFE&quot;] = $Rotation_Helper/Gun_Fire_Points/Knife_Point weapons[&quot;PISTOL&quot;] = $Rotation_Helper/Gun_Fire_Points/Pistol_Point weapons[&quot;RIFLE&quot;] = $Rotation_Helper/Gun_Fire_Points/Rifle_Point var gun_aim_point_pos = $Rotation_Helper/Gun_Aim_Point.global_transform.origin for weapon in weapons: var weapon_node = weapons[weapon] if weapon_node != null: weapon_node.player_node = self weapon_node.look_at(gun_aim_point_pos, Vector3(0, 1, 0)) weapon_node.rotate_object_local(Vector3(0, 1, 0), deg2rad(180)) current_weapon_name = &quot;UNARMED&quot; changing_weapon_name = &quot;UNARMED&quot; UI_status_label = $HUD/Panel/Gun_label flashlight = $Rotation_Helper/Flashlightبیایید آنچه تغییر کرده است را مرور کنیم. ابتدا گره AnimationPlayer را دریافت کرده و آن را به متغیر animation_manager اختصاص می دهیم. سپس عملکرد callback را روی FuncRef تنظیم می کنیم که عملکرد fire_bullet بازیکن را فراخوانی کند. در حال حاضر ما تابع fire_bullet را ننوشته ایم ، اما به زودی به آنجا خواهیم رسید. بعد همه گره های سلاح را می گیریم و آنها را به سلاح ها اختصاص می دهیم. این به ما امکان می دهد فقط به نام گره های سلاح (KNIFE ، PISTOL یا RIFLE) دسترسی پیدا کنیم. سپس موقعیت جهانی Gun_Aim_Point را بدست می آوریم تا بتوانیم اسلحه های بازیکن را بچرخانیم. سپس ما از طریق هر سلاح در اسلحه عبور می کنیم. ما ابتدا گره سلاح را بدست می آوریم. اگر گره اسلحه تهی نباشد ، سپس متغیر player_node آن را روی این اسکریپت (Player.gd) تنظیم می کنیم. سپس ما باید آن را با استفاده از تابع look_at در gun_aim_point_pos نگاه کنیم و سپس آن را 180 درجه در محور Y بچرخانیم. توجه داشته باشید ما تمام آن نقاط سلاح را 180 درجه روی محور Y آنها می چرخانیم زیرا دوربین ما به سمت عقب است. اگر همه این نقاط سلاح را 180 درجه نچرخانیم ، همه سلاح ها به عقب شلیک می شوند. سپس نام current_weapon_name و change_weapon_name را به UNARMED تنظیم می کنیم. سرانجام ، ما برچسب UI را از HUD خود دریافت می کنیم. بیایید فراخوانی عملکرد جدیدی را به _physics_proces اضافه کنیم تا بتوانیم اسلحه ها را تغییر دهیم. این کد جدید است:func _physics_process(delta): process_input(delta) process_movement(delta) process_changing_weapons(delta)حالا ما اسلحه های process_changing_ را فراخوانی می کنیم. حالا بیایید تمام کد ورودی player را برای سلاح های موجود در process_input اضافه کنیم. کد زیر را اضافه کنید:# ---------------------------------- # Changing weapons. var weapon_change_number = WEAPON_NAME_TO_NUMBER[current_weapon_name] if Input.is_key_pressed(KEY_1): weapon_change_number = 0 if Input.is_key_pressed(KEY_2): weapon_change_number = 1 if Input.is_key_pressed(KEY_3): weapon_change_number = 2 if Input.is_key_pressed(KEY_4): weapon_change_number = 3 if Input.is_action_just_pressed(&quot;shift_weapon_positive&quot;): weapon_change_number += 1 if Input.is_action_just_pressed(&quot;shift_weapon_negative&quot;): weapon_change_number -= 1 weapon_change_number = clamp(weapon_change_number, 0, WEAPON_NUMBER_TO_NAME.size() - 1) if changing_weapon == false: if WEAPON_NUMBER_TO_NAME[weapon_change_number] != current_weapon_name: changing_weapon_name = WEAPON_NUMBER_TO_NAME[weapon_change_number] changing_weapon = true # ---------------------------------- # ---------------------------------- # Firing the weapons if Input.is_action_pressed(&quot;fire&quot;): if changing_weapon == false: var current_weapon = weapons[current_weapon_name] if current_weapon != null: if animation_manager.current_state == current_weapon.IDLE_ANIM_NAME: animation_manager.set_animation(current_weapon.FIRE_ANIM_NAME)بیایید با اضافه کردن چگونگی تغییر سلاح ، موارد اضافی را مرور کنیم. ابتدا شماره سلاح فعلی را دریافت می کنیم و آن را به weapon_change_number اختصاص می دهیم. سپس بررسی می کنیم که آیا یکی از کلیدهای عددی (کلیدهای 1-4) فشرده شده است یا خیر. در صورت وجود ، weapon_change_number را بر روی مقدار تعیین شده در آن کلید تنظیم می کنیم. توجه داشته باشید دلیل اینکه کلید 1 به 0 نگاشت می شود این است که اولین عنصر در یک لیست نگاشته می شود ، نه یک. بیشتر اکسسوری های لیست / آرایه ها در اکثر زبان های برنامه نویسی به جای 1 از 0 شروع می شوند. برای اطلاعات بیشتر به https://en.wikipedia.org/wiki/Nero-base-numberمراجعه کنیدبعد بررسی می کنیم که shift_weapon_positive یا shift_weapon_negative فشار داده شده است. اگر یکی از آنها باشد ، عدد 1 را از gun_change_num جمع و کم می کنیم. از آنجا که ممکن است بازیکن weapon_change_number را به خارج از تعداد اسلحه ای که بازیکن در اختیار دارد منتقل کرده باشد ، ما آن را گیره می کنیم بنابراین نمی تواند از حداکثر تعداد اسلحه ای که بازیکن دارد بیشتر شود و عدد 0 یا weapon_change_number را تغییر می دهد.سپس بررسی می کنیم که آیا بازیکن در حال تغییر اسلحه نیست. اگر نیست ، سپس بررسی می کنیم که آیا سلاحی که بازیکن می خواهد به آن تغییر دهد سلاح جدیدی است یا نه اگر سلاحی که بازیکن می خواهد به آن تغییر دهد سلاح جدیدی است ، پس از آن change_weapon_name را به اسلحه در gun_change_number تنظیم کرده و change_weapon را روی true قرار می دهیم. برای شلیک سلاح ابتدا بررسی می کنیم که آیا عمل آتش فشرده شده است یا خیر. سپس بررسی می کنیم که بازیکن تغییر سلاح نمی دهد. بعد گره سلاح را برای سلاح فعلی دریافت می کنیم. اگر گره سلاح فعلی برابر با null نباشد و پخش کننده در وضعیت IDLE_ANIM_NAME خود باشد ، ما انیمیشن پخش کننده را روی FIRE_ANIM_NAME اسلحه فعلی تنظیم می کنیم. بیایید کد زیر را به process_changing_weapons اضافه کنید:func process_changing_weapons(delta): if changing_weapon == true: var weapon_unequipped = false var current_weapon = weapons[current_weapon_name] if current_weapon == null: weapon_unequipped = true else: if current_weapon.is_weapon_enabled == true: weapon_unequipped = current_weapon.unequip_weapon() else: weapon_unequipped = true if weapon_unequipped == true: var weapon_equipped = false var weapon_to_equip = weapons[changing_weapon_name] if weapon_to_equip == null: weapon_equipped = true else: if weapon_to_equip.is_weapon_enabled == false: weapon_equipped = weapon_to_equip.equip_weapon() else: weapon_equipped = true if weapon_equipped == true: changing_weapon = false current_weapon_name = changing_weapon_name changing_weapon_name = &quot;&quot;بیایید مرور کنیم آنچه در اینجا اتفاق می افتد:اولین کاری که ما انجام می دهیم اطمینان از دریافت ورودی برای تغییر سلاح است. این کار را با اطمینان از صحت changing_weapons انجام می دهیم. در مرحله بعد ما یک متغیر (arms_unequipped) تعریف می کنیم ، بنابراین می توانیم بررسی کنیم که آیا سلاح فعلی با موفقیت مجهز شده است یا خیر. اگر سلاح فعلی پوچ نیست ، پس باید بررسی کنیم که آیا سلاح فعال است یا خیر. اگر سلاح فعال باشد ، ما عملکرد unequip_weapon آن را فراخوانی می کنیم تا انیمیشن unquip را شروع کند. اگر اسلحه فعال نباشد ، arms_unequipped را به true تنظیم می کنیم زیرا سلاح با موفقیت تجهیز نشده است. اگر سلاح فعلی خنثیnull است ، پس می توانیم به سادگی weapon_unequipped را به true تنظیم کنیم. دلیل این که ما این بررسی را انجام می دهیم این است که هیچ اسکریپت / گره اسلحه ای برای UNARMED وجود ندارد ، اما همچنین هیچ انیمیشنی برای UNARMED وجود ندارد ، بنابراین ما می توانیم تجهیز سلاحی را که بازیکن می خواهد تغییر دهد ، شروع کنیم.اگر بازیکن اسلحه فعلی را با موفقیت مجهز نکرده استarms_unequipped == true ما باید سلاح جدید را تجهیز کنیم. ابتدا یک متغیر جدید (arms_equipped) برای پیگیری اینکه آیا بازیکن با موفقیت سلاح جدید را مجهز کرده است یا خیر ، تعریف می کنیم. سپس سلاحی را دریافت می کنیم که بازیکن می خواهد به آن تغییر دهد. اگر سلاحی که بازیکن می خواهد به آن تغییر دهد پوچ نیست ، سپس بررسی می کنیم که آیا فعال است یا خیر. اگر فعال نباشد ، ما تابع equip_weapon آن را فراخوانی می کنیم تا شروع به تجهیز سلاح کند. اگر اسلحه فعال باشد ، arms_equipped را به true تنظیم می کنیم. اگر اسلحه ای که بازیکن می خواهد به آن تغییر دهد خالی از سکنه است ، ما به سادگی arms_equipped را به true تنظیم می کنیم زیرا هیچ گره / اسکریپتی برای UNARMED نداریم و همچنین هیچ انیمیشن نداریم. در آخر ، بررسی می کنیم که آیا بازیکن سلاح جدید را با موفقیت مجهز کرده است یا خیر. اگر او این کار را انجام داده باشد ، ما change_weapon را false قرار می دهیم زیرا بازیکن دیگر سلاح را تغییر نمی دهد. ما از زمانی که سلاح فعلی تغییر کرده است ، current_weapon_name را بر روی changing_weapon_name قرار می دهیم و سپس changing_weapon_name را به یک رشته خالی تنظیم می کنیم.حالا ، ما باید یک عملکرد دیگر به بازیکن اضافه کنیم ، و سپس بازیکن آماده شلیک اسلحه است! ما باید fire_bullet را اضافه کنیم که توسط AnimationPlayer در آن نقاطی که قبلاً در مسیر عملکرد AnimationPlayer تنظیم کردیم فراخوانی می شود:func fire_bullet(): if changing_weapon == true: return weapons[current_weapon_name].fire_weapon()اجازه دهید بررسی کنیم که این عملکرد چه کاری انجام می دهد:ابتدا بررسی می کنیم که آیا بازیکن در حال تغییر اسلحه است یا خیر. اگر بازیکن در حال تغییر سلاح است ، ما شلیک نمی خواهیم ، بنابراین return برمیگردانیم.نکته return تماس فراخوانی بقیه عملکرد را متوقف می کند. در این حالت ، ما یک متغیر را برنمی گردانیم زیرا فقط علاقه مند نیستیم که بقیه کد را اجرا کنیم و همچنین وقتی این تابع را فراخوانی می کنیم بدنبال متغیر برگشتی نیستیم. سپس با فراخوانی عملکرد به fire_weapon  سلاح فعلی که بازیکن برای شلیک استفاده می کند ، میگوییم.نکته یادتان هست که چگونه اشاره کردیم که سرعت انیمیشن ها برای شلیک سریعتر از سایر انیمیشن ها بود؟ با تغییر سرعت انیمیشن شلیک ، می توانید سرعت شلیک گلوله را تغییر دهید! قبل از اینکه آماده شویم تا سلاح های جدید خود را آزمایش کنیم ، هنوز کمی کار برای انجام دادن داریم.ایجاد برخی از اجسام آزمایشیبا رفتن به پنجره برنامه نویسی ، کلیک روی &quot;file&quot;  یک اسکریپت جدید ایجاد کنید. این اسکریپت را RigidBody_hit_test نامگذاری کرده و مطمئن شوید که RigidBody گسترش یافته است. اکنون باید این کد را اضافه کنیم:extends RigidBody const BASE_BULLET_BOOST = 9; func _ready(): pass func bullet_hit(damage, bullet_global_trans): var direction_vect = bullet_global_trans.basis.z.normalized() * BASE_BULLET_BOOSTapply_impulse((bullet_global_trans.origin - global_transform.origin).normalized(), direction_vect * damage)بیایید نحوه کار bullet_hit را بررسی کنیم:ابتدا بردار جهت دار گلوله را بدست می آوریم. به این ترتیب است که می توان فهمید گلوله از کدام جهت به RigidBody برخورد می کند. ما برای شلیک کردن به RigidBody در همان جهتی که از گلوله استفاده می شود استفاده خواهیم کرد. توجه داشته باشید ما باید بردار جهت را توسط BASE_BULLET_BOOST تقویت کنیم تا گلوله ها کمی بیشتر از یک مشت جمع شوند و گره های RigidBody را به روشی قابل مشاهده حرکت دهیم. اگر در هنگام برخورد گلوله ها با RigidBody ، واکنش کم یا بیشتری می خواهید ، می توانید BASE_BULLET_BOOST را روی مقادیر پایین یا بالاتر تنظیم کنید. سپس ما یک ضربه را با استفاده از apply_impulse اعمال می کنیم. ابتدا باید موقعیت را برای ضربه محاسبه کنیم. از آنجا که apply_impulse یک بردار را نسبت به RigidBody می گیرد ، ما باید فاصله RigidBody تا گلوله را محاسبه کنیم. ما این کار را با کم کردن منشا / موقعیت جهانی RigidBody از مبدا / موقعیت جهانی گلوله انجام می دهیم. این فاصله از RigidBody تا گلوله را به ما می رساند. ما این بردار را عادی می کنیم تا اندازه برخورد کننده تاثیری بر میزان حرکت گلوله ها از RigidBody نداشته باشد. سرانجام ، ما باید نیروی ضربه را محاسبه کنیم. برای این منظور ، از جهتی که گلوله رو به آن است استفاده می کنیم و آن را در آسیب گلوله ضرب می کنیم. این نتیجه خوبی می دهد و برای گلوله های قوی تر ، نتیجه قوی تری می گیریم. اکنون باید این اسکریپت را به همه گره های RigidBody که می خواهیم تحت تأثیر قرار بگیریم ، ضمیمه کنیم. Testing_Area.tscn را باز کرده و تمام مکعب هایی را که در گره Cubes قرار دارند انتخاب کنید.نکته اگر مکعب بالا را انتخاب کنید ، و سپس Shift را فشار دهید و آخرین مکعب را انتخاب کنید ، Godot تمام مکعب های بین را انتخاب می کند! هنگامی که همه مکعب ها را انتخاب کردید ، در inspectore پایین بروید تا به قسمت &quot;scripts&quot; برسید. روی کشویی کلیک کنید و &quot;load&quot; را انتخاب کنید. اسکریپت جدید ایجاد شده RigidBody_hit_test.gd خود را باز کنید.یادداشت های نهاییدر قسمت 3 ، ما مهمات را به سلاح ها و همچنین برخی از صداها اضافه خواهیم کرد!حتماً بار دیگر کد را بخوانید! شما می توانید پروژه تمام شده این قسمت را از اینجا بارگیری کنید:https://docs.godotengine.org/en/stable/_downloads/62938c307343ee71b4fb22939a9a780f/Godot_FPS_Part_2.ziphttps://vrgl.ir/KNFOGقسمت اول</description>
                <category>عباس زنگارکی فراهانی</category>
                <author>عباس زنگارکی فراهانی</author>
                <pubDate>Sun, 11 Oct 2020 21:07:39 +0330</pubDate>
            </item>
                    <item>
                <title>اموزش ساخت بازی با گودو(گودوت)قسمت اول</title>
                <link>https://virgool.io/coderlife/%D8%A7%D9%85%D9%88%D8%B2%D8%B4-%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D8%A7%D8%B2%DB%8C-%D8%A8%D8%A7-%DA%AF%D9%88%D8%AF%D9%88%DA%AF%D9%88%D8%AF%D9%88%D8%AA-widwrdapzku5</link>
                <description>نمای کلی قسمتدر این قسمت ما یک بازیکن اول شخص خواهیم ساخت که می تواند در محیط اطراف حرکت کند.با پایان این قسمت ، شما یک شخصیت اول شخص خواهید داشت که می تواند در محیط بازی حرکت کند ، دوی سرعت بزند ، با دوربین اول شخص مبتنی بر ماوس به اطراف نگاه کند ، به هوا بپرد و یک چراغ فلاش را خاموش و روشن کند.گودو را راه اندازی کنید و پروژه( https://docs.godotengine.org/en/stable/_downloads/06eb944abda1cabe2ed92be2d4f42744/Godot_FPS_Starter.zip )موجود درassets را باز کنید.ابتدا تنظیمات پروژه را باز کرده و به برگه &quot;Input Map&quot; بروید. خواهید دید که چندین اقدام قبلاً تعریف شده است. ما از این اقدامات برای بازیکن خود استفاده خواهیم کرد. در صورت تمایل می توانید کلیدهای مربوط به این اقدامات را تغییر دهید.بیایید یک ثانیه وقت بگذاریم تا ببینیم چه چیزی در assets داریم.چندین صحنه در Assets گنجانده شده است. به عنوان مثال ، در res: // ما 14 صحنه داریم که با گذراندن این مجموعه آموزشی ، بیشتر آنها را بازدید خواهیم کرد. در حال حاضر اجازه دهید Player.tscn را باز کنیم.دسته ای از صحنه ها و چند بافت در پوشه Assets وجود دارد. اگر می خواهید می توانید به این موارد نگاه کنید ، اما ما در این مجموعه آموزشی از طریق دارایی ها کاوش نخواهیم کرد. دارایی ها شامل همه مدل های استفاده شده برای هر یک از سطوح و همچنین برخی از بافت ها و مواد است.ایجاد منطق حرکت FPS پس از باز کردن Player.tscn ، بیایید نگاهی سریع به نحوه راه اندازی آن بیندازیم ابتدا به نحوه تنظیم شکلهای برخورد بازیکن توجه کنید. استفاده از کپسول اشاره عمودی(collision shape /capsul shape) به عنوان شکل برخورد(collision) برای بازیکن ، در اکثر بازی های اول شخص کاملاً رایج است.ما یک مربع کوچک(collision shape/box shape) به &quot;پاهای&quot; بازیکن اضافه می کنیم تا بازیکن احساس کند در یک نقطه متعادل استما می خواهیم &quot;پا&quot; کمی بالاتر از پایین کپسول باشد ، بنابراین می توانیم از لبه ها کمی بچرخانیم. محل قرار دادن &quot;پاها&quot; به سطح شما و احساسی که می خواهید بازیکن شما داشته باشد بستگی دارد.در بسیاری از مواقع بازیکن وقتی به لبه می رود و از صفحه خارج می شود ، متوجه دایره ای بودن شکل برخورد(collision shape) می شود. ما مربع کوچکی را در پایین کپسول اضافه می کنیم تا لبه های لغزنده و اطراف آن را کاهش دهیم.نکته دیگری که باید توجه کنید این است که چند گره از فرزندان Rotation_Helper هستند. به این دلیل که Rotation_Helper شامل تمام گره هایی است که می خواهیم در محور X بچرخانیم (بالا و پایین). دلیل این امر این است که می توانیم Player را در محور Y و Rotation_helper را در محور X بچرخانیم.اگر ما از Rotation_helper استفاده نمی کردیم ، احتمالاً موارد چرخش همزمان در هر دو محور X و Y داشتیم که در برخی موارد به طور بالقوه به حالت چرخش در هر سه محور تبدیل می شود. برای اطلاعات بیشتر به ( https://docs.godotengine.org/en/stable/tutorials/3d/using_transforms.html#doc-using-transforms ) مراجعه کنیدیک اسکریپت جدید به گره Player متصل کنید و آن را Player.gd بنامید. بیایید بازیکن خود را با اضافه کردن توانایی حرکت در اطراف ، نگاه کردن با ماوس و پرش ، برنامه ریزی کنیم. کد زیر را به Player.gd اضافه کنید:extends KinematicBodyconst GRAVITY = -24.8var vel = Vector3()const MAX_SPEED = 20const JUMP_SPEED = 18const ACCEL = 4.5var dir = Vector3()const DEACCEL= 16const MAX_SLOPE_ANGLE = 40 var camera var rotation_helper var MOUSE_SENSITIVITY = 0.05func _ready():camera = $Rotation_Helper/Camerarotation_helper = $Rotation_Helper Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)func _physics_process(delta): process_input(delta) process_movement(delta)func process_input(delta): # ---------------------------------- # Walking dir = Vector3()var cam_xform = camera.get_global_transform() var input_movement_vector = Vector2()if Input.is_action_pressed(&amp;quotmovement_forward&amp;quot): input_movement_vector.y += 1if Input.is_action_pressed(&amp;quotmovement_backward&amp;quot): input_movement_vector.y -= 1if Input.is_action_pressed(&amp;quotmovement_left&amp;quot): input_movement_vector.x -= 1if Input.is_action_pressed(&amp;quotmovement_right&amp;quot): input_movement_vector.x += 1 input_movement_vector = input_movement_vector.normalized()# Basis vectors are already normalized. dir += -cam_xform.basis.z * input_movement_vector.y dir += cam_xform.basis.x * input_movement_vector.x # ---------------------------------- # ---------------------------------- # Jumping if is_on_floor():if Input.is_action_just_pressed(&amp;quotmovement_jump&amp;quot):vel.y = JUMP_SPEED # ---------------------------------- # ---------------------------------- # Capturing/Freeing the cursorif Input.is_action_just_pressed(&amp;quotui_cancel&amp;quot):if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)else: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) # ----------------------------------func process_movement(delta): dir.y = 0dir = dir.normalized()vel.y += delta * GRAVITYvar hvel = velhvel.y = 0var target = dirtarget *= MAX_SPEEDvar accel if dir.dot(hvel) &gt; 0:accel = ACCELelse: accel = DEACCELhvel = hvel.linear_interpolate(target, accel * delta)vel.x = hvel.xvel.z = hvel.zvel = move_and_slide(vel, Vector3(0, 1, 0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE)) func _input(event): if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: rotation_helper.rotate_x(deg2rad(event.relative.y * MOUSE_SENSITIVITY)) self.rotate_y(deg2rad(event.relative.x * MOUSE_SENSITIVITY * -1))var camera_rot = rotation_helper.rotation_degreescamera_rot.x = clamp(camera_rot.x, -70, 70) rotation_helper.rotation_degrees = camera_rotاین کد زیادی است ، بنابراین اجازه دهید آن را با تابع تقسیم کنیم:ابتدا ، برخی متغیرهای کلاس را تعریف می کنیم تا نحوه حرکت بازیکن ما در جهان را تعیین کنیم.در طول این آموزش ، متغیرهای تعریف شده در خارج از توابع به عنوان &quot;متغیرهای کلاس&quot; معرفی می شوند. این بدان دلیل است که می توانیم از هر مکان اسکریپت به هر یک از این متغیرها دسترسی پیدا کنیم.بیایید هر یک از متغیرهای کلاس را مرور کنیم:Gravity: چقدر نیروی گرانش ما را به سمت پایین می کشاند. vel: سرعت KinematicBody ما. MAX_SPEED: سریعترین سرعتی که می توانیم بدست آوریم. همین که به این سرعت رسیدیم ، سریعتر پیش نمی رویم. JUMP_SPEED: چقدر می توانیم پرش کنیم. ACCEL: چقدر سریع شتاب می گیریم. هرچه مقدار بالاتر باشد ، ما زودتر به حداکثر سرعت می رسیم. DEACCEL: با چه سرعتی سرعت را کاهش می دهیم. هرچه مقدار بالاتر باشد ، زودتر متوقف خواهیم شد. MAX_SLOPE_ANGLE: شدیدترین زاویه ای که KinematicBody ما به عنوان &quot;کف&quot; در نظر خواهد گرفت.camera: گره دوربین. rotation_helper: گره فضایی که همه چیزهایی را که می خواهیم در محور X بچرخانیم (بالا و پایین) دارد. MOUSE_SENSITIVITY: حساسیت ماوس چقدر است. به نظر من مقداری از 0.05 برای موس من خوب کار می کند ، اما ممکن است لازم باشد آن را براساس حساسیت موشواره(موس) خود تغییر دهید.می توانید بسیاری از این متغیرها را اصلاح کنید تا نتایج متفاوتی بدست آورید. به عنوان مثال ، با کاهش GRAVITY و  یا افزایش JUMP_SPEED می توانید شخصیت احساس &quot;شناور&quot; تری پیدا کنید. خیالتان راحت باشد!شاید متوجه شده باشید که MOUSE_SENSITIVITY مانند سایر ثابتها در همه حروف نوشته شده است ، اما MOUSE_SENSITIVITY یک ثابت نیست. دلیل این امر این است که ما می خواهیم مانند یک متغیر ثابت (متغیری که نمی تواند تغییر کند) در کل اسکریپت با آن رفتار کنیم ، اما می خواهیم بعداً وقتی تنظیمات قابل تنظیم را اضافه می کنیم ، مقدار آن را تغییر دهیم. بنابراین ، برای اینکه به خود یادآوری کنیم که مانند یک ثابت با آن رفتار کنیم ، این نام در همه کلاه ها ذکر شده استحالا بیایید به عملکرد _ready نگاه کنیم: ابتدا گره های دوربین و rotation_helper را بدست آورده و در متغیرهای خود ذخیره می کنیم.سپس باید حالت موس را روی capture تنظیم کنیم ، بنابراین ماوس نمی تواند از پنجره بازی خارج شود.با این کار ماوس پنهان می شود و در مرکز صفحه نگهداری می شود. این کار را به دو دلیل انجام می دهیم: دلیل اول این است که نمی خواهیم بازیکن هنگام پخش نشانگر ماوس خود را ببیند.دلیل دوم این است که ما نمی خواهیم مکان نما از پنجره بازی خارج شود. اگر مکان نما از پنجره بازی خارج شود ، ممکن است مواردی وجود داشته باشد که بازیکن خارج از پنجره کلیک کند ، و سپس تمرکز بازی از بین می رود. برای اطمینان از اینکه هیچ یک از این موارد اتفاق نمی افتد ، مکان نما را پنهان میکنیمبه ( https://docs.godotengine.org/en/stable/classes/class_input.html#class-input ) برای حالت های مختلف ماوس مراجعه کنید. ما فقط در این مجموعه آموزشی از MOUSE_MODE_CAPTURED و MOUSE_MODE_VISIBLE استفاده خواهیم کرد.بعد بیایید نگاهی به physics_process بیندازیم: تمام کاری که ما در physics_process انجام می دهیم فراخوانی دو عملکرد است: process_input و process_movementprocess_input جایی خواهد بود که همه کدهای مربوط به ورودی player را ذخیره می کنیم. ما می خواهیم قبل از هر چیز دیگری آن را صدا کنیم ، بنابراین ورودی جدید player برای کار داریم.process_movement جایی است که ما تمام داده های لازم را به KinematicBody ارسال می کنیم تا بتواند در دنیای بازی حرکت کند.بیایید روند_input بعدی را بررسی کنیم: ابتدا dir را بر روی یک Vector3 خالی تنظیم می کنیم. dir برای ذخیره مسیری که بازیکن قصد دارد به سمت آن حرکت کند استفاده خواهد شد. از آنجا که نمی خواهیم ورودی قبلی player فراتر از یک تماس process_movement باشد ، player را تنظیم می کنیم.در مرحله بعدی ، تغییر شکل جهانی دوربین را دریافت می کنیم و آن را نیز در متغیر cam_xform ذخیره می کنیم. دلیل اینکه ما به تبدیل جهانی دوربین نیاز داریم این است که بتوانیم از بردارهای جهت دار آن استفاده کنیم. بسیاری از کاربران بردارهای جهت دهنده را گیج کننده دانسته اند ، بنابراین بیایید یک ثانیه وقت بگذاریم و نحوه کار آنها را توضیح دهیم:فضای جهان را می توان چنین تعریف کرد: فضایی که همه اشیا در آن قرار می گیرند ، نسبت به یک نقطه مبدا ثابت. هر جسم ، مهم نیست که دو بعدی باشد یا سه بعدی ، در فضای جهان جایگاهی دارد. به بیان دیگر: فضای جهان فضایی در جهان است که موقعیت ، چرخش و مقیاس هر جسم را می توان با یک نقطه ثابت ، شناخته شده و ثابت به نام مبدا اندازه گیری کرد.در گودو ، مبدا در موقعیت (0 ، 0 ، 0) با چرخش (0 ، 0 ، 0) و مقیاس (1 ، 1 ، 1) است.هنگامی که ویرایشگر Godot را باز می کنید و یک گره Spatial based انتخاب می کنید ، یک gizmo ظاهر می شود. هر یک از فلش ها به طور پیش فرض با استفاده از جهت های فضای جهان نشان داده می شوند.اگر می خواهید با استفاده از بردارهای جهت دار فضایی جهان حرکت کنید ، مانند این کار می کنید:if Input.is_action_pressed(&amp;quotmovement_forward&amp;quot): node.translate(Vector3(0, 0, 1))if Input.is_action_pressed(&amp;quotmovement_backward&amp;quot): node.translate(Vector3(0, 0, -1)) if Input.is_action_pressed(&amp;quotmovement_left&amp;quot): node.translate(Vector3(1, 0, 0))if Input.is_action_pressed(&amp;quotmovement_right&amp;quot): node.translate(Vector3(-1, 0, 0))توجه کنید که برای بدست آوردن بردارهای جهت دار فضای جهانی نیازی به انجام هیچ گونه محاسبه ای نداریم. ما می توانیم چند متغیر Vector3 تعریف کنیم و مقادیر نشان داده شده در هر جهت را وارد کنیم.بسیار خوب ، بازگشت به process_input: بعد یک متغیر جدید به نام input_movement_vector درست می کنیم و آن را به یک Vector2 خالی اختصاص می دهیم. ما از این برای ایجاد یک محور مجازی، برای ترسیم ورودی بازیکن به حرکت استفاده خواهیم کرد.این ممکن است فقط برای صفحه کلید بیش از حد به نظر برسد ، اما بعداً وقتی ورودی joypad را اضافه می کنیم ، منطقی خواهد بود.بر اساس اینکه عمل حرکت جهت دار فشرده می شود(Input.is_action_pressed) ، input_movement_vector را اضافه یا از آن کم می کنیم. بعد از اینکه هر یک از اقدامات حرکت جهت دار را بررسی کردیم ، input_movement_vector را عادی می کنیم. این امر باعث می شود که مقادیر input_movement_vector در یک دایره واحد 1 شعاع باشد.بعد ما بردار Z محلی دوربین را برمبنای (input_movement_vector.y) به dir اضافه می کنیم. این امر به این صورت است که هنگام جلو یا عقب رفتن، ما محور Z محلی دوربین را اضافه می کنیم تا playerنسبت به دوربین به جلو یا عقب حرکت کند.از آنجا که دوربین با 180 درجه چرخانده می شود ، ما باید بردار  Z را چرخانیم. به طور معمول جلو محور Z مثبت است ، بنابراین استفاده از basic.z.normalized () کارساز است ، اما ما از -basis.z.normalized () استفاده می کنیم زیرا محور Z دوربین ما در مقایسه با, بازیکن به سمت عقب است.ما برای وکتور X محلی دوربین همین کار را می کنیم و به جای استفاده از input_movement_vector.y در عوض از input_movement_vector.x استفاده می کنیم. این امر باعث می شود جایی که player به چپ / راست فشار می دهد ، نسبت به دوربین حرکت می کند. در مرحله بعدی با استفاده از عملکرد  is_on_floor بررسی می کنیم که آیا بازیکن روی زمین است یا خیر. اگر اینگونه باشد ، بررسی می کنیم که آیا عمل &quot;move_jump&quot; فشرده شده است یا خیر. اگر اینگونه باشد ، سرعت Y بازیکن را روی JUMP_SPEED تنظیم می کنیم.از آنجا که ما در حال تنظیم سرعت Y هستیم ، player به هوا می پرد. سپس عملکرد ui_cancel را بررسی می کنیم. این امر بدین ترتیب است که می توان با فشار دادن دکمه ESCAPE، مکان نما را آزاد و ضبط کرد. ما این کار را انجام می دهیم زیرا در غیر این صورت هیچ راهی برای آزاد کردن مکان نما نداریمبرای آزاد کردن / گرفتن مکان نما ، بررسی می کنیم که آیا ماوس قابل مشاهده است یا خیر. اگر باشد ، آن را ضبط می کنیم و اگر نباشد ، آن را قابل مشاهده می کنیم . این تمام کاری است که ما در حال حاضر برای process_input انجام می دهیم. ما چندین بار به این عملکرد باز خواهیم گشت زیرا پیچیدگی های بیشتری به player خود اضافه می کنیم.حالا بیایید به process_movement نگاه کنیم: ابتدا با صفر قرار دادن مقدار Y آن اطمینان حاصل می کنیم که dir هیچ حرکتی در محور Y ندارد. بعد dir را عادی می کنیم تا اطمینان حاصل کنیم که در یک دایره واحد 1 (شعاع) قرار داریم. صرف نظر از اینکه پخش کننده مستقیم حرکت می کند یا مورب ، این امر باعث می شود که ما با سرعت ثابت حرکت کنیم. اگر ما نرمال نمی شدیم ، بازیکن سریعتر از مورب حرکت می کرد تا وقتی که مستقیم می رود.بعد با اضافه کردن GRAVITY * delta به سرعت Y بازیکن ، گرانش را به بازیکن اضافه می کنیم. پس از آن سرعت player را به یک متغیر جدید (به نام hvel) اختصاص می دهیم و هر حرکتی را در محور Y حذف می کنیم. بعد یک متغیر جدید (target) را برای بردار جهت player تنظیم می کنیم. سپس آن را در حداکثر سرعت پخش ضرب می کنیم ، بنابراین می دانیم که بازیکن در مسیری که توسط dir فراهم می شود ، تا کجا حرکت خواهد کرد.بعد از آن یک متغیر جدید برای شتاب ایجاد می کنیم ، به نام accel. سپس محصول نقطه hvel را می گیریم تا ببینیم آیا player مطابق hvel حرکت می کند یا خیر. به یاد داشته باشید ، hvel هیچ سرعت Y ندارد ، به این معنی که ما فقط در حال حرکت بازیکن به جلو ، عقب ، چپ یا راست هستیم. اگر بازیکن طبق hvel حرکت می کند ، ما accel را روی ACCEL ثابت قرار می دهیم تا بازیکن شتاب بگیرد ، در غیر این صورت accel را روی ثابت DEACCEL قرار می دهیم تا بازیکن کند شود.سپس سرعت افقی را بین یکدیگر قرار می دهیم ، سرعت X و Z بازیکن را روی سرعت افقی تنظیم می کنیم و move_and_slide را فراخوانی می کنیم تا KinematicBody بتواند بازیکن را از طریق دنیای فیزیک حرکت دهد. نکته تمام کدهای موجود در process_movement دقیقاً همان کد حرکت از نسخه ی نمایشی کاراکتر Kinematic هست! عملکرد نهایی ما تابع _input است و خوشبختانه نسبتاً کوتاه است:ابتدا اطمینان حاصل می کنیم که رویدادی که با آن روبرو هستیم یک رویداد InputEventMouseMotion است. ما همچنین می خواهیم مکان یاب را بگیریم یا خیر ، اگر نمی خواهیم چرخش کنیم. اگر رویداد واقعاً یک رویداد حرکت ماوس باشد و مکان نما گرفته شود ، ما بر اساس حرکت نسبی ماوس که توسط InputEventMouseMotion ارائه شده است می چرخیم. ابتدا گره rotation_helper را در محور X می چرخانیم ، با استفاده از مقدار Y حرکت ماوس ، که توسط InputEventMouseMotion ارائه شده است.سپس کل KinematicBody را در محور Y با مقدار X حرکت نسبی ماوس می چرخانیم.در آخر ، چرخش X rotation_helper را گیره می کنیم تا بین -70 و 70 درجه باشد تا بازیکن نتواند خودش را وارونه بچرخاند. برای تست کد ، اگر هنوز صحنه ای با نام Testing_Area.tscn باز نشده است ، آن را باز کنید. با مرور قسمتهای بعدی آموزش ، از این صحنه استفاده خواهیم کرد ، بنابراین حتماً آن را در یکی از برگه های صحنه خود باز نگه دارید.دادن یک چراغ قوه به بازیکن و گزینه دویدن سریع: قبل از شروع کار سلاح ها ، باید چند مورد دیگر اضافه کنیم. بسیاری از بازی های FPS دارای گزینه دوی سرعت و چراغ قوه هستند. ما به راحتی می توانیم این موارد را به بازیکن خود اضافه کنیم ، پس اجازه دهید این کار را انجام دهیم!ابتدا به چند متغیر کلاس دیگر در اسکریپت player خود نیاز داریم:const MAX_SPRINT_SPEED = 30 const SPRINT_ACCEL = 18var is_sprinting = false var flashlightتمام متغیرهای دوی سرعت دقیقاً مشابه متغیرهای غیر دوی سرعت با نام های مشابه کار می کنند. is_sprinting بولینی است برای پیگیری اینکه آیا بازیکن در حال دویدن است یا خیر ، و چراغ قوه متغیری است که ما برای نگه داشتن گره نور فلش پخش کننده از آن استفاده خواهیم کرد. اکنون باید چند خط کد اضافه کنیم ، از _readyشروع می شود. موارد زیر را به _ready اضافه کنید:flashlight = $Rotation_Helper/Flashlightاین گره Flashlight را می گیرد و آن را به متغیر چراغ قوه اختصاص می دهد. حالا باید مقداری از کد را در process_input تغییر دهیم. موارد زیر را در قسمت process_input اضافه کنید:# ---------------------------------- # Sprinting if Input.is_action_pressed(&amp;quotmovement_sprint&amp;quot): is_sprinting = true else: is_sprinting = false # ---------------------------------- # ---------------------------------- # Turning the flashlight on/off if Input.is_action_just_pressed(&amp;quotflashlight&amp;quot): if flashlight.is_visible_in_tree(): flashlight.hide() else: flashlight.show() # ----------------------------------بیایید موارد اضافی را مرور کنیم: ما  is_sprint به true تنظیم میکنیم هنگامی که پخش کننده عمل move_sprint را نگه می دارد ، و false هنگام انتشار action move_sprint نادرست است. در process_movement ما کدی را اضافه می کنیم که باعث می شود player سرعت بیشتری داشته باشد. در اینجا در process_input ما فقط قصد تغییر متغیر is_sprinting را داریم.ما کاری شبیه آزاد کردن / گرفتن مکان نما برای کار با چراغ قوه انجام می دهیم. ابتدا بررسی می کنیم که آیا عمل چراغ قوه فشرده شده است یا خیر. اگر چنین بود ، سپس بررسی می کنیم که آیا چراغ قوه در درخت صحنه قابل مشاهده است. اگر اینگونه باشد ، آنرا پنهان می کنیم و اگر اینگونه نباشد ، آنرا نشان می دهیم. اکنون ما باید در process_movement دو مورد را تغییر دهیم. ابتدا target * = MAX_SPEED را با موارد زیر جایگزین کنید:if is_sprinting: target *= MAX_SPRINT_SPEEDelse: target *= MAX_SPEEDاکنون به جای اینکه همیشه هدف را در MAX_SPEED ضرب کنیم ، ابتدا بررسی می کنیم که آیا player در حال دویدن است یا نه. اگرplayer در حال دویدن است ، ما در عوض هدف را در MAX_SPRINT_SPEED ضرب می کنیم. اکنون تنها چیزی که باقی مانده تغییر شتاب هنگام دوی سرعت است. accel = ACCEL را به موارد زیر تغییر دهید:if is_sprinting: accel = SPRINT_ACCELelse: accel = ACCELاکنون ، هنگامی که بازیکن در حال دویدن سریع است ، ما به جای ACCEL از SPRINT_ACCEL استفاده خواهیم کرد ، که سرعت بازیکن را سریعتر می کند. اکنون اگر Shift را فشار دهید باید بتوانید دوی سرعت بزنید و با فشار دادن F می توانید چراغ فلش را خاموش و روشن کنید! برو امتحان کن شما می توانید متغیرهای کلاس مربوط به سرعت را تغییر دهید تا پخش کننده هنگام دویدن سریعتر یا کندتر شود!اینم پروژه ساخته شده تا اینجای کارhttps://docs.godotengine.org/en/stable/_downloads/6e5beb2c329d95c6efbfd2bd57bd8067/Godot_FPS_Part_1.zipقسمت دومhttps://vrgl.ir/AVt3b</description>
                <category>عباس زنگارکی فراهانی</category>
                <author>عباس زنگارکی فراهانی</author>
                <pubDate>Sun, 04 Oct 2020 12:19:27 +0330</pubDate>
            </item>
            </channel>
</rss>