feat(角色): 添加角色阴影渲染功能

新增 CharacterShadowActor 类用于处理角色阴影的渲染
在 CharacterObject 中实现阴影的同步和渲染逻辑
移除 GameDebugActor 中不再使用的合成纹理预览代码
添加 EnsureCompositeTextureReady 方法确保纹理准备就绪
This commit is contained in:
2026-04-07 07:08:53 +08:00
parent 808431f92c
commit e570fec599
12 changed files with 259 additions and 218 deletions

View File

@@ -66,6 +66,10 @@ public:
void SetScale(const Vec2& scale); void SetScale(const Vec2& scale);
void SetScale(float scale); void SetScale(float scale);
const Vec2& GetSkew() const { return skew_; }
void SetSkew(const Vec2& skew);
void SetSkew(float skewX, float skewY);
const Transform2D& GetLocalTransform() const; const Transform2D& GetLocalTransform() const;
const Transform2D& GetWorldTransform() const; const Transform2D& GetWorldTransform() const;
@@ -176,6 +180,7 @@ private:
Vec2 position_; Vec2 position_;
float rotation_; float rotation_;
Vec2 scale_ = Vec2(1.0f, 1.0f); Vec2 scale_ = Vec2(1.0f, 1.0f);
Vec2 skew_;
Vec2 size_; Vec2 size_;
Vec2 anchor_; Vec2 anchor_;
bool visible_; bool visible_;

View File

