修改Tom总流程,完善动画机的状态设置以及游戏逻辑;在core/platform新增win和板端的鼠标点击/触控的逻辑代码/驱动;测试游戏内容。

This commit is contained in:
HP 2026-06-08 12:53:38 +08:00
parent 24fe01af46
commit 8785368913
11 changed files with 671 additions and 270 deletions

View File

@ -21,6 +21,7 @@ set(CORE_SOURCES
src/Core/Platform/AlsaAudioInput.cpp src/Core/Platform/AlsaAudioInput.cpp
src/Core/Platform/AlsaAudioOutput.cpp src/Core/Platform/AlsaAudioOutput.cpp
src/Core/Platform/EvdevButtonInput.cpp src/Core/Platform/EvdevButtonInput.cpp
src/Core/Platform/EvdevTouchInput.cpp
src/Core/Rasterizer/Rasterizer.cpp src/Core/Rasterizer/Rasterizer.cpp
src/Core/Rasterizer/TriangleRasterizer.cpp src/Core/Rasterizer/TriangleRasterizer.cpp
src/Core/Scene/Camera.cpp src/Core/Scene/Camera.cpp
@ -37,6 +38,7 @@ else()
src/Core/Platform/SdlAudioInput.cpp src/Core/Platform/SdlAudioInput.cpp
src/Core/Platform/SdlAudioOutput.cpp src/Core/Platform/SdlAudioOutput.cpp
src/Core/Platform/SdlKeyboardButtonInput.cpp src/Core/Platform/SdlKeyboardButtonInput.cpp
src/Core/Platform/SdlPointerInput.cpp
) )
endif() endif()

View File

@ -4,10 +4,17 @@ set_source_files_properties(${TOM_ATLAS_HEADER} PROPERTIES GENERATED TRUE)
add_executable(${TOM_GAME_TARGET} add_executable(${TOM_GAME_TARGET}
Main.cpp Main.cpp
src/app/TomGameApp.cpp
src/audio/VoiceEffect.cpp
src/audio/VoicePlayer.cpp
src/audio/VoiceRecorder.cpp
${TOM_ATLAS_HEADER} ${TOM_ATLAS_HEADER}
) )
target_include_directories(${TOM_GAME_TARGET} PRIVATE target_include_directories(${TOM_GAME_TARGET} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/app
${CMAKE_CURRENT_SOURCE_DIR}/src/audio
${CMAKE_CURRENT_SOURCE_DIR}/generated ${CMAKE_CURRENT_SOURCE_DIR}/generated
) )

View File

