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

Хотя, не советую её использовать. Да, вы получите чуть больше оперативки, но функция 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));

Проблема решена.

Заключение.

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

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

Пожалуйста отключи блокировщик рекламы, или внеси сайт в белый список!

Please disable your adblocker or whitelist this site!