Phaser是什么?

Phaser是一个HTML5游戏框架,目的是为了快速地制作跨浏览器的HTML5游戏。 这个框架,主要发掘了现代浏览器(兼及桌面和移动两类系统)的优点,所以对浏览器的唯一要求是,就是只要支持画布(canvas)标签就可以了。

第一个Phaser 3 游戏实例教程

我们将通过一个小游戏学习怎样使用Phaser。这个小游戏里会有个玩家(player)在平台(platform)上来回跑、跳,收集星星,躲避炸弹。在实践过程中,除了熟悉Phaser的api使用外,顺便阐述Phaser框架的一些核心特性。

基本要求

  1. 会用javascript
  2. 需要一个搭建好的web服务(比如nginx、apache、iis、tomcat等等)
  3. 下载这个zip文件,它含有本教程每一步的代码和资源。

再次确认,你已经读过《起步指南》,它会引导你怎样下载Phaser,搭建本地开发环境,顺便看一眼Phaser项目的结构及其核心api。

如果你已经看过《起步指南》,那么应当已经已经把一切准备工作都搭建好,准备好写代码了。请下载本教程所需资源,并解压到你的web服务器根目录下。

代码结构

在编辑器中选中、打开part1.html页面。前面是一小段HTML样板代码,它引入了Phaser;接下来的代码结构如下:

``` var config = { type: Phaser.AUTO, width: 800, height: 600, scene: { preload: preload, create: create, update: update } };

var game = new Phaser.Game(config);

function preload () { }

function create () { }

function update () { } ```

这个config(配置)对象意味着你怎么配置Phaser游戏。有很多选项可以放在这个对象里,当你的Phaser知识增加时,你会碰到它们。不过在本教程中,我们只打算设置渲染器(renderer)、尺寸和默认Scene(场景)。

一个Phaser.Game对象实例(instance)赋值给一个叫game的局部变量,上述配置对象传给这个实例。这将开始启动Phaser的过程。

在Phaser 2 中,对象game用作几乎所有内部系统的入口,并常常是通过全局变量访问它。在Phaser 3 中不再如此,在全局变量中存储游戏实例不再有用。

属性type可以是Phaser.CANVAS,或者Phaser.WEBGL,或者Phaser.AUTO。这是你要给你的游戏使用的渲染环境(context)。推荐值是Phaser.AUTO,它将自动尝试使用WebGL,如果浏览器或设备不支持,它将回退为Canvas。Phaser生成的画布元素(canvas element)将径直添加到文档中调用脚本的那个节点上,不过也可以在游戏配置中指定一个父容器,如果你需要的话。

属性widthheight设定了Phaser即将生成的画布元素的尺寸,在此例中是800 x 600 像素。这是游戏显示所用的分辨率,而你的游戏世界(world)可以是任意尺寸。

本教程后面还会涉及配置对象的scene属性的更多细节。

加载资源

让我们加载游戏所需资源。要做到这一点,你要在场景中的一个叫preload(预加载)的函数内部,调用Phaser的Loader(加载器)。Phaser启动时会自动找到这个函数,并加载里面定义好的所有资源。

目前preload函数是空的。把它改为:

function preload () { this.load.image('sky', 'assets/sky.png'); this.load.image('ground', 'assets/platform.png'); this.load.image('star', 'assets/star.png'); this.load.image('bomb', 'assets/bomb.png'); this.load.spritesheet('dude', 'assets/dude.png', { frameWidth: 32, frameHeight: 48 } ); }

这样将加载5个资源:4张图(image)和一个精灵表单(sprite sheet)。也许对于某些人,它看起来够明白了,但我还想说说第一个参数,它叫资源的key(键值,即'sky','bomb')。这个字符串是一个链接,指向已加载的资源,你在代码中生成游戏对象时将用到它。你可以随意使用任何有效的JavaScript字符串作为键值。

显示图像

