Add game framework and whitebox level
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,8 @@
|
||||
#version 330 core
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
FragColor = v_color;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec2 a_position;
|
||||
layout (location = 1) in vec2 a_texCoord;
|
||||
layout (location = 2) in vec4 a_color;
|
||||
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
gl_Position = u_projection * u_view * vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"shaders": {
|
||||
"sprite": {
|
||||
"vertex": "sprite.vert",
|
||||
"fragment": "sprite.frag"
|
||||
},
|
||||
"colored_quad": {
|
||||
"vertex": "colored_quad.vert",
|
||||
"fragment": "colored_quad.frag"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 330 core
|
||||
in vec2 v_texCoord;
|
||||
in vec4 v_color;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
FragColor = texture(u_texture, v_texCoord) * v_color;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec2 a_position;
|
||||
layout (location = 1) in vec2 a_texCoord;
|
||||
layout (location = 2) in vec4 a_color;
|
||||
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
out vec2 v_texCoord;
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
v_texCoord = a_texCoord;
|
||||
v_color = a_color;
|
||||
gl_Position = u_projection * u_view * vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
# Game Architecture
|
||||
|
||||
This project keeps the game layer separate from the Frostbite2D engine. The
|
||||
engine remains in `Frostbite2D/`; game-specific code lives under `game/`.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```text
|
||||
game/
|
||||
├─ assets/
|
||||
│ ├─ character/ Runtime character spritesheets.
|
||||
│ └─ shaders/ Runtime shaders used by the game target.
|
||||
├─ docs/
|
||||
│ └─ ARCHITECTURE.md This document.
|
||||
└─ src/
|
||||
├─ actor/ Game actors such as player, enemy, projectile.
|
||||
├─ combat/ Hitbox, hurtbox, damage and combat resolution types.
|
||||
├─ core/ Shared config, input snapshots and asset paths.
|
||||
├─ data/ Level and gameplay data definitions.
|
||||
├─ movement/ Reusable movement and collision helpers.
|
||||
├─ scene/ Game scenes and level orchestration.
|
||||
└─ main.cpp Game executable entry point.
|
||||
```
|
||||
|
||||
## Runtime Flow
|
||||
|
||||
```text
|
||||
main.cpp
|
||||
-> Application::init()
|
||||
-> SceneManager::ReplaceScene(WhiteboxScene)
|
||||
-> WhiteboxScene::onEnter()
|
||||
-> CreateWhiteboxLevel()
|
||||
-> create PlayerActor
|
||||
-> pass PlatformWorld to PlayerActor
|
||||
-> every frame:
|
||||
-> PlayerActor samples InputState
|
||||
-> PlayerActor moves through PlatformMover
|
||||
-> PlayerActor updates animation
|
||||
-> WhiteboxScene updates camera
|
||||
-> WhiteboxScene draws whitebox level
|
||||
```
|
||||
|
||||
## Module Responsibilities
|
||||
|
||||
`core`
|
||||
|
||||
- `game_config.h` stores shared tuning constants such as virtual resolution,
|
||||
gravity, player speed and attack duration.
|
||||
- `asset_paths.h` stores stable runtime asset paths.
|
||||
- `input_state.*` converts raw SDL keyboard state into an engine-independent
|
||||
`InputState` snapshot. Player logic consumes this snapshot instead of reading
|
||||
SDL directly.
|
||||
|
||||
`movement`
|
||||
|
||||
- `PlatformWorld` owns level boundaries and platform rectangles.
|
||||
- `MovementBody` is a reusable position/size/velocity container.
|
||||
- `PlatformMover` applies gravity, clamps horizontal bounds and resolves basic
|
||||
top-side platform landing.
|
||||
|
||||
`data`
|
||||
|
||||
- `LevelDefinition` groups collision and whitebox props.
|
||||
- `CreateWhiteboxLevel()` currently returns the first hard-coded blockout level.
|
||||
Later this can be replaced by JSON, Tiled, LDtk, or a custom editor export
|
||||
without changing `PlayerActor`.
|
||||
|
||||
`actor`
|
||||
|
||||
- `PlayerActor` owns player sprites, player state and animation.
|
||||
- It depends on `InputStateProvider` and `PlatformMover`, so input and movement
|
||||
can be reused for enemies or changed independently.
|
||||
|
||||
`scene`
|
||||
|
||||
- `WhiteboxScene` orchestrates the current level: it loads level data, creates
|
||||
actors, draws whitebox geometry and controls the camera.
|
||||
|
||||
`combat`
|
||||
|
||||
- `hitbox.h` defines early `Hitbox` and `Hurtbox` structs plus team filtering.
|
||||
Full ACT combat should build from this instead of embedding hit detection in
|
||||
actors.
|
||||
|
||||
## Current Build Target
|
||||
|
||||
`xmake.lua` defines `Frostbite2DGame` for Windows. It compiles all
|
||||
`game/src/**.cpp` files, links against `Frostbite2D`, and copies `game/assets`
|
||||
next to the executable after build.
|
||||
|
||||
Build command:
|
||||
|
||||
```powershell
|
||||
xmake -b Frostbite2DGame
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
build/windows/x64/release/Frostbite2DGame.exe
|
||||
```
|
||||
|
||||
## Next Architecture Steps
|
||||
|
||||
1. Add `actor/enemy_actor.*` and reuse `PlatformMover`.
|
||||
2. Add a `combat/CombatSystem` that collects active hitboxes and hurtboxes each
|
||||
frame, resolves hits, and emits damage events.
|
||||
3. Replace hard-coded animation timing in `PlayerActor` with data definitions.
|
||||
4. Move `CreateWhiteboxLevel()` to an external level format once the first
|
||||
whitebox loop is stable.
|
||||
5. Add a `scene/gameplay_scene` base if multiple levels share spawn, camera,
|
||||
combat and pause logic.
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "player_actor.h"
|
||||
|
||||
#include "../core/asset_paths.h"
|
||||
#include "../core/game_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kSpriteOffsetX = -32.0f;
|
||||
constexpr float kSpriteOffsetY = -24.0f;
|
||||
|
||||
} // namespace
|
||||
|
||||
PlayerActor::PlayerActor() {
|
||||
SetSize(64.0f, 104.0f);
|
||||
SetTopLeftPosition(160.0f, 380.0f);
|
||||
loadSprites();
|
||||
}
|
||||
|
||||
void PlayerActor::SetMovementWorld(const PlatformWorld* world) {
|
||||
movementWorld_ = world;
|
||||
}
|
||||
|
||||
void PlayerActor::loadSprites() {
|
||||
idleSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleIdle);
|
||||
runSprite_ = frostbite2D::Sprite::createFromFile(assets::kMaleRun);
|
||||
punchSprite_ = frostbite2D::Sprite::createFromFile(assets::kMalePunch);
|
||||
|
||||
auto configure = [this](frostbite2D::Ptr<frostbite2D::Sprite> sprite) {
|
||||
if (!sprite) {
|
||||
return;
|
||||
}
|
||||
sprite->SetSize(config::kPlayerFrameSize, config::kPlayerFrameSize);
|
||||
sprite->SetSourceRect(frostbite2D::Rect(0.0f, 0.0f,
|
||||
config::kPlayerFrameSize,
|
||||
config::kPlayerFrameSize));
|
||||
sprite->SetTopLeftPosition(kSpriteOffsetX, kSpriteOffsetY);
|
||||
sprite->SetVisible(false);
|
||||
AddChild(sprite);
|
||||
};
|
||||
|
||||
configure(idleSprite_);
|
||||
configure(runSprite_);
|
||||
configure(punchSprite_);
|
||||
setAnimState(AnimState::Idle);
|
||||
}
|
||||
|
||||
void PlayerActor::OnUpdate(float deltaTime) {
|
||||
deltaTime = std::min(deltaTime, 1.0f / 30.0f);
|
||||
updateInput();
|
||||
updatePhysics(deltaTime);
|
||||
updateAnimation(deltaTime);
|
||||
}
|
||||
|
||||
void PlayerActor::updateInput() {
|
||||
const InputState input = inputProvider_.Sample();
|
||||
velocity_.x = input.moveX * config::kPlayerMoveSpeed;
|
||||
if (input.moveX < 0.0f) {
|
||||
facingLeft_ = true;
|
||||
} else if (input.moveX > 0.0f) {
|
||||
facingLeft_ = false;
|
||||
}
|
||||
|
||||
if (input.jumpPressed && grounded_) {
|
||||
velocity_.y = config::kPlayerJumpSpeed;
|
||||
grounded_ = false;
|
||||
}
|
||||
|
||||
if (input.attackPressed) {
|
||||
attackTimer_ = config::kAttackDuration;
|
||||
setAnimState(AnimState::Punch);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActor::updatePhysics(float deltaTime) {
|
||||
if (!movementWorld_) {
|
||||
return;
|
||||
}
|
||||
|
||||
MovementBody body;
|
||||
body.position = GetTopLeftPosition();
|
||||
body.size = GetSize();
|
||||
body.velocity = velocity_;
|
||||
body.grounded = grounded_;
|
||||
|
||||
body = platformMover_.Step(body, *movementWorld_, config::kGravity, deltaTime);
|
||||
velocity_ = body.velocity;
|
||||
grounded_ = body.grounded;
|
||||
SetTopLeftPosition(body.position);
|
||||
}
|
||||
|
||||
void PlayerActor::updateAnimation(float deltaTime) {
|
||||
if (attackTimer_ > 0.0f) {
|
||||
attackTimer_ = std::max(0.0f, attackTimer_ - deltaTime);
|
||||
setAnimState(AnimState::Punch);
|
||||
} else if (std::abs(velocity_.x) > 1.0f) {
|
||||
setAnimState(AnimState::Run);
|
||||
} else {
|
||||
setAnimState(AnimState::Idle);
|
||||
}
|
||||
|
||||
int frameCount = 10;
|
||||
float frameDuration = 0.1f;
|
||||
if (animState_ == AnimState::Idle) {
|
||||
frameDuration = 0.16f;
|
||||
} else if (animState_ == AnimState::Punch) {
|
||||
frameDuration = 0.07f;
|
||||
}
|
||||
|
||||
animTimer_ += deltaTime;
|
||||
while (animTimer_ >= frameDuration) {
|
||||
animTimer_ -= frameDuration;
|
||||
frameIndex_ = (frameIndex_ + 1) % frameCount;
|
||||
}
|
||||
|
||||
auto sprite = activeSprite();
|
||||
if (sprite) {
|
||||
sprite->SetSourceRect(
|
||||
frostbite2D::Rect(frameIndex_ * config::kPlayerFrameSize, 0.0f,
|
||||
config::kPlayerFrameSize,
|
||||
config::kPlayerFrameSize));
|
||||
sprite->SetFlippedX(facingLeft_);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActor::setAnimState(AnimState state) {
|
||||
if (animState_ != state) {
|
||||
animState_ = state;
|
||||
animTimer_ = 0.0f;
|
||||
frameIndex_ = 0;
|
||||
}
|
||||
|
||||
if (idleSprite_) {
|
||||
idleSprite_->SetVisible(state == AnimState::Idle);
|
||||
}
|
||||
if (runSprite_) {
|
||||
runSprite_->SetVisible(state == AnimState::Run);
|
||||
}
|
||||
if (punchSprite_) {
|
||||
punchSprite_->SetVisible(state == AnimState::Punch);
|
||||
}
|
||||
}
|
||||
|
||||
frostbite2D::Ptr<frostbite2D::Sprite> PlayerActor::activeSprite() const {
|
||||
switch (animState_) {
|
||||
case AnimState::Run:
|
||||
return runSprite_;
|
||||
case AnimState::Punch:
|
||||
return punchSprite_;
|
||||
case AnimState::Idle:
|
||||
default:
|
||||
return idleSprite_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "../core/input_state.h"
|
||||
#include "../movement/platform_world.h"
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
class PlayerActor : public frostbite2D::Actor {
|
||||
public:
|
||||
PlayerActor();
|
||||
|
||||
void SetMovementWorld(const PlatformWorld* world);
|
||||
void OnUpdate(float deltaTime) override;
|
||||
|
||||
private:
|
||||
enum class AnimState {
|
||||
Idle,
|
||||
Run,
|
||||
Punch
|
||||
};
|
||||
|
||||
void loadSprites();
|
||||
void updateInput();
|
||||
void updatePhysics(float deltaTime);
|
||||
void updateAnimation(float deltaTime);
|
||||
void setAnimState(AnimState state);
|
||||
frostbite2D::Ptr<frostbite2D::Sprite> activeSprite() const;
|
||||
|
||||
const PlatformWorld* movementWorld_ = nullptr;
|
||||
InputStateProvider inputProvider_;
|
||||
PlatformMover platformMover_;
|
||||
frostbite2D::Ptr<frostbite2D::Sprite> idleSprite_;
|
||||
frostbite2D::Ptr<frostbite2D::Sprite> runSprite_;
|
||||
frostbite2D::Ptr<frostbite2D::Sprite> punchSprite_;
|
||||
|
||||
frostbite2D::Vec2 velocity_;
|
||||
AnimState animState_ = AnimState::Idle;
|
||||
float animTimer_ = 0.0f;
|
||||
float attackTimer_ = 0.0f;
|
||||
int frameIndex_ = 0;
|
||||
bool facingLeft_ = false;
|
||||
bool grounded_ = false;
|
||||
};
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
enum class Team {
|
||||
Player,
|
||||
Enemy
|
||||
};
|
||||
|
||||
struct Hitbox {
|
||||
frostbite2D::Rect bounds;
|
||||
Team ownerTeam = Team::Player;
|
||||
int damage = 1;
|
||||
float activeSeconds = 0.0f;
|
||||
};
|
||||
|
||||
struct Hurtbox {
|
||||
frostbite2D::Rect bounds;
|
||||
Team ownerTeam = Team::Enemy;
|
||||
};
|
||||
|
||||
inline bool CanHit(const Hitbox& hitbox, const Hurtbox& hurtbox) {
|
||||
return hitbox.ownerTeam != hurtbox.ownerTeam &&
|
||||
hitbox.bounds.intersects(hurtbox.bounds);
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace ns_game::assets {
|
||||
|
||||
constexpr const char* kMaleIdle =
|
||||
"assets/character/male/Male_spritesheet_idle.png";
|
||||
constexpr const char* kMaleRun =
|
||||
"assets/character/male/Male_spritesheet_run.png";
|
||||
constexpr const char* kMalePunch =
|
||||
"assets/character/male/Male_spritesheet_punch_1.png";
|
||||
|
||||
} // namespace ns_game::assets
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace ns_game::config {
|
||||
|
||||
constexpr int kVirtualWidth = 1280;
|
||||
constexpr int kVirtualHeight = 720;
|
||||
constexpr float kPlayerFrameSize = 128.0f;
|
||||
constexpr float kPlayerMoveSpeed = 300.0f;
|
||||
constexpr float kPlayerJumpSpeed = -620.0f;
|
||||
constexpr float kGravity = 1800.0f;
|
||||
constexpr float kAttackDuration = 0.28f;
|
||||
|
||||
} // namespace ns_game::config
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "input_state.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
namespace {
|
||||
|
||||
bool isDown(const Uint8* keys, SDL_Scancode key) {
|
||||
return keys && keys[key] != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
InputState InputStateProvider::Sample() {
|
||||
const Uint8* keys = SDL_GetKeyboardState(nullptr);
|
||||
InputState state;
|
||||
|
||||
if (isDown(keys, SDL_SCANCODE_A) || isDown(keys, SDL_SCANCODE_LEFT)) {
|
||||
state.moveX -= 1.0f;
|
||||
}
|
||||
if (isDown(keys, SDL_SCANCODE_D) || isDown(keys, SDL_SCANCODE_RIGHT)) {
|
||||
state.moveX += 1.0f;
|
||||
}
|
||||
|
||||
const bool jumpHeld =
|
||||
isDown(keys, SDL_SCANCODE_SPACE) || isDown(keys, SDL_SCANCODE_W) ||
|
||||
isDown(keys, SDL_SCANCODE_UP);
|
||||
const bool attackHeld =
|
||||
isDown(keys, SDL_SCANCODE_J) || isDown(keys, SDL_SCANCODE_K);
|
||||
|
||||
state.jumpPressed = jumpHeld && !jumpHeldLastFrame_;
|
||||
state.attackPressed = attackHeld && !attackHeldLastFrame_;
|
||||
|
||||
jumpHeldLastFrame_ = jumpHeld;
|
||||
attackHeldLastFrame_ = attackHeld;
|
||||
return state;
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
struct InputState {
|
||||
float moveX = 0.0f;
|
||||
bool jumpPressed = false;
|
||||
bool attackPressed = false;
|
||||
};
|
||||
|
||||
class InputStateProvider {
|
||||
public:
|
||||
InputState Sample();
|
||||
|
||||
private:
|
||||
bool jumpHeldLastFrame_ = false;
|
||||
bool attackHeldLastFrame_ = false;
|
||||
};
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "../movement/platform_world.h"
|
||||
#include <frostbite2D/types/type_color.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
struct WhiteboxProp {
|
||||
frostbite2D::Rect bounds;
|
||||
frostbite2D::Color color;
|
||||
};
|
||||
|
||||
struct LevelDefinition {
|
||||
PlatformWorld collision;
|
||||
std::vector<WhiteboxProp> props;
|
||||
};
|
||||
|
||||
LevelDefinition CreateWhiteboxLevel();
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "level_definition.h"
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
LevelDefinition CreateWhiteboxLevel() {
|
||||
LevelDefinition level;
|
||||
level.collision.levelLeft = 0.0f;
|
||||
level.collision.levelRight = 1600.0f;
|
||||
level.collision.platforms = {
|
||||
frostbite2D::Rect(0.0f, 600.0f, 1600.0f, 80.0f),
|
||||
frostbite2D::Rect(230.0f, 500.0f, 220.0f, 28.0f),
|
||||
frostbite2D::Rect(560.0f, 430.0f, 260.0f, 28.0f),
|
||||
frostbite2D::Rect(930.0f, 510.0f, 260.0f, 28.0f),
|
||||
frostbite2D::Rect(1250.0f, 420.0f, 230.0f, 28.0f),
|
||||
};
|
||||
level.props = {
|
||||
{frostbite2D::Rect(118.0f, 392.0f, 88.0f, 208.0f),
|
||||
frostbite2D::Color(0.26f, 0.30f, 0.34f, 1.0f)},
|
||||
{frostbite2D::Rect(780.0f, 250.0f, 72.0f, 180.0f),
|
||||
frostbite2D::Color(0.26f, 0.30f, 0.34f, 1.0f)},
|
||||
{frostbite2D::Rect(1440.0f, 300.0f, 80.0f, 300.0f),
|
||||
frostbite2D::Color(0.26f, 0.30f, 0.34f, 1.0f)},
|
||||
};
|
||||
return level;
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "scene/whitebox_scene.h"
|
||||
#include "core/game_config.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
auto config = frostbite2D::AppConfig::createDefault();
|
||||
config.appName = "NS Unknown Game";
|
||||
config.windowConfig.title = "NS Unknown Game - Whitebox";
|
||||
config.windowConfig.width = 1280;
|
||||
config.windowConfig.height = 720;
|
||||
config.windowConfig.resizable = true;
|
||||
config.windowConfig.vsync = true;
|
||||
config.useVirtualResolution = true;
|
||||
config.virtualWidth = ns_game::config::kVirtualWidth;
|
||||
config.virtualHeight = ns_game::config::kVirtualHeight;
|
||||
config.resolutionMode = frostbite2D::ResolutionScaleMode::Fit;
|
||||
config.renderStyleProfile = frostbite2D::RenderStyleProfileId::PixelArt2D;
|
||||
config.maxFps = 60;
|
||||
|
||||
auto& app = frostbite2D::Application::get();
|
||||
if (!app.init(config)) {
|
||||
std::puts("NS Unknown Game init failed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
app.run([]() {
|
||||
frostbite2D::SceneManager::get().ReplaceScene(
|
||||
frostbite2D::MakePtr<ns_game::WhiteboxScene>());
|
||||
});
|
||||
|
||||
app.shutdown();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "platform_world.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
MovementBody PlatformMover::Step(const MovementBody& body,
|
||||
const PlatformWorld& world, float gravity,
|
||||
float deltaTime) const {
|
||||
MovementBody next = body;
|
||||
const frostbite2D::Vec2 previousPosition = next.position;
|
||||
|
||||
next.velocity.y += gravity * deltaTime;
|
||||
next.position += next.velocity * deltaTime;
|
||||
next.grounded = false;
|
||||
|
||||
const float previousBottom = previousPosition.y + next.size.y;
|
||||
const float newBottom = next.position.y + next.size.y;
|
||||
|
||||
for (const auto& platform : world.platforms) {
|
||||
const bool horizontallyOverlaps =
|
||||
next.position.x + next.size.x > platform.left() &&
|
||||
next.position.x < platform.right();
|
||||
const bool wasAbove = previousBottom <= platform.top() + 4.0f;
|
||||
const bool crossedTop = newBottom >= platform.top();
|
||||
if (next.velocity.y >= 0.0f && horizontallyOverlaps && wasAbove &&
|
||||
crossedTop) {
|
||||
next.position.y = platform.top() - next.size.y;
|
||||
next.velocity.y = 0.0f;
|
||||
next.grounded = true;
|
||||
}
|
||||
}
|
||||
|
||||
next.position.x =
|
||||
std::clamp(next.position.x, world.levelLeft, world.levelRight - next.size.x);
|
||||
return next;
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
struct PlatformWorld {
|
||||
float levelLeft = 0.0f;
|
||||
float levelRight = 1280.0f;
|
||||
std::vector<frostbite2D::Rect> platforms;
|
||||
};
|
||||
|
||||
struct MovementBody {
|
||||
frostbite2D::Vec2 position;
|
||||
frostbite2D::Vec2 size;
|
||||
frostbite2D::Vec2 velocity;
|
||||
bool grounded = false;
|
||||
};
|
||||
|
||||
class PlatformMover {
|
||||
public:
|
||||
MovementBody Step(const MovementBody& body, const PlatformWorld& world,
|
||||
float gravity, float deltaTime) const;
|
||||
};
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "whitebox_scene.h"
|
||||
|
||||
#include "../core/game_config.h"
|
||||
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
namespace {
|
||||
|
||||
void drawRect(const frostbite2D::Rect& rect, const frostbite2D::Color& color) {
|
||||
frostbite2D::Renderer::get().drawQuad(rect, color);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WhiteboxScene::onEnter() {
|
||||
frostbite2D::Scene::onEnter();
|
||||
level_ = CreateWhiteboxLevel();
|
||||
|
||||
player_ = frostbite2D::MakePtr<PlayerActor>();
|
||||
player_->SetMovementWorld(&level_.collision);
|
||||
AddChild(player_);
|
||||
}
|
||||
|
||||
void WhiteboxScene::OnUpdate(float deltaTime) {
|
||||
(void)deltaTime;
|
||||
updateCamera();
|
||||
}
|
||||
|
||||
void WhiteboxScene::Render() {
|
||||
drawWhitebox();
|
||||
frostbite2D::Scene::Render();
|
||||
}
|
||||
|
||||
void WhiteboxScene::drawWhitebox() const {
|
||||
const float worldWidth = level_.collision.levelRight - level_.collision.levelLeft;
|
||||
drawRect(frostbite2D::Rect(level_.collision.levelLeft, 0.0f, worldWidth,
|
||||
config::kVirtualHeight),
|
||||
frostbite2D::Color(0.10f, 0.12f, 0.14f, 1.0f));
|
||||
|
||||
for (float x = level_.collision.levelLeft; x <= level_.collision.levelRight;
|
||||
x += 80.0f) {
|
||||
drawRect(frostbite2D::Rect(x, 0.0f, 1.0f, config::kVirtualHeight),
|
||||
frostbite2D::Color(0.20f, 0.22f, 0.25f, 1.0f));
|
||||
}
|
||||
for (float y = 0.0f; y <= config::kVirtualHeight; y += 80.0f) {
|
||||
drawRect(frostbite2D::Rect(level_.collision.levelLeft, y, worldWidth, 1.0f),
|
||||
frostbite2D::Color(0.20f, 0.22f, 0.25f, 1.0f));
|
||||
}
|
||||
|
||||
for (const auto& prop : level_.props) {
|
||||
drawRect(prop.bounds, prop.color);
|
||||
}
|
||||
|
||||
for (const auto& platform : level_.collision.platforms) {
|
||||
drawRect(platform, frostbite2D::Color(0.62f, 0.68f, 0.73f, 1.0f));
|
||||
drawRect(frostbite2D::Rect(platform.left(), platform.top(), platform.width(), 4.0f),
|
||||
frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void WhiteboxScene::updateCamera() {
|
||||
if (!player_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* renderer = frostbite2D::Application::get().getRenderer();
|
||||
if (!renderer || !renderer->getCamera()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto playerPos = player_->GetTopLeftPosition();
|
||||
const float targetX = std::clamp(playerPos.x - 420.0f,
|
||||
level_.collision.levelLeft,
|
||||
level_.collision.levelRight -
|
||||
static_cast<float>(config::kVirtualWidth));
|
||||
renderer->getCamera()->setPosition(frostbite2D::Vec2(targetX, 0.0f));
|
||||
}
|
||||
|
||||
} // namespace ns_game
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "../actor/player_actor.h"
|
||||
#include "../data/level_definition.h"
|
||||
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
|
||||
namespace ns_game {
|
||||
|
||||
class WhiteboxScene : public frostbite2D::Scene {
|
||||
public:
|
||||
void onEnter() override;
|
||||
void OnUpdate(float deltaTime) override;
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void drawWhitebox() const;
|
||||
void updateCamera();
|
||||
|
||||
LevelDefinition level_;
|
||||
frostbite2D::Ptr<PlayerActor> player_;
|
||||
};
|
||||
|
||||
} // namespace ns_game
|
||||
Reference in New Issue
Block a user