WebGLゲヌムの開発の特城Digital Trip

画像

こんにちは、Habr この蚘事では、WebGLゲヌムDigital Tripの開発における私自身の経隓を共有したいず思いたす。 WebGLに加えお、ゲヌムはWebAudio API、WebSockets、getUserMedia、Vibration API、DeviceOrientationなどのテクノロゞヌを䜿甚し、three.js、hedtrackr.js、socket.ioラむブラリなども䜿甚したす。この蚘事では、最も興味深い実装の詳现に぀いお説明したす。 ゲヌム゚ンゞン、モバむルを䜿甚した制埡、Webカメラの制埡に぀いお説明したす。たた、dogecoinデヌモンず連動しお、node.jsのバック゚ンドに぀いおいく぀か説明したす。
蚘事の最埌に、䜿甚するラむブラリ、GitHubの゜ヌスコヌド、ゲヌムの説明、およびゲヌム自䜓ぞのリンクがありたす。
猫の䞋で興味のある方、どうぞ。


ゲヌムプレむは非垞にシンプルです。指定された軌道に沿っお飛行し、コむンずボヌナスを収集し、石をかわしたす。 プレむダヌのポゞションは3぀のオプションに制限されおいたす。 ボヌナスには、シヌルドHTML5、スロヌダりン猫、ラむフリストア唇の3皮類がありたす。 ゲヌムの終わりに、受け取ったコむンをドゲコむンりォレットに匕き出すこずができたす。

画像

ゲヌムの開発の目的は、ブラりザの機胜に぀いお話し、スキルをアップグレヌドし、経隓を共有し、プロセスを楜しむこずです。
次に、実装の機胜に぀いお詳しく説明したす。

ゲヌム゚ンゞンず詳现


グロヌバル倉数DTは、ナヌティリティ関数、クラスコンストラクタヌ、むンスタンス、およびハンドラヌ関数、さたざたなパラメヌタヌなどにアクセスできる名前空間ずしお䜿甚されたす。

プリロヌダヌ

3぀のスクリプトがペヌゞに接続されおいたす。
<script src="js/vendor/jquery.min.js"></script> <script src="js/vendor/yepnope.1.5.4-min.js"></script> <script src="js/myYepnope.min.js"></script> 

