Which 3D engine to use?

Well, it’s time to pick which engine/sdk I am going to use to make my dice game.

Now the big question is what game engine to use. There may be many to choose from, but from a mobile app perspective, there seem to be only 3 that I would want to use. Of course, I’m ruling out Unity, because it is not open source. I’m trying to make an open source game, and I’d like to use open source tools so that anyone can openly tinker with it. I’m also ruling out several that work for Android, but the sdk is only available on Windows computers (irrlicht for example). That leaves the three main options of LibGDX, GoDot, Ogre, and jMonkeyEngine. There may be others, these are just the ones I know about.

Above is the quote from my post last week. I needed to do some research to decide which 3D engine and SDK (software development kit) to use to make my game. As I said, I’m sticking with open source tools, so that ruled out Unity and several other big name brands of SDK’s. I also am using a Linux computer, so it can’t be an SDK that only works on Windows or Mac. So, that leaves me with four main options that I could come up with. Obviously there are more options out there, but I’m trying to stick with ones that meet these criteria:

  1. Active development – Something that is still modern and will get updated over time, so I don’t have to learn a new SDK after I get the hang of this one.
  2. Guides and example code – I’m new to this, so I need it to have plenty of examples and guides to springboard off of.
  3. Active community – A place where I can go to ask questions if I get stuck.
  4. Easy Android integration – The whole point of this is to make an Android game, so it should be easy to get to that point.
  5. Java or C++ compatible – I don’t want to learn ANOTHER programming language just to make this game. I want to use and improve the tools I already have.
  6. Easy to use – It needs to make sense to me, or it will not work. If the API calls are ridiculous, I wont accomplish much.

So, with those in mind, I have sorted it out between my top four choices. Before I get to that, though, there are a lot of other options, but they don’t meet the above criteria, so I couldn’t spend my time on them, even though they may be great engines to use. That said, here is the breakdown of my thoughts on the top four choices, and what I plan to do. It’s not really a review, just my thoughts on what to do.

libGDX

I’ve actually used libGDX before, to make my open source game “Critical Velocity“, which was a 2D side scrolling space ship game similar to flappy bird. It does have an active community, is still being developed, and has lots of guides and tutorials and example code to draw from. It doesn’t have an SDK, you just add the java libraries to your app and use your standard Android SDK platform to build it, which is a big plus.

libGDX

The only downside is that it is made for 2D games with 3D kind of as an add on, which seems to still be under development. But, I would be familiar with it, since I’ve used it before, albeit for a 2D game. It also doesn’t have an “SDK” it is rather a library that you add to your own app.

jMonkeyEngine

There are quire a few perks to using jMonkeyEngine, it is actively being developed, and it seems to have a pretty good community behind it. There seem to be a lot of guides, documentation, and explanation of methods and classes, which is super helpful for me. It also seems to be a well fleshed out 3D engine, with a sustainable future and some neat features, most of which I am too new to understand. All the programming is done in Java, which is a plus for me, since it is a language that I am already familiar with.

jMonkeyEngine

I am not sure if I can see a downside to using this setup. It seems to be pretty solid, there are a lot of indie games made with it, and it even has book tutorials on Amazon, so this might be a good direction to go. I tried it out a little bit, and got confused right off the bat, but I’m not sure it that’s because of the interface, or because I am new to 3D game development.

Godot

Next up on the list is Godot. Hailed as “the game engine you waited for”, it seems pretty impressive. With a super permissive MIT license, an attractive interface, a visual editor, and full c++ support, this sounds too good to be open source true.

Godot

The only concern I have is that it will require some other languages as well. It seems to support c++, of which I am only a beginner, but used to. Yet it also uses GDScript as well, which is an open source scripting code they made just for this development SDK. Where I see that as a problem is it will probably only be useful inside of Godot.

Ogre

Last up is Ogre. Ogre seems to be very actively developed at the moment. So active, that it appears to have created some sort of rift between the different versions. From what I can gather by doing a little research (just my opinion based on what I saw), there is the old version Ogre1, which you can use to make Android games, and the new version OgreNext, which only works in Windows and Linux for now, with hopes of adding Android later.

If you use Ogre1, it appears to be more of a library that you add, like libGDX, but if you use OgreNext, it seems to be a full fledged SDK. So, I’m not sure about how I would want to use it.

It also appears that Ogre1 and OgreNext do not work the same, so if I learn Ogre1 to build my Android games, will I need to relearn OgreNext so I can continue with that in the future? Perhaps that’s just my perception, but my perception being my reality, I don’t think I can put in double the effort to learn how to make 3D games.

Final result

I think I am going to give Godot a try. However, if it doesn’t work out, jMonkeyEngine is the runner up. Ogre seems to be undergoing some massive changes, and I’d like to work with something that is a bit more stable, but libGDX doesn’t seem to have all the “bells and whistles”, seeing that it is just a library to add to your current development.

I struggled this week between jMonkeyEngine and Godot, but in the end settled with Godot because the interface was so intuitive. I was able to walk through a few things in no time. Literally, within minutes and without any documentation, I loaded someone else’s project and was playing a game and editing code. I am also looking to expand my c++ skills rather than work on my Java. So, Godot it is, at least, for now….

Linux – keep it simple.