@@ -58,6 +58,16 @@ void Actor::SetScale(float scale) {
markTransformDirty(); markTransformDirty();
} }
void Actor::SetSkew(const Vec2& skew) {
skew_ = skew;
markTransformDirty();
}
void Actor::SetSkew(float skewX, float skewY) {
skew_ = Vec2(skewX, skewY);
markTransformDirty();
}
void Actor::SetSize(const Vec2& size) { void Actor::SetSize(const Vec2& size) {
size_ = size; size_ = size;
markTransformDirty(); markTransformDirty();
@@ -123,6 +133,7 @@ void Actor::updateLocalTransform() const {
Vec2 anchorOffset = Vec2(anchor_.x * size_.x, anchor_.y * size_.y); Vec2 anchorOffset = Vec2(anchor_.x * size_.x, anchor_.y * size_.y);
localTransform_ = Transform2D::translation(position_.x, position_.y) * localTransform_ = Transform2D::translation(position_.x, position_.y) *
Transform2D::rotation(rotation_) * Transform2D::rotation(rotation_) *
Transform2D::skewing(skew_.x, skew_.y) *
Transform2D::scaling(scale_.x, scale_.y) * Transform2D::scaling(scale_.x, scale_.y) *
Transform2D::translation(-anchorOffset.x, -anchorOffset.y); Transform2D::translation(-anchorOffset.x, -anchorOffset.y);
} }

View File

@@ -51,6 +51,7 @@ public:
animation::AniFrame GetCurrentFrameInfo() const; animation::AniFrame GetCurrentFrameInfo() const;
void SetActionFrameFlagCallback(ActionFrameFlagCallback callback); void SetActionFrameFlagCallback(ActionFrameFlagCallback callback);
void SetActionEndCallback(ActionEndCallback callback); void SetActionEndCallback(ActionEndCallback callback);
bool EnsureCompositeTextureReady();
bool HasCompositeTexture() const; bool HasCompositeTexture() const;
Ptr<Texture> GetCompositeTexture() const; Ptr<Texture> GetCompositeTexture() const;
Vec2 GetCompositeTextureSize() const; Vec2 GetCompositeTextureSize() const;

View File

@@ -5,6 +5,7 @@
#include "character/CharacterDataLoader.h" #include "character/CharacterDataLoader.h"
#include "character/CharacterEquipmentManager.h" #include "character/CharacterEquipmentManager.h"
#include "character/CharacterInputRouter.h" #include "character/CharacterInputRouter.h"
#include "character/CharacterShadowActor.h"
#include "character/CharacterStateMachine.h" #include "character/CharacterStateMachine.h"
#include <frostbite2D/2d/actor.h> #include <frostbite2D/2d/actor.h>
#include <frostbite2D/event/event.h> #include <frostbite2D/event/event.h>
@@ -31,7 +32,7 @@ class GameMap;
class CharacterObject : public Actor { class CharacterObject : public Actor {
public: public:
CharacterObject() = default; CharacterObject() = default;
~CharacterObject() override = default; ~CharacterObject() override;
/// @brief 二段初始化角色。 /// @brief 二段初始化角色。
/// @param jobId 职业 id用来加载职业配置、动作定义和动画资源。 /// @param jobId 职业 id用来加载职业配置、动作定义和动画资源。
@@ -77,7 +78,10 @@ public:
/// ///
/// 顺序固定为:命令缓冲推进 -> 采样实时输入 -> 生成意图 -> 状态机更新 /// 顺序固定为:命令缓冲推进 -> 采样实时输入 -> 生成意图 -> 状态机更新
/// -> motor 推进 -> 投影到 Actor 坐标。 /// -> motor 推进 -> 投影到 Actor 坐标。
void Update(float deltaTime) override;
void OnUpdate(float deltaTime) override; void OnUpdate(float deltaTime) override;
void OnAdded(Actor* parent) override;
void PrepareRenderFrame();
/// @brief 统一输入入口。 /// @brief 统一输入入口。
/// ///
@@ -182,6 +186,9 @@ private:
void ApplyMapMovementConstraints(const CharacterWorldPosition& previousPosition); void ApplyMapMovementConstraints(const CharacterWorldPosition& previousPosition);
void QueueMapTransitionIfNeeded(); void QueueMapTransitionIfNeeded();
void SyncActorPositionFromWorld(); void SyncActorPositionFromWorld();
void SyncShadowAttachment();
void SyncShadowPresentation();
void DetachShadowActor();
bool SetActionStrict(const std::string& actionName, bool SetActionStrict(const std::string& actionName,
const char* phase, const char* phase,
const std::string& requestedActionId); const std::string& requestedActionId);
@@ -238,6 +245,10 @@ private:
/// 真正负责播放角色分层动画的 Actor 子节点。 /// 真正负责播放角色分层动画的 Actor 子节点。
RefPtr<CharacterAnimation> animationManager_ = nullptr; RefPtr<CharacterAnimation> animationManager_ = nullptr;
/// ???? normal ???????????
RefPtr<CharacterShadowActor> shadowActor_ = nullptr;
GameMap* shadowAttachedMap_ = nullptr;
/// 缓存上一帧 deltaTime方便状态和脚本系统查询。 /// 缓存上一帧 deltaTime方便状态和脚本系统查询。
float lastDeltaTime_ = 0.0f; float lastDeltaTime_ = 0.0f;

View File

@@ -0,0 +1,32 @@
#pragma once
#include <frostbite2D/2d/sprite.h>
namespace frostbite2D {
struct CharacterShadowFrameSnapshot {
bool visible = false;
Ptr<Texture> texture = nullptr;
Vec2 textureSize = Vec2::Zero();
Vec2 groundAnchorInTexture = Vec2::Zero();
Vec2 groundScreenPosition = Vec2::Zero();
int zOrder = 0;
uint64 textureVersion = 0;
};
class CharacterShadowActor : public Sprite {
public:
CharacterShadowActor();
void ApplyFrameSnapshot(const CharacterShadowFrameSnapshot& snapshot);
private:
void ApplyTextureState(const CharacterShadowFrameSnapshot& snapshot);
Ptr<Texture> appliedTexture_ = nullptr;
Vec2 appliedTextureSize_ = Vec2::Zero();
Vec2 appliedGroundAnchorInTexture_ = Vec2::Zero();
uint64 appliedTextureVersion_ = 0;
};
} // namespace frostbite2D

View File

@@ -1,14 +1,11 @@
#pragma once #pragma once
#include <frostbite2D/2d/actor.h> #include <frostbite2D/2d/actor.h>
#include <frostbite2D/2d/sprite.h>
namespace frostbite2D { namespace frostbite2D {
class CharacterObject; class CharacterObject;
class GameMap; class GameMap;
class NineSliceActor; class NineSliceActor;
class Sprite;
class TextSprite; class TextSprite;
/** /**
@@ -36,10 +33,6 @@ private:
void initOverlay(); void initOverlay();
void updateOverlay(); void updateOverlay();
void updateMapDebugHighlight(); void updateMapDebugHighlight();
void updateCompositePreview();
Vec2 computeScreenMatchedPreviewSize(const Vec2& textureSize) const;
void updateCompositePreviewMarker(const Vec2& textureSize, const Vec2& previewSize,
const Vec2& originInTexture);
void setOverlayVisible(bool visible); void setOverlayVisible(bool visible);
GameDebugActor(); GameDebugActor();
@@ -50,12 +43,6 @@ private:
RefPtr<NineSliceActor> background_; RefPtr<NineSliceActor> background_;
RefPtr<TextSprite> coordText_; RefPtr<TextSprite> coordText_;
RefPtr<TextSprite> actionText_; RefPtr<TextSprite> actionText_;
RefPtr<TextSprite> compositeText_;
RefPtr<Sprite> compositePreview_;
RefPtr<Actor> compositeOriginMarker_;
CharacterObject* previewCharacter_ = nullptr;
uint64 previewCompositeVersion_ = 0;
bool previewTextureAvailable_ = false;
}; };
} // namespace frostbite2D } // namespace frostbite2D

View File

@@ -30,9 +30,11 @@ public:
/// 地图进入场景后的初始化入口,目前主要负责音乐播放。 /// 地图进入场景后的初始化入口,目前主要负责音乐播放。
void Enter(); void Enter();
void Update(float deltaTime) override; void Update(float deltaTime) override;
void Render() override;
/// 将运行时对象挂到 normal 层,并按 y 值设置基础排序。 /// 将运行时对象挂到 normal 层,并按 y 值设置基础排序。
void AddObject(RefPtr<Actor> object); void AddObject(RefPtr<Actor> object);
void AddObjectToLayer(const std::string& layerName, RefPtr<Actor> object);
/// 返回地图推荐的默认相机关注点。 /// 返回地图推荐的默认相机关注点。
Vec2 GetDefaultCameraFocus() const; Vec2 GetDefaultCameraFocus() const;
@@ -75,6 +77,7 @@ private:
void InitMoveArea(); void InitMoveArea();
/// 将各层转换到屏幕空间;远景层在这里做视差滚动。 /// 将各层转换到屏幕空间;远景层在这里做视差滚动。
void updateLayerPositions(const Vec2& cameraFocus); void updateLayerPositions(const Vec2& cameraFocus);
void PrepareRuntimeObjectsForRender();
/// 原始地图配置,作为运行时装配地图内容的输入。 /// 原始地图配置,作为运行时装配地图内容的输入。
game::MapConfig mapConfig_; game::MapConfig mapConfig_;

View File

@@ -441,6 +441,11 @@ void CharacterAnimation::SetActionEndCallback(ActionEndCallback callback) {
RefreshRuntimeCallbacks(); RefreshRuntimeCallbacks();
} }
bool CharacterAnimation::EnsureCompositeTextureReady() {
RefreshCompositeTextureIfNeeded();
return HasCompositeTexture();
}
bool CharacterAnimation::HasCompositeTexture() const { bool CharacterAnimation::HasCompositeTexture() const {
return compositeCanvas_ && compositeCanvas_->IsCanvasReady() && return compositeCanvas_ && compositeCanvas_->IsCanvasReady() &&
GetCurrentCompositeFrameInfo().valid; GetCurrentCompositeFrameInfo().valid;

View File

@@ -1,4 +1,5 @@
#include "character/CharacterObject.h" #include "character/CharacterObject.h"
#include "character/CharacterShadowActor.h"
#include "common/math/GameMath.h" #include "common/math/GameMath.h"
#include "map/GameMap.h" #include "map/GameMap.h"
#include "world/GameWorld.h" #include "world/GameWorld.h"
@@ -43,14 +44,24 @@ Vec3 ToWorldVector(const CharacterWorldPosition& position) {
static_cast<float>(position.z)); static_cast<float>(position.z));
} }
constexpr int kShadowNormalLayerZBias = 1000000;
} // namespace } // namespace
CharacterObject::~CharacterObject() {
DetachShadowActor();
shadowActor_ = nullptr;
}
bool CharacterObject::Construction(int jobId) { bool CharacterObject::Construction(int jobId) {
ScopedStartupTrace startupTrace("CharacterObject::Construction"); ScopedStartupTrace startupTrace("CharacterObject::Construction");
// Reset all runtime state before rebuilding the character from config. // Reset all runtime state before rebuilding the character from config.
EnableEventReceive(); EnableEventReceive();
DetachShadowActor();
RemoveAllChildren(); RemoveAllChildren();
animationManager_ = nullptr; animationManager_ = nullptr;
shadowActor_ = nullptr;
shadowAttachedMap_ = nullptr;
config_.reset(); config_.reset();
currentAction_.clear(); currentAction_.clear();
actionLibrary_ = CharacterActionLibrary(); actionLibrary_ = CharacterActionLibrary();
@@ -102,6 +113,9 @@ bool CharacterObject::Construction(int jobId) {
} }
AddChild(animationManager_); AddChild(animationManager_);
shadowActor_ = MakePtr<CharacterShadowActor>();
shadowAttachedMap_ = nullptr;
{ {
ScopedStartupTrace stageTrace("CharacterObject initial action setup"); ScopedStartupTrace stageTrace("CharacterObject initial action setup");
if (!RequireAction("idle", "CharacterObject::Construction")) { if (!RequireAction("idle", "CharacterObject::Construction")) {
@@ -172,6 +186,57 @@ void CharacterObject::SyncActorPositionFromWorld() {
SetZOrder(worldPosition.y); SetZOrder(worldPosition.y);
} }
void CharacterObject::DetachShadowActor() {
if (shadowActor_ && shadowActor_->GetParent()) {
shadowActor_->GetParent()->RemoveChild(shadowActor_);
}
shadowAttachedMap_ = nullptr;
}
void CharacterObject::SyncShadowAttachment() {
if (!shadowActor_) {
return;
}
GameMap* map = FindOwningMap();
if (!map) {
DetachShadowActor();
return;
}
if (shadowAttachedMap_ == map && shadowActor_->GetParent()) {
return;
}
if (shadowActor_->GetParent()) {
shadowActor_->GetParent()->RemoveChild(shadowActor_);
}
map->AddObjectToLayer("normal", shadowActor_);
shadowAttachedMap_ = map;
}
void CharacterObject::SyncShadowPresentation() {
if (!shadowActor_) {
return;
}
CharacterShadowFrameSnapshot snapshot;
snapshot.groundScreenPosition = motor_.position.ToGroundPosition();
snapshot.zOrder = motor_.position.y - kShadowNormalLayerZBias;
if (HasCompositeTexture()) {
snapshot.visible = true;
snapshot.texture = GetCompositeTexture();
snapshot.textureSize = GetCompositeTextureSize();
snapshot.groundAnchorInTexture = GetCompositeGroundAnchorInTexture();
snapshot.textureVersion = GetCompositeTextureVersion();
}
shadowActor_->ApplyFrameSnapshot(snapshot);
}
GameMap* CharacterObject::FindOwningMap() const { GameMap* CharacterObject::FindOwningMap() const {
Actor* node = GetParent(); Actor* node = GetParent();
while (node) { while (node) {
@@ -317,6 +382,19 @@ void CharacterObject::ApplyHit(const HitContext& hit) {
stateMachine_.ForceHurt(*this, hurtAction); stateMachine_.ForceHurt(*this, hurtAction);
} }
void CharacterObject::Update(float deltaTime) {
Actor::Update(deltaTime);
SyncShadowAttachment();
SyncShadowPresentation();
}
void CharacterObject::PrepareRenderFrame() {
if (animationManager_) {
animationManager_->EnsureCompositeTextureReady();
}
SyncShadowPresentation();
}
void CharacterObject::OnUpdate(float deltaTime) { void CharacterObject::OnUpdate(float deltaTime) {
// Fixed update order keeps input, state transitions, and motion deterministic. // Fixed update order keeps input, state transitions, and motion deterministic.
lastDeltaTime_ = deltaTime; lastDeltaTime_ = deltaTime;
@@ -332,6 +410,12 @@ void CharacterObject::OnUpdate(float deltaTime) {
SetFacing(motor_.facing); SetFacing(motor_.facing);
} }
void CharacterObject::OnAdded(Actor* parent) {
(void)parent;
SyncShadowAttachment();
SyncShadowPresentation();
}
bool CharacterObject::OnEvent(const Event& event) { bool CharacterObject::OnEvent(const Event& event) {
if (!IsEventReceiveEnabled() || !inputEnabled_) { if (!IsEventReceiveEnabled() || !inputEnabled_) {
return false; return false;

View File

@@ -0,0 +1,61 @@
#include "character/CharacterShadowActor.h"
#include <algorithm>
namespace frostbite2D {
namespace {
constexpr Color kShadowColor(0.0f, 0.0f, 0.0f, 0.42f);
const Vec2 kShadowScale(0.9f, 0.30f);
constexpr float kShadowSkewX = 50.0f;
} // namespace
CharacterShadowActor::CharacterShadowActor() {
SetName("characterShadow");
SetColor(kShadowColor);
SetScale(kShadowScale);
// Anchor stays on the ground point while the upper silhouette falls left.
SetSkew(kShadowSkewX, 0.0f);
SetBlendMode(BlendMode::Normal);
SetVisible(false);
}
void CharacterShadowActor::ApplyFrameSnapshot(
const CharacterShadowFrameSnapshot& snapshot) {
bool hasTexture = snapshot.visible && snapshot.texture && snapshot.textureSize.x > 0.0f &&
snapshot.textureSize.y > 0.0f;
if (!hasTexture) {
SetVisible(false);
return;
}
ApplyTextureState(snapshot);
SetPosition(snapshot.groundScreenPosition);
SetZOrder(snapshot.zOrder);
SetVisible(true);
}
void CharacterShadowActor::ApplyTextureState(
const CharacterShadowFrameSnapshot& snapshot) {
if (appliedTexture_ == snapshot.texture &&
appliedTextureSize_ == snapshot.textureSize &&
appliedGroundAnchorInTexture_ == snapshot.groundAnchorInTexture &&
appliedTextureVersion_ == snapshot.textureVersion) {
return;
}
SetTexture(snapshot.texture);
SetSize(snapshot.textureSize);
float safeWidth = std::max(snapshot.textureSize.x, 1.0f);
float safeHeight = std::max(snapshot.textureSize.y, 1.0f);
SetAnchor(std::clamp(snapshot.groundAnchorInTexture.x / safeWidth, 0.0f, 1.0f),
std::clamp(snapshot.groundAnchorInTexture.y / safeHeight, 0.0f, 1.0f));
appliedTexture_ = snapshot.texture;
appliedTextureSize_ = snapshot.textureSize;
appliedGroundAnchorInTexture_ = snapshot.groundAnchorInTexture;
appliedTextureVersion_ = snapshot.textureVersion;
}
} // namespace frostbite2D

View File

@@ -4,10 +4,7 @@
#include "ui/NineSliceActor.h" #include "ui/NineSliceActor.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <algorithm> #include <algorithm>
#include <cmath>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/2d/text_sprite.h> #include <frostbite2D/2d/text_sprite.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/scene/scene.h> #include <frostbite2D/scene/scene.h>
#include <limits> #include <limits>
@@ -20,37 +17,8 @@ constexpr float kDebugHudMarginY = 12.0f;
constexpr float kDebugHudPaddingX = 8.0f; constexpr float kDebugHudPaddingX = 8.0f;
constexpr float kDebugHudPaddingY = 6.0f; constexpr float kDebugHudPaddingY = 6.0f;
constexpr float kDebugHudLineGap = 4.0f; constexpr float kDebugHudLineGap = 4.0f;
constexpr float kDebugHudPreviewGap = 8.0f;
constexpr float kDebugHudOriginMarkerHalfExtent = 3.0f;
constexpr char kDebugHudPopupImg[] = "sprite/interface/newstyle/windows/popup/popup.img"; constexpr char kDebugHudPopupImg[] = "sprite/interface/newstyle/windows/popup/popup.img";
class DebugCrosshairActor : public Actor {
public:
void Render() override {
if (!IsVisible()) {
return;
}
Renderer& renderer = Renderer::get();
Vec2 center = GetWorldTransform().transformPoint(Vec2::Zero());
Color color = Colors::Yellow;
color.a *= GetWorldOpacity();
if (color.a <= 0.0f) {
return;
}
float lineLength = kDebugHudOriginMarkerHalfExtent * 2.0f + 1.0f;
renderer.drawQuad(
Rect(center.x - kDebugHudOriginMarkerHalfExtent, center.y, lineLength,
1.0f),
color);
renderer.drawQuad(
Rect(center.x, center.y - kDebugHudOriginMarkerHalfExtent, 1.0f,
lineLength),
color);
}
};
void ConfigureTextLine(RefPtr<TextSprite> textSprite, const char* name, void ConfigureTextLine(RefPtr<TextSprite> textSprite, const char* name,
int zOrder) { int zOrder) {
if (!textSprite) { if (!textSprite) {
@@ -117,11 +85,6 @@ void GameDebugActor::SetDebugMap(GameMap* map) {
} }
void GameDebugActor::SetTrackedCharacter(CharacterObject* character) { void GameDebugActor::SetTrackedCharacter(CharacterObject* character) {
if (trackedCharacter_ != character) {
previewCharacter_ = nullptr;
previewCompositeVersion_ = 0;
previewTextureAvailable_ = false;
}
trackedCharacter_ = character; trackedCharacter_ = character;
updateOverlay(); updateOverlay();
} }
@@ -132,16 +95,6 @@ void GameDebugActor::ClearDebugContext() {
} }
debugMap_ = nullptr; debugMap_ = nullptr;
trackedCharacter_ = nullptr; trackedCharacter_ = nullptr;
previewCharacter_ = nullptr;
previewCompositeVersion_ = 0;
previewTextureAvailable_ = false;
if (compositePreview_) {
compositePreview_->SetTexture(nullptr);
compositePreview_->SetSize(0.0f, 0.0f);
}
if (compositeOriginMarker_) {
compositeOriginMarker_->SetVisible(false);
}
setOverlayVisible(false); setOverlayVisible(false);
} }
@@ -151,7 +104,7 @@ void GameDebugActor::OnUpdate(float deltaTime) {
} }
void GameDebugActor::initOverlay() { void GameDebugActor::initOverlay() {
if (background_ || coordText_ || actionText_ || compositeText_ || compositePreview_) { if (background_ || coordText_ || actionText_) {
return; return;
} }
@@ -173,42 +126,17 @@ void GameDebugActor::initOverlay() {
ConfigureTextLine(actionText_, "debugActionText", 1); ConfigureTextLine(actionText_, "debugActionText", 1);
AddChild(actionText_); AddChild(actionText_);
compositeText_ = TextSprite::create();
ConfigureTextLine(compositeText_, "debugCompositeText", 1);
AddChild(compositeText_);
compositePreview_ = MakePtr<Sprite>();
compositePreview_->SetName("debugCompositePreview");
compositePreview_->SetZOrder(1);
AddChild(compositePreview_);
compositeOriginMarker_ = MakePtr<DebugCrosshairActor>();
compositeOriginMarker_->SetName("debugCompositeOriginMarker");
compositeOriginMarker_->SetZOrder(2);
compositeOriginMarker_->SetVisible(false);
AddChild(compositeOriginMarker_);
setOverlayVisible(false); setOverlayVisible(false);
} }
void GameDebugActor::updateOverlay() { void GameDebugActor::updateOverlay() {
if (!coordText_ || !actionText_ || !compositeText_ || !compositePreview_) { if (!coordText_ || !actionText_) {
setOverlayVisible(false); setOverlayVisible(false);
return; return;
} }
updateMapDebugHighlight(); updateMapDebugHighlight();
if (!trackedCharacter_) { if (!trackedCharacter_) {
if (compositePreview_) {
compositePreview_->SetTexture(nullptr);
compositePreview_->SetSize(0.0f, 0.0f);
}
if (compositeOriginMarker_) {
compositeOriginMarker_->SetVisible(false);
}
previewCharacter_ = nullptr;
previewCompositeVersion_ = 0;
previewTextureAvailable_ = false;
setOverlayVisible(false); setOverlayVisible(false);
return; return;
} }
@@ -233,28 +161,14 @@ void GameDebugActor::updateOverlay() {
frameCount); frameCount);
actionText_->SetText(actionTextBuffer); actionText_->SetText(actionTextBuffer);
updateCompositePreview();
Vec2 coordTextSize = coordText_->GetTextSize(); Vec2 coordTextSize = coordText_->GetTextSize();
Vec2 actionTextSize = actionText_->GetTextSize(); Vec2 actionTextSize = actionText_->GetTextSize();
Vec2 compositeTextSize = compositeText_->GetTextSize(); float panelWidth = std::max(coordTextSize.x, actionTextSize.x) + kDebugHudPaddingX * 2.0f;
Vec2 previewSize = compositePreview_->IsVisible() ? compositePreview_->GetSize()
: Vec2::Zero();
float textBlockWidth = std::max(coordTextSize.x,
std::max(actionTextSize.x, compositeTextSize.x));
float panelWidth = std::max(textBlockWidth, previewSize.x) + kDebugHudPaddingX * 2.0f;
float contentHeight = coordTextSize.y; float contentHeight = coordTextSize.y;
if (actionText_->IsVisible()) { if (actionText_->IsVisible()) {
contentHeight += kDebugHudLineGap + actionTextSize.y; contentHeight += kDebugHudLineGap + actionTextSize.y;
} }
if (compositeText_->IsVisible()) {
contentHeight += kDebugHudLineGap + compositeTextSize.y;
}
if (compositePreview_->IsVisible()) {
contentHeight += kDebugHudPreviewGap + previewSize.y;
}
float panelHeight = contentHeight + kDebugHudPaddingY * 2.0f; float panelHeight = contentHeight + kDebugHudPaddingY * 2.0f;
Vec2 panelSize(panelWidth, panelHeight); Vec2 panelSize(panelWidth, panelHeight);
@@ -275,20 +189,6 @@ void GameDebugActor::updateOverlay() {
currentY += actionTextSize.y; currentY += actionTextSize.y;
} }
if (compositeText_->IsVisible()) {
currentY += kDebugHudLineGap;
compositeText_->SetPosition(kDebugHudPaddingX, currentY);
currentY += compositeTextSize.y;
}
if (compositePreview_->IsVisible()) {
currentY += kDebugHudPreviewGap;
compositePreview_->SetPosition(kDebugHudPaddingX, currentY);
updateCompositePreviewMarker(trackedCharacter_->GetCompositeTextureSize(),
compositePreview_->GetSize(),
trackedCharacter_->GetCompositeOriginInTexture());
}
setOverlayVisible(true); setOverlayVisible(true);
} }
@@ -310,94 +210,6 @@ void GameDebugActor::updateMapDebugHighlight() {
debugMap_->SetDebugHighlightedMoveAreaIndex(moveAreaIndex); debugMap_->SetDebugHighlightedMoveAreaIndex(moveAreaIndex);
} }
void GameDebugActor::updateCompositePreview() {
if (!trackedCharacter_ || !compositePreview_ || !compositeText_) {
return;
}
Ptr<Texture> compositeTexture = trackedCharacter_->GetCompositeTexture();
bool textureAvailable =
trackedCharacter_->HasCompositeTexture() && compositeTexture != nullptr;
uint64 compositeVersion =
textureAvailable ? trackedCharacter_->GetCompositeTextureVersion() : 0;
bool needsRefresh = previewCharacter_ != trackedCharacter_ ||
previewCompositeVersion_ != compositeVersion ||
previewTextureAvailable_ != textureAvailable;
if (needsRefresh) {
previewCharacter_ = trackedCharacter_;
previewCompositeVersion_ = compositeVersion;
previewTextureAvailable_ = textureAvailable;
if (textureAvailable) {
compositePreview_->SetTexture(compositeTexture);
} else {
compositePreview_->SetTexture(nullptr);
compositePreview_->SetSize(0.0f, 0.0f);
compositePreview_->SetVisible(false);
if (compositeOriginMarker_) {
compositeOriginMarker_->SetVisible(false);
}
}
}
if (!textureAvailable) {
compositeText_->SetText("Composite: unavailable");
compositeText_->SetVisible(true);
return;
}
Vec2 textureSize = trackedCharacter_->GetCompositeTextureSize();
Vec2 previewSize = computeScreenMatchedPreviewSize(textureSize);
Vec2 origin = trackedCharacter_->GetCompositeOriginInTexture();
Vec2 groundAnchor = trackedCharacter_->GetCompositeGroundAnchorInTexture();
compositePreview_->SetSize(previewSize);
compositePreview_->SetVisible(true);
updateCompositePreviewMarker(textureSize, previewSize, origin);
char compositeTextBuffer[256];
SDL_snprintf(
compositeTextBuffer, sizeof(compositeTextBuffer),
"Composite: tex %.0fx%.0f preview %.0fx%.0f origin(%.0f, %.0f) ground(%.0f, %.0f) ver %llu",
textureSize.x, textureSize.y, previewSize.x, previewSize.y, origin.x,
origin.y, groundAnchor.x, groundAnchor.y,
static_cast<unsigned long long>(trackedCharacter_->GetCompositeTextureVersion()));
compositeText_->SetText(compositeTextBuffer);
compositeText_->SetVisible(true);
}
Vec2 GameDebugActor::computeScreenMatchedPreviewSize(const Vec2& textureSize) const {
if (!trackedCharacter_ || textureSize.x <= 0.0f || textureSize.y <= 0.0f) {
return Vec2::Zero();
}
Renderer& renderer = Renderer::get();
Camera* worldCamera = renderer.getCamera();
float zoom = worldCamera ? worldCamera->getZoom() : 1.0f;
Vec2 worldScale = math::extractScale(trackedCharacter_->GetWorldTransform().matrix);
return Vec2(std::max(textureSize.x * std::abs(worldScale.x) * zoom, 1.0f),
std::max(textureSize.y * std::abs(worldScale.y) * zoom, 1.0f));
}
void GameDebugActor::updateCompositePreviewMarker(const Vec2& textureSize,
const Vec2& previewSize,
const Vec2& originInTexture) {
if (!compositeOriginMarker_ || !compositePreview_ || textureSize.x <= 0.0f ||
textureSize.y <= 0.0f || previewSize.x <= 0.0f || previewSize.y <= 0.0f) {
if (compositeOriginMarker_) {
compositeOriginMarker_->SetVisible(false);
}
return;
}
float scaleX = previewSize.x / textureSize.x;
float scaleY = previewSize.y / textureSize.y;
Vec2 previewTopLeft = compositePreview_->GetPosition();
compositeOriginMarker_->SetPosition(previewTopLeft.x + originInTexture.x * scaleX,
previewTopLeft.y + originInTexture.y * scaleY);
compositeOriginMarker_->SetVisible(compositePreview_->IsVisible());
}
void GameDebugActor::setOverlayVisible(bool visible) { void GameDebugActor::setOverlayVisible(bool visible) {
if (background_) { if (background_) {
background_->SetVisible(visible); background_->SetVisible(visible);
@@ -408,17 +220,6 @@ void GameDebugActor::setOverlayVisible(bool visible) {
if (actionText_) { if (actionText_) {
actionText_->SetVisible(visible); actionText_->SetVisible(visible);
} }
if (compositeText_) {
compositeText_->SetVisible(visible);
}
if (compositePreview_) {
compositePreview_->SetVisible(visible && previewTextureAvailable_);
}
if (compositeOriginMarker_) {
compositeOriginMarker_->SetVisible(visible && previewTextureAvailable_ &&
compositePreview_ &&
compositePreview_->IsVisible());
}
} }
} // namespace frostbite2D } // namespace frostbite2D

View File

@@ -1,5 +1,6 @@
#include "audio/MapAudioController.h" #include "audio/MapAudioController.h"
#include "common/math/GameMath.h" #include "common/math/GameMath.h"
#include "character/CharacterObject.h"
#include "map/GameMap.h" #include "map/GameMap.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <frostbite2D/2d/sprite.h> #include <frostbite2D/2d/sprite.h>
@@ -157,6 +158,29 @@ void GameMap::Enter() {
void GameMap::Update(float deltaTime) { Actor::Update(deltaTime); } void GameMap::Update(float deltaTime) { Actor::Update(deltaTime); }
void GameMap::Render() {
PrepareRuntimeObjectsForRender();
Actor::Render();
}
void GameMap::PrepareRuntimeObjectsForRender() {
auto layerIt = layerMap_.find("normal");
if (layerIt == layerMap_.end() || !layerIt->second) {
return;
}
for (auto it = layerIt->second->GetChildren().begin();
it != layerIt->second->GetChildren().end(); ++it) {
if (!*it) {
continue;
}
if (auto* character = dynamic_cast<CharacterObject*>((*it).Get())) {
character->PrepareRenderFrame();
}
}
}
void GameMap::InitTile() { void GameMap::InitTile() {
if (mapConfig_.tilePaths.empty() && mapConfig_.extendedTilePaths.empty()) { if (mapConfig_.tilePaths.empty() && mapConfig_.extendedTilePaths.empty()) {
return; return;
@@ -403,12 +427,28 @@ void GameMap::updateLayerPositions(const Vec2 &cameraFocus) {
} }
void GameMap::AddObject(RefPtr<Actor> object) { void GameMap::AddObject(RefPtr<Actor> object) {
AddObjectToLayer("normal", object);
}
void GameMap::AddObjectToLayer(const std::string& layerName, RefPtr<Actor> object) {
if (!object) { if (!object) {
return; return;
} }
// Keep dynamic objects on the normal layer and sort them by y.
auto layerIt = layerMap_.find(layerName);
if (layerIt == layerMap_.end() || !layerIt->second) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"GameMap: layer %s missing, fallback to normal",
layerName.c_str());
layerIt = layerMap_.find("normal");
if (layerIt == layerMap_.end() || !layerIt->second) {
return;
}
}
// Keep runtime objects sorted by their ground y inside the target layer.
object->SetZOrder(static_cast<int>(object->GetPosition().y)); object->SetZOrder(static_cast<int>(object->GetPosition().y));
layerMap_["normal"]->AddObject(object); layerIt->second->AddObject(object);
} }
Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const { Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const {