質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

1回答

1112閲覧

JavaScriptでFNF音ゲー制作(ノーツの動作及びシーンについて)

falcon_function

総合スコア10

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

0クリップ

投稿2023/07/15 11:17

実現したいこと

  • 秒数に応じて、落ちてくるノーツの移動速度を一定に保ちたい
  • シーン切り替え用のクラス作成
  • 音声及び画像すべてが読み込まれた際に、いつでも描画・音声を実行できるようにしたい

前提

・複数画像を描画することには成功(約2000個あったとしても)したが、読み込みに重い部分がある。
・シーンの切り替えクラスを実装したいが、そもそもどのように実装すればよいのか見当がついていない

発生している問題・エラーメッセージ

1:イメージとしては、ノーツ・判定枠の各画像座標を元に、上から下からでも、また判定枠自体が動いていたとしても、秒数で重なるようにノーツを一定量移動させたい。(常にrequestAnimationFrameが60FPS動作するという保証がないため) しかし、クラスNotesの落ちてくる秒数が飾りになってしまっている。 2:シーン切り替え用のクラスを作成したいが、は以下のようなものを実装したい。 ●タイトル画面(Enterキーのみでメニュー画面に行く) ●メニュー画面(コンフィグ、曲選択の二種類のオプションがある、操作は矢印キーのみ、ESCキーでタイトル画面に戻れる) ●ポーズ画面(ブラウザが非アクティブになったら、ゲーム実行時のみ強制的に呼び出す) ●コンフィグ画面(今現在は上下左右キーの割り当てキー・FPSの調整ができればよい) ●曲選択画面(本家FNFのストーリーモードのみ、フリープレイはなし) https://www.newgrounds.com/portal/view/770371 ●ゲーム実行画面 ●ゲームオーバー画面 3:実行してみるとわかるが、若干遅延がかかってから画像が描画されているため、まだ処理が重いと思われる。 DOMコンテンツ読み込み完了時に音声と画像が読み込まれた状態にしたい。

該当のソースコード

JavaScript

