目录

PixiJS游戏开发170行代码实现谷歌小恐龙

PixiJS游戏开发:170行代码实现谷歌小恐龙

介绍

谷歌小恐龙是一款从谷歌浏览器移植而来的小游戏:

游戏介绍: 谷歌小恐龙是一款休闲益智类的跑酷游戏,无论是联网还是断网都能玩。在游戏中,玩家需要控制小恐龙跳跃来躲避各种障碍,如仙人掌、小鸟等,同时尽可能获得更高的分数。

游戏特色:

玩法简单易懂,容易上手。

游戏界面设计简约,可以带来轻松的游戏体验。

游戏操作:

在浏览器(如Chrome)中,可以通过输入“chrome://dino”来打开游戏。

点击空格可以使小恐龙跳跃,向下箭头按键可以使小恐龙以蹲姿躲避障碍。

使用技术

Pixi.js是一个强大的2D WebGL渲染引擎,主要用于创建交互式的图形和动画。

  1. 起源与发展

    • Pixi.js由Goodboy Digital开发,并于2013年首次发布。起初,它作为内部工具用于解决市场上复杂且对移动设备性能和资源要求较高的2D渲染引擎的问题。
    • 随着时间的推移,Pixi.js被开源并发布到GitHub上,迅速受到开发者的喜爱,因其出色的性能和易用性。
  2. 技术特点

    • 高性能 :Pixi.js通过利用WebGL技术实现硬件加速渲染,可在多种平台上流畅运行。它还使用批处理技术,可以一次性绘制大量精灵,提高了渲染效率。
    • 跨平台 :Pixi.js支持多个平台,包括Web、移动和桌面应用程序,以及广泛的浏览器,如Chrome、Firefox、Safari、Edge等。
    • 易于使用 :Pixi.js提供简单且直观的API,易于上手和学习。它还提供了一套丰富的功能,如精灵(Sprite)、粒子系统(Particle System)、滤镜(Filter)等。
    • 可扩展性 :Pixi.js具有良好的可扩展性,支持插件和自定义扩展。开发者可以根据自己的需求来定制和扩展Pixi.js的功能,并与其他库或框架集成。
    • 跨浏览器兼容性 :Pixi.js在多种浏览器中都能良好地运行,提供了一致的API体验。
  3. 应用场景

    • 游戏开发 :Pixi.js提供了强大的游戏开发功能,包括精灵动画、物理引擎集成、碰撞检测等,支持各种类型的游戏开发,如休闲游戏、角色扮演游戏等。
    • 数据可视化 :Pixi.js可用于创建各种数据可视化图表和图形,将复杂数据以直观的方式展示给用户。
    • 广告创意 :Pixi.js的灵活性和高性能使其适合用于创建吸引人的动态广告。
    • UI组件 :Pixi.js还可以用于构建高性能的图形界面,如滑块、按钮等。

创建小恐龙

//初始化Pixi容器,设置背景为白色,抗锯齿开启
const app = new PIXI.Application({ width: window.innerWidth, height: window.innerHeight, background: 'white', antialias: true });
//添加到网页中
    document.body.appendChild(app.view)
//创建一个容器,存放游戏内容
    const container = new PIXI.Container()
//添加到舞台
    app.stage.addChild(container);
//加载素材纹理
    const baseTexture = PIXI.BaseTexture.from('./googlepino.png')
//裁剪出小恐龙的纹理
    const pinoTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(10, 0, 80, 50))
//创建精灵
    const pino = new PIXI.Sprite(pinoTexture)
//设置位置
    pino.position.set(10, window.innerHeight - 85)
//添加到容器中
    container.addChild(pino)
//设置小恐龙奔跑的纹理数组
    const runTexture = []
//切割出每一帧
    for (let i = 0; i < 2; i++) {
        runTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(935 + (i * 45), 0, 45, 50))
        )
    }
//设置小恐龙蹲下的纹理数组
    const squatTexture = []
//切割出每一帧
    for (let i = 0; i < 2; i++) {
        squatTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(1110 + (i * 60), 20, 60, 30))
        )
    }
//创建动画精灵
    const runAnimation = new PIXI.AnimatedSprite(runTexture)
//设置动画播放速度
    runAnimation.animationSpeed = 0.1
