Search code examples
cgame-physicsgame-development

How to make movement for a lunar lander game smoother on an embedded system with a 320x240 display in C?


I am making a lunar lander game in C on an embedded system and we have a fairly simple movement vectors. It's not complete yet, but i am running into a problem where the movement of the lander itself looks very choppy, I believe this is because the display driver that I am using only allows ints to be passed in, in order to draw.

The logic I have right now adds a velocity value to the box position, and every tick there is essentially an acceleration constant being added to the velocity being added to the position. This allows gravity to be a constantly pulling it down but also allows vertical and horizontal thrusts to be added based on the cos and sin values which are calculated from the angles of the lander.

Here is a link to my system (sorry for the poor quality I never post to youtube) https://youtube.com/shorts/NVvTxVoZjY4

All of the code is very large but I will post parts that I think are relevant.

#include "game_control.h"
#include "buttons.h"
#include "display.h"
#include "stdio.h"
#include "lander.h"

#define THRUST_SCALER 0.3

int16_t x0 = -10;
int16_t y_point0 = 0;
int16_t x1 = 0;
int16_t y_point1 = 0;
int16_t x2 = 0;
int16_t y2 = 10;
int16_t x3 = -10;
int16_t y3 = 10;
int16_t x_origin = 0;
int16_t y_origin = 120;
#define gravity 0.05
double y_velocity = 0.9;
double x_velocity = 1;
bool tick_is_odd = true;
int8_t tick_fourth = 0;
int8_t rotate = 0;

double thrust_x = 0.;
double thrust_y = 0.;

struct lander_t the_lander;


void map1() {
  display_drawLine(0, 240, 100, 200, DISPLAY_WHITE);
  display_drawLine(100, 200, 160, 220, DISPLAY_WHITE);
  display_drawLine(160, 220, 190, 220, DISPLAY_DARK_YELLOW);
  display_drawLine(190, 220, 300, 160, DISPLAY_WHITE);
  display_drawLine(300, 160, 320, 240, DISPLAY_WHITE);
}