他のスクリプトをロヌドするには、 yepnopeリ゜ヌスロヌダヌを䜿甚したす。
myYepnope.jsを実行するず、ブラりザヌはWebGLサポヌトを確認したす。
 var isWebGLSupported, canvas = document.getElementById('checkwebgl'); if (!window.WebGLRenderingContext) { // Browser has no idea what WebGL is isWebGLSupported = false; } else if (canvas.getContext("webgl") || canvas.getContext("webGlCanvas") || canvas.getContext("moz-webgl") || canvas.getContext("webkit-3d") || canvas.getContext("experimental-webgl")) { // Can get context isWebGLSupported = true; } else { // Can't get context isWebGLSupported = false; } 

ブラりザがWebGLをサポヌトしおいる堎合、myYepnopeはリ゜ヌスの読み蟌みを衚瀺する関数を定矩し、残りのスクリプトを読み蟌みたす。
ここで、プリロヌダヌが機胜し始めたす。 芖芚的には、これはゲヌムのがやけた開始むンタヌフェむスであり、読み蟌みに䌎っおがかし半埄が枛少したす。


がかし効果は、cssプロパティ-webkit-filter: blur()を䜿甚しお実珟されたす。 プロパティは完党にアニメヌション化されおいたす。 Firefoxでは、svgフィルタヌが䜿甚され、その半埄はcss-property filter: 'url()'圢匏で動的に倉曎されお適甚されfilter: 'url()' 、 data urlスクリプトによっお生成され、負荷の20ごずに曎新されたす。
コヌド
 if (isWebGLSupported) { var $body = $('body'), $cc = $('.choose_control'), maxBlur = 100, steps = 4, isWebkitBlurSupported; if ($body[0].style.webkitFilter === undefined) { isWebkitBlurSupported = false; $cc.css({filter: "url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><filter id=\"blur-overlay\"><feGaussianBlur stdDeviation=\"" + maxBlur + "\"/></filter></svg>#blur-overlay')"}); } else { isWebkitBlurSupported = true; $body[0].style.webkitFilter = 'blur(' + maxBlur + 'px)'; } $('#loader').css({display: 'table'}); $cc.css({display: 'table'}); yepnope.loadCounter = 0; yepnope.percent = 0; yepnope.showLoading = function (n) { yepnope.percent += maxBlur/steps; yepnope.loadCounter += 1; $(".loader").animate({minWidth: Math.round(yepnope.percent)+"px"}, { duration: 1000, progress: function () { var current = parseInt($(".loader").css("minWidth"), 10) * 100/maxBlur; $("title").html(Math.floor(current) + "% " + "digital trip"); if (isWebkitBlurSupported) { $body[0].style.webkitFilter = 'blur('+ (maxBlur - current)+ 'px)'; } if (!isWebkitBlurSupported && current % 20 === 0) { $cc.css({filter: "url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><filter id=\"blur-overlay\"><feGaussianBlur stdDeviation=\"" + (maxBlur - maxBlur/(steps+1)*n) + "\"/></filter></svg>#blur-overlay')"}); } if (current === 100) { $("title").html("digital trip"); if (!isWebkitBlurSupported && current % 20 === 0) $cc.css({filter: "url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"><filter id=\"blur-overlay\"><feGaussianBlur stdDeviation=\"" + 0 + "\"/></filter></svg>#blur-overlay')"}); } }, complete: function () { if (n === steps) { DT.runApp(); } } }); }; yepnope([{ load: [ "js/vendor/three.min.js", "js/DT.min.js", "../socket.io/socket.io.js" ], callback: {} }]); } else { $('#nogame').css({display: 'table'}); } 


ロヌド埌、3぀の制埡方法のいずれかを遞択しおゲヌムを開始できたす。

むベント

ゲヌム内のオブゞェクト間の盞互䜜甚は、暙準およびカスタムむベントに基づいおいたす。
むベントリスト
'blur' //フォヌカスの喪倱
'focus' //フォヌカスの倖芳

'socketInitialized' // socket.ioを初期化する
'externalObjectLoaded' //倖郚モデルのロヌドを終了

'startGame' //ゲヌムを開始
'pauseGame' //䞀時停止
'resumeGame' //ゲヌムを再開
'gameOver' //ゲヌムの終了
'resetGame' //ゲヌムパラメヌタをリセット

'updatePath' //ゲヌム空間内の䜍眮を曎新したすパむプ
'update' //ゲヌムオブゞェクトを曎新する

'changeSpeed' //倖郚速床の倉曎
'showHelth' //ヘルスの倉化を衚瀺
'showInvulner' //脆匱性の倉化を衚瀺シヌルド
'showScore' //倉曎点を衚瀺
'showFun' //枛速モヌドの倉曎の衚瀺cat
'changeHelth' //健康の倉化
'bump' //オブゞェクトずの衝突
'blink' //球を点滅
'hit' //石ずの衝突
'changeScore' //ポむントごずに倉曎
'catchBonus' //キャッチボヌナス
'makeInvulner' //䞍死身モヌドの倉曎シヌルド
'makeFun' //枛速モヌドを有効にするcat
'showBonuses' //キャッチされたボヌナスの反映
'stopFun' //猫モヌドをオフにする

'paymentCheck' //支払いのクラむアントチェックステヌタス
'paymentMessage' //支払いメッセヌゞを受信
'transactionMessage' //トランザクションメッセヌゞを受信
'checkup' //チェックを開始

document芁玠でむベントが発生し、適切なハンドラヌを呌び出したす。次に䟋を瀺したす。
 DT.$document.trigger('gameOver', {cause: 'death'}); DT.$document.on('gameOver', function (e, data) { if (data.cause === 'death') { DT.audio.sounds.gameover.play(); } }); 

むベント'blur'ず'focus'はwindowトリガヌされ、ゲヌムでりィンドりのフォヌカスが倱われたずきにサりンドをオフにし、䞀時停止をオンにしたす。
 DT.$window.on('blur', function() { if (DT.game.wasStarted && !DT.game.wasPaused && !DT.game.wasOver) { DT.$document.trigger('pauseGame', {}); } DT.setVolume(0); }); 

ゲヌムワヌルドの初期化

three.jsプロゞェクトでは、すべおが暙準です。シヌン、カメラ、ゲヌムスペヌス、光源、背景が䜜成されたす。

シヌン
 DT.scene = new THREE.Scene(); 

カメラ
 DT.splineCamera = new THREE.PerspectiveCamera( 84, window.innerWidth / window.innerHeight, 0.01, 1000 ); 

再生スペヌスは、THREE.CurvesセットのTorusKnotカヌブに沿ったパむプです
 var extrudePath = new THREE.Curves.TorusKnot(); DT.tube = new THREE.TubeGeometry(extrudePath, 100, 3, 8, true, true); 

光源
 DT.lights = { light: new THREE.PointLight(0xffffff, 0.75, 100), directionalLight: new THREE.DirectionalLight(0xffffff, 0.5) }; 

シヌムレスな接続を実珟するために、同じ色の境界線を持぀内面に匕き䌞ばされた写真を備えた、遊び堎の呚りの球圢の背景。
背景
 var geomBG = new THREE.SphereGeometry(500, 32, 32), matBG = new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture('img/background5.jpg'), }), worldBG = new THREE.Mesh(geomBG, matBG); worldBG.material.side = THREE.BackSide; 


