{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "title: p5.js教程07-让图动起来\n", "date: 2018-06-06 20:17:55\n", "tags: [p5.js]\n", "toc: true\n", "xiongzhang: true\n", "\n", "\n", "---\n", "\n", "\n", "\n", "> 声明: 本文由[DataScience](http://mlln.cn)发表,未经允许不得转载。 转载请注明[本文链接](http://mlln.cn)mlln.cn, 并在文后留言`转载`.\n", "\n", "本文代码运行环境:\n", "\n", "- windows10\n", "- jupyter notebook\n", "- p5.js 0.6.1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在此之前的教程中你可能已经见过动态的效果了, 不过我们在这里更详细和系统的说明下。\n", "\n", "就像翻书一样,屏幕上的动画是通过不断显示不同的图像来显示动态效果的。我们需要绘制图像,然后绘制稍微不同的图像,然后绘制另一个图像,依此类推, 让这些图像连续出现。当以足够快的速度呈现一组相似图像时,我们的大脑将这些图像转换为运动。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(下面我们注册p5魔法, 以便于在jupyter notebook中使用p5.js, 如果你不用notebook, 请忽略这部分python代码)" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "from IPython.core.magic import register_cell_magic\n", "from IPython.display import IFrame\n", "\n", "TEMPLATE = \"\"\"\n", "\n", "\n", "\n", "\n", "\n", "\n", "Notebook中显示P5.js页面\n", "\n", "\n", "
p5.js效果展示: %(name)s
\n", "\n", "\n", "\n", "\"\"\"\n", "\n", "\n", "@register_cell_magic\n", "def p5(line, cell):\n", " file_id, kws = line.split(' ')[0], line.split(' ')[1:]\n", " kwargs = {}\n", " for kw in kws:\n", " k, v = kw.split('=')\n", " kwargs[k] = v\n", " filename = f\"p5js-html/p5-07-{file_id}.html\"\n", " with open(filename, \"w\", encoding='utf8') as fp:\n", " fp.write(TEMPLATE % {\"script\": cell, \"name\": filename})\n", " if 'height' not in kwargs:\n", " kwargs['height'] = '200px'\n", " return IFrame(filename, width=\"100%\", **kwargs)\n", "\n", "del p5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 帧的概念\n", "\n", "为了创建平滑的运动,p5.js尝试以每秒60帧的速度运行draw()内的代码。帧是draw()函数的一次执行,帧速率是每秒绘制的帧数。因此,每秒绘制60帧的程序意味着程序每秒60运行60次draw()函数。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 控制运动速度和方向\n", "\n", "通常我们用物体在每帧的位移大小来表示速度。" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%p5 speed height=250\n", "// 速度是每帧1个像素\n", "let speed = 1\n", "// 方向有1 和 -1两个取值\n", "let direction = 1\n", "let x = 1\n", "function setup() { \n", " createCanvas(220, 220); \n", " \n", "}\n", "\n", "function draw() {\n", " // 更改小球的x坐标实现运动效果\n", " x += speed*direction;\n", " background(204); \n", " ellipse(x, 100, 20, 20); \n", " if(x > 220){\n", " direction = -1\n", " }else if(x < 0){\n", " direction = 1\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "更复杂的案例, 制作一个随机运动的小球。" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%p5 speed-random height=280\n", "// 速度是每帧1个像素\n", "// 用random生成一个随机数\n", "let speed_x = 1 \n", "let speed_y = 2 \n", "// 方向有1 和 -1两个取值\n", "let direction_x = 1\n", "let direction_y = 1\n", "let x = 1\n", "let y = 50\n", "function setup() { \n", " createCanvas(220, 220); \n", " // 使用random方法可以生成处于两个参数之间的数字\n", " speed_x *= random(1, 10) * 0.1\n", " speed_y *= random(5, 10) * 0.1\n", " // 方向也是随机的\n", " if (random(0, 1)>0.5){\n", " direction_x *= -1\n", " }\n", " if (random(0, 1)>0.5){\n", " direction_y *= -1\n", " }\n", "}\n", "\n", "function draw() {\n", " // 更改小球的x坐标实现运动效果\n", " x += speed_x*direction_x;\n", " y += speed_y*direction_y;\n", " background(204); \n", " ellipse(x, y, 20, 20); \n", " // 判断小球在x方向上是否出界\n", " if(x > 210){\n", " direction_x = -1\n", " // 如果出界, 调转方向\n", " }else if(x < 10){\n", " direction_x = 1\n", " }\n", " // 判断小球在y方向上是否出界\n", " if(y > 210){\n", " // 如果出界, 调转方向\n", " direction_y = -1\n", " }else if(y < 10){\n", " direction_y = 1\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 计时\n", "\n", "\n", "每个p5.js程序都会计算自启动以来经过的时间。它以毫秒(千分之一秒)计,因此在1秒后,计数器为1,000; 5秒后,它达到5,000; 1分钟后,它是60,000。我们可以使用此计数器在特定时间触发动画。 millis()函数返回此计数器值。\n", "\n", "下面我们制作一个可以定时改变颜色的小球。" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%p5 speed-random-change-color height=280\n", "// 速度是每帧1个像素\n", "// 用random生成一个随机数\n", "let speed_x = 1 \n", "let speed_y = 2 \n", "// 方向有1 和 -1两个取值\n", "let direction_x = 1\n", "let direction_y = 1\n", "let x = 1\n", "let y = 50\n", "\n", "// 记录上次更改颜色的时间\n", "let change_time = 0;\n", "function setup() { \n", " createCanvas(220, 220); \n", " // 使用random方法可以生成处于两个参数之间的数字\n", " speed_x *= random(1, 10) * 0.1\n", " speed_y *= random(5, 10) * 0.1\n", " // 方向也是随机的\n", " if (random(0, 1)>0.5){\n", " direction_x *= -1\n", " }\n", " if (random(0, 1)>0.5){\n", " direction_y *= -1\n", " }\n", "}\n", "\n", "function draw() {\n", " // 更改小球的x坐标实现运动效果\n", " x += speed_x*direction_x;\n", " y += speed_y*direction_y;\n", " background(204); \n", " ellipse(x, y, 20, 20); \n", " // 判断小球在x方向上是否出界\n", " if(x > 210){\n", " direction_x = -1\n", " // 如果出界, 调转方向\n", " }else if(x < 10){\n", " direction_x = 1\n", " }\n", " // 判断小球在y方向上是否出界\n", " if(y > 210){\n", " // 如果出界, 调转方向\n", " direction_y = -1\n", " }else if(y < 10){\n", " direction_y = 1\n", " }\n", " // 获取当前时间\n", " let current = millis()\n", "\n", " // 每隔1000毫秒执行一次\n", " // 判断时间是否超过1000毫秒\n", " if (current - change_time > 1000){\n", " fill(random(1, 255), random(1, 255), random(1, 255))\n", " // change_time的值\n", " change_time = current\n", " }\n", "}" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }