GithubHelp home page GithubHelp logo

roout / platformer Goto Github PK

View Code? Open in Web Editor NEW
2.0 3.0 0.0 38.28 MB

Attempt to create some decent game

License: GNU General Public License v2.0

CMake 2.86% C++ 92.48% C 0.92% Objective-C 0.89% Objective-C++ 2.85%
game-development game cocos2dx 2d-game platformer

platformer's Introduction

Platformer 2D

This is an attempt to make a 2D Platformer game and complete the whole development cycle.
The final step will be publishing the game to the google play.

Target platforms:

  • Android
  • Linux
  • Windows

Now works only on windows.

Tools

  1. Dragon Bones
  2. Cocos2dx v4.0 engine + tools needed by this engine (Python2, etc)
  3. CMake 3.16 or higher
  4. MSVC compiler with c++17 or higher
  5. Tiled 1.2
  6. Texture Packer
  7. Python 3
  8. Jtoc - custom tool for generating cpp classes from JSON schemas

Progress

You can see the current progress at videos below.
FPS is not steady due to potato laptop: it doesn't like recording...

Ability showcast

Current player's abilities:

Key Description
A, D Move left-right
W, space Jump and double jump
F Sword attack. Has 3 types of animations: top, mid, bottom
E Special attack, has cast time, deals more damage
G Fireball attack
Q Dash

Images:
walkthrough walkthrough walkthrough walkthrough

You can see more here: Youtube link

Level walkthough

You can perceive level as a sandbox. Level boarders are shown on the debug screen.
Now sandbox contains only boxes and typical platforms. Can be restarted and paused.
Debug mode has several usefull flags:

  • show physics level boundaries
  • invicibility
  • current state of the player and NPCs

Images:
debug

You can see more here: Youtube link

Boss fight

First boss is a forest bandit. Boss has following abitilies:

Ability Description
Chain's attack Slow attack ahead of yourself: doesn't move and attack 3 times, each attack longer than previous
Fireballs Send several fireballs
Jump with chain attack Deal damage on attack
Dash Special attack, has cast time, deals more damage
Summon fire cloud Calls fire cloud which attack with fireballs from the sky and doesn't depend on boss

Images:
boss

You can see more here: Youtube link

Credits

Sergei Nevstruev - programming Andrew Sadovnikov - graphics/animations

platformer's People

Contributors

roout avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

platformer's Issues

Physics Crash on chipmunk error: body's force is invalid

Aborting due to Chipmunk error: Body's force is invalid. Failed condition: cpfabs(v.x) != INFINITY && cpfabs(v.y) != INFINITY

Crashed when minimizing & rolling out game window on Win32 at least. May be manifested on pause/resume. The problem should exist due to applying Infinity-force when passing to movement update function dt equel 0.f.

Filename of the level shouldn't matter when loading it.

Game shouldn't care what the filename of the %level%.tmx is. Just load everything and let the user choose.
This lead to two problems:

  • add level menu (let the user choose from all loaded levels)
  • structure level's asset in Resource folder

Validate tile map

Add validation for tilemap:

  • all enemies have to have unique custom id property
  • all enemies have to have influence rectangle with custom owner-id property which is same as the owner's one.

Create tool for tilemap

Issue description:
When using one tileset with banch of unique properties you may need to add few new tiles to this tileset. This force you to merge the current picture with new tiles and generate new tileset. As the result the information about the previous tileset was lost,
Note:
cocos2dx doesn't support an usage of several tilesets for one layer.

Add archer

An archer is a unit with states: idle and shoot. Player receive some damage from the contact with archer and arrows.

Spear man attack

Spear man should attack from the certain range. The spear is long so ther is no need to come close as the warrior does

Horizontal movement stops on collision

When jumping and colliding (along x-axis) with obstacle player still must be moving according to user input so that when the player is above the obstacle it will continue moving along x-axis without a need to release&press key again.

For now unit stops.

Spider's death

Let spider fall on the death and remove web only when the spider is removed

Projetile collision with ground

A pojectile from the player collide with the ground when it's just being created!
The difference is clear when you attack being on the ground and in the air

Fat callback functions for the Physics Contact event

