1
0
mirror of https://github.com/borgesdan/xn65 synced 2024-12-29 21:54:47 +01:00

Implementações em PlatformStarterKit

This commit is contained in:
Danilo 2024-05-27 16:44:01 -03:00
parent 912937a021
commit a4fffeae62
26 changed files with 1260 additions and 18 deletions

View File

@ -10,6 +10,7 @@ namespace xna {
Game::Game() {
impl = unew<PlatformImplementation>();
services = New<GameServiceContainer>();
auto iservice = reinterpret_pointer_cast<IServiceProvider>(services);
_contentManager = New<ContentManager>("", services);
_gameWindow = New<GameWindow>();

View File

@ -24,7 +24,7 @@ namespace xna {
pad.IsConnected = state.connected;
pad.PackedNumber = state.packet;
pad.Sticks = GamePadThumbSticks(
pad.ThumbSticks = GamePadThumbSticks(
Vector2(state.thumbSticks.leftX, state.thumbSticks.leftY),
Vector2(state.thumbSticks.rightX, state.thumbSticks.rightY));
pad.Triggers = GamePadTriggers(state.triggers.left, state.triggers.right);
@ -62,7 +62,7 @@ namespace xna {
pad.IsConnected = state.connected;
pad.PackedNumber = state.packet;
pad.Sticks = GamePadThumbSticks(
pad.ThumbSticks = GamePadThumbSticks(
Vector2(state.thumbSticks.leftX, state.thumbSticks.leftY),
Vector2(state.thumbSticks.rightX, state.thumbSticks.rightY));
pad.Triggers = GamePadTriggers(state.triggers.left, state.triggers.right);

View File

@ -353,4 +353,26 @@ namespace xna {
return texture2d;
}
Int Texture2D::Width() const {
if (!impl) return 0;
return static_cast<Int>(impl->dxDescription.Width);
}
Int Texture2D::Height() const {
if (!impl) return 0;
return static_cast<Int>(impl->dxDescription.Height);
}
Rectangle Texture2D::Bounds() const {
if (!impl) return {};
return Rectangle(
0, 0,
static_cast<Int>(impl->dxDescription.Width),
static_cast<Int>(impl->dxDescription.Height)
);
}
}

View File

@ -35,6 +35,8 @@ namespace xna {
struct PlatformImplementation;
uptr<PlatformImplementation> impl = nullptr;
};
using PSoundEffect = sptr<SoundEffect>;
}
#endif

View File

@ -14,12 +14,12 @@ namespace xna {
public:
friend class ContentReader;
ContentManager(String const& rootDirectory, sptr<GameServiceContainer> const& services) :
ContentManager(String const& rootDirectory, sptr<IServiceProvider> const& services) :
_rootDirectory(rootDirectory){
_services = services;
};
static sptr<GameServiceContainer> Services() {
static sptr<IServiceProvider> Services() {
return _services;
}
@ -65,7 +65,7 @@ namespace xna {
std::vector<Byte> byteBuffer;
inline const static String contentExtension = ".xnb";
inline static sptr<GameServiceContainer> _services = nullptr;
inline static sptr<IServiceProvider> _services = nullptr;
};
}

View File

@ -144,6 +144,9 @@ namespace xna {
//TODO: Not implemented.
//static constexpr TimeSpan FromMinutes(int64_t minutes, int64_t seconds = 0, int64_t milliseconds = 0, int64_t microseconds = 0);
static constexpr TimeSpan FromSeconds(int seconds) {
return FromSeconds(static_cast<int64_t>(seconds));
}
static constexpr TimeSpan FromSeconds(int64_t seconds) {
return FromUnits(seconds, TicksPerSecond, MinSeconds, MaxSeconds);
@ -292,11 +295,11 @@ namespace xna {
return a.Divide(b);
}
friend TimeSpan operator<(TimeSpan const& a, TimeSpan const& b) {
friend bool operator<(TimeSpan const& a, TimeSpan const& b) {
return a._ticks < b._ticks;
}
friend TimeSpan operator<=(TimeSpan const& a, TimeSpan const& b) {
friend bool operator<=(TimeSpan const& a, TimeSpan const& b) {
return a._ticks <= b._ticks;
}

View File

@ -20,18 +20,58 @@ namespace xna {
Matrix const& transformMatrix = Matrix::Identity()
);
void End();
void Draw(sptr<Texture2D> const& texture, Vector2 const& position, Color const& color) {
Draw(*texture, position, color);
}
void Draw(Texture2D& texture, Vector2 const& position, Color const& color);
void Draw(sptr<Texture2D> const& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color) {
Draw(*texture, position, sourceRectangle, color);
}
void Draw(Texture2D& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color);
void Draw(sptr<Texture2D> const& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, float scale, SpriteEffects effects, float layerDepth) {
Draw(*texture, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
}
void Draw(Texture2D& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, float scale, SpriteEffects effects, float layerDepth);
void Draw(sptr<Texture2D> const& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, Vector2 const& scale, SpriteEffects effects, float layerDepth) {
Draw(*texture, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
}
void Draw(Texture2D& texture, Vector2 const& position, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, Vector2 const& scale, SpriteEffects effects, float layerDepth);
void Draw(sptr<Texture2D> const& texture, Rectangle const& destinationRectangle, Color const& color) {
Draw(*texture, destinationRectangle, color);
}
void Draw(Texture2D& texture, Rectangle const& destinationRectangle, Color const& color);
void Draw(sptr<Texture2D> const& texture, Rectangle const& destinationRectangle, Rectangle const* sourceRectangle, Color const& color) {
Draw(*texture, destinationRectangle, sourceRectangle, color);
}
void Draw(Texture2D& texture, Rectangle const& destinationRectangle, Rectangle const* sourceRectangle, Color const& color);
void Draw(sptr<Texture2D> const& texture, Rectangle const& destinationRectangle, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, SpriteEffects effects, float layerDepth) {
Draw(*texture, destinationRectangle, sourceRectangle, color, rotation, origin, effects, layerDepth);
}
void Draw(Texture2D& texture, Rectangle const& destinationRectangle, Rectangle const* sourceRectangle, Color const& color,
float rotation, Vector2 const& origin, SpriteEffects effects, float layerDepth);
void Viewport(xna::Viewport const& value);
void DrawString(sptr<SpriteFont> const& spriteFont, String const& text, Vector2 const& position, Color const& color) {
DrawString(*spriteFont, text, position, color);
}
void DrawString(SpriteFont& spriteFont, String const& text, Vector2 const& position, Color const& color);
void DrawString(sptr<SpriteFont> const& spriteFont, String const& text, Vector2 const& position, Color const& color,
float rotation, Vector2 const& origin, float scale, SpriteEffects effects, float layerDepth) {
DrawString(*spriteFont, text, position, color, rotation, origin, scale, effects, layerDepth);
}
void DrawString(SpriteFont& spriteFont, String const& text, Vector2 const& position, Color const& color,
float rotation, Vector2 const& origin, float scale, SpriteEffects effects, float layerDepth);

View File

@ -35,7 +35,6 @@ namespace xna {
using PTexture2D = sptr<Texture2D>;
using UTexture2D = uptr<Texture2D>;
}
#endif

View File

@ -193,21 +193,21 @@ namespace xna {
case xna::Buttons::BigButton:
return this->Buttons.BigButton == ButtonState::Pressed;
case xna::Buttons::LeftThumbstickLeft:
return this->Sticks.Left().X < 0.5F;
return this->ThumbSticks.Left().X < 0.5F;
case xna::Buttons::LeftThumbstickRight:
return this->Sticks.Left().X > 0.5F;
return this->ThumbSticks.Left().X > 0.5F;
case xna::Buttons::LeftThumbstickDown:
return this->Sticks.Left().Y > 0.5F;
return this->ThumbSticks.Left().Y > 0.5F;
case xna::Buttons::LeftThumbstickUp:
return this->Sticks.Left().Y < 0.5F;
return this->ThumbSticks.Left().Y < 0.5F;
case xna::Buttons::RightThumbstickLeft:
return this->Sticks.Right().X < 0.5F;
return this->ThumbSticks.Right().X < 0.5F;
case xna::Buttons::RightThumbstickRight:
return this->Sticks.Right().X > 0.5F;
return this->ThumbSticks.Right().X > 0.5F;
case xna::Buttons::RightThumbstickDown:
return this->Sticks.Right().Y > 0.5F;
return this->ThumbSticks.Right().Y > 0.5F;
case xna::Buttons::RightThumbstickUp:
return this->Sticks.Right().Y < 0.5F;
return this->ThumbSticks.Right().Y < 0.5F;
case xna::Buttons::LeftTrigger:
return this->Triggers.Left() > 0.5F;
case xna::Buttons::RightTrigger:
@ -225,7 +225,7 @@ namespace xna {
GamePadDPad Dpad{};
bool IsConnected{false};
Ulong PackedNumber{0};
GamePadThumbSticks Sticks{};
GamePadThumbSticks ThumbSticks{};
GamePadTriggers Triggers{};
};

View File

@ -1,3 +1,4 @@
#define NOMINMAX
#include "xnaerror.hpp"
#include "types.hpp"
#include "helpers.hpp"

View File

@ -0,0 +1,13 @@
# CMakeList.txt : CMake project for CmakeSharedExeTest, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (BlankApp WIN32 "game.cpp" "animation.cpp" "enemy.cpp" "level.cpp" "player.cpp" "gem.cpp")
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET BlankApp PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.
target_link_libraries(BlankApp Xn65)

View File

@ -0,0 +1,39 @@
#include "animation.hpp"
namespace PlatformerStarterKit {
void AnimationPlayer::PlayAnimation(xna::sptr<PlatformerStarterKit::Animation> const& animation) {
if (this->animation == animation)
return;
// Start the new animation.
this->animation = animation;
this->frameIndex = 0;
this->time = 0.0f;
}
void AnimationPlayer::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch, xna::Vector2& position, xna::SpriteEffects spriteEffects) {
if (animation == nullptr)
std::exception();
// Process passing time.
time += (float)gameTime.ElapsedGameTime.TotalSeconds();
while (time > animation->FrameTime()) {
time -= animation->FrameTime();
// Advance the frame index; looping or clamping as appropriate.
if (animation->IsLooping()) {
frameIndex = (frameIndex + 1) % animation->FrameCount();
}
else {
frameIndex = std::min(frameIndex + 1, animation->FrameCount() - 1);
}
}
// Calculate the source rectangle of the current frame.
const auto source = xna::Rectangle(frameIndex * animation->Texture()->Height(), 0, animation->Texture()->Height(), animation->Texture()->Height());
// Draw the current frame.
spriteBatch.Draw(animation->Texture(), position, &source, xna::Colors::White, 0.0f, Origin(), 1.0f, spriteEffects, 0.0f);
}
}

View File

@ -0,0 +1,75 @@
#ifndef PLATFORMSTARTERKIT_ANIMATION_HPP
#define PLATFORMSTARTERKIT_ANIMATION_HPP
#include "xna.hpp"
namespace PlatformerStarterKit {
/*
* Represents an animated texture.
* Currently, this class assumes that each frame of animation is
* as wide as each animation is tall. The number of frames in the
* animation are inferred from this.
*/
class Animation {
public:
Animation(xna::sptr<xna::Texture2D> const& texture, float frameTime, bool isLooping):
texture(texture), frameTime(frameTime), isLooping(isLooping){}
inline xna::sptr<xna::Texture2D> Texture() const {
return texture;
}
constexpr float FrameTime() const {
return frameTime;
}
constexpr bool IsLooping() const {
return isLooping;
}
inline int FrameCount() const {
return texture->Width() / texture->Height();
}
inline int FrameWidth() const {
return texture->Height();
}
inline int FrameHeight() const {
return texture->Height();
}
private:
xna::sptr<xna::Texture2D> texture = nullptr;
float frameTime = 0;
bool isLooping = false;
};
//Controls playback of an Animation.
struct AnimationPlayer {
xna::sptr<Animation> Animation() const {
return animation;
}
constexpr int FrameIndex() const {
return frameIndex;
}
inline xna::Vector2 Origin() const {
return xna::Vector2(
animation->FrameWidth() / 2.0f,
animation->FrameHeight()
);
}
void PlayAnimation(xna::sptr<PlatformerStarterKit::Animation> const& animation);
void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch, xna::Vector2& position, xna::SpriteEffects spriteEffects);
private:
xna::sptr<PlatformerStarterKit::Animation> animation = nullptr;
int frameIndex = 0;
float time = 0;
};
}
#endif

View File

@ -0,0 +1,28 @@
#ifndef PLATFORMSTARTERKIT_CIRCLE_HPP
#define PLATFORMSTARTERKIT_CIRCLE_HPP
#include "xna.hpp"
namespace PlatformerStarterKit {
//Represents a 2D circle.
struct Circle {
xna::Vector2 Center{};
float Radius{ 0 };
constexpr Circle() = default;
constexpr Circle(xna::Vector2 const& position, float radius):
Center(position), Radius(radius){}
constexpr bool Intersects(xna::Rectangle const& rectangle) const {
const auto v = xna::Vector2(xna::MathHelper::Clamp(Center.X, rectangle.Left(), rectangle.Right()),
xna::MathHelper::Clamp(Center.Y, rectangle.Top(), rectangle.Bottom()));
const auto direction = Center - v;
auto distanceSquared = direction.LengthSquared();
return ((distanceSquared > 0) && (distanceSquared < Radius * Radius));
}
};
}
#endif

View File

@ -0,0 +1,79 @@
#include "enemy.hpp"
#include "level.hpp"
#include "player.hpp"
namespace PlatformerStarterKit {
Enemy::Enemy(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position, xna::String const& spriteSet):
level(level), position(position)
{
LoadContent(spriteSet);
}
xna::sptr<PlatformerStarterKit::Level> Enemy::Level() const
{
return level;
}
xna::Rectangle Enemy::BoundingRectangle() const
{
int left = static_cast<int>(std::round(position.X - sprite.Origin().X)) + localBounds.X;
int top = static_cast<int>(std::round(position.Y - sprite.Origin().Y)) + localBounds.Y;
return xna::Rectangle(left, top, localBounds.Width, localBounds.Height);
}
void Enemy::LoadContent(xna::String const& spriteSet)
{
auto _spriteSet = "Sprites/" + spriteSet + "/";
runAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>(_spriteSet + "Run"), 0.1f, true);
idleAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>(_spriteSet + "Idle"), 0.15f, true);
sprite.PlayAnimation(idleAnimation);
const auto width = static_cast<int>(idleAnimation->FrameWidth() * 0.35);
const auto left = (idleAnimation->FrameWidth() - width) / 2;
const auto height = static_cast<int>(idleAnimation->FrameWidth() * 0.7);
const auto top = idleAnimation->FrameHeight() - height;
localBounds = xna::Rectangle(left, top, width, height);
}
void Enemy::Update(xna::GameTime const& gameTime)
{
const auto elapsed = static_cast<float>(gameTime.ElapsedGameTime.TotalSeconds());
const auto posX = position.X + localBounds.Width / 2 * (int)direction;
const auto tileX = static_cast<int>(std::floor(posX / Tile::Width)) - static_cast<int>(direction);
const auto tileY = static_cast<int>(std::floor(position.Y / Tile::Height));
if (waitTime > 0) {
waitTime = std::max(0.0f, waitTime - static_cast<float>(gameTime.ElapsedGameTime.TotalSeconds()));
if (waitTime <= 0.0f) {
direction = (FaceDirection)(-static_cast<int>(direction));
}
}
else {
if (level->GetCollision(tileX + static_cast<int>(direction), tileY - 1) == TileCollision::Impassable ||
level->GetCollision(tileX + static_cast<int>(direction), tileY) == TileCollision::Passable) {
waitTime = MaxWaitTime;
}
else {
const auto velocity = xna::Vector2(static_cast<int>(direction) * MoveSpeed * elapsed, 0.0f);
position = position + velocity;
}
}
}
void Enemy::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch)
{
if (!level->Player()->IsAlive() ||
level->ReachedExit() ||
level->TimeRemaining() == xna::TimeSpan::Zero() ||
waitTime > 0) {
sprite.PlayAnimation(idleAnimation);
}
else {
sprite.PlayAnimation(runAnimation);
}
const auto flip = static_cast<int>(direction) > 0 ? xna::SpriteEffects::FlipHorizontally : xna::SpriteEffects::None;
sprite.Draw(gameTime, spriteBatch, position, flip);
}
}

View File

@ -0,0 +1,51 @@
#ifndef PLATFORMSTARTERKIT_ENEMY_HPP
#define PLATFORMSTARTERKIT_ENEMY_HPP
#include "xna.hpp"
#include "animation.hpp"
namespace PlatformerStarterKit {
//Facing direction along the X axis.
enum class FaceDirection {
Left = -1,
Right = 1,
};
class Level;
// A monster who is impeding the progress of our fearless adventurer.
class Enemy {
public:
Enemy(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position, xna::String const& spriteSet);
public:
xna::sptr<PlatformerStarterKit::Level> Level() const;
constexpr xna::Vector2 Position() const {
return position;
}
xna::Rectangle BoundingRectangle() const;
void LoadContent(xna::String const& spriteSet);
void Update(xna::GameTime const& gameTime);
void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch);
private:
static constexpr float MaxWaitTime = 0.5f;
static constexpr float MoveSpeed = 128.0f;
xna::sptr<PlatformerStarterKit::Level> level;
xna::Vector2 position{};
xna::Rectangle localBounds{};
xna::sptr<Animation> runAnimation = nullptr;
xna::sptr<Animation> idleAnimation = nullptr;
AnimationPlayer sprite{};
FaceDirection direction = FaceDirection::Left;
float waitTime = 0.0F;
};
}
#endif

View File

@ -0,0 +1,36 @@
#ifndef PLATFORMSTARTERKIT_EXTENSIONS_HPP
#define PLATFORMSTARTERKIT_EXTENSIONS_HPP
#include "xna.hpp"
namespace PlatformerStarterKit {
struct RectangleExtensions {
static xna::Vector2 GetIntersectionDepth(xna::Rectangle const& rectA, xna::Rectangle const& rectB) {
const auto halfWidthA = rectA.Width / 2.0f;
const auto halfHeightA = rectA.Height / 2.0f;
const auto halfWidthB = rectB.Width / 2.0f;
const auto halfHeightB = rectB.Height / 2.0f;
const auto centerA = xna::Vector2(rectA.Left() + halfWidthA, rectA.Top() + halfHeightA);
const auto centerB = xna::Vector2(rectB.Left() + halfWidthB, rectB.Top() + halfHeightB);
const auto distanceX = centerA.X - centerB.X;
const auto distanceY = centerA.Y - centerB.Y;
const auto minDistanceX = halfWidthA + halfWidthB;
const auto minDistanceY = halfHeightA + halfHeightB;
if (std::abs(distanceX) >= minDistanceX || std::abs(distanceY) >= minDistanceY)
return xna::Vector2::Zero();
const auto depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
const auto depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return xna::Vector2(depthX, depthY);
}
static constexpr xna::Vector2 GetBottomCenter(xna::Rectangle const& rect) {
return xna::Vector2(rect.X + rect.Width / 2.0f, rect.Bottom());
}
};
}
#endif

View File

@ -0,0 +1,74 @@
// xna.cpp : Defines the entry point for the application.
//
#include "xna.hpp"
#include "player.hpp"
#include "enemy.hpp"
#include "level.hpp"
#include "gem.hpp"
using namespace std;
using namespace xna;
namespace PlatformerStarterKit {
class Game1 : public Game {
public:
Game1() : Game() {
Content()->RootDirectory("Content");
}
void Initialize() override {
auto game = reinterpret_cast<Game*>(this);
graphics = New<GraphicsDeviceManager>(game->shared_from_this());
graphics->Initialize();
std::any device = graphicsDevice;
services->AddService(*typeof<GraphicsDevice>(), device);
Game::Initialize();
}
void LoadContent() override {
spriteBatch = New<SpriteBatch>(*graphicsDevice);
Game::LoadContent();
}
void Update(GameTime const& gameTime) override {
if (Keyboard::GetState().IsKeyDown(Keys::Escape) || GamePad::GetState(PlayerIndex::One).IsButtonDown(Buttons::Back))
Exit();
Game::Update(gameTime);
}
void Draw(GameTime const& gameTime) override {
graphicsDevice->Clear(Colors::CornflowerBlue);
Game::Draw(gameTime);
}
private:
sptr<GraphicsDeviceManager> graphics = nullptr;
sptr<SpriteBatch> spriteBatch = nullptr;
sptr<SpriteFont> hudFont = nullptr;
sptr<Texture2D> winOverlay = nullptr;
sptr<Texture2D> loseOverlay = nullptr;
sptr<Texture2D> diedOverlay = nullptr;
int levelIndex = -1;
sptr<Level> level = nullptr;
bool wasContinuePressed;
TimeSpan WarningTime = TimeSpan::FromSeconds(30);
static constexpr int TargetFrameRate = 60;
static constexpr int BackBufferWidth = 1280;
static constexpr int BackBufferHeight = 720;
static constexpr Buttons ContinueButton = Buttons::A;
};
}
int APIENTRY WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) {
xna::Platform::Init();
auto game = snew<PlatformerStarterKit::Game1>();
const auto result = game->Run();
return result;
}

View File

@ -0,0 +1,38 @@
#include "gem.hpp"
#include "level.hpp"
namespace PlatformerStarterKit {
Gem::Gem(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position) :
level(level), basePosition(position){
LoadContent();
}
void Gem::LoadContent()
{
texture = level->Content()->Load<xna::PTexture2D>("Sprites/Gem");
origin = xna::Vector2(texture->Width() / 2.0f, texture->Height() / 2.0f);
collectedSound = level->Content()->Load<xna::PSoundEffect>("Sounds/GemCollected");
}
void Gem::Update(xna::GameTime const& gameTime)
{
constexpr float BounceHeight = 0.18f;
constexpr float BounceRate = 3.0f;
constexpr float BounceSync = -0.75f;
const auto t = gameTime.TotalGameTime.TotalSeconds()
* BounceRate + Position().X * BounceSync;
bounce = static_cast<float>(std::sin(t)) * BounceHeight * texture->Height();
}
void Gem::OnCollected(xna::sptr<Player>& collectedBy)
{
collectedSound->Play();
}
void Gem::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch)
{
spriteBatch.Draw(texture, Position(), nullptr, Color, 0.0f, origin, 1.0f, xna::SpriteEffects::None, 0.0f);
}
}

View File

@ -0,0 +1,45 @@
#ifndef PLATFORMSTARTERKIT_GEM_HPP
#define PLATFORMSTARTERKIT_GEM_HPP
#include "xna.hpp"
#include "circle.hpp"
#include "tile.hpp"
namespace PlatformerStarterKit {
class Level;
class Player;
class Gem {
public:
Gem(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position);
public:
static constexpr int PointValue = 30;
static constexpr xna::Color Color = xna::Colors::White;
constexpr xna::Vector2 Position() const {
return basePosition + xna::Vector2(0.0f, bounce);
}
constexpr Circle BoundingCircle() const {
return Circle(Position(), Tile::Width / 3.0f);
}
xna::sptr<PlatformerStarterKit::Level> Level() const;
void LoadContent();
void Update(xna::GameTime const& gameTime);
void OnCollected(xna::sptr<Player>& collectedBy);
void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch);
private:
xna::PTexture2D texture = nullptr;
xna::Vector2 origin{};
xna::PSoundEffect collectedSound = nullptr;
xna::Vector2 basePosition{};
float bounce{ 0.0F };
xna::sptr<PlatformerStarterKit::Level> level = nullptr;
};
}
#endif

View File

@ -0,0 +1,257 @@
#include "enemy.hpp"
#include "extensions.hpp"
#include "level.hpp"
#include "player.hpp"
#include "gem.hpp"
#include <cstdlib>
#include <fstream>
#include <string>
namespace PlatformerStarterKit {
Level::Level(xna::sptr<xna::IServiceProvider> const& serviceProvider, xna::String const& path)
{
srand(354668);
content = xna::snew<xna::ContentManager>("Content", serviceProvider);
timeRemaining = xna::TimeSpan::FromMinutes(2.0);
LoadTiles(path);
layers = std::vector<xna::PTexture2D>(3);
for (size_t i = 0; i < layers.size(); ++i) {
const auto segmentIndex = rand() % 3;
layers[i] = content->Load<xna::PTexture2D>("Backgrounds/Layer" + i + '_' + segmentIndex);
exitReachedSound = content->Load<xna::PSoundEffect>("Sounds/ExitReached");
}
}
xna::sptr<PlatformerStarterKit::Player> Level::Player() const
{
return player;
}
void Level::LoadTiles(xna::String const& path)
{
int width = 0;
std::vector<xna::String> lines;
std::fstream reader(path);
xna::String line;
std::getline(reader, line);
width = line.size();
while (!reader.eofbit) {
if (line.size() != width)
std::exception("The length of line {0} is different from all preceeding lines.");
lines.push_back(line);
line.clear();
std::getline(reader, line);
}
tiles = std::vector<std::vector<Tile>>(width, std::vector<Tile>(lines.size()));
for (size_t y = 0; y < lines.size(); ++y) {
for (size_t x = 0; x < width; ++x) {
auto tileType = lines[y][x];
tiles[x][y] = LoadTile(tileType, x, y);
}
}
if (!player)
std::exception("A level must have a starting point.");
if (exit == InvalidPosition)
std::exception("A level must have an exit.");
}
Tile Level::LoadTile(char tileType, int x, int y) {
switch (tileType) {
case '.':
return Tile(nullptr, TileCollision::Passable);
case 'X':
return LoadExitTile(x, y);
case 'G':
return LoadGemTile(x, y);
case '-':
return LoadTile("Platform", TileCollision::Platform);
case 'A':
return LoadEnemyTile(x, y, "MonsterA");
case 'B':
return LoadEnemyTile(x, y, "MonsterB");
case 'C':
return LoadEnemyTile(x, y, "MonsterC");
case 'D':
return LoadEnemyTile(x, y, "MonsterD");
case '~':
return LoadVarietyTile("BlockB", 2, TileCollision::Platform);
case ':':
return LoadVarietyTile("BlockB", 2, TileCollision::Passable);
case '1':
return LoadStartTile(x, y);
case '#':
return LoadVarietyTile("BlockA", 7, TileCollision::Impassable);
default:
std::exception("Unsupported tile type character");
}
}
Tile Level::LoadTile(xna::String const& name, TileCollision collision) {
return Tile(content->Load<xna::PTexture2D>("Tiles/" + name), collision);
}
Tile Level::LoadVarietyTile(xna::String const& baseName, int variationCount, TileCollision collision) {
auto index = rand() % variationCount;
return LoadTile(baseName + std::to_string(index), collision);
}
Tile Level::LoadStartTile(int x, int y) {
if (player != nullptr)
std::exception("A level may only have one starting point.");
start = RectangleExtensions::GetBottomCenter(GetBounds(x, y));
const auto _this = shared_from_this();
player = xna::snew<PlatformerStarterKit::Player>(_this, start);
return Tile(nullptr, TileCollision::Passable);
}
Tile Level::LoadExitTile(int x, int y) {
if (exit != InvalidPosition)
std::exception("A level may only have one exit.");
exit = GetBounds(x, y).Center();
return LoadTile("Exit", TileCollision::Passable);
}
Tile Level::LoadEnemyTile(int x, int y, xna::String const& spriteSet) {
const auto position = RectangleExtensions::GetBottomCenter(GetBounds(x, y));
const auto _this = shared_from_this();
enemies.push_back(xna::snew<Enemy>(_this, position, spriteSet));
return Tile(nullptr, TileCollision::Passable);
}
Tile Level::LoadGemTile(int x, int y) {
const auto position = GetBounds(x, y).Center();
const auto _this = shared_from_this();
gems.push_back(xna::snew<Gem>(_this, xna::Vector2(position.X, position.Y)));
return Tile(nullptr, TileCollision::Passable);
}
void Level::UpdateGems(xna::GameTime const& gameTime)
{
for (size_t i = 0; i < gems.size(); ++i) {
auto& gem = gems[i];
gem->Update(gameTime);
if (gem->BoundingCircle().Intersects(player->BoundingRectangle())) {
gems.erase(gems.begin() + i--);
OnGemCollected(gem, player);
}
}
}
void Level::OnPlayerKilled(xna::sptr<Enemy> killedBy)
{
player->OnKilled(killedBy);
}
void Level::UpdateEnemies(xna::GameTime const& gameTime)
{
for (size_t i = 0; i < enemies.size(); ++i) {
auto& enemy = enemies[i];
if (enemy->BoundingRectangle().Intersects(player->BoundingRectangle())) {
OnPlayerKilled(enemy);
}
}
}
void Level::OnExitReached()
{
player->OnReachedExit();
exitReachedSound->Play();
reachedExit = true;
}
void Level::DrawTiles(xna::SpriteBatch& spriteBatch)
{
for (size_t y = 0; y < Height(); ++y) {
for (size_t x = 0; x < Width(); ++x) {
auto& texture = tiles[x][y].Texture;
if (texture) {
const auto p = xna::Vector2(x, y);
const auto s = xna::Vector2(x, y);
const auto position = p * s;
spriteBatch.Draw(texture, position, xna::Colors::White);
}
}
}
}
void Level::OnGemCollected(xna::sptr<Gem>& gem, xna::sptr<PlatformerStarterKit::Player>& collectedBy)
{
score += Gem::PointValue;
gem->OnCollected(collectedBy);
}
void Level::Update(xna::GameTime const& gameTime) {
if (!player->IsAlive() || timeRemaining == xna::TimeSpan::Zero()) {
player->ApplyPhysics(gameTime);
}
else if (reachedExit) {
auto seconds = static_cast<int64_t>(std::round(gameTime.ElapsedGameTime.TotalSeconds() * 100.0f));
seconds = std::min(seconds, static_cast<int64_t>(std::ceil(timeRemaining.TotalSeconds())));
timeRemaining = timeRemaining - xna::TimeSpan::FromSeconds(seconds);
score += seconds * PointsPerSecond;
}
else {
timeRemaining = timeRemaining - gameTime.ElapsedGameTime;
player->Update(gameTime);
UpdateGems(gameTime);
if (player->BoundingRectangle().Top() >= Height() * Tile::Height)
OnPlayerKilled(nullptr);
UpdateEnemies(gameTime);
if (player->IsAlive() &&
player->IsOnGround() &&
player->BoundingRectangle().Contains(exit)) {
OnExitReached();
}
}
if (timeRemaining < xna::TimeSpan::Zero())
timeRemaining = xna::TimeSpan::Zero();
}
void Level::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch)
{
for (size_t i = 0; i <= EntityLayer; ++i)
spriteBatch.Draw(layers[i], xna::Vector2::Zero(), xna::Colors::White);
DrawTiles(spriteBatch);
/*foreach(Gem gem in gems)
gem.Draw(gameTime, spriteBatch);
Player.Draw(gameTime, spriteBatch);
foreach(Enemy enemy in enemies)
enemy.Draw(gameTime, spriteBatch);
for (int i = EntityLayer + 1; i < layers.Length; ++i)
spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);*/
}
void Level::StartNewLife() {
player->Reset(start);
}
}

View File

@ -0,0 +1,103 @@
#ifndef PLATFORMSTARTERKIT_LEVEL_HPP
#define PLATFORMSTARTERKIT_LEVEL_HPP
#include "xna.hpp"
#include "tile.hpp"
namespace PlatformerStarterKit {
class Player;
class Gem;
class Enemy;
/*
* A uniform grid of tiles with collections of gems and enemies.
* The level owns the player and controls the game's win and lose
* conditions as well as scoring.
*/
class Level : std::enable_shared_from_this<Level> {
public:
Level(xna::sptr<xna::IServiceProvider> const& serviceProvider, xna::String const& path );
public:
xna::sptr<PlatformerStarterKit::Player> Player() const;
constexpr int Score() const {
return score;
}
constexpr bool ReachedExit() const {
return reachedExit;
}
constexpr xna::TimeSpan TimeRemaining() const {
return timeRemaining;
}
xna::sptr<xna::ContentManager> Content() const {
return content;
}
constexpr xna::Rectangle GetBounds(int x, int y) const {
return xna::Rectangle(x * Tile::Width, y * Tile::Height, Tile::Width, Tile::Height);
}
constexpr TileCollision GetCollision(int x, int y) const {
if (x < 0 || x >= Width())
return TileCollision::Impassable;
if (y < 0 || y >= Height())
return TileCollision::Passable;
return tiles[x][y].Collision;
}
constexpr int Width() const {
return tiles.size();
}
constexpr int Height() const {
return tiles[0].size();
}
void Update(xna::GameTime const& gameTime);
void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch);
void StartNewLife();
private:
static constexpr xna::Point InvalidPosition = xna::Point(-1, -1);
static constexpr int PointsPerSecond = 5;
std::vector<std::vector<Tile>> tiles;
std::vector<xna::PTexture2D> layers;
static constexpr int EntityLayer = 2;
xna::sptr<PlatformerStarterKit::Player> player = nullptr;
std::vector<xna::sptr<Gem>> gems;
std::vector<xna::sptr<Enemy>> enemies;
xna::Vector2 start{};
xna::Point exit = InvalidPosition;
int score = 0;
bool reachedExit = false;
xna::TimeSpan timeRemaining{};
xna::sptr<xna::ContentManager> content = nullptr;
xna::sptr<xna::SoundEffect> exitReachedSound = nullptr;
private:
void LoadTiles(xna::String const& path);
Tile LoadTile(char tileType, int x, int y);
Tile LoadTile(xna::String const& name, TileCollision collision);
Tile LoadVarietyTile(xna::String const& baseName, int variationCount, TileCollision collision);
Tile LoadStartTile(int x, int y);
Tile LoadExitTile(int x, int y);
Tile LoadEnemyTile(int x, int y, xna::String const& spriteSet);
Tile LoadGemTile(int x, int y);
void UpdateGems(xna::GameTime const& gameTime);
void OnPlayerKilled(xna::sptr<Enemy> killedBy);
void UpdateEnemies(xna::GameTime const& gameTime);
void OnExitReached();
void DrawTiles(xna::SpriteBatch& spriteBatch);
void OnGemCollected(xna::sptr<Gem>& gem, xna::sptr<PlatformerStarterKit::Player>& collectedBy);
};
}
#endif

View File

@ -0,0 +1,228 @@
#include "player.hpp"
#include "level.hpp"
#include "enemy.hpp"
#include "extensions.hpp"
namespace PlatformerStarterKit {
Player::Player(xna::sptr<PlatformerStarterKit::Level> const& level, xna::Vector2 const& position)
: level(level)
{
LoadContent();
Reset(position);
}
void Player::LoadContent()
{
idleAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Idle"), 0.1f, true);
runAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Run"), 0.1f, true);
jumpAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Jump"), 0.1f, false);
celebrateAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Celebrate"), 0.1f, false);
dieAnimation = xna::snew<Animation>(level->Content()->Load<xna::PTexture2D>("Sprites/Player/Die"), 0.1f, false);
const auto width = static_cast<int>(idleAnimation->FrameWidth() * 0.4);
const auto left = (idleAnimation->FrameWidth() - width) / 2;
const auto height = static_cast<int>(idleAnimation->FrameWidth() * 0.8);
const auto top = idleAnimation->FrameHeight() - height;
localBounds = xna::Rectangle(left, top, width, height);
killedSound = level->Content()->Load<xna::PSoundEffect>("Sounds/PlayerKilled");
jumpSound = level->Content()->Load<xna::PSoundEffect>("Sounds/PlayerJump");
fallSound = level->Content()->Load<xna::PSoundEffect>("Sounds/PlayerFall");
}
void Player::Reset(xna::Vector2 const& position)
{
Position = position;
Velocity = xna::Vector2::Zero();
isAlive = true;
sprite->PlayAnimation(idleAnimation);
}
void Player::ApplyPhysics(xna::GameTime const& gameTime)
{
float elapsed = static_cast<float>(gameTime.ElapsedGameTime.TotalSeconds());
auto previousPosition = Position;
Velocity.X += movement * MoveAcceleration * elapsed;
Velocity.Y = xna::MathHelper::Clamp(Velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);
Velocity.Y = DoJump(Velocity.Y, gameTime);
if (IsOnGround())
Velocity.X *= GroundDragFactor;
else
Velocity.X *= AirDragFactor;
Velocity.X = xna::MathHelper::Clamp(Velocity.X, -MaxMoveSpeed, MaxMoveSpeed);
Position = Position + (Velocity * elapsed);
Position = xna::Vector2(std::round(Position.X), std::round(Position.Y));
HandleCollisions();
if (Position.X == previousPosition.X)
Velocity.X = 0;
if (Position.Y == previousPosition.Y)
Velocity.Y = 0;
}
void Player::Update(xna::GameTime const& gameTime)
{
GetInput();
ApplyPhysics(gameTime);
if (IsAlive() && IsOnGround()) {
if (std::abs(Velocity.X) - 0.02f > 0) {
sprite->PlayAnimation(runAnimation);
}
else {
sprite->PlayAnimation(idleAnimation);
}
}
movement = 0.0f;
isJumping = false;
}
void Player::GetInput()
{
auto gamePadState = xna::GamePad::GetState(xna::PlayerIndex::One);
auto keyboardState = xna::Keyboard::GetState();
movement = gamePadState.ThumbSticks.Left().X * MoveStickScale;
if (std::abs(movement) < 0.5f)
movement = 0.0f;
if (gamePadState.IsButtonDown(xna::Buttons::DPadLeft) ||
keyboardState.IsKeyDown(xna::Keys::Left) ||
keyboardState.IsKeyDown(xna::Keys::A)) {
movement = -1.0f;
}
else if (gamePadState.IsButtonDown(xna::Buttons::DPadRight) ||
keyboardState.IsKeyDown(xna::Keys::Right) ||
keyboardState.IsKeyDown(xna::Keys::D)) {
movement = 1.0f;
}
isJumping =
gamePadState.IsButtonDown(JumpButton) ||
keyboardState.IsKeyDown(xna::Keys::Space) ||
keyboardState.IsKeyDown(xna::Keys::Up) ||
keyboardState.IsKeyDown(xna::Keys::W);
}
void Player::OnKilled(xna::sptr<Enemy>& killedBy)
{
isAlive = false;
if (!killedBy)
killedSound->Play();
else
fallSound->Play();
sprite->PlayAnimation(dieAnimation);
}
void Player::Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch)
{
if (Velocity.X > 0)
flip = xna::SpriteEffects::FlipHorizontally;
else if (Velocity.X < 0)
flip = xna::SpriteEffects::None;
sprite->Draw(gameTime, spriteBatch, Position, flip);
}
void Player::OnReachedExit()
{
sprite->PlayAnimation(celebrateAnimation);
}
float Player::DoJump(float velocityY, xna::GameTime const& gameTime)
{
if (isJumping) {
if ((!wasJumping && IsOnGround()) || jumpTime > 0.0f) {
if (jumpTime == 0.0f)
jumpSound->Play();
jumpTime += static_cast<float>(gameTime.ElapsedGameTime.TotalSeconds());
sprite->PlayAnimation(jumpAnimation);
}
if (0.0f < jumpTime && jumpTime <= MaxJumpTime) {
velocityY = JumpLaunchVelocity * (1.0f - static_cast<float>(std::pow(jumpTime / MaxJumpTime, JumpControlPower)));
}
else {
jumpTime = 0.0f;
}
}
else {
jumpTime = 0.0f;
}
wasJumping = isJumping;
return velocityY;
}
void Player::HandleCollisions()
{
auto bounds = BoundingRectangle();
auto leftTile = std::floor(static_cast<float>(bounds.Left()) / Tile::Width);
auto rightTile = std::ceil((static_cast<float>(bounds.Right()) / Tile::Width)) - 1;
auto topTile = std::floor(static_cast<float>(bounds.Top()) / Tile::Height);
auto bottomTile = std::ceil((static_cast<float>(bounds.Bottom()) / Tile::Height)) - 1;
isOnGround = false;
for (size_t y = topTile; y <= bottomTile; ++y) {
for (size_t x = leftTile; x <= rightTile; ++x) {
auto collision = level->GetCollision(x, y);
if (collision != TileCollision::Passable) {
auto tileBounds = level->GetBounds(x, y);
auto depth = RectangleExtensions::GetIntersectionDepth(bounds, tileBounds);
if (depth != xna::Vector2::Zero()) {
auto absDepthX = std::abs(depth.X);
auto absDepthY = std::abs(depth.Y);
if (absDepthY < absDepthX || collision == TileCollision::Platform) {
if (previousBottom <= tileBounds.Top())
isOnGround = true;
if (collision == TileCollision::Impassable || IsOnGround()) {
Position = xna::Vector2(Position.X, Position.Y + depth.Y);
bounds = BoundingRectangle();
}
}
else if (collision == TileCollision::Impassable)
{
Position = xna::Vector2(Position.X + depth.X, Position.Y);
bounds = BoundingRectangle();
}
}
}
}
}
previousBottom = bounds.Bottom();
}
xna::sptr<PlatformerStarterKit::Level> Player::Level() const
{
return level;
}
xna::Rectangle Player::BoundingRectangle() const {
auto left = std::round(Position.X - sprite->Origin().X) + localBounds.X;
auto top = std::round(Position.Y - sprite->Origin().Y) + localBounds.Y;
return xna::Rectangle(
static_cast<int>(left),
static_cast<int>(top),
localBounds.Width,
localBounds.Height);
}
}

