前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Three.js 手写跳一跳小游戏(下)

Three.js 手写跳一跳小游戏(下)

作者头像
神说要有光zxg
发布于 2023-08-28 11:47:59
发布于 2023-08-28 11:47:59
53801
代码可运行
举报
运行总次数:1
代码可运行

上篇文章我们实现了跳一跳的雏形:

这篇文章我们继续做。

现在是只有 7 个方块,而实际上方块应该是动态生成的。

比如最开始只有两个,跳到一个方块后,自动出现下一个,并且向左还是向右是随机的。

我们改一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const currentCubePos = {x: 0, y: 0, z: -100};
let direction = 'right';

记录下当前的位置和方向。

点击的时候判断下,如果是向右就改变 z 的位置,否则改变 x 的位置。

然后生成下一个方块,也是随机向左或者向右。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
document.body.addEventListener('click', () => {
    if(direction === 'right') {
        targetCameraPos.z = camera.position.z - 100
        targetCameraFocus.z -= 100
        targetPlayerPos.z -=100;
    } else {
        targetCameraPos.x = camera.position.x - 100
        targetCameraFocus.x -= 100
        targetPlayerPos.x -=100;        
    }

    speed = 5;

    const num = Math.random();
    if(num > 0.5) {
        currentCubePos.z -= 100;
        direction = 'right';
    } else {
        currentCubePos.x -= 100;
        direction = 'left';
    }
    createCube(currentCubePos.x, currentCubePos.z);
});

现在就变成了这样:

每次跳的时候,在随机方向生成一个新方块。

现在是 click 的时候就跳,实际上应该是 mousedown 的时候蓄力,mouseup 的时候跳。

我们先把 click 事件注释掉:

move 玩家和摄像机的逻辑也注释掉:

然后添加 mousedown 和 mousedup 事件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let pressed = false;
let speed = 0;
let speedY = 0;

document.body.addEventListener('mousedown', () => {
    speed = 0;
    speedY = 0;
    pressed = true;
});
document.body.addEventListener('mouseup', () => {
    pressed = false;
    console.log(speed);
})

mousedown 的时候把速度设为 0,然后标记 pressed 为 true。

在 mouseup 的时候标记 pressed 为 false,并且打印速度。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

function speedUp() {
    if(pressed) {
        speed += 0.1;
        speedY += 0.1;
    }
}

