Laser tag S08 – Programming Buttons

Love Jesus, Love Code

This tutorial will go for about 1hr.

This section will help you to understand how buttons work. We will be starting with a simple button that creates a shot, then we will aim to add in different fire modes such as automatic, burst and semi-auto.

In this tutorial, we will:

  1. Create a simple trigger button to flash lights and play sound
  2. Add different shot modes
  3. Create a reload button
  4. Change sound effects to sci-fi

During the game the team lights will be on dull. When the gun shoots the LED’s will go bright white.

From the previous tutorial, make sure you save the program NodeMCU_LaserTag_S08

1. Create a simple trigger button to flash lights and play sound

To get the trigger button up and running, we will be programming an automatic trigger to start with. Things will get more complex later.

simple shoot flowchart
  1. In the tab called NodeMCU_LaserTag_S08.ino in the //pin declarations settings, we need to declare the pins used for the buttons
int triggerPin = 0; // D3 - Push button for primary fire. Low = pressed
int reloadPin = 12; // D6 - Push button for reload button. Low = pressed
  1. In the tab called NodeMCU_LaserTag_S08.ino just below the “// Current Data on Player” section, add another section called “//Code Variables”.
// Code Variables
bool deadLight = false;
bool canFire = true; 
int triggerButton = 0; // Trigger Button reading
int previousTriggerState = 0; // Last Trigger Reading
int reloadButton = 0; // Reload Button reading
int previousReloadState = 0; // Last Trigger 2 Reading (For reaload)

The variable triggerButton and previousTriggerState are used together to work out if the trigger button has been changed such as if the trigger button was pressed or released. This will be important for semi-automatic firing mode, and to ensure only one reload is done at a time.

  1. In the main program tab at the end of the setup() loop, add the following code:
  //setup pins
  pinMode(triggerPin, INPUT_PULLUP);
  pinMode(reloadPin, INPUT_PULLUP);

The pinMode determines if the pin is to have some voltage or be set to earth. When the button is pressed, it earths out the pin. If the pin is already earthed, it will act like the button is constantly pressed down. We will set both the trigger and reload pin to the pull-up position.

  1. Create a new tab called Shoot
  2. In the shoot tab, create a new subroutine called triggers. This subroutine will check for button presses and will be called on the main loop. Copy in the following code:
void triggers() { // Checks to see if the triggers have been pressed

  triggerButton = digitalRead(triggerPin); // Looks up current trigger button state
  
  if(triggerButton == LOW){ //trigger button pressed
    shoot();
  }

}

void shoot(){
  //flash LED strip on
    strip.begin();
    setAll(0xFF,0xFF,0xFF);

    // return LED strip back to 
    strip.show(); // Initialize all pixels to 'off'
    teamColour(); // change back to team colour
    
    // play shoot sound
    mp3.playFileByIndexNumber(1);
    
   // reduce ammo by 1

}

In the code above, there are two subroutines. The first subroutine “triggers()”, is used to detect if the trigger button has been pressed. It first looks and sees if the button has been pressed. If it is pressed it will store the value “LOW”, if not it will hold the value “HIGH”. Low and high values can also be represented as a number: LOW = 0, HIGH = 1. The second subroutine “shoot()”, flashes white at full strength before going back to the team colour.

  1. Go to the Game.ino tab.
  2. Add triggers(); into the normal game play loop
void gameLoop(){
  if (gameState == 1){ //normal game play
    gameTimer();
    triggers();
  }
  // game state 2 is paused
  if (gameState == 3){ //dead - cannot use trigger
    deadCycle(); 
    gameTimer();
  }
  if (gameState == 4){ //game over
    deadCycle();
  }
}
  1. Upload the code
  2. When you press the fire button, it should now make the LED lights go bright white.
Button pressed – white colour at full strength
No button pressed – team colour showing

2. Add different shot modes

Adding different shot modes

  1. Go to the main program file go to the //timers section and add the variable unsigned long shootDelayTime;. This will store the length of time between the last shot.
  2. In the main program file go to the //Game variables in order of Cloning Data section and ensure roundsMin = 0x03; and set fireSelect = 0x01. The speakers do not handle a fire rate higher than 400 and it will the gun to burst mode.
  3. Change the triggers subroutine to the following:
void triggers() { // Checks to see if the triggers have been pressed

  previousTriggerState = triggerButton;  // Stores previous trigger state
  triggerButton = digitalRead(triggerPin); // Looks up current trigger button state
  
  if(triggerButton == LOW){ //trigger button pressed
    if (shotsLeft > 0 && shotReady()){  //checks for ammo and if enough time passed since last shot
      shoot();
    }
    else if(shotsLeft <= 0 && shotReady() && triggerButton != previousTriggerState){
      noAmmo();
    }
  }
  //replenish burstShots to original burstSize on triggerButton up
  if (triggerButton == HIGH){
    burstShots = burstSize;  
  }
}

Instead of letting the gun shoot at any time, it will now check to see if it is ok to shoot. For the semi-automatic mode we have added previousTriggerState which gives one shot per press of the button. It needs to check if the fire button was previously up. For the burst mode, we are also adding the variable burstShots to count how many shots in the burst have been fired. We have also added a noAmmo() subroutine and shotReady() function which will be programmed below

4. Add the following code at the end of the Shoot.ino tab:

void noAmmo(){
  mp3.playFileByIndexNumber(2);
}