Building a Pinewood Derby Timer: Finishing the arch and adding the last of the wiring!

Well, I finally finished it! The pinewood derby timer is done. Check it out!

First, I had to finish the wiring for the lights and the sensors, and then I had to finish the arch.

Then, I had to program it. Fortunately, https://www.dfgtec.com/ has a whole section on programming the board, and what settings you should change or adjust for your needs. Super helpful site!

Here is my final code:

/*================================================================================*
Pinewood Derby Timer Version 3.00 – 31 Oct 2016
http://www.dfgtec.com/pdt

Flexible and affordable Pinewood Derby timer that interfaces with the
following software:
– PD Test/Tune/Track Utility
– Grand Prix Race Manager software

Refer to the website for setup and usage instructions.
Copyright (C) 2011-2018 David Gadberry

This work is licensed under the Creative Commons Attribution-NonCommercial-
ShareAlike 3.0 Unported License. To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
94041, USA.
*================================================================================*/

/*—————————————–*
– TIMER CONFIGURATION –
*—————————————–*/
#define NUM_LANES 4 // number of lanes

#define LED_DISPLAY 1 // Enable lane place/time displays
//#define DUAL_DISP 1 // dual displays (front/back) per lane (4 lanes max)
//#define LARGE_DISP 1 // utilize large Adafruit displays (see website)
#define SHOW_PLACE 1 // Show place mode
#define PLACE_DELAY 4 // Delay (secs) when displaying place/time
#define MIN_BRIGHT 4 // minimum display brightness (0-15)
#define MAX_BRIGHT 10 // maximum display brightness (0-15)

#define GATE_RESET 1 // Enable closing start gate to reset timer
/*—————————————–*
– END –
*—————————————–*/
#ifdef LED_DISPLAY // LED control libraries
#include “Wire.h”
#include “Adafruit_LEDBackpack.h”
#include “Adafruit_GFX.h”
#endif

/*—————————————–*
– static definitions –
*—————————————–*/
#define PDT_VERSION “3.00” // software version
#define MAX_LANE 6 // maximum number of lanes (Uno)
#define MAX_DISP 8 // maximum number of displays (Adafruit)

#define mREADY 0 // program modes
#define mRACING 1
#define mFINISH 2
#define mTEST 3

#define START_TRIP LOW // start switch trip condition
#define NULL_TIME 99.999 // null (non-finish) time
#define NUM_DIGIT 4 // timer resolution (# of decimals)
#define DISP_DIGIT 4 // total number of display digits

#define PWM_LED_ON 220
#define PWM_LED_OFF 255
#define char2int(c) (c – ‘0’)

//
// serial messages <- to timer
// -> from timer
//
#define SMSG_ACKNW ‘.’ // -> acknowledge message

#define SMSG_POWER ‘P’ // -> start-up (power on or hard reset)

#define SMSG_CGATE ‘G’ // <- check gate
#define SMSG_GOPEN ‘O’ // -> gate open

#define SMSG_RESET ‘R’ // <- reset
#define SMSG_READY ‘K’ // -> ready

#define SMSG_SOLEN ‘S’ // <- start solenoid
#define SMSG_START ‘B’ // -> race started
#define SMSG_FORCE ‘F’ // <- force end
#define SMSG_RSEND ‘Q’ // <- resend race data

#define SMSG_LMASK ‘M’ // <- mask lane
#define SMSG_UMASK ‘U’ // <- unmask all lanes

#define SMSG_GVERS ‘V’ // <- request timer version
#define SMSG_DEBUG ‘D’ // <- toggle debug on/off
#define SMSG_GNUML ‘N’ // <- request number of lanes

/*—————————————–*
– pin assignments –
*—————————————–*/
byte BRIGHT_LEV = A0; // brightness level
byte RESET_SWITCH = 8; // reset switch
byte STATUS_LED_R = 9; // status LED (red)
byte STATUS_LED_B = 11; // status LED (blue)
byte STATUS_LED_G = 10; // status LED (green)
byte START_GATE = 12; // start gate switch
byte START_SOL = 13; // start solenoid

// Display # 1 2 3 4 5 6 7 8
int DISP_ADD [MAX_DISP] = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77}; // display I2C addresses

// Lane # 1 2 3 4 5 6
byte LANE_DET [MAX_LANE] = { 2, 3, 4, 5, 6, 7}; // finish detection pins

/*—————————————–*
– global variables –
*—————————————–*/
boolean fDebug = false; // debug flag
boolean ready_first; // first pass in ready state flag
boolean finish_first; // first pass in finish state flag

unsigned long start_time; // race start time (microseconds)
unsigned long lane_time [MAX_LANE]; // lane finish time (microseconds)
int lane_place [MAX_LANE]; // lane finish place
boolean lane_mask [MAX_LANE]; // lane mask status

int serial_data; // serial data
byte mode; // current program mode

float display_level = -1.0; // display brightness level

