diff --git a/docs/plan/XCEngine音频模块架构设计.md b/docs/plan/XCEngine音频模块架构设计.md new file mode 100644 index 00000000..fbddf3c9 --- /dev/null +++ b/docs/plan/XCEngine音频模块架构设计.md @@ -0,0 +1,1640 @@ +# XCEngine - 音频模块架构设计文档 + +> **借鉴 Unity 音频架构概念设计** + +--- + +## 音频流程图 + +``` +┌─────────────────────────────────────────────────────────────────────────────────────┐ +│ 音频流程 │ +└─────────────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ AudioSource │────▶│ Spatialize │────▶│ EffectChain │────▶│ Output │ +│ 声源组件 │ │ 3D空间化 │ │ 效果链 │ │ 音频输出 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ AudioClip │ │ HRTF │ │ FFT │ │ AudioBackend│ +│ 音频资源 │ │ 头部相关传输 │ │ 傅里叶变换 │ │ 音频后端 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ Reverbation │ │ WASAPI │ + │ 混响效果 │ │ OpenAL │ + └─────────────┘ │ CoreAudio │ + └─────────────┘ + +流程说明: +1. AudioSourceComponent 负责播放 AudioClip,管理播放状态 +2. AudioListenerComponent 接收声音,计算3D空间化参数 +3. Spatialize 根据 Listener 位置计算 panning、distance attenuation、doppler +4. EffectChain 处理 DSP 效果链(FFT、Reverb、EQ等) +5. AudioBackend 抽象层负责与具体音频API交互 +6. 最终输出到音频设备 +``` + +``` +┌─────────────────────────────────────────────────────────────────────────────────────┐ +│ AudioBackend 抽象层架构 │ +└─────────────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ AudioSystem │ 音频系统入口 + └────────┬────────┘ + │ + ┌───────────────────────┼───────────────────────┐ + │ │ │ + ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ + │ WASAPIBackend│ │OpenALBackend │ │CoreAudioBackend│ + └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + └───────────────────────┼───────────────────────┘ + │ + ┌────────▼────────┐ + │ IAudioBackend │ 抽象音频后端接口 + └────────┬────────┘ + │ + ┌──────────────────────────────────┼──────────────────────────────────┐ + │ │ │ │ │ │ │ + ┌────▼────┐ ┌───▼────┐ ┌────▼┐ ┌──▼────┐ ┌───▼───┐ ┌───▼────┐ ┌──▼────┐ + │AudioBuffer│ │AudioBus │ │Fence│ │Mixer │ │Effect │ │HRTF │ │Resampler│ + │ 音频缓冲 │ │ 音频总线 │ │围栏 │ │ 混音器 │ │ 效果 │ │ 空间化 │ │ 重采样 │ + └──────────┘ └────────┘ └─────┘ └────────┘ └───────┘ └────────┘ └────────┘ +``` + +> **重要声明**:本架构借鉴 Unity 音频系统的核心概念与设计模式,包括: +> - `AudioSourceComponent` / `AudioListenerComponent` - 声源与监听器组件 +> - `AudioClip` / `AudioMixer` - 音频资源与混音器 +> - `EffectChain` - 效果器链 +> - `Spatializer` - 3D空间化处理器 +> - `AudioBackend` - 音频后端抽象 +> +> **注**:Unity底层C++音频架构未公开,本设计基于公开API概念与通用音频引擎模式实现。 +> +> 版本: 1.0 +> 日期: 2026-03-20 +> 目标: 构建专业级游戏引擎音频系统 + +--- + +## 第一章 核心类型定义 + +### 1.1 音频基础类型 + +```cpp +namespace XCEngine { +namespace Audio { + +// ============================================ +// 前置类型定义 +// ============================================ + +enum class AudioResourceType { + AudioClip, + AudioMixer, + AudioBank +}; + +enum class AudioLoadState { + Unloaded, + Loading, + Loaded, + Failed +}; + +enum class AudioFormat { + Unknown, + WAV, + OGG, + MP3, + FLAC, + AAC +}; + +enum class SpeakerMode { + Mono, + Stereo, + Surround51, + Surround71, + Surround51_2, + Surround71_2 +}; + +enum class AudioChannel { + FrontLeft, + FrontRight, + FrontCenter, + LFE, + BackLeft, + BackRight, + SideLeft, + SideRight +}; + +struct AudioConfig { + uint32_t sampleRate = 48000; + uint16_t channels = 2; + uint16_t bitsPerSample = 16; + SpeakerMode speakerMode = SpeakerMode::Stereo; + uint32_t bufferSize = 8192; + uint32_t bufferCount = 2; +}; + +// ============================================ +// 3D 音频参数 +// ============================================ + +struct Audio3DParams { + float dopplerLevel = 1.0f; + float speedOfSound = 343.0f; + float minDistance = 1.0f; + float maxDistance = 500.0f; + float panLevel = 1.0f; + float spread = 0.0f; + float reverbZoneMix = 1.0f; +}; + +enum class PanMode { + Pan3D, + Pan2D +}; + +enum class VolumeSource { + Direct, + Path Occlusion, + Transmission, + Obstruction +}; + +// ============================================ +// 音频缓冲区描述 +// ============================================ + +struct AudioBufferDesc { + uint32_t size = 0; + uint32_t channels = 2; + uint32_t sampleRate = 48000; + uint16_t bitsPerSample = 16; + bool isFloat = false; + bool isCompressed = false; + AudioFormat format = AudioFormat::Unknown; +}; + +// ============================================ +// 播放状态 +// ============================================ + +enum class PlayState { + Stopped, + Playing, + Paused +}; + +enum class StopMode { + Immediate, + AllowFadeOut +}; + +// ============================================ +// 空间化参数 +// ============================================ + +struct SpatializerParams { + float azimuth = 0.0f; + float elevation = 0.0f; + float distance = 0.0f; + float volumeDb = 0.0f; + float panCartesianX = 0.0f; + float panCartesianY = 0.0f; + bool isOccluded = false; + bool isObstructed = false; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 1.2 IAudioResource 基类 + +```cpp +namespace XCEngine { +namespace Audio { + +class IAudioResource { +public: + virtual ~IAudioResource() = default; + + virtual void SetName(const String& name) { m_name = name; } + virtual const String& GetName() const { return m_name; } + + virtual AudioResourceType GetType() const = 0; + virtual AudioLoadState GetLoadState() const { return m_loadState; } + + virtual bool IsValid() const { return m_loadState == AudioLoadState::Loaded; } + virtual void Load() = 0; + virtual void Unload() = 0; + + virtual int GetRefCount() const { return m_refCount.load(); } + virtual void AddRef() { m_refCount.fetch_add(1); } + virtual void Release() { + if (m_refCount.fetch_sub(1) == 1) { + delete this; + } + } + +protected: + IAudioResource() : m_loadState(AudioLoadState::Unloaded), m_memoryUsage(0) {} + + String m_name; + AudioLoadState m_loadState; + uint64_t m_memoryUsage; + std::atomic m_refCount{1}; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第二章 音频资源 + +### 2.1 AudioClip + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioClip : public IAudioResource { +public: + AudioClip(); + AudioClip(const String& filePath); + ~AudioClip(); + + AudioResourceType GetType() const override { return AudioResourceType::AudioClip; } + + void Load() override; + void Unload() override; + + // 音频数据访问 + const int16_t* GetData() const { return m_data.data(); } + int16_t* GetData() { return m_data.data(); } + + const float* GetDataFloat() const { return m_dataFloat.data(); } + float* GetDataFloat() { return m_dataFloat.data(); } + + uint32_t GetDataSize() const { return m_dataSize; } + uint32_t GetSampleCount() const { return m_sampleCount; } + uint32_t GetChannelCount() const { return m_channels; } + uint32_t GetSampleRate() const { return m_sampleRate; } + uint16_t GetBitsPerSample() const { return m_bitsPerSample; } + float GetDuration() const { return m_duration; } + + AudioFormat GetFormat() const { return m_format; } + bool IsCompressed() const { return m_isCompressed; } + bool IsStreamed() const { return m_isStreamed; } + + // 频谱数据查询 + bool HasFrequencyData() const { return m_hasFrequencyData; } + const float* GetFrequencyData() const { return m_frequencyData.data(); } + uint32_t GetFrequencyDataSize() const { return m_frequencyData.size(); } + + // 资源路径 + void SetResourcePath(const String& path) { m_resourcePath = path; } + const String& GetResourcePath() const { return m_resourcePath; } + +private: + void ProcessWavFile(); + void DecodeCompressedData(); + void GenerateFrequencyData(); + void ConvertToFloat(); + +private: + std::vector m_data; + std::vector m_dataFloat; + std::vector m_frequencyData; + + uint32_t m_dataSize = 0; + uint32_t m_sampleCount = 0; + uint16_t m_channels = 2; + uint32_t m_sampleRate = 48000; + uint16_t m_bitsPerSample = 16; + float m_duration = 0.0f; + + AudioFormat m_format = AudioFormat::Unknown; + bool m_isCompressed = false; + bool m_isStreamed = false; + bool m_hasFrequencyData = false; + + String m_resourcePath; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 2.2 AudioMixer + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioMixer : public IAudioResource { +public: + AudioMixer(); + ~AudioMixer(); + + AudioResourceType GetType() const override { return AudioResourceType::AudioMixer; } + + void Load() override; + void Unload() override; + + // 混音器参数 + float GetVolume() const { return m_volume; } + void SetVolume(float volume, float fadeTime = 0.0f); + + bool IsMuted() const { return m_isMuted; } + void SetMuted(bool muted) { m_isMuted = muted; } + + // 子混音器组 + class MixerGroup* GetGroup(const String& name); + class MixerGroup* CreateGroup(const String& name); + void RemoveGroup(const String& name); + + const std::map>& GetGroups() const { return m_groups; } + + // 输出混音器 + void SetOutputMixer(AudioMixer* mixer); + AudioMixer* GetOutputMixer() const { return m_outputMixer; } + +private: + float m_volume = 1.0f; + bool m_isMuted = false; + + std::map> m_groups; + AudioMixer* m_outputMixer = nullptr; +}; + +class MixerGroup { +public: + MixerGroup(const String& name); + ~MixerGroup(); + + const String& GetName() const { return m_name; } + + float GetVolume() const { return m_volume; } + void SetVolume(float volume, float fadeTime = 0.0f); + + bool IsMuted() const { return m_isMuted; } + void SetMuted(bool muted) { m_isMuted = muted; } + + bool IsSoloed() const { return m_isSoloed; } + void SetSoloed(bool soloed) { m_isSoloed = soloed; } + + // 效果器 + void AddEffect(IAudioEffect* effect, size_t index = SIZE_MAX); + void RemoveEffect(IAudioEffect* effect); + void SetEffectEnabled(IAudioEffect* effect, bool enabled); + + const std::vector& GetEffects() const { return m_effects; } + + // 音频处理 + void Process(float* buffer, uint32_t sampleCount, uint32_t channels); + +private: + String m_name; + float m_volume = 1.0f; + bool m_isMuted = false; + bool m_isSoloed = false; + + std::vector m_effects; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 2.3 AudioBank (用于分段加载) + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioBank : public IAudioResource { +public: + AudioBank(); + ~AudioBank(); + + AudioResourceType GetType() const override { return AudioResourceType::AudioBank; } + + void Load() override; + void Unload() override; + + // 事件触发 + void TriggerEvent(const String& eventName); + void TriggerEvent(int eventId); + + // 获取嵌入的音频片段 + AudioClip* GetClip(const String& name); + AudioClip* GetClip(int id); + + const std::map& GetAllClips() const { return m_clips; } + +private: + std::map m_clips; + std::map m_eventMap; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第三章 DSP 效果系统 + +### 3.1 IAudioEffect 接口 + +```cpp +namespace XCEngine { +namespace Audio { + +class IAudioEffect { +public: + virtual ~IAudioEffect() = default; + + virtual const char* GetName() const = 0; + virtual void Process(float* buffer, uint32_t sampleCount, uint32_t channels) = 0; + + virtual void SetEnabled(bool enabled) { m_enabled = enabled; } + virtual bool IsEnabled() const { return m_enabled; } + + virtual void SetParameter(const char* name, float value) = 0; + virtual float GetParameter(const char* name) const = 0; + + virtual size_t GetParameterCount() const = 0; + virtual const char* GetParameterName(size_t index) const = 0; + +protected: + bool m_enabled = true; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 3.2 FFTFilter (频谱分析) + +```cpp +namespace XCEngine { +namespace Audio { + +class FFTFilter : public IAudioEffect { +public: + FFTFilter(size_t fftSize = 1024); + ~FFTFilter(); + + const char* GetName() const override { return "FFTFilter"; } + void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; + + void SetEnabled(bool enabled) override; + + void SetFFTSize(size_t size); + size_t GetFFTSize() const { return m_fftSize; } + + void SetParameter(const char* name, float value) override; + float GetParameter(const char* name) const override; + size_t GetParameterCount() const override; + const char* GetParameterName(size_t index) const override; + + // 频谱数据访问 + const float* GetMagnitudeSpectrum() const { return m_magnitudeSpectrum.data(); } + const float* GetPhaseSpectrum() const { return m_phaseSpectrum.data(); } + size_t GetSpectrumSize() const { return m_spectrumSize; } + + // 设置时域核(用于滤波) + void SetTimeDomainKernel(const std::vector& kernel); + void AddTimeDomainKernel(const std::vector& kernel); + + // 频域核 + void SetFreqDomainKernel(const std::vector& kernel); + void AddFreqDomainKernel(const std::vector& kernel); + +private: + void Init(); + void ComplexVectorProduct(const std::vector& inputA, + const std::vector& inputB, + std::vector* result); + void InverseFFTScaling(std::vector* signal); + +private: + size_t m_fftSize; + size_t m_spectrumSize; + + bool m_analysisMode = true; + + std::vector m_magnitudeSpectrum; + std::vector m_phaseSpectrum; + + std::vector m_filterState; + std::vector m_window; + + bool m_kernelDefined = false; + std::vector m_kernelTimeDomainBuffer; + std::vector m_kernelFreqDomainBuffer; + + int m_bufferSelector = 0; + std::vector> m_signalTimeDomainBuffer; + std::vector> m_signalFreqDomainBuffer; + std::vector m_filteredFreqDomainBuffer; + + kiss_fftr_cfg m_forwardFFT = nullptr; + kiss_fftr_cfg m_inverseFFT = nullptr; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 3.3 Reverbation (混响) + +```cpp +namespace XCEngine { +namespace Audio { + +class Reverbation : public IAudioEffect { +public: + Reverbation(int blockSize = 1024, int samplingRate = 48000, float reverbTime = 2.0f); + ~Reverbation(); + + const char* GetName() const override { return "Reverbation"; } + void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; + + void SetParameter(const char* name, float value) override; + float GetParameter(const char* name) const override; + size_t GetParameterCount() const override; + const char* GetParameterName(size_t index) const override; + + // 混响参数 + void SetReverbTime(float seconds); + float GetReverbTime() const { return m_reverbTime; } + + void SetDecay(float decay) { m_decay = decay; } + float GetDecay() const { return m_decay; } + + void SetPreDelay(float ms); + float GetPreDelay() const { return m_preDelayMs; } + + void SetHighFreqDamping(float ratio); + float GetHighFreqDamping() const { return m_highFreqDamping; } + + // 脉冲响应 + const std::vector& GetImpulseResponseLeft() const { return m_impulseResponseLeft; } + const std::vector& GetImpulseResponseRight() const { return m_impulseResponseRight; } + + float GetQuietPeriod() const { return m_quietPeriodSec; } + +private: + void RenderImpulseResponse(int blockSize, int samplingRate, float reverbTime); + static float FloatRand(); + +private: + int m_blockSize; + float m_reverbTime = 2.0f; + float m_decay = 0.7f; + float m_preDelayMs = 20.0f; + float m_highFreqDamping = 0.5f; + + std::vector m_impulseResponseLeft; + std::vector m_impulseResponseRight; + float m_quietPeriodSec; + + std::unique_ptr m_leftReverbFilter; + std::unique_ptr m_rightReverbFilter; + + std::vector m_reverbInput; + std::vector m_reverbOutputLeft; + std::vector m_reverbOutputRight; + int m_reverbOutputReadPos = 0; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 3.4 EQ (均衡器) + +```cpp +namespace XCEngine { +namespace Audio { + +class Equalizer : public IAudioEffect { +public: + Equalizer(); + ~Equalizer(); + + const char* GetName() const override { return "Equalizer"; } + void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; + + void SetParameter(const char* name, float value) override; + float GetParameter(const char* name) const override; + size_t GetParameterCount() const override; + const char* GetParameterName(size_t index) const override; + + // 频段控制 + enum class Band { + LowPass, + HighPass, + BandPass, + Notch, + LowShelf, + HighShelf, + Peaking + }; + + void SetBand(size_t index, Band type, float frequency, float gainDb, float Q); + void SetBandEnabled(size_t index, bool enabled); + + struct BandParams { + Band type = Band::Peaking; + float frequency = 1000.0f; + float gainDb = 0.0f; + float Q = 1.0f; + bool enabled = true; + }; + + const BandParams& GetBand(size_t index) const { return m_bands[index]; } + +private: + void UpdateCoefficients(size_t index); + void ProcessBand(float* buffer, uint32_t sampleCount, size_t bandIndex); + +private: + static constexpr size_t MaxBands = 10; + std::array m_bands; + std::array, MaxBands> m_coefficients; + + std::vector m_x1, m_x2, m_y1, m_y2; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 3.5 Dynamics (动态效果器) + +```cpp +namespace XCEngine { +namespace Audio { + +class Compressor : public IAudioEffect { +public: + Compressor(); + ~Compressor(); + + const char* GetName() const override { return "Compressor"; } + void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; + + void SetParameter(const char* name, float value) override; + float GetParameter(const char* name) const override; + size_t GetParameterCount() const override; + const char* GetParameterName(size_t index) const override; + + void SetThreshold(float db); + float GetThreshold() const { return m_thresholdDb; } + + void SetRatio(float ratio); + float GetRatio() const { return m_ratio; } + + void SetAttack(float ms); + float GetAttack() const { return m_attackMs; } + + void SetRelease(float ms); + float GetRelease() const { return m_releaseMs; } + + float GetGainReduction() const { return m_gainReduction; } + +private: + float m_thresholdDb = -20.0f; + float m_ratio = 4.0f; + float m_attackMs = 10.0f; + float m_releaseMs = 100.0f; + + float m_gainReduction = 0.0f; + float m_envelope = 0.0f; +}; + +class Limiter : public IAudioEffect { +public: + Limiter(); + ~Limiter(); + + const char* GetName() const override { return "Limiter"; } + void Process(float* buffer, uint32_t sampleCount, uint32_t channels) override; + + void SetParameter(const char* name, float value) override; + float GetParameter(const char* name) const override; + size_t GetParameterCount() const override; + const char* GetParameterName(size_t index) const override; + + void SetCeiling(float db); + float GetCeiling() const { return m_ceilingDb; } + + void SetRelease(float ms); + float GetRelease() const { return m_releaseMs; } + +private: + float m_ceilingDb = -0.3f; + float m_releaseMs = 50.0f; + float m_envelope = 0.0f; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第四章 3D 空间音频 + +### 4.1 SpatializerParams 空间化参数 + +```cpp +namespace XCEngine { +namespace Audio { + +struct SpatializerParams { + Vector3 listenerPosition; + Quaternion listenerRotation; + Vector3 listenerVelocity; + + Vector3 sourcePosition; + Vector3 sourceVelocity; + Quaternion sourceRotation; + + Audio3DParams params; + + float GetDistanceAttenuation() const; + float GetPan() const; + float GetDopplerShift() const; +}; +``` + +### 4.2 HRTF 空间化 + +```cpp +namespace XCEngine { +namespace Audio { + +class HRTF { +public: + HRTF(); + ~HRTF(); + + // 方向设置 + void SetDirection(int elevation, int azimuth); + void GetDirection(int& elevation, int& azimuth); + + // HRTF 脉冲响应 + void GetLeftEarTimeHRTF(std::vector& data); + void GetLeftEarFreqHRTF(std::vector& data); + void GetRightEarTimeHRTF(std::vector& data); + void GetRightEarFreqHRTF(std::vector& data); + + // 空间化处理 + void Process(const float* input, float* outputLeft, float* outputRight, + size_t sampleCount, size_t channels); + + // 淡入淡出处理 + void ApplyFadeWindow(const std::vector& blockLast, + const std::vector& blockNext, + std::vector* fadeResult); + + // 重采样 + void Resample(const float* input, float* output, size_t inputSize, + size_t outputSize, size_t channels); + +private: + void BuildReacherTree(); + int SearchNearestIndex(int elevation, int azimuth); + double CalculateSphereDistance(int elev1, int azim1, int elev2, int azim2); + +private: + int m_realAzimuth = 0; + int m_realElevation = 0; + int m_azimuth = 0; + int m_elevation = 0; + bool m_isSwapLeftRight = false; + int m_directionIndex = 0; + + using ResampledHRTFT = std::vector; + using ResampledHRTFPairT = std::pair; + + std::vector m_hrtfResampledTimeDomain; + std::vector m_hrtfResampledFreqDomain; + + std::map, std::vector>> m_hrtfDirections; + + std::unique_ptr m_filter; + std::unique_ptr m_hrtfFilterLeft; + std::unique_ptr m_hrtfFilterRight; + + std::vector m_fadeWindow; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 4.3 DopplerEffect 多普勒效应 + +```cpp +namespace XCEngine { +namespace Audio { + +class DopplerEffect { +public: + DopplerEffect(); + ~DopplerEffect(); + + void SetSpeedOfSound(float metersPerSecond); + float GetSpeedOfSound() const { return m_speedOfSound; } + + void SetDopplerLevel(float level); + float GetDopplerLevel() const { return m_dopplerLevel; } + + float CalculatePitchShift(const Vector3& sourceVelocity, + const Vector3& listenerVelocity, + const Vector3& sourceToListener); + +private: + float m_speedOfSound = 343.0f; + float m_dopplerLevel = 1.0f; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 4.4 OcclusionSystem 遮蔽系统 + +```cpp +namespace XCEngine { +namespace Audio { + +class OcclusionSystem { +public: + OcclusionSystem(); + ~OcclusionSystem(); + + // 遮蔽计算 + float CalculateOcclusion(const Vector3& sourcePos, const Vector3& listenerPos, + const std::vector& obstaclePositions); + + float CalculateObstruction(const Vector3& sourcePos, const Vector3& listenerPos, + const Vector3& obstaclePos); + + // 遮蔽参数 + void SetOcclusionHighFreqAbsorption(float value); + float GetOcclusionHighFreqAbsorption() const { return m_occlusionHighFreqAbsorb; } + + void SetObstructionHighFreqDamping(float value); + float GetObstructionHighFreqDamping() const { return m_obstructionHighFreqDamp; } + +private: + float m_occlusionHighFreqAbsorb = 0.5f; + float m_obstructionHighFreqDamp = 0.5f; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第五章 音频后端抽象层 + +### 5.1 IAudioBackend 接口 + +```cpp +namespace XCEngine { +namespace Audio { + +class IAudioBackend { +public: + virtual ~IAudioBackend() = default; + + // 初始化与销毁 + virtual bool Initialize(const AudioConfig& config) = 0; + virtual void Shutdown() = 0; + + // 设备查询 + virtual String GetDeviceName() const = 0; + virtual void GetAvailableDevices(std::vector& devices) = 0; + virtual bool SetDevice(const String& deviceName) = 0; + + // 混音器控制 + virtual float GetMasterVolume() const = 0; + virtual void SetMasterVolume(float volume) = 0; + + virtual bool IsMuted() const = 0; + virtual void SetMuted(bool muted) = 0; + + // 播放控制 + virtual void Start() = 0; + virtual void Stop() = 0; + virtual void Suspend() = 0; + virtual void Resume() = 0; + + // 音频处理 + virtual void ProcessAudio(float* buffer, uint32_t bufferSize, + uint32_t channels, uint32_t sampleRate) = 0; + + // 状态查询 + virtual bool IsRunning() const = 0; + virtual AudioConfig GetConfig() const = 0; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 5.2 WASAPI 后端 + +```cpp +namespace XCEngine { +namespace Audio { +namespace WASAPI { + +class WASAPIBackend : public IAudioBackend { +public: + WASAPIBackend(); + ~WASAPIBackend() override; + + bool Initialize(const AudioConfig& config) override; + void Shutdown() override; + + String GetDeviceName() const override; + void GetAvailableDevices(std::vector& devices) override; + bool SetDevice(const String& deviceName) override; + + float GetMasterVolume() const override; + void SetMasterVolume(float volume) override; + + bool IsMuted() const override; + void SetMuted(bool muted) override; + + void Start() override; + void Stop() override; + void Suspend() override; + void Resume() override; + + void ProcessAudio(float* buffer, uint32_t bufferSize, + uint32_t channels, uint32_t sampleRate) override; + + bool IsRunning() const override { return m_isRunning; } + AudioConfig GetConfig() const override { return m_config; } + +private: + MMRESULT InitDevice(); + MMRESULT InitClient(); + MMRESULT InitBuffer(); + + static DWORD WINAPI AudioThreadProc(LPVOID lpParameter); + void AudioThread(); + + void OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2); + static void CALLBACK StaticAudioCallback(HWAVEOUT hwo, UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2); + +private: + bool m_isRunning = false; + std::thread m_audioThread; + + AudioConfig m_config; + + WAVEFORMATEX m_waveFormat = {}; + HWAVEOUT m_hWaveOut = nullptr; + HANDLE m_hEvent = nullptr; + + std::vector m_mixBuffer; + uint32_t m_mixBufferSize = 0; + + std::vector m_waveOutCaps; + String m_deviceName; + + IAudioEffect* m_masterEffect = nullptr; +}; + +} // namespace WASAPI +} // namespace Audio +} // namespace XCEngine +``` + +### 5.3 OpenAL 后端 + +```cpp +namespace XCEngine { +namespace Audio { +namespace OpenAL { + +class OpenALBackend : public IAudioBackend { +public: + OpenALBackend(); + ~OpenALBackend() override; + + bool Initialize(const AudioConfig& config) override; + void Shutdown() override; + + String GetDeviceName() const override; + void GetAvailableDevices(std::vector& devices) override; + bool SetDevice(const String& deviceName) override; + + float GetMasterVolume() const override; + void SetMasterVolume(float volume) override; + + bool IsMuted() const override; + void SetMuted(bool muted) override; + + void Start() override; + void Stop() override; + void Suspend() override; + void Resume() override; + + void ProcessAudio(float* buffer, uint32_t bufferSize, + uint32_t channels, uint32_t sampleRate) override; + + bool IsRunning() const override { return m_isRunning; } + AudioConfig GetConfig() const override { return m_config; } + +private: + bool m_isRunning = false; + AudioConfig m_config; + + ALCdevice* m_device = nullptr; + ALCcontext* m_context = nullptr; + ALuint m_source = 0; + ALuint m_buffers[2]; + + std::vector m_mixBuffer; +}; + +} // namespace OpenAL +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第六章 音频系统核心 + +### 6.1 AudioSystem + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioSystem { +public: + static AudioSystem& Get(); + + void Initialize(const AudioConfig& config); + void Shutdown(); + + void Update(float deltaTime); + + // 后端控制 + void SetBackend(std::unique_ptr backend); + IAudioBackend* GetBackend() const { return m_backend.get(); } + + // 设备控制 + String GetCurrentDevice() const; + void SetDevice(const String& deviceName); + void GetAvailableDevices(std::vector& devices); + + // 全局混音控制 + float GetMasterVolume() const; + void SetMasterVolume(float volume); + + bool IsMuted() const; + void SetMuted(bool muted); + + // 音频处理 + void ProcessAudio(float* buffer, uint32_t sampleCount, uint32_t channels); + + // 资源管理 + AudioClip* LoadAudioClip(const String& filePath); + void UnloadAudioClip(AudioClip* clip); + + AudioMixer* CreateMixer(const String& name); + void DestroyMixer(AudioMixer* mixer); + + // 空间化 + void SetListenerTransform(const Vector3& position, const Quaternion& rotation); + void SetListenerVelocity(const Vector3& velocity); + + const Vector3& GetListenerPosition() const { return m_listenerPosition; } + const Quaternion& GetListenerRotation() const { return m_listenerRotation; } + const Vector3& GetListenerVelocity() const { return m_listenerVelocity; } + + // 统计信息 + struct Stats { + uint32_t activeSources; + uint32_t totalSources; + uint64_t memoryUsage; + float cpuUsage; + }; + const Stats& GetStats() const { return m_stats; } + +private: + AudioSystem() = default; + ~AudioSystem() = default; + + AudioSystem(const AudioSystem&) = delete; + AudioSystem& operator=(const AudioSystem&) = delete; + + void ProcessSource(AudioSourceComponent* source); + void ApplyGlobalEffects(float* buffer, uint32_t sampleCount, uint32_t channels); + +private: + std::unique_ptr m_backend; + + Vector3 m_listenerPosition = Vector3::Zero(); + Quaternion m_listenerRotation = Quaternion::Identity(); + Vector3 m_listenerVelocity = Vector3::Zero(); + + std::vector> m_loadedClips; + std::map> m_mixers; + + std::vector m_activeSources; + + Stats m_stats = {}; + float m_deltaTime = 0.0f; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 6.2 AudioSourceComponent + +```cpp +namespace XCEngine { +namespace Components { + +class AudioSourceComponent : public Component { +public: + AudioSourceComponent(); + ~AudioSourceComponent(); + + // 播放控制 + void Play(); + void Pause(); + void Stop(StopMode mode = StopMode::Immediate); + bool IsPlaying() const { return m_playState == Audio::PlayState::Playing; } + bool IsPaused() const { return m_playState == Audio::PlayState::Paused; } + + // 音频剪辑 + void SetClip(Audio::AudioClip* clip); + Audio::AudioClip* GetClip() const { return m_clip; } + + // 播放参数 + void SetVolume(float volume); + float GetVolume() const { return m_volume; } + + void SetPitch(float pitch); + float GetPitch() const { return m_pitch; } + + void SetPan(float pan); + float GetPan() const { return m_pan; } + + // 循环 + void SetLooping(bool loop); + bool IsLooping() const { return m_isLooping; } + + // 3D 空间化 + void SetSpatialize(bool spatialize); + bool IsSpatialize() const { return m_spatialize; } + + void Set3DParams(const Audio::Audio3DParams& params); + const Audio::Audio3DParams& Get3DParams() const { return m_3DParams; } + + void SetDopplerLevel(float level); + float GetDopplerLevel() const { return m_3DParams.dopplerLevel; } + + void SetSpread(float spread); + float GetSpread() const { return m_3DParams.spread; } + + void SetReverbZoneMix(float mix); + float GetReverbZoneMix() const { return m_3DParams.reverbZoneMix; } + + // 混音器 + void SetOutputMixer(Audio::AudioMixer* mixer); + Audio::AudioMixer* GetOutputMixer() const { return m_outputMixer; } + + // 播放位置 + void SetTime(float seconds); + float GetTime() const; + float GetDuration() const; + + // 能量检测 + float GetEnergy() const { return m_energy; } + void StartEnergyDetect(); + void StopEnergyDetect(); + bool IsEnergyDetecting() const { return m_isEnergyDetecting; } + + // Component 虚函数 + void Update(float deltaTime) override; + void OnEnable() override; + void OnDisable() override; + void OnDestroy() override; + +private: + void Apply3DAttenuation(); + void ProcessEffects(float* buffer, uint32_t sampleCount); + void UpdateEnergy(const float* buffer, uint32_t sampleCount); + +private: + Audio::AudioClip* m_clip = nullptr; + Audio::AudioMixer* m_outputMixer = nullptr; + + PlayState m_playState = PlayState::Stopped; + bool m_isLooping = false; + + float m_volume = 1.0f; + float m_pitch = 1.0f; + float m_pan = 0.0f; + bool m_spatialize = true; + + Audio::Audio3DParams m_3DParams; + + // 播放状态 + uint64_t m_samplePosition = 0; + double m_lastingTime = 0.0; + + // 能量检测 + bool m_isEnergyDetecting = false; + float m_energy = 0.0f; + float m_maxEnergy = 5.0f; + std::deque m_energyHistory; + + // 效果链 + std::vector m_effects; + std::unique_ptr m_hrtf; + std::unique_ptr m_doppler; + + // 输出缓冲 + static constexpr size_t BufferSize = 8192; + std::vector m_outputBuffer; +}; + +} // namespace Components +} // namespace XCEngine +``` + +### 6.3 AudioListenerComponent + +```cpp +namespace XCEngine { +namespace Components { + +class AudioListenerComponent : public Component { +public: + AudioListenerComponent(); + ~AudioListenerComponent(); + + // 能量检测 + float GetEnergy() const { return m_energy; } + const float* GetFrequencyData() const { return m_frequencyData.data(); } + size_t GetFrequencyDataSize() const { return m_frequencyData.size(); } + + // 全局参数 + void SetMasterVolume(float volume); + float GetMasterVolume() const { return m_masterVolume; } + + void SetMute(bool mute); + bool IsMute() const { return m_mute; } + + // 空间化参数 + void SetDopplerLevel(float level); + float GetDopplerLevel() const { return m_dopplerLevel; } + + void SetSpeedOfSound(float metersPerSecond); + float GetSpeedOfSound() const { return m_speedOfSound; } + + // 混响 + void SetReverbLevel(float level); + float GetReverbLevel() const { return m_reverbLevel; } + + void SetReverb(Audio::AudioMixer* reverb); + Audio::AudioMixer* GetReverb() const { return m_reverb; } + + // Component 虚函数 + void Update(float deltaTime) override; + +private: + float m_masterVolume = 1.0f; + bool m_mute = false; + float m_dopplerLevel = 1.0f; + float m_speedOfSound = 343.0f; + float m_reverbLevel = 1.0f; + + Audio::AudioMixer* m_reverb = nullptr; + + float m_energy = 0.0f; + std::vector m_frequencyData; +}; + +} // namespace Components +} // namespace XCEngine +``` + +--- + +## 第七章 事件系统 + +### 7.1 AudioEvent + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioEvent { +public: + AudioEvent() = default; + AudioEvent(const String& name) : m_name(name) {} + + using Callback = std::function; + using FloatCallback = std::function; + + uint64_t Subscribe(Callback callback); + uint64_t Subscribe(FloatCallback callback, float param); + + void Unsubscribe(uint64_t id); + void ProcessUnsubscribes(); + + void Invoke() const; + void Invoke(float param) const; + + void Clear(); + + const String& GetName() const { return m_name; } + void SetName(const String& name) { m_name = name; } + +private: + struct ListenerBase { + virtual ~ListenerBase() = default; + virtual void Call() = 0; + virtual void CallFloat(float) = 0; + }; + + template + struct Listener : ListenerBase { + Listener(Func&& f) : func(std::move(f)) {} + void Call() override { func(); } + void CallFloat(float) override { + if constexpr (std::is_invocable_v) { + func(0.0f); + } else { + func(); + } + } + Func func; + }; + + String m_name; + std::vector>> m_listeners; + std::vector m_pendingUnsubscribes; + uint64_t m_nextId = 0; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +### 7.2 AudioEventSystem + +```cpp +namespace XCEngine { +namespace Audio { + +class AudioEventSystem { +public: + static AudioEventSystem& Get(); + + void Initialize(); + void Shutdown(); + + // 事件创建 + AudioEvent* CreateEvent(const String& name); + AudioEvent* GetEvent(const String& name); + AudioEvent* GetEvent(uint64_t id); + + void DestroyEvent(const String& name); + void DestroyEvent(uint64_t id); + + // 触发事件 + void TriggerEvent(const String& name); + void TriggerEvent(uint64_t id); + void TriggerEvent(const String& name, float param); + void TriggerEvent(uint64_t id, float param); + + // 订阅 + uint64_t Subscribe(const String& eventName, AudioEvent::Callback callback); + uint64_t Subscribe(const String& eventName, AudioEvent::FloatCallback callback, float param); + + // 状态 + bool HasEvent(const String& name) const; + bool HasEvent(uint64_t id) const; + size_t GetEventCount() const { return m_events.size(); } + +private: + AudioEventSystem() = default; + ~AudioEventSystem() = default; + + std::map> m_events; + std::map m_idToEvent; + uint64_t m_nextEventId = 0; +}; + +} // namespace Audio +} // namespace XCEngine +``` + +--- + +## 第八章 资源管理集成 + +### 8.1 AudioClipLoader + +```cpp +namespace XCEngine { +namespace Resources { + +class AudioClipLoader : public IResourceLoader { +public: + AudioClipLoader(); + ~AudioClipLoader() override; + + bool CanLoad(const String& extension) const override; + IResource* Load(const String& filePath) override; + bool LoadAsync(const String& filePath, std::function callback) override; + void Unload(IResource* resource) override; + + // 获取支持的文件格式 + static const std::vector& GetSupportedExtensions(); + +private: + static std::vector s_supportedExtensions; +}; + +} // namespace Resources +} // namespace XCEngine +``` + +### 8.2 ResourceManager 集成 + +```cpp +namespace XCEngine { +namespace Resources { + +// 在 ResourceManager 中添加音频相关接口 + +template<> +class ResourceHandle { +public: + ResourceHandle() = default; + explicit ResourceHandle(Audio::AudioClip* resource); + + Audio::AudioClip* Get() const { return m_resource; } + Audio::AudioClip* operator->() const { return m_resource; } + + explicit operator bool() const { return m_resource != nullptr; } + +private: + Audio::AudioClip* m_resource = nullptr; +}; + +} // namespace Resources +} // namespace XCEngine +``` + +--- + +## 第九章 目录结构 + +``` +XCEngine/ +├── engine/ # 引擎核心库 +│ ├── include/XCEngine/ +│ │ ├── Audio/ # 音频模块 +│ │ │ ├── AudioSystem.h # 音频系统入口 +│ │ │ ├── AudioConfig.h # 配置结构 +│ │ │ ├── AudioTypes.h # 基础类型定义 +│ │ │ ├── IAudioBackend.h # 后端抽象接口 +│ │ │ ├── AudioBackendFactory.h # 后端工厂 +│ │ │ │ +│ │ │ ├── AudioClip.h # 音频剪辑资源 +│ │ │ ├── AudioMixer.h # 混音器 +│ │ │ ├── AudioBank.h # 音频银行 +│ │ │ │ +│ │ │ ├── IAudioEffect.h # 效果器接口 +│ │ │ ├── FFTFilter.h # FFT频谱分析 +│ │ │ ├── Reverbation.h # 混响效果 +│ │ │ ├── Equalizer.h # 均衡器 +│ │ │ ├── Compressor.h # 压缩器 +│ │ │ ├── Limiter.h # 限制器 +│ │ │ ├── DopplerEffect.h # 多普勒效应 +│ │ │ ├── OcclusionSystem.h # 遮蔽系统 +│ │ │ │ +│ │ │ ├── Spatializer.h # 空间化处理 +│ │ │ ├── HRTF.h # HRTF实现 +│ │ │ │ +│ │ │ ├── AudioEvent.h # 音频事件 +│ │ │ ├── AudioEventSystem.h # 事件系统 +│ │ │ │ +│ │ │ ├── WASAPI/ +│ │ │ │ └── WASAPIBackend.h # WASAPI后端 +│ │ │ └── OpenAL/ +│ │ │ └── OpenALBackend.h # OpenAL后端 +│ │ │ │ +│ │ │ └── XCEngineAudio.h # 主头文件 +│ │ │ +│ │ └── Components/ +│ │ ├── AudioSourceComponent.h # 声源组件 +│ │ └── AudioListenerComponent.h # 监听器组件 +│ │ +│ └── src/Audio/ +│ ├── AudioSystem.cpp +│ ├── AudioClip.cpp +│ ├── AudioMixer.cpp +│ ├── FFTFilter.cpp +│ ├── Reverbation.cpp +│ ├── Equalizer.cpp +│ ├── HRTF.cpp +│ ├── DopplerEffect.cpp +│ ├── OcclusionSystem.cpp +│ ├── AudioEventSystem.cpp +│ ├── WASAPI/ +│ │ └── WASAPIBackend.cpp +│ └── OpenAL/ +│ └── OpenALBackend.cpp +│ +├── mvs/ # 示例应用 +│ └── Music fluctuations/ # 音频示例(参考) +│ ├── source/ +│ │ ├── audio/ # 基础音频 +│ │ ├── audio3d/ # 3D音频 +│ │ ├── kissfft/ # FFT库 +│ │ └── libsamplerate/ # 重采样库 +│ └── res/ # 音频资源 +│ +└── tests/Audio/ # 音频模块测试 + ├── AudioSystemTest.cpp + ├── AudioClipTest.cpp + ├── SpatializerTest.cpp + └── HRTFTest.cpp +``` + +--- + +## 附录:实现优先级 + +### 第一阶段 - 基础音频 +1. AudioSystem 核心框架 +2. AudioClip 资源加载(WAV格式) +3. AudioSourceComponent 播放控制 +4. AudioListenerComponent 监听器 +5. WASAPI 后端实现 + +### 第二阶段 - 3D 空间音频 +1. HRTF 空间化 +2. DopplerEffect 多普勒效应 +3. OcclusionSystem 遮蔽系统 +4. 距离衰减 + +### 第三阶段 - 效果系统 +1. FFTFilter 频谱分析 +2. Reverbation 混响 +3. Equalizer 均衡器 +4. Compressor/Limiter 动态效果 +5. 效果链系统 + +### 第四阶段 - 混音与事件 +1. AudioMixer 混音器 +2. AudioEvent 事件系统 +3. AudioBank 分段加载 +4. OpenAL 后端实现 + +### 第五阶段 - 优化与扩展 +1. 资源异步加载 +2. 性能优化 +3. profiler 集成 +4. 更多音频格式支持 diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 5503124c..992f0d07 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -207,6 +207,19 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourcePackage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourceDependencyGraph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/ResourcePath.cpp + + # Components + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/Component.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/TransformComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/GameObject.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/TransformComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/GameObject.cpp + + # Scene + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/SceneManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneManager.cpp ) target_include_directories(XCEngine PUBLIC diff --git a/engine/include/XCEngine/Components/Component.h b/engine/include/XCEngine/Components/Component.h new file mode 100644 index 00000000..f7c5bef3 --- /dev/null +++ b/engine/include/XCEngine/Components/Component.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Components { + +class GameObject; +class TransformComponent; +class Scene; + +class Component { +public: + virtual ~Component() = default; + + virtual void Awake() {} + virtual void Start() {} + virtual void Update(float deltaTime) {} + virtual void FixedUpdate() {} + virtual void LateUpdate(float deltaTime) {} + virtual void OnEnable() {} + virtual void OnDisable() {} + virtual void OnDestroy() {} + + virtual std::string GetName() const = 0; + + GameObject* GetGameObject() const { return m_gameObject; } + TransformComponent& transform() const; + Scene* GetScene() const; + + bool IsEnabled() const { return m_enabled; } + void SetEnabled(bool enabled) { + if (m_enabled != enabled) { + m_enabled = enabled; + if (m_enabled) { + OnEnable(); + } else { + OnDisable(); + } + } + } + +protected: + Component() = default; + +private: + bool m_enabled = true; + +protected: + GameObject* m_gameObject = nullptr; + + friend class GameObject; +}; + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/include/XCEngine/Components/GameObject.h b/engine/include/XCEngine/Components/GameObject.h new file mode 100644 index 00000000..114839ca --- /dev/null +++ b/engine/include/XCEngine/Components/GameObject.h @@ -0,0 +1,174 @@ +#pragma once + +#include "Component.h" +#include "TransformComponent.h" +#include +#include +#include +#include + +namespace XCEngine { +namespace Components { + +class Scene; + +class GameObject { +public: + using ID = uint64_t; + static constexpr ID INVALID_ID = 0; + + GameObject(); + explicit GameObject(const std::string& name); + ~GameObject(); + + ID GetID() const { return m_id; } + const std::string& GetName() const { return m_name; } + void SetName(const std::string& name) { m_name = name; } + + Scene* GetScene() const { return m_scene; } + + TransformComponent* GetTransform() { return m_transform; } + const TransformComponent* GetTransform() const { return m_transform; } + + template + T* AddComponent(Args&&... args) { + auto component = std::make_unique(std::forward(args)...); + component->m_gameObject = this; + T* ptr = component.get(); + m_components.emplace_back(std::move(component)); + return ptr; + } + + template + T* GetComponent() { + for (auto& comp : m_components) { + if (T* casted = dynamic_cast(comp.get())) { + return casted; + } + } + return nullptr; + } + + template + const T* GetComponent() const { + for (auto& comp : m_components) { + if (T* casted = dynamic_cast(comp.get())) { + return casted; + } + } + return nullptr; + } + + template + std::vector GetComponents() { + std::vector result; + for (auto& comp : m_components) { + if (T* casted = dynamic_cast(comp.get())) { + result.push_back(casted); + } + } + return result; + } + + template + std::vector GetComponents() const { + std::vector result; + for (auto& comp : m_components) { + if (const T* casted = dynamic_cast(comp.get())) { + result.push_back(casted); + } + } + return result; + } + + template + T* GetComponentInChildren() { + T* comp = GetComponent(); + if (comp) { + return comp; + } + for (auto* child : m_children) { + comp = child->GetComponentInChildren(); + if (comp) { + return comp; + } + } + return nullptr; + } + + template + T* GetComponentInParent() { + if (m_parent) { + T* comp = m_parent->GetComponent(); + if (comp) { + return comp; + } + return m_parent->GetComponentInParent(); + } + return nullptr; + } + + template + std::vector GetComponentsInChildren() { + std::vector result; + std::vector comps = GetComponents(); + result.insert(result.end(), comps.begin(), comps.end()); + + for (auto* child : m_children) { + std::vector childComps = child->GetComponentsInChildren(); + result.insert(result.end(), childComps.begin(), childComps.end()); + } + return result; + } + + // Hierarchy + GameObject* GetParent() const { return m_parent; } + void SetParent(GameObject* parent); + void SetParent(GameObject* parent, bool worldPositionStays); + + size_t GetChildCount() const { return m_children.size(); } + GameObject* GetChild(size_t index) const; + std::vector GetChildren() const; + void DetachChildren(); + + // Active state + bool IsActive() const { return m_activeSelf; } + void SetActive(bool active); + bool IsActiveInHierarchy() const; + + // Static find + static GameObject* Find(const std::string& name); + static std::vector FindObjectsOfType(); + static std::vector FindGameObjectsWithTag(const std::string& tag); + + // Lifecycle + void Awake(); + void Start(); + void Update(float deltaTime); + void FixedUpdate(); + void LateUpdate(float deltaTime); + void OnDestroy(); + + void Destroy(); + +private: + ID m_id = INVALID_ID; + std::string m_name; + bool m_activeSelf = true; + + GameObject* m_parent = nullptr; + std::vector m_children; + + Scene* m_scene = nullptr; + TransformComponent* m_transform = nullptr; + std::vector> m_components; + + static ID s_nextID; + static std::unordered_map& GetGlobalRegistry(); + + friend class Scene; + friend class SceneManager; +}; + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/include/XCEngine/Components/TransformComponent.h b/engine/include/XCEngine/Components/TransformComponent.h new file mode 100644 index 00000000..fb0f9b3b --- /dev/null +++ b/engine/include/XCEngine/Components/TransformComponent.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Component.h" +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Components { + +class GameObject; +class Scene; + +enum class Space { + Self, + World +}; + +class TransformComponent : public Component { +public: + TransformComponent(); + ~TransformComponent() override = default; + + std::string GetName() const override { return "Transform"; } + + // Local space getters/setters + const Math::Vector3& GetLocalPosition() const { return m_localPosition; } + void SetLocalPosition(const Math::Vector3& position) { m_localPosition = position; SetDirty(); } + + const Math::Quaternion& GetLocalRotation() const { return m_localRotation; } + void SetLocalRotation(const Math::Quaternion& rotation) { m_localRotation = rotation; SetDirty(); } + + const Math::Vector3& GetLocalScale() const { return m_localScale; } + void SetLocalScale(const Math::Vector3& scale) { m_localScale = scale; SetDirty(); } + + Math::Vector3 GetLocalEulerAngles() const; + void SetLocalEulerAngles(const Math::Vector3& eulers); + + // World space getters/setters + Math::Vector3 GetPosition() const; + void SetPosition(const Math::Vector3& position); + + Math::Quaternion GetRotation() const; + void SetRotation(const Math::Quaternion& rotation); + + Math::Vector3 GetScale() const; + void SetScale(const Math::Vector3& scale); + + // Direction vectors + Math::Vector3 GetForward() const; + Math::Vector3 GetRight() const; + Math::Vector3 GetUp() const; + + // Matrix operations + const Math::Matrix4x4& GetLocalToWorldMatrix() const; + Math::Matrix4x4 GetWorldToLocalMatrix() const; + + // Hierarchy - parent + TransformComponent* GetParent() const { return m_parent; } + void SetParent(TransformComponent* parent, bool worldPositionStays = true); + void SetParent(TransformComponent* parent) { SetParent(parent, true); } + + // Hierarchy - children + size_t GetChildCount() const { return m_children.size(); } + TransformComponent* GetChild(size_t index) const; + TransformComponent* Find(const std::string& name) const; + void DetachChildren(); + + // Hierarchy - sibling + int GetSiblingIndex() const { return m_siblingIndex; } + void SetSiblingIndex(int index); + void SetAsFirstSibling(); + void SetAsLastSibling(); + + // Transform operations + void LookAt(const Math::Vector3& target); + void LookAt(const Math::Vector3& target, const Math::Vector3& up); + void Rotate(const Math::Vector3& eulers, Space relativeTo = Space::Self); + void Rotate(const Math::Vector3& axis, float angle, Space relativeTo = Space::Self); + void Translate(const Math::Vector3& translation, Space relativeTo = Space::Self); + + // Point/direction transforms + Math::Vector3 TransformPoint(const Math::Vector3& point) const; + Math::Vector3 InverseTransformPoint(const Math::Vector3& point) const; + Math::Vector3 TransformDirection(const Math::Vector3& direction) const; + Math::Vector3 InverseTransformDirection(const Math::Vector3& direction) const; + + // Internal + void SetDirty(); + void UpdateWorldTransform() const; + void NotifyHierarchyChanged(); + +private: + Math::Vector3 m_localPosition = Math::Vector3::Zero(); + Math::Quaternion m_localRotation = Math::Quaternion::Identity(); + Math::Vector3 m_localScale = Math::Vector3::One(); + + TransformComponent* m_parent = nullptr; + std::vector m_children; + + mutable Math::Matrix4x4 m_localToWorldMatrix; + mutable Math::Vector3 m_worldPosition; + mutable Math::Quaternion m_worldRotation; + mutable Math::Vector3 m_worldScale; + mutable bool m_dirty = true; + int m_siblingIndex = 0; + + friend class GameObject; +}; + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/include/XCEngine/Scene/Scene.h b/engine/include/XCEngine/Scene/Scene.h new file mode 100644 index 00000000..f4ccb769 --- /dev/null +++ b/engine/include/XCEngine/Scene/Scene.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Components { + +class Scene { +public: + using GameObjectID = uint64_t; + static constexpr GameObjectID INVALID_GAMEOBJECT_ID = 0; + + Scene(); + explicit Scene(const std::string& name); + ~Scene(); + + const std::string& GetName() const { return m_name; } + void SetName(const std::string& name) { m_name = name; } + + bool IsActive() const { return m_active; } + void SetActive(bool active) { m_active = active; } + + GameObject* CreateGameObject(const std::string& name, GameObject* parent = nullptr); + void DestroyGameObject(GameObject* gameObject); + + GameObject* Find(const std::string& name) const; + GameObject* FindGameObjectWithTag(const std::string& tag) const; + + template + T* FindObjectOfType() const { + for (auto* go : GetRootGameObjects()) { + if (T* comp = go->GetComponent()) { + return comp; + } + if (T* comp = FindInChildren(go)) { + return comp; + } + } + return nullptr; + } + + template + std::vector FindObjectsOfType() const { + std::vector results; + for (auto* go : GetRootGameObjects()) { + if (T* comp = go->GetComponent()) { + results.push_back(comp); + } + FindInChildren(go, results); + } + return results; + } + + std::vector GetRootGameObjects() const; + + void Update(float deltaTime); + void FixedUpdate(float fixedDeltaTime); + void LateUpdate(float deltaTime); + + void Load(const std::string& filePath); + void Save(const std::string& filePath); + + Core::Event& OnGameObjectCreated() { return m_onGameObjectCreated; } + Core::Event& OnGameObjectDestroyed() { return m_onGameObjectDestroyed; } + +private: + GameObject* FindInChildren(GameObject* parent, const std::string& name) const; + + template + T* FindInChildren(GameObject* parent) const { + for (size_t i = 0; i < parent->GetChildCount(); ++i) { + GameObject* child = parent->GetChild(i); + if (T* comp = child->GetComponent()) { + return comp; + } + if (T* comp = FindInChildren(child)) { + return comp; + } + } + return nullptr; + } + + template + void FindInChildren(GameObject* parent, std::vector& results) const { + for (size_t i = 0; i < parent->GetChildCount(); ++i) { + GameObject* child = parent->GetChild(i); + if (T* comp = child->GetComponent()) { + results.push_back(comp); + } + FindInChildren(child, results); + } + } + + std::string m_name; + bool m_active = true; + std::unordered_map> m_gameObjects; + std::vector m_rootGameObjects; + std::unordered_set m_gameObjectIDs; + + Core::Event m_onGameObjectCreated; + Core::Event m_onGameObjectDestroyed; + + friend class GameObject; + friend class SceneManager; +}; + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/include/XCEngine/Scene/SceneManager.h b/engine/include/XCEngine/Scene/SceneManager.h new file mode 100644 index 00000000..95596f14 --- /dev/null +++ b/engine/include/XCEngine/Scene/SceneManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include "Scene.h" +#include +#include +#include +#include + +namespace XCEngine { +namespace Components { + +class SceneManager { +public: + static SceneManager& Get(); + + Scene* CreateScene(const std::string& name); + void LoadScene(const std::string& filePath); + void LoadSceneAsync(const std::string& filePath, std::function callback); + void UnloadScene(Scene* scene); + void UnloadScene(const std::string& sceneName); + + void SetActiveScene(Scene* scene); + void SetActiveScene(const std::string& sceneName); + Scene* GetActiveScene() const { return m_activeScene; } + + Scene* GetScene(const std::string& name) const; + std::vector GetAllScenes() const; + + Core::Event& OnSceneLoaded() { return m_onSceneLoaded; } + Core::Event& OnSceneUnloaded() { return m_onSceneUnloaded; } + Core::Event& OnActiveSceneChanged() { return m_onActiveSceneChanged; } + +private: + SceneManager() = default; + ~SceneManager() = default; + SceneManager(const SceneManager&) = delete; + SceneManager& operator=(const SceneManager&) = delete; + + Scene* m_activeScene = nullptr; + std::unordered_map> m_scenes; + + Core::Event m_onSceneLoaded; + Core::Event m_onSceneUnloaded; + Core::Event m_onActiveSceneChanged; +}; + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Components/GameObject.cpp b/engine/src/Components/GameObject.cpp new file mode 100644 index 00000000..295217c5 --- /dev/null +++ b/engine/src/Components/GameObject.cpp @@ -0,0 +1,190 @@ +#include "Components/GameObject.h" +#include "Components/TransformComponent.h" +#include "Scene/Scene.h" + +namespace XCEngine { +namespace Components { + +GameObject::ID GameObject::s_nextID = 1; + +GameObject::GameObject() + : m_name("GameObject") { + m_id = s_nextID++; + m_transform = new TransformComponent(); + m_transform->m_gameObject = this; +} + +GameObject::GameObject(const std::string& name) + : m_name(name) { + m_id = s_nextID++; + m_transform = new TransformComponent(); + m_transform->m_gameObject = this; +} + +GameObject::~GameObject() { + if (m_transform) { + delete m_transform; + m_transform = nullptr; + } + m_components.clear(); +} + +std::unordered_map& GameObject::GetGlobalRegistry() { + static std::unordered_map registry; + return registry; +} + +void GameObject::SetParent(GameObject* parent) { + SetParent(parent, true); +} + +void GameObject::SetParent(GameObject* parent, bool worldPositionStays) { + if (m_parent == parent) { + return; + } + + Math::Vector3 worldPos = worldPositionStays ? GetTransform()->GetPosition() : GetTransform()->GetLocalPosition(); + Math::Quaternion worldRot = worldPositionStays ? GetTransform()->GetRotation() : GetTransform()->GetLocalRotation(); + Math::Vector3 worldScale = worldPositionStays ? GetTransform()->GetScale() : GetTransform()->GetLocalScale(); + + if (m_parent) { + auto& siblings = m_parent->m_children; + siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end()); + } + + m_parent = parent; + + if (m_parent) { + m_parent->m_children.push_back(this); + } + + if (worldPositionStays) { + GetTransform()->SetPosition(worldPos); + GetTransform()->SetRotation(worldRot); + GetTransform()->SetScale(worldScale); + } + + GetTransform()->SetDirty(); +} + +GameObject* GameObject::GetChild(size_t index) const { + if (index < m_children.size()) { + return m_children[index]; + } + return nullptr; +} + +std::vector GameObject::GetChildren() const { + return m_children; +} + +void GameObject::DetachChildren() { + for (auto* child : m_children) { + if (child) { + child->m_parent = nullptr; + } + } + m_children.clear(); +} + +void GameObject::SetActive(bool active) { + if (m_activeSelf != active) { + m_activeSelf = active; + if (m_parent == nullptr || m_parent->IsActiveInHierarchy()) { + } + } +} + +bool GameObject::IsActiveInHierarchy() const { + if (!m_activeSelf) { + return false; + } + if (m_parent) { + return m_parent->IsActiveInHierarchy(); + } + return true; +} + +GameObject* GameObject::Find(const std::string& name) { + auto& registry = GetGlobalRegistry(); + for (auto& pair : registry) { + if (pair.second->GetName() == name) { + return pair.second; + } + } + return nullptr; +} + +std::vector GameObject::FindObjectsOfType() { + auto& registry = GetGlobalRegistry(); + std::vector result; + for (auto& pair : registry) { + result.push_back(pair.second); + } + return result; +} + +std::vector GameObject::FindGameObjectsWithTag(const std::string& tag) { + auto& registry = GetGlobalRegistry(); + std::vector result; + for (auto& pair : registry) { + if (pair.second->GetName() == tag) { + result.push_back(pair.second); + } + } + return result; +} + +void GameObject::Awake() { + for (auto& comp : m_components) { + comp->Awake(); + } +} + +void GameObject::Start() { + for (auto& comp : m_components) { + if (comp->IsEnabled()) { + comp->Start(); + } + } +} + +void GameObject::Update(float deltaTime) { + for (auto& comp : m_components) { + if (comp->IsEnabled()) { + comp->Update(deltaTime); + } + } +} + +void GameObject::FixedUpdate() { + for (auto& comp : m_components) { + if (comp->IsEnabled()) { + comp->FixedUpdate(); + } + } +} + +void GameObject::LateUpdate(float deltaTime) { + for (auto& comp : m_components) { + if (comp->IsEnabled()) { + comp->LateUpdate(deltaTime); + } + } +} + +void GameObject::OnDestroy() { + for (auto& comp : m_components) { + comp->OnDestroy(); + } +} + +void GameObject::Destroy() { + OnDestroy(); + if (m_scene) { + m_scene->DestroyGameObject(this); + } +} + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Components/TransformComponent.cpp b/engine/src/Components/TransformComponent.cpp new file mode 100644 index 00000000..29bc4876 --- /dev/null +++ b/engine/src/Components/TransformComponent.cpp @@ -0,0 +1,277 @@ +#include "Components/TransformComponent.h" +#include "Components/GameObject.h" +#include "Scene/Scene.h" +#include + +namespace XCEngine { +namespace Components { + +TransformComponent::TransformComponent() = default; + +void TransformComponent::SetDirty() { + m_dirty = true; + for (auto* child : m_children) { + if (child) { + child->SetDirty(); + } + } +} + +void TransformComponent::UpdateWorldTransform() const { + if (!m_dirty) { + return; + } + + if (m_parent) { + const Math::Matrix4x4& parentMatrix = m_parent->GetLocalToWorldMatrix(); + Math::Matrix4x4 localMatrix = Math::Matrix4x4::TRS(m_localPosition, m_localRotation, m_localScale); + m_localToWorldMatrix = parentMatrix * localMatrix; + } else { + m_localToWorldMatrix = Math::Matrix4x4::TRS(m_localPosition, m_localRotation, m_localScale); + } + + m_localToWorldMatrix.Decompose(m_worldPosition, m_worldRotation, m_worldScale); + m_dirty = false; +} + +const Math::Matrix4x4& TransformComponent::GetLocalToWorldMatrix() const { + UpdateWorldTransform(); + return m_localToWorldMatrix; +} + +Math::Matrix4x4 TransformComponent::GetWorldToLocalMatrix() const { + return GetLocalToWorldMatrix().Inverse(); +} + +Math::Vector3 TransformComponent::GetPosition() const { + UpdateWorldTransform(); + return m_worldPosition; +} + +void TransformComponent::SetPosition(const Math::Vector3& position) { + if (m_parent) { + Math::Matrix4x4 worldToParent = m_parent->GetWorldToLocalMatrix(); + m_localPosition = worldToParent.MultiplyPoint(position); + } else { + m_localPosition = position; + } + SetDirty(); +} + +Math::Quaternion TransformComponent::GetRotation() const { + UpdateWorldTransform(); + return m_worldRotation; +} + +void TransformComponent::SetRotation(const Math::Quaternion& rotation) { + if (m_parent) { + Math::Quaternion parentInverse = m_parent->GetRotation().Inverse(); + m_localRotation = parentInverse * rotation; + } else { + m_localRotation = rotation; + } + SetDirty(); +} + +Math::Vector3 TransformComponent::GetScale() const { + UpdateWorldTransform(); + return m_worldScale; +} + +void TransformComponent::SetScale(const Math::Vector3& scale) { + if (m_parent) { + const Math::Vector3& parentScale = m_parent->GetScale(); + m_localScale = Math::Vector3( + parentScale.x != 0.0f ? scale.x / parentScale.x : 0.0f, + parentScale.y != 0.0f ? scale.y / parentScale.y : 0.0f, + parentScale.z != 0.0f ? scale.z / parentScale.z : 0.0f + ); + } else { + m_localScale = scale; + } + SetDirty(); +} + +Math::Vector3 TransformComponent::GetLocalEulerAngles() const { + return m_localRotation.ToEulerAngles() * Math::RAD_TO_DEG; +} + +void TransformComponent::SetLocalEulerAngles(const Math::Vector3& eulers) { + m_localRotation = Math::Quaternion::FromEulerAngles(eulers * Math::DEG_TO_RAD); + SetDirty(); +} + +Math::Vector3 TransformComponent::GetForward() const { + return GetRotation() * Math::Vector3::Forward(); +} + +Math::Vector3 TransformComponent::GetRight() const { + return GetRotation() * Math::Vector3::Right(); +} + +Math::Vector3 TransformComponent::GetUp() const { + return GetRotation() * Math::Vector3::Up(); +} + +void TransformComponent::SetParent(TransformComponent* parent, bool worldPositionStays) { + if (m_parent == parent) { + return; + } + + Math::Vector3 worldPos = worldPositionStays ? GetPosition() : m_localPosition; + Math::Quaternion worldRot = worldPositionStays ? GetRotation() : m_localRotation; + Math::Vector3 worldScale = worldPositionStays ? GetScale() : m_localScale; + + if (m_parent) { + auto& siblings = m_parent->m_children; + siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end()); + } + + m_parent = parent; + + if (m_parent) { + m_parent->m_children.push_back(this); + } + + if (worldPositionStays) { + SetPosition(worldPos); + Math::Quaternion newLocalRot = m_parent ? m_parent->GetRotation().Inverse() * worldRot : worldRot; + m_localRotation = newLocalRot; + SetScale(worldScale); + } + + SetDirty(); +} + +TransformComponent* TransformComponent::GetChild(size_t index) const { + if (index < m_children.size()) { + return m_children[index]; + } + return nullptr; +} + +TransformComponent* FindInHierarchy(GameObject* go, const std::string& name); + +TransformComponent* TransformComponent::Find(const std::string& name) const { + GameObject* go = m_gameObject; + if (!go) { + return nullptr; + } + + Scene* scene = go->GetScene(); + if (!scene) { + return nullptr; + } + + for (auto* rootGO : scene->GetRootGameObjects()) { + TransformComponent* found = FindInHierarchy(rootGO, name); + if (found) { + return found; + } + } + return nullptr; +} + +TransformComponent* FindInHierarchy(GameObject* go, const std::string& name) { + if (!go) { + return nullptr; + } + if (go->GetName() == name) { + return go->GetTransform(); + } + for (size_t i = 0; i < go->GetChildCount(); ++i) { + if (TransformComponent* found = FindInHierarchy(go->GetChild(i), name)) { + return found; + } + } + return nullptr; +} + +void TransformComponent::DetachChildren() { + for (auto* child : m_children) { + if (child) { + child->m_parent = nullptr; + } + } + m_children.clear(); +} + +void TransformComponent::SetSiblingIndex(int index) { + m_siblingIndex = index; +} + +void TransformComponent::SetAsFirstSibling() { + if (m_parent) { + m_parent->SetSiblingIndex(0); + } +} + +void TransformComponent::SetAsLastSibling() { + if (m_parent) { + m_parent->SetSiblingIndex(static_cast(m_parent->GetChildCount()) - 1); + } +} + +void TransformComponent::LookAt(const Math::Vector3& target) { + LookAt(target, Math::Vector3::Up()); +} + +void TransformComponent::LookAt(const Math::Vector3& target, const Math::Vector3& up) { + Math::Vector3 forward = Math::Vector3::Normalize(target - GetPosition()); + if (Math::Vector3::Magnitude(forward) < Math::EPSILON) { + return; + } + SetRotation(Math::Quaternion::LookRotation(forward, up)); +} + +void TransformComponent::Rotate(const Math::Vector3& eulers, Space relativeTo) { + Math::Quaternion rotation = Math::Quaternion::FromEulerAngles(eulers * Math::DEG_TO_RAD); + if (relativeTo == Space::Self) { + m_localRotation = m_localRotation * rotation; + } else { + m_localRotation = rotation * m_localRotation; + } + SetDirty(); +} + +void TransformComponent::Rotate(const Math::Vector3& axis, float angle, Space relativeTo) { + Math::Quaternion rotation = Math::Quaternion::FromAxisAngle(axis, angle * Math::DEG_TO_RAD); + if (relativeTo == Space::Self) { + m_localRotation = m_localRotation * rotation; + } else { + m_localRotation = rotation * m_localRotation; + } + SetDirty(); +} + +void TransformComponent::Translate(const Math::Vector3& translation, Space relativeTo) { + if (relativeTo == Space::Self) { + m_localPosition = m_localPosition + translation; + } else { + m_localPosition = m_localPosition + translation; + } + SetDirty(); +} + +Math::Vector3 TransformComponent::TransformPoint(const Math::Vector3& point) const { + return GetLocalToWorldMatrix().MultiplyPoint(point); +} + +Math::Vector3 TransformComponent::InverseTransformPoint(const Math::Vector3& point) const { + return GetWorldToLocalMatrix().MultiplyPoint(point); +} + +Math::Vector3 TransformComponent::TransformDirection(const Math::Vector3& direction) const { + return GetRotation() * direction; +} + +Math::Vector3 TransformComponent::InverseTransformDirection(const Math::Vector3& direction) const { + return GetRotation().Inverse() * direction; +} + +void TransformComponent::NotifyHierarchyChanged() { + SetDirty(); +} + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Scene/Scene.cpp b/engine/src/Scene/Scene.cpp new file mode 100644 index 00000000..63fd02c0 --- /dev/null +++ b/engine/src/Scene/Scene.cpp @@ -0,0 +1,151 @@ +#include "Scene/Scene.h" +#include "Components/GameObject.h" + +namespace XCEngine { +namespace Components { + +Scene::Scene() + : m_name("Untitled") { +} + +Scene::Scene(const std::string& name) + : m_name(name) { +} + +Scene::~Scene() { + m_gameObjects.clear(); +} + +GameObject* Scene::CreateGameObject(const std::string& name, GameObject* parent) { + auto gameObject = std::make_unique(name); + GameObject* ptr = gameObject.get(); + + m_gameObjectIDs.insert(ptr->m_id); + m_gameObjects.emplace(ptr->m_id, std::move(gameObject)); + + if (parent) { + ptr->SetParent(parent); + } else { + m_rootGameObjects.push_back(ptr->m_id); + } + + ptr->m_scene = this; + ptr->Awake(); + + m_onGameObjectCreated.Invoke(ptr); + + return ptr; +} + +void Scene::DestroyGameObject(GameObject* gameObject) { + if (!gameObject || gameObject->m_scene != this) { + return; + } + + for (auto* child : gameObject->GetChildren()) { + DestroyGameObject(child); + } + + if (gameObject->m_parent) { + auto& siblings = gameObject->m_parent->m_children; + siblings.erase(std::remove(siblings.begin(), siblings.end(), gameObject), siblings.end()); + } else { + m_rootGameObjects.erase( + std::remove(m_rootGameObjects.begin(), m_rootGameObjects.end(), gameObject->m_id), + m_rootGameObjects.end() + ); + } + + m_onGameObjectDestroyed.Invoke(gameObject); + + gameObject->OnDestroy(); + + m_gameObjectIDs.erase(gameObject->m_id); + m_gameObjects.erase(gameObject->m_id); +} + +GameObject* Scene::Find(const std::string& name) const { + for (auto* go : GetRootGameObjects()) { + if (go->GetName() == name) { + return go; + } + GameObject* found = FindInChildren(go, name); + if (found) { + return found; + } + } + return nullptr; +} + +GameObject* Scene::FindInChildren(GameObject* parent, const std::string& name) const { + for (size_t i = 0; i < parent->GetChildCount(); ++i) { + GameObject* child = parent->GetChild(i); + if (child->GetName() == name) { + return child; + } + GameObject* found = FindInChildren(child, name); + if (found) { + return found; + } + } + return nullptr; +} + +GameObject* Scene::FindGameObjectWithTag(const std::string& tag) const { + for (auto* go : GetRootGameObjects()) { + if (go->GetName() == tag) { + return go; + } + GameObject* found = FindInChildren(go, tag); + if (found) { + return found; + } + } + return nullptr; +} + +std::vector Scene::GetRootGameObjects() const { + std::vector result; + for (auto id : m_rootGameObjects) { + auto it = m_gameObjects.find(id); + if (it != m_gameObjects.end()) { + result.push_back(it->second.get()); + } + } + return result; +} + +void Scene::Update(float deltaTime) { + for (auto* go : GetRootGameObjects()) { + if (go->IsActiveInHierarchy()) { + go->Update(deltaTime); + } + } +} + +void Scene::FixedUpdate(float fixedDeltaTime) { + for (auto* go : GetRootGameObjects()) { + if (go->IsActiveInHierarchy()) { + go->FixedUpdate(); + } + } +} + +void Scene::LateUpdate(float deltaTime) { + for (auto* go : GetRootGameObjects()) { + if (go->IsActiveInHierarchy()) { + go->LateUpdate(deltaTime); + } + } +} + +void Scene::Load(const std::string& filePath) { + m_gameObjects.clear(); + m_rootGameObjects.clear(); +} + +void Scene::Save(const std::string& filePath) { +} + +} // namespace Components +} // namespace XCEngine \ No newline at end of file diff --git a/engine/src/Scene/SceneManager.cpp b/engine/src/Scene/SceneManager.cpp new file mode 100644 index 00000000..bd4b6da1 --- /dev/null +++ b/engine/src/Scene/SceneManager.cpp @@ -0,0 +1,116 @@ +#include "Scene/SceneManager.h" + +namespace XCEngine { +namespace Components { + +SceneManager& SceneManager::Get() { + static SceneManager instance; + return instance; +} + +Scene* SceneManager::CreateScene(const std::string& name) { + auto scene = std::make_unique(name); + Scene* ptr = scene.get(); + m_scenes[name] = std::move(scene); + + if (!m_activeScene) { + m_activeScene = ptr; + } + + m_onSceneLoaded.Invoke(ptr); + return ptr; +} + +void SceneManager::LoadScene(const std::string& filePath) { + auto scene = std::make_unique(); + scene->Load(filePath); + Scene* ptr = scene.get(); + + std::string name = filePath; + size_t pos = name.find_last_of("/\\"); + if (pos != std::string::npos) { + name = name.substr(pos + 1); + } + pos = name.find_last_of('.'); + if (pos != std::string::npos) { + name = name.substr(0, pos); + } + + m_scenes[name] = std::move(scene); + m_onSceneLoaded.Invoke(ptr); +} + +void SceneManager::LoadSceneAsync(const std::string& filePath, std::function callback) { + LoadScene(filePath); + if (callback) { + Scene* scene = GetScene(filePath); + callback(scene); + } +} + +void SceneManager::UnloadScene(Scene* scene) { + if (!scene) { + return; + } + + if (m_activeScene == scene) { + m_activeScene = nullptr; + } + + auto it = m_scenes.begin(); + while (it != m_scenes.end()) { + if (it->second.get() == scene) { + m_onSceneUnloaded.Invoke(scene); + it = m_scenes.erase(it); + break; + } else { + ++it; + } + } + + if (m_activeScene == nullptr && !m_scenes.empty()) { + m_activeScene = m_scenes.begin()->second.get(); + m_onActiveSceneChanged.Invoke(m_activeScene); + } +} + +void SceneManager::UnloadScene(const std::string& sceneName) { + auto it = m_scenes.find(sceneName); + if (it != m_scenes.end()) { + UnloadScene(it->second.get()); + } +} + +void SceneManager::SetActiveScene(Scene* scene) { + if (m_activeScene == scene) { + return; + } + m_activeScene = scene; + m_onActiveSceneChanged.Invoke(scene); +} + +void SceneManager::SetActiveScene(const std::string& sceneName) { + auto it = m_scenes.find(sceneName); + if (it != m_scenes.end()) { + SetActiveScene(it->second.get()); + } +} + +Scene* SceneManager::GetScene(const std::string& name) const { + auto it = m_scenes.find(name); + if (it != m_scenes.end()) { + return it->second.get(); + } + return nullptr; +} + +std::vector SceneManager::GetAllScenes() const { + std::vector result; + for (auto& pair : m_scenes) { + result.push_back(pair.second.get()); + } + return result; +} + +} // namespace Components +} // namespace XCEngine \ No newline at end of file