// Tick the game control logic
//
// This function should tick the lander and check for wins on the game
void gameControl_tick() {


  uint8_t button_value = buttons_read();
  printf("vertical velocity: %f\n", y_velocity);
  printf("horizontal velocity: %f\n\n", x_velocity);
  

  if((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK){
    lean_right(&the_lander);

  } else if ((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK){
    lean_left(&the_lander);

  }





  //testing rotations given preset rotation values
  if (y_point0 <= 230) {
    display_drawLine(x0, y_point0, x1, y_point1, DISPLAY_BLACK);
    display_drawLine(x1, y_point1, x2, y2, DISPLAY_BLACK);
    display_drawLine(x2, y2, x3, y3, DISPLAY_BLACK);
    display_drawLine(x3, y3, x0, y_point0, DISPLAY_BLACK);
    display_drawPixel(x0, y_point0, DISPLAY_BLACK);
    y_point0 = y_point0 + (int)y_velocity;
    y_point1 = y_point1 + (int)y_velocity;
    y2 = y2 + (int)y_velocity;
    y3 = y3 + (int)y_velocity;
    x0 = x0 + x_velocity;
    x1 = x1 + x_velocity;
    x2 = x2 + x_velocity;
    x3 = x3 + x_velocity;
    if (tick_fourth == 1) {
      tick_fourth = 0;
      // if statements to turn left
      if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
          (rotate == 0)) {
        x0 = x0 + 3;
        y_point0 = y_point0 - 1;
        x1 = x1 + 1;
        y_point1 = y_point1 + 3;
        x2 = x2 - 3;
        y2 = y2 + 1;
        x3 = x3 - 1;
        y3 = y3 - 3;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == 1)) {
        x0 = x0 + 2;
        y_point0 = y_point0 - 1;
        x1 = x1 + 1;
        y_point1 = y_point1 + 2;
        x2 = x2 - 2;
        y2 = y2 + 1;
        x3 = x3 - 1;
        y3 = y3 - 2;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == 2)) {
        x0 = x0 + 2;
        y_point0 = y_point0 + 1;
        x1 = x1 - 1;
        y_point1 = y_point1 + 2;
        x2 = x2 - 2;
        y2 = y2 - 1;
        x3 = x3 + 1;
        y3 = y3 - 2;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == 3)) {
        x0 = x0 + 3;
        y_point0 = y_point0 + 1;
        x1 = x1 - 1;
        y_point1 = y_point1 + 3;
        x2 = x2 - 3;
        y2 = y2 - 1;
        x3 = x3 + 1;
        y3 = y3 - 3;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == -1)) {
        x0 = x0 + 1;
        y_point0 = y_point0 - 3;
        x1 = x1 + 3;
        y_point1 = y_point1 + 1;
        x2 = x2 - 1;
        y2 = y2 + 3;
        x3 = x3 - 3;
        y3 = y3 - 1;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == -2)) {
        x0 = x0 + 1;
        y_point0 = y_point0 - 2;
        x1 = x1 + 2;
        y_point1 = y_point1 + 1;
        x2 = x2 - 1;
        y2 = y2 + 2;
        x3 = x3 - 2;
        y3 = y3 - 1;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == -3)) {
        x0 = x0 - 1;
        y_point0 = y_point0 - 2;
        x1 = x1 + 2;
        y_point1 = y_point1 - 1;
        x2 = x2 + 1;
        y2 = y2 + 2;
        x3 = x3 - 2;
        y3 = y3 + 1;
        rotate++;
      } else if (((button_value & BUTTONS_BTN0_MASK) == BUTTONS_BTN0_MASK) &&
                 (rotate == -4)) {
        x0 = x0 - 1;
        y_point0 = y_point0 - 3;
        x1 = x1 + 3;
        y_point1 = y_point1 - 1;
        x2 = x2 + 1;
        y2 = y2 + 3;
        x3 = x3 - 3;
        y3 = y3 + 1;
        rotate++;
      }

      // turn right calculations:
      else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
               (rotate == 1)) {
        x0 = x0 - 3;
        y_point0 = y_point0 + 1;
        x1 = x1 - 1;
        y_point1 = y_point1 - 3;
        x2 = x2 + 3;
        y2 = y2 - 1;
        x3 = x3 + 1;
        y3 = y3 + 3;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == 2)) {
        x0 = x0 - 2;
        y_point0 = y_point0 + 1;
        x1 = x1 - 1;
        y_point1 = y_point1 - 2;
        x2 = x2 + 2;
        y2 = y2 - 1;
        x3 = x3 + 1;
        y3 = y3 + 2;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == 3)) {
        x0 = x0 - 2;
        y_point0 = y_point0 - 1;
        x1 = x1 + 1;
        y_point1 = y_point1 - 2;
        x2 = x2 + 2;
        y2 = y2 + 1;
        x3 = x3 - 1;
        y3 = y3 + 2;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == 4)) {
        x0 = x0 - 3;
        y_point0 = y_point0 - 1;
        x1 = x1 + 1;
        y_point1 = y_point1 - 3;
        x2 = x2 + 3;
        y2 = y2 + 1;
        x3 = x3 - 1;
        y3 = y3 + 3;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == 0)) {
        x0 = x0 - 1;
        y_point0 = y_point0 + 3;
        x1 = x1 - 3;
        y_point1 = y_point1 - 1;
        x2 = x2 + 1;
        y2 = y2 - 3;
        x3 = x3 + 3;
        y3 = y3 + 1;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == -1)) {
        x0 = x0 - 1;
        y_point0 = y_point0 + 2;
        x1 = x1 - 2;
        y_point1 = y_point1 - 1;
        x2 = x2 + 1;
        y2 = y2 - 2;
        x3 = x3 + 2;
        y3 = y3 + 1;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == -2)) {
        x0 = x0 + 1;
        y_point0 = y_point0 + 2;
        x1 = x1 - 2;
        y_point1 = y_point1 + 1;
        x2 = x2 - 1;
        y2 = y2 - 2;
        x3 = x3 + 2;
        y3 = y3 - 1;
        rotate--;
      } else if (((button_value & BUTTONS_BTN3_MASK) == BUTTONS_BTN3_MASK) &&
                 (rotate == -3)) {
        x0 = x0 + 1;
        y_point0 = y_point0 + 3;
        x1 = x1 - 3;
        y_point1 = y_point1 + 1;
        x2 = x2 - 1;
        y2 = y2 - 3;
        x3 = x3 + 3;
        y3 = y3 - 1;
        rotate--;
      }
    }

    display_drawLine(x0, y_point0, x1, y_point1, DISPLAY_CYAN);
    display_drawLine(x1, y_point1, x2, y2, DISPLAY_CYAN);
    display_drawLine(x2, y2, x3, y3, DISPLAY_CYAN);
    display_drawLine(x3, y3, x0, y_point0, DISPLAY_CYAN);
    display_drawPixel(x0, y_point0, DISPLAY_YELLOW);