#ifdef LARGE_DISP
unsigned char msgGateC[] = {0x6D, 0x41, 0x00, 0x0F, 0x07}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x41, 0x00, 0x3F, 0x5E}; // S=OP
unsigned char msgLight[] = {0x41, 0x41, 0x00, 0x00, 0x07}; // == L
unsigned char msgDark [] = {0x41, 0x41, 0x00, 0x00, 0x73}; // == d
#else
unsigned char msgGateC[] = {0x6D, 0x48, 0x00, 0x39, 0x38}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x48, 0x00, 0x3F, 0x73}; // S=OP
unsigned char msgLight[] = {0x48, 0x48, 0x00, 0x00, 0x38}; // == L
unsigned char msgDark [] = {0x48, 0x48, 0x00, 0x00, 0x5e}; // == d
#endif
unsigned char msgDashT[] = {0x40, 0x40, 0x00, 0x40, 0x40}; // —-
unsigned char msgDashL[] = {0x00, 0x00, 0x00, 0x40, 0x00}; // –
unsigned char msgBlank[] = {0x00, 0x00, 0x00, 0x00, 0x00}; // (blank)

#ifdef LED_DISPLAY // LED display control
Adafruit_7segment disp_mat[MAX_DISP];
#endif

void initialize(boolean powerup=false);
void dbg(int, const char * msg, int val=-999);
void smsg(char msg, boolean crlf=true);
void smsg_str(const char * msg, boolean crlf=true);

/*================================================================================*
SETUP TIMER
*================================================================================*/
void setup()
{
/*—————————————–*
– hardware setup –
*—————————————–*/
pinMode(STATUS_LED_R, OUTPUT);
pinMode(STATUS_LED_B, OUTPUT);
pinMode(STATUS_LED_G, OUTPUT);
pinMode(START_SOL, OUTPUT);
pinMode(RESET_SWITCH, INPUT);
pinMode(START_GATE, INPUT);
pinMode(BRIGHT_LEV, INPUT);

digitalWrite(RESET_SWITCH, HIGH); // enable pull-up resistor
digitalWrite(START_GATE, HIGH); // enable pull-up resistor

digitalWrite(START_SOL, LOW);

#ifdef LED_DISPLAY
for (int n=0; n<MAX_DISP; n++)
{
disp_mat[n] = Adafruit_7segment();
disp_mat[n].begin(DISP_ADD[n]);
disp_mat[n].clear();
disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();
}
#endif

for (int n=0; n<MAX_LANE; n++)
{
pinMode(LANE_DET[n], INPUT);

digitalWrite(LANE_DET[n], HIGH); // enable pull-up resistor
}
set_display_brightness();

/*—————————————–*
– software setup –
*—————————————–*/
Serial.begin(9600);
smsg(SMSG_POWER);

/*—————————————–*
– check for test mode –
*—————————————–*/
if (digitalRead(RESET_SWITCH) == LOW)
{
mode = mTEST;
test_pdt_hw();
}

/*—————————————–*
– initialize timer –
*—————————————–*/
initialize(true);
unmask_all_lanes();
}
/*================================================================================*
MAIN LOOP
*================================================================================*/
void loop()
{
process_general_msgs();

switch (mode)
{
case mREADY:
timer_ready_state();
break;
case mRACING:
timer_racing_state();
break;
case mFINISH:
timer_finished_state();
break;
}
}
/*================================================================================*
TIMER READY STATE
*================================================================================*/
void timer_ready_state()
{
if (ready_first)
{
set_status_led();
clear_displays();

ready_first = false;
}

if (serial_data == int(SMSG_SOLEN)) // activate start solenoid
{
digitalWrite(START_SOL, HIGH);
smsg(SMSG_ACKNW);
}

if (digitalRead(START_GATE) == START_TRIP) // timer start
{
start_time = micros();

digitalWrite(START_SOL, LOW);

smsg(SMSG_START);
delay(100);

mode = mRACING;
}

return;
}

