refactor(character): 使用整数坐标优化角色位置同步

- 将CharacterWorldPosition改为使用整数坐标,避免浮点精度问题
- 添加位置余数处理,确保移动平滑性
- 统一角色位置同步逻辑到SyncActorPositionFromWorld方法
- 修改地图移动检测使用整数坐标判断
This commit is contained in:
2026-04-05 11:42:39 +08:00
parent c4eefab70c
commit 2b0cfc6ce5
5 changed files with 122 additions and 37 deletions

View File

@@ -133,13 +133,18 @@ void CharacterObject::SetDirection(int direction) {
void CharacterObject::SetCharacterPosition(const Vec2& pos) {
motor_.SetGroundPosition(pos);
SetWorldPosition(motor_.position);
SyncActorPositionFromWorld();
}
void CharacterObject::SetWorldPosition(const CharacterWorldPosition& pos) {
motor_.position = pos;
SetPosition(pos.ToScreenPosition());
SetZOrder(static_cast<int>(pos.y));
motor_.SetWorldPosition(pos);
SyncActorPositionFromWorld();
}
void CharacterObject::SyncActorPositionFromWorld() {
const CharacterWorldPosition& worldPosition = motor_.position;
SetPosition(worldPosition.ToScreenPosition());
SetZOrder(worldPosition.y);
}
void CharacterObject::PushCommand(const CharacterCommand& command) {
@@ -240,7 +245,7 @@ void CharacterObject::OnUpdate(float deltaTime) {
currentIntent_ = commandBuffer_.BuildIntent();
stateMachine_.Update(*this, commandBuffer_, currentIntent_, deltaTime);
motor_.Update(deltaTime);
SetWorldPosition(motor_.position);
SyncActorPositionFromWorld();
SetFacing(motor_.facing);
}

View File

@@ -7,6 +7,7 @@
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/resource/audio_database.h>
#include <algorithm>
#include <cmath>
namespace frostbite2D {
@@ -23,6 +24,19 @@ static const int kLayerOrders[] = {
constexpr int kTileRootZOrder = -1000000;
constexpr float kExtendedTileStepY = 120.0f;
int RoundWorldCoordinate(float value) {
return static_cast<int>(std::lround(value));
}
Vec2 MakeIntegerWorldPoint(int x, int y) {
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
Vec3 MakeIntegerWorldPosition(int x, int y, int z) {
return Vec3(static_cast<float>(x), static_cast<float>(y),
static_cast<float>(z));
}
// 地图里的 tile/img 统一走这里创建,失败时返回空 Sprite
// 占位,避免整张地图中断。
Ptr<Sprite> createMapSprite(const std::string& path, int index) {
@@ -349,20 +363,24 @@ void GameMap::AddObject(RefPtr<Actor> object) {
}
Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const {
Vec3 result = curPos;
int currentX = RoundWorldCoordinate(curPos.x);
int currentY = RoundWorldCoordinate(curPos.y);
int currentZ = RoundWorldCoordinate(curPos.z);
Vec3 result = MakeIntegerWorldPosition(currentX, currentY, currentZ);
if (movableArea_.empty()) {
result += posOffset;
return result;
return MakeIntegerWorldPosition(RoundWorldCoordinate(curPos.x + posOffset.x),
RoundWorldCoordinate(curPos.y + posOffset.y),
RoundWorldCoordinate(curPos.z + posOffset.z));
}
float targetX = curPos.x + posOffset.x;
float targetY = curPos.y + posOffset.y;
int targetX = RoundWorldCoordinate(curPos.x + posOffset.x);
int targetY = RoundWorldCoordinate(curPos.y + posOffset.y);
// X/Y
// 分开判断,这样贴着边移动时不会因为一个方向越界而把另一个方向也一起锁死。
// Check X and Y separately so edge sliding does not lock both axes.
bool isXValid = false;
for (const auto& area : movableArea_) {
if (area.containsPoint(Vec2(targetX, curPos.y))) {
if (area.containsPoint(MakeIntegerWorldPoint(targetX, currentY))) {
isXValid = true;
break;
}
@@ -370,26 +388,28 @@ Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const
bool isYValid = false;
for (const auto& area : movableArea_) {
if (area.containsPoint(Vec2(curPos.x, targetY))) {
if (area.containsPoint(MakeIntegerWorldPoint(currentX, targetY))) {
isYValid = true;
break;
}
}
if (isXValid) {
result.x = targetX;
result.x = static_cast<float>(targetX);
}
if (isYValid) {
result.y = targetY;
result.y = static_cast<float>(targetY);
}
result.z += posOffset.z;
result.z = static_cast<float>(RoundWorldCoordinate(curPos.z + posOffset.z));
return result;
}
GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const {
// moveArea_ 和配置里的 target 是一一对应的,命中后直接返回同索引的目标信息。
// moveArea_ and townMovableAreaTargets share the same index mapping.
int currentX = RoundWorldCoordinate(curPos.x);
int currentY = RoundWorldCoordinate(curPos.y);
for (size_t i = 0; i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size(); ++i) {
if (moveArea_[i].containsPoint(Vec2(curPos.x, curPos.y))) {
if (moveArea_[i].containsPoint(MakeIntegerWorldPoint(currentX, currentY))) {
return mapConfig_.townMovableAreaTargets[i];
}
}

View File

@@ -1,9 +1,20 @@
#include "world/GameTown.h"
#include "character/CharacterObject.h"
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>
namespace frostbite2D {
namespace {
Vec2 RoundWorldPoint(const Vec2& pos) {
return Vec2(static_cast<float>(std::lround(pos.x)),
static_cast<float>(std::lround(pos.y)));
}
} // namespace
GameTown::GameTown() = default;
bool GameTown::Init(int index, const std::string& townPath) {
@@ -96,7 +107,12 @@ void GameTown::AddCharacter(RefPtr<Actor> actor, int areaIndex) {
if (actor) {
cameraController_.SetDebugEnabled(false);
if (areaIndex == -2 && sariaRoomPos_.x >= 0.0f && sariaRoomPos_.y >= 0.0f) {
actor->SetPosition(sariaRoomPos_);
Vec2 spawnPos = RoundWorldPoint(sariaRoomPos_);
if (auto* character = dynamic_cast<CharacterObject*>(actor.Get())) {
character->SetCharacterPosition(spawnPos);
} else {
actor->SetPosition(spawnPos);
}
}
mapIt->map->AddObject(actor);
cameraController_.SetFocus(actor->GetPosition());