Win32 - Questions about Settimer/ TimeSetEvent

Started by
18 comments, last by Airbatz 4 years, 2 months ago

I plan to do some sprite animation, and obviously it will require timing of some sort for the frames and movement of the sprites. I am aware of two methods to perform timing, though I'm sure there are others. I have seen it stated many times that Settimer is not the most accurate implementation, if not difficult to work with. I was wondering if it is possible/ advised to implement two seperate Settimer events to control both frame speed and movement speed respectively.

My program will not have more than about 8 instances of a 28-frame sprite on-screen at any given time. With this in mind, is there really any advantage to using a multimedia timer? And by extension, what would be an example of a program that would be better off with a multimedia timer?

Below is a basic multimedia timer example I found on the net. It doesn't do anything special, but maybe it can be of some use to people who are looking for one. This is my C version with some changes. Where would I put the code if using this method? I suppose that it isn't handled in WM_TIMER?

#include <windows.h>
#include <stdio.h>

void CALLBACK TimeProc ( UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 )
    {
      printf("waiting\n");
    }

 int main( void )
    {
        UINT uDelay = 1000; //period between each occurance of an event
        UINT uResolution = 1; //precision
        DWORD dwUser = NULL; // User instance data
        UINT fuEvent = TIME_PERIODIC; //You also choose TIME_ONESHOT;

        MMRESULT FTimerID;
        timeBeginPeriod( 1 );
        FTimerID = timeSetEvent(uDelay, uResolution, TimeProc, dwUser, fuEvent);
        if(FTimerID==NULL){printf("Failed to generate multimedia timer.\n");}

        Sleep( 10000 ); //how long the timer runs for
        timeKillEvent( FTimerID );
        timeEndPeriod( 1 );
        return 0;
    }

None

Advertisement

Well for one it looks to be deprecated for `CreateTimerQueueTimer`. In either case, you don't need a window message loop (`GetMessage` / `PeekMessage`) to use it which might be useful for some applications, as it uses it's own thread, which can also be useful over some other constructs (the main thread can be busy/blocked) but does mean dealing with all the multi-thread safety stuff.

While any message based timer executes in the message pump/window thread synchronously, which avoids multi-threading, but if your event was say some network stuff that takes 5 seconds, the window is “locked up” during that time (although you could of course then spawn a thread explicitly, or use events to get async behaviour).

Games and applications probably have a window message loop to use and in most cases would be desirable to use the same thread as the window. For example if any other windows are to be created/destroyed/updated, redrawn, etc.

In the case of games specifically, you are probably looping constantly anyway, and games don't generally worry about a very low idle CPU (if every say browser tab or other application used say 1% CPU just to exist looping around, that would be a problem). In that case a lot of timing is just done by the “game loop”. This could be in many forms, depending on if you have fixed or variable step update, if the thing you are using a fixed step, is it one frame per step (e.g. 30 or 60fps animation) or less than that (e.g. 10fps animation, but 60Hz loop).

Hello, sorry for the late reply. I saw that it was deprecated, but wanted to try the Multimedia Timer if there was any advantage to do doing so. My program currently uses SetTimer() which I've determined to work just fine for my needs. I managed to solve the problem for now by using some switch…case statements in WM_TIMER to allow for both timers to work in parallel.

I'm pretty much doing everything using a bunch of functions in WM_TIMER and WM_PAINT. For the loop I am using a fixed step update with a frame per step, if I understood you correctly. Here is the code to my program in it's current iteration. Excuse the mess :P, It is just for testing, and there are other problems with it. If there is anything that is blatantly wrong or could be done smarter, please advise!

#define WIN32_LEAN_AND_MEAN
#define TIMER_VELOCITY 1
#define TIMER_ANIMATE  2

#include <windows.h>
#include <stdlib.h>
#include "resource.h"

HICON hMyIcon;
HBITMAP graphics[2];
HBITMAP g_hbmMask = NULL;
HDC hdcBuffer;
HBITMAP hbmBuffer;

typedef struct SpriteAnimation
{
  int xpos; 
  int ypos; 
  int frames; 
  int x_velocity; 
  int counter; 
  int direction; 

}SAKURA;
 SAKURA g_spriteInfo;