@ -1,4 +1,3 @@
#include <algorithm>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
@ -6,12 +5,12 @@
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include "Color.h" #include "DefaultHardware.h"
#include "Display.h" #include "Display.h"
#include "DrawContext.h" #include "DrawContext.h"
#include "TimeSource.h" #include "TimeSource.h"
#include "Timer.h" #include "Timer.h"
#include "tom_atlas.h" #include "app/TomGameApp.h"
#ifdef USE_FRAMEBUFFER #ifdef USE_FRAMEBUFFER
#include "FBDisplay.h" #include "FBDisplay.h"
@ -108,25 +107,6 @@ namespace
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
} }
} }
static const RenderData::SpriteRegion& SelectTomFrame(
uint32_t animation_time_ms,
const RenderData::SpriteRegion* const* speaking_frames,
size_t speaking_frame_count)
{
const uint32_t phase = (animation_time_ms / 1000u) % 6u;
if (phase < 2u)
{
return TomAtlas::tom_stand;
}
if (phase < 3u)
{
return TomAtlas::tom_listhen;
}
const size_t frame_index = (animation_time_ms / 120u) % speaking_frame_count;
return *speaking_frames[frame_index];
}
} }
int main(int argc, char* argv[]) int main(int argc, char* argv[])
@ -145,28 +125,37 @@ int main(int argc, char* argv[])
return -1; return -1;
} }
const RenderData::SpriteRegion* speaking_frames[] = { Platform::DefaultAudioInput audioInput;
&TomAtlas::tom_say1, Platform::DefaultAudioOutput audioOutput;
&TomAtlas::tom_say2, Platform::DefaultButtonInput buttonInput;
&TomAtlas::tom_say3, Platform::DefaultPointerInput pointerInput;
&TomAtlas::tom_say4,
&TomAtlas::tom_say3, if (!buttonInput.init())
&TomAtlas::tom_say2 {
}; std::cerr << "[WARN] Button input init failed; physical key trigger is disabled." << std::endl;
const size_t speaking_frame_count = sizeof(speaking_frames) / sizeof(speaking_frames[0]); }
if (!pointerInput.init("", ScreenWidth, ScreenHeight))
{
std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl;
}
Core::DrawContext ctx(ScreenWidth, ScreenHeight); Core::DrawContext ctx(ScreenWidth, ScreenHeight);
Core::Timer timer(options.target_fps); Core::Timer timer(options.target_fps);
Platform::SteadyTimeSource time_source; Platform::SteadyTimeSource time_source;
Game::TomGameApp app(
ScreenWidth,
ScreenHeight,
&audioInput,
&audioOutput,
&buttonInput,
&pointerInput);
std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl; std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl;
bool is_running = true; bool is_running = true;
uint32_t animation_time_ms = 0;
while (is_running) while (is_running)
{ {
timer.begin_frame(time_source.get_time_ms()); timer.begin_frame(time_source.get_time_ms());
animation_time_ms += timer.fixed_delta_ms();
bool should_quit = false; bool should_quit = false;
display->poll_events(should_quit); display->poll_events(should_quit);
@ -175,26 +164,16 @@ int main(int argc, char* argv[])
is_running = false; is_running = false;
} }
ctx.clear(RenderData::Color(18, 18, 24, 255)); app.update(timer.fixed_delta_ms());
ctx.draw_sprite_region(0, 0, TomAtlas::background); app.draw(ctx);
const RenderData::SpriteRegion& tom = SelectTomFrame(
animation_time_ms,
speaking_frames,
speaking_frame_count);
const int32_t tom_x = (ScreenWidth - tom.width) / 2;
const int32_t tom_y = std::max(0, ScreenHeight - tom.height - 72);
ctx.draw_sprite_region(tom_x, tom_y, tom);
const int32_t button_x = (ScreenWidth - TomAtlas::ui_record.width) / 2;
const int32_t button_y = ScreenHeight - TomAtlas::ui_record.height - 16;
ctx.draw_sprite_region(button_x, button_y, TomAtlas::ui_record);
ctx.present(display); ctx.present(display);
SleepRemainingFrameTime(timer, time_source); SleepRemainingFrameTime(timer, time_source);
} }
pointerInput.shutdown();
buttonInput.shutdown();
audioInput.shutdown();
audioOutput.shutdown();
display->shutdown(); display->shutdown();
delete display; delete display;
return 0; return 0;

View File

