feat: 添加游戏核心模块,包括地图、角色、场景和世界管理
实现游戏基础架构,包含以下主要功能: - 地图系统:支持地图加载、图层管理和相机控制 - 角色系统:实现角色装备、动画和行为管理 - 场景系统:提供测试场景和世界场景切换 - 世界管理:处理城镇和区域切换逻辑 - 数据加载:添加角色和装备配置加载器 这些改动为游戏开发奠定了基础框架,支持后续功能扩展
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user