consoledevСтатьи

Первая игра под PSP

Автор:

Прошло две недели, как я купил себе PSP, и вот – первая игра готова. Конечно же это Тетрис. На примере него я покажу, как можно быстро сделать простую игрушку. Для крупных коммерческих проектов этот опыт нисколько не поможет, даже наоборот, окажется вредным, но для пробы пера вполне покатит.

Подготовка к работе.
Для начала надо раздобыть компилятор и SDK. Я пользовался набором с www.devkitpro.org (компилятор gcc и собранные библиотеки PSP homebrew SDK) и svn://svn.ps2dev.org/psp/ (отсюда я взял psp-порт MikMod).
Для запуска кода потребуется PSP с прошивкой 1.5 и программа KXploit (которая патчит готовый EBOOT.PBP) либо No-KXploit (резидент для PSP, с ним патчить не придется).
Makefile для сборки проекта берем из любого примера к SDK, и вписываем необходимые нам данные:

# данные для браузера PSP
# иконка игры, PNG 144x80
PSP_EBOOT_ICON = ICON0.PNG
# фоновая картинка, PNG 480x272
PSP_EBOOT_PIC1 = PIC1.PNG
# список объектников, требуемых для компиляции
OBJS =  main.o font.o sound.o savegame.o game.o graphic.o
# список необходимых библиотек
LIBS = -lpspgu -lmikmod -lmmio -lpspaudiolib -lpspaudio

Главный модуль программы.
В главном модуле надо вписать служебную информацию:

// имя программы, атрибуты, номер версии (major и minor)
PSP_MODULE_INFO("Tetris", 0, 1, 1);
// программа работает в user-mode, использование векторного сопроцессора разрешено
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);

И добавить необходимые обработчики:

// обработчик выхода из программы (вызывается системой по нажатию Home – Yes)
int exit_callback(int arg1, int arg2, void *common);
// регистрация обработчика выхода
int CallbackThread(SceSize args, void *argp);
// создание callback-thread'а, вызывается из функции main
int SetupCallbacks(void);

Возможно эти действия можно упростить, но пока я в них не вникал, а просто передрал из примеров SDK.
В самой функции main я вызываю SetupCallbacks, инициализирую графику, звук и ввод, загружаю необходимые ресурсы и вхожу в бесконечный игровой цикл. Выход из программы осуществляется, как это принято, нажатием кнопки Home.

Ввод.
Инициализуется ввод вызовом двух функций :

// устанавливаем частоту опроса состояния
sceCtrlSetSamplingCycle(0);
// выбираем тип опроса – цифровой или аналоговый
sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

Получать данные от контроллера можно двумя способами – непосредственным или буферизованным.

// непосредственный опрос состояния (аналог - GetDeviceState в DirectInput)
int sceCtrlReadBufferPositive(SceCtrlData *pad_data, int count);
// чтение данных из буфера ввода (аналог - GetDeviceData в DirectInput)
int sceCtrlReadLatch(SceCtrlLatch *latch_data);

Я использовал буферизованный ввод.

Загрузка-восстановление.
Нам это потребуется, чтобы иметь постоянную таблицу рекордов. Все сохраненные данные (а также дополнительный контент, аддоны) для игр на PSP хранятся в папке PSP/SAVEDATA на memory stick для того, чтобы из браузера PSP можно было просмотреть список сохраненок и, при желании удалить. Для работы с сохраненками используются функции sceUtilitySavedata* и структура SceUtilitySavedataParam. Код для работы с этим всем находится в файле savegame.cpp, опять же, по большей части, подранным из примеров PSP SDK. Разобраться в нем труда не составит.

Звук.
Со звуком я разбираться пока не стал, вместо этого прикрутил MikMod. Для коммерческого проекта это, конечно, ни в какие ворота не лезет, но в первом проекте мне было лень копаться в премудростях PSP-шного звука. В следующем проекте я это исправлю.

Графика.
Графический API весьма похож на OpenGL (а еще больше на Glide, царство ему небесное), так что разобраться с ним труда не составит. Хотя, конечно, чтобы эффективно его использовать, придется долго учиться.
Инициализацию графики и действия выполняемые в начале и конце каждого кадра можно посмотреть в graphic.cpp, вывод текста – в font.cpp, а загрузку текстуры с палитрой и отрисовку спрайта – в game.cpp.
Быстро пройдемся по основным функциям sceGu*:

// Инициализация графики
sceGuInit();
// Завершение работы с графикой
sceGuTerm();
// Начало работы с новый дисплей-листом
sceGuStart(GU_DIRECT, list); // лист должен быть выровненным на границу 16 байт
// Установка формата пикселей и размера фреймбуфера
// (обычно SCR_WIDTH=480, SCR_HEIGHT=272, BUF_WIDTH=512, PIXEL_SIZE=2 или 4)
sceGuDrawBuffer(GU_PSM_5650, (void*)0, BUF_WIDTH);
// Установка размеров экрана 
// Пусть FRAME_SIZE = BUF_WIDTH * SCR_HEIGHT * PIXEL_SIZE
sceGuDispBuffer(SCR_WIDTH, SCR_HEIGHT, (void*) FRAME_SIZE, BUF_WIDTH); 
// Установка размеров экрана 
sceGuDepthBuffer((void*)(FRAME_SIZE*2), BUF_WIDTH);
// Установка смещения. Пространство рендеринга на PSP – 4096х4096, мы рисуем в центре
sceGuOffset(2048 - (SCR_WIDTH/2), 2048 - (SCR_HEIGHT/2));
// Установка окна - координаты центра и размеры
sceGuViewport(2048, 2048, SCR_WIDTH, SCR_HEIGHT);
// Пространство значений глубины
sceGuDepthRange(65535, 0);
// Закончить текущий дисплей-лист
sceGuFinish();
// Ждать конца отрисовки дисплей-листа
sceGuSync(0, 0);
// Ждать вертикальной синхронизации
sceDisplayWaitVblankStart();
// Поменять местами видимый и рабочий буферы
sceGuSwapBuffers();
// Включить(GU_TRUE) или выключить(GU_FALSE) дисплей
sceGuDisplay(GU_TRUE);
// Выбор формата палитры
sceGuClutMode(GU_PSM_4444, 0, 0xff, 0);
// Загрузка палитры
sceGuClutLoad((PALETTE_ENTRIES/8), pPalette);
// Установка форматы текстуры
sceGuTexMode(GU_PSM_T8, 0, 0, 0);
// Установка текущей текстуры
sceGuTexImage(TEX_LOD, TEX_WIDTH, TEX_HEIGHT, TEXBUF_WIDTH, pTexture);
// Выбор типа фильтрации текстуры (анизотропки нету:)
sceGuTexFilter(GU_LINEAR, GU_LINEAR);
// Отрисовать набор примитивов (индексированных или нет)
sceGuDrawArray(PRIMITIVE_TYPE, VERTEX_FORMAT, VERTEX_COUNT, pIndices, pVertices);

Заключение.
Выкладываю в SVN полные исходники своего Тетриса, своих копирайтов я там не ставил, ибо большая часть кода написана не мной. :) Пользуйтесь ими, как пожелаете.

12 июня 2006 (Обновление: 24 сен 2006)

Комментарии [2]