@ -3,56 +3,67 @@
#include "AudioInput.h" #include "AudioInput.h"
#include "AudioOutput.h" #include "AudioOutput.h"
#include "ButtonInput.h" #include "ButtonInput.h"
#include "FrameBuffer.h" #include "Color.h"
#include "SpriteRasterizer.h" #include "DrawContext.h"
#include "Image.h" #include "PointerInput.h"
#include "SpriteRegion.h"
#include "tom_atlas.h"
#include <algorithm>
#include <cstddef>
#include <iostream>
namespace
{
const uint32_t DefaultMaxRecordMs = 5000u;
const uint32_t SpeakingFrameMs = 90u;
const int32_t TomBottomPadding = 72;
const int32_t ButtonBottomPadding = 16;
const RenderData::SpriteRegion* const SpeakingFrames[] = {
&TomAtlas::tom_say1,
&TomAtlas::tom_say2,
&TomAtlas::tom_say3,
&TomAtlas::tom_say4,
&TomAtlas::tom_say3,
&TomAtlas::tom_say2
};
static const RenderData::SpriteRegion& SelectSpeakingFrame(uint32_t animationMs)
{
const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]);
const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount;
return *SpeakingFrames[frameIndex];
}
}
namespace Game namespace Game
{ {
TomGameApp::TomGameApp( TomGameApp::TomGameApp(
Core::FrameBuffer* frameBuffer, int32_t screenWidth,
Rasterizer::SpriteRasterizer* spriteRasterizer, int32_t screenHeight,
Platform::IAudioInput* audioInput, Platform::IAudioInput* audioInput,
Platform::IAudioOutput* audioOutput, Platform::IAudioOutput* audioOutput,
Platform::IButtonInput* buttonInput) : Platform::IButtonInput* buttonInput,
frameBuffer(frameBuffer), Platform::IPointerInput* pointerInput)
spriteRasterizer(spriteRasterizer), : audioInput(audioInput),
audioInput(audioInput), audioOutput(audioOutput),
audioOutput(audioOutput), buttonInput(buttonInput),
buttonInput(buttonInput), pointerInput(pointerInput),
state(TomGameState::Idle), state(TomGameState::Idle),
screenWidth(frameBuffer ? frameBuffer->get_width() : 0), screenWidth(screenWidth),
screenHeight(frameBuffer ? frameBuffer->get_height() : 0), screenHeight(screenHeight),
tomX(0), buttonX(0),
tomY(0), buttonY(0),
buttonX(0), audioSampleRate(16000),
buttonY(0), audioChannels(1),
audioSampleRate(16000), maxRecordMs(DefaultMaxRecordMs),
audioChannels(1), recordingElapsedMs(0),
pitchFactor(1.45f), speakingAnimationMs(0),
outputGain(1.15f) pitchFactor(1.45f),
outputGain(1.15f)
{ {
recorder.configure(audioSampleRate, audioChannels); configure_audio(audioSampleRate, audioChannels);
} calculate_button_layout();
void TomGameApp::set_assets(const TomGameAssets& assets)
{
this->assets = assets;
speakingFrames.clear();
if (assets.tomSay1 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay1));
if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2));
if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3));
if (assets.tomSay4 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay4));
if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3));
if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2));
speakingAnimator.set_frames(speakingFrames);
speakingAnimator.set_frame_time(0.08f);
speakingAnimator.set_loop(true);
speakingAnimator.stop();
calculate_layout();
} }
void TomGameApp::configure_audio(uint32_t sampleRate, uint32_t channels) void TomGameApp::configure_audio(uint32_t sampleRate, uint32_t channels)
@ -66,142 +77,142 @@ namespace Game
audioChannels = channels; audioChannels = channels;
} }
recorder.configure(audioSampleRate, audioChannels); recorder.configure(
audioSampleRate,
audioChannels,
static_cast<float>(maxRecordMs) / 1000.0f,
0.0f,
static_cast<float>(maxRecordMs) / 1000.0f,
0.0f);
} }
void TomGameApp::update(float deltaTime, bool buttonPressed) void TomGameApp::update(uint32_t deltaMs)
{ {
bool trigger = buttonPressed; bool recordTrigger = false;
if (buttonInput != nullptr) update_input(recordTrigger);
{
buttonInput->update();
trigger = trigger || buttonInput->was_pressed();
}
switch (state) switch (state)
{ {
case TomGameState::Idle: case TomGameState::Idle:
if (trigger) if (recordTrigger)
{ {
start_recording(); start_recording();
} }
break; break;
case TomGameState::Recording: case TomGameState::Recording:
if (audioInput != nullptr) update_recording(deltaMs);
{
recorder.update(*audioInput, 512);
}
if (trigger || recorder.is_finished())
{
finish_recording();
}
break;
case TomGameState::Processing:
process_voice();
break; break;
case TomGameState::Speaking: case TomGameState::Speaking:
speakingAnimator.update(deltaTime); update_speaking(deltaMs);
if (audioOutput == nullptr)
{
back_to_idle();
break;
}
player.update(*audioOutput, 1024);
if (player.is_finished())
{
back_to_idle();
}
break; break;
} }
} }
void TomGameApp::draw() void TomGameApp::draw(Core::DrawContext& ctx)
{ {
if (spriteRasterizer == nullptr) ctx.clear_color(RenderData::Color(18, 18, 24, 255));
ctx.draw_sprite_region(0, 0, TomAtlas::background);
const RenderData::SpriteRegion* tom = &TomAtlas::tom_stand;
if (state == TomGameState::Recording)
{ {
return; tom = &TomAtlas::tom_listhen;
}
else if (state == TomGameState::Speaking)
{
tom = &SelectSpeakingFrame(speakingAnimationMs);
} }
if (assets.background != nullptr) const int32_t tomX = (screenWidth - tom->width) / 2;
int32_t tomY = screenHeight - tom->height - TomBottomPadding;
if (tomY < 0)
{ {
spriteRasterizer->DrawImage(*assets.background, 0, 0); tomY = 0;
} }
const RenderData::Sprite tomSprite = get_current_tom_sprite(); ctx.draw_sprite_region(tomX, tomY, *tom);
if (tomSprite.is_valid()) ctx.draw_sprite_region(buttonX, buttonY, TomAtlas::ui_record);
{
spriteRasterizer->DrawSprite(tomSprite, tomX, tomY);
}
if (assets.button != nullptr)
{
spriteRasterizer->DrawImage(*assets.button, buttonX, buttonY);
}
} }
bool TomGameApp::is_ready() const void TomGameApp::update_input(bool& recordTrigger)
{ {
return frameBuffer != nullptr && recordTrigger = false;
spriteRasterizer != nullptr &&
assets.background != nullptr &&
assets.tomStand != nullptr &&
assets.tomListen != nullptr &&
assets.button != nullptr &&
speakingFrames.size() == 6;
}
bool TomGameApp::is_button_hit(int32_t x, int32_t y) const if (buttonInput != nullptr)
{
if (assets.button == nullptr)
{ {
return false; buttonInput->update();
recordTrigger = recordTrigger || buttonInput->was_pressed();
} }
return x >= buttonX && if (pointerInput != nullptr)
x < buttonX + assets.button->get_width() &&
y >= buttonY &&
y < buttonY + assets.button->get_height();
}
void TomGameApp::calculate_layout()
{
screenWidth = frameBuffer ? frameBuffer->get_width() : 0;
screenHeight = frameBuffer ? frameBuffer->get_height() : 0;
if (assets.button != nullptr)
{ {
buttonX = (screenWidth - assets.button->get_width()) / 2; pointerInput->update();
buttonY = screenHeight - assets.button->get_height() - 16; if (pointerInput->was_pressed() &&
} is_record_button_hit(pointerInput->get_x(), pointerInput->get_y()))
const RenderData::Sprite tomSprite = get_current_tom_sprite();
if (tomSprite.is_valid())
{
tomX = (screenWidth - tomSprite.width) / 2;
tomY = screenHeight - tomSprite.height - 72;
if (tomY < 0)
{ {
tomY = 0; recordTrigger = true;
} }
} }
} }
void TomGameApp::start_recording() void TomGameApp::start_recording()
{ {
if (audioInput != nullptr && !audioInput->is_open()) if (audioInput == nullptr)
{ {
audioInput->init("default", audioSampleRate, audioChannels); std::cerr << "[WARN] Audio input is unavailable." << std::endl;
return;
} }
if (audioOutput != nullptr && audioOutput->is_open())
{
audioOutput->shutdown();
}
if (!audioInput->is_open() &&
!audioInput->init("default", audioSampleRate, audioChannels))
{
std::cerr << "[WARN] Audio input init failed." << std::endl;
return;
}
audioSampleRate = audioInput->get_sample_rate();
audioChannels = audioInput->get_channels();
player.stop(); player.stop();
recorder.configure(audioSampleRate, audioChannels); recorder.configure(
audioSampleRate,
audioChannels,
static_cast<float>(maxRecordMs) / 1000.0f,
0.0f,
static_cast<float>(maxRecordMs) / 1000.0f,
0.0f);
recorder.start(); recorder.start();
recordingElapsedMs = 0;
speakingAnimationMs = 0;
state = TomGameState::Recording; state = TomGameState::Recording;
calculate_layout(); }
void TomGameApp::update_recording(uint32_t deltaMs)
{
recordingElapsedMs += deltaMs;
if (audioInput != nullptr)
{
uint32_t requestCount = (audioSampleRate * audioChannels * deltaMs) / 1000u;
if (requestCount < audioChannels)
{
requestCount = audioChannels;
}
requestCount -= requestCount % audioChannels;
recorder.update(*audioInput, static_cast<int>(std::min<uint32_t>(requestCount, 2048u)));
}
if (recordingElapsedMs >= maxRecordMs || recorder.is_finished())
{
finish_recording();
}
} }
void TomGameApp::finish_recording() void TomGameApp::finish_recording()
@ -211,11 +222,11 @@ namespace Game
recorder.stop(); recorder.stop();
} }
state = TomGameState::Processing; if (audioInput != nullptr && audioInput->is_open())
} {
audioInput->shutdown();
}
void TomGameApp::process_voice()
{
std::vector<int16_t> samples = recorder.get_samples(); std::vector<int16_t> samples = recorder.get_samples();
samples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels); samples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels);
samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels); samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels);
@ -226,7 +237,7 @@ namespace Game
void TomGameApp::start_speaking(const std::vector<int16_t>& samples) void TomGameApp::start_speaking(const std::vector<int16_t>& samples)
{ {
if (samples.empty()) if (samples.empty() || audioOutput == nullptr)
{ {
back_to_idle(); back_to_idle();
return; return;
@ -234,47 +245,46 @@ namespace Game
player.set_voice(samples, audioSampleRate, audioChannels); player.set_voice(samples, audioSampleRate, audioChannels);
player.play(); player.play();
speakingAnimationMs = 0;
speakingAnimator.reset();
speakingAnimator.play();
state = TomGameState::Speaking; state = TomGameState::Speaking;
calculate_layout(); }
void TomGameApp::update_speaking(uint32_t deltaMs)
{
speakingAnimationMs += deltaMs;
if (audioOutput == nullptr)
{
back_to_idle();
return;
}
player.update(*audioOutput, 1024);
if (player.is_finished())
{
back_to_idle();
}
} }
void TomGameApp::back_to_idle() void TomGameApp::back_to_idle()
{ {
player.stop(); player.stop();
speakingAnimator.stop(); recordingElapsedMs = 0;
speakingAnimationMs = 0;
state = TomGameState::Idle; state = TomGameState::Idle;
calculate_layout();
} }
void TomGameApp::draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y) void TomGameApp::calculate_button_layout()
{ {
if (spriteRasterizer == nullptr || !sprite.is_valid()) buttonX = (screenWidth - TomAtlas::ui_record.width) / 2;
{ buttonY = screenHeight - TomAtlas::ui_record.height - ButtonBottomPadding;
return;
}
spriteRasterizer->DrawSprite(sprite, centerX - sprite.width / 2, y);
} }
const RenderData::Sprite TomGameApp::get_current_tom_sprite() const bool TomGameApp::is_record_button_hit(int32_t x, int32_t y) const
{ {
if (state == TomGameState::Recording && assets.tomListen != nullptr) return x >= buttonX &&
{ x < buttonX + TomAtlas::ui_record.width &&
return RenderData::Sprite(assets.tomListen); y >= buttonY &&
} y < buttonY + TomAtlas::ui_record.height;
if (state == TomGameState::Speaking)
{
const RenderData::Sprite* sprite = speakingAnimator.get_current_sprite();
if (sprite != nullptr)
{
return *sprite;
}
}
return RenderData::Sprite(assets.tomStand);
} }
} }