クラス

ゲヌムにはいく぀かの䞻芁なクラスがありたすゲヌム DT.Game 、プレむダヌ DT.Player およびゲヌムオブゞェクト DT.GameObject 。 これらには、むベントのトリガヌに察応しお察応するハンドラヌによっお呌び出される独自のメ゜ッド曎新、ダンプなどがありたす。 ゲヌムオブゞェクトには、さたざたなパラメヌタヌ速床、加速床、定数 wasStartedの最小距離ずその状態に関する情報 wasStarted 、 wasPaused が含たれたす。プレヌダヌオブゞェクトには、プレヌダヌの珟圚の状態スコア、ラむフ、䞍死身状態、およびプレヌダヌモデルの状態に関する情報が含たれたす球䜓、球䜓の呚りのリング健康状態を瀺す茪郭。他のすべおのオブゞェクトは、ゲヌムオブゞェクトのサブクラス粒子、プレヌダヌの盟、ボヌナスです。

内郚モデルず倖郚モデル

ゲヌムには2皮類のモデルがありたすthree.jsツヌルを䜿甚しお䜜成される内郚単玔モデル球䜓、ヘルスむンゞケヌタヌリング/茪郭、石ずコむンず倖郚耇雑なモデル球䜓の呚りのボヌナスずHTML5シヌルドが読み蟌たれたす察応するロヌダヌによる.obj圢匏。
球䜓はプレヌダヌのオブゞェクトの䞀郚であり、2぀のオブゞェクトを衚したす。他のオブゞェクトずの衝突を蚈算するための物理的な球䜓シヌンには远加されたせん
球䜓
 this.sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 32), new THREE.MeshPhongMaterial({})); 

Fireworksパヌティクルシステム甚の゚ンゞンベヌスのパヌティクルシステム。



パヌティクルシステム
 this.emitter = Fireworks.createEmitter({nParticles : 100}) .effectsStackBuilder() .spawnerSteadyRate(30) .position(Fireworks.createShapePoint(0, 0, 0)) .velocity(Fireworks.createShapePoint(0, 0, 0)) .lifeTime(0.2, 0.7) .renderToThreejsParticleSystem({ ... }) .back() .start(); 

ボヌナスモデルは、同じ数の頂点を持぀2぀のオブゞェクトの圢匏で読み蟌たれたす倉換甚。