要显示已经加载的一张图像,我们把下面的代码到create(生成)函数中:

this.add.image(400, 300, 'sky');

你可以在part3.html中看到这行代码。如果你是在浏览器中加载的,你现在应该看到一个游戏画面,布满蓝色天空作为背景:

400300是图像坐标的x值和y值。为什么是400和300呢?这是因为,在Phaser 3 中,所有游戏对象的定位都默认基于它们的中心点。这个背景图像的尺寸是800 x 600像素,所以,如果我们显示它时将它的中心定在0 x 0,你将只能看到它的右下角。如果我们显示它时定位在400 x 300,你能看到整体。

提示: 你可以用setOrigin(设置原点)来改变这种情况。比如代码this.add.image(0, 0, 'sky').setOrigin(0, 0),将把图像的绘制定位点重置为左上角。在Phaser 2 中,定位点是通过属性anchor(锚点)获取的,但在Phaser 3 中则通过属性originXoriginY

游戏对象的显示顺序与你生成它们的顺序一致。所以,如果你想放一个星星的精灵在背景上,你就要保证在添加天空(sky)图像之后才添加星星(star)图像:

function create () { this.add.image(400, 300, 'sky'); this.add.image(400, 300, 'star'); }

如果你先放star(星星)图像,它将被sky(天空)图像盖住。

建立游戏世界

在底层,代码this.add.image生成一个新的Image(图形)类游戏对象,并把它添加到当前场景的显示列表(display list)中。你的所有游戏对象都活在这个列表中。你可以把图像放置在任何位置,Phaser不会介意。当然,如果图像位于0x0到800x600这个区域之外,那么你视觉上看不到它,因为它已“脱离画面”,但它仍旧在场景中存在。

场景(Scene)自身没有确定的尺寸,在所有方向上都是无限延展的。镜头(Camera)系统控制着你观看场景的视野,你可以随意移动、推拉已激活的镜头。你还可以另外生成一些镜头,用于别的观看场景的视野。这一话题已经超出本特定教程,完全可以说,Phaser 3 的镜头系统,能力大大地超过Phaser 2的。以前完全不可能的东西现在可以了。

现在让我们搭建场景,添加一张背景图像和几个平台。这是更新后的create函数:

``` var platforms;

function create () { this.add.image(400, 300, 'sky');

platforms = this.physics.add.staticGroup();platforms.create(400, 568, 'ground').setScale(2).refreshBody();platforms.create(600, 400, 'ground');
platforms.create(50, 250, 'ground');
platforms.create(750, 220, 'ground');

} ```

快速扫一眼这些代码,你可以看到一个对this.physics的调用。这意味着我们在使用Arcade(游乐场)物理系统(Physics system),不过在此之前我们还需要把它添加到游戏配置中,以便告诉Phaser我们的游戏需要它。所以让我们更新一下,引入对物理系统的支持。这是修订后的游戏配置:

var config = { type: Phaser.AUTO, width: 800, height: 600, physics: { default: 'arcade', arcade: { gravity: { y: 300 }, debug: false } }, scene: { preload: preload, create: create, update: update } };

新加的是physics属性。这些代码就位之后,如果运行它(你可以在本教程的zip文件中part4.html中找到),你将看到一个更有游戏样子的场景:

我们有了一个背景和一些平台,可以这些平台到底怎么样才能运作起来呢?

平台

我们刚加了一堆代码到create函数中,此函数应该更详尽地解释一下。首先,这一部分:

platforms = this.physics.add.staticGroup();

这一句生成一个静态物理组(Group),并把这个组赋值给局部变量platforms。在Arcade物理系统中,有动态的和静态的两类物体(body)。动态物体可以通过外力比如速度(velocity)、加速度(acceleration),得以四处移动。它可以跟其他对象发生反弹(bounce)、碰撞(collide),此类碰撞受物体质量和其他因素影响。