View File

@ -1,24 +1,13 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "../audio/VoicePlayer.h" #include "../audio/VoicePlayer.h"
#include "../audio/VoiceRecorder.h" #include "../audio/VoiceRecorder.h"
#include "../components/SpriteAnimator.h"
#include "Sprite.h"
namespace Core namespace Core
{ {
class FrameBuffer; class DrawContext;
}
namespace Rasterizer
{
class SpriteRasterizer;
}
namespace RenderData
{
class Image;
} }
namespace Platform namespace Platform
@ -26,6 +15,7 @@ namespace Platform
class IAudioInput; class IAudioInput;
class IAudioOutput; class IAudioOutput;
class IButtonInput; class IButtonInput;
class IPointerInput;
} }
namespace Game namespace Game
@ -34,85 +24,58 @@ namespace Game
{ {
Idle, Idle,
Recording, Recording,
Processing,
Speaking Speaking
}; };
struct TomGameAssets
{
const RenderData::Image* background;
const RenderData::Image* tomStand;
const RenderData::Image* tomListen;
const RenderData::Image* tomSay1;
const RenderData::Image* tomSay2;
const RenderData::Image* tomSay3;
const RenderData::Image* tomSay4;
const RenderData::Image* button;
TomGameAssets() :
background(nullptr),
tomStand(nullptr),
tomListen(nullptr),
tomSay1(nullptr),
tomSay2(nullptr),
tomSay3(nullptr),
tomSay4(nullptr),
button(nullptr)
{}
};
class TomGameApp class TomGameApp
{ {
private: private:
Core::FrameBuffer* frameBuffer;
Rasterizer::SpriteRasterizer* spriteRasterizer;
Platform::IAudioInput* audioInput; Platform::IAudioInput* audioInput;
Platform::IAudioOutput* audioOutput; Platform::IAudioOutput* audioOutput;
Platform::IButtonInput* buttonInput; Platform::IButtonInput* buttonInput;
Platform::IPointerInput* pointerInput;
TomGameAssets assets;
TomGameState state; TomGameState state;
VoiceRecorder recorder; VoiceRecorder recorder;
VoicePlayer player; VoicePlayer player;
SpriteAnimator speakingAnimator;
std::vector<RenderData::Sprite> speakingFrames;
int32_t screenWidth; int32_t screenWidth;
int32_t screenHeight; int32_t screenHeight;
int32_t tomX;
int32_t tomY;
int32_t buttonX; int32_t buttonX;
int32_t buttonY; int32_t buttonY;
uint32_t audioSampleRate; uint32_t audioSampleRate;
uint32_t audioChannels; uint32_t audioChannels;
uint32_t maxRecordMs;
uint32_t recordingElapsedMs;
uint32_t speakingAnimationMs;
float pitchFactor; float pitchFactor;
float outputGain; float outputGain;
void calculate_layout(); void update_input(bool& recordTrigger);
void start_recording(); void start_recording();
void update_recording(uint32_t deltaMs);
void finish_recording(); void finish_recording();
void process_voice();
void start_speaking(const std::vector<int16_t>& samples); void start_speaking(const std::vector<int16_t>& samples);
void update_speaking(uint32_t deltaMs);
void back_to_idle(); void back_to_idle();
void draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y);
const RenderData::Sprite get_current_tom_sprite() const; void calculate_button_layout();
bool is_record_button_hit(int32_t x, int32_t y) const;
public: public:
TomGameApp( TomGameApp(
Core::FrameBuffer* frameBuffer, int32_t screenWidth,
Rasterizer::SpriteRasterizer* spriteRasterizer, int32_t screenHeight,
Platform::IAudioInput* audioInput, Platform::IAudioInput* audioInput,
Platform::IAudioOutput* audioOutput, Platform::IAudioOutput* audioOutput,
Platform::IButtonInput* buttonInput = nullptr); Platform::IButtonInput* buttonInput,
Platform::IPointerInput* pointerInput);
void set_assets(const TomGameAssets& assets);
void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1); void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1);
void update(float deltaTime, bool buttonPressed = false); void update(uint32_t deltaMs);
void draw(); void draw(Core::DrawContext& ctx);
bool is_ready() const;
bool is_button_hit(int32_t x, int32_t y) const;
TomGameState get_state() const { return state; } TomGameState get_state() const { return state; }
float get_record_volume() const { return recorder.get_last_volume(); } float get_record_volume() const { return recorder.get_last_volume(); }
float get_play_progress() const { return player.get_progress(); } float get_play_progress() const { return player.get_progress(); }