1const canvas = document.getElementById("fnfcanvas") 2const ctx = canvas.getContext("2d"); 3let g_width = canvas.width = 1020; 4let g_height = canvas.height = 680; 5let sprite = 0; 6let flags = false; 7const audio = new Audio("loop006.mp3"); 8let images = []; //画像データURL群。ただし要素指定の必要あり。 9let audios = []; //音声データURL群。ただし要素番号指定の必要あり。 10let imagesindex = 0; 11 12let nextDataindex = 0; 13let visibleNotes=[]; 14window.addEventListener("click",(event)=>{ 15 if(!flags){ 16 audio.volume = 0.01; 17 audio.loop = false; 18 audio.play(); 19 flags = true; 20 } 21 22}); 23function playingtime(event){ 24 if(flags){ 25 let plytime = audio.currentTime; 26 if(param.start){ 27 param.totaltime=Math.abs(param.end-param.start-plytime); 28 } 29 requestAnimationFrame(playingtime,event); 30 } 31} 32audio.addEventListener("timeupdate",(event)=>{ 33 if(flags){ 34 param.end = event.timeStamp / 1000; 35 requestAnimationFrame(playingtime,(param.end)); 36 } else { 37 cancelAnimationFrame(playingtime) 38 } 39}); 40const param={ 41 totaltime: 0, 42 start: 0, 43 end: 0, 44 bench_start: 0, 45 bench_end: 0 46} 47const multiway={ 48 UP: 440, 49 DOWN: 660, 50 LEFT: 220, 51 RIGHT: 880 52} 53function Min2MaxRand(max,min){ 54 const double_max = max.toString().split('.'); 55 const double_min = min.toString().split('.'); 56 if(double_max[1] > 0 || double_min[1] > 0){ 57 const calc = Math.random() * (max-min+1)+min; 58 return calc; 59 } else { 60 const calc = Math.random() * (max-min+1)+min; 61 return Math.floor(calc); 62 } 63} 64//ノーツそのものの制御(座標・加速度・画像の大きさ・落ちてくる秒数) 65class Notes{ 66 constructor(x,y,VelX,VelY,width,height,time,image,ways){ 67 this.x = x; 68 this.y = y; 69 this.VelX = VelX; 70 this.VelY = VelY; 71 this.width = width; 72 this.height = height; 73 this.time = time; 74 this.image = image; 75 this.ways = ways; 76 } 77 notesdrawing(x,y,VelX,VelY,width,height,time,image,ways){ 78 ctx.clearRect(0,0,g_width,g_height); 79 } 80 notesmoving(x,y,VelX,VelY,width,height,time,image,ways){ 81 //ここにVelYの挙動およびVelXなどの挙動を書く。 82 this.y += this.VelY; 83 if(this.y < -this.height){ 84 this.y = window.innerHeight; 85 } 86 ctx.clearRect(0,0,g_height,g_width); 87 for(let i = 0; i < notes_LEGENDALY.length;i++){ 88 ctx.drawImage(notes_LEGENDALY[i].img,(sprite*220),0,220,220,notes_LEGENDALY[i].x,notes_LEGENDALY[i].y,notes_LEGENDALY[i].width,notes_LEGENDALY[i].height); 89 } 90 } 91} 92let notes_LEGENDALY = []; 93//const notes_LASO = []; 94//const notes_SLASO = []; 95//譜面データの読み込み 96for(let i = 0; i < 200;i++){ 97 const test = new Notes((Min2MaxRand(620,0)),(Min2MaxRand(0,g_height)),1,1,110,110,(0.1+i),"arrow_down_left.png","left"); 98 notes_LEGENDALY.push(test); 99} 100 101for(let i = 0; i < notes_LEGENDALY.length;i++){ 102 notes_LEGENDALY[i].img = new Image(); 103 notes_LEGENDALY[i].img.src = notes_LEGENDALY[i].image; 104 let y = notes_LEGENDALY[i].y 105 notes_LEGENDALY[i].img.addEventListener("load",(event)=>{ 106 //ctx.clearRect(0,0,g_width,g_height); 107 ctx.drawImage(notes_LEGENDALY[i].img,(sprite*220),0,220,220,notes_LEGENDALY[i].x,y,notes_LEGENDALY[i].width,notes_LEGENDALY[i].height); 108 }); 109} 110function loop(){ 111 if(param.end){ 112 while(nextDataindex < (notes_LEGENDALY.length-1)){ 113 if(notes_LEGENDALY[nextDataindex].time <= Math.abs(param.end-10)){ 114 /*今は空白*/ 115 } else if(notes_LEGENDALY[nextDataindex].time > Math.abs(param.end - 10)){ 116 break; 117 } 118 nextDataindex++; 119 } 120 for(let i = 0;i < visibleNotes.length;i++){ 121 if(visibleNotes[i].time < param.end){ 122 visibleNotes.splice(i,1); 123 } 124 } 125 } 126 ctx.clearRect(0,0,g_width,g_height); 127 for(let i = 0; i < notes_LEGENDALY.length;i++){ 128 notes_LEGENDALY[i].y -= notes_LEGENDALY[i].VelY; 129 if(notes_LEGENDALY[i].y < -notes_LEGENDALY[i].height){ 130 notes_LEGENDALY[i].y = g_height; 131 } 132 ctx.drawImage(notes_LEGENDALY[i].img,(sprite*220),0,220,220,notes_LEGENDALY[i].x,notes_LEGENDALY[i].y,notes_LEGENDALY[i].width,notes_LEGENDALY[i].height); 133 } 134 requestAnimationFrame(loop); 135} 136requestAnimationFrame(loop); 137//判定枠 138class notes_lane extends Notes{ 139 constructor(x,y,VelX,VelY,width,height,time,image){ 140 super(x,y,VelX,VelY,width,height,time,image); 141 this.lanecollision(x,y,VelX,VelY,width,height,time); 142 } 143 lanecollision(x,y,VelX,VelY,width,height,time){ 144 145 } 146 lanedrawing(){ 147 ctx.clearRect(0,0,width,height); 148 } 149} 150//ノーツの種類(上から通常・半透明・ダメージノーツ・即死ノーツ) 151class Notes_Elements{ 152 constructor(){ 153 this.normal = 'normal'; 154 this.invisible = 'invisible'; 155 this.reverse = 'damage'; 156 this.death = 'death'; 157 } 158} 159const Judge={ 160 PERFECT: 0.032, 161 GOOD: 0.07, 162 BAD: 1.1, 163 MISS: 1.5 164}; 165 166 167class Pushing{ 168 constructor(up_push,down_push,left_push,right_push){ 169 this.up_push = false; 170 this,down_push = false; 171 this.left_push = false; 172 this.right_push = false; 173 } 174} 175class Pushing_continue extends Pushing{ 176 constructor(up_pushing,down_pushing,left_pushing,right_pushing){ 177 super(up_pushing,down_pushing,left_pushing,right_pushing); 178 } 179 180} 181const NotesJudge= function(key_send,start,fin){ 182 const judgetime = Math.abs(10-Math.abs(start-fin)); 183 if(key_send.up_push){ 184 if((judgetime <= Judge.PERFECT) && (judgetime >= 0)){ 185 console.log(`Perfect: ${judgetime}ms`); 186 flags = false; 187 audio.pause(); 188 audio.currentTime = 0; 189 } 190 191 if(judgetime <= Judge.GOOD && judgetime > Judge.PERFECT){ 192 console.log(`GOOD: ${judgetime}ms`); 193 flags = false; 194 audio.pause(); 195 audio.currentTime = 0; 196 } 197 198 if(judgetime <= Judge.BAD && judgetime > Judge.GOOD){ 199 console.log(`BAD: ${judgetime}ms`); 200 flags = false; 201 audio.pause(); 202 audio.currentTime = 0; 203 } 204 if(judgetime <= Judge.MISS && judgetime > Judge.BAD){ 205 console.log(`MISS: ${judgetime}ms`); 206 flags = false; 207 audio.pause(); 208 audio.currentTime = 0; 209 } 210 console.log("UPPUSHED"); 211 } 212 213 if(key_send.down_push){ 214 if(judgetime <= Judge.PERFECT && judgetime >= 0){ 215 console.log(`DPerfect: ${judgetime}ms`); 216 } 217 218 if(judgetime <= Judge.GOOD && judgetime > Judge.PERFECT){ 219 console.log(`DGOOD: ${judgetime}ms`); 220 } 221 222 if(judgetime <= Judge.BAD && judgetime > Judge.GOOD){ 223 console.log(`DBAD: ${judgetime}ms`); 224 } 225 if(judgetime <= Judge.MISS && judgetime > Judge.BAD){ 226 console.log(`DMISS: ${judgetime}ms`); 227 } 228 console.log("DOWNPUSHED"); 229 } 230 231 if(key_send.left_push){ 232 if(judgetime <= Judge.PERFECT && judgetime >= 0){ 233 console.log(`Perfect: ${judgetime}ms`); 234 } 235 236 if(judgetime <= Judge.GOOD && judgetime > Judge.PERFECT){ 237 console.log(`GOOD: ${judgetime}ms`); 238 } 239 240 if(judgetime <= Judge.BAD && judgetime > Judge.GOOD){ 241 console.log(`BAD: ${judgetime}ms`); 242 } 243 if(judgetime <= Judge.MISS && judgetime > Judge.BAD){ 244 console.log(`MISS: ${judgetime}ms`); 245 } 246 console.log("LEFTPUSHED"); 247 } 248 249 if(key_send.right_push){ 250 if(judgetime <= Judge.PERFECT && judgetime >= 0){ 251 console.log(`Perfect: ${judgetime}ms`); 252 } 253 254 if(judgetime <= Judge.GOOD && judgetime > Judge.PERFECT){ 255 console.log(`GOOD: ${judgetime}ms`); 256 } 257 258 if(judgetime <= Judge.BAD && judgetime > Judge.GOOD){ 259 console.log(`BAD: ${judgetime}ms`); 260 } 261 if(judgetime <= Judge.MISS && judgetime > Judge.BAD){ 262 console.log(`MISS: ${judgetime}ms`); 263 } 264 console.log("RIGHTPUSHED"); 265 } 266}; 267const check = new Pushing(); 268document.addEventListener('keydown',(event)=>{ 269 switch (event.key){ 270 case 'ArrowUp': 271 if(flags){ 272 check.up_push = true; 273 param.start = event.timeStamp / 1000; 274 NotesJudge(check,param.start,param.totaltime); 275 } 276 break; 277 case 'ArrowDown': 278 if(flags){ 279 check.down_push = true; 280 param.start = event.timeStamp / 1000; 281 NotesJudge(check,param.start,param.totaltime); 282 } 283 break; 284 case 'ArrowLeft': 285 if(flags){ 286 check.left_push = true; 287 param.start = event.timeStamp; 288 NotesJudge(check,param.start,param.totaltime); 289 } 290 break; 291 case 'ArrowRight': 292 if(flags){ 293 check.right_push = true; 294 param.start = event.timeStamp; 295 NotesJudge(check,param.start,param.totaltime); 296 } 297 break; 298 } 299 console.log(`keydown: ${param.start}ms`); 300}); 301 302 303document.addEventListener('keyup', (event) => { 304 const check = new Pushing(); 305 switch (event.key){ 306 case 'ArrowUp': 307 check.up_push = false; 308 break; 309 case 'ArrowDown': 310 param.end = event.timeStamp / 1000; 311 check.down_push = false; 312 break; 313 case 'ArrowLeft': 314 param.end = event.timeStamp / 1000; 315 check.left_push = false; 316 break; 317 case 'ArrowRight': 318 param.end = event.timeStamp / 1000; 319 check.right_push = false; 320 break; 321 } 322 console.log(`keyup: ${param.end}ms`); 323});

