lolirelia / Flappy

Multiplayer Flappy bird game made in C using Raylib for rendering and LibUV for UDP Networking
8 stars 1 forks source link

Collaborate on Flappy? #1

Open sominator opened 1 week ago

sominator commented 1 week ago

Hi there!

I'm a solo dev looking to help with FOSS projects to improve my C/C++ and collaboration. I'm curious whether you're planning to do more work on Flappy and if I can lend a hand.

I have some raylib experience but otherwise mostly Unity/Godot/C# and Phaser/JS, also Defold & Colyseus (networking). I'm a quick learner and easy to work with, but would have some questions while getting onboarded to lower level stuff like memory management.

Please let me know if you're interested, and thanks in advance!

lolirelia commented 1 week ago

@sominator Hi there!

At some point I did plan on going back to the code when I had free time and implementing client side prediction. What sort of work did you have in mind? By all means feel free to fork the repo and make pull requests. The game right now has a 100ms render delay (You don't see your inputs applied until 100ms passes) and there aren't any sprites. It's just rectangles. So the former will require client side prediction which in a game as small as this is very simple to implement. And sprites which will require some container to retrieve sprite identifiers from and render them accordingly.

sominator commented 1 week ago

Sounds awesome! If you're able to give me a sketch of how you'd like server reconciliation to work on the client side I'd be happy to take a crack at it. I've looked at the source and don't quite understand all of the net code as it's lower level than the frameworks I've worked with, but am happy to learn.

For sprites, are you just wanting to implement textures for the two characters?

lolirelia commented 1 week ago

@sominator Yes the sprites are very basic. Just for the characters. The rectangular walls if needs sprites can be achieved but at the moment not a priority.

Here's how the reconciliation can work (Note I will use tick/sequence interchangeably) :

A tick number can only increment in a fixed update(both on the server and client)

The client will need a fixed update function similar to how the server performs a fixed update. The client already has the loop to perform a fixed update, it just needs the update function. The update function can be called here:

        accumulator += GetFrameTime();
        while (accumulator >= kTimestep) {
            accumulator -= kTimestep;
            ++g_tick;
        }

Within the while (accumulator >= kTimestep) block right after ++g_tick; The client will need to run the same physics simulation for itself as the server does for it. As it stands the client only sends an input to the server if there's a change in y velocity ( mouse up or down). The first adjustment which needs to be made is that input needs to be stored until the client fixed update actually occurs. The input will be sent to the server every update tick . This will the client's input (mouse up or down) and a tick number. Currently in client.c in the main function when a player release or holds the mouse, the input is packed into a 64bit structure (uint64_t) using bit-packing. I'll make an edit to gameshared.h where

#define kPackPlayerInput(x, y) \
    (x = (((uint64_t)y << 48) | (uint16_t)kEFlappyPacketIdInput))

Also packs the player's tick number.
After each completed client update, the predicted position and tick number will be stored into a structure and that structure into a container.

On the server side: The server will receive packets from the client every fixed frame. The server will assign if the player is flapping or not depending on the input(it currently does this already) and it will assign a tick number to the player's structure.

struct PlayerServerside {
    struct PlayerClient player;
    Vector2 velocity;
    uint8_t isflapping;
    uint32_t ipv4;
    uint16_t port;
    struct sockaddr_in addrin;
}

When the server performs it's update on each client, the tick number it received for that client can be packed into kPackPlayerId This structure needs to be updated to hold the server's tick and player id(x:uint64), and position (y:uint64) and client's reconciliation tick (z:uint64).

    uint64_t x;
    uint64_t y;
};

I'll make adjustments to kPackPlayerId so that the tick can be packed in there as well. Each client will receive the gamestate and check the tick associated with their player id in the gamestate packet. It can then refer to the container where it stored it's predicted position and tick number to decide either to continue its own trajectory or reset to the server's state.

That might be a lot to take in and read, in short: kPackPlayerId and kPackPlayerInput both need to be edited to pack a tick number.

Client needs to perform a fixed update similar to the server's side physics Client needs to store its predicted position and tick number Client needs to send a packet to the server every update Client needs to send its own tick number in the input packet

Server needs to assign the client's tick along with the input as its received. Server needs to perform an update. Server needs to send back that same tick number along with the newly updated position for each client.

lolirelia commented 1 week ago

See aca4889 e4eec9c 04fb014