HBITMAP CreateBitmapMask( HBITMAP hbmColour, COLORREF crTransparent )
{
  HDC hdcMem, hdcMem2;
  HBITMAP hbmMask;
  BITMAP bm;

  GetObject( hbmColour, sizeof( BITMAP ), &bm );
  hbmMask = CreateBitmap( bm.bmWidth, bm.bmHeight, 1, 1, NULL );

  hdcMem = CreateCompatibleDC( 0 );
  hdcMem2 = CreateCompatibleDC( 0 );

  SelectObject( hdcMem, hbmColour );
  SelectObject( hdcMem2, hbmMask );

  SetBkColor( hdcMem, crTransparent );

  BitBlt( hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY );
  BitBlt( hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT );

  DeleteDC( hdcMem );
  DeleteDC( hdcMem2 );

  return hbmMask;
}

void DrawScene( HDC hdc, RECT* prc )
{
  HDC hdcBuffer         = CreateCompatibleDC( hdc );
  HBITMAP hbmBuffer     = CreateCompatibleBitmap( hdc, prc->right, prc->bottom );
  HBITMAP hbmOldBuffer  = (HBITMAP)SelectObject( hdcBuffer, hbmBuffer );
  HDC hdcMem            = CreateCompatibleDC( hdc );

  BitBlt(hdcBuffer, g_spriteInfo.xpos, g_spriteInfo.ypos, 15, 15, hdcMem, 15*(g_spriteInfo.frames%28), 0, SRCAND); //the sprite

  SelectObject(hdcMem,graphics[1]);
  BitBlt(hdcBuffer, g_spriteInfo.xpos, g_spriteInfo.ypos, 15, 15, hdcMem, 15*(g_spriteInfo.frames%28), 0, SRCPAINT);

  SelectObject(hdcMem,graphics[1]);
  BitBlt( hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY );

  SelectObject( hdcMem, hbmBuffer );
  DeleteDC( hdcMem );

  SelectObject( hdcBuffer, hbmOldBuffer );
  DeleteDC( hdcBuffer );
  DeleteObject( hbmBuffer );
}

void AnimateSprite( RECT* prc )
{
g_spriteInfo.frames+=TIMER_ANIMATE;
}

void UpdateSpritePosition( RECT* prc )
{
  g_spriteInfo.ypos += TIMER_VELOCITY; 
  g_spriteInfo.counter++;

		if(g_spriteInfo.counter == 12)
		{
			if(g_spriteInfo.direction == 0 )
                {
                g_spriteInfo.x_velocity = 2; 
                g_spriteInfo.direction = 1; 
                }

			else if (g_spriteInfo.direction == 1) 
                {
                g_spriteInfo.x_velocity = -2; 
                g_spriteInfo.direction = 0; 
			}
			g_spriteInfo.counter = 0; 
		}
		g_spriteInfo.xpos += g_spriteInfo.x_velocity;

if ( g_spriteInfo.ypos > 290 ) //when the sprite goes beyond the bottom of the window 
  {
     g_spriteInfo.ypos = -7; //re-set the position of the sprite to be above the top of the window
     g_spriteInfo.xpos = abs(rand()*405/RAND_MAX); //randomize the x position of the sprite
  }
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
  switch ( msg )
  {
    case WM_CREATE:
      {
        BITMAP bm;
        SelectObject(hdcBuffer, hbmBuffer);
        graphics[1] = LoadBitmap( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDB_SAKR ) );
        graphics[0] = CreateBitmapMask( graphics[1], RGB( 255, 0, 255 ) );

        GetObject( graphics, sizeof( bm ), &bm );

        ZeroMemory( &g_spriteInfo, sizeof( g_spriteInfo ) );
        g_spriteInfo.xpos = abs(rand()*405/RAND_MAX+40); //randomize starting x position
        g_spriteInfo.direction = 0;

        SetTimer( hwnd, TIMER_VELOCITY, 40, NULL );
        SetTimer( hwnd, TIMER_ANIMATE, 200, NULL );
      }
      return 0;

    case WM_COMMAND:
      return 0;

    case WM_CLOSE:
      DestroyWindow( hwnd );
      return 0;

    case WM_PAINT:
      {
        RECT rcClient;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint( hwnd, &ps );
        GetClientRect( hwnd, &rcClient );

        DrawScene( hdc, &rcClient );

        EndPaint( hwnd, &ps );
      }
      return 0;

    case WM_TIMER:
      {
        RECT rcClient;
        GetClientRect( hwnd, &rcClient );

    switch(wParam)
      {
      case TIMER_VELOCITY:
        UpdateSpritePosition( &rcClient );
        break;

      case TIMER_ANIMATE:
        AnimateSprite( &rcClient );
        break;
      }
        InvalidateRect( hwnd, NULL, FALSE );
      }
      return 0;

    case WM_DESTROY:
      KillTimer( hwnd, TIMER_VELOCITY );
      KillTimer( hwnd, TIMER_ANIMATE );
      DeleteObject( graphics );
      DeleteObject( g_hbmMask );

      PostQuitMessage( 0 );
      return 0;
  }
  return DefWindowProc( hwnd, msg, wParam, lParam );
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  hMyIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON ) );
  WNDCLASSEX wc = {0};
  HWND hwnd;
  MSG Msg;

  wc.cbSize = sizeof( WNDCLASSEX );
  wc.style = 0;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = hMyIcon;
  wc.hIconSm = NULL;
  wc.hCursor = LoadCursor( NULL, IDC_ARROW );
  wc.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1 );
  wc.lpszMenuName = NULL;
  wc.lpszClassName = TEXT( "Buttons" );

  RegisterClassEx( &wc );

  hwnd = CreateWindowEx( 0, TEXT("Buttons"), "Timer Test",
    WS_OVERLAPPED | WS_SYSMENU |WS_VISIBLE |WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 405, 290, NULL, NULL, hInstance, NULL );

  ShowWindow( hwnd, nCmdShow );
  UpdateWindow( hwnd );

  while ( GetMessage( &Msg, NULL, 0, 0 ) > 0 )
  {
    TranslateMessage( &Msg );
    DispatchMessage( &Msg );
  }
  return Msg.wParam;
}

