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.

This commit is contained in:
Phew
2026-03-15 22:35:00 +01:00
parent 9bf87677a4
commit a8c6a01422
3 changed files with 104 additions and 41 deletions

View File

@@ -4,24 +4,29 @@
class Particle class Particle
{ {
public: public:
void Spawn(int pos); void Spawn(int pos, int life, int aging_speed, const int color[3]);
void Tick(int USE_GRAVITY); void Tick(int USE_GRAVITY);
void Kill(); void Kill();
bool Alive(); bool Alive();
int _pos; int _pos;
int _power; int _power;
int _color[3];
private: private:
int _life; int _life;
int _alive; int _alive;
int _sp; 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; _pos = pos;
_sp = random(-200, 200); _sp = random(-life, life);
_power = 255; _power = 255;
_alive = 1; _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){ void Particle::Tick(int USE_GRAVITY){
@@ -33,7 +38,7 @@ void Particle::Tick(int USE_GRAVITY){
_sp += _life/10; _sp += _life/10;
} }
if(USE_GRAVITY && _pos > 500) _sp -= 10; if(USE_GRAVITY && _pos > 500) _sp -= 10;
_power = 100 - _life; _power = 100 - _aging*_life;
if(_power <= 0){ if(_power <= 0){
Kill(); Kill();
}else{ }else{

View File

@@ -53,6 +53,7 @@
#include "sound.h" #include "sound.h"
#include "settings.h" #include "settings.h"
#include "wifi_ap.h" #include "wifi_ap.h"
#include "colors.h"
#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000) #if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000)
#error "Requires FastLED 3.1 or later; check github for latest code." #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_WIPEUP_DUR 200
#define STARTUP_SPARKLE_DUR 1300 #define STARTUP_SPARKLE_DUR 1300
#define STARTUP_FADE_DUR 1500 #define STARTUP_FADE_DUR 1500
#define COLOR_SELECT_DUR 1000
#define GAMEOVER_SPREAD_DURATION 1000 #define GAMEOVER_SPREAD_DURATION 1000
#define GAMEOVER_FADE_DURATION 1500 #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 stageStartTime; // Stores the time the stage changed for stages that are time based
long killTime; long killTime;
bool lastLevel = false; bool lastLevel = false;
int killPos = 0;
int score = 0; int score = 0;
@@ -256,8 +259,7 @@ void setup() {
player[0].setColor(0,255,0); player[0].setColor(0,255,0);
player[1].setColor(127,2,255); player[1].setColor(127,2,255);
for (Player& p : player) 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(); checkSerialInput();
if(stage == PLAY){ if(stage == PLAY){
if(player[0].attacking){ bool attacking = false;
for (const Player& p: player) attacking = attacking || p.attacking;
if(attacking){
SFXattacking(); SFXattacking();
}else{ }else{
SFXtilt(joystickTilt[0]); int tilt = -1000;
for (int i=0; i<NUM_PLAYERS; i++) tilt = max(joystickTilt[i], tilt);
SFXtilt(tilt);
} }
}else if(stage == DEAD){ }else if(stage == DEAD){
SFXdead(); SFXdead();
@@ -284,12 +291,12 @@ void loop() {
long frameTimer = mm; long frameTimer = mm;
previousMillis = mm; previousMillis = mm;
// check activity of any player // check activity of any player, used to stop screensaver
bool isMoving = false; bool isMoving = false;
for (int i=0; i < NUM_PLAYERS; i++) { for (int i=0; i < NUM_PLAYERS; i++) {
isMoving = isMoving && isMoving = isMoving ||
(abs(joystickTilt[i]) > user_settings.joystick_deadzone) || ((abs(joystickTilt[i]) > user_settings.joystick_deadzone) ||
(abs(joystickWobble[i]) >= user_settings.attack_threshold); (abs(joystickWobble[i]) >= user_settings.attack_threshold));
} }
if(isMoving) if(isMoving)
@@ -321,7 +328,22 @@ void loop() {
}else if(stage == PLAY){ }else if(stage == PLAY){
// PLAYING // PLAYING
// allow selecting player color during first sec of first level
if (levelNumber == 0) {
if ((stageStartTime + COLOR_SELECT_DUR) > mm) {
for (Player& p : player) { 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 (p.captured) continue;
if (joystickWobble[p.Id()] >= user_settings.attack_threshold) p.startAttack(mm); if (joystickWobble[p.Id()] >= user_settings.attack_threshold) p.startAttack(mm);
@@ -360,25 +382,26 @@ void loop() {
// Ticks and draw calls // Ticks and draw calls
FastLED.clear(); FastLED.clear();
// exploding players during PLAY stage
tickParticles();
for (Player& p : player) for (Player& p : player)
tickConveyors(p); tickConveyors(p);
tickSpawners(); tickSpawners();
tickBoss(); tickBoss();
tickLava(); tickLava();
for (Player& p : player)
if (!p.captured) tickEnemies(p); for (Player& p : player) if (!p.captured) tickEnemies(p);
for (const Player& p : player) { for (const Player& p : player) if (!p.captured) drawAttack(p);
if (p.Alive()) { for (const Player& p : player) if (!p.captured) drawPlayer(p);
drawPlayer(p);
drawAttack(p);
}
}
drawExit(); drawExit();
}else if(stage == DEAD){ }else if(stage == DEAD){
// DEAD // DEAD
FastLED.clear(); FastLED.clear();
tickDie(player[0], mm); // TODO: fix me! Add whoDied function! tickDie(mm);
if(!tickParticles()){ if(!tickParticles()){
loadLevel(); loadLevel();
} }
@@ -702,8 +725,7 @@ void nextLevel(){
for (Player& p : player) for (Player& p : player)
p.Lives ( user_settings.lives_per_level ); p.Lives ( user_settings.lives_per_level );
} } else {
else {
for (Player& p : player) { for (Player& p : player) {
if (!p.Alive()) p.Lives ( user_settings.lives_per_level ); if (!p.Alive()) p.Lives ( user_settings.lives_per_level );
} }
@@ -732,6 +754,11 @@ void die(Player& p){
allDead &= !pp.Alive(); 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) // someone is still playing the level, continue (an animation for the dead player should be added)
if(stillPlaying>0) return; 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 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(); stageStartTime = millis();
stage = DEAD; stage = DEAD;
} }
killTime = millis(); killTime = millis();
killPos = p.position;
} }
// ---------------------------------- // ----------------------------------
@@ -924,19 +949,24 @@ void tickLava(){
} }
bool tickParticles(){ bool tickParticles(){
// TODO: the new version of this function is a bit stupid. Should be rewritten.
bool stillActive = false; bool stillActive = false;
uint8_t brightness; float brightness;
uint8_t r, g, b;
// loop over particles
for(int p = 0; p < PARTICLE_COUNT; p++){ for(int p = 0; p < PARTICLE_COUNT; p++){
if(particlePool[p].Alive()){ if(particlePool[p].Alive()){
particlePool[p].Tick(USE_GRAVITY); particlePool[p].Tick(USE_GRAVITY);
if (particlePool[p]._power < 5) if (particlePool[p]._power < 5) brightness = (float) (5 - particlePool[p]._power) * 10 / 255;
{ else brightness = (float) particlePool[p]._power / 255;
brightness = (5 - particlePool[p]._power) * 10;
leds[getLED(particlePool[p]._pos)] += CRGB(brightness, brightness/2, brightness/2);\ r = (uint8_t) ( particlePool[p]._color[0] * brightness);
} g = (uint8_t) ( particlePool[p]._color[1] * brightness);
else b = (uint8_t) ( particlePool[p]._color[2] * brightness);
leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0);
leds[getLED(particlePool[p]._pos)] += CRGB(r, g, b);
stillActive = true; 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 duration = 200; // milliseconds
const int width = 20; // half width of the explosion 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 int brightness = map((mm-stageStartTime), 0, duration, 255, 150); // this allows a fade from white to red
// fill up // fill up
int n = _max(map(((mm-stageStartTime)), 0, duration, getLED(p.position), getLED(p.position)+width), 0); int n = _max(map(((mm-stageStartTime)), 0, duration, getLED(killPos), getLED(killPos)+width), 0);
for(int i = getLED(p.position); i<= n; i++){ for(int i = getLED(killPos); i<= n; i++){
leds[i] = CRGB(255, brightness, brightness); leds[i] = CRGB(255, brightness, brightness);
} }
// fill to down // fill to down
n = _max(map(((mm-stageStartTime)), 0, duration, getLED(p.position), getLED(p.position)-width), 0); n = _max(map(((mm-stageStartTime)), 0, duration, getLED(killPos), getLED(killPos)-width), 0);
for(int i = getLED(p.position); i>= n; i--){ for(int i = getLED(killPos); i>= n; i--){
leds[i] = CRGB(255, brightness, brightness); leds[i] = CRGB(255, brightness, brightness);
} }
} }
@@ -1226,6 +1256,9 @@ void getInput(){
// if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90; // if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90;
// if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90; // if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90;
// if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD; // if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD;
// NB: This is not multiplayer yet
int16_t ax, ay, az; int16_t ax, ay, az;
int16_t gx, gy, gz; int16_t gx, gy, gz;
@@ -1273,6 +1306,8 @@ void getInput() {
//bool right = digitalRead(rightButtonPinNumber) == LOW; //bool right = digitalRead(rightButtonPinNumber) == LOW;
bool right = left; bool right = left;
// Player 1
joystickTilt[0] = 0; joystickTilt[0] = 0;
if (up) { if (up) {
joystickTilt[0] = 90 ; joystickTilt[0] = 90 ;
@@ -1287,7 +1322,7 @@ void getInput() {
} }
// Player 2
up = digitalRead(upButtonPinNumber2) == LOW; up = digitalRead(upButtonPinNumber2) == LOW;
down = digitalRead(downButtonPinNumber2) == LOW; down = digitalRead(downButtonPinNumber2) == LOW;

23
TWANG32/colors.h Normal file
View File

@@ -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));
}