624 lines
14 KiB
C
624 lines
14 KiB
C
/*
|
|
Cybershot ps vita main.c
|
|
Made by Sem van der Hoeven
|
|
*/
|
|
|
|
//TODO tweak values
|
|
//TODO create start screen
|
|
//TODO make old school windows style interface / buttons etc
|
|
//TODO create ship color select ?
|
|
//TODO game over screen
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <psp2/ctrl.h>
|
|
#include <psp2/kernel/processmgr.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <vita2d.h>
|
|
|
|
#include "sprites/sprites.h"
|
|
#include "system/timing.h"
|
|
#include "system/control_input.h"
|
|
#include "toolbox/drawing.h"
|
|
|
|
#define printf psvDebugScreenPrintf
|
|
|
|
#define SCREEN_HEIGTH 544
|
|
#define SCREEN_WIDTH 940
|
|
#define SIMPLE_ENEMY_MAX_AMOUNT 20
|
|
#define ENEMY_MAX_AMOUNT 40
|
|
#define BULLET_MARGIN 5.0 // extra hitbox space to make sure bullets hit
|
|
#define MENU_SWITCH_DELAY 50 // delay in ms for when a menu screen is switched.
|
|
|
|
typedef enum
|
|
{
|
|
START,
|
|
MENU,
|
|
GAME,
|
|
GAMEOVER
|
|
} game_state;
|
|
|
|
uint8_t running, drawing_circle;
|
|
game_state current_state = START;
|
|
|
|
stick_data left_stick = {0, 0}, right_stick = {0, 0};
|
|
SceCtrlData pad;
|
|
uint8_t cross_pressed;
|
|
|
|
uint8_t current_bullet;
|
|
BULLET bullets[255];
|
|
|
|
uint8_t current_smoke_particle;
|
|
SMOKE_PARTICLE smoke_particles[255];
|
|
|
|
vita2d_pgf *pgf;
|
|
vita2d_pvf *pvf;
|
|
|
|
SceUInt64 deltaTime = 0; // delta time in ms
|
|
SceKernelSysClock sysclock;
|
|
timing_timer bullet_timer = {0, 250, 0}; // 0 as starting time, 250 ms timeout, not elapsed
|
|
timing_timer menu_switch_input_delay_timer = {0, 200, 0}; // 0 as starting time, 100 ms timeout, not elapsed
|
|
timing_timer score_timer = {0, 100, 0}; // timer to update score
|
|
timing_timer simple_enemy_spawn_timer = {0, 500, 0}; // timer to spawn a new simple enemy
|
|
timing_timer complex_enemy_spawn_timer = {0, 2000, 0}; // timer to spawn a new complex enemy
|
|
|
|
ENEMY_SPRITE enemies[ENEMY_MAX_AMOUNT];
|
|
uint32_t enemy_count;
|
|
|
|
int score;
|
|
|
|
float player_x, player_y, radius;
|
|
|
|
/**
|
|
* @brief should be called when an unhandlable exception or error occurs. Triggers coredump.
|
|
*
|
|
*/
|
|
__attribute__((__noreturn__)) void shit_yourself(void)
|
|
{
|
|
while (1)
|
|
{
|
|
*(int *)(0xAA) = 0x55; // trigger coredump
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief initializes all variables
|
|
*/
|
|
void init_variables()
|
|
{
|
|
running = 1;
|
|
drawing_circle = 0;
|
|
cross_pressed = 0;
|
|
current_bullet = 0;
|
|
current_smoke_particle = 0;
|
|
enemy_count = 0;
|
|
player_x = SCREEN_WIDTH / 2;
|
|
player_y = 500;
|
|
radius = 5.0;
|
|
score = 0;
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ GENERATE SPRITES ------------------
|
|
// ################################################################
|
|
|
|
/**
|
|
* @brief initializes the sprites
|
|
*/
|
|
void init_sprites()
|
|
{
|
|
int i;
|
|
for (i = 0; i < 255; i++)
|
|
{
|
|
BULLET temp = {NONACTIVE, 0, 100, RGBA8(0, i, 255, 255), 300.0};
|
|
bullets[i] = temp;
|
|
}
|
|
|
|
for (i = 0; i < 255; i++)
|
|
{
|
|
SMOKE_PARTICLE s = {NONACTIVE, 0, 0, SMOKE_START_RADIUS};
|
|
smoke_particles[i] = s;
|
|
}
|
|
|
|
// add simple enemies
|
|
for (i = 0; i < SIMPLE_ENEMY_MAX_AMOUNT; i++)
|
|
{
|
|
ENEMY_SPRITE e = {NONACTIVE, SIMPLE, 0, 0, SIMPLE_ENEMY_COLOR, SIMPLE_ENEMY_MOVEMENT_SPEED, SIMPLE_ENEMY_SCORE};
|
|
enemies[i] = e;
|
|
enemy_count++;
|
|
}
|
|
for (i = SIMPLE_ENEMY_MAX_AMOUNT; i < ENEMY_MAX_AMOUNT; i++)
|
|
{
|
|
ENEMY_SPRITE e = {NONACTIVE, COMPLEX, 0, 0, COMPLEX_ENEMY_COLOR, COMPLEX_ENEMY_MOVEMENT_SPEED, COMPLEX_ENEMY_SCORE};
|
|
enemies[i] = e;
|
|
enemy_count++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief generates a bullet
|
|
*
|
|
*/
|
|
void generate_bullet()
|
|
{
|
|
// {1, x1_pos, y1_pos, RGBA8(100, 100, 0, 255)};
|
|
bullets[current_bullet].active = ACTIVE;
|
|
bullets[current_bullet].x = player_x;
|
|
bullets[current_bullet].y = player_y - SHIP_HEIGHT;
|
|
bullets[current_bullet].color = RGBA8(255, 100, 0, 255);
|
|
current_bullet = (current_bullet + 1) % 254;
|
|
}
|
|
|
|
/**
|
|
* @brief generates a smoke particle
|
|
*
|
|
*/
|
|
void generate_smoke_particle()
|
|
{
|
|
smoke_particles[current_smoke_particle].active = ACTIVE;
|
|
smoke_particles[current_smoke_particle].x = player_x;
|
|
smoke_particles[current_smoke_particle].y = player_y - SHIP_HEIGHT;
|
|
smoke_particles[current_smoke_particle].radius = SMOKE_START_RADIUS;
|
|
smoke_particles[current_smoke_particle].explosion_direction = 1;
|
|
current_smoke_particle = (current_smoke_particle + 1) % 254;
|
|
}
|
|
|
|
/**
|
|
* @brief generates an enemy
|
|
*
|
|
* @param enemy the enemy to generate
|
|
*/
|
|
void generate_enemy(ENEMY_SPRITE *enemy)
|
|
{
|
|
enemy->active = ACTIVE;
|
|
enemy->x = toolbox_random_float(0, SCREEN_WIDTH - 1);
|
|
enemy->y = 0;
|
|
}
|
|
/**
|
|
* @brief generates a simple enemy
|
|
*
|
|
*/
|
|
void generate_simple_enemy()
|
|
{
|
|
for (int i = 0; i < SIMPLE_ENEMY_MAX_AMOUNT; i++)
|
|
{
|
|
if (enemies[i].active == NONACTIVE)
|
|
{
|
|
generate_enemy(&enemies[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief generates a complex enemy
|
|
*
|
|
*/
|
|
void generate_complex_enemy()
|
|
{
|
|
for (int i = SIMPLE_ENEMY_MAX_AMOUNT; i < ENEMY_MAX_AMOUNT; i++)
|
|
{
|
|
if (enemies[i].active == NONACTIVE)
|
|
{
|
|
generate_enemy(&enemies[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ END GENERATE SPRITES ------------------
|
|
// ################################################################
|
|
|
|
// ################################################################
|
|
// ------------------------ COLLISION ------------------
|
|
// ################################################################
|
|
|
|
/**
|
|
* @brief checks if a bullet has hit the given object
|
|
*
|
|
* @param bullet the bullet to check for
|
|
* @param x1 the x pos of the center of the other object
|
|
* @param x2 the y pos of the center of the other object
|
|
* @param width the width of the other object
|
|
* @param heigth the heigth of the other object
|
|
* @return uint8_t 0 if no collision, 1 if there is a collision.
|
|
*/
|
|
uint8_t bullet_is_collision(BULLET *bullet, float x, float y, float width, float heigth)
|
|
{
|
|
return toolbox_is_collision(bullet->x + BULLET_WIDTH / 2.0, bullet->y + BULLET_HEIGTH / 2.0, BULLET_WIDTH + BULLET_MARGIN, BULLET_HEIGTH, x, y, width, heigth);
|
|
}
|
|
|
|
/**
|
|
* @brief checks wether a bullet has hit an enemy
|
|
*
|
|
* @param bullet the bulle to check for
|
|
* @param enemy the enemy to check for
|
|
* @return uint8_t 0 if no collision, 1 if there is a collision
|
|
*/
|
|
uint8_t bullet_hit_enemy(BULLET *bullet, ENEMY_SPRITE *enemy)
|
|
{
|
|
if (enemy->enemy_type == SIMPLE)
|
|
{
|
|
return bullet_is_collision(bullet, enemy->x, enemy->y, SIMPLE_ENEMY_SIZE, SIMPLE_ENEMY_SIZE);
|
|
}
|
|
else if (enemy->enemy_type == COMPLEX)
|
|
{
|
|
return bullet_is_collision(bullet, enemy->x, enemy->y, COMPLEX_ENEMY_SIZE, COMPLEX_ENEMY_SIZE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t enemy_hit_player(ENEMY_SPRITE *enemy)
|
|
{
|
|
if (enemy->enemy_type == SIMPLE)
|
|
{
|
|
return toolbox_is_collision(enemy->x, enemy->y, SIMPLE_ENEMY_SIZE, SIMPLE_ENEMY_SIZE, player_x, player_y - PLAYER_Y_OFFSET, PLAYER_WIDTH, PLAYER_HEIGTH);
|
|
}
|
|
else if (enemy->enemy_type == COMPLEX)
|
|
{
|
|
return toolbox_is_collision(enemy->x, enemy->y, COMPLEX_ENEMY_SIZE, COMPLEX_ENEMY_SIZE, player_x, player_y - PLAYER_Y_OFFSET, PLAYER_WIDTH, PLAYER_HEIGTH);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief checks the collision for all bullets
|
|
*
|
|
*/
|
|
void check_bullet_collisions()
|
|
{
|
|
for (int e = 0; e < enemy_count; e++)
|
|
{
|
|
if (enemies[e].active == ACTIVE)
|
|
{
|
|
for (int b = 0; b < 255; b++)
|
|
{
|
|
if (bullets[b].active == ACTIVE)
|
|
{
|
|
if (bullet_hit_enemy(&bullets[b], &enemies[e]))
|
|
{
|
|
bullets[b].active = NONACTIVE;
|
|
enemies[e].active = NONACTIVE;
|
|
score += enemies[e].score;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief checks the collision for all enemies with the player
|
|
*
|
|
* @returns true if there was a collision
|
|
*/
|
|
SceBool check_player_collisions()
|
|
{
|
|
SceBool res = SCE_FALSE;
|
|
for (int i = 0; i < enemy_count; i++)
|
|
{
|
|
if (enemies[i].active == ACTIVE)
|
|
{
|
|
if (enemy_hit_player(&enemies[i]))
|
|
{
|
|
res = SCE_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ END COLLISION ------------------
|
|
// ################################################################
|
|
|
|
void init()
|
|
{
|
|
/* to enable analog sampling */
|
|
sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG);
|
|
|
|
vita2d_init();
|
|
vita2d_set_clear_color(RGBA8(0x40, 0x40, 0x40, 0xFF));
|
|
init_variables();
|
|
|
|
pgf = vita2d_load_default_pgf();
|
|
pvf = vita2d_load_default_pvf();
|
|
|
|
memset(&pad, 0, sizeof(pad));
|
|
|
|
init_sprites();
|
|
|
|
//TODO add other enemies
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ UPDATE FUNCTIONS ------------------
|
|
// ################################################################
|
|
|
|
void update_start()
|
|
{
|
|
timing_update_timer(&menu_switch_input_delay_timer, deltaTime);
|
|
timing_check_timer_elapsed(&menu_switch_input_delay_timer);
|
|
if (cross_pressed)
|
|
if (menu_switch_input_delay_timer.elapsed)
|
|
{
|
|
current_state = MENU;
|
|
menu_switch_input_delay_timer.elapsed = 0;
|
|
}
|
|
}
|
|
|
|
void update_menu()
|
|
{
|
|
timing_update_timer(&menu_switch_input_delay_timer, deltaTime);
|
|
timing_check_timer_elapsed(&menu_switch_input_delay_timer);
|
|
|
|
if (cross_pressed)
|
|
if (menu_switch_input_delay_timer.elapsed)
|
|
current_state = GAME;
|
|
}
|
|
|
|
void update_game()
|
|
{
|
|
|
|
timing_update_timer(&bullet_timer, deltaTime); // update timer
|
|
timing_check_timer_elapsed(&bullet_timer);
|
|
timing_update_timer(&score_timer, deltaTime);
|
|
timing_check_timer_elapsed(&score_timer);
|
|
timing_update_timer(&simple_enemy_spawn_timer, deltaTime);
|
|
timing_check_timer_elapsed(&simple_enemy_spawn_timer);
|
|
timing_update_timer(&complex_enemy_spawn_timer, deltaTime);
|
|
timing_check_timer_elapsed(&complex_enemy_spawn_timer);
|
|
|
|
if (cross_pressed)
|
|
{
|
|
if (bullet_timer.elapsed)
|
|
{
|
|
generate_bullet();
|
|
bullet_timer.elapsed = 0;
|
|
generate_smoke_particle();
|
|
}
|
|
}
|
|
|
|
if (abs(left_stick.x) > 15)
|
|
player_x += ctrl_input_calc_value(left_stick.x, deltaTime);
|
|
if (abs(left_stick.y) > 15)
|
|
player_y += ctrl_input_calc_value(left_stick.y, deltaTime);
|
|
|
|
if (player_x <= 0)
|
|
player_x = 0;
|
|
if (player_x >= SCREEN_WIDTH)
|
|
player_x = SCREEN_WIDTH - 1;
|
|
if (player_y <= 0)
|
|
player_y = 0;
|
|
if (player_y >= SCREEN_HEIGTH)
|
|
player_y = SCREEN_HEIGTH - 1;
|
|
|
|
if (check_player_collisions() == SCE_TRUE)
|
|
{
|
|
current_state = GAMEOVER;
|
|
menu_switch_input_delay_timer.elapsed = 0;
|
|
return;
|
|
}
|
|
|
|
if (score_timer.elapsed)
|
|
score += 1;
|
|
|
|
check_bullet_collisions();
|
|
|
|
for (int i = 0; i < 255; i++)
|
|
{
|
|
bullets[i].y -= bullets[i].movement_speed * (deltaTime / 1000.0);
|
|
|
|
if (bullets[i].y <= 0)
|
|
{
|
|
bullets[i].active = 0;
|
|
}
|
|
|
|
smoke_particles[i].radius += smoke_particles[i].explosion_direction * (SMOKE_DISSAPPEAR_SPEED * (deltaTime / 1000.0)) * (SMOKE_MAX_RADIUS / smoke_particles[i].radius);
|
|
|
|
if (smoke_particles[i].radius >= SMOKE_MAX_RADIUS)
|
|
smoke_particles[i].explosion_direction = -1;
|
|
|
|
if (smoke_particles[i].radius <= 0)
|
|
smoke_particles[i].active = 0;
|
|
}
|
|
|
|
if (simple_enemy_spawn_timer.elapsed)
|
|
{
|
|
generate_simple_enemy();
|
|
simple_enemy_spawn_timer.elapsed = 0;
|
|
}
|
|
|
|
if (complex_enemy_spawn_timer.elapsed)
|
|
{
|
|
generate_complex_enemy();
|
|
complex_enemy_spawn_timer.elapsed = 0;
|
|
}
|
|
|
|
for (int i = 0; i < ENEMY_MAX_AMOUNT; i++)
|
|
{
|
|
if (enemies[i].active == ACTIVE)
|
|
{
|
|
enemies[i].y += enemies[i].movement_speed;
|
|
if (enemies[i].y >= SCREEN_HEIGTH + COMPLEX_ENEMY_SIZE) // complex enemy is biggest
|
|
{
|
|
enemies[i].active = NONACTIVE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void update_gameover()
|
|
{
|
|
timing_update_timer(&menu_switch_input_delay_timer, deltaTime);
|
|
timing_check_timer_elapsed(&menu_switch_input_delay_timer);
|
|
if (cross_pressed)
|
|
if (menu_switch_input_delay_timer.elapsed)
|
|
{
|
|
current_state = START;
|
|
init_variables();
|
|
init_sprites();
|
|
menu_switch_input_delay_timer.elapsed = 0;
|
|
}
|
|
}
|
|
|
|
void update()
|
|
{
|
|
deltaTime = timing_get_deltatime(&sysclock);
|
|
cross_pressed = 0;
|
|
if (deltaTime < 0)
|
|
shit_yourself();
|
|
|
|
sceCtrlPeekBufferPositive(0, &pad, 1);
|
|
|
|
if (pad.buttons & SCE_CTRL_START)
|
|
running = 0;
|
|
if (pad.buttons & SCE_CTRL_CROSS)
|
|
cross_pressed = 1;
|
|
|
|
ctrl_input_get_leftstick(&pad, &left_stick);
|
|
|
|
// ctrl_input_get_rightstick(&pad, &right_stick);
|
|
|
|
switch (current_state)
|
|
{
|
|
case START:
|
|
update_start();
|
|
break;
|
|
|
|
case MENU:
|
|
update_menu();
|
|
break;
|
|
|
|
case GAME:
|
|
update_game();
|
|
break;
|
|
case GAMEOVER:
|
|
update_gameover();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ END UPDATE FUNCTIONS ------------------
|
|
// ################################################################
|
|
|
|
// ################################################################
|
|
// ------------------------ DRAW FUNCTIONS ------------------
|
|
// ################################################################
|
|
|
|
void draw_start()
|
|
{
|
|
vita2d_pvf_draw_text(pvf, 700, 80, RGBA8(0, 255, 0, 255), 1.0f, "Cybershot");
|
|
}
|
|
|
|
void draw_menu()
|
|
{
|
|
vita2d_pvf_draw_text(pvf, 700, 80, RGBA8(0, 255, 0, 255), 1.0f, "menu sletjes");
|
|
}
|
|
|
|
void draw_game()
|
|
{
|
|
sprites_draw_player(player_x, player_y, PLAYER_SCALE);
|
|
// vita2d_draw_rectangle(x2_pos, y2_pos, (float)10, (float)10, RGBA8(169, 60, 23, 255));
|
|
|
|
// vita2d_draw_rectangle(300, 50, 30, 30, RGBA8(255, 0, 255, 255));
|
|
|
|
char fps[15] = "fps: ";
|
|
sprintf(fps, "%d", timing_get_fps(deltaTime));
|
|
|
|
vita2d_pvf_draw_text(pvf, 700, 80, RGBA8(0, 255, 0, 255), 1.0f, fps);
|
|
|
|
char timertext[100];
|
|
sprintf(timertext, "time %lu", bullet_timer.time);
|
|
vita2d_pgf_draw_text(pgf, 10, 30, RGBA8(0, 255, 150, 255), 1.0f, timertext);
|
|
|
|
for (int i = 0; i < 255; i++)
|
|
{
|
|
sprites_draw_bullet(&bullets[i]);
|
|
sprites_draw_smoke_circle(&smoke_particles[i]);
|
|
}
|
|
|
|
for (int i = 0; i < enemy_count; i++)
|
|
{
|
|
sprites_draw_enemy(&enemies[i]);
|
|
}
|
|
|
|
char score_text[40];
|
|
sprintf(score_text, "score: %07d", score);
|
|
vita2d_pvf_draw_text(pvf, 700, 100, RGBA8(0, 255, 0, 255), 1.0f, score_text);
|
|
|
|
drawing_draw_rectangle_open(0, 0, SCREEN_WIDTH, SCREEN_HEIGTH, 10, RGBA8(98, 124, 158, 255));
|
|
}
|
|
|
|
void draw_gameover()
|
|
{
|
|
char score_text[40];
|
|
sprintf(score_text, "score: %07d", score);
|
|
vita2d_pvf_draw_text(pvf, 700, 80, RGBA8(0, 255, 0, 255), 1.0f, "Game over");
|
|
vita2d_pvf_draw_text(pvf, 700, 100, RGBA8(0, 255, 0, 255), 1.0f, score_text);
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
vita2d_start_drawing();
|
|
vita2d_clear_screen();
|
|
|
|
switch (current_state)
|
|
{
|
|
case START:
|
|
draw_start();
|
|
break;
|
|
|
|
case MENU:
|
|
draw_menu();
|
|
break;
|
|
|
|
case GAME:
|
|
draw_game();
|
|
break;
|
|
case GAMEOVER:
|
|
draw_gameover();
|
|
break;
|
|
}
|
|
|
|
vita2d_end_drawing();
|
|
vita2d_swap_buffers();
|
|
}
|
|
|
|
// ################################################################
|
|
// ------------------------ END DRAW FUNCTIONS ------------------
|
|
// ################################################################
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
init();
|
|
|
|
while (running)
|
|
{
|
|
update();
|
|
draw();
|
|
}
|
|
|
|
/*
|
|
* vita2d_fini() waits until the GPU has finished rendering,
|
|
* then we can free the assets freely.
|
|
*/
|
|
vita2d_fini();
|
|
// vita2d_free_texture(image);
|
|
vita2d_free_pgf(pgf);
|
|
vita2d_free_pvf(pvf);
|
|
|
|
sceKernelExitProcess(0);
|
|
return 0;
|
|
}
|