So, originally, I began learning C++ in order to create an application that would interface with my website and thereby allow me to manage the content that I post easily. These words are currently being written in that application. I still need to work out a few bugs in the code, but it's operational & gets the job done. Now, in order to increase my programming knowledge, I decided to write a simple game that called for the utilisation of Win32 API, for the handling of graphics using SFML, and for the application of WinSock.
The general flow of the finished programme would, in effect, look a little like this: open the game -> start the game -> play the game -> finish the game -> game takes your score & other particulars -> game sends this data over to server -> server receives data & in turn sends it to a MySQL database -> database is updated with said data -> website displays stats based on this data.
Easy for the seasoned programmer, I imagine; but I still consider myself greene, & indeed such a task in front of my eyes looked & still looks like a mountain whose other side can be accessed only by climbing that mountain's breadth. But I got the job done. The code, as you will no doubt see, & which I will point out in germane moments, has its problems. Even as a novice, I know there are better and more elegant ways to achieve the same results, especially when we pass information from the client (game) to the server. My goal, however, was to get the job done; although I attempted to find the best solution for every problem that I came across, perfected code was the last thing on my mind. I feel as if there are still better and more ideal methods to get better results(in the Army we lived by the idea of continual "position improvement"), & I will in fact return to this project to add more functionality, interactivity, & complexity to the game. Guy Eating Apples will probably become Guy Saves His Bro, or something along those lines, in the future. Who knows?
Essentially, I wrote two programmes. Number one: the game. Number two: a *UDP* server which takes stats from the game. The code (in PDF forme) for these programmes you can download here & here, & these will be the documents that I will be referencing in the paragraphs below.
Now a bit (ha!) of general information. In the Spirit of reducing time of development, all sprites elements were procured from other sources. Here is the link for the protagonist's sprite sheet, & here is the link for the apple sprite, both with designer credits. My lovely girlfriend, Krystel, was kind enough to draw the title card for the start screen. Click here to see it. Here's a link to the download page (with designer's info) for the "collect" sfx. If you're old enough to remember, the music is taken from the old NES Friday the 13th game released in 1989. Catchy little tune. Big thanks go out to the following YouTube channels who proved to be invaluable know-how resources for the development of this game: ChiliTomatoNoodle, codegopher, The Cherno, & Sloan Kelly (readers will recognise pieces of his code in the networking aspects of the PDFs).
Now, as for the server, I wrote a UDP server, as opposed to TCP, thinking I wouldn't need to maintain a connexion between client & server when passing stats. It seemed like a goode idea at the time, then it didn't seem like a goode idea, assuming we had multiple players connecting at once, but now that I think about it, rewriting parts of the code would leave UDP as the preferred "connexion" type. More on that later. But, if anybody has thoughts about this or anything at all, let me know in the comments system that I will be implementing shortly after this entry's being posted. I'm always open to suggestions, & inclined to put them into practise if they are better & more elegant solutions than mine, which, at this point, is true probably 90% of the time.
Now, as for the game itself, it is compatible only on Windows OS. I don't possess the knowledge to port this to Mac. Goode news is, in the future I'm planning to purchase a Mac desktop & have been looking into XCode & Objective C in the last few weeks. For now, I'm strictly a Windows guy, & since I like working with raw Win32 API, I decided that using a mixture of SFML (for gfx) and Win32 API (for building the overall .exe & UI) would be a fun exercise. (For a couple of days I had been debating whether I ought to use SFML or SDL2 for the gfx handling of the game, & was convinced to go with SFML based on the ease with which codegopher was able to get things going. There was another reason, also, but it has long since escaped my memoury.)
In "guy_apples_code.pdf" (which I will henceforth call "the game code" , or some explicit derivation thereto) , in MAIN.CPP, beginning on PDF pg. 2, on Line. (Ln.) #117, you'll see that we're already in a standard "main" , i.e., WinMain, function, & that there is a comment reading, "Create parent window's class". This is a task which I could have let SFML handle with RenderWindow . . . but call me crazy, I like the amount of control which Win32 API forces you to take in creating our parent window (if you're hip to HTML, it's like creating a DIV container/wrapper that will hold ALL the content displayed on-screen, & that DIV will of course need some CSS applied to it, as well.). So, in Ln. #111 (main.cpp), we've declared a WNDCLASS data structure, & created an object of that data structure, "WindowClass". For my fellow novices, that would be following the same idea if, e.g., we had created a normal header file (with a class declared therein) and its accompanying .cpp file. I imagine, somewhere in "windows.h" (Ln. #12), there's a class declaration named WNDCLASS. I say all this because at first I had a lot o' trouble wrapping my head around juste exactly what "WNDCLASS" was. Anyway, beginning on Ln. #118, we fill out all the necessary properties contained in WNDCLASS. Although it seems like a lot to handle, over the weeks, I've come to realise that it's pretty standard and common sense information we're entering, the most significant o' which, I think, is the "lpfnWndProc" property. I'll go into that later. Essentially, what we're saying is, I'm about to create the main window that'll open after double-clicking the icon of our game, but before I do that, here's a list of the properties it will have. Following the code, we then register the class, to make sure it's valid, by passing in the object's address in memory.
Now things become a little interesting. Overall, I want this application to be a Win32 API programme, but I want to take advantage of SFML. The documentation on integrating SFML into Win32 (not the other way around) is pretty straightforward. Basically, first we'll create the actual window whose class we juste registered. In the game code, on Ln. #131, I've called it "GameWindow", which is a variable (or handle) that will contain this window. Notice that the data type is HWND, which if I'm not mistaken, stands for "Handle to Window", or something to that effect. (This is how Win32 "stores" the window for later referencing) So the CreateWIndowEx(); function does the actual window-creating. The parameters it takes describes the properties of the window itself, not the class we juste registered. Where on the screen it will pop out, how big it'll be in pixels, whether the user will be able to resize the window, what lettering will appear in the title bar, &c. You'll notice that there are two HWNDs, "GameWindow" and "ChildSFML"; the latter will of course be the "window" in which I shall display all the gfx. Beginning on Ln. #136, we will now actually SHOW the windows to the user. But, using SFML, on Ln. #138, we will specify that the SFML RenderWindow object we've created will be contained in the ChildSFML child window. As a matter o' fact, on Ln. #132, the fourth-to-last parameter of CreateWindowEx(); specifies that ChildSFML's parent window is in fact "GameWindow"; ChildSFML also has a "Static" class, which I imagine is a predefined class of windows (like the one we juste created & registered) that defines the window's behaviour; "Static" in this case juste means that it'll merely sit there & do nothing. Scratch that. All it has to do is contain the SFML frame we've passed into it, come what may. Also, we've specified the "WS_CHILD" style in the same line. Setting all this up would have been much easier in SFML, but like I said, I prefer to do it in the Win32 API way.
Now we will set the game/window loop. I write it like that because we have to take into account that both Win32 and SFML will be tracking and processing messages. Actually, Win32 calls these entities messages, & SFML events. I think, however, we can think of "messages" and "events" as being the same thing in theory, in that both Win32 and SFML look out for & track whether the user has pressed/let go of a certain key or button or has interacted with some kind of information, or has sent some kind of command to the programme, or what have you. Simply put, if you DO anything in the programme, it has a corresponding message/event that is sent to the OS. (If I got the wrong/partly wrong, feel free to explain it better in the comments sexion) I understand the general idea & what "I" need to do in order to streamline this process. Things become a little hazy in my head once I try explaining it.
On the Win32 side o' things, in order to "HANDLE A MESSAGE" (that's a phrase you're going to hear a lot in Win32), we need to setup some kind o' function that processes the messages. Now, here's the cool part. This function won't be called by the user, i.e., you or me, instead it'll be called by Windows itself. So this function will be called by Windows itself, & Windows will itself pass in the parameters defined in the function's declaration; these parameters will include the message & any extra information accompanying that message. SFML, I imagine, does something similar, but SFML also simplifies the job & doesn't require us, the programmer, to write out the SFML analogue o' this function. For Win32 API, we must supply this function, & if you take a look on Ln. #59, you'll see the "LRESULT CALLBACK WndProc();" function. Seem familiar? That's because, when we filled out the parent window's class, we said that all the message handling procedures o' the parent window will go through a function called "WndProc", & indeed, if we want ANYTHING to work in our programme, both ourselves and Windows must have a way o' handling information. The parameters we declare, "HWND hwnd, UINT msg, &c.", from left to right are: the handle to the window that is associated to this function, the message that was sent to Windows, & wParam & lParam signify any additional information supplied along with the message. This extra information, e.g., may be a unique identifier associated with a button you pressed, which in turn effected, e.g., the dispatching of the WM_COMMAND message, & so the switch statement we use at the top o' the function's body will run the code according the whatever "msg" supplied in the parentheses (Ln. #60).
But, since the programme is essentially running on a loop, I imagine messages are ALWAYS being sent to Windows, & really we don't want to handle every single message that comes Windows's way, juste the ones that interest us at certain points in time. So, before anything else, we must define some kind of default behaviour that'll let Windows do its thing without having to rely on us, i.e., it could juste use the WndProc(); function on its own, as intended. If we're not particularly interested in handling this or that message, windows will simply use the DefWindowProc(); function which we have defined as the default case in our switch statement, Ln. #102, & that's that. Notice how we've declared the same parameters that we declared in the WndProc(); function. So, if we're not handling this or that message, Windows will call the WndProc(); function, passing the message therein, it won't run into any special case that we've predefined in the switch statement, it'll reach the default statement, use DefWindowProc();, & repeat the process continuously; that is, 'til it receives WM_CLOSE message, which in that CASE, we've said to our switch statement, if you get the WM_CLOSE message, run the DestroyWindow(); function, which will initiate the window's being closed.
If, however, the message given to our switch statement is the WM_CREATE message, run all THIS code (Lns. #62--#72), which is basically the creation of all the other child windows contained in out parent window. Of WM_CREATE, the Microsoft Documentation says:
quote: |
Sent when an application requests that a window be created by calling the CreateWindowEx or CreateWindow function. The window procedure of the new window receives this message after the window is created, but before the window becomes visible. |
Originally published on Fri Aug 07 21:56:46 2020, & last updated on Wed Aug 12 23:57:05 2020