Advertising (This ad goes away for registered users. You can Login or Register)

[DEV TUT] Vita Programming Tutorials: Part 2 -- Controller

Post here your guides, tips, how-to, etc...
Locked
thevitamaster
Posts: 10
Joined: Tue Aug 16, 2016 5:49 am

[DEV TUT] Vita Programming Tutorials: Part 2 -- Controller

Post by thevitamaster »

Hi guys,
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;
	
}
2. The define preprocessor directive
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
So, with this directive we instruct the C preprocessor to replace all occurences of SCREEN_W with 960 (the screen width in pixels of the vita).
The basic syntax of a define directive is:

Code: Select all

#define [symbol to replace] [code to replace your symbol with]
I have called these objects used in the define directive symbols, because they are not variables: You can't assign values to these symbols. They're just text.
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 :shock:. 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>
This include directive is imperative if you want to process controller input.
********************************************************************************************************************

Code: Select all

SceCtrlData pad;
Remember how I mentioned enums in the last tutorial? This variable type was created with the enum keyword and typedefed so that we can just type

Code: Select all

SceCtrlData pad;
instead of

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) {
This is important: We have to run our code which processes controller input in an infinite (or finite for as long as we need) loop, so that we continously read the controller input instead of only once.
Also, we need to use a while loop to not only draw our shapes once.
********************************************************************************************************************

Code: Select all

int circle_draw_true;
We will use this variable later on to signal if the cross button has been pressed. Why don't we just do if(pad.Buttons & SCE_CTRL_CROSS){draw circle}?
Because then we'd only draw a circle if the cross button is being actively pressed.
********************************************************************************************************************

Code: Select all

sceCtrlPeekBufferPositive(0, &pad, 1);
With this we can get the control device state information. The arguments supplied to this function call have the following meaning:
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) {
Here, we check if the cross button has been pressed using a bitwise and (see the explanation above).

Code: Select all

circle_draw_true = 1;
Assign one to circle_draw_true if cross has been pressed.
********************************************************************************************************************

Code: Select all

sceCtrlPeekBufferPositive(0, &pad, 1);
We have to call this again because we are in a nested control statement and want to access control device state information.
********************************************************************************************************************
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;
Here, we declare and initialzie four boundary variables which we use later in our game logic to stop the ball from going out of the vita screen: boundaryXLeft for the left horizontal boundary of the vita screen, boundaryXRight for the right horizontal boundary of the vita screen, boundaryYUp for the vertical up boundary and boundaryYDown for the vertical down boundary.
If you're confused about where 0 is and where the maximum pixel is, take a look at this:
Image
********************************************************************************************************************

Code: Select all

int firstMovingX = 420;
int firstMovingY = SCREEN_H / 2;

int secondMovingX = 520;
int secondMovingY = SCREEN_H / 2;
We want to create two *** later on: the first ball at 420x, 272y and the second one at 520x, 272y. So we want them at the same height, hence the same Y value, but 100px apart horizontally, hence the 100px difference in the X values.
********************************************************************************************************************

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;
		}
So now we do two things: First, we draw the text "Press cross to draw and move a blue circle!" at (10x, 50y)px with size 1.0.
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){
Start the game while loop and set the default speed value to 3.
********************************************************************************************************************

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;
				}
This is our code so that player one can move the ball with the directional arrows: It checks if a directional arrow has been pressed and also, if the ball is within the boundary, then, if both are true, subtracts the speed from firstMovingX / firstMovingY and does the same in case an addition is necessary.
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:
Image
********************************************************************************************************************

Code: Select all

				if(pad.buttons & SCE_CTRL_START){
					circle_draw_true = 0; 
					break;
				}
Goes back to the title screen if start is pressed.
********************************************************************************************************************

Code: Select all

				if(pad.buttons & SCE_CTRL_RTRIGGER){
					speed++;
				}
				if((pad.buttons & SCE_CTRL_LTRIGGER) && (speed > 0)){
					speed--;
				}
Increments speed by one if the right trigger is pressed and decrements the speed by one if the left trigger is pressed.
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;
				}
This is the same code to move the ball for the second player. The same explanation applies:
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!");}
This code is our simple collision checking routine. We use a collision checking method which would actually only apply to rectangles / squares because we don't want to get into too deep stuff with square roots.
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);
This is our debug information in case something inexplicable occurs.
********************************************************************************************************************

Code: Select all

				vita2d_pgf_draw_text(pgf, SCREEN_W - 300, SCREEN_H - 20, LIME, 1.0f, "Press start to exit.");
The player can press the start button 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);
Here, we draw two circles for player one and player two with dynamic locations: We perform various operations on the firstMoving and secondMoving variables as seen above.
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
This "just works".

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
Installation:
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
3. Press circle on your vita and navigate to ux0:, then press cross when the vitadoom.vpk file is highlighted
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
6. Close molecularshell and open the Doom bubble
7. ???
8. Profit!!!

See you in the next part!
Advertising
xtc2014
Posts: 6
Joined: Thu Jan 30, 2014 3:32 am

Re: [DEV TUT] Vita Programming Tutorials: Part 2 -- Controll

Post by xtc2014 »

Another great tutorial :)
Thanks again for putting this together and explaining the code. Please continue to write more tutorials.
Advertising
gokuhs
Posts: 11
Joined: Thu Mar 10, 2011 2:24 pm

Re: [DEV TUT] Vita Programming Tutorials: Part 2 -- Controller

Post by gokuhs »

Thanks for the tutorial ;)
Locked

Return to “Tutorials”