使用Rot.js构建类似rogue的游戏

2020年12月30日11:10:54 发表评论 80 次浏览

本文概述

流氓类游戏是角色扮演视频游戏的子流派, 其特征通常是基于回合的游戏玩法, 随机生成的关卡以及可玩角色的永久死亡。这种类型的流行产品是《龙与地下城》, 这是一款基于角色的游戏, 其中许多角色在幻想的环境中进行虚构的冒险。

类Rogue游戏有很多表征流行特征。这经常会刺激休闲游戏和新手游戏玩家争夺流氓类游戏的"真实特征"。

我们的博客文章介绍了如何使用Rot.js创建一个库-该库可帮助在浏览器环境中开发类似rogue的游戏。

Rot.js完善的功能包括:RNG(用于以过程方式生成级别), 寻路(用于玩家和AI), FOV计算和键绑定配置, 仅举几例。

当查看网络上的流氓游戏状态时, 很明显, 很多流氓类游戏已创建并在浏览器中可用。 Rot.js凭借其惊人的易于使用的功能将自己置于对话的中间。

Rot.js如何为你提供帮助?

Rot.js是一组JavaScript库, 旨在帮助开发类似rogue的浏览器环境。

Rot.js旨在帮助开发类似roguelike游戏的传统琐事。它是根据"libtcod", 它提供了一个简单的API, 可帮助开发人员了解他们未来游戏的真实色彩, 输入和实用程序。

Rot.js为用户提供了非常强大的基于画布的显示, 键盘映射以及难以想象的随机映射生成可能性。

那里存在大量的游戏用Rot.js制作。这些可以用于启发或展示目的。

Rot.js的功能

我们将使用Rot.js构建一个简单的roguelike游戏。我们的目标是利用Rotjs提供的图形, RNG, 调度和寻路API。

显示

ROT.Display()提供可以配置为游戏规格的基于画布的输出。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用Rot.js构建类似rogue的游戏1

使用以下值配置显示:

  • 宽度–水平大小, 以字符为单位
  • 高度–垂直大小, 以字符为单位
  • 字体大小–以像素为单位
  • 字体系列
  • fg- 前景色
  • bg- 背景颜色
  • 间距–间距系数
  • 布局–布局算法; " rect"或" hex"

这些非常重要, 因为它们是用户看到的以及游戏的显示方式。

平铺地图

Rot.js还允许开发人员创建图块, 这些图块可用于组织图块, 堆叠或自定义其颜色和外观以适合游戏。

瓷砖风格的图形是在Gameboy上的Pokemon等游戏中看到的图形。如果你想在游戏中使用高保真图形, 则此选项非常适合。使用Rot.js非常容易实现:你只需将字符引用为图块即可。通过浏览docs更好地了解如何切片你的tileSet变成不同的瓷砖。

地图制作

Rot.js中存在几种地图生成器工具。它们的使用方式相同:

  • ROT.Map。在其他相关选项中被实例化为一个对象及其大小。
  • 接下来, 致电创造函数, 需要一个回调函数。该回调函数应重复调用并传递到地图信息中。
  • 重要的是你的回调函数X, ÿ地图单元格和值对应于生成的单元格的类型。

Rot.js附带3种不同类型的生成器:迷宫, 蜂窝电话和地牢。可以对它们进行调整和随机化, 以适合你想要的游戏设计。

迷宫发电机

迷宫生成器的图片。

该算法系列使用以下回调值:

  • 0: 空的空间
  • 1:墙

Rot.js的MAP函数附带3种不同的迷宫生成器:DividedMaze, Icey的迷宫和Eller的Perfect Maze。

细胞自动机发生器

蜂窝自动机生成器的图片。

这些看牛的地图被称为洞穴系统。可以使用Rot.js的Cellular生成器创建它们。如上图所示, 地图的形状没有遵循任何特定的几何形状。

这种复杂性是通过使ROT.Map.CellularAPI。

与其他地图类型一样, 元胞自动机也接受height和width参数。除此之外, 我们可以配置:天生的, 生存, 拓扑结构, 设定(x, y, value)和-随机化(概率), 有关详细信息, 请参见docs.

地牢生成器

地下城生成器的图片。

这组地图生成器可产生走廊和房间。

Rot.js提供的地牢生成器可帮助创建类盗贼游戏特有的地牢地图效果。在下面的实际示例中, 我们将使用地牢生成器创建地图。

随机产生

