ゲームライブラリ構築までの道「インベーダー編」#4 : ドット絵管理方法

2020年7月23日

Javascript テクノロジー プログラミング 特集

eyecatch 16進数はかつてBASIC言語で、キャラクタを作った時に覚えた、ユゲタです。 16進数は理屈は分かっていても、実際使う時に、頭の中で2進数に変換して考えるんですが、未だに対比表を見ながらでないと、覚えきれません。 そして、前回まで表示していた画像素材のpngフォーマットで、色変更が柔軟にできないという事で、ビットマップ画像を16進数で管理したいと思います。 そのドット表示方法などを構築してみたいと思います。

本日のIT謎掛け

「16進数」と、かけまして・・・ 「アントレプレナー」と、ときます。 そのココロは・・・ ゼロイチの理解が必要です。

16進数対比表

16進数 : 2進数 0 : 0000 1 : 0001 2 : 0010 3 : 0011 4 : 0100 5 : 0101 6 : 0110 7 : 0111 8 : 1000 9 : 1001 A : 1010 B : 1011 C : 1100 D : 1101 E : 1110 F : 1111

ソースコード

(function(w,d){ var __options = { canvas_size : { x : null , y : null }, "cannon" : { type : "bit", fill : "black", bitSize : 4, src : "images/dot/cannon.dot", x : 10, y : 200, w : 64, h : 64, moveX : 10 }, "uso" : { type : "bit", fill : "black", bitSize : 4, src : "images/dot/ufo.dot", x : 10, y : 10, w : 64, h : 64, moveX : 10 }, "invader" : { "crab" : [ { type : "bit", fill : "black", src : 'images/dot/crab_1.dot', bitSize : 4, x : 10, y : 64, w : 64, h : 64 }, { type : "bit", fill : "black", src : "images/dot/crab_2.dot", bitSize : 4, x : 10, y : 64, w : 64, h : 64 } ], "octpus" : [ { type : "bit", fill : "black", src : "images/dot/octpus_1.dot", bitSize : 4, x : 80, y : 64, w : 64, h : 64 }, { type : "bit", fill : "black", src : "images/dot/octpus_2.dot", bitSize : 4, x : 80, y : 64, w : 64, h : 64 } ], "squid" : [ { type : "bit", fill : "black", bitSize : 4, src : "images/dot/squid_1.dot", x : 150, y : 64, w : 64, h : 64 }, { type : "bit", fill : "black", bitSize : 4, src : "images/dot/squid_2.dot", x : 150, y : 64, w : 64, h : 64 } ] } }; var MAIN = function(canvas_selector){ this.canvas_selector = canvas_selector || "canvas"; this.setCanvas(this.canvas_selector); this.set_imageMax(); this.pattern = 0; this.view(this.pattern); this.event_set(); this.animation_roop(30); }; MAIN.prototype.setCanvas = function(selector){ this.canvas_elm = d.querySelector(selector); if(w.innerWidth < this.canvas_elm.offsetWidth){ this.canvas_elm.setAttribute("width" , w.innerWidth); } if(w.innerHeight < this.canvas_elm.offsetHeight){ this.canvas_elm.setAttribute("height" , w.innerHeight); } __options.canvas_size.x = this.canvas_elm.offsetWidth; __options.canvas_size.y = this.canvas_elm.offsetHeight; __options.cannon.y = __options.canvas_size.y - 100; // smooth this.ctx = this.canvas_elm.getContext("2d"); this.ctx.imageSmoothingEnabled = false; this.ctx.mozImageSmoothingEnabled = false; this.ctx.webkitImageSmoothingEnabled = false; this.ctx.msImageSmoothingEnabled = false; }; MAIN.prototype.clear = function(){ this.ctx.clearRect(0, 0, this.canvas_elm.width, this.canvas_elm.height); }; MAIN.prototype.view = function(pattern){ // invader for(var i in __options.invader){ this.image(__options.invader[i][pattern]); } // canon this.image(__options.cannon); } MAIN.prototype.image_cache = []; MAIN.prototype.image = function(options){ if(!this.canvas_elm){return;} if(!options){return;} // 新規読み込み if(typeof this.image_cache[options.src] === "undefined"){ switch(options.type){ case "file": this.image_file_set(options); break; case "bit": this.image_bit_set(options); break; } } // キャッシュ利用 else{ switch(options.type){ case "file": this.image_draw(options); break; case "bit": this.image_bit_make(options); break; } } }; MAIN.prototype.image_file_set = function(options){ this.image_cache[options.src] = new Image(); var img = this.image_cache[options.src]; img.src = options.src; img.onload = (function(options){ this.image_draw(options , img); }).bind(this , options); }; MAIN.prototype.image_bit_set = function(options){ this.image_cache[options.src] = ""; new AJAX({ url : options.src, methdo : "GET", async : true, onSuccess : (function(options , data){ var char16 = data.split("\n"); options.bits = []; for(var i in char16){ // 16進数を2進数に変換 var char2 = parseInt(char16[i] , 16).toString(2); // ゼロパディング用桁数算出 var str_count = Math.pow(char16[i].length , 2); var zero = new Array(str_count).fill("0").join("") // サイズ取得 // options.bitSize = Math.ceil(options.w / str_count * 10) / 10; options.bitSize = options.w / str_count; // char2 char2 = (zero + char2).slice(-str_count); options.bits.push(char2); } this.image_bit_make(options); }).bind(this , options) }); }; MAIN.prototype.image_bit_make = function(options){ if(!options.bits){return;} this.ctx.fillStyle = options.fill; this.ctx.strokeStyle = null; this.ctx.strikeWidth = 0; for(var i=0; i<options.bits.length; i++){ for(var j=0; j<options.bits[i].length; j++){ var bit = options.bits[i].charAt(j); if(bit == 0){continue;} var w = options.bitSize; var h = options.bitSize; var x = options.x + (j * w); var y = options.y + (i * h); this.ctx.fillRect(x , y , Math.ceil(w) , Math.ceil(h)); } } }; MAIN.prototype.image_draw = function(options){ if(typeof this.image_cache[options.src] !== "object"){return} var img = this.image_cache[options.src]; this.ctx.drawImage(img , options.x, options.y ,options.w , options.h); }; MAIN.prototype.animation_roop = function(time){ var func = (function(e){this.animation(e)}).bind(this); this.animation_proc(); if(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame){ window.requestAnimationFrame(func); } else{ time = time || 10; anim_flg = setTimeout(func , time); } }; MAIN.prototype.animation_proc = function(){ switch(this.keydown_flg){ case "right": this.cannon_move(__options.cannon.x + __options.cannon.moveX); break; case "left": this.cannon_move(__options.cannon.x - __options.cannon.moveX); break; } }; MAIN.prototype.cannon_move = function(pos){ if(pos < 0){ pos = 0; } else if(pos > this.canvas_elm.offsetWidth - __options.cannon.w){ pos = this.canvas_elm.offsetWidth - __options.cannon.w; } __options.cannon.x = pos; }; MAIN.prototype.animation = function(){ this.clear(); this.nextPattern(300); this.view(this.pattern); this.animation_roop(30); } MAIN.prototype.nextPattern = function(frame_rate){ this.prev_time = this.prev_time || 0; if((+new Date()) - this.prev_time < frame_rate){return} this.prev_time = (+new Date()); this.pattern++; if(this.pattern >= this.image_max){ this.pattern = 0; } }; MAIN.prototype.set_imageMax = function(){ for(var i in __options.invader){ this.image_max = __options.invader[i].length; break; } }; /* Event */ MAIN.prototype.event_set = function(){ // new LIB().event(window , "click" , (function(e){this.click(e)}).bind(this)); new LIB().event(w , "keydown" , (function(e){this.keydown(e)}).bind(this)); new LIB().event(w , "keyup" , (function(e){this.keyup(e)}).bind(this)); new LIB().event(w , "mousemove" , (function(e){this.mousemove(e)}).bind(this)); new LIB().event(w , "touchmove" , (function(e){this.touchmove(e)}).bind(this)); new LIB().event(w , "touchend" , (function(e){this.touchend(e)}).bind(this)); }; MAIN.prototype.keydown = function(e){ switch(e.keyCode){ case 37: // <- case 'ArrowLeft': this.keydown_flg = "left"; break; case 39: // -> case 'ArrowRight': this.keydown_flg = "right"; break; } }; MAIN.prototype.keyup = function(e){ this.keydown_flg = false; }; MAIN.prototype.mousemove = function(e){ // if(this.flg_gamestart !== true){return;} this.mousePos = this.mousePos || e.clientX; this.cannon_move(__options.cannon.x + (e.clientX - this.mousePos)); this.mousePos = e.clientX; }; MAIN.prototype.touchmove = function(e){ // if(this.flg_gamestart !== true){return;} if(!e || !e.touches || e.touches.length > 1){ this.mousePos = null; return; } this.mousePos = typeof this.mousePos === "number" ? this.mousePos : e.touches[0].clientX; this.cannon_move(__options.cannon.x + (e.touches[0].clientX - this.mousePos)); this.mousePos = e.touches[0].clientX; }; MAIN.prototype.touchend = function(e){ this.mousePos = null; }; var LIB = function(){}; LIB.prototype.event = function(target, mode, func , flg){ flg = (flg) ? flg : false; if (target.addEventListener){target.addEventListener(mode, func, flg)} else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})} }; var AJAX = function(option){//console.log(option); if(!option){return} var httpoj = this.createHttpRequest(); if(!httpoj){return;} // var option = new MAIN().setOption(options); var data = this.setQuery(option); if(!data.length){ option.method = "get"; } httpoj.open( option.method , option.url , option.async ); httpoj.setRequestHeader('Content-Type', option.type); httpoj.onreadystatechange = function(){ if (this.readyState==4 && httpoj.status == 200){ option.onSuccess(this.responseText); } }; // FormData 送信用 if(typeof option.form === "object" && Object.keys(option.form).length){ httpoj.send(option.form); } // query整形後 送信 else{ if(data.length){ httpoj.send(data.join("&")); } else{ httpoj.send(); } } }; AJAX.prototype.dataOption = { url:"", query:{}, querys:[], data:{}, form:{}, async:"true", method:"POST", type:"application/x-www-form-urlencoded", onSuccess:function(res){}, onError:function(res){} }; AJAX.prototype.createHttpRequest = function(){ //Win ie用 if(window.ActiveXObject){ //MSXML2以降用; try{return new ActiveXObject("Msxml2.XMLHTTP")} catch(e){ //旧MSXML用; try{return new ActiveXObject("Microsoft.XMLHTTP")} catch(e2){return null} } } //Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用; else if(window.XMLHttpRequest){return new XMLHttpRequest()} else{return null} }; // URL-Queryの作成 AJAX.prototype.setQuery = function(option){ var data = []; if(typeof option.datas !== "undefined"){ for(var key of option.datas.keys()){ data.push(key + "=" + option.datas.get(key)); } } if(typeof option.query !== "undefined"){ for(var i in option.query){ data.push(i+"="+encodeURIComponent(option.query[i])); } } if(typeof option.querys !== "undefined"){ for(var i=0;i<option.querys.length;i++){ if(typeof option.querys[i] == "Array"){ data.push(option.querys[i][0]+"="+encodeURIComponent(option.querys[i][1])); } else{ var sp = option.querys[i].split("="); data.push(sp[0]+"="+encodeURIComponent(sp[1])); } } } return data; } new LIB().event(w , "load" , function(){new MAIN("#mycanvas")}); })(window,document);

解説

__optionsという画像データの設定項目に"type"を追加して、これまでのpngなどの画像ファイルはtype:"file"、ドット絵の扱いはtype="bit"というフラグを付けて管理することにしました。 ちなみに、登場する画像は、全てbit管理に変更しました。 容量を比較すると、これまでのpngファイルは、140バイト使っていたのに対して、 16進数のデータファイルは、79バイトと半分ぐらいのサイズになるので、ネットで扱う場合に効率的です。 この16進数データについては、1つだけサンプルで掲載しておきます。 0000 0000 0000 0820 0440 0FE0 1BB0 3FF8 2FE8 2828 06C0 0000 0000 0000 0000 0000 これを2進数に変換して、0と1の文字列の1の部分にcanvasでrectを表示するという方法です。 実際に表示させてみると、これまでのpngとなんら違和感が無いこともよくわかります。

Github

ソースは以下にアップしています。(ver:0.3) https://github.com/yugeta/game_invader

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