Scripting: Bring Self API up to Actor API parity

Updates the Self API to expose more uix.Actor fields:

* Doodad()
* GetBoundingRect()
* HasGravity()
* IsFrozen()
* IsMobile()
* LayerCount()
* ListItems()
* SetGrounded()
* SetWet()
* Velocity() - note: it was Self.GetVelocity() before.

Self.GetVelocity is deprecated and made an alias to Velocity.
This commit is contained in:
Noah 2024-04-26 22:34:13 -07:00
parent 21847f5e57
commit c4456ac51b
3 changed files with 146 additions and 100 deletions

View File

@ -49,6 +49,16 @@ game:
* If you receive a map with custom doodads, you can **install** the doodads
into your copy of the game and use them in your own maps.
# Developer Documentation
In case you are reading this from the game's open source repository, take a
look in the `docs/` folder or the `*.md` files in the root of the repository.
Some to start with:
* [Building](Building.md) the game (tl;dr. run bootstrap.py)
* [Tour of the Code](docs/Tour%20of%20the%20Code.md)
# Keybindings
Global Keybindings:
@ -134,52 +144,6 @@ four directions but characters with gravity only move left and right
and have the dedicated "Jump" button. This differs from regular
keyboard controls where the Up arrow is to Jump.
# Built-In Doodads
A brief introduction to the built-in doodads available so far:
- **Characters**
- Blue Azulian: this is used as the player character for now. If
dragged into a level, it doesn't do anything but is affected
by gravity.
- Red Azulian: an example mobile mob for now. It walks back and
forth, changing directions only when it comes across an
obstacle and can't proceed any further.
- **Doors and Keys**
- Colored Doors: these act like solid barriers until the player or
another doodad collects the matching colored key.
- Colored Keys: these are collectable items that allow the player or
another doodad to open the door of matching color. Note that
inventories are doodad-specific, so other mobs besides the
player can steal a key before the player gets it! (For now)
- Electric Door: this mechanical door stays closed and only
opens when it receives a power signal from a linked button.
- Trapdoor: this door allows one-way access, but once it's closed
behind you, you can't pass through it from that side!
- **Buttons**
- Button: while pressed by a player or other doodad, the button
emits a power signal to any doodad it is linked to. Link a button
to an electric door, and the door will open while the button is
pressed and close when the button is released.
- Sticky Button: this button will stay pressed once activated and
will not release unless it receives a power signal from another
linked doodad. For example, one Button that links to a Sticky
Button will release the sticky button if pressed.
- **Switches**
- Switch: when touched by the player or other doodad, the switch will
toggle its state from "OFF" to "ON" or vice versa. Link it to an
Electric Door to open/close the door. Link switches _to each other_ as
well as to a door, and all switches will stay in sync with their ON/OFF
state when any switch is pressed.
- **Crumbly Floor**
- This rocky floor will break and fall away after being stepped on.
- **Two State Blocks**
- Blue and orange blocks that will toggle between solid and pass-thru
whenever the corresponding ON/OFF block is hit.
- **Start and Exit Flags**
- The "Go" flag lets you pick a spawn point for the player character.
- The "Exit" flag marks the level goal.
# Developer Console
Press `Enter` at any time to open the developer console. The console

57
docs/Tour of the Code.md Normal file
View File

@ -0,0 +1,57 @@
# Tour of the Code
Here is a brief tour of where to find important parts of the game's code.
## Basic App Structure
**cmd/doodle/main.go** is the main function for Sketchy Maze and handles the command line flags and bootstrapping the game.
**pkg/doodle.go** holds the root Doodle (`d`) struct, the base object for the meat of the program - its setup funcs and MainLoop are here.
### Scenes
The gameplay is divided into scenes, each in a **pkg/\*_scene.go** file:
* MainScene is the title screen (default)
* PlayScene is play mode
* EditScene is the level editor, etc.
**pkg/scene.go** defines the interface for Scene itself and the `d.Goto(scene)` function.
The game changes scenes with Goto and the MainLoop calls the active scene's functions (Loop and Draw).
## Doodad JavaScript API
### pkg/scripting
This package provides the generic JavaScript API used by the developer console and doodads.
**pkg/scripting/js_api.go** defines global and generic JavaScript functions to doodads' scope, e.g.:
* console.log and friends
* Sound.Play
* RGBA, Point, and Vector
* Flash, GetTick
* time.Now() and friends
* Events
* setTimeout, setInterval..
* Self: provided externally.
### pkg/uix/scripting.go
The home of **MakeScriptAPI()** which installs various Play Mode objects into the script's environment, e.g.
* Actors and Level API
* Self: a surface of uix.Actor capabilities that point to the local actor.
## Collision Detection
### pkg/collision
The algorithmic stuff is here - the collision package focused on just the math and general types to handle logic for:
* Collision with level geometry ('pixel perfect')
* Collision between actors
* BoundingRect and collision boxes
### pkg/uix/actor_collision.go

View File