function render() {
    // moveCamera();
    // movePlayer();
    speedUp();

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

然后按下的时候每帧都增加速度。

按下一段时间再松开,这时会打印现在的速度,这就是蓄力。

为什么有两个速度呢?

因为蓄力之后有两个方向的移动,一个是 x 轴或者 z 轴,一个是 y 轴。

然后在 movePlayer 的时候分别用 speed 和 speedY 计算现在的位置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function movePlayer() {
    player.position.y += speedY;

    if(player.position.y < 17.5) {
        player.position.y = 17.5;
    } else {
        if(direction === 'right') {
            player.position.z -= speed;
        } else {
            player.position.x -= speed;
        }
    }
    speedY -= 0.3;
}

function speedUp() {
    if(pressed) {
        speed += 0.1;
        speedY += 0.1;
    }
}

function render() {
    if(!pressed) {
        // moveCamera();
        movePlayer();
    }
    speedUp();

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

现在只有非 pressed 状态才会移动,按下的时候不移动。

移动的截止条件就是 y 轴到了 17.5,也就是平台高度,这个时候就要判断是否跳到了下一个平台。

试一下:

没啥问题,蓄力不同的时间,跳的远近不同。

然后加上移动 camera 的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function moveCamera() {
    if(player.position.y > 17.5) {
        if(direction === 'right') {
            camera.position.z -= speed;
            cameraFocus.z -= speed;
        } else {
            camera.position.x -= speed;
            cameraFocus.x -= speed;
        }
        
        camera.lookAt(cameraFocus.x, cameraFocus.y, cameraFocus.z);
    }
}

function render() {
    if(!pressed) {
        moveCamera();
        movePlayer();
    }
    speedUp();

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

结束条件同样是玩家跳到了平台高度。

然后相机的位置和焦点的 x 或者 z 轴同步移动。

这样玩家就始终在屏幕中央了。

然后每跳一次生成下一个方块:

当玩家的 y 到了 17.5 的时候,生成下一个方块。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(player.position.y < 17.5) {
    player.position.y = 17.5;

    if(stopped === false){
        const distance = Math.floor(50 + Math.random() * 100);

        const num = Math.random();
        if(num > 0.5) {
            currentCubePos.z -= distance;
            direction = 'right';
        } else {
            currentCubePos.x -= distance;
            direction = 'left';
        }
        createCube(currentCubePos.x, currentCubePos.z);
    }

方向随机,距离是 50 到 150 的随机值。

我们只保留一个方块,把之前创建的第二个方块去掉:

这样从第一个方块开始就是随机方向和距离的:

然后判断下是否跳成功了:

判断逻辑也很简单,就是 x 或者 z 是否是在下个平台的范围内。

判断下 player.position.x 是否在方块中心点 currentCubePos.x 加减 15 的范围内。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function playerInCube() {
    if(direction === 'right') {
        if( player.position.z < currentCubePos.z + 15 && player.position.z > currentCubePos.z - 15) {
            return true;
        }
    } else if(direction === 'left') {
        if( player.position.x < currentCubePos.x + 15 && player.position.x > currentCubePos.x - 15) {
            return true;
        }
    }
    return false;
}

跳完之后,如果跳到了下一个方块,就累加分数,否则就提示失败,并打印当前分数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(playerInCube()) {
    score++;
    console.log(score);
} else {
    console.log('失败, 当前分数' + score);
}

分数有点问题,没跳的时候就已经加一了。

这是以为最开始也会触发一次判断,在方块上,所以分数加一了。

我们把初始分数设置为 -1 就好了。

这样就对了:

然后我们加个 html 标签来展示当前分数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div id="score">0</div>

添加样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#score {
    font-size: 50px;
    color: #fff;
    position: fixed;
    top: 10%;
    left: 10%;
}

然后在分数更新时更新这个标签的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
document.getElementById('score').innerHTML = score;

测试下:

没啥问题。

然后加上失败时的提示的 html:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div id="fail">您的分数为<span id="score2">0</span></div>

加上样式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#fail {
    position: fixed;
    background: rgba(0,0,0,0.5);
    left: 0;
    right: 0;
    width: 100%;
    height: 100%;
    font-size: 70px;
    color: #fff;
    padding-left: 40%;
    padding-top: 200px;
    /* display: none; */
}

最开始 display 为 none,失败的时候把它显示出来:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
document.getElementById('fail').style.display = 'block';
document.getElementById('score2').innerHTML = score;

不过现在有个问题,失败了还是创建下一个方块了。

我们改一下:

把创建方块的逻辑移动到这里:

测试下:

然后我们处理下细节:

把 axesHelper 也就是坐标轴去掉:

然后蓄力的时候加个缩短的效果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function speedUp() {
    if(pressed) {
        speed += 0.1;
        speedY += 0.1;

        if(player.scale.y> 0) {
            player.scale.y -= 0.001;
        }
        player.position.y -= 15 - 15 * player.scale.y

        if(player.position.y < 10) {
            player.position.y = 10;
        }
    } else {
        player.scale.y = 1;
    }
}

改变高度直接修改 scale 就行,每次渲染,scale.y 减 0.001,但是不能小于 0。

并且还要改变 position.y,让它一直贴着方块,本来 player 高度是 15,减去缩小后的高度,少了多少, position.y 就减多少。

当然,如果 position.y 低于方块了,就设置到方块的上面,也就是 10。

如果不是在按下的状态,就恢复 scale.y 为 1

再就是黑色和背景颜色太接近了,我们换个颜色:

这样,我们的跳一跳小游戏就完成了。

全部代码如下,一共 200 多行代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跳一跳</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
    <script src="https://www.unpkg.com/three@0.154.0/build/three.js"></script>
    <style>
        #score {
            font-size: 50px;
            color: #fff;
            position: fixed;
            top: 10%;
            left: 10%;
        }
        #fail {
            position: fixed;
            background: rgba(0,0,0,0.5);
            left: 0;
            right: 0;
            width: 100%;
            height: 100%;
            font-size: 70px;
            color: #fff;
            padding-left: 40%;
            padding-top: 200px;
            display: none;
        }
    </style>