而内置Math.random()函数提供适合游戏开发目的的结果, 这是相当弱的。最值得注意的是, 不可能播种生成器以重现适当的值序列。

这是旋转RNG对象发挥其数学功能:getUniform(), getNormal(平均值, stddev), getPercentage(), getItem(array), 随机播放(数组), getWeightedValue(), getState()和setState().

异步与调度

Rot.js提供了多种工具来帮助进行事件管理, 安排转弯, 计时和相关测量。

对于事件管理, Rot提供了一个事件队列, 可帮助维护已排序的优先级列表。事件队列提供了用于添加, 删除和检索内容的基本API。

Rot.js有3种内置的调度策略(简单, 速度, 动作持续时间)可以分别对应你正在制作的游戏。同样重要的是要注意Rot.js支持异步游戏引擎。

异步任务可以使用JavaScript中可用的本机异步await或promise处理。旋转引擎准备好进行基于承诺的异步控制流:如果有任何参与者从其返回" thenable"法案()方法, 引擎会自行锁定并等待thenable解析(通过其然后()方法)。

寻找路径

游戏开发中的寻路或寻径是两点之间最短路径的标绘。这是解决迷宫游戏的一种实用变体-维基百科.

路径极大地帮助了类似流氓的游戏, 具有更好的AI, 感觉和交互性。 Rot.js通过以下方式实现路径Djikstra的算法, 它基于查找图中两个节点之间的最短路径。

按键映射和用户输入

照顾用户输入归结为监听适当的键盘事件(按键, 按键, 键控)并对其进行处理。 Rot.js不提供任何支持方法或对象。相反, 它定义了一大套ROT.KEYS.VK_ *用来区分按键的常数。

你可能想看看完整清单。此功能使用户可以使用从诸如" W-A-S-D"或任何其他键发出的键盘事件来"玩"游戏。

JavaScript函数扩展

腐烂有非常方便的功能扩展in实用工具, 这可能有助于数字和字符串操作。

建立我们的无赖游戏

流氓游戏的例子。

目的

我们的目标是通过创建一个利用其主要功能的小游戏来学习流氓, 以了解每个游戏的工作方式。游戏的名称是"记录火箭"。

为了赢得这场比赛, 玩家必须让火箭找到地球并进入生成的蜂窝地图的下一层。太好了吧?

我们从哪里开始?

我们在浏览器中, 因此显然我们从基本的HTML, CSS和JavaScript文件开始。 Rot.js是一个JavaScript库, 因此它作为基本脚本安装或导入到HTML文件中。

<body>
  <script src="src/index.js"></script>
    <div class="center" id="canvas"></div>
</body>

这里, index.js将是包含Rot.js作为导入的脚本。 Rot.js的显示将附加到带有ID画布的元素上, 并带有以下行让canvas = document.getElementById(" canvas");脚本何时运行。

现在, 你可以包括CSS文件, 其中包含不同的导入字体(如果需要的话)或其他可以使画布看起来不错的样式。

在编写游戏代码时, 采用这种简单方法会更容易:准备游戏对象(世界, 游戏引擎, 颜色, 显示, 玩家等), 并使用其应使用的属性对其进行初始化。

在我们的游戏中, 我们有以下四个对象:显示选项, 颜色, 游戏, 游戏世界和播放器。其中, 我们有3个动态对象和1个静态对象(知道这将有助于我们正确构造JavaScript代码)。

我们的代码如下所示:

const displayOptions = {
  // the display configuration for ROTJS (see above)
}

const colors = {
  // colors configuration for different characters
}

let Game = {
  // game attributes and functions
}

let GameWorld = {
  // attributes related to Map and play space
}

let Player = {
  // everything related to the player–the game won't play itself :)
}

我选择了一种对象方法来简化本教程。但是, 任何经验丰富的JavaScript开发人员都会觉得, 如果使用类语法(他们是对的)。

请注意, 动态对象使用让, 它允许你重新分配对象的某些属性(因此将它们称为动态), 而不是const, 这是只读的。

现在, 我们可以相应地填充我们的配置属性。我们从这些开始, 因为它们使框架得以如上所示建立。它们将在整个编码过程中使用。

const displayOptions = {
  // Configure the display
  bg: "white", // background
  fg: "dimGrey", // foreground
  fontFamily: "Fira Mono", // font (use a monospace for esthetics)
  width: 25, height: 20, // canvas height and width
  fontSize: 18, // canvas fontsize
  forceSquareRatio: true // make the canvas squared ratio
};

// Object containing colors per tile
// you may add as much as you want corresponding to characters
// they will then be customly applied to each character
const colors = {
  ".": "lightgrey" // the moveable path
};

现在, 我们的游戏已具备所需的设置和配置, 我们必须开始在屏幕上显示内容。

全球游戏引擎的要素(负责使游戏"可玩")将放在游戏目的。

let Game = {
  map: [], win: false, // initialize the game at start time
  init: async function () {
    // we make the init function sleep to help load fonts
    await sleep(500).then(() => { 
      // pass the configuration defined as arguments
      Display = new ROT.Display(displayOptions);
      let canvas = document.getElementById("canvas");
      // append the created display to the HTML element
      canvas.appendChild(Display.getContainer());
    });
    Display.clear(); // remove anything displayed
    this.createLevel(); // create level
    Player.init(); // initialize the player attributes
    this.engine(); // start the game engine
    this.draw();
  }, engine: async function () {
    // this is responsible of watching the player move and updating
    // the display accordingly. It is all we need as engine
    while (true) {
      await Player.act(); 
      this.draw();
    }
  }, // we seperate the generating function so as to help recall it anytime, // in case we need a new generated level
  createLevel: function () {
    GameWorld.generate();
  }, draw: function () {
    Display.clear();
    GameWorld.draw();
    Player.draw();
  }, // when the game is over, we end it.
  endGame: function () {
    this.win = true;
    Display.clear();
    Display.draw(8, 8, "You logged the rocket!", "violet");
  }
};

以上是完整的游戏目的。让我们简要地介绍一下不同的功能。

  • 在里面通过传入选项并启动使游戏正常运行的不同功能来创建游戏显示
  • 发动机贯穿整个游戏。在这里, 我们只需要播放器相应地移动和更新UI
  • createLevel将使用我们上面说明的生成技术-特别是蜂窝地图生成器
  • 画只需在适当的时候使用Rot.js将所有内容绘制到UI
  • 结束游戏赢得比赛后, 火箭将清除用户界面(火箭触地)

现在我们的游戏引擎已经创建, 我们必须看看游戏世界.

在此对象中, 我们将使用Rot.js随机生成地图, 并使用一些技巧使墙阻挡玩家的移动。

let GameWorld = {
  map: [], moveSpace: [], generate: function () {
    let map = [];
    for (let i = 0; i < displayOptions.width; i++) {
      map[i] = [];
      for (let j = 0; j < displayOptions.height; j++) {
        map[i][j] = "+"; // create the walls around the map
      }
    }
    let freeCells = []; // this is where we shall store the moveable space
    // we create a cellular map using RotJS
    let digger = new ROT.Map.Cellular(
      displayOptions.width - 2, displayOptions.height - 2
    );
    // randomize(probability) set all cells to "alive" with a 
    // given probability (0 = no cells, 1 = all cells)
    digger.randomize(0.4);
    digger.create((x, y, value) => {
      if (value) {
        map[x + 1][y + 1] = "🌖"; // create the walls
      } else {
        freeCells.push({ x: x + 1, y: y + 1 });
        map[x + 1][y + 1] = "."; // add . to every free space just for esthetics
      }
    });

    // put the exit gate on the last free cell
    const lastFreeCell = freeCells.pop();
    map[lastFreeCell.x][lastFreeCell.y] = "🌍";
    this.map = map;
    this.freeCells = freeCells;
  }, // make it impossible to pass through if across an obstacle
  isPassable: function (x, y) {
    if (GameWorld.map[x][y] === "+" || GameWorld.map[x][y] === "🌖") {
      return false;
    } else {
      return true;
    }
  }, draw: function () {
    this.map.forEach((element, x) => {
      element.forEach((element, y) => {
        Display.draw(x, y, element, colors[element] || "red");
      });
    });
  }
};

以上是我们的游戏世界, 其中包含我们的地图生成器和地图选项。

  • 生成是一切发生的地方。它负责使地图每次按照给定的配置重新渲染时都不同
  • 我们创建地图周围的墙(+)有两个for循环
  • 我们使用创建随机细胞图ROT.Map.Cellular具有限制的宽度和高度, 因此为-2。这是为了将地图强制进入预定义的墙
  • 我们将细胞图随机化的可能性是有活细胞0.4(阅读文档)
  • 然后, 我们根据手机地图创建障碍墙, 并使其余的手机"自由"行走
  • 将出口放置在生成的地图的最后一列和最后一行
  • isPassable可以帮助我们知道玩家是否试图越过障碍物或墙壁以拒绝进入
  • 绘图功能使用颜色预定义了静态对象以绘制ASCII字符并为其着色, 并将其余字符默认设置为红色

