added Timer feature
This commit is contained in:
parent
6511994a53
commit
ad1a178320
10
README.md
10
README.md
|
@ -191,6 +191,16 @@ Useful text manipulation:
|
||||||
|
|
||||||
Changing the font style assumes a tile width of 32 and the same symbol indexes! It also resets the font color and map so call this before doing anything else.
|
Changing the font style assumes a tile width of 32 and the same symbol indexes! It also resets the font color and map so call this before doing anything else.
|
||||||
|
|
||||||
|
#### Utilities
|
||||||
|
|
||||||
|
There's a game `Timer` class available using `engine->getTimer()`. It counts miliseconds, seconds, minutes and hours.
|
||||||
|
Do not forget this is an estimate as modulo and divide operations are expensive.
|
||||||
|
|
||||||
|
Each VBlank occurs every 280806 cycles (`CYCLES_PER_BLANK`), each cycle is 59.59ns
|
||||||
|
So, each VBlank occurs every 16.73322954 miliseconds or 16733.22954 microseconds.
|
||||||
|
The microseconds after the comma are rounded so irregularities are bound to occur after hours of timing..
|
||||||
|
|
||||||
|
|
||||||
#### Error logging
|
#### Error logging
|
||||||
|
|
||||||
The text stream is also used if something goes wrong, there's a macro `failure_gba(WHOOPS)` that prints file, line, method and "exception" message to the text stream background.
|
The text stream is also used if something goes wrong, there's a macro `failure_gba(WHOOPS)` that prints file, line, method and "exception" message to the text stream background.
|
||||||
|
|
|
@ -43,10 +43,19 @@ void SampleStartScene::load() {
|
||||||
.buildPtr();
|
.buildPtr();
|
||||||
|
|
||||||
TextStream::instance().setText("PRESS START", 3, 8);
|
TextStream::instance().setText("PRESS START", 3, 8);
|
||||||
|
|
||||||
|
engine->getTimer()->start();
|
||||||
engine->enqueueMusic(zelda_music_16K_mono, zelda_music_16K_mono_bytes);
|
engine->enqueueMusic(zelda_music_16K_mono, zelda_music_16K_mono_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SampleStartScene::tick(u16 keys) {
|
void SampleStartScene::tick(u16 keys) {
|
||||||
|
TextStream::instance().setText(engine->getTimer()->to_string(), 18, 1);
|
||||||
|
|
||||||
|
if(pressingAorB && !((keys & KEY_A) || (keys & KEY_B))) {
|
||||||
|
engine->getTimer()->toggle();
|
||||||
|
pressingAorB = false;
|
||||||
|
}
|
||||||
|
|
||||||
if(keys & KEY_START) {
|
if(keys & KEY_START) {
|
||||||
if(!engine->isTransitioning()) {
|
if(!engine->isTransitioning()) {
|
||||||
engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes);
|
engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes);
|
||||||
|
@ -63,5 +72,7 @@ void SampleStartScene::tick(u16 keys) {
|
||||||
animation->flipVertically(true);
|
animation->flipVertically(true);
|
||||||
} else if(keys & KEY_DOWN) {
|
} else if(keys & KEY_DOWN) {
|
||||||
animation->flipVertically(false);
|
animation->flipVertically(false);
|
||||||
|
} else if((keys & KEY_A) || (keys & KEY_B)) {
|
||||||
|
pressingAorB = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ private:
|
||||||
std::unique_ptr<Sprite> animation;
|
std::unique_ptr<Sprite> animation;
|
||||||
std::unique_ptr<Sprite> finalFantasyGuy;
|
std::unique_ptr<Sprite> finalFantasyGuy;
|
||||||
std::unique_ptr<Sprite> smiley;
|
std::unique_ptr<Sprite> smiley;
|
||||||
|
bool pressingAorB = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::vector<Sprite *> sprites() override;
|
std::vector<Sprite *> sprites() override;
|
||||||
|
|
|
@ -18,7 +18,7 @@ add_library(${PROJECT_NAME}
|
||||||
src/background/text_stream.cpp
|
src/background/text_stream.cpp
|
||||||
src/background/background.cpp
|
src/background/background.cpp
|
||||||
src/effects/fade_out_scene.cpp
|
src/effects/fade_out_scene.cpp
|
||||||
src/sound_control.cpp src/scene.cpp)
|
src/sound_control.cpp src/scene.cpp src/timer.cpp)
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <libgba-sprite-engine/effects/scene_effect.h>
|
#include <libgba-sprite-engine/effects/scene_effect.h>
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "sound_control.h"
|
#include "sound_control.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
#define GBA_SCREEN_WIDTH 240
|
#define GBA_SCREEN_WIDTH 240
|
||||||
#define GBA_SCREEN_HEIGHT 160
|
#define GBA_SCREEN_HEIGHT 160
|
||||||
|
@ -26,6 +27,7 @@ private:
|
||||||
bool disableTextBg;
|
bool disableTextBg;
|
||||||
SpriteManager spriteManager;
|
SpriteManager spriteManager;
|
||||||
|
|
||||||
|
static std::unique_ptr<Timer> timer;
|
||||||
static std::unique_ptr<SoundControl> activeChannelA;
|
static std::unique_ptr<SoundControl> activeChannelA;
|
||||||
static std::unique_ptr<SoundControl> activeChannelB;
|
static std::unique_ptr<SoundControl> activeChannelB;
|
||||||
|
|
||||||
|
@ -40,6 +42,7 @@ private:
|
||||||
public:
|
public:
|
||||||
GBAEngine();
|
GBAEngine();
|
||||||
|
|
||||||
|
Timer* getTimer();
|
||||||
void setScene(Scene* scene);
|
void setScene(Scene* scene);
|
||||||
void dynamicallyAddSprite(Sprite* s) { spriteManager.add(s); }
|
void dynamicallyAddSprite(Sprite* s) { spriteManager.add(s); }
|
||||||
void transitionIntoScene(Scene* scene, SceneEffect* effect);
|
void transitionIntoScene(Scene* scene, SceneEffect* effect);
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Created by Wouter Groeneveld on 06/12/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GBA_SPRITE_ENGINE_PROJECT_TIMER_H
|
||||||
|
#define GBA_SPRITE_ENGINE_PROJECT_TIMER_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
class Timer {
|
||||||
|
private:
|
||||||
|
int microsecs, msecs, secs, minutes, hours;
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Timer() : active(false) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
void start();
|
||||||
|
void toggle() {
|
||||||
|
if(isActive()) stop();
|
||||||
|
else start();
|
||||||
|
}
|
||||||
|
bool isActive() { return active; }
|
||||||
|
void stop();
|
||||||
|
void onvblank();
|
||||||
|
|
||||||
|
std::string to_string();
|
||||||
|
|
||||||
|
int getMsecs() { return msecs; }
|
||||||
|
int getSecs() { return secs; }
|
||||||
|
int getMinutes() { return minutes; }
|
||||||
|
int getHours() { return hours; }
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, Timer& timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //GBA_SPRITE_ENGINE_PROJECT_TIMER_H
|
|
@ -9,12 +9,17 @@
|
||||||
|
|
||||||
std::unique_ptr<SoundControl> GBAEngine::activeChannelA;
|
std::unique_ptr<SoundControl> GBAEngine::activeChannelA;
|
||||||
std::unique_ptr<SoundControl> GBAEngine::activeChannelB;
|
std::unique_ptr<SoundControl> GBAEngine::activeChannelB;
|
||||||
|
std::unique_ptr<Timer> GBAEngine::timer;
|
||||||
|
|
||||||
void GBAEngine::vsync() {
|
void GBAEngine::vsync() {
|
||||||
while (REG_VCOUNT >= 160);
|
while (REG_VCOUNT >= 160);
|
||||||
while (REG_VCOUNT < 160);
|
while (REG_VCOUNT < 160);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer* GBAEngine::getTimer() {
|
||||||
|
return GBAEngine::timer.get();
|
||||||
|
}
|
||||||
|
|
||||||
void GBAEngine::onVBlank() {
|
void GBAEngine::onVBlank() {
|
||||||
// WARNING this is a very dangerous piece of code.
|
// WARNING this is a very dangerous piece of code.
|
||||||
// GBA IRQs seem eager to crash or eat up CPU. Get in, disable stuff, work, enable, get out!
|
// GBA IRQs seem eager to crash or eat up CPU. Get in, disable stuff, work, enable, get out!
|
||||||
|
@ -23,6 +28,8 @@ void GBAEngine::onVBlank() {
|
||||||
unsigned short tempInterruptState = REG_IF;
|
unsigned short tempInterruptState = REG_IF;
|
||||||
|
|
||||||
if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) {
|
if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) {
|
||||||
|
GBAEngine::timer->onvblank();
|
||||||
|
|
||||||
if(GBAEngine::activeChannelA) {
|
if(GBAEngine::activeChannelA) {
|
||||||
if(GBAEngine::activeChannelA->done()) {
|
if(GBAEngine::activeChannelA->done()) {
|
||||||
GBAEngine::activeChannelA->reset();
|
GBAEngine::activeChannelA->reset();
|
||||||
|
@ -91,6 +98,7 @@ void GBAEngine::enqueueSound(const s8 *data, int totalSamples, int sampleRate, S
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAEngine::GBAEngine() {
|
GBAEngine::GBAEngine() {
|
||||||
|
GBAEngine::timer = std::unique_ptr<Timer>(new Timer());
|
||||||
// setup screen control flags
|
// setup screen control flags
|
||||||
REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3;
|
REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// Created by Wouter Groeneveld on 06/12/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <libgba-sprite-engine/timer.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
void Timer::onvblank() {
|
||||||
|
if(!active) return;
|
||||||
|
|
||||||
|
// each VBlank occurs every 280806 cycles (CYCLES_PER_BLANK), each cycle is 59.59ns
|
||||||
|
// So, each VBlank occurs every 16.73322954 miliseconds or 16733.22954 microseconds
|
||||||
|
// So, each second is 59,76132686219041 vblanks.
|
||||||
|
// to avoid any big rounding errors, divisions and modulos, estimate!
|
||||||
|
msecs += 16;
|
||||||
|
microsecs += 733;
|
||||||
|
if(microsecs >= 1000) {
|
||||||
|
msecs++;
|
||||||
|
microsecs -= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msecs >= 1000) {
|
||||||
|
secs++;
|
||||||
|
msecs -= 1000;
|
||||||
|
}
|
||||||
|
if(secs >= 60) {
|
||||||
|
minutes++;
|
||||||
|
secs -= 60;
|
||||||
|
}
|
||||||
|
if(minutes >= 60) {
|
||||||
|
hours++;
|
||||||
|
minutes -= 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Timer::to_string() {
|
||||||
|
std::ostringstream stringStream;
|
||||||
|
stringStream << *this;
|
||||||
|
return stringStream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream &os, Timer &timer) {
|
||||||
|
os << timer.hours << "h:" << timer.minutes << "m:" << timer.secs << "s:" << timer.msecs;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::reset() {
|
||||||
|
microsecs = msecs = secs = minutes = hours = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::start() {
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::stop() {
|
||||||
|
active = false;
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ add_executable(unittest
|
||||||
../engine/src/sprites/sprite.cpp
|
../engine/src/sprites/sprite.cpp
|
||||||
../engine/src/allocator.cpp
|
../engine/src/allocator.cpp
|
||||||
../engine/src/palette/combined_palette.cpp
|
../engine/src/palette/combined_palette.cpp
|
||||||
)
|
../engine/src/timer.cpp
|
||||||
|
timertest.cpp)
|
||||||
|
|
||||||
target_link_libraries(unittest ${GTEST_LIBRARY}/build/libgtest.a ${GTEST_LIBRARY}/build/libgtest_main.a)
|
target_link_libraries(unittest ${GTEST_LIBRARY}/build/libgtest.a ${GTEST_LIBRARY}/build/libgtest_main.a)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// Created by Wouter Groeneveld on 06/12/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libgba-sprite-engine/timer.h>
|
||||||
|
|
||||||
|
class TimerSuite : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
Timer *t;
|
||||||
|
|
||||||
|
virtual void TearDown() {
|
||||||
|
delete t;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void SetUp() {
|
||||||
|
t = new Timer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(TimerSuite, OnVBlankIncreasesMsecsAmountOfTimeIfStarted) {
|
||||||
|
t->start();
|
||||||
|
t->onvblank();
|
||||||
|
t->stop();
|
||||||
|
t->onvblank();
|
||||||
|
|
||||||
|
ASSERT_EQ(t->getMsecs(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TimerSuite, OnVBlankAfterEnoughTimesToGetToSec) {
|
||||||
|
t->start();
|
||||||
|
|
||||||
|
for(int i = 0; i < 100; i++) {
|
||||||
|
t->onvblank();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(t->getSecs(), 1);
|
||||||
|
ASSERT_EQ(t->getMsecs(), 673);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TimerSuite, OnVBlankDoesNothingIfNotEnabled) {
|
||||||
|
t->onvblank();
|
||||||
|
|
||||||
|
ASSERT_EQ(t->getMsecs(), 0);
|
||||||
|
}
|
Loading…
Reference in New Issue