sominator commented 1 week ago

Thank you so much for these notes! I think I understand the basics of what's needed from what you've shared and will take a closer look at the code and let you know if I have any specific questions. Thank you again!

lolirelia commented 1 week ago

@sominator There are some clarifications which need to be addressed:

If anything else comes to my mind, I'll post it here.

sominator commented 1 week ago

Excellent! Thanks for these clarifications.

sominator commented 1 week ago

I'm getting a bunch of errors on the raylib header when attempting to build the binaries via make:

//snip
..\Flappy\raylib\src\raylib.h(1368): error C2061: syntax error: identifier 'rec'
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ';'
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ','
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ')'
//etc.

I'm working on a Windows machine; do you think it might have to do with my C compiler? I haven't had this issue with raylib in the past.

lolirelia commented 1 week ago

I'm getting a bunch of errors on the raylib header when attempting to build the binaries via make:

//snip
..\Flappy\raylib\src\raylib.h(1368): error C2061: syntax error: identifier 'rec'
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ';'
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ','
..\Flappy\raylib\src\raylib.h(1368): error C2059: syntax error: ')'
//etc.

I'm working on a Windows machine; do you think it might have to do with my C compiler? I haven't had this issue with raylib in the past.

The issue is libuv includes windows.h somewhere down the library. Raylib has conflicting function names with the win32 API. I didn't take this into consideration since I wrote it on a MacOS. The solution I believe would be to move all libuv related code to its own source file and make sure that specific header/source does not include raylib. A bit of a pain but doable.

2

lolirelia commented 1 week ago

Client netcode isolation from raylib was successful. Server is still left. See 5db259f

Are you able to compile just the Client on your end, after the latest commit?

sominator commented 1 week ago

Yes, can confirm the client builds in VS after the commit.

lolirelia commented 1 week ago

Let me know if server compiles for you.

sominator commented 6 days ago

Can confirm the server now compiles as well.

Also, CMake doesn't seem to automatically generate a makefile on Windows, so the -G "Unix Makefiles" argument is required to run make.

Note that attempting to run either binary (server.exe or client.exe) currently fails with the error that uv.dll was not found.

lolirelia commented 6 days ago

Can confirm the server now compiles as well.

Also, CMake doesn't seem to automatically generate a makefile on Windows, so the -G "Unix Makefiles" argument is required to run make.

Note that attempting to run either binary (server.exe or client.exe) currently fails with the error that uv.dll was not found.

Compile the entire solution as your currently doing in Visual Studio then go into the Libuv build release/debug folder and you’ll find uv.dll. Place that in the folder with the server and client executables. Also copy the resources folder from the main project folder and place that with the executables as well.

sominator commented 6 days ago

Hmm, not quite there yet. Building the solution via VS returns an application error 0xc000007b when trying to run the server or client.

Using make allows me to run both executables (server first), but the game is static upon initialization:

Flappy

lolirelia commented 6 days ago

It gives 0x000007b when executing or compiling? And did you allow the server access to your network when you executed it in order to bind to a port?

sominator commented 6 days ago
lolirelia commented 6 days ago
  • No errors building the solution via VS (although attempting to run it through the debugger yields error "Unable to start program '[root]\x64\Debug\ALL_BUILD.' Access is denied.")
  • Executing the release versions of server.exe and client.exe built via VS gives 0x000007b.
  • Executing server.exe and client.exe built via make throw no errors, but launches to the static client window (yes, I allowed the server access to the network upon launching).

I'm really not sure about this error. I can't reproduce it myself so it's hard to know what might be wrong. I did test it on windows myself and it does work for me.

sominator commented 6 days ago

I'll continue to look into it and post if I find a solution. I'm sorry I haven't been much help!

sominator commented 3 days ago

Just a quick update that I'm able to get the executables to work by using VS's CMake integration.

The game still launches to the static client screen, whether through the executables or via the debugger. Where would be the best place for me to start error logging to see if the client and server are connecting properly? In the InitLuv() blocks?

lolirelia commented 3 days ago

Just a quick update that I'm able to get the executables to work by using VS's CMake integration.

The game still launches to the static client screen, whether through the executables or via the debugger. Where would be the best place for me to start error logging to see if the client and server are connecting properly? In the InitLuv() blocks?

In luvserver.c inside the on_recv function. tatic void on_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* rcvbuf, const struct sockaddr* addr, unsigned flags) Inside the if (packetid == kEFlappyPacketInsertPlayer) Can you do a printf("received insert player packet from the client");