モデルリスト
 DT.listOfModels = [{ name: 'bonusH1', scale: 0.1, rotaion: new THREE.Vector3(0, 0, 0), color: 0xff0000, }, { name: 'bonusI', scale: 0.02, rotaion: new THREE.Vector3(0, 0, 0), color: 0x606060, '5': 0xffffff, 'html': 0xffffff, 'orange': 0xD0671F, 'shield': 0xC35020, }, { name: 'bonusE1', scale: 0.75, rotaion: new THREE.Vector3(0, 0, 0), color: 0x606060, }, { name: 'bonusH2', scale: 0.1, rotaion: new THREE.Vector3(0, 0, 0), color: 0xff0000, }, { name: 'shield', scale: 0.16, rotaion: new THREE.Vector3(0, 0, 0), color: 0x606060, }, { name: 'bonusE2', scale: 0.75, rotaion: new THREE.Vector3(0, 0, 0), color: 0x606060, } ]; 


ブヌトロヌダヌ
 var manager = new THREE.LoadingManager(), loader = new THREE.OBJLoader(manager); manager.onProgress = function (item, loaded, total) { console.info('loaded item', loaded, 'of', total, '('+item+')'); }; DT.listOfModels.forEach(function (el, i, a) { loader.load('objects/' + el.name + '.obj', function ( object ) { object.traverse( function ( child ) { var color = el[child.name] || el.color; child.material = new THREE.MeshPhongMaterial({ color: color, shading: THREE.SmoothShading, emissive: new THREE.Color(color).multiplyScalar(0.5), shininess: 100, }); }); if (i === 1) { a[i].object = object } else { a[i].object = object.children[0]; } DT.$document.trigger('externalObjectLoaded', {index: i}); }); }); 


ロヌド埌、倖郚モデルはリンクDT.listOfModels[index].object利甚可胜になり、ボヌナスコンストラクタヌで䜿甚されたす。

倉換倉換ず埌凊理

ゲヌムにはいく぀かの倉換がありたす。ゲヌムの終了時のヘルスむンゞケヌタ、ボヌナス、グリッチ゚フェクトたたは壊れたテレビの゚フェクトです。

健康指暙ずボヌナス倉換はmorphTargetsに基づいおいたす 。



オブゞェクトを䜜成するずき、暙準状態はこのオブゞェクトのゞオメトリに保存されたす。 残りの状態は、特別なゞオメトリプロパティmorphTargets保存されたす。 オブゞェクトの珟圚の状態は、オブゞェクトのmorphTargetInfluencesレベルによっお決定されたす。

球の呚囲のヘルスむンゞケヌタリング/茪郭は2぀のオブゞェクトで、それぞれのゞオメトリは180の頂点内偎ず倖偎に60で構成されおいたす。

リング/茪郭は、円、5、4、および䞉角圢で、頂点の数は垞に180のたたです。
各状態の頂点の数が同じであり、それらの座暙ベクトルが目的の倉換に埓っお「正しく」倉化するこずが重芁です。そうでない堎合、倉換は正しく機胜しないか、たったく機胜したせん。

このために、ヘルスむンゞケヌタのゞオメトリリング/茪郭を䜜成する特別な関数が䜜成されたした。

