mirror of https://github.com/dsrw/enu.git
Better docs. WIP.
This commit is contained in:
parent
8eb73136c6
commit
dc078979f7
|
@ -24,6 +24,7 @@ app/_dlls/enu.lib
|
|||
|
||||
generated/
|
||||
app/export_presets.cfg
|
||||
docs/build
|
||||
dist/
|
||||
vmlib/stdlib
|
||||
dist_config.json
|
||||
|
|
807
README.md
807
README.md
|
@ -6,810 +6,3 @@
|
|||
|
||||
Enu lets you build and explore worlds using a familiar block-building interface and a Logo inspired API.
|
||||
It aspires to make 3D development more accessible, and will eventually be usable to create standalone games.
|
||||
|
||||
*Note: The docs for Enu 0.2 are a work in progress. Most of the core ideas are here, but a fair bit of revision
|
||||
is required. The 0.2 docs will be targeted towards new programmers, with 'Note for Nimians` sections aimed at more
|
||||
experienced folks to explain what's really going on. However, things are all over the place right now, with the
|
||||
intended audience changing paragraph by paragraph.*
|
||||
|
||||
*Notes for Nimians: Enu tries to simplify some Nim concepts, mainly to defer explaining unfamiliar terms. In particular,
|
||||
Enu tries to hide most things related to types, calls procs 'actions', and avoids immutable variables. I believe this is
|
||||
the right approach for new programmers, but I expect that more sophisticated developers will use a style closer to
|
||||
traditional Nim.*
|
||||
|
||||
# Goals
|
||||
|
||||
Enu is meant for anyone who wants to explore, experiment, or make games, but particular care has been taken to make
|
||||
it usable by younger people who may struggle with reading or typing. However, rather than bypassing the keyboard with
|
||||
a Scratch-like visual programming language, Enu attempts to reduce and simplify the keystrokes required for a text-based
|
||||
language, while (hopefully) preserving most of the flexibility text-based code offers.
|
||||
|
||||
With this in mind, Enu tries to:
|
||||
|
||||
- Reduce nesting. Indentation can be tricky for new programmers.
|
||||
- Reduce the use of the shift key. Lower case is used almost everywhere. Commands are written to
|
||||
avoid underscores and parenthesis. By default (for now at least), a `;` keypress is interpreted as `:`, as colons are
|
||||
required frequently in Nim (and require shift, at least on US English keyboards) while semi-colons are not.
|
||||
|
||||
- Omit or shorten identifier names. `me` instead of `self/this`. `-` instead of `proc`. `5.times:` or `5.x:` instead of
|
||||
`for i in 0..5:`. Single letter shortcuts for many common commands.
|
||||
|
||||
- Pretends to avoid types. Enu code is Nim code and is statically typed, but a fair amount of effort has been spent
|
||||
hiding this fact. Types are great, but are confusing for new programmers.
|
||||
|
||||
- Spatial organization. No files. Code is text, but it's accessed through an object in the virtual world.
|
||||
|
||||
- Avoids events. Tries to make all flow based on loops and conditionals.
|
||||
|
||||
# Demo
|
||||
|
||||
[Inky: Isolation. A 90 minute game built with Enu and Nim](https://youtu.be/9e9sLsmsu_o)
|
||||
|
||||
[Potato Zombies: Helping a 6 year old build a 3D game with Enu and Nim](https://fosdem.org/2022/schedule/event/nim_potatozombies/)
|
||||
|
||||
[Building 3D Games with Enu 0.2 - NimConf 2021](https://youtu.be/ECJsq7BeZ8w)
|
||||
|
||||
[Enu 0.1 demo video](https://youtu.be/upg77dMBGDE):
|
||||
|
||||
[![Enu 0.1 Demo](media/screenshot_1.png)](https://youtu.be/upg77dMBGDE)
|
||||
|
||||
### Outdated Demos
|
||||
|
||||
- [Enu 0.01 Intro and Demo](https://youtu.be/AW0PT9j976s)
|
||||
- [Introducing Enu - NimConf 2020](https://youtu.be/3l6tsKM1cY8)
|
||||
|
||||
# Programming Enu
|
||||
|
||||
## Units
|
||||
|
||||
Entities/objects in Enu are referred to as units, and have a base type of `Unit`. Currently there are `Build` units
|
||||
(voxel objects) and `Bot` units (the robot). There will be more in the future.
|
||||
|
||||
`me` is the unit that owns a script, and is equivalent to `self`/`this` in other environments. `me` was selected
|
||||
because it's easier for a child to type. `me.` can be auto-inserted when accessing properties of the unit. For example,
|
||||
`me.speed = 10` would commonly be written as `speed = 10`. There are probably bugs with this behavior. Please report
|
||||
them.
|
||||
|
||||
## Prototypes
|
||||
|
||||
Enu uses a prototype based object system for units. To allow a unit to be a prototype, you give it a name:
|
||||
|
||||
`name ghost`
|
||||
|
||||
Then create a new instance in a different script with `.new`:
|
||||
|
||||
`var ghost2 = ghost.new`
|
||||
|
||||
You can also provide parameters, which can be overridden when creating a new instance:
|
||||
|
||||
`name ghost(color = white, speed = 5, spookiness = 10)`
|
||||
|
||||
These become properties of the unit (ie `me.spookiness = 5`), but can be treated like variables in the unit's script
|
||||
due to auto `me` insertion (`spookiness = 200`).
|
||||
|
||||
To create a new instance with specific property values:
|
||||
|
||||
`var ghost2 = ghost.new(spookiness = 11)`
|
||||
|
||||
Parameters can have a default value (`spookiness = 10`), which makes them optional when creating a new instance. If they
|
||||
should be required, or there's no reasonable default value to use, specify a type (`spookiness: int`) instead, or omit
|
||||
both the value and the type, which will make the type `auto`. Because `auto` can be implicit, `name ghost(a, b: int)`
|
||||
treats parameters differently than `proc ghost(a, b: int)` would. With the proc, `a` and `b` are both `int`, whereas
|
||||
the `name` version would make `a` `auto` and `b` `int`.
|
||||
|
||||
`speed`, `color`, `global` can always be passed to a new instance, even if the prototype name doesn't include them.
|
||||
|
||||
## Random numbers
|
||||
|
||||
Generally, if an Enu command takes a number, it will be a `float`. However, `int` will auto-convert to `float`, and
|
||||
when a numeric `Range` is passed to something expecting a number, a random value within the range will be selected. So, even
|
||||
though `forward` expects a `float`, the following are all valid:
|
||||
|
||||
```nim
|
||||
forward 1.0
|
||||
forward 1
|
||||
forward 1.0..5.0 # Convert to a random float between 1.0 and 5.0
|
||||
forward 1..5 # Convert to a random int between 1 and 5, then convert the int to a float
|
||||
```
|
||||
|
||||
The `in` operator can be used between two numbers to test for random chance. For example:
|
||||
|
||||
```nim
|
||||
if 1 in 2:
|
||||
echo "I should be hit 50% of the time"
|
||||
if 1 in 100:
|
||||
echo "I should be hit 1% of the time"
|
||||
```
|
||||
|
||||
By default random numbers in Enu are based partially on the time and will be different each time a script is executed.
|
||||
However, sometimes you want randomness to create variety, but want the same values to be chosen each time a script is
|
||||
run. This is especially important when using randomness in a `Build` that you plan to manually edit later. To ensure the
|
||||
same values are selected each time a script is run, set the unit's `seed` property to some integer of your choosing,
|
||||
ie `seed = 12345` or `me.seed = 54321`.
|
||||
|
||||
Any child units instanced by a unit with a seed value will get the same seed by default. However, it will still get
|
||||
a unique random number generator, so changing the script for a child object won't impact the random numbers selected by
|
||||
the parent.
|
||||
|
||||
# Commands
|
||||
|
||||
## `move/build`
|
||||
|
||||
When dealing with a `Build` unit, commands can do different things depending on whether the unit is in `build`
|
||||
mode or `move` mode. `move` mode moves the unit around, while `build` creates new blocks. By default a `Build` is in
|
||||
`build` mode. Often you'll pass the `me` unit to `move/build`, but it's also possible to pass other units. For example:
|
||||
|
||||
```nim
|
||||
build me # generally not required, as it's the default
|
||||
|
||||
# make a shape:
|
||||
back 5
|
||||
right 3
|
||||
|
||||
# move it into position:
|
||||
move me
|
||||
up 3
|
||||
|
||||
# create another unit and add some blocks
|
||||
var enemy = ghost.new
|
||||
build enemy
|
||||
down 5
|
||||
|
||||
# move it into position
|
||||
move enemy
|
||||
up 5
|
||||
```
|
||||
|
||||
It's also possible to call commands directly against a unit instance, but they will always use `move` mode, regardless
|
||||
of which mode is in use:
|
||||
|
||||
```nim
|
||||
build enemy
|
||||
up 5 # build 5 blocks up
|
||||
enemy.up 5 # move up 5
|
||||
```
|
||||
|
||||
## `forward/back/up/down/left/right`
|
||||
|
||||
Move or build x number of blocks in the specified direction. Defaults to 1 block.
|
||||
|
||||
```nim
|
||||
forward 5
|
||||
enemy.up 2
|
||||
```
|
||||
|
||||
## `turn`
|
||||
|
||||
Turn a unit. Can be passed:
|
||||
- a number in degrees. Positive for clockwise, negative for counter-clockwise. Ex. `turn 180`
|
||||
- a direction (`forward/back/up/down/left/right`) which will turn in that direction. 90 degrees by default.
|
||||
Ex. `turn left`, or `turn up, 180`
|
||||
- a unit to turn towards. Ex. `turn player`
|
||||
- a negative unit to turn away from. Ex. `turn -player`
|
||||
|
||||
## `near(less_than = 5.0) / far(greater_than = 100.0)`
|
||||
|
||||
Returns true or false if a unit is nearer/farther than the specified distance. For example:
|
||||
|
||||
```nim
|
||||
if player.near:
|
||||
echo "the player is 5m or closer"
|
||||
|
||||
if player.far:
|
||||
echo "the player is 100m or farther"
|
||||
|
||||
if player.near(10):
|
||||
echo "the player is 10m or closer"
|
||||
|
||||
if player.far(25):
|
||||
echo "the player is 25m or farther"
|
||||
```
|
||||
|
||||
## `hit`
|
||||
|
||||
If a unit is touching another unit, return the vector of the contact. Defaults to testing against `me`. For example:
|
||||
|
||||
```nim
|
||||
if player.hit:
|
||||
echo "I'm touching the player"
|
||||
|
||||
if player.hit == UP:
|
||||
echo "The player is on top of me"
|
||||
|
||||
if player.hit(enemy1):
|
||||
echo "The player hit enemy1"
|
||||
```
|
||||
|
||||
## `position/postion=`
|
||||
|
||||
Gets or set the position of a unit as a Vector3. `me` by default.
|
||||
|
||||
```nim
|
||||
if player.hit(enemy):
|
||||
# if the player hits `enemy`, reset the player position to the center of the world.
|
||||
player.position = vec3(0, 0, 0)
|
||||
```
|
||||
|
||||
## `start_position`
|
||||
|
||||
The starting position of a unit. Missing currently, but will be in in 0.2.
|
||||
|
||||
## `speed/speed=`
|
||||
|
||||
Gets or sets the speed of a unit. `me` by default.
|
||||
|
||||
While building, speed refers to the number of blocks placed per frame. In the future this will be normalized to 60fps,
|
||||
but currently the speed is tied to the framerate. Setting speed to 0 will build everything at once.
|
||||
|
||||
While moving, this is the movement speed in meters per second.
|
||||
|
||||
Switching between build and move mode doesn't impact the speed, except in the case of switching to move mode from build
|
||||
mode with a speed of 0. `speed = 0` is extremely common for build mode, but makes things appear broken in move mode,
|
||||
as nothing will actually move, so switching to move mode with a speed of 0 will automatically reset the speed to 1.
|
||||
|
||||
## `scale/scale=`
|
||||
|
||||
Sets the scale/size of a unit. `me` by default.
|
||||
|
||||
## `glow/glow=`
|
||||
|
||||
Specifies the glow/brightness of a unit. `me` by default. Currently does nothing for bots, but will in the future.
|
||||
|
||||
## `global/global=`
|
||||
|
||||
Specifies if a unit is in global space, or the space of its parent. If `global = true` and the parent unit moves,
|
||||
child units are unaffected. If `global = false`, the child will move with its parent. Does nothing for top level units,
|
||||
as they're always global.
|
||||
|
||||
By default, new `Build` units are `global = false` and new `Bot` units are `global = true`.
|
||||
|
||||
## `rotation`
|
||||
|
||||
Gets the rotation of a unit as a Vector3.
|
||||
|
||||
## `velocity/velocity=`
|
||||
|
||||
Gets or sets the velocity of a unit, as a Vector3. Currently buggy.
|
||||
|
||||
## `color/color=`
|
||||
|
||||
Gets or sets a units color. `me` by default. For `Build` units, this only impacts blocks placed after the property
|
||||
is set. For `Bot` units this does nothing, but in the future it will change their color.
|
||||
|
||||
## `bounce`
|
||||
|
||||
Bounces a unit in the air. Currently only works for the player.
|
||||
|
||||
## `save/restore`
|
||||
|
||||
`Build` units only. `save` the position, direction, drawing state, and color of the draw point, to `restore` it later.
|
||||
Can optionally take a name string to enable saving/restoring multiple points.
|
||||
|
||||
## `reset`
|
||||
|
||||
Instantly return unit to start position and resets rotation and scale.
|
||||
|
||||
## `home`
|
||||
|
||||
Moves a unit to its start position via a `forward`, `left`, `down` sequence with appropriate values. Can fail if there
|
||||
are obstructions along the way. Compare `position` to `start_position` after running to test for success.
|
||||
|
||||
## `sleep(seconds = -1.0)`
|
||||
|
||||
Do nothing for the specified number of seconds. If no argument is provided, or the argument is < 0, this will wait for
|
||||
0.5 seconds or until unit is interrupted, which will end the `sleep` prematurely. This allows the following:
|
||||
|
||||
```nim
|
||||
forever:
|
||||
sleep()
|
||||
if player.hit:
|
||||
echo "ouch!"
|
||||
```
|
||||
|
||||
Currently, any collision will trigger an interrupt. This will be expanded in the future.
|
||||
|
||||
## `forever`
|
||||
|
||||
Alias for `while true`
|
||||
|
||||
## `cycle`
|
||||
|
||||
Alternate between a list of values, returning the next element each time the cycle is called.
|
||||
|
||||
```nim
|
||||
forever:
|
||||
sleep 1
|
||||
echo cycle("one", "two", "three")
|
||||
```
|
||||
|
||||
# Shorthand Commands
|
||||
|
||||
Many Enu command also have a 1 letter alias. These are harder to read, but can reduce friction for folks new to
|
||||
typing.
|
||||
|
||||
The aliases are:
|
||||
|
||||
- `f` - `forward`
|
||||
- `b` - `back`
|
||||
- `l` - `left`
|
||||
- `r` - `right`
|
||||
- `u` - `up`
|
||||
- `d` - `down`
|
||||
- `t` - `turn`. Can be combined with shorthand directions, so `turn right` can be expressed as `t r`
|
||||
- `o` - `while true:` (o was selected because its shape is a loop)
|
||||
- `x` - `times`. `5.x:` will run a code block 5 times.
|
||||
|
||||
In action:
|
||||
|
||||
```nim
|
||||
# draw a cube (with no top)
|
||||
10.x:
|
||||
4.x:
|
||||
f 10
|
||||
t r
|
||||
u 1
|
||||
```
|
||||
|
||||
# Actions
|
||||
|
||||
Procedures/functions in Enu are referred to as actions, mainly to avoid explaining the term procedure, subroutine, or
|
||||
function, and to tie them to [Action Loops](#action-loops) defined below. Their syntax resembles markdown lists, and
|
||||
have the same parameter rules as [prototype](#prototypes) names. That is, mostly the same
|
||||
as Nim procs, but types can be omitted, making the parameter implicitly `auto`.
|
||||
|
||||
```nim
|
||||
- hello(name):
|
||||
echo "hello ", name
|
||||
|
||||
- goodbye(name = "Vin"):
|
||||
echo "goodbye ", name
|
||||
|
||||
hello "Claire"
|
||||
goodbye "Cal"
|
||||
```
|
||||
|
||||
Action parameters are automatically shadowed by a variable with the same name and value, making them mutable within the
|
||||
action. Enu tries to avoid the concept of immutable values.
|
||||
|
||||
It's also possible to specify a return type between the closing bracket of the parameter list and the colon:
|
||||
|
||||
```nim
|
||||
- hello(name) string:
|
||||
"hello " & $name
|
||||
|
||||
echo hello("Scott")
|
||||
```
|
||||
|
||||
However, at this point it's probably better to use a `proc`.
|
||||
|
||||
# Action Loops
|
||||
|
||||
*Note for Nimians: Action Loops are state machines, and any proc can be a state. If the proc has a return value it will
|
||||
be discarded.*
|
||||
|
||||
Action Loops can help control the complexity of the logic for your units. They allow you to run complicated lists of
|
||||
actions and switch between them easily when situations change.
|
||||
|
||||
You can create your own [actions](#actions), or you can call any of the built-in Enu [commands](#commands) like
|
||||
`forward`, `back`, `turn`, `sleep`, etc.
|
||||
|
||||
## `loop`
|
||||
|
||||
An Action Loop always has one and only one current action, which it will call repeatedly until you switch to some
|
||||
other action. The default action is `nil`. The first thing a loop must do is switch from `nil` to another action, using
|
||||
the little switch arrow `->`.
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward
|
||||
# I'll go forward forever!
|
||||
```
|
||||
|
||||
The little switch arrow (`->`) will switch from the action on the left to the action on the right if it's encountered
|
||||
and the left action has just completed. If the loop goes through and no switches match, the current action will be
|
||||
run again.
|
||||
|
||||
We can control which switches get run by putting them in `if` statements.
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward
|
||||
if player.far:
|
||||
forward -> back
|
||||
if player.near:
|
||||
back -> forward
|
||||
```
|
||||
|
||||
In the above example, the loop immediately switches to `forward`, and will go forward indefinitely until one of the
|
||||
conditions is met and the action is switched to something else. If the player gets too far away (more than 100m) and
|
||||
the action is `forward`, the action will be switched to `back`. If the player is near (5m) and the action is `back`,
|
||||
it will switch to `forward`. However, if the player is near and the action is `forward`, nothing will change. The
|
||||
`if player.near` statement will be true, but `back -> forward` is ignored, since the current action isn't `back`.
|
||||
|
||||
If you want your loop to end at some point, you can switch back to `nil`:
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward(10) # Some actions can take additional parameters.
|
||||
forward -> nil
|
||||
# I'll run `forward(10)` a single time, then stop and end the loop.
|
||||
```
|
||||
|
||||
Let's look at something more complicated, and introduce the big switch arrow (`==>`) and change actions.
|
||||
|
||||
```nim
|
||||
- wander:
|
||||
speed = 1
|
||||
forward 5..10
|
||||
turn -45..45
|
||||
|
||||
- charge:
|
||||
speed = 5
|
||||
turn player
|
||||
forward 1
|
||||
|
||||
- flee:
|
||||
speed = 3
|
||||
turn -player
|
||||
forward 1
|
||||
|
||||
- attack:
|
||||
player.bounce 5
|
||||
turn 360
|
||||
|
||||
var health = 3
|
||||
|
||||
loop:
|
||||
nil -> wander
|
||||
if 1 in 50:
|
||||
# when each `wander` action finishes, there's a 1 in 50 (2%) chance of our unit getting a sudden
|
||||
# burst of energy and switching to the `charge` action. Otherwise we just keep wandering.
|
||||
wander -> charge
|
||||
|
||||
if health == 0:
|
||||
# we died. Exit the loop. We want this to happen immediately, not after the action finishes, so we use
|
||||
# the big switch arrow. We use the special `any` action to say that this should happen regardless
|
||||
# of the running action.
|
||||
any ==> nil
|
||||
|
||||
if player.hit:
|
||||
# if the player touches us while we're wandering, we flee. We want it to happen the instant the player touches us,
|
||||
# not when our current `wander` is done, so we use the big switch arrow
|
||||
wander ==> flee:
|
||||
# this is a change action. If the action switches here, the change action will also run once.
|
||||
health -= 1
|
||||
# if the player touches us while we're charging, we attack immediately.
|
||||
charge ==> attack
|
||||
|
||||
if player.far:
|
||||
# if we're fleeing the player, we go back to wandering when they get far away
|
||||
flee -> wander
|
||||
|
||||
# Switch to wander when our attack is done. We always want this to happen, so it isn't in a
|
||||
# conditional. It only does anything if the current action is `attack`, and we only do it when
|
||||
# the attack is done becuase we're using the little switch arrow
|
||||
attack -> wander
|
||||
|
||||
```
|
||||
|
||||
## Child Loops
|
||||
|
||||
Actions are generally just a simple lists of commands. It's fine to put logic in them, but anything complicated
|
||||
will quickly get unwieldy. Imagine we have a unit that performs two complicated actions, `find_treasure` and
|
||||
`fight_monster`. `find_treasure` might need to `navigate` an area, `locate` items of interest, `interact` with them,
|
||||
then return to `home_base` to deposit them. `fight_monster` could require actions like `evade`, `attack`, `hide`, and
|
||||
`flee`.
|
||||
|
||||
Combining all of this in a single action loop would give us a lot of actions, many of which are mostly unrelated,
|
||||
and managing our switches could get very complicated. However, making them entirely separate isn't ideal either, as
|
||||
there's probably some common functionality between them (die if our health gets too low, respawn if we get stuck). We
|
||||
also need to switch between the two actions.
|
||||
|
||||
A good way to manage this is by making `find_treasure` and `fight_monster` child loops rather than regular actions.
|
||||
We can treat them like regular actions in our main `loop`, but when we switch to them they'll be able to perform
|
||||
more sophisticated logic than a normal action could. In addition, our main loop will continue to run along side
|
||||
the the child loop, so we can quickly switch out of the child loop with a big switch arrow `==>` in response to
|
||||
certain conditions, without either loop needing to worry about higher level concerns.
|
||||
|
||||
Our main loop could look something like this:
|
||||
|
||||
```nim
|
||||
loop find_treasure:
|
||||
# our treasure logic goes here. This loop doesn't have an exit condition.
|
||||
if treasure_found:
|
||||
# We found it. Start looking again. This will switch from any action apart
|
||||
# from `look_for_chest`
|
||||
others ==> look_for_chest
|
||||
|
||||
loop fight_monster:
|
||||
# fight logic here. This loop should exit when the monster dies
|
||||
if monster.dead:
|
||||
any ==> nil
|
||||
|
||||
loop:
|
||||
# We want our unit to find_treasure indefinately. `find_treasure` doesn't exit (switch to nil)
|
||||
nil -> find_treasure
|
||||
if monster.near:
|
||||
# find_treasure doesn't need to know anything about monsters. We can break out of it
|
||||
# with a big switch arrow if we encounter one.
|
||||
find_treasure ==> fight_monster
|
||||
|
||||
# fight_monster does have an exit condition (the monster dies), so we can wait for it to finish
|
||||
# using the little switch arrow, then go back to finding treasure.
|
||||
fight_monster -> find_treasure
|
||||
|
||||
if health == 0:
|
||||
# if our health drops to 0, it doesn't matter what else we're doing. Die immediately. Break out
|
||||
# of any action (except die) with a big switch arrow.
|
||||
(any, -die) ==> die
|
||||
|
||||
# this would also work:
|
||||
# others ==> die
|
||||
if stuck:
|
||||
# respawn if we're stuck, but only from our two child loops. We don't want to respawn if we're
|
||||
# already respawning, or we're dead. Implementation of `respawn` not shown.
|
||||
(find_treasure, fight_monster) ==> respawn
|
||||
|
||||
# we're done respawning. Treasure time!
|
||||
respawn -> find_treasure
|
||||
```
|
||||
|
||||
Child loops can also call other child loops, in which case both the parent and grandparent loops can use `==>` to break
|
||||
out of the top level loop. There's no set limit to nesting depth.
|
||||
|
||||
## `->` Little Switch Arrow
|
||||
|
||||
Switches from one action to another, after the first action has finished running.
|
||||
|
||||
```nim
|
||||
draw_box -> draw_stairs
|
||||
```
|
||||
|
||||
## `==>` Big Switch Arrow
|
||||
|
||||
Switches from one action to another immediately. Will interrupt the running action.
|
||||
|
||||
```nim
|
||||
if player.near:
|
||||
sleep ==> offer_quest
|
||||
```
|
||||
|
||||
## More about Action Loops
|
||||
|
||||
### `as`
|
||||
|
||||
Actions are just procedures, and they can take parameters. Sometimes you want to run the same action
|
||||
in different situations with a different action name. You could do this by creating a new action that
|
||||
calls the first one, but you can also use `as` to give an action a different name.
|
||||
|
||||
```nim
|
||||
# explore action and health var not shown.
|
||||
- flee(distance = 100):
|
||||
turn -player
|
||||
forward distance
|
||||
|
||||
loop:
|
||||
nil -> explore
|
||||
if player.near and health > 2:
|
||||
explore -> flee
|
||||
elif player.near and health <= 2:
|
||||
explore ==> flee(200) as really_flee
|
||||
if player.far:
|
||||
flee -> explore
|
||||
if player.far(150):
|
||||
really_flee -> explore
|
||||
```
|
||||
|
||||
### Special from actions
|
||||
|
||||
Often loops will switch from a single action to another. However, sometimes you want to allow switching from
|
||||
a variety of actions.
|
||||
|
||||
- `any -> some_action` - switch from any action to the target action. This will switch (and run any change action)
|
||||
even if we're already running the target action. In this example we used a little switch arrow, so it still won't
|
||||
happen until the current action actually completes.
|
||||
- `others -> some_action` - Same as `any`, but it excludes the target action.
|
||||
- `(action1, action2)` - Multiple from actions can be supported by putting them in a tuple.
|
||||
- `(any, -action2, -action3)` - Switch from any action except `action2` and `action3`.
|
||||
|
||||
### When do action loops run?
|
||||
|
||||
An action loops will run whenever its executing action finishes. In addition, action loops will run
|
||||
every 0.5 seconds, and when something triggers an interrupt. Currently only the start and end of a collision with the
|
||||
player trigger an interrupt, but this will be expanded.
|
||||
|
||||
When using child loops, the top level loop runs first, then walks down the stack of loops until the currently executing
|
||||
loop is reached.
|
||||
|
||||
# Examples
|
||||
|
||||
*TODO: Include examples of new 0.2 functionality*
|
||||
|
||||
Draw a square:
|
||||
```nim
|
||||
forward 10
|
||||
right 10
|
||||
back 10
|
||||
left 10
|
||||
```
|
||||
or:
|
||||
```nim
|
||||
4.times:
|
||||
forward 10
|
||||
turn_right()
|
||||
```
|
||||
![Square Example](media/square_example.png)
|
||||
Create a twisting tower:
|
||||
```nim
|
||||
var
|
||||
length = 20
|
||||
height = 50
|
||||
|
||||
height.times:
|
||||
left length / 2
|
||||
back length / 2
|
||||
4.times:
|
||||
forward length
|
||||
turn right
|
||||
forward length / 2
|
||||
right length / 2
|
||||
turn 5
|
||||
up 1
|
||||
```
|
||||
![Tower Example](media/tower_example.png)
|
||||
|
||||
Draw randomly:
|
||||
```nim
|
||||
up 10
|
||||
forward 10
|
||||
(50..100).times:
|
||||
forward 2..5
|
||||
turn -180..180
|
||||
up 0..2
|
||||
```
|
||||
![Random Example](media/random_example.png)
|
||||
|
||||
Set the color to blue randomly with a 1 in 50 chance. Otherwise set it to white:
|
||||
```nim
|
||||
if 1 in 50:
|
||||
color = blue
|
||||
else:
|
||||
color = white
|
||||
```
|
||||
or as a one-liner:
|
||||
```nim
|
||||
color = if 1 in 50: blue else: white
|
||||
```
|
||||
![Random Color Example](media/random_color_example.png)
|
||||
|
||||
Move forward 10 times, cycling through colors:
|
||||
```nim
|
||||
10.times:
|
||||
color = cycle(red, black, blue)
|
||||
forward 1
|
||||
```
|
||||
![Color Cycle Example](media/cycle_example.png)
|
||||
|
||||
# Install
|
||||
|
||||
Download from https://github.com/dsrw/enu/releases. The Windows version isn't signed, and
|
||||
UAC will warn that it's untrusted. This will be fixed in a future release.
|
||||
|
||||
The Linux version hasn't been tested particularly well, but it works for me under Ubuntu 20.04. Please report any issues.
|
||||
|
||||
The world format will change in a future release. Worlds created in 0.1 won't be supported in future versions.
|
||||
|
||||
# Build and Run
|
||||
|
||||
```console
|
||||
$ nimble prereqs
|
||||
$ nimble build
|
||||
$ nimble import_assets
|
||||
$ nimble start
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Enu requires a custom Godot version, which lives in `vendor/godot`. This will be fetched
|
||||
and built as part of `nimble prereqs`.
|
||||
|
||||
See https://docs.godotengine.org/en/3.2/development/compiling/index.html
|
||||
|
||||
# Usage
|
||||
|
||||
## Keyboard/Mouse
|
||||
|
||||
- `ESC` - toggle mouse capture and to dismiss editor windows. Reloads script changes.
|
||||
- `W, A, S, D` - move around when mouse is captured.
|
||||
- `Space` - jump. Double jump to toggle flying. Hold to go up while flying.
|
||||
- `Shift` - run.
|
||||
- `C` - go down while flying.
|
||||
- `~` - toggle the console.
|
||||
- `F` - toggle fullscreen.
|
||||
- `1` - enter edit mode.
|
||||
- `2 - 9` - change active action.
|
||||
- `Mouse Wheel Up/Down` - change active action.
|
||||
- `Alt` - reload script changes. Hold to temporarily capture the mouse and move, so you can
|
||||
change your view without having to switch away from what you're doing.
|
||||
- `Cmd+P / Ctrl+P` - Pause scripts.
|
||||
- `Cmd+Shift+S / Ctrl+Shift+S` - Save and reload all scripts, then pause. If you have a script that makes a unit
|
||||
inaccessible (ex. moves the unit below the ground) this is a way to get things back to their start positions so they
|
||||
can be edited.
|
||||
- `Left Click` - Place a block/object or open the code for the currently selected object.
|
||||
- `Right Click` - Remove a block/object.
|
||||
|
||||
## XBox / Playstation Controller
|
||||
|
||||
- `Left Stick` - move.
|
||||
- `Right Stick` - change view.
|
||||
- `A / X` - jump. Double jump to toggle flying. Hold to go up while flying.
|
||||
- `B / ◯` - go down while flying. Dismiss code editor.
|
||||
- `Y / △` - toggle edit mode.
|
||||
- `L1 / R1` - change active action.
|
||||
- `L2` - place a block/object or open the code for the currently selected object.
|
||||
- `R2` - remove a block/object.
|
||||
- `L3` - run.
|
||||
|
||||
Enu currently includes 6 block types/colors, and 1 object model (a robot). This will be greatly
|
||||
expanded in the future.
|
||||
|
||||
## Building
|
||||
|
||||
Drop a block or robot with the left mouse button/controller trigger, remove it with the right. Adjoining blocks will be
|
||||
combined into a single structure. With the mouse captured, building works more or less like MineCraft. Release the mouse
|
||||
by pressing ESC to draw blocks using the mouse cursor.
|
||||
|
||||
Code by switching to the code tool by left clicking/triggering on an object or structure. Changes are applied when the
|
||||
code window is closed (ESC key) or CTRL is pressed. Holding CTRL will also temprarly grab the mouse and allow you to
|
||||
change your position.
|
||||
|
||||
# Config
|
||||
|
||||
The Enu data directory lives in `~/Library/Application Support/enu` on Mac, `%AppData%\enu` on Windows, and
|
||||
`~/.local/share/enu` on Linux. `config.json` has a few configurable options:
|
||||
|
||||
`mega_pixels`: The render resolution, in mega pixels. Increase for more detail. Decrease for better performance.
|
||||
|
||||
`font_size`: The font size. DPI is currently ignored, so hidpi screens will require a higher number.
|
||||
|
||||
`dock_icon_size`: Size of the icons in the dock. DPI is currently ignored, so hidpi screens will require a higher number.
|
||||
|
||||
`world`: The world/project to load. Change this to create a new world.
|
||||
|
||||
`show_stats`: Show FPS and other stats.
|
||||
|
||||
`start_full_screen`: Whether to start Enu full screen, or in a window.
|
||||
|
||||
`semicolon_as_colon`: Both `;` and `:` will be interpreted as `:`, allowing `:` to be typed without shift. Sometimes useful for new typists.
|
||||
|
||||
# TODO for 0.2
|
||||
|
||||
### Pivot point
|
||||
|
||||
Currently it isn't possible to change the pivot point for a unit, and the default point isn't properly centered for
|
||||
most builds, making it difficult to rotate builds nicely. Enu 0.2 will use the draw point for the pivot point, allowing
|
||||
it to be moved, and will shift everything over 0.5m, allowing most builds to rotate in a balanced way. There will
|
||||
also be a command to move the draw point (and thus the pivot point) in the exact center of a build.
|
||||
|
||||
### REPL?
|
||||
|
||||
We need a way to switch worlds without editing a config file. Adding a REPL may be the easiest way to accomplish
|
||||
this, and is something I wanted to add anyway.
|
||||
|
||||
### Testing and bug fixes
|
||||
|
||||
Enu has been under heavy development for a year without a great deal of testing, so there are undoubtedly bugs. I
|
||||
believe these will be minor, but there are probably a fair number of them.
|
||||
|
||||
### v0.2.x - v0.3
|
||||
|
||||
- [ ] iOS support.
|
||||
- [ ] Move script execution off the main thread.
|
||||
- [ ] Inventory
|
||||
- [ ] Settings UI
|
||||
- [ ] Allow the editor pane and action bar to be resized from within Enu.
|
||||
- [ ] Better collision support
|
||||
- [ ] Blocks of any color
|
||||
- [ ] In game help
|
||||
- [ ] Easy way to switch worlds in-game
|
||||
- [ ] Support loading worlds from anywhere, not just the Enu data directory
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import nimibook
|
||||
|
||||
var book = init_book_with_toc:
|
||||
section "Introduction", "intro.nim":
|
||||
entry "Goals", "intro/goals.nim"
|
||||
entry "Demos", "intro/demos.nim"
|
||||
entry "Install or Build", "intro/building.nim"
|
||||
|
||||
section "Basics", "basics.nim":
|
||||
entry "Controls and Usage", "basics/controls.nim"
|
||||
entry "Programming", "basics/programming.nim"
|
||||
entry "Config", "basics/config.nim"
|
||||
|
||||
section "Coding Enu", "coding.nim":
|
||||
entry "Concepts", "coding/concepts.nim"
|
||||
entry "Commands", "coding/commands.nim"
|
||||
entry "Shorthand Commands", "coding/shorthand.nim"
|
||||
entry "Random Numbers", "coding/random_numbers.nim"
|
||||
|
||||
section "Action Loops", "action_loops.nim":
|
||||
entry "Actions", "action_loops/actions.nim"
|
||||
entry "Loops", "action_loops/loops.nim"
|
||||
entry "Arrows", "action_loops/arrows.nim"
|
||||
entry "Child Loops", "action_loops/child_loops.nim"
|
||||
entry "More About Action Loops", "action_loops/more.nim"
|
||||
|
||||
section "Examples", "examples.nim":
|
||||
entry "Shapes", "example/shapes.nim"
|
||||
|
||||
entry "TODO", "todo.nim"
|
||||
|
||||
nimibook_cli(book)
|
|
@ -0,0 +1,26 @@
|
|||
# Action Loops
|
||||
|
||||
Action loops let you control the behavior of a unit in a (somewhat) straightforward way. At least I think it's straightforward. You might disagree.
|
||||
|
||||
They look like this:
|
||||
|
||||
```nim
|
||||
- boogie:
|
||||
position = position.fuzzed(0.5)
|
||||
|
||||
- shake:
|
||||
left 1
|
||||
right 1
|
||||
|
||||
- rest:
|
||||
sleep 5
|
||||
|
||||
loop:
|
||||
nil -> shake
|
||||
if player.near:
|
||||
shake -> boogie
|
||||
elif player.far(50):
|
||||
(rest, boogie) -> shake
|
||||
elif player.far(200):
|
||||
(boogie, shake) -> rest
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "action_loops.md"
|
|
@ -0,0 +1,51 @@
|
|||
import nimib, nimibook, pretty
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
# Actions
|
||||
|
||||
Procedures/functions in Enu are referred to as actions, mainly to avoid explaining the term procedure, subroutine, or
|
||||
function, and to tie them to [Action Loops](#action-loops) defined below. Their syntax resembles markdown lists, and
|
||||
have the same parameter rules as [prototype](#prototypes) names. That is, mostly the same
|
||||
as Nim procs, but types can be omitted, making the parameter implicitly `auto`.
|
||||
|
||||
```nim
|
||||
- hello(name):
|
||||
echo "hello ", name
|
||||
|
||||
- goodbye(name = "Vin"):
|
||||
echo "goodbye ", name
|
||||
|
||||
hello "Claire"
|
||||
goodbye "Cal"
|
||||
```
|
||||
|
||||
Action parameters are automatically shadowed by a variable with the same name and value, making them mutable within the
|
||||
action. Enu tries to avoid the concept of immutable values.
|
||||
|
||||
It's also possible to specify a return type between the closing bracket of the parameter list and the colon:
|
||||
|
||||
```nim
|
||||
- hello(name) string:
|
||||
"hello " & $name
|
||||
|
||||
echo hello("Scott")
|
||||
```
|
||||
|
||||
However, at this point it's probably better to use a `proc`.
|
||||
|
||||
# Action Loops
|
||||
|
||||
*Note for Nimians: Action Loops are state machines, and any proc can be a state. If the proc has a return value it will
|
||||
be discarded.*
|
||||
|
||||
Action Loops can help control the complexity of the logic for your units. They allow you to run complicated lists of
|
||||
actions and switch between them easily when situations change.
|
||||
|
||||
You can create your own [actions](#actions), or you can call any of the built-in Enu [commands](#commands) like
|
||||
`forward`, `back`, `turn`, `sleep`, etc.
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,25 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## `->` Little Switch Arrow
|
||||
|
||||
Switches from one action to another, after the first action has finished running.
|
||||
|
||||
```nim
|
||||
draw_box -> draw_stairs
|
||||
```
|
||||
|
||||
## `==>` Big Switch Arrow
|
||||
|
||||
Switches from one action to another immediately. Will interrupt the running action.
|
||||
|
||||
```nim
|
||||
if player.near:
|
||||
sleep ==> offer_quest
|
||||
```
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,73 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## Child Loops
|
||||
|
||||
Actions are generally just a simple lists of commands. It's fine to put logic in them, but anything complicated
|
||||
will quickly get unwieldy. Imagine we have a unit that performs two complicated actions, `find_treasure` and
|
||||
`fight_monster`. `find_treasure` might need to `navigate` an area, `locate` items of interest, `interact` with them,
|
||||
then return to `home_base` to deposit them. `fight_monster` could require actions like `evade`, `attack`, `hide`, and
|
||||
`flee`.
|
||||
|
||||
Combining all of this in a single action loop would give us a lot of actions, many of which are mostly unrelated,
|
||||
and managing our switches could get very complicated. However, making them entirely separate isn't ideal either, as
|
||||
there's probably some common functionality between them (die if our health gets too low, respawn if we get stuck). We
|
||||
also need to switch between the two actions.
|
||||
|
||||
A good way to manage this is by making `find_treasure` and `fight_monster` child loops rather than regular actions.
|
||||
We can treat them like regular actions in our main `loop`, but when we switch to them they'll be able to perform
|
||||
more sophisticated logic than a normal action could. In addition, our main loop will continue to run along side
|
||||
the the child loop, so we can quickly switch out of the child loop with a big switch arrow `==>` in response to
|
||||
certain conditions, without either loop needing to worry about higher level concerns.
|
||||
|
||||
Our main loop could look something like this:
|
||||
|
||||
```nim
|
||||
loop find_treasure:
|
||||
# our treasure logic goes here. This loop doesn't have an exit condition.
|
||||
if treasure_found:
|
||||
# We found it. Start looking again. This will switch from any action apart
|
||||
# from `look_for_chest`
|
||||
others ==> look_for_chest
|
||||
|
||||
loop fight_monster:
|
||||
# fight logic here. This loop should exit when the monster dies
|
||||
if monster.dead:
|
||||
any ==> nil
|
||||
|
||||
loop:
|
||||
# We want our unit to find_treasure indefinately. `find_treasure` doesn't exit (switch to nil)
|
||||
nil -> find_treasure
|
||||
if monster.near:
|
||||
# find_treasure doesn't need to know anything about monsters. We can break out of it
|
||||
# with a big switch arrow if we encounter one.
|
||||
find_treasure ==> fight_monster
|
||||
|
||||
# fight_monster does have an exit condition (the monster dies), so we can wait for it to finish
|
||||
# using the little switch arrow, then go back to finding treasure.
|
||||
fight_monster -> find_treasure
|
||||
|
||||
if health == 0:
|
||||
# if our health drops to 0, it doesn't matter what else we're doing. Die immediately. Break out
|
||||
# of any action (except die) with a big switch arrow.
|
||||
(any, -die) ==> die
|
||||
|
||||
# this would also work:
|
||||
# others ==> die
|
||||
if stuck:
|
||||
# respawn if we're stuck, but only from our two child loops. We don't want to respawn if we're
|
||||
# already respawning, or we're dead. Implementation of `respawn` not shown.
|
||||
(find_treasure, fight_monster) ==> respawn
|
||||
|
||||
# we're done respawning. Treasure time!
|
||||
respawn -> find_treasure
|
||||
```
|
||||
|
||||
Child loops can also call other child loops, in which case both the parent and grandparent loops can use `==>` to break
|
||||
out of the top level loop. There's no set limit to nesting depth.
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,108 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## `loop`
|
||||
|
||||
An Action Loop always has one and only one current action, which it will call repeatedly until you switch to some
|
||||
other action. The default action is `nil`. The first thing a loop must do is switch from `nil` to another action, using
|
||||
the little switch arrow `->`.
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward
|
||||
# I'll go forward forever!
|
||||
```
|
||||
|
||||
The little switch arrow (`->`) will switch from the action on the left to the action on the right if it's encountered
|
||||
and the left action has just completed. If the loop goes through and no switches match, the current action will be
|
||||
run again.
|
||||
|
||||
We can control which switches get run by putting them in `if` statements.
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward
|
||||
if player.far:
|
||||
forward -> back
|
||||
if player.near:
|
||||
back -> forward
|
||||
```
|
||||
|
||||
In the above example, the loop immediately switches to `forward`, and will go forward indefinitely until one of the
|
||||
conditions is met and the action is switched to something else. If the player gets too far away (more than 100m) and
|
||||
the action is `forward`, the action will be switched to `back`. If the player is near (5m) and the action is `back`,
|
||||
it will switch to `forward`. However, if the player is near and the action is `forward`, nothing will change. The
|
||||
`if player.near` statement will be true, but `back -> forward` is ignored, since the current action isn't `back`.
|
||||
|
||||
If you want your loop to end at some point, you can switch back to `nil`:
|
||||
|
||||
```nim
|
||||
loop:
|
||||
nil -> forward(10) # Some actions can take additional parameters.
|
||||
forward -> nil
|
||||
# I'll run `forward(10)` a single time, then stop and end the loop.
|
||||
```
|
||||
|
||||
Let's look at something more complicated, and introduce the big switch arrow (`==>`) and change actions.
|
||||
|
||||
```nim
|
||||
- wander:
|
||||
speed = 1
|
||||
forward 5..10
|
||||
turn -45..45
|
||||
|
||||
- charge:
|
||||
speed = 5
|
||||
turn player
|
||||
forward 1
|
||||
|
||||
- flee:
|
||||
speed = 3
|
||||
turn -player
|
||||
forward 1
|
||||
|
||||
- attack:
|
||||
player.bounce 5
|
||||
turn 360
|
||||
|
||||
var health = 3
|
||||
|
||||
loop:
|
||||
nil -> wander
|
||||
if 1 in 50:
|
||||
# when each `wander` action finishes, there's a 1 in 50 (2%) chance of our unit getting a sudden
|
||||
# burst of energy and switching to the `charge` action. Otherwise we just keep wandering.
|
||||
wander -> charge
|
||||
|
||||
if health == 0:
|
||||
# we died. Exit the loop. We want this to happen immediately, not after the action finishes, so we use
|
||||
# the big switch arrow. We use the special `any` action to say that this should happen regardless
|
||||
# of the running action.
|
||||
any ==> nil
|
||||
|
||||
if player.hit:
|
||||
# if the player touches us while we're wandering, we flee. We want it to happen the instant the player touches us,
|
||||
# not when our current `wander` is done, so we use the big switch arrow
|
||||
wander ==> flee:
|
||||
# this is a change action. If the action switches here, the change action will also run once.
|
||||
health -= 1
|
||||
# if the player touches us while we're charging, we attack immediately.
|
||||
charge ==> attack
|
||||
|
||||
if player.far:
|
||||
# if we're fleeing the player, we go back to wandering when they get far away
|
||||
flee -> wander
|
||||
|
||||
# Switch to wander when our attack is done. We always want this to happen, so it isn't in a
|
||||
# conditional. It only does anything if the current action is `attack`, and we only do it when
|
||||
# the attack is done becuase we're using the little switch arrow
|
||||
attack -> wander
|
||||
|
||||
```
|
||||
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,55 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## More about Action Loops
|
||||
|
||||
### `as`
|
||||
|
||||
Actions are just procedures, and they can take parameters. Sometimes you want to run the same action
|
||||
in different situations with a different action name. You could do this by creating a new action that
|
||||
calls the first one, but you can also use `as` to give an action a different name.
|
||||
|
||||
```nim
|
||||
# explore action and health var not shown.
|
||||
- flee(distance = 100):
|
||||
turn -player
|
||||
forward distance
|
||||
|
||||
loop:
|
||||
nil -> explore
|
||||
if player.near and health > 2:
|
||||
explore -> flee
|
||||
elif player.near and health <= 2:
|
||||
explore ==> flee(200) as really_flee
|
||||
if player.far:
|
||||
flee -> explore
|
||||
if player.far(150):
|
||||
really_flee -> explore
|
||||
```
|
||||
|
||||
### Special from actions
|
||||
|
||||
Often loops will switch from a single action to another. However, sometimes you want to allow switching from
|
||||
a variety of actions.
|
||||
|
||||
- `any -> some_action` - switch from any action to the target action. This will switch (and run any change action)
|
||||
even if we're already running the target action. In this example we used a little switch arrow, so it still won't
|
||||
happen until the current action actually completes.
|
||||
- `others -> some_action` - Same as `any`, but it excludes the target action.
|
||||
- `(action1, action2)` - Multiple from actions can be supported by putting them in a tuple.
|
||||
- `(any, -action2, -action3)` - Switch from any action except `action2` and `action3`.
|
||||
|
||||
### When do action loops run?
|
||||
|
||||
An action loops will run whenever its executing action finishes. In addition, action loops will run
|
||||
every 0.5 seconds, and when something triggers an interrupt. Currently only the start and end of a collision with the
|
||||
player trigger an interrupt, but this will be expanded.
|
||||
|
||||
When using child loops, the top level loop runs first, then walks down the stack of loops until the currently executing
|
||||
loop is reached.
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,6 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: "## Basics"
|
||||
nb_save
|
|
@ -0,0 +1,18 @@
|
|||
# Config
|
||||
|
||||
The Enu data directory lives in `~/Library/Application Support/enu` on Mac, `%AppData%\enu` on Windows, and
|
||||
`~/.local/share/enu` on Linux. `config.json` has a few configurable options:
|
||||
|
||||
`mega_pixels`: The render resolution, in mega pixels. Increase for more detail. Decrease for better performance.
|
||||
|
||||
`font_size`: The font size. DPI is currently ignored, so hidpi screens will require a higher number.
|
||||
|
||||
`dock_icon_size`: Size of the icons in the dock. DPI is currently ignored, so hidpi screens will require a higher number.
|
||||
|
||||
`world`: The world/project to load. Change this to create a new world.
|
||||
|
||||
`show_stats`: Show FPS and other stats.
|
||||
|
||||
`start_full_screen`: Whether to start Enu full screen, or in a window.
|
||||
|
||||
`semicolon_as_colon`: Both `;` and `:` will be interpreted as `:`, allowing `:` to be typed without shift. Sometimes useful for new typists.
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "config.md"
|
|
@ -0,0 +1,47 @@
|
|||
# Usage
|
||||
|
||||
## Keyboard/Mouse
|
||||
|
||||
- `ESC` - toggle mouse capture and to dismiss editor windows. Reloads script changes.
|
||||
- `W, A, S, D` - move around when mouse is captured.
|
||||
- `Space` - jump. Double jump to toggle flying. Hold to go up while flying.
|
||||
- `Shift` - run.
|
||||
- `C` - go down while flying.
|
||||
- `~` - toggle the console.
|
||||
- `F` - toggle fullscreen.
|
||||
- `1` - enter edit mode.
|
||||
- `2 - 9` - change active action.
|
||||
- `Mouse Wheel Up/Down` - change active action.
|
||||
- `Alt` - reload script changes. Hold to temporarily capture the mouse and move, so you can
|
||||
change your view without having to switch away from what you're doing.
|
||||
- `Cmd+P / Ctrl+P` - Pause scripts.
|
||||
- `Cmd+Shift+S / Ctrl+Shift+S` - Save and reload all scripts, then pause. If you have a script that makes a unit
|
||||
inaccessible (ex. moves the unit below the ground) this is a way to get things back to their start positions so they
|
||||
can be edited.
|
||||
- `Left Click` - Place a block/object or open the code for the currently selected object.
|
||||
- `Right Click` - Remove a block/object.
|
||||
|
||||
## XBox / Playstation Controller
|
||||
|
||||
- `Left Stick` - move.
|
||||
- `Right Stick` - change view.
|
||||
- `A / X` - jump. Double jump to toggle flying. Hold to go up while flying.
|
||||
- `B / ◯` - go down while flying. Dismiss code editor.
|
||||
- `Y / △` - toggle edit mode.
|
||||
- `L1 / R1` - change active action.
|
||||
- `L2` - place a block/object or open the code for the currently selected object.
|
||||
- `R2` - remove a block/object.
|
||||
- `L3` - run.
|
||||
|
||||
Enu currently includes 6 block types/colors, and 1 object model (a robot). This will be greatly
|
||||
expanded in the future.
|
||||
|
||||
## Building
|
||||
|
||||
Drop a block or robot with the left mouse button/controller trigger, remove it with the right. Adjoining blocks will be
|
||||
combined into a single structure. With the mouse captured, building works more or less like MineCraft. Release the mouse
|
||||
by pressing ESC to draw blocks using the mouse cursor.
|
||||
|
||||
Code by switching to the code tool by left clicking/triggering on an object or structure. Changes are applied when the
|
||||
code window is closed (ESC key) or CTRL is pressed. Holding CTRL will also temprarly grab the mouse and allow you to
|
||||
change your position.
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "controls.md"
|
|
@ -0,0 +1,6 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: "## Programming"
|
||||
nb_save
|
|
@ -1,4 +1,4 @@
|
|||
# Parts of an Enu Program
|
||||
# Coding Enu!!
|
||||
|
||||
Enu is programmed with a language called [Nim](https://nim-lang.org). This tutorial teach some of the
|
||||
basics of Nim, as well as a few special features that are unique to Enu.
|
||||
|
@ -12,7 +12,7 @@ general information, such as the author of the code and when it was written.
|
|||
They start with a `#` sign. Everything else on the line is ignored.
|
||||
|
||||
```nim
|
||||
# Copyright Scott Wadden, 2022
|
||||
# Copyright Scott Wadden, 2023
|
||||
|
||||
var last_row = false # we only want to change colors on the last row
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "coding.md"
|
|
@ -0,0 +1,199 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
# Commands
|
||||
|
||||
## `move/build`
|
||||
|
||||
When dealing with a `Build` unit, commands can do different things depending on whether the unit is in `build`
|
||||
mode or `move` mode. `move` mode moves the unit around, while `build` creates new blocks. By default a `Build` is in
|
||||
`build` mode. Often you'll pass the `me` unit to `move/build`, but it's also possible to pass other units. For example:
|
||||
|
||||
```nim
|
||||
build me # generally not required, as it's the default
|
||||
|
||||
# make a shape:
|
||||
back 5
|
||||
right 3
|
||||
|
||||
# move it into position:
|
||||
move me
|
||||
up 3
|
||||
|
||||
# create another unit and add some blocks
|
||||
var enemy = ghost.new
|
||||
build enemy
|
||||
down 5
|
||||
|
||||
# move it into position
|
||||
move enemy
|
||||
up 5
|
||||
```
|
||||
|
||||
It's also possible to call commands directly against a unit instance, but they will always use `move` mode, regardless
|
||||
of which mode is in use:
|
||||
|
||||
```nim
|
||||
build enemy
|
||||
up 5 # build 5 blocks up
|
||||
enemy.up 5 # move up 5
|
||||
```
|
||||
|
||||
## `forward/back/up/down/left/right`
|
||||
|
||||
Move or build x number of blocks in the specified direction. Defaults to 1 block.
|
||||
|
||||
```nim
|
||||
forward 5
|
||||
enemy.up 2
|
||||
```
|
||||
|
||||
## `turn`
|
||||
|
||||
Turn a unit. Can be passed:
|
||||
- a number in degrees. Positive for clockwise, negative for counter-clockwise. Ex. `turn 180`
|
||||
- a direction (`forward/back/up/down/left/right`) which will turn in that direction. 90 degrees by default.
|
||||
Ex. `turn left`, or `turn up, 180`
|
||||
- a unit to turn towards. Ex. `turn player`
|
||||
- a negative unit to turn away from. Ex. `turn -player`
|
||||
|
||||
## `near(less_than = 5.0) / far(greater_than = 100.0)`
|
||||
|
||||
Returns true or false if a unit is nearer/farther than the specified distance. For example:
|
||||
|
||||
```nim
|
||||
if player.near:
|
||||
echo "the player is 5m or closer"
|
||||
|
||||
if player.far:
|
||||
echo "the player is 100m or farther"
|
||||
|
||||
if player.near(10):
|
||||
echo "the player is 10m or closer"
|
||||
|
||||
if player.far(25):
|
||||
echo "the player is 25m or farther"
|
||||
```
|
||||
|
||||
## `hit`
|
||||
|
||||
If a unit is touching another unit, return the vector of the contact. Defaults to testing against `me`. For example:
|
||||
|
||||
```nim
|
||||
if player.hit:
|
||||
echo "I'm touching the player"
|
||||
|
||||
if player.hit == UP:
|
||||
echo "The player is on top of me"
|
||||
|
||||
if player.hit(enemy1):
|
||||
echo "The player hit enemy1"
|
||||
```
|
||||
|
||||
## `position/postion=`
|
||||
|
||||
Gets or set the position of a unit as a Vector3. `me` by default.
|
||||
|
||||
```nim
|
||||
if player.hit(enemy):
|
||||
# if the player hits `enemy`, reset the player position to the center of the world.
|
||||
player.position = vec3(0, 0, 0)
|
||||
```
|
||||
|
||||
## `start_position`
|
||||
|
||||
The starting position of a unit. Missing currently, but will be in in 0.2.
|
||||
|
||||
## `speed/speed=`
|
||||
|
||||
Gets or sets the speed of a unit. `me` by default.
|
||||
|
||||
While building, speed refers to the number of blocks placed per frame. In the future this will be normalized to 60fps,
|
||||
but currently the speed is tied to the framerate. Setting speed to 0 will build everything at once.
|
||||
|
||||
While moving, this is the movement speed in meters per second.
|
||||
|
||||
Switching between build and move mode doesn't impact the speed, except in the case of switching to move mode from build
|
||||
mode with a speed of 0. `speed = 0` is extremely common for build mode, but makes things appear broken in move mode,
|
||||
as nothing will actually move, so switching to move mode with a speed of 0 will automatically reset the speed to 1.
|
||||
|
||||
## `scale/scale=`
|
||||
|
||||
Sets the scale/size of a unit. `me` by default.
|
||||
|
||||
## `glow/glow=`
|
||||
|
||||
Specifies the glow/brightness of a unit. `me` by default. Currently does nothing for bots, but will in the future.
|
||||
|
||||
## `global/global=`
|
||||
|
||||
Specifies if a unit is in global space, or the space of its parent. If `global = true` and the parent unit moves,
|
||||
child units are unaffected. If `global = false`, the child will move with its parent. Does nothing for top level units,
|
||||
as they're always global.
|
||||
|
||||
By default, new `Build` units are `global = false` and new `Bot` units are `global = true`.
|
||||
|
||||
## `rotation`
|
||||
|
||||
Gets the rotation of a unit as a Vector3.
|
||||
|
||||
## `velocity/velocity=`
|
||||
|
||||
Gets or sets the velocity of a unit, as a Vector3. Currently buggy.
|
||||
|
||||
## `color/color=`
|
||||
|
||||
Gets or sets a units color. `me` by default. For `Build` units, this only impacts blocks placed after the property
|
||||
is set. For `Bot` units this does nothing, but in the future it will change their color.
|
||||
|
||||
## `bounce`
|
||||
|
||||
Bounces a unit in the air. Currently only works for the player.
|
||||
|
||||
## `save/restore`
|
||||
|
||||
`Build` units only. `save` the position, direction, drawing state, and color of the draw point, to `restore` it later.
|
||||
Can optionally take a name string to enable saving/restoring multiple points.
|
||||
|
||||
## `reset`
|
||||
|
||||
Instantly return unit to start position and resets rotation and scale.
|
||||
|
||||
## `home`
|
||||
|
||||
Moves a unit to its start position via a `forward`, `left`, `down` sequence with appropriate values. Can fail if there
|
||||
are obstructions along the way. Compare `position` to `start_position` after running to test for success.
|
||||
|
||||
## `sleep(seconds = -1.0)`
|
||||
|
||||
Do nothing for the specified number of seconds. If no argument is provided, or the argument is < 0, this will wait for
|
||||
0.5 seconds or until unit is interrupted, which will end the `sleep` prematurely. This allows the following:
|
||||
|
||||
```nim
|
||||
forever:
|
||||
sleep()
|
||||
if player.hit:
|
||||
echo "ouch!"
|
||||
```
|
||||
|
||||
Currently, any collision will trigger an interrupt. This will be expanded in the future.
|
||||
|
||||
## `forever`
|
||||
|
||||
Alias for `while true`
|
||||
|
||||
## `cycle`
|
||||
|
||||
Alternate between a list of values, returning the next element each time the cycle is called.
|
||||
|
||||
```nim
|
||||
forever:
|
||||
sleep 1
|
||||
echo cycle("one", "two", "three")
|
||||
```
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,49 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## Units
|
||||
|
||||
Entities/objects in Enu are referred to as units, and have a base type of `Unit`. Currently there are `Build` units
|
||||
(voxel objects) and `Bot` units (the robot). There will be more in the future.
|
||||
|
||||
`me` is the unit that owns a script, and is equivalent to `self`/`this` in other environments. `me` was selected
|
||||
because it's easier for a child to type. `me.` can be auto-inserted when accessing properties of the unit. For example,
|
||||
`me.speed = 10` would commonly be written as `speed = 10`. There are probably bugs with this behavior. Please report
|
||||
them.
|
||||
|
||||
## Prototypes
|
||||
|
||||
Enu uses a prototype based object system for units. To allow a unit to be a prototype, you give it a name:
|
||||
|
||||
`name ghost`
|
||||
|
||||
Then create a new instance in a different script with `.new`:
|
||||
|
||||
`var ghost2 = ghost.new`
|
||||
|
||||
You can also provide parameters, which can be overridden when creating a new instance:
|
||||
|
||||
`name ghost(color = white, speed = 5, spookiness = 10)`
|
||||
|
||||
These become properties of the unit (ie `me.spookiness = 5`), but can be treated like variables in the unit's script
|
||||
due to auto `me` insertion (`spookiness = 200`{.ruby}).
|
||||
|
||||
To create a new instance with specific property values:
|
||||
|
||||
```nim
|
||||
var ghost2 = ghost.new(spookiness = 11)
|
||||
```
|
||||
|
||||
Parameters can have a default value (`spookiness = 10`), which makes them optional when creating a new instance. If they
|
||||
should be required, or there's no reasonable default value to use, specify a type (`spookiness: int`) instead, or omit
|
||||
both the value and the type, which will make the type `auto`. Because `auto` can be implicit, `name ghost(a, b: int)`
|
||||
treats parameters differently than `proc ghost(a, b: int)` would. With the proc, `a` and `b` are both `int`, whereas
|
||||
the `name` version would make `a` `auto` and `b` `int`.
|
||||
|
||||
`speed`, `color`, `global` can always be passed to a new instance, even if the prototype name doesn't include them.
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,41 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
## Random numbers
|
||||
|
||||
Generally, if an Enu command takes a number, it will be a `float`. However, `int` will auto-convert to `float`, and
|
||||
when a numeric `Range` is passed to something expecting a number, a random value within the range will be selected. So, even
|
||||
though `forward` expects a `float`, the following are all valid:
|
||||
|
||||
```nim
|
||||
forward 1.0
|
||||
forward 1
|
||||
forward 1.0..5.0 # Convert to a random float between 1.0 and 5.0
|
||||
forward 1..5 # Convert to a random int between 1 and 5, then convert the int to a float
|
||||
```
|
||||
|
||||
The `in` operator can be used between two numbers to test for random chance. For example:
|
||||
|
||||
```nim
|
||||
if 1 in 2:
|
||||
echo "I should be hit 50% of the time"
|
||||
if 1 in 100:
|
||||
echo "I should be hit 1% of the time"
|
||||
```
|
||||
|
||||
By default random numbers in Enu are based partially on the time and will be different each time a script is executed.
|
||||
However, sometimes you want randomness to create variety, but want the same values to be chosen each time a script is
|
||||
run. This is especially important when using randomness in a `Build` that you plan to manually edit later. To ensure the
|
||||
same values are selected each time a script is run, set the unit's `seed` property to some integer of your choosing,
|
||||
ie `seed = 12345` or `me.seed = 54321`.
|
||||
|
||||
Any child units instanced by a unit with a seed value will get the same seed by default. However, it will still get
|
||||
a unique random number generator, so changing the script for a child object won't impact the random numbers selected by
|
||||
the parent.
|
||||
|
||||
"""
|
||||
|
||||
nb_save
|
|
@ -0,0 +1,36 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
Shorthand Commands
|
||||
|
||||
Many Enu command also have a 1 letter alias. These are harder to read, but can reduce friction for folks new to
|
||||
typing.
|
||||
|
||||
The aliases are:
|
||||
|
||||
- `f` - `forward`
|
||||
- `b` - `back`
|
||||
- `l` - `left`
|
||||
- `r` - `right`
|
||||
- `u` - `up`
|
||||
- `d` - `down`
|
||||
- `t` - `turn`. Can be combined with shorthand directions, so `turn right` can be expressed as `t r`
|
||||
- `o` - `while true:` (o was selected because its shape is a loop)
|
||||
- `x` - `times`. `5.x:` will run a code block 5 times.
|
||||
|
||||
In action:
|
||||
|
||||
```nim
|
||||
# draw a cube (with no top)
|
||||
10.x:
|
||||
4.x:
|
||||
f 10
|
||||
t r
|
||||
u 1
|
||||
```
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,6 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: "## Shapes"
|
||||
nb_save
|
|
@ -0,0 +1,68 @@
|
|||
# Examples
|
||||
|
||||
*TODO: Include examples of new 0.2 functionality*
|
||||
|
||||
Draw a square:
|
||||
```nim
|
||||
forward 10
|
||||
right 10
|
||||
back 10
|
||||
left 10
|
||||
```
|
||||
or:
|
||||
```nim
|
||||
4.times:
|
||||
forward 10
|
||||
turn_right()
|
||||
```
|
||||
![Square Example](media/square_example.png)
|
||||
Create a twisting tower:
|
||||
```nim
|
||||
var
|
||||
length = 20
|
||||
height = 50
|
||||
|
||||
height.times:
|
||||
left length / 2
|
||||
back length / 2
|
||||
4.times:
|
||||
forward length
|
||||
turn right
|
||||
forward length / 2
|
||||
right length / 2
|
||||
turn 5
|
||||
up 1
|
||||
```
|
||||
![Tower Example](media/tower_example.png)
|
||||
|
||||
Draw randomly:
|
||||
```nim
|
||||
up 10
|
||||
forward 10
|
||||
(50..100).times:
|
||||
forward 2..5
|
||||
turn -180..180
|
||||
up 0..2
|
||||
```
|
||||
![Random Example](media/random_example.png)
|
||||
|
||||
Set the color to blue randomly with a 1 in 50 chance. Otherwise set it to white:
|
||||
```nim
|
||||
if 1 in 50:
|
||||
color = blue
|
||||
else:
|
||||
color = white
|
||||
```
|
||||
or as a one-liner:
|
||||
```nim
|
||||
color = if 1 in 50: blue else: white
|
||||
```
|
||||
![Random Color Example](media/random_color_example.png)
|
||||
|
||||
Move forward 10 times, cycling through colors:
|
||||
```nim
|
||||
10.times:
|
||||
color = cycle(red, black, blue)
|
||||
forward 1
|
||||
```
|
||||
![Color Cycle Example](media/cycle_example.png)
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "examples.md"
|
|
@ -0,0 +1,28 @@
|
|||
import nimib, nimibook
|
||||
import enuib
|
||||
|
||||
nb_init(theme = use_enu)
|
||||
nb_text: """
|
||||
|
||||
3D live coding, implemented in Nim.
|
||||
|
||||
![Enu Screenshot](media/screenshot_2.png)
|
||||
|
||||
Enu lets you build and explore worlds using a familiar block-building interface
|
||||
and a Logo inspired API. It aspires to make 3D development more accessible, and
|
||||
will eventually be usable to create standalone games.
|
||||
|
||||
*Note: The docs for Enu 0.2 are a work in progress. Most of the core ideas are here, but a fair bit of revision is required. The 0.2 docs will be targeted
|
||||
towards new programmers, with 'Note for Nimians` sections aimed at more
|
||||
experienced folks to explain what's really going on. However, things are all
|
||||
over the place right now, with the intended audience changing paragraph by
|
||||
paragraph.*
|
||||
|
||||
*Notes for Nimions: Enu tries to simplify some Nim concepts, mainly to defer
|
||||
explaining unfamiliar terms. In particular, Enu tries to hide most things
|
||||
related to types, calls procs 'actions', and avoids immutable variables. I
|
||||
believe this is the right approach for new programmers, but I expect that more
|
||||
sophisticated developers will use a style closer to traditional Nim.*
|
||||
|
||||
"""
|
||||
nb_save
|
|
@ -0,0 +1,24 @@
|
|||
## Install
|
||||
|
||||
Download from https://github.com/dsrw/enu/releases. The Windows version isn't signed, and
|
||||
UAC will warn that it's untrusted. This will be fixed in a future release.
|
||||
|
||||
The Linux version hasn't been tested particularly well, but it works for me under Ubuntu 20.04. Please report any issues.
|
||||
|
||||
The world format will change in a future release. Worlds created in 0.1 won't be supported in future versions.
|
||||
|
||||
# Build and Run
|
||||
|
||||
```console
|
||||
$ nimble prereqs
|
||||
$ nimble build
|
||||
$ nimble import_assets
|
||||
$ nimble start
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Enu requires a custom Godot version, which lives in `vendor/godot`. This will be fetched
|
||||
and built as part of `nimble prereqs`.
|
||||
|
||||
See https://docs.godotengine.org/en/3.2/development/compiling/index.html
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "building.md"
|
|
@ -0,0 +1,16 @@
|
|||
## Demos
|
||||
|
||||
[Inky: Isolation. A 90 minute game built with Enu and Nim](https://youtu.be/9e9sLsmsu_o)
|
||||
|
||||
[Potato Zombies: Helping a 6 year old build a 3D game with Enu and Nim](https://fosdem.org/2022/schedule/event/nim_potatozombies/)
|
||||
|
||||
[Building 3D Games with Enu 0.2 - NimConf 2021](https://youtu.be/ECJsq7BeZ8w)
|
||||
|
||||
[Enu 0.1 demo video](https://youtu.be/upg77dMBGDE):
|
||||
|
||||
[![Enu 0.1 Demo](media/screenshot_1.png)](https://youtu.be/upg77dMBGDE)
|
||||
|
||||
### Outdated Demos
|
||||
|
||||
- [Enu 0.01 Intro and Demo](https://youtu.be/AW0PT9j976s)
|
||||
- [Introducing Enu - NimConf 2020](https://youtu.be/3l6tsKM1cY8)
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "demos.md"
|
|
@ -0,0 +1,23 @@
|
|||
## Goals
|
||||
|
||||
Enu is meant for anyone who wants to explore, experiment, or make games, but particular care has been taken to make
|
||||
it usable by younger people who may struggle with reading or typing. However, rather than bypassing the keyboard with
|
||||
a Scratch-like visual programming language, Enu attempts to reduce and simplify the keystrokes required for a text-based
|
||||
language, while (hopefully) preserving most of the flexibility text-based code offers.
|
||||
|
||||
With this in mind, Enu tries to:
|
||||
|
||||
- Reduce nesting. Indentation can be tricky for new programmers.
|
||||
- Reduce the use of the shift key. Lower case is used almost everywhere. Commands are written to
|
||||
avoid underscores and parenthesis. By default (for now at least), a `;` keypress is interpreted as `:`, as colons are
|
||||
required frequently in Nim (and require shift, at least on US English keyboards) while semi-colons are not.
|
||||
|
||||
- Omit or shorten identifier names. `me` instead of `self/this`. `-` instead of `proc`. `5.times:` or `5.x:` instead of
|
||||
`for i in 0..5:`. Single letter shortcuts for many common commands.
|
||||
|
||||
- Pretends to avoid types. Enu code is Nim code and is statically typed, but a fair amount of effort has been spent
|
||||
hiding this fact. Types are great, but are confusing for new programmers.
|
||||
|
||||
- Spatial organization. No files. Code is text, but it's accessed through an object in the virtual world.
|
||||
|
||||
- Avoids events. Tries to make all flow based on loops and conditionals.
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "goals.md"
|
|
@ -0,0 +1,401 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
|
||||
<head>
|
||||
<!-- Book generated using nimibook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
{{#is_print }}
|
||||
<meta name="robots" content="noindex"/>
|
||||
{{/is_print}}
|
||||
{{#base_url}}
|
||||
<base href="{{ base_url }}">
|
||||
{{/base_url}}
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
{{> head}}
|
||||
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff"/>
|
||||
|
||||
{{&favicon_escaped}}
|
||||
{{#favicon_svg}}
|
||||
<link rel="icon" href="{{ path_to_root }}assets/favicon.svg">
|
||||
{{/favicon_svg}}
|
||||
{{#favicon_png}}
|
||||
<link rel="shortcut icon" href="{{ path_to_root }}assets/favicon.png">
|
||||
{{/favicon_png}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/css/variables.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/css/general.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/css/chrome.css">
|
||||
{{#print_enable}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/css/print.css"
|
||||
media="print">
|
||||
{{/print_enable}}
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet"
|
||||
href="{{ path_to_root }}assets/FontAwesome/css/font-awesome.min.css">
|
||||
{{#copy_fonts}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/fonts/fonts.css">
|
||||
{{/copy_fonts}}
|
||||
|
||||
<!-- Highlight.js Stylesheets - I could use nimib native highlight but let's keep it for styling... -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}assets/css/highlight.css">
|
||||
<link rel="stylesheet"
|
||||
href="{{ path_to_root }}assets/css/tomorrow-night.css">
|
||||
<link rel="stylesheet"
|
||||
href="{{ path_to_root }}assets/css/ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets - why the "../"? -->
|
||||
{{#additional_css}}
|
||||
<link rel="stylesheet" href="../{{ path_to_root }}{{ . }}">
|
||||
{{/additional_css}}
|
||||
|
||||
{{#mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script async type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/mathjax_support}}
|
||||
|
||||
{{&latex}}
|
||||
|
||||
{{^disableHighlightJs}}
|
||||
{{{highlightJs}}}
|
||||
{{/disableHighlightJs}}
|
||||
|
||||
<!-- plausible analytics (new in nimibook) -->
|
||||
{{#plausible_analytics_url}}
|
||||
<script defer data-domain="{{plausible_analytics_url}}"
|
||||
src="https://plausible.io/js/plausible.js"></script>
|
||||
{{/plausible_analytics_url}}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "{{ path_to_root }}/assets";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script type="text/javascript">
|
||||
var theme;
|
||||
try {
|
||||
theme = localStorage.getItem('mdbook-theme');
|
||||
} catch (e) {
|
||||
}
|
||||
if (theme === null || theme === undefined) {
|
||||
theme = default_theme;
|
||||
}
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('{{ default_theme }}')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script type="text/javascript">
|
||||
var html = document.querySelector('html');
|
||||
var sidebar = 'hidden';
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try {
|
||||
sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
} catch (e) {
|
||||
}
|
||||
sidebar = sidebar || 'visible';
|
||||
}
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
{{> toc }}<!-- I could use also an unescaped context value -->
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
{{> header}}
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky bordered">
|
||||
<div class="left-buttons">
|
||||
<button id="sidebar-toggle" class="icon-button" type="button"
|
||||
title="Toggle Table of Contents"
|
||||
aria-label="Toggle Table of Contents"
|
||||
aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button"
|
||||
title="Change theme" aria-label="Change theme"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes"
|
||||
role="menu">
|
||||
<li role="none">
|
||||
<button role="menuitem" class="theme"
|
||||
id="light">{{#theme_option}}{{light}}{{/theme_option}}</button>
|
||||
</li>
|
||||
<li role="none">
|
||||
<button role="menuitem" class="theme"
|
||||
id="rust">{{#theme_option}}{{rust}}{{/theme_option}}</button>
|
||||
</li>
|
||||
<li role="none">
|
||||
<button role="menuitem" class="theme"
|
||||
id="coal">{{#theme_option}}{{coal}}{{/theme_option}}</button>
|
||||
</li>
|
||||
<li role="none">
|
||||
<button role="menuitem" class="theme"
|
||||
id="navy">{{#theme_option}}{{navy}}{{/theme_option}}</button>
|
||||
</li>
|
||||
<li role="none">
|
||||
<button role="menuitem" class="theme"
|
||||
id="ayu">{{#theme_option}}
|
||||
Ayu!!!{{/theme_option}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{#search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button"
|
||||
title="Search. (Shortkey: s)"
|
||||
aria-label="Toggle Searchbar" aria-expanded="false"
|
||||
aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
{{/search_enabled}}
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">{{ book_title }}</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
{{#print_enable}}
|
||||
<a href="{{ path_to_root }}print.html"
|
||||
title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
{{/print_enable}}
|
||||
{{#git_repository_url}}
|
||||
<a href="{{git_repository_url}}" title="Git repository"
|
||||
aria-label="Git repository">
|
||||
<i id="git-repository-button"
|
||||
class="fa {{git_repository_icon}}"></i>
|
||||
</a>
|
||||
{{/git_repository_url}}
|
||||
{{#git_repository_edit_url}}
|
||||
<a href="{{git_repository_edit_url}}"
|
||||
title="Suggest an edit" aria-label="Suggest an edit">
|
||||
<i id="git-edit-button" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{{/git_repository_edit_url}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#search_enabled}}
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar"
|
||||
placeholder="Search this book ..."
|
||||
aria-controls="searchresults-outer"
|
||||
aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer"
|
||||
class="searchresults-outer hidden">
|
||||
<div id="searchresults-header"
|
||||
class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/search_enabled}}
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script type="text/javascript">
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
{{#blocks}}
|
||||
{{&.}}
|
||||
{{/blocks}}
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{previous}}"
|
||||
class="mobile-nav-chapters previous"
|
||||
title="Previous chapter" aria-label="Previous chapter"
|
||||
aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{next}}"
|
||||
class="mobile-nav-chapters next" title="Next chapter"
|
||||
aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{previous}}"
|
||||
class="nav-chapters previous" title="Previous chapter"
|
||||
aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{next}}"
|
||||
class="nav-chapters next" title="Next chapter"
|
||||
aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
{{#livereload}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script type="text/javascript">
|
||||
var socket = new WebSocket("{{{livereload}}}");
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
{{/livereload}}
|
||||
|
||||
{{#google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script type="text/javascript">
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '{{google_analytics}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
{{/google_analytics}}
|
||||
|
||||
{{#playground_line_numbers}}
|
||||
<script type="text/javascript">
|
||||
window.playground_line_numbers = true;
|
||||
</script>
|
||||
{{/playground_line_numbers}}
|
||||
|
||||
{{#playground_copyable}}
|
||||
<script type="text/javascript">
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
{{/playground_copyable}}
|
||||
|
||||
{{#playground_js}}
|
||||
<script src="{{ path_to_root }}ace.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}editor.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js"
|
||||
type="text/javascript" charset="utf-8"></script>
|
||||
{{/playground_js}}
|
||||
|
||||
{{#search_js}}
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}searcher.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
{{/search_js}}
|
||||
|
||||
<script src="{{ path_to_root }}assets/js/clipboard.min.js"
|
||||
type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{ path_to_root }}assets/js/book.js" type="text/javascript"
|
||||
charset="utf-8"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
{{#additional_js}}
|
||||
<script type="text/javascript" src="{{ ../path_to_root }}{{ . }}"></script>
|
||||
{{/additional_js}}
|
||||
|
||||
{{#is_print}}
|
||||
{{#mathjax_support}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function () {
|
||||
MathJax.Hub.Register.StartupHook('End', function () {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{/mathjax_support}}
|
||||
{{^mathjax_support}}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function () {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
</script>
|
||||
{{/mathjax_support}}
|
||||
{{/is_print}}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
# TODO for 0.2
|
||||
|
||||
### Pivot point
|
||||
|
||||
Currently it isn't possible to change the pivot point for a unit, and the default point isn't properly centered for
|
||||
most builds, making it difficult to rotate builds nicely. Enu 0.2 will use the draw point for the pivot point, allowing
|
||||
it to be moved, and will shift everything over 0.5m, allowing most builds to rotate in a balanced way. There will
|
||||
also be a command to move the draw point (and thus the pivot point) in the exact center of a build.
|
||||
|
||||
### REPL?
|
||||
|
||||
We need a way to switch worlds without editing a config file. Adding a REPL may be the easiest way to accomplish
|
||||
this, and is something I wanted to add anyway.
|
||||
|
||||
### Testing and bug fixes
|
||||
|
||||
Enu has been under heavy development for a year without a great deal of testing, so there are undoubtedly bugs. I
|
||||
believe these will be minor, but there are probably a fair number of them.
|
||||
|
||||
### v0.2.x - v0.3
|
||||
|
||||
- [ ] iOS support.
|
||||
- [ ] Move script execution off the main thread.
|
||||
- [ ] Inventory
|
||||
- [ ] Settings UI
|
||||
- [ ] Allow the editor pane and action bar to be resized from within Enu.
|
||||
- [ ] Better collision support
|
||||
- [ ] Blocks of any color
|
||||
- [ ] In game help
|
||||
- [ ] Easy way to switch worlds in-game
|
||||
- [ ] Support loading worlds from anywhere, not just the Enu data directory
|
|
@ -0,0 +1,2 @@
|
|||
import enuib
|
||||
md "todo.md"
|
|
@ -0,0 +1 @@
|
|||
switch("path", this_dir())
|
|
@ -0,0 +1,74 @@
|
|||
import std / [sugar, strutils, os, enumerate, pathnorm, macros]
|
||||
import pkg / [pretty, nimibook, nimib, nimib / themes]
|
||||
import pkg / nimibook / [types, commands, entries, toc_render]
|
||||
|
||||
export pathutils, pretty
|
||||
# adapted from https://raw.githubusercontent.com/pietroppeter/nimibook/ef700f646db8ec0bbe8a3319cbb3561aaac89a34/src/nimibook/themes.nim
|
||||
|
||||
const document* = hl_html static_read("./book/template.html.mustache")
|
||||
|
||||
proc use_enu*(doc: var NbDoc) =
|
||||
doc.context["path_to_root"] = doc.srcDirRel.string & "/" # I probably should make sure to have / at the end
|
||||
|
||||
# templates are in memory
|
||||
doc.partials["document"] = document
|
||||
# if they need to be overriden a specific template folder should be created in nbSrcDir
|
||||
doc.templateDirs = @[doc.srcDir.string / "templates"]
|
||||
|
||||
# book.json is publicly accessible (sort of a public static api)
|
||||
let bookPath = doc.homeDir.string / "book.json"
|
||||
# load book object
|
||||
var book = load(bookPath)
|
||||
|
||||
# book configuration
|
||||
doc.context["language"] = book.language
|
||||
doc.context["default_theme"] = book.default_theme
|
||||
doc.context["description"] = book.description
|
||||
doc.context["favicon_escaped"] = book.favicon_escaped
|
||||
doc.context["preferred_dark_theme"] = book.preferred_dark_theme
|
||||
doc.context["theme_option"] = book.theme_option
|
||||
doc.context["book_title"] = book.title
|
||||
doc.context["git_repository_url"] = book.git_repository_url
|
||||
doc.context["git_repository_icon"] = book.git_repository_icon
|
||||
doc.context["plausible_analytics_url"] = book.plausible_analytics_url
|
||||
doc.context["highlightJs"] = highlightJsTags
|
||||
|
||||
var thisEntry: Entry
|
||||
# process toc
|
||||
for i, entry in enumerate(book.toc.entries.mitems):
|
||||
if normalizePath(entry.url) == normalizePath(doc.filename.replace('\\', '/')): # replace needed for windows
|
||||
thisEntry = entry
|
||||
entry.isActive = true
|
||||
let
|
||||
prevUrl = book.prevEntryUrl i
|
||||
nextUrl = book.nextEntryUrl i
|
||||
if prevUrl.len > 0:
|
||||
doc.context["previous"] = prevUrl
|
||||
if nextUrl.len > 0:
|
||||
doc.context["next"] = nextUrl
|
||||
break
|
||||
doc.partials["toc"] = render book.toc
|
||||
|
||||
# html.head.title (what appears in the tab)
|
||||
doc.context["title"] = thisEntry.title & " - " & book.title
|
||||
|
||||
template load_md*(file) =
|
||||
const text = static_read file
|
||||
nb_init(theme = use_enu)
|
||||
nb_text(text)
|
||||
|
||||
template caller_path: string =
|
||||
instantiation_info(0, true).filename
|
||||
|
||||
proc root_dir: string =
|
||||
caller_path().splitfile.dir
|
||||
|
||||
template md*(file: string) =
|
||||
let base_path = root_dir() / "book"
|
||||
let caller_path = instantiation_info(-1, true).filename.split_file.dir
|
||||
let diff = caller_path.replace(base_path, "").dup(remove_prefix("/"))
|
||||
|
||||
nb_init(theme = use_enu, this_file_rel = diff / file)
|
||||
let text = read_file(caller_path / file)
|
||||
nb_text(text)
|
||||
nb_save
|
|
@ -0,0 +1,9 @@
|
|||
[nimib]
|
||||
src_dir = "book"
|
||||
home_dir = "build"
|
||||
|
||||
[nimibook]
|
||||
language = "en-us"
|
||||
title = "Enu"
|
||||
description = "Build, together"
|
||||
default_theme = "dark"
|
|
@ -37,7 +37,8 @@ requires "nim >= 1.6.10",
|
|||
"chroma",
|
||||
"markdown",
|
||||
"chronicles",
|
||||
"dotenv"
|
||||
"dotenv",
|
||||
"nimibook"
|
||||
|
||||
proc godot_bin(target = target): string =
|
||||
result = this_dir() & &"/vendor/godot/bin/godot.{target}.opt.tools.{cpu}{exe_ext}"
|
||||
|
|
Loading…
Reference in New Issue