//- 関数リスト - .. // 1: app.init .. // 2: app.onpreloadx .. // 3: app.afterOnload .. // 4: app.scheduleAdd .. // 5: app.newEnemy .. // 6: app.entrySprite .. // 7: app.deleteSprite .. // 8: app.run .. // 9: app.addStar .. // 10: app.drawTextCenter .. // 11: app.draw .. // 12: app.keyType .. // 13: app.keySense .. // 14: app.fire .. // 15: ImagePalette .. // 16: ImagePalette.prototype.drawImage .. // 17: ImagePalette.prototype.drawImage_single .. // 18: Sprite .. // 19: Sprite.prototype.setWidth .. // 20: Sprite.prototype.setHeight .. // 21: Sprite.prototype.setWidthWithAspectHeight .. // 22: Sprite.prototype.draw .. // 23: Sprite.prototype.frame .. // 24: Sprite.prototype.moveFunction .. // 25: Sprite.prototype.animFrameChecker .. // 26: Sprite.prototype.moveFrameChecker .. // 27: Maps .. // 28: Maps.prototype.ready .. // 29: Maps.prototype.frame .. // 30: Maps.prototype.draw .. // 31: Map .. console.log( "20181125-index.js loading.." ); //---今月のスクリプト /* this.canvasApply(200,200); は何をしているのか? this.canvasEL.style.width を変更している this.canvasEL.style.height を変更している this.pixelsize を変更している this.resetMozaic(); を実行している 以上 this.ready(); は何をしているか? this.draw() を実行 this.onscrollx() を意図的実行 onscrollx()はcanvasが視聴者の目に入ったかどうかを見ている。 目に入ったら、this.start()、入らなくなったらthis.stop()を実行している。 scroll のイベントリスナを設定 以上 コンストラクタAppはcanvasをどのように用意しているか? function App( id, element ) { 引数のelementについてタグ名がCANVASなら、elementをthis.canvasELとする。 canvasではないなら、canvas自動生成を行う。 canvas自動生成は、elementを親としてcanvasを設置する。 (そのIDはid + "_canvas"である) プログラムの流れ app.init() 一時終了 画像がロードされて、app.onpreloadx() すべてロードされて、app.afterOnload() app.ready()→app.onscrollx()→this.start() タイマで、app.run() キー入力で、app.keyType()、app.keySense() マウスで、app.onmousedownx()、app.onclickx() タッチで、app.ontouchx()未開発 対応付け 一般 このプログラム init afterOnload */ var parentEL = document.getElementById( "sl_monthlyJS_canvas" ); var app = new App( "sl_monthlyJS" ); addEventListener( "load", app.exec.bind( app, parentEL ), false ); //関数 1 / 31 .. app.init = function() { console.log( "-init" ); //canvasについて設定 this.canvasEL.style.border = "solid 0px red"; if( typeof baseURL !== "undefined" ) { this.baseURL = baseURL; this.setCanvasSize( setCanvasSizeW, setCanvasSizeH ); this.pixelsize = pixelsize; //モザイクサイズ this.isWidth100per = isWidth100per; //canvasを親要素の横幅に合わせる this.isScreenFit = isScreenFit; this.isKeepAspect = isKeepAspect; //そのとき縦横アスペクト比を維持する this.isPixelZoom = isPixelZoom; //ドットストレッチ this.isSmoothZoom = isSmoothZoom; //スムーズストレッチ } else { this.baseURL = "20181125-indexJS/"; this.setCanvasSize( 512, 256 ); this.pixelsize = 2; //モザイクサイズ this.isWidth100per = 1; //canvasを親要素の横幅に合わせる this.isScreenFit = 0; this.isKeepAspect = 0; //そのとき縦横アスペクト比を維持する this.isPixelZoom = 0; //ドットストレッチ this.isSmoothZoom = 0; //スムーズストレッチ } this.canvasEL.style.backgroundColor = "black"; //設定後の適用 this.canvasApply(); var p = document.getElementById( "whiteareaID" ); this.toBackgroundOf( p ); //定数 this.EnemyType = 1; this.SupporterMissileType = 2; this.FighterType = 4; this.a = 0; this.toA = -1; this.toAMode = false; this.pause = false; //---画像 this.images = new Object(); this.imageSRCs = [ "map1.png", "map2.png", "map3.png", "message1.png", "fighter.png", "sm.png", "enemy1.png", "enemy2.png", "enemy3.png", ]; this.drawFLG = "preload"; this.preloadCNT = 0; this.draw(); for( var i = 0; i < this.imageSRCs.length; i++ ) { var img = new Image(); img.onload = this.onpreloadx.bind( this ); img.src = this.baseURL + this.imageSRCs[ i ]; img.name = this.imageSRCs[ i ].match( /^(.+)\./ )[ 1 ]; this.images[ img.name ] = img; } }; //関数 2 / 31 .. app.onpreloadx = function( e ) { this.preloadCNT++; console.log( "preloaded: " + this.preloadCNT + "/" + Object.keys( this.images ).length + " : " + e.target.name ); this.draw(); //check. if( 1 && this.preloadCNT == Object.keys( this.images ).length ) this.afterOnload(); }; //関数 3 / 31 .. app.afterOnload = function() { console.log( "afterOnload" ); this.drawFLG = ""; //星々 this.stars = new Array(); for( var i = 0; i < 30; i++ ) { this.addStar( Math.random() * this.canvasW ); } this.supporterMissiles = new Array(); this.enemies = new Array(); this.sprites = new Object(); this.enemyCnt = 0; this.supporterMissileCnt = 0; //自機 this.fighter = new Sprite( "fighter" ); with( this.fighter ) { imagePalette = new ImagePalette( this.cc, this.images.fighter, 142, 36, 90, 36 ); x = 105; y = 80; setWidthWithAspectHeight( 38 ); type = this.FighterType; } this.entrySprite( this.fighter ); this.maps = new Maps( this ); //---スケジュール this.schedule = new Array(); this.scheduleAdd( 5, function() { var enemy = this.newEnemy( 2 ); enemy.y = this.canvasH / 3; } ); var nico = function( tm ) { //ニコ編隊 for( var j = 0; j < 5; j++ ) { for( var i = 0; i < 5; i++ ) { var tm2 = tm + i*2 + j * 50; if( j % 2 ) { this.scheduleAdd( tm2, function() { var enemy = this.newEnemy( 1 ); enemy.y = this.canvasH / 3; } ); } else { this.scheduleAdd( tm2, function() { var enemy = this.newEnemy( 1 ); enemy.y = this.canvasH / 3 * 2; } ); } } } }.bind( this ); nico( 10 ); nico( 650 ); //くるくるりぼん tm = 350; this.scheduleAdd( tm, function() { ( this.newEnemy( 2 ) ).y = this.canvasH / 4; } ); this.scheduleAdd( tm, function() { ( this.newEnemy( 2 ) ).y = this.canvasH / 4 * 3; } ); //上下サンドイッチ隊 var sandwich = function( tm ) { this.scheduleAdd( tm, function() { this.newEnemy( 3, 1 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 3, 2 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 3, 3 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 3, 4 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 3, 5 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 4, 1 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 4, 2 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 4, 3 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 4, 4 ); } ); this.scheduleAdd( tm, function() { this.newEnemy( 4, 5 ); } ); }.bind( this ); sandwich( 540 ); sandwich( 950 ); this.scheduleAdd( 300, function() { this.maps.ready( "map" ); } ); this.scheduleAdd( 1200, function() { this.maps.ready( "message" ); } ); this.timerMS = 50; this.ready(); }; //関数 4 / 31 .. app.scheduleAdd = function( tm, func ) { //check. if( ! this.schedule[ tm ] ) this.schedule[ tm ] = new Array(); this.schedule[ tm ].push( func.bind( this ) ); } //関数 5 / 31 .. app.newEnemy = function( type, mutation ) { var id = "enemy" + this.enemyCnt++; var enemy = new Sprite( id, this ); with( enemy ) { type = this.EnemyType; imagePalette = new ImagePalette( this.cc, this.images.enemy1, 72, 50, 51, 50 ); x = this.canvasW; y = this.canvasH / 2; frame = function() { //this is sprite. this.moveFrameChecker( this.x < 0 ); this.animFrameChecker(); }; } switch( type ) { case 1: //ニコ with( enemy ) { frame = function() { //this is sprite. this.moveFrameChecker( this.x < 0 ); this.imagePalette.index ++; //check. if( this.imagePalette.index > this.imagePalette.maxIndex ) this.imagePalette.index = 0; }; moveFunction = function() { var bx = this.x; this.x -= this.speed / 2; var f = function( x ) { return Math.sin( x * 0.1 ) * 30; } this.y += f( this.x ) - f( bx ); }; } break; case 2: //くるくるりぼん with( enemy ) { width = 48; height = 48; imagePalette = new ImagePalette( this.cc, this.images.enemy2, 72, 75, 60, 75 ); speed = 8; animFrameMax = 3; } break; case 3: //サンドイッチ隊上から with( enemy ) { x = app.canvasW / 2 + 50 * mutation; y = 0; imagePalette = new ImagePalette( this.cc, this.images.enemy3, 72, 60, 60, 60 ); imagePalette.index = mutation; width = 48; height = 48; animFrameMax = 4; frame = function() { //this is sprite. this.moveFrameChecker( this.y > app.canvasH ); this.animFrameChecker(); }; moveFunction = function() { this.x -= this.speed / 2; this.y += this.speed; }; } break; case 4: //サンドイッチ隊下から with( enemy ) { x = app.canvasW / 2 + 50 * mutation; y = app.canvasH; imagePalette = new ImagePalette( this.cc, this.images.enemy3, 72, 60, 60, 60 ); imagePalette.index = mutation; width = 48; height = 48; animFrameMax = 4; frame = function() { //this is sprite. this.moveFrameChecker( this.y < 0 ); this.animFrameChecker(); }; moveFunction = function() { this.x -= this.speed / 2; this.y -= this.speed; }; } break; } this.entrySprite( enemy ); return enemy; }; //関数 6 / 31 .. app.entrySprite = function( sprite ) { switch( sprite.type ) { case this.EnemyType: this.enemies.push( sprite ); break; case this.SupporterMissileType: this.supporterMissiles.push( sprite ); break; case this.FighterType: break; default: alert( "error at app.entrySprite\n\nundefined type: " + sprite.type ); } this.sprites[ sprite.id ] = sprite; } //関数 7 / 31 .. app.deleteSprite = function( sprite ) { switch( sprite.type ) { case this.EnemyType: this.enemies.splice( this.enemies.indexOf( sprite ), 1 ); break; case this.SupporterMissileType: this.supporterMissiles.splice( this.supporterMissiles.indexOf( sprite ), 1 ); break; case this.FighterType: break; default: alert( "error at app.deleteSprite\n\nundefined type: " + sprite.type ); } delete this.sprites[ sprite.id ]; }; //frame //関数 8 / 31 .. app.run = function() { this.keySense(); //debug. if( this.toAMode ) { if( this.a == this.toA ) { this.stop(); this.toAMode = false; this.timerMS = this.toABak; this.pause = true; this.start(); } } if( this.pause ) { this.draw(); return; } //スプライト for( var name in this.sprites ) { var sprite = this.sprites[ name ]; //check. if( sprite.deleteFlg ) { this.deleteSprite( sprite ); continue; } sprite.frame(); } //スケジュール if( this.schedule[ this.a ] ) { for( var i = 0; i < this.schedule[ this.a ].length; i++ ) { this.schedule[ this.a ][ i ](); } } //地上 this.maps.frame(); //当たり判定 for( var i = 0; i < this.supporterMissiles.length; i++ ) { var sm = this.supporterMissiles[ i ]; for( var j = 0; j < this.enemies.length; j++ ) { var en = this.enemies[ j ]; //check. if( en.deleteFlg ) continue; var hitX1 = sm.x >= ( en.x - en.centerX ); var hitX2 = sm.x <= ( en.x + en.centerX ); var hitY1 = sm.y >= ( en.y - en.centerY ); var hitY2 = sm.y <= ( en.y + en.centerY ); if( hitX1 && hitX2 && hitY1 && hitY2 ) { sm.deleteFlg = true; en.deleteFlg = true; break; } } } //背景 星々 for( var i = this.stars.length - 1; i >= 0; i-- ) { var star = this.stars[ i ]; star[ 0 ] -= 4; //check if( star[ 0 ] < 0 ) this.stars.splice( i, 1 ); } if( this.a % 5 == 0 ) { this.addStar( this.canvasW ); } this.draw(); this.a++; }; app.colors = [ "#00f", "#888", "yellow" ]; //関数 9 / 31 .. app.addStar = function( gx ) { var color = this.colors[ Math.floor( Math.random() * this.colors.length ) ]; gx = Math.round( gx ); var gy = Math.round( Math.random() * this.canvasH ); this.stars.push( [ gx, gy, color ] ); } //関数 10 / 31 .. app.drawTextCenter = function( t ) { var cc = this.cc; var sz = 24; var met = cc.measureText( t ); var x = ( this.canvasW - met.width ) / 2 - met.width / 2; var y = ( this.canvasH - sz ) / 2; cc.font = sz + "px ''"; cc.fillStyle = "darkblue"; cc.fillText( t, x+4,y+4 ); cc.fillStyle = "cyan"; cc.fillText( t, x-2,y ); cc.fillStyle = "cyan"; cc.fillText( t, x+1,y ); cc.fillStyle = "cyan"; cc.fillText( t, x,y-2 ); cc.fillStyle = "cyan"; cc.fillText( t, x,y+1 ); cc.fillStyle = "blue"; cc.fillText( t, x,y ); } //関数 11 / 31 .. app.draw = function() { var cc = this.cc; cc.clearRect( 0, 0, this.canvasW, this.canvasH ); if( this.drawFLG == "preload" ) { this.drawTextCenter( "PRELOADING IMAGES.." ); return; } //☆彡 描画 for( var i = this.stars.length - 1; i >= 0; i-- ) { var star = this.stars[ i ]; var gx = star[ 0 ]; var gy = star[ 1 ]; cc.fillStyle = star[ 2 ]; cc.fillRect( gx, gy, 1, 1 ); } //スプライト 描画 for( var name in this.sprites ) { var sprite = this.sprites[ name ]; sprite.draw( cc ); } //地上 描画 this.maps.draw( cc ); cc.font = "18px ''"; cc.fillStyle = "white"; cc.fillText( "a: " + this.a + " sprites: " + Object.keys( this.sprites ).length, 50, 20 ); if( this.pause ) { this.drawTextCenter( "PAUSE" ); } }; //関数 12 / 31 .. app.keyType = function( keynum ) { switch( keynum ) { case 90: this.fire(); break; //z case 65: //a this.stop(); this.toA = Number( prompt( "moveto:", this.a ) ); this.toAMode = true; this.toABak = this.timerMS; this.timerMS = 1; this.a = 0; this.start(); break; case 80: this.pause = ! this.pause; break; //p default: // console.log( this.id + " key typed: " + keynum ); } }; //関数 13 / 31 .. app.keySense = function() { //keySenseはプログラマーがrun()などから適宜呼ぶ //サンプル //キーテーブルをすべて処理 for( var i = 0; i < this.keytable.length; i++ ) { var keynum = this.keytable[ i ]; switch( keynum ) { case 37: this.fighter.x -= this.fighter.speed; break; case 39: this.fighter.x += this.fighter.speed; break; case 38: this.fighter.y -= this.fighter.speed; break; case 40: this.fighter.y += this.fighter.speed; break; default: } } }; //関数 14 / 31 .. app.fire = function() { var missile = new Sprite( "sm" + this.supporterMissileCnt++, this ); with( missile ) { type = this.SupporterMissileType; imagePalette = new ImagePalette( this.cc, this.images.sm ); x = this.fighter.x + this.fighter.centerX - 8; y = this.fighter.y + 4; speed += 8; setWidthWithAspectHeight( 8 ); frame = function() { //"this" is missile. this.x += this.speed; this.deleteFlg = this.x > this.app.canvasW; }; } this.entrySprite( missile ); }; //---class //関数 15 / 31 .. function ImagePalette( cc, image, areaWidth, areaHeight, width, height ) { this.cc = cc; this.image = image; if( arguments.length == 2 ) { this.drawImage = this.drawImage_single; this.width = this.image.width; this.height = this.image.height; } else { this.areaWidth = areaWidth; this.areaHeight = areaHeight; this.width = width; this.height = height; this.columns = Math.floor( image.width / this.areaWidth ); this.rows = Math.floor( image.height / this.areaHeight ); this.maxIndex = this.columns * this.rows; this.index = 0; } } //関数 16 / 31 .. ImagePalette.prototype.drawImage = function( dx, dy, dw, dh ) { var sx = this.index * this.areaWidth; this.cc.drawImage( this.image, sx, 0, this.width, this.height, dx, dy, dw, dh ); }; //関数 17 / 31 .. ImagePalette.prototype.drawImage_single = function( dx, dy, dw, dh ) { this.cc.drawImage( this.image, dx, dy, dw, dh ); }; //関数 18 / 31 .. function Sprite( id, app ) { this.app = app; this.type = null; this.id = id; this.imagePalette = null; this.x = 100; this.y = 100; this.setWidth( 32 ); this.setHeight( 32 ); this.speed = 8; this.deleteFlg = false; this.moveFrameCnt = 0; this.moveFrameMax = 1; this.animFrameCnt = 0; this.animFrameMax = 1; } //関数 19 / 31 .. Sprite.prototype.setWidth = function( width ) { this.width = width; this.centerX = width / 2; }; //関数 20 / 31 .. Sprite.prototype.setHeight = function( height ) { this.height = height; this.centerY = height /2; } //関数 21 / 31 .. Sprite.prototype.setWidthWithAspectHeight = function( width ) { this.setWidth( width ); this.setHeight( this.width / this.imagePalette.width * this.imagePalette.height ); }; //関数 22 / 31 .. Sprite.prototype.draw = function( cc ) { cc.save(); cc.translate( this.x - this.centerX, this.y - this.centerY ); this.imagePalette.drawImage( 0, 0, this.width, this.height ); cc.restore(); }; //関数 23 / 31 .. Sprite.prototype.frame = function( cc ) { }; //関数 24 / 31 .. Sprite.prototype.moveFunction = function( cc ) { this.x -= this.speed; }; //関数 25 / 31 .. Sprite.prototype.animFrameChecker = function( cc ) { if( this.animFrameCnt++ == this.animFrameMax ) { this.animFrameCnt = 0; this.imagePalette.index ++; //check. if( this.imagePalette.index > this.imagePalette.maxIndex ) this.imagePalette.index = 0; } }; //関数 26 / 31 .. Sprite.prototype.moveFrameChecker = function( isDelete ) { if( this.moveFrameCnt++ == this.moveFrameMax ) { this.moveFrameCnt = 0; this.moveFunction(); this.deleteFlg = isDelete; } } //関数 27 / 31 .. function Maps( app ) { this.app = app; this.array = new Array(); this.visibles = new Array(); this.frameCnt = 0; this.frameMax = 1; this.index = 0; this.movePixel = 4; } //関数 28 / 31 .. Maps.prototype.ready = function( mapName ) { this.array = new Array(); var nums = new Array(); for( var name in this.app.images ) { if( name.indexOf( mapName ) == 0 ) { var num = Number( name.substr( mapName.length ) ); nums.push( num ); } } nums.sort( function( a, b ) { if( a < b ) return -1; if( a > b ) return 1; return 0; } ); for( var i = 0; i < nums.length; i++ ) { this.array.push( new Map( this.app.images[ mapName + nums[ i ] ] ) ); } this.visibles.push( this.array[ 0 ] ); this.index = 0; } //関数 29 / 31 .. Maps.prototype.frame = function() { //check. if( this.frameCnt++ != this.frameMax ) { return; } this.frameCnt = 0; var allEnd = true; for( var i = 0; i < this.visibles.length; i++ ) { var map = this.visibles[ i ]; //サイズを更新(ウィンドウリサイズを考慮して) map.width = map.image.width * ( this.app.canvasH / map.image.height ); map.height = this.app.canvasH; map.scrollX += this.movePixel; map.endFlg = map.scrollX >= map.width + this.app.canvasW; //check. 画面右端にマッチして、次のマップが必要か。 if( map == this.array[ this.index ] && map.scrollX >= map.width ) { this.index++; console.log( 321, this.index ); if( this.index < this.array.length ) { this.visibles.push( this.array[ this.index ] ); } } if( map.endFlg == false ) { allEnd = false; } } if( allEnd ) { this.visibles = new Array(); } } //関数 30 / 31 .. Maps.prototype.draw = function( cc ) { for( var i = 0; i < this.visibles.length; i++ ) { var map = this.visibles[ i ]; cc.drawImage( map.image, this.app.canvasW - map.scrollX, 0, map.width, map.height ); } } //関数 31 / 31 .. function Map( image ) { this.scrollX = 0; this.image = image; this.endFlg = false; this.width = 0; this.height = 0; } //---/class