/*================================================================================*
TIMER RACING STATE
*================================================================================*/
void timer_racing_state()
{
int lanes_left, finish_order, lane_status[NUM_LANES];
unsigned long current_time, last_finish_time;
set_status_led();
clear_displays();

finish_order = 0;
last_finish_time = 0;

lanes_left = NUM_LANES;
for (int n=0; n<NUM_LANES; n++)
{
if (lane_mask[n]) lanes_left–;
}

while (lanes_left)
{
current_time = micros();

for (int n=0; n<NUM_LANES; n++) lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanes

for (int n=0; n<NUM_LANES; n++)
{
if (lane_time[n] == 0 && lane_status[n] == HIGH && !lane_mask[n]) // car has crossed finish line
{
lanes_left–;

lane_time[n] = current_time – start_time;

if (lane_time[n] > last_finish_time)
{
finish_order++;
last_finish_time = lane_time[n];
}
lane_place[n] = finish_order;

update_display(n, lane_place[n], lane_time[n], SHOW_PLACE);
}
}

serial_data = get_serial_data();

if (serial_data == int(SMSG_FORCE) || serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // force race to end
{
lanes_left = 0;
smsg(SMSG_ACKNW);
}
}

send_race_results();

mode = mFINISH;

return;
}
/*================================================================================*
TIMER FINISHED STATE
*================================================================================*/
void timer_finished_state()
{
if (finish_first)
{
set_status_led();
finish_first = false;
}

if (GATE_RESET && digitalRead(START_GATE) != START_TRIP) // gate closed
{
delay(500); // ignore any switch bounce

if (digitalRead(START_GATE) != START_TRIP) // gate still closed
{
initialize(); // reset timer
}
}

if (serial_data == int(SMSG_RSEND)) // resend race data
{
smsg(SMSG_ACKNW);
send_race_results();
}

set_display_brightness();
display_race_results();

return;
}
/*================================================================================*
PROCESS GENERAL SERIAL MESSAGES
*================================================================================*/
void process_general_msgs()
{
int lane;
char tmps[50];
serial_data = get_serial_data();

if (serial_data == int(SMSG_GVERS)) // get software version
{
sprintf(tmps, “vert=%s”, PDT_VERSION);
smsg_str(tmps);
}

else if (serial_data == int(SMSG_GNUML)) // get number of lanes
{
sprintf(tmps, “numl=%d”, NUM_LANES);
smsg_str(tmps);
}

else if (serial_data == int(SMSG_DEBUG)) // toggle debug
{
fDebug = !fDebug;
dbg(true, “toggle debug = “, fDebug);
}

else if (serial_data == int(SMSG_CGATE)) // check start gate
{
if (digitalRead(START_GATE) == START_TRIP) // gate open
{
smsg(SMSG_GOPEN);
}
else
{
smsg(SMSG_ACKNW);
}
}

else if (serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // timer reset
{
if (digitalRead(START_GATE) != START_TRIP) // only reset if gate closed
{
initialize();
}
else
{
smsg(SMSG_GOPEN);
}
}

else if (serial_data == int(SMSG_LMASK)) // lane mask
{
delay(100);
serial_data = get_serial_data();

lane = serial_data – 48;
if (lane >= 1 && lane <= NUM_LANES)
{
lane_mask[lane-1] = true;

dbg(fDebug, “set mask on lane = “, lane);
}
smsg(SMSG_ACKNW);
}

else if (serial_data == int(SMSG_UMASK)) // unmask all lanes
{
unmask_all_lanes();
smsg(SMSG_ACKNW);
}

return;
}
/*================================================================================*
TEST PDT FINISH DETECTION HARDWARE
*================================================================================*/
void test_pdt_hw()
{
int lane_status[NUM_LANES];
char ctmp[10];
smsg_str(“TEST MODE”);
set_status_led();
delay(2000);

/*—————————————–*
show status of lane detectors
*—————————————–*/
while(true)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanes

if (lane_status[n] == HIGH)
{
update_display(n, msgDark); smsg_str(“Dark”); smsg_str(n);
}
else
{
update_display(n, msgLight); smsg_str(“Light”); smsg_str(n);
}
}

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}

/*—————————————–*
show status of start gate switch
*—————————————–*/
while(true)
{
if (digitalRead(START_GATE) == START_TRIP)
{
update_display(0, msgGateO);
}
else
{
update_display(0, msgGateC);
}

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}

/*—————————————–*
show pattern for brightness adjustment
*—————————————–*/
while(true)
{
#ifdef LED_DISPLAY
set_display_brightness();

for (int n=0; n<NUM_LANES; n++)
{
sprintf(ctmp,”%04d”, (int)display_level);

disp_mat[n].clear();

disp_mat[n].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n].writeDigitNum(4, char2int(ctmp[3]), false);

disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();

#ifdef DUAL_DISP
disp_mat[n+4].clear();

disp_mat[n+4].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n+4].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n+4].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n+4].writeDigitNum(4, char2int(ctmp[3]), false);

disp_mat[n+4].drawColon(false);
disp_mat[n+4].writeDisplay();
#endif
}
#endif

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
break;
}
delay(1000);
}
}
/*================================================================================*
SEND RACE RESULTS TO COMPUTER
*================================================================================*/
void send_race_results()
{
float lane_time_sec;
for (int n=0; n<NUM_LANES; n++) // send times to computer
{
lane_time_sec = (float)(lane_time[n] / 1000000.0); // elapsed time (seconds)

if (lane_time_sec == 0) // did not finish
{
lane_time_sec = NULL_TIME;
}

Serial.print(n+1);
Serial.print(” – “);
Serial.println(lane_time_sec, NUM_DIGIT); // numbers are rounded to NUM_DIGIT
// digits by println function
}

return;
}
/*================================================================================*
RACE FINISHED – DISPLAY PLACE / TIME FOR ALL LANES
*================================================================================*/
void display_race_results()
{
unsigned long now;
static boolean display_mode;
static unsigned long last_display_update = 0;
if (!SHOW_PLACE) return;

now = millis();

if (last_display_update == 0) // first cycle
{
last_display_update = now;
display_mode = false;
}

if ((now – last_display_update) > (unsigned long)(PLACE_DELAY * 1000))
{
dbg(fDebug, “display_race_results”);

for (int n=0; n<NUM_LANES; n++)
{
update_display(n, lane_place[n], lane_time[n], display_mode);
}

display_mode = !display_mode;
last_display_update = now;
}

return;
}
/*================================================================================*
SEND MESSAGE TO DISPLAY
*================================================================================*/
void update_display(int lane, unsigned char msg[])
{

#ifdef LED_DISPLAY
disp_mat[lane].clear();
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
#endif

for (int d = 0; d<=4; d++)
{
disp_mat[lane].writeDigitRaw(d, msg[d]);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(d, msg[d]);
#endif
}

disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
#endif

return;
}
/*================================================================================*
UPDATE LANE PLACE/TIME DISPLAY
*================================================================================*/
void update_display(int lane, int display_place, unsigned long display_time, int display_mode)
{
int c;
char ctime[10], cplace[4];
double display_time_sec;
boolean showdot;

// dbg(fDebug, “led: lane = “, lane);
// dbg(fDebug, “led: plce = “, display_place);
// dbg(fDebug, “led: time = “, display_time);

#ifdef LED_DISPLAY
if (display_mode)
{
if (display_place > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);

sprintf(cplace,”%1d”, display_place);
disp_mat[lane].writeDigitNum(3, char2int(cplace[0]), false);

disp_mat[lane].writeDisplay();

#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
disp_mat[lane+4].writeDigitNum(3, char2int(cplace[0]), false);
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashL);
}
}
else
{
if (display_time > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
#endif

display_time_sec = (double)(display_time / (double)1000000.0); // elapsed time (seconds)
dtostrf(display_time_sec, (DISP_DIGIT+1), DISP_DIGIT, ctime); // convert to string

// Serial.print(“ctime = [“); Serial.print(ctime); Serial.println(“]”);
c = 0;
for (int d = 0; d<DISP_DIGIT; d++)
{
#ifdef LARGE_DISP
showdot = false;
#else
showdot = (ctime[c + 1] == ‘.’);
#endif
disp_mat[lane].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#endif

c++; if (ctime[c] == ‘.’) c++;
}

#ifdef LARGE_DISP
disp_mat[lane].writeDigitRaw(2, 16);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(2, 16);
#endif
#endif

disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashT);
}
}
#endif