View File

@ -4,10 +4,12 @@
#include "AlsaAudioInput.h" #include "AlsaAudioInput.h"
#include "AlsaAudioOutput.h" #include "AlsaAudioOutput.h"
#include "EvdevButtonInput.h" #include "EvdevButtonInput.h"
#include "EvdevTouchInput.h"
#else #else
#include "SdlAudioInput.h" #include "SdlAudioInput.h"
#include "SdlAudioOutput.h" #include "SdlAudioOutput.h"
#include "SdlKeyboardButtonInput.h" #include "SdlKeyboardButtonInput.h"
#include "SdlPointerInput.h"
#endif #endif
namespace Platform namespace Platform
@ -16,9 +18,11 @@ namespace Platform
typedef AlsaAudioInput DefaultAudioInput; typedef AlsaAudioInput DefaultAudioInput;
typedef AlsaAudioOutput DefaultAudioOutput; typedef AlsaAudioOutput DefaultAudioOutput;
typedef EvdevButtonInput DefaultButtonInput; typedef EvdevButtonInput DefaultButtonInput;
typedef EvdevTouchInput DefaultPointerInput;
#else #else
typedef SdlAudioInput DefaultAudioInput; typedef SdlAudioInput DefaultAudioInput;
typedef SdlAudioOutput DefaultAudioOutput; typedef SdlAudioOutput DefaultAudioOutput;
typedef SdlKeyboardButtonInput DefaultButtonInput; typedef SdlKeyboardButtonInput DefaultButtonInput;
typedef SdlPointerInput DefaultPointerInput;
#endif #endif
} }

