Upload del repo clone di twang

This commit is contained in:
Jasssbo 2025-08-28 22:41:22 +02:00
parent 9c164ceb04
commit a20252c357
13 changed files with 1873 additions and 0 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
# Autogenerated VS/MD solution and project files
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
# Unity3D generated meta files
*.pidb.meta
# Unity3D Generated File On Crash Reports
sysinfo.txt

42
Boss.h Normal file
View file

@ -0,0 +1,42 @@
#include "Arduino.h"
class Boss
{
public:
void Spawn();
void Hit();
void Kill();
bool Alive();
int _pos;
int _lives;
int _ticks;
private:
bool _alive;
};
void Boss::Spawn(){
_pos = 800;
_lives = 3;
_alive = 1;
}
void Boss::Hit(){
_lives --;
if(_lives == 0) {
Kill();
return;
}
if(_lives == 2){
_pos = 200;
}else if(_lives == 1){
_pos = 600;
}
}
bool Boss::Alive(){
return _alive;
}
void Boss::Kill(){
_alive = 0;
}

23
Conveyor.h Normal file
View file

@ -0,0 +1,23 @@
#include "Arduino.h"
class Conveyor
{
public:
void Spawn(int startPoint, int endPoint, int dir);
void Kill();
int _startPoint;
int _endPoint;
int _dir;
bool _alive;
};
void Conveyor::Spawn(int startPoint, int endPoint, int dir){
_startPoint = startPoint;
_endPoint = endPoint;
_dir = dir;
_alive = true;
}
void Conveyor::Kill(){
_alive = false;
}

55
Enemy.h Normal file
View file

@ -0,0 +1,55 @@
#include "Arduino.h"
class Enemy
{
public:
void Spawn(int pos, int dir, int sp, int wobble);
void Tick();
void Kill();
bool Alive();
int _pos;
int _wobble;
int playerSide;
private:
int _dir;
int _sp;
int _alive;
int _origin;
};
void Enemy::Spawn(int pos, int dir, int sp, int wobble){
_pos = pos;
_dir = dir; // 0 = left, 1 = right
_wobble = wobble; // 0 = no, >0 = yes, value is width of wobble
_origin = pos;
_sp = sp;
_alive = 1;
}
void Enemy::Tick(){
if(_alive){
if(_wobble > 0){
_pos = _origin + (sin((millis()/3000.0)*_sp)*_wobble);
}else{
if(_dir == 0){
_pos -= _sp;
}else{
_pos += _sp;
}
if(_pos > 1000) {
Kill();
}
if(_pos <= 0) {
Kill();
}
}
}
}
bool Enemy::Alive(){
return _alive;
}
void Enemy::Kill(){
_alive = 0;
}

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Critters
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

37
Lava.h Normal file
View file

@ -0,0 +1,37 @@
#include "Arduino.h"
class Lava
{
public:
void Spawn(int left, int right, int ontime, int offtime, int offset, char* state);
void Kill();
int Alive();
int _left;
int _right;
int _ontime;
int _offtime;
int _offset;
long _lastOn;
char* _state;
private:
int _alive;
};
void Lava::Spawn(int left, int right, int ontime, int offtime, int offset, char* state){
_left = left;
_right = right;
_ontime = ontime;
_offtime = offtime;
_offset = offset;
_alive = 1;
_lastOn = millis()-offset;
_state = state;
}
void Lava::Kill(){
_alive = 0;
}
int Lava::Alive(){
return _alive;
}

59
Particle.h Normal file
View file

@ -0,0 +1,59 @@
#include "Arduino.h"
#define FRICTION 1
class Particle
{
public:
void Spawn(int pos);
void Tick(int USE_GRAVITY);
void Kill();
bool Alive();
int _pos;
int _power;
private:
int _life;
int _alive;
int _sp;
};
void Particle::Spawn(int pos){
_pos = pos;
_sp = random(-200, 200);
_power = 255;
_alive = 1;
_life = 220 - abs(_sp);
}
void Particle::Tick(int USE_GRAVITY){
if(_alive){
_life ++;
if(_sp > 0){
_sp -= _life/10;
}else{
_sp += _life/10;
}
if(USE_GRAVITY && _pos > 500) _sp -= 10;
_power = 100 - _life;
if(_power <= 0){
Kill();
}else{
_pos += _sp/7.0;
if(_pos > 1000){
_pos = 1000;
_sp = 0-(_sp/2);
}
else if(_pos < 0){
_pos = 0;
_sp = 0-(_sp/2);
}
}
}
}
bool Particle::Alive(){
return _alive;
}
void Particle::Kill(){
_alive = 0;
}

78
README.md Normal file
View file