return;
}
/*================================================================================*
CLEAR LANE PLACE/TIME DISPLAYS
*================================================================================*/
void clear_displays()
{
dbg(fDebug, “led: CLEAR”);

for (int n=0; n<NUM_LANES; n++)
{
if (mode == mRACING || mode == mTEST)
{
update_display(n, msgBlank); // racing
}
else
{
update_display(n, msgDashT); // ready
}
}

return;
}
/*================================================================================*
SET LANE DISPLAY BRIGHTNESS
*================================================================================*/
void set_display_brightness()
{
float new_level;

#ifdef LED_DISPLAY
new_level = long(1023 – analogRead(BRIGHT_LEV)) / 1023.0F * 15.0F;
new_level = min(new_level, (float)MAX_BRIGHT);
new_level = max(new_level, (float)MIN_BRIGHT);

if (fabs(new_level – display_level) > 0.3F) // deadband to prevent flickering
{ // between levels
dbg(fDebug, “led: BRIGHT”);

display_level = new_level;

for (int n=0; n<NUM_LANES; n++)
{
disp_mat[n].setBrightness((int)display_level);
#ifdef DUAL_DISP
disp_mat[n+4].setBrightness((int)display_level);
#endif
}
}
#endif

return;
}
/*================================================================================*
SET TIMER STATUS LED
*================================================================================*/
void set_status_led()
{
int r_lev, b_lev, g_lev;

dbg(fDebug, “status led = “, mode);

r_lev = PWM_LED_OFF;
b_lev = PWM_LED_OFF;
g_lev = PWM_LED_OFF;

if (mode == mREADY) // blue
{
b_lev = PWM_LED_ON;
}
else if (mode == mRACING) // green
{
g_lev = PWM_LED_ON;
}
else if (mode == mFINISH) // red
{
r_lev = PWM_LED_ON;
}
else if (mode == mTEST) // yellow
{
r_lev = PWM_LED_ON;
g_lev = PWM_LED_ON;
}

analogWrite(STATUS_LED_R, r_lev);
analogWrite(STATUS_LED_B, b_lev);
analogWrite(STATUS_LED_G, g_lev);

return;
}
/*================================================================================*
READ SERIAL DATA FROM COMPUTER
*================================================================================*/
int get_serial_data()
{
int data = 0;

if (Serial.available() > 0)
{
data = Serial.read();
dbg(fDebug, “ser rec = “, data);
}

return data;
}
/*================================================================================*
INITIALIZE TIMER
*================================================================================*/
void initialize(boolean powerup)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_time[n] = 0;
lane_place[n] = 0;
}

start_time = 0;
set_status_led();
digitalWrite(START_SOL, LOW);

// if power up and gate is open -> goto FINISH state
if (powerup && digitalRead(START_GATE) == START_TRIP)
{
mode = mFINISH;
}
else
{
mode = mREADY;

smsg(SMSG_READY);
delay(100);
}
Serial.flush();

ready_first = true;
finish_first = true;

return;
}
/*================================================================================*
UNMASK ALL LANES
*================================================================================*/
void unmask_all_lanes()
{
dbg(fDebug, “unmask all lanes”);

for (int n=0; n<NUM_LANES; n++)
{
lane_mask[n] = false;
}

return;
}
/*================================================================================*
SEND DEBUG TO COMPUTER
*================================================================================*/
void dbg(int flag, const char * msg, int val)
{
char tmps[50];
if (!flag) return;

smsg_str(“dbg: “, false);
smsg_str(msg, false);

if (val != -999)
{
sprintf(tmps, “%d”, val);
smsg_str(tmps);
}
else
{
smsg_str(“”);
}

return;
}
/*================================================================================*
SEND SERIAL MESSAGE (CHAR) TO COMPUTER
*================================================================================*/
void smsg(char msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}

