一小时学会 Web 游戏






4.76/5 (19投票s)
我将演示如何使用 Web 技术和仅两个外部库从头开始构建一个游戏,并且将在不到一小时的时间内完成。
开发游戏并不需要一套全新的技能。事实上,您现有的 HTML、JavaScript、CSS 等 Web 开发技能对于各种游戏来说都绰绰有余。当您使用 Web 技术构建游戏时,它几乎可以在任何带有浏览器的设备上运行。
为了证明这一点,我将演示如何使用 Web 技术和仅两个外部库从头开始构建一个游戏,并且将在不到一小时的时间内完成。我将涵盖各种游戏开发主题,从基本设计和布局、控件和精灵,到简单对手的人工智能(AI)。我甚至将开发该游戏,使其能够在 PC、平板电脑和智能手机上运行。如果您有作为 Web 开发人员或其他开发领域的编程经验,但没有游戏编写经验,本文将帮助您入门。如果您给我一小时,我保证会教您入门。
开始运行
我将在 Visual Studio 中进行所有开发,这使得在我进行更改时能够快速执行 Web 应用程序。请确保拥有最新版本的 Visual Studio,以便能够跟随。我使用的是 Visual Studio 2013 Pro,但已使用 Visual Studio 2013 Community 更新了代码。另外,如果您有 Mac 或 Linux,现在 Visual Studio Code 也可以跨平台使用。
此应用程序不需要服务器代码,因此我首先在 Visual Studio 中创建一个新的、空的网页项目。我将使用空的 C# 模板作为网站,方法是选择“文件 | 新建 | ASP.NET 空网站”后选择“Visual C#”选项。
index HTML 文件仅需要三个资源:jQuery、一个主样式表和一个主 JavaScript 文件。我向项目中添加了一个名为 style.css 的空 CSS 文件和一个名为 ping.js 的空 JavaScript 文件,以避免在加载页面时出现错误。
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
<script src="ping.js"></script>
<link rel="stylesheet" href="style.css"></script>
</head>
<body>
</body>
</html>
另外,别忘了在各种浏览器和设备上测试此应用程序(或任何其他应用程序)。虽然我编写的代码与 Chrome、Firefox 和 Microsoft Edge 等现代浏览器兼容,但最好还是仔细检查。现在,您可以使用 免费虚拟机 和 http://www.browserstack.com 等工具进行测试。
基本设计
我正在构建的游戏是 Pong 的一个变体,我称之为 Ping。Ping 的规则与 Pong 基本相同,只是任何一方都可以抓住球,然后直接或向上/向下以一定角度将其弹回。通常,在构建游戏之前,最好先绘制出您希望游戏外观的样子。对于这款游戏,我希望看到的整体布局如图 1 所示。
一旦我完成了游戏设计布局,就可以通过将每个元素添加到 HTML 来构建游戏。但需要注意的一点是,我将把记分牌和控件分组,以确保它们放在一起。因此,我逐一添加了这些元素,如下所示:
<div id="arena">
<div id="score">
<h1>
<span id="playerScore">0</span>
<span id="opponentScore">0</span>
</h1>
</div>
<div id="player"></div>
<div id="opponent"></div>
<div id="ball"></div>
<div id="controls-left">
<div id="up"></div>
<div id="down"></div>
</div>
<div id="controls-right">
<div id="left"></div>
<div id="right"></div>
</div>
</div>
用风格玩
如果您加载此页面,您将看不到任何内容,因为没有应用样式。我已经在我 HTML 中设置了一个指向 main.css 文件的链接,所以我将在一个具有该名称的新文件中放置所有 CSS。我首先要做的是将屏幕上的所有元素定位。页面主体需要占用整个屏幕,所以我首先设置这个。
body {
margin: 0px;
height: 100%;
}
其次,我需要让竞技场填充整个屏幕,并应用竞技场背景图像(参见图 2)。
#arena {
background-image: url(arena.png);
background-size: 100% 100%;
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
接下来,我将定位记分牌。我希望它出现在顶部中央,覆盖其他元素。position: absolute 属性允许我将其放置在任何我想要的位置,left: 50% 将其放置在窗口顶部的一半处,但从记分牌元素的左侧开始。为了确保它完全居中,我使用了 transform 属性,z-index 属性则确保它始终位于顶部。
#score {
position: absolute;
z-index: 1000;
left: 50%;
top: 5%;
transform: translate(-50%, 0%);
}
我还希望文本字体具有复古主题。大多数现代浏览器允许我包含自己的字体。我从 codeman38 (zone38.net) 找到了合适的 Press Start 2P 字体。要将字体添加到记分牌,我必须创建一个新的字体面。
@font-face {
font-family: 'PressStart2P';
src: url('PressStart2P.woff');
}
现在,分数在 h1 标签中,所以我可以为所有 h1 标签设置字体。万一字体丢失,我会提供一些备用选项。
h1 {
font-family: 'PressStart2P', 'Georgia', serif;
}
对于其他元素,我将使用精灵图。精灵图包含游戏所需的所有图像,都在一个文件中(参见图 3)。
任何具有此图上图像的元素都将分配一个精灵类。然后,对于每个元素,我将使用 background-position 来定义我要显示的精灵图的哪个部分。
.sprite {
background-image: url("sprites.png");
width: 128px;
height: 128px;
}
接下来,我将精灵类添加到所有将使用精灵图的元素中。我将需要短暂地回到 HTML 来完成这个操作。
<div id="player" class="sprite"></div>
<div id="opponent" class="sprite"></div>
<div id="ball" class="sprite"></div>
<div id="controls-left">
<div id="up" class="sprite"></div>
<div id="down" class="sprite"></div>
</div>
<div id="controls-right">
<div id="left" class="sprite"></div>
<div id="right" class="sprite"></div>
</div>
现在我需要为每个元素指示精灵在图上的位置。同样,我将使用 background-position 来完成此操作。
#player { position: absolute; background-position: 0px 128px; } #opponent { position: absolute; background-position: 0px 0px; } #ball { position: absolute; background-position: 128px 128px; } #right { background-position: 64px 192px; } #left { background-position: 64px 0px; } #down { background-position: 128px 192px; } #up { background-position: 128px 0px; }
player、opponent 和 ball 上的 position: absolute 属性将允许我使用 JavaScript 来移动它们。如果您现在查看页面,您会看到控件和球附有不必要的零件。这是因为精灵尺寸小于默认的 128 像素,所以我将它们调整到正确的大小。只有一个球,所以我将直接设置其大小。
#ball {
position: absolute;
width: 64px;
height: 64px;
background-position: 128px 128px;
}
有四个控件元素(用户可以按按钮来移动玩家),因此我需要为它们创建一个特殊的类。我还会添加边距,以便它们周围有一些空间。
.control {
margin: 16px;
width: 64px;
height: 64px;
}
添加此类后,游戏中的控件看起来好多了。
<div id="controls-left">
<div id="up" class="sprite control"></div>
<div id="down" class="sprite control"></div>
</div>
<div id="controls-right">
<div id="left" class="sprite control"></div>
<div id="right" class="sprite control"></div>
</div>
我需要做的最后一件事是定位控件,以便在移动设备上运行页面时,它们能够靠近用户的手指。我将把它们固定在屏幕底部角落。
#controls-left {
position: absolute;
left: 0; bottom: 0;
}
#controls-right {
position: absolute;
right: 0; bottom: 0;
}
这个设计的一个优点是所有元素都使用相对定位。这意味着屏幕可以有多种不同的尺寸,同时仍然能让游戏看起来不错。
跟随弹跳的球
现在我将让球移动起来。对于 JavaScript 代码,我在 HTML 中引用了一个名为 ping.js 的文件,就像我处理 CSS 一样。我将把这段代码添加到具有该名称的新文件中。我将为球和每个玩家创建对象,但我将为对象使用工厂模式。
这是一个简单的概念。Ball 函数在调用时创建一个新球。无需使用 new 关键字。此模式通过明确可用的对象属性,减少了对 this 变量的一些混淆。而且,因为我只有一个小时来制作这个游戏,所以我需要尽量减少任何令人困惑的概念。
我制作简单 Ball 类时,此模式的结构如下:
var Ball = function( {
// List of variables only the object can see (private variables).
var velocity = [0,0];
var position = [0,0];
var element = $('#ball');
var paused = false;
// Method that moves the ball based on its velocity. This method is only used
// internally and will not be made accessible outside of the object.
function move(t) {
}
// Update the state of the ball, which for now just checks
// if the play is paused and moves the ball if it is not.
// This function will be provided as a method on the object.
function update(t) {
// First the motion of the ball is handled
if(!paused) {
move(t);
}
}
// Pause the ball motion.
function pause() {
paused = true;
}
// Start the ball motion.
function start() {
paused = false;
}
// Now explicitly set what consumers of the Ball object can use.
// Right now this will just be the ability to update the state of the ball,
// and start and stop the motion of the ball.
return {
update: update,
pause: pause,
start: start
}
要创建一个新球,我只需调用我定义的这个函数。
var ball = Ball();
现在我想让球在屏幕上移动和弹跳。首先,我需要在某个时间间隔调用 update 函数来创建球的动画。现代浏览器提供了一个为此目的设计的函数,称为 requestAnimationFrame。它接受一个函数作为参数,并在下次运行其动画周期时调用传入的函数。这使得球在浏览器准备好更新时能够平滑地移动。当它调用传入的函数时,它会提供自页面加载以来的秒数。这对于确保动画随时间保持一致至关重要。在游戏中,requestAnimationFrame 的用法如下所示:
var lastUpdate = 0;
var ball = Ball();
function update(time) {
var t = time - lastUpdate;
lastUpdate = time;
ball.update(t);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
请注意,requestAnimationFrame 再次在函数中被调用,因为球已经完成了更新。这确保了连续的动画。
虽然这段代码可以正常工作,但可能会有一个问题,即脚本在页面完全加载之前就开始运行。为了避免这种情况,我将在页面加载时使用 jQuery 来启动代码。
var ball;
var lastUpdate;
$(document).ready(function() {
lastUpdate = 0;
ball = Ball();
requestAnimationFrame(update);
});
因为我知道球的速度(速度)和自上次更新以来的时间,所以我可以做一些简单的物理计算来让球前进。
var position = [300, 300];
var velocity = [-1, -1];
var move = function(t) {
position[0] += velocity[0] * t;
position[1] += velocity[1] * t;
element.css('left', position[0] + 'px');
element.css('top', position[1] + 'px');
}
尝试运行代码,您会看到球以一定角度移动并移出屏幕。这样可以玩一小会儿,但一旦球滚出屏幕边缘,乐趣就停止了。因此,下一步是让球从屏幕边缘反弹,如第 7 图所示。添加此代码并运行应用程序将显示一个持续弹跳的球。可移动玩家
现在是时候创建 Player 对象了。完善 player 类中的第一步将是让 move 函数改变玩家的位置。side 变量将指示玩家所在的场地一侧,这将决定玩家如何在水平方向上定位。传递给 move 函数的 y 值将表示玩家向上或向下移动的距离。
var Player = function (elementName, side) {
var position = [0,0];
var element = $('#'+elementName);
var move = function(y) {
}
return {
move: move,
getSide: function() { return side; },
getPosition: function() { return position; }
}
}
然后,我们可以布局玩家的移动,当玩家精灵到达窗口顶部或底部时停止移动。
var move = function(y) {
// Adjust the player's position.
position[1] += y;
// If the player is off the edge of the screen, move it back.
if (position[1] <= 0) {
position[1] = 0;
}
// The height of the player is 128 pixels, so stop it before any
// part of the player extends off the screen.
if (position[1] >= innerHeight - 128) {
position[1] = innerHeight - 128;
}
// If the player is meant to stick to the right side, set the player position
// to the right edge of the screen.
if (side == 'right') {
position[0] = innerWidth - 128;
}
// Finally, update the player's position on the page.
element.css('left', position[0] + 'px');
element.css('top', position[1] + 'px');
}
我现在可以创建两个玩家,并让他们移动到屏幕的相应一侧。
player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);
键盘输入
理论上你可以移动玩家,但如果没有指令,它不会移动。为左侧的玩家添加一些控件。您有两种方式来控制该玩家:使用键盘(在 PC 上)和点击控件(在平板电脑和手机上)。
为了确保跨平台触摸输入和鼠标输入的兼容性,我将使用出色的统一框架 Hand.js (handjs.codeplex.com)。首先,我将在 HTML 的 head 部分添加脚本。
<script src="hand.minified-1.3.8.js"></script>
然后,我将使用 Hand.js 和 jQuery 来控制玩家,当您按下键盘按键 A 和 Z,或者当您点击控件时。
var distance = 24; // The amount to move the player each step.
$(document).ready(function() {
lastUpdate = 0;
player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);
ball = Ball();
// pointerdown is the universal event for all types of pointers -- a finger,
// a mouse, a stylus and so on.
$('#up') .bind("pointerdown", function() {player.move(-distance);});
$('#down') .bind("pointerdown", function() {player.move(distance);});
requestAnimationFrame(update);
});
$(document).keydown(function(event) {
var event = event || window.event;
// This code converts the keyCode (a number) from the event to an uppercase
// letter to make the switch statement easier to read.
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'A':
player.move(-distance);
break;
case 'Z':
player.move(distance);
break;
}
return false;
});
接住球
当球弹跳时,我想让玩家接住它。当球被接住时,球将有一个所有者,并跟随该所有者的移动。我将向 ball 的 move 方法添加功能,允许其拥有一个所有者,球将随后跟随该所有者。
var move = function(t) {
// If there is an owner, move the ball to match the owner's position.
if (owner !== undefined) {
var ownerPosition = owner.getPosition();
position[1] = ownerPosition[1] + 64;
if (owner.getSide() == 'left') {
position[0] = ownerPosition[0] + 64;
} else {
position[0] = ownerPosition[0];
}
// Otherwise, move the ball using physics. Note the horizontal bouncing
// has been removed -- ball should pass by a player if it
// isn't caught.
} else {
// If the ball hits the top or bottom, reverse the vertical speed.
if (position[1] - 32 <= 0 || position[1] + 32 >= innerHeight) {
velocity[1] = -velocity[1];
}
position[0] += velocity[0] * t;
position[1] += velocity[1] * t;
}
element.css('left', (position[0] - 32) + 'px');
element.css('top', (position[1] - 32) + 'px');
}
目前,没有办法获取 Player 对象的位置,所以我将向 Player 对象添加 getPosition 和 getSide 访问器。
return {
move: move,
getSide: function() { return side; },
getPosition: function() { return position; }
}
现在,如果球有一个所有者,它将跟随该所有者移动。但是,我如何确定所有者呢?必须有人接住球。让我们确定当其中一个玩家精灵碰到球时。当这种情况发生时,我将把球的所有者设置为该玩家。
var update = function(t) {
// First the motion of the ball is handled.
if(!paused) {
move(t);
}
// The ball is under control of a player, no need to update.
if (owner !== undefined) {
return;
}
// First, check if the ball is about to be grabbed by the player.
var playerPosition = player.getPosition();
if (position[0] <= 128 &&
position[1] >= playerPosition[1] &&
position[1] <= playerPosition[1] + 128) {
console.log("Grabbed by player!");
owner = player;
}
// Then the opponent...
var opponentPosition = opponent.getPosition();
if (position[0] >= innerWidth - 128 &&
position[1] >= opponentPosition[1] &&
position[1] <= opponentPosition[1] + 128) {
console.log("Grabbed by opponent!");
owner = opponent;
}
如果您现在尝试玩游戏,您会发现球从屏幕顶部反弹,您可以移动玩家来接住它。那么,如何发球呢?这就是右侧控件的作用——瞄准球。让我们为玩家添加一个“fire”函数,以及一个 aim 属性。
var aim = 0;
var fire = function() {
// Safety check: if the ball doesn't have an owner, don't not mess with it.
if (ball.getOwner() !== this) {
return;
}
var v = [0,0];
// Depending on the side the player is on, different directions will be thrown.
// The ball should move at the same speed, regardless of direction --
// with some math you can determine that moving .707 pixels on the
// x and y directions is the same speed as moving one pixel in just one direction.
if (side == 'left') {
switch(aim) {
case -1:
v = [.707, -.707];
break;
case 0:
v = [1,0];
break;
case 1:
v = [.707, .707];
}
} else {
switch(aim) {
case -1:
v = [-.707, -.707];
break;
case 0:
v = [-1,0];
break;
case 1:
v = [-.707, .707];
}
}
ball.setVelocity(v);
// Release control of the ball.
ball.setOwner(undefined);
}
// The rest of the Ball definition code goes here...
return {
move: move,
fire: fire,
getSide: function() { return side; },
setAim: function(a) { aim = a; },
getPosition: function() { return position; },
}
然后,我们可以增强键盘功能,以设置玩家的 aim 和 fire 函数。瞄准的工作方式会略有不同。当瞄准键被释放时,aim 将恢复到直线。
$(document).keydown(function(event) {
var event = event || window.event;
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'A':
player.move(-distance);
break;
case 'Z':
player.move(distance);
break;
case 'K':
player.setAim(-1);
break;
case 'M':
player.setAim(1);
break;
case ' ':
player.fire();
break;
}
return false;
});
$(document).keyup(function(event) {
var event = event || window.event;
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'K':
case 'M':
player.setAim(0);
break;
}
return false;
});
最后的补充将是对所有控件的触摸支持。我将使右侧的控件改变玩家的瞄准。我还将使其可以通过点击屏幕上的任意位置来发球。
$('#left') .bind("pointerdown", function() {player.setAim(-1);});
$('#right') .bind("pointerdown", function() {player.setAim(1);});
$('#left') .bind("pointerup", function() {player.setAim(0);});
$('#right') .bind("pointerup", function() {player.setAim(0);});
$('body') .bind("pointerdown", function() {player.fire();});
保持得分
当球经过某个玩家时,我想改变分数并将球交给该玩家。我将使用自定义事件,以便我可以将评分与其他现有对象分开。update 函数正在变长,所以我将添加一个新的私有函数 called checkScored。
function checkScored() {
if (position[0] <= 0) {
pause();
$(document).trigger('ping:opponentScored');
}
if (position[0] >= innerWidth) {
pause();
$(document).trigger('ping:playerScored');
}
}
下面的代码响应这些事件来更新分数并传递球。将此代码添加到 JavaScript 文档的底部。
$(document).on('ping:playerScored', function(e) {
console.log('player scored!');
score[0]++;
$('#playerScore').text(score[0]);
ball.setOwner(opponent);
ball.start();
});
$(document).on('ping:opponentScored', function(e) {
console.log('opponent scored!');
score[1]++;
$('#opponentScore').text(score[1]);
ball.setOwner(player);
ball.start();
});
现在,当球越过您的对手时(这并不难,因为对手不动),您的分数会增加,球也会交给对手。但是,对手只会拿着球。
变得聪明
您差不多快要完成一个游戏了。要是能有个对手一起玩就好了。最后一步,我将展示如何通过简单的 AI 来控制对手。对手将尝试与移动的球保持平行。如果对手抓住球,它将随机移动,并以随机方向发球。为了让 AI 感觉更人性化,我将在所有操作中添加延迟。请注意,这并不是高度智能的 AI,但它将是您可以与之对弈的对象。
设计这种系统时,最好以状态为单位进行思考。对手 AI 有三种可能的状态:跟随、瞄准/射击和等待。我将使用状态之间的延迟来增加一些更人性化的元素。从 AI 对象开始,先只考虑这一点。
function AI(playerToControl) {
var ctl = playerToControl;
var State = {
WAITING: 0,
FOLLOWING: 1,
AIMING: 2
}
var currentState = State.FOLLOWING;
}
根据 AI 的状态,我希望它执行不同的操作。就像球一样,我将创建一个 update 函数,可以在 requestAnimationFrame 中调用它,让 AI 根据其状态执行操作。
function update() {
switch (currentState) {
case State.FOLLOWING:
// Do something to follow the ball.
break;
case State.WAITING:
// Do something to wait.
break;
case State.AIMING:
// Do something to aim.
break;
}
}
FOLLOWING 状态很简单。对手在球的垂直方向上移动,然后 AI 转换为 WAITING 状态以注入一些反应时间的延迟。下面的代码展示了这两种状态。
function moveTowardsBall() {
// Move the same distance the player would move, to make it fair.
if(ball.getPosition()[1] >= ctl.getPosition()[1] + 64) {
ctl.move(distance);
} else {
ctl.move(-distance);
}
}
function update() {
switch (currentState) {
case State.FOLLOWING:
moveTowardsBall();
currentState = State.WAITING;
case State.WAITING:
setTimeout(function() {
currentState = State.FOLLOWING;
}, 400);
break;
}
}
}
AI 在跟随球和等待片刻之间交替。现在将代码添加到全局更新函数中。
function update(time) {
var t = time - lastUpdate;
lastUpdate = time;
ball.update(t);
ai.update();
requestAnimationFrame(update);
}
运行游戏时,您会看到对手跟随球的移动——在不到 30 行代码中实现了不错的 AI。当然,如果对手抓住球,它什么也不会做。所以,作为本小时的最后一个技巧,是时候处理 AIMING 状态的操作了。
我希望 AI 随机移动几次,然后以随机方向发球。让我们添加一个执行此操作的私有函数。将 aimAndFire 函数添加到 AIMING case 语句中,即可实现一个功能齐全的 AI 对手。
function repeat(cb, cbFinal, interval, count) {
var timeout = function() {
repeat(cb, cbFinal, interval, count-1);
}
if (count <= 0) {
cbFinal();
} else {
cb();
setTimeout(function() {
repeat(cb, cbFinal, interval, count-1);
}, interval);
}
}
function aimAndFire() {
// Repeat the motion action 5 to 10 times.
var numRepeats = Math.floor(5 + Math.random() * 5);
function randomMove() {
if (Math.random() > .5) {
ctl.move(-distance);
} else {
ctl.move(distance);
}
}
function randomAimAndFire() {
var d = Math.floor( Math.random() * 3 - 1 );
opponent.setAim(d);
opponent.fire();
// Finally, set the state to FOLLOWING.
currentState = State.FOLLOWING;
}
repeat(randomMove, randomAimAndFire, 250, numRepeats);
}
总结
至此,您已经拥有了一个功能齐全的网页游戏,可以在 PC、智能手机和平板电脑上运行。这个游戏有很多可能的改进。例如,在智能手机上以纵向模式运行时,它看起来会有点笨拙,所以您需要确保以横向模式握持手机才能正常工作。这只是 Web 及其他领域游戏开发可能性的一个小型演示。
感谢技术专家 Mohamed Ameen Ibrahim 审阅本文。
更多关于 JavaScript 的实践
本文是 Microsoft 技术传播者关于实际 JavaScript 学习、开源项目和互操作性最佳实践的网络开发系列文章的一部分,包括 Microsoft Edge 浏览器和新的 EdgeHTML 渲染引擎。
我们鼓励您在 Windows 10 默认浏览器 Microsoft Edge 等各种浏览器和设备上进行测试,可以使用 dev.modern.IE 上的 免费工具。
- 扫描您的网站是否存在过时库、布局问题和可访问性问题
- 将虚拟机用于 Mac、Linux 和 Windows
- 在您自己的设备上远程测试 Microsoft Edge
- GitHub 上的编程实验室:跨浏览器测试和最佳实践
来自我们工程师和布道者的 Microsoft Edge 和 Web 平台深度技术学习
- Microsoft Edge Web Summit 2015(关于新浏览器、新支持的 Web 平台标准以及 JavaScript 社区特邀嘉宾的期望)
- 哇,我可以在 Mac 和 Linux 上测试 Edge 和 IE!(来自 Rey Bango)
- 在不破坏 Web 的情况下推进 JavaScript(来自 Christian Heilmann)
- 让 Web 正常工作的 Microsoft Edge 渲染引擎(来自 Jacob Rossi)
- 使用 WebGL 释放 3D 渲染能力(来自 David Catuhe,包括 vorlon.JS 和 babylonJS 项目)
- 托管 Web 应用和 Web 平台创新(来自 Kevin Hill 和 Kiril Seksenov,包括 manifold.JS 项目)
更多免费的跨平台工具和网络平台资源
- 适用于 Linux、MacOS 和 Windows 的 Visual Studio Code
- 使用 node.JS 进行编码,并在 Azure 上享受 免费试用。