@ -93,60 +93,10 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} {
"Filename": actor.Doodad().Filename,
"Title": actor.Doodad().Title,
// functions
"ID": actor.ID,
"Size": actor.Size,
"GetTag": actor.Doodad().Tag,
"Options": actor.Options,
"GetOption": func(name string) interface{} {
opt := actor.GetOption(name)
if opt == nil {
return nil
}
return opt.Value
},
"Position": actor.Position,
"MoveTo": func(p render.Point) {
actor.MoveTo(p)
actor.SetGrounded(false)
},
"MoveBy": func(p render.Point) {
actor.MoveBy(p)
actor.SetGrounded(false)
},
"IsOnScreen": actor.IsOnScreen,
// // TODO: passing this to actor.IsOnScreen didn't work?
// return actor.Position().Inside(actor.Canvas.ViewportRelative())
// },
"Grounded": actor.Grounded,
"SetHitbox": actor.SetHitbox,
"Hitbox": actor.Hitbox,
"SetVelocity": actor.SetVelocity,
"GetVelocity": actor.Velocity,
"SetMobile": actor.SetMobile,
"SetInventory": actor.SetInventory,
"HasInventory": actor.HasInventory,
"SetGravity": actor.SetGravity,
"Invulnerable": actor.Invulnerable,
"SetInvulnerable": actor.SetInvulnerable,
"AddAnimation": actor.AddAnimation,
"IsAnimating": actor.IsAnimating,
"IsPlayer": actor.IsPlayer,
"PlayAnimation": actor.PlayAnimation,
"StopAnimation": actor.StopAnimation,
"ShowLayer": actor.ShowLayer,
"ShowLayerNamed": actor.ShowLayerNamed,
"Inventory": actor.Inventory,
"AddItem": actor.AddItem,
"RemoveItem": actor.RemoveItem,
"HasItem": actor.HasItem,
"ClearInventory": actor.ClearInventory,
"Destroy": actor.Destroy,
"Freeze": actor.Freeze,
"Unfreeze": actor.Unfreeze,
"IsWet": actor.IsWet,
"Hide": actor.Hide,
"Show": actor.Show,
/*
Unique options to the Self API over regular Actors.
*/
"GetLinks": func() []map[string]interface{} {
var result = []map[string]interface{}{}
for _, linked := range w.GetLinkedActors(actor) {
@ -160,5 +110,80 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} {
// Update the doodad that the camera should focus on.
w.FollowActor = actor.ID()
},
// functions
"GetTag": actor.Doodad().Tag,
/*
Expose the uix.Actor API surface area, in the same order
that these functions are defined in their respective files
to keep it easy to maintain.
*/
// actor.go
"ID": actor.ID,
"Doodad": actor.Doodad,
"SetGravity": actor.SetGravity,
"SetMobile": actor.SetMobile,
"SetInventory": actor.SetInventory,
"IsMobile": actor.IsMobile,
"IsPlayer": actor.IsPlayer,
"HasInventory": actor.HasInventory,
"HasGravity": actor.HasGravity,
"Invulnerable": actor.Invulnerable,
"SetInvulnerable": actor.SetInvulnerable,
"IsWet": actor.IsWet,
"IsOnScreen": actor.IsOnScreen,
"SetWet": actor.SetWet,
"Size": actor.Size,
"Velocity": actor.Velocity,
"GetVelocity": actor.Velocity, // TODO: deprecated, old Self.GetVelocity inconsistency
"SetVelocity": actor.SetVelocity,
"Position": actor.Position,
"MoveTo": func(p render.Point) {
actor.MoveTo(p)
actor.SetGrounded(false)
},
"MoveBy": func(p render.Point) {
actor.MoveBy(p)
actor.SetGrounded(false)
},
"Grounded": actor.Grounded,
"SetGrounded": actor.SetGrounded,
"IsCoyoteTime": actor.IsCoyoteTime,
"Hide": actor.Hide,
"Show": actor.Show,
"Freeze": actor.Freeze,
"Unfreeze": actor.Unfreeze,
"IsFrozen": actor.IsFrozen,
// Not: SetUsing, SetNoclip
"AddItem": actor.AddItem,
"RemoveItem": actor.RemoveItem,
"ClearInventory": actor.ClearInventory,
"HasItem": actor.HasItem,
"ListItems": actor.ListItems,
"Inventory": actor.Inventory,
"GetBoundingRect": actor.GetBoundingRect,
"SetHitbox": actor.SetHitbox,
"Hitbox": actor.Hitbox,
"Options": actor.Options,
"GetOption": func(name string) interface{} {
opt := actor.GetOption(name)
if opt == nil {
return nil
}
return opt.Value
},
"LayerCount": actor.LayerCount,
"ShowLayer": actor.ShowLayer,
"ShowLayerNamed": actor.ShowLayerNamed,
"Destroy": actor.Destroy,
// actor_animation.go
"AddAnimation": actor.AddAnimation,
"PlayAnimation": actor.PlayAnimation,
"IsAnimating": actor.IsAnimating,
"StopAnimation": actor.StopAnimation,
}
}