return;
}
/*================================================================================*
SEND SERIAL MESSAGE (STRING) TO COMPUTER
*================================================================================*/
void smsg_str(const char * msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}

return;
}

A little long for posting here, but I wanted to post any changes I had made in case it helps someone else! Now the timers work, alternating between places and time per track/car! This was a really fun project, and I highly recommend it to anyone looking for a fun electronics project to work on.

I might still dress it up a bit, but it is fully functional as is, now.

Linux – keep it simple.

Building a Pinewood Derby Timer: Wiring in the displays

Now that I have the displays mounted on the pinewood derby timer, it was time to wire them in. I still don’t have lights or sensors in yet, but I thought I could just hook up the displays for now. It seemed to work well!

I am using the schematics per the https://www.dfgtec.com/ website. Fortunately, the creator of the schematics, David Gadberry was kind enough to give it a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License, so guys like me can tinker with it!

Linux – keep it simple.

Building a Pinewood Derby Timer: 3D printing some display covers

Well, I decided to print a few display covers to go around the LED displays. This covers up the unsightly hole that I cut from the box, and seems to “class up” the displays a bit. I like the way they turned out. If I could post files here, I’d post the gcode and openCad files, but the free version of WordPress doesn’t work well that way.

Either way, took me only two tries to find a style I liked, and a couple hours total to complete this part of the project.

Linux – keep it simple.

Building a Pinewood Derby Timer: The Box for the “Arch”

As I have been constructing this Pinewood Derby Timer, I needed some sort of box or enclosure to make the top of the arch. So I took apart a junk piece of equipment and cut out holes for the LED displays to go into it. I also drilled some holes for the LED’s to shine out the bottom and some holes for the Arduino card USB interface and power adapter plug on the side.

The box was originally black, and I tried to paint it white. Unfortunately, all the paint peeled, and I ended up grinding it down to bare metal. I’m not sure if I should paint it or not now. I guess I’ll have to see the end result.

I also am not sure if I’ll just put some sort of “face” on it, or fake front to cover it up. The holes didn’t cut perfectly rectangular, so I want to cover that up somehow. I’ll have to think on it. Maybe I can use the ol’ 3D printer to come up with something.

Linux – keep it simple.

Building a Pinewood Derby Timer: Setting up the LED displays

So one of the driving components (pun intended) of the derby timer is the LED displays. I went with the smaller ones from Adafruit, as we wouldn’t need a second power supply if we go this route, and I’d like to keep the unit as simple as possible.

You have to solder the displays to the backer board, and then you have to solder jumper pins across them to serialize each display. If you don’t do this part, your displays all show the same thing! I found that out the hard way! So be sure to solder those little jumpers. For convenience, the PDT website: https://www.dfgtec.com/pdt

has a reference table:

PDT Lane # PDT Lane # I2C Address # Jumpers
A0 A1 A2
1 1 F 0
2 2 F 1 x
3 3 F 2 x
4 4 F 3 x x
5 1 B 4 x
6 2 B 5 x x
3 B 6 x x
4 B 7 x x x

Key: – = open, x = shorted; F = front, B = back

This is really handy. I really appreciate all the work they put into this, and it is fun to build along with a guide that covers about 95% of the process!

Linux – keep it simple.

Building a Pinewood Derby Timer: Getting the schematics and parts list…

A local church group that I attend needed some help with an Awana project. They use a pinewood derby track for racing cars, and they needed a timer built for it. I don’t attend the Awana club usually, and I was not present for the races last time they held them, but I guess they borrowed a track and timer for those races. Now they bought their own track, and need a timer for it.

I priced the times out online, you can buy some nice pre-made ones for about $350-$400. That’s a lot of money. After a little research, I found we could make one for about $85! So, here I am, building a timer.

I found a lot of reference material out there for making your own timer, but the best by far was here: https://www.dfgtec.com/pdt 

It is a great and informative website that uses an Ardiuno Uno powered configuration with several parts from Adafruit and elsewhere. They have done all the heavy lifting on the build and, best of all, their schematics were open source! So, I went with it.

5

They sold some of the parts on their website, and others you had to get elsewhere, but it was very clean and informative. They sold this PDT shield for the Arduino, and then you have to solder all the parts onto it, which was pretty fun to do, as the shield made everything a lot easier to maintain.

Now I just have to finish putting it together!

Linux – keep it simple.

Arduino Asteroids Game: Randomly!

game3

I wanted to post a video, but sadly the free version of WordPress doesn’t allow this. So, you can click here to watch the video of me playing “Asteroids Game!” on my Uno with the LCD Keypad Shield. Sorry for the low quality of the video, but I’m sure you’ll enjoy it all the same.

The game is complete, and you are welcome to head over to my GitLab and download the ino file and compile it yourself. Remember, you will need the LCD Keypad Shield library as well. Some stats about the game and program:

Sketch uses 7510 bytes (23%) of program storage space. Maximum is 32256 bytes.
Global variables use 473 bytes (23%) of dynamic memory, leaving 1575 bytes for local variables. Maximum is 2048 bytes.