</head>
<body>
    <div id="score">0</div>
    <div id="fail">您的分数为 <span id="score2">0</span></div>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);

const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });

renderer.setSize(width, height);
renderer.setClearColor(0x333333);

camera.position.set(100, 100, 100);
camera.lookAt(scene.position);

const directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set(40, 100, 60);

scene.add( directionalLight );

document.body.appendChild(renderer.domElement)

// const axesHelper = new THREE.AxesHelper( 1000 );
// axesHelper.position.set(0,0,0);
// scene.add( axesHelper );

const targetCameraPos = { x: 100, y: 100, z: 100 };

const cameraFocus = { x: 0, y: 0, z: 0 };
const targetCameraFocus = { x: 0, y: 0, z: 0 };

const playerPos = { x: 0, y: 17.5, z: 0};
const targetPlayerPos = { x: 0, y: 17.5, z: 0};

let player;
let speed = 0;
let speedY = 0;

const currentCubePos = {x: 0, y: 0, z: 0};
let direction = 'right';

let pressed = false;

function createCube(x, z) {
    const geometry = new THREE.BoxGeometry( 30, 20, 30 );
    const material = new THREE.MeshPhongMaterial( {color: 0xffffff} );
    const cube = new THREE.Mesh( geometry, material ); 
    cube.position.x = x;
    cube.position.z = z;
    scene.add( cube );
}

function create() {


    function createPlayer() {
        const geometry = new THREE.BoxGeometry( 5, 15, 5 );
        const material = new THREE.MeshPhongMaterial( {color: 0xffff00} );
        const player = new THREE.Mesh( geometry, material ); 
        player.position.x = 0;
        player.position.y = 17.5;
        player.position.z = 0;
        scene.add( player )
        return player;
    }

    player = createPlayer();

    createCube(0, 0);

    document.body.addEventListener('mousedown', () => {
        speed = 0;
        speedY = 0;
        pressed = true;
    });

    document.body.addEventListener('mouseup', () => {
        pressed = false;
    })
}

function moveCamera() {
    if(player.position.y > 17.5) {
        if(direction === 'right') {
            camera.position.z -= speed;
            cameraFocus.z -= speed;
        } else {
            camera.position.x -= speed;
            cameraFocus.x -= speed;
        }
        
        camera.lookAt(cameraFocus.x, cameraFocus.y, cameraFocus.z);
    }
}

function playerInCube() {
    if(direction === 'right') {
        if( player.position.z < currentCubePos.z + 15 && player.position.z > currentCubePos.z - 15) {
            return true;
        }
    } else if(direction === 'left') {
        if( player.position.x < currentCubePos.x + 15 && player.position.x > currentCubePos.x - 15) {
            return true;
        }
    }
    return false;
}

let score = -1;
let stopped = true;
function movePlayer() {
    player.position.y += speedY;

    if(player.position.y < 17.5) {
        player.position.y = 17.5;

        if(stopped === false){

            if(playerInCube()) {
                score++;
                document.getElementById('score').innerHTML = score;

                const distance = Math.floor(50 + Math.random() * 100);

                const num = Math.random();
                if(num > 0.5) {
                    currentCubePos.z -= distance;
                    direction = 'right';
                } else {
                    currentCubePos.x -= distance;
                    direction = 'left';
                }
                createCube(currentCubePos.x, currentCubePos.z);
            } else {
                document.getElementById('fail').style.display = 'block';
                document.getElementById('score2').innerHTML = score;
            }

        }

        stopped = true;
    } else {
        stopped = false;
        if(direction === 'right') {
            player.position.z -= speed;
        } else {
            player.position.x -= speed;
        }
    }
    speedY -= 0.3;
}

