feat: 添加游戏核心模块,包括地图、角色、场景和世界管理
实现游戏基础架构,包含以下主要功能: - 地图系统:支持地图加载、图层管理和相机控制 - 角色系统:实现角色装备、动画和行为管理 - 场景系统:提供测试场景和世界场景切换 - 世界管理:处理城镇和区域切换逻辑 - 数据加载:添加角色和装备配置加载器 这些改动为游戏开发奠定了基础框架,支持后续功能扩展
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <frostbite2D/2d/actor.h>
|
#include <frostbite2D/2d/actor.h>
|
||||||
#include <frostbite2D/types/type_math.h>
|
#include <frostbite2D/types/type_math.h>
|
||||||
44
Game/include/character/CharacterAnimation.h
Normal file
44
Game/include/character/CharacterAnimation.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "character/CharacterDataLoader.h"
|
||||||
|
#include "character/CharacterEquipmentManager.h"
|
||||||
|
#include <frostbite2D/2d/actor.h>
|
||||||
|
#include <frostbite2D/animation/animation.h>
|
||||||
|
#include <frostbite2D/base/RefPtr.h>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
class CharacterObject;
|
||||||
|
|
||||||
|
class CharacterAnimation : public Actor {
|
||||||
|
public:
|
||||||
|
using ActionAnimationList = std::map<std::string, std::vector<RefPtr<Animation>>>;
|
||||||
|
|
||||||
|
bool Init(CharacterObject* parent,
|
||||||
|
const character::CharacterConfig& config,
|
||||||
|
const CharacterEquipmentManager& equipmentManager);
|
||||||
|
|
||||||
|
void SetAction(const std::string& actionName);
|
||||||
|
void SetDirection(int direction);
|
||||||
|
const std::string& GetCurrentAction() const { return currentActionTag_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string FormatImgPath(std::string path, Animation::ReplaceData data);
|
||||||
|
|
||||||
|
void CreateAnimationBySlot(const std::string& actionName,
|
||||||
|
const std::string& slotName,
|
||||||
|
const std::string& actionPath,
|
||||||
|
const character::CharacterConfig& config,
|
||||||
|
const CharacterEquipmentManager& equipmentManager);
|
||||||
|
void ApplyFlipRecursive(Actor* actor, bool flipped) const;
|
||||||
|
|
||||||
|
CharacterObject* parent_ = nullptr;
|
||||||
|
ActionAnimationList actionAnimations_;
|
||||||
|
std::string currentActionTag_;
|
||||||
|
int direction_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
44
Game/include/character/CharacterDataLoader.h
Normal file
44
Game/include/character/CharacterDataLoader.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace frostbite2D::character {
|
||||||
|
|
||||||
|
struct JobConfig {
|
||||||
|
std::string name;
|
||||||
|
std::map<std::string, int> defaultAvatarList;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CharacterConfig {
|
||||||
|
int jobId = -1;
|
||||||
|
std::string jobTag;
|
||||||
|
std::string baseJobPath;
|
||||||
|
JobConfig baseJobConfig;
|
||||||
|
std::map<std::string, std::string> animationPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EquipmentVariation {
|
||||||
|
int layer = 0;
|
||||||
|
std::string animationGroup;
|
||||||
|
std::array<int, 2> imgFormat = {0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EquipmentConfig {
|
||||||
|
int id = -1;
|
||||||
|
std::string path;
|
||||||
|
std::string name;
|
||||||
|
std::vector<int> usableJobs;
|
||||||
|
std::map<int, std::vector<EquipmentVariation>> jobAnimations;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool loadCharacterIndex(std::map<int, std::string>& outIndex);
|
||||||
|
std::optional<CharacterConfig> loadCharacterConfig(int jobId);
|
||||||
|
|
||||||
|
bool loadEquipmentIndex(std::map<int, std::string>& outIndex);
|
||||||
|
std::optional<EquipmentConfig> loadEquipmentConfig(int equipmentId);
|
||||||
|
|
||||||
|
} // namespace frostbite2D::character
|
||||||
22
Game/include/character/CharacterEquipmentManager.h
Normal file
22
Game/include/character/CharacterEquipmentManager.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "character/CharacterDataLoader.h"
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
class CharacterEquipmentManager {
|
||||||
|
public:
|
||||||
|
bool Init(const character::JobConfig& jobConfig);
|
||||||
|
|
||||||
|
const character::EquipmentConfig* GetEquip(const std::string& slotName) const;
|
||||||
|
const std::map<std::string, character::EquipmentConfig>& GetDefaultEquipment() const {
|
||||||
|
return defaultEquipment_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, character::EquipmentConfig> defaultEquipment_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
43
Game/include/character/CharacterObject.h
Normal file
43
Game/include/character/CharacterObject.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "character/CharacterAnimation.h"
|
||||||
|
#include "character/CharacterDataLoader.h"
|
||||||
|
#include "character/CharacterEquipmentManager.h"
|
||||||
|
#include <frostbite2D/2d/actor.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
class CharacterObject : public Actor {
|
||||||
|
public:
|
||||||
|
CharacterObject() = default;
|
||||||
|
~CharacterObject() override = default;
|
||||||
|
|
||||||
|
bool Construction(int jobId);
|
||||||
|
|
||||||
|
void SetAction(const std::string& actionName);
|
||||||
|
void SetDirection(int direction);
|
||||||
|
void SetCharacterPosition(const Vec2& pos);
|
||||||
|
|
||||||
|
int GetJobId() const { return jobId_; }
|
||||||
|
int GetGrowType() const { return growType_; }
|
||||||
|
int GetDirection() const { return direction_; }
|
||||||
|
const std::string& GetCurrentAction() const { return currentAction_; }
|
||||||
|
|
||||||
|
const character::CharacterConfig* GetConfig() const {
|
||||||
|
return config_ ? &config_.value() : nullptr;
|
||||||
|
}
|
||||||
|
const CharacterEquipmentManager& GetEquipmentManager() const { return equipmentManager_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int jobId_ = -1;
|
||||||
|
int growType_ = -1;
|
||||||
|
int direction_ = 1;
|
||||||
|
std::string currentAction_;
|
||||||
|
std::optional<character::CharacterConfig> config_;
|
||||||
|
CharacterEquipmentManager equipmentManager_;
|
||||||
|
RefPtr<CharacterAnimation> animationManager_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "GameDataLoader.h"
|
#include "map/GameDataLoader.h"
|
||||||
#include "GameMapLayer.h"
|
#include "map/GameMapLayer.h"
|
||||||
#include <frostbite2D/2d/actor.h>
|
#include <frostbite2D/2d/actor.h>
|
||||||
#include <frostbite2D/audio/music.h>
|
#include <frostbite2D/audio/music.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Camera/GameCameraController.h"
|
#include "camera/GameCameraController.h"
|
||||||
#include "GameMap.h"
|
#include "character/CharacterObject.h"
|
||||||
|
#include "map/GameMap.h"
|
||||||
#include <frostbite2D/scene/scene.h>
|
#include <frostbite2D/scene/scene.h>
|
||||||
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
@@ -17,6 +18,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
GameCameraController cameraController_;
|
GameCameraController cameraController_;
|
||||||
|
RefPtr<CharacterObject> character_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
RefPtr<GameMap> map_;
|
RefPtr<GameMap> map_;
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Camera/GameCameraController.h"
|
#include "camera/GameCameraController.h"
|
||||||
#include "GameDataLoader.h"
|
#include "map/GameDataLoader.h"
|
||||||
#include "GameMap.h"
|
#include "map/GameMap.h"
|
||||||
#include <frostbite2D/2d/actor.h>
|
#include <frostbite2D/2d/actor.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "GameTown.h"
|
#include "world/GameTown.h"
|
||||||
#include <frostbite2D/scene/scene.h>
|
#include <frostbite2D/scene/scene.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
#include <frostbite2D/resource/sound_pack_archive.h>
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
#include <frostbite2D/scene/scene.h>
|
#include <frostbite2D/scene/scene.h>
|
||||||
#include <frostbite2D/scene/scene_manager.h>
|
#include <frostbite2D/scene/scene_manager.h>
|
||||||
#include "Actor/GameMapTestScene.h"
|
#include "scene/GameMapTestScene.h"
|
||||||
#include "Actor/GameWorld.h"
|
#include "world/GameWorld.h"
|
||||||
|
|
||||||
using namespace frostbite2D;
|
using namespace frostbite2D;
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "Camera/GameCameraController.h"
|
#include "camera/GameCameraController.h"
|
||||||
#include "Actor/GameMap.h"
|
#include "map/GameMap.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <frostbite2D/graphics/camera.h>
|
#include <frostbite2D/graphics/camera.h>
|
||||||
180
Game/src/character/CharacterAnimation.cpp
Normal file
180
Game/src/character/CharacterAnimation.cpp
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#include "character/CharacterAnimation.h"
|
||||||
|
#include "character/CharacterObject.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <frostbite2D/2d/sprite.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::array<const char*, 12> kAvatarParts = {
|
||||||
|
"weapon_avatar", "aurora_avatar", "hair_avatar", "hat_avatar",
|
||||||
|
"face_avatar", "breast_avatar", "coat_avatar", "skin_avatar",
|
||||||
|
"waist_avatar", "pants_avatar", "shoes_avatar", "weapon"};
|
||||||
|
|
||||||
|
std::string truncatePath(const std::string& path) {
|
||||||
|
size_t slashPos = path.find_last_of('/');
|
||||||
|
if (slashPos == std::string::npos) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return path.substr(0, slashPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string actionTail(const std::string& path) {
|
||||||
|
size_t slashPos = path.find_last_of('/');
|
||||||
|
if (slashPos == std::string::npos) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return path.substr(slashPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool CharacterAnimation::Init(CharacterObject* parent,
|
||||||
|
const character::CharacterConfig& config,
|
||||||
|
const CharacterEquipmentManager& equipmentManager) {
|
||||||
|
parent_ = parent;
|
||||||
|
actionAnimations_.clear();
|
||||||
|
currentActionTag_.clear();
|
||||||
|
direction_ = 1;
|
||||||
|
|
||||||
|
for (const auto& [actionName, actionPath] : config.animationPath) {
|
||||||
|
for (const char* slotName : kAvatarParts) {
|
||||||
|
CreateAnimationBySlot(actionName, slotName, actionPath, config, equipmentManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionAnimations_.empty()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterAnimation: no usable action animations for job %d",
|
||||||
|
config.jobId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionAnimations_.count("rest") > 0) {
|
||||||
|
SetAction("rest");
|
||||||
|
} else {
|
||||||
|
SetAction(actionAnimations_.begin()->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CharacterAnimation::FormatImgPath(std::string path, Animation::ReplaceData data) {
|
||||||
|
size_t pos = path.find("%04d");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
path.replace(pos, 4, "%02d%02d");
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[512] = {};
|
||||||
|
std::snprintf(buffer, sizeof(buffer), path.c_str(), data.param1, data.param2);
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimation::CreateAnimationBySlot(
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& slotName,
|
||||||
|
const std::string& actionPath,
|
||||||
|
const character::CharacterConfig& config,
|
||||||
|
const CharacterEquipmentManager& equipmentManager) {
|
||||||
|
if (slotName == std::string("skin_avatar")) {
|
||||||
|
Animation::ReplaceData replaceData(0, 0);
|
||||||
|
if (const auto* equip = equipmentManager.GetEquip(slotName)) {
|
||||||
|
auto it = equip->jobAnimations.find(config.jobId);
|
||||||
|
if (it != equip->jobAnimations.end() && !it->second.empty()) {
|
||||||
|
replaceData.param1 = it->second.front().imgFormat[0];
|
||||||
|
replaceData.param2 = it->second.front().imgFormat[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto animation = MakePtr<Animation>(actionPath, FormatImgPath, replaceData);
|
||||||
|
if (!animation || !animation->IsUsable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
animation->SetVisible(false);
|
||||||
|
AddChild(animation);
|
||||||
|
actionAnimations_[actionName].push_back(animation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* equip = equipmentManager.GetEquip(slotName);
|
||||||
|
if (!equip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto jobAniIt = equip->jobAnimations.find(config.jobId);
|
||||||
|
if (jobAniIt == equip->jobAnimations.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string equipDir = truncatePath(equip->path);
|
||||||
|
std::string actionPathTail = actionTail(actionPath);
|
||||||
|
for (const auto& variation : jobAniIt->second) {
|
||||||
|
if (variation.animationGroup.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string aniPath = equipDir + "/" + variation.animationGroup + actionPathTail;
|
||||||
|
auto animation = MakePtr<Animation>(
|
||||||
|
aniPath, FormatImgPath,
|
||||||
|
Animation::ReplaceData(variation.imgFormat[0], variation.imgFormat[1]));
|
||||||
|
if (!animation || !animation->IsUsable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation->SetVisible(false);
|
||||||
|
animation->SetZOrder(variation.layer);
|
||||||
|
AddChild(animation);
|
||||||
|
actionAnimations_[actionName].push_back(animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimation::SetAction(const std::string& actionName) {
|
||||||
|
auto nextIt = actionAnimations_.find(actionName);
|
||||||
|
if (nextIt == actionAnimations_.end()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterAnimation: action %s missing, keep %s", actionName.c_str(),
|
||||||
|
currentActionTag_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentActionTag_.empty()) {
|
||||||
|
auto currentIt = actionAnimations_.find(currentActionTag_);
|
||||||
|
if (currentIt != actionAnimations_.end()) {
|
||||||
|
for (auto& animation : currentIt->second) {
|
||||||
|
animation->Reset();
|
||||||
|
animation->SetVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& animation : nextIt->second) {
|
||||||
|
animation->Reset();
|
||||||
|
animation->SetVisible(true);
|
||||||
|
}
|
||||||
|
currentActionTag_ = actionName;
|
||||||
|
SetDirection(direction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimation::SetDirection(int direction) {
|
||||||
|
direction_ = direction >= 0 ? 1 : -1;
|
||||||
|
ApplyFlipRecursive(this, direction_ < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimation::ApplyFlipRecursive(Actor* actor, bool flipped) const {
|
||||||
|
if (!actor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* sprite = dynamic_cast<Sprite*>(actor)) {
|
||||||
|
sprite->SetFlippedX(flipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& child : actor->GetChildren()) {
|
||||||
|
ApplyFlipRecursive(child.Get(), flipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
447
Game/src/character/CharacterDataLoader.cpp
Normal file
447
Game/src/character/CharacterDataLoader.cpp
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
#include "character/CharacterDataLoader.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <frostbite2D/resource/pvf_archive.h>
|
||||||
|
#include <frostbite2D/resource/script_parser.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace frostbite2D::character {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr char kCharacterListPath[] = "character/character.lst";
|
||||||
|
constexpr char kEquipmentListPath[] = "equipment/equipment.lst";
|
||||||
|
|
||||||
|
constexpr auto kTagJobName = u8"[职业名称]";
|
||||||
|
constexpr auto kTagDefaultAvatar = u8"[默认时装]";
|
||||||
|
constexpr auto kTagDefaultAvatarEnd = u8"[/默认时装]";
|
||||||
|
constexpr auto kTagJobLabel = u8"[职业标签]";
|
||||||
|
constexpr auto kTagBaseJob = u8"[基础职业属性]";
|
||||||
|
constexpr auto kTagActionAnimation = u8"[动作动画]";
|
||||||
|
constexpr auto kTagActionAnimationEnd = u8"[/动作动画]";
|
||||||
|
constexpr char kTagName[] = "[name]";
|
||||||
|
constexpr char kTagUsableJob[] = "[usable job]";
|
||||||
|
constexpr char kTagUsableJobEnd[] = "[/usable job]";
|
||||||
|
constexpr char kTagAnimationJob[] = "[animation job]";
|
||||||
|
constexpr char kTagVariation[] = "[variation]";
|
||||||
|
constexpr char kTagLayerVariation[] = "[layer variation]";
|
||||||
|
constexpr char kTagEquipmentAniScript[] = "[equipment ani script]";
|
||||||
|
|
||||||
|
std::string toLowerCase(const std::string& str) {
|
||||||
|
std::string result = str;
|
||||||
|
std::transform(result.begin(), result.end(), result.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int toInt(const std::string& value, int fallback = 0) {
|
||||||
|
try {
|
||||||
|
return std::stoi(value);
|
||||||
|
} catch (...) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string trim(const std::string& value) {
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = value.size();
|
||||||
|
while (start < end && std::isspace(static_cast<unsigned char>(value[start]))) {
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
while (end > start &&
|
||||||
|
std::isspace(static_cast<unsigned char>(value[end - 1]))) {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
return value.substr(start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isToken(const std::string& token, const char* expected) {
|
||||||
|
return trim(token) == expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScriptTokenStream {
|
||||||
|
public:
|
||||||
|
explicit ScriptTokenStream(const std::string& path)
|
||||||
|
: path_(toLowerCase(path)) {
|
||||||
|
auto& pvf = PvfArchive::get();
|
||||||
|
auto rawData = pvf.getFileRawData(path_);
|
||||||
|
if (!rawData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptParser parser(*rawData, path_);
|
||||||
|
if (!parser.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& value : parser.parseAll()) {
|
||||||
|
tokens_.push_back(value.toString());
|
||||||
|
}
|
||||||
|
valid_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
bool isEnd() const { return index_ >= tokens_.size(); }
|
||||||
|
|
||||||
|
std::string get() {
|
||||||
|
if (isEnd()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return tokens_[index_++];
|
||||||
|
}
|
||||||
|
|
||||||
|
void back() {
|
||||||
|
if (index_ > 0) {
|
||||||
|
--index_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string path_;
|
||||||
|
std::vector<std::string> tokens_;
|
||||||
|
size_t index_ = 0;
|
||||||
|
bool valid_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
int jobNameToIndex(const std::string& jobName) {
|
||||||
|
static const std::unordered_map<std::string, int> kJobMap = {
|
||||||
|
{"[swordman]", 0}, {"[fighter]", 1}, {"[gunner]", 2},
|
||||||
|
{"[mage]", 3}, {"[priest]", 4}, {"[atgunner]", 5},
|
||||||
|
{"[thief]", 6}, {"[atfighter]", 7}, {"[atmage]", 8},
|
||||||
|
{"[demonic swordman]", 9}, {"[creatormage]", 10},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = kJobMap.find(toLowerCase(jobName));
|
||||||
|
return it == kJobMap.end() ? -1 : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string normalizeCharacterPath(const std::string& path) {
|
||||||
|
auto& pvf = PvfArchive::get();
|
||||||
|
std::string normalized = pvf.normalizePath(path);
|
||||||
|
if (normalized.rfind("character/", 0) == 0) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return pvf.normalizePath("character/" + normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logTokenPreview(const char* label, const std::vector<std::string>& tokens) {
|
||||||
|
std::string preview;
|
||||||
|
size_t previewCount = std::min<size_t>(tokens.size(), 8);
|
||||||
|
for (size_t i = 0; i < previewCount; ++i) {
|
||||||
|
if (!preview.empty()) {
|
||||||
|
preview += " | ";
|
||||||
|
}
|
||||||
|
preview += tokens[i];
|
||||||
|
}
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s: %s", label, preview.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<JobConfig> parseJobConfig(const std::string& path) {
|
||||||
|
ScriptTokenStream stream(path);
|
||||||
|
if (!stream.isValid()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: missing job config %s", path.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobConfig config;
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string segment = stream.get();
|
||||||
|
if (isToken(segment, kTagJobName)) {
|
||||||
|
config.name = stream.get();
|
||||||
|
} else if (isToken(segment, kTagDefaultAvatar)) {
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string slotName = stream.get();
|
||||||
|
if (isToken(slotName, kTagDefaultAvatarEnd)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
config.defaultAvatarList[slotName] = toInt(stream.get(), -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, std::string>& characterIndexCache() {
|
||||||
|
static std::map<int, std::string> cache;
|
||||||
|
static bool loaded = false;
|
||||||
|
if (!loaded) {
|
||||||
|
ScriptTokenStream stream(kCharacterListPath);
|
||||||
|
if (stream.isValid()) {
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string indexToken = stream.get();
|
||||||
|
if (indexToken.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::string pathToken = stream.get();
|
||||||
|
if (pathToken.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cache[toInt(indexToken, -1)] = normalizeCharacterPath(pathToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, std::string>& equipmentIndexCache() {
|
||||||
|
static std::map<int, std::string> cache;
|
||||||
|
static bool loaded = false;
|
||||||
|
if (!loaded) {
|
||||||
|
ScriptTokenStream stream(kEquipmentListPath);
|
||||||
|
if (stream.isValid()) {
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string indexToken = stream.get();
|
||||||
|
if (indexToken.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::string pathToken = stream.get();
|
||||||
|
if (pathToken.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cache[toInt(indexToken, -1)] =
|
||||||
|
PvfArchive::get().normalizePath("equipment/" + pathToken);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: equipment index %s not found, fallback to skin-only character rendering",
|
||||||
|
kEquipmentListPath);
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, CharacterConfig>& characterConfigCache() {
|
||||||
|
static std::map<int, CharacterConfig> cache;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, EquipmentConfig>& equipmentConfigCache() {
|
||||||
|
static std::map<int, EquipmentConfig> cache;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendVariationForJobs(EquipmentConfig& config,
|
||||||
|
const std::vector<int>& jobIndices,
|
||||||
|
int param1,
|
||||||
|
int param2,
|
||||||
|
ScriptTokenStream& stream) {
|
||||||
|
std::vector<EquipmentVariation> variations;
|
||||||
|
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string token = stream.get();
|
||||||
|
if (token == kTagLayerVariation) {
|
||||||
|
EquipmentVariation variation;
|
||||||
|
variation.layer = toInt(stream.get(), 0);
|
||||||
|
variation.animationGroup = PvfArchive::get().normalizePath(stream.get());
|
||||||
|
variation.imgFormat = {param1, param2};
|
||||||
|
variations.push_back(variation);
|
||||||
|
|
||||||
|
if (!stream.isEnd()) {
|
||||||
|
std::string maybeTag = stream.get();
|
||||||
|
if (maybeTag == kTagEquipmentAniScript) {
|
||||||
|
if (!stream.isEnd()) {
|
||||||
|
std::string ignoredScriptPath = stream.get();
|
||||||
|
(void)ignoredScriptPath;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.path.find("skin") != std::string::npos) {
|
||||||
|
EquipmentVariation variation;
|
||||||
|
variation.layer = 0;
|
||||||
|
variation.animationGroup.clear();
|
||||||
|
variation.imgFormat = {param1, param2};
|
||||||
|
variations.push_back(variation);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int jobIndex : jobIndices) {
|
||||||
|
if (jobIndex < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& target = config.jobAnimations[jobIndex];
|
||||||
|
target.insert(target.end(), variations.begin(), variations.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool loadCharacterIndex(std::map<int, std::string>& outIndex) {
|
||||||
|
outIndex = characterIndexCache();
|
||||||
|
return !outIndex.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CharacterConfig> loadCharacterConfig(int jobId) {
|
||||||
|
auto cacheIt = characterConfigCache().find(jobId);
|
||||||
|
if (cacheIt != characterConfigCache().end()) {
|
||||||
|
return cacheIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto indexIt = characterIndexCache().find(jobId);
|
||||||
|
if (indexIt == characterIndexCache().end()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: character job %d not found in index", jobId);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptTokenStream stream(indexIt->second);
|
||||||
|
if (!stream.isValid()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: unable to load character config %s",
|
||||||
|
indexIt->second.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterConfig config;
|
||||||
|
config.jobId = jobId;
|
||||||
|
std::vector<std::string> tokenPreview;
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string segment = stream.get();
|
||||||
|
if (tokenPreview.size() < 8) {
|
||||||
|
tokenPreview.push_back(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isToken(segment, kTagJobLabel)) {
|
||||||
|
std::string jobTag = stream.get();
|
||||||
|
if (jobTag.size() >= 2 && jobTag.front() == '[' && jobTag.back() == ']') {
|
||||||
|
jobTag = jobTag.substr(1, jobTag.size() - 2);
|
||||||
|
}
|
||||||
|
config.jobTag = toLowerCase(jobTag);
|
||||||
|
} else if (isToken(segment, kTagBaseJob)) {
|
||||||
|
config.baseJobPath = PvfArchive::get().normalizePath(stream.get());
|
||||||
|
auto baseJobConfig = parseJobConfig(config.baseJobPath);
|
||||||
|
if (baseJobConfig) {
|
||||||
|
config.baseJobConfig = *baseJobConfig;
|
||||||
|
} else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: base job config missing for job %d from %s",
|
||||||
|
jobId, config.baseJobPath.c_str());
|
||||||
|
}
|
||||||
|
} else if (isToken(segment, kTagActionAnimation)) {
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string actionName = stream.get();
|
||||||
|
if (isToken(actionName, kTagActionAnimationEnd)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::string actionPath = stream.get();
|
||||||
|
config.animationPath[actionName] = PvfArchive::get().normalizePath(
|
||||||
|
"character/" + config.jobTag + "/" + actionPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.jobTag.empty() || config.animationPath.empty()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: character job %d config incomplete (jobTag=%s, baseJobPath=%s, actionCount=%d, script=%s)",
|
||||||
|
jobId, config.jobTag.c_str(), config.baseJobPath.c_str(),
|
||||||
|
static_cast<int>(config.animationPath.size()), indexIt->second.c_str());
|
||||||
|
logTokenPreview("CharacterDataLoader token preview", tokenPreview);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
characterConfigCache()[jobId] = config;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadEquipmentIndex(std::map<int, std::string>& outIndex) {
|
||||||
|
outIndex = equipmentIndexCache();
|
||||||
|
return !outIndex.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<EquipmentConfig> loadEquipmentConfig(int equipmentId) {
|
||||||
|
auto cacheIt = equipmentConfigCache().find(equipmentId);
|
||||||
|
if (cacheIt != equipmentConfigCache().end()) {
|
||||||
|
return cacheIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto indexIt = equipmentIndexCache().find(equipmentId);
|
||||||
|
if (indexIt == equipmentIndexCache().end()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: equipment %d missing from equipment.lst",
|
||||||
|
equipmentId);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptTokenStream stream(indexIt->second);
|
||||||
|
if (!stream.isValid()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterDataLoader: unable to load equipment %d from %s",
|
||||||
|
equipmentId, indexIt->second.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentConfig config;
|
||||||
|
config.id = equipmentId;
|
||||||
|
config.path = indexIt->second;
|
||||||
|
SDL_Log("CharacterDataLoader: equipment %d -> %s", equipmentId, config.path.c_str());
|
||||||
|
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string segment = stream.get();
|
||||||
|
if (segment == kTagName) {
|
||||||
|
config.name = stream.get();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment == kTagUsableJob) {
|
||||||
|
while (!stream.isEnd()) {
|
||||||
|
std::string jobName = stream.get();
|
||||||
|
if (jobName == kTagUsableJobEnd) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int jobIndex = jobNameToIndex(jobName);
|
||||||
|
if (jobIndex >= 0) {
|
||||||
|
config.usableJobs.push_back(jobIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment != kTagAnimationJob) {
|
||||||
|
if (segment == kTagVariation) {
|
||||||
|
int param1 = toInt(stream.get(), 0);
|
||||||
|
int param2 = toInt(stream.get(), 0);
|
||||||
|
appendVariationForJobs(config, config.usableJobs, param1, param2, stream);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string jobName = stream.get();
|
||||||
|
int jobIndex = jobNameToIndex(jobName);
|
||||||
|
if (jobIndex < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.isEnd()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string variationTag = stream.get();
|
||||||
|
if (variationTag != kTagVariation) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int param1 = toInt(stream.get(), 0);
|
||||||
|
int param2 = toInt(stream.get(), 0);
|
||||||
|
appendVariationForJobs(config, {jobIndex}, param1, param2, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
equipmentConfigCache()[equipmentId] = config;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D::character
|
||||||
50
Game/src/character/CharacterEquipmentManager.cpp
Normal file
50
Game/src/character/CharacterEquipmentManager.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "character/CharacterEquipmentManager.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
bool CharacterEquipmentManager::Init(const character::JobConfig& jobConfig) {
|
||||||
|
defaultEquipment_.clear();
|
||||||
|
|
||||||
|
if (jobConfig.defaultAvatarList.empty()) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterEquipmentManager: no default avatar entries, fallback to skin-only character rendering");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int loadedCount = 0;
|
||||||
|
for (const auto& [slotName, equipmentId] : jobConfig.defaultAvatarList) {
|
||||||
|
if (equipmentId < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto equipmentConfig = character::loadEquipmentConfig(equipmentId);
|
||||||
|
if (!equipmentConfig) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterEquipmentManager: equipment %d for slot %s unavailable, skip layered part",
|
||||||
|
equipmentId, slotName.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultEquipment_[slotName] = *equipmentConfig;
|
||||||
|
++loadedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedCount == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterEquipmentManager: no avatar equipment loaded, fallback to skin-only character rendering");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const character::EquipmentConfig* CharacterEquipmentManager::GetEquip(
|
||||||
|
const std::string& slotName) const {
|
||||||
|
auto it = defaultEquipment_.find(slotName);
|
||||||
|
if (it == defaultEquipment_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
61
Game/src/character/CharacterObject.cpp
Normal file
61
Game/src/character/CharacterObject.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "character/CharacterObject.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
bool CharacterObject::Construction(int jobId) {
|
||||||
|
RemoveAllChildren();
|
||||||
|
animationManager_ = nullptr;
|
||||||
|
config_.reset();
|
||||||
|
currentAction_.clear();
|
||||||
|
|
||||||
|
auto config = character::loadCharacterConfig(jobId);
|
||||||
|
if (!config) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterObject: failed to load job %d config", jobId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
jobId_ = jobId;
|
||||||
|
growType_ = -1;
|
||||||
|
direction_ = 1;
|
||||||
|
config_ = *config;
|
||||||
|
equipmentManager_.Init(config_->baseJobConfig);
|
||||||
|
|
||||||
|
animationManager_ = MakePtr<CharacterAnimation>();
|
||||||
|
if (!animationManager_->Init(this, *config_, equipmentManager_)) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CharacterObject: no layered avatar animations available, character will stay empty");
|
||||||
|
}
|
||||||
|
AddChild(animationManager_);
|
||||||
|
|
||||||
|
if (config_->animationPath.count("rest") > 0) {
|
||||||
|
SetAction("rest");
|
||||||
|
} else if (!config_->animationPath.empty()) {
|
||||||
|
SetAction(config_->animationPath.begin()->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDirection(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterObject::SetAction(const std::string& actionName) {
|
||||||
|
currentAction_ = actionName;
|
||||||
|
if (animationManager_) {
|
||||||
|
animationManager_->SetAction(actionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterObject::SetDirection(int direction) {
|
||||||
|
direction_ = direction >= 0 ? 1 : -1;
|
||||||
|
if (animationManager_) {
|
||||||
|
animationManager_->SetDirection(direction_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterObject::SetCharacterPosition(const Vec2& pos) {
|
||||||
|
SetPosition(pos);
|
||||||
|
SetZOrder(static_cast<int>(pos.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "Actor/GameDataLoader.h"
|
#include "map/GameDataLoader.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <frostbite2D/resource/pvf_archive.h>
|
#include <frostbite2D/resource/pvf_archive.h>
|
||||||
#include <frostbite2D/resource/script_parser.h>
|
#include <frostbite2D/resource/script_parser.h>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "Actor/GameMap.h"
|
#include "map/GameMap.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <frostbite2D/2d/sprite.h>
|
#include <frostbite2D/2d/sprite.h>
|
||||||
#include <frostbite2D/animation/animation.h>
|
#include <frostbite2D/animation/animation.h>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "Actor/GameMapLayer.h"
|
#include "map/GameMapLayer.h"
|
||||||
#include <frostbite2D/graphics/renderer.h>
|
#include <frostbite2D/graphics/renderer.h>
|
||||||
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "Actor/GameMapTestScene.h"
|
#include "scene/GameMapTestScene.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <frostbite2D/animation/animation.h>
|
|
||||||
namespace frostbite2D {
|
namespace frostbite2D {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -25,15 +25,30 @@ void GameMapTestScene::onEnter() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddChild(map_);
|
AddChild(map_);
|
||||||
|
|
||||||
|
character_ = MakePtr<CharacterObject>();
|
||||||
|
if (!character_->Construction(0)) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"GameMapTestScene: failed to construct default character");
|
||||||
|
character_.Reset();
|
||||||
|
} else {
|
||||||
|
Vec2 spawnPos = map_->ClampCameraFocus(map_->GetDefaultCameraFocus(), 1.2f);
|
||||||
|
character_->SetCharacterPosition(spawnPos);
|
||||||
|
map_->AddObject(character_);
|
||||||
|
}
|
||||||
|
|
||||||
cameraController_.SetMap(map_.Get());
|
cameraController_.SetMap(map_.Get());
|
||||||
cameraController_.SetZoom(1.2f);
|
cameraController_.SetZoom(1.2f);
|
||||||
cameraController_.ClearTarget();
|
cameraController_.SetTarget(character_.Get());
|
||||||
cameraController_.SetDebugEnabled(true);
|
cameraController_.SetDebugEnabled(false);
|
||||||
cameraController_.SnapToDefaultFocus();
|
if (character_) {
|
||||||
|
cameraController_.SetFocus(character_->GetPosition());
|
||||||
|
} else {
|
||||||
|
cameraController_.ClearTarget();
|
||||||
|
cameraController_.SnapToDefaultFocus();
|
||||||
|
}
|
||||||
map_->Enter();
|
map_->Enter();
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameMapTestScene::onExit() {
|
void GameMapTestScene::onExit() {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "Actor/GameTown.h"
|
#include "world/GameTown.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "Actor/GameWorld.h"
|
#include "world/GameWorld.h"
|
||||||
#include "Actor/GameDataLoader.h"
|
#include "map/GameDataLoader.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <frostbite2D/scene/scene_manager.h>
|
#include <frostbite2D/scene/scene_manager.h>
|
||||||
|
|
||||||
Reference in New Issue
Block a user