Updates and New Features

- JOYSTICK_DEBUG was left on...turned it off
 - Web page fields now have
type='number' so they bring up a number keyboard
 - Refresh the
brightness when it changes.
 - More Screen Savers
 - Better looking
reset after gameover
 - Better looking restart after boss kill
 -
Updated readme with video and latest info
This commit is contained in:
bdring
2018-05-01 12:49:05 -05:00
parent 56d8127345
commit 2a480255e0
2 changed files with 322 additions and 167 deletions

View File

@@ -3,6 +3,8 @@ An ESP32 based, 1D, LED strip, dungeon crawler. inspired by Line Wobbler by Robi
This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring of [Buildlog.net Blog](http://www.buildlog.net/blog?s=twang) This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring of [Buildlog.net Blog](http://www.buildlog.net/blog?s=twang)
[![Youtube Video](http://www.buildlog.net/blog/wp-content/uploads/2018/05/vid_thumb.png)](https://www.youtube.com/watch?v=RXpfa-ZvUMA)
![TWANG LED Game](http://www.buildlog.net/blog/wp-content/uploads/2018/01/20180111_130909-1.jpg?s=200) ![TWANG LED Game](http://www.buildlog.net/blog/wp-content/uploads/2018/01/20180111_130909-1.jpg?s=200)
## Why ESP32? ## Why ESP32?
@@ -31,8 +33,12 @@ This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring
![](http://www.buildlog.net/blog/wp-content/uploads/2018/03/20180328_122254.jpg) ![](http://www.buildlog.net/blog/wp-content/uploads/2018/03/20180328_122254.jpg)
## Coming Soon: ## TO DO List:
<<<<<<< HEAD
- Wireless features~~
- 2 Player features by linking controllers. TBD
=======
- Setting - Setting
- I want to figure out a way to have the LED count be set via the web server. I often bring several length LED strings to an event because I don't know which size is most appropriate. A last minute recompile is not a good solution. - I want to figure out a way to have the LED count be set via the web server. I often bring several length LED strings to an event because I don't know which size is most appropriate. A last minute recompile is not a good solution.
- Wireless features~~ - Wireless features~~
@@ -46,7 +52,18 @@ This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring
- More robust. - More robust.
- Integrated audio amplifier. - Integrated audio amplifier.
- Python (it might be fun to make a Python version) - Python (it might be fun to make a Python version)
>>>>>>> origin/master
- Digitized Audio
- Currently the port uses the same square wave tones of the the Arduino version.
- I want to convert to digitized high quality sound effects.
- Possibly mix multiple sounds so things like lava and movement sound good at the same time.
- Better looking mobile web interface (looks more like a web app)
- Python (it might be fun to make a Python version)
**BTW:** Since you have red this far... If you want to contribute, contact me and I might be able to get you some free or discounted hardware.
## Required libraries: ## Required libraries:
* [FastLED](http://fastled.io/) * [FastLED](http://fastled.io/)
@@ -61,12 +78,12 @@ This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring
See [Buildlog.net Blog](http://www.buildlog.net/blog?s=twang) for more details. See [Buildlog.net Blog](http://www.buildlog.net/blog?s=twang) for more details.
Super easy to use kits and ready to play units will be available on Tindie soon. Super easy to use kits and ready to play units [are available on Tindie](https://www.tindie.com/products/33366583/twang32-led-strip-game/).
![TWANG 32 Controller](http://www.buildlog.net/blog/wp-content/uploads/2018/03/20180319_080636.jpg) ![TWANG 32 Controller](http://www.buildlog.net/blog/wp-content/uploads/2018/03/20180319_080636.jpg)
## Enclosure ## Enclosure
They will be on Thingiverse soon. [The STL files are here](http://www.buildlog.net/blog/wp-content/uploads/2018/04/twang32_stl.zip).
![TWANG32](http://www.buildlog.net/blog/wp-content/uploads/2018/03/twang32_enclosure.jpg) ![TWANG32](http://www.buildlog.net/blog/wp-content/uploads/2018/03/twang32_enclosure.jpg)
@@ -118,4 +135,8 @@ They all call different functions and variables to setup the level. Each one is
* speed: The direction and speed of the travel. Negative moves to base and positive moves towards exit. Must be less than +/- max player speed. * speed: The direction and speed of the travel. Negative moves to base and positive moves towards exit. Must be less than +/- max player speed.
**spawnBoss();** (only one, don't edit boss level) **spawnBoss();** (only one, don't edit boss level)
<<<<<<< HEAD
* There are no parameters for a boss, they always spawn in the same place and have 3 lives. Tweak the values of Boss.h to modify * There are no parameters for a boss, they always spawn in the same place and have 3 lives. Tweak the values of Boss.h to modify
=======
* There are no parameters for a boss, they always spawn in the same place and have 3 lives. Tweak the values of Boss.h to modify
>>>>>>> origin/master

View File

@@ -9,13 +9,13 @@
It was inspired by Robin Baumgarten's Line Wobbler Game It was inspired by Robin Baumgarten's Line Wobbler Game
Recent Changes Recent Changes
- fixed bug in settings when initial read failed. It was - JOYSTICK_DEBUG was left on...turned it off
trying to turn off the sound timer while it was still NULL - Web page fields now have type='number' so they bring up a number keyboard
- Number of LEDs is now adjustable via serial and wifi. Woot, no - Refresh the brightness when it changes.
recompile just to change strip length. - More Screen Savers
- A little refactoring so Wifi and Serial interfaces can share - Better looking reset after gameover
some code. - Better looking restart after boss kill
- Added Refresh button to web page - Updated readme with video and latest info
*/ */
@@ -45,7 +45,7 @@
#define VIRTUAL_LED_COUNT 1000 #define VIRTUAL_LED_COUNT 1000
// what type of LED Strip....pick one // what type of LED Strip....uncomment only one
#define USE_APA102 #define USE_APA102
//#define USE_NEOPIXEL //#define USE_NEOPIXEL
@@ -303,10 +303,12 @@ void loop() {
{ {
FastLED.clear(); FastLED.clear();
save_game_stats(false); // boss not killed save_game_stats(false); // boss not killed
//score = 0; // reset the score
levelNumber = 0; // restart from the beginning
stage = STARTUP;
stageStartTime = millis();
lives = user_settings.lives_per_level; lives = user_settings.lives_per_level;
loadLevel();
} }
} }
@@ -322,6 +324,7 @@ void loop() {
// --------------------------------- // ---------------------------------
void loadLevel(){ void loadLevel(){
// leave these alone // leave these alone
FastLED.setBrightness(user_settings.led_brightness);
updateLives(); updateLives();
cleanupLevel(); cleanupLevel();
playerAlive = 1; playerAlive = 1;
@@ -399,13 +402,13 @@ void loadLevel(){
break; break;
case 2: case 2:
// Spawning enemies at exit every 2 seconds // Spawning enemies at exit every 2 seconds
spawnPool[0].Spawn(VIRTUAL_LED_COUNT, 3000, 2, 0, 0); spawnPool[0].Spawn(1000, 3000, 2, 0, 0);
break; break;
case 3: case 3:
// Lava intro // Lava intro
spawnLava(400, 490, 2000, 2000, 0, Lava::OFF); spawnLava(400, 490, 2000, 2000, 0, Lava::OFF);
spawnEnemy(350, 0, 1, 0); spawnEnemy(350, 0, 1, 0);
spawnPool[0].Spawn(VIRTUAL_LED_COUNT, 5500, 3, 0, 0); spawnPool[0].Spawn(1000, 5500, 3, 0, 0);
break; break;
case 4: case 4:
@@ -432,7 +435,7 @@ void loadLevel(){
break; break;
case 7: case 7:
// Conveyor of enemies // Conveyor of enemies
spawnConveyor(50, VIRTUAL_LED_COUNT, 6); spawnConveyor(50, 1000, 6);
spawnEnemy(300, 0, 0, 0); spawnEnemy(300, 0, 0, 0);
spawnEnemy(400, 0, 0, 0); spawnEnemy(400, 0, 0, 0);
spawnEnemy(500, 0, 0, 0); spawnEnemy(500, 0, 0, 0);
@@ -442,19 +445,19 @@ void loadLevel(){
spawnEnemy(900, 0, 0, 0); spawnEnemy(900, 0, 0, 0);
break; break;
case 8: // spawn train; case 8: // spawn train;
spawnPool[0].Spawn(900, VIRTUAL_LED_COUNT, 3, 0, 0); spawnPool[0].Spawn(900, 1300, 2, 0, 0);
break; break;
case 9: // spawn train skinny width; case 9: // spawn train skinny attack width;
attack_width = 32; attack_width = 32;
spawnPool[0].Spawn(900, 1800, 2, 0, 0); spawnPool[0].Spawn(900, 1800, 2, 0, 0);
break; break;
case 10: // evil fast split spawner case 10: // evil fast split spawner
spawnPool[0].Spawn(550, 1100, 3, 0, 0); spawnPool[0].Spawn(550, 1500, 2, 0, 0);
spawnPool[1].Spawn(550, 1100, 3, 1, 0); spawnPool[1].Spawn(550, 1500, 2, 1, 0);
break; break;
case 11: // split spawner with exit blocking lava case 11: // split spawner with exit blocking lava
spawnPool[0].Spawn(500, 1100, 3, 0, 0); spawnPool[0].Spawn(500, 1200, 2, 0, 0);
spawnPool[1].Spawn(500, 1100, 3, 1, 0); spawnPool[1].Spawn(500, 1200, 2, 1, 0);
spawnLava(900, 950, 2200, 800, 2000, Lava::OFF); spawnLava(900, 950, 2200, 800, 2000, Lava::OFF);
break; break;
case 12: case 12:
@@ -462,21 +465,22 @@ void loadLevel(){
spawnLava(195, 300, 2000, 2000, 0, Lava::OFF); spawnLava(195, 300, 2000, 2000, 0, Lava::OFF);
spawnLava(400, 500, 2000, 2000, 0, Lava::OFF); spawnLava(400, 500, 2000, 2000, 0, Lava::OFF);
spawnLava(600, 700, 2000, 2000, 0, Lava::OFF); spawnLava(600, 700, 2000, 2000, 0, Lava::OFF);
spawnPool[0].Spawn(VIRTUAL_LED_COUNT, 3800, 4, 0, 0); spawnPool[0].Spawn(1000, 3800, 4, 0, 0);
break; break;
case 13: case 13:
// Sin enemy #2 practice (slow conveyor) // Sin enemy #2 practice (slow conveyor)
spawnEnemy(700, 1, 7, 275); spawnEnemy(700, 1, 7, 275);
spawnEnemy(500, 1, 5, 250); spawnEnemy(500, 1, 5, 250);
spawnPool[0].Spawn(VIRTUAL_LED_COUNT, 5500, 4, 0, 3000); spawnPool[0].Spawn(1000, 5500, 4, 0, 3000);
spawnPool[1].Spawn(0, 5500, 5, 1, 10000); spawnPool[1].Spawn(0, 5500, 5, 1, 10000);
spawnConveyor(100, 900, -4); spawnConveyor(100, 900, -4);
break; break;
case 14: case 14:
// Sin enemy #2 (fast conveyor) // Sin enemy #2 (fast conveyor)
spawnEnemy(800, 1, 7, 275);
spawnEnemy(700, 1, 7, 275); spawnEnemy(700, 1, 7, 275);
spawnEnemy(500, 1, 5, 250); spawnEnemy(500, 1, 5, 250);
spawnPool[0].Spawn(VIRTUAL_LED_COUNT, 5500, 4, 0, 3000); spawnPool[0].Spawn(1000, 3000, 4, 0, 3000);
spawnPool[1].Spawn(0, 5500, 5, 1, 10000); spawnPool[1].Spawn(0, 5500, 5, 1, 10000);
spawnConveyor(100, 900, -6); spawnConveyor(100, 900, -6);
break; break;
@@ -496,9 +500,9 @@ void spawnBoss(){
} }
void moveBoss(){ void moveBoss(){
int spawnSpeed = 2500; int spawnSpeed = 1800;
if(boss._lives == 2) spawnSpeed = 2000; if(boss._lives == 2) spawnSpeed = 1600;
if(boss._lives == 1) spawnSpeed = 1500; if(boss._lives == 1) spawnSpeed = 1000;
spawnPool[0].Spawn(boss._pos, spawnSpeed, 3, 0, 0); spawnPool[0].Spawn(boss._pos, spawnSpeed, 3, 0, 0);
spawnPool[1].Spawn(boss._pos, spawnSpeed, 3, 1, 0); spawnPool[1].Spawn(boss._pos, spawnSpeed, 3, 1, 0);
} }
@@ -574,12 +578,17 @@ void levelComplete(){
void nextLevel(){ void nextLevel(){
levelNumber ++; levelNumber ++;
if(lastLevel) if(lastLevel) {
levelNumber = 0; stage = STARTUP;
stageStartTime = millis();
lives = user_settings.lives_per_level;
}
else {
lives = user_settings.lives_per_level; lives = user_settings.lives_per_level;
loadLevel(); loadLevel();
} }
}
void gameOver(){ void gameOver(){
@@ -856,19 +865,18 @@ void tickComplete(long mm) // the boss is dead
void tickBossKilled(long mm) // boss funeral void tickBossKilled(long mm) // boss funeral
{ {
static uint8_t gHue = 0;
FastLED.setBrightness(255); // super bright!
int brightness = 0; int brightness = 0;
FastLED.clear(); FastLED.clear();
if(stageStartTime+500 > mm){
int n = _max(map(((mm-stageStartTime)), 0, 500, user_settings.led_count, 0), 0); if(stageStartTime+6500 > mm){
for(int i = user_settings.led_count; i>= n; i--){ gHue++;
brightness = (sin(((i*10)+mm)/500.0)+1)*255; fill_rainbow( leds, user_settings.led_count, gHue, 7); // FastLED's built in rainbow
leds[i].setHSV(brightness, 255, 50); if( random8() < 200) { // add glitter
} leds[ random16(user_settings.led_count) ] += CRGB::White;
SFXbosskilled();
}else if(stageStartTime+6500 > mm){
for(int i = user_settings.led_count; i>= 0; i--){
brightness = (sin(((i*10)+mm)/500.0)+1)*255;
leds[i].setHSV(brightness, 255, 50);
} }
SFXbosskilled(); SFXbosskilled();
}else if(stageStartTime+7000 > mm){ }else if(stageStartTime+7000 > mm){
@@ -879,7 +887,6 @@ void tickBossKilled(long mm) // boss funeral
} }
SFXcomplete(); SFXcomplete();
}else{ }else{
save_game_stats(true); // true = boss was killed
nextLevel(); nextLevel();
} }
} }
@@ -1044,36 +1051,23 @@ void save_game_stats(bool bossKill)
// --------- SCREENSAVER ----------- // --------- SCREENSAVER -----------
// --------------------------------- // ---------------------------------
void screenSaverTick(){ void screenSaverTick(){
int n, b, c, i;
long mm = millis(); long mm = millis();
int mode = (mm/30000)%5; int mode = (mm/30000)%5;
SFXcomplete(); // turn off sound...play testing showed this to be a problem SFXcomplete(); // turn off sound...play testing showed this to be a problem
for(i = 0; i<user_settings.led_count; i++){
leds[i].nscale8(250);
}
if (mode == 0) { if (mode == 0) {
// Marching green <> orange LED_march();
n = (mm/250)%10;
b = 10+((sin(mm/500.00)+1)*20.00);
c = 20+((sin(mm/5000.00)+1)*33);
for(i = 0; i<user_settings.led_count; i++){
if(i%10 == n){
leds[i] = CHSV( c, 255, 150);
} }
else if (mode == 1) {
random_LED_flashes();
} }
}else if(mode >= 1){ else if (mode == 2)
// Random flashes sinelon();
else if (mode == 3)
randomSeed(mm); juggle();
for(i = 0; i<user_settings.led_count; i++){ else {
if(random8(20) == 0){ Fire2012();
leds[i] = CHSV( 25, 255, 100);
}
}
} }
} }
@@ -1239,4 +1233,144 @@ long map_constrain(long x, long in_min, long in_max, long out_min, long out_max)
return map(x, in_min, in_max, out_min, out_max); return map(x, in_min, in_max, out_min, out_max);
} }
// Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
////
// This basic one-dimensional 'fire' simulation works roughly as follows:
// There's a underlying array of 'heat' cells, that model the temperature
// at each point along the line. Every cycle through the simulation,
// four steps are performed:
// 1) All cells cool down a little bit, losing heat to the air
// 2) The heat from each cell drifts 'up' and diffuses a little
// 3) Sometimes randomly new 'sparks' of heat are added at the bottom
// 4) The heat from each cell is rendered as a color into the leds array
// The heat-to-color mapping uses a black-body radiation approximation.
//
// Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).
//
// This simulation scales it self a bit depending on NUM_LEDS; it should look
// "OK" on anywhere from 20 to 100 LEDs without too much tweaking.
//
// I recommend running this simulation at anywhere from 30-100 frames per second,
// meaning an interframe delay of about 10-35 milliseconds.
//
// Looks best on a high-density LED setup (60+ pixels/meter).
//
//
// There are two main parameters you can play with to control the look and
// feel of your fire: COOLING (used in step 1 above), and SPARKING (used
// in step 3 above).
//
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 50, suggested range 20-100
#define COOLING 75
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#define SPARKING 40
//======================================== SCREEN SAVERS =================
void Fire2012()
{
// Array of temperature readings at each simulation cell
static byte heat[VIRTUAL_LED_COUNT]; // the most possible
bool gReverseDirection = false;
// Step 1. Cool down every cell a little
for( int i = 0; i < user_settings.led_count; i++) {
heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / user_settings.led_count) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for( int k= user_settings.led_count - 1; k >= 2; k--) {
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if( random8() < SPARKING ) {
int y = random8(7);
heat[y] = qadd8( heat[y], random8(160,255) );
}
// Step 4. Map from heat cells to LED colors
for( int j = 0; j < user_settings.led_count; j++) {
CRGB color = HeatColor( heat[j]);
int pixelnumber;
if( gReverseDirection ) {
pixelnumber = (user_settings.led_count-1) - j;
} else {
pixelnumber = j;
}
leds[pixelnumber] = color;
}
}
void LED_march() {
int n, b, c, i;
long mm = millis();
for(i = 0; i<user_settings.led_count; i++){
leds[i].nscale8(250);
}
// Marching green <> orange
n = (mm/250)%10;
b = 10+((sin(mm/500.00)+1)*20.00);
c = 20+((sin(mm/5000.00)+1)*33);
for(i = 0; i<user_settings.led_count; i++){
if(i%10 == n){
leds[i] = CHSV( c, 255, 150);
}
}
}
void random_LED_flashes() {
long mm = millis();
int i;
for(i = 0; i<user_settings.led_count; i++){
leds[i].nscale8(250);
}
randomSeed(mm);
for(i = 0; i<user_settings.led_count; i++){
if(random8(20) == 0){
leds[i] = CHSV( 25, 255, 100);
}
}
}
void sinelon()
{
static uint8_t gHue = 0; // rotating "base color" used by many of the patterns
gHue++;
// a colored dot sweeping back and forth, with fading trails
fadeToBlackBy( leds, user_settings.led_count, 20);
int pos = beatsin16(13,0,user_settings.led_count);
leds[pos] += CHSV( gHue, 255, 192);
}
void juggle() {
// eight colored dots, weaving in and out of sync with each other
fadeToBlackBy( leds, user_settings.led_count, 20);
byte dothue = 0;
for( int i = 0; i < 4; i++) {
leds[beatsin16( i+7, 0, user_settings.led_count-1 )] |= CHSV(dothue, 200, 255);
dothue += 64;
}
}