welcome to part two of my new tutorial series on programming for the Vita, where we be laying the groundwork for you to develop your own homebrew / port games to the vita! Today we will be doing a controller sample with our beautiful unofficial VitaSDK and vita2dlib.
Requirements:
- Vita with Henkaku
********************************************************************************************************************
- Unofficial VitaSDK and vita2dlib installed on a linux system (see mylast tutorial)
********************************************************************************************************************
- Local Area Network connectivity between Vita and linux system
********************************************************************************************************************
Table of Contents
1. main.c
2. The define preprocessor directive
3. Explanation of control functions in main.c
4. Explanation of game logic in main.c
5. Explanation of shape drawing functions in main.c
6. Installation
1. main.c
Code: Select all
#include <psp2/display.h>
#include <psp2/ctrl.h>
#include <psp2/moduleinfo.h>
#include <psp2/kernel/processmgr.h>
#include <vita2d.h>
#define SCREEN_W 960
#define SCREEN_H 544
#define BLACK RGBA8(0, 0, 0, 255)
#define LIME RGBA8(0, 255, 20, 255)
#define RED RGBA8(255, 0, 0, 255)
#define BLUE RGBA8(0, 0, 255, 255)
#define WHITE RGBA8(255, 255, 255, 255)
PSP2_MODULE_INFO(0, 0, "CtrlSample");
int boundaryXLeft = 0;
int boundaryXRight = SCREEN_W;
int boundaryYUp = 0;
int boundaryYDown = SCREEN_H;
int firstMovingX = 420;
int firstMovingY = SCREEN_H / 2;
int secondMovingX = 520;
int secondMovingY = SCREEN_H / 2;
int main() {
vita2d_pgf *pgf;
vita2d_init();
vita2d_set_clear_color(WHITE);
pgf = vita2d_load_default_pgf();
SceCtrlData pad;
while (1) {
vita2d_start_drawing();
vita2d_clear_screen();
int circle_draw_true;
sceCtrlPeekBufferPositive(0, &pad, 1);
vita2d_pgf_draw_text(pgf, 10, 50, BLACK, 1.0f, "Press cross to draw and move a blue circle!");
if (pad.buttons & SCE_CTRL_CROSS) {
vita2d_clear_screen();
circle_draw_true = 1;
}
if (circle_draw_true == 1){
vita2d_end_drawing();
vita2d_swap_buffers();
sceDisplayWaitVblankStart();
int speed = 3;
while(circle_draw_true == 1){
vita2d_start_drawing();
vita2d_clear_screen();
sceCtrlPeekBufferPositive(0, &pad, 1);
if ((pad.buttons & SCE_CTRL_UP) && (firstMovingY > boundaryYUp)){
firstMovingY -= speed;
}
else if ((pad.buttons & SCE_CTRL_DOWN) && (firstMovingY < boundaryYDown)){
firstMovingY += speed;
}
else if ((pad.buttons & SCE_CTRL_RIGHT) && (firstMovingX < boundaryXRight)){
firstMovingX += speed;
}
else if ((pad.buttons & SCE_CTRL_LEFT) && (firstMovingX > boundaryXLeft)){
firstMovingX -= speed;
}
if(pad.buttons & SCE_CTRL_START){
circle_draw_true = 0;
break;
}
if(pad.buttons & SCE_CTRL_RTRIGGER){
speed++;
}
if(pad.buttons & SCE_CTRL_LTRIGGER){
speed--;
}
if ((pad.buttons & SCE_CTRL_TRIANGLE) && (secondMovingY > boundaryYUp)){
secondMovingY -= speed;
}
else if ((pad.buttons & SCE_CTRL_CROSS) && (secondMovingY < boundaryYDown)){
secondMovingY += speed;
}
else if ((pad.buttons & SCE_CTRL_CIRCLE) && (secondMovingX < boundaryXRight)){
secondMovingX += speed;
}
else if ((pad.buttons & SCE_CTRL_SQUARE) && (secondMovingX > boundaryXLeft)){
secondMovingX -= speed;
}
int diffX = firstMovingX - secondMovingX;
if(diffX < 0){
diffX = secondMovingX - firstMovingX;
}
int diffY = firstMovingY - secondMovingY;
if(diffY < 0){
diffY = secondMovingY - firstMovingY;
}
if((diffX < 100) && (diffY < 100)){vita2d_pgf_draw_text(pgf, 500, 100, RED, 1.0f, "COLLISION!");}
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 100, LIME, 1.0f, "firstMovingX: %d", firstMovingX);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 130, LIME, 1.0f, "firstMovingY: %d", firstMovingY);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 160, LIME, 1.0f, "secondMovingX: %d", secondMovingX);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 190, LIME, 1.0f, "secondMovingY: %d", secondMovingY);
vita2d_pgf_draw_text(pgf, SCREEN_W - 300, SCREEN_H - 20, LIME, 1.0f, "Press start to exit.");
vita2d_draw_fill_circle(firstMovingX, firstMovingY, 50, BLUE);
vita2d_draw_fill_circle(secondMovingX, secondMovingY, 50, LIME);
vita2d_end_drawing();
vita2d_swap_buffers();
}
}
vita2d_end_drawing();
vita2d_swap_buffers();
sceDisplayWaitVblankStart();
}
vita2d_fini();
vita2d_free_pgf(pgf);
sceKernelExitProcess(0);
return 0;
}
So, first of all, we'll be looking at a new kind of C preprocessor directive which will make churning out code a lot easier for you in the future: the define directive!
Let's look at an example of a define directive in our main.c file:
Code: Select all
#define SCREEN_W 960
The basic syntax of a define directive is:
Code: Select all
#define [symbol to replace] [code to replace your symbol with]
Well, how do we read / dictate this kind of directive? Simple: We just say, "replace [symbol] with [symbol]".
The define directive is handled during the preprocessing pass of the compilation process in which our C code is translated to Assembly.
The used assembly language depends on your type of processor: If you have a standard Intel or AMD processor and linux distribution of the same kind, it will be compiled to AT&T / Intel x86 or x64 assembly.
On the other hand, if you have an ARM processor, it will be compiled to assembly with ARM syntax. Finally, if you have a RISC processor such as the Sun UltraSPARC, your compiler will compile our C code to RISC assembly language.
Oh, and by the way, if you've wondered what RISC processors are good for: They come from a different train of thought. Basically, people who prefer and support RISC processors over Intel processors believe that a simplified instruction set (that would mean more assembly language per C instruction) combined with a very fast and efficient microprocessor provide higher performance, hence the title Reduced Instruction Set Computing (RISC).
If you want to know more about how modern languages work and even want to create one yourself, I highly recommend the second part of the nand2tetris course.
3. Explanation of control functions in main.c
Now that we've talked about the #define preprocessor directive, we are going to move on to the control functions present in the C code. But first, you have to understand something basic about input devices:
They have access to some form of read/write memory, usually the Random Access Memory . But why? Because they can interface with the processor that way! The processor has access to the RAM, the input device as well (to a specific range at least) and we can program the processor in C. That way, we can know which key is currently being pressed and evaluate the keypress as well as process it.
A simplified fictional example: The vita controller input is mapped to the RAM range 16000-16002. If a button is pressed, the input device writes the corresponding value (1 for cross, 2 for square, etc.) to the RAM range 16000-16002. If one button is pressed, it writes the value to 16000. If two buttons are pressed at the same time (simultaneously), the device writes one value each to 16000-16002. Now we can access that RAM range in C (actually it is abstracted in C, we'd be closer to the actual values in ARM assembly), see which buttons are being pressed and take appropriate action.
But there is one problem in our fictional example: The input device is only mapped to 16000-16002: It can only register three button presses at the same (exact) time. So, how do we solve that issue? Well, the Vita has 13 input pads, so for all the buttons, we assign RAM addresses and write a one to them if they are on and a zero if they are off.
For the left and right sticks, we just write the value in desired detail (e.g horizontal, vertical and diagonal axis or 360° circumference) and for the touchpad on the back, we can just write two values, namely X and Y, in the form of a tuple (x, y) to memory. With the touchpad on the back, we also have a resolution issue: how wide and high should the touchpad be in memory. Should it be 960 pixels for the width and 544 pixels for the height like the screen of the vita?
That would actually make sense because we can then directly map the touchpad on the back to the screen: if the back touchpad writes 900 (x), 500(y) to RAM, we can then display a dot on the display without having to convert the value. That way, we save processor cycles which would make a program which uses the back touchpad in correspondence with the screen more efficient in terms of processor cycles used.
Now that we know how input devices work on a low level, we can make better sense of the C code used to interface and process the input devices. We'll go through the functions in the order of our code:
********************************************************************************************************************
Code: Select all
#include <psp2/ctrl.h>
********************************************************************************************************************
Code: Select all
SceCtrlData pad;
Code: Select all
SceCtrlData pad;
Code: Select all
enum SceCtrlData pad;
Here, we create a new variable called pad of type SceCtrlData. This type is defined in psp2/ctrl.h.
We will use our pad variable a lot later on to check if a button is pressed.
********************************************************************************************************************
Code: Select all
while (1) {
Also, we need to use a while loop to not only draw our shapes once.
********************************************************************************************************************
Code: Select all
int circle_draw_true;
Because then we'd only draw a circle if the cross button is being actively pressed.
********************************************************************************************************************
Code: Select all
sceCtrlPeekBufferPositive(0, &pad, 1);
1. Port - 0 should be used
2. Pointer pad_data - we give the address (therefore the &) of our pad variable of type SceCtrlData here.
3. Buffers count - just leave this at 1
********************************************************************************************************************
Code: Select all
if (pad.buttons & SCE_CTRL_CROSS) {
Code: Select all
circle_draw_true = 1;
********************************************************************************************************************
Code: Select all
sceCtrlPeekBufferPositive(0, &pad, 1);
********************************************************************************************************************
4. Explanation of game logic in main.c
So, now let's look at the game logic:
Code: Select all
int boundaryXLeft = 0;
int boundaryXRight = SCREEN_W;
int boundaryYUp = 0;
int boundaryYDown = SCREEN_H;
If you're confused about where 0 is and where the maximum pixel is, take a look at this:
********************************************************************************************************************
Code: Select all
int firstMovingX = 420;
int firstMovingY = SCREEN_H / 2;
int secondMovingX = 520;
int secondMovingY = SCREEN_H / 2;
********************************************************************************************************************
Code: Select all
vita2d_pgf_draw_text(pgf, 10, 50, BLACK, 1.0f, "Press cross to draw and move a blue circle!");
if (pad.buttons & SCE_CTRL_CROSS) {
vita2d_clear_screen();
circle_draw_true = 1;
}
And then, if the player presses cross, we assign the value one to the variable circle_draw_true and clear the screen for what is to come.
********************************************************************************************************************
Code: Select all
if (circle_draw_true == 1){
vita2d_end_drawing();
vita2d_swap_buffers();
sceDisplayWaitVblankStart();
int speed = 3;
while(circle_draw_true == 1){
********************************************************************************************************************
Code: Select all
if ((pad.buttons & SCE_CTRL_UP) && (firstMovingY > boundaryYUp)){
firstMovingY -= speed;
}
else if ((pad.buttons & SCE_CTRL_DOWN) && (firstMovingY < boundaryYDown)){
firstMovingY += speed;
}
else if ((pad.buttons & SCE_CTRL_RIGHT) && (firstMovingX < boundaryXRight)){
firstMovingX += speed;
}
else if ((pad.buttons & SCE_CTRL_LEFT) && (firstMovingX > boundaryXLeft)){
firstMovingX -= speed;
}
Read these if statements as:
1. If Up is pressed and firstMovingY is within the boundary of the vertical top, decrease firstMovingY by speed.
1.1 Explanation: When the Up button is pressed, we decrease firstMovingY by speed because as the Y value decreases, it goes closer to the top.
2. If Down is pressed and firstMovingY is within the boundary of the vertical bottom, increase firstMovingY by speed.
2.1 Explanation: When the down button is pressed, we increase firstMovingY by speed because as the Y value increases, it moves further to the bottom.
3. If Right is pressed and firstMovingX is within the horizontal right boundary, increase firstMovingX by speed.
3.1 Explanation: When the right button is pressed, we increase firstMovingX by speed because as the X value increases, it moves further to the right.
4. If Left is pressed and firstMovingX is within the horizontal left boundary, decrease firstMovingX by speed.
4.1 Explanation: When the left button is pressed, we decrease firstMovingX by speed because as the X value decreases, it moves further to the left.
This can also be deduced from this schematic:
********************************************************************************************************************
Code: Select all
if(pad.buttons & SCE_CTRL_START){
circle_draw_true = 0;
break;
}
********************************************************************************************************************
Code: Select all
if(pad.buttons & SCE_CTRL_RTRIGGER){
speed++;
}
if((pad.buttons & SCE_CTRL_LTRIGGER) && (speed > 0)){
speed--;
}
The latter if statement also checks that the speed is greater than 0, because if it was say -2, then we would have a problem with subtracting and adding to the firstMoving and secondMoving values:
If we did firstMovingX -= speed; with a negative speed, we would be adding to firstMovingX because two negatives (-) make a positive.
On the other hand, if we did firstMovingX += speed; with a negative speed, we would be subtracting from firstMovingX because one positive (+) and one negative (-) make a negative (-).
********************************************************************************************************************
Code: Select all
if ((pad.buttons & SCE_CTRL_TRIANGLE) && (secondMovingY > boundaryYUp)){
secondMovingY -= speed;
}
else if ((pad.buttons & SCE_CTRL_CROSS) && (secondMovingY < boundaryYDown)){
secondMovingY += speed;
}
else if ((pad.buttons & SCE_CTRL_CIRCLE) && (secondMovingX < boundaryXRight)){
secondMovingX += speed;
}
else if ((pad.buttons & SCE_CTRL_SQUARE) && (secondMovingX > boundaryXLeft)){
secondMovingX -= speed;
}
1. If Triangle is pressed and firstMovingY is within the boundary of the vertical top, decrease firstMovingY by speed.
1.1 Explanation: When the triangle button is pressed, we decrease firstMovingY by speed because as the Y value decreases, it goes closer to the top.
2. If Cross is pressed and firstMovingY is within the boundary of the vertical bottom, increase firstMovingY by speed.
2.1 Explanation: When the cross button is pressed, we increase firstMovingY by speed because as the Y value increases, it moves further to the bottom.
3. If Circle is pressed and firstMovingX is within the horizontal right boundary, increase firstMovingX by speed.
3.1 Explanation: When the circle button is pressed, we increase firstMovingX by speed because as the X value increases, it moves further to the right.
4. If Square is pressed and firstMovingX is within the horizontal left boundary, decrease firstMovingX by speed.
4.1 Explanation: When the square button is pressed, we decrease firstMovingX by speed because as the X value decreases, it moves further to the left.
********************************************************************************************************************
Code: Select all
int diffX = firstMovingX - secondMovingX;
if(diffX < 0){
diffX = secondMovingX - firstMovingX;
}
int diffY = firstMovingY - secondMovingY;
if(diffY < 0){
diffY = secondMovingY - firstMovingY;
}
if((diffX < 100) && (diffY < 100)){vita2d_pgf_draw_text(pgf, 500, 100, RED, 1.0f, "COLLISION!");}
So, what this code does, is:
1. Create a new variable called diffX and assign the difference of firstMovingX and secondMovingX to it.
2. If diffX is negative, we reverse the subtraction because then secondMovingX is on the right side of secondMovingX.
3. Create a new variable called diffY and assign the difference of firstMovingY and secondMovingY to it.
4. If diffY is negative, we reverse the subtraction because then secondMovingY is below firstMovingY.
5. If diffX < 100 and diffY < 100, draw "COLLISION!" on the screen at 500x and 100y in red and make it 1.0 big.
********************************************************************************************************************
Code: Select all
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 100, LIME, 1.0f, "firstMovingX: %d", firstMovingX);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 130, LIME, 1.0f, "firstMovingY: %d", firstMovingY);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 160, LIME, 1.0f, "secondMovingX: %d", secondMovingX);
vita2d_pgf_draw_textf(pgf, SCREEN_W - 200, 190, LIME, 1.0f, "secondMovingY: %d", secondMovingY);
********************************************************************************************************************
Code: Select all
vita2d_pgf_draw_text(pgf, SCREEN_W - 300, SCREEN_H - 20, LIME, 1.0f, "Press start to exit.");
********************************************************************************************************************
5. Explanation of shape drawing functions in main.c
Finally, in our explanation section, let's go our one and only shape drawing function:
Code: Select all
vita2d_draw_fill_circle(firstMovingX, firstMovingY, 50, BLUE);
vita2d_draw_fill_circle(secondMovingX, secondMovingY, 50, LIME);
The arguments for the function call are in the following order:
1. X value
2. Y value
3. Size of the circle in pixels
4. Color of the circle (the color is produced with the RGBA8 function, but we used a define preprocessor directive to make the code more readable)
********************************************************************************************************************
6. Installation
Makefile:
Code: Select all
TITLE_ID = VITA2DCTRL
TARGET = vita2dctrlsample
OBJS = main.o
LIBS = -lvita2d -lSceKernel_stub -lSceDisplay_stub -lSceGxm_stub \
-lSceSysmodule_stub -lSceCtrl_stub -lScePgf_stub \
-lSceCommonDialog_stub -lfreetype -lpng -ljpeg -lz -lm -lc
PREFIX = arm-vita-eabi
CC = $(PREFIX)-gcc
CFLAGS = -Wl,-q -Wall -O3
ASFLAGS = $(CFLAGS)
all: $(TARGET).vpk
%.vpk: eboot.bin
vita-mksfoex -s TITLE_ID=$(TITLE_ID) "$(TARGET)" param.sfo
eboot.bin: $(TARGET).velf
vita-make-fself $< $@
%.velf: %.elf
vita-elf-create $< $@
$(TARGET).elf: $(OBJS)
$(CC) $(CFLAGS) $^ $(LIBS) -o $@
%.o: %.png
$(PREFIX)-ld -r -b binary -o $@ $^
clean:
@rm -rf $(TARGET).vpk $(TARGET).velf $(TARGET).elf $(OBJS) \
eboot.bin param.sfo
Compilation of our Controller program:
1. Copy the code of main.c and Makefile and put them in two seperate files with their equivalent names.
2. Open a terminal, change your directory to that of the two files and run:
Code: Select all
make
1. Open molecularshell on your vita and press select
2. Open a terminal and type:
Code: Select all
cd ~
wget https://github.com/xyzz/Vita_Doom/releases/download/1.0/vitadoom.vpk
ftp [ip displayed on your vita] [port displayed on your vita] (without the brackets)
put ~/vitadoom.vpk /ux0:vitadoom.vpk
4. Press select again
5. Open a terminal and type:
Code: Select all
ftp [ip displayed on your vita] [port displayed on your vita] (without the brackets)
put path/to/eboot.bin /ux0:app/DOOM00000/eboot.bin
put path/to/param.sfo /ux0:app/DOOM00000/sce_sys/param.sfo
7. ???
8. Profit!!!
See you in the next part!