03: migrate player control to the server

This commit is contained in:
0xdeadbeer 2023-11-18 11:55:22 +01:00
parent 771e6f749b
commit 51177e3a19
16 changed files with 598 additions and 2 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.ccls-cache/ .ccls-cache/
.cache/
build/ build/
*.o *.o
compile_commands.json

View File

@ -10,7 +10,6 @@ const Uint32 RENDERER_FLAGS = SDL_RENDERER_ACCELERATED;
int GAME_RUNNING = 1; int GAME_RUNNING = 1;
struct game game; struct game game;
int movement_speed = 10; int movement_speed = 10;
int gravity = 8;
double delta_time; double delta_time;
double last_frame; double last_frame;
@ -169,7 +168,7 @@ int main(int argc, char *argv[]) {
switch_animation(player, idle_animation); switch_animation(player, idle_animation);
if (game.jump) { if (game.jump) {
switch_animation(player, jump_animation); switch_animation(player, jump_animation);
player->force -= movement_speed * 2.5; player->force -= movement_speed * 3;
player->y += player->force; player->y += player->force;
player->colliding = 0; player->colliding = 0;
} }

View File

@ -0,0 +1,28 @@
CC=gcc
CFLAGS=`pkg-config --cflags sdl2 SDL2_image SDL2_net`
LDFLAGS=`pkg-config --libs sdl2 SDL2_image SDL2_net`
TARGET=client
SDIR=src
ADIR=assets
ODIR=build
SRC=$(shell find $(SDIR) -type f -name *.c)
OBJ=$(SRC:.c=.o)
all: $(TARGET)
.PHONY: default
$(TARGET): $(OBJ)
mkdir -p build
cp -rf $(ADIR) $(ODIR)/$(ADIR)
$(CC) -o $(ODIR)/$@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
run:
$(ODIR)/$(TARGET)
.PHONY: clean
clean:
rm -f $(ODIR)/$(TARGET) $(OBJ)

View File

@ -0,0 +1,7 @@
<p align="center">
<img src=".imgs/showcase.png" width="300" />
</p>
# Network - Client
Client-side code for 03-network

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,264 @@
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_net.h>
#include "structs.h"
#define DEBUG 1
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 9080
#define SERVER_MESSAGE_LEN 10 // 0-1 BYTE: Action details, 2-9: Other data (not sure we even need this)
#define CLIENT_MESSAGE_LEN 4 // 4 PROPERTIES (each of 4 BYTES)
const int SCREEN_WIDTH = 1000;
const int SCREEN_HEIGHT = 480;
const Uint32 WINDOW_FLAGS = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;
const Uint32 RENDERER_FLAGS = SDL_RENDERER_ACCELERATED;
int GAME_RUNNING = 1;
struct game game;
int movement_speed = 10;
double delta_time;
double last_frame;
// network
TCPsocket server_socket;
// scene
struct object *player;
void prepare_scene(void) {
SDL_SetRenderDrawColor(game.renderer, 96, 128, 255, 255);
SDL_RenderClear(game.renderer);
}
void key(SDL_KeyboardEvent *event) {
if (event->repeat != 0)
return;
if (event->keysym.scancode == SDL_SCANCODE_J)
game.left = !game.left;
if (event->keysym.scancode == SDL_SCANCODE_K)
game.right = !game.right;
if (event->keysym.scancode == SDL_SCANCODE_SPACE)
game.jump = !game.jump;
}
void handle_input(void) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
GAME_RUNNING = 0;
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
key(&event.key);
break;
default:
break;
}
if (event.type == SDL_QUIT) {
GAME_RUNNING = 0;
}
}
}
SDL_Texture *load_texture(const char *path) {
SDL_Texture *texture;
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Loading %s", path);
texture = IMG_LoadTexture(game.renderer, path);
return texture;
}
void switch_animation(struct object *object, SDL_Texture *animation) {
if (object->texture == animation)
return;
object->animation_slide = 0;
object->texture = animation;
}
struct object *create_object(SDL_Texture *texture, int scale, int resolution) {
struct object *object = (struct object *) calloc(1, sizeof(struct object));
if (object == NULL) {
fprintf(stderr, "Error: failed allocating memory for new object\n");
return NULL;
}
object->texture = texture;
object->resolution = resolution;
object->animation_speed = 13;
object->scale = scale;
return object;
}
void draw_object(struct object *object) {
int texture_width;
SDL_Rect src;
SDL_Rect dest;
src.x = object->animation_slide * object->resolution;
src.y = 0;
src.w = object->resolution;
src.h = object->resolution;
dest.x = object->x;
dest.y = object->y;
dest.w = object->resolution * object->scale;
dest.h = object->resolution * object->scale;
SDL_RenderCopyEx(game.renderer, object->texture, &src, &dest, 0, NULL, object->flip);
// update animation slide
SDL_QueryTexture(object->texture, NULL, NULL, &texture_width, NULL);
object->animation_clock += object->animation_speed*(delta_time/1000);
if (object->animation_clock >= 1) {
object->animation_clock = 0;
object->animation_slide = (object->animation_slide+1) % (texture_width / object->resolution); // clock arithmetic: jump back to first animation slide
}
}
void present_scene(void) {
SDL_RenderPresent(game.renderer);
}
int handle_server(void *data) {
for (;;) {
int message[CLIENT_MESSAGE_LEN];
int recv_len = SDLNet_TCP_Recv(server_socket, message, sizeof(int)*CLIENT_MESSAGE_LEN);
if (!recv_len) {
fprintf(stderr, "Error: failed receiving message from server\n%s\n", SDLNet_GetError());
break;
}
if (DEBUG)
fprintf(stdout, "Notice: received '%d' bytes from server\n", recv_len);
player->x = message[0];
player->y = message[1];
player->colliding = message[2];
player->force = message[3];
}
return 0;
}
int connect_to_server(void) {
IPaddress ip;
if (SDLNet_ResolveHost(&ip, SERVER_ADDR, SERVER_PORT) != 0) {
fprintf(stderr, "Error: resolving host of server\n%s\n", SDLNet_GetError());
return -1;
}
server_socket = SDLNet_TCP_Open(&ip);
if (!server_socket) {
fprintf(stderr, "Error: failed opening socket to '%s' at '%d'\n%s\n", SERVER_ADDR, SERVER_PORT, SDLNet_GetError());
return -1;
}
SDL_CreateThread(handle_server, "server", NULL);
return 0;
}
int main(int argc, char *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "Error: could not initialize SDL\n%s\n", SDL_GetError());
return -1;
}
if (SDLNet_Init() != 0) {
fprintf(stderr, "Error: could not initialize SDL net\n%s\n", SDL_GetError());
return -1;
}
game.window = SDL_CreateWindow("03-network", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, WINDOW_FLAGS);
if (game.window == NULL) {
fprintf(stderr, "Error: could not create window\n%s\n", SDL_GetError());
return -1;
}
game.renderer = SDL_CreateRenderer(game.window, -1, RENDERER_FLAGS);
if (game.renderer == NULL) {
fprintf(stderr, "Error: could not create renderer\n%s\n", SDL_GetError());
return -1;
}
// load player animations
SDL_Texture *idle_animation = load_texture("assets/player/idle.png");
SDL_Texture *jump_animation = load_texture("assets/player/jump.png");
SDL_Texture *run_animation = load_texture("assets/player/run.png");
player = create_object(idle_animation, 4, 48);
if (connect_to_server() != 0)
return -1;
// game loop
while (GAME_RUNNING) {
double current_frame = SDL_GetTicks();
delta_time = current_frame - last_frame;
last_frame = current_frame;
prepare_scene();
handle_input();
// The reason why I don't do
// that much game development
// in my free time. Damn youuu ifs
if (player->colliding) {
if (game.left)
switch_animation(player, run_animation);
if (game.right)
switch_animation(player, run_animation);
if (!game.left && !game.right)
switch_animation(player, idle_animation);
if (game.jump) {
switch_animation(player, jump_animation);
uint8_t message[SERVER_MESSAGE_LEN];
message[0] = 2;
SDLNet_TCP_Send(server_socket, message, sizeof(uint8_t)*SERVER_MESSAGE_LEN);
}
}
if (game.left) {
player->flip = SDL_FLIP_HORIZONTAL;
uint8_t message[SERVER_MESSAGE_LEN];
message[0] = 1;
SDLNet_TCP_Send(server_socket, message, sizeof(uint8_t)*SERVER_MESSAGE_LEN);
}
if (game.right) {
player->flip = SDL_FLIP_NONE;
uint8_t message[SERVER_MESSAGE_LEN];
message[0] = 0;
SDLNet_TCP_Send(server_socket, message, sizeof(uint8_t)*SERVER_MESSAGE_LEN);
}
draw_object(player);
present_scene();
SDL_Delay(16);
}
SDL_DestroyWindow(game.window);
SDL_Quit();
return 0;
}

