让我们用 NativeScript 来玩点乐子吧!在这篇文章中,我们将开始构建一个类似
迷宫 的游戏。我们将使用 TypeScript 来完成这个项目,但和以往一样,纯 Javascript 也是可行的。查看
这个 github 仓库 以获取最终结果。
项目设置
为了简洁起见,假设我们已经完成了
NativeScript 设置。运行以下 3 个命令来设置我们的新项目
tns create ns-game
cd ns-game
tns install typescript
默认模板创建了我们不需要的文件。让我们删除
main-view-model.js 和
main-page.js 文件,并创建一个空
main-menu.ts 文件。
我们执行的“tns install typescript”命令安装了必要的钩子,这些钩子将在我们构建项目时转译 TS 文件。如果您使用的是纯 Javascript,则不需要此命令。
作为最后的清理工作,将 main-page.xml 文件中的主体内容替换为以下代码
<
GridLayout
>
<
Label
text
=
"Here be dragons!"
/>
</
GridLayout
>
</
Page
>
让我们第一次运行项目。切换到您的命令行程序,并输入以下命令:“tns run android/ios” 该命令的结果如下
物理引擎
现在我们需要引入一个物理引擎来驱动我们的游戏。我们将使用
nativescript-physics-js 插件,它包含一个 NativeScript 渲染器,用于
PhysicsJS 库。
使用 CLI 从 NPM 安装插件
tns plugin add nativescript-physics-js
现在让我们添加必要的 UI 来托管我们的物理场景(main-page.xml)
<
GridLayout
>
<!-- Definte the container for the physics scene -->
<
GridLayout
id
=
"container"
/>
<!-- Label for meta info is not required -->
<
Label
id
=
"meta"
/>
</
GridLayout
>
</
Page
>
#container 元素将被物理渲染器用于显示场景。
#meta 标签是可选的 - 它将显示当前 FPS。这些元素的样式在 app.css 文件中定义
#container {
width
:
300
;
height
:
300
;
background-color
: lightgreen;
}
#meta {
font-family
:
monospace
;
font-size
:
10
;
horizontal-align:
right
;
vertical-align
:
top
;
margin
:
5
;
}
接下来,让我们在页面的
pageLoaded 事件中启动场景(main-page.ts)。
import Physics = require(
"nativescript-physics-js"
);
import { Page } from
"ui"
;
var
page: Page;
var
init =
false
;
export
function
pageLoaded(args) {
// Prevent double initialization
if
(init) {
return
;
}
// Get references to container and meta-info views
var
page = args.object;
var
container = page.getViewById(
"container"
);
var
metaText = page.getViewById(
"meta"
);
// Create physics world
var
world = Physics({sleepDisabled:
true
});
// Add {N} renderer
world.add(Physics.renderer(
'ns'
, {
container: container,
metaText: metaText,
meta:
true
}));
// Add behaviors
world.add([
Physics.behavior(
'edge-collision-detection'
, { aabb: Physics.aabb(0, 0, 300, 300) }),
// Scene bounds
Physics.behavior(
'body-collision-detection'
),
// Collision related
Physics.behavior(
'body-impulse-response'
),
// Collision related
Physics.behavior(
'sweep-prune'
),
// Collision related
Physics.behavior(
'constant-acceleration'
)
// Gravity
]);
// Start ticking - render new scene every 20ms
world.on(
'step'
,
function
() { world.render() });
setInterval(
function
() { world.step(Date.now()); }, 20);
}
总结一下到目前为止的工作,我们已经
- 创建了一个 Physics 对象 (word)。sleepDisabled 标志阻止物理体进入休眠状态 - 现在不用担心这个。
- 使用我们在 XML 中创建的 UI 容器和元标签初始化 NativeScript 渲染器。
- 添加了一些物理行为 - edge-collision-detection、碰撞和重力。 有关行为的更多信息.
- 启动了一个世界计时器 - 每 20 毫秒渲染一个步骤
最后,创建一个函数,将一个实际的物体(在本例中是一个球体)添加到世界中(我们应该在所有初始化之后调用它)
export
function
pageLoaded(args) {
// … initialization
// 添加球
addBall(world, 50, 150);
}
function
addBall(world, x: number, y: number){
var
ball = Physics.body(
'circle'
, {
label:
"ball"
,
x: x,
y: y,
radius: 15,
label:
"ball"
,
styles: { image:
"~/images/ball.png"
}
});
ball.restitution = 0.3;
world.add(ball);
}
球是一个“圆形”物体。我们在样式对象中传递的图像由 NS 渲染器用于加载物体的图像。
恢复 是物体的“弹性”。我们希望球感觉“沉重”,所以给它一个相对较低的值。我们还分配了一个标签“ball”作为以后的方便参考。
添加交互
虽然弹跳球很酷,但我们可以通过能够通过旋转手机来控制球的方向来使其更酷。同样,我们将使用一个
插件 来附加到加速度计事件
tns plugin add nativescript-accelerometer
现在,更新代码,使加速度计影响重力向量的方向。在 main-page.ts 中,初始化“常数加速度(又称“重力”)行为。
// 添加行为
var
gravity = Physics.behavior(
'constant-acceleration'
, { acc: { x: 0, y: 0 } });
world.add([
// 其他行为
gravity
]);
// 在创建世界后 1 秒开始加速度计事件。
setTimeout(
function
() {
accService.startAccelerometerUpdates((data) => {
var
xAcc = -data.x * 0.003;
var
yAcc = data.y * 0.003;
gravity.setAcceleration({ x: xAcc, y: yAcc });
})
}, 1000);
这里没什么特别的 - 在应用程序启动后 1 秒附加到加速度计事件,并在每次加速度计报告事件时更新重力向量。为了获得额外的积分,可以使用一个常量 gravity_scale。
const gravity_scale = 0.003;
我们可以稍后对其进行微调以获得最佳体验。让我们现在享受这种互动
添加一些游戏逻辑
使用很少的代码,并使用预先存在的库,我们现在有一个由移动设备移动控制的相当逼真的球。现在到了有趣的部分 - 定义其他游戏元素。
从在舞台上添加墙壁开始。将以下代码添加到 main-page.ts 中
function
addWall(world, x: number, y: number, width: number, height:number, angle: number = 0){
world.add(Physics.body(
'rectangle'
, {
treatment:
'static'
,
x: x,
y: y,
width: width,
height: height,
angle: angle,
styles: { color:
"orange"
}
}));
}
墙壁是具有“静态”处理的矩形物体 - 它们不能移动。同样,样式对象由渲染器用于设置物体的颜色。
接下来:定义**目标** - 我们要将球推进去的地方
function
addTarget(world, x: number, y: number){
world.add(Physics.body(
'circle'
, {
label:
'target'
,
treatment:
'static'
,
x: x,
y: y,
radius: 20,
styles: { image:
"~/images/target.png"
}
}));
}
请注意,我们给它一个标签,就像我们在球上做的那样。我们将使用这些标签来检测获胜条件:球击中目标。引擎为我们提供了
几种检测碰撞的方法。我们需要检测“球”和“目标”物体碰撞时的碰撞(在世界初始化中添加代码)
var
query = Physics.query({
$or: [
{ bodyA: { label:
'ball'
}, bodyB: { label:
'target'
} }
, { bodyB: { label:
'target'
}, bodyA: { label:
'ball'
} }
]
});
world.on(
'collisions:detected'
,
function
(data, e) {
if
(Physics.util.find(data.collisions, query)) {
world.pause();
alert(
"You Win!!!"
)
}
});
现在我们拥有定义游戏的第一关所需的所有方法
// 物理世界初始化后
addWall(world, 0, 150, 20, 300);
addWall(world, 300, 150, 20, 300);
addWall(world, 150, 0, 300, 20);
addWall(world, 150, 300, 300, 20);
addWall(world, 150, 250, 10, 200);
addTarget(world, 225, 225);
addBall(world, 50, 250);
让我们玩
使用很少的源代码,我们已经能够制作一个迷宫式游戏。此外,此游戏无需任何额外步骤即可在原生 iOS 和 Android 上使用。在以后的文章中,我们将更新游戏,如下所示
- 创建关卡选择器屏幕并从文件加载关卡。
- 跟踪进度和分数。
- 在球撞到墙壁时播放声音。
您可以在
github 中找到所有代码。