Three.jsを使ってWEBブラウザ上で 3Dスロットマシン ゲーム(Three.js[r145]を使用)
WEBブラウザ上で動作する3Dスロットマシン。Startボタンでスロットマシンが回転し、Stopボタンでスロットが停止します。
ソースコード
<!DOCTYPE html> <html lang="ja"> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=2.0,user-scalable=yes"> <meta charset="UTF-8"> <script src="./three_r145/three.min.js"></script> <script src="./three_r145/OrbitControls.js"></script> <style> .slot3DB{ display:inline-block; box-sizing:border-box; width:100%; min-width:20px; max-width:calc(100% - 0.5em); margin:6px; padding:0.5em; color:#000; text-shadow:1px 1px 2px #999; background:#fff; border:0px none #000; text-decoration:none; box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.4); cursor:pointer; border-radius:10px 10px 10px 10px; vertical-align:middle; user-select: none; word-break:break-all; word-wrap:anywhere; } .slot3DB:active{ box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.4) inset; } .slot3DB:hover{} .slot3DS{ background:#cfc; padding:1em 4em; } </style> <script> var png=CreateTextPng(); var scene,camera,renderer; // 位置 1:加速 2:減速 速度 var slot3D={d:[0,0,0],ds:[0,0,0],dv:[0,0,0],status:-1,bstop:[null,null,null],bstart:null,g:[null,null,null],result:null}; var main = function () { //描画先canvasを取得して幅と高さ取得 let can=document.getElementById('can'); let w = parseInt(can.style.width);//幅取得 let h = parseInt(can.style.height);//高さ取得 //■シーン scene = new THREE.Scene(); scene.background = new THREE.Color(0xccddff); //■カメラ //THREE.PerspectiveCamera( 画角(度), アスペクト比, カメラに写る最短距離, カメラに写る最長距離 ); camera = new THREE.PerspectiveCamera(20, w/h, 0.1, 10000); camera.position.set(0, 0, 24);//カメラの位置を設定 //camera.rotation.set(0,0,0);//カメラの角度を(0度,0度,0度)にする camera.lookAt(new THREE.Vector3(0, 0, 0));//カメラの向きを(0,0,0)座標にする scene.add(camera);//カメラをシーンに追加 //■レンダラー renderer = new THREE.WebGLRenderer({canvas:can, antialias: true});//アンチエイリアス有効 renderer.setSize(w, h); //シャドー(影)マップを有効にする renderer.shadowMap.enabled = true; renderer.setClearColor(0x000000, 1);//背景色を設定 //■光源------------------------------------------------ //環境光(すべての物体にすべての方向から与える光)光の当たらない部分でも真っ黒にならない var AmbientLight=new THREE.AmbientLight(0xffffff,0.4); scene.add( AmbientLight );//環境光をシーンに追加 //スポットライト(色,強さ,距離,角度(angle),半影[0-1],減衰[1,1]) const spotLight=new THREE.SpotLight(0xffffff, 1, 160,Math.PI/8, 0.4, 1); spotLight.position.set(8,12,20); spotLight.target.position.set(0,0,0); spotLight.castShadow=true;//影を表示可能に spotLight.shadow.mapSize.width = 4096; //影をきれいにする spotLight.shadow.mapSize.height = 4096; //影をきれいにする spotLight.shadow.radius=10; //影をぼかす scene.add(spotLight); scene.add(spotLight.target); let m=new THREE.MeshLambertMaterial({ color:0xdddddd,side:THREE.FrontSide }); let mm=new THREE.MeshLambertMaterial({ color:0xff3344,side:THREE.FrontSide,transparent:true,opacity:0.6 }); let bgeo=new THREE.BoxGeometry(1,1,1); let bm1=new THREE.Mesh(bgeo,m); bm1.scale.set(10,8,2); bm1.position.set(0,1,-3); bm1.castShadow=true; bm1.receiveShadow=true; scene.add(bm1); let bm2=new THREE.Mesh(bgeo,m); bm2.scale.set(10,2,4); bm2.position.set(0,4,0); bm2.castShadow=true; bm2.receiveShadow=true; scene.add(bm2); let bm3=new THREE.Mesh(bgeo,m); bm3.scale.set(10,4,6); bm3.position.set(0,-5,-1); bm3.castShadow=true; bm3.receiveShadow=true; scene.add(bm3); let bm4=new THREE.Mesh(bgeo,m); bm4.scale.set(2,6,4); bm4.position.set(-4,0,0); bm4.castShadow=true; bm4.receiveShadow=true; scene.add(bm4); let bm5=new THREE.Mesh(bgeo,m); bm5.scale.set(2,6,4); bm5.position.set(4,0,0); bm5.castShadow=true; bm5.receiveShadow=true; scene.add(bm5); let bm6=new THREE.Mesh(bgeo,mm); bm6.castShadow=true; bm6.scale.set(6,0.1,0.1); bm6.position.set(0,0.72,2-0.05); scene.add(bm6); let bm7=new THREE.Mesh(bgeo,mm); bm7.castShadow=true; bm7.scale.set(6,0.1,0.1); bm7.position.set(0,-0.5,2-0.05); scene.add(bm7); //テクスチャのロード let texture = new THREE.TextureLoader().load( png ); let material=new THREE.MeshLambertMaterial({ color:0xffffff, map:texture ,side:THREE.FrontSide, transparent:false, opacity:1.0, }); //■円柱ジオメトリの作成(上部半径,底半径,高さ,分割数(三角形の数)[8],高さ面の分割数[1],上下が空いてるか[false],始角度[0],角度[Math.PI*2]) let cgeo=new THREE.CylinderGeometry(2,2,1.6,32,1,true,0,Math.PI*2); let m1=new THREE.Mesh(cgeo,material); let m2=new THREE.Mesh(cgeo,material); let m3=new THREE.Mesh(cgeo,material); m1.castShadow=true; m1.receiveShadow=true; m2.castShadow=true; m2.receiveShadow=true; m3.castShadow=true; m3.receiveShadow=true; m1.rotation.set(-Math.PI/10,0,-Math.PI/2); m1.position.set(-1.6,0,0); m2.rotation.set(-Math.PI/10,0,-Math.PI/2); m2.position.set(0,0,0); m3.rotation.set(-Math.PI/10,0,-Math.PI/2); m3.position.set(1.6,0,0); let g1=new THREE.Group(); g1.add(m1); let g2=new THREE.Group(); g2.add(m2); let g3=new THREE.Group(); g3.add(m3); scene.add(g1); scene.add(g2); scene.add(g3); slot3D.g[0]=g1; slot3D.g[1]=g2; slot3D.g[2]=g3; for(let i=0;i<3;i++){ slot3D.bstop[i]=document.querySelector('#bstop'+(i+1)); slot3D.bstop[i].addEventListener('click',function(){ let id=parseInt(event.target.id.substr(-1))-1; if(slot3D.status==1 && slot3D.ds[id]==1){ slot3D.ds[id]=2; } }); } slot3D.bstart=document.querySelector('#bstart'); slot3D.bstart.addEventListener('click',function(){ slot3D.result.innerHTML=''; if(slot3D.status==1){return;} slot3D.status=1; for(let i=0;i<slot3D.d.length;i++){ slot3D.ds[i]=1;//加速 slot3D.dv[i]=0;//速度 } }); slot3D.result=document.querySelector('#slotr'); //オービットコントロールの作成(マウスの左右ボタンドラッグ、ホイール等でカメラが動くようになる) const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.target.set(0,0,0); controls.update(); controls.enabled=false; setInterval(renderLoop,33); } function renderLoop () { renderer.render(scene, camera); starting(); } window.addEventListener('DOMContentLoaded', main, false); function starting(){ if(slot3D.status==-1){return;} for(let i=0;i<slot3D.d.length;i++){ if(slot3D.ds[i]==1){ slot3D.dv[i]+=Math.floor(1+Math.random()*3); if(slot3D.dv[i]>30){slot3D.dv[i]=30;} }else if(slot3D.ds[i]==2){ if(slot3D.dv[i]>2){ slot3D.dv[i]-=(Math.floor(Math.random()*slot3D.dv[i]/10)+1); if(slot3D.dv[i]<2){ if(slot3D.d[i]%2==1){slot3D.d[i]++;} slot3D.dv[i]=2; } }else{ if(slot3D.d[i]%2==1){slot3D.d[i]++;} slot3D.dv[i]=2; } } if(slot3D.ds[i]!=0){ slot3D.d[i]+=slot3D.dv[i]; slot3D.d[i]%=1000; if(slot3D.ds[i]==2&&slot3D.dv[i]==2&&(slot3D.d[i]%100)==0){ slot3D.ds[i]=0; slot3D.dv[i]=0; } } } if(slot3D.ds[0]==0 && slot3D.ds[1]==0 && slot3D.ds[2]==0){ slot3D.status=0; if(slot3D.d[0]==700 && slot3D.d[1]==700 && slot3D.d[2]==700){ slot3D.result.innerHTML="大あたり"; }else if(slot3D.d[0]==slot3D.d[1] && slot3D.d[1]==slot3D.d[2]){ slot3D.result.innerHTML="あたり"; }else if(slot3D.d[0]==slot3D.d[1] || slot3D.d[1]==slot3D.d[2] || slot3D.d[0]==slot3D.d[2]){ slot3D.result.innerHTML="おしい"; }else{ slot3D.result.innerHTML="はずれ"; } } for(let i=0;i<slot3D.g.length;i++){ slot3D.g[i].rotation.set(-Math.PI/500*slot3D.d[i],0,0); } } //png画像を返す function CreateTextPng(){ let can=document.createElement("canvas"); can.width='800'; can.height='80'; let ctx=can.getContext("2d", {willReadFrequently:true}); let family= 'Verdana,Roboto,"Droid Sans","游ゴシック",YuGothic,"メイリオ",Meiryo,'+ '"ヒラギノ角ゴ ProN W3","Hiragino Kaku Gothic ProN","MS Pゴシック",sans-serif'; ctx.font="80px "+family; ctx.textBaseline="ideographic"; ctx.textAlign="center"; //透明にする //ctx.globalCompositeOperation = 'destination-out'; ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle="rgb(255,255,255)"; ctx.fillRect(0,0,can.width,can.height); //通常描画にする ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle="rgb(0,0,0)"; for(let i=0;i<10;i++){ ctx.save(); ctx.rotate(-Math.PI/2);//反時計回りが正、ラジアン単位、回転中心は(0,0)左上 ctx.translate(-80,0); ctx.fillText(i,40,i*80+80); ctx.restore(); } let png=can.toDataURL('image/png'); return png; } </script> </head> <body> <h3 class="diag"></h3> <p class="normal"> <br> <canvas id="can" style="width:800px; height:480px; max-width:calc(100vw - 32px);max-height:calc((100vw - 32px) * 480 / 800);-ms-touch-action:none;touch-action:none;cursor:grab;"></canvas> <br> </p> <div style="display:flex;flex-wrap:wrap;justify-content:center;width:100%;max-width:800px;"> <div style="width:20%;text-align:center;"><a class="slot3DB" id="bstop1">Stop</a></div> <div style="width:20%;text-align:center;"><a class="slot3DB" id="bstop2">Stop</a></div> <div style="width:20%;text-align:center;"><a class="slot3DB" id="bstop3">Stop</a></div> </div> <div style="width:100%;max-width:800px;text-align:center;"><a class="slot3DB slot3DS" id="bstart">Start</a></div> <div id="slotr" style="width:100%;max-width:360px;font-size:32px;color:red;font-weight:bold;"> </div> </body> </html>