None

I am glad to share how I perform timing on my 2d game engine, here is what the main loop, looks like,

 case WM_PAINT: // Paint the game
        {
            hdc = BeginPaint(hWnd,&ps);

            dw_timestart = timeGetTime(); // start timing

            ProcessInput(); // any key input
            UpdateObjects(); // update game objects
            RenderFrame(); // paint game

            dw_timestop = timeGetTime(); // stop timing

            EndPaint(hWnd,&ps);

            // store elapsed game cycle
            f_cycle_t = dw_timestop - dw_timestart;

            break;
        }

You can also the C++ timer functions which, is more portable, I'll be doing that eventually.

This is how this works,

1. dw_timestart stores the current timer

2. the game engine then does some work gets input, updates logic for objects and then renders a frame, this is called a cycle.

3. we record the time the game cycle finished.

4. the we store the elapsed time in “f_cycle_t" this is your “delta time”

You animate your sprite using the delta timer this way.

void PLAYER2D::moveleft(vector<SPRITE2D>&v_mgsprites)
// Moves the player sprite left
{
    // avoid out of bound error
    if(p_sprite->getx() < 0) return;

    // try to move the sprite
    if(p_sprite->moveleft(f_velocity*f_cycle_t,v_mgsprites))
        i_state = GE_LEFT; // update state
}

f_velocity is how fast you want your sprite to move … say .50 or something and then you multiply it by the delta timer.

Of course in your main game loop you want to run a check to monitor the delta timer because if the timer starts to increase too high, it's going to break your game, sprites will go through walls and collision system will break, so if the timer gets too high, you need to lock at it at its maximum setting and start dropping frames instead. I don't have this check currently active on the example I gave but it is something you can focus on later when your game becomes so saturated that you need to start monitoring it.

You will also deal with acceleration which, requires a different algorithm, for example, here is my jump algorithm for the player sprite.