//设置隐藏
    runAnimation.visible = false
//设置位置
    runAnimation.position.set(50, window.innerHeight - 85)
//添加到容器
    container.addChild(runAnimation)

https://i-blog.csdnimg.cn/blog_migrate/8ae9f24006c3778ca87ed26bf975c779.png

该部分我们创建了小恐龙和他的奔跑动作,蹲下的动作

创建仙人掌

//裁剪出仙人掌的纹理
const cactusTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(356, 0, 25, 55))
//创建精灵
const cactus = new PIXI.Sprite(cactusTexture)
//设置位置,默认在屏幕中央
cactus.position.set(window.innerWidth / 2, window.innerHeight - 85)
//添加到容器中
container.addChild(cactus)

https://i-blog.csdnimg.cn/blog_migrate/2b84c631ab5a68b015aad01d24b45e9f.png

该部分我们创建了一个仙人掌精灵

创建飞鸟

//创建飞鸟的纹理数组
    const birdFlyTexture = []
//裁剪出每一个动作
    for (let i = 0; i < 2; i++) {
        birdFlyTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(135 + (i * 45), 0, 45, 30))
        )
    }
//创建动画精灵
    const flyAnimation = new PIXI.AnimatedSprite(birdFlyTexture)
//设置动画播放速度
    flyAnimation.animationSpeed = 0.1
//默认隐藏
    flyAnimation.visible = false
//设置位置,x坐标为随机
    flyAnimation.position.set(window.innerWidth + Math.floor(Math.random() * (2000 - 1000 + 1)) + 1000, window.innerHeight - 100)
//添加到容器
    container.addChild(flyAnimation)

https://i-blog.csdnimg.cn/blog_migrate/cc3e8c54529a820547d7748dc5e0f77a.png

该部分我们创建了一个飞鸟精灵

创建地面

//裁剪出地面的纹理    
    const groundTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(10, 50, 1200, 20))
//创建地面精灵
    const ground = new PIXI.TilingSprite(groundTexture)
//设置宽度和高度
    ground.width = window.innerWidth
    ground.height = 20
//设置位置
    ground.position.set(0, window.innerHeight - 50)
//添加到容器中
    container.addChild(ground)

该部分我们创建了一个地面精灵

https://i-blog.csdnimg.cn/blog_migrate/c5c1c04ee4af7f5a098bc49ba50ffb36.png

添加文字提示

//初始化分数为0
let score = 0
//创建文字,设置字体为30,居中对齐
    const text = new PIXI.Text('开始游戏', {
        fontSize: 30,
        align: 'center'
    })
//设置到屏幕正中央
    text.anchor.set(0.5)
    text.position.set(window.innerWidth / 2, window.innerHeight / 2)
//添加到容器中
    container.addChild(text)
//添加事件,点击的时候开始游戏
    text.interactive = true
    text.on('click', () => {
        playGame()
    })
//创建分数提示文字
    const tip = new PIXI.Text('当前得分为' + score, {
        fontSize: 30,
        align: 'center'
    })
    tip.position.set(window.innerWidth / 2, window.innerHeight / 2)
    tip.anchor.set(0.5)
//默认不显示
    tip.visible = false
//添加到容器中
    container.addChild(tip)

该部分我们创建了游戏的文字提示,当我们点击开始游戏时,游戏开始

    function playGame() {
        text.visible = false
        tip.visible = true
        isPlayGame = true
        pino.visible = false
        runAnimation.visible = true
        runAnimation.play()
    }

    function gameOver() {
        isPlayGame = false
        tip.text = '游戏结束,得分为' + score + ',点击重新开始'

        tip.interactive = true
        tip.on('click', () => {
            location.reload()
        })
    }

游戏开始和游戏结束的代码,当游戏开始时我们隐藏开始游戏文字,显示分数提示文字,隐藏小恐龙精灵,显示奔跑动画精灵

当游戏结束时更改游戏提示信息,然后点击时刷新页面,重新开始游戏

游戏开始

    let isPlayGame = false
    let speed = 10
    let isJump = false
    let up = true

