返回博客首页
← 所有文章

使用 NativeScript 开始游戏开发

2016 年 1 月 29 日 — 作者 Alexander Vakrilov

让我们用 NativeScript 来玩点乐子吧!在这篇文章中,我们将开始构建一个类似 迷宫 的游戏。我们将使用 TypeScript 来完成这个项目,但和以往一样,纯 Javascript 也是可行的。查看 这个 github 仓库 以获取最终结果。

项目设置

为了简洁起见,假设我们已经完成了 NativeScript 设置。运行以下 3 个命令来设置我们的新项目
tns create ns-game
cd ns-game
tns install typescript

默认模板创建了我们不需要的文件。让我们删除 main-view-model.jsmain-page.js 文件,并创建一个空 main-menu.ts 文件。

我们执行的“tns install typescript”命令安装了必要的钩子,这些钩子将在我们构建项目时转译 TS 文件。如果您使用的是纯 Javascript,则不需要此命令。

作为最后的清理工作,将 main-page.xml 文件中的主体内容替换为以下代码
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded" actionBarHidden="true">
  <GridLayout>
    <Label text="Here be dragons!"/>
  </GridLayout>
</Page>


让我们第一次运行项目。切换到您的命令行程序,并输入以下命令:“tns run android/ios” 该命令的结果如下
first-run

物理引擎

现在我们需要引入一个物理引擎来驱动我们的游戏。我们将使用 nativescript-physics-js 插件,它包含一个 NativeScript 渲染器,用于 PhysicsJS 库。
使用 CLI 从 NPM 安装插件
tns plugin add nativescript-physics-js

现在让我们添加必要的 UI 来托管我们的物理场景(main-page.xml)
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded" actionBarHidden="true">
    <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”作为以后的方便参考。


ball-drop


添加交互

虽然弹跳球很酷,但我们可以通过能够通过旋转手机来控制球的方向来使其更酷。同样,我们将使用一个 插件 来附加到加速度计事件
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;

我们可以稍后对其进行微调以获得最佳体验。让我们现在享受这种互动
accelerometer-action

添加一些游戏逻辑

使用很少的代码,并使用预先存在的库,我们现在有一个由移动设备移动控制的相当逼真的球。现在到了有趣的部分 - 定义其他游戏元素。

从在舞台上添加墙壁开始。将以下代码添加到 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);

让我们玩
first-level

使用很少的源代码,我们已经能够制作一个迷宫式游戏。此外,此游戏无需任何额外步骤即可在原生 iOS 和 Android 上使用。在以后的文章中,我们将更新游戏,如下所示
  • 创建关卡选择器屏幕并从文件加载关卡。
  • 跟踪进度和分数。
  • 在球撞到墙壁时播放声音。
您可以在 github 中找到所有代码。