Building a Line Wobbler Clone with Twang

In my previous post I wrote about being transfixed by the Line Wobbler at the Video Games exhibit that runs through February at the Victoria & Albert Museum in London.  In this follow-up I’m going to try and document how Tim, Meredith, and I actually built a clone of the Line Wobbler called Twang (“an Arduino-based 1D dungeon crawler”) and you can find the code at this Github repo. I followed the list of things to buy from the Github repo, and they were right on. Here’s the specific pieces I bought for reference:

I would add to that list the following few things:

I think that’s all, but I may add to the above if I remember anything else. The whole thing will cost you around $85 US.

The other things you need to get are the required libraries for the Arduino that are listed on the Github page, I have them all downloaded in this zip file for easy, one-click downloading, but they can all be found online as well.

Twang Joystick Enclosure

Beyond the hardware and the software, you will also need to 3D print an enclosure. We actually bought a Ultimaker3 printer last week, so it was a perfect test for our new workhorse 3d printer—that machine is awesome. We (royal, this was all Tim) modified the Twang joystick enclosure a bit, and that was a wild process (more on that shortly). You can also explore the buildlog.net remix of the Twang joystick which is very cool.

Twang joystick remix by Buildlog.net

So, once you have all the pieces and have printed the enclosure you should be ready to start building your game. One of the cooler part for me was Tim’s ability to customize the Twang joystick to adjust for a different speaker size as well as increase the depth of the gyroscope casing to fit the wires.

First mod was reducing the thickness of one section of the cover plate so that the speaker would fit cleanly in the enclosure. To do this Tim essentially removed a layer of the coverplate using Tinkercad, and it worked perfectly. You can see in the bottom corner we left a small triangle of the original depth to keep the coverplate fastened.

Arduino Twang joystick

The first task we tackled early in the week was learning how to solder the requisite wires on the gyroscope. Meredith and I actually worked through a practice soldering kit given this as our first time, and after about an hour we felt comfortable enough soldering the pins to the gyroscope board. There may be a more efficient way to solder given you will only be using the VCC, GND, SCL, SDA, and INT pins, but we decided to solder the entire pin connector so we could use the Arduino wires to just slide them on cleanly. That said, this is also why we needed to reprint the casing for the gyroscope that sits on top of the doorstop, it was not deep enough if you include the pins like we did.

Arduino Twang joystick

Arduino Twang joystick

Getting the piece soldered was a big deal for us. I did say before I began this build: “Can’t I get someone to do the soldering for me?” Not very Maker-compliant thinking 🙂

Arduino Twang joystick

Once the pins were soldered we color-coded each of the gyroscope connections as shown above based on the colored wires we had. 

Arduino Twang joystick

As you can see from the image above the gyroscope goes into the top of the joystick that sits on the doorstop. So, with the gyroscope soldered and placed in the joystick casing and the wires run through the doorstop it was time to plug them into the Arduino board. Thankfully buildlog.net once again bailed us out by making an awesome graphic illustrating where all the pins go (note they use an alternative color scheme than ours):

You can see that the SCL and SDA can go where he has them sitting before the AREF, or in ports 20 and 21 which are labeled accordingly. The INT pin goes in port 2 and the Vcc pin can use the 3V power port (he has 5V listed, but I saved that for the LEDs and both worked—probably cause we used fewer LEDs) and the ground goes to GND. Once all those are plugged in you should see the green power light on the gyroscope.

Arduino Twang joystick

If that worked you should be able to move on the the LED lights. I messed up here, which cost us some time. The LEDs will have five wired, a power and ground wire (red and black respectively that are not pictured above) and the LED specific wires (data (green), clock (red), and black (ground)) that are pictured above. The clock (red) goes into port 3, and green (data) into port 4, and LED ground (black) goes into a GND port. As I mentioned before, it is recommended to use a different 5V power supply for the LED lights, but possibly because we only used 300 we did not need to, so we plugged the power and ground for the LED strip into the Arduino board as well, but your mileage may vary on this one.

Arduino Twang joystick

Once you have the LED wired you can then wire the gyroscope using the diagram above and the 3V power port as I specified.

The speaker is the final bit, and I have yet to solder ours, but this is basically two Arduino wires that you plug into ports 11 and 12. This was crucial for us cause I did not think to plugin it in while we were troubleshooting the LED lights I had in the wrong ports, but once Tim had the idea we could establish the game was working given the sounds played.  So if you have the gyroscope hooked up, the LED lights plugged in and have loaded the code and libraries all to no avail, be sure to connect the speakers.

Now we are ready to download the Arduino IDE interface and start loading libraries and see if this thing works (note you can do this when ever you want, but there is really no need until the LED lights and gyroscope are hooked up). The Arduino IDE is basically your desktop app to upload libraries and edit and upload Arduino code (which is written in simplified C/C++). The first thing to do is plug the USB a to USB B convertor in your computer and go to Tools–>Port to see if you see the Arduino in the USB port. If you do you can start uploading the libraries.

The screenshot above is how you upload the various zip files of the required dependencies for Twang. Go to Sketch–>Include Library–>Add .ZIP Library. Once you have uploaded all the required libraries you can then upload the Twang files using the Open icon (upward arrow) and find the Twang.ino file in the TWANG folder with all the requisite code (it is the TWANG.zip file in the dependency and core files I linked to above) on your desktop or where ever you are keeping it.



At this point the code should be ready to be uploaded to the Arduino using the right-facing Arrow icon and it will then be pushed to the Arduino. You can use the Arduino IDE to compile and re-upload the code using the right-facing arrow (upload) and the check mark (compile) icons. These will be useful given you may need to make some edits. We had to change the number of LEDs, for example. We got a strip with 300 LEDs, so we needed to edit the following line of the Twang code from 1000 300 in the // LED Setup area::

#define NUM_LEDS 300


After that, we needed to define the type of LED lights we’re using, this is another detail I missed that Tim picked up on. In the //Fast LED section you can change the type of LEDs from APA102 

FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);

to WS2812B, which is the type of LED lights we bought (which are cheaper):

