Move dev-assets/guidebook into its own repository

This commit is contained in:
Noah 2020-11-20 19:08:31 -08:00
parent 6cd5f17e9b
commit fade085695
18 changed files with 29 additions and 1004 deletions

View File

@ -1,16 +0,0 @@
SHELL := /bin/bash
# `make serve` to serve the mkdocs site on localhost.
.PHONY: serve
serve:
mkdocs serve
# `make build` to build the static documentation site.
.PHONY: build
build:
mkdocs build
# `make clean` cleans everything up.
.PHONY: clean
clean:
rm -rf site

View File

@ -1,73 +0,0 @@
#!/usr/bin/env python3
import codecs
import glob
import os
import markdown
import jinja2
import re
def main():
if not os.path.isdir("./compiled"):
print("Make output directory: ./compiled")
os.mkdir("./compiled")
os.mkdir("./compiled/pages")
# Render the main index.html template.
with codecs.open("./pages/index.html", "r", "utf-8") as fh:
html = fh.read()
html = render_template(html)
with open("./compiled/index.html", "w") as outfh:
outfh.write(html)
# Load the Markdown wrapper HTML template.
html_wrapper = "$CONTENT"
with codecs.open("./pages/markdown.html", "r", "utf-8") as fh:
html_wrapper = fh.read()
for md in glob.glob("./pages/*.md"):
filename = md.split(os.path.sep)[-1]
htmlname = filename.replace(".md", ".html")
print("Compile Markdown: {} -> {}".format(filename, htmlname))
with codecs.open(md, 'r', 'utf-8') as fh:
data = fh.read()
rendered = markdown.markdown(data,
extensions=["codehilite", "fenced_code"],
)
html = html_wrapper.replace("$CONTENT", rendered)
html = render_template(html,
title=title_from_markdown(data),
)
with open(os.path.join("compiled", "pages", htmlname), "w") as outfh:
outfh.write(html)
jinja_env = jinja2.Environment()
def render_template(input, *args, **kwargs):
templ = jinja_env.from_string(input)
return templ.render(
app_name="Project: Doodle",
app_version=get_app_version(),
*args, **kwargs
)
def title_from_markdown(text):
"""Retrieve the title from the first Markdown header."""
for line in text.split("\n"):
if line.startswith("# "):
return line[2:]
def get_app_version():
"""Get the app version from pkg/branding/branding.go in Doodle"""
ver = re.compile(r'Version\s*=\s*"(.+?)"')
with codecs.open("../../pkg/branding/branding.go", "r", "utf-8") as fh:
text = fh.read()
for line in text.split("\n"):
m = ver.search(line)
if m:
return m[1]
if __name__ == "__main__":
main()

View File

