SGDK. Охотимся на баги.

Предисловие.

За то время что я работал в 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 — уменьшает фрагментацию памяти. На практике, вы получите больше оперативной памяти в игре.

Пример функции деаллокации:

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();
}

Аллокация и деаллокация во время игры — плохая идея.

Сильно, не рекомендую аллоцировать и деаллоцировать память во время игры (кроме спрайтов), единственное место где это оправдано, это загрузка уровня. Создайте лучше переменную, которая будет постоянно сидеть в 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);

Лучше, данный режим скролла не использовать, т.к. это баг самой приставки.

Заключение.

Данной статьей я хотел вас предостеречь от огромных трат по времени на поиск ошибок в коде. Надеюсь, данная информация вам окажется полезной.

Итоговый результат.