Thursday, October 7, 2010

XNA: Lesson8: Game Audio - اصوات اللعبة

Lesson 8: Game Audio
الدرس الثامن: استخدام الاصوات في اللعبة



بسم الله و الصلاة و السلام علي رسول الله ... صلي الله عليه و سلم...

اليوم ان شاء الله نتحدث عن كيفية اضافة الاصوات و المؤثرات الصوتية علي اللعبة ...

و قد قامت ميكروسوفت باضافة برنامج ضمن مجموعة برامج ال XNA يقوم بتحويل صيغة ملفات الصوت الي ملفات يفهمها ال XNA حتي تستطيع التعامل معها داخل اللعبة ... هذا البرنامج اسمه XACT ...

بداية اخواني يجب ان تركزوا تماما في هذا الدرس لانه ليس برمجة بقدر ما هو تعلم طريقة اضافة الصوت للمشروع ...

قبل التعرف علي البرنامج نحمل ملف الصوت علي الرابط التالي:

ثم ننشأ مجلد فارغ في اي مكان علي الجهاز و نضع فيه هذا الملف ...

و نبدا الان بالتعرف علي هذا البرنامج ...

-          لبدء برنامج XACT:
-          Start è Programs è Mircosoft XNA Game Studio 3.0 è Tools è Cross-Platform Audio Creation Tool (XACT).
-          سيفتح لك برنامج XACT بالصورة التالية:


-          نبدا مشروع جديد من خلال القائمة:
-          File è New Project
-          ستفتح لك نافذة لتحديد مكان حفظ المشروع ... يجب ان تكون نفس المجلد الذي تم تكوينه سابقا الذي به ملف الصوت.  (هذه النقطة هامة جدا جدا).
-          بعد تكوين المشروع ستظهر قائمة علي الشمال بالشكل التالي:


-          يجب الان ان نكون Wave Bank و Sound Bank و ذلك لادخال ملفات الصوت الخاصة بنا عليهم ...
-          كما ترون بالصورة نضغط كليك يمين علي Wave Banks ثم نختار New Wave Bank و نترك نفس الاسم الافتراضي Wabe Bank  ...
-          ظهرت لنا الان شاشة جديدة داخل البرنامج عنوانها Wave Bank بالشكل التالي:


في الشاشة التي ظهرت Wave Bank نضغط كليك يمين ثم نختار Insert Wave File لكي ندخل ملفات الصوت الخاصة بنا علي البرنامج...

ثم نختر ملف الصوت من ملف المشروع ...

نلاحظ ان الملف ظهر داخل مربع ال Wave Bank ...

ثم نضغط كليك يمين علي ال Sound Bank و نختر New Sound Bank و نترك نفس الاسم الافتراضي Sound Bank ...

ستظهر لنا شاشة مثل ال Wave Bank  اسمها Sound Bank ... و لكن ...
سنجد شاشة ال Sound Bank  مقسمة الي 4 اقسام ... يوجد قسمين بالشمال اسمهم Sound Name و Cue Name...

الان ننظم شكل الشاشات المعروضة كالتالي ... من قائمة:
Window è Tile Horizontally

الان الشاشات معروضة امامنا و مترتبة ...

كل ما سنعمله بمنتهي البساطة هو اختيار ملف الصوت من القائمة Wave Bank و نعمل سحب و القاء Drag and Drop  داخل شاشة ال Sound Bank  في قسم ال Cue Name ... و سنجد ان البرنامج كون لنا تلقائيا في قسم ال Sound Name   ملف بنفس الاسم ... بحيث يصبح شكل الشاشات كالتالي:



ثم نحفظ المشروع و نغلق البرنامج ...



استخدام الصوت داخل اللعبة:
الان نفتح المشروع الاخير الخاص بالدرس السابع ... و في ال Solution Explorer نضغط كليك يمين علي فولدر ال Content و نضيف فولدر جديد نسميه Audio  ...

ثم نضغط كليك يمين علي فولدر ال Audio  و نختر Addè Existing Item

ثم نفتح الفولدر الخاص بمشروع XACT الذي قمنا بعمله ... سنلاحظ ان هناك ملف تم انشاءه ينتهي ب .XAP
نختاره و نختر ملف الصوت الذي حملناه ثم نضغط Add ...

الان قد اضفنا ملف الصوت و اضفنا ملف المشروع الذي ينتهي ب .XAP

و الان نفتح ملف ال Game1.CS

نعرف المتغيرات التالية بعد هذا السطر مباشرة

SpriteBatch spriteBatch;

و هي متغيرات سوف نستخدمها لبرمجة الصوت ...
AudioEngine audioEngine;
WaveBank waveBank;
SoundBank soundBank;