And in luvclient.c

static void on_send(uv_udp_send_t* req, int status) {
    assert(req != NULL);
    assert(req->data != NULL);
    free(req->data);
    free(req);
    if (status) {
    }
}

inside if(status){ } Do a print printf("failed to send packet\n");

sominator commented 3 days ago

Done. No logs from the server; several "failed to send packet" logs from the client.

lolirelia commented 3 days ago

Done. No logs from the server; several "failed to send packet" logs from the client.

Can you print the status? printf("failed with status: %d\n",status);

sominator commented 3 days ago

Sure thing, the status is -4090.

lolirelia commented 3 days ago

Sure thing, the status is -4090.

Can you try printf("Failed with error: %s\n",uv_err_name(status));

sominator commented 3 days ago

Yes: failed with error EADDRNOTAVAIL.

lolirelia commented 3 days ago

Yes: failed with error EADDRNOTAVAIL.

Can you try changing the port number to something else? In both luvserver.c and luvclient.c replace the 23456 with another port number.

If that fails to do anything. Try changing the host address in both luvserver.c and luvclient.c to "127.0.0.1" instead of "0.0.0.0"

sominator commented 2 days ago

Changing the port returns the same error EADDRNOTAVAIL.

Changing the host address to "127.0.0.1" throws an error in client.c: "Run-Time Check Failure #3 - The variable 'myposition' is being used without being initialized."

The exception appears at line 84:

DrawText(TextFormat("%.2f:%.2f\n", myposition.x,myposition.y),
         25, 25, 20, GOLD);

myposition is initialized in the if(render[ndex].id == g_myid) block, within for (int ndex = 0; ndex < kMaxNumberOfPlayers; ++ndex), so I'm not sure if there's an issue here.

lolirelia commented 2 days ago

Changing the port returns the same error EADDRNOTAVAIL.

Changing the host address to "127.0.0.1" throws an error in client.c: "Run-Time Check Failure #3 - The variable 'myposition' is being used without being initialized."

The exception appears at line 84:

DrawText(TextFormat("%.2f:%.2f\n", myposition.x,myposition.y),
         25, 25, 20, GOLD);

myposition is initialized in the if(render[ndex].id == g_myid) block, within for (int ndex = 0; ndex < kMaxNumberOfPlayers; ++ndex), so I'm not sure if there's an issue here.

Line 66: change Vector2 myposition to Vector2 myposition = Vector2Zero();

sominator commented 2 days ago

That worked! Out of curiosity, why does the vector have to be initialized here to not throw an exception?

Client now connects with no status error and server logs "received insert player packet from the client" upon connection.

Also, I don't think this is intended: LMC is barely able to keep up with the game; in the gif I'm clicking rapidly but only able to keep the character floating, not move higher up the Y axis. I would suspect this to be an issue with server communication?

client_Hj56BcEEGi

lolirelia commented 2 days ago

@sominator try holding down the left mouse button instead of clicking rapidly. Hold down to elevate, release to fall. Gravity is always acting on the player as well as an x velocity.

As for the vector not being initialized, the loop runs multiple times before it receives its first packet from the server. Which until then can hold some strange variable decided by the OS which can lead to undefined behavior when using that variable .

sominator commented 2 days ago

Lol, whoops. Holding down LMB works fine, thanks for the clarification. I see the delay that you were referring to.

Thanks also about the vector; I understand the issue now.

One other question: do you use a tool for hot reloading the server while debugging? In NodeJS I've used nodemon but not sure if there's a similar (or better) workflow with C/C++.

lolirelia commented 2 days ago

Lol, whoops. Holding down LMB works fine, thanks for the clarification. I see the delay that you were referring to.

Thanks also about the vector; I understand the issue now.

One other question: do you use a tool for hot reloading the server while debugging? In NodeJS I've used nodemon but not sure if there's a similar (or better) workflow with C/C++.

There isn't a concept of "hot reloading" in C since it's a machine compiled language. One trick would be to load dynamic libraries, which hold the critical functioning code and the main module simply loads the modules. Then at runtime you could unload the module, recompile specifically just the module, reload it back and run the code again. It's just unnecessary and requires OS dependency. Since each OS has their own api for dynamically loading modules.

sominator commented 2 days ago

Understood. Thank you!