diff --git a/framework/platform-dx/game.cpp b/framework/platform-dx/game.cpp index 73b1e0c..4fae11f 100644 --- a/framework/platform-dx/game.cpp +++ b/framework/platform-dx/game.cpp @@ -10,6 +10,7 @@ namespace xna { Game::Game() { impl = unew(); services = New(); + auto iservice = reinterpret_pointer_cast(services); _contentManager = New("", services); _gameWindow = New(); diff --git a/framework/platform-dx/gamepad.cpp b/framework/platform-dx/gamepad.cpp index 0c84a34..95fd9ab 100644 --- a/framework/platform-dx/gamepad.cpp +++ b/framework/platform-dx/gamepad.cpp @@ -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); diff --git a/framework/platform-dx/texture.cpp b/framework/platform-dx/texture.cpp index 8ed9a15..3b73c0d 100644 --- a/framework/platform-dx/texture.cpp +++ b/framework/platform-dx/texture.cpp @@ -353,4 +353,26 @@ namespace xna { return texture2d; } + + Int Texture2D::Width() const { + if (!impl) return 0; + + return static_cast(impl->dxDescription.Width); + } + + Int Texture2D::Height() const { + if (!impl) return 0; + + return static_cast(impl->dxDescription.Height); + } + + Rectangle Texture2D::Bounds() const { + if (!impl) return {}; + + return Rectangle( + 0, 0, + static_cast(impl->dxDescription.Width), + static_cast(impl->dxDescription.Height) + ); + } } \ No newline at end of file diff --git a/inc/audio/soundeffect.hpp b/inc/audio/soundeffect.hpp index 94c0e4d..aff06a8 100644 --- a/inc/audio/soundeffect.hpp +++ b/inc/audio/soundeffect.hpp @@ -35,6 +35,8 @@ namespace xna { struct PlatformImplementation; uptr impl = nullptr; }; + + using PSoundEffect = sptr; } #endif \ No newline at end of file diff --git a/inc/content/manager.hpp b/inc/content/manager.hpp index a1a6ff7..54af74d 100644 --- a/inc/content/manager.hpp +++ b/inc/content/manager.hpp @@ -14,12 +14,12 @@ namespace xna { public: friend class ContentReader; - ContentManager(String const& rootDirectory, sptr const& services) : + ContentManager(String const& rootDirectory, sptr const& services) : _rootDirectory(rootDirectory){ _services = services; }; - static sptr Services() { + static sptr Services() { return _services; } @@ -65,7 +65,7 @@ namespace xna { std::vector byteBuffer; inline const static String contentExtension = ".xnb"; - inline static sptr _services = nullptr; + inline static sptr _services = nullptr; }; } diff --git a/inc/csharp/timespan.hpp b/inc/csharp/timespan.hpp index 7bda83d..cbd46a5 100644 --- a/inc/csharp/timespan.hpp +++ b/inc/csharp/timespan.hpp @@ -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(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; } diff --git a/inc/graphics/sprite.hpp b/inc/graphics/sprite.hpp index 96061e2..05f0709 100644 --- a/inc/graphics/sprite.hpp +++ b/inc/graphics/sprite.hpp @@ -20,18 +20,58 @@ namespace xna { Matrix const& transformMatrix = Matrix::Identity() ); void End(); + void Draw(sptr 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 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 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 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 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 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 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 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 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); diff --git a/inc/graphics/texture.hpp b/inc/graphics/texture.hpp index c9a6206..baea647 100644 --- a/inc/graphics/texture.hpp +++ b/inc/graphics/texture.hpp @@ -35,7 +35,6 @@ namespace xna { using PTexture2D = sptr; - using UTexture2D = uptr; } #endif \ No newline at end of file diff --git a/inc/input/gamepad.hpp b/inc/input/gamepad.hpp index 55d5f39..f77ca95 100644 --- a/inc/input/gamepad.hpp +++ b/inc/input/gamepad.hpp @@ -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{}; }; diff --git a/inc/xna.hpp b/inc/xna.hpp index cba8b0f..77647d6 100644 --- a/inc/xna.hpp +++ b/inc/xna.hpp @@ -1,3 +1,4 @@ +#define NOMINMAX #include "xnaerror.hpp" #include "types.hpp" #include "helpers.hpp" diff --git a/samples/02_PlatfformerStarterKit/CMakeLists.txt b/samples/02_PlatfformerStarterKit/CMakeLists.txt new file mode 100644 index 0000000..b97c3e5 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/CMakeLists.txt @@ -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) diff --git a/samples/02_PlatfformerStarterKit/animation.cpp b/samples/02_PlatfformerStarterKit/animation.cpp new file mode 100644 index 0000000..770663e --- /dev/null +++ b/samples/02_PlatfformerStarterKit/animation.cpp @@ -0,0 +1,39 @@ +#include "animation.hpp" + +namespace PlatformerStarterKit { + void AnimationPlayer::PlayAnimation(xna::sptr 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); + } +} \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/animation.hpp b/samples/02_PlatfformerStarterKit/animation.hpp new file mode 100644 index 0000000..e1e001d --- /dev/null +++ b/samples/02_PlatfformerStarterKit/animation.hpp @@ -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 const& texture, float frameTime, bool isLooping): + texture(texture), frameTime(frameTime), isLooping(isLooping){} + + inline xna::sptr 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 texture = nullptr; + float frameTime = 0; + bool isLooping = false; + }; + + //Controls playback of an Animation. + struct AnimationPlayer { + xna::sptr 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 const& animation); + void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch, xna::Vector2& position, xna::SpriteEffects spriteEffects); + + private: + xna::sptr animation = nullptr; + int frameIndex = 0; + float time = 0; + }; +} + +#endif \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/circle.hpp b/samples/02_PlatfformerStarterKit/circle.hpp new file mode 100644 index 0000000..1f9953b --- /dev/null +++ b/samples/02_PlatfformerStarterKit/circle.hpp @@ -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 \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/enemy.cpp b/samples/02_PlatfformerStarterKit/enemy.cpp new file mode 100644 index 0000000..f19eca7 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/enemy.cpp @@ -0,0 +1,79 @@ +#include "enemy.hpp" +#include "level.hpp" +#include "player.hpp" + +namespace PlatformerStarterKit { + Enemy::Enemy(xna::sptr const& level, xna::Vector2 const& position, xna::String const& spriteSet): + level(level), position(position) + { + LoadContent(spriteSet); + } + + xna::sptr Enemy::Level() const + { + return level; + } + + xna::Rectangle Enemy::BoundingRectangle() const + { + int left = static_cast(std::round(position.X - sprite.Origin().X)) + localBounds.X; + int top = static_cast(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(level->Content()->Load(_spriteSet + "Run"), 0.1f, true); + idleAnimation = xna::snew(level->Content()->Load(_spriteSet + "Idle"), 0.15f, true); + sprite.PlayAnimation(idleAnimation); + + const auto width = static_cast(idleAnimation->FrameWidth() * 0.35); + const auto left = (idleAnimation->FrameWidth() - width) / 2; + const auto height = static_cast(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(gameTime.ElapsedGameTime.TotalSeconds()); + + const auto posX = position.X + localBounds.Width / 2 * (int)direction; + const auto tileX = static_cast(std::floor(posX / Tile::Width)) - static_cast(direction); + const auto tileY = static_cast(std::floor(position.Y / Tile::Height)); + + if (waitTime > 0) { + waitTime = std::max(0.0f, waitTime - static_cast(gameTime.ElapsedGameTime.TotalSeconds())); + if (waitTime <= 0.0f) { + direction = (FaceDirection)(-static_cast(direction)); + } + } + else { + if (level->GetCollision(tileX + static_cast(direction), tileY - 1) == TileCollision::Impassable || + level->GetCollision(tileX + static_cast(direction), tileY) == TileCollision::Passable) { + waitTime = MaxWaitTime; + } + else { + const auto velocity = xna::Vector2(static_cast(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(direction) > 0 ? xna::SpriteEffects::FlipHorizontally : xna::SpriteEffects::None; + sprite.Draw(gameTime, spriteBatch, position, flip); + } +} \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/enemy.hpp b/samples/02_PlatfformerStarterKit/enemy.hpp new file mode 100644 index 0000000..20514eb --- /dev/null +++ b/samples/02_PlatfformerStarterKit/enemy.hpp @@ -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 const& level, xna::Vector2 const& position, xna::String const& spriteSet); + + public: + xna::sptr 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 level; + xna::Vector2 position{}; + xna::Rectangle localBounds{}; + + xna::sptr runAnimation = nullptr; + xna::sptr idleAnimation = nullptr; + AnimationPlayer sprite{}; + FaceDirection direction = FaceDirection::Left; + + float waitTime = 0.0F; + }; +} + +#endif \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/extensions.hpp b/samples/02_PlatfformerStarterKit/extensions.hpp new file mode 100644 index 0000000..d0dc982 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/extensions.hpp @@ -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 \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/game.cpp b/samples/02_PlatfformerStarterKit/game.cpp new file mode 100644 index 0000000..1f2c3c8 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/game.cpp @@ -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(this); + graphics = New(game->shared_from_this()); + graphics->Initialize(); + + std::any device = graphicsDevice; + services->AddService(*typeof(), device); + + Game::Initialize(); + } + + void LoadContent() override { + spriteBatch = New(*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 graphics = nullptr; + sptr spriteBatch = nullptr; + sptr hudFont = nullptr; + sptr winOverlay = nullptr; + sptr loseOverlay = nullptr; + sptr diedOverlay = nullptr; + int levelIndex = -1; + sptr 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(); + const auto result = game->Run(); + return result; +} diff --git a/samples/02_PlatfformerStarterKit/gem.cpp b/samples/02_PlatfformerStarterKit/gem.cpp new file mode 100644 index 0000000..d562288 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/gem.cpp @@ -0,0 +1,38 @@ +#include "gem.hpp" +#include "level.hpp" + +namespace PlatformerStarterKit { + Gem::Gem(xna::sptr const& level, xna::Vector2 const& position) : + level(level), basePosition(position){ + LoadContent(); + } + + void Gem::LoadContent() + { + texture = level->Content()->Load("Sprites/Gem"); + origin = xna::Vector2(texture->Width() / 2.0f, texture->Height() / 2.0f); + collectedSound = level->Content()->Load("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(std::sin(t)) * BounceHeight * texture->Height(); + } + + void Gem::OnCollected(xna::sptr& 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); + } +} \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/gem.hpp b/samples/02_PlatfformerStarterKit/gem.hpp new file mode 100644 index 0000000..15caa9c --- /dev/null +++ b/samples/02_PlatfformerStarterKit/gem.hpp @@ -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 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 Level() const; + + void LoadContent(); + void Update(xna::GameTime const& gameTime); + void OnCollected(xna::sptr& 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 level = nullptr; + }; +} + +#endif \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/level.cpp b/samples/02_PlatfformerStarterKit/level.cpp new file mode 100644 index 0000000..4bf2f34 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/level.cpp @@ -0,0 +1,257 @@ +#include "enemy.hpp" +#include "extensions.hpp" +#include "level.hpp" +#include "player.hpp" +#include "gem.hpp" +#include +#include +#include + +namespace PlatformerStarterKit { + Level::Level(xna::sptr const& serviceProvider, xna::String const& path) + { + srand(354668); + + content = xna::snew("Content", serviceProvider); + timeRemaining = xna::TimeSpan::FromMinutes(2.0); + + LoadTiles(path); + + layers = std::vector(3); + + for (size_t i = 0; i < layers.size(); ++i) { + const auto segmentIndex = rand() % 3; + layers[i] = content->Load("Backgrounds/Layer" + i + '_' + segmentIndex); + + exitReachedSound = content->Load("Sounds/ExitReached"); + } + } + xna::sptr Level::Player() const + { + return player; + } + + void Level::LoadTiles(xna::String const& path) + { + int width = 0; + std::vector 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>(width, std::vector(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("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(_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(_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(_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 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, xna::sptr& 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(std::round(gameTime.ElapsedGameTime.TotalSeconds() * 100.0f)); + seconds = std::min(seconds, static_cast(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); + } +} \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/level.hpp b/samples/02_PlatfformerStarterKit/level.hpp new file mode 100644 index 0000000..8cbfc61 --- /dev/null +++ b/samples/02_PlatfformerStarterKit/level.hpp @@ -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 { + public: + Level(xna::sptr const& serviceProvider, xna::String const& path ); + + public: + xna::sptr Player() const; + + constexpr int Score() const { + return score; + } + + constexpr bool ReachedExit() const { + return reachedExit; + } + + constexpr xna::TimeSpan TimeRemaining() const { + return timeRemaining; + } + + xna::sptr 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> tiles; + std::vector layers; + static constexpr int EntityLayer = 2; + xna::sptr player = nullptr; + std::vector> gems; + std::vector> enemies; + + xna::Vector2 start{}; + xna::Point exit = InvalidPosition; + int score = 0; + bool reachedExit = false; + xna::TimeSpan timeRemaining{}; + xna::sptr content = nullptr; + xna::sptr 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 killedBy); + void UpdateEnemies(xna::GameTime const& gameTime); + void OnExitReached(); + void DrawTiles(xna::SpriteBatch& spriteBatch); + void OnGemCollected(xna::sptr& gem, xna::sptr& collectedBy); + }; +} + +#endif \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/player.cpp b/samples/02_PlatfformerStarterKit/player.cpp new file mode 100644 index 0000000..1275bab --- /dev/null +++ b/samples/02_PlatfformerStarterKit/player.cpp @@ -0,0 +1,228 @@ +#include "player.hpp" +#include "level.hpp" +#include "enemy.hpp" +#include "extensions.hpp" + +namespace PlatformerStarterKit { + Player::Player(xna::sptr const& level, xna::Vector2 const& position) + : level(level) + { + LoadContent(); + Reset(position); + } + + void Player::LoadContent() + { + idleAnimation = xna::snew(level->Content()->Load("Sprites/Player/Idle"), 0.1f, true); + runAnimation = xna::snew(level->Content()->Load("Sprites/Player/Run"), 0.1f, true); + jumpAnimation = xna::snew(level->Content()->Load("Sprites/Player/Jump"), 0.1f, false); + celebrateAnimation = xna::snew(level->Content()->Load("Sprites/Player/Celebrate"), 0.1f, false); + dieAnimation = xna::snew(level->Content()->Load("Sprites/Player/Die"), 0.1f, false); + + const auto width = static_cast(idleAnimation->FrameWidth() * 0.4); + const auto left = (idleAnimation->FrameWidth() - width) / 2; + const auto height = static_cast(idleAnimation->FrameWidth() * 0.8); + const auto top = idleAnimation->FrameHeight() - height; + localBounds = xna::Rectangle(left, top, width, height); + + killedSound = level->Content()->Load("Sounds/PlayerKilled"); + jumpSound = level->Content()->Load("Sounds/PlayerJump"); + fallSound = level->Content()->Load("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(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& 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(gameTime.ElapsedGameTime.TotalSeconds()); + sprite->PlayAnimation(jumpAnimation); + } + + if (0.0f < jumpTime && jumpTime <= MaxJumpTime) { + velocityY = JumpLaunchVelocity * (1.0f - static_cast(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(bounds.Left()) / Tile::Width); + auto rightTile = std::ceil((static_cast(bounds.Right()) / Tile::Width)) - 1; + auto topTile = std::floor(static_cast(bounds.Top()) / Tile::Height); + auto bottomTile = std::ceil((static_cast(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 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(left), + static_cast(top), + localBounds.Width, + localBounds.Height); + } +} \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/player.hpp b/samples/02_PlatfformerStarterKit/player.hpp new file mode 100644 index 0000000..2f78c9c --- /dev/null +++ b/samples/02_PlatfformerStarterKit/player.hpp @@ -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 const& level, xna::Vector2 const& position); + + public: + xna::sptr 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& killedBy); + void Draw(xna::GameTime const& gameTime, xna::SpriteBatch& spriteBatch); + void OnReachedExit(); + + public: + xna::Vector2 Position{}; + xna::Vector2 Velocity{}; + + private: + xna::sptr idleAnimation = nullptr; + xna::sptr runAnimation = nullptr; + xna::sptr jumpAnimation = nullptr; + xna::sptr celebrateAnimation = nullptr; + xna::sptr dieAnimation = nullptr; + xna::SpriteEffects flip = xna::SpriteEffects::None; + xna::sptr sprite = nullptr; + xna::sptr killedSound = nullptr; + xna::sptr jumpSound = nullptr; + xna::sptr fallSound = nullptr; + xna::sptr 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 \ No newline at end of file diff --git a/samples/02_PlatfformerStarterKit/tile.hpp b/samples/02_PlatfformerStarterKit/tile.hpp new file mode 100644 index 0000000..9435b8a --- /dev/null +++ b/samples/02_PlatfformerStarterKit/tile.hpp @@ -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 \ No newline at end of file diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 90f3ffd..bbeea5b 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -3,4 +3,4 @@ # # Add source to this project's executable. -add_subdirectory ("01_blank") +add_subdirectory ("02_PlatfformerStarterKit")