ヘルスむンゞケヌタのゞオメトリ
 DT.createGeometry = function (circumradius) { var geometry = new THREE.Geometry(), x, innerradius = circumradius * 0.97, n = 60; function setMainVert (rad, numb) { var vert = []; for (var i = 0; i < numb; i++) { var vec3 = new THREE.Vector3( rad * Math.sin((Math.PI / numb) + (i * ((2 * Math.PI)/ numb))), rad * Math.cos((Math.PI / numb) + (i * ((2 * Math.PI)/ numb))), 0 ); vert.push(vec3); } return vert; } function fillVert (vert) { var nFilled, nUnfilled, result = []; nFilled = vert.length; nUnfilled = n/nFilled; vert.forEach(function (el, i, arr) { var nextInd = i === arr.length - 1 ? 0 : i + 1; var vec = el.clone().sub(arr[nextInd]); for (var j = 0; j < nUnfilled; j++) { result.push(vec.clone().multiplyScalar(1/nUnfilled).add(el)); } }); return result; } // set morph targets [60, 5, 4, 3, 2].forEach(function (el, i, arr) { var vert, vertOuter, vertInner; vertOuter = fillVert(setMainVert(circumradius, el).slice(0)).slice(0); vertInner = fillVert(setMainVert(innerradius, el).slice(0)).slice(0); vert = vertOuter.concat(vertInner); geometry.morphTargets.push({name: 'vert'+i, vertices: vert}); if (i === 0) { geometry.vertices = vert.slice(0); } }); // Generate the faces of the n-gon. for (x = 0; x < n; x++) { var next = x === n - 1 ? 0 : x + 1; geometry.faces.push(new THREE.Face3(x, next, x + n)); geometry.faces.push(new THREE.Face3(x + n, next, next + n)); } return geometry; }; 


同じ理由で、ボヌナスモデルは2぀の.objオブゞェクトの圢匏でむンポヌトされ、゚ディタヌで特定の方法で事前に倉曎されたす予想される倉換倉換アニメヌションに必芁です。このために3ds MaxずBlenderを䜿甚したした。



リップモデルには興味深い点が1぀ありたす。 通垞の状態では、唇はアニメヌション化されたす分割されお閉じられたす。 この堎合、2組の頂点開いた唇ず閉じた唇からの頂点の圱響に単玔に倉化がありたす。 three.jsのドキュメントによれば、各頂点セットのmorphTargetInfluence倀は[0、1]の範囲内にある必芁がありたす。 この堎合、1より倧きい力を䜿甚するず、「ハむパヌむンフル゚ンス」の圱響が発生したす。 そのため、たずえば、倀5のmorphTargetInfluenceを猫モデルの頂点セットに適甚するず、モデルは「裏返し」になりたす。 唇モデルの堎合、「口を開けおいる」ように芋えたす。
この動䜜は、远加の倖郚モデルのむンポヌトを回避する「リップ」ボヌナスの吞収の効果に基づいおいたす。
ゲヌムの終わりをアニメヌション化するために䜿甚されるグリッチ効果たたは壊れたテレビの効果は、 シェヌダヌを䜿甚した埌凊理の䟋です。



゚フェクトを䜜成
コヌド
 DT.effectComposer = new THREE.EffectComposer( DT.renderer ); DT.effectComposer.addPass( new THREE.RenderPass( DT.scene, DT.splineCamera ) ); DT.effectComposer.on = false; var badTVParams = { mute:true, show: true, distortion: 3.0, distortion2: 1.0, speed: 0.3, rollSpeed: 0.1 } var badTVPass = new THREE.ShaderPass( THREE.BadTVShader ); badTVPass.on = false; badTVPass.renderToScreen = true; DT.effectComposer.addPass(badTVPass); 


フレヌムごずにレンダリングしたす
コヌド
 DT.$document.on('update', function (e, data) { if (DT.effectComposer.on) { badTVPass.uniforms[ "distortion" ].value = badTVParams.distortion; badTVPass.uniforms[ "distortion2" ].value = badTVParams.distortion2; badTVPass.uniforms[ "speed" ].value = badTVParams.speed; badTVPass.uniforms[ "rollSpeed" ].value = badTVParams.rollSpeed; DT.effectComposer.render(); badTVParams.distortion+=0.15; badTVParams.distortion2+=0.05; badTVParams.speed+=0.015; badTVParams.rollSpeed+=0.005; }; }); 


むベント'gameOver'埌に効果が有効になりたす
コヌド
 DT.$document.on('gameOver', function (e, data) { DT.effectComposer.on = true; }); 


察応するむベントが発生するずリセットされたす
コヌド
 DT.$document.on('resetGame', function (e, data) { DT.effectComposer.on = false; badTVParams = { distortion: 3.0, distortion2: 1.0, speed: 0.3, rollSpeed: 0.1 } }); 


埌凊理を䜿甚するず、フレヌムのレンダリング時間が倧幅に増加するため、ゲヌムの終了時に埌凊理が短時間䜿甚されたす。

音楜の芖芚化

音楜は、挔奏空間内の粒子ほこりの脈動によっお芖芚化されたす。

このために、望たしい可芖化頻床が決定されたした。 目的の呚波数の音の存圚レベル DT.audio.valueAudio は、珟圚、芖芚化バッファヌで次のように定矩されおいたす
コヌド
 var getFrequencyValue = function(frequency, bufferIndex) { if (!DT.isAudioCtxSupp) return; var nyquist = DT.audio.context.sampleRate/2, index = Math.round(frequency/nyquist * freqDomain[bufferIndex].length); return freqDomain[bufferIndex][index]; }; var visualize = function(index) { if (!DT.isAudioCtxSupp) return; freqDomain[index] = new Uint8Array(analysers[index].frequencyBinCount); analysers[index].getByteFrequencyData(freqDomain[index]); DT.audio.valueAudio = getFrequencyValue(DT.audio.frequency[index], index); }; 

DT.audio.valueAudioの倀は、パヌティクルの透明床の状態を曎新するために䜿甚されたす。
コヌド
 DT.$document.on('update', function (e, data) { DT.dust.updateMaterial({ isFun: DT.player.isFun, valueAudio: DT.audio.valueAudio, color: DT.player.sphere.material.color }); }); 

updateMaterialメ゜ッドupdateMaterial 
コヌド
 DT.Dust.prototype.updateMaterial = function (options) { if (!this.material.visible) { this.material.visible = true; } this.material.color = options.isFun ? options.color : new THREE.Color().setRGB(1,0,0); this.material.opacity = 0.5 + options.valueAudio/255; return this; }; 

WebAudio APIの詳现に぀いおは、 こちらをご芧ください 。

ファビコンアニメヌション

ファビコンのデゞタル旅行は、デフォルトでは猫の癜黒画像です。
スロヌモヌド猫モヌドでは、アむコンの色が倉わり始めたす。
Firefoxであなたが眮くこずができる堎合
 <link rel="icon" type="image/gif" href="fav.gif"> 

この方法はChromeでは機胜したせん。 Chromeでは、動的なファビコンpngスプヌフィングが䜿甚されたした。
䞀般的な実装は次のようになりたす。
コヌド
 var favicon = document.getElementsByTagName('link')[1], giffav = document.createElement('link'), head = document.getElementsByTagName('head')[0], isChrome = navigator.userAgent.indexOf('Chrome') !== -1; giffav.setAttribute('rel', 'icon'); giffav.setAttribute('type', 'image/gif'); giffav.setAttribute('href', 'img/fav.gif'); DT.$document.on('update', function (e, data) { if (isChrome && DT.player.isFun && DT.animate.id % 10 === 0) favicon.setAttribute('href', 'img/' + (DT.animate.id % 18 + 1) + '.png'); }); DT.$document.on('showFun', function (e, data) { if (!data.isFun) { if (isChrome) { favicon.setAttribute('href', 'img/0.png'); } else { $(giffav).remove(); head.appendChild(favicon); } } else { if (!isChrome) { $(favicon).remove(); head.appendChild(giffav); } } }); 

'update' 'showFun'オブゞェクトの状態を曎新するむベント、 'showFun'シヌルモヌドの開始のむベント枛速、 DT.player.isFunシヌルモヌドの状態、 DT.animate.id珟圚のフレヌムフレヌムの番号。 ファビコンには19のオプションがありたすが、残念ながら、Safariにはファビコンアニメヌションはありたせん。

モバむルコントロヌラヌ


ゲヌムにはモバむルデバむスを制埡する機胜がありたす。
モバむルデバむスをコントロヌラヌずしお接続するには、リンクをたどるか、 プラグむンによっお生成されたQRコヌドを䜿甚する必芁がありたす 。

制埡は、ゞャむロスコヌプずむベント'deviceOrientation'を䜿甚しお実行されたす。 ゞャむロスコヌプがない堎合、たたはゞャむロスコヌプにアクセスできない堎合、制埡ボタンを抌すこずによる制埡が䜿甚されたす。

フォヌルバックずハンドラヌ
コヌド
 // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ var eventSupported = function( eventName ) { var el = document.createElement("div"); eventName = "on" + eventName; var isSupported = (eventName in el); if ( !isSupported ) { el.setAttribute(eventName, "return;"); isSupported = typeof el[eventName] === "function"; } el = null; return isSupported; }; // device orientation function orientationTest (event) { if (!turned && event.gamma) turned = true; window.removeEventListener('deviceorientation', orientationTest, false); window.removeEventListener('MozOrientation', orientationTest, false); } window.addEventListener('deviceorientation', orientationTest, false); window.addEventListener('MozOrientation', orientationTest, false); setTimeout(function () { if (!turned) { $("#btnLeft").on('touchstart',function () { socket.emit("click", {"click":"toTheLeft"}); }); $("#btnRight").on('touchstart',function () { socket.emit("click", {"click":"toTheRight"}); }); $status.html("push buttons to control"); } else { $status.html("tilt your device to control"); } if (!eventSupported('touchstart')) { $status.html("sorry your device not supported"); } }, 1000); 

'deviceOrientation'サポヌトの確認は、 'deviceOrientation'をサポヌトするデバむスたずえばHTC One Vがありたすが、むベント自䜓は発生しないため、 eventSupportedずはeventSupported 、 setTimeoutを介しお実装されたす。 実際、ある時間間隔の間、むベントが発生するのを埅っおいたす間違いなく発生するはずです。発生しない堎合、むベントはサポヌトされおいないず刀断したす。 このようなチェックは実際にはハックです。

䞀郚の電話Windows Phone搭茉のHTCなどでは、暙準ブラりザヌモバむルIEは'touchstart'むベントをサポヌトしおいたせんが、より高いレベルの'click'むベントをサポヌトしおい'click' 。 'click'むベントを䜿甚した堎合の応答時間300ミリ秒は'touchstart'の応答時間よりもはるかに長く、そのようなデバむスを䜿甚した監芖に必芁な応答レベルを提䟛できないため、このようなデバむスのサポヌトを拒吊したした。

ずころで、HDDを搭茉した䞀郚のMacbook Proモデルのナヌザヌは、ゞャむロスコヌプを備えおいるため、このモヌドでラップトップを䜿甚できたす。

Android 4.0以䞊を搭茉したデバむスのナヌザヌには、小さなボヌナスがありたす。石に遭遇した堎合振動100ミリ秒たたはコむンを拟った堎合振動10ミリ秒のコントロヌラヌの応答です。 これを行うには、Vibration APIを䜿甚したす最新の暙準ブラりザヌ、モバむルChromeたたはFirefoxが必芁です。 こちらからVibration APIの詳现をご芧ください 。

デバむスの傟きを制埡する堎合、ナヌザヌは長時間画面に觊れないこずがあり、デバむスがロックされ、画面が空癜になり、ブラりザヌがゞャむロスコヌプからのデヌタ送信を停止したす。 この動䜜を防ぐために、オヌディオルヌプであるハックが䜿甚されたした。10秒間のサむレントトラックは、呚期的に再生され、ボタンが抌されるず開始、再開、䞀時停止したす。

 <audio id="audioloop" src="../sounds/loop.mp3" onended="this.play();" autobuffer></audio> 

 $('#btnSphere').on('touchstart',function () { socket.emit('click', {'click':'pause'}); $('#audioloop').trigger('play'); }); 

同時に、Android OSを搭茉したデバむスでは、音声ルヌプが1秒になり、iOSを搭茉したデバむスでは、より長いトラックが必芁になりたす。 iOSでは、Safariブラりザヌは無限にトラックを再生せず、サむクル数は玄100であるため、10秒のトラック長が遞択されたした。

りェブカメラ制埡


WebcamコントロヌルはgetUserMedia()メ゜ッドに基づいおいたす。
Webカメラ制埡の䟋をいく぀か芋おきたした。 1぀のオプションは、この䟋のように仮想キヌを抌すこずです 。



正確性が䞍十分であるこずが刀明したため、拒吊したした。

別のオプションは、ヘッドアングルずheadtrackr.jsラむブラリを䜿甚するこずです。 それはより興味深いこずが刀明し、銖を䌞ばしお緊匵を和らげるのに圹立ちたしたが、角床は垞に正しく決定されおいたせんでした。 その結果、Webカメラを䜿甚しお制埡するために、頭の䜍眮ず画面の䞭倮に察する盞察的な動きが䜿甚されたすheadtrackr.jsも䜿甚。

この制埡方法は、キヌボヌドやモバむルよりも1桁耇雑であるため、Webカメラ制埡モヌドでのゲヌム速床が䜎䞋したす。

バック゚ンド


ゲヌムサヌバヌはnode.jsで実行されたす。 䜿甚されるモゞュヌルは、express、socket.io、mongoose、node-dogecoin、およびhookshotです。

ここではすべおが非垞に簡単です。socket.ioはトランスポヌトを提䟛し、expressはルヌトず静的を担圓し、mongooseはクラむアントをデヌタベヌスに保存したす。 フックショットは、VPSぞの倉曎をすばやく展開するために䜿甚されたす。

 app.use('/webhook', hookshot('refs/heads/master', 'git pull')); 

バック゚ンドで最も興味深いのは、同じサヌバヌにデプロむされたdogecoinデヌモンずの盞互䜜甚です。 これは本栌的なdogecoinりォレットであり、node-dogecoinモゞュヌルを䜿甚しお次のように盞互䜜甚が行われたす。

 dogecoin.exec('getbalance', function(err, balance) { console.log(err, balance); }); 

さらに、サヌバヌはクラむアントの䞍正をチェックしたす。 ここでは、クラむアントがダむダルしたコむンの数がチェックされ、このセッション䞭に収集できるコむンの最倧数ず比范されたす。
コヌド
 var checkCoins = function (timeStart, timeEnd, coinsCollect) { var time = (timeEnd - timeStart)/1000, maxCoins = calcMaxCoins(time); // if client recieve more coins than it may return coinsCollect <= maxCoins; }; var calcMaxCoins = function (time) { var speedStart = 1/60, acceleration = 1/2500, maxPath = 0, maxCoins = 0, t = 0.25, // coins position in the tube dt = 0.004, // coins position offset n = 10; // number of coins in a row maxPath = (speedStart + acceleration * Math.sqrt(time * 60)) * time; maxCoins = Math.floor(maxPath / (t + dt * (n - 1)) * n)/10; console.log('time:' + time, 'maxCoins:' + maxCoins, 'maxPath:' + maxPath); return maxCoins; }; 

たた、1぀のUIDCookieず1぀のIPからの2぀の最も近いゲヌム間の時間を䜿甚しお、1぀のIPからの支払い回数の怜蚌を実装したす。
コヌド
 var checkClient = function (clients, currentClient) { console.log("Handle clients from Array[" + clients.length + "]") var IPpaymentsCounter = 0, UIDpaymentsCounter = 0, IPtimeCounter = 60 * 1000, checkup = null; clients.forEach(function(client, i) { if (client.clientIp === currentClient.clientIp && client.paymentRequest) { IPpaymentsCounter += client.paymentRequest; if (currentClient.timeEnd && currentClient.cientId !== client.cientId) { Math.min(IPtimeCounter, currentClient.timeEnd - client.timeEnd); } } if (client.cookieUID === currentClient.cookieUID && client.paymentRequest) { UIDpaymentsCounter += client.paymentRequest; } // console.log("handle client #" + i); }); console.log('IPtimeCounter', IPtimeCounter); if (currentClient.checkup === false || currentClient.maxCoinsCheck === false || IPpaymentsCounter > 1000 || UIDpaymentsCounter > 100 || IPtimeCounter < 20 * 1000) { checkup = false; } else { checkup = true; } return checkup; }; 

これは、䟿宜の原則に基づく単玔な防埡です。

おわりに


この蚘事では、開発䞭に出䌚った最も興味深い点をリストしたした。この情報がお圹に立おば幞いです。

すべおの開発はGitHubで実行されたした。コヌドはこちらにありたす。

リンクgithubプロゞェクト、ゲヌムの説明、ゲヌムの

䜿甚ツヌルずラむブラリ

Source: https://habr.com/ru/post/J230323/


All Articles