Three.jsを使ってWEBブラウザ上で 3Dスロットマシン ゲーム(Three.js[r145]を使用)
JavaScriptとThree.jsを使って、Webブラウザ上で動く3Dスロットマシンを作ってみました。
本記事では、リールの回転アニメーションや当たり判定のロジックなど、スロットゲームの基本構成を実装例とともに解説します。
「three.jsでゲームを作ってみたい」「スロットマシンの仕組みを学びたい」という方におすすめの内容です。
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>
