#include "Player.h" #include "debug.h" #include "dictionary.h" #include "input_axis.h" #include "physics_world.h" #include "PlayerStates.h" #include "Layers.h" #include "program.h" #include "variant.h" const Vector PLAYER_SPEED = { 1.0f, 0.50f }; static const float PLAYER_INPUT_RATE = 1.f/15.f; START_REFLECT(Player) REFLECT_TYPECLASS(Player, Drop) REFLECT_TYPECLASS(Player, PhysicsEntity) REFLECT_TYPECLASS(Player, BehaviourEntity) REFLECT_TYPECLASS(Player, Transformable) END_REFLECT(Player) impl_Drop_for(Player, DestroyPlayer ) impl_BehaviourEntity_for(Player, PlayerStart, PlayerUpdate, PlayerDraw, PlayerGetDepth ) impl_Transformable_for(Player, PlayerGetTransform ) impl_PhysicsEntity_for(Player, PlayerGetRigidBody, PlayerOnCollision, PlayerOnOverlap ) static inline void Internal_PlayerInitInput(Player* self) { playerinput_add(self->playerInput, CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_A, SDL_SCANCODE_D)), (InputDelegateFn)PlayerHorizontalInput); playerinput_add(self->playerInput, CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_S, SDL_SCANCODE_W)), (InputDelegateFn)PlayerVerticalInput); playerinput_add(self->playerInput, KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_SPACE)), (InputDelegateFn)PlayerJumpInput); playerinput_add(self->playerInput, KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_J)), (InputDelegateFn)PlayerLightAttackInput); playerinput_add(self->playerInput, KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_K)), (InputDelegateFn)PlayerHeavyAttackInput); } Player* MakePlayer() { Player* self = malloc(sizeof(Player)); ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for new Player instance"); *self = (Player) { .transform = IdentityTransform, .height = 0.f, .rigidbody = NULL, .physicsCollider = NULL, .hitbox = NULL, .verticalVelocity = 0.f, .playerInput = playerinput_new(self, -1), .moveInput = ZeroVector, .attackInput = 0, .animationTriggers = 0, .facing = 1, .sprite = sprite_new_empty(), .idle = NULL, .walk = NULL, .jump = NULL, .jab_a = NULL, .jab_b = NULL, .kick_a = NULL, .slash = NULL, .air_heavy = NULL, .slide = NULL, .animationStateMachine = NULL, .pushInputTimer = 0.f, .inputLog = list_from_type(PlayerInputFrame), }; self->rigidbody = rigidbody_make(Player_as_PhysicsEntity(self)); // physics collider used for movement self->physicsCollider = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){ MakeVector(-0.2f, -0.065f), MakeVector( 0.2f, -0.065f), MakeVector( 0.2f, 0.065f), MakeVector(-0.2f, 0.065f) }, 4), 0, PHYSICS_LAYER_CHARACTERS, PHYSICS_LAYER_DEFAULT); // hitbox is used for combat only self->hitbox = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){ MakeVector(-0.1f, -0.9f), MakeVector( 0.1f, -0.9f), MakeVector( 0.1f, 0.00f), MakeVector(-0.1f, 0.00f) }, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // empty mask and overlap means this does not detect collisions itself sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f)); // load and configure animations self->idle = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop); self->walk = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 5.f, LoopMode_Loop); self->jump = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jumping.png", IVectorFrom(512)), 1.f, LoopMode_Stop); self->jab_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_A.png", IVectorFrom(512)), 10.f, LoopMode_Stop); self->jab_b = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_B.png", IVectorFrom(512)), 10.f, LoopMode_Stop); self->kick_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Kick_A.png", IVectorFrom(512)), 12.f, LoopMode_Stop); self->slash = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slash.png", IVectorFrom(512)), 12.f, LoopMode_Stop); self->air_heavy = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Air_Heavy.png", IVectorFrom(512)), 10.f, LoopMode_Stop); self->slide = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slide.png", IVectorFrom(512)), 1.f, LoopMode_Loop); self->animationStateMachine = state_machine_init(self, PlayerIdle()); return self; } BehaviourEntity SpawnPlayer(Dictionary* args) { Variant arg; Player* self = MakePlayer(); Internal_PlayerInitInput(self); if(dictionary_try_get(args, "position", &arg) && arg.type == Variant_Vector) self->transform.position = arg.as_vector; return Player_as_BehaviourEntity(self); } void DestroyPlayer(Player* self) { // deregister and free physics components physics_world_remove_entity(Player_as_PhysicsEntity(self)); collider_destroy(self->physicsCollider); rigidbody_destroy(self->rigidbody); playerinput_drop(self->playerInput); // erase animations animation_sprite_destroy(self->idle); animation_sprite_destroy(self->walk); animation_sprite_destroy(self->jump); animation_sprite_destroy(self->jab_a); animation_sprite_destroy(self->jab_b); animation_sprite_destroy(self->kick_a); animation_sprite_destroy(self->air_heavy); animation_sprite_destroy(self->slide); sprite_destroy(self->sprite); state_machine_destroy(self->animationStateMachine); } void PlayerStart(Player* self) { animation_sprite_play_from(self->currentAnimation, 0.0f); } static void PlayerPushInput(Player* self) { // the current input state PlayerInputFrame state = { .time = game_time(), .direction = self->moveInput, .light = self->attackInput == 1, .heavy = self->attackInput == 2, .jump = self->jumpInput, .facing = self->facing, }; // push onto the end of the log list_add(&self->inputLog, &state); // erase oldest input from the log if(self->inputLog.len >= 5) list_erase(&self->inputLog, 0); // log current input state LOG_INFO("%f %f, L:%x, H:%x, J:%x", state.direction.x, state.direction.y, state.light, state.heavy, state.jump); } void PlayerUpdate(Player* self, float deltaTime) { // update state machine state_machine_update(self->animationStateMachine, deltaTime); // update gravity self->height += self->verticalVelocity * deltaTime; if(self->height <= 0.f) self->height = 0.f; } void PlayerDraw(Player* self) { // create a new transform that adjusts the "ground" transform to reflect distance from the ground as well Transform trans = self->transform; trans.position.y -= self->height; animation_sprite_draw(self->currentAnimation, &trans); } void PlayerHorizontalInput(Player* self, InputEvent value) { self->moveInput.x = value.as_float; PlayerPushInput(self); } void PlayerVerticalInput(Player* self, InputEvent value) { self->moveInput.y = -value.as_float; PlayerPushInput(self); } void PlayerJumpInput(Player* self, InputEvent value) { if(value.as_bool) { self->jumpInput = value.as_bool; PlayerPushInput(self); } } void PlayerLightAttackInput(Player* self, InputEvent value) { if(value.as_bool) { self->attackInput = 1; PlayerPushInput(self); } } void PlayerHeavyAttackInput(Player* self, InputEvent value) { if(value.as_bool) { self->attackInput = 2; PlayerPushInput(self); } } void PlayerOnCollision(Player* self, Collision collision) {} void PlayerOnOverlap(Player* self, Collider* other) {} Transform* PlayerGetTransform(Player* self) { return &self->transform; } RigidBody* PlayerGetRigidBody(Player* self) { return self->rigidbody; } PlayerInputFrame* PlayerInputHistory(Player* self) { return (PlayerInputFrame*)self->inputLog.data; } int PlayerInputIsQuarterCircleForward(Player* self) { // at least four inputs have to be logged for a valid quartercircle // (three directional, one button, in that order) if(self->inputLog.len < 4) return 0; PlayerInputFrame* history = PlayerInputHistory(self); // the most recent input (assumed to be a button input) const size_t last = self->inputLog.len-1; // the forward direction at the assumed start of the action const float forward = history[last-3].facing; if(game_time() - history[last-2].time > 0.225f) return 0; // check if the three inputs before the most recent input are a quartercircle towards the facing direction at last-3 (three before current) // current (history[last]) is assumed to be a button input return veqf(history[last-3].direction, MakeVector(0.f, 1.f)) && veqf(history[last-2].direction, MakeVector(forward, 1.f)) && veqf(history[last-1].direction, MakeVector(forward, 0.f)); }