欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

经典之选-利用js与css打造的3D年兽小游戏,让你盘点儿时欢乐!

最编程 2024-08-11 20:19:22
...

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

前言

快过年,各大游戏中都陆续添加了打年兽的休闲玩法。之前学习了css的3D相关知识,一直想实践一下,这次就使用css加上js实现3D版打年兽小游戏。

效果演示

在线体验

20220113_112717.gif

游戏玩法说明

  1. 完全模拟打地鼠游戏的玩法,将地鼠换成年兽,使用锤子敲击得分。
  2. 每敲中5次速度升级一次,速度提升100毫秒。
  3. 3次失败后游戏结束。

实现流程

设计3D地面

微信截图_20220113115135.png

将main层div作为3D容器,ground层div作为地面,通过X,Y坐标旋转调整地面的默认角度transform: rotateY(30deg) rotateX(60deg);,同时将ground层设置成3D容器,为后面在地面上添加年兽准备。代码如下。 页面元素:

<div class="main">
    <div class="ground">
    </div>
</div>

样式布局:

.main{
    width:400px;height:400px;
    margin:0px auto;
    perspective-origin: 50% 200px;
    perspective: 2000px;
    transform-style: preserve-3d;
    backface-visibility: hidden;
}
.main .ground{
    width:100%;height:100%;
    transform: rotateY(30deg) rotateX(60deg);
    background: rgb(7, 255, 138);
    position: relative;
    perspective-origin: 50% 100px;
    perspective: 1000px;
    transform-style: preserve-3d;
    backface-visibility: hidden;
}

设计3D相机效果

20220113_133739.gif

我们知道3D坐标分为X、Y、Z三个方向,X轴为水平方向,Y轴为竖直方向,Z轴为垂直与屏幕方向。要实现上图中的效果,保持Z轴不变,只改变X、Y轴旋转角度即可。

具体操作逻辑为监听鼠标点击后的移动事件,通过计算当前鼠标位置相对于鼠标刚点击时的位置X、Y轴像素偏移量动态调整、Y轴的旋转角度,代码如下。

document.addEventListener("mousedown",function(e){
    click = true;
    sp = {x:e.clientX,y:e.clientY}
});

document.addEventListener("mousemove",function(e){
    if(!click){
        return;
    }
    var ydeg = (deg.y + (e.clientX - sp.x)/10);
    var xdeg = (deg.x - (e.clientY - sp.y)/10);
    $('.ground').css({transform:'rotateY('+ydeg+'deg) rotateX('+xdeg+'deg)'})
    deg = {x:xdeg,y:ydeg};
    sp = {x:e.clientX,y:e.clientY}
});

document.addEventListener("mouseup",function(e){
    click = false;
})

设计地洞效果

微信截图_20220113134535.png

通过白色背景线条在水平和竖直方向将地面分成4X4棋盘,每一片代表一个地洞,代码如下。

var topVal = 100;
for (var i = 0; i < 4; i++) {
    var div = document.createElement("div");
    div.style.top = topVal + "px";
    div.style.left = "0px";
    div.style.width = '100%';
    div.style.height = '2px';
    $('.ground').append(div)
    topVal += 100;
}

var leftVal = 100;
for (var i = 0; i < 4; i++) {
    var div = document.createElement("div");
    div.style.left = leftVal + "px";
    div.style.top = "0px";
    div.style.width = '2px';
    div.style.height = '100%';
    $('.ground').append(div)
    leftVal += 100;
}

设计年兽

微信截图_20220113135028.png

在第一个地洞的位置画一个3D盒子作为年兽身体,长80px,宽30px,高30px,离地面高度为10px,代码如下。

页面元素

<div class="main">
    <div class="ground">
        <div class="nian body front"></div>
        <div class="nian body left"></div>
        <div class="nian body back"></div>
        <div class="nian body right"></div>
        <div class="nian body top"></div>
        <div class="nian body bottom"></div>
    </div>
</div>

style样式

.main .ground > .body{
    top:0px;
    left:35px;
    background: rgb(243, 253, 255);
    height: 80px;
    width: 80px;
}
.main .ground > .front{
    width: 30px;
    transform: translateZ(40px);
}
.main .ground > .left{
    width:30px;
    transform: translateZ(40px) rotateY(90deg);
    transform-origin: left center;
}

.main .ground > .back{
    width: 30px;
    transform: translateZ(10px) rotateY(180deg);
 }

.main .ground > .right{
    width: 30px;
    transform:translateZ(40px)   rotateY(-90deg);
    transform-origin: right;
}

.main .ground > .top{
    height: 30px;
    width: 30px;
    transform: translateZ(40px) rotateX(-90deg);
    transform-origin: top center;
}

.main .ground > .bottom{
    height: 30px;
    width: 30px;
    top:50px;
    transform: translateZ(40px) rotateX(90deg);
    transform-origin: bottom center;
}

然后一次画出年兽的头部、脚步3D盒子,效果如下,代码见底部完整代码。