if(b_isjumping) // Jump algorithm
    {
        i_state = GE_JUMPING; // update state

        if(b_havelink) // if we have a link
        {
            // unlink from the parent sprite
            v_mgsprites[p_sprite->getparent()].unlink(p_sprite->getid());
            b_havelink = false;
        }

        f_jmpfactor -= 0.0035 * f_cycle_t / 2; // slow down as we go up

        // move the sprite up
        if(!p_sprite->moveup(f_jmpfactor*f_cycle_t,v_mgsprites) || f_jmpfactor < 0)
        {
            // if we hit something or the jump completes
            f_jmpfactor = ct_JMPFACTOR; // reset jump factor
            f_gravity = 0; // kill the gravity
            b_isfalling = true; // now falling
            b_isjumping = false; // not jumping
        }
    }

If you're doing acceleration and animating, you have to provide the acceleration rate 0.0035 on the example above and then multiply by the delta timer and divided it by two … you then animate the sprite using that calculation by the delta timer again … failure to do this will result in different jump heights under different game loads. It took me months to figure this stuff out but happy to share it.

Feel free to ask questions, my way may not be the best way, so others may have better ways to do it.

Again, thank you for sharing your work here and taking the time to explain it! I tend to learn by taking apart working examples rather than from scratch, so this is really helpful. My program actually has very little user input required, with only a few click actions on windows. Sprite animation/movement is just there for decorative purposes as the program is a demo/intro and not a game.

Do you only have one timer for the whole thing, or seperate ones for different events? I ask because I need to spawn several instances of the same sprite, but with some having different speeds. With my current timing method, I will probably need to use several SetTimer() calls. I should also ask - What is the best/simplest way to blit multiple instances of a sprite to the screen?

None

Airbatz said:

Again, thank you for sharing your work here and taking the time to explain it! I tend to learn by taking apart working examples rather than from scratch, so this is really helpful. My program actually has very little user input required, with only a few click actions on windows. Sprite animation/movement is just there for decorative purposes as the program is a demo/intro and not a game.

Do you only have one timer for the whole thing, or seperate ones for different events? I ask because I need to spawn several instances of the same sprite, but with some having different speeds. With my current timing method, I will probably need to use several SetTimer() calls. I should also ask - What is the best/simplest way to blit multiple instances of a sprite to the screen?

The technique is the same, to make sure you get sprite animation timed correctly, just measure the length of each cycle and that is your delta time. The speed of your sprite is then multiplied by the delta time. Even if it is not a game, you should strive to standardize a practice in place to get consistent results. Burn this into your brain, make functions or classes that you can re-use in other programs.

The problem with SetTimer() for sprite animation is that it will not be accurate for animations and it will flood your Window procedure the more frequent it is. That is how I tried it first and it never worked right.

DWORD dw_timestart;
DWORD dw_timestop;
float f_cycle_t;

float sprite_speed = 0.50;
float sprite_x_position = 0;

dw_timestart = timeGetTime(); // start timing

// your program does all work even non-sprite related work here

dw_timestop = timeGetTime(); // stop timing

f_cycle_t = dw_timestop - dw_timestart; // delta time

// move the sprite
sprite_x_position = sprite_x_position + (sprite_speed * f_cycle_t);

every sprite has an x position … so if you have multiple sprites … to move all of them at different speeds you simply set different sprite speeds for each sprite but you always multiply that speed by the delta time … you see why this is better? Using multiple timers will break your animation as the message might get lost in the message loop. In your message loop to constantly force a redraw when your windows is in focus use, do this,

    // Run the message loop
    while(GetMessage(&amp;amp;msg,NULL,0,0))
    {
        // process messages
        TranslateMessage(&amp;amp;msg);
        DispatchMessage(&amp;amp;msg);

        // always force a redraw for the game loop
        if(GetFocus()==hWnd)InvalidateRect(hWnd,NULL,false);
    }

you could use peek but this is simpler, this is message loop on my 2d game

The only timer in my program is,

f_cycle_t = dw_timestop - dw_timestart; // delta time

The only place I use SetTimer() is in the sound engine which is pretty ridiculously simple, here is the entire program using SetTimer()

#include <tchar.h>
#include <windows.h>
#include <mmsystem.h>
#include <fstream>
#include <vector>
#include <string>

#include "resource.h"