与此明显不同的是,静态物体只有位置和尺寸。重力对它没有影响,你不能给它设置速度,有东西跟它碰撞时,它一点都不动。名副其实,完全是静态的。所以用作地面和平台很完美,我们打算让玩家在上面来回跑动。

那么什么是组呢?如其名所示,是把近似对象组织在一起的手段,控制对象全体就像控制一个统一的个体。你也可以检查组与其他游戏对象之间的碰撞。组能够生成自己的游戏对象,这是通过便利的辅助函数如create实现的。物理组会自动生成已经开启物理系统的子项(children),省得你处理时跑腿。

平台组做好了,我们现在可以用它生成平台:

``` platforms.create(400, 568, 'ground').setScale(2).refreshBody();

platforms.create(600, 400, 'ground'); platforms.create(50, 250, 'ground'); platforms.create(750, 220, 'ground'); ```

这就生成了场景,如前所见。

在预加载时,我们输入了图像'ground'。它是个简单的绿色长方形,尺寸是400 x 32像素,将用于我们的基础平台:

上述代码的第一行,添加一张新的地面图像到400 x 568的位置(请记住,图像定位基于中心点)——问题是,我们需要这个平台撑满游戏的宽度。否则玩家就会掉出边界。要做到这一点,我们用函数setScale(2)把它按x2(两倍)缩放。现在它的尺寸是800 x 64了,恰好符合我们的要求。要调用refreshBody(),这是因为我们缩放的是一个 静态 物体,所以必须把所作变动告诉物理世界(physics world)。

地面已经缩放、就位,现在该别的平台了:

platforms.create(600, 400, 'ground'); platforms.create(50, 250, 'ground'); platforms.create(750, 220, 'ground');

这个步骤跟前面完全相同,只是不需要缩放,因为他们的尺寸本来就正好。

3个平台已经放到画面各处,距离合适,以便玩家能蹦上去。

那么让我们添加玩家。

玩家

我们已经有了可爱、诱人的平台,但还没有人在上面跑动。让我们改一下。

做一个新的变量player,并把下面的代码添加到create函数中。你可以在part5.html中看到这些:

``` player = this.physics.add.sprite(100, 450, 'dude');

player.setBounce(0.2); player.setCollideWorldBounds(true);

this.anims.create({ key: 'left', frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }), frameRate: 10, repeat: -1 });

this.anims.create({ key: 'turn', frames: [ { key: 'dude', frame: 4 } ], frameRate: 20 });

this.anims.create({ key: 'right', frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }), frameRate: 10, repeat: -1 }); ```

这里有两件不同的事情:生成物理精灵(sprite),生成精灵能用到的几个动画。

物理精灵

代码第一部分生成精灵:

``` player = this.physics.add.sprite(100, 450, 'dude');

player.setBounce(0.2); player.setCollideWorldBounds(true); ```

这样生成一个新的精灵,叫player(玩家),位于100 x 450像素,在游戏的下部。精灵是通过物理游戏对象工厂函数(Physics Game Object Factory,即this.physics.add)生成的,这意味着它默认拥有一个动态物体。

精灵生成后,被赋予0.2的一点点反弹(bounce)值。这意味着,它跳起后着地时始终会弹起那么一点点。然后精灵设置了与世界边界(bound)的碰撞。——边界默认在游戏尺寸之外。我们(通过player.setCollideWorldBounds(true))把游戏(的世界边界)设置为800 x 600后,玩家就不能不跑出这个区域了。这样会让玩家停下来,不能跑出画面边界,或跳出顶边。

动画

如果回顾一下preload函数,你会看到'dude'是作为精灵表单(sprite sheet)载入的,而非图像。这是因为它包含了动画帧(frame)。完整的精灵表单是这个样子的:

总共有9帧,4帧向左跑动,1帧面向镜头,4帧向右跑动。注意:Phaser支持翻转精灵,以节省动画帧,不过因为这是教程,我们将保持老派做法。

我们定义两个动画,叫'left'和'right'。这是'left'动画:

this.anims.create({ key: 'left', frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }), frameRate: 10, repeat: -1 });

'left'动画使用0, 1, 2, 3帧,跑动时每秒10帧。'repeat -1'告诉动画要循环播放。

这是我们的标准跑动周期。反方向的动画把这些重复一下,键值用'right'。最后一个动画键值用'turn'(转身)。

补充信息: 在Phaser 3 中,动画管理器(Animation Manager)是全局系统。其中生成的动画是全局变量,所有游戏对象都能用到它们。它们分享基础的动画数据,同时管理自己的时间轴(timeline)。这就使我们能够在某时定义一个动画,却可以应用到任意多的游戏对象上。这有别于Phaser 2,那时动画只属于据以生成动画的特定游戏对象。

添加物理系统

Phaser支持多种物理系统,每一种都以插件形式运作,任何Phaser场景都能使用它们。在本文写作时,已经装有Arcade, Impact, Matter.js三种物理系统。针对本教程,我们将给我们的游戏使用Arcade物理系统,它简单,轻量,完美地支持移动浏览器。

物理精灵在生成时,即被赋予body(物体)属性,这个属性指向它的Arcade物理系统的Body。它表示精灵是Arcade物理引擎中的一个物体。物体对象有很多属性和方法,我们可以玩一下。

比如,在一个精灵上模仿重力效果,可以这么简单写:

player.body.setGravityY(300)

这是个随意的值,但逻辑讲,值越大你的对象感觉越重,下落越快。如果你把这些加到你的代码里,或者运行part5.html,你会看到玩家不停地往下落,完全无视我们先前生成的地面:

原因在于,我们还没有测试地面和玩家之间的碰撞。

我们已经跟Phaser说,我们的地面和平台将是静态物体。但是我们没有那么做,反而生成了动态的。如此,当玩家和他们碰撞时,玩家会停止一瞬,然后全部崩塌。这是因为,除非别那么说,否则地面精灵是会移动的物体,当玩家碰到它时,碰撞导致的力会作用语地面,因此两个物体交换彼此的速度,于是地面也开始下落。

要想玩家能与平台碰撞,我们可以生成一个碰撞对象。该对象监控两个物体(可以是组),检测二者之间的碰撞和重叠事件。如果发生事件,这时它可以随意调用我们的回调函数。不过仅仅就与平台间的碰撞而言,我们没必要那么做:

this.physics.add.collider(player, platforms);

碰撞器(Collider)是施魔法的地方。它接收两个对象,检测二者之间的碰撞,并使二者分开。在本例中,我们把玩家精灵和平台组交给它。它很聪明,可以执行针对所有组成员的碰撞,所以这一个调用就能处理与组合以及所有平台的碰撞。结果就有了一个稳固的平台,不再崩塌:

\

键盘控制

碰撞很棒了,不过我们非常想玩家动起来。你可能想到了,去找文档,搜一搜怎样添加事件监听器,但这里不需要。Phaser有内置的键盘管理器,用它的一个好处体现在这样一个方便的小函数:

cursors = this.input.keyboard.createCursorKeys();

这里把四个属性up, down, left, right(都是Key对象的实例),植入光标(cursor)对象。然后我们要做的就是在update循环中做这样一些轮询:

``` if (cursors.left.isDown) { player.setVelocityX(-160);

player.anims.play('left', true);

} else if (cursors.right.isDown) { player.setVelocityX(160);

player.anims.play('right', true);

} else { player.setVelocityX(0);

player.anims.play('turn');

}

if (cursors.up.isDown && player.body.touching.down) { player.setVelocityY(-330); } ```

我们添加了很多代码,不过都还相当易读。

它做的第一件事,是查看方向左键是不是正被按下。如果是,我们应用一个负的水平速度,开动奔跑动画'left'。如果是方向右键正被按下,我们按字面意思做反向动作。通过清除速度值,再如此设置,一帧一帧,形成一个“走走停停”(stop-start)式的运动。

