スケッチサンプルにある”TETRIS with M5STACK”を改造し、
ゲームシステム、操作方法の改良及び、画像、音声を追加したモノを作りました。
次項目の改良をした、バイナリとソースを公開していましたが、
ふと思い立って、現状を調べた所(2012の判例含めて)、
クローンゲーの氾濫の認識から想像したより、著作権的に不味そうなので、公開を取り下げました。
回避の為に枠サイズの調整などは可能ですが、
元のソースにテトリスの名前が有り、これは変更できない事もあります。
公式サンプルに有った為、バイナリ、ソースの一般的な公開も問題ないと判断してしまいました。
割と遊べる形(ガラケーアプリ前期なら売れるレベル)にしてしまったという事もありますが……
ま、現実的にそう大きな問題もないでしょうが、私も別の所では著作権者ですので、念の為。
GPL界隈や、諸々の調査/認識不足に関する反省は別として、
それなりに遊べるゲームも作れる端末で有る事の検証、
バイナリ公開の問題点の把握を含めた実験、
自身のシステム把握と、環境構築という目的は達していますので、作った事は良しとします。
(私的(フォロワー様)にでしたら、ソース他を出すことも構いませんので、TWで連絡ください)
改造のポイントだけ、増加して記載しておきます。
ご参考になれば幸いです。
*改良点
・MP3複数トラック再生実装。
+音質は極めて悪く、ノイズも問題なので何とかしたいが、ライブラリ/ハード的には無理っぽい?
+スレッド化でなんとかならないか?
+後日、何とかした。下記にソース追加。
・効果音及びBGMを実装。
+SD/SPIFFS両対応。
・SDメニュー(M5Stack-SD-Menu)対応
・起動時にBボタン同時押しで、BGM再生追加 (デフォルトはSEのみ)
・同、Cボタンで同時押しで、全ミュート追加
・Aボタンで左移動、Cボタンで右移動。移動ボタン長押しで、連続移動可。
・Bボタンで回転。Bボタン長押し後に離すと、瞬時落下。
・レベル及びスコア実装。
・『http://shikarunochi.matrix.jp/?p=2296』(しかるのち)様のソースより、
ネクストブロック表示部分を移植。スコア表示部も引用。
・ブロックデザインを変更。
●2018/10/11
・ソースにコメントを追加し、すこし整理。
・スコアを変更時のみ描画するように改修。
・Bボタン長押し時に、ブロックが光る要素追加。
+落下モードがわかりやすくなった筈。
・ブート時に、Aボタンと、他の何れかのボタンを同時押しで、
キーチェーンゲームっぽい見た目になる要素を追加。
+Sound選択も有効、全押しで、SEのみのモードとなる。
*改造のポイント
・ボタン長押で落下/連続移動
・冒頭変数宣言
1 2 |
boolean but_B = false; byte LONG_P_CT =8;//200ms |
・void loop()内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Point next_pos; int next_rot = rot; GetNextPosRot(&next_pos, &next_rot); //2018/10/9 FALL Add if (started&&but_B){ while(-1){ if(ReviseScreen(next_pos, next_rot)){ClearKeys();break;} next_pos.X = pos.X; next_pos.Y = pos.Y; next_pos.Y += 1; } //snd_play(1,"/fall.mp3",0.4f); } else ReviseScreen(next_pos, next_rot); |
・キー判定部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//======================================================================== //2018/10/9 T.K Change void ClearKeys() { but_A=false; but_B=false; but_LEFT=false; but_RIGHT=false;} //======================================================================== //KEY INFO (A=bit3,C=bit2,B=bit4) //2018/10/9 T.K Replacement short Key=0,R_Key=0,Btn_A_ct=0,Btn_B_ct=0,Btn_C_ct=0; bool KeyPadLoop(){ if(M5.BtnA.read()){Key|=8;Btn_A_ct++;if(Btn_A_ct>LONG_P_CT)Key|=64;}//Acquire long press. else {if((Key&8)&&!(Key&64))R_Key|=8;Key&=(~8);Key&=(~64);Btn_A_ct=0;} // if(M5.BtnC.read()){Key|=4;Btn_C_ct++;if(Btn_C_ct<LONG_P_CT)Key|=128;}//Acquire long press. else {if((Key&4)&&!(Key&128))R_Key|=4; Key&=(~4);Key&=(~128);Btn_C_ct=0;} // if(M5.BtnB.read()){Key|=16;Btn_B_ct++;}//Acquire long press. else {if(Key&16){if(Btn_B_ct<LONG_P_CT)R_Key|=16; else R_Key|=32;} Key&=(~16);Btn_B_ct=0;} // if((R_Key&8)||Key&64){R_Key&=(~8);ClearKeys();but_LEFT =true;} if((R_Key&4)||Key&128){R_Key&=(~4);ClearKeys();but_RIGHT=true;} if((R_Key&16)){R_Key&=(~16);ClearKeys();but_A =true;} if((R_Key&32)){R_Key&=(~32);ClearKeys();but_B =true;} if(but_LEFT||but_RIGHT||but_A)return true; return false; } |
・落下受付時フラッシュ
・冒頭変数宣言
1 |
unsigned long systimer = 0; |
・void loop()内ラスト
1 |
systimer++; |
・void setup(void)内
1 2 3 4 5 6 7 8 9 10 11 |
//----------------------------// Make Block ---------------------------- make_block( 0, BLACK); // Type No, Color make_block( 1, 0x00F0); // DDDD RED 0000000011110000 1111000000000000 make_block( 2, 0xFBE4); // DD,DD PUPLE 1111101111100100 1110010011111011 make_block( 3, 0xFF00); // D__,DDD BLUE 1111111100000000 0000000011111111 make_block( 4, 0xFF87); // DD_,_DD GREEN 1111111110000111 1000011111111111 make_block( 5, 0x87FF); // __D,DDD YELLO 1000011111111111 1111111110000111 make_block( 6, 0xF00F); // _DD,DD_ LIGHT GREEN 1111000000001111 1111000000001111 make_block( 7, 0xF8FC); // _D_,DDD PINK 1111100011111100 1111100011111100 make_block( 8, 0xFFFF); // _D_,DDD FLASH //2018/10/11 T.K Add //---------------------------------------------------------------------- |
・bool ReviseScreen(Point next_pos, int next_rot)内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
bool fall=false; for (int i = 0; i < 4; ++i) screen[pos.X + block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = 0; if (GetSquares(block, next_pos, next_rot, next_squares)) { for (int i = 0; i < 4; ++i){ //2018/10/11 T.K Flash(When long pressed) Add if(Btn_B_ct>LONG_P_CT&&(systimer>>1)&1) screen[next_squares[i].X][next_squares[i].Y] = 8; else screen[next_squares[i].X][next_squares[i].Y] = block.color; } pos = next_pos; rot = next_rot; } else { |
・ブロックパターン設定/モノクロモード対応
+元の色コードの解釈は反転している?
縦ブロックが赤指定だとすると、コメントアウトした色の方が正しいが……
・冒頭変数宣言
1 |
byte Color_mode=0;//0:ORIGINAL 1:Monotone(unused) 2:liquid crystal |
・make_block( int n , uint16_t color )置き換え。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
//======================================================================== //2018/10/9 T.K Change float block_pattern[]={ 0.9f,1.0f,1.1f,1.2f,1.3f,1.2f, 1.0f,1.1f,1.2f,1.3f,1.2f,1.1f, 1.1f,1.2f,1.3f,1.2f,1.1f,1.0f, 1.2f,1.3f,1.2f,1.1f,1.0f,0.9f, 1.3f,1.2f,1.1f,1.0f,0.9f,1.0f, 1.2f,1.1f,1.0f,0.9f,1.0f,1.1f, }; void make_block( int n , uint16_t color ){ // Make Block color color=(color >> 8) | (color << 8); for ( int i =0 ; i < 12; i++ ) for ( int j =0 ; j < 12; j++ ){ short r=(short)((float)(color>>11)*block_pattern[(j>>1)*6+(i>>1)]); if(r>0x1f)r=0x1f;//5bit short g=(short)((float)((color>>5)&0x3f)*block_pattern[(j>>1)*6+(i>>1)]); if(g>0x3f)g=0x3f;//6bit short b=(short)((float)(color&0x1f)*block_pattern[(j>>1)*6+(i>>1)]); if(b>0x1f)b=0x1f;//5bit //BlockImage[n][i][j] = (r<<11) | (g<<5) | b; // Block color //2018/10/9 This one is closer to the original source comment ??? T.K //2018/10/11 T.K Monotone Mode Add if(Color_mode!=0){ if(Color_mode==1)//Monotone { g=(g+(r<<1)+(b<<1)+0x1f)>>2; BlockImage[n][i][j] = ((g>>1)<<11) | (g<<5) | (g>>1); // Block color // } else {//liquid crystal g=(g+(r<<1)+(b<<1)+0x1f)>>2; g=(short)((float)g*1.1f); if(g>0x3f)g=0x3f;//6bit r=(short)((float)g*0.9f)>>1; if(r>0x1f)r=0x1f;//5bit b=(short)((float)g*0.5f)>>1; if(b>0x1f)b=0x1f;//5bit BlockImage[n][i][j] = (r<<11) | (g<<5) | b; // Block color // } } else BlockImage[n][i][j] = (b<<11) | (g<<5) | r; // Block color // if ( i == 0 || j == 0 ) BlockImage[n][i][j] = 0; // BLACK Line } } |
*音周り+調速変更
+別タスク版に変更。音質、安定性共に向上。
+使用するMP3は、16bit44MHzモノラルデータをビットレート48kbpsに変換。
+レートなどの仕様は全てのMP3で統一すること。
+50%でノーマライズすると音割れしない。
+SPIFFSにデータがあれば其方を優先、無ければSDに探しに行く。
+複数トラックを再生する場合は、基本SPIFFSに置く事を推奨。
・冒頭 インクルード/変数宣言
+BGMは、snd_play(0,”/km_bgm_01.mp3″,0.3f);
SEは、snd_play(1,”/fall.mp3″,0.4f);という形で発声させる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include "SPIFFS.h" #include "AudioFileSourceSD.h" #include "AudioFileSourceSPIFFS.h" #include "AudioFileSourceID3.h" #include "AudioGeneratorMP3.h" #include "AudioOutputI2S.h" #include "AudioOutputMixer.h" // #define S_TR_MAX 2 AudioGeneratorMP3 *mp3[S_TR_MAX]; AudioFileSourceSD *file[S_TR_MAX]; AudioFileSourceSPIFFS *fileSP[S_TR_MAX]; AudioOutputI2S *out; AudioFileSourceID3 *id3[S_TR_MAX]; AudioOutputMixer *mixer; AudioOutputMixerStub *stub[S_TR_MAX]; // byte Sound_mode=0;//0:SE/BGM全再生 1:BGMミュート 2:ミュート unsigned long FrameTime = 0; int game_speed = 25; // 25msec |
・void setup()ラスト
1 2 3 4 |
//2018/10/9 T.K Add snd_init(); // if(Sound_mode==0)snd_play(0,"/sample.mp3",0.3f);//任意のmp3ファイル |
・void loop()冒頭
1 2 |
void loop() { FrameTime = millis();//調速 |
・void loop()内ラスト近辺
+delay(25);を削除
+下記追加
1 2 3 4 |
while(-1){ if(FrameTime+game_speed < millis())break; delay(1); } |
・効果音発声サンプル GetNextPosRot(Point* pnext_pos, int* pnext_rot) 内
1 2 |
*pnext_rot = (*pnext_rot + block.numRotate - 1)%block.numRotate; if(block.color!=2)snd_play(1,"/enter.mp3",0.2f); //2018/10/9 T.K Add 10/11 T.K Change |
・追加コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
//======================================================================== // //sound_system // //2018/10/9 T.K Add 10/27 T.K Change // byte snd_loop_on[S_TR_MAX]; byte snd_kick[S_TR_MAX]; const char *snd_k_filename[S_TR_MAX]; float snd_k_v[S_TR_MAX]; // void task1(void * pvParameters) { //2018/10/9 T.K Add // out = new AudioOutputI2S(0,1); // Output to builtInDAC //INTERNAL_DAC out->SetOutputModeMono(true); out->stop(); mixer = new AudioOutputMixer(48, out); // for(int i=0;i<S_TR_MAX;i++)stub[i] = mixer->NewInput(); // delay(100); //loop while(-1) { //KICKER for(int i=0;i<S_TR_MAX;i++){ if(snd_kick[i]==1){snd_play_t(i,snd_k_filename[i],snd_k_v[i]);snd_kick[i]=0;}//PLAY else if(snd_kick[i]==2){snd_stop_t(i);snd_kick[i]=0;}//STOP } //BGM if(mp3[0]!=nullptr&&mp3[0]->isRunning()) { if (!mp3[0]->loop())snd_play_t(0,snd_k_filename[0],snd_k_v[0]); } //SE for(int i=1;i<S_TR_MAX;i++){ if (mp3[i]!=nullptr&&mp3[i]->isRunning()) { if (!mp3[i]->loop()){snd_stop_t(i);} } } delay(1); } } // void snd_init(){ if(Sound_mode==2)return; for(int i=0;i<S_TR_MAX;i++){mp3[i]=nullptr;stub[i]=nullptr;snd_kick[i]=0;} // Task 1 xTaskCreatePinnedToCore(task1, "Task1", 8192, NULL, 1, NULL, 0); } // void snd_play(int nm,const char *filename,float v){ if(Sound_mode==2||(Sound_mode==1&&nm==0))return; if(nm>=S_TR_MAX)nm=S_TR_MAX-1; snd_k_filename[nm]=filename;snd_k_v[nm]=v;snd_kick[nm]=1;//KICK PLAY COMMAND } // void snd_play_t(int nm,const char *filename,float v){ if(Sound_mode==2)return; snd_stop_t(nm); // File f = SPIFFS.open(filename, "r"); if(!f||f.isDirectory()){ f.close(); if(file[nm]!=nullptr)delete file[nm]; char t_name[48] = {'/','m','p','3', '\0'}; strcat(t_name, filename); file[nm] = new AudioFileSourceSD(t_name); if(file[nm]){id3[nm] = new AudioFileSourceID3(file[nm]);} else return; } else { f.close(); if(fileSP[nm]!=nullptr)delete fileSP[nm]; fileSP[nm] = new AudioFileSourceSPIFFS(filename); if(fileSP[nm]){id3[nm] = new AudioFileSourceID3(fileSP[nm]);} else return; } if(stub[nm]!=nullptr){ stub[nm]->SetGain(v); mp3[nm] = new AudioGeneratorMP3(); mp3[nm]->begin(id3[nm], stub[nm]); } } // void snd_stop(int nm){ if(Sound_mode==2)return; snd_kick[nm]=2;//KICK STOP COMMAND } // void snd_stop_t(int nm){ if(Sound_mode==2)return; if (stub[nm]!=nullptr){stub[nm]->SetGain(0);} if (mp3[nm]!=nullptr){mp3[nm]->stop();delete mp3[nm];mp3[nm]=nullptr;id3[nm]->close();delete id3[nm];}//delete!!! if (stub[nm]!=nullptr){stub[nm]->stop();} if(nm==0)dacWrite(25, 0); // Speaker OFF } |