الشبكة العربية لمطوري الألعاب

محترف  انس مشاركة 1

السلام عليكم.

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

Frame Rate
Frame Skipe
Frame Counter

ها هو الكود الذي وضعته و الذي لا يعمل اطلاقا كما يجب :


#ifdef __cplusplus
    #include 
#else
    #include 
#endif

#include "SDL.h"

#include "inc/cPlayer.h"
#include "inc/cEvent.h"
#include "inc/InfoDebug.h"
#include "inc/cTimer.h"
#include "inc/cFps.h"


int main ( int argc, char** argv )
{


    // initialize SDL video
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        printf( "Unable to init SDL: %s\n", SDL_GetError() );
        return 1;
    }

    // make sure SDL cleans up before exit
    atexit(SDL_Quit);

    // create a new window
    SDL_Surface* screen = SDL_SetVideoMode(640, 480, 16,
                                           SDL_HWSURFACE|SDL_DOUBLEBUF);
    if ( !screen )
    {
        printf("Unable to set 640x480 video: %s\n", SDL_GetError());
        return 1;
    }

    cPlayer p1;
    p1.GetScreenPointer(screen);
    p1.m_sprite.load_image("img/Character Boy.png");
    cEvent GameEvent;

    InfoDebug text;
    cTimer time;
    cFps fps;

/////////////////////////////////////////////////
/////////////////////////////////////////////////
    fps.SetFrameRate(60);
    while ( GameEvent.IsQuit()!= true)
    {

        fps.Start();
        GameEvent.Update();
        p1.move(GameEvent.KeyState());

        // DRAWING STARTS HERE

        // clear screen
        SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 200, 200, 200));

        // draw bitmap
        p1.blit();

        text.show_Var(fps.GetTicks(),"fps.GetTicks",screen,200,250);
        text.show_Var(fps.GetFrameNumber(),"fps.GetFrameNumber",screen,200,280);

        // Update FPS
        fps.Update();

        // finally, update the screen☺
        SDL_Flip(screen);
    } // end main loop


    // all is well ;)
    printf("Exited cleanly\n");
    return 0;
}
كود الـ Fps كلاس :


#ifndef CFPS_H
#define CFPS_H

#include 
#include "inc/cTimer.h"


class cFps
{
    public:
        cFps();
       ~cFps();
    void SetFrameRate(int frame_rate);
    void Start();
    void Update();
    inline int GetFrameRate(){return m_frame_rate;}
    inline int GetFrameNumber(){return m_frame_number;}
    inline int GetTicks(){ return m_fps.GetTicks(); }

    protected:
    cTimer m_fps;
    int m_frame_rate;
    int m_frame_number;
    int m_last_ticks;

    private:
};

#endif // CFPS_H


#include "inc/cFps.h"

cFps::cFps()
{
   m_frame_rate =0;
   m_frame_number=0;
}

cFps::~cFps()
{
    //dtor
}

void cFps::SetFrameRate( int frame_rate){
    m_frame_rate = frame_rate;
    }

void cFps::Start(){
    m_fps.Start();
    }

void cFps::Update(){

    m_frame_number++;


    if( (m_fps.GetStartingTime()-m_last_ticks) < (1000/m_frame_rate) ){
        SDL_Delay( (1000/m_frame_rate)- m_fps.GetTicks() );      
        }
    m_last_ticks = m_fps.GetStartingTime();

    }

كود الـ Timer :

#ifndef CTIMER_H
#define CTIMER_H

#include "SDL.h"

class cTimer
{
    public:
        cTimer();
        virtual ~cTimer();
        int GetTicks();
        void Start();
        void Pause();
        void UnPause();
        void Stop();
        inline bool IsStarted(){return m_started;}
        inline bool IsPaused(){return m_paused;}
        inline int  GetStartingTime(){return m_ticks_when_started;}

    protected:
    int m_ticks_when_paused;
    int m_ticks_when_started;

    bool m_started;
    bool m_paused;

    private:
};

#endif // CTIMER_H



#include "inc/cTimer.h"

cTimer::cTimer()
{
      m_ticks_when_paused = 0;

      m_started = false;
      m_paused  = false;

}

cTimer::~cTimer(){
    //dtor
}