نشرح الكود:
AudioEngine audioEngine;
ال AudiiEngine  هي مكتبة توصلك بخدمات الصوت علي كارت الصوت في جهازك ...

WaveBank waveBank;
هو مخزن يحمل soundBanks

SoundBank soundBank;
هو مخزن يحمل عدة ملفات صوتية ...


الان نحمل مخازن الصوت ... نتجه الي الدالة LoadContent و نكتب الكود التالي في نهاية الدالة :

audioEngine = new AudioEngine(@"Content\Audio\mysounds.xgs");
waveBank = new WaveBank(audioEngine, @"Content\Audio\Wave Bank.xwb");
soundBank = new SoundBank(audioEngine, @"Content\Audio\Sound Bank.xsb");

طبعا ناخذ بالنا من شئ مهم جدا ... نحن اضفنا ملف ينتهي ب .XAP و هنا نضيف ملفات تاخذ مسارات مختلفة و الحقيقة ان هذه الملفات كلها يتضمنها الملف .XAP و في وقت ال Compile يتم تكوينها بواسطة ال XNA حتي في وقت التشغيل تجدها في الفولدرات المشار اليها ... و لكي تشاهد هذه الملفات اذهب الي المسار التالي ...
Project Folder \ bin\x86\Debug\Content\Audio


الان نحن حملنا الملف الاساسي لمشروع XNA  ... ثم حملنا ال Wave Bank الذي قمنا بعمله في المشروع و سميناه بنفس الاسم الافتراضي ... و ايضا حملنا ال Sound Bank الذي حملناه في المشروع و سميناه بنفس الاسم الافتراضي ...


و الان في نفس الدالة LoadContent سنجعل الصوت يبدا مع بداية اللعبة كالاتي:
soundBank.PlayCue("skullcollision");

ثم نشغل اللعبة و نلاحظ ظهور الصوت ...


تطبيق اليوم ...
اجعل الصوت يعمل عند اصطدام الكائن بحروف الشاشة ....

Monday, October 4, 2010

XNA Lesson 7: Circle Collision Detection and Game Input

Lesson 7: Circle Collision Detection and Game Input
الدرس السابع: اكتشاف التصادم الدائري و التعامل مع لوحة المفاتيح و الماوس للتحكم باللعبة


بسم الله و الصلاة و السلام عليكم رسول الله ....

اكتشاف التصادم الدائري:
عندما قمنا باكتشاف التصادم بين الكائنات فان الكمبيوتر لا زال يري الصورة علي انها مربع و بالتالي كانت التصادمات ليست دقيقة بعض الشئ ... لذلك سنقوم ببرمجة اللعبة الان للتكتشف التصادم بين الكائنات الدائرية مثل الكرة في حالتنا هنا ....

و بالتالي نتجه الي الملف clsSprite و نعرف الخصائص التالية ضمن ال properties:

public Vector2 size { get; set; }
public Vector2 center { get { return position + (size / 2); } }
public float radius { get { return size.X / 2; } }

public Vector2 size { get; set; }
يمثل مقاس الصورة ... طولها و عرضها ...

public Vector2 center { get { return position + (size / 2); } }
يمثل منتصف الصورة بالضبط و يحسب بمكان الصورة و طبعا هذا يكون في اقصي شمال اعلي الصورة ثم يضيف اليه المقاس مقسوم علي 2 مثلا
مكان الصورة = 0,0 ........ و مقاس الصورة = 40,40 ....... اذن منتصف الصورة يساوي ...
Center = (0, 0) + ((40, 40) /2) = (0, 0) + (20, 20) = (20, 20)

اذن بالفعل منتصف الصورة هو x=20 و y=20 .....



public float radius { get { return size.X / 2; } }
يمثل نصف قطر الصورة الدائرية و يحسب طبعا بعرض الصورة مقسوم علي 2 ... يعني لو عندنا صورة قطرها 40 بكسل اذن نصف قطرها هو 20 بكسل ...

ثم نذهب الي ال Consrtuctor و نعدله ليصبح كالاتي:
   public clsSprite(Texture2D newTexture, Vector2 newPosition, int screenWidth, int screenHeight)
        {
            texture = newTexture;
            position = newPosition;
            screenSize = new Vector2(screenWidth, screenHeight);
            size = new Vector2(texture.Width, texture.Height);
        }

و هنا قمنا بملء خاصية ال size بطول و عرض الصورة ليصبح التعامل معهم اسهل من خلال ال size....