View File

@ -0,0 +1,205 @@
#include "EvdevTouchInput.h"
#include <algorithm>
#include <iostream>
#if defined(__linux__)
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <unistd.h>
#endif
namespace Platform
{
EvdevTouchInput::EvdevTouchInput()
: device_path_("/dev/input/event1"),
screen_width_(0),
screen_height_(0),
x_(0),
y_(0),
raw_x_(0),
raw_y_(0),
abs_x_min_(0),
abs_x_max_(0),
abs_y_min_(0),
abs_y_max_(0),
opened_(false),
down_(false),
pressed_(false),
released_(false),
has_abs_x_range_(false),
has_abs_y_range_(false)
#if defined(__linux__)
,
fd_(-1)
#endif
{
}
EvdevTouchInput::~EvdevTouchInput()
{
shutdown();
}
bool EvdevTouchInput::init(
const std::string& device_path,
int32_t screen_width,
int32_t screen_height)
{
shutdown();
device_path_ = device_path.empty() ? "/dev/input/event1" : device_path;
screen_width_ = screen_width;
screen_height_ = screen_height;
x_ = 0;
y_ = 0;
raw_x_ = 0;
raw_y_ = 0;
down_ = false;
pressed_ = false;
released_ = false;
has_abs_x_range_ = false;
has_abs_y_range_ = false;
#if defined(__linux__)
fd_ = open(device_path_.c_str(), O_RDONLY | O_NONBLOCK);
if (fd_ < 0)
{
std::cerr << "EvdevTouchInput open failed: " << device_path_ << std::endl;
return false;
}
input_absinfo abs_x;
if (ioctl(fd_, EVIOCGABS(ABS_X), &abs_x) == 0 ||
ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_X), &abs_x) == 0)
{
abs_x_min_ = abs_x.minimum;
abs_x_max_ = abs_x.maximum;
has_abs_x_range_ = abs_x_max_ > abs_x_min_;
}
input_absinfo abs_y;
if (ioctl(fd_, EVIOCGABS(ABS_Y), &abs_y) == 0 ||
ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_Y), &abs_y) == 0)
{
abs_y_min_ = abs_y.minimum;
abs_y_max_ = abs_y.maximum;
has_abs_y_range_ = abs_y_max_ > abs_y_min_;
}
opened_ = true;
return true;
#else
std::cerr << "EvdevTouchInput is only implemented on Linux evdev." << std::endl;
return false;
#endif
}
void EvdevTouchInput::update()
{
pressed_ = false;
released_ = false;
if (!opened_)
{
return;
}
#if defined(__linux__)
input_event event;
while (true)
{
const ssize_t bytes = read(fd_, &event, sizeof(event));
if (bytes == static_cast<ssize_t>(sizeof(event)))
{
if (event.type == EV_ABS)
{
if (event.code == ABS_X || event.code == ABS_MT_POSITION_X)
{
raw_x_ = event.value;
x_ = map_x(raw_x_);
}
else if (event.code == ABS_Y || event.code == ABS_MT_POSITION_Y)
{
raw_y_ = event.value;
y_ = map_y(raw_y_);
}
else if (event.code == ABS_MT_TRACKING_ID)
{
set_down(event.value >= 0);
}
}
else if (event.type == EV_KEY)
{
if (event.code == BTN_TOUCH || event.code == BTN_LEFT)
{
set_down(event.value != 0);
}
}
}
else
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
std::cerr << "EvdevTouchInput read failed." << std::endl;
}
break;
}
}
#endif
}
void EvdevTouchInput::shutdown()
{
#if defined(__linux__)
if (fd_ >= 0)
{
close(fd_);
fd_ = -1;
}
#endif
opened_ = false;
down_ = false;
pressed_ = false;
released_ = false;
}
int32_t EvdevTouchInput::map_x(int32_t raw_x) const
{
if (screen_width_ <= 0 || !has_abs_x_range_)
{
return raw_x;
}
const int32_t clamped = std::max(abs_x_min_, std::min(raw_x, abs_x_max_));
const int64_t numerator = static_cast<int64_t>(clamped - abs_x_min_) * static_cast<int64_t>(screen_width_ - 1);
return static_cast<int32_t>(numerator / static_cast<int64_t>(abs_x_max_ - abs_x_min_));
}
int32_t EvdevTouchInput::map_y(int32_t raw_y) const
{
if (screen_height_ <= 0 || !has_abs_y_range_)
{
return raw_y;
}
const int32_t clamped = std::max(abs_y_min_, std::min(raw_y, abs_y_max_));
const int64_t numerator = static_cast<int64_t>(clamped - abs_y_min_) * static_cast<int64_t>(screen_height_ - 1);
return static_cast<int32_t>(numerator / static_cast<int64_t>(abs_y_max_ - abs_y_min_));
}
void EvdevTouchInput::set_down(bool down)
{
if (down && !down_)
{
pressed_ = true;
}
else if (!down && down_)
{
released_ = true;
}
down_ = down;
}
}

