From a8c6a01422dc8860fdeaf7fb7d1d41d90624a7bd Mon Sep 17 00:00:00 2001 From: Phew Date: Sun, 15 Mar 2026 22:35:00 +0100 Subject: [PATCH] Fixed bug with dead players being zombies (not visible but playing). Added option to change color at the beginning of level 1. Explosion animation now has player's color and takes place for all of them when they are killed (before only for last one). Final explosion animation and gameover now start where last player was killed. --- TWANG32/Particle.h | 15 ++++--- TWANG32/TWANG32.ino | 107 +++++++++++++++++++++++++++++--------------- TWANG32/colors.h | 23 ++++++++++ 3 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 TWANG32/colors.h diff --git a/TWANG32/Particle.h b/TWANG32/Particle.h index b9b4ff2..91f85a8 100644 --- a/TWANG32/Particle.h +++ b/TWANG32/Particle.h @@ -4,24 +4,29 @@ class Particle { public: - void Spawn(int pos); + void Spawn(int pos, int life, int aging_speed, const int color[3]); void Tick(int USE_GRAVITY); void Kill(); bool Alive(); int _pos; int _power; + int _color[3]; private: int _life; int _alive; int _sp; + int _aging; }; -void Particle::Spawn(int pos){ +void Particle::Spawn(int pos, int life, int aging_speed, const int color[3]){ _pos = pos; - _sp = random(-200, 200); + _sp = random(-life, life); _power = 255; _alive = 1; - _life = 220 - abs(_sp); + _life = life - abs(_sp); + _aging = aging_speed; + + for (int i = 0; i< 3; i++) _color[i] = color[i]; } void Particle::Tick(int USE_GRAVITY){ @@ -33,7 +38,7 @@ void Particle::Tick(int USE_GRAVITY){ _sp += _life/10; } if(USE_GRAVITY && _pos > 500) _sp -= 10; - _power = 100 - _life; + _power = 100 - _aging*_life; if(_power <= 0){ Kill(); }else{ diff --git a/TWANG32/TWANG32.ino b/TWANG32/TWANG32.ino index 84c1068..ff09234 100644 --- a/TWANG32/TWANG32.ino +++ b/TWANG32/TWANG32.ino @@ -53,6 +53,7 @@ #include "sound.h" #include "settings.h" #include "wifi_ap.h" +#include "colors.h" #if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000) #error "Requires FastLED 3.1 or later; check github for latest code." @@ -89,6 +90,7 @@ int attack_width = DEFAULT_ATTACK_WIDTH; #define STARTUP_WIPEUP_DUR 200 #define STARTUP_SPARKLE_DUR 1300 #define STARTUP_FADE_DUR 1500 +#define COLOR_SELECT_DUR 1000 #define GAMEOVER_SPREAD_DURATION 1000 #define GAMEOVER_FADE_DURATION 1500 @@ -157,6 +159,7 @@ enum stages { long stageStartTime; // Stores the time the stage changed for stages that are time based long killTime; bool lastLevel = false; +int killPos = 0; int score = 0; @@ -256,8 +259,7 @@ void setup() { player[0].setColor(0,255,0); player[1].setColor(127,2,255); for (Player& p : player) - p.Lives ( 2 ); - //p.Lives ( user_settings.lives_per_level ); + p.Lives ( user_settings.lives_per_level ); } @@ -269,10 +271,15 @@ void loop() { checkSerialInput(); if(stage == PLAY){ - if(player[0].attacking){ + bool attacking = false; + for (const Player& p: player) attacking = attacking || p.attacking; + + if(attacking){ SFXattacking(); }else{ - SFXtilt(joystickTilt[0]); + int tilt = -1000; + for (int i=0; i user_settings.joystick_deadzone) || - (abs(joystickWobble[i]) >= user_settings.attack_threshold); + isMoving = isMoving || + ((abs(joystickTilt[i]) > user_settings.joystick_deadzone) || + (abs(joystickWobble[i]) >= user_settings.attack_threshold)); } if(isMoving) @@ -321,7 +328,22 @@ void loop() { }else if(stage == PLAY){ // PLAYING + // allow selecting player color during first sec of first level + if (levelNumber == 0) { + if ((stageStartTime + COLOR_SELECT_DUR) > mm) { + for (Player& p : player) { + if (joystickWobble[p.Id()] >= user_settings.attack_threshold) { + random_player_color(p); + stageStartTime = mm; + return; + } + } + } + } + + // real action here for (Player& p : player) { + if (p.captured) continue; if (joystickWobble[p.Id()] >= user_settings.attack_threshold) p.startAttack(mm); @@ -360,25 +382,26 @@ void loop() { // Ticks and draw calls FastLED.clear(); + + // exploding players during PLAY stage + tickParticles(); + for (Player& p : player) tickConveyors(p); tickSpawners(); tickBoss(); tickLava(); - for (Player& p : player) - if (!p.captured) tickEnemies(p); - for (const Player& p : player) { - if (p.Alive()) { - drawPlayer(p); - drawAttack(p); - } - } + + for (Player& p : player) if (!p.captured) tickEnemies(p); + for (const Player& p : player) if (!p.captured) drawAttack(p); + for (const Player& p : player) if (!p.captured) drawPlayer(p); + drawExit(); }else if(stage == DEAD){ // DEAD FastLED.clear(); - tickDie(player[0], mm); // TODO: fix me! Add whoDied function! + tickDie(mm); if(!tickParticles()){ loadLevel(); } @@ -702,8 +725,7 @@ void nextLevel(){ for (Player& p : player) p.Lives ( user_settings.lives_per_level ); - } - else { + } else { for (Player& p : player) { if (!p.Alive()) p.Lives ( user_settings.lives_per_level ); } @@ -732,6 +754,11 @@ void die(Player& p){ allDead &= !pp.Alive(); } + for(int ip = 0; ip < PARTICLE_COUNT; ip++){ + if(stillPlaying>0) particlePool[ip].Spawn(p.position, 25, 3, p.color); + else particlePool[ip].Spawn(p.position, 200, 1, p.color); // final explosion + } + // someone is still playing the level, continue (an animation for the dead player should be added) if(stillPlaying>0) return; @@ -743,13 +770,11 @@ void die(Player& p){ } else // some still have lifes to live, go to DEAD stage and repat the level { - for(int ip = 0; ip < PARTICLE_COUNT; ip++){ - particlePool[ip].Spawn(p.position); - } stageStartTime = millis(); stage = DEAD; } killTime = millis(); + killPos = p.position; } // ---------------------------------- @@ -924,19 +949,24 @@ void tickLava(){ } bool tickParticles(){ + // TODO: the new version of this function is a bit stupid. Should be rewritten. bool stillActive = false; - uint8_t brightness; + float brightness; + uint8_t r, g, b; + + // loop over particles for(int p = 0; p < PARTICLE_COUNT; p++){ if(particlePool[p].Alive()){ particlePool[p].Tick(USE_GRAVITY); - if (particlePool[p]._power < 5) - { - brightness = (5 - particlePool[p]._power) * 10; - leds[getLED(particlePool[p]._pos)] += CRGB(brightness, brightness/2, brightness/2);\ - } - else - leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0); + if (particlePool[p]._power < 5) brightness = (float) (5 - particlePool[p]._power) * 10 / 255; + else brightness = (float) particlePool[p]._power / 255; + + r = (uint8_t) ( particlePool[p]._color[0] * brightness); + g = (uint8_t) ( particlePool[p]._color[1] * brightness); + b = (uint8_t) ( particlePool[p]._color[2] * brightness); + + leds[getLED(particlePool[p]._pos)] += CRGB(r, g, b); stillActive = true; } @@ -1033,7 +1063,7 @@ void tickBossKilled(long mm) // boss funeral } } -void tickDie(const Player& p, long mm) { // a short bright explosion...particles persist after it. +void tickDie(long mm) { // a short bright explosion...particles persist after it. const int duration = 200; // milliseconds const int width = 20; // half width of the explosion @@ -1042,14 +1072,14 @@ void tickDie(const Player& p, long mm) { // a short bright explosion...particles int brightness = map((mm-stageStartTime), 0, duration, 255, 150); // this allows a fade from white to red // fill up - int n = _max(map(((mm-stageStartTime)), 0, duration, getLED(p.position), getLED(p.position)+width), 0); - for(int i = getLED(p.position); i<= n; i++){ + int n = _max(map(((mm-stageStartTime)), 0, duration, getLED(killPos), getLED(killPos)+width), 0); + for(int i = getLED(killPos); i<= n; i++){ leds[i] = CRGB(255, brightness, brightness); } // fill to down - n = _max(map(((mm-stageStartTime)), 0, duration, getLED(p.position), getLED(p.position)-width), 0); - for(int i = getLED(p.position); i>= n; i--){ + n = _max(map(((mm-stageStartTime)), 0, duration, getLED(killPos), getLED(killPos)-width), 0); + for(int i = getLED(killPos); i>= n; i--){ leds[i] = CRGB(255, brightness, brightness); } } @@ -1226,6 +1256,9 @@ void getInput(){ // if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90; // if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90; // if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD; + + // NB: This is not multiplayer yet + int16_t ax, ay, az; int16_t gx, gy, gz; @@ -1273,6 +1306,8 @@ void getInput() { //bool right = digitalRead(rightButtonPinNumber) == LOW; bool right = left; + // Player 1 + joystickTilt[0] = 0; if (up) { joystickTilt[0] = 90 ; @@ -1287,7 +1322,7 @@ void getInput() { } - + // Player 2 up = digitalRead(upButtonPinNumber2) == LOW; down = digitalRead(downButtonPinNumber2) == LOW; diff --git a/TWANG32/colors.h b/TWANG32/colors.h new file mode 100644 index 0000000..4b39c0d --- /dev/null +++ b/TWANG32/colors.h @@ -0,0 +1,23 @@ +void random_player_color(Player& p) +{ + float h = (float)random(0, 100000) / 100000.0; + float s = 0.95f; // high saturation + float v = 1.0f; // max brightness + + float c = v * s; + float x = c * (1 - fabsf(fmodf(h * 6.0f, 2) - 1)); + float m = v - c; + + float r1=0, g1=0, b1=0; + + if (h < 1.0/6) { r1=c; g1=x; } + else if (h < 2.0/6) { r1=x; g1=c; } + else if (h < 3.0/6) { g1=c; b1=x; } + else if (h < 4.0/6) { g1=x; b1=c; } + else if (h < 5.0/6) { r1=x; b1=c; } + else { r1=c; b1=x; } + + p.setColor( (unsigned char)((r1 + m) * 255), + (unsigned char)((g1 + m) * 255), + (unsigned char)((b1 + m) * 255)); +}