123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- #include "game_player.h"
- #include "SDL2/SDL.h"
- #include "sdl_helper.h"
- #include "game_map.h"
- #include "game_math.h"
- #define PLAYER_MULTIPLIER(p) (float) (p->level *0.2)
- #define PLAYER_JUMP(p) (-1 -p->level *0.2)
- #define PLAYER_SPEED_RUNNING(p) (0.1 +p->level *0.05)
- #define PLAYER_SPEED_MAX(p) (1 +p->level *0.1)
- #define PLAYER_BUMP_POWER(p) (2 +p->level *0.2)
- #define PLAYER_ATTACK_POWER(p) (5 +p->level *0.8)
- #define PLAYER_CAST_COOLDOWN(p) (30 -p->level *1)
- /* animation states - used to change frames on the player's texture
- * the texture is divined on the x axis by animation frames,
- * and on the y axis by animations
- */
- const int ANIMATION_IDLE = 0;
- const int ANIMATION_RUN = 1;
- const int ANIMATION_JUMP = 2;
- const int ANIMATION_FALL = 3;
- const int ANIMATION_CAST = 4;
- const int ANIMATION_ATTACK = 4;
- /* secret functions - not meant for the public's eyes
- */
- // cast is the action of the skill itself
- void game_player_cast(struct game_player *p) {
- /* currently the only skill is to increase the height the platform the player stands on
- * and the two surrrounding platforms
- */
- int platform_id = game_map_platform_id(p->map, p->rect.x);
- game_map_platform_rise(p->map, platform_id, -15);
- game_map_platform_rise(p->map, platform_id-1, -10);
- game_map_platform_rise(p->map, platform_id+1, -10);
- // wait ~1 second before casting again
- p->casting_cooldown = PLAYER_CAST_COOLDOWN(p);
- }
- // animate
- int game_player_animate(struct game_player *p) {
- /* advance animation frames
- * currently it does so every 10 frames, but this might change in the future
- */
- p->anim_count++;
- if (p->anim_count > 10) {
- // animate player's texture to next frame
- p->tex_rect.x += p->tex_size;
- // reset animation counter
- p->anim_count = 0;
- // end of animation - repeat
- if (p->tex_rect.x > p->tex_size *3) {
- // reset animation frame
- p->tex_rect.x = 0;
- // animation ended
- return 1;
- }
- } // player frame animation
- // animation keeps going
- return 0;
- } // animate
- // collision player with water
- int game_player_water_collide(struct game_player *p) {
- return p->rect.y +p->rect.h > game_map_water_y(p->map);
- }
- // collision player with water - deep enough to drown
- int game_player_water_drown(struct game_player *p) {
- return p->rect.y > game_map_water_y(p->map);
- }
- void game_player_movement(struct game_player *p) {
- /* handle movement
- * if user is moving left or right, move the velocity_x towards -1 or 1
- * the final speed of the character is its own speed times speec_counter
- * if user is not moving, slowly decelerate
- */
- switch (p->move) {
- // not moving - rest
- case MOVE_NONE:
- p->velocity_x *= 0.8;
- if (p->velocity_x < 0.1
- && p->velocity_x > -0.1) p->velocity_x = 0;
- break;
- // moving rightwards
- case MOVE_RIGHT:
- p->velocity_x += PLAYER_SPEED_RUNNING(p);
- if (p->velocity_x > PLAYER_SPEED_MAX(p)) p->velocity_x = PLAYER_SPEED_MAX(p);
- break;
- // moving leftwards
- case MOVE_LEFT:
- p->velocity_x -= PLAYER_SPEED_RUNNING(p);
- if (p->velocity_x < -PLAYER_SPEED_MAX(p)) p->velocity_x = -PLAYER_SPEED_MAX(p);
- break;
- }
- // temporary way of assigning the running animation based on speed
- if (p->velocity_x > 0 && p->state == IDLE) {
- p->tex_rect.y = p->tex_size *ANIMATION_RUN;
- }
- else if (p->velocity_x < 0 && p->state == IDLE) {
- p->tex_rect.y = p->tex_size *ANIMATION_RUN;
- }
- else if (p->state == IDLE) {
- p->tex_rect.y = p->tex_size *ANIMATION_IDLE;
- }
- /* movement & collision
- */
- if (p->velocity_x != 0) {
- // player is in the water, delay speed
- if (game_player_water_collide(p)) {
- p->velocity_x *= 0.8;
- }
- // movement
- p->rect.x += p->velocity_x *p->speed_max;
- // check collision against each player
- for (int i = 0; i < 2; i++) {
- // not on self
- if (&p->players[i] == p) continue;
- // actual collision
- if (game_math_collide(&p->players[i].rect, &p->rect)) {
- p->rect.x = p->players[i].rect.x +(p->velocity_x > 0 ? -p->rect.w : +p->rect.w);
- break;
- }
- }
- } // movement
- // moved to a new platform
- if (game_map_platform_id(p->map, p->rect.x) != p->platform_id) {
- // get new platform id and height
- int new_platform_id = game_map_platform_id(p->map, p->rect.x);
- int new_platform_y = game_map_platform_y(p->map, new_platform_id);
- // platform is above player, don't let them move there!
- if (new_platform_y < p->rect.y +p->rect.h) {
- // move player back
- if (new_platform_id > p->platform_id) {
- p->rect.x = (p->platform_id+1) *p->map->grid_width -1 +p->map->offset_x;
- }
- else {
- p->rect.x = p->platform_id *p->map->grid_width +p->map->offset_x;
- }
- // cancel player's speed
- p->velocity_x = 0;
- }
- // platform is below (or same level as) player, assign new platform to player
- else {
- p->platform_id = new_platform_id;
- p->state = FALL;
- }
- }
- }
- /* initialise player
- * for now initialise him on the top left part of the screen
- * with 48x48 size
- * not moving, not standing on a surface
- * initialise some sample speed and falling/jumping data
- */
- void game_player_init(struct game_player *p, struct game_map *m) {
- // player's size (width and height)
- p->tex_size = 48;
- // player's texture and rect
- p->tex = load_image("images/character_0.png");
- p->rect.x = 0;
- p->rect.y = 0;
- p->rect.w = p->tex_size;
- p->rect.h = p->tex_size;
- /* player's texture rect, should always have the same width and height,
- * the x and y should change to point to the specific animation/frame
- */
- p->tex_rect.x = 0;
- p->tex_rect.y = 0;
- p->tex_rect.w = p->tex_size;
- p->tex_rect.h = p->tex_size;
- // animation
- p->state = IDLE;
- p->anim_count = 0;
- p->looking_side = LOOKING_RIGHT;
- // movement
- p->move = MOVE_NONE;
- p->speed_max = 5;
- p->velocity_x = 0;
- p->velocity_y = 0;
- /* get the map and mark where the player is on the map
- * based on starting position
- */
- p->map = m;
- p->platform_id = game_map_platform_id(p->map, p->rect.x);
- // ai
- p->map_target = 0;
- // casting skill variables
- p->casting_cooldown = 0;
- // player's starting level
- p->level = 0;
- } // init
- /* the player when updates:
- * moves left/right
- * moves up/down with gravity
- */
- void game_player_update(struct game_player *p) {
- // casting is on cooldown, decrease it
- if (p->casting_cooldown > 0) p->casting_cooldown--;
- /* ideally the whole update function should use this switch
- * do something depending on its state
- */
- int platform_y = game_map_platform_y(p->map, p->platform_id);
- switch (p->state) {
- /* when attacking, move to the looking direction non-stop,
- * until animation runs out, or player collides with another player
- * or the map
- */
- case ATTACK:
- // when animation is over, fall to the platform
- if (game_player_animate(p)) {
- p->state = FALL;
- // a small velocity makes a nice after-attack bouncing effect
- p->velocity_y = -0.5;
- p->velocity_x = -1 *p->looking_side;
- }
- // actual movement towards looking direction
- p->rect.x += PLAYER_ATTACK_POWER(p) *p->looking_side;
- // check collision with other players (for now with magic numbers)
- for (int i = 1; i < 2; i++) {
- // check collision on x and y
- if (game_math_collide(&p->players[i].rect, &p->rect)) {
- p->rect.x = p->players[i].rect.x +(p->looking_side == LOOKING_RIGHT ? -p->rect.w -1 : p->rect.w +5);
- // if collision happens, stop attacking, move current player with a "bump" jump away
- p->state = FALL;
- p->velocity_y = -0.5;
- p->velocity_x = -1 *p->looking_side;
- // ideally, the targeted player is bounced away here
- game_player_bump(&p->players[i], p);
- // only first player matters
- break;
- }
- } // collision with players
- // moved to new platform
- int new_id;
- if ((new_id = game_map_platform_id(p->map, p->rect.x)) != p->platform_id) {
- // not colliding with it, use it as current platform
- if (p->rect.y +p->rect.h < game_map_platform_y(p->map, new_id)) {
- p->platform_id = new_id;
- p->state = FALL;
- }
- // colliding with it, stop, and attack it
- else {
- p->rect.x = game_map_platform_x(p->map, p->platform_id)
- +(new_id > p->platform_id?
- game_map_platform_width(p->map) : 0);
- p->state = FALL;
- p->velocity_y = -0.5;
- p->velocity_x = -1 *p->looking_side;
- // get attacking platform (either left or right)
- int target_platform = p->platform_id
- +(p->looking_side == LOOKING_RIGHT ? 1: -1);
- // attack platform
- game_map_platform_rise(p->map, target_platform, 10);
- }
- }
- // for now ignore the rest of update
- return;
- // player is sitting on the ground
- case IDLE:
- game_player_animate(p);
- // follow standing platform
- p->rect.y = platform_y -p->rect.h;
- // nullify speed for now
- p->velocity_y = 0;
- // movement
- game_player_movement(p);
- break;
- // player is jumping
- case JUMP:
- game_player_animate(p);
- /* gravity
- * as long as the player stands on a surface, nothing happens
- * if player is jumping, advance the jump counter, which goes
- * from 0 to -1, once its done, player stops jumping, and is now falling
- * because of gravity
- */
- p->velocity_y -= 0.4;
- if (p->velocity_y <= PLAYER_JUMP(p)) {
- p->state = FALL;
- }
- // animation is jumping
- p->tex_rect.y = p->tex_size *ANIMATION_JUMP;
- // move player up/down, if player goes below platform, they are now standing on it
- p->rect.y += p->velocity_y * 10;
- if (game_player_water_drown(p)) {
- p->state = DROWN;
- }
- else
- if (p->rect.y +p->rect.h > platform_y) {
- p->rect.y = platform_y -p->rect.h;
- p->velocity_y = 0;
- p->state = IDLE;
- }
- // collision on players
- for (int i = 0; i < 2; i++) {
- // not on self
- if (&p->players[i] == p) continue;
- // actual collision
- if (game_math_collide(&p->players[i].rect, &p->rect)) {
- p->rect.y = p->players[i].rect.y +(p->velocity_y > 0 ? -p->rect.h : p->rect.h);
- p->state = FALL;
- p->velocity_y = 0;
- break;
- }
- }
- // when jumping, the character still moves left/right
- game_player_movement(p);
- break;
- // player is falling
- case FALL:
- game_player_animate(p);
- // player keeps falling at increasing capped speed
- p->velocity_y += 0.2;
- if (p->velocity_y > 1) p->velocity_y = 1;
- if (p->velocity_y > 0) {
- // animation is falling
- p->tex_rect.y = p->tex_size *ANIMATION_FALL;
- }
- else {
- // animation is jumping
- p->tex_rect.y = p->tex_size *ANIMATION_JUMP;
- }
- // move player up/down, if player goes below platform, they are now standing on it
- p->rect.y += p->velocity_y * 10;
- if (game_player_water_drown(p)) {
- p->state = DROWN;
- }
- else
- if (p->rect.y +p->rect.h > platform_y) {
- p->rect.y = platform_y -p->rect.h;
- p->velocity_y = 0;
- p->state = IDLE;
- }
- // if player touches another player, step on them
- else {
- for (int i = 0; i < 2; i++) {
- // not on self
- if (&p->players[i] == p) continue;
- // actual collision
- if (game_math_collide(&p->players[i].rect, &p->rect)) {
- p->rect.y = p->players[i].rect.y +(p->velocity_y > 0 ? -p->rect.h : p->rect.h);
- p->velocity_y = 0;
- break;
- }
- }
- }
- // when falling, the character still moves left/right
- game_player_movement(p);
- break;
- // player is casting skill
- case CAST:
- // casting is done, apply effect and fall
- if (game_player_animate(p)) {
- game_player_cast(p);
- p->state = FALL;
- }
- // don't move during casting
- p->move = MOVE_NONE;
- // follow standing platform
- p->rect.y = platform_y -p->rect.h;
- // nullify speed for now
- p->velocity_y = 0;
- break;
- /* players drown when falling deep enough in the water
- * they remain there, floating, forever
- */
- case DROWN:
- // for now do nothing
- return;
- default:
- break;
- }
- } // update
- /* draw player using its rect
- */
- void game_player_draw(struct game_player *p) {
- p->rect.x -= p->tex_size/2;
- if (p->looking_side == LOOKING_RIGHT) {
- p->tex_rect.x += 192; // 4 frames * 48 pixels = 192
- }
- SDL_RenderCopy(ren, p->tex, &p->tex_rect, &p->rect);
- if (p->looking_side == LOOKING_RIGHT) {
- p->tex_rect.x -= 192;
- }
- p->rect.x += p->tex_size/2;
- }
- /* movement
- */
- void game_player_moveleft(struct game_player *p) {
- // some states take control away
- if (p->state == CAST || p->state == DROWN || p->state == ATTACK) return;
- p->move = MOVE_LEFT;
- p->looking_side = LOOKING_LEFT;
- }
- void game_player_moveright(struct game_player *p) {
- // some states take control away
- if (p->state == CAST || p->state == DROWN || p->state == ATTACK) return;
- p->move = MOVE_RIGHT;
- p->looking_side = LOOKING_RIGHT;
- }
- void game_player_movestop(struct game_player *p) {
- p->move = MOVE_NONE;
- }
- /* jump
- */
- void game_player_jump(struct game_player *p) {
- if (p->state == IDLE) {
- p->state = JUMP;
- }
- }
- // player's special skill
- void game_player_useskill(struct game_player *p) {
- // skills usage:
- // only on platform
- // can't use while in water
- // can't use when in cooldown
- if (p->state != IDLE
- || game_player_water_collide(p)
- || p->casting_cooldown > 0) return;
- p->state = CAST;
- p->anim_count = 0;
- p->tex_rect.x = 0;
- p->tex_rect.y = p->tex_size *ANIMATION_CAST;
- } // use skill
- /* player's attack
- * change state (for behaviour) and texture rect (to animate properly)
- */
- void game_player_attack(struct game_player *p) {
- // attack is only allowed while player is on the platform (not doing something else)
- if (p->state != IDLE) {
- return;
- }
- p->state = ATTACK;
- p->anim_count = 0;
- p->tex_rect.x = 0;
- p->tex_rect.y = p->tex_size *ANIMATION_ATTACK;
- } // attack
- // ai behaviour
- void game_player_ai(struct game_player *p) {
- /* find the tallest map platform
- * is iterated platform taller?
- * then player's target is that platform now
- */
- for (int i = 0; i < 10; i++) {
- if (game_map_platform_y(p->map, i) < game_map_platform_y(p->map, p->map_target)) {
- p->map_target = i;
- }
- }
- // find target platform's x
- int target_x = game_map_platform_x(p->map, p->map_target) +p->map->grid_width/2;
- // approach it
- if (p->rect.x > target_x) {
- game_player_moveleft(p);
- }
- else {
- game_player_moveright(p);
- }
- // if too close to target, don't do anything else
- if (p->rect.x -target_x > -p->map->grid_width/3
- && p->rect.x -target_x < p->map->grid_width/3) {
- game_player_movestop(p);
- game_player_useskill(p);
- return;
- }
- /* if standing on platform
- * and target platform is taller
- * jump
- */
- if (p->state == IDLE
- && game_map_platform_y(p->map, p->map_target) < game_map_platform_y(p->map, p->platform_id)) {
- game_player_jump(p);
- }
- } // ai
- void game_player_bump(struct game_player *p, struct game_player *p2) {
- int from_direction = p2->looking_side;
- p->velocity_x = PLAYER_BUMP_POWER(p2) *from_direction;
- p->velocity_y = -1;
- p->state = FALL;
- p->looking_side = from_direction == LOOKING_LEFT ? LOOKING_RIGHT : LOOKING_LEFT;
- }
- // collision data
- int game_player_left(struct game_player *p) { return p->rect.x; }
- int game_player_top (struct game_player *p) { return p->rect.y; }
- int game_player_right (struct game_player *p) { return p->rect.x +p->rect.w; }
- int game_player_bottom(struct game_player *p) { return p->rect.y +p->rect.h; }
- int game_player_centerx(struct game_player *p) { return p->rect.x +p->rect.w/2; }
- int game_player_centery(struct game_player *p) { return p->rect.y +p->rect.h/2; }
- void game_player_offset(struct game_player *s, int x, int y) {
- s->rect.x += x;
- s->rect.y += y;
- }
- int game_player_get_state(struct game_player *s) { return s->state; }
|