/*
       _____                       _____       __
      / ___/____  __  ______  ____/ /__ \ ____/ /
      \__ \/ __ \/ / / / __ \/ __  /__/ // __  /
     ___/ / /_/ / /_/ / / / / /_/ // __// /_/ /
    /____/\____/\__,_/_/ /_/\__,_//____/\__,_/ (tm)

    Sound2d Interface for ChaosEffex 2d Game engine.
    Copyright (c) 2019 Rafael Perez-Santana
    Contact; misterfaosfx@gmail.com

    This application is launched as a separate process by ChaosEffex to allow mixing
    audio playback with PlaySound() the theory is that every instance of this application
    acts as an audio layer, to allow play multiple sounds with out stopping the others.

    A one minute timer ensures that no running copies of Sound2d are left running if the
    game engine quits/crashes for any reason. We do this by looking for the window every
    minute and terminating this application if we do not find it!

    The features; waveOutPause() &amp;amp;amp; waveOutRestart() do not seem to work.
*/

using namespace std;

// for sound system
#define sound2d_play 1100
#define sound2d_stop 1101
#define sound2d_volume 1102
#define sound2d_pause 1103
#define sound2d_resume 1104

#define TIMER1 1001 // timer identifier

vector<string>v_sg_sound_data; // sound index
string sg_filename; // file to open for sounds

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("sound2d1");

int WINAPI WinMain // main entry point
(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nCmdShow)
{
    HWND hWnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon(hThisInstance,MAKEINTRESOURCE(APP_ICON));
    wincl.hIconSm = LoadIcon(hThisInstance,MAKEINTRESOURCE(APP_ICON));
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */

    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&amp;amp;amp;wincl)) return 0;

    sg_filename = lpszArgument; // copy the argument value

    /* The class is registered, let's create the program*/
    hWnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           lpszArgument,        /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           100,100,             /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    if(!sg_filename.size())
    {
        /*  If the size of sg_filename is 0 then no argument was specified. The
            argument is the name used to set the window title so that we can find it.
            It also is an alias to the file that contains the list of sounds to play,
            so with out the argument we cannot continue. */

        MessageBox(hWnd,"No argument specified. Terminating.","Error",MB_ICONERROR);

        return 0; // cannot continue
    }

    /* Make the window visible on the screen */
    ShowWindow (hWnd,nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&amp;amp;amp;messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&amp;amp;amp;messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&amp;amp;amp;messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_CREATE:
        {
            // temp variables
            string sg_lineoftext; // buffer
            ifstream ssg_inputfile; // to read from file

            ssg_inputfile.open(".\\" + sg_filename + ".txt"); // open file for input

            // read in lines of text until we reach the end
            while(getline(ssg_inputfile,sg_lineoftext))v_sg_sound_data.push_back(sg_lineoftext);

            ssg_inputfile.close(); // close the file

            // set default volume
            waveOutSetVolume(NULL,(32767 << 0) + (32767 << 16));

            // create a 1 minute timer
            SetTimer(hWnd,TIMER1,60000,(TIMERPROC)NULL);

            break;
        }

        case WM_TIMER:
        {
            /*  This application is launched as a separate process by the ChaosEffex game
                engine. Every 1 minutes, we try to find the game engine window to see if it is
                active, if it is not, we terminate the sound system. This is done to make sure
                we don't end up with running copies of Sound2d shall the game engine crash
                for any reason. */

            if(wParam==TIMER1)
                // look for ChaosEffex, if we don't find it, quit.
                if(FindWindow("ChaosEffex",NULL)==NULL) PostQuitMessage(0);

            break;
        }

        case sound2d_pause: // pauses sound playback
        {
            waveOutPause(NULL);

            break;
        }

        case sound2d_resume: // resumes sound playback
        {
            waveOutRestart(NULL);

            break;
        }

        case sound2d_stop:
        {
            PlaySound(NULL,0,0); // stop play back of any sound

            break;
        }

        case sound2d_volume: // sets the volume
        {
            /// wParam = new volume
            /// lParam = ignored.

            waveOutSetVolume(NULL,(wParam << 0) + (wParam << 16));

            break;
        }

        case sound2d_play: // play a sound
        {
            /// wParam = sound index to play
            /// lParam = 0 not looped, 1 looped

            if(lParam==0)
            {
                // play a sound only once
                PlaySound(v_sg_sound_data[wParam].c_str(),NULL,SND_FILENAME|SND_ASYNC|SND_NOSTOP);
            }
            else if(lParam==1)
            {
                // play a sound looped
                PlaySound(v_sg_sound_data[wParam].c_str(),NULL,SND_FILENAME|SND_LOOP|SND_ASYNC);
            }

            break;
        }

        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;

        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hWnd, message, wParam, lParam);
    }

    return 0;
}