设置游戏初始值

    app.ticker.add(delta => {
        if (isPlayGame) {
//地面精灵x每次减10,达到地面滚动的效果
            ground.tilePosition.x -= 10
//仙人掌滚动
            cactus.x -= speed
//判断仙人掌是否已超出屏幕左侧,分数加1
            if (cactus.x <= 0) {
//将仙人掌在移动到屏幕右侧
                cactus.x = window.innerWidth
                score += 1
            }
//判断飞鸟是否出现
            if (flyAnimation.visible) {
//移动飞鸟
                flyAnimation.x -= speed
//判断飞鸟是否已超出屏幕左侧,分数加1
                if (flyAnimation.x <= 0) {
//将飞鸟在移动到屏幕右侧
                    flyAnimation.x = window.innerWidth + Math.floor(Math.random() * (2000 - 1000 + 1)) + 1000
                    score += 1
                }
            }
//改变分数信息
            tip.text = '当前得分为' + score
//当分数为5时,显示飞鸟
            if (score == 0) {
                flyAnimation.visible = true
                flyAnimation.play()
            } else if (score == 10) {
//分数为10时,加快速度
                speed = 12
            } else if (score == 20) {
                speed = 15
            }
//判断恐龙是否跳跃
            if (isJump) {
//如果向上的话,y减10,否则就加10
                if (up) {
                    runAnimation.y -= 10
                } else {
                    runAnimation.y += 10
                }
//判断恐龙是否跳到最高处,然后向下
                if (runAnimation.y <= ground.y - 150) {
                    up = false
                }
//判断恐龙是否回到原点,跳跃结束
                if (runAnimation.y > ground.y - 40) {
                    isJump = false
                }
            }
//判断恐龙和仙人掌和飞鸟时候碰撞到一起,游戏结束
            if (b.hit(runAnimation, cactus) || b.hit(runAnimation, flyAnimation)) {
                gameOver()
            }
        }
    })

该部分我们完成了游戏运行,恐龙跳跃,分数判断,如果恐龙与障碍物碰撞到一起了游戏结束,

由于PixiJS没有内置的碰撞检测,所以我们使用第三方库Bump.js来判断碰撞检测

创建Bump对象,将PIXI传入

 let b = new Bump(PIXI)

操作恐龙

//保存恐龙的y坐标初始值    
    let y = runAnimation.y
//添加键盘事件
    window.addEventListener('keydown', e => {
//按下空格时跳跃
        if (e.code == 'Space') {
            e.preventDefault()
            isJump = true
            up = true
        } else if (e.key == 'ArrowDown') {
//按下下箭头时恐龙切换蹲下状态
            e.preventDefault()
            runAnimation.textures = squatTexture
            runAnimation.y = y + 20
            runAnimation.play()
        }
    })
//添加键盘事件,松开下箭头后恐龙切回奔跑状态
    window.addEventListener('keyup', e => {
        if (e.key == 'ArrowDown') {
            e.preventDefault()
            runAnimation.textures = runTexture
            runAnimation.y = y
            runAnimation.play()
        }
    })

该部分我们完成了恐龙的跳跃和蹲下操作

效果

游戏最终效果如下,喜欢请点个关注,谢谢

https://i-blog.csdnimg.cn/blog_migrate/3477eee33441ab1508213c7f0c824be3.gif

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://www.kkkk1000.com/js/bump.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/pixi.js@7.2/dist/pixi.min.js"></script>
</head>

<body></body>

