289 lines
11 KiB
C
289 lines
11 KiB
C
|
// full controller demo: anims, input, collide; @todo: gamepad, input opts, easing on hits, notify on gamepad connect
|
||
|
// - rlyeh, public domain.
|
||
|
//
|
||
|
// Compile with:
|
||
|
// `make demos\04-controller.c` (windows)
|
||
|
// `sh MAKE.bat demos/04-controller.c` (linux, osx)
|
||
|
|
||
|
#include "fwk.h"
|
||
|
|
||
|
int main() {
|
||
|
// 75% window, MSAAx2 flag
|
||
|
window_create(75, WINDOW_MSAA2);
|
||
|
|
||
|
// fx: load all post fx files in all subdirs
|
||
|
fx_load("fx**.fs");
|
||
|
|
||
|
// create a camera
|
||
|
camera_t cam = camera();
|
||
|
camera_enable(&cam);
|
||
|
|
||
|
// config 3d model #1
|
||
|
model_t witch = model("witch/witch.obj", 0);
|
||
|
model_set_texture(witch, texture("witch/witch_diffuse.tga.png", 0));
|
||
|
mat44 witch_pivot; vec3 witch_p = {-5,0,-5}, witch_r={-180,180,0}, witch_s={0.1,-0.1,0.1};
|
||
|
|
||
|
// config 3d model #2
|
||
|
model_t girl = model("kgirl/kgirls01.fbx", 0);
|
||
|
mat44 girl_pivot; vec3 girl_p = {0,0,0}, girl_r = {270,0,0}, girl_s = {2,2,2};
|
||
|
|
||
|
// skybox
|
||
|
skybox_t sky = skybox("cubemaps/stardust", 0);
|
||
|
|
||
|
// BGM
|
||
|
audio_play( audio_stream("waterworld-map.fur"), 0 );
|
||
|
|
||
|
// editor loop
|
||
|
while( window_swap() ) {
|
||
|
|
||
|
// game camera
|
||
|
profile("Game.Camera") {
|
||
|
camera_t *cam = camera_get_active();
|
||
|
|
||
|
static vec3 source;
|
||
|
do_once source = cam->position;
|
||
|
|
||
|
vec3 target = add3(girl_p, vec3(0,10,0));
|
||
|
target = add3(target, scale3(norm3(sub3(source, target)), 10.0));
|
||
|
source = mix3(source, target, 1-0.99f);
|
||
|
|
||
|
camera_teleport(cam, source);
|
||
|
camera_lookat(cam, vec3(girl_p.x,0,girl_p.z));
|
||
|
}
|
||
|
|
||
|
// render begin (postfx)
|
||
|
fx_begin();
|
||
|
|
||
|
// skybox
|
||
|
skybox_render(&sky, cam.proj, cam.view);
|
||
|
|
||
|
// world
|
||
|
ddraw_grid(0);
|
||
|
ddraw_flush();
|
||
|
|
||
|
// models
|
||
|
compose44(girl.pivot, girl_p, eulerq(girl_r), girl_s);
|
||
|
model_render(girl, cam.proj, cam.view, girl.pivot, 0);
|
||
|
|
||
|
compose44(witch.pivot, witch_p, eulerq(witch_r), witch_s);
|
||
|
model_render(witch, cam.proj, cam.view, witch.pivot, 0);
|
||
|
|
||
|
// render end (postfx)
|
||
|
fx_end();
|
||
|
|
||
|
// input controllers
|
||
|
|
||
|
double GAME_JUMP_DOWN = input_down(KEY_Z);
|
||
|
double GAME_FIRE_DOWN = input_down(KEY_X);
|
||
|
double GAME_JUMP = input(KEY_Z);
|
||
|
double GAME_FIRE = input(KEY_X);
|
||
|
double GAME_LEFT = input(KEY_J);
|
||
|
double GAME_RIGHT = input(KEY_L);
|
||
|
double GAME_UP = input(KEY_I);
|
||
|
double GAME_DOWN = input(KEY_K);
|
||
|
double GAME_AXISX = input(KEY_L) - input(KEY_J);
|
||
|
double GAME_AXISY = input(KEY_I) - input(KEY_K);
|
||
|
|
||
|
if( !input_anykey() ) {
|
||
|
if( input(GAMEPAD_CONNECTED) ) {
|
||
|
vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), 0.15f /*15% deadzone*/);
|
||
|
GAME_JUMP_DOWN = input_down(GAMEPAD_A);
|
||
|
GAME_FIRE_DOWN = input_down(GAMEPAD_B) || input_down(GAMEPAD_X) || input_down(GAMEPAD_Y);
|
||
|
GAME_JUMP = input(GAMEPAD_A);
|
||
|
GAME_FIRE = input(GAMEPAD_B) || input(GAMEPAD_X) || input(GAMEPAD_Y);
|
||
|
GAME_AXISX = filtered_lpad.x;
|
||
|
GAME_AXISY = filtered_lpad.y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// animation controllers
|
||
|
|
||
|
profile("Game.Animate scene") if( !window_has_pause() ) {
|
||
|
float delta = window_delta() * 30; // 30fps anim
|
||
|
|
||
|
// animate girl
|
||
|
girl.curframe = model_animate(girl, girl.curframe + delta);
|
||
|
|
||
|
// jump controller: jump duration=1.5s, jump height=6 units, anims (expo->circ)
|
||
|
float jump_delta = 1.0;
|
||
|
static double jump_timer = 0, jump_ss = 1.5, jump_h = 6;
|
||
|
if( GAME_JUMP_DOWN ) if( jump_timer == 0 ) jump_timer = time_ss();
|
||
|
jump_delta = clampf(time_ss() - jump_timer, 0, jump_ss) * (1.0/jump_ss);
|
||
|
if( jump_delta >= 1 ) { jump_timer = 0; }
|
||
|
float y = ease_ping_pong( jump_delta, ease_out_expo, ease_out_circ);
|
||
|
girl_p.y = y * jump_h;
|
||
|
|
||
|
// punch controller
|
||
|
float punch_delta = 1;
|
||
|
if( jump_delta >= 1 ) {
|
||
|
static vec3 origin;
|
||
|
static double punch_timer = 0, punch_ss = 0.5;
|
||
|
if( GAME_FIRE_DOWN ) if( punch_timer == 0 ) punch_timer = time_ss(), origin = girl_p;
|
||
|
punch_delta = clampf(time_ss() - punch_timer, 0, punch_ss) * (1.0/punch_ss);
|
||
|
if( punch_delta >= 1 ) { punch_timer = 0; }
|
||
|
else {
|
||
|
float x = ease_out_expo( punch_delta );
|
||
|
vec3 fwd = rotate3q(vec3(0,0,1), eulerq(vec3(girl_r.x - 170,girl_r.y,girl_r.z)));
|
||
|
vec3 mix = mix3(girl_p, add3(origin,scale3(fwd,x*2)), x);
|
||
|
girl_p.x = mix.x, girl_p.z = mix.z;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int modern_controller = 1;
|
||
|
int running = 0;
|
||
|
|
||
|
// girl controller
|
||
|
|
||
|
// locomotion vars
|
||
|
float speed = 0.2f * delta;
|
||
|
float yaw_boost = GAME_AXISY > 0 ? 1.0 : 1.75;
|
||
|
if(punch_delta < 1) yaw_boost = 0.0; // if firing...
|
||
|
else if(punch_delta <= 0.1) yaw_boost = 4.0; // unless initial punch chaining, extra yaw
|
||
|
|
||
|
// old fashioned locomotion controller (boat controller)
|
||
|
if(!modern_controller) {
|
||
|
running = GAME_AXISY > 0;
|
||
|
|
||
|
girl_r.x -= 170;
|
||
|
quat q = eulerq(girl_r); // += custom.pivot
|
||
|
vec3 rgt = rotate3q(vec3(1,0,0), q);
|
||
|
vec3 up = rotate3q(vec3(0,1,0), q);
|
||
|
vec3 fwd = rotate3q(vec3(0,0,1), q);
|
||
|
vec3 dir = scale3(fwd, speed * GAME_AXISY * (GAME_AXISY > 0 ? 2.0 : 0.5));
|
||
|
girl_r.x += speed * 20.0 * yaw_boost * GAME_AXISX; // yaw
|
||
|
girl_p = add3(girl_p, dir);
|
||
|
girl_r.x += 170;
|
||
|
}
|
||
|
|
||
|
// modern locomotion controller (mario 3d)
|
||
|
if(modern_controller) {
|
||
|
running = GAME_AXISX != 0 || GAME_AXISY != 0;
|
||
|
|
||
|
camera_t *cam = camera_get_active();
|
||
|
vec3 fwd = sub3(girl_p, cam->position); fwd.y = 0; fwd = norm3(fwd);
|
||
|
vec3 rgt = norm3(cross3(fwd, vec3(0,1,0)));
|
||
|
|
||
|
// target
|
||
|
vec3 dir = add3(
|
||
|
scale3(fwd, GAME_AXISY),
|
||
|
scale3(rgt, GAME_AXISX)
|
||
|
); dir.y = 0; dir = norm3(dir);
|
||
|
|
||
|
// smoothing
|
||
|
static vec3 olddir; do_once olddir = dir;
|
||
|
dir = mix3(dir, olddir, 1 - (yaw_boost / 4.0) * 0.85);
|
||
|
olddir = dir;
|
||
|
|
||
|
// vis
|
||
|
// ddraw_arrow(girl_p, add3(girl_p,scale3(dir,10)));
|
||
|
|
||
|
// apply direction
|
||
|
girl_p = add3(girl_p, scale3(dir, speed * 2));
|
||
|
|
||
|
// apply rotation
|
||
|
{
|
||
|
girl_r.x -= 170;
|
||
|
quat q = eulerq(girl_r);
|
||
|
vec3 fwdg = rotate3q(vec3(0,0,1), q);
|
||
|
girl_r.x += 170;
|
||
|
|
||
|
//float cosAngle = dot3(dir,fwdg);
|
||
|
//float angle = acos(cosAngle) * TO_DEG;
|
||
|
float angle = TO_DEG * ( atan2(fwdg.z, fwdg.x) - atan2(dir.z, dir.x));
|
||
|
|
||
|
if( !isnan(angle) ) {
|
||
|
girl_r.x -= angle;
|
||
|
while(girl_r.x> 180) girl_r.x-=360;
|
||
|
while(girl_r.x<-180) girl_r.x+=360;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// anim loops
|
||
|
if( jump_delta < 1 ) { // jump/kick anim
|
||
|
#if 0
|
||
|
girl.curframe = clampf(girl.curframe, 184, 202);
|
||
|
if( girl.curframe > 202-4 && GAME_FIRE_DOWN ) girl.curframe = 184+4;
|
||
|
#else
|
||
|
#define loopf(frame, min, max) (frame < min ? min : frame > max ? min + frame - max : frame)
|
||
|
if(girl.curframe >= 203)
|
||
|
girl.curframe = loopf(girl.curframe, 203, 220);
|
||
|
else
|
||
|
girl.curframe = clampf(girl.curframe, 184, 202);
|
||
|
if( girl.curframe > 202-4 && girl.curframe < 208 && GAME_FIRE_DOWN ) girl.curframe = 203;
|
||
|
#endif
|
||
|
}
|
||
|
else if( punch_delta < 1 ) { // punch anim
|
||
|
girl.curframe = clampf(girl.curframe, 90, 101);
|
||
|
if( girl.curframe > 101-6 && GAME_FIRE_DOWN ) girl.curframe = 101-6;
|
||
|
}
|
||
|
else if( running ) {
|
||
|
// loop running anim
|
||
|
if( girl.curframe < 65 ) girl.curframe = 65;
|
||
|
if( girl.curframe > 85 ) girl.curframe = 65;
|
||
|
}
|
||
|
else { // loop idle anim
|
||
|
if( girl.curframe > 59 ) girl.curframe = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Game collisions
|
||
|
|
||
|
profile("Game.collisions") {
|
||
|
bool punching = girl.curframe >= 90 && girl.curframe < 101;
|
||
|
bool air_kicking = girl.curframe >= 184 && girl.curframe < 202;
|
||
|
bool jump_kicking = girl.curframe >= 203 && girl.curframe < 220;
|
||
|
bool attacking = punching || air_kicking || jump_kicking;
|
||
|
|
||
|
if( attacking ) {
|
||
|
aabb boxg = model_aabb(girl, girl_pivot);
|
||
|
aabb boxw = model_aabb(witch, witch_pivot);
|
||
|
#if 0 // halve aabb. ok
|
||
|
{
|
||
|
vec3 diag = sub3(boxg.max, boxg.min);
|
||
|
vec3 halve = scale3(diag, 0.25);
|
||
|
vec3 center = scale3(add3(boxg.min, boxg.max), 0.5);
|
||
|
boxg.min = sub3(center, halve);
|
||
|
boxg.max = add3(center, halve);
|
||
|
}
|
||
|
#endif
|
||
|
hit* h = aabb_hit_aabb(boxg, boxw);
|
||
|
if( h && GAME_FIRE ) {
|
||
|
vec3 dir = norm3(sub3(witch_p, girl_p));
|
||
|
witch_p = add3(witch_p, mul3(dir,vec3(1,0,1)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ui
|
||
|
if( ui_panel("Input", 0) ) { // @todo: showcase input binding
|
||
|
ui_section("Controllers");
|
||
|
ui_label("Gamepad #1");
|
||
|
ui_label("Keys I/J/K/L + Z/X");
|
||
|
ui_panel_end();
|
||
|
}
|
||
|
if( ui_panel("FX", 0) ) {
|
||
|
for( int i = 0; i < 64; ++i ) {
|
||
|
char *name = fx_name(i); if( !name ) break;
|
||
|
bool b = fx_enabled(i);
|
||
|
if( ui_bool(name, &b) ) fx_enable(i, b);
|
||
|
}
|
||
|
ui_panel_end();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// vec2 do_gamepad_polarity = vec2(+1,+1);
|
||
|
// vec2 do_gamepad_sensitivity = vec2(0.1f,0.1f);
|
||
|
// vec2 do_mouse_polarity = vec2(+1,-1);
|
||
|
// vec2 do_mouse_sensitivity = vec2(0.2f,0.2f);
|
||
|
// float do_gamepad_deadzone = 0.15f;//
|
||
|
//
|
||
|
// if(ui_separator()) {}
|
||
|
// if(ui_slider("Gamepad deadzone", &do_gamepad_deadzone)) {}
|
||
|
// if(ui_float2("Gamepad polarity", do_gamepad_polarity.v2)) {}
|
||
|
// if(ui_float2("Gamepad sensitivity", do_gamepad_sensitivity.v2)) {}
|
||
|
// if(ui_separator()) {}
|
||
|
// if(ui_float2("Mouse polarity", do_mouse_polarity.v2)) {}
|
||
|
// if(ui_float2("Mouse sensitivity", do_mouse_sensitivity.v2)) {}//
|