|
1 | 1 | # bevy_mod_js_scripting
|
2 | 2 |
|
3 |
| -Work in progress javascript scripting integration into bevy. |
4 |
| -Proper readme coming soon. |
| 3 | +`bevy_mod_js_scripting` is an experimental scripting integration crate for writing javascript/typescript scripts and running them |
| 4 | +in `bevy` with full access to the ECS values like components and resources. |
| 5 | + |
| 6 | + |
| 7 | +## Example |
| 8 | + |
| 9 | +```ts |
| 10 | +// type safe access to resources and values |
| 11 | +type Scoreboard = { score: number }; |
| 12 | +const Scoreboard: BevyType<Scoreboard> = { typeName: "breakout::Scoreboard" }; |
| 13 | + |
| 14 | +// script-local variables can be used for easy cross-frame state |
| 15 | +let i = 0; |
| 16 | + |
| 17 | +function run() { |
| 18 | + // increment score every 60 frames |
| 19 | + if(i % 60 == 0) { |
| 20 | + let score = world.resource(Scoreboard)!; |
| 21 | + score.score += 1; |
| 22 | + // logging works via `trace`, `debug`, `info`, `warn`, `error` |
| 23 | + info(score.score); |
| 24 | + } |
| 25 | + |
| 26 | + // query components |
| 27 | + for (const item of world.query(Transform, Aabb)) { |
| 28 | + let [transform, aabb] = item.components; |
| 29 | + info("Translation:", transform.translation.toString()); |
| 30 | + info("AABB Center:", aabb.center.toString()); |
| 31 | + |
| 32 | + // call methods on value references (requires app code setup, see headless.rs) |
| 33 | + let normalized = transform.scale.normalize(); |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +export default { |
| 38 | + // execute the `run` function in the update stage |
| 39 | + update: run, |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +More examples can be found in the [examples](./examples/) folder. |
| 44 | +Also check out the [punchy wiki page](https://github.com/fishfolks/punchy/wiki/Scripting) on scripting, which uses `bevy_mod_js_scripting`. |
| 45 | + |
| 46 | +## Current Status |
| 47 | + |
| 48 | +Currently supported operations are |
| 49 | +- resource access (`world.resource(Time)`) |
| 50 | +- world information (`world.components`, `world.resources`, `world.entities`) |
| 51 | +- queries (`world.query(Ball, Velocity).map(({ entity, components }) => components[1])`) |
| 52 | +- component insertion (`world.insert(value)`) |
| 53 | +- dealing with ecs value references (`Value.create`, `Value.patch`) |
| 54 | + |
| 55 | +## Design decisions |
| 56 | + |
| 57 | +<details> |
| 58 | +<summary>Types</summary> |
| 59 | + |
| 60 | +In `bevy_ecs`, the common methods for dealing with ECS values all take a type parameter, like |
| 61 | +```rs |
| 62 | +world.resource::<T>(); // or |
| 63 | +world.query<(Entity, &Component)>(); |
| 64 | +``` |
| 65 | +Ideally we would be able to write |
| 66 | +```ts |
| 67 | +let time = world.resource<Time>(); |
| 68 | +``` |
| 69 | +as well in typescript, but since typescript just transpiles to javascript without adding any new runtime capabilities, we cannot associate any runtime values with the `Time` type. |
| 70 | + |
| 71 | + |
| 72 | +Instead, what we need to do is write `type` definition with an associated variable of type `BevyType<T>`, which contains the referenced type's type name. |
| 73 | + |
| 74 | +```ts |
| 75 | +type Transform = { |
| 76 | + translation: Vec3, |
| 77 | + rotation: Quat, |
| 78 | + scale: Vec3, |
| 79 | +}; |
| 80 | +const Transform: BevyType<Transform> = { |
| 81 | + typeName: "bevy_transform::components::global_transform::GlobalTransform" |
| 82 | +}; |
| 83 | + |
| 84 | +// `world.resource` is typed so that typescript can infer `transform` to be of type `Transform | undefined` |
| 85 | +let transform = world.resource(Transform); |
| 86 | +``` |
| 87 | + |
| 88 | +Similarly, queries list their types like |
| 89 | +```ts |
| 90 | +for (item of world.query(Ball, Velocity)) { |
| 91 | + info(item.entity); |
| 92 | + let [_ball, velocity] = item.components; |
| 93 | + // velocity is properly typed |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +Currently, there is a pregenerated list of bevy types in [./types/bevy_types.ts](./types/bevy_types.ts), and you can also just define your own ones. |
| 98 | + |
| 99 | +</details> |
| 100 | +<details> |
| 101 | +<summary>Javascript Values & ECS Value references</summary> |
| 102 | + |
| 103 | +When you call `world.resource` (or any other method returning references to ECS values), what you get is not just a simple javascript object corresponding to the rust value, but instead a `Proxy` which defers all accesses/modifications to the actual value inside the bevy world. |
| 104 | + |
| 105 | +Only leaf values, like `transform.translation.x`, which can be natively represented as a javascript primitive, are automatically converted to/from the rust representation on gets and sets. |
| 106 | + |
| 107 | +This means that |
| 108 | +```ts |
| 109 | +let transform = world.resource(Transform); |
| 110 | +let translation = transform.translation; |
| 111 | +// typeof translation.x == "number" |
| 112 | +translation.x = 3.0; |
| 113 | +``` |
| 114 | + |
| 115 | +If you want to create a new value reference, for example for inserting a new resource, the current APIs to do that are `Value.create` and `Value.patch`. |
| 116 | + |
| 117 | +```ts |
| 118 | +let transform = Value.create(Transform); |
| 119 | +let vec3 = Value.create(Vec3, { x: 0.0, y: 1.0, z: 2.0 }); |
| 120 | +transform.translation = vec3; |
| 121 | +world.insertResource(Transform, transform); |
| 122 | +``` |
| 123 | + |
| 124 | +Expect to see changes in this area as we figure out the best way to deal with the interaction of javascript objects and value references. |
| 125 | +</details> |
5 | 126 |
|
6 | 127 | ## Web support
|
7 | 128 |
|
|
0 commit comments