Have fun!

As for Blitting multiple instance of your sprite in the application window … use classes, I want to burn this in your head..if you had a sprite class like this

class spriteobject
{
   public:
   void move_x();
   void setspeed(float f_val) { f_speed = f_val; }
   void drawsprite();
   
   private:
   float f_x_position;
   float f_speed = 0.50; // default speed   
};

// to use multiple instances of the same object create them!
spriteobject sprite1;
spriteobject sprite2;
spriteobject sprite3;
spriteobject sprite4;

// set speeds!
sprite1.setspeed(.15);
sprite2.setspeed(1);
sprite3.setspeed(2);
sprite4.setspeed(.50);

// move all the sprites!
sprite1.move_x();
sprite2.move_x();
sprite3.move_x();
sprite4.move_x();

// then draw them in new positions!
sprite1.drawsprite();
sprite2.drawsprite();
sprite3.drawsprite();
sprite4.drawsprite();

// you can also use the c++ vector
vector<spriteobject> v_mysprites(4);

// now you can do this
v_mysprites[2].setspeed(.50);

// instead of calling each one, cycle through them in a for loop
for (unsigned int i = 0; i < v_mysprites.size(); i++)
{
	v_mysprites[i].move_x();
	v_mysprites[i].drawsprite();
}

Lean how to model your information in classes, it is sooo much easier.

Best;

Rafael

Hello Rafael,

I managed to implement your timing method and it works very well! Could you please take a look at my code and let me know if I have done it right? For animating the actual frames, I used a simple counter for now as I wasn't sure how to do it with the delta. Right now I am trying to set up a class-like implementation for the different sprite instances. I'm using C only, so I'll have to find some workarounds for the C++ specific functions. My code using your timer is below. I like your idea for layering sounds!

DWORD dw_timestart;
DWORD dw_timestop;
float f_cycle_t;

typedef struct SpriteAnimation
{
  int frames;
  int direction;
  float counter2;
  float counter;
  float sprite_speed;
  float sdrift_speed;
  float animation_speed;
  float x;
  float y;

}SpriteAnimation;
SpriteAnimation g_SpriteInfo;

This is at the top of my program.

void UpdateSpritePosition( RECT* prc)
{
  g_SpriteInfo.y +=(g_SpriteInfo.sprite_speed * f_cycle_t); 
  g_SpriteInfo.counter+=0.5;
  
		if(g_SpriteInfo.counter == 150.0)
		{
			if(g_SpriteInfo.direction == 0 ) 
                {
                g_SpriteInfo.sdrift_speed = 0.04; 
                g_SpriteInfo.direction = 1; 
                }

			else if (g_SpriteInfo.direction == 1) 
                {
                g_SpriteInfo.sdrift_speed = -0.04; 
                g_SpriteInfo.direction = 0; 
			}
			g_SpriteInfo.counter = 0.00; pattern
		}
		g_SpriteInfo.x += g_SpriteInfo.sdrift_speed; 

if ( g_SpriteInfo.y > 290 ) 
  {
     g_SpriteInfo.y = -7; 

     g_SpriteInfo.x = abs(rand()*405/RAND_MAX); sprite

  }
}

The sprite update function.

void AnimateFrames ( RECT* prc)
{

  g_SpriteInfo.counter2+=0.5;

		if(g_SpriteInfo.counter2 == 100.0)
		{
        g_SpriteInfo.frames+=1;
        g_SpriteInfo.counter2 = 0;
        }
}

How I'm animating the frames.

case WM_PAINT:
      {
        RECT rcClient;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint( hwnd, &ps );

        GetClientRect( hwnd, &rcClient );
        dw_timestart = timeGetTime();

        DrawMain( hdc, &rcClient );
        AnimateFrames (&rcClient);
        UpdateSpritePosition( &rcClient);

        dw_timestop = timeGetTime();
        EndPaint( hwnd, &ps );

        f_cycle_t = dw_timestop - dw_timestart;
        InvalidateRect( hwnd, NULL, FALSE );
      }
      return 0;