試したこと

・こちらのURLを参考に、drawImageで複数描画することには成功した。
https://stackoverflow.com/questions/54060731/how-can-i-animate-multiple-instances-of-drawimage-on-a-canvas

・こちらのURLを参考にしてシーン作成のイメージを固めたが、自分が作っているものとの対比がうまくいかなかった。
https://sbfl.net/blog/2016/05/18/javascript-danmaku-stg-1/

補足情報(FW/ツールのバージョンなど)

ブラウザ:Chrome 114.0.5735.134
PCのスペック: CPU: i7-12700 2.10 GHz
メモリ: 32GB
Windows11 Home 22H2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

秒数に応じて移動速度を一定に保つ部分については自己解決しました。
到達したい座標 / 目標秒数 * deltaTIme / 1000とすることで実装できました。
deltaTimeはrequestAnimationFrameでコールバックした際のDOMHighResTimeStampで取得したものを使っています。

JavaScript

1let lasttime = 0; 2function loop(timestamp){ 3 if(!lastTime){lastTime = timestamp;} 4 let deltaTime = timestamp - lastTime; 5 lastTime = timestamp; 6 ctxs[2].clearRect(0,0,g_width,g_height); 7 for(let i = 0; i < n_L.length;i++){ 8 let deltaY = g_height / n_L[i].time *deltaTime / 1000; 9 n_L[i].y -= deltaY; 10 if(n_L[i].y < -n_L[i].height){ 11 n_L[i].y = g_height; 12 } 13 //ctxs[2].save(); 14 //ctxs[2].translate(n_L[i].x+n_L[i].width/4,n_L[i].y+n_L[i].height/4); 15 //ctxs[2].rotate(Angle * RadtoDeg); 16 //ctxs[2].translate(-(n_L[i].x+n_L[i].width/4),-(n_L[i].y+n_L[i].height/4)); 17 ctxs[2].drawImage(n_L[i].img,(sprite*110),0,220,220,n_L[i].x,n_L[i].y,n_L[i].width/2,n_L[i].height/2); 18 //ctxs[2].restore(); 19 20 } 21 Angle += 30 * deltaTime / 1000; 22 requestAnimationFrame(loop); 23} 24requestAnimationFrame(loop);

投稿2023/08/12 12:24

falcon_function

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問