و الان ننشا دالة جديدة تحت دالة ال collide بالضبط كالاتي:
public bool circleCollides(clsSprite otherSprite)
{
return (Vector2.Distance(this.center, otherSprite.center) <
                this.radius + otherSprite.radius);
}

و هنا قمنا بمقارنة المسافة بين نقطتين باستخدام Vector2.Distance و هي تحسب المسافة بين نقطتين ... فحسبنا المسافة بين منتصف هذه الصورة و منتصف الصورة الاخري بحيث اذا كانت المسافة بينهما اصغر من نصف قطر الصورة الاولي + نصف قطر الصورة التالية اذن يحدث التصادم ....

مش فاهمين ... طيب صلوا علي النبي (اللهم صلي علي سيدنا محمد و علي اله و صحبه و سلم) ....

هذه الصورة ستوضح لنا كل ما نريد معرفته ان شاء الله :::




اظن الصورة شرحت كل شئ ....

الان نذهب الي الملف Game1.cs و نعدل الحدث update بحيث نستخدم الدالة circleCollides بدلا من الدالة Collide ... و نشغل اللعبة بحيث تحتوي علي كائنين فقط و نلاحظ اكتشاف التصادم ....




الان دعونا ننتقل لموضوع درس اليوم و هو التعامل مع لوحة المفاتيح و الماوس او ما يسمي مدخلات اللعبة ...

مدخلات اللعبة قد تكون Game Pad او ذراع الالعاب ... او لوحة المفاتيح ... او الماوس ... و في مشروع ناتال الذي قامت به ميكروسوفت الان يمكنك اللعب بذاتك يعني تقف امام الشاشة و تلعب بجسمك و جهاز ال xbox يلقط حركاتك و ينفذها باللعبة ......

و الان تعالوا نحرك الكرات علي الشاشة بواسطة الاسهم بدلا من الحركة التلقائية لها ....

اولا نفتح المشروع السابق و الذي يحتوي علي الكرات المتحركة ...
ثانيا: نحذف كل الكائنات و نترك كائن واحد علي اللعبة ...

و الان دعونا نعرف زر لاغلاق اللعبة ...
فنذهب الي الحدث update و نعدله بحيث يكون هكذا:
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            // TODO: Add your update logic here
            ball1.Move();

            base.Update(gameTime);
        }

ما الذي قمنا بفعله هنا:
طبعا اللعبة في البداية تفحص ذراع الالعاب اذا كان اللاعب يضغط علي زر Back فانها تغلق اللعبة و بما اننا لسنا نلعب بذارع العاب فاننا نستخدم لوحة المفاتيح لنفحص اذا كان اللاعب يضغط علي زر Escape و دعونا نفصل قليلا هنا ...
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

Keyboard هنا هي تمثل نسخة من لوحة المفاتيح الان ...
GetState() هي دالة ترجع بحالة كل مفتاح من لوحة المفاتيح ...
IsKeyDown تفحص اذا كان زر معين مضغوط عليه ...
Keys.Escape ........ keys هي اسماء المفاتيح جميعا و طبعا هنا نحن نفحص زر escape....


نشغل اللعبة و نضغط علي زر Escape لنغلق اللعبة ....

الان دعونا نحرك الكرة بالاسهم بدلا من تحركها تلقائيا ... فنذهب الي الحدث Update لنجعله كالاتي:
  protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            KeyboardState keyState = Keyboard.GetState();

            if (keyState.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            // TODO: Add your update logic here
            if (keyState.IsKeyDown(Keys.Up))
            {
                ball1.position += new Vector2(0, -5);
            }
            if (keyState.IsKeyDown(Keys.Down))
            {
                ball1.position += new Vector2(0, 5);
            }
            if (keyState.IsKeyDown(Keys.Left))
            {
                ball1.position += new Vector2(-5, 0);
            }
            if (keyState.IsKeyDown(Keys.Right))
            {
                ball1.position += new Vector2(5, 0);
            }

            base.Update(gameTime);
        }

هنا قمنا بحذف الدالة move بحيث لا يتحرك الكائن بمفرده و قمنا بفحص الزر المضغوط عليه الان و نحرك الكائن علي حسب الاتجاه اللي ضغط عليه بسرعة 5 بكسل ...

دعونا نشغل اللعبة الان ... نري الكائن ثابت لا يتحرك ... نبدا نحركه بالاسهم و نري .............




الان قمنا بتحريك العنصر بواسطة لوحة المفاتيح ...

