参考 GitHub - rustyscreeps/screeps-starter-rust: Starter Rust AI for Screeps, the JavaScript-based MMO game

安装相关cli

1
cargo install cargo-screeps

命令包含了构建代码、上传代码等操作。

下载模板文件

1
2
git clone https://github.com/rustyscreeps/screeps-starter-rust.git
cd screeps-starter-rust

模板 (版本:d91b60f9a13eb0bd763b094acb6a1d749bb1b12f) 中包含的文件:

1
2
3
4
5
6
7
8
9
10
./
├── Cargo.toml
├── example-screeps.toml
├── javascript
│ └── main.js
├── LICENSE
├── README.md
└── src
├── lib.rs
└── logging.rs

模板文件说明

example-screeps.toml 用于 cargo-screeps 的配置。

javascript/main.js 为游戏主入口,其中内容如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
"use strict";
let wasm_module;

// replace this with the name of your module
const MODULE_NAME = "screeps-starter-rust";

function console_error(...args) {
console.log(...args);
Game.notify(args.join(' '));
}

module.exports.loop = function () {
try {
if (wasm_module) {
wasm_module.loop();
} else {
// attempt to load the wasm only if there's enough bucket to do a bunch of work this tick
if (Game.cpu.bucket < 500) {
console.log("we are running out of time, pausing compile!" + JSON.stringify(Game.cpu));
return;
}

// delect the module from the cache, so we can reload it
if (MODULE_NAME in require.cache) {
delete require.cache[MODULE_NAME];
}
// load the wasm module
wasm_module = require(MODULE_NAME);
// load the wasm instance!
wasm_module.initialize_instance();
// run the setup function, which configures logging
wasm_module.setup();
// go ahead and run the loop for its first tick
wasm_module.loop();
}
} catch (error) {
console_error("caught exception:", error);
if (error.stack) {
console_error("stack trace:", error.stack);
}
console_error("resetting VM next tick.");
wasm_module = null;
}
}

文件中 wasm_module 保存了 wasm 的实例。如果 wasm 的实例存在,就调用 loop 函数运行游戏逻辑。如果 wasm 的实例不存在 (由于更新代码或 screeps 进行了内存回收等原因导致实列被销毁),重新载入 wasm 并且调用 setup 函数进行初始化,然后再运行游戏逻辑

src/logging.rs 为辅助文件,用于日志的实现。基本上就是进行 log 格式的创建,不做过多说明。在 setup 阶段调用一下 setup_logging 函数就行。

src/lib.rs 为 rust 的实现逻辑入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use std::cell::RefCell;
use std::collections::{hash_map::Entry, HashMap};

use log::*;
use screeps::{
constants::{ErrorCode, Part, ResourceType},
enums::StructureObject,
find, game,
local::ObjectId,
objects::{Creep, Source, StructureController},
prelude::*,
};
use wasm_bindgen::prelude::*;

mod logging;

// add wasm_bindgen to any function you would like to expose for call from js
#[wasm_bindgen]
pub fn setup() {
logging::setup_logging(logging::Info);
}

// this is one way to persist data between ticks within Rust's memory, as opposed to
// keeping state in memory on game objects - but will be lost on global resets!
thread_local! {
static CREEP_TARGETS: RefCell<HashMap<String, CreepTarget>> = RefCell::new(HashMap::new());
}

// this enum will represent a creep's lock on a specific target object, storing a js reference
// to the object id so that we can grab a fresh reference to the object each successive tick,
// since screeps game objects become 'stale' and shouldn't be used beyond the tick they were fetched
#[derive(Clone)]
enum CreepTarget {
Upgrade(ObjectId<StructureController>),
Harvest(ObjectId<Source>),
}

// to use a reserved name as a function name, use `js_name`:
#[wasm_bindgen(js_name = loop)]
pub fn game_loop() {
debug!("loop starting! CPU: {}", game::cpu::get_used());
// mutably borrow the creep_targets refcell, which is holding our creep target locks
// in the wasm heap
CREEP_TARGETS.with(|creep_targets_refcell| {
let mut creep_targets = creep_targets_refcell.borrow_mut();
debug!("running creeps");
for creep in game::creeps().values() {
run_creep(&creep, &mut creep_targets);
}
});

debug!("running spawns");
let mut additional = 0;
for spawn in game::spawns().values() {
debug!("running spawn {}", String::from(spawn.name()));

let body = [Part::Move, Part::Move, Part::Carry, Part::Work];
if spawn.room().unwrap().energy_available() >= body.iter().map(|p| p.cost()).sum() {
// create a unique name, spawn.
let name_base = game::time();
let name = format!("{}-{}", name_base, additional);
// note that this bot has a fatal flaw; spawning a creep
// creates Memory.creeps[creep_name] which will build up forever;
// these memory entries should be prevented (todo doc link on how) or cleaned up
match spawn.spawn_creep(&body, &name) {
Ok(()) => additional += 1,
Err(e) => warn!("couldn't spawn: {:?}", e),
}
}
}

info!("done! cpu: {}", game::cpu::get_used())
}

其中需要关注两个函数 setupgame_loop

setup 为 wasm 实例创建的时候调用的函数,在其中可以实现日志初始化、数据初始化的逻辑。

game_loop 通过 #[wasm_bindgen(js_name = loop)] 的标注 (rust 中称为过程宏) 将其改名为wasm 里运行的 loop 函数,这也是游戏中每 tick 运行的主逻辑。