微信截图_20220113135724.png

设计年兽出现效果

transVal对象存储了年兽每个部位的最终tansform变换值,用来提供jQuery展示动画;年兽出现位置随机,通过随机函数生成年兽最终的位置,通过top、left样式属性定位,通过Jquery的animate函数动态调整translateZ的值,形成年兽重地下钻出来的效果。

设置定时器,到期自动执行游戏主体函数,游戏主体函数逻辑在下面的内容介绍。

var transVal = {
    front:{
        translateZ:{val:40,un:'px'}
    },
    left:{
        translateZ:{val:40,un:'px'},
        rotateY:{val:90,un:'deg'}
    },
    right:{
        translateZ:{val:40,un:'px'},
        rotateY:{val:-90,un:'deg'}
    },
    back:{
        translateZ:{val:10,un:'px'},
        rotateY:{val:180,un:'deg'}
    },
    top:{
        translateZ:{val:40,un:'px'},
        rotateX:{val:-90,un:'deg'}
    },
    bottom:{
        translateZ:{val:40,un:'px'},
        rotateX:{val:90,un:'deg'}
    },
    'head-front':{
        translateZ:{val:80,un:'px'}
    },
    'head-left':{
        translateZ:{val:80,un:'px'},
        rotateY:{val:90,un:'deg'}
    },
    'head-right':{
        translateZ:{val:80,un:'px'},
        rotateY:{val:-90,un:'deg'}
    },
    'head-back':{
        translateZ:{val:41,un:'px'},
        rotateY:{val:180,un:'deg'}
    },
    'head-top':{
        translateZ:{val:80,un:'px'},
        rotateX:{val:-90,un:'deg'}
    },
    'head-bottom':{
        translateZ:{val:65,un:'px'},
        rotateX:{val:-90,un:'deg'}
    },
    'foot-front':{
        translateZ:{val:10,un:'px'}
    },
    'foot-left':{
        translateZ:{val:10,un:'px'},
        rotateY:{val:90,un:'deg'}
    },
    'foot-right':{
        translateZ:{val:10,un:'px'},
        rotateY:{val:-90,un:'deg'}
    },
    'foot-back':{
        translateZ:{val:0,un:'px'},
        rotateY:{val:180,un:'deg'}
    },
    'foot-top':{
        translateZ:{val:10,un:'px'},
        rotateX:{val:-90,un:'deg'}
    },
    'foot-bottom':{
        translateZ:{val:10,un:'px'},
        rotateX:{val:90,un:'deg'}
    }
}
function showNianshou() {
    $('.nian').show()
    if(!start){
        return;
    }
    var topNum = parseInt(Math.random()*4)
    var leftNum = parseInt(Math.random()*4)
    $('.body').css({top:topNum*100+'px',left:leftNum*100 + 35 +'px'})
    $('.head').css({top:topNum*100+ 60 +'px',left:leftNum*100 + 30 +'px'})
    $('.head-bottom').css({top:topNum*100+ 60 +'px',left:leftNum*100 + 27 +'px'})
    $('.foot1').css({top:topNum*100 +'px',left:leftNum*100 + 35 +'px'})
    $('.foot2').css({top:topNum*100 +'px',left:leftNum*100 + 55 +'px'})
    $('.foot3').css({top:topNum*100+ 75 +'px',left:leftNum*100 + 35 +'px'})
    $('.foot4').css({top:topNum*100+ 75 +'px',left:leftNum*100 + 55 +'px'})
    $('.nian').animate({opacity:1},{
        step:function(now,fix){
            var obj = transVal[$(this).attr('class').split(' ')[2]]
            if(obj){
                var s = ' '
                for (var v in obj){
                    if(v.indexOf('translate') >= 0){
                        s += v + '('+ (obj[v].val - 80  + now * 80) + obj[v].un +') '
                    }else{
                        s += v + '('+obj[v].val+ obj[v].un +') '
                    }
                }
                $(this).css({transform:s})
            }
        },
        duration:timer
    },'linear')
    setTimeout(function(){
        hiding = false
        setTimeout(function(){clickFun(undefined,true)},timer)
    },timer)
}

设计年兽消失效果

同上通过Jquery的animate函数动态调整translateZ的值,形成年兽钻回地下果,代码如下。

function hideNianshou(){
    $('.nian').animate({opacity:0},{
        step:function(now,fix){
            var obj = transVal[$(this).attr('class').split(' ')[2]]
            if(obj){
                var s = ' '
                for (var v in obj){
                    if(v.indexOf('translate') >= 0){
                        s += v + '('+ (obj[v].val - 80  + now * 80) + obj[v].un +') '
                    }else{
                        s += v + '('+obj[v].val+ obj[v].un +') '
                    }
                }
                $(this).css({transform:s})
            }
        },
        duration:timer
    },'linear');
}

设计年兽打中效果

