Skip to content

Commit

Permalink
movement, gravity, jumps, block collision
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmytro Lytovchenko committed Sep 30, 2011
1 parent ef928a2 commit 6eeddae
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 28 deletions.
2 changes: 2 additions & 0 deletions src/game_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ bool GameState_Play::Think( MyGame * game )
{
// give the World a chance to play its internal logic, like move monsters and animate things
m_world->Think();
// give the Player a chance to check keys, test powerup collisions etc
if( ! m_world->m_pause_flag ) m_player->Think();

// TODO: Design a way to return events from the World::Think, like inform about player taking damage/dying

Expand Down
170 changes: 156 additions & 14 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,61 @@
#include "player.h"
#include "world.h"

#undef min
#include <algorithm>


Player::Player()
: m_lives(INITIAL_LIVES_COUNT)
, m_position(0, 0, World::CELL_BOX_SIZE, World::CELL_BOX_SIZE)
, m_position(0, 0, World::CELL_BOX_SIZE-1, World::CELL_BOX_SIZE-1), m_speed(0,0)
, m_last_facing(FACING_RIGHT)
{
m_character_right[0] = m_sprite_manager.GetSprite( "textures/mario_r1.png" );
m_character_right[1] = m_sprite_manager.GetSprite( "textures/mario_r2.png" );
m_character_right[0][0] = m_sprite_manager.GetSprite( "textures/mario_r1.png" );
m_character_right[0][1] = m_sprite_manager.GetSprite( "textures/mario_r2.png" );
m_character_right[1][0] = m_sprite_manager.GetSprite( "textures/mario_l1.png" );
m_character_right[1][1] = m_sprite_manager.GetSprite( "textures/mario_l2.png" );

// this will not create another HGE, instead we will get the global
// unique HGE object which is already started
m_hge = hgeCreate( HGE_VERSION );
}


Player::~Player()
{
delete m_character_right[0];
delete m_character_right[1];
delete m_character_right[0][0];
delete m_character_right[0][1];
delete m_character_right[1][0];
delete m_character_right[1][1];
}


void Player::EnterWorld( World * w )
{
m_world = w;

// we need a simple way to determine player starting position
// we scan the world for '@' and use that as start
for( uint32_t r = 0; r < w->m_world_height; ++r ) {
for( uint32_t c = 0; c < w->m_world_width; ++c ) {
if( w->At(r,c) == World::WORLD_CELL_PLAYER_START )
{
// found the @ character, set player position
m_position.Set(
c * World::CELL_BOX_SIZE, r * World::CELL_BOX_SIZE,
(c+1) * World::CELL_BOX_SIZE - 1, (r+1) * World::CELL_BOX_SIZE - 1
);
MoveTo( c * World::CELL_BOX_SIZE, r * World::CELL_BOX_SIZE );
}
}
}
}


hgeVector Player::GetPos()
hgeRect Player::GetScreenPosition()
{
return hgeVector(m_position.x1, m_position.y1);
hgeRect spos = m_position;
spos.x1 -= m_world->m_camera_pos.x;
spos.x2 -= m_world->m_camera_pos.x;
spos.y1 -= m_world->m_camera_pos.y;
spos.y2 -= m_world->m_camera_pos.y;
return spos;
}


Expand All @@ -61,7 +77,7 @@ hgeSprite * Player::GetSprite()
// total of 2 frames, hence the modulo of 2
uint32_t f = (milliseconds / 333) % 2;

return m_character_right[f];
return m_character_right[m_last_facing][f];
}


Expand All @@ -70,7 +86,133 @@ void Player::Render( World * world )
hgeSprite * spr = GetSprite();
if( ! spr ) return;

hgeVector pos = GetPos();
hgeRect pos = GetScreenPosition();

spr->Render( pos.x - world->m_camera_pos.x, pos.y - world->m_camera_pos.y );
spr->Render( pos.x1, pos.y1 );

m_hge->Gfx_RenderLine( pos.x1, pos.y1, pos.x2, pos.y1 );
m_hge->Gfx_RenderLine( pos.x2, pos.y1, pos.x2, pos.y2 );
m_hge->Gfx_RenderLine( pos.x2, pos.y2, pos.x1, pos.y2 );
m_hge->Gfx_RenderLine( pos.x1, pos.y1, pos.x1, pos.y2 );
}


void Player::Think()
{
float delta = m_hge->Timer_GetDelta();

if( m_hge->Input_GetKeyState( HGEK_RIGHT )
|| m_hge->Input_GetKeyState( HGEK_D ) )
{
m_last_facing = FACING_RIGHT;
m_speed.x = BASE_MOVING_SPEED;
}
else if( m_hge->Input_GetKeyState( HGEK_LEFT )
|| m_hge->Input_GetKeyState( HGEK_A ) )
{
m_last_facing = FACING_LEFT;
m_speed.x = -BASE_MOVING_SPEED;
}
else {
// stop moving if neither right nor left pressed, but preserve facing
m_speed.x = 0;
}

float dy = m_speed.y * delta;
float dx = m_speed.x * delta;

bool solid_under_bottom_left = m_world->IsSolidAt( m_position.x1, m_position.y2+dy );
bool solid_under_bottom_right = m_world->IsSolidAt( m_position.x2, m_position.y2+dy );
bool standing_flag = solid_under_bottom_left || solid_under_bottom_right;

if( standing_flag )
{
if (m_hge->Input_GetKeyState( HGEK_W )
|| m_hge->Input_GetKeyState( HGEK_SPACE )
|| m_hge->Input_GetKeyState( HGEK_UP ))
{
m_speed.y = -JumpSpeed();
}
}

//-----------------------------------------------------------------
// Get speed affected by gravity if our feet are not standing firm
//-----------------------------------------------------------------

// gravity drags us down, man (to the limit of MAX_FALL_SPEED)
m_speed.y = std::min( m_speed.y + m_world->GravityAccel(), (float)MAX_FALL_SPEED );
dy = m_speed.y * delta;

// if we are standing firmly on feet
if (m_speed.y > 0 )
{
// stop falling
if( standing_flag )
{
m_speed.y = 0;
}
else {
hgeRect future_pos = m_position;
future_pos.y1 += m_speed.y * delta;
future_pos.y2 += m_speed.y * delta;
if( m_world->TestBlockCollisionAt( future_pos ) ) m_position = future_pos;
}
}
else
// jumping/flying up
if( m_speed.y < 0) {
// 2 is a magic number to allow sliding down/jumping through tight 1-block wide holes
bool solid_above_top_left = m_world->IsSolidAt( m_position.x1+2, m_position.y1-dy );
bool solid_above_top_right = m_world->IsSolidAt( m_position.x2-2, m_position.y1-dy );
bool hitting_the_ceiling = solid_above_top_left || solid_above_top_right;
if( hitting_the_ceiling ) {
// stop flying, hit the ceiling
m_speed.y = 0;
} else {
hgeRect future_pos = m_position;
future_pos.y1 += m_speed.y * delta;
future_pos.y2 += m_speed.y * delta;
if( m_world->TestBlockCollisionAt( future_pos ) ) m_position = future_pos;
}
}

// moving left, test if we hit the wall
if( m_speed.x < 0) {
bool solid_top_left = m_world->IsSolidAt( m_position.x1-dx, m_position.y1 );
bool solid_bottom_left = m_world->IsSolidAt( m_position.x1-dx, m_position.y2 );
bool hitting_left_wall = solid_top_left || solid_bottom_left;
if( hitting_left_wall ) {
// stop moving, hit the wall
m_speed.x = 0;
} else {
hgeRect future_pos = m_position;
future_pos.x1 += m_speed.x * delta;
future_pos.x2 += m_speed.x * delta;
if( m_world->TestBlockCollisionAt( future_pos ) ) m_position = future_pos;
}
}
else
// moving right, test if we hit the wall
if( m_speed.x > 0) {
bool solid_top_right = m_world->IsSolidAt( m_position.x2+dx, m_position.y1 );
bool solid_bottom_right = m_world->IsSolidAt( m_position.x2+dx, m_position.y2 );
bool hitting_right_wall = solid_top_right || solid_bottom_right;
if( hitting_right_wall ) {
// stop moving, hit the wall
m_speed.x = 0;
} else {
hgeRect future_pos = m_position;
future_pos.x1 += m_speed.x * delta;
future_pos.x2 += m_speed.x * delta;
if( m_world->TestBlockCollisionAt( future_pos ) ) m_position = future_pos;
}
}
}

void Player::MoveTo( float x, float y )
{
m_position.Set(
x, y,
x + World::CELL_BOX_SIZE - 1.0f,
y + World::CELL_BOX_SIZE - 1.0f );
}
43 changes: 36 additions & 7 deletions src/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,36 @@ class Player
// You can use acceleration to affect this value when player presses buttons
// to simulate inertia. Or you can write exact speeds when player presses buttons
// to do instant turns and direction changes.
hgeVertex m_speed;
hgeVector m_speed;

// pixels per second when player is moving (remember 64 is cell size)
static const int BASE_MOVING_SPEED = 120;

// Hack this value for infinite lives
int m_lives;
int m_lives;

// world we have entered, must not be NULL when game is active
World * m_world;

enum {
FACING_RIGHT = 0,
FACING_LEFT = 1
};

// make box slightly smaller for the player
//static const int PADDING_LEFT = 12;
//static const int PADDING_RIGHT = 12;
static const int MAX_FALL_SPEED = 300;

// set this to 0 to render sprite facing right, 1 to render sprite facing left
// used in GetSprite to return the correct facing
int m_last_facing;

SpriteManager m_sprite_manager;

hgeSprite * m_character_right[2];
hgeSprite * m_character_right[2][2];

HGE * m_hge;

public:
// A bounding box for character sprite used for rendering and collisions
Expand All @@ -45,11 +67,13 @@ class Player
// jumping.
// This can be modified to return different values depending if player picks up
// some bonus for higher jumps or eats some weakening mushroom
float JumpAccel() {
return 50.0f;
float JumpSpeed() {
return 420.0f;
}

hgeVector GetPos();
//hgeVector GetPos();
// returns position projected from world to screen coordinates
hgeRect GetScreenPosition();

// this one should reduce lives count
virtual void Die();
Expand All @@ -61,5 +85,10 @@ class Player
virtual void Render( World * world );

// called by gamestate when player enters the world and game begins
void EnterWorld( World * w );
virtual void EnterWorld( World * w );

// Here check keyboard
virtual void Think();

void MoveTo( float x, float y );
};
24 changes: 22 additions & 2 deletions src/world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ void World::Think()
m_camera_pos.x += d * 16;

// test if player was pushed out of screen fully (to be pushed out partially is allowed)
if (m_player->GetPos().x < CELL_BOX_SIZE) {
if ((m_player->m_position.x2 - m_camera_pos.x) < CELL_BOX_SIZE)
{
m_player->Die();
m_pause_flag = true;
}
Expand Down Expand Up @@ -148,7 +149,7 @@ void World::Render()
CellType cell_contents = this->At( r, c );
switch( cell_contents )
{
case WORLD_CELL_SOLID:
case WORLD_CELL_WALL1:
// find position in world and render it
m_sprite_brick1->Render(
c * CELL_BOX_SIZE - m_camera_pos.x,
Expand All @@ -166,3 +167,22 @@ void World::Render()
(*i)->Render();
}
}


bool World::TestBlockCollisionAt( const hgeRect & rc )
{
// we simplify calculation by only testing 4 corners
// using as advantage the fact, that player has same size as world blocks
if( IsSolidAt(rc.x1, rc.y1) ) return false;
if( IsSolidAt(rc.x1, rc.y2) ) return false;
if( IsSolidAt(rc.x2, rc.y1) ) return false;
if( IsSolidAt(rc.x2, rc.y2) ) return false;

return true;
}


bool World::IsSolid( CellType contents )
{
return contents == WORLD_CELL_WALL1;
}
22 changes: 17 additions & 5 deletions src/world.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <list>

#include <hge.h>
#include <hgerect.h>
#include <hgevector.h>

// this is for standard integer types like uint32_t (very useful)
Expand Down Expand Up @@ -51,9 +52,6 @@ class World

HGE * m_hge;

// is the game running or paused (pause to animate player death for example)
bool m_pause_flag;

// this also delimits max world width in cells, increase if your world grows wider
// actual world will be as wide as the widest line in your level file
// -- this const contains no secret, moving it to public section
Expand All @@ -64,6 +62,9 @@ class World
object_list_t m_objects;

public:
// is the game running or paused (pause to animate player death for example)
bool m_pause_flag;

// This stores all loaded textures for the world, including blocks and creatures, but not player
SpriteManager m_sprite_manager;

Expand Down Expand Up @@ -92,7 +93,7 @@ class World
enum {
WORLD_CELL_EMPTY = ' ',
WORLD_CELL_PLAYER_START = '@',
WORLD_CELL_SOLID = '#',
WORLD_CELL_WALL1 = '#',
WORLD_CELL_MONEY = '$'
};

Expand All @@ -118,11 +119,22 @@ class World
// fall harder or slower, make it negative to change gravity vector up
// This can be modified to return different values depending if player picks up something
// or flips a gravity switch (if you wish)
virtual float GravityAccel() { return 20.0f; }
virtual float GravityAccel() { return 10.0f; }

// Draws the world in its current state. This function must not call or perform
// any other game logic, only drawing
virtual void Render();

// tests if rect rc is allowed to be in the world and does not collide a solid block
virtual bool TestBlockCollisionAt( const hgeRect & rc );

// tests if cell type is solid or pass-through
inline bool IsSolidAt( float x, float y ) {
return IsSolid(
At( (uint32_t)(y / CELL_BOX_SIZE), (uint32_t)(x / CELL_BOX_SIZE) )
);
}
virtual bool IsSolid( CellType contents );
};


Expand Down

0 comments on commit 6eeddae

Please sign in to comment.