View File

@ -0,0 +1,80 @@
#ifndef PLATFORMSTARTERKIT_PLAYER_HPP
#define PLATFORMSTARTERKIT_PLAYER_HPP
#include "xna.hpp"
#include "animation.hpp"
namespace PlatformerStarterKit {
class Level;
class Enemy;
// Our fearless adventurer!
class Player {
public:
Player(xna::sptr<Level> const& level, xna::Vector2 const& position);
public:
xna::sptr<PlatformerStarterKit::Level> Level() const;
constexpr bool IsAlive() const {
return isAlive;
}
constexpr bool IsOnGround() const {
return isOnGround;
}
xna::Rectangle BoundingRectangle() const;
void LoadContent();
void Reset(xna::Vector2 const& position);
void ApplyPhysics(xna::GameTime const& gameTime);
void Update(xna::GameTime const& gameTime);
void GetInput();
void OnKilled(xna::sptr<Enemy>& killedBy);
void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch);
void OnReachedExit();
public:
xna::Vector2 Position{};
xna::Vector2 Velocity{};
private:
xna::sptr<Animation> idleAnimation = nullptr;
xna::sptr<Animation> runAnimation = nullptr;
xna::sptr<Animation> jumpAnimation = nullptr;
xna::sptr<Animation> celebrateAnimation = nullptr;
xna::sptr<Animation> dieAnimation = nullptr;
xna::SpriteEffects flip = xna::SpriteEffects::None;
xna::sptr<AnimationPlayer> sprite = nullptr;
xna::sptr<xna::SoundEffect> killedSound = nullptr;
xna::sptr<xna::SoundEffect> jumpSound = nullptr;
xna::sptr<xna::SoundEffect> fallSound = nullptr;
xna::sptr<PlatformerStarterKit::Level> level = nullptr;
bool isAlive = false;
float previousBottom = 0.0F;
bool isOnGround = false;
float movement = 0.0f;
bool isJumping = false;
bool wasJumping = false;
float jumpTime = false;
xna::Rectangle localBounds{};
static constexpr float MoveAcceleration = 14000.0f;
static constexpr float MaxMoveSpeed = 2000.0f;
static constexpr float GroundDragFactor = 0.58f;
static constexpr float AirDragFactor = 0.65f;
static constexpr float MaxJumpTime = 0.35f;
static constexpr float JumpLaunchVelocity = -4000.0f;
static constexpr float GravityAcceleration = 3500.0f;
static constexpr float MaxFallSpeed = 600.0f;
static constexpr float JumpControlPower = 0.14f;
static constexpr float MoveStickScale = 1.0f;
static constexpr xna::Buttons JumpButton = xna::Buttons::A;
float DoJump(float velocityY, xna::GameTime const& gameTime);
void HandleCollisions();
};
}
#endif

View File

@ -0,0 +1,28 @@
#ifndef PLATFORMSTARTERKIT_TILE_HPP
#define PLATFORMSTARTERKIT_TILE_HPP
namespace PlatformerStarterKit {
// Controls the collision detection and response behavior of a tile.
enum class TileCollision {
Passable = 0,
Impassable = 1,
Platform = 2,
};
struct Tile {
xna::PTexture2D Texture = nullptr;
TileCollision Collision{};
constexpr static int Width = 64;
constexpr static int Height = 48;
constexpr static xna::Vector2 Size() {
return{ Width, Height };
}
constexpr Tile() = default;
Tile(xna::PTexture2D const& texture, TileCollision collision) :
Texture(texture), Collision(collision) {}
};
}
#endif

View File

@ -3,4 +3,4 @@
#
# Add source to this project's executable.
add_subdirectory ("01_blank")
add_subdirectory ("02_PlatfformerStarterKit")