@ -0,0 +1,78 @@
# TWANG
A Arduino-based, 1D, LED loving, dungeon crawler. inspired by Line Wobbler by Robin B
## Video playlist
A playlist that shows the development of TWANG and the game in both a desktop and house-sized form can be found here: https://www.youtube.com/watch?v=9yf_VINmbTE&list=PL1_Z89_x_Dff-XhOxlx6sQ38wJqe1X2M0
## Required libraries:
* FastLED: https://github.com/FastLED/FastLED/files/4608545/FastLED.zip
* I2Cdev
* MPU6050: https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050
* RunningMedian: http://playground.arduino.cc/Main/RunningMedian
## Hardware used:
* Arduino MEGA/NANO
* 3 LEDs for life indicator
* APA102-C LED light strip. The more the better, maximum of 1000. Tested with 2x 144/meter and 12x 60/meter strips. The FastLED lib works with the less expensive WS2812 LEDs, i've not tried them but should be fine.
* 5v power supply, assume around 40mW per LED to calculate size
* MPU6050 accelerometer
* Spring doorstop, I used these: http://smile.amazon.com/gp/product/B00J4Y5BU2
## Enclosure
Files to print your own enclosure can be found here: http://www.thingiverse.com/thing:1116899
## Overview
TWANG was developed quickly to make my Halloween lights interactive, the code is fairly well commmented but could be improved. The following is a quick overview of the code to help you understand and tweak the game to your needs.
The game is played on a 1000 unit line, the position of enemies, the player, lava etc range from 0 to 1000 and the LEDs that represent them are derived using the getLED() function. You don't need to worry about this but it's good to know for things like the width of the attack and player max move speed. Regardles of the number of LEDs, everything takes place in this 1000 unit wide line.
**ATMEGA4809**
The TWANG4809 sketch is intended for use with the Arduino Nano Every and the Uno Wifi REV2 both of which use the Atmega 4809 processor.
At the time of writing, these boards do not have out of the box support for FastLED or ToneAC.
This sketch removes the ToneAC functionality.
For FastLED to function, install [this](https://github.com/FastLED/FastLED/files/4608545/FastLED.zip) library instead of the one listed in Arduino IDE.
**//LED SETUP** Defines the quantity of LEDs as well as the data and clock pins used. I've tested several APA102-C strips and the color order sometimes changes from BGR to GBR or GRB, if the player is not blue, the exit green and the enemies red, this is the bit you want to change. Brightness should range from 50 to 255, use a lower number if playing at night or wanting to use a smaller power supply. "DIRECTION" can be set to 0 or 1 to flip the game orientation. In setup() there is a "FastLED.addLeds()" line, in there you could change it to another brand of LED strip like the cheaper WS2812.
The game also has 3 regular LEDs for life indicators (the player gets 3 lives which reset each time they level up). The pins for these LEDs are stored in lifeLEDs[] and are updated in the updateLives() function
**//JOYSTICK SETUP** All parameters are commented in the code, you can set it to work in both forward/backward as well as side-to-side mode by changing JOYSTICK_ORIENTATION. Adjust the ATTACK_THRESHOLD if the "Twanging" is overly sensitive and the JOYSTICK_DEADZONE if the player slowly drifts when there is no input (because it's hard to get the MPU6050 dead level).
**//WOBBLE ATTACK** Sets the width, duration (ms) of the attack.
**//POOLS** These are the object pools for enemies, particles, lava, conveyors etc. You can modify the quantity of any of them if your levels use more or if you want to save some memory, just remember to update the respective counts to avoid errors.
**//USE_GRAVITY** 0/1 to set if particles created by the player getting killed should fall towards the start point, the BEND_POINT variable can be set to mark the point at which the strip of LEDs goes from been horizontal to vertical. The game is 1000 units wide (regardless of number of LED's) so 500 would be the mid point. If this is confusing just set USE_GRAVITY to 0.
## Modifying / Creating levels
Find the loadLevel() function, in there you can see a switch statment with the 10 levels I created. They all call different functions and variables to setup the level. Each one is described below:
**playerPosition;** Where the player starts on the 0 to 1000 line. If not set it defaults to 0. I set it to 200 in the first level so the player can see movement even if the first action they take is to push the joystick left
**spawnEnemy(position, direction, speed, wobble);**
* position: 0 to 1000
* direction: 0/1, initial direction of travel
* speed: >=0, speed of the enemy, remember the game is 1000 wide and runs at 60fps. I recommend between 1 and 4
* wobble: 0=regular moving enemy, 1=sine wave enemy, in this case speed sets the width of the wave
**spawnPool[poolNumber].Spawn(position, rate, speed, direction);**
* A spawn pool is a point which spawns enemies forever
* position: 0 to 1000
* rate: milliseconds between spawns, 1000 = 1 second
* speed: speed of the enemis it spawns
* direction: 0=towards start, 1=away from start
**spawnLava(startPoint, endPoint, ontime, offtime, offset);**
* startPoint: 0 to 1000
* endPoint: 0 to 1000, combined with startPoint this sets the location and size of the lava
* ontime: How lomg (ms) the lava is ON for
* offset: How long (ms) after the level starts before the lava turns on, use this to create patterns with multiple lavas
**spawnConveyor(startPoint, endPoint, direction);**
* startPoint, endPoint: Same as lava
* direction: the direction of travel 0/1
**spawnBoss()**
* There are no parramaters for a boss, they always spawn in the same place and have 3 lives. Tweak the values of Boss.h to modify
Feel free to edit, comment on the YouTube video (link at top) if you have any questions.

35
Spawner.h Normal file
View file

@ -0,0 +1,35 @@
#include "Arduino.h"
class Spawner
{
public:
void Spawn(int pos, int rate, int sp, int dir, long activate);
void Kill();
int Alive();
int _pos;
int _rate;
int _sp;
int _dir;
long _lastSpawned;
long _activate;
private:
int _alive;
};
void Spawner::Spawn(int pos, int rate, int sp, int dir, long activate){
_pos = pos;
_rate = rate;
_sp = sp;
_dir = dir;
_activate = millis()+activate;
_alive = 1;
}
void Spawner::Kill(){
_alive = 0;
_lastSpawned = 0;
}
int Spawner::Alive(){
return _alive;
}

734
TWANG.ino Normal file
View file

@ -0,0 +1,734 @@
// 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 475
#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 1 // 0/1 use gravity (LED strip going up wall)
#define BEND_POINT 550 // 0/1000 point at which the LED strip goes up the wall
#define LED_TYPE APA102//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 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<LED_TYPE, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);
//If using Neopixels, use
//FastLED.addLeds<LED_TYPE, DATA_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(1);
// Life LEDs
for(int i = 0; i<3; i++){
pinMode(lifeLEDs[i], OUTPUT);
digitalWrite(lifeLEDs[i], HIGH);
}
loadLevel();
}
void loop() {
long mm = millis();
int brightness = 0;
if(stage == "PLAY"){
if(attacking){
SFXattacking();
}else{
SFXtilt(joystickTilt);
}
}else if(stage == "DEAD"){
SFXdead();
}
if (mm - previousMillis >= 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 < mm) attacking = 0;
// If not attacking, check if they should be
if(!attacking && joystickWobble > 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 < 0) playerPosition = 0;
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< n; i++){
brightness = 255;
leds[i] = CRGB(0, brightness, 0);
}
SFXwin();
}else if(stageStartTime+1200 > 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<enemyCount; e++){
if(!enemyPool[e].Alive()){
enemyPool[e].Spawn(pos, dir, sp, wobble);
enemyPool[e].playerSide = pos > 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<conveyorCount; i++){
conveyorPool[i].Kill();
}
boss.Kill();
}
void levelComplete(){
stageStartTime = millis();
stage = "WIN";
if(levelNumber == LEVEL_COUNT) stage = "COMPLETE";
lives = 3;
updateLives();
}
void nextLevel(){
levelNumber ++;
if(levelNumber > 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<enemyCount; i++){
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)){
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) ||
(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); i++){
leds[i] = CRGB::DarkRed;
leds[i] %= 100;
}
// CHECK COLLISION
if(getLED(playerPosition) > getLED(boss._pos - BOSS_WIDTH/2) && getLED(playerPosition) < getLED(boss._pos + BOSS_WIDTH)){
die();
return;
}
// CHECK FOR ATTACK
if(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))
){
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<ee; led++){
b = 5;
n = (-led + (m/100)) % 5;
if(dir == -1) n = (led + (m/100)) % 5;
b = (5-n)/2.0;
if(b > 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<=getLED(playerPosition+(ATTACK_WIDTH/2))-1; i++){
leds[i] = CRGB(0, 0, n);
}
if(n > 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 && LP._right > pos) return true;
}
}
return false;
}
void updateLives(){
// Updates the life LEDs to show how many lives the player has left
for(int i = 0; i<3; i++){
digitalWrite(lifeLEDs[i], lives>i?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<NUM_LEDS; i++){
if(random8(200) == 0){
leds[i] = CHSV( 25, 255, 100);
}
}
}
}
// ---------------------------------
// ----------- JOYSTICK ------------
// ---------------------------------
void getInput(){
// This is responsible for the player movement speed and attacking.
// You can replace it with anything you want that passes a -90>+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) < JOYSTICK_DEADZONE) a = 0;
if(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 -= 500;
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();
}

729
TWANG4809.ino Normal file
View file

@ -0,0 +1,729 @@
// 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 475
#define DATA_PIN 3
#define CLOCK_PIN 4
#define LED_COLOR_ORDER GRB//Try BGR or 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 0 // 0/1000 point at which the LED strip goes up the wall
#define LED_TYPE APA102//type of LED strip to use(APA102 - DotStar, WS2811 - NeoPixel)
// 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);
//Buttons
//pinMode(LEFT_PIN, INPUT);
//pinMode(RIGHT_PIN, INPUT);
//pinMode(ATTACK_PIN, INPUT);
// MPU
Wire.begin();
accelgyro.initialize();
// Fast LED
FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(1);
// Life LEDs
for(int i = 0; i<3; i++){
pinMode(lifeLEDs[i], OUTPUT);
digitalWrite(lifeLEDs[i], HIGH);
}
loadLevel();
}
void loop() {
long mm = millis();
int brightness = 0;
/*
if(stage == "PLAY"){
if(attacking){
SFXattacking();
}else{
SFXtilt(joystickTilt);
}
}else if(stage == "DEAD"){
SFXdead();
}
*/
if (mm - previousMillis >= 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 < mm) attacking = 0;
// If not attacking, check if they should be
if(!attacking && joystickWobble > 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 < 0) playerPosition = 0;
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< n; i++){
brightness = 255;
leds[i] = CRGB(0, brightness, 0);
}
//SFXwin();
}else if(stageStartTime+1200 > 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<enemyCount; e++){
if(!enemyPool[e].Alive()){
enemyPool[e].Spawn(pos, dir, sp, wobble);
enemyPool[e].playerSide = pos > 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<conveyorCount; i++){
conveyorPool[i].Kill();
}
boss.Kill();
}
void levelComplete(){
stageStartTime = millis();
stage = "WIN";
if(levelNumber == LEVEL_COUNT) stage = "COMPLETE";
lives = 3;
updateLives();
}
void nextLevel(){
levelNumber ++;
if(levelNumber > 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<enemyCount; i++){
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)){
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) ||
(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); i++){
leds[i] = CRGB::DarkRed;
leds[i] %= 100;
}
// CHECK COLLISION
if(getLED(playerPosition) > getLED(boss._pos - BOSS_WIDTH/2) && getLED(playerPosition) < getLED(boss._pos + BOSS_WIDTH)){
die();
return;
}
// CHECK FOR ATTACK
if(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))
){
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<ee; led++){
b = 5;
n = (-led + (m/100)) % 5;
if(dir == -1) n = (led + (m/100)) % 5;
b = (5-n)/2.0;
if(b > 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<=getLED(playerPosition+(ATTACK_WIDTH/2))-1; i++){
leds[i] = CRGB(0, 0, n);
}
if(n > 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 && LP._right > pos) return true;
}
}
return false;
}
void updateLives(){
// Updates the life LEDs to show how many lives the player has left
for(int i = 0; i<3; i++){
digitalWrite(lifeLEDs[i], lives>i?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<NUM_LEDS; i++){
if(random8(200) == 0){
leds[i] = CHSV( 25, 255, 100);
}
}
}
}
// ---------------------------------
// ----------- JOYSTICK ------------
// ---------------------------------
void getInput(){
// This is responsible for the player movement speed and attacking.
// You can replace it with anything you want that passes a -90>+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) < JOYSTICK_DEADZONE) a = 0;
if(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 -= 500;
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();
}
*/

0
dfd.py
View file

38
iSin.h Normal file
View file

@ -0,0 +1,38 @@
#include "Arduino.h"
class iSin
{
public:
int convert(long x);
private:
uint8_t isinTable8[91] = {
0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44,
49, 53, 57, 62, 66, 70, 75, 79, 83, 87,
91, 96, 100, 104, 108, 112, 116, 120, 124, 128,
131, 135, 139, 143, 146, 150, 153, 157, 160, 164,
167, 171, 174, 177, 180, 183, 186, 190, 192, 195,
198, 201, 204, 206, 209, 211, 214, 216, 219, 221,
223, 225, 227, 229, 231, 233, 235, 236, 238, 240,
241, 243, 244, 245, 246, 247, 248, 249, 250, 251,
252, 253, 253, 254, 254, 254, 255, 255, 255, 255
};
};
int iSin::convert(long x)
{
boolean pos = true; // positive - keeps an eye on the sign.
if (x < 0)
{
x = -x;
pos = !pos;
}
if (x >= 360) x %= 360;
if (x > 180)
{
x -= 180;
pos = !pos;
}
if (x > 90) x = 180 - x;
if (pos) return isinTable8[x]/2 ;
return -isinTable8[x]/2 ;
}