السبت، 20 ديسمبر 2014

الدرس 1 أسس برمجة الألعاب (جافا) - حلقة اللعبة [ Game Loop ]

مرحبا بك في مدونة صُنَاع الألعاب، سأحاول في أول درس من سلسلة الدروس التي سنقدمها لكم عبر هذه المدونة شرح أساسيات برمجة الألعاب الإلكترونية.
المكتسبات الازمة لمتابعة الدرس :
  • أساسيات البرمجة بلغة الجافا
  • برنامج إيكليبس (Eclipse) يمكن تحميله من هذا الرابط
  • 30 دقيقة من وقتك :)

ماهي الألعاب الإلكترونية؟
ببساطة هي عبارة عن برنامج إلكتروني عادي كباقي البرامج الأخرى مع فرق بسيط جدا، المتعة والترفيه الذي تقدمه لمستخدميها.

كيف أصنع لعبة جيدة؟
لصناعة لعبة جيدة يجب أولا إيجاد فكرة وقصة متميزة. فصناعة الألعاب هي تقريبا كصناعة الأفلام بل أصعب من ذلك. لأن الألعاب تجعل من المستخدم محور الإهتمام وتعطيه القدرة على إتخاذ القرار على عكس الأفلام حيث نكون مشاهدين خارجيين.
إذن أولا الفكرة و تميزها :).

ثانيا تصميم المستويات، وهو دور مصمم العبة (Game designer) وهي خطوة مهمة جدا لنجاح اللعبة.

ثالثا الغرافكس أو رسوميات اللعبة  وهو دور فنان الغرافيست (Graphic artist)، كلما زاد جمالها كلما زادت اللعبة جمالا.

رابعا الصوتيات المستخدمة في اللعبة، يجب أن تلائم القصة ،الرسوم وكذلك الموضوع الأساسي للعبة.

خامسا وأخيرا مدى مشاركة اللاعب في العبة، فكلما زادت مشاركته في القرارات المتخدة في اللعبة كلما زاد حبه لها.

ومحبوا برمجة الألعاب المستقلون يبدؤون بتطوير كل هذه المهارات لإنشاء ألعابهم، ففقط الشركات قادرة على جمع فريق عمل إحترافي حيث يقوم كل فرد بإتمام جزء من اللعبة. من التصميم مرورا بالرسم والبرمجة إلى تأليف الصوتيات.
لقد لقِيَتْ العديد من الألعاب المصممة من قبل مستقلين نجاحا باهرا لتميزها. أنصحكم بالبحث عنها. وهذا مقطع من فلم وثائقي جميل يتحدث عن بعظ من هؤلاء الأشخاص (Inde game the movie trailer)  لاكننا لانجد للأسف الكثير منها في العالم العربي رغم كثرة المبرمجين.

هل تريد صناعة لعبة جيدة؟ فلننطلق من الصفر.


إن اللعبة هي عبارة عن حلقة لامتناهية  (Game Loop) تتكرر طول مدة إشتغال اللعبة وتقوم بتحديث معلومات الكائنات (Objects) المكونة لها كالموقع على الشاشة، الوقت، الطاقة... ثم تقوم برسمها على الشاشة وذللك في مجال زمني محدد وهنا نتكلم عن عدد القطات في الثانية (FPS : Frames per second) وهو عدد اللقطات التي تقوم حلقة اللعبة بتحديثها و رسمها في الثانية.
صورة توضيحية لحلقة اللعبة

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

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

في بداية  عالم السينما كان عدد اللقطات يتراوح بين 16 إلى 18 لقطة في الثانية، اليوم ومع تطور التكنلوجيا معيار PAL في أوروبا هو 25 لقطة في الثانية  وفي أمريكا واسيا معيارNTSC هو 30 لقطة في الثانية. 
في مجال الألعاب هذا المعيار يتعلق بالأجهزة المستعملة لإظهار الصورة ويتراوح بين 30 إلى 60  لقطة في الثانية. وذللك لضمان سلاسة اللعبة.

الان أصبحت تعلم أساس برمجة أي لعبة. تحميل الموارد، ثم تكرار :  تحديث المعلومات و الرسم.
كيف يمكننا برمجة كل هذا؟ الأمر سهل جدا.
  // قيمة منطقية تتحكم في إستمرار حلقة اللعبة
  boolean running= true;
  load(); // وظيفة تقوم بتحميل موارد اللعبة
  while(running){
    update(); // وظيفة تقوم بتحديث خاصيات ومكونات اللعبة
    draw(); // وظيفة تقوم برسم مكونات اللعبة بعد التحديث
  }