Also, the entire game is programmed in less than 600 lines (would be significantly less without all the explanation blocks I put in there), and the ino file is less than 20 kb!

One of the biggest parts of the game is making sure that the asteroids show up in a random fashion. With only 4 asteroids, it is important that the pattern is not discernible by the player, or they will easily be able to win over and over again. Here is how I called it:

gameAsteroidOne = get_random(4);

I am calling the “get_random” function with an integer (in this case) of 4. That will make more sense once I show you the function:

int get_random (int addOn) {
srand(millis()+addOn); // Use time since turned on and add on number as seed for random number.
int r = rand() % 10; // Returns a random integer between 0 and 10.
r += 10; // Add 10 to keep it off of the display screen.
return r;
}

You see, computers can’t actually make random numbers, so you have to “seed” it with something to grow a number from. As a “seed” I input the time in milliseconds that the game has been running. From this it makes a “random” number from 1 to 10. It then adds 10 to that number to place the asteroid off of the screen, giving the appearance that it existed before and slowly came into view.

The problem is that sometimes the game loop runs fast enough where two asteroids are asking for a new random number at the exact same time. If they do, they will both receive the exact same “random” number. This will cause them to “overlap” or be in the same physical spot, which means that they will both go off screen at the same time, and request a new random number at the same time, receiving the same “random” number again. It’s a loop.

To solve this, I added the integer “addOn” to the milliseconds to make a new number. This “addOn” number is passed with each asteroid, and each one has a different number, creating a new instance of “seed” for the random number generator. That way no two should return the exact same “random” number. Although it can still happen.

Just like rolling two dice can sometimes give you the same number, the random function can still randomly return the same number. The smaller the number pool, a six sided dice would have a pool of 6, our game has a pool of 10, the more likely a repeat of a random number happens.

Another case scenario is when asteroid one, for say, goes off screen, while asteroid two is 2 steps behind. Asteroid one gets a random number of, let’s say 6, which added to 10 makes 16. Now the asteroids move. One is 15, two is 1. Then one is 14, and two is 0. Then one is 13, and two falls off screen, getting a new number, which randomly could be 3, which, when added to 10, makes 13, and the two asteroids now occupy the same space.

A programmer could add a check for this, and if the asteroids are in the same space, cause a new random number to be chosen, or add or subtract a number to it, but in this simple game, it happens so rarely that it isn’t really a problem. The player just gets a bonus when those two asteroids pass next time.

Either way, I hope that you have enjoyed the game itself, and more so the creation of it. The game is open source and feel free to put it to good use!

Linux – keep it simple.

Arduino Asteroids Game: Play mode and pause state!

Today was the big push! It took a while to figure out how to implement what I wanted to do, but now it’s done! The game play mode and pause state are completed! You can, of course, check out the full commits on my GitLab, but I plan to go over some of the most important parts here.