最后, 我们创建我们的播放器:负责与游戏互动的人。

let Player = {
  x: null, y: null, init: function () {
    let playerStart = GameWorld.freeCells[0]; // put the player in the first available freecell
    this.x = playerStart.x;
    this.y = playerStart.y;
  }, draw: function () {
    Display.draw(this.x, this.y, "🚀", "black");
  }, act: async function () {
    let action = false;
    while (!action) {
      await new Promise((resolve) => setTimeout(resolve, 100));
      let e = await new Promise((resolve) => {
        window.addEventListener("keydown", resolve, { once: true });
      });
      action = this.handleKey(e);
    } //Await a valid movement
    // make it end when the rocket reaches the earth
    if (GameWorld.map[this.x][this.y] === "🌍") {
      Game.endGame();
      Game.createLevel();
      this.init();
    }
  }, handleKey: function (e) {
    var keyCode = [];
    //Arrows keys
    keyCode[38] = 0; // key-up
    keyCode[39] = 2; // key-right
    keyCode[40] = 4; // key-down
    keyCode[37] = 6; // key-left
    var code = e.keyCode;
    if (!(code in keyCode)) {
      return false;
    }
    let diff = ROT.DIRS[8][keyCode];
    if (GameWorld.isPassable(this.x + diff[0], this.y + diff[1])) {
      this.x += diff[0];
      this.y += diff[1];
      this.justMoved = true;
      return true;
    } else {
      return false;
    }
  }
};
  • 首先, 我们在里面我们的玩家在第一个可用的freecell上
  • 吸引玩家, 在我们的例子中是火箭
  • 在Rot.js的帮助下, 我们使播放器根据击键动作(输入映射)并将键码附加到移动动作(左, 右, 上, 下)
  • 伴随着这一点, 我们使游戏引擎了解到, 当火箭到达地球时, 该游戏就被视为获胜

最后几行帮助我们从浏览器捕获关键事件并加载游戏。这三行是脚本和浏览器之间的深层连接。

// listen to keystrokes
window.addEventListener(
  "keydown", function (e) {
    // space and arrow keys
    if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
      e.preventDefault();
    }
  }, false
);
// load the game
window.onload = Game.init();
// focus on the canvas
window.focus();

瞧!我们的游戏已经准备就绪。你可以在下面的链接上尝试一下, 随时在此处浏览完整的游戏代码, 进行分叉, 并进行添加或添加任何所需的内容。

rot-js-log-rocket rot-js-log-rocket由blurdylan使用rot-js

Rot.js完成了每次创建一个随机映射, 映射键并在浏览器中显示所有内容的重大任务。

仅从这个小游戏中, 我们就可以说明以下流氓功能:天然气, 输入键映射和显示。如果我们需要添加敌人和角色来玩, 我们可能会使用Rot.js寻路和调度功能。

总结

我们的游戏可以做出很多很好的改进:

  • 使火箭在途中收集虫子(功能)
  • 添加不同的游戏级别(功能)
  • 某些游戏级别无法播放(错误)
  • 添加可能杀死火箭并结束游戏的流星
  • 添加叙述以使游戏更具吸引力
  • 更大的地图和更好的随机算法

使游戏开发变得美丽的原因在于, 它可以发挥你的创造力。只要使用正确的工具, 你就可以构建自己想要的任何东西。

Rot.js之类的库使使用Web工具进行的游戏开发更加容易访问。通过灵活的库, 可以为以前很难掌握和生产的概念和技术提供出色的抽象。

日志火箭:全面了解你的网络应用

LogRocket仪表板免费试用横幅

日志火箭是一个前端应用程序监视解决方案, 可让你重播问题, 就好像问题发生在你自己的浏览器中一样。 notlogy无需猜测错误发生的原因, 也不要求用户提供屏幕截图和日志转储, 而是让你重播会话以快速了解出了什么问题。无论框架如何, 它都能与任何应用完美配合, 并具有用于记录来自Redux, Vuex和@ ngrx / store的其他上下文的插件。

除了记录Redux动作和状态外, notlogy还会记录控制台日志, JavaScript错误, 堆栈跟踪, 带有标题+正文, 浏览器元数据和自定义日志的网络请求/响应。它还使用DOM来记录页面上的HTML和CSS, 甚至可以为最复杂的单页面应用程序重新创建像素完美的视频。

免费试用

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: