Skip to content
Browse files

0 score, lives, game over, deadly spikes

  • Loading branch information...
1 parent bf1d7a7 commit 47d21ae5c97fdc71c274e27beec58921ff30645b Dmytro Lytovchenko committed
Showing with 171 additions and 41 deletions.
  1. +4 −4 bin/level_01.txt
  2. +2 −0 src/game.cpp
  3. +1 −1 src/game_state.cpp
  4. +99 −21 src/player.cpp
  5. +10 −0 src/player.h
  6. +25 −5 src/world.cpp
  7. +30 −10 src/world.h
View
8 bin/level_01.txt
@@ -3,10 +3,10 @@
# $ A A $$A A A ! #
# ################ $ ####### ############# ##########
# $ # ############ #### ### #
-# @ ######## A ## $ # #### #
-# ### $ ########### ### ########### #### # #
-# ######### $ A $ $ A $ # ### $ ### $ A #
-######################################^^^^^^^^^^^##########^^^^^^^######################^^^^^^^^##############################
+# @ ##^^#### A ## $ # #### #
+# # $ ##^######## ### ########### #### # #
+# ###^^#### $ A $ $ A $ # ### $ ### $ A #
+#############^###^^###################^^^^^^^^^^^##########^^^^^^^######################^^^^^^^^##############################
* * *
only first 9 lines of this file will be read
View
2 src/game.cpp
@@ -52,6 +52,8 @@ bool MyGame::RenderFunc()
"dt:%.3f\nFPS:%d",
m_game->m_hge->Timer_GetDelta(), m_game->m_hge->Timer_GetFPS()
);
+
+ // print with black shadow
m_game->m_font->SetColor(ARGB(255,0,0,0)); // black
m_game->m_font->printf( 7, 7, HGETEXT_LEFT, fps_text );
m_game->m_font->SetColor(ARGB(255,255,255,255)); // white
View
2 src/game_state.cpp
@@ -489,7 +489,7 @@ 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();
+ m_player->Think();
// TODO: Design a way to return events from the World::Think, like inform about player taking damage/dying
View
120 src/player.cpp
@@ -2,6 +2,7 @@
// controls keyboard interaction and game rules
#include "player.h"
#include "world.h"
+#include "game.h"
#undef min
#include <algorithm>
@@ -10,7 +11,7 @@
Player::Player()
: m_lives(INITIAL_LIVES_COUNT)
, m_position(0, 0, World::CELL_BOX_SIZE-1, World::CELL_BOX_SIZE-1), m_speed(0,0)
- , m_last_facing(FACING_RIGHT)
+ , m_last_facing(FACING_RIGHT), m_is_dead(false), m_money(0)
{
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" );
@@ -61,18 +62,12 @@ hgeRect Player::GetScreenPosition()
}
-void Player::Die()
-{
- // TODO: invent the way for player to inform the gamestate or the world about
- // level game restart or scroll back to allow player to continue
- m_lives--;
-}
-
-
hgeSprite * Player::GetSprite()
{
+ // As an exercise for the reader:
+ // Do not return animated frame, if horizontal speed is zero
+
uint32_t milliseconds = GetTickCount();
-
// we want frames to change every 333 msec from 0 to 1
// total of 2 frames, hence the modulo of 2
uint32_t f = (milliseconds / 333) % 2;
@@ -81,8 +76,33 @@ hgeSprite * Player::GetSprite()
}
+// MyGame pointer is only needed to access the font, otherwise we don't need it
void Player::Render( World * world )
{
+ MyGame * game = MyGame::m_game;
+
+ // print with black shadow
+ game->m_font->SetColor( ARGB(255, 0, 0, 0) );
+ game->m_font->printfb( 2, 2, World::SCREEN_WIDTH, 40, HGETEXT_RIGHT, "Score: %d Lives: %d", m_money, m_lives );
+ game->m_font->SetColor( ARGB(255, 255, 255, 255) );
+ game->m_font->printfb( 0, 0, World::SCREEN_WIDTH, 40, HGETEXT_RIGHT, "Score: %d Lives: %d", m_money, m_lives );
+
+ if( m_is_dead ) {
+ // test for >0 instead of >=0 because respawn requires at least 1 life to resurrect
+ if( m_lives > 0 ) {
+ game->m_font->printfb( 0, World::SCREEN_HEIGHT/2,
+ World::SCREEN_WIDTH, 40, HGETEXT_CENTER,
+ "You have died. Press ENTER to respawn." );
+ } else {
+ game->m_font->printfb( 0, World::SCREEN_HEIGHT/2,
+ World::SCREEN_WIDTH, 40, HGETEXT_CENTER,
+ "You have died. Game Over." );
+ }
+ // no rendering when we're dead
+ // should display blood and all those guts around the death place or the corpse
+ return;
+ }
+
hgeSprite * spr = GetSprite();
if( ! spr ) return;
@@ -99,6 +119,18 @@ void Player::Render( World * world )
void Player::Think()
{
+ if( m_is_dead ) {
+ // press ENTER when dead leads to respawn
+ if( m_hge->Input_GetKeyState( HGEK_ENTER ) ) {
+ Respawn();
+ }
+ return;
+ }
+ if( m_world->m_pause_flag ) {
+ // check for pause key to unpause
+ return;
+ }
+
float delta = m_hge->Timer_GetDelta();
if( m_hge->Input_GetKeyState( HGEK_RIGHT )
@@ -121,8 +153,8 @@ void Player::Think()
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 solid_under_bottom_left = m_world->IsSolidAtXY( m_position.x1, m_position.y2+dy );
+ bool solid_under_bottom_right = m_world->IsSolidAtXY( m_position.x2, m_position.y2+dy );
bool standing_flag = solid_under_bottom_left || solid_under_bottom_right;
if( standing_flag )
@@ -143,10 +175,10 @@ void Player::Think()
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 we are falling
if (m_speed.y > 0 )
{
- // stop falling
+ // if we were falling but now we stand firmly on feet - stop falling
if( standing_flag )
{
m_speed.y = 0;
@@ -162,8 +194,8 @@ void Player::Think()
// 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 solid_above_top_left = m_world->IsSolidAtXY( m_position.x1+2, m_position.y1-dy );
+ bool solid_above_top_right = m_world->IsSolidAtXY( 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
@@ -179,8 +211,8 @@ void Player::Think()
// 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 solid_top_left = m_world->IsSolidAtXY( m_position.x1-dx, m_position.y1 );
+ bool solid_bottom_left = m_world->IsSolidAtXY( 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
@@ -195,8 +227,8 @@ void Player::Think()
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 solid_top_right = m_world->IsSolidAtXY( m_position.x2+dx, m_position.y1 );
+ bool solid_bottom_right = m_world->IsSolidAtXY( 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
@@ -208,6 +240,22 @@ void Player::Think()
if( m_world->TestBlockCollisionAt( future_pos ) ) m_position = future_pos;
}
}
+
+ // if we fall below the world
+ if( m_position.y1 >= m_world->m_world_height * World::CELL_BOX_SIZE ) {
+ Die();
+ m_world->OnPlayerDied();
+ }
+
+ // if we step at least 25% cell deep into the spikes, we die
+ World::CellType left_foot = m_world->AtXY( m_position.x1, m_position.y2 - World::CELL_BOX_SIZE*0.25f );
+ World::CellType right_foot = m_world->AtXY( m_position.x2, m_position.y2 - World::CELL_BOX_SIZE*0.25f );
+ if( m_world->IsKillOnTouch( left_foot )
+ || m_world->IsKillOnTouch( right_foot ) )
+ {
+ Die();
+ m_world->OnPlayerDied();
+ }
}
void Player::MoveTo( float x, float y )
@@ -216,4 +264,34 @@ void Player::MoveTo( float x, float y )
x, y,
x + World::CELL_BOX_SIZE - 1.0f,
y + World::CELL_BOX_SIZE - 1.0f );
-}
+}
+
+
+void Player::Die()
+{
+ // TODO: invent the way for player to inform the gamestate or the world about
+ // level game restart or scroll back to allow player to continue
+ m_is_dead = true;
+}
+
+
+void Player::Respawn()
+{
+ m_lives--;
+ if( m_lives >= 0 ) {
+ // Here add actions to move player to start location,
+ // to scroll window slightly back to allow playing, to give temporary immunity
+ // to monsters and strip the player of all buffs
+ m_is_dead = false;
+ m_world->m_pause_flag = false;
+
+ EnterWorld( m_world );
+ m_world->m_camera_pos.x = 0; // reset the camera to the start
+ }
+ else {
+ // out of spare lives - Game over
+ // As an exercise for the reader: replace this game over text with a proper
+ // gamestate which will show some animation or the Game Over text or show hi-scores
+ MyGame::m_game->ShowMainMenuScreen();
+ }
+}
View
10 src/player.h
@@ -10,6 +10,7 @@
class World;
+class MyGame;
// Player class
// controls keyboard interaction and game rules
@@ -27,6 +28,11 @@ class Player
// Hack this value for infinite lives
int m_lives;
+ // Hack this value for infinite gold, u jelly american treasury?
+ int m_money;
+
+ // flag to render death animation/effects/blood splash when player died
+ bool m_is_dead;
// world we have entered, must not be NULL when game is active
World * m_world;
@@ -82,6 +88,7 @@ class Player
virtual hgeSprite * GetSprite();
// Draws character over the world, using World's camera to calculate positions
+ // MyGame pointer is only needed to access the font, otherwise we don't need it
virtual void Render( World * world );
// called by gamestate when player enters the world and game begins
@@ -91,4 +98,7 @@ class Player
virtual void Think();
void MoveTo( float x, float y );
+
+ // Recovery after death
+ virtual void Respawn();
};
View
30 src/world.cpp
@@ -23,11 +23,13 @@ World::World( Player * plr, const std::string & filename )
m_sprite_brick1 = m_sprite_manager.GetSprite("textures/brick1.png");
m_sprite_sky = m_sprite_manager.GetSprite("textures/sky1.png");
+ m_sprite_spikes = m_sprite_manager.GetSprite("textures/spikes.png");
}
World::~World()
{
+ delete m_sprite_spikes;
delete m_sprite_brick1;
delete m_sprite_sky;
@@ -110,10 +112,16 @@ void World::Think()
if ((m_player->m_position.x2 - m_camera_pos.x) < CELL_BOX_SIZE)
{
m_player->Die();
- m_pause_flag = true;
+ OnPlayerDied();
}
}
+void World::OnPlayerDied()
+{
+ // play some animations and maybe restart the game
+ m_pause_flag = true;
+}
+
World::CellType & World::At( uint32_t row, uint32_t col )
{
@@ -156,6 +164,13 @@ void World::Render()
r * CELL_BOX_SIZE - m_camera_pos.y
);
break;
+ case WORLD_CELL_SPIKES:
+ // find position in world and render it
+ m_sprite_spikes->Render(
+ c * CELL_BOX_SIZE - m_camera_pos.x,
+ r * CELL_BOX_SIZE - m_camera_pos.y
+ );
+ break;
} // end switch
} // end for columns
} // end for rows
@@ -173,10 +188,10 @@ 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;
+ if( IsSolidAtXY(rc.x1, rc.y1) ) return false;
+ if( IsSolidAtXY(rc.x1, rc.y2) ) return false;
+ if( IsSolidAtXY(rc.x2, rc.y1) ) return false;
+ if( IsSolidAtXY(rc.x2, rc.y2) ) return false;
return true;
}
@@ -186,3 +201,8 @@ bool World::IsSolid( CellType contents )
{
return contents == WORLD_CELL_WALL1;
}
+
+bool World::IsKillOnTouch( CellType contents )
+{
+ return contents == WORLD_CELL_SPIKES;
+}
View
40 src/world.h
@@ -32,14 +32,15 @@ class WorldObject;
// playing rules. World defines conditions when player wins.
class World
{
-protected:
- // this is the player, World does not own the Character and will not delete it on world's end
- Player * m_player;
-
+public:
// we define world cell as unsigned integer. But in future this may be changed to a
// more complicated struct or class etc. to have more control over world structure
typedef uint32_t CellType;
+protected:
+ // this is the player, World does not own the Character and will not delete it on world's end
+ Player * m_player;
+
// Rectangular array of the world lined up row after row, this is for memory storage
// simplicity, to allow worlds of arbitrary size and to avoid more complicated memory
// management - we let std::vector do the memory job. Use At() function to access cells
@@ -49,6 +50,7 @@ class World
// Leaving this as exercise for the reader - to organize sprites better
hgeSprite * m_sprite_brick1;
hgeSprite * m_sprite_sky;
+ hgeSprite * m_sprite_spikes;
HGE * m_hge;
@@ -97,7 +99,8 @@ class World
WORLD_CELL_EMPTY = ' ',
WORLD_CELL_PLAYER_START = '@',
WORLD_CELL_WALL1 = '#',
- WORLD_CELL_MONEY = '$'
+ WORLD_CELL_MONEY = '$',
+ WORLD_CELL_SPIKES = '^'
};
public:
@@ -105,10 +108,6 @@ class World
World( Player * plr, const std::string & filename );
virtual ~World();
- // Returns a read/writable reference to a world cell. You can read and write to it
- // like if it was a real array element
- CellType & At( uint32_t row, uint32_t col );
-
// default world can never be "won", you have to inherit the world class
// and override Victory function to define own rules when player wins
virtual bool Victory() { return false; }
@@ -128,19 +127,40 @@ class World
// any other game logic, only drawing
virtual void Render();
+ // Returns a read/writable reference to a world cell. You can read and write to it
+ // like if it was a real array element
+ CellType & At( uint32_t row, uint32_t col );
+
+ // return not reference but value. Since we can return non existing values beyond the
+ // world limits, we cannot return an actual cell, so read only there we go
+ inline CellType AtXY( float x, float y)
+ {
+ // always solid for ahead of the visible screen
+ if( x > m_camera_pos.x + SCREEN_WIDTH ) return WORLD_CELL_WALL1;
+ // always not solid below the world
+ if( y >= m_world_height * CELL_BOX_SIZE ) return WORLD_CELL_EMPTY;
+
+ return At( (uint32_t)(y / CELL_BOX_SIZE), (uint32_t)(x / CELL_BOX_SIZE) );
+ }
+
// 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 ) {
+ inline bool IsSolidAtXY( float x, float y ) {
// always solid for ahead of the visible screen
if( x > m_camera_pos.x + SCREEN_WIDTH ) return true;
+ // always not solid below the world
+ if( y >= m_world_height * CELL_BOX_SIZE ) return false;
return IsSolid(
At( (uint32_t)(y / CELL_BOX_SIZE), (uint32_t)(x / CELL_BOX_SIZE) )
);
}
virtual bool IsSolid( CellType contents );
+ virtual bool IsKillOnTouch( CellType contents );
+
+ virtual void OnPlayerDied();
};

0 comments on commit 47d21ae

Please sign in to comment.
Something went wrong with that request. Please try again.