From 08686e0fd6efed2baa8b4262f0e4139a280150ab Mon Sep 17 00:00:00 2001 From: Phew Date: Tue, 6 Jan 2026 23:04:15 +0100 Subject: [PATCH] Started encpsulation of player logic --- TWANG32/Player.h | 89 +++++++++++++++++++++++++ TWANG32/TWANG32.ino | 159 +++++++++++++++++++++----------------------- 2 files changed, 164 insertions(+), 84 deletions(-) create mode 100644 TWANG32/Player.h diff --git a/TWANG32/Player.h b/TWANG32/Player.h new file mode 100644 index 0000000..4881842 --- /dev/null +++ b/TWANG32/Player.h @@ -0,0 +1,89 @@ +#include "Arduino.h" + +class Player +{ + public: + Player(int[3], int, int); + void Lives(int); + int Lives(); + void Kill(); + bool Alive(); + void moveto(int); + void moveby(int); + int color[3]; + int position; + bool attacking; + int last_attack; + void updateState(bool tilted, bool wobbled, int time); + void startAttack(int time); + int _lastState; // 0-> idle; 1-> move; 2 -> attack + private: + int _lives; + int _state; + + int _attackDuration; +}; + +Player::Player(int player_color[3], int lives, int attackDuration){ + _lives = lives; + attacking = false; + + color[0] = player_color[0]; + color[1] = player_color[1]; + color[2] = player_color[2]; + + _attackDuration = attackDuration; + + _lastState = 0; // start as idle; +} + +void Player::updateState(bool tilted, bool wobbled, int time) { + + // attack timed out + if(attacking && last_attack+_attackDuration < time) attacking = false; + + // no longer attacking + if (!wobbled) attacking = false; + + _lastState = 0; + if (tilted) _lastState = 1; // moving + if (wobbled) _lastState = 2; // attacking +} + +void Player::startAttack(int time){ + // if already attacking just return + if (attacking) return; + + // I got tired of attacking, I should be idle or moving for a little before attacking again. + if (_lastState == 2) return; + + last_attack = time; + attacking = true; +} + +void Player::Lives(int n){ + _lives = n; +} + +bool Player::Alive(){ + return _lives > 0; +} + +void Player::Kill(){ + _lives--; + if (_lives < 0) _lives = 0; +} + +int Player::Lives(){ + return _lives; +} + +void Player::moveby(int amount){ + position += amount; + if (position<0) position=0; +} + +void Player::moveto(int amount){ + position = amount; + if (position<0) position=0; +} diff --git a/TWANG32/TWANG32.ino b/TWANG32/TWANG32.ino index 997b473..4ba11b7 100644 --- a/TWANG32/TWANG32.ino +++ b/TWANG32/TWANG32.ino @@ -22,6 +22,7 @@ Usage Notes: + - Changes attack model and added Player class - Changes to LED strip and other hardware are in config.h - Change the strip type to what you are using and compile/load firmware - Use Serial port or Wifi to set your strip length. @@ -42,6 +43,7 @@ #include "twang_mpu.h" #endif #include "Enemy.h" +#include "Player.h" #include "Particle.h" #include "Spawner.h" #include "Lava.h" @@ -75,9 +77,9 @@ int joystickWobble = 0; // Stores the max amount of acceleration (wob #define DEFAULT_ATTACK_WIDTH 70 // Width of the wobble attack, world is 1000 wide int attack_width = DEFAULT_ATTACK_WIDTH; #define ATTACK_DURATION 1000 // Duration of a wobble attack (ms) -long attackMillis = 0; // Time the attack started -bool attacking = 0; // Is the attack in progress? -bool canAttackAgain = 0; // Have I finished my previous attack? +//long attackMillis = 0; // Time the attack started +//bool attacking = 0; // Is the attack in progress? +//bool canAttackAgain = 0; // Have I finished my previous attack? #define BOSS_WIDTH 40 // TODO all animation durations should be defined rather than literals @@ -135,6 +137,9 @@ Conveyor conveyorPool[CONVEYOR_COUNT] = { Boss boss = Boss(); +int pcolor[3] = {0,255,0}; +Player player = Player(pcolor, LIVES_PER_LEVEL, ATTACK_DURATION); + enum stages { STARTUP, PLAY, @@ -146,11 +151,8 @@ enum stages { } stage; long stageStartTime; // Stores the time the stage changed for stages that are time based -int playerPosition; // Stores the player position int playerPositionModifier; // +/- adjustment to player position -bool playerAlive; long killTime; -int lives = LIVES_PER_LEVEL; bool lastLevel = false; int score = 0; @@ -241,7 +243,7 @@ void setup() { stage = STARTUP; stageStartTime = millis(); - lives = user_settings.lives_per_level; + player.Lives ( user_settings.lives_per_level ); } void loop() { @@ -252,7 +254,7 @@ void loop() { checkSerialInput(); if(stage == PLAY){ - if(attacking){ + if(player.attacking){ SFXattacking(); }else{ SFXtilt(joystickTilt); @@ -270,7 +272,7 @@ void loop() { previousMillis = mm; if((abs(joystickTilt) > user_settings.joystick_deadzone) || - (joystickWobble >= user_settings.attack_threshold)) + (abs(joystickWobble) >= user_settings.attack_threshold)) { lastInputTime = mm; if(stage == SCREENSAVER){ @@ -300,48 +302,38 @@ void loop() { } }else if(stage == PLAY){ // PLAYING - if(attacking && attackMillis+ATTACK_DURATION < mm) { - attacking = 0; - canAttackAgain = 0; - } - if(joystickWobble < user_settings.attack_threshold){ - attacking = 0; - canAttackAgain = 1; - } + if (joystickWobble >= user_settings.attack_threshold) player.startAttack(mm); - // If not attacking, check if they should be - if(!attacking && canAttackAgain && (joystickWobble >= user_settings.attack_threshold)){ - attackMillis = mm; - attacking = 1; - } + player.updateState( + abs(joystickTilt) > user_settings.joystick_deadzone, + abs(joystickWobble) >= user_settings.attack_threshold, + mm); // If still not attacking, move! - playerPosition += playerPositionModifier; - if(!attacking){ + player.moveby(playerPositionModifier); + if(!player.attacking){ SFXtilt(joystickTilt); //int moveAmount = (joystickTilt/6.0); // 6.0 is ideal at 16ms interval (6.0 / (16.0 / MIN_REDRAW_INTERVAL)) int moveAmount = (joystickTilt/(6.0)); // 6.0 is ideal at 16ms interval if(DIRECTION) moveAmount = -moveAmount; moveAmount = constrain(moveAmount, -MAX_PLAYER_SPEED, MAX_PLAYER_SPEED); - playerPosition -= moveAmount; - if(playerPosition < 0) - playerPosition = 0; + player.moveby( -moveAmount ); // stop player from leaving if boss is alive - if (boss.Alive() && playerPosition >= VIRTUAL_LED_COUNT) // move player back - playerPosition = 999; //(user_settings.led_count - 1) * (1000.0/user_settings.led_count); + if (boss.Alive() && player.position >= VIRTUAL_LED_COUNT) // move player back + player.moveto( 999 ); //(user_settings.led_count - 1) * (1000.0/user_settings.led_count); - if(playerPosition >= VIRTUAL_LED_COUNT && !boss.Alive()) { + if(player.position >= VIRTUAL_LED_COUNT && !boss.Alive()) { // Reached exit! levelComplete(); return; } } - if(inLava(playerPosition)){ - die(); + if(inLava(player.position)){ + die(player); } // Ticks and draw calls @@ -351,13 +343,13 @@ void loop() { tickBoss(); tickLava(); tickEnemies(); - drawPlayer(); - drawAttack(); + drawPlayer(player); + drawAttack(player); drawExit(); }else if(stage == DEAD){ // DEAD FastLED.clear(); - tickDie(mm); + tickDie(player, mm); if(!tickParticles()){ loadLevel(); } @@ -377,7 +369,7 @@ void loop() { // restart from the beginning stage = STARTUP; stageStartTime = millis(); - lives = user_settings.lives_per_level; + player.Lives ( user_settings.lives_per_level ); } } //FastLED.show(); @@ -393,12 +385,11 @@ void loadLevel(){ FastLED.setBrightness(user_settings.led_brightness); updateLives(); cleanupLevel(); - playerAlive = 1; lastLevel = false; // this gets changed on the boss level /// Defaults...OK to change the following items in the levels below attack_width = DEFAULT_ATTACK_WIDTH; - playerPosition = 0; + player.moveto( 0 ); /* ==== Level Editing Guide =============== Level creation is done by adding to or editing the switch statement below @@ -451,7 +442,7 @@ void loadLevel(){ ===== Other things you can adjust per level ================ Player Start position: - playerPosition = xxx; + player.moveto( xxx ); The size of the TWANG attack @@ -461,7 +452,7 @@ void loadLevel(){ */ switch(levelNumber){ case 0: // basic introduction - playerPosition = 200; + player.moveto( 200 ); spawnEnemy(1, 0, 0, 0); break; case 1: @@ -508,7 +499,7 @@ void loadLevel(){ break; case 8: // lava moving up - playerPosition = 200; + player.moveto( 200 ); spawnLava(10, 180, 2000, 2000, 0, Lava::OFF, 0, 0.5); spawnEnemy(350, 0, 1, 0); spawnPool[0].Spawn(1000, 5500, 3, 0, 0); @@ -610,7 +601,7 @@ void spawnEnemy(int pos, int dir, int speed, int wobble){ for(int e = 0; e playerPosition?1:-1; + enemyPool[e].playerSide = pos > player.position?1:-1; return; } } @@ -662,21 +653,21 @@ void levelComplete(){ } if (levelNumber != 0) // no points for the first level { - score = score + (lives * 10); // + score = score + (player.Lives() * 10); // } } void nextLevel(){ levelNumber ++; - + if(lastLevel) { stage = STARTUP; stageStartTime = millis(); - lives = user_settings.lives_per_level; + player.Lives ( user_settings.lives_per_level ); } else { - lives = user_settings.lives_per_level; + player.Lives ( user_settings.lives_per_level ); loadLevel(); } } @@ -687,19 +678,19 @@ void gameOver(){ loadLevel(); } -void die(){ - playerAlive = 0; - if(levelNumber > 0) - lives --; +void die(Player p){ - if(lives == 0){ + if(levelNumber > 0) + p.Kill(); + + if(!player.Alive()){ stage = GAMEOVER; stageStartTime = millis(); } else { - for(int p = 0; p < PARTICLE_COUNT; p++){ - particlePool[p].Spawn(playerPosition); + for(int ip = 0; ip < PARTICLE_COUNT; ip++){ + particlePool[ip].Spawn(p.position); } stageStartTime = millis(); stage = DEAD; @@ -754,8 +745,8 @@ void tickEnemies(){ if(enemyPool[i].Alive()){ enemyPool[i].Tick(); // Hit attack? - if(attacking){ - if(enemyPool[i]._pos > playerPosition-(attack_width/2) && enemyPool[i]._pos < playerPosition+(attack_width/2)){ + if(player.attacking){ + if(enemyPool[i]._pos > player.position-(attack_width/2) && enemyPool[i]._pos < player.position+(attack_width/2)){ enemyPool[i].Kill(); SFXkill(); } @@ -770,10 +761,10 @@ void tickEnemies(){ } // Hit player? if( - (enemyPool[i].playerSide == 1 && enemyPool[i]._pos <= playerPosition) || - (enemyPool[i].playerSide == -1 && enemyPool[i]._pos >= playerPosition) + (enemyPool[i].playerSide == 1 && enemyPool[i]._pos <= player.position) || + (enemyPool[i].playerSide == -1 && enemyPool[i]._pos >= player.position) ){ - die(); + die(player); return; } } @@ -789,15 +780,15 @@ void tickBoss(){ leds[i] %= 100; } // CHECK COLLISION - if(getLED(playerPosition) > getLED(boss._pos - BOSS_WIDTH/2) && getLED(playerPosition) < getLED(boss._pos + BOSS_WIDTH)){ - die(); + if(getLED(player.position) > getLED(boss._pos - BOSS_WIDTH/2) && getLED(player.position) < getLED(boss._pos + BOSS_WIDTH)){ + die(player); return; } // CHECK FOR ATTACK - if(attacking){ + if(player.attacking){ if( - (getLED(playerPosition+(attack_width/2)) >= getLED(boss._pos - BOSS_WIDTH/2) && getLED(playerPosition+(attack_width/2)) <= getLED(boss._pos + BOSS_WIDTH/2)) || - (getLED(playerPosition-(attack_width/2)) <= getLED(boss._pos + BOSS_WIDTH/2) && getLED(playerPosition-(attack_width/2)) >= getLED(boss._pos - BOSS_WIDTH/2)) + (getLED(player.position+(attack_width/2)) >= getLED(boss._pos - BOSS_WIDTH/2) && getLED(player.position+(attack_width/2)) <= getLED(boss._pos + BOSS_WIDTH/2)) || + (getLED(player.position-(attack_width/2)) <= getLED(boss._pos + BOSS_WIDTH/2) && getLED(player.position-(attack_width/2)) >= getLED(boss._pos - BOSS_WIDTH/2)) ){ boss.Hit(); if(boss.Alive()){ @@ -811,8 +802,8 @@ void tickBoss(){ } } -void drawPlayer(){ - leds[getLED(playerPosition)] = CRGB(0, 255, 0); +void drawPlayer(Player p){ + leds[getLED(p.position)] = CRGB(p.color[0], p.color[1], p.color[2]); } void drawExit(){ @@ -918,7 +909,7 @@ void tickConveyors(){ leds[led] = CRGB(0, 0, b); } - if(playerPosition > conveyorPool[i]._startPoint && playerPosition < conveyorPool[i]._endPoint){ + if(player.position > conveyorPool[i]._startPoint && player.position < conveyorPool[i]._endPoint){ playerPositionModifier = speed; } } @@ -980,7 +971,7 @@ void tickBossKilled(long mm) // boss funeral } } -void tickDie(long mm) { // a short bright explosion...particles persist after it. +void tickDie(Player p, long mm) { // a short bright explosion...particles persist after it. const int duration = 200; // milliseconds const int width = 20; // half width of the explosion @@ -989,14 +980,14 @@ void tickDie(long mm) { // a short bright explosion...particles persist after it 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(playerPosition), getLED(playerPosition)+width), 0); - for(int i = getLED(playerPosition); i<= n; i++){ + 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++){ leds[i] = CRGB(255, brightness, brightness); } // fill to down - n = _max(map(((mm-stageStartTime)), 0, duration, getLED(playerPosition), getLED(playerPosition)-width), 0); - for(int i = getLED(playerPosition); i>= n; i--){ + n = _max(map(((mm-stageStartTime)), 0, duration, getLED(p.position), getLED(p.position)-width), 0); + for(int i = getLED(p.position); i>= n; i--){ leds[i] = CRGB(255, brightness, brightness); } } @@ -1009,13 +1000,13 @@ void tickGameover(long mm) { if(stageStartTime+GAMEOVER_SPREAD_DURATION > mm) // Spread red from player position to top and bottom { // fill to top - int n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), user_settings.led_count), 0); - for(int i = getLED(playerPosition); i<= n; i++){ + int n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(player.position), user_settings.led_count), 0); + for(int i = getLED(player.position); i<= n; i++){ leds[i] = CRGB(255, 0, 0); } // fill to bottom - n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), 0), 0); - for(int i = getLED(playerPosition); i>= n; i--){ + n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(player.position), 0), 0); + for(int i = getLED(player.position); i>= n; i--){ leds[i] = CRGB(255, 0, 0); } SFXgameover(); @@ -1067,7 +1058,7 @@ void drawLives() FastLED.clear(); int pos = 0; - for (int i = 0; i < lives; i++) + for (int i = 0; i < player.Lives(); i++) { for (int j=0; j<4; j++) { @@ -1084,21 +1075,21 @@ void drawLives() -void drawAttack(){ - if(!attacking) return; - int n = map(millis() - attackMillis, 0, ATTACK_DURATION, 100, 5); - for(int i = getLED(playerPosition-(attack_width/2))+1; i<=getLED(playerPosition+(attack_width/2))-1; i++){ +void drawAttack(Player p){ + if(!p.attacking) return; + int n = map(millis() - p.last_attack, 0, ATTACK_DURATION, 100, 5); + for(int i = getLED(p.position-(attack_width/2))+1; i<=getLED(p.position+(attack_width/2))-1; i++){ leds[i] = CRGB(0, 0, n); } if(n > 90) { n = 255; - leds[getLED(playerPosition)] = CRGB(255, 255, 255); + leds[getLED(p.position)] = CRGB(255, 255, 255); }else{ n = 0; - leds[getLED(playerPosition)] = CRGB(0, 255, 0); + leds[getLED(p.position)] = CRGB(0, 255, 0); } - leds[getLED(playerPosition-(attack_width/2))] = CRGB(n, n, 255); - leds[getLED(playerPosition+(attack_width/2))] = CRGB(n, n, 255); + leds[getLED(p.position-(attack_width/2))] = CRGB(n, n, 255); + leds[getLED(p.position+(attack_width/2))] = CRGB(n, n, 255); } int getLED(int pos){