玩家精灵只有键被按下时才移动,抬起时立即停止。Phaser也允许你用动量(momentum)和加速度(acceleration)生成更为复杂的动作,不过这里已经得到我们的游戏所需要的效果了。键盘检测的最后部分,如果没有键被按下,就设置动画为'turn',水平速度为0。

赶快

代码的最后部分添加了跳起功能。方向up键是跳起键,我们检查它有没有被按下。不过我们同时也检测玩家是不是正与地面接触,否则在半空中还会往上跳。

如果所有这些条件都符合,我们应用一个垂直速度,330像素每秒。玩家会自动落回地面,因为有重力。控制已经就位,我们现在有了一个可以探索的游戏世界。请加载part7.html,玩一玩。尝试调整各个值,比如跳起值330,调低,调高,看看会有什么效果。

收集星星

该给我们的小游戏定个目标了。让我们撒几颗星星到场景中,让玩家来收集。要做到这一点,我们会生成一个新的组,叫'stars',再充实它。在生成函数中,我们加入如下代码(这些可以在part8.html中看到):

``` stars = this.physics.add.group({ key: 'star', repeat: 11, setXY: { x: 12, y: 0, stepX: 70 } });

stars.children.iterate(function (child) {

child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));

}); ```

这个过程跟我们生成平台组近似。因为需要星星移动、反弹,我们生成动态物理组,而不是静态的。

组可以接收配置对象,以便于设置。在本例中,组配置对象有3个部分:首先,它设置纹理key(键值)为星星图像。这意味着配置对象生成的所有子项,都将被默认地赋予星星纹理。然后,它设置重复值为11。因为它自动生成一个子项,重复11次就意味着我们总共将得到12颗,这正好是我们的游戏所需要的。

最后的部分是setXY——这用来设置组的12个子项的位置。每个子项都将如此放置:初始是x: 12,y: 0,然后x步进70。这意味着第一个子项将位于12 x 0;第二个离开70像素,位于82 x 0;第三个在152 x 0,依次类推。'step'(步进)值用于组生成子项时加以排布,真是很方便的手段。选用值70是因为,这意味着所有12个子项将完美地横跨着布满画面。

下一段代码遍历组中所有子项,给它们的bounce.y赋予0.4到0.8之间的随机值,反弹范围在0(不反弹)到1之间(完全反弹)。因为星星都是在y等于0的位置产出的,重力将把它们往下拉,直到与平台或地面碰撞为止。反弹值意味着它们将随机地反弹上来,直到最终恢复安定为止。

如果现在我们这样就运行代码,星星会落下并穿过游戏底边,消失不见了。要防止这个问题,我们就要检测它们与平台的碰撞。我们可以再使用一个碰撞器对象来做这件事:

this.physics.add.collider(stars, platforms);

与此类似,我们也将检测玩家是否与星星重叠:

this.physics.add.overlap(player, stars, collectStar, null, this);

这会告诉Phaser,要检查玩家与组中任何一颗星星的重叠。如果检测到,他们就会被传递到'collectStar'函数:

function collectStar (player, star) { star.disableBody(true, true); }

简单来说,星星带着个已关闭的物体,其父级游戏对象被设置为不活动、不可见,即将它从显示中移除。现在运行一下游戏,我们得到一个玩家,它左冲右突的,跳起,从平台反弹,收集头顶上落下的星星。不错,毕竟就这么几行、多半看起来还很好理解的代码:)

\

计分

最后我们打算给游戏增加两处改进:一个需要躲避的敌人,它会杀死玩家;收集到星星时得分。首先是得分。

为了做这个,我们将使用游戏对象Text(文本)。在此我们生成两个新的变量,一个持有实际得分,一个文本对象本身:

var score = 0; var scoreText;

scoreTextcreate函数中构建:

scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });

