feat(动画): 添加角色动作合成纹理功能
实现角色动作的离屏渲染合成功能,支持获取合成纹理及其相关信息: 1. 新增CanvasActor用于离屏渲染 2. 新增RenderTexture封装FBO和纹理 3. 扩展Renderer支持离屏渲染到纹理 4. 为CharacterAnimation添加合成纹理生成逻辑 5. 在调试界面添加合成纹理预览功能
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
#include "common/debug/GameDebugActor.h"
|
||||
#include "common/debug/GameDebugActor.h"
|
||||
#include "character/CharacterObject.h"
|
||||
#include "map/GameMap.h"
|
||||
#include "ui/NineSliceActor.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/2d/text_sprite.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <limits>
|
||||
|
||||
@@ -15,8 +19,38 @@ constexpr float kDebugHudMarginX = 12.0f;
|
||||
constexpr float kDebugHudMarginY = 12.0f;
|
||||
constexpr float kDebugHudPaddingX = 8.0f;
|
||||
constexpr float kDebugHudPaddingY = 6.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";
|
||||
|
||||
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,
|
||||
int zOrder) {
|
||||
if (!textSprite) {
|
||||
@@ -83,6 +117,11 @@ void GameDebugActor::SetDebugMap(GameMap* map) {
|
||||
}
|
||||
|
||||
void GameDebugActor::SetTrackedCharacter(CharacterObject* character) {
|
||||
if (trackedCharacter_ != character) {
|
||||
previewCharacter_ = nullptr;
|
||||
previewCompositeVersion_ = 0;
|
||||
previewTextureAvailable_ = false;
|
||||
}
|
||||
trackedCharacter_ = character;
|
||||
updateOverlay();
|
||||
}
|
||||
@@ -93,6 +132,16 @@ void GameDebugActor::ClearDebugContext() {
|
||||
}
|
||||
debugMap_ = 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);
|
||||
}
|
||||
|
||||
@@ -102,7 +151,7 @@ void GameDebugActor::OnUpdate(float deltaTime) {
|
||||
}
|
||||
|
||||
void GameDebugActor::initOverlay() {
|
||||
if (background_ || coordText_) {
|
||||
if (background_ || coordText_ || actionText_ || compositeText_ || compositePreview_) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,43 +169,233 @@ void GameDebugActor::initOverlay() {
|
||||
ConfigureTextLine(coordText_, "debugCoordText", 1);
|
||||
AddChild(coordText_);
|
||||
|
||||
actionText_ = TextSprite::create();
|
||||
ConfigureTextLine(actionText_, "debugActionText", 1);
|
||||
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);
|
||||
}
|
||||
|
||||
void GameDebugActor::updateOverlay() {
|
||||
if (!coordText_ || !debugMap_ || !trackedCharacter_ ||
|
||||
!debugMap_->IsDebugModeEnabled()) {
|
||||
if (debugMap_) {
|
||||
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
|
||||
}
|
||||
if (!coordText_ || !actionText_ || !compositeText_ || !compositePreview_) {
|
||||
setOverlayVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
updateMapDebugHighlight();
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterWorldPosition& worldPosition = trackedCharacter_->GetWorldPosition();
|
||||
|
||||
char coordTextBuffer[96];
|
||||
SDL_snprintf(coordTextBuffer, sizeof(coordTextBuffer), "角色坐标: (%d, %d, %d)",
|
||||
worldPosition.x, worldPosition.y, worldPosition.z);
|
||||
coordText_->SetText(coordTextBuffer);
|
||||
|
||||
int frameCount = std::max(trackedCharacter_->GetCurrentAnimationFrameCount(), 1);
|
||||
int frameIndex = std::clamp(trackedCharacter_->GetCurrentAnimationFrameIndex(), 0,
|
||||
frameCount - 1);
|
||||
char actionTextBuffer[128];
|
||||
SDL_snprintf(actionTextBuffer, sizeof(actionTextBuffer),
|
||||
"Action: %s dir:%c frame:%d/%d",
|
||||
trackedCharacter_->GetCurrentAction().empty()
|
||||
? "<none>"
|
||||
: trackedCharacter_->GetCurrentAction().c_str(),
|
||||
trackedCharacter_->GetDirection() >= 0 ? 'R' : 'L', frameIndex + 1,
|
||||
frameCount);
|
||||
actionText_->SetText(actionTextBuffer);
|
||||
|
||||
updateCompositePreview();
|
||||
|
||||
Vec2 coordTextSize = coordText_->GetTextSize();
|
||||
Vec2 actionTextSize = actionText_->GetTextSize();
|
||||
Vec2 compositeTextSize = compositeText_->GetTextSize();
|
||||
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;
|
||||
if (actionText_->IsVisible()) {
|
||||
contentHeight += kDebugHudLineGap + actionTextSize.y;
|
||||
}
|
||||
if (compositeText_->IsVisible()) {
|
||||
contentHeight += kDebugHudLineGap + compositeTextSize.y;
|
||||
}
|
||||
if (compositePreview_->IsVisible()) {
|
||||
contentHeight += kDebugHudPreviewGap + previewSize.y;
|
||||
}
|
||||
|
||||
float panelHeight = contentHeight + kDebugHudPaddingY * 2.0f;
|
||||
Vec2 panelSize(panelWidth, panelHeight);
|
||||
if (background_) {
|
||||
background_->SetSize(panelSize);
|
||||
}
|
||||
|
||||
SetPosition(kDebugHudMarginX, kDebugHudMarginY);
|
||||
SetScale(1.0f);
|
||||
|
||||
float currentY = kDebugHudPaddingY;
|
||||
coordText_->SetPosition(kDebugHudPaddingX, currentY);
|
||||
currentY += coordTextSize.y;
|
||||
|
||||
if (actionText_->IsVisible()) {
|
||||
currentY += kDebugHudLineGap;
|
||||
actionText_->SetPosition(kDebugHudPaddingX, currentY);
|
||||
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);
|
||||
}
|
||||
|
||||
void GameDebugActor::updateMapDebugHighlight() {
|
||||
if (!debugMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trackedCharacter_ || !debugMap_->IsDebugModeEnabled()) {
|
||||
debugMap_->SetDebugHighlightedMoveAreaIndex(GameMap::kInvalidMoveAreaIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
const CharacterWorldPosition& worldPosition = trackedCharacter_->GetWorldPosition();
|
||||
Vec3 currentWorldPos(static_cast<float>(worldPosition.x),
|
||||
static_cast<float>(worldPosition.y),
|
||||
static_cast<float>(worldPosition.z));
|
||||
size_t moveAreaIndex = debugMap_->FindMoveAreaIndex(currentWorldPos);
|
||||
debugMap_->SetDebugHighlightedMoveAreaIndex(moveAreaIndex);
|
||||
}
|
||||
|
||||
char coordTextBuffer[96];
|
||||
SDL_snprintf(coordTextBuffer, sizeof(coordTextBuffer), "角色坐标: (%d, %d, %d)",
|
||||
worldPosition.x, worldPosition.y, worldPosition.z);
|
||||
coordText_->SetText(coordTextBuffer);
|
||||
|
||||
Vec2 coordTextSize = coordText_->GetTextSize();
|
||||
float panelWidth = coordTextSize.x + kDebugHudPaddingX * 2.0f;
|
||||
float panelHeight = coordTextSize.y + kDebugHudPaddingY * 2.0f;
|
||||
Vec2 panelSize(panelWidth, panelHeight);
|
||||
if (background_) {
|
||||
background_->SetSize(panelSize);
|
||||
void GameDebugActor::updateCompositePreview() {
|
||||
if (!trackedCharacter_ || !compositePreview_ || !compositeText_) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetPosition(kDebugHudMarginX, kDebugHudMarginY);
|
||||
SetScale(1.0f);
|
||||
coordText_->SetPosition(kDebugHudPaddingX, kDebugHudPaddingY);
|
||||
setOverlayVisible(true);
|
||||
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) {
|
||||
@@ -166,6 +405,20 @@ void GameDebugActor::setOverlayVisible(bool visible) {
|
||||
if (coordText_) {
|
||||
coordText_->SetVisible(visible);
|
||||
}
|
||||
if (actionText_) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user