.. and the WM_PAINT

None

@Airbatz Yes you are doing it correctly! I had no idea you were working with just C I must have totally over looked that, so you're approach must be more linear as opposed to object oriented if you're using just C. It just means you'll have wayyy more functions to do certain tasks … C++ simply takes that approach and encapsulates the functions into a specific object along with some extra features.

No matter how many sprites you add now, the delta time will increase ever so slightly to keep your animation constant.

The secret to awesome performance with the Windows GDI and graphics is to use something I refer to as Quantum Indexing … if you had a 2d map with 50,000 sprites on it, you would have to cycle through all 50,000 sprites to see whether or not they were in the viewport to draw them … if you use a tile map however, you can perform a quantum leap via a co-ordinate reference.

int TILEGRID2D::getspriteindex(int i_xpos, int i_ypos)
// Gets a sprite index via the x and y, I call it; QuantumIndexing™
{
    // correct out of bound errors
    if(i_xpos < 0) i_xpos = 0; if(i_ypos < 0) i_ypos = 0;

    // does a quantum leap and directly gets the index associated
    // with the sprite located at the specified x and y co-ordinates!
    return (int)((i_ypos - i_offset_y) / 100) * i_gridwidth + (int)((i_xpos - i_offset_x) / 100);
}

So all I do is get the position of the player sprite and use that formula to find where on the tile map it is located, then a special function takes over and draws the contents of the tile map at that location,

void TILEGRID2D::drawspritegrid(HDC hdc_surface,float f_xpos, float f_ypos, int i_width, int i_height,bool b_padding)
// draws a grid of sprites at the specified x and y within the confines of width and height.
{
    // grid padding, for left and right sides.
    int i_padding = 2; if(!b_padding) i_padding = 0;

    // how many tiles are required?
    int i_x_total = (i_width / ge_tilewidth) + i_padding;
    int i_y_total = (i_height / ge_tileheight) + i_padding;

    int i_column = 0, i_row = 0; // to cycle through loop

    /// draw grid : experiment
    for(int i = getspriteindex(f_xpos,f_ypos); i_row != i_y_total; i++)
    {
        // check to see if out of bounds
        if(i >= i_gridwidth * i_gridheight) return;

        drawsprite(hdc_surface,i); // draw the sprite

        i_column++;

        if(i_column == i_x_total) { i =  i + (i_gridwidth - i_x_total); i_column = 0; i_row++; }
    }
}

drawspritegrid() is used by the viewport class to render all the different layers, this is where all the magic happens!