<script>
    const app = new PIXI.Application({ width: window.innerWidth, height: window.innerHeight, background: 'white', antialias: true });
    document.body.appendChild(app.view)

    const container = new PIXI.Container()
    app.stage.addChild(container);

    const baseTexture = PIXI.BaseTexture.from('./googlepino.png')

    const pinoTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(10, 0, 80, 50))
    const pino = new PIXI.Sprite(pinoTexture)
    pino.position.set(10, window.innerHeight - 85)
    container.addChild(pino)

    const runTexture = []
    for (let i = 0; i < 2; i++) {
        runTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(935 + (i * 45), 0, 45, 50))
        )
    }
    const squatTexture = []
    for (let i = 0; i < 2; i++) {
        squatTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(1110 + (i * 60), 20, 60, 30))
        )
    }
    const runAnimation = new PIXI.AnimatedSprite(runTexture)
    runAnimation.animationSpeed = 0.1
    runAnimation.visible = false
    runAnimation.position.set(50, window.innerHeight - 85)
    container.addChild(runAnimation)

    const birdFlyTexture = []
    for (let i = 0; i < 2; i++) {
        birdFlyTexture.push(
            new PIXI.Texture(baseTexture, new PIXI.Rectangle(135 + (i * 45), 0, 45, 30))
        )
    }
    const flyAnimation = new PIXI.AnimatedSprite(birdFlyTexture)
    flyAnimation.animationSpeed = 0.1
    flyAnimation.visible = false
    flyAnimation.position.set(window.innerWidth + Math.floor(Math.random() * (2000 - 1000 + 1)) + 1000, window.innerHeight - 100)
    container.addChild(flyAnimation)

    const groundTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(10, 50, 1200, 20))
    const ground = new PIXI.TilingSprite(groundTexture)
    ground.width = window.innerWidth
    ground.height = 20
    ground.position.set(0, window.innerHeight - 50)
    container.addChild(ground)

    const cactusTexture = new PIXI.Texture(baseTexture, new PIXI.Rectangle(356, 0, 25, 55))
    const cactus = new PIXI.Sprite(cactusTexture)
    cactus.position.set(window.innerWidth / 2, window.innerHeight - 85)
    container.addChild(cactus)

    let score = 0

    const text = new PIXI.Text('开始游戏', {
        fontSize: 30,
        align: 'center'
    })
    text.anchor.set(0.5)
    text.position.set(window.innerWidth / 2, window.innerHeight / 2)
    container.addChild(text)

    text.interactive = true
    text.on('click', () => {
        playGame()
    })
    const tip = new PIXI.Text('当前得分为' + score, {
        fontSize: 30,
        align: 'center'
    })
    tip.position.set(window.innerWidth / 2, window.innerHeight / 2)
    tip.anchor.set(0.5)
    tip.visible = false
    container.addChild(tip)

    let isPlayGame = false
    let speed = 10
    let isJump = false
    let up = true

    function playGame() {
        text.visible = false
        tip.visible = true
        isPlayGame = true
        pino.visible = false
        runAnimation.visible = true
        runAnimation.play()
    }

    function gameOver() {
        isPlayGame = false
        tip.text = '游戏结束,得分为' + score + ',点击重新开始'

        tip.interactive = true
        tip.on('click', () => {
            location.reload()
        })
    }

    let b = new Bump(PIXI)

    app.ticker.add(delta => {
        if (isPlayGame) {
            ground.tilePosition.x -= 10
            cactus.x -= speed
            if (cactus.x <= 0) {
                cactus.x = window.innerWidth
                score += 1
            }
            if (flyAnimation.visible) {
                flyAnimation.x -= speed
                if (flyAnimation.x <= 0) {
                    flyAnimation.x = window.innerWidth + Math.floor(Math.random() * (2000 - 1000 + 1)) + 1000
                    score += 1
                }
            }
            tip.text = '当前得分为' + score
            if (score == 5) {
                flyAnimation.visible = true
                flyAnimation.play()
            } else if (score == 10) {
                speed = 12
            } else if (score == 20) {
                speed = 15
            }
            if (isJump) {
                if (up) {
                    runAnimation.y -= 10
                } else {
                    runAnimation.y += 10
                }
                if (runAnimation.y <= ground.y - 150) {
                    up = false
                }
                if (runAnimation.y > ground.y - 40) {
                    isJump = false
                }
            }
            if (b.hit(runAnimation, cactus) || b.hit(runAnimation, flyAnimation)) {
                gameOver()
            }
        }
    })

    let y = runAnimation.y
    window.addEventListener('keydown', e => {
        if (e.code == 'Space') {
            e.preventDefault()
            isJump = true
            up = true
        } else if (e.key == 'ArrowDown') {
            e.preventDefault()
            runAnimation.textures = squatTexture
            runAnimation.y = y + 20
            runAnimation.play()
        }
    })

    window.addEventListener('keyup', e => {
        if (e.key == 'ArrowDown') {
            e.preventDefault()
            runAnimation.textures = runTexture
            runAnimation.y = y
            runAnimation.play()
        }
    })
</script>

</html>

图片素材:

https://i-blog.csdnimg.cn/blog_migrate/02a25bc73cdfe8ac16bbb5150b7d0d63.png