同上通过Jquery的animate函数动态调整translateZ的值,形成年兽被打死的效果,码如下。

function deadNianshou(){
    $('.nian').animate({opacity:0},{
        step:function(now,fix){
            $(this).css({transform:'translateZ('+now * 40+'px)'})
        },
        duration:timer
    },'linear');
}

设计打击效果

锤子素材

chuizi.png

页面中添加锤子元素,同时给main层添加鼠标移动监听,调整锤子的位置使其跟随鼠标的光标移动。

<img id="chui" src="${rc.contextPath}/static/image/chuizi.png" width="50" style="position: absolute;">

在移动锤子的位置时判断锤子是否正在执行打击动画,正在执行时锤子的位置不变,动画执行完,再次跟随鼠标光标移动。

$('.main')[0].addEventListener('mousemove',function(e){
    if(!hiting){
        $('#chui').css({left:e.clientX-5+'px',top:e.clientY - 55'px'})
    }
})

给ground层添加点击时间监听,执行锤子的打击动画

$('.ground')[0].addEventListener('click',function(e){
    $('#chui').addClass("hit");
    hiting = true
    setTimeout(function(){
        hiting = false
        $('#chui').removeClass("hit');
    },300)
})

20220113_142552.gif

打击动画通过keyframes 实现。

.hit{
    animation: hit ease 0.3s;
}
@keyframes hit {
    0%{
        transform: rotate(0deg) ;
    }
    80%{
        transform: rotate(30deg) translateX(50px) translateY(-100px);
    }
    100%{
        transform: rotate(0deg);
    }
}

游戏主体函数设计

给年兽添加点击事件,当年兽被点击后执行游戏主体函数。

$('.nian').on('click',clickFun);

游戏主题函数两个参数,第一个为点击时间Event,第二个参数为是否自动执行自动则为true手动点击为undefinded.

通过hiding值防止主体函数并发执行导致游戏画面不可控。

主体函数被自动执行则说明,点击的慢了,此时执行年兽消失函数,执行点击失败后逻辑,重新执行年兽出现函数。

主体函数因年兽被点击后执行,则说明打到了年兽,此时计算成功次数并判断是否提升游戏速度,每成功击中5次提升一次速度,重新执行年兽出现函数。

function clickFun(e,auto){
    if(e){
        e.stopPropagation();
    }
    if(hiding){
        return;
    }
    hiding = true
    if(auto){
        hideNianshou()
        setTimeout(function(){
            dealAfterFail();
            showNianshou()
        },timer)
    }else{
        $('#chui').addClass("hit");
        hiting = true
        setTimeout(function(){
            hiting = false
            $('#chui').removeClass("hit");
            sucTimes ++
            if(sucTimes%5==0){
                showMsg('速度加快了')
                timer -= 100
            }
            if(timer < 400){
                timer = 400
            }
            deadNianshou();
            setTimeout(function(){
                showMsg('成功打到'+sucTimes +'只年兽')
                showNianshou()
            },timer)
        },300)
    }
}

设计打击年兽失败后函数

累加失败次数,当失败次数达到3时,游戏结束,弹出游戏结束消息。

function dealAfterFail(){
    failTimes ++
    if(failTimes == 3){
        showMsg('GAME OVER,成功打到'+sucTimes+'只年兽');
        start = false
    }
}

游戏弹出消息设计

页面添加div设置其位于页面右上角,默认隐藏,提供showMsg函数动态修改div文字内容,2秒后自动消失。

<div class="msg">速度加快了</div>
.msg{
    display: none;
    width:300px;
    height:50px;
    line-height: 50px;
    text-align: center;
    color: #CC2222;
    position: absolute;
    right: 10px;
    top:10px;
    background: rgba(255,255,255,.8);
    border-radius: 5px;
}
.msg:after{
    content: ' ';
    clear: both;
}
function showMsg(msg){
    $('.msg').html(msg);
    $('.msg').show();
    setTimeout(function(){$('.msg').hide();},2000)
}

设计开始菜单

页面添加列表元素,添加点击时间。

<ul class="hover">
    <li onclick="reStart()">开始</li>
</ul>
ul{
    display: block;
    position: absolute;
    top:10px;
    left:10px;
    width: 200px;
    background: rgba(255,255,255,.3);
}
li{
    display: block;
    list-style: none;
    width:100px;
    height: 30px;
    text-align: center;
    line-height: 30px;
    color: #fff;
    background: rgba(255,0,0,.7);
    border-radius: 2px;
    margin:10px auto;
    cursor: pointer;
}
li:hover{
    background: rgba(255,0,0,1);
    transform: scale(1.1);
}

年兽默认隐藏。

$('.nian').hide()

点击开始后,重置游戏参数,执行年兽消失函数,执行年兽出现函数。

function reStart(){
    start = true
    sucTimes = 0
    failTimes = 0
    timer = 800
    hideNianshou()
    setTimeout(showNianshou,timer)
}

完整代码

3D兽小游戏源码