Предисловие.
За то время что я работал в SGDK, я повстречал немало багов, и, многие меня скорее поймут. Фикс багов — самая разочаровывающая часть программирования. В этой статье я рассмотрю пару не-очевидных багов и их решение.
Какой эмулятор использовать?
Используйте BlastEm эмулятор, т.к. у него лучшая эмуляция железа сеги, среди доступных.
Я изменил структуру и теперь игра сломана.
Удалите папку out в своем SGDK проекте, и скомпилируйте заново.
Ошибка ILLEGAL INSTRUCTION !
Данная ошибка в 99% случаев связана с выходом за границы массива. Разберем реальный пример такого непреднамеренного выхода.
Допустим, у нас есть структура EntityMerged:
typedef struct {
bool alive;
u16 entityType;
} EntityMerged;
где, entityType хранит индекс в массиве.
Вы выделяете память под массив структуры EntityMerged, таким образом.
EntityMerged_arr = MEM_alloc(sizeof(EntityMerged)*20);
Массив EntityMerged_arr хранит мусор, поэтому нужно убедится что, до тех пор пока данный массив не заполнят нормальными данными, он не будет использоваться.
for(u16 i=0; i < 20; i++){
EntityMerged_arr[i].alive = FALSE;
EntityMerged_arr[i].entityType = 0;
}
Проблема решена.
У меня черный экран.
Причины могут быть следующими:
- Вы вошли в бесконечный цикл.
- Не хватает оперативной памяти (RAM)
что-бы узнать количество RAM, используйте эту комманду:
KLog_U1("FreeMem: ", MEM_getFree());
Утечка памяти.
Проверяйте количество RAM, с помощью комманды
KLog_U1("FreeMem: ", MEM_getFree());
Если видите что количество RAM стремительно уменьшается, то двигаемся дальше.
Высвобождайте память указателей, с помощью этой комманды:
MEM_free(curLocalVariables);
Порядок тоже важен, например в этом случае:
MEM_free(curEntityAll->EntityMerged_arr);
MEM_free(curEntityAll);
Здесь, EntityMerged_arr находится внутри curEntityAll, поэтому, нужно сначала высвободить все вложенное (EntityMerged_arr), а уже затем, избавиться от тела (curEntityAll).
И в заключение, вы можете использовать:
MEM_pack();
MEM_pack — уменьшает фрагментацию памяти. На практике, вы получите больше оперативной памяти в игре.
Хотя, не советую её использовать. Да, вы получите чуть больше оперативки, но функция MEM_pack смещает данные (упаковывает) что приводит к сломанным указателям (указателям на неаллоцированную память) а это гарантированный вылет игры. Оно вам надо?
Пример функции деаллокации:
void deallocLevel(){
//Deallocate prev entityData to avoid memory leak
MEM_free(curLocalVariables);
MEM_free(curEntityAll->EntityBulletMerged_arr);
MEM_free(curEntityAll->EntityMerged_arr);
MEM_free(curEntityAll->Trigger_arr);
MEM_free(curEntityAll);
MEM_free(bga);
MEM_free(bgb);
//MEM_pack();
}
При переходе между уровнями игра вылетает.
Причина в использовании MEM_pack, эта функция упаковывает данные, попутно портя указатели. Вы можете использовать её, но убедитесь на 100% что ни 1 указатель не используется.
Аллокация и деаллокация во время игры — плохая идея.
Сильно, не рекомендую аллоцировать и деаллоцировать память во время игры (кроме спрайтов), единственное место где это оправдано, это загрузка уровня. Создайте лучше переменную, которая будет постоянно сидеть в RAM, а лучше, аллоцируйте её во время загрузки уровня, тогда проблем не будет.
ForceRedraw и мусорные тайлы
При использовании MAP_scrollToEx в режиме forceRedraw
if(bga) MAP_scrollToEx(bga, cameraPosition.x, cameraPosition.y, TRUE);
if(bgb) MAP_scrollToEx(bgb, cameraPosition.x, cameraPosition.y, TRUE);
Чаще используйте SYS_doVBlankProcess(), иначе получите мусорные тайлы.
Вот так, данная сцена выглядит после фикса.
SYS_doVBlankProcess();
if(bga) MAP_scrollToEx(bga, cameraPosition.x, cameraPosition.y, TRUE);
SYS_doVBlankProcess();
if(bgb) MAP_scrollToEx(bgb, cameraPosition.x, cameraPosition.y, TRUE);
SYS_doVBlankProcess();
На картинке мусорные тайлы.
Может быть, в вашей картинке слишком много уникальных тайлов, из-за чего, в VRAM сеги попросту не хватает места. Измените картинку и попробуйте заново. Либо, скачайте прогу для уменьшения количества уникальных тайлов, картинка будет пикселизированной, но это лучше чем ничего.
Левая колонка при скролле, ведет себя странно.
Данный баг связан с режимом скролла.
VDP_setScrollingMode(HSCROLL_TILE, VSCROLL_2TILE);
Лучше, данный режим скролла не использовать, т.к. это баг самой приставки.
Вылет игры, из-за спрайта.
В игре у вас есть объект, который меняет анимацию (SPR_setAnim) или кадр (SPR_setFrame) , в зависимости от состояния. И в момент смены анимации, игра вылетает с ошибкой ILLEGAL INSTRUCTION. Причина ошибки в том что ваш Sprite* = NULL, поэтому, перед добавлением делайте проверку.
if(!entity->onScreen) {
if(entity->spr == NULL) {
entity->spr = SPR_addSprite(entity->sprDef, posX_OnCam, posY_OnCam, TILE_ATTR(PAL2, 11, FALSE, FALSE));
}
}
И вместе с SPR_releaseSprite, приравнивайте Sprite* к NULL.
if ((posX_OnCam < -entity->size.x) || (posX_OnCam > 320) || (posY_OnCam < -entity->size.y) || (posY_OnCam > 224)) {
if(entity->onScreen) {
if(entity->spr) {
SPR_releaseSprite(entity->spr);
entity->spr = NULL;
}
entity->onScreen = FALSE;
}
}
Спрайтовый слой отстает от тайлового (BG_A, BG_B).
Причина кроется в неправильном порядке вызова функций. Например так.
showEntityAll();
updatePlayerPos();
updateCameraPos();
- Позиция камеры зависит от позиции игрока
- Позиция любого объекта на уровне (entity) зависит от позиции камеры.
Т.е. в неправильном примере выше, обновление позиции спрайта entity запаздывает на кадр, что ужасно.
А вот, правильный вариант
updatePlayerPos();
updateCameraPos();
showEntityAll();
SPR_update();
SYS_doVBlankProcess();
Физика игрока сломана.
Частая ошибка, связанная с частым копированием кода.
u32 max_pos_x = plr.posInt.x + plr.aabb.max.x;
u32 max_pos_y = plr.posInt.x + plr.aabb.max.x;
Порой, лучше вручную написать код, нежели постоянно копировать его, ловя тонну ошибок.
Использование GENS KMod и BlastEm в связке.
- GENS KMod — не очень хороший эмулятор
- BlastEm — напротив, лучший эмулятор из доступных
Если у вас игра вылетает на BlastEm и не вылетает на GENS Kmod то, скорей всего, вы накосячили с указателями или передачей данных в структуру.
Вылетает табличка No SDL 2 Mapping exists for input guide on gamepad 0 в BlastEm.
Отключите джойстик от компьютера.
Значение переменной по указателю не меняется.
Допустим, вы по указателю решили изменить значение переменной.
curLvlData->controlScript = CUSTOM_SCRIPT_controlGenLevel_0_choose_save;
Но значение не меняется, что делать?
Если оно не меняется, значит ваш указатель, указывает на константу.
curLvlData = LevelFull_arr[levelNum].lvl;
Чтобы это исправить, нужно аллоцировать память под переменную, и перекопировать данные константы (LevelFull_arr[levelNum].lvl), в переменную (curLvlData).
curLvlData = MEM_alloc(sizeof(Level));
memcpy(curLvlData, LevelFull_arr[levelNum].lvl, sizeof(Level));
Проблема решена.
Заключение.
Данной статьей я хотел вас предостеречь от огромных трат по времени на поиск ошибок в коде. Надеюсь, данная информация вам окажется полезной.