16 x 16是显示文本的坐标位置。'score: 0'是要显示的默认字符串,接下来的对象包含字号、填充色。因为没有指定字体,实际上将用Phaser默认的,即Courier。

下一步我们要调整collectStar函数,以便玩家捡到一颗星星时分数会提高,文本会更新以反映出新状态:

``` function collectStar (player, star) { star.disableBody(true, true);

score += 10;
scoreText.setText('Score: ' + score);

} ```

这样一来,每颗星星加10分,scoreText将更新,显示出新的总分。如果运行part9.html,你可以看到星星掉下来,收集星星时分数会提高。

最后一节我们将添几个坏蛋。

跳跳蛋

现在该添加一些坏蛋,以此给我们的游戏收尾。这将给游戏增添很棒的挑战因素,这是此前缺乏的。

想法是这样的:你第一次收集到所有星星后,将放出一个跳跳弹。这个炸弹只是随机地在平台上各处跳,如果收集它,你就死了。所有星星会重新产出,以便你可以再次收集,如果你完成了,又会放出另一个炸弹。这将给玩家一个挑战:别死掉,取得尽可能高的分数。

我们首先需要的东西是给炸弹用的一个组,还有几个碰撞器:

``` bombs = this.physics.add.group();

this.physics.add.collider(bombs, platforms);

this.physics.add.collider(player, bombs, hitBomb, null, this); ```

炸弹当然会跳出平台,如果玩家碰到它们,我们将调用hitBomb函数。这个函数所作的就是停止游戏,使玩家变成红色:

``` function hitBomb (player, bomb) { this.physics.pause();

player.setTint(0xff0000);player.anims.play('turn');gameOver = true;

} ```

现在看来还不错,不过我们要放出一个炸弹。要做到这一点,我们改一下collectStar函数:

``` function collectStar (player, star) { star.disableBody(true, true);

score += 10;
scoreText.setText('Score: ' + score);if (stars.countActive(true) === 0)
{stars.children.iterate(function (child) {child.enableBody(true, child.x, 0, true, true);});var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);var bomb = bombs.create(x, 16, 'bomb');bomb.setBounce(1);bomb.setCollideWorldBounds(true);bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);}

} ```

我们使用一个组的方法countActive,看看有多少星星还活着。如果没有了,那么玩家把它们收集完了,于是我们使用迭代函数重新激活所有星星,重置它们的y位置为0。这将使所有星星再次从画面顶部落下。

下一部分代码生成一个炸弹。首先,我们取一个随机x坐标给它,始终在玩家的对侧画面,以便给玩家个机会。然后生成炸弹,设置它跟世界碰撞,反弹,拥有随机速度。

最终结果是个很棒的小炸弹精灵,它在画面上跳呀跳。尺寸小,开始的时候易于躲避。不过数量增加后就变得比较棘手!

我们的游戏已经做好了:)

结论

现在你已经学会怎样生成有物理属性的精灵,学会控制它的动作,学会使它与其他对象在一个小小的游戏世界里互动。你还可以做很多事情,以便增强它。为什么不扩展平台的尺寸并允许镜头摇动呢?也许可以增加不同类型的坏蛋,不同分值的收集活动,或者给玩家一个血条(health bar)。

或者,为了做个非暴力型的,你可以把它做成比快游戏(speed-run),仅仅挑战人们去尽可能快地收集星星。

有了本教程中所学到的东西,还有你能得到几百个实例的帮助,你现在已经为将来的项目准备了牢靠的基础。不过你总还会有疑问,需要建议,或者想分享你一直在做的东西,到时候请随意到Phaser论坛请求帮助。

Facebook即时游戏

Phaser 3 完全支持生成Facebook即时游戏。现在你已经学会怎样做Phaser游戏,为什么不看看怎样方便地转换为即时游戏呢?在我们专门 的《起步指南》里有。

