Day15. 一起动手做弹珠台!(3)

嘿,今天又是实作环节!比起单纯的范例,这个环节是希望把我们聊的模组/功能实际应用,让大家看看应用在实际的程序上,这些函式/属性会怎麽被运用。

虽然这个实例可能不一定大家都会尝试,但如果可以,希望大家都能有个机会自己尝试发想一个题目。如我们一开始讲得一样,有目的的学习,才会让你在了解这些知识的时候更有方向、更有印象。

开始前一样来看一下需求清单,今天要利用前面两天的时间控制与镜头控制,来实现下面我们画粗体的功能。

  • 建立方钉和颜色钉
  • 方钉和颜色钉会是固定的
  • 建立圆球
  • 圆球需要自由落体,受到重力影响
  • 圆球要能和方钉碰撞产生弹跳
  • 圆球和颜色钉碰撞的时候要侦测到碰撞并变色
  • 镜头移动,上带到下
  • 在开始前,画面要是静止的,直到我们发出Signal
  • 滑鼠和球体互动 (追加功能)

啊,如果读者比对我们先前的清单会发现笔者多划掉了下面这一项。

  • 在开始前,画面要是静止的,直到我们发出Signal

因为这项其实在我们有 runner button 的部分的当下就已经完成了,所以我先把它划掉了。

  • 镜头移动,上带到下

今天预计要实作得这项外,我们会根据原始影片再加上一项:

  • 当球与颜色钉靠近一定距离,减慢时间并聚光在球上

为了最後的效果,笔者其实加了比上面列的东西更多,但不会刻意去提,毕竟主角是 Matter.js ,只会在有和 Matter.js 相关的功能上着墨多些,若对实作效果,可以另外留言询问。

那麽事不宜迟,我们直接开始今天的部分。

今天的Demo
今天的Demo原始码
https://ithelp.ithome.com.tw/upload/images/20210930/20142057i4kHE7FJaM.png

今天 Demo 的原始码有点进去的读者应该会发现我拆成资料夹了,毕竟整个档案内容越来越多,把所有 js 挤进去会让後续整理或修改麻烦得多。目前笔者是 By feature 做分档,重点的范例码还是会复制出来,大家不用担心。

首先我们看到视角控制的部分,逻辑上是要跟着镜头走,所以要做在 frameUpdate 的函式里。

function frameUpdated()
{
    var cameraSpeedByFrame = 1.5 * isZoomIn? 0.3 : 1;
    Bounds.translate(render.bounds, {x:0,y:1.5});
    checkClosedToAnyColorBlock(blockList, mainBall);
}

这里面的 cameraSpeedByFrame 就是镜头每个 frame 移动的 y 座标距离,也就是移动速度,isZoomIn 会是我们稍後提到判断是否目前是球靠近颜色钉、时间减缓流逝的状态。当时间减缓流逝的时候,镜头的移动距离也要乘上对应的倍率,才不会发生镜头过於迅速的往下的问题。

这里我们用的是 Day13 提到的 Bounds 模组里的 translate,来以增量平移 bounds 的座标,我们平移的对象就是我们的 render.bounds ,也就是直接影响我们画面显示的 bounds。

到这行为止,其实每秒就已经会让镜头垂直向下了,可以注意到我们 x 不要变动,这是刻意的,在原本的需求里面镜头就只会垂直移动。

下一行的 checkClosedToAnyColorBlock 其实就是来判断是否进入靠近距离的部分,距离靠近,就要触发减缓时间流逝与聚光灯效果。

function checkClosedToAnyColorBlock(blockList,mainBall)
{
    for(var i in blockList)
    {
        if(blockList[i])
        {
            var label = blockList[i].label;
            if(label == "GoldBlock" || label == "RainbowBlock")
            {
                if(getDistanceWithPoints({x:blockList[i].position .x, y:blockList[i].position .y},{x:mainBall.position .x,y:mainBall.position .y}) < zoomInDistance)
                {
                    triggerZoomIn();
                    isZoomIn = true;
                    return;
                }
            }
        }
    }
    isZoomIn = false;
    triggerZoomOut();
}

这边是我们判断距离的逻辑,会针对现存所有钉子做距离判断,只要球和任一根颜色钉距离接近到定义距离内的时候,就会触发 ZoomIn,并改变当下 isZoomIn状态。如果没有靠近任何一颗颜色钉,则解除 ZoomIn 状态,回到正常状态。

function triggerZoomIn()
{
    engine.timing.timeScale = 0.3;
    var mainBallX = mainBall.position.x;
    var mainBallY = mainBall.position.y - render.bounds.min.y;
    var backgroundString = "radial-gradient(10px 10px at " + mainBallX + "px " + mainBallY + "px, transparent 0, transparent 60px, rgba(0, 0, 0, 0.5) 65px)";
    var style = document.getElementById("overlayDiv").style;
    style.background = backgroundString;
    style.display = "block";
}

function triggerZoomOut()
{
    engine.timing.timeScale = 1;
    var style = document.getElementById("overlayDiv").style;
    style.display = "none";
}

这两段就是处理缓慢时间流逝或复原时间流逝的部分,可以看到如果要缓慢的话我们会把时间流逝速度变成 0.3 倍,也就是我们上面同步对镜头做缓慢的倍率。要复原的话,就是把镜头速度变回 1 倍,时间流逝就会正常。

这边稍稍提一下聚光灯的实作,是用一个 div 盖在 canvas 上,透过 position : absolute 做到相叠,且插入 div 顺序要在插入 canvas 之後。至於聚光灯的 css,相信 google 就会有各种实作了。最後就是把聚光灯的位置透过变数组成字串,在安到上层的 div style上,就能达成聚光在球上的效果。

要注意的是聚光灯的位置要扣掉镜头的 Y 轴向位移,否则会发现怎麽镜头一直在球的下方,可以看到我们 mainBallY 有减去 render.bounds.min.y ,也就是我们目前 render.bounds 的上方 y 边界座标。

到这里为止,除了一些美术,我们已经完全还原了当初我们的目标,也就是那个影片的效果。其实这个实作也算告一段落。但在影片中,球的落下是不可控的,我们後面有段加码,也就是剩下的最後一个需求:让滑鼠/点击行为能和画面互动,让我们来控制/影响球的落下。

明天,让我们来了解一下 Matter.js 里面滑鼠怎麽和物体产生互动。


<<:  Flutter基础介绍与实作-Day16 Onboarding、Login、Sign Up范例实作(3)

>>:  [Day 15] ML 实验管理 — 翻开覆盖的陷阱卡~ 记帐小本本!

Day 23 CSS3 < 目标选择器>

CSS3新增的目标选择器类型: 1.属性选择器 属性选择器可以根据元素特定属性来选择元素,这样就可以...

网路方面被问到的案例整理 - 你也可以成为网路高手

接到 User 说网路不通 , 你可以请 User 先看一下主机後方网路线是否灯号有亮或重新插拔一下...

Vue3 ( JsES6、this、指令、OptionAPI ) -1

1.v-mould写入、渲染 (1) (2) (3) (4) (5) (6) 重点: 1.阵列 与 ...

【Day22】Git 版本控制 - 修改 commit 纪录:rebase

commit 版本的时候可以写下一些讯息,以便他人或未来自己查看的时候可以快速理解。但是,有时候写程...

Day2 介绍网页的基础架构

要开始写网页之前,首先要了解到网页是如何形成的,其实网页形成非常的简单,透过网页浏览器(google...