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

2020年7月23日

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

t f B! P L
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

このブログを検索

プロフィール

自分の写真
プログラミングとサーバーを心の底から楽しむクリエーターです。 経営者であり、開発者でもありますが、得意としているのは、アイデア創出で、出来高は無限大です。

ブログ アーカイブ

QooQ