int cTimer::GetTicks(){


    if(m_started == true){

        if( m_paused == true){
            return m_ticks_when_paused;
            }

        return ( SDL_GetTicks()-m_ticks_when_started );
        }
    // Timer isn't started yet !
    return 0;
    }
void cTimer::Start(){

    m_started = true;
    m_paused  = false;

    m_ticks_when_started = SDL_GetTicks();
    }

void cTimer::Pause(){
    if( (m_started==true)&&( m_paused==false ) ){

         m_paused = true;
         m_ticks_when_paused = (SDL_GetTicks()-m_ticks_when_started) ;

         }
    }      
void cTimer::UnPause(){
    if( ( m_paused == true) ) {

        m_paused= false;
        m_ticks_when_started = SDL_GetTicks()-m_ticks_when_paused;
        m_ticks_when_paused  = 0;

        }
    }
void cTimer::Stop(){
    m_started = false;
    m_paused  = false;

    }

اتمنى ان اجد المساعدة، فعلا لقد اختلطت المفاهيم التي كنت اظن انني اتمكن منها.

سلام

خبير مدير وسام البهنسي مشاركة 2

بتاريخ 29/رمضان/1431 03:49 م، قطب انس حاجبيه بشدة وهو يقول:

هل من الممكن توضيح هذه المفاهيم :

Frame Rate
Frame Skipe
Frame Counter

هذا ما يحدث عندما نتعامل مع مصطلحات بلغة أجنبية. أنت تسأل عن مسألتين مستقلتين. الأولى هي عداد الأداء، والثانية هي مؤقت اللعبة.
 
مؤقت اللعبة هو أداة تستخدمها لضبط سرعة تحديث اللعبة، بحيث تحافظ على 60 أو 30 لقطة في الثانية بشكل ثابت.
 
عداد الأداء يستخدم لقياس الزمن المستغرق في حساب لقطة واحدة من اللعبة، أو حتى أي إجراء فيها. لقد قمنا بتنفيذ عداد أداء في لعبة عبر السدم، ويمكنك الاطلاع على الكود للاستفادة منه واستيعاب طريقة عمله. انظر الموضوع:
 
http://www.agdn-online.com/communities.aspx?view=posts&threadid=812
 
 
أما لتوقيت اللعبة فانظر الموضوع:
 
http://www.agdn-online.com/communities.aspx?view=posts&threadid=391
 
 
تدعم SDL مؤقتاً عالي الدقة، لكنه على الأغلب مفصول. يجب أن ترفع دقة المؤقت إن أردت استخدامه كعداد أداء. أما لتوقيت اللعبة، فقد تكتفي بمؤقت بدقة ميللي ثانية واحدة.
 
معدل رسم اللقطات (frame rate) يقدم لك عدد اللقطات التي تم رسمها في الثانية الواحدة. بعض الألعاب تفضل تثبيت هذا المعدل عند 30 أو 60 (كما نفعل في عبر السدم وفعلنا في وادي الملوك وأسطول الحرية)، والبعض الآخر يفضل إجبار الجهاز على رسم أعلى عدد من اللقطات. الحالة الثانية تضع عبئاً أكبر على المبرمج، لأن حسابات التحديث يجب أن تكون قادرة على التعامل مع تحديثات بأزمنة غير ثابتة، وبالتالي القيم التي تتعلق بالزمن كالسرعة والتسارع ستحتاج لأن تعاير وفقاً لذلك. هذا الأسلوب عموماً تجده في الألعاب ذات المشاهد المعقدة، والتي يصعب ضبطها لتعمل في سرعة 30 أو 60 لقطة ثابتة، وإنما يُسمح لها بالنزول دون ذلك أحياناً.
 
بما أننا نتحدث عن مصطلحات، فالحالة الأولى تدعى (معدل لقطات مُـثبـَّت) (locked frame rate)، أما الثانية فتدعى (معدل لقطات متغيـّر)  (variable frame rate).
 
