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(&msg,NULL,0,0))
{
// process messages
TranslateMessage(&msg);
DispatchMessage(&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; 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;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;messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&amp;amp;messages);
/* Send message to WindowProcedure */
DispatchMessage(&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