feat(角色): 添加角色阴影渲染功能
新增 CharacterShadowActor 类用于处理角色阴影的渲染 在 CharacterObject 中实现阴影的同步和渲染逻辑 移除 GameDebugActor 中不再使用的合成纹理预览代码 添加 EnsureCompositeTextureReady 方法确保纹理准备就绪
This commit is contained in:
@@ -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_;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
32
Game/include/character/CharacterShadowActor.h
Normal file
32
Game/include/character/CharacterShadowActor.h
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
61
Game/src/character/CharacterShadowActor.cpp
Normal file
61
Game/src/character/CharacterShadowActor.cpp
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user