@ -1,16 +0,0 @@
#!/bin/bash
if [[ ! -d "./venv" ]]; then
echo Creating Python virtualenv...
python3 -m venv ./venv
source ./venv/bin/activate
pip install -r requirements.txt
else
source ./venv/bin/activate
fi
python build.py
# Copy static files in.
mkdir -p compiled/pages/res
cp -r pages/res/*.* compiled/pages/res/

View File

@ -1,3 +0,0 @@
# About Project: Doodle
Lorem ipsum.

View File

@ -1,20 +0,0 @@
# Drawing Doodads in an External Program
Doodad sprites can be drawn using any image editor and saved as .png files
(with transparency). You can then create a doodad file from your series of
PNG images.
Most of the built-in doodads that ship with the game were created in this way.
## Create a Doodad from Images
Save each frame of your doodad as a separate PNG image and then use the `doodad`
command-line tool to convert them to a `.doodad` file.
```bash
# Usage:
doodad convert [options] <inputs.png> <output.doodad>
# Example:
doodad convert door-closed.png door-open.png door.doodad
```

View File

@ -1,27 +0,0 @@
# Drawing a Doodad In-Game
Project: Doodle has some **limited** support to draw your doodad sprites
in-game. Currently you can only draw one frame (image) for the doodad
and save it to disk.
To start a new doodad, open the game and enter the level editor.
Select the "New Doodad" button at the top of the screen to start drawing a
new doodad. Choose the size (square) of its sprite when prompted.
Doodads saved in-game go in your user config directory for the game. On Linux,
this is at ~/.config/doodle.
If you want to create a doodad with multiple frames (to animate it or have
varying states that change the doodad's appearance in the level), the
`doodad` tool is recommended. See
[drawing images in an external program](edit-external.md).
## Future Planned Features
Creating doodads in-game is intended to be a fully supported feature. The
following features are planned to be supported:
* Support editing multiple frames instead of only the first frame.
* Implement some features only available on the `doodad` tool using in-game
UI, such as attaching JavaScripts to the doodad.

View File

@ -1,43 +0,0 @@
# Creating Custom Doodads
Project: Doodle is designed to be modder friendly and provides tools to help
you create your own custom doodads to use in your levels.
You can draw the sprites for the doodad either in-game or using an external
image editor. Then, you can program their logic using JavaScript to make them
"do" stuff in-game and interact with the player and other doodads.
* Drawing your Doodad's Sprites
* [In-Game](edit-in-game.md)
* [In an External Program](edit-external.md)
* Program its Behavior
* [JavaScript](scripts.md)
## doodad (Command Line Tool)
Your copy of the game should have shipped with a `doodad` command-line tool
bundled with it. On Windows it's called `doodad.exe` and should be in the same
folder as the game executable. On Mac OS, it is inside the .app bundle.
The `doodad` tool provides a command-line interface to create and inspect
doodad and level files from the game. You'll need to use this tool, at the very
least, to attach a JavaScript to your doodad to make it "do" stuff in-game.
You can create a doodad from PNG images on disk, attach or view the JavaScript
source on them, and view/edit metadata.
```bash
# (the $ represents the shell prompt in a command-line terminal)
# See metadata about a doodad file.
$ doodad show /path/to/custom.doodad
# Create a new doodad based on PNG images on disk.
$ doodad convert frame0.png frame1.png frame2.png output.doodad
# Add and view a custom script attached to the doodad.
$ doodad install-script index.js custom.doodad
$ doodad show --script custom.doodad
```
More info on the [`doodad` tool](../doodad-tool.md) here.

View File

@ -1,564 +0,0 @@
# Doodad Scripts
Doodads are programmed using JavaScript which gives them their behavior
and ability to interact with the player and other doodads.
Doodad scripts are run during "Play Mode" when a level _containing_ the doodad
is being played. You can install a JavaScript (.js) file into a doodad using
the command-line `doodad` program.
An example Doodad script looks like the following:
```javascript
// main() is called on level initialization for each
// instance ("actor") of the doodad.
function main() {
// Logs go to the game's log file (standard output on Linux/Mac).
console.log("%s initialized!", Self.Title);
// If our doodad has 'solid' parts that should prohibit movement,
// define the hitbox here. Coordinates are relative so 0,0 is the
// top-left pixel of the doodad's sprite.
Self.SetHitbox(0, 0, 64, 12);
// Handle a collision when another doodad (or player) has entered
// the space of our doodad.
Events.OnCollide(function(e) {
// The `e` object holds information about the event.
console.log("Actor %s has entered our hitbox!", e.Actor.ID());
// InHitbox is `true` if we defined a hitbox for ourselves, and
// the colliding actor is inside of the hitbox we defined.
if (e.InHitbox) {
// To prohibit movement, return false from the OnCollide handler.
// If you don't return false, the actor is allowed to keep on
// moving through.
return false;
}
// When movement is finalized, OnCollide is called one final time
// with e.Settled=true; it is only then that a doodad should run
// event handlers for a logical collide event.
if (e.Settled) {
// do something
Message.Publish("power", true);
}
});
// OnLeave is called when an actor, who was previously colliding with
// us, is no longer doing so.
Events.OnLeave(function(e) {
console.log("Actor %s has stopped colliding!", e.Actor.ID());
})
}
```
# Installing a Doodad Script
Use the command-line `doodad` tool to attach a script to your doodad file:
```bash
# Attach the JavaScript at "script.js" to the doodad file "filename.doodad"
doodad install-script script.js filename.doodad
# To view the script currently attached to a doodad
# (prints the script to your terminal)
doodad show --script filename.doodad
```
# Testing Your Script
The best way to test your doodad script is to use it in a level!
Run the game in a console to watch the log output, and you can use functions
like `console.log()` in your script to help debug issues. Drag your custom
doodad into a level and playtest it! Your script's main() function is called
when the level instance of your doodad is initialized.
# JavaScript API
The following global variables are available to all Doodad scripts.
## Self
Self holds data about the current doodad instance loaded inside of a level.
**String attributes:**
* Self.Title: the doodad title.
* Self.Filename: the doodad filename (useful for inventory items).
Methods are below.
### Self.ID() string
Returns the "actor ID" of the doodad instance loaded inside of a level. This
is usually a random UUID string that was saved with the level data.
### Self.GetTag(string name) string
Return a "tag" that was saved with the doodad's file data.
Tags are an arbitrary key/value data store attached to the doodad file.
You can use the `doodad.exe` tool shipped with the game to view and manage tags
on your own custom doodads:
```bash
# Command-line doodad tool usage:
# Show information about a doodad, look for the "Tags:" section.
doodad show filename.doodad
# Set a tag. "-t" for short.
doodad edit-doodad --tag 'color=blue' filename.doodad
# Set the tag to empty to remove it.
doodad edit-doodad -t 'color=' filename.doodad
```
This is useful for a set of multiple doodads to share the same script but
have different behavior depending on how each is tagged.
### Self.Position() Point
Returns the doodad's current position in the level.
Point is an object with .X and .Y integer values.
```javascript
var p = Self.Position()
console.log("I am at %d,%d", p.X, p.Y)
```
### Self.SetHitbox(x, y, w, h int)
Configure the "solid hitbox" of this doodad.
The X and Y coordinates are relative to the doodad's sprite: (0,0) is the top
left pixel of the doodad. The W and H are the width and height of the hitbox
starting at those coordinates.
When another doodad enters the area of your doodad's sprite (for example, the
player character has entered the square shape of your doodad sprite) your script
begins to receive OnCollide events from the approaching actor.
The OnCollide event tells you if the invading doodad is inside your custom
hitbox which you define here (`InHitbox`) making it easy to make choices based
on that status.
Here's an example script for a hypothetical "locked door" doodad that acts
solid but only on a thin rectangle in the middle of its sprite:
```javascript
// Example script for a "locked door"
function main() {
// Suppose the doodad's sprite size is 64x64 pixels square.
// The door is in side profile where the door itself ranges from pixels
// (20, 0) to (24, 64)
Self.SetHitbox(20, 0, 24, 64)
// OnCollide handlers.
Events.OnCollide(function(e) {
// The convenient e.InHitbox tells you if the colliding actor is
// inside the hitbox we defined.
if (e.InHitbox) {
// Return false to protest the collision (act solid).
return false;
}
});
}
```
### Self.SetVelocity(Velocity)
Set the doodad's velocity. Velocity is a type that can be created with the
Velocity() constructor, which takes an X and Y value:
```javascript
Self.SetVelocity( Velocity(3.2, 7.0) );
```
A positive X velocity propels the doodad to the right. A positive Y velocity
propels the doodad downward.
### Self.SetMobile(bool)
Call `SetMobile(true)` if the doodad will move on its own.
This is for mobile doodads such as the player character and enemy mobs.
Stationary doodads like buttons, doors, and trapdoors do not mark themselves
as mobile.
Mobile doodads incur extra work for the game doing collision checking so only
set this to `true` if your doodad will move (i.e. changes its Velocity or
Position).
```javascript
Self.SetMobile(true);
```
### Self.SetGravity(bool)
Set whether gravity applies to this doodad. By default doodads are stationary
and do not fall downwards. The player character and some mobile enemies that
want to be affected by gravity should opt in to this.
```javascript
Self.SetGravity(true);
```
### Self.ShowLayer(index int)
Switch the active layer of the doodad to the layer at this index.
A doodad file can contain multiple layers, or images. The first and default
layer is at index zero, the second layer at index 1, and so on.
```javascript
Self.ShowLayer(0); // 0 is the first and default layer
Self.ShowLayer(1); // show the second layer instead
```
### Self.ShowLayerNamed(name string)
Switch the active layer by name instead of index.
Each layer has an arbitrary name that it can be addressed by instead of needing
to keep track of the layer index.
Doodads created by the command-line `doodad` tool will have their layers named
automatically by their file name. The layer **indexes** will retain the same
order of file names passed in, with 0 being the first file:
```bash
# Doodad tool-created doodads have layers named after their file names.
# example "open-1.png" will be named "open-1"
doodad convert door.png open-1.png open-2.png open-3.png my-door.doodad
```
### Self.AddAnimation(name string, interval int, layers list)
Register a named animation for your doodad. `interval` is the time in
milliseconds before going to the next frame. `layers` is an array of layer
names or indexes to be used for the animation.
Doodads can animate by having multiple frames (images) in the same file.
Layers are ordered (layer 0 is the first, then increments from there) and
each has a name. This function can take either identifier to specify
which layers are part of the animation.
```javascript
// Animation named "open" using named layers, 100ms delay between frames.
Self.AddAnimation("open", 100, ["open-1", "open-2", "open-3"]);
// Animation named "close" using layers by index.
Self.AddAnimation("close", 100, [3, 2, 1]);
```
### Self.PlayAnimation(name string, callback func())
This starts playing the named animation. The callback function will be called
when the animation has completed.
```javascript
Self.PlayAnimation("open", function() {
console.log("I've finished opening!");
// The callback is optional; use null if you don't need it.
Self.PlayAnimation("close", null);
});
```
### Self.IsAnimating() bool
Returns true if an animation is currently being played.
### Self.StopAnimation()
Stops any currently playing animation.
* Self.Doodad(): a pointer to the doodad's file data.
* Self.Doodad().Title: get the title of the doodad file.
* Self.Doodad().Author: the name of the author who wrote the doodad.
* Self.Doodad().Script: the doodad's JavaScript source code. Note that
modifying this won't have any effect in-game, as the script had already
been loaded into the interpreter.
* Self.Doodad().GameVersion: the version of {{ app_name }} that was used
when the doodad was created.
### Self.Destroy()
This destroys the current instance of the doodad as it appears in a level.
For example, a Key destroys itself when it's picked up so that it disappears
from the level and can't be picked up again. Call this function when the
doodad instance should be destroyed and removed from the active level.
-----
## Console Logging
Like in node.js and the web browser, `console.log` and friends are available
for logging from a doodad script. Logs are emitted to the same place as the
game's logs are.
```javascript
console.log("Hello world!");
console.log("Interpolate strings '%s' and numbers '%d'", "string", 123);
console.debug("Debug messages shown when the game is in debug mode");
console.warn("Warning-level messages");
console.error("Error-level messages");
```
-----
## Timers and Intervals
Like in a web browser, functions such as setTimeout and setInterval are
supported in doodad scripts.
### setTimeout(function, milliseconds int) int
setTimeout calls your function after the specified number of milliseconds.
1000ms are in one second.
Returns an integer "timeout ID" that you'll need if you want to cancel the
timeout with clearTimeout.
### setInterval(function, milliseconds int) int
setInterval calls your function repeatedly after every specified number of
milliseconds.
Returns an integer "interval ID" that you'll need if you want to cancel the
interval with clearInterval.
### clearTimeout(id int)
Cancels the timeout with the given ID.
### clearInterval(id int)
Cancels the interval with the given ID.
-----
## Type Constructors
Some methods may need data of certain native types that aren't available in
JavaScript. These global functions will initialize data of the correct types:
### RGBA(red, green, blue, alpha uint8)
Creates a Color type from red, green, blue and alpha values (integers between
0 and 255).
### Point(x, y int)
Creates a Point object with X and Y coordinates.
### Vector(x, y float64)
Creates a Vector object with X and Y dimensions.
-----
## Global Functions
Some useful globally available functions:
### EndLevel()
This ends the current level, i.e. to be used by the goal flag.
### Flash(message string, args...)
Flash a message on screen to the user.
Flashed messages appear at the bottom of the screen and fade out after a few
moments. If multiple messages are flashed at the same time, they stack from the
bottom of the window with the newest message on bottom.
Don't abuse this feature as spamming it may annoy the player.
### GetTick() uint64
Returns the current game tick. This value started at zero when the game was
launched and increments every frame while running.
### time.Now() time.Time
This exposes the Go standard library function `time.Now()` that returns the
current date and time as a Go time.Time value.
### time.Add(t time.Time, milliseconds int64) time.Time
Add a number of milliseconds to a Go Time value.
--------
## Event Handlers
Doodad scripts can respond to certain events using functions on the global
`Events` variable.
### Events.OnCollide( func(event) )
OnCollide is called when another actor is colliding with your doodad's sprite
box. The function is given a CollideEvent object which has the following
attributes:
* Actor: the doodad which is colliding with your doodad.
* Overlap (Rect): a rectangle of where the two doodads' boxes are overlapping,
relative to your doodad sprite's box. That is, if the Actor was moving in from
the left side of your doodad, the X value would be zero and W would be the
number of pixels of overlap.
* InHitbox (bool): true if the colliding actor's hitbox is intersecting with
the hitbox you defined with SetHitbox().
* Settled (bool): This is `false` when the game is trying to move the colliding
doodad and is sussing out whether or not your doodad will act solid and
protest its movement. When the game has settled the location of the colliding
doodad it will call OnCollide a final time with Settled=true. If your doodad
has special behavior when touched (i.e. a button that presses in), you should
wait until Settled=true before running your handler for that.
### Events.OnLeave( func(event) )
Called when an actor that _was_ colliding with your doodad is no longer
colliding (or has left your doodad's sprite box).
### Events.RunKeypress( func(event) )
Handle a keypress. `event` is an `event.State` from the render engine.
TODO: document that.
-----
## Pub/Sub Communication
Doodads in a level are able to send and receive messages to other doodads,
either those that they are **linked** to or those that listen on a more
'broadcast' frequency.
> **Linking** is when the level author connected two doodads together with
> the Link Tool. The two doodads' scripts can communicate with each other
> in-game over that link.
For example, if the level author links a Button to an Electric Door, the button
can send a "power" event to the door so that it can open when a player touches
the button.
Doodads communicate in a "publisher/subscriber" model: one doodad publishes an
event with a name and data, and other doodads subscribe to the named event to
receive that data.
### Official, Standard Pub/Sub Messages
The following message names and data types are used by the game's default
doodads. You're free to use these in your own custom doodads.
If extending this list with your own custom events, be careful to choose a
unique namespace to prevent collision with other users' custom doodads and
their custom event names.
| Name | Data Type | Description |
|------|-----------|--------------|
| power | boolean | Communicates a "powered" (true) or "not powered" state, as in a Button to an Electric Door. |
| broadcast:state-change | boolean | An "ON/OFF" button was hit and all state blocks should flip. |
### Message.Publish(name string, data...)
Publish a named message to all of your **linked** doodads.
`data` is a list of arbitrary arguments to send with the message.
```javascript
// Example button doodad that emits a "power" (bool) state to linked doodads
// that subscribe to this event.
function main() {
// When an actor collides with the button, emit a powered state.
Events.OnCollide(function(e) {
Message.Publish("power", true);
});
// When the actor leaves the button, turn off the power.
Events.OnLeave(function(e) {
Message.Publish("power", false);
})
}
```
### Message.Subscribe(name string, function)
Subscribe to a named message from any **linked** doodads.
The function receives the data that was passed with the message. Its data type
is arbitrary and will depend on the type of message.
```javascript
// Example electronic device doodad that responds to power from linked buttons.
function main() {
// Boolean to store if our electric device has juice.
var powered = false;
// Do something while powered
setInterval(function() {
if (powered) {
console.log("Brmm...")
}
}, 1000);
// Subscribe to the `power` event by a linked button or other power source.
Message.Subscribe("power", function(boolValue) {
console.log("Powered %s!", boolValue === true ? "on" : "off");
powered = boolValue;
});
}
```
### Message.Broadcast(name string, data...)
This publishes a named message to **every** doodad in the level, whether it
was linked to the broadcaster or not.
For example the "ON/OFF" button globally toggles a boolean state in every
state block that subscribes to the `broadcast:state-change` event.
If you were to broadcast an event like `power` it would activate every single
power-sensitive doodad on the level.
```javascript
// Example two-state block that globally receives the state-change broadcast.
function main() {
var myState = false;
Message.Subscribe("broadcast:state-change", function(boolValue) {
// Most two-state blocks just flip their own state, ignoring the
// boolValue passed with this message.
myState = !myState;
});
}
// Example ON/OFF block that emits the state-change broadcast. It also
// subscribes to the event to keep its own state in sync with all the other
// ON/OFF blocks on the level when they get hit.
function main() {
var myState = false;
// Listen for other ON/OFF button activations to keep our state in
// sync with theirs.
Message.Subscribe("broadcast:state-change", function(boolValue) {
myState = boolValue;
});
// When collided with, broadcast the state toggle to all state blocks.
Events.OnCollide(function(e) {
if (e.Settled) {
myState = !!myState;
Message.Broadcast("broadcast:state-change", myState);
}
})
}
```

View File

@ -1,99 +0,0 @@
# Creating Custom Levels
One of the core gameplay features is its Level Editor which lets you draw your
own custom maps to play and share with others.
From the game's Main Menu, click on the "Create a Level" button to open the
level editor. To edit an existing custom level, click on the "Edit a Level"
button instead.
## Level Properties
When creating a new level, you first choose some settings for it. These are
described below:
### Page Type
This setting controls the size and boundaries of your level, and control the
appearance of the notebook paper background of your level.
* **Bounded** is the default. The camera won't scroll past the top-left corner
of the page (0,0), and the level size is capped to 2550x3300, or the
approximate size of an 11:9 standard sheet of notebook paper in pixels.
* **No Negative Space** is like Bounded, but the width and height of the level
have no boundary. Levels can grow "infinitely" to the right and downward
but no negative coordinates past the top or left edge.
* **Unbounded** allows for "infinite" sized maps that give unlimited room to
grow your level. The wallpaper on this level type only uses the "tiling"
pattern, so notebook-themed levels won't show the top/left decorations.
### Wallpaper
The wallpaper affects the "theme" of your level. Project: Doodle is themed around
hand-drawn mazes on paper, so the built-in themes look like various kinds of
paper.
* **Notebook** looks like standard ruled notebook paper. It's a white paper with
blue horizontal lines, a single red vertical line down the left, and a wide
margin on the top and left edges.
* **Legal Pad** looks like ruled yellow legal pad. It's similar to Notebook but
has yellow paper and a second vertical red line down the left.
* **Blueprint** is a dark blueprint paper background with a repeating grid pattern.
Notably, the default Color Palette for this theme is different than normal:
"solid" lines are white instead of black, to show up better against the dark
background.
The decorations of the wallpaper vary based on the Page Type. For example, the
Notebook and Legal Pad have extra padding on the top of the page and red lines
going down just the left side, and the rest of the level uses the repeating blue
lines pattern. The page types and their effect on the wallpapers are:
* **Bounded** and **No Negative Space** will show the decorations for the top
and left edges of the page, as these levels are bounded on their top/left
corner.
* **Unbounded** levels only use the repeating tiled pattern across the entire
level, because there is no top-left boundary to anchor those decorations to.
## Editor Mode Interface
![Level Editor View](../images/editor-1.png)
Quick 5-minute tour of what you're looking at:
* The top of the window has your **Menu Bar**:
* **New Level** opens the "Create a New Level" menu.
* **New Doodad** opens the Doodad Editor for drawing a new custom doodad.
You're prompted for the size of the doodad, which will be its width and
height boundary. For example, a size of "100" means a 100x100 pixel graphic
for your custom doodad.
* **Save** and **Save as...** let you save the current Level or Doodad you're
drawing to disk. "Save" will only ask for the filename once whereas "Save as"
asks every time.
* **Load** opens the "Edit a Level" menu to choose a Level or Doodad to edit.
* **Options** options the Level Options dialog so you can modify the page type
and wallpaper setting.
* The panel on the left side of the window is your **Tool Box**. Clicking these
buttons activates a specific drawing tool or mode:
* **Pencil Tool** lets you click, drag, and draw pixels of your selected
Palette color onto your level freehand.
* **Line Tool** lets you easily draw a straight line between two points. Click
in your level where you want the first point to be, and drag your mouse to
the second point. Release the mouse to commit the line to your drawing.
* **Rectangle Tool** lets you easily draw rectangles on your level.
* **Ellipse Tool** lets you draw circles or elliptical shapes.
* **Doodad Tool** lets you drag doodads such as buttons and doors onto your
level. See the [Doodad Tool](#doodad-tool) below.
* **Link Tool** lets you link doodads together so that they can interact off
each other. For example, a Button connected to an Electric Door will cause
the door to open and close when the button is pressed. See [Link Tool](#link-tool)
below.
* **Eraser Tool** cleans up your mistakes. Click and drag over pixels you've
drawn to delete the pixels from your level.
* **Brush Size:** the "Size:" label shows the brush size of your current drawing
tool. This translates to the line thickness, or how big your pixels are when
drawn into the level. Click the + and - buttons to increase or decrease the
brush size, and draw thicker or thinner lines.
* The panel on the right side of the window is your **Palette** of colors to
draw with.
![Doodad Toolbar View](../images/editor-2.png)

View File

@ -1,118 +0,0 @@
# Doodad Tool
The game ships with a command-line program called `doodad` which assists in
creating and managing custom doodads and levels.
The `doodad` tool can show and set details on .doodad and .level files used by
the game, create new doodads from PNG images and attach custom JavaScript source
to program behavior of doodads.
## Where to Find It
The `doodad` tool should be in the same place as the game executable.
On Windows, the program is called `doodad.exe` and comes in the zip file next
to the game executable, `doodle.exe`.
On Linux, it will typically be at `/opt/project-doodle/doodad`.
On Mac OS, it is found inside the .app bundle.
## Usage
Run `doodad --help` to get usage information.
The program includes several sub-commands, such as `doodad convert`. Type a
subcommand and `--help` to get help on that command, for example:
```bash
doodad convert --help
```
# Examples
Here are some common scenarios and use cases for the doodad tool.
## Show
```bash
# Usage:
doodad show [doodad or level filename]
```
Shows metadata and details about a level or doodad file.
Examples:
```bash
$ doodad show button.doodad
===== Doodad: button.doodad =====
Headers:
File version: 1
Game version: 0.0.10-alpha
Doodad title: Button
Author: Noah
Locked: true
Hidden: false
Script size: 473 bytes
Palette:
- Swatch name: Color<#000000+ff>
Attributes: solid
Color: #000000
- Swatch name: Color<#666666+ff>
Attributes: none
Color: #666666
- Swatch name: Color<#999999+ff>
Attributes: fire
Color: #999999
Layer 0: button1
Chunks:
Pixels Per Chunk: 37^2
Number Generated: 1
Coordinate Range: (0,0) ... (36,36)
World Dimensions: 36x36
Use -chunks or -verbose to serialize Chunks
Layer 1: button2
Chunks:
Pixels Per Chunk: 37^2
Number Generated: 1
Coordinate Range: (0,0) ... (36,36)
World Dimensions: 36x36
Use -chunks or -verbose to serialize Chunks
```
## Convert
```bash
# Usage:
doodad convert [options] <input files.png> <output file.doodad>
```
### Creating a Doodad from PNG images
Suppose you have PNG images named "frame0.png" through "frame3.png" and want
to create a doodad from those images. This will convert them to the doodad
file "custom.doodad":
```bash
# Convert PNG images into a doodad.
doodad convert frame0.png frame1.png frame2.png frame3.png custom.doodad
# The same, but also attach custom tags with the doodad.
doodad convert --tag color=blue frame{0,1,2,3}.png custom.doodad
```
### Convert a level to a PNG image
```bash
doodad convert my.level output.png
```
### Create a level from a PNG image
```bash
doodad convert level.png output.level
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

View File

@ -1,15 +0,0 @@
# Welcome to Project: Doodle
![](images/main-menu.png)
[Project: Doodle](about.md) is a drawing-based maze game themed around hand-drawn
maps on paper. You can draw a level for a 2D platformer game, drag-and-drop
"doodads" such as buttons and doors into your level, and play it.
## Table of Contents
* [Creating Custom Levels](custom-levels/)
* [Creating Custom Doodads](custom-doodads/)
* [Draw Sprites In-Game](custom-doodads/edit-in-game.md)
* [Draw Sprites with an External Program](custom-doodads/edit-external.md)
* [Program Them with JavaScript](custom-doodads/scripts.md)

View File

@ -1,8 +0,0 @@
site_name: "Project: Doodle Guidebook"
nav:
- Home: index.md
- About: about.md
- "Custom Doodads": custom-doodads/index.md
markdown_extensions:
- toc:
permalink: "#"

View File

@ -1,2 +0,0 @@
markdown
jinja2

7
go.mod
View File

@ -11,12 +11,18 @@ require (
git.kirsle.net/go/render v0.0.0-20200710023247-e5f4c3a16860
git.kirsle.net/go/ui v0.0.0-20200710023146-e2a561fbd04c
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214 // indirect
github.com/alexkohler/nakedret v1.0.0 // indirect
github.com/client9/misspell v0.3.4 // indirect
github.com/google/uuid v1.1.2
github.com/gordonklaus/ineffassign v0.0.0-20201107091007-3b93a8888063 // indirect
github.com/jgautheron/goconst v0.0.0-20201108215931-f8e4fe8351cd // indirect
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/kisielk/errcheck v1.4.0 // indirect
github.com/mdempsky/maligned v0.0.0-20201101000000-d73c43cb16d0 // indirect
github.com/mdempsky/unconvert v0.0.0-20200228143138-95ecdbfc0b5f // indirect
github.com/opennota/check v0.0.0-20180911053232-0c771f5545ff // indirect
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
github.com/stripe/safesql v0.2.0 // indirect
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect
github.com/urfave/cli v1.22.5
github.com/urfave/cli/v2 v2.3.0
@ -27,6 +33,7 @@ require (
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
honnef.co/go/tools v0.0.1-2020.1.6 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 // indirect

22
go.sum
View File

@ -13,15 +13,21 @@ git.kirsle.net/go/render v0.0.0-20200710023247-e5f4c3a16860 h1:0jaJRNlBb/OCB/QSk
git.kirsle.net/go/render v0.0.0-20200710023247-e5f4c3a16860/go.mod h1:ss7pvZbGWrMaDuZwyUTjV9+T0AJwAkxZZHwMFsvHrkk=
git.kirsle.net/go/ui v0.0.0-20200710023146-e2a561fbd04c h1:8fvCXsFGK91WtiTIAdklAN3gHVNYD0hvFvWfznCLMGc=
git.kirsle.net/go/ui v0.0.0-20200710023146-e2a561fbd04c/go.mod h1:Q93RpHNXvbGr3dQ9mmcEN8ZC3dtNQ4FwivSx53tKOVA=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214 h1:YI/8G3uLbYyowJeOPVL6BMKe2wbL54h0FdEKmncU6lU=
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214/go.mod h1:Ef5UOtJdJ5rVFObdOVsrNgKV/Wf4I+daTCSk8GTrHIk=
github.com/alexflint/go-arg v0.0.0-20160306200701-e71d6514f40a h1:Bc+P30eTWphhueyACA/fjiHJXRDq/kGiqO38nGxvml0=
github.com/alexflint/go-arg v0.0.0-20160306200701-e71d6514f40a/go.mod h1:PHxo6ZWOLVMZZgWSAqBynb/KhIqoGO6WKwOVX7rM9dg=
github.com/alexkohler/nakedret v1.0.0 h1:S/bzOFhZHYUJp6qPmdXdFHS5nlWGFmLmoc8QOydvotE=
github.com/alexkohler/nakedret v1.0.0/go.mod h1:tfDQbtPt67HhBK/6P0yNktIX7peCxfOp0jO9007DrLE=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gordonklaus/ineffassign v0.0.0-20201107091007-3b93a8888063 h1:dKprcOvlsvqfWn/iGvz+oYuC2axESeSMuF8dDrWMNsE=
@ -30,6 +36,8 @@ github.com/jgautheron/goconst v0.0.0-20201108215931-f8e4fe8351cd h1:xfrCuNSOStub
github.com/jgautheron/goconst v0.0.0-20201108215931-f8e4fe8351cd/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=
github.com/kisielk/errcheck v1.4.0 h1:ueN6QYA+c7eDQo7ebpNdYR8mUJZThiGz9PEoJEMGPzA=
github.com/kisielk/errcheck v1.4.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -37,14 +45,21 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mdempsky/maligned v0.0.0-20201101000000-d73c43cb16d0 h1:+6XJvFZBYbNv/nSekNWFZyaGNMXcPnZ4n/HHoCXn+Ms=
github.com/mdempsky/maligned v0.0.0-20201101000000-d73c43cb16d0/go.mod h1:3UB4iTzhLciyWcrrvXSkrtCIU+IJ5GCfEmnleHRsxL4=
github.com/mdempsky/unconvert v0.0.0-20200228143138-95ecdbfc0b5f h1:Kc3s6QFyh9DLgInXpWKuG+8I7R7lXbnP7mcoOVIt6KY=
github.com/mdempsky/unconvert v0.0.0-20200228143138-95ecdbfc0b5f/go.mod h1:AmCV4WB3cDMZqgPk+OUQKumliiQS4ZYsBt3AXekyuAU=
github.com/opennota/check v0.0.0-20180911053232-0c771f5545ff h1:lRHufowVGvUvxGsPveAZOpSa/9T5Gpxg6d7UbHCA9MQ=
github.com/opennota/check v0.0.0-20180911053232-0c771f5545ff/go.mod h1:tydB+MZxWpY8M/NRu7jQhND/mXuLAPsKcSV6JkzofsA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU=
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stripe/safesql v0.2.0 h1:xiefmCDd8c35PVSGrL2FhBiaKxviXnGziBDOpOejeBE=
github.com/stripe/safesql v0.2.0/go.mod h1:q7b2n0JmzM1mVGfcYpanfVb2j23cXZeWFxcILPn3JV4=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
@ -68,6 +83,7 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
@ -91,11 +107,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201030204249-4fc0492b8eca h1:KWfVIHfTHZf4IQhrLjrbG+kLyoSym7yYp0WgqtOVH9s=
golang.org/x/tools v0.0.0-20201030204249-4fc0492b8eca/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -115,6 +135,8 @@ gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=