دعونا الان نحرك العنصر بواسطة الماوس:
نذهب الي الحدث Update و نعدله ليصبح كالاتي:
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            KeyboardState keyState = Keyboard.GetState();

            if (keyState.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            // move the object with keyboard
            if (keyState.IsKeyDown(Keys.Up))
            {
                ball1.position += new Vector2(0, -5);
            }
            if (keyState.IsKeyDown(Keys.Down))
            {
                ball1.position += new Vector2(0, 5);
            }
            if (keyState.IsKeyDown(Keys.Left))
            {
                ball1.position += new Vector2(-5, 0);
            }
            if (keyState.IsKeyDown(Keys.Right))
            {
                ball1.position += new Vector2(5, 0);
            }


            // move the object with mouse
            if (Mouse.GetState().LeftButton == ButtonState.Pressed)
            {
                ball1.position = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
            }

            base.Update(gameTime);
        }


ما الذي فعلناه في هذا الكود:
            if (Mouse.GetState().LeftButton == ButtonState.Pressed)
            {
                ball1.position = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
            }

اولا فحصنا حالة الكليك الشمال بالماوس اذا كانت مضغوطة جعلنا الكائن يتبع حركة الماوس دائما ...


بهذا نكون انتهينا من الدرس السابع و الخاص بتحريك العناصر الدائرية و التحكم بادخالات اللعبة ...

تطبيق الدرس:
زود سرعة الكرة عند التحرك بالاسهم ..................



Sunday, October 3, 2010

XNA: Lesson6: اكتشاف التصادم بين كائنات اللعبة

الدرس السادس: اكتشاف التصادم بين كائنين
Coding for collision detection between two objects


بسم الله و الصلاة و السلام علي رسول الله ... اللهم لا سهل الا ما جعلته سهلا و انت تجعل الحزن اذا شئت سهلا ...


تعلمنا في الدرس السابق كيف نكتشف تصادم الكائن بحدود شاشة اللعبة بحيث نقرر اذا كان سيرجع مرة اخري الي اللعبة ام مسموح باختفائه ...

في هذا الدرس ان شاء الله سوف نتعلم كيف نكتشف تصادم كائنين في اللعبة ببعضها البعض ... طيب و ما فائدة هذا ؟؟؟

استطيع ان اقول لكم ان هذا يمثل لب اللعبة نفسها يعني بها نعرف مثلا كيف تصطدم طائرة بالجبل مثلا داخل اللعبة او نعرف اذا اصاب الرمح العدو فاماته ام لا ... و نعرف بها مثلا اذا كانت الكرة دخلت المرمي ام لا تخيلوا لعبة كرة بلا اهداف J ...

هناك خوارزميات كثيرة لاكتشاف التصادم منها المعقد و منها البسيط ...

المعقد مثلا انك تطابق كل بكسل في الصورة بالبكسل المقابل لها بالصورة المقابلة و هذا ليس مناسب و يحدث في حدود بعيدة و للحصول علي الدقة العالية فيه يتطلب اجهزة عالية المستوي جدا .....

و منها البسيط الذي سنستخدمه هنا و هو خوارزم او اسلوب الصندوق او "Bounding Boxes" و هو مطابقة اطار الصورة الاولي باطار الصورة الثانية فاذا تداخلوا فهذا يعني تصادم مثلا الصورة التالية:


ماذا نلاحظ ؟؟؟
قسمنا الصور الي مربعان كبيران ليغطوا كل الصورة ... و بالتالي معنا الان طائرتان نريد ان نكتشف التصادم الخاص بهم ... بمنتهي البساطة اذا تلاقت حدود الطائرة الاولي مع حدود الطائرة الثانية فهذا يعني تصادم ...






-          الان دعونا نطبق مثال علي التصادم:
o       اولا سنقوم بتحميل الصورة التالية و التي ستكون صورة الكائن الذي سنحركه من الرابط التالي:
o       http://lh3.ggpht.com/_60yQUD53BPE/TKfftPYXVgI/AAAAAAAAA0k/KZoc6ed01BE/ball.pngافتح تطبيق الدرس الخامس (الأخير).
o       اضف الصورة التي قمت بتحميلها الي الفولدر Content كما تعلمنا مسبقا ...
o       افتح الكلاس clsSprite.
o       و سنضيف دالة اكتشاف التصادم بين كائنين و هي ستكون كالتالي:


        public bool Collide(clsSprite otherSprite)
        {
            Rectangle thisRec = new Rectangle((int)this.position.X, (int)this.position.Y,
                this.texture.Width, this.texture.Height);

            Rectangle otherSpriteRec = new Rectangle((int)otherSprite.position.X, (int)otherSprite.position.Y,
                otherSprite.texture.Width, otherSprite.texture.Height);

            if (otherSpriteRec.Intersects(thisRec))
            {
                return true;
            }

            return false;
        }