bool OnContactBegin(cocos2d::PhysicsContact& contact) {
enum { BODY_A, BODY_B };
cocos2d::PhysicsShape * const shapes[2] = {
contact.getShapeA(),
contact.getShapeB()
};
cocos2d::PhysicsBody * const bodies[2] = {
shapes[BODY_A]->getBody(),
shapes[BODY_B]->getBody()
};
cocos2d::Node * const nodes[2] = {
bodies[BODY_A]->getNode(),
bodies[BODY_B]->getNode()
};
if( !nodes[BODY_A] || !nodes[BODY_B] ) {
return false;
}
const bool isUnitSensor[2] = {
shapes[BODY_A]->getCategoryBitmask() == Utils::CreateMask(core::CategoryBits::GROUND_SENSOR),
shapes[BODY_B]->getCategoryBitmask() == Utils::CreateMask(core::CategoryBits::GROUND_SENSOR)
};
// There are nodes one of which is with unit sensor attached
// i.e. basicaly it's unit and other collidable body
if ( isUnitSensor[BODY_A] || isUnitSensor[BODY_B] ) {
Unit * unit { dynamic_cast<Unit*>(isUnitSensor[BODY_A]? nodes[BODY_A] : nodes[BODY_B]) };
unit->SetContactWithGround(true);
return true;
}
/// Platform & Unit
const int bodyMasks[2] = {
bodies[BODY_A]->getCategoryBitmask(),
bodies[BODY_B]->getCategoryBitmask()
};
const bool isPlatform[2] = {
bodyMasks[BODY_A] == Utils::CreateMask(core::CategoryBits::PLATFORM),
bodyMasks[BODY_B] == Utils::CreateMask(core::CategoryBits::PLATFORM)
};
if( isPlatform[BODY_A] || isPlatform[BODY_B] ) {
const auto platformIndex { isPlatform[BODY_A]? BODY_A: BODY_B };
const auto moveUpwards { helper::IsGreater(bodies[platformIndex ^ 1]->getVelocity().y, 0.f, 0.000001f) };
// Ordinates before collision:
const auto unitBottomOrdinate { nodes[platformIndex ^ 1]->getPosition().y };
const auto platformTopOrdinate {
nodes[platformIndex]->getPosition().y +
nodes[platformIndex]->getContentSize().height / 2.f
};
const auto canPassThrough { helper::IsLesser(unitBottomOrdinate, platformTopOrdinate, 0.00001f) };
return !(moveUpwards || canPassThrough);
}
/// Spikes & Unit
const bool isTrap[2] = {
bodyMasks[BODY_A] == Utils::CreateMask(core::CategoryBits::TRAP),
bodyMasks[BODY_B] == Utils::CreateMask(core::CategoryBits::TRAP)
};
if( isTrap[BODY_A] || isTrap[BODY_B] ) {
const auto trapIndex { isTrap[BODY_A]? BODY_A: BODY_B };
const auto unit { dynamic_cast<Unit*>(nodes[trapIndex^1]) };
const auto trap { dynamic_cast<Traps::Trap*>(nodes[trapIndex]) };
trap->CurseTarget(unit);
return false;
}
/// Projectile & (Unit or Barrel)
const bool isProjectile[2] = {
bodyMasks[BODY_A] == Utils::CreateMask(core::CategoryBits::PROJECTILE),
bodyMasks[BODY_B] == Utils::CreateMask(core::CategoryBits::PROJECTILE)
};
const auto unitMask { Utils::CreateMask(core::CategoryBits::HERO, core::CategoryBits::ENEMY) };
const bool isUnit[2] = {
(bodyMasks[BODY_A] & unitMask) > 0,
(bodyMasks[BODY_B] & unitMask) > 0
};
if( isProjectile[BODY_A] || isProjectile[BODY_B] ) {
const auto projectileIndex { isProjectile[BODY_A]? BODY_A: BODY_B };
const auto proj { dynamic_cast<Projectile*>(nodes[projectileIndex]) };
// damage target if possible
if(isUnit[projectileIndex ^ 1]) {
const auto unit { dynamic_cast<Unit*>(nodes[projectileIndex^1]) };
unit->AddCurse<Curses::CurseClass::INSTANT>(
Curses::CurseHub::ignored,
proj->GetDamage()
);
} else if( bodyMasks[projectileIndex ^ 1] == Utils::CreateMask(core::CategoryBits::BARREL)) {
const auto barrel { dynamic_cast<Barrel*>(nodes[projectileIndex^1]) };
barrel->Explode();
}
// destroy projectile
proj->Collapse();
// end contact, no need to process collision
return false;
}
return true;
}
bool OnContactSeparate(cocos2d::PhysicsContact& contact) {
enum { BODY_A, BODY_B };
cocos2d::PhysicsShape * const shapes[2] = {
contact.getShapeA(),
contact.getShapeB()
};
cocos2d::PhysicsBody * const bodies[2] = {
shapes[BODY_A]->getBody(),
shapes[BODY_B]->getBody()
};
cocos2d::Node * const nodes[2] = {
bodies[BODY_A]->getNode(),
bodies[BODY_B]->getNode()
};
if( !nodes[BODY_A] || !nodes[BODY_B] ) {
return false;
}
const int bodyMasks[2] = {
bodies[BODY_A]->getCategoryBitmask(),
bodies[BODY_B]->getCategoryBitmask()
};
bool isUnitSensor[2] = {
shapes[BODY_A]->getCategoryBitmask() == Utils::CreateMask(core::CategoryBits::GROUND_SENSOR),
shapes[BODY_B]->getCategoryBitmask() == Utils::CreateMask(core::CategoryBits::GROUND_SENSOR)
};
if (nodes[BODY_A] && nodes[BODY_B] && (isUnitSensor[BODY_A] || isUnitSensor[BODY_B]) ) {
Unit * heroView { dynamic_cast<Unit*>(isUnitSensor[BODY_A]? nodes[BODY_A] : nodes[BODY_B]) };
bool onGround {
isUnitSensor[BODY_A]?
helper::IsEquel(bodies[BODY_A]->getVelocity().y, 0.f, 0.000001f):
helper::IsEquel(bodies[BODY_B]->getVelocity().y, 0.f, 0.000001f)
};
heroView->SetContactWithGround(onGround);
return true;
}
// handle contact of spikes and unit
const bool isTrap[2] = {
bodyMasks[BODY_A] == Utils::CreateMask(core::CategoryBits::TRAP),
bodyMasks[BODY_B] == Utils::CreateMask(core::CategoryBits::TRAP)
};
if( isTrap[BODY_A] || isTrap[BODY_B] ) {
const auto trapIndex { isTrap[BODY_A]? BODY_A: BODY_B };
const auto unit { dynamic_cast<Unit*>(nodes[trapIndex^1]) };
const auto trap { dynamic_cast<Traps::Trap*>(nodes[trapIndex]) };
trap->RemoveCurse(unit);
return false;
}
return true;
}

Need to be breaked and divided between different application layers cuz it's obviously not responsibility of only the LevelScene

Move influence area to Tiled

Motivation

  1. It's uncomfortable to add new influence area to just added unit
  2. To narrow down responsibility of waypoints supplier
  3. To avoid passing map's size and tile's size to Influence factory method

Expect to change:

  • map of the level (tmx file)
  • map parser
  • stuff with enemy initialization

Rework Level::Restart function

  • add type which will keep all data which shouldn't be destructed (retain the pointers)
  • add to LevelScene constructor with this type which will aquire ownership over all resources from the type above instead of allocating them like simple constructor
  • then instead of using custom restart function in LevelScene I just replace scene on restart

In this way it's possible to get rid of the bunch of dynamic_cast's on pause/death

dynamic_cast<LevelScene*>(level)->Restart();

Out of level bounds

Remove all stuff that is out of level bounds: endless fall from the level gaps.

fix staying at the air by shrinking body

bug-staying-at-the-air

Can be solved by:

  • shrinking the physics body without changing anything else
  • make hit box a separate shape and shrinking the body
  • make body from two different shapes

Animation on death rotates!

When the unit die and the death animation is being played, it can rotate depending on position of player!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.