if (gameState == 3) { // Play state.
// The player has entered play mode.

gameTime++;
if (gameTime > 1){
gameTime = 0;
gamePaused = false;
}

This portion allows the game to pause. An odd occurrence is that you cannot press the pause button and release it fast enough. It will essentially get pressed and check that it is pressed about 1000 times per second, so I needed to add an “unpause” delay, and this is how I did it. If the buttons were software buttons, I could have done this differently, but they are physical buttons, so I needed this to make it work every time.

gameShoot = true; // you have not shot yet this round.
gameAsteroidOne–; // First asteroid starting space.
gameAsteroidTwo–; // Second asteroid starting space.
gameAsteroidThree–; // Third asteroid starting space.
gameAsteroidFour–; // Fourth Asteroid starting space.

Here, we increment (or move) each asteroid by one space.

if (gameAsteroidOne == -1) {
if (gamePosition == 0) {
// Game over, you lose.
gameState = 6;
gameLineNum = 0;
} else {
gameScore++; // get a point!
gameSpeed = set_gameSpeed(gameScore);
gameAsteroidOne = get_random(4);
}
} // end gameAsteroidOne.

In this block, I am figuring out everything for asteroid number one. So, if it’s position (which we moved just the block before this) is now -1, then it is at the position on the screen where the player’s ship is. It now checks if it is on the same row as the ship (gamePosition is the position of the ship, 0 for upper, 1 for lower). If they both are on the same row, then a collision has occurred. So, you switch to the lose screen.

If they are not on the same row, then the player gets a point for passing the asteroid. Then the game speed is increased (there are some other factors that change it as well), and the asteroid is moved to a random new location.

if (gameAsteroidTwo == -1) {
if (gamePosition == 0) {
// Game over, you lose.
gameState = 6;
gameLineNum = 0;
} else {
gameScore++; // get a point!
gameSpeed = set_gameSpeed(gameScore);
gameAsteroidTwo = get_random(2);
}
} // end gameAsteroidTwo.

if (gameAsteroidThree == -1) {
if (gamePosition == 1) {
// Game over, you lose.
gameState = 6;
gameLineNum = 0;
} else {
gameScore++; // get a point!
gameSpeed = set_gameSpeed(gameScore);
gameAsteroidThree = get_random(3);
}
} // end gameAsteroidThree.

if (gameAsteroidFour == -1) {
if (gamePosition == 1) {
// Game over, you lose.
gameState = 6;
gameLineNum = 0;
} else {
gameScore++; // get a point!
gameSpeed = set_gameSpeed(gameScore);
gameAsteroidFour = get_random(1);
}
} // end gameAsteroidFour.

if (gameScore > 99){
// You win! Great job!
gameState = 5;
gameLineNum = 0;
}

These are the portions for asteroids two through four, which work identically to asteroid one. Note that there are two asteroids on row 0, and two asteroids on row 1. Four asteroids total. It seems rather limited, but it is appropriate for such a small screen.

// Set up our display.
// The space ship
String shipUpper = ” “;
String shipLower = ” “;
if (gamePosition == 0){
shipUpper = “>”;
shipLower = ” “;
} else {
shipUpper = ” “;
shipLower = “>”;
}

Here we check the location of the player’s space ship and set the upper and lower icons appropriate for the display.

// Upper blocks.
lcd.setCursor(0,0);
lcd.print(“00”);
lcd.setCursor(2,0);
lcd.print(gameBullets);
lcd.setCursor(3,0);
lcd.print(“|”);
lcd.setCursor(4,0);
lcd.print(shipUpper);

for (int a = 5; a < 17; a++){
lcd.setCursor(a,0);
if (a == gameAsteroidOne+5){
lcd.print(“*”);
} else if (a == gameAsteroidTwo+5){
lcd.print(“*”);
} else {
lcd.print(” “);
}
}

Now we draw the upper blocks. As you can see, I just set the LCD cursor to the correct spot and print the text to display what I want displayed. The “for” loop cycles through the remaining display area, or “asteroid field” and checks for asteroids. If there is one, it displays an asterisk, if not, then a blank space.

// Lower blocks.
lcd.setCursor(0,1);
lcd.print(gameScore);
if (gameScore < 10) {
lcd.setCursor(1,1);
lcd.print(” “);
} else if (gameScore < 100) {
lcd.setCursor(2,1);
lcd.print(” “);
}

lcd.setCursor(3,1);
lcd.print(“|”);
lcd.setCursor(4,1);
lcd.print(shipLower);

for (int a = 5; a < 17; a++){
lcd.setCursor(a,1);
if (a == gameAsteroidThree+5){
lcd.print(“*”);
} else if (a == gameAsteroidFour+5){
lcd.print(“*”);
} else {
lcd.print(” “);
}
}

Here we draw the lower line of the screen. It works basically the same as the upper line, but with the score instead of the number of bullets.

gameDelay = 0;
while (gameDelay < 1000){
// Add a delay in the loop.
delay(1);
gameDelay+=gameSpeed;
lcd_key = read_LCD_buttons(); // read the buttons

switch (lcd_key){

case btnRIGHT:{
// Shoot command
if (gameBullets > 0 && gameShoot) {
if (gamePosition == 0){
lcd.setCursor(5,0);
lcd.print(“————“);
gameBullets–;
gameShoot = false; // You already shot.
gameAsteroidOne = get_random(4);
gameAsteroidTwo = get_random(2);
} else {
lcd.setCursor(5,1);
lcd.print(“————“);
gameBullets–;
gameShoot = false; // You already shot.
gameAsteroidThree = get_random(3);
gameAsteroidFour = get_random(1);
}
}
break;
}
case btnLEFT:{
if (!gamePaused) {
gameState = 4;
gameLineNum = 0;
gamePaused = true;
}
break;
}
case btnUP:{
gamePosition = 0;
break;
}
case btnDOWN:{
gamePosition = 1;
break;
}
case btnSELECT:{
gameState = 0;
gameLineNum = 0;
break;
}
}
}
} // End gameState 3, play state.

The remainder is the delayed “while” loop, which checks the status of the buttons every millisecond and increments towards ending this “round” of display. Because it is using a counter that is based on game speed, the game will speed up as the game increases. At first, an entire game loop takes about 1 second, then .5 seconds, then .33 seconds, and finally, .25 seconds towards the end of the game.

The pause mode is much simpler:

if (gameState == 4) { // Pause state.
// The player paused the game.
lcd_key = read_LCD_buttons(); // read the buttons

switch (lcd_key){

case btnLEFT:{
gameState = 3;
gameLineNum = 0;
break;
}
case btnSELECT:{
gameState = 0;
gameLineNum = 0;
break;
}
}
} // End gameState 4, pause state.

The pause mode doesn’t actually draw anything, which is good, because then the screen displays what it was displaying last. Hence the game still visually appears, but no calculations are made to move anything.

Next post I’ll discuss the random number generator and some background information, as well as try to post a video of the game being played. If you have an LCD Keypad Shield and Arduino Uno, feel free to download my GitLab repository and play the game! You will need the LCD Keypad Shield library as well. Be sure to let me know if you tried it out!

Linux – keep it simple.

Arduino Asteroids Game: Win some, Lose some!

It isn’t really a game if you can’t win or lose. You need some driving force to make the game worth playing. With that, I also needed a way to inform the user that they either won or lost the game. Hence enter these win and lose game states. Almost identical to the credits or information screens, these win/lose screens just display that the winner won or the loser lost.

Feel free to check out the latest commit on this, but there isn’t much to see that is different from the info/credits commits.

Linux – keep it simple.