View File

@ -0,0 +1,40 @@
#ifndef STRUCTS_H
#define STRUCTS_H
#include <SDL2/SDL.h>
struct game {
SDL_Window *window;
SDL_Renderer *renderer;
int left;
int right;
int jump;
};
struct animation {
SDL_Texture *tilemap;
int resolution;
int skippable;
};
struct object {
SDL_Texture *texture;
int resolution; // of the texture/every tile
int x;
int y;
int scale;
Uint32 flip;
// animation
double animation_clock; // everytime it reaches 1, the texture switches to the next slide
double animation_speed; // how fast will the clock reach 1 with respect to delta_time
int animation_slide; // the current slide of the animation
int colliding;
int force;
};
#endif

4
03-network/script Normal file
View File

@ -0,0 +1,4 @@
Message:
* 10 Bytes long
* 0-1 byte: action
* 2-10 byte: additional data

View File

@ -0,0 +1,28 @@
CC=gcc
CFLAGS=`pkg-config --cflags sdl2 SDL2_net`
LDFLAGS=`pkg-config --libs sdl2 SDL2_net`
TARGET=server
SDIR=src
ADIR=assets
ODIR=build
SRC=$(shell find $(SDIR) -type f -name *.c)
OBJ=$(SRC:.c=.o)
all: $(TARGET)
.PHONY: default
$(TARGET): $(OBJ)
mkdir -p build
cp -rf $(ADIR) $(ODIR)/$(ADIR)
$(CC) -o $(ODIR)/$@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
run:
$(ODIR)/$(TARGET)
.PHONY: clean
clean:
rm -f $(ODIR)/$(TARGET) $(OBJ)