void VIEWPORT2D::render(int x,int y,int t,BITMAP2D&amp;background,
BACKBUFFER2D&amp;backbuffer,DEVMODE2D&amp;devmode,vector<TILEGRID2D>&amp;v_tilegrid,vector<SPRITE2D>&amp;v_objects)
// The viewport function renders the game at the specified x and y co-ordinates using 't' to track a target sprite
// from the object sprite layer. It uses the specified backbuffer &amp; sprite vectors to render to the viewport.
{
    if(!devmode.getmode()) // if developer mode is off
    {
        // track the target sprite
        if(v_objects[t].getx() >= i_width/2)f_xbbIndex=v_objects[t].getx()+ge_tilewidth-i_width/2;
        if(v_objects[t].getx() <= i_width/2)f_xbbIndex=v_objects[t].getx()+ge_tilewidth-i_width/2;
        if(v_objects[t].gety() >= i_height/2)f_ybbIndex=v_objects[t].gety()-i_height/2;
        if(v_objects[t].gety() <= i_height/2)f_ybbIndex=v_objects[t].gety()-i_height/2;
    }

    // only render when the developer bitmap tile selector is off
    if((devmode.getmode() &amp;&amp; !devmode.tileselectorstatus()) || !devmode.getmode())
    {
        // stay within bounds of back buffer dimensions
        if(f_xbbIndex < 0) f_xbbIndex = 0; if(f_ybbIndex < 0) f_ybbIndex=0;
        if(f_xbbIndex+i_width > backbuffer.getwidth()) f_xbbIndex=backbuffer.getwidth()-i_width;
        if(f_ybbIndex+i_height > backbuffer.getheight()) f_ybbIndex=backbuffer.getheight()-i_height;

        // draw the tiles directly to the background surface.
        v_tilegrid[tB].drawspritegrid(background.getsurface(),f_xbbIndex,f_ybbIndex,i_width,i_height,true);

        // paint the background to the back buffer.
        background.paintbk(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,
                           f_xbbIndex * (background.getwidth()-i_width+1) / (backbuffer.getwidth()-i_width+1),
                           f_ybbIndex * (background.getheight()-i_height+1) / (backbuffer.getheight()-i_height+1));

        // parallax algorithm for tiled background
        //f_offset_x=f_xbbIndex-(f_xbbIndex*(v_tilegrid[tB].getpixelwidth()-i_width+1)/(backbuffer.getwidth()-i_width+1));
        //f_offset_y=f_ybbIndex-(f_ybbIndex*(v_tilegrid[tB].getpixelheight()-i_height+1)/(backbuffer.getheight()-i_height+1));
        //v_tilegrid[tB].grid_offset((int)f_offset_x,(int)f_offset_y); // offset the grid

        v_tilegrid[dS].drawspritegrid(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,true); // decoraction sprites
        v_tilegrid[bS].drawspritegrid(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,true); // background sprites

        // paint object sprites, we use 'v_renderobjects'
        for(int i=0; i < (int)v_renderobjects.size(); i++) v_objects[v_renderobjects[i]].paintsprite(backbuffer.get());

        v_tilegrid[mS].drawspritegrid(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,true); // main sprite layer
        v_tilegrid[fS].drawspritegrid(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,true); // foreground sprites
    }

    devmode.render(background,backbuffer,v_tilegrid,f_xbbIndex,f_ybbIndex); // render developer interface

    if(b_blend_effect) // special blend effects, if any
    {
        // draws a translucent box over the entire viewport
        blendeffect.blendsp(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height,0,0,byte_blend_effect);

        blend_effect_timer(); // runs the timer for special effects
    }

    // cycles per second information
    TxtOut(backbuffer.get(),f_xbbIndex+140,f_ybbIndex+i_clientheight-25," "+f_tosg((int)f_cycles_sec)+" ");

    // copy the back buffer contents to the window DC ________ lotta coffee c[_]
    BitBlt(hdc,x,y,i_width,i_height,backbuffer.get(),f_xbbIndex,f_ybbIndex,SRCCOPY);

    v_renderobjects.clear(); // clean up the render list
}

I'm actually working on my 2d game engine as we speak. I'm currently drawing the grid using windows functions and it is very slow, so I want to create an asset and render a skeleton grid on it so that I only paint a portion of it when I'm in mad edit mode to improve performance … a larger blit operation is better than many smaller blit operations you just want to make sure that what ever you are blitting is what's going to be seen otherwise it is wasted work.

You are doing great, keep us updated! I don't claim to know it all but I can share from what I have learned during my 1 year of research with the windows GDI and that year was spent working on all this stuff with countless hours of slept and tons of ideas that got tested and thrown out the window … I'm constantly experimenting to see what works and what doesn't to keep everything running smoothly.

Here is a little something to get you excited!

Firstly, thanks for showing the progress on your engine. I've been following it closely! It's nice to see what goes into something like this as it really puts the requirements for a comparable project into perspective. I love those parallax backgrounds!

I have read much more about C than C++, even though I'm pretty much a beginner, so I decided to go with it. There seems to be a greater need for pointers to achieve the equivalent of some C++ functions. I admit pointers are not my strong point, but I learn as I go ?

Regarding the sprite instances, would I be setting them in WMCREATE and calling the draw and move functions in WM_PAINT instead of UpdateSpritePosition() as I'm doing now?

There is one other thing that I have no clue how to go about though, (and this will need a whole new topic I'm sure). Is it possible by some means to play and loop a MIDI stored in the executable, directly from memory using only the MCI? Every time this question comes up, the answer is that it can't be done. I know DirectX provides an interface for this, but that means the user has to have it installed on their system, which I want to avoid.

Sorry for all the questions, I'm sure you are busy with your own stuff. Thank you for your help!

None

This topic is closed to new replies.

Advertisement