إسقاط اللقطات (frame skip أو frame drop) هو أسلوب يلجأ إليه المبرمج في حالات انحدار أداء اللعبة. فلو أصبح الزمن المستغرق لحساب لقطة واحدة أكثر من 16.666 ميللي ثانية واللعبة مثبتة للعمل على 60 لقطة في الثانية، فهذا الأسلوب ينص على الامتناع عن رسم اللقطة الحالية، والاكتفاء بعملية التحديث فقط. هكذا نوفر على المعالج حسابات الرسم، ونحافظ على ثبات سرعة اللعبة.  إن لم تفعل ذلك، فإن اللعبة ستبطأ سرعتها المنطقية مع انحدار الأداء، فيصبح اللاعب يشعر وكأن الشخصية مثلاً أبطأ أو أن الزمن كله قد تباطأ. هذه مشكلة كارثية في الألعاب الجماعية طبعاً.
 
 
أخيراً، عداد اللقطات (frame counter) بوجهة النظر الشائعة هو مقياس أداء، فكلما ارتفع كلما كانت اللعبة أسرع على الجهاز. فنقول مثلاً: اللعبة س تعمل بسرعة 70 لقطة في الثانية، واللعبة ص تعمل بسرعة 80 لقطة في الثانية. أي أن ص أفضل من س.  وبالمثل يقول أحدهم:  كانت لعبتي تعمل بسرعة 55 لقطة في الثانية، فعندما أدخلت تأثير الانعكاسات، انخفض الأداء إلى 12 لقطة في الثانية فقط. أي أن الانعكاسات تستهلك 55-12 = 43 لقطة في الثانية.
 
ما سبق يعتبر واحد من أكثر الأخطاء شيوعاً بين المبرمجين، حتى المحترفين منهم. ولعلي أكتب تدوينة عن هذا الموضوع إن شاء الله. لكن حالياً، أنصحك بعدم استخدام هذا العداد، وإنما استخدم عداد أداء كما في عبر السدم.

وسام البهنسي
مبرمج في إنفيديا وإنفريمز

محترف  انس مشاركة 3

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

-كيف نعرف الزمن اللازم الذي سنوقف به البرنامج (delay) ؟

خبير مدير وسام البهنسي مشاركة 4

في 30/رمضان/1431 12:53 م، قال انس بهدوء وتؤدة:

-كيف نعرف الزمن اللازم الذي سنوقف به البرنامج (delay) ؟

فلنفرض لعبة تعمل بمعدل 60 لقطة في الثانية. أي أن كل لقطة لها 16.66 ميلي ثانية كي تتم حساباتها.
 
1- اللقطة الأولى تبدأ في الزمن 0 ميلي ثانية... (لنفرض أنها استغرقت 5 ميلي ثانية في الواقع، لكننا لا نعلم ذلك بعد)...
 
2- نقيس الزمن الحالي، نجد أنه 5 ميلي ثانية... لكن اللقطة السابقة بدأت في 0، يعني أن هذه اللقطة يجب أن تبدأ في 16.66. ونحن الآن عند 5، لذلك فلنهدر بعض الوقت ريثما يحين الزمن المناسب...
 
3- ننام بعض الوقت (ميلي ثانية واحدة مثلاً) ثم نكشف عن الزمن.. أصبح 6... فلننم ثانية... 7... وهكذا...
 
4- أصبح الزمن 17! تأخرنا بعض الشيء، لكن لا مشكلة... فلنحسب اللقطة الثانية فوراً.... (استغرقت 6 ميلي ثانية هذه المرة)...
 
5- نكشف الزمن... أصبح 23... لكن اللقطة الثالثة تبدأ عند 2*16.66 = 33.32 ميلي ثانية. مرة أخرى نهدر بعض الوقت...
 
وهكذا...
 
هذه هي الخوارزمية العامة. على أرض الواقع، لا تبدأ لعبتك في الزمن 0، وإنما عند القيمة التي يعطيك إياها نظام التشغيل (تساوي الزمن الذي مرّ منذ إقلاع الجهاز). لذلك نعتبر تلك القيمة هي مبدأ الزمن للعبة بدلاً من الصفر.
 
وهكذا تجد أن حساب مقدار التأخير سهل. هو ببساطة الزمن الحالي مطروحاً من (زمن البداية + رقم اللقطة * الزمن المتاح لكل لقطة).
 
إجراء النوم في ويندوز اسمه ()Sleep، ويأخذ قيمة زمنية تحدد كم ميلي ثانية تريد أن تنام. لا تستخدم أكثر من 1، فالإجراء مصمم لينام _ على الأقل _ الزمن الذي تحدده أنت، ولن يلتزم بالضرورة بنفس الزمن المحدد.

وسام البهنسي
مبرمج في إنفيديا وإنفريمز