bool shotReady(){
  // This function checks to see if enough time past since last shot (depends on shot mode)
  if (fireSelect == 0 && triggerButton != previousTriggerState){  //semi-auto
    return true;
  }
  else if (fireSelect == 1 && millis()>shotDelay+shotDelayTime && burstShots > 0){  //burst
    burstShots -= 1;
    return true;
  }
  else if (fireSelect == 2 && millis()>shotDelay+shotDelayTime){  //full auto
    return true;
  } 
  return false;
}

The noAmmo() subroutine plays a sound when there is no ammo left. It will not allow another shoot action for 2 seconds.

The shotReady() function returns a true value if the gun is ready to fire depending on the shot mode. The fireSelect variable should either be 0 – semi-auto, 1 – burst, or 2 – full auto.

3. Create a reload button

The reload button will have a similar action to the semi-auto fire button. This will ensure you only use one clip per button press.

  1. In the Shoot.ino tab add the following code under the similar code for the trigger button:
  previousReloadState = reloadButton;
  reloadButton = digitalRead(reloadPin);
  1. In the Shoot.ino tab add the following code under the similar code for the trigger button:
if(reloadButton == LOW && reloadButton != previousReloadState){ 
    if (magazines>0){
      reload();
    }
    else {
      noAmmo();
    }
  } 
  1. In the Shoot.ino tab add the following subroutine just above the noAmmo() subroutine:
void reload(){
  magazines--; 
  shotsLeft = magSize; 
  shotDelayTime = millis() + reloadDelay*1000; 
  mp3.playFileByIndexNumber(4);
}

It is important to be able to play a sound once the gun has reloaded. We currently only play a sound at the start of the reloading. We also need to play a sound at the end. We will create a new boolean variable called reloading to indicate if the gun is still reloading. Then create some code to play the reload complete sound.

  1. Go to the main program tab and at the end of the section //Code Variables, place the variable reloading.
bool reloading = false;
  1. Go to the Shoot.ino tab and at the end of the triggers() subroutine place the following code:
  if (reloading &&  millis()>shotDelay+shotDelayTime){ //reloading true and passed reload time
    mp3.playFileByIndexNumber(4);
    reloading = false;
  }
  1. Go to the Shoot.ino tab and at the start of the reload() subroutine place the following code:
void reload(){
  reloading = true;

4. Change sound effects to sci-fi

Milestag has a variable that allows the guns to have different sound modes. 0 is military, 1 is sci-fi reload and 2 is silent. We will use case statements to select the correct sound. If the soundSet value is not 0 or 1 there will be no sound.

  1. In the Shoot.ino tab, in the shoot() subroutine, replace the existing mp3 sound code with the following code:
    // play shoot sound
    switch (soundSet){ 
      case 0: mp3.playFileByIndexNumber(1); break; // military shot
      case 1: mp3.playFileByIndexNumber(21); break; // si-fi shot
    } 
  1. In the Shoot.ino tab, in the reload() subroutine, replace the existing mp3 sound code with the following code:
  switch (soundSet){ 
    case 0: mp3.playFileByIndexNumber(4); break; // military reload
    case 1: mp3.playFileByIndexNumber(22); break; // si-fi reload
  }
  1. In the Shoot.ino tab, in the triggers() subroutine find the reloading code and replace it with:
  if (reloading &&  millis()>shotDelay+shotDelayTime){ //after reload time - do this:
    switch (soundSet){ 
      case 0: mp3.playFileByIndexNumber(4); break; // military reload end
      case 1: mp3.playFileByIndexNumber(22); break; // si-fi reload end
    } 
    reloading = false;
  }

Christian Content

Timing in the game is really important. We not only use timing to count down to the end of the game, but we also use timing for the shot-rate, and how long the reload takes. When the player dies, the flashing of the LEDs on and off also requires a timer.

In the game, we also benefit from audio and visual cues that tell us about the time. There is a time display on the gun which tells you how long the game has to go. There is a second reload sound to let you know when your gun is ready to shoot. And the shot sound gives an indication of how fast you are shooting.

Timing is really important, especially when waiting for the next thing to happen.

The Bible contains some fascinating passages that talk about timing. We will look at one today.

2 Peter 3:8–13 (HCSB)

8 Dear friends, don’t let this one thing escape you: With the Lord one day is like a thousand years, and a thousand years like one day. 9 The Lord does not delay His promise, as some understand delay, but is patient with you, not wanting any to perish but all to come to repentance. 10 But the Day of the Lord will come like a thief; on that day the heavens will pass away with a loud noise, the elements will burn and be dissolved, and the earth and the works on it will be disclosed. 11 Since all these things are to be destroyed in this way, it is clear what sort of people you should be in holy conduct and godliness 12 as you wait for and earnestly desire the coming of the day of God. The heavens will be on fire and be dissolved because of it, and the elements will melt with the heat. 13 But based on His promise, we wait for the new heavens and a new earth, where righteousness will dwell.

It’s fascinating how the Bible talks about timing, and the future of our world for that matter.

And unlike the game, we can be left wondering when certain things are going to happen in the future.

There are scientific theories about the future of our earth. They predict that in a few billion years, our sun will enter the red giant stage. In this stage, the sun gets larger and will eventually engulf the earth. Both the Bible and science predict that our sky and earth will melt with heat.

But when our Earth is destroyed by fire, God provides a solution. He will create a new heaven and new earth where the righteous will dwell. Unlike the laser tag computer game, we do not have any indication of the time this will occur. There is no countdown clock, or warning sound. Verse 10 says that this day will come like a thief. The only way to be ready for this day is to be righteous now, or put another way, “right with God”. This rightness with God only comes through trusting in and following Jesus.




        

Leave a Reply