#include "raylib.h" #include "raymath.h" #if defined(PLATFORM_WEB) #include #endif typedef enum { O = 0, // nothing //P = 1, // player t = 2, // tree T = 3, // troll area B = 4, // bandit area T_B = 5, // troll base B_B = 6, // bandit base //G = 7, // gate (exit) } MapSymbol; typedef enum { AA_IDLE = 0, AA_CHARGE = 1, AA_ATTACK = 2, } AttackAnimState; typedef enum { T_IDLE = 0, T_FOLLOW = 1, T_AGGRO = 2, } TrollState; typedef enum { HA_IDLE = 0, HA_PLAY = 1, } HitAnimState; typedef struct ScuffString { char *buffer; int len; int capacity; } String; typedef struct State { // how much scaling to apply from original asset resolution float render_scale; // game positions bool is_player_hit; Vector2 pixels_per_gridbox; Vector2 mouse_position; Vector2 player_position; Color player_tint; // @note: normally when we place a texture, the coordinates // are the top left coordinates. This will cause a disconnect // when moving the player with a point and click motion. // To fix that we calculate a new position so the player_coordinates // are in the center. This will make it so when the player clicks on the // ground somewhere, the character sprite will be properly centered on that point. Rectangle player_render_rect; Vector2 player_position_target; Vector2 bandit_position; // troll state int troll_state; float troll_move_delta; Vector2 troll_position; Vector2 troll_last_player_position; Vector2 troll_move_dir; Vector2 troll_position_target; Rectangle troll_render_rect; Color troll_tint_base; Color troll_tint; // - troll attack bool troll_anim_attack_play; int troll_attack_anime_state; double t_troll_charge_anim_start; float t_troll_charge_anim_duration; float t_troll_anim_freq; float troll_attack_offset_max; float t_troll_attack_anim_start; float t_troll_attack_anim_duration; // player movement bool is_player_move; Vector2 player_move_dir; // @todo // direction player is being hit from, incorporate that into the gameplay code // For now, just update the player_move_dir to the player_hit_dir // Vector2 player_hit_dir; float player_move_delta; // amount player will move // target position player will move to // 1. the player can move intentionally, in which case carry forward from player position target // 2. the player can move from feedback of being hit, NOT IMPLEMENTED YET int player_hit_anim_state; float player_hit_anim_speed; float t_player_hit_anim_duration; float t_player_hit_anim_start; Vector2 player_move_target; // game sprites Texture2D *grass_sprite; Texture2D *player_sprite; Texture2D *troll_sprite; Texture2D *troll_weapon_sprite; Texture2D *bandit_sprite; Texture2D *bandit_weapon_sprite; Texture2D *troll_area_floor_sprite; Texture2D *bandit_area_floor_sprite; Texture2D *troll_base_sprite; Texture2D *bandit_base_sprite; String *action_log; } State; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- const int screenWidth = 1280; const int screenHeight = 1024; State mega_state = {}; // layer ground int floor_grid_map[8][8] = {{O, O, O, O, O, O, O, O}, {O, O, O, O, T, T, T, T}, {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}, {B, B, B, B, T, T, T, T}}; // vegetation grid int floor_cover_grid_map[8][8] = {{O, O, O, O, O, O, O, O}, {O, O, O, O, O, O, O, O}, {O, O, O, O, O, O, O, O}, {O, O, O, O, O, O, O, O}, {O, O, O, O, O, O, O, T_B}, {O, B_B, O, O, O, O, O, O}, {O, O, O, O, O, O, O, O}, {O, O, O, O, O, O, O, O}}; Vector2 grid_dims = {8, 8}; //---------------------------------------------------------------------------------- // Module functions declaration //---------------------------------------------------------------------------------- void UpdateDrawFrame(); // Update and Draw one frame int main(void) { InitWindow(screenWidth, screenHeight, "raylib browser example"); SetTargetFPS(60); Texture2D grass_sprite = LoadTexture("./assets/dungeon/floor/grass/grass_flowers_blue_1_new.png"); Texture2D player_sprite = LoadTexture("./assets/player/base/human_male.png"); Texture2D troll_sprite = LoadTexture("./assets/monster/hill_giant_old.png"); Texture2D bandit_sprite = LoadTexture("./assets/monster/unique/maurice_new.png"); Texture2D troll_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_northeast_new.png"); Texture2D bandit_area_floor_sprite = LoadTexture("./assets/dungeon/floor/dirt_east_new.png"); Texture2D bandit_base_sprite = LoadTexture("./assets/dungeon/floor/hive_3.png"); Texture2D troll_base_sprite = LoadTexture("./assets/dungeon/floor/dirt_2_old.png"); Texture2D troll_weapon_sprite = LoadTexture("./assets/item/weapon/club_old.png"); mega_state.render_scale = 2.0f; mega_state.grass_sprite = &grass_sprite; mega_state.player_sprite = &player_sprite; mega_state.troll_sprite = &troll_sprite; mega_state.bandit_sprite = &bandit_sprite; mega_state.troll_area_floor_sprite = &troll_area_floor_sprite; mega_state.bandit_area_floor_sprite = &bandit_area_floor_sprite; mega_state.bandit_base_sprite = &bandit_base_sprite; mega_state.troll_base_sprite = &troll_base_sprite; mega_state.troll_weapon_sprite = &troll_weapon_sprite; mega_state.player_position = (Vector2){50.0f, 50.0f}; mega_state.player_hit_anim_speed = 50.0f; // pixels per second; mega_state.t_player_hit_anim_duration = 0.2f; mega_state.player_render_rect = (Rectangle){.x=0,.y=0,.width=player_sprite.width,.height=player_sprite.height*mega_state.render_scale}; mega_state.player_move_delta = 5.0f; mega_state.pixels_per_gridbox = (Vector2){screenWidth/grid_dims.x, screenHeight/grid_dims.y}; mega_state.player_tint = (Color){255, 255, 255, 255}; mega_state.troll_state = T_IDLE; mega_state.bandit_position = (Vector2){64.0f, 800.0f}; mega_state.troll_position = (Vector2){1024.0f, 600.0f}; mega_state.troll_move_delta = 2.0f; mega_state.t_troll_charge_anim_duration = 1.0f; mega_state.troll_tint_base = (Color){255, 255, 255, 255}; mega_state.troll_tint = mega_state.troll_tint_base; mega_state.t_troll_attack_anim_duration = 0.4f; mega_state.troll_attack_offset_max = 100.0f; mega_state.troll_render_rect = (Rectangle){.x=0,.y=0,.width=troll_sprite.width*mega_state.render_scale,.height=troll_sprite.height*mega_state.render_scale}; char *str_buffer = (char*)MemAlloc(512*sizeof(char)); mega_state.action_log = &(String){.buffer=str_buffer, .capacity=512, .len=0}; #if defined(PLATFORM_WEB) emscripten_set_main_loop(UpdateDrawFrame, 0, 1); #else SetTargetFPS(60); while(!WindowShouldClose()) { UpdateDrawFrame(); } #endif CloseWindow(); return 0; } void DrawMapFloorTiles(float scale) { // @note: testing this, might need lighting Color BlackTint = {128,128,128,255}; Texture2D grass = *mega_state.grass_sprite; float winc = (float)grass.width * scale; float hinc = (float)grass.height * scale; Vector2 grid_pos = {0, 0}; for (int ypos=0; yposwidth; src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.troll_weapon_sprite->width, .height=mega_state.troll_weapon_sprite->height}; dest_rect = (Rectangle){.x=troll_weapon_centered.x, .y=troll_weapon_centered.y, .width=0.5f*scale*mega_state.troll_weapon_sprite->width, .height=0.5f*scale*mega_state.troll_weapon_sprite->height}; DrawTexturePro(*mega_state.troll_weapon_sprite, src_rect, dest_rect, (Vector2){0,0}, 0, RAYWHITE); // - troll sprite DrawTextureEx(*mega_state.troll_sprite, (Vector2){mega_state.troll_render_rect.x, mega_state.troll_render_rect.y}, 0, scale, mega_state.troll_tint); // draw collision box DrawRectangleLines(mega_state.troll_render_rect.x, mega_state.troll_render_rect.y, mega_state.troll_render_rect.width, mega_state.troll_render_rect.height, RED); // draw Bandit Vector2 bandit_center = GetSpriteCenterPosition(*mega_state.bandit_sprite, mega_state.bandit_position, scale); src_rect = (Rectangle){.x=0, .y=0, .width=-mega_state.bandit_sprite->width, .height=mega_state.bandit_sprite->height}; dest_rect = (Rectangle){.x=bandit_center.x, .y=bandit_center.y, .width=scale*mega_state.bandit_sprite->width, .height=scale*mega_state.bandit_sprite->height}; DrawTexturePro(*mega_state.bandit_sprite, src_rect, dest_rect, (Vector2){0,0}, 0, (Color){255, 128, 128, 255}); // draw player Vector2 player_center = GetSpriteCenterPosition(*mega_state.player_sprite, mega_state.player_position, scale); DrawTextureEx(*mega_state.player_sprite, player_center, 0, scale, mega_state.player_tint); //DrawTextureEx(*mega_state.player_sprite, (Vector2){mega_state.player_render_rect.x, mega_state.player_render_rect.y}, 0, scale, RAYWHITE); DrawRectangleLines(mega_state.player_render_rect.x, mega_state.player_render_rect.y, mega_state.player_render_rect.width, mega_state.player_render_rect.height, RED); } void UpdateDrawFrame(void) { BeginDrawing(); { // INPUT mega_state.mouse_position = GetMousePosition(); if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mega_state.player_hit_anim_state != HA_PLAY) { mega_state.is_player_move = true; mega_state.player_position_target = mega_state.mouse_position; mega_state.player_move_dir = Vector2Normalize(Vector2Subtract(mega_state.player_position_target, mega_state.player_position)); } if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { // @debug: cleaning up troll collision state mega_state.troll_attack_anime_state = AA_IDLE; mega_state.troll_tint = WHITE; mega_state.is_player_hit = false; } // PROCESSING if (mega_state.player_hit_anim_state == HA_PLAY) { float anim_length = GetTime() - mega_state.t_player_hit_anim_start; float ft = GetFrameTime(); float pixel_per_second_per_ft = ft*mega_state.player_hit_anim_speed/mega_state.t_player_hit_anim_duration; mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, pixel_per_second_per_ft)); mega_state.player_tint = (Color){255, 128, 128, 255}; if (anim_length >= mega_state.t_player_hit_anim_duration) { mega_state.player_hit_anim_state = HA_IDLE; mega_state.player_tint = (Color){255, 255, 255, 255}; } } if (mega_state.is_player_move) { mega_state.player_position = Vector2Add(mega_state.player_position, Vector2Scale(mega_state.player_move_dir, mega_state.player_move_delta)); if (Vector2Distance(mega_state.player_position, mega_state.player_position_target) <= 5.0f) { mega_state.is_player_move = false; } } // calculate center positions Vector2 player_centered = GetRenderCenterFromTL(mega_state.player_render_rect, mega_state.player_position); mega_state.player_render_rect.x = player_centered.x; mega_state.player_render_rect.y = player_centered.y; // process enemies detecting the player // troll // get player location on map // map_world_to_grid Vector2 grid_pos = Vector2Divide(Vector2Multiply(mega_state.player_position, grid_dims), (Vector2){screenWidth, screenHeight}); int tile_at_player = floor_grid_map[(int)grid_pos.y][(int)grid_pos.x]; bool in_troll_area = true; bool troll_persue_player = true; if (tile_at_player != T) { in_troll_area = false; if (mega_state.troll_state != T_AGGRO) { mega_state.troll_attack_anime_state = AA_IDLE; troll_persue_player = false; } } float player_troll_dist = Vector2Distance(mega_state.player_position, mega_state.troll_position); bool troll_detect = false; bool troll_attack_start = false; float slow_multiplier = mega_state.troll_attack_anime_state == AA_CHARGE ? 0.2f : 1.0f; if (player_troll_dist < 200 && troll_persue_player) { troll_detect = true; mega_state.troll_state = T_FOLLOW; if (player_troll_dist < 80 && mega_state.troll_attack_anime_state == AA_IDLE) { troll_attack_start = true; } } if (troll_detect && mega_state.troll_attack_anime_state != AA_ATTACK) { mega_state.troll_last_player_position = mega_state.player_position; mega_state.troll_position_target = mega_state.troll_last_player_position; mega_state.troll_move_dir = Vector2Normalize(Vector2Subtract(mega_state.troll_position_target, mega_state.troll_position)); if (player_troll_dist > 10.0f) { // @note: this actually might not even be needed // move only if both are more than 10pixels apart. // it becomes jarring other wise mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, slow_multiplier*mega_state.troll_move_delta)); } } Vector2 troll_centered = GetSpriteCenterPosition(*mega_state.troll_sprite, mega_state.troll_position, mega_state.render_scale); mega_state.troll_render_rect.x = troll_centered.x; mega_state.troll_render_rect.y = troll_centered.y; if (troll_attack_start) { // start attack animation mega_state.troll_anim_attack_play = true; mega_state.t_troll_charge_anim_start = GetTime(); mega_state.troll_attack_anime_state = AA_CHARGE; } float charge_time = 0.0f; float charge_color_delta = 0.0f; if (mega_state.troll_attack_anime_state == AA_CHARGE) { // get color based on time float charge_progress = (GetTime() - mega_state.t_troll_charge_anim_start) / mega_state.t_troll_charge_anim_duration; mega_state.troll_tint.r = Clamp(mega_state.troll_tint_base.r - (charge_progress*mega_state.troll_tint_base.r), 0, 255); if (charge_progress >= 1.0f) { mega_state.troll_attack_anime_state = AA_ATTACK; mega_state.t_troll_attack_anim_start = GetTime(); } } else if (mega_state.troll_attack_anime_state == AA_ATTACK) { // maximum distance to attack jump in // mega_state.troll_attack_offset_max = 10.0f; // mega_state.troll_attack_anim_duration = 1.0f; float anim_length = GetTime() - mega_state.t_troll_attack_anim_start; float ft = GetFrameTime(); float pixel_per_second_per_ft = ft*mega_state.troll_attack_offset_max/mega_state.t_troll_attack_anim_duration; mega_state.troll_position = Vector2Add(mega_state.troll_position, Vector2Scale(mega_state.troll_move_dir, pixel_per_second_per_ft)); mega_state.troll_tint = (Color){255, 128, 255, 255}; // get troll center position and check for collision with player // now check if colliding with player bool is_colliding = CheckCollisionRecs(mega_state.player_render_rect, mega_state.troll_render_rect); if (is_colliding) { mega_state.is_player_hit = true; } if (anim_length >= mega_state.t_troll_attack_anim_duration || is_colliding) { mega_state.troll_attack_anime_state = AA_IDLE; mega_state.troll_tint = mega_state.troll_tint_base; if (is_colliding) { // make player get hit mega_state.player_hit_anim_state = HA_PLAY; mega_state.t_player_hit_anim_start = GetTime(); mega_state.player_move_dir = mega_state.troll_move_dir; mega_state.is_player_move = false; // disable walk check, stuff been thrown off } } } float player_bandit_delta = Vector2Distance(mega_state.player_position, mega_state.bandit_position); bool bandit_detect = false; if (player_bandit_delta < 200) { bandit_detect = true; } // RENDERING ClearBackground(RAYWHITE); DrawMapFloorTiles(2.0f); DrawFloorCoverTiles(2.0f); DrawCharacters(2.0f); // do text rendering at the end // draw mouse position for debugging if (mega_state.is_player_hit) { DrawText("player hit", 500, 20, 20, GREEN); } if (troll_detect) { DrawText("Detecting", mega_state.troll_position.x, mega_state.troll_position.y + mega_state.troll_sprite->height + 20, 20, GREEN); } if (bandit_detect) { DrawText("Detecting", mega_state.bandit_position.x, mega_state.bandit_position.y + mega_state.bandit_sprite->height + 20, 20, GREEN); } DrawText(TextFormat("FPS: %d", GetFPS()), 1200, 20, 20, GREEN); DrawText(TextFormat("Player Tile: %d", tile_at_player), 20, 20, 20, GREEN); } EndDrawing(); }