feat(地图系统): 实现角色移动约束和地图切换功能
添加地图移动区域检测和角色移动约束逻辑 引入地图切换请求队列机制,支持延迟处理角色传送 在CharacterObject中实现地图边界检测和位置约束应用
This commit is contained in:
@@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
class GameMap;
|
||||||
|
|
||||||
/// @brief 角色系统的主聚合对象。
|
/// @brief 角色系统的主聚合对象。
|
||||||
///
|
///
|
||||||
/// 这个类可以理解为“角色外壳”:
|
/// 这个类可以理解为“角色外壳”:
|
||||||
@@ -170,6 +172,9 @@ private:
|
|||||||
void CommitPendingActionContext(const std::string& defaultRequestedActionId,
|
void CommitPendingActionContext(const std::string& defaultRequestedActionId,
|
||||||
const std::string& defaultSourceActionId,
|
const std::string& defaultSourceActionId,
|
||||||
CharacterStateId defaultSourceStateId);
|
CharacterStateId defaultSourceStateId);
|
||||||
|
GameMap* FindOwningMap() const;
|
||||||
|
void ApplyMapMovementConstraints(const CharacterWorldPosition& previousPosition);
|
||||||
|
void QueueMapTransitionIfNeeded();
|
||||||
void SyncActorPositionFromWorld();
|
void SyncActorPositionFromWorld();
|
||||||
bool SetActionStrict(const std::string& actionName,
|
bool SetActionStrict(const std::string& actionName,
|
||||||
const char* phase,
|
const char* phase,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public:
|
|||||||
Vec3 CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const;
|
Vec3 CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const;
|
||||||
/// 检查当前位置是否进入 town move area,用于切图/传送判定。
|
/// 检查当前位置是否进入 town move area,用于切图/传送判定。
|
||||||
MapMoveArea CheckIsItMoveArea(const Vec3& curPos) const;
|
MapMoveArea CheckIsItMoveArea(const Vec3& curPos) const;
|
||||||
|
bool TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const;
|
||||||
const std::vector<MapMoveArea>& GetMoveAreaInfo() const;
|
const std::vector<MapMoveArea>& GetMoveAreaInfo() const;
|
||||||
Rect GetMovablePositionArea(size_t index) const;
|
Rect GetMovablePositionArea(size_t index) const;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "world/GameTown.h"
|
#include "world/GameTown.h"
|
||||||
#include <frostbite2D/scene/scene.h>
|
#include <frostbite2D/scene/scene.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
|
|
||||||
@@ -13,18 +14,28 @@ public:
|
|||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
void Update(float deltaTime) override;
|
||||||
|
|
||||||
void AddCharacter(RefPtr<Actor> actor, int townId);
|
void AddCharacter(RefPtr<Actor> actor, int townId);
|
||||||
void MoveCharacter(RefPtr<Actor> actor, int townId, int area);
|
void MoveCharacter(RefPtr<Actor> actor, int townId, int area);
|
||||||
|
void RequestMoveCharacter(RefPtr<Actor> actor, int townId, int area);
|
||||||
|
|
||||||
static GameWorld* GetWorld();
|
static GameWorld* GetWorld();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct PendingCharacterMove {
|
||||||
|
RefPtr<Actor> actor;
|
||||||
|
int townId = -1;
|
||||||
|
int area = -1;
|
||||||
|
};
|
||||||
|
|
||||||
bool InitWorld();
|
bool InitWorld();
|
||||||
|
void ProcessPendingCharacterMove();
|
||||||
|
|
||||||
std::map<int, std::string> townPathMap_;
|
std::map<int, std::string> townPathMap_;
|
||||||
std::map<int, RefPtr<GameTown>> townMap_;
|
std::map<int, RefPtr<GameTown>> townMap_;
|
||||||
RefPtr<Actor> mainActor_;
|
RefPtr<Actor> mainActor_;
|
||||||
|
std::optional<PendingCharacterMove> pendingCharacterMove_;
|
||||||
int curTown_ = -1;
|
int curTown_ = -1;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "character/CharacterObject.h"
|
#include "character/CharacterObject.h"
|
||||||
|
#include "map/GameMap.h"
|
||||||
|
#include "world/GameWorld.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <cmath>
|
||||||
#include <frostbite2D/core/application.h>
|
#include <frostbite2D/core/application.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -34,6 +37,15 @@ const char* NonEmptyOrPlaceholder(const std::string& value, const char* placehol
|
|||||||
return value.empty() ? placeholder : value.c_str();
|
return value.empty() ? placeholder : value.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vec3 ToWorldVector(const CharacterWorldPosition& position) {
|
||||||
|
return Vec3(static_cast<float>(position.x), static_cast<float>(position.y),
|
||||||
|
static_cast<float>(position.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 RoundWorldCoordinate(float value) {
|
||||||
|
return static_cast<int32>(std::lround(value));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool CharacterObject::Construction(int jobId) {
|
bool CharacterObject::Construction(int jobId) {
|
||||||
@@ -147,6 +159,53 @@ void CharacterObject::SyncActorPositionFromWorld() {
|
|||||||
SetZOrder(worldPosition.y);
|
SetZOrder(worldPosition.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameMap* CharacterObject::FindOwningMap() const {
|
||||||
|
Actor* node = GetParent();
|
||||||
|
while (node) {
|
||||||
|
if (auto* map = dynamic_cast<GameMap*>(node)) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
node = node->GetParent();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterObject::ApplyMapMovementConstraints(
|
||||||
|
const CharacterWorldPosition& previousPosition) {
|
||||||
|
GameMap* map = FindOwningMap();
|
||||||
|
if (!map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 previousWorldPos = ToWorldVector(previousPosition);
|
||||||
|
Vec3 worldOffset(static_cast<float>(motor_.position.x - previousPosition.x),
|
||||||
|
static_cast<float>(motor_.position.y - previousPosition.y),
|
||||||
|
static_cast<float>(motor_.position.z - previousPosition.z));
|
||||||
|
Vec3 resolvedWorldPos = map->CheckIsItMovable(previousWorldPos, worldOffset);
|
||||||
|
motor_.position.x = RoundWorldCoordinate(resolvedWorldPos.x);
|
||||||
|
motor_.position.y = RoundWorldCoordinate(resolvedWorldPos.y);
|
||||||
|
motor_.position.z = RoundWorldCoordinate(resolvedWorldPos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterObject::QueueMapTransitionIfNeeded() {
|
||||||
|
GameMap* map = FindOwningMap();
|
||||||
|
if (!map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMap::MapMoveArea target;
|
||||||
|
if (!map->TryGetMoveAreaTarget(ToWorldVector(motor_.position), target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameWorld* world = GameWorld::GetWorld();
|
||||||
|
if (!world) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
world->RequestMoveCharacter(RefPtr<Actor>(this), target.town, target.area);
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterObject::PushCommand(const CharacterCommand& command) {
|
void CharacterObject::PushCommand(const CharacterCommand& command) {
|
||||||
commandBuffer_.Submit(command);
|
commandBuffer_.Submit(command);
|
||||||
}
|
}
|
||||||
@@ -244,7 +303,10 @@ void CharacterObject::OnUpdate(float deltaTime) {
|
|||||||
inputRouter_.EmitCommands(commandBuffer_);
|
inputRouter_.EmitCommands(commandBuffer_);
|
||||||
currentIntent_ = commandBuffer_.BuildIntent();
|
currentIntent_ = commandBuffer_.BuildIntent();
|
||||||
stateMachine_.Update(*this, commandBuffer_, currentIntent_, deltaTime);
|
stateMachine_.Update(*this, commandBuffer_, currentIntent_, deltaTime);
|
||||||
|
CharacterWorldPosition previousPosition = motor_.position;
|
||||||
motor_.Update(deltaTime);
|
motor_.Update(deltaTime);
|
||||||
|
ApplyMapMovementConstraints(previousPosition);
|
||||||
|
QueueMapTransitionIfNeeded();
|
||||||
SyncActorPositionFromWorld();
|
SyncActorPositionFromWorld();
|
||||||
SetFacing(motor_.facing);
|
SetFacing(motor_.facing);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ bool GameMap::LoadMap(const std::string &mapName) {
|
|||||||
InitTile();
|
InitTile();
|
||||||
InitBackgroundAnimation();
|
InitBackgroundAnimation();
|
||||||
InitMapAnimation();
|
InitMapAnimation();
|
||||||
// InitVirtualMovableArea();
|
InitVirtualMovableArea();
|
||||||
// InitMoveArea();
|
InitMoveArea();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -405,15 +405,24 @@ Vec3 GameMap::CheckIsItMovable(const Vec3& curPos, const Vec3& posOffset) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const {
|
GameMap::MapMoveArea GameMap::CheckIsItMoveArea(const Vec3& curPos) const {
|
||||||
|
MapMoveArea target;
|
||||||
|
if (TryGetMoveAreaTarget(curPos, target)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return MapMoveArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameMap::TryGetMoveAreaTarget(const Vec3& curPos, MapMoveArea& outTarget) const {
|
||||||
// moveArea_ and townMovableAreaTargets share the same index mapping.
|
// moveArea_ and townMovableAreaTargets share the same index mapping.
|
||||||
int currentX = RoundWorldCoordinate(curPos.x);
|
int currentX = RoundWorldCoordinate(curPos.x);
|
||||||
int currentY = RoundWorldCoordinate(curPos.y);
|
int currentY = RoundWorldCoordinate(curPos.y);
|
||||||
for (size_t i = 0; i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size(); ++i) {
|
for (size_t i = 0; i < moveArea_.size() && i < mapConfig_.townMovableAreaTargets.size(); ++i) {
|
||||||
if (moveArea_[i].containsPoint(MakeIntegerWorldPoint(currentX, currentY))) {
|
if (moveArea_[i].containsPoint(MakeIntegerWorldPoint(currentX, currentY))) {
|
||||||
return mapConfig_.townMovableAreaTargets[i];
|
outTarget = mapConfig_.townMovableAreaTargets[i];
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MapMoveArea();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GameMap::MapMoveArea>& GameMap::GetMoveAreaInfo() const {
|
const std::vector<GameMap::MapMoveArea>& GameMap::GetMoveAreaInfo() const {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ void GameWorld::onExit() {
|
|||||||
Scene::onExit();
|
Scene::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorld::Update(float deltaTime) {
|
||||||
|
Scene::Update(deltaTime);
|
||||||
|
ProcessPendingCharacterMove();
|
||||||
|
}
|
||||||
|
|
||||||
bool GameWorld::InitWorld() {
|
bool GameWorld::InitWorld() {
|
||||||
townPathMap_ = game::loadTownList();
|
townPathMap_ = game::loadTownList();
|
||||||
if (townPathMap_.empty()) {
|
if (townPathMap_.empty()) {
|
||||||
@@ -77,6 +82,26 @@ void GameWorld::MoveCharacter(RefPtr<Actor> actor, int townId, int area) {
|
|||||||
AddChild(townIt->second);
|
AddChild(townIt->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorld::RequestMoveCharacter(RefPtr<Actor> actor, int townId, int area) {
|
||||||
|
if (!actor) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"GameWorld: ignore move request without actor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingCharacterMove_ = PendingCharacterMove{actor, townId, area};
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameWorld::ProcessPendingCharacterMove() {
|
||||||
|
if (!pendingCharacterMove_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingCharacterMove move = *pendingCharacterMove_;
|
||||||
|
pendingCharacterMove_.reset();
|
||||||
|
MoveCharacter(move.actor, move.townId, move.area);
|
||||||
|
}
|
||||||
|
|
||||||
GameWorld* GameWorld::GetWorld() {
|
GameWorld* GameWorld::GetWorld() {
|
||||||
return dynamic_cast<GameWorld*>(SceneManager::get().GetCurrentScene());
|
return dynamic_cast<GameWorld*>(SceneManager::get().GetCurrentScene());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user