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:
parent
912937a021
commit
a4fffeae62
@ -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>();
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -35,6 +35,8 @@ namespace xna {
|
||||
struct PlatformImplementation;
|
||||
uptr<PlatformImplementation> impl = nullptr;
|
||||
};
|
||||
|
||||
using PSoundEffect = sptr<SoundEffect>;
|
||||
}
|
||||
|
||||
#endif
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -35,7 +35,6 @@ namespace xna {
|
||||
|
||||
|
||||
using PTexture2D = sptr<Texture2D>;
|
||||
using UTexture2D = uptr<Texture2D>;
|
||||
}
|
||||
|
||||
#endif
|
@ -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{};
|
||||
};
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#define NOMINMAX
|
||||
#include "xnaerror.hpp"
|
||||
#include "types.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
13
samples/02_PlatfformerStarterKit/CMakeLists.txt
Normal file
13
samples/02_PlatfformerStarterKit/CMakeLists.txt
Normal 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)
|
39
samples/02_PlatfformerStarterKit/animation.cpp
Normal file
39
samples/02_PlatfformerStarterKit/animation.cpp
Normal 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);
|
||||
}
|
||||
}
|
75
samples/02_PlatfformerStarterKit/animation.hpp
Normal file
75
samples/02_PlatfformerStarterKit/animation.hpp
Normal 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
|
28
samples/02_PlatfformerStarterKit/circle.hpp
Normal file
28
samples/02_PlatfformerStarterKit/circle.hpp
Normal 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
|
79
samples/02_PlatfformerStarterKit/enemy.cpp
Normal file
79
samples/02_PlatfformerStarterKit/enemy.cpp
Normal 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);
|
||||
}
|
||||
}
|
51
samples/02_PlatfformerStarterKit/enemy.hpp
Normal file
51
samples/02_PlatfformerStarterKit/enemy.hpp
Normal 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
|
36
samples/02_PlatfformerStarterKit/extensions.hpp
Normal file
36
samples/02_PlatfformerStarterKit/extensions.hpp
Normal 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
|
74
samples/02_PlatfformerStarterKit/game.cpp
Normal file
74
samples/02_PlatfformerStarterKit/game.cpp
Normal 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;
|
||||
}
|
38
samples/02_PlatfformerStarterKit/gem.cpp
Normal file
38
samples/02_PlatfformerStarterKit/gem.cpp
Normal 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);
|
||||
}
|
||||
}
|
45
samples/02_PlatfformerStarterKit/gem.hpp
Normal file
45
samples/02_PlatfformerStarterKit/gem.hpp
Normal 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
|
257
samples/02_PlatfformerStarterKit/level.cpp
Normal file
257
samples/02_PlatfformerStarterKit/level.cpp
Normal 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);
|
||||
}
|
||||
}
|
103
samples/02_PlatfformerStarterKit/level.hpp
Normal file
103
samples/02_PlatfformerStarterKit/level.hpp
Normal 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
|
228
samples/02_PlatfformerStarterKit/player.cpp
Normal file
228
samples/02_PlatfformerStarterKit/player.cpp
Normal 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);
|
||||
}
|
||||
}
|
80
samples/02_PlatfformerStarterKit/player.hpp
Normal file
80
samples/02_PlatfformerStarterKit/player.hpp
Normal 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
|
28
samples/02_PlatfformerStarterKit/tile.hpp
Normal file
28
samples/02_PlatfformerStarterKit/tile.hpp
Normal 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
|
@ -3,4 +3,4 @@
|
||||
#
|
||||
|
||||
# Add source to this project's executable.
|
||||
add_subdirectory ("01_blank")
|
||||
add_subdirectory ("02_PlatfformerStarterKit")
|
||||
|
Loading…
x
Reference in New Issue
Block a user