View File

@ -0,0 +1,3 @@
# Network - Server
Server-side code for 03-network

View File

@ -0,0 +1,20 @@
#ifndef DEFS_H
#define DEFS_H
#include <SDL2/SDL.h>
// MCS: Message Communication Standard
#define SERVER_MESSAGE_LEN 10
#define CLIENT_MESSAGE_LEN 4
// MCSA: Message Communication Standard Actions
#define RIGHT_MOVEMENT 0
#define LEFT_MOVEMENT 1
#define JUMP_MOVEMENT 2
#define CROUCH_MOVEMENT 3
// OC: Object Constants
#define GRAVITY 2
#define MOVEMENT_SPEED 10
#endif

View File

@ -0,0 +1,181 @@
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_net.h>
#include <SDL2/SDL_error.h>
#include "structs.h"
#include "defs.h"
#define PORT 9080
#define DEBUG 1
int objects_count = 0;
struct object **objects_map;
int handle_player(void *data) {
struct connection *connection_data = (struct connection *) data;
struct object *obj = objects_map[connection_data->obj_id];
memset(obj, 0, sizeof(struct object));
SDLNet_SocketSet set = SDLNet_AllocSocketSet(1);
if (set == NULL) {
fprintf(stderr, "Error: cannot allocate memory for a new socket set\n");
return -1;
}
int ret = SDLNet_TCP_AddSocket(set, connection_data->socket);
if (ret == -1) {
fprintf(stderr, "Error: max socket count in socket set reached\n");
return -1;
}
for (;;) {
char message[SERVER_MESSAGE_LEN];
int ready_sockets = SDLNet_CheckSockets(set, 10);
if (ready_sockets == -1) {
fprintf(stderr, "Error: cannot call select() system call with SDLNet_CheckSockets()\n");
continue;
}
if (ready_sockets == 0)
goto update_client;
int recv_len = SDLNet_TCP_Recv(connection_data->socket, message, SERVER_MESSAGE_LEN);
if (!recv_len) {
// player disconnected
fprintf(stderr, "Error: failed receiving message\n%s\n", SDLNet_GetError());
break;
}
if (DEBUG)
fprintf(stdout, "Notice: received '%s' and '%d' bytes\n", message, recv_len);
// parsing the message
uint8_t action = message[0];
switch (action) {
case RIGHT_MOVEMENT:
obj->x += MOVEMENT_SPEED;
break;
case LEFT_MOVEMENT:
obj->x -= MOVEMENT_SPEED;
break;
case JUMP_MOVEMENT:
obj->force -= MOVEMENT_SPEED * 2.5;
obj->colliding = 0;
break;
case CROUCH_MOVEMENT: // TODO
break;
default:
break;
}
update_client:
if (obj->y < 200) {
obj->force += GRAVITY;
} else {
obj->colliding = 1;
// obj->force = 0;
obj->y = 200;
}
// gravity
obj->y += obj->force;
// Update the client with information
// Note: the order OBVIOUSLY matters
int reply[] = {
obj->x,
obj->y,
obj->colliding,
obj->force
};
if (DEBUG)
fprintf(stdout, "Notice: new player properties (%d, %d, %d, %d)\n", reply[0], reply[1], reply[2], reply[3]);
SDLNet_TCP_Send(connection_data->socket, reply, sizeof(int)*CLIENT_MESSAGE_LEN);
}
SDLNet_FreeSocketSet(set);
SDLNet_TCP_Close(connection_data->socket);
free(obj);
free(data);
return 0;
}
void catch_alarm(int sig) {
fprintf(stdout, "Notice: force stopping server...\n");
exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
signal(SIGINT, catch_alarm);
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "Error: could not initialize SDL\n%s\n", SDL_GetError());
return -1;
}
if (SDLNet_Init() != 0) {
fprintf(stderr, "Error: could not initialize SDL net\n%s\n", SDL_GetError());
return -1;
}
IPaddress ip;
if (SDLNet_ResolveHost(&ip, NULL, PORT) == -1) {
fprintf(stderr, "Error: failed resolving host \n%s\n", SDL_GetError());
return -1;
}
TCPsocket server = SDLNet_TCP_Open(&ip);
if (!server) {
fprintf(stderr, "Error: failed opening socket at %d\n%s\n", PORT, SDL_GetError());
return -1;
}
for (;;) {
TCPsocket client = SDLNet_TCP_Accept(server);
if (!client) {
SDL_Delay(100);
continue;
}
fprintf(stdout, "Notice: accepted a connection from client!\n");
objects_map = (struct object **) realloc(objects_map, sizeof(struct object *)*(objects_count+1));
if (objects_map == NULL) {
fprintf(stderr, "Error: failed reallocating objects hash map\n");
return -1;
}
struct object *new_object = (struct object *) calloc(1, sizeof(struct object));
if (new_object == NULL) {
fprintf(stderr, "Error: failed allocating memory for new object\n");
return -1;
}
objects_map[objects_count] = new_object;
struct connection *new_connection = (struct connection *) calloc(1, sizeof(struct connection));
if (new_connection == NULL) {
fprintf(stderr, "Error: failed allocating memory for new connection object\n");
return -1;
}
new_object->id = objects_count;
new_connection->socket = client;
new_connection->obj_id = objects_count;
objects_count++;
SDL_CreateThread(handle_player, "client", new_connection);
}
return 0;
}

View File

@ -0,0 +1,20 @@
#ifndef STRUCTS_H
#define STRUCTS_H
#include <SDL2/SDL_net.h>
struct connection {
TCPsocket socket;
int obj_id;
};
struct object {
int id;
int x;
int y;
int colliding;
int force;
};
#endif