الدالة اسمها Collide يعني تصادم  و ترجع بنعم او لا يعني اذا حدث تصادم ترجع ب true و اذا لم يحدث تصادم ترجع ب false ...

الدالة تستقبل parameter من نوع clsSprite و هو الكائن الاخر الذي سنفحص تصادمه مع هذا الكائن ...

كما ذكرنا مسبقا نحن سنستخدم الجوريزم اكتشاف التصادم بين الكائنين باستخدام اسلوب الصندوق المحيط بالصورة و بالتالي يجب ان نحدد الصندوق او المربع حول كل صورة كالتالي:

Rectangle thisRec = new Rectangle((int)this.position.X, (int)this.position.Y,
                this.texture.Width, this.texture.Height);
قمنا بتعريف مربع يحتوي علي ابعاد الصورة الاولي ...

Rectangle otherSpriteRec = new Rectangle((int)otherSprite.position.X, (int)otherSprite.position.Y,
                otherSprite.texture.Width, otherSprite.texture.Height);
قمنا بتعريف مربع يحتوي علي ابعاد الصورة الثانية...

if (otherSpriteRec.Intersects(thisRec))
{
            return true;
}

return false;
قمنا باستخدام دالة Intersects و الموجودة ضمن العنصر Rectangle و هي تقوم باكتشاف اذا كان هناك تداخل بين مربعين و قمنا باعطاء الدالة مربع الصورة الاخري لكي تطابق بينه و بين مربع الصورة الاولي .... فاذا كانت ابعادهم متداخلة ستعطي true و اذا كانت ابعادهم غير متداخلة ستعطي false...

و بهذا انتهت دالة اكتشاف التصادم و هذه الدالة باسلوب تطابق المربعات او ال Bounding Boxes .... و هذا الاسلوب ليس دقيق مائة في المائة لان هناك ابعاد دائرية مثل حالة الكرة هنا لكنها حتي الان تفي بالغرض و سنتعلم طرق اكثر تعقيدا فيما بعد ...




  • دعونا الان ننتقل الي الملف Game1.CS:
    • نعرف كائنين clsSprite كما بالاتي:
clsSprite sprite1;
clsSprite sprite2;


    • نعدل ابعاد الشاشة في ال Constructor Game1 كالاتي:
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 900;
            graphics.PreferredBackBufferHeight = 600;
        }


    • ثم نذهب الي حدث ال LoadContent و نحمل الكائنات:
            sprite1 = new clsSprite(Content.Load<Texture2D>("ball"),
                    Vector2.Zero,
                    graphics.PreferredBackBufferWidth,
                    graphics.PreferredBackBufferHeight);

            sprite1.velocity = new Vector2(2, 2);

            sprite2 = new clsSprite(Content.Load<Texture2D>("ball"),
                new Vector2(300, 300),
                graphics.PreferredBackBufferWidth,
                graphics.PreferredBackBufferHeight);

            sprite2.velocity = new Vector2(3, 3);

و ذلك كما تعلمنا مسبقا طبعا .... قمنا بتعريف كائنين في موضعين مختلفين ... و بسرعات مختلفة ...



ثم نذهب الان الي الحدث Update و نعدله ليصبح كالاتي:
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
            sprite1.Move();
            sprite2.Move();

            if (sprite1.Collide(sprite2))
            {
                Vector2 tempVelocity = sprite1.velocity;
                sprite1.velocity = sprite2.velocity;
                sprite2.velocity = tempVelocity;
            }

            base.Update(gameTime);
        }


هنا قمنا بتحريك العنصرين ثم اضفنا كود اكتشاف التصادم حيث نفذنا الدالة collide و اعطيناها الكائن الاخر لكي تفحص التصادم فاذا حدث التصادم نعكس السرعات بحيث ياخذ كل كائن سرعة الكائن الاخر و يمشي عكسه .............





  • تطبيق الدرس:
قم بانشاء كائن ثالث في اللعبة و اكتب كود التصادم بحيث لا يصطدم الثلاث كائنات ببعضهم البعض ...
رابط حل التطبيق:
http://www.mediafire.com/?l8hg68gl4dpnzzq

  • ملاحظة:
نلاحظ ان الصورة التي نستخدمها هي صورة كرة و لكن اكتشاف التصادم لا يتم بالدقة الكافية بحيث يتعامل مع الصورة علي انها مربع و بالتالي في بعض الاحيان يكتشف التصادم في اماكن تكون بعيدة عن الكرة و لهذا حل ان شاء الله و هو اكتشاف التصادم بين الكائنات المدورة مثل الكرة مثلا و ان شاء الله في الدرس القادم سنتعلم كيف نطبقه ...............