بخش سوم آموزش یونیتی متوسطه (آموزش اسکریپت نویسی یونیتی)
فهرست مطالب
آخرین به روزرسانی در 16/03/2022
در بخش سوم از آموزش یونیتی متوسطه تحت عنوان آموزش اسکریپت نویسی یونیتی قرار داریم.
در بخش های قبلی از آموزش متوسطه ما به مباحث انیمیشن سازی در یونیتی و فیزیک یونیتی پرداختیم.
لازم به ذکر است ما قبلا در بخش دوم از آموزش یونیتی مقدماتی معرفی کوتاهی در رابطه با اسکریپت نویسی در یونیتی
داشتیم و در این بخش قصد کامل کردن و پرداختن تخصصی تر به این مبحث را داریم.
توجه : آموزش های دوره ی متوسطه مستقل از یکدیگر هستند و این آموزش به غیر از آشنایی به مباحث اولیه ی یونیتی پیش نیاز دیگری را ندارد.
پیش نیاز این دوره ی آموزشی تسلط و درک مفاهیم اولیه ی زبان برنامه نویسی C# می باشد.
مقدمه
همانطور که قبلا گفته شده است و می دانید یکی از بزرگ ترین مزایای یونیتی نسبت به سایر موتورهای بازی سازی
در زبان قدرتمند آن سی شارپ می باشد.
شما با استفاده از این زبان و اسکریپ نویسی در آن می توانید منطق یونیتی را ساخته و بک اند یک بازی را در هر سبک و با هر نوع فیچر هایی پیاده سازی کنید.
جالب است بدانید که یونیتی API های مستندی را در نیز در اختیار کاربران می گذارد که این موارد را حتی برای توسعه دهندگان تازه کار آسان می کند.
در آموزش اسکریپت نویسی یونتی ، شما یک تیرانداز از بالا به پایین میسازید که از اسکریپت Unity برای کنترل زیاد شدن دشمنان، کنترل بازیکن، شلیک گلولهها و دیگر جنبههای مهم گیمپلی استفاده میکند.
شروع کار
برای شروع کار ما از یک پروژه ی آماده استفاده می کنیم.
پروژه ی آماده ی BlockBuster را دانلود کرده، آن را از حالت فشرده خارج کرده و پوشه ایجاد شده را در Unity باز کنید.
چیزی که در یونیتی مشاهده می کنید به شکل زیر می باشد :
در نمای Scene به اطراف نگاه کنید.
یک میدان کوچک وجود دارد که میدان نبرد بازی، دوربین و نور خواهد بود.
اگر چیدمان شما با اسکرین شات متفاوت است، منوی کشویی بالا سمت راست را انتخاب کرده و آن را به 2 در 3 تغییر دهید.
حال بازی ما بدون کارکتر اصلی خود هیچ معنا و مفهومی را ندارد.
پس در قدم بعدی اولین وظیفه شما این است که یک GameObject بسازید تا بازیکن را در صحنه نمایش دهد.
ساخت بازیکن
در Hierarchy روی دکمه Create کلیک کنید و از قسمت3D ، Sphere را انتخاب کنید.
کره را در (X:0، Y:0.5، Z:0) قرار دهید و نام آن را Player بگذارید:
Unity از یک سیستم entity-component برای ساخت GameObjects خود استفاده می کند.
این بدان معناست که همه GameObjects کانتینرهایی برای کامپوننت ها هستند که میتوان آنها را به رفتار و properties آن متصل کرد.
در اینجا چند نمونه از کامپوننت هایی که یونیتی تعبیه کرده است آورده شده است:
Tranform : هر GameObject دارای این کامپوننت است. Position ، rotation و scale ، GameObject را در اختیار دارد.
Box Collider : کلایدری به شکل مکعب که می تواند برای تشخیص برخورد استفاده شود.
Mesh Filter : داده های مش که برای نمایش یک مدل سه بعدی استفاده می شود.
Player GameObject باید به برخورد با آبجکت های دیگر در صحنه پاسخ دهد.
برای انجام این کار، Player را در پنجره Hierarchy انتخاب کنید و روی دکمه Add Component در پنجره Inspector کلیک کنید.
Physics > Rigidbody را در منوی باز شده انتخاب کنید، این یک کامپوننت Rigidbody به Player اضافه می کند تا بتواند از موتور فیزیک Unity استفاده کند.
مقادیر Rigidody را به این صورت تنظیم کنید: Drag را روی ۱، Angular Drag را روی ۰ تنظیم کنید و کادر Y را در کنار Freeze Position علامت بزنید.
این اطمینان حاصل می کند که پخش کننده نمی تواند بالا و پایین حرکت کند و هنگام چرخش هیچ damping اضافه ای ندارد.
ایجاد اسکریپت حرکت بازیکن
اکنون که Player آماده است، زمان ایجاد اسکریپتی است که ورودی را از صفحه کلید دریافت می کند و پلیر را به اطراف حرکت می دهد.
در پنجره Project بر روی دکمه Create کلیک کرده و Folder را انتخاب کنید.
نام پوشه جدید را Scripts بگذارید و یک زیر پوشه در آن به نام Player ایجاد کنید.
در داخل پوشه Player، روی دکمه Create کلیک کنید و C# Script را انتخاب کنید.
نام اسکریپت جدید خود را PlayerMovement بگذارید.
sequence به این صورت است:
نکته : استفاده از پوشههایی از این قبیل سازماندهی همه چیز را بر اساس نقششان آسان میکند و به هم ریختگی و بی نظمی در پروژه را کاهش میدهد.
روی اسکریپت PlayerMovement.cs دوبار کلیک کنید.
با این کار code editor مورد نظر شما با اسکریپت بارگذاری شده باز می شود.
یونیتی با MonoDevelop از پیش نصب شده روی همه پلتفرم ها ارائه می شود و کاربران ویندوز می توانند ویژوال استودیو را نصب کنند و در عوض هنگام اجرای installer از آن استفاده کنند.
ما در این آموزش فرض میکنیم که شما از MonoDevelop استفاده میکنید اما کاربران ویژوال استودیو نیز می توانند بدون هیچ مشکلی از این آموزش و بخش های اسکریپت نویسی آن استفاده کنند.
هنگامی که editor انتخابی شما باز شد، با موارد زیر مواجه خواهید شد:
این کلاس پیشفرض Unity برای اسکریپتهای جدید ایجاد میکند.
این اسکریپت از کلاس پایه MonoBehaviour مشتق شده است که مطمئن می شود این اسکریپت در حلقه بازی اجرا می شود و دارای عملکرد اضافی برای واکنش به event های خاص است.
اگر با دنیای iOS آشنایی دارید، این شی معادل UIViewController است.
با اجرای اسکریپت، یونیتی چندین روش را به ترتیب از پیش تعیین شده فراخوانی می کند.
در اینجا چند مورد از رایج ترین آنها وجود دارد:
() Start : این متد یک بار درست قبل از اینکه اسکریپت اولین به روز رسانی خود را دریافت کند فراخوانی می شود.
() Update : در حالی که بازی در حال اجرا است و اسکریپت فعال است، این روش در هر فریم فعال می شود.
() OnDestroy : این متد درست قبل از اینکه GameObject که این اسکریپت به آن متصل شده است از بین برود فراخوانی می شود.
() OnCollisionEnter : هنگامی که کلایدر یا rigidbody این اسکریپت به کلایدر یا rigidbody دیگر متصل می شود، این متد فراخوانی می شود.
برای لیست کامل رویدادها به Unity’s documentation on MonoBehaviours. مراجعه کنید.
این دو خط را بالای متد () Start اضافه کنید:
public float acceleration;
public float maxSpeed;
نتیجه باید به شکل زیر باشد :
اینها variable declarations عمومی هستند، به این معنی که این متغیرها در Inspector قابل مشاهده خواهند بود و بدون نیاز به رفت و برگشت بین اسکریپت و ویرایشگر می توان آنها را تغییر داد.
شتاب (acceleration ) نشان می دهد که سرعت بازیکن (Player’s) در طول زمان ( maxSpeed ) چقدر افزایش می یابد.
درست در زیر آن، متغیرهای زیر را دنبال کنید:
private Rigidbody rigidBody;
private KeyCode[] inputKeys;
private Vector3[] directionsForKeys;
متغیرهای Private (خصوصی) را نمی توان از طریق Inspector تنظیم کرد، این مسئولیت توسعه دهندگان است که آنها را در زمان مناسب مقداردهی اولیه کنند.
rigidBody یک ارجاع به کامپوننت Rigidbody که به Player GameObject متصل است، خواهد داشت.
inputKeys آرایه ای از keycodes است که برای تشخیص ورودی استفاده می شود.
directionsForKeys آرایه ای از متغیرهای Vector3 را نگه می دارد که داده های directional را نگه می دارد.
متد () Start را با روش زیر جایگزین کنید:
void Start () {
inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right };
rigidBody = GetComponent();
}
این قطعه کد مسیرهای مربوطه را برای هر کلید پیوند می دهد.
به عنوان مثال. با فشار دادن W جسم به جلو حرکت می کند.
خط آخر به مولفه Rigidbody متصل شده اشاره می کند و آن را برای استفاده بعدی در متغیر rigidBody ذخیره می کند.
برای اینکه واقعاً پلیر را جابجا کنید، باید ورودی را از صفحه کلید کنترل کنید.
نام () Update را به () FixedUpdate تغییر دهید و کد زیر را اضافه کنید :
// 1
void FixedUpdate () {
for (int i = 0; i < inputKeys.Length; i++){
var key = inputKeys[i];
// 2
if(Input.GetKey(key)) {
// 3
Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime;
}
}
چند مورد مهم در اینجا وجود دارد:
() FixedUpdate مستقل از frame rate است و باید هنگام کار با Rigidbodies استفاده شود.
این متد به جای اجرای سریعترین زمان ممکن، در یک بازه زمانی ثابت اجرا میشود.
این حلقه بررسی می کند که آیا هر یک از کلیدهای ورودی فشرده شده است یا خیر.
جهت کلید فشرده شده را بدست آورید، آن را در شتاب و تعداد ثانیه هایی که برای تکمیل آخرین فریم طول کشید ضرب کنید.
این یک بردار جهت تولید می کند (سرعت در محورهای X، Y و Z) که از آن برای حرکت دادن آبجکت Player استفاده می کنید.
اگر در برنامه نویسی بازی تازه کار هستید، ممکن است از خود بپرسید که چرا باید در Time.deltaTime ضرب کنید.
در حالی که بازی در حال اجرا است، frame rate بسته به سخت افزاری که دارد متفاوت است، این ممکن است باعث شود عملکردها در سخت افزار های قدرتمند خیلی سریع و در سخت افزار های ضعیف خیلی کند اتفاق بیفتد که می تواند باعث ایجاد مشکل شود.
قاعده کلی این است که وقتی یک عمل را در هر فریم (ثابت) انجام می دهید، باید در Time.deltaTime ضرب کنید.
متد زیر را در زیر FixedUpdate اضافه کنید:
void movePlayer(Vector3 movement) {
if(rigidBody.velocity.magnitude * acceleration > maxSpeed) {
rigidBody.AddForce(movement * -1);
} else {
rigidBody.AddForce(movement);
}
}
روش فوق با اعمال نیرو به ridigbody باعث حرکت آن می شود.
اگر سرعت فعلی از maxSpeed بیشتر شود، نیرو در جهت مخالف می رود تا سرعت پلیر کاهش یابد و به طور موثر حداکثر سرعت را محدود می کند.
در () FixedUpdate، قبل از پرانتز بستن عبارت if، خط زیر را اضافه کنید:
movePlayer(movement);
این اسکریپت را ذخیره کرده و به ویرایشگر Unity برگردید.
در پنجره Project، اسکریپت PlayerMovement را روی Player داخل Hierarchy بکشید.
افزودن یک اسکریپت به GameObject یک نمونه از یک کامپوننت ایجاد می کند، به این معنی که تمام کدها برای GameObjectی که آن را به آن پیوست کرده اید اجرا می شود.
از Inspector برای تنظیم Acceleration روی 625 و Max Speed روی 4375 استفاده کنید:
scene را اجرا کنید و Player را با کلیدهای WASD حرکت دهید:
نتیجه ی کار بسیار مطلوب است.
اما با این حال، یک مشکل واضح وجود دارد ، به اینصورتکه بازیکن می تواند به سرعت از دید خارج شود، که مبارزه با کارکترهای بد داستان را دشوار می کند.
ایجاد اسکریپت دوربین
در پوشه Scripts یک اسکریپت جدید به نام CameraRig ایجاد کنید و آن را به Main Camera (دوربین اصلی) متصل کنید.
حالا متغیرهای زیر را در داخل کلاس CameraRig که تازه ایجاد شده است ، درست بالای متد() Start ایجاد کنید:
public float moveSpeed;
public GameObject target;
private Transform rigTransform;
همانطور که ممکن است حدس بزنید، moveSpeed سرعتی است که دوربین با آن هدف را دنبال می کند ؛ که این هدف می تواند هر آبجکت بازی در داخل scene باشد.
در داخل () Start، خط زیر را اضافه کنید:
rigTransform = this.transform.parent;
این کد یک رفرنس به transform آبجکت دوربین والد در hierarchy صحنه دریافت می کند.
هر آبجکت در یک صحنه دارای یک transform است که position ، rotation و scale یک جسم را توصیف می کند.
در همان اسکریپت، متد زیر را اضافه کنید:
void FixedUpdate () {
if(target == null){
return;
}
rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position,
Time.deltaTime * moveSpeed);
}
کد حرکت CameraRig کمی سادهتر از کد PlayerMovement است.
این به این دلیل است که شما به Rigidbody نیاز ندارید.
به سادگی موقعیت یابی بین موقعیت های rigTransform و هدف کافی است.
() Vector3.Lerp دو نقطه در فضا و یک float در محدوده [0, 1] می گیرد که یک نقطه در امتداد دو نقطه پایانی را توصیف می کند.
نقطه پایانی سمت چپ 0 است و نقطه پایانی سمت راست 1 است.
با ارسال 0.5 به () Lerp یک نقطه دقیقاً بین هر دو نقطه پایانی باز می گردد.
این باعث می شود که rigTransform با کمی کاهش به موقعیت هدف نزدیک شود.
به طور خلاصه باید گفت ، دوربین بازیکن را دنبال می کند.
حال به یونیتی باز میگردیم ؛ در ابتدای کار مطمئن شوید که Main Camera همچنان در hierarchy انتخاب شده است.
در Inspector، Move Speed را روی 8 و Target را روی Player قرار دهید:
بازی را پلی کنید و در صحنه حرکت کنید.
دوربین باید transform هدف را به آرامی هر کجا که می رود دنبال کند.
ساخت دشمن
پیروزی در یک بازی تیراندازی بدون دشمن آسان است، اما بسیار کسل کننده و بدون چالش است.
با کلیک روی GameObject\3D Object\Cube از منوی بالا، یک مکعب دشمن ایجاد کنید.
نام مکعب خود را به Enemy تغییر دهید و یک کامپوننت Rigidbody اضافه کنید.
در Inspector، ابتدا تبدیل مکعب را روی (0,0,5.4) قرار دهید.
در بخش Constraints کامپوننت Rigidbody، تیک Y را در دسته Freeze Position علامت بزنید.
اکنون دشمنان خود را باید هوشمنئد تر کنیم.
یک اسکریپت به نام Enemy در پوشه Scripts ایجاد کنید.
سپس متغیرهای عمومی زیر را در کلاس اضافه کنید:
public float moveSpeed;
public int health;
public int damage;
public Transform targetTransform;
احتمالاً می توانید به سادگی بفهمید که آن متغیرها چه چیزی را نشان می دهند.
شما قبلاً از moveSpeed برای ایجاد ریگ دوربین استفاده کردید و در اینجا نیز همین تأثیر را دارد.
سلامتی (health ) و آسیب (damage ) به تعیین زمان مرگ دشمن و میزان آسیب رساندن به بازیکن کمک می کند.
در نهایت، targetTransform به transform بازیکن اشاره می کند.
وقتی صحبت از Player شد، باید یک کلاس ایجاد کنید تا نشان دهنده تمام هدف های بازیکن باشد که دشمن می خواهد نابود کند.
در Project Browser ، پوشه Player را انتخاب کنید و یک اسکریپت جدید به نام Player ایجاد کنید.
این اسکریپت به برخوردها واکنش نشان می دهد و سلامت بازیکن را کنترل می کند.
برای ویرایش اسکریپت دوبار کلیک کنید.
متغیر عمومی زیر را برای ذخیره سلامت بازیکن اضافه کنید:
public int health = 3;
این یک مقدار پیش فرض برای سلامتی ارائه می دهد، اما می توان آن را در Inspector نیز تغییر داد.
برای مدیریت برخورد، متد های زیر را اضافه کنید:
void collidedWithEnemy(Enemy enemy) {
// Enemy attack code
if(health <= 0) {
// Todo
}
}
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent();
collidedWithEnemy(enemy);
}
() OnCollisionEnter زمانی فعال می شود که دو rigidbodies با کالوژن (برخورددهنده) با هم تماس داشته باشند.
آرگومان Collision حاوی اطلاعاتی در مورد مواردی مانند نقاط تماس و سرعت ضربه است.
در این مورد، شما فقط به مولفه Enemy آبجکت برخورد کننده توجه دارید، بنابراین می توانید() collidedWithEnemy را فراخوانی کنید و منطق حمله را اجرا کنید که در ادامه اضافه خواهید کرد.
void FixedUpdate () {
if(targetTransform != null) {
this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed);
}
}
public void TakeDamage(int damage) {
health -= damage;
if(health <= 0) {
Destroy(this.gameObject);
}
}
public void Attack(Player player) {
player.health -= this.damage;
Destroy(this.gameObject);
}
شما قبلاً با ()FixedUpdate آشنا هستید، تفاوت جزئی این است که شما از () MoveTowards به جای () Lerp استفاده می کنید.
این به این دلیل است که دشمن باید همیشه با سرعت یکسانی حرکت کند و با نزدیک شدن به هدف راحت نباشد.
هنگامی که یک دشمن با projectile (گلوله) مورد اصابت قرار می گیرد، () TakeDamage فراخوانی می شود.
وقتی دشمن به سلامت 0 برسد خودش را نابود می کند.
() Attack مشابه است ؛ به اینصورتکه به بازیکن آسیب وارد می کند و سپس دشمن خود را نابود می کند.
به Player.cs برگردید و در () collidedWithEnemy، کامنت کد حمله Enemy را با عبارت زیر جایگزین کنید:
enemy.Attack(this);
بازیکن آسیب می بیند و دشمن در این فرآیند خود ویران می شود.
به Unity برگردید.
اسکریپت Enemy را به آبجکت Enemy وصل کنید و در Inspector مقادیر زیر را روی Enemy تنظیم کنید:
- Move Speed: 5
- Health: 2
- Damage: 1
- Target Transform: Player
در بازی، برخورد دشمن با بازیکن، یک حمله دشمن شناخته می شود.
تشخیص برخورد با فیزیک یونیتی تقریباً یک کار پیش پا افتاده است.
در نهایت، اسکریپت Player را به Player در Hierarchy متصل کنید.
بازی را اجرا کنید :
هنگامی که دشمن به Player می رسد، حمله را با موفقیت انجام می دهد و متغیر سلامت بازیکن را به 2 کاهش می دهد.
اما یک NullReferenceException در console پرتاب می شود که به اسکریپت Player اشاره می کند:
بازیکن می تواند نه تنها با دشمنان، بلکه با سایر بخش های دنیای بازی مانند Arena برخورد کند.
این آبجکت های بازی اسکریپت Enemy ندارند و بنابراین () GetComponent مقدار NULL را برمیگرداند.
Player.cs را باز کنید.
در () OnCollisionEnter ()، collidedWithEnemy را در یک عبارت if قرار دهید:
if(enemy) {
collidedWithEnemy(enemy);
}
کار با Prefabs
تنها دوری کردن از دشمنان و فرار از آن ها جالب نیست و زمان است که مبارزه ای برای بازی ترتیب بدیم.
روی دکمه Create در Hierarchy کلیک کنید و 3D Object/Capsule را انتخاب کنید.
نام آن را Projectile بگذارید و مقادیر transform زیر را به آن بدهید :
- Position : (0, 0, 0)
- Rotation : (90, 0, 0)
- Scale : (0.075, 0.246, 0.075)
هر بار که بازیکن شلیک می کند، نمونه ای از Projectile (گلوله) را شلیک می کند.
برای تحقق این امر، باید یک Prefab ایجاد کنید.
برخلاف آبجکت هایی که از قبل در صحنه دارید، Prefabs بر حسب درخواست توسط منطق بازی ایجاد می شوند.
یک پوشه جدید تحت Assets به نام Prefabs ایجاد کنید.
حالا آبجکت Projectile را به داخل این پوشه بکشید.
Prefab شما به کمی اسکریپت نویسی نیاز دارد.
یک اسکریپت جدید در پوشه Scripts به نام Projectile ایجاد کنید و متغیرهای کلاس زیر را به آن اضافه کنید:
public float speed;
public int damage;
Vector3 shootDirection;
درست مانند هر جسم متحرکی که تاکنون در این آموزش دیده شده است، این مورد نیز دارای متغیرهای speed (سرعت) و damage (آسیب) است، زیرا بخشی از منطق مبارزه است.
بردار shootDirection تعیین می کند که Projectile (گلوله) به کجا خواهد رفت.
با پیاده سازی متدهای زیر در کلاس، آن بردار را وارد کار کنید:
// 1
void FixedUpdate () {
this.transform.Translate(shootDirection * speed, Space.World);
}
// 2
public void FireProjectile(Ray shootRay) {
this.shootDirection = shootRay.direction;
this.transform.position = shootRay.origin;
}
// 3
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent();
if(enemy) {
enemy.TakeDamage(damage);
}
Destroy(this.gameObject);
}
Projectile متفاوت از هر چیز دیگری در این بازی حرکت می کند.
هدفی ندارد یا نیرویی در طول زمان به آن اعمال می شود. در عوض، برای کل lifecycle خود در جهتی از پیش تعیین شده حرکت می کند.
در اینجا شما موقعیت شروع و جهت Prefab را تعیین می کنید.
این آرگومنت Ray بسیار هوشمندانه به نظر می رسد، اما به زودی نحوه محاسبه آن را خواهید آموخت.
اگر گلوله با دشمن برخورد کند، () TakeDamage را فراخوانی می کند و خود را نابود می کند.
در Hierarchy صحنه، اسکریپت Projectile را به Projectile GameObject متصل کنید.
Speed را روی 0.2 و Damage را روی 1 تنظیم کنید، سپس روی دکمه Apply که در نزدیکی بالای Inspector قرار دارد کلیک کنید.
با این کار، تغییراتی که به تازگی ایجاد کردهاید، در تمام نمونههای این پیش ساخته اعمال میشود.
پرتاب Projectiles (گلوله)
اکنون که prefab دارید که می تواند حرکت کند و آسیب وارد کند، آماده شروع shooting هستید.
در داخل پوشه Player، یک اسکریپت جدید به نام PlayerShooting ایجاد کنید و آن را به Player موجود در صحنه متصل کنید.
در داخل کلاس، متغیرهای زیر را وارد کنید:
public Projectile projectilePrefab;
public LayerMask mask;
اولین متغیر حاوی رفرنس به Projectile Prefab است که قبلا ایجاد کرده اید.
هر بار که بازیکن شما یک گلوله شلیک می کند، یک نمونه جدید از این Prefab ایجاد می کنید.
از متغیر mask برای فیلتر کردن GameObjects استفاده می شود.
مواقعی در بازی شما وجود دارد که باید بدانید collider (برخورددهنده) در جهت خاصی وجود دارد یا خیر.
برای انجام این کار، Unity می تواند یک invisible ray (پرتو نامرئی) را از یک نقطه خاص به سمتی که شما مشخص کرده اید پرتاب کند.
به احتمال زیاد با GameObject های زیادی مواجه خواهید شد که با پرتو تلاقی می کنند، بنابراین استفاده از یک ماسک به شما امکان می دهد هر گونه آبجکت های ناخواسته را فیلتر کنید.
Raycast ها فوق العاده مفید هستند و می توانند برای اهداف مختلفی استفاده شوند.
آنها معمولاً برای آزمایش اینکه آیا بازیکن دیگری مورد اصابت گلوله قرار گرفته است یا خیر استفاده می شوند، اما می توانید از آنها برای بررسی اینکه آیا زیر نشانگر ماوس geometry وجود دارد یا خیر استفاده کنید.
تصویر زیر پرتویی را نشان می دهد که از یک مکعب به یک مخروط می ریزد.
از آنجایی که پرتو یک ماسک نماد کره روی خود دارد، GameObect را نادیده می گیرد و یک ضربه به مخروط را گزارش می دهد:
متد زیر را به PlayerShooting.cs اضافه کنید:
void shoot(RaycastHit hit){
// 1
var projectile = Instantiate(projectilePrefab).GetComponent();
// 2
var pointAboveFloor = hit.point + new Vector3(0, this.transform.position.y, 0);
// 3
var direction = pointAboveFloor - transform.position;
// 4
var shootRay = new Ray(this.transform.position, direction);
Debug.DrawRay(shootRay.origin, shootRay.direction * 100.1f, Color.green, 2);
// 5
Physics.IgnoreCollision(GetComponent(), projectile.GetComponent());
// 6
projectile.FireProjectile(shootRay);
}
کد بالا به این صورت است:
خط1 : یک گلوله Prefab را نمونه سازی می کند و کامپوننت گلوله آن را دریافت می کند تا بتوان آن را مقدار دهی اولیه کرد.
خط2 : این نقطه همیشه شبیه (x، 0.5، z) است.
X و Z مختصاتی روی زمینی هستند که پرتوهای پرتاب شده از موقعیت کلیک ماوس به آنجا برخورد می کند.
این محاسبه مهم است، زیرا گلوله باید موازی با زمین باشد ؛ در غیر این صورت شما شلیک درستی را نخواهید داشت.
خط3 : جهت را از Player GameObject به pointAboveFloor محاسبه می کند.
خط4 : یک پرتو جدید ایجاد می کند که مسیر گلوله را بر اساس مبدا و جهت آن توصیف می کند.
خط5 : این خط به موتور فیزیک یونیتی میگوید که برخورد بین کالوژن (برخورددهنده) Player و برخورد دهنده گلوله را نادیده بگیرد.
در غیر این صورت، () OnCollisionEnter در اسکریپت Projectile قبل از اینکه فرصتی برای پرواز داشته باشد فراخوانی می شود.
خط6 : در نهایت، مسیر گلوله را تعیین می کند.
با وجود منطق شلیک، متدهای زیر را اضافه کنید تا بازیکن ماشه را بکشد :
// 1
void raycastOnMouseClick () {
RaycastHit hit;
Ray rayToFloor = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(rayToFloor.origin, rayToFloor.direction * 100.1f, Color.red, 2);
if(Physics.Raycast(rayToFloor, out hit, 100.0f, mask, QueryTriggerInteraction.Collide)) {
shoot(hit);
}
}
// 2
void Update () {
bool mouseButtonDown = Input.GetMouseButtonDown(0);
if(mouseButtonDown) {
raycastOnMouseClick();
}
این متد یک پرتو از دوربین به نقطه ای که ماوس کلیک کرده است می اندازد.
سپس بررسی می کند که ببیند آیا این پرتو یک آبجکت بازی را با LayerMask داده شده قطع می کند یا خیر.
در هر بهروزرسانی، اسکریپت فشار دادن دکمه سمت چپ ماوس را بررسی میکند.
اگر یکی را پیدا کرد، raycastOnMouseClick را فراخوانی می کند.
به Unity برگردید و متغیرهای زیر را در Inspector تنظیم کنید:
- Projectile Prefab : از پوشه prefab به گلوله اشاره کنید.
- Floor : Mask
توجه : یونیتی دارای مقدار محدودی از لایه های از پیش تعریف شده است که می توانید از آنها ماسک ایجاد کنید.
شما می توانید با کلیک بر روی منوی کشویی لایه GameObject و انتخاب افزودن لایه، لایه خود را ایجاد کنید:
برای اختصاص دادن یک لایه به GameObject، آن را از منوی کشویی Layer انتخاب کنید:
پروژه را اجرا کنید و به شکل دلخواه شلیک کنید، گلوله ها در جهت مورد نظر شلیک می شوند.
اگر گلوله ها در جهت حرکت باشند، بسیار عملکرد شلیک زیباتر می شد.
برای رفع این مشکل، اسکریپت Projectile.cs را باز کرده و متد زیر را اضافه کنید:
void rotateInShootDirection() {
Vector3 newRotation = Vector3.RotateTowards(transform.forward, shootDirection, 0.01f, 0.0f);
transform.rotation = Quaternion.LookRotation(newRotation);
}
در انتهای () FireProjectile، یک رفرنس به () rotateInShootDirection اضافه کنید.
اکنون () FireProjectile باید به شکل زیر باشد:
public void FireProjectile(Ray shootRay) {
this.shootDirection = shootRay.direction;
this.transform.position = shootRay.origin;
rotateInShootDirection();
}
یک بار دیگر بازی را اجرا کنید و در چند جهت مختلف شلیک کنید.
این بار گلوله ها به سمتی که شلیک می شوند اشاره می کنند:
تولید شخصیت های بد بیشتر
داشتن تنها یک دشمن چندان چالش برانگیز نیست.
اما اکنون که در مورد Prefabs میدانید، میتوانید تعداد بیشتری از دشمنانی را که میخواهید ایجاد کنید.
برای اینکه کار پلیر را چالش برانیگز تر کنید میتوانید health (سلامت) ، speed (سرعت) و location (مکان) هر دشمن را تصادفی کنید.
ایجاد یک آبجکت خالی بازی ، GameObject\Create Empty نام آن را EnemyProducer بگذارید و یک کامپوننت Box Collider اضافه کنید.
مقادیر را در Inspector به صورت زیر تنظیم کنید:
- Position:(0, 0, 0)
- Box Collider:
- Is Trigger:true
- Center:(0, 0.5, 0)
- Size:(29, 1, 29)
کلایدری که به آن متصل کرده اید، فضای سه بعدی خاصی را در داخل Arena تعریف می کند.
برای مشاهده این موضوع، Enemy Producer GameObject را در Hierarchy انتخاب کنید و به نمای Scene نگاه کنید:
شما در حال نوشتن یک اسکریپت هستید که یک مکان تصادفی را در این فضا در امتداد محور X و Z انتخاب می کند و یک Prefab دشمن را نمونه سازی می کند.
یک اسکریپت جدید به نام EnemyProducer ایجاد کنید و آن را به EnemyProducer GameObject متصل کنید.
در کلاس تازه راه اندازی شده، اعضای نمونه زیر را اضافه کنید:
public bool shouldSpawn;
public Enemy[] enemyPrefabs;
public float[] moveSpeedRange;
public int[] healthRange;
private Bounds spawnArea;
private GameObject player;
متغیر اول افزایش تعداد را فعال و غیرفعال می کند.
این اسکریپت یک prefab تصادفی دشمن را از enemyPrefabs انتخاب می کند و آن را نمونه سازی می کند.
دو آرایه بعدی مقدار حداقل و حداکثر سرعت و سلامت را مشخص می کنند.
ناحیه افزایش تعداد همان جعبه سبز رنگی است که در نمای Scene دیدید.
در نهایت، شما به یک رفرنس به Player نیاز دارید و آن را به عنوان هدف به افراد بد ارسال کنید.
در داخل اسکریپت، متدهای زیر را تعریف کنید:
public void SpawnEnemies(bool shouldSpawn) {
if(shouldSpawn) {
player = GameObject.FindGameObjectWithTag("Player");
}
this.shouldSpawn = shouldSpawn;
}
void Start () {
spawnArea = this.GetComponent().bounds;
SpawnEnemies(shouldSpawn);
InvokeRepeating("spawnEnemy", 0.5f, 1.0f);
}
() SpawnEnemies یک رفرنس از یک آبجکت بازی با تگ Player دریافت می کند و تعیین می کند که آیا دشمن باید افزایش پیدا کند یا خیر.
() Start ناحیه spawn را مقداردهی اولیه می کند و فراخوانی یک متد را 0.5 ثانیه پس از شروع بازی برنامه ریزی می کند.
علاوه بر اینکه به عنوان یک متد تنظیم کننده عمل می کند، () SpawnEnemies همچنین رفرنس یک آبجکت بازی با تگ Player را دریافت می کند.
آبجکت بازی Player هنوز تگ گذاری نشده است ؛ اکنون این کار را انجام خواهید داد.
آبجکت Player را از Hierarchy انتخاب کنید و سپس در تب Inspector، Player را از منوی کشویی Tag انتخاب کنید:
اکنون باید کد افزایش تعداد یک دشمن را بنویسید.
اسکریپت Enemy را باز کنید و متد زیر را اضافه کنید:
public void Initialize(Transform target, float moveSpeed, int health) {
this.targetTransform = target;
this.moveSpeed = moveSpeed;
this.health = health;
}
این به سادگی به عنوان یک تنظیم کننده برای ایجاد آبجکت عمل می کند.
مرحله بعدی: کدی برای ایجاد دسته های دشمنان شما.
EnemyProducer.cs را باز کنید و متدهای زیر را اضافه کنید:
Vector3 randomSpawnPosition() {
float x = Random.Range(spawnArea.min.x, spawnArea.max.x);
float z = Random.Range(spawnArea.min.z, spawnArea.max.z);
float y = 0.5f;
return new Vector3(x, y, z);
}
void spawnEnemy() {
if(shouldSpawn == false || player == null) {
return;
}
int index = Random.Range(0, enemyPrefabs.Length);
var newEnemy = Instantiate(enemyPrefabs[index], randomSpawnPosition(), Quaternion.identity) as Enemy;
newEnemy.Initialize(player.transform,
Random.Range(moveSpeedRange[0], moveSpeedRange[1]),
Random.Range(healthRange[0], healthRange[1]));
}
تنها کاری که () spawnEnemy انجام می دهد این است که یک prefab تصادفی دشمن را انتخاب می کند، آن را در یک موقعیت تصادفی نمونه سازی می کند و متغیرهای عمومی اسکریپت Enemy را مقداردهی اولیه می کند.
EnemyProducer.cs تقریبا آماده است.
به یونیتی برگردید.
با کشیدن آبجکت Enemy از Hierarchy به پوشه Prefabs، یک Enemy prefab ایجاد کنید.
آبجکت دشمن را از صحنه بردارید ؛دیگر به آن نیاز ندارید.
سپس متغیرهای عمومی اسکریپت Enemy Producer را به شرح زیر تنظیم کنید:
- Should Spawn:True
- Enemy Prefabs:
- Size:1
- Element 0:Reference the enemy prefab
- Move Speed Range:
- Size:2
- Element 0:3
- Element 1:8
- Health Range:
- Size:2
- Element 0:2
- Element 1:6
بازی را اجرا کنید و آن را بررسی کنید.
یک Cylinder و Capsule سه بعدی در صحنه ایجاد کنید.
آنها را به ترتیب Enemy2 و Enemy3 نامگذاری کنید.
همان پروسه ای که قبلاً با دشمن اول انجام دادید را انجام بدهید، یک کامپوننت Rigidbody و اسکریپت Enemy را به هر دوی آنها اضافه کنید.
Enemy2 را انتخاب کنید و پیکربندی آن را در Inspector به این صورت تغییر دهید:
- Scale:(0, 0.5, 0)
- Rigidbody:
- Use Gravity:False
- Freeze Position:Y
- Freeze Rotation:X, Y, Z
- Enemy Component:
- Move Speed:5
- Health:2
- Damage:1
- Target Transform:None
در مرحله بعد، آنها را به Prefabs تبدیل کنید، همان با پروسه ای را که با دشمن اصلی انجام دادید، و به همه آنها در Enemy Producer اشاره کردید.
مقادیر موجود در Inspector باید به شکل زیر باشد:
- Size: 3
- Element 0: Enemy
- Element 1: Enemy2
- Element 2: Enemy3
بازی را پلی کنید؛ prefab های مختلفی را در داخل Arena خواهید دید.
پیاده سازی Game Controller
اکنون که تیراندازی، حرکت و دشمنان را در اختیار دارید، یک کنترلر بازی اولیه را نیاز خواهیم داشت.
به اینصورتکه هنگامی که بازیکن “مرده” بازی را دوباره راه اندازی می کند.
اما ابتدا باید مکانیزمی ایجاد کنید تا به هر طرف اطلاع دهید که بازیکن به سلامت صفر رسیده است.
اسکریپت Player را باز کنید و موارد زیر را در بالای اعلان کلاس اضافه کنید:
using System;
در داخل کلاس event پابلیک کد زیر را اضافه کنید:
public event Action onPlayerDeath;
event یک ویژگی زبان C# است که به شما امکان می دهد تغییرات در آبجکت ها را برای هر listeners پخش کنید.
ویرایش () collidedWithEnemy به شکل کد زیر:
void collidedWithEnemy(Enemy enemy) {
enemy.Attack(this);
if(health <= 0) {
if(onPlayerDeath != null) {
onPlayerDeath(this);
}
}
رویدادها متدی منظم برای آبجکت ها فراهم می کنند تا تغییرات حالت را بین خود سیگنال دهند.
یک کنترلر بازی به رویداد اعلام شده در بالا بسیار توجه دارد.
در پوشه Scripts یک اسکریپت جدید به نام GameController ایجاد کنید.
برای ویرایش فایل دوبار کلیک کنید و متغیرهای زیر را به آن اضافه کنید:
public EnemyProducer enemyProducer;
public GameObject playerPrefab;
اسکریپت باید تا حدودی بر تولید دشمن کنترل داشته باشد، زیرا پس از از بین رفتن بازیکن، ایجاد دشمنان منطقی نیست.
همچنین، راه اندازی مجدد بازی به این معنی است که شما باید Player را دوباره بسازید.
متد های زیر را اضافه کنید:
void Start () {
var player = GameObject.FindGameObjectWithTag("Player").GetComponent();
player.onPlayerDeath += onPlayerDeath;
}
void onPlayerDeath(Player player) {
enemyProducer.SpawnEnemies(false);
Destroy(player.gameObject);
Invoke("restartGame", 3);
}
در () Start، اسکریپت به اسکریپت Player اشاره می کند و برای رویدادی که قبلا ایجاد کرده اید مشترک می شود.
هنگامی که سلامت بازیکن به 0 رسید () onPlayerDeath فراخوانی می شود، تولید دشمن متوقف می شود، آبجکت Player از صحنه حذف می شود و متد () restartGame پس از 3 ثانیه فراخوانی می شود.
در نهایت، اجرای restart game action را اضافه کنید:
void restartGame() {
var enemies = GameObject.FindGameObjectsWithTag("Enemy");
foreach (var enemy in enemies)
{
Destroy(enemy);
}
var playerObject = Instantiate(playerPrefab, new Vector3(0, 0.5f, 0), Quaternion.identity) as GameObject;
var cameraRig = Camera.main.GetComponent();
cameraRig.target = playerObject;
enemyProducer.SpawnEnemies(true);
playerObject.GetComponent().onPlayerDeath += onPlayerDeath;
}
در اینجا شما کمی مرتب سازی می کنید : تمام دشمنان را در صحنه نابود می کنید و یک شی Player جدید ایجاد می کنید.
سپس هدف ریگ دوربین را مجدداً به این نمونه اختصاص میدهید، تولید دشمن را از سر میگیرید و کنترلر بازی را در رویداد مرگ بازیکن مشترک میکنید.
حالا به Unity برگردید، پوشه Prefabs را باز کنید و تگ تمام Enemy prefabs را به Enemy تغییر دهید.
سپس، آبجکت بازی Player را با کشیدن آن به پوشه Prefabs به یک Prefab تبدیل کنید.
یک آبجکت خالی از بازی ایجاد کنید، نام آن را GameController بگذارید و اسکریپتی را که ایجاد کرده اید ضمیمه کنید.
تمام رفرنس های مورد نیاز را در Inspector متصل کنید.
مهرشاد شادان مهر
مدرس سئو ، طراح سایت ، انیماتور
قهرمان زندگی شما در چند سال آینده ی شما می باشد
Everything is very open with a precise clarification of the challenges. It was really informative. Your website is useful. Thanks for sharing!