الاَن وبعد ما أصبح لدينا الهيكل الأساسي للعبة، كيف يممكننا جعلها تشتغل بنظام القطات في الثانية FPS.
لنجعل العبة تشتغل ب 60 لقطة في الثانية. إذن FPS = 60
كم يلزمنا من الوقت لإظهار لقطة واحدة : targetTime = 1000 / FPS  (ألف هو عدد أجزاء الثانية)
إذن سنجعل البرنامج يشغل وظيفتي التحديث و الرسم ثم نقوم بتوقيفه حتى تكتمل المدة targetTime ثم نعيد تشغيله هكذا نضمن عدد القطات المطلوبة في الثانية.
    private int FPS = 60;
    private long targetTime = 1000 / FPS;
    while (running) {
      //نسجل توقيت البداية
      start = System.currentTimeMillis();
      // نشغل وظيفتي التحديث و الرسم
      update();
      draw();
      // نقوم بحسابة مدة إشتغال الوظيفتين 
      elapsed = System.currentTimeMillis() - start;
      // نقوم بحسابة مدة التوقف
      wait = targetTime - elapsed / 1000;

      try {
       if (wait > 0) {
         Thread.sleep(wait); // نوقف البرنامج
       }
      } catch (Exception e) {}
    }

لنقم برسم شيء ما على الشاشة. لكن قبل ذللك يجب علينا أولا تحضير مساحة الرسم.
سنقوم بتحظير نافذة جيفريم JFrame وهي عبارة عن النافذة التي ستحمل مساحة الرسم في لغة الجافا.

ولأجل ذلك فلنبدئ بكتابت الكلاس الرئيسي الذي سيشغل البرنامج.
public class Game {

    public static void main(String[] args) {
       // عنوان نافذة الرسم
       JFrame window = new JFrame("أول لعبة"); 
       window.add(new GamePanel());  // الكلاس الرئيسي المحدد لحلقة العبة

       // لإغلاق النافذة عند الضغط على علامة إكس
       window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       window.setResizable(false); // تعطيل إمكانية تغيير حجم النافذة
       window.pack(); // تحديد حجم النافذة ليتوافق مع حجم مكوناته
       
       // تحديد مكان إظهار النافذة وسط الشاشة
       window.setLocationRelativeTo(null);
       window.setVisible(true); // إظهار النافذة
    }

}

الأن لننشئ مساحة الرسم والتي ستحتوي كذلك حلقة اللعبة ووظائف التحميل، التحديث و الرسم كوظائف أساسية.
مساحة الرسم ستكون عبارة عن خيط (Thread) مستقل  لتشغيل الحلقة.
public class GamePanel extends JPanel implements Runnable {
     
     //حجم مساحة الرسم
     public static final int WIDTH = 320;
     public static final int HEIGHT = 240;

     // لون للرسم 
     private Color color;

     //منشئ الكلاس
     public GamePanel() {
        super();
        //حجم مساحة الرسم
        setPreferredSize(new Dimension(WIDTH, HEIGHT)); 
        setFocusable(true); // تفعيل التركيز على المساحة
        requestFocus();// طلب التركيز
     }

    private void load(){} // وظيفة التحميل
    private void update(){} // وظيفة التحديث
    private void draw(){} // وظيفة الرسم
   
    // (Thread)  هذه الوظيفة تقوم بالإشتغال عند بداية الخيط
    private void run(){

        load();

        long start;
        long elapsed;
        long wait;

        while(running){
            //نسجل توقيت البداية
            start = System.currentTimeMillis();
            // نشغل وظيفتي التحديث و الرسم
            update();
            draw();
            // نقوم بحسابة مدة إشتغال الوظيفتين
            elapsed = System.currentTimeMillis() - start;
            // نقوم بحسابة مدة التوقف
           wait = targetTime - elapsed / 1000;
           try {
           if (wait > 0) {
              Thread.sleep(wait); // نوقف البرنامج
           }
           } catch (Exception e) {}
        }
    }
}

الا لنقم برسم ألوان عشوائية تتغير بعد كل لقطة على الشاشة و ذللك بإكمال الوظائف المخصصة لذلك.
  • وظيفة التحميل