FastLED.addLeds<WS2812B, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);

If you have APA102 lights no need to make this change, and there may be others varieties of LEDs, so be sure to check which type of lights you ordered to make the correct edit here.

OK, I think that is everything, and the sheer thrill of getting this running was worth the time. And the game plays quite well. I am gonna try and add more LEDs to the string and see if I can get the lives indicator working, so I may add updates and follow-ups, but you can see the came play in the videos below. If you to try and make this thing, I do not think you will regret it—and it could make a fun light display for the holidays 🙂

 

This entry was posted in Arduino, video games and tagged , , , , . Bookmark the permalink.

15 Responses to Building a Line Wobbler Clone with Twang

  1. You just made all our Christmas dreams come true. Special team building exercise firmly on the horizon…

  2. George Meadows says:

    Arduinos – is there anything they can’t do?

  3. Brian says:

    Dang, this looks awesome.

  4. Pingback: Reclaim 2018 | bavatuesdays

  5. Pingback: A Few Parting Shots for 2018 | bavatuesdays

  6. stijn says:

    Hi, thanx for this inspiring build! i tried this but stumbled on this problem:
    when adjusting the code for the LEd’s to WS2812B it gives the error:
    “no matching function for call to ‘CFastLED::addleds(CRGB) [300], int)’
    i also see that the line you adjust :
    FastLED.addLeds(leds, NUM_LEDS);
    is different from the screenshot, here there is no CLOCK_PIN,
    i tried to remove this as well but nothing works…

  7. stijn says:

    Thanks for the inspiring build!
    I tried to make this but stumbled upon the next problem:
    when adjusting the line FastLED.addLeds(leds, NUM_LEDS);
    i get the error: ‘no matching function for call to ‘CFastLED::addleds(CRGB [300], int)’
    when i looked at your screenschot above the is no CLOCK_PIN in this line.
    I removed this also but i don’t get the LED’s to light up..
    Any idea’s what could be the problem here? Thanx!

  8. Stu says:

    Hi, not sure if your still monitoring this, Fabulous article and I got it working but for some reason it now when I compile it gives me the following error
    “no matching function for call to ‘CFastLED::addLeds(CRGB [300], int)'”
    I can compile when it is set to the APA102 lights but when I change it back to the WS2812B which I use it gives me the error above..
    Kinda lost to the reason why this would be.

    Just in case you can help (or if anyone else can) I am posting the twang file here

    Many thanks for any help

    // Required libs
    #include “FastLED.h”
    #include “I2Cdev.h”
    #include “MPU6050.h”
    #include “Wire.h”
    #include “toneAC.h”
    #include “iSin.h”
    #include “RunningMedian.h”

    // Included libs
    #include “Enemy.h”
    #include “Particle.h”
    #include “Spawner.h”
    #include “Lava.h”
    #include “Boss.h”
    #include “Conveyor.h”

    // MPU
    MPU6050 accelgyro;
    int16_t ax, ay, az;
    int16_t gx, gy, gz;

    // LED setup
    #define NUM_LEDS 300
    #define DATA_PIN 3
    #define CLOCK_PIN 4
    #define LED_COLOR_ORDER BGR//GBR
    #define BRIGHTNESS 150
    #define DIRECTION 1 // 0 = right to left, 1 = left to right
    #define MIN_REDRAW_INTERVAL 16 // Min redraw interval (ms) 33 = 30fps / 16 = 63fps
    #define USE_GRAVITY 1 // 0/1 use gravity (LED strip going up wall)
    #define BEND_POINT 25 // 0/1000 point at which the LED strip goes up the wall

    // GAME
    long previousMillis = 0; // Time of the last redraw
    int levelNumber = 0;
    long lastInputTime = 0;
    #define TIMEOUT 30000
    #define LEVEL_COUNT 9
    #define MAX_VOLUME 10
    iSin isin = iSin();

    // JOYSTICK
    #define JOYSTICK_ORIENTATION 1 // 0, 1 or 2 to set the angle of the joystick
    #define JOYSTICK_DIRECTION 1 // 0/1 to flip joystick direction
    #define ATTACK_THRESHOLD 30000 // The threshold that triggers an attack
    #define JOYSTICK_DEADZONE 5 // Angle to ignore
    int joystickTilt = 0; // Stores the angle of the joystick
    int joystickWobble = 0; // Stores the max amount of acceleration (wobble)

    // WOBBLE ATTACK
    #define ATTACK_WIDTH 70 // Width of the wobble attack, world is 1000 wide
    #define ATTACK_DURATION 500 // Duration of a wobble attack (ms)
    long attackMillis = 0; // Time the attack started
    bool attacking = 0; // Is the attack in progress?
    #define BOSS_WIDTH 40

    // PLAYER
    #define MAX_PLAYER_SPEED 10 // Max move speed of the player
    char* stage; // what stage the game is at (PLAY/DEAD/WIN/GAMEOVER)
    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 = 3;

    // POOLS
    int lifeLEDs[3] = {52, 50, 40};
    Enemy enemyPool[10] = {
    Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy()
    };
    int const enemyCount = 10;
    Particle particlePool[40] = {
    Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle()
    };
    int const particleCount = 40;
    Spawner spawnPool[2] = {
    Spawner(), Spawner()
    };
    int const spawnCount = 2;
    Lava lavaPool[4] = {
    Lava(), Lava(), Lava(), Lava()
    };
    int const lavaCount = 4;
    Conveyor conveyorPool[2] = {
    Conveyor(), Conveyor()
    };
    int const conveyorCount = 2;
    Boss boss = Boss();

    CRGB leds[NUM_LEDS];
    RunningMedian MPUAngleSamples = RunningMedian(5);
    RunningMedian MPUWobbleSamples = RunningMedian(5);

    void setup() {
    Serial.begin(9600);
    while (!Serial);

    // MPU
    Wire.begin();
    accelgyro.initialize();

    // Fast LED
    FastLED.addLeds(leds, NUM_LEDS);
    FastLED.setBrightness(BRIGHTNESS);
    FastLED.setDither(1);

    // Life LEDs
    for(int i = 0; i= MIN_REDRAW_INTERVAL) {
    getInput();
    long frameTimer = mm;
    previousMillis = mm;

    if(abs(joystickTilt) > JOYSTICK_DEADZONE){
    lastInputTime = mm;
    if(stage == “SCREENSAVER”){
    levelNumber = -1;
    stageStartTime = mm;
    stage = “WIN”;
    }
    }else{
    if(lastInputTime+TIMEOUT < mm){
    stage = "SCREENSAVER";
    }
    }
    if(stage == "SCREENSAVER"){
    screenSaverTick();
    }else if(stage == "PLAY"){
    // PLAYING
    if(attacking && attackMillis+ATTACK_DURATION ATTACK_THRESHOLD){
    attackMillis = mm;
    attacking = 1;
    }

    // If still not attacking, move!
    playerPosition += playerPositionModifier;
    if(!attacking){
    int moveAmount = (joystickTilt/6.0);
    if(DIRECTION) moveAmount = -moveAmount;
    moveAmount = constrain(moveAmount, -MAX_PLAYER_SPEED, MAX_PLAYER_SPEED);
    playerPosition -= moveAmount;
    if(playerPosition = 300 && !boss.Alive()) {
    // Reached exit!
    levelComplete();
    return;
    }
    }

    if(inLava(playerPosition)){
    die();
    }

    // Ticks and draw calls
    FastLED.clear();
    tickConveyors();
    tickSpawners();
    tickBoss();
    tickLava();
    tickEnemies();
    drawPlayer();
    drawAttack();
    drawExit();
    }else if(stage == “DEAD”){
    // DEAD
    FastLED.clear();
    if(!tickParticles()){
    loadLevel();
    }
    }else if(stage == “WIN”){
    // LEVEL COMPLETE
    FastLED.clear();
    if(stageStartTime+500 > mm){
    int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0);
    for(int i = NUM_LEDS; i>= n; i–){
    brightness = 255;
    leds[i] = CRGB(0, brightness, 0);
    }
    SFXwin();
    }else if(stageStartTime+1000 > mm){
    int n = max(map(((mm-stageStartTime)), 500, 1000, NUM_LEDS, 0), 0);
    for(int i = 0; i mm){
    leds[0] = CRGB(0, 255, 0);
    }else{
    nextLevel();
    }
    }else if(stage == “COMPLETE”){
    FastLED.clear();
    SFXcomplete();
    if(stageStartTime+500 > mm){
    int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0);
    for(int i = NUM_LEDS; i>= n; i–){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else if(stageStartTime+5000 > mm){
    for(int i = NUM_LEDS; i>= 0; i–){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else if(stageStartTime+5500 > mm){
    int n = max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0);
    for(int i = 0; i< n; i++){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else{
    nextLevel();
    }
    }else if(stage == "GAMEOVER"){
    // GAME OVER!
    FastLED.clear();
    stageStartTime = 0;
    }

    Serial.print(millis()-mm);
    Serial.print(" – ");
    FastLED.show();
    Serial.println(millis()-mm);
    }
    }

    // ———————————
    // ———— LEVELS ————-
    // ———————————
    void loadLevel(){
    updateLives();
    cleanupLevel();
    playerPosition = 0;
    playerAlive = 1;
    switch(levelNumber){
    case 0:
    // Left or right?
    playerPosition = 200;
    spawnEnemy(1, 0, 0, 0);
    break;
    case 1:
    // Slow moving enemy
    spawnEnemy(900, 0, 1, 0);
    break;
    case 2:
    // Spawning enemies at exit every 2 seconds
    spawnPool[0].Spawn(1000, 3000, 2, 0, 0);
    break;
    case 3:
    // Lava intro
    spawnLava(400, 490, 2000, 2000, 0, "OFF");
    spawnPool[0].Spawn(1000, 5500, 3, 0, 0);
    break;
    case 4:
    // Sin enemy
    spawnEnemy(700, 1, 7, 275);
    spawnEnemy(500, 1, 5, 250);
    break;
    case 5:
    // Conveyor
    spawnConveyor(100, 600, -1);
    spawnEnemy(800, 0, 0, 0);
    break;
    case 6:
    // Conveyor of enemies
    spawnConveyor(50, 1000, 1);
    spawnEnemy(300, 0, 0, 0);
    spawnEnemy(400, 0, 0, 0);
    spawnEnemy(500, 0, 0, 0);
    spawnEnemy(600, 0, 0, 0);
    spawnEnemy(700, 0, 0, 0);
    spawnEnemy(800, 0, 0, 0);
    spawnEnemy(900, 0, 0, 0);
    break;
    case 7:
    // Lava run
    spawnLava(195, 300, 2000, 2000, 0, "OFF");
    spawnLava(350, 455, 2000, 2000, 0, "OFF");
    spawnLava(510, 610, 2000, 2000, 0, "OFF");
    spawnLava(660, 760, 2000, 2000, 0, "OFF");
    spawnPool[0].Spawn(1000, 3800, 4, 0, 0);
    break;
    case 8:
    // Sin enemy #2
    spawnEnemy(700, 1, 7, 275);
    spawnEnemy(500, 1, 5, 250);
    spawnPool[0].Spawn(1000, 5500, 4, 0, 3000);
    spawnPool[1].Spawn(0, 5500, 5, 1, 10000);
    spawnConveyor(100, 900, -1);
    break;
    case 9:
    // Boss
    spawnBoss();
    break;
    }
    stageStartTime = millis();
    stage = "PLAY";
    }

    void spawnBoss(){
    boss.Spawn();
    moveBoss();
    }

    void moveBoss(){
    int spawnSpeed = 2500;
    if(boss._lives == 2) spawnSpeed = 2000;
    if(boss._lives == 1) spawnSpeed = 1500;
    spawnPool[0].Spawn(boss._pos, spawnSpeed, 3, 0, 0);
    spawnPool[1].Spawn(boss._pos, spawnSpeed, 3, 1, 0);
    }

    void spawnEnemy(int pos, int dir, int sp, int wobble){
    for(int e = 0; e playerPosition?1:-1;
    return;
    }
    }
    }

    void spawnLava(int left, int right, int ontime, int offtime, int offset, char* state){
    for(int i = 0; i<lavaCount; i++){
    if(!lavaPool[i].Alive()){
    lavaPool[i].Spawn(left, right, ontime, offtime, offset, state);
    return;
    }
    }
    }

    void spawnConveyor(int startPoint, int endPoint, int dir){
    for(int i = 0; i<conveyorCount; i++){
    if(!conveyorPool[i]._alive){
    conveyorPool[i].Spawn(startPoint, endPoint, dir);
    return;
    }
    }
    }

    void cleanupLevel(){
    for(int i = 0; i<enemyCount; i++){
    enemyPool[i].Kill();
    }
    for(int i = 0; i<particleCount; i++){
    particlePool[i].Kill();
    }
    for(int i = 0; i<spawnCount; i++){
    spawnPool[i].Kill();
    }
    for(int i = 0; i<lavaCount; i++){
    lavaPool[i].Kill();
    }
    for(int i = 0; i LEVEL_COUNT) levelNumber = 0;
    loadLevel();
    }

    void gameOver(){
    levelNumber = 0;
    loadLevel();
    }

    void die(){
    playerAlive = 0;
    if(levelNumber > 0) lives –;
    updateLives();
    if(lives == 0){
    levelNumber = 0;
    lives = 3;
    }
    for(int p = 0; p < particleCount; p++){
    particlePool[p].Spawn(playerPosition);
    }
    stageStartTime = millis();
    stage = "DEAD";
    killTime = millis();
    }

    // ———————————-
    // ——– TICKS & RENDERS ———
    // ———————————-
    void tickEnemies(){
    for(int i = 0; i playerPosition-(ATTACK_WIDTH/2) && enemyPool[i]._pos < playerPosition+(ATTACK_WIDTH/2)){
    enemyPool[i].Kill();
    SFXkill();
    }
    }
    if(inLava(enemyPool[i]._pos)){
    enemyPool[i].Kill();
    SFXkill();
    }
    // Draw (if still alive)
    if(enemyPool[i].Alive()) {
    leds[getLED(enemyPool[i]._pos)] = CRGB(255, 0, 0);
    }
    // Hit player?
    if(
    (enemyPool[i].playerSide == 1 && enemyPool[i]._pos = playerPosition)
    ){
    die();
    return;
    }
    }
    }
    }

    void tickBoss(){
    // DRAW
    if(boss.Alive()){
    boss._ticks ++;
    for(int i = getLED(boss._pos-BOSS_WIDTH/2); i getLED(boss._pos – BOSS_WIDTH/2) && getLED(playerPosition) = 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))
    ){
    boss.Hit();
    if(boss.Alive()){
    moveBoss();
    }else{
    spawnPool[0].Kill();
    spawnPool[1].Kill();
    }
    }
    }
    }
    }

    void drawPlayer(){
    leds[getLED(playerPosition)] = CRGB(0, 255, 0);
    }

    void drawExit(){
    if(!boss.Alive()){
    leds[NUM_LEDS-1] = CRGB(0, 0, 255);
    }
    }

    void tickSpawners(){
    long mm = millis();
    for(int s = 0; s<spawnCount; s++){
    if(spawnPool[s].Alive() && spawnPool[s]._activate < mm){
    if(spawnPool[s]._lastSpawned + spawnPool[s]._rate < mm || spawnPool[s]._lastSpawned == 0){
    spawnEnemy(spawnPool[s]._pos, spawnPool[s]._dir, spawnPool[s]._sp, 0);
    spawnPool[s]._lastSpawned = mm;
    }
    }
    }
    }

    void tickLava(){
    int A, B, p, i, brightness, flicker;
    long mm = millis();
    Lava LP;
    for(i = 0; i<lavaCount; i++){
    flicker = random8(5);
    LP = lavaPool[i];
    if(LP.Alive()){
    A = getLED(LP._left);
    B = getLED(LP._right);
    if(LP._state == "OFF"){
    if(LP._lastOn + LP._offtime < mm){
    LP._state = "ON";
    LP._lastOn = mm;
    }
    for(p = A; p<= B; p++){
    leds[p] = CRGB(3+flicker, (3+flicker)/1.5, 0);
    }
    }else if(LP._state == "ON"){
    if(LP._lastOn + LP._ontime < mm){
    LP._state = "OFF";
    LP._lastOn = mm;
    }
    for(p = A; p<= B; p++){
    leds[p] = CRGB(150+flicker, 100+flicker, 0);
    }
    }
    }
    lavaPool[i] = LP;
    }
    }

    bool tickParticles(){
    bool stillActive = false;
    for(int p = 0; p < particleCount; p++){
    if(particlePool[p].Alive()){
    particlePool[p].Tick(USE_GRAVITY);
    leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0);
    stillActive = true;
    }
    }
    return stillActive;
    }

    void tickConveyors(){
    int b, dir, n, i, ss, ee, led;
    long m = 10000+millis();
    playerPositionModifier = 0;

    for(i = 0; i<conveyorCount; i++){
    if(conveyorPool[i]._alive){
    dir = conveyorPool[i]._dir;
    ss = getLED(conveyorPool[i]._startPoint);
    ee = getLED(conveyorPool[i]._endPoint);
    for(led = ss; led 0) leds[led] = CRGB(0, 0, b);
    }

    if(playerPosition > conveyorPool[i]._startPoint && playerPosition < conveyorPool[i]._endPoint){
    if(dir == -1){
    playerPositionModifier = -(MAX_PLAYER_SPEED-4);
    }else{
    playerPositionModifier = (MAX_PLAYER_SPEED-4);
    }
    }
    }
    }
    }

    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 90) {
    n = 255;
    leds[getLED(playerPosition)] = CRGB(255, 255, 255);
    }else{
    n = 0;
    leds[getLED(playerPosition)] = CRGB(0, 255, 0);
    }
    leds[getLED(playerPosition-(ATTACK_WIDTH/2))] = CRGB(n, n, 255);
    leds[getLED(playerPosition+(ATTACK_WIDTH/2))] = CRGB(n, n, 255);
    }

    int getLED(int pos){
    // The world is 1000 pixels wide, this converts world units into an LED number
    return constrain((int)map(pos, 0, 300, 0, NUM_LEDS-1), 0, NUM_LEDS-1);
    }

    bool inLava(int pos){
    // Returns if the player is in active lava
    int i;
    Lava LP;
    for(i = 0; i<lavaCount; i++){
    LP = lavaPool[i];
    if(LP.Alive() && LP._state == "ON"){
    if(LP._left pos) return true;
    }
    }
    return false;
    }

    void updateLives(){
    // Updates the life LEDs to show how many lives the player has left
    for(int i = 0; ii?HIGH:LOW);
    }
    }

    // ———————————
    // ——— SCREENSAVER ———–
    // ———————————
    void screenSaverTick(){
    int n, b, c, i;
    long mm = millis();
    int mode = (mm/20000)%2;

    for(i = 0; i<NUM_LEDS; i++){
    leds[i].nscale8(250);
    }
    if(mode == 0){
    // 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<NUM_LEDS; i++){
    if(i%10 == n){
    leds[i] = CHSV( c, 255, 150);
    }
    }
    }else if(mode == 1){
    // Random flashes
    randomSeed(mm);
    for(i = 0; i+90 value to joystickTilt
    // and any value to joystickWobble that is greater than ATTACK_THRESHOLD (defined at start)
    // For example you could use 3 momentery buttons:
    // if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90;
    // if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90;
    // if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD;

    accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
    int a = (JOYSTICK_ORIENTATION == 0?ax:(JOYSTICK_ORIENTATION == 1?ay:az))/166;
    int g = (JOYSTICK_ORIENTATION == 0?gx:(JOYSTICK_ORIENTATION == 1?gy:gz));
    if(abs(a) 0) a -= JOYSTICK_DEADZONE;
    if(a < 0) a += JOYSTICK_DEADZONE;
    MPUAngleSamples.add(a);
    MPUWobbleSamples.add(g);

    joystickTilt = MPUAngleSamples.getMedian();
    if(JOYSTICK_DIRECTION == 1) {
    joystickTilt = 0-joystickTilt;
    }
    joystickWobble = abs(MPUWobbleSamples.getHighest());
    }

    // ———————————
    // ————– SFX ————–
    // ———————————
    void SFXtilt(int amount){
    int f = map(abs(amount), 0, 90, 80, 900)+random8(100);
    if(playerPositionModifier 0) f += 200;
    toneAC(f, min(min(abs(amount)/9, 5), MAX_VOLUME));

    }
    void SFXattacking(){
    int freq = map(sin(millis()/2.0)*1000.0, -1000, 1000, 500, 600);
    if(random8(5)== 0){
    freq *= 3;
    }
    toneAC(freq, MAX_VOLUME);
    }
    void SFXdead(){
    int freq = max(1000 – (millis()-killTime), 10);
    freq += random8(200);
    int vol = max(10 – (millis()-killTime)/200, 0);
    toneAC(freq, MAX_VOLUME);
    }
    void SFXkill(){
    toneAC(2000, MAX_VOLUME, 1000, true);
    }
    void SFXwin(){
    int freq = (millis()-stageStartTime)/3.0;
    freq += map(sin(millis()/20.0)*1000.0, -1000, 1000, 0, 20);
    int vol = 10;//max(10 – (millis()-stageStartTime)/200, 0);
    toneAC(freq, MAX_VOLUME);
    }

    void SFXcomplete(){
    noToneAC();
    }

  9. SamHay says:

    This is so great!

    In downloading the repo and installing the libraries, my first compile gets a boatload of “redifinition errors” for variables and functions. have any of you all had the same thing happen?

  10. Jim says:

    Hi @SamHay,

    I had the same problem when I first set this up. I can’t remember what the fix was, but here is the code that I use. This works for me:

    // Required libs
    #include “FastLED.h”
    #include “I2Cdev.h”
    #include “MPU6050.h”
    #include “Wire.h”
    #include “toneAC.h”
    #include “iSin.h”
    #include “RunningMedian.h”

    // Included libs
    #include “Enemy.h”
    #include “Particle.h”
    #include “Spawner.h”
    #include “Lava.h”
    #include “Boss.h”
    #include “Conveyor.h”

    // MPU
    MPU6050 accelgyro;
    int16_t ax, ay, az;
    int16_t gx, gy, gz;

    // LED setup
    #define NUM_LEDS 600
    #define DATA_PIN 3
    #define CLOCK_PIN 4
    #define LED_COLOR_ORDER BGR //if colours aren’t working, try GRB or GBR
    #define BRIGHTNESS 150 //Use a lower value for lower current power supplies(<2 amps)
    #define DIRECTION 1 // 0 = right to left, 1 = left to right
    #define MIN_REDRAW_INTERVAL 16 // Min redraw interval (ms) 33 = 30fps / 16 = 63fps
    #define USE_GRAVITY 0 // 0/1 use gravity (LED strip going up wall)
    #define BEND_POINT 300 // 0/1000 point at which the LED strip goes up the wall
    #define LED_TYPE WS2811//type of LED strip to use(APA102 – DotStar, WS2811 – NeoPixel) For Neopixels, uncomment line #108 and comment out line #106

    // GAME
    long previousMillis = 0; // Time of the last redraw
    int levelNumber = 0;
    long lastInputTime = 0;
    #define TIMEOUT 30000
    #define LEVEL_COUNT 9
    #define MAX_VOLUME 10
    iSin isin = iSin();

    // JOYSTICK
    #define JOYSTICK_ORIENTATION 1 // 0, 1 or 2 to set the angle of the joystick
    #define JOYSTICK_DIRECTION 1 // 0/1 to flip joystick direction
    #define ATTACK_THRESHOLD 30000 // The threshold that triggers an attack
    #define JOYSTICK_DEADZONE 5 // Angle to ignore
    int joystickTilt = 0; // Stores the angle of the joystick
    int joystickWobble = 0; // Stores the max amount of acceleration (wobble)

    // WOBBLE ATTACK
    #define ATTACK_WIDTH 200 // Width of the wobble attack, world is 1000 wide
    #define ATTACK_DURATION 500 // Duration of a wobble attack (ms)
    long attackMillis = 0; // Time the attack started
    bool attacking = 0; // Is the attack in progress?
    #define BOSS_WIDTH 40

    // PLAYER
    #define MAX_PLAYER_SPEED 6 // Max move speed of the player
    char* stage; // what stage the game is at (PLAY/DEAD/WIN/GAMEOVER)
    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 = 3;

    // POOLS
    int lifeLEDs[3] = {52, 50, 40};
    Enemy enemyPool[10] = {
    Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy(), Enemy()
    };
    int const enemyCount = 10;
    Particle particlePool[40] = {
    Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle(), Particle()
    };
    int const particleCount = 40;
    Spawner spawnPool[2] = {
    Spawner(), Spawner()
    };
    int const spawnCount = 2;
    Lava lavaPool[4] = {
    Lava(), Lava(), Lava(), Lava()
    };
    int const lavaCount = 4;
    Conveyor conveyorPool[2] = {
    Conveyor(), Conveyor()
    };
    int const conveyorCount = 2;
    Boss boss = Boss();

    CRGB leds[NUM_LEDS];
    RunningMedian MPUAngleSamples = RunningMedian(5);
    RunningMedian MPUWobbleSamples = RunningMedian(5);

    void setup() {
    Serial.begin(9600);
    while (!Serial);

    // MPU
    Wire.begin();
    accelgyro.initialize();

    // Fast LED
    //FastLED.addLeds(leds, NUM_LEDS);
    //If using Neopixels, use
    FastLED.addLeds(leds, NUM_LEDS);
    FastLED.setBrightness(BRIGHTNESS);
    FastLED.setDither(1);

    // Life LEDs
    for(int i = 0; i= MIN_REDRAW_INTERVAL) {
    getInput();
    long frameTimer = mm;
    previousMillis = mm;

    if(abs(joystickTilt) > JOYSTICK_DEADZONE){
    lastInputTime = mm;
    if(stage == “SCREENSAVER”){
    levelNumber = -1;
    stageStartTime = mm;
    stage = “WIN”;
    }
    }else{
    if(lastInputTime+TIMEOUT < mm){
    stage = "SCREENSAVER";
    }
    }
    if(stage == "SCREENSAVER"){
    screenSaverTick();
    }else if(stage == "PLAY"){
    // PLAYING
    if(attacking && attackMillis+ATTACK_DURATION ATTACK_THRESHOLD){
    attackMillis = mm;
    attacking = 1;
    }

    // If still not attacking, move!
    playerPosition += playerPositionModifier;
    if(!attacking){
    int moveAmount = (joystickTilt/6.0);
    if(DIRECTION) moveAmount = -moveAmount;
    moveAmount = constrain(moveAmount, -MAX_PLAYER_SPEED, MAX_PLAYER_SPEED);
    playerPosition -= moveAmount;
    if(playerPosition = 1000 && !boss.Alive()) {
    // Reached exit!
    levelComplete();
    return;
    }
    }

    if(inLava(playerPosition)){
    die();
    }

    // Ticks and draw calls
    FastLED.clear();
    tickConveyors();
    tickSpawners();
    tickBoss();
    tickLava();
    tickEnemies();
    drawPlayer();
    drawAttack();
    drawExit();
    }else if(stage == “DEAD”){
    // DEAD
    FastLED.clear();
    if(!tickParticles()){
    loadLevel();
    }
    }else if(stage == “WIN”){
    // LEVEL COMPLETE
    FastLED.clear();
    if(stageStartTime+500 > mm){
    int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0);
    for(int i = NUM_LEDS; i>= n; i–){
    brightness = 255;
    leds[i] = CRGB(0, brightness, 0);
    }
    SFXwin();
    }else if(stageStartTime+1000 > mm){
    int n = max(map(((mm-stageStartTime)), 500, 1000, NUM_LEDS, 0), 0);
    for(int i = 0; i mm){
    leds[0] = CRGB(0, 255, 0);
    }else{
    nextLevel();
    }
    }else if(stage == “COMPLETE”){
    FastLED.clear();
    SFXcomplete();
    if(stageStartTime+500 > mm){
    int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0);
    for(int i = NUM_LEDS; i>= n; i–){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else if(stageStartTime+5000 > mm){
    for(int i = NUM_LEDS; i>= 0; i–){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else if(stageStartTime+5500 > mm){
    int n = max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0);
    for(int i = 0; i< n; i++){
    brightness = (sin(((i*10)+mm)/500.0)+1)*255;
    leds[i].setHSV(brightness, 255, 50);
    }
    }else{
    nextLevel();
    }
    }else if(stage == "GAMEOVER"){
    // GAME OVER!
    FastLED.clear();
    stageStartTime = 0;
    }

    Serial.print(millis()-mm);
    Serial.print(" – ");
    FastLED.show();
    Serial.println(millis()-mm);
    }
    }

    // ———————————
    // ———— LEVELS ————-
    // ———————————
    void loadLevel(){
    updateLives();
    cleanupLevel();
    playerPosition = 0;
    playerAlive = 1;
    switch(levelNumber){
    case 0:
    // Left or right?
    playerPosition = 200;
    spawnEnemy(1, 0, 0, 0);
    break;
    case 1:
    // Slow moving enemy
    spawnEnemy(900, 0, 1, 0);
    break;
    case 2:
    // Spawning enemies at exit every 2 seconds
    spawnPool[0].Spawn(1000, 3000, 2, 0, 0);
    break;
    case 3:
    // Lava intro
    spawnLava(400, 490, 2000, 2000, 0, "OFF");
    spawnPool[0].Spawn(1000, 5500, 3, 0, 0);
    break;
    case 4:
    // Sin enemy
    spawnEnemy(700, 1, 7, 275);
    spawnEnemy(500, 1, 5, 250);
    break;
    case 5:
    // Conveyor
    spawnConveyor(100, 600, -1);
    spawnEnemy(800, 0, 0, 0);
    break;
    case 6:
    // Conveyor of enemies
    spawnConveyor(50, 1000, 1);
    spawnEnemy(300, 0, 0, 0);
    spawnEnemy(400, 0, 0, 0);
    spawnEnemy(500, 0, 0, 0);
    spawnEnemy(600, 0, 0, 0);
    spawnEnemy(700, 0, 0, 0);
    spawnEnemy(800, 0, 0, 0);
    spawnEnemy(900, 0, 0, 0);
    break;
    case 7:
    // Lava run
    spawnLava(195, 300, 2000, 2000, 0, "OFF");
    spawnLava(350, 455, 2000, 2000, 0, "OFF");
    spawnLava(510, 610, 2000, 2000, 0, "OFF");
    spawnLava(660, 760, 2000, 2000, 0, "OFF");
    spawnPool[0].Spawn(1000, 3800, 4, 0, 0);
    break;
    case 8:
    // Sin enemy #2
    spawnEnemy(700, 1, 7, 275);
    spawnEnemy(500, 1, 5, 250);
    spawnPool[0].Spawn(1000, 5500, 4, 0, 3000);
    spawnPool[1].Spawn(0, 5500, 5, 1, 10000);
    spawnConveyor(100, 900, -1);
    break;
    case 9:
    // Boss
    spawnBoss();
    break;
    }
    stageStartTime = millis();
    stage = "PLAY";
    }

    void spawnBoss(){
    boss.Spawn();
    moveBoss();
    }

    void moveBoss(){
    int spawnSpeed = 2500;
    if(boss._lives == 2) spawnSpeed = 2000;
    if(boss._lives == 1) spawnSpeed = 1500;
    spawnPool[0].Spawn(boss._pos, spawnSpeed, 3, 0, 0);
    spawnPool[1].Spawn(boss._pos, spawnSpeed, 3, 1, 0);
    }

    void spawnEnemy(int pos, int dir, int sp, int wobble){
    for(int e = 0; e playerPosition?1:-1;
    return;
    }
    }
    }

    void spawnLava(int left, int right, int ontime, int offtime, int offset, char* state){
    for(int i = 0; i<lavaCount; i++){
    if(!lavaPool[i].Alive()){
    lavaPool[i].Spawn(left, right, ontime, offtime, offset, state);
    return;
    }
    }
    }

    void spawnConveyor(int startPoint, int endPoint, int dir){
    for(int i = 0; i<conveyorCount; i++){
    if(!conveyorPool[i]._alive){
    conveyorPool[i].Spawn(startPoint, endPoint, dir);
    return;
    }
    }
    }

    void cleanupLevel(){
    for(int i = 0; i<enemyCount; i++){
    enemyPool[i].Kill();
    }
    for(int i = 0; i<particleCount; i++){
    particlePool[i].Kill();
    }
    for(int i = 0; i<spawnCount; i++){
    spawnPool[i].Kill();
    }
    for(int i = 0; i<lavaCount; i++){
    lavaPool[i].Kill();
    }
    for(int i = 0; i LEVEL_COUNT) levelNumber = 0;
    loadLevel();
    }

    void gameOver(){
    levelNumber = 0;
    loadLevel();
    }

    void die(){
    playerAlive = 0;
    if(levelNumber > 0) lives –;
    updateLives();
    if(lives == 0){
    levelNumber = 0;
    lives = 3;
    }
    for(int p = 0; p < particleCount; p++){
    particlePool[p].Spawn(playerPosition);
    }
    stageStartTime = millis();
    stage = "DEAD";
    killTime = millis();
    }

    // ———————————-
    // ——– TICKS & RENDERS ———
    // ———————————-
    void tickEnemies(){
    for(int i = 0; i playerPosition-(ATTACK_WIDTH/2) && enemyPool[i]._pos < playerPosition+(ATTACK_WIDTH/2)){
    enemyPool[i].Kill();
    SFXkill();
    }
    }
    if(inLava(enemyPool[i]._pos)){
    enemyPool[i].Kill();
    SFXkill();
    }
    // Draw (if still alive)
    if(enemyPool[i].Alive()) {
    leds[getLED(enemyPool[i]._pos)] = CRGB(255, 0, 0);
    }
    // Hit player?
    if(
    (enemyPool[i].playerSide == 1 && enemyPool[i]._pos = playerPosition)
    ){
    die();
    return;
    }
    }
    }
    }

    void tickBoss(){
    // DRAW
    if(boss.Alive()){
    boss._ticks ++;
    for(int i = getLED(boss._pos-BOSS_WIDTH/2); i getLED(boss._pos – BOSS_WIDTH/2) && getLED(playerPosition) = 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))
    ){
    boss.Hit();
    if(boss.Alive()){
    moveBoss();
    }else{
    spawnPool[0].Kill();
    spawnPool[1].Kill();
    }
    }
    }
    }
    }

    void drawPlayer(){
    leds[getLED(playerPosition)] = CRGB(0, 255, 0);
    }

    void drawExit(){
    if(!boss.Alive()){
    leds[NUM_LEDS-1] = CRGB(0, 0, 255);
    }
    }

    void tickSpawners(){
    long mm = millis();
    for(int s = 0; s<spawnCount; s++){
    if(spawnPool[s].Alive() && spawnPool[s]._activate < mm){
    if(spawnPool[s]._lastSpawned + spawnPool[s]._rate < mm || spawnPool[s]._lastSpawned == 0){
    spawnEnemy(spawnPool[s]._pos, spawnPool[s]._dir, spawnPool[s]._sp, 0);
    spawnPool[s]._lastSpawned = mm;
    }
    }
    }
    }

    void tickLava(){
    int A, B, p, i, brightness, flicker;
    long mm = millis();
    Lava LP;
    for(i = 0; i<lavaCount; i++){
    flicker = random8(5);
    LP = lavaPool[i];
    if(LP.Alive()){
    A = getLED(LP._left);
    B = getLED(LP._right);
    if(LP._state == "OFF"){
    if(LP._lastOn + LP._offtime < mm){
    LP._state = "ON";
    LP._lastOn = mm;
    }
    for(p = A; p<= B; p++){
    leds[p] = CRGB(3+flicker, (3+flicker)/1.5, 0);
    }
    }else if(LP._state == "ON"){
    if(LP._lastOn + LP._ontime < mm){
    LP._state = "OFF";
    LP._lastOn = mm;
    }
    for(p = A; p<= B; p++){
    leds[p] = CRGB(150+flicker, 100+flicker, 0);
    }
    }
    }
    lavaPool[i] = LP;
    }
    }

    bool tickParticles(){
    bool stillActive = false;
    for(int p = 0; p < particleCount; p++){
    if(particlePool[p].Alive()){
    particlePool[p].Tick(USE_GRAVITY);
    leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0);
    stillActive = true;
    }
    }
    return stillActive;
    }

    void tickConveyors(){
    int b, dir, n, i, ss, ee, led;
    long m = 10000+millis();
    playerPositionModifier = 0;

    for(i = 0; i<conveyorCount; i++){
    if(conveyorPool[i]._alive){
    dir = conveyorPool[i]._dir;
    ss = getLED(conveyorPool[i]._startPoint);
    ee = getLED(conveyorPool[i]._endPoint);
    for(led = ss; led 0) leds[led] = CRGB(0, 0, b);
    }

    if(playerPosition > conveyorPool[i]._startPoint && playerPosition < conveyorPool[i]._endPoint){
    if(dir == -1){
    playerPositionModifier = -(MAX_PLAYER_SPEED-4);
    }else{
    playerPositionModifier = (MAX_PLAYER_SPEED-4);
    }
    }
    }
    }
    }

    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 90) {
    n = 255;
    leds[getLED(playerPosition)] = CRGB(255, 255, 255);
    }else{
    n = 0;
    leds[getLED(playerPosition)] = CRGB(0, 255, 0);
    }
    leds[getLED(playerPosition-(ATTACK_WIDTH/2))] = CRGB(n, n, 255);
    leds[getLED(playerPosition+(ATTACK_WIDTH/2))] = CRGB(n, n, 255);
    }

    int getLED(int pos){
    // The world is 1000 pixels wide, this converts world units into an LED number
    return constrain((int)map(pos, 0, 1000, 0, NUM_LEDS-1), 0, NUM_LEDS-1);
    }

    bool inLava(int pos){
    // Returns if the player is in active lava
    int i;
    Lava LP;
    for(i = 0; i<lavaCount; i++){
    LP = lavaPool[i];
    if(LP.Alive() && LP._state == "ON"){
    if(LP._left pos) return true;
    }
    }
    return false;
    }

    void updateLives(){
    // Updates the life LEDs to show how many lives the player has left
    for(int i = 0; ii?HIGH:LOW);
    }
    }

    // ———————————
    // ——— SCREENSAVER ———–
    // ———————————
    void screenSaverTick(){
    int n, b, c, i;
    long mm = millis();
    int mode = (mm/20000)%2;

    for(i = 0; i<NUM_LEDS; i++){
    leds[i].nscale8(250);
    }
    if(mode == 0){
    // 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<NUM_LEDS; i++){
    if(i%10 == n){
    leds[i] = CHSV( c, 255, 150);
    }
    }
    }else if(mode == 1){
    // Random flashes
    randomSeed(mm);
    for(i = 0; i+90 value to joystickTilt
    // and any value to joystickWobble that is greater than ATTACK_THRESHOLD (defined at start)
    // For example you could use 3 momentery buttons:
    // if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90;
    // if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90;
    // if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD;

    accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
    int a = (JOYSTICK_ORIENTATION == 0?ax:(JOYSTICK_ORIENTATION == 1?ay:az))/166;
    int g = (JOYSTICK_ORIENTATION == 0?gx:(JOYSTICK_ORIENTATION == 1?gy:gz));
    if(abs(a) 0) a -= JOYSTICK_DEADZONE;
    if(a < 0) a += JOYSTICK_DEADZONE;
    MPUAngleSamples.add(a);
    MPUWobbleSamples.add(g);

    joystickTilt = MPUAngleSamples.getMedian();
    if(JOYSTICK_DIRECTION == 1) {
    joystickTilt = 0-joystickTilt;
    }
    joystickWobble = abs(MPUWobbleSamples.getHighest());
    }

    // ———————————
    // ————– SFX ————–
    // ———————————
    void SFXtilt(int amount){
    int f = map(abs(amount), 0, 90, 80, 900)+random8(100);
    if(playerPositionModifier 0) f += 200;
    toneAC(f, min(min(abs(amount)/9, 5), MAX_VOLUME));

    }
    void SFXattacking(){
    int freq = map(sin(millis()/2.0)*1000.0, -1000, 1000, 500, 600);
    if(random8(5)== 0){
    freq *= 3;
    }
    toneAC(freq, MAX_VOLUME);
    }
    void SFXdead(){
    int freq = max(1000 – (millis()-killTime), 10);
    freq += random8(200);
    int vol = max(10 – (millis()-killTime)/200, 0);
    toneAC(freq, MAX_VOLUME);
    }
    void SFXkill(){
    toneAC(2000, MAX_VOLUME, 1000, true);
    }
    void SFXwin(){
    int freq = (millis()-stageStartTime)/3.0;
    freq += map(sin(millis()/20.0)*1000.0, -1000, 1000, 0, 20);
    int vol = 10;//max(10 – (millis()-stageStartTime)/200, 0);
    toneAC(freq, MAX_VOLUME);
    }

    void SFXcomplete(){
    noToneAC();
    }

  11. Pingback: TWANG! Building an Arduino-Based LED Strip Game | Jasongraphix

Leave a Reply to Brian Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.