Phaser 3 入门实例教程相关推荐

  1. php页面get方法实现ajax,入门实例教程

    ajax,入门实例教程 本例针对php页面,做了一个小的demo加深对ajax的理解 1.文档结构: 共有ajax.php 和action.php 2个页面. 2.源码如下: /*ajax.php页面 ...

  2. Web Components入门实例教程

    代码示例 <!-- 定义组件模板 --> <template id="UserNameTemplate"><style>.user-name { ...

  3. [转]React 入门实例教程

    React 入门实例教程 作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获 ...

  4. 视频教程-Swift5语言入门实例教程-Swift

    Swift5语言入门实例教程 15年以上IT行业工作经验.8年以上IT行业教学经验.丰富的项目经验和授课经验,授课形式不拘一格.熟悉iOS开发,网页开发.Java开发.平面设计等技术,是一名经验丰富的 ...

  5. 读阮一峰的React 入门实例教程有感

    读阮一峰的React 入门实例教程有感 阮一峰的React入门实例教程其实我在一年前就读过,当时就想学习React,其实那个时候刚刚jQuery入门,啥也不懂,看得云里雾里,所以后来就没有继续研究下去 ...

  6. Very Good!!! - React 入门实例教程

    现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...

  7. React 入门实例教程(原作者: 阮一峰)

    转载(http://www.ruanyifeng.com/blog/2015/03/React.html) 现在最热门的前端框架,毫无疑问是 React . 上周,基于 react 的 React N ...

  8. mxGraph 入门实例教程

    在上一篇文章 <记一次绘图框架技术选型: jsPlumb VS mxGraph> 中,提到了我为什么要去学习 mxGraph.在入门时我遇到了以下几个问题 官方文档偏向理论,没能较好地结合 ...

  9. Reactjs 入门实例教程

    来自:http://www.ruanyifeng.com/blog/2015/03/react.html 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Na ...

最新文章

  1. 阿里云代码超限2040M remote: error: hook declined to update refs/heads
  2. python添加excel模块_python操作Excel模块openpyxl
  3. 两个php的build文件,PHP编译安装中遇到的两个错误和解决方法
  4. hibernate映射关系的配置
  5. 中路径查找器的功能_还在用路径查找器?试试它吧!
  6. django和mysql如何建模_Django中的多个数据库和多个模型
  7. vue中引用swiper轮播插件
  8. 2018顺丰视觉岗笔试几个知识点
  9. Spring Security(09)——Filter
  10. stm32f103不同系列之间的代码移植
  11. android-应用签名
  12. SQL数据库置疑问题的处理方法
  13. 竞赛大佬在华为:网络专家出身斯坦福物理系,还有人“工作跟读博差不多”...
  14. [机缘参悟-13]:菩提心,一切“利他”之心
  15. MP中的条件更新操作
  16. 实现网上购物系统的后台管理(增、删、改、查图书)。
  17. MBRGPT硬盘分区类型属性详解(Win下更改/设置OEM/恢复分区方法)
  18. 支付宝转账到银行卡的二维码
  19. 设施网络选址的基本方法,网络设施选址的方法
  20. 盘点苹果2016WWDC精彩看点, iOS 10官方细节要来了

热门文章

  1. 如何设置国际PayPal
  2. hyper v虚拟机启动黑屏怎么办?
  3. 猿创征文|瑞吉外卖——移动端_地址管理
  4. 浙江大学计算机考研真题及答案,浙江大学计算机考研真题-20210531140358.docx-原创力文档...
  5. 经典人工智能及开发工具--不看会后悔!!
  6. 淘宝API接口系列,获取购买到的商品订单列表,订单详情,订单物流,收货地址列表,买家信息,买家token,卖出的商品订单列表
  7. 查看java进程占用内存_如何查看java进程大批占用内存
  8. exe4j打成的exe文件运行时弹窗this executable was created with an evaluation version of exe4j问题解决
  9. 移动端h5文字长按复制_H5实现移动端复制文字功能
  10. 历史经验之js个200经验收藏