function speedUp() {
    if(pressed) {
        speed += 0.1;
        speedY += 0.1;

        if(player.scale.y> 0) {
            player.scale.y -= 0.001;
        }
        player.position.y -= 15 - 15 * player.scale.y

        if(player.position.y < 10) {
            player.position.y = 10;
        }
    } else {
        player.scale.y = 1;
    }
}

function render() {
    if(!pressed) {
        moveCamera();
        movePlayer();
    }
    speedUp();

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

create();
render();
</script>
</body>
</html>

总结

上篇文章我们写了基础代码,完成了方块的创建,跳的过程以及摄像机跟拍。

这篇文章我们实现了蓄力,落点判断,以及分数计算。

首先,我们不再提前创建方块,改成随机方向,随机距离创建。

监听 mousedown 和 mouseup 来实现蓄力,mousedown 的时候不断增加速度,mouseup 的时候移动玩家和摄像机。

speedY 会逐渐变小,所以会有下落的过程,落到方块高度的时候,判断下是否在下个方块内,如果是,就累加分数,否则提示游戏结束,输出分数。

我们还做了蓄力的时候改变方块高度,通过调整 scale.y 和 position.y 来实现的。

这样,我们就通过 three.js 实现了跳一跳小游戏。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神光的编程秘籍 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Three.js 手写跳一跳小游戏(上)
玩家从一个方块跳到下一个方块,如果没跳过去就算失败,跳过去了就会再出现下一个方块。
神说要有光zxg
2023/08/28
7120
Three.js 手写跳一跳小游戏(上)
『Three.js』几个简单的入门动画(新手篇)
如果对上面的代码还不太理解,可以先看看 《『Three.js』起飞!》 ,坐标轴方面可以看看 《『Three.js』辅助坐标轴》 。
德育处主任
2022/09/09
2.7K0
『Three.js』几个简单的入门动画(新手篇)
Unity开发微信跳一跳小游戏
首先建一个3D空项目,要记住项目名称不能有中文、空格、特殊字符(别问我是怎么知道的T_T)。
叶茂林
2023/07/13
3770
Unity开发微信跳一跳小游戏
『Three.js』辅助坐标轴
在日常开发和学习中,坐标轴能粗略的帮我们定位元素位置和关系。所以我使用 Three.js 学习和开发时基本都会打开坐标轴。
德育处主任
2022/09/23
2.5K0
『Three.js』辅助坐标轴
不到30行代码实现一个酷炫H5全景
前言:本文将围绕:了解什么是全景 --> 怎么构成全景 --> 全景交互原理来进行讲解,手把手教你从零基础实现一个酷炫的Web全景,并讲解其中的原理。小白也能学习,建议收藏学习,有任何疑问,请在评论区讨论,笔者经常查看并回复。
coder_koala
2021/07/08
2.5K0
不到30行代码实现一个酷炫H5全景
DeepSeek重磅更新!让它写个贪吃蛇试试
这次R1-0528更新主要在语义精准性,复杂逻辑推理,长文本处理稳定性方面做了优化。
大风写全栈
2025/06/09
1110
DeepSeek重磅更新!让它写个贪吃蛇试试
Three.js教程(4):相机
相机这部分的内容并不是很多,Three.js主要支持两种相机,一种是PerspectiveCamera即透视投影摄像机,另一种是OrthographicCamera即正交投影摄像机。两种相机都是继承自Camera对象,Camera对象又是继承自Object3D。
kai666666
2020/10/17
2.4K0
Three.js基础
通过scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });来强制设置场景中对象的材质,极端情况可以做性能优化。
小刀c
2024/04/03
3150
Three.js基础
Three.js系列: 游戏中的第一/三人称视角
大家好,我是秋风,在上一篇中说到了Three.js 系列的目标以及宝可梦游戏,那么今天就来通过Three.js 来谈谈关于游戏中的视角跟随问题。
秋风的笔记
2022/03/29
3.3K1
Three.js系列:   游戏中的第一/三人称视角
学习Three.js
window resize 需要设置camera的aspect 属性,设置renderer的尺寸
小刀c
2024/04/03
2320
【愚公系列】2023年08月 Three.js专题-粒子特效案例
粒子特效是一种视觉效果,可以模拟出许多粒子在空间中的运动和变化,形成各种美丽的图案和动态效果。常见的粒子特效包括烟雾、火焰、水流、星空、气泡等,可以在电影、电视、游戏等领域中得到广泛应用。实现粒子特效,需要使用计算机图形学技术,如粒子系统、计算流体力学等。
愚公搬代码
2025/05/28
970
【愚公系列】2023年08月 Three.js专题-粒子特效案例
用 Three.js 和 AudioContext 实现音乐频谱的 3D 可视化
最近听了一首很好听的歌《一路生花》,于是就想用 Three.js 做个音乐频谱的可视化,最终效果是这样的:
神说要有光zxg
2021/12/04
2.9K0
用 Three.js 和 AudioContext 实现音乐频谱的 3D 可视化
探究Three.js中模型移动与旋转的交互逻辑
Three.js作为一个功能强大的JavaScript 3D库,极大地简化了在网页上创建和展示3D图形的过程。它在游戏开发、产品展示、虚拟现实等众多领域都被广泛应用。通过Three.js,开发者能够轻松创建出复杂的三维场景和交互性强的3D应用,为用户带来沉浸式的体验。
Front_Yue
2025/03/16
3950
探究Three.js中模型移动与旋转的交互逻辑
Three.js入门
Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象。你可以在它的主页上看到许多精采的演示。Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D。 Three.js的核心五步就是: 1.设置three.js渲染器 2.设置摄像机camera 3.设置场景scene 4.设置光源light 5.设置物体object 1.设置three.js渲染器 三维空间里的物体映射到二维平面的
李海彬
2018/03/22
8.1K0
云图三维 | Three.js 后期处理
后置处理通常是指应用到2D图像上的某种特效或者是滤镜。在ThreeJs的场景中,我们有由很多网格(mesh)构成的场景(scene)渲染成的2D图像。一般来说,图像被直接渲染成canvas,然后在浏览器中展示,然而在结果被输出到canvas之前,我们也可以通过另外的一个render target并应用一些后置效果。这被称为Post Processing,因为它发生在主场景渲染过程之后。
玖柒的小窝
2021/11/06
3.2K0
云图三维 | Three.js 后期处理
用 Three.js 画一个哆啦A梦的时光机
在 three.js 里以向右的方向为 x 轴,向上的方向为 y 轴,向前的方向为 z 轴:
神说要有光zxg
2023/08/28
6050
用 Three.js 画一个哆啦A梦的时光机
three.js 带更新文字的旋转地球
查看旋转地球效果 主要用到几个知识点 (1)显示文字是使用了three.js 的精灵(Sprite),精灵的文字方向始终面向相机,文字是在canvas中画的,精灵的材质就是加载的带有文字的canvas function showText(){ const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); ctx.canvas.width =256; const x =0;
tianyawhl
2019/04/17
10.1K0
three.js 带更新文字的旋转地球
加载obj模型和mtl材质文件 Three.js
原文: https://threejs.org/examples/?q=obj#webgl_loader_obj_mtl 代码: <!DOCTYPE html> <html lang="en"> <
周星星9527
2021/11/15
7.2K0
加载obj模型和mtl材质文件 Three.js
Three.js 的 3D 粒子动画:群星送福
粒子是指原子、分子等组成物体的最小单位。在 2D 中,这种最小单位是像素,在 3D 中,最小单位是顶点。
神说要有光zxg
2022/03/03
4.7K0
Three.js 的 3D 粒子动画:群星送福
three.js简单实现一个3D三角函数学习理解
1.Three.js简介 Three.js是一个基于JavaScript编写的开源3D图形库,利用WebGL技术在网页上渲染3D图形。它提供了许多高级功能,如几何体、纹理、光照、阴影等,以便开发者能够快速地创建复杂且逼真的3D场景。同时,Three.js还具有很好的跨平台和跨浏览器兼容性,让用户无需安装任何插件就可以在现代浏览器上观看3D内容。
曾高飞
2025/05/19
1080
相关推荐
Three.js 手写跳一跳小游戏(上)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验