//this is where the velocities are actually being incremented given the calculated thrust values.
    y_velocity = y_velocity + gravity - (THRUST_SCALER *thrust_y);
    x_velocity = x_velocity + (THRUST_SCALER * thrust_x);
    
  }

  //change thrust value if button 1 is being pressed
  if((button_value & BUTTONS_BTN1_MASK) == BUTTONS_BTN1_MASK){
    thrust_x = get_thrust_x(&the_lander);
    thrust_y = get_thrust_y(&the_lander);
  } else {
    thrust_x = 0;
    thrust_y = 0;
  }
  tick_is_odd = !tick_is_odd;
  tick_fourth++;
}

Please forgive the poor coding practices, i am still testing concepts.

Here is the struct of how the lander is implemented.


void lander_init(struct lander_t *lander){
    lander->angle = 90;
//there going to be more values here, location etc, but I haven't implemented them yet.

}

Here is how the thrust and angles are being calculated.

static double cos_degrees(double theta){
    
    //convert radians to degrees
    theta = theta * (3.14 / 180);
    // printf("cos_degrees %f\n", cos(theta));
    return cos(theta);

}

static double sin_degrees(double theta){
    
    //convert radians to degrees
    theta = theta * (3.14 / 180);
    // printf("sin_degrees %f\n", sin(theta));
    return sin(theta);

}


//calculates the x and y thrust values given the current angle
double get_thrust_y(struct lander_t *lander){
    return sin_degrees(lander->angle);
}

double get_thrust_x(struct lander_t *lander){
    return cos_degrees(lander->angle);
}


//increment the angle 
void lean_left(struct lander_t *lander){
    if(lander->angle < 180){
    lander->angle += 10;
    }
}

//decrement the angle
void lean_right(struct lander_t *lander){
    if(lander->angle > 0){
    lander->angle -= 10;
    }
}

Any suggestions would be greatly appreciated! Thank you for your help! Has anyone else run into a similar problem in that their acceleration on a screen like this is always discrete?

here is a link to the github for the display driver that I am using. https://github.com/byu-cpe/ecen330_student/blob/main/include/display.h

I have tried changing the tick speed of the game to try and make it less noticeable but i'm not satisfied.

I was thinking there was some kind help drawing function that I could create that would take in a current acceleration or velocity value and somehow draw them smoother but I haven't been able to think through how that would work.


Solution

  • I was able to slow down the movement by alternating how often i was ticking the state machine that controlled the game. So for speeds that were between 0 and 1 I could create 3 speeds between them by ticking every 4th tick, or every third or every other and so on. This allowed me to make the movement slower and seem more analog instead of always adding in discrete pixels, since you are still added one pixel to the position but you are only adding it half as often instead of trying to increment every time.

    let me know if that doesn't make sense or if anyone else has other questions about it.