سنحتاج في هذا المثال إلى لون الذي سنجعله أزرق في البداية وسنغيره في وظيفة التحديث،  كما سنقوم بتهييء الخاصية
 running = true

private void load() {
 this.color = Color.blue;
 running = true;
}
  • وظيفة التحديث
  هنا سنقوم بتغير اللون عشوائيا عبر إنشاء ثلاثة أرقام عشوائية التي ستكون الون 
R : الأحمر
G : الأخضر
B : الأزرق

وظيفة ()rndInt تقوم فقط بإنشاء رقمنا العشوائي المحصور بين 0 و 256 عدد الألوان الممكنة. 

private void update() {
 int r = rndInt(0, 256);
 int b = rndInt(0, 256);
 int g = rndInt(0, 256);
 this.color = new Color(r, g, b);
}

private int rndInt(int i, int maxDimension) {
 return i + (int) (Math.random() * (maxDimension - i));
}

  • وظيفة الرسم
 هنا سنقوم برسم مستطيل بحجم الشاشة وملون بالون الذي أنشأناه في وظيفة التحديث. الأمر في غاية البساطة
private void draw() {
 Graphics g2 = getGraphics();
 g2.setColor(color);
 g2.fillRect(0, 0, WIDTH, HEIGHT);
 g2.dispose();
}
سنضيف اخر وظيفة والتي ستمكننا من تشغيل البرنامج
    // خيط العبة
    private Thread thread;

    public void addNotify() {
      super.addNotify();
      if (thread == null) {
            thread = new Thread(this); // نقوم بتهيء الخيط
            // ثم نقوم بتشغيله مما سيشغل الوظيفة
            // run()
            // التي تحتوي بدورها على حلقة اللعبة
            thread.start();   
       }
    }

الكود الكامل للمثال :

 
public class GamePanel extends JPanel implements Runnable {

    //حجم مساحة الرسم
    public static final int WIDTH = 320;
    public static final int HEIGHT = 240;


    private Thread thread;
    private boolean running;
    private int FPS = 60;
    private long targetTime = 1000 / FPS;

    // لون للرسم
    private Color color;

     //منشئ الكلاس
     public GamePanel() {
        super();
        //حجم مساحة الرسم
        setPreferredSize(new Dimension(WIDTH, HEIGHT)); 
        setFocusable(true); // تفعيل التركيز على المساحة
        requestFocus();// طلب التركيز
     }

    public void addNotify() {
        super.addNotify();
        if (thread == null) {
            thread = new Thread(this); // نقوم بتهيء الخيط     
            // ثم نقوم بتشغيله مما سيشغل الوظيفة            
            // run()
            // التي تحتوي بدورها على حلقة اللعبة
            thread.start();
         }
    }

    private void load() {
      this.color = Color.blue;
      running = true;
    }

    private void update() {
      int r = rndInt(0, 256);
      int b = rndInt(0, 256);
      int g = rndInt(0, 256);
      this.color = new Color(r, g, b);
    }

    private void draw() {
      Graphics g2 = getGraphics();
      g2.setColor(color);
      g2.fillRect(0, 0, WIDTH, HEIGHT);
      g2.dispose();
    }

    // (Thread)  هذه الوظيفة تقوم بالإشتغال عند بداية الخيط
    private void run(){

        load();

        long start;
        long elapsed;
        long wait;

        while(running){
            //نسجل توقيت البداية
            start = System.currentTimeMillis();
            // نشغل وظيفتي التحديث و الرسم
            update();
            draw();
            // نقوم بحسابة مدة إشتغال الوظيفتين
            elapsed = System.currentTimeMillis() - start;
            // نقوم بحسابة مدة التوقف
           wait = targetTime - elapsed / 1000;
           try {
           if (wait > 0) {
              Thread.sleep(wait); // نوقف البرنامج
           }
           } catch (Exception e) {}
        }
      }
   }

    private int rndInt(int i, int maxDimension) {
      return i + (int) (Math.random() * (maxDimension - i));
    }
}


هنا نتيجة إشتغال البرنامج الألوان تتغير عشوائيا في كل لقطة.


يمكنك تحميل المشروع وكذلك النتيجة الكاملة من هنا :

أتمنا أن تكونوا قد إستفدتم من هذا الدرس وإلى القاء في تتمة سلسلة دروس أسس برمجة الألعاب بالجافا.

  


ليست هناك تعليقات:

إرسال تعليق

من نحن

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

راسلنا

الاسم

بريد إلكتروني *

رسالة *