View File

@ -0,0 +1,54 @@
#pragma once
#include "PointerInput.h"
namespace Platform
{
class EvdevTouchInput : public IPointerInput
{
private:
std::string device_path_;
int32_t screen_width_;
int32_t screen_height_;
int32_t x_;
int32_t y_;
int32_t raw_x_;
int32_t raw_y_;
int32_t abs_x_min_;
int32_t abs_x_max_;
int32_t abs_y_min_;
int32_t abs_y_max_;
bool opened_;
bool down_;
bool pressed_;
bool released_;
bool has_abs_x_range_;
bool has_abs_y_range_;
#if defined(__linux__)
int fd_;
#endif
int32_t map_x(int32_t raw_x) const;
int32_t map_y(int32_t raw_y) const;
void set_down(bool down);
public:
EvdevTouchInput();
~EvdevTouchInput();
bool init(
const std::string& device_path = "/dev/input/event1",
int32_t screen_width = 0,
int32_t screen_height = 0) override;
void update() override;
void shutdown() override;
bool is_open() const override { return opened_; }
bool is_down() const override { return down_; }
bool was_pressed() const override { return pressed_; }
bool was_released() const override { return released_; }
int32_t get_x() const override { return x_; }
int32_t get_y() const override { return y_; }
const std::string& get_device_path() const override { return device_path_; }
};
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <cstdint>
#include <string>
namespace Platform
{
class IPointerInput
{
public:
virtual ~IPointerInput() {}
virtual bool init(
const std::string& device_path = "",
int32_t screen_width = 0,
int32_t screen_height = 0) = 0;
virtual void update() = 0;
virtual void shutdown() = 0;
virtual bool is_open() const = 0;
virtual bool is_down() const = 0;
virtual bool was_pressed() const = 0;
virtual bool was_released() const = 0;
virtual int32_t get_x() const = 0;
virtual int32_t get_y() const = 0;
virtual const std::string& get_device_path() const = 0;
};
}

View File

@ -0,0 +1,109 @@
#include "SdlPointerInput.h"
#include <algorithm>
#include <iostream>
namespace
{
static bool EnsureSdlEvents()
{
if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0)
{
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
return false;
}
if ((SDL_WasInit(SDL_INIT_EVENTS) & SDL_INIT_EVENTS) == 0 &&
SDL_InitSubSystem(SDL_INIT_EVENTS) < 0)
{
std::cerr << "SDL events init failed: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
}
namespace Platform
{
SdlPointerInput::SdlPointerInput()
: device_path_("mouse"),
screen_width_(0),
screen_height_(0),
x_(0),
y_(0),
opened_(false),
down_(false),
pressed_(false),
released_(false)
{
}
SdlPointerInput::~SdlPointerInput()
{
shutdown();
}
bool SdlPointerInput::init(
const std::string& device_path,
int32_t screen_width,
int32_t screen_height)
{
if (!EnsureSdlEvents())
{
return false;
}
device_path_ = device_path.empty() ? "mouse" : device_path;
screen_width_ = screen_width;
screen_height_ = screen_height;
x_ = 0;
y_ = 0;
opened_ = true;
down_ = false;
pressed_ = false;
released_ = false;
return true;
}
void SdlPointerInput::update()
{
pressed_ = false;
released_ = false;
if (!opened_)
{
return;
}
SDL_PumpEvents();
int mouse_x = 0;
int mouse_y = 0;
const Uint32 state = SDL_GetMouseState(&mouse_x, &mouse_y);
if (screen_width_ > 0)
{
mouse_x = std::max(0, std::min(mouse_x, screen_width_ - 1));
}
if (screen_height_ > 0)
{
mouse_y = std::max(0, std::min(mouse_y, screen_height_ - 1));
}
x_ = mouse_x;
y_ = mouse_y;
const bool current_down = (state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
pressed_ = current_down && !down_;
released_ = !current_down && down_;
down_ = current_down;
}
void SdlPointerInput::shutdown()
{
opened_ = false;
down_ = false;
pressed_ = false;
released_ = false;
}
}

View File

@ -0,0 +1,40 @@
#pragma once
#include "PointerInput.h"
#include <SDL.h>
namespace Platform
{
class SdlPointerInput : public IPointerInput
{
private:
std::string device_path_;
int32_t screen_width_;
int32_t screen_height_;
int32_t x_;
int32_t y_;
bool opened_;
bool down_;
bool pressed_;
bool released_;
public:
SdlPointerInput();
~SdlPointerInput();
bool init(
const std::string& device_path = "mouse",
int32_t screen_width = 0,
int32_t screen_height = 0) override;
void update() override;
void shutdown() override;
bool is_open() const override { return opened_; }
bool is_down() const override { return down_; }
bool was_pressed() const override { return pressed_; }
bool was_released() const override { return released_; }
int32_t get_x() const override { return x_; }
int32_t get_y() const override { return y_; }
const std::string& get_device_path() const override { return device_path_; }
};
}