Overhaul the Platformer Physics System
* Player character now experiences acceleration and friction when walking around the map! * Actor position and movement had to be converted from int's (render.Point) to float64's to support fine-grained acceleration steps. * Added "physics" package and physics.Vector to be a float64 counterpart for render.Point. Vector is used for uix.Actor.Position() for the sake of movement math. Vector is flattened back to a render.Point for collision purposes, since the levels and hitboxes are pixel-bound. * Refactor the uix.Actor to no longer extend the doodads.Drawing (so it can have a Position that's a Vector instead of a Point). This broke some code that expected `.Doodad` to directly reference the Drawing.Doodad: now you had to refer to it as `a.Drawing.Doodad` which was ugly. Added convenience method .Doodad() for a shortcut. * Moved functions like GetBoundingRect() from doodads package to collision, where it uses its own slimmer Actor interface for just the relevant methods it needs.
This commit is contained in:
parent
c3d7348843
commit
08e65c32b5
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
log.Info("Azulian '%s' initialized!", Self.Doodad.Title);
|
log.Info("Azulian '%s' initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
var playerSpeed = 4;
|
var playerSpeed = 4;
|
||||||
var gravity = 4;
|
var gravity = 4;
|
||||||
|
@ -28,8 +28,10 @@ function main() {
|
||||||
}
|
}
|
||||||
sampleTick++;
|
sampleTick++;
|
||||||
|
|
||||||
var Vx = playerSpeed * (direction === "left" ? -1 : 1);
|
// TODO: Vector() requires floats, pain in the butt for JS,
|
||||||
Self.SetVelocity(Point(Vx, 0));
|
// the JS API should be friendlier and custom...
|
||||||
|
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
|
||||||
|
Self.SetVelocity(Vector(Vx, 0.0));
|
||||||
|
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("walk-"+direction, null);
|
Self.PlayAnimation("walk-"+direction, null);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Events.OnLeave(function(e) {
|
// Events.OnLeave(function(e) {
|
||||||
// console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
// console.log("%s has stopped touching %s", e, Self.Doodad().Title)
|
||||||
// Self.Canvas.SetBackground(RGBA(0, 0, 1, 0));
|
// Self.Canvas.SetBackground(RGBA(0, 0, 1, 0));
|
||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
var pressed = false;
|
var pressed = false;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
var color = Self.Doodad.Tag("color");
|
var color = Self.Doodad().Tag("color");
|
||||||
var keyname = "key-" + color + ".doodad";
|
var keyname = "key-" + color + ".doodad";
|
||||||
|
|
||||||
// Layers in the doodad image.
|
// Layers in the doodad image.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
Self.AddAnimation("open", 100, [0, 1, 2, 3]);
|
Self.AddAnimation("open", 100, [0, 1, 2, 3]);
|
||||||
Self.AddAnimation("close", 100, [3, 2, 1, 0]);
|
Self.AddAnimation("close", 100, [3, 2, 1, 0]);
|
||||||
|
@ -9,7 +9,7 @@ function main() {
|
||||||
Self.SetHitbox(16, 0, 32, 64);
|
Self.SetHitbox(16, 0, 32, 64);
|
||||||
|
|
||||||
Message.Subscribe("power", function(powered) {
|
Message.Subscribe("power", function(powered) {
|
||||||
console.log("%s got power=%+v", Self.Doodad.Title, powered);
|
console.log("%s got power=%+v", Self.Doodad().Title, powered);
|
||||||
|
|
||||||
if (powered) {
|
if (powered) {
|
||||||
if (animating || opened) {
|
if (animating || opened) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
function main() {
|
function main() {
|
||||||
var color = Self.Doodad.Tag("color");
|
var color = Self.Doodad().Tag("color");
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
if (e.Settled) {
|
if (e.Settled) {
|
||||||
e.Actor.AddItem(Self.Doodad.Filename, 0);
|
e.Actor.AddItem(Self.Doodad().Filename, 0);
|
||||||
Self.Destroy();
|
Self.Destroy();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
function main() {
|
function main() {
|
||||||
Self.AddAnimation("open", 0, [1]);
|
Self.AddAnimation("open", 0, [1]);
|
||||||
var unlocked = false;
|
var unlocked = false;
|
||||||
var color = Self.Doodad.Tag("color");
|
var color = Self.Doodad().Tag("color");
|
||||||
|
|
||||||
Self.SetHitbox(16, 0, 32, 64);
|
Self.SetHitbox(16, 0, 32, 64);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
console.log(Object.keys(console));
|
console.log(Object.keys(console));
|
||||||
console.log(Object.keys(log));
|
console.log(Object.keys(log));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Exit Flag.
|
// Exit Flag.
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
Self.SetHitbox(22+16, 16, 75-16, 86);
|
Self.SetHitbox(22+16, 16, 75-16, 86);
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
var state = false;
|
var state = false;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s ID '%s' initialized!", Self.Doodad.Title, Self.ID());
|
console.log("%s ID '%s' initialized!", Self.Doodad().Title, Self.ID());
|
||||||
Self.SetHitbox(0, 0, 33, 33);
|
Self.SetHitbox(0, 0, 33, 33);
|
||||||
|
|
||||||
// When the button is activated, don't keep toggling state until we're not
|
// When the button is activated, don't keep toggling state until we're not
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
// Switch has two frames:
|
// Switch has two frames:
|
||||||
// 0: Off
|
// 0: Off
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
function main() {
|
function main() {
|
||||||
// What direction is the trapdoor facing?
|
// What direction is the trapdoor facing?
|
||||||
var direction = Self.Doodad.Tag("direction");
|
var direction = Self.Doodad().Tag("direction");
|
||||||
console.log("Trapdoor(%s) initialized", direction);
|
console.log("Trapdoor(%s) initialized", direction);
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
|
|
|
@ -16,7 +16,7 @@ function main() {
|
||||||
// other doodads.
|
// other doodads.
|
||||||
|
|
||||||
// Logs go to the game's log file (standard output on Linux/Mac).
|
// Logs go to the game's log file (standard output on Linux/Mac).
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
// If our doodad has 'solid' parts that should prohibit movement,
|
// If our doodad has 'solid' parts that should prohibit movement,
|
||||||
// define the hitbox here. Coordinates are relative so 0,0 is the
|
// define the hitbox here. Coordinates are relative so 0,0 is the
|
||||||
|
@ -59,13 +59,13 @@ Self holds information about the current doodad. The full surface area of
|
||||||
the Self object is subject to change, but some useful things you can access
|
the Self object is subject to change, but some useful things you can access
|
||||||
from it include:
|
from it include:
|
||||||
|
|
||||||
* Self.Doodad: a pointer to the doodad's file data.
|
* Self.Doodad(): a pointer to the doodad's file data.
|
||||||
* Self.Doodad.Title: get the title of the doodad file.
|
* Self.Doodad().Title: get the title of the doodad file.
|
||||||
* Self.Doodad.Author: the name of the author who wrote the doodad.
|
* Self.Doodad().Author: the name of the author who wrote the doodad.
|
||||||
* Self.Doodad.Script: the doodad's JavaScript source code. Note that
|
* 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
|
modifying this won't have any effect in-game, as the script had already
|
||||||
been loaded into the interpreter.
|
been loaded into the interpreter.
|
||||||
* Self.Doodad.GameVersion: the version of {{ app_name }} that was used
|
* Self.Doodad().GameVersion: the version of {{ app_name }} that was used
|
||||||
when the doodad was created.
|
when the doodad was created.
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
|
@ -14,7 +14,7 @@ Provide a script file with a `main` function:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s initialized!", Self.Doodad().Title);
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
Events.OnCollide( function() {
|
Events.OnCollide( function() {
|
||||||
|
@ -84,18 +84,18 @@ The global variable `Self` holds an API for the current doodad. The full
|
||||||
surface area of this API is subject to change, but some useful examples you
|
surface area of this API is subject to change, but some useful examples you
|
||||||
can do with this are as follows.
|
can do with this are as follows.
|
||||||
|
|
||||||
### Self.Doodad
|
### Self.Doodad()
|
||||||
|
|
||||||
Self.Doodad is a pointer into the doodad's metadata file. Not
|
Self.Doodad() is a pointer into the doodad's metadata file. Not
|
||||||
all properties in there can be written to or read from the
|
all properties in there can be written to or read from the
|
||||||
JavaScript engine, but some useful attributes are:
|
JavaScript engine, but some useful attributes are:
|
||||||
|
|
||||||
* `str Self.Doodad.Title`: the title of the doodad.
|
* `str Self.Doodad().Title`: the title of the doodad.
|
||||||
* `str Self.Doodad.Author`: the author name of the doodad.
|
* `str Self.Doodad().Author`: the author name of the doodad.
|
||||||
* `str Self.Doodad.Script`: your own source code. Note that
|
* `str Self.Doodad().Script`: your own source code. Note that
|
||||||
editing this won't have any effect in-game, as your doodad's
|
editing this won't have any effect in-game, as your doodad's
|
||||||
source has already been loaded into the interpreter.
|
source has already been loaded into the interpreter.
|
||||||
* `str Self.Doodad.GameVersion`: the game version that created
|
* `str Self.Doodad().GameVersion`: the game version that created
|
||||||
the doodad.
|
the doodad.
|
||||||
|
|
||||||
### Self.ShowLayer(int index)
|
### Self.ShowLayer(int index)
|
||||||
|
|
|
@ -14,9 +14,9 @@ var (
|
||||||
ScrollboxVert = 128
|
ScrollboxVert = 128
|
||||||
|
|
||||||
// Player speeds
|
// Player speeds
|
||||||
PlayerMaxVelocity = 6
|
PlayerMaxVelocity float64 = 6
|
||||||
PlayerAcceleration = 2
|
PlayerAcceleration float64 = 0.2
|
||||||
Gravity = 6
|
Gravity float64 = 6
|
||||||
|
|
||||||
// Default chunk size for canvases.
|
// Default chunk size for canvases.
|
||||||
ChunkSize = 128
|
ChunkSize = 128
|
||||||
|
|
42
pkg/collision/bounding_rect.go
Normal file
42
pkg/collision/bounding_rect.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package collision
|
||||||
|
|
||||||
|
import "git.kirsle.net/go/render"
|
||||||
|
|
||||||
|
// GetBoundingRect computes the full pairs of points for the bounding box of
|
||||||
|
// the actor.
|
||||||
|
//
|
||||||
|
// The X,Y coordinates are the position in the level of the actor,
|
||||||
|
// The W,H are the size of the actor's drawn box.
|
||||||
|
func GetBoundingRect(a Actor) render.Rect {
|
||||||
|
var (
|
||||||
|
P = a.Position()
|
||||||
|
S = a.Size()
|
||||||
|
)
|
||||||
|
return render.Rect{
|
||||||
|
X: P.X,
|
||||||
|
Y: P.Y,
|
||||||
|
W: S.W,
|
||||||
|
H: S.H,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoundingRectHitbox returns the bounding rect of the Actor taking into
|
||||||
|
// account their self-declared collision hitbox.
|
||||||
|
//
|
||||||
|
// The rect returned has the X,Y coordinate set to the actor's position, plus
|
||||||
|
// the X,Y of their hitbox, if any.
|
||||||
|
//
|
||||||
|
// The W,H of the rect is the W,H of their declared hitbox.
|
||||||
|
//
|
||||||
|
// If the actor has NOT declared its hitbox, this function returns exactly the
|
||||||
|
// same way as GetBoundingRect() does.
|
||||||
|
func GetBoundingRectHitbox(a Actor, hitbox render.Rect) render.Rect {
|
||||||
|
rect := GetBoundingRect(a)
|
||||||
|
if !hitbox.IsZero() {
|
||||||
|
rect.X += hitbox.X
|
||||||
|
rect.Y += hitbox.Y
|
||||||
|
rect.W = hitbox.W
|
||||||
|
rect.H = hitbox.H
|
||||||
|
}
|
||||||
|
return rect
|
||||||
|
}
|
|
@ -7,6 +7,15 @@ import (
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Actor is a subset of the uix.Actor interface with just the methods needed
|
||||||
|
// for collision checking purposes.
|
||||||
|
type Actor interface {
|
||||||
|
Position() render.Point
|
||||||
|
Size() render.Rect
|
||||||
|
Grounded() bool
|
||||||
|
SetGrounded(bool)
|
||||||
|
}
|
||||||
|
|
||||||
// BoxCollision holds the result of a collision BetweenBoxes.
|
// BoxCollision holds the result of a collision BetweenBoxes.
|
||||||
type BoxCollision struct {
|
type BoxCollision struct {
|
||||||
// A and B are the indexes of the boxes sent to BetweenBoxes.
|
// A and B are the indexes of the boxes sent to BetweenBoxes.
|
||||||
|
|
|
@ -3,7 +3,6 @@ package collision
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
@ -53,7 +52,7 @@ CollidesWithGrid checks if a Doodad collides with level geometry.
|
||||||
|
|
||||||
The `target` is the point the actor wants to move to on this tick.
|
The `target` is the point the actor wants to move to on this tick.
|
||||||
*/
|
*/
|
||||||
func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
||||||
var (
|
var (
|
||||||
P = d.Position()
|
P = d.Position()
|
||||||
S = d.Size()
|
S = d.Size()
|
||||||
|
@ -72,7 +71,7 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test all of the bounding boxes for a collision with level geometry.
|
// Test all of the bounding boxes for a collision with level geometry.
|
||||||
if ok := result.ScanBoundingBox(doodads.GetBoundingRect(d), grid); ok {
|
if ok := result.ScanBoundingBox(GetBoundingRect(d), grid); ok {
|
||||||
// We've already collided! Try to wiggle free.
|
// We've already collided! Try to wiggle free.
|
||||||
if result.Bottom {
|
if result.Bottom {
|
||||||
if !d.Grounded() {
|
if !d.Grounded() {
|
||||||
|
|
|
@ -10,8 +10,8 @@ type Actor interface {
|
||||||
ID() string
|
ID() string
|
||||||
|
|
||||||
// Position and velocity, not saved to disk.
|
// Position and velocity, not saved to disk.
|
||||||
Position() render.Point
|
Position() render.Point // DEPRECATED
|
||||||
Velocity() render.Point
|
Velocity() render.Point // DEPRECATED for uix.Actor
|
||||||
Size() render.Rect
|
Size() render.Rect
|
||||||
Grounded() bool
|
Grounded() bool
|
||||||
SetGrounded(bool)
|
SetGrounded(bool)
|
||||||
|
@ -24,51 +24,3 @@ type Actor interface {
|
||||||
MoveBy(render.Point) // Add {X,Y} to current Position.
|
MoveBy(render.Point) // Add {X,Y} to current Position.
|
||||||
MoveTo(render.Point) // Set current Position to {X,Y}.
|
MoveTo(render.Point) // Set current Position to {X,Y}.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoundingRect computes the full pairs of points for the bounding box of
|
|
||||||
// the actor.
|
|
||||||
//
|
|
||||||
// The X,Y coordinates are the position in the level of the actor,
|
|
||||||
// The W,H are the size of the actor's drawn box.
|
|
||||||
func GetBoundingRect(d Actor) render.Rect {
|
|
||||||
var (
|
|
||||||
P = d.Position()
|
|
||||||
S = d.Size()
|
|
||||||
)
|
|
||||||
return render.Rect{
|
|
||||||
X: P.X,
|
|
||||||
Y: P.Y,
|
|
||||||
W: S.W,
|
|
||||||
H: S.H,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBoundingRectHitbox returns the bounding rect of the Actor taking into
|
|
||||||
// account their self-declared collision hitbox.
|
|
||||||
//
|
|
||||||
// The rect returned has the X,Y coordinate set to the actor's position, plus
|
|
||||||
// the X,Y of their hitbox, if any.
|
|
||||||
//
|
|
||||||
// The W,H of the rect is the W,H of their declared hitbox.
|
|
||||||
//
|
|
||||||
// If the actor has NOT declared its hitbox, this function returns exactly the
|
|
||||||
// same way as GetBoundingRect() does.
|
|
||||||
func GetBoundingRectHitbox(d Actor, hitbox render.Rect) render.Rect {
|
|
||||||
rect := GetBoundingRect(d)
|
|
||||||
if !hitbox.IsZero() {
|
|
||||||
rect.X += hitbox.X
|
|
||||||
rect.Y += hitbox.Y
|
|
||||||
rect.W = hitbox.W
|
|
||||||
rect.H = hitbox.H
|
|
||||||
}
|
|
||||||
return rect
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBoundingRectWithHitbox is like GetBoundingRect but adjusts it for the
|
|
||||||
// relative hitbox of the actor.
|
|
||||||
// func GetBoundingRectWithHitbox(d Actor, hitbox render.Rect) render.Rect {
|
|
||||||
// rect := GetBoundingRect(d)
|
|
||||||
// rect.W = hitbox.W
|
|
||||||
// rect.H = hitbox.H
|
|
||||||
// return rect
|
|
||||||
// }
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ type Drawing struct {
|
||||||
|
|
||||||
// NewDrawing creates a Drawing actor based on a Doodad drawing. If you pass
|
// NewDrawing creates a Drawing actor based on a Doodad drawing. If you pass
|
||||||
// an empty ID string, it will make a random UUIDv4 ID.
|
// an empty ID string, it will make a random UUIDv4 ID.
|
||||||
func NewDrawing(id string, doodad *Doodad) Drawing {
|
func NewDrawing(id string, doodad *Doodad) *Drawing {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = uuid.Must(uuid.NewRandom()).String()
|
id = uuid.Must(uuid.NewRandom()).String()
|
||||||
}
|
}
|
||||||
return Drawing{
|
return &Drawing{
|
||||||
id: id,
|
id: id,
|
||||||
Doodad: doodad,
|
Doodad: doodad,
|
||||||
size: doodad.Rect(),
|
size: doodad.Rect(),
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// Package dummy implements a dummy doodads.Drawing.
|
// Package dummy implements a dummy doodads.Drawing.
|
||||||
package dummy
|
package dummy
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/pkg/doodads"
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
)
|
||||||
|
|
||||||
// Drawing is a dummy doodads.Drawing that has no data.
|
// Drawing is a dummy doodads.Drawing that has no data.
|
||||||
type Drawing struct {
|
type Drawing struct {
|
||||||
doodads.Drawing
|
Drawing *doodads.Drawing
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDrawing creates a new dummy drawing.
|
// NewDrawing creates a new dummy drawing.
|
||||||
|
@ -14,3 +17,8 @@ func NewDrawing(id string, doodad *doodads.Doodad) *Drawing {
|
||||||
Drawing: doodads.NewDrawing(id, doodad),
|
Drawing: doodads.NewDrawing(id, doodad),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the underlying doodads.Drawing.
|
||||||
|
func (d *Drawing) Size() render.Rect {
|
||||||
|
return d.Drawing.Size()
|
||||||
|
}
|
||||||
|
|
|
@ -348,7 +348,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
|
||||||
b.Actor.AddLink(idA)
|
b.Actor.AddLink(idA)
|
||||||
|
|
||||||
// Reset the Link tool.
|
// Reset the Link tool.
|
||||||
d.Flash("Linked '%s' and '%s' together", a.Doodad.Title, b.Doodad.Title)
|
d.Flash("Linked '%s' and '%s' together", a.Doodad().Title, b.Doodad().Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the drop handler for draggable doodads.
|
// Set up the drop handler for draggable doodads.
|
||||||
|
|
|
@ -142,7 +142,7 @@ func (d *Doodle) DrawCollisionBox(actor doodads.Actor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rect = doodads.GetBoundingRect(actor)
|
rect = collision.GetBoundingRect(actor)
|
||||||
box = collision.GetCollisionBox(rect)
|
box = collision.GetCollisionBox(rect)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
15
pkg/physics/math.go
Normal file
15
pkg/physics/math.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package physics
|
||||||
|
|
||||||
|
// Lerp performs linear interpolation between two numbers.
|
||||||
|
//
|
||||||
|
// a and b are the two bounds of the number, and t is a fraction between 0 and
|
||||||
|
// 1 that will return a number between a and b. If t=0, returns a; if t=1,
|
||||||
|
// returns b.
|
||||||
|
func Lerp(a, b, t float64) float64 {
|
||||||
|
return (1.0-t)*a + t*b
|
||||||
|
}
|
||||||
|
|
||||||
|
// LerpInt runs lerp using integers.
|
||||||
|
func LerpInt(a, b int, t float64) float64 {
|
||||||
|
return Lerp(float64(a), float64(b), t)
|
||||||
|
}
|
38
pkg/physics/math_test.go
Normal file
38
pkg/physics/math_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package physics_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLerp(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
Inputs []float64
|
||||||
|
Expect float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Inputs: []float64{0, 1, 0.75},
|
||||||
|
Expect: 0.75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Inputs: []float64{0, 100, 0.5},
|
||||||
|
Expect: 50.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Inputs: []float64{10, 75, 0.3},
|
||||||
|
Expect: 29.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Inputs: []float64{30, 2, 0.75},
|
||||||
|
Expect: 9,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result := physics.Lerp(test.Inputs[0], test.Inputs[1], test.Inputs[2])
|
||||||
|
if result != test.Expect {
|
||||||
|
t.Errorf("Lerp(%+v) expected %f but got %f", test.Inputs, test.Expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
pkg/physics/movement.go
Normal file
33
pkg/physics/movement.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package physics
|
||||||
|
|
||||||
|
// Mover is a moving object.
|
||||||
|
type Mover struct {
|
||||||
|
Acceleration float64
|
||||||
|
Friction float64
|
||||||
|
// Gravity Vector
|
||||||
|
|
||||||
|
// // Position and previous frame's position.
|
||||||
|
// Position render.Point
|
||||||
|
// OldPosition render.Point
|
||||||
|
//
|
||||||
|
// // Speed and previous frame's speed.
|
||||||
|
// Speed render.Point
|
||||||
|
// OldSpeed render.Point
|
||||||
|
MaxSpeed Vector
|
||||||
|
//
|
||||||
|
// // Object is on the ground and its grounded state last frame.
|
||||||
|
// Grounded bool
|
||||||
|
// WasGrounded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMover initializes state for a moving object.
|
||||||
|
func NewMover() *Mover {
|
||||||
|
return &Mover{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // UpdatePhysics runs calculations on the mover's physics each frame.
|
||||||
|
// func (m *Mover) UpdatePhysics() {
|
||||||
|
// m.OldPosition = m.Position
|
||||||
|
// m.OldSpeed = m.Speed
|
||||||
|
// m.WasGrounded = m.Grounded
|
||||||
|
// }
|
54
pkg/physics/vector.go
Normal file
54
pkg/physics/vector.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package physics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vector holds floating point values on an X and Y coordinate.
|
||||||
|
type Vector struct {
|
||||||
|
X float64
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVector creates a Vector from X and Y values.
|
||||||
|
func NewVector(x, y float64) Vector {
|
||||||
|
return Vector{
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VectorFromPoint converts a render.Point into a vector.
|
||||||
|
func VectorFromPoint(p render.Point) Vector {
|
||||||
|
return Vector{
|
||||||
|
X: float64(p.X),
|
||||||
|
Y: float64(p.Y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the vector is zero.
|
||||||
|
func (v Vector) IsZero() bool {
|
||||||
|
return v.X == 0 && v.Y == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the vector.
|
||||||
|
func (v *Vector) Add(other Vector) {
|
||||||
|
v.X += other.X
|
||||||
|
v.Y += other.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPoint converts the vector into a render.Point with integer coordinates.
|
||||||
|
func (v Vector) ToPoint() render.Point {
|
||||||
|
return render.Point{
|
||||||
|
X: int(math.Round(v.X)),
|
||||||
|
Y: int(math.Round(v.Y)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encoding of the vector.
|
||||||
|
func (v Vector) String() string {
|
||||||
|
return fmt.Sprintf("%f,%f", v.X, v.Y)
|
||||||
|
}
|
53
pkg/physics/vector_test.go
Normal file
53
pkg/physics/vector_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package physics_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test converting points to vectors and back again.
|
||||||
|
func TestVectorPoint(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
In render.Point
|
||||||
|
Mid physics.Vector
|
||||||
|
Add physics.Vector
|
||||||
|
Out render.Point
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
In: render.NewPoint(102, 102),
|
||||||
|
Mid: physics.NewVector(102.0, 102.0),
|
||||||
|
Out: render.NewPoint(102, 102),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
In: render.NewPoint(64, 128),
|
||||||
|
Mid: physics.NewVector(64.0, 128.0),
|
||||||
|
Add: physics.NewVector(0.4, 0.6),
|
||||||
|
Out: render.NewPoint(64, 129),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
// Convert point to vector.
|
||||||
|
v := physics.VectorFromPoint(test.In)
|
||||||
|
if v != test.Mid {
|
||||||
|
t.Errorf("Unexpected Vector from Point(%s): wanted %s, got %s",
|
||||||
|
test.In, test.Mid, v,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other vector.
|
||||||
|
v.Add(test.Add)
|
||||||
|
|
||||||
|
// Verify output point rounded down correctly.
|
||||||
|
out := v.ToPoint()
|
||||||
|
if out != test.Out {
|
||||||
|
t.Errorf("Unexpected output vector from Point(%s) -> V(%s) + V(%s): wanted %s, got %s",
|
||||||
|
test.In, test.Mid, test.Add, test.Out, out,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,8 +39,8 @@ func (s *PlayScene) setupInventoryHud() {
|
||||||
|
|
||||||
// Add the inventory frame to the screen frame.
|
// Add the inventory frame to the screen frame.
|
||||||
s.screen.Place(s.invenFrame, ui.Place{
|
s.screen.Place(s.invenFrame, ui.Place{
|
||||||
Left: 40,
|
Top: 40,
|
||||||
Bottom: 40,
|
Right: 40,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Hide inventory if empty.
|
// Hide inventory if empty.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/apps/doodle/pkg/scripting"
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
||||||
"git.kirsle.net/apps/doodle/pkg/uix"
|
"git.kirsle.net/apps/doodle/pkg/uix"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
|
@ -51,6 +52,7 @@ type PlayScene struct {
|
||||||
|
|
||||||
// Player character
|
// Player character
|
||||||
Player *uix.Actor
|
Player *uix.Actor
|
||||||
|
playerPhysics *physics.Mover
|
||||||
antigravity bool // Cheat: disable player gravity
|
antigravity bool // Cheat: disable player gravity
|
||||||
noclip bool // Cheat: disable player clipping
|
noclip bool // Cheat: disable player clipping
|
||||||
playerJumpCounter int // limit jump length
|
playerJumpCounter int // limit jump length
|
||||||
|
@ -235,6 +237,14 @@ func (s *PlayScene) setupPlayer() {
|
||||||
s.drawing.AddActor(s.Player)
|
s.drawing.AddActor(s.Player)
|
||||||
s.drawing.FollowActor = s.Player.ID()
|
s.drawing.FollowActor = s.Player.ID()
|
||||||
|
|
||||||
|
// Set up the movement physics for the player.
|
||||||
|
s.playerPhysics = &physics.Mover{
|
||||||
|
MaxSpeed: physics.NewVector(balance.PlayerMaxVelocity, balance.PlayerMaxVelocity),
|
||||||
|
// Gravity: physics.NewVector(balance.Gravity, balance.Gravity),
|
||||||
|
Acceleration: 0.025,
|
||||||
|
Friction: 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the player character's script in the VM.
|
// Set up the player character's script in the VM.
|
||||||
if err := s.scripting.AddLevelScript(s.Player.ID()); err != nil {
|
if err := s.scripting.AddLevelScript(s.Player.ID()); err != nil {
|
||||||
log.Error("PlayScene.Setup: scripting.InstallActor(player) failed: %s", err)
|
log.Error("PlayScene.Setup: scripting.InstallActor(player) failed: %s", err)
|
||||||
|
@ -412,7 +422,7 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
s.drawing.Present(d.Engine, s.drawing.Point())
|
s.drawing.Present(d.Engine, s.drawing.Point())
|
||||||
|
|
||||||
// Draw out bounding boxes.
|
// Draw out bounding boxes.
|
||||||
d.DrawCollisionBox(s.Player)
|
d.DrawCollisionBox(s.Player.Drawing)
|
||||||
|
|
||||||
// Draw the UI screen and any widgets that attached to it.
|
// Draw the UI screen and any widgets that attached to it.
|
||||||
s.screen.Compute(d.Engine)
|
s.screen.Compute(d.Engine)
|
||||||
|
@ -445,35 +455,79 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
|
|
||||||
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
||||||
func (s *PlayScene) movePlayer(ev *event.State) {
|
func (s *PlayScene) movePlayer(ev *event.State) {
|
||||||
var playerSpeed = balance.PlayerMaxVelocity
|
var (
|
||||||
|
playerSpeed = float64(balance.PlayerMaxVelocity)
|
||||||
|
velocity = s.Player.Velocity()
|
||||||
|
direction float64
|
||||||
|
jumping bool
|
||||||
|
)
|
||||||
|
|
||||||
// If antigravity enabled and the Shift key is pressed down, move the
|
// Antigravity: player can move anywhere with arrow keys.
|
||||||
// player by only one pixel per tick.
|
if s.antigravity {
|
||||||
if s.antigravity && ev.Shift {
|
velocity.X = 0
|
||||||
playerSpeed = 1
|
velocity.Y = 0
|
||||||
}
|
|
||||||
|
|
||||||
var velocity render.Point
|
// Shift to slow your roll to 1 pixel per tick.
|
||||||
|
if ev.Shift {
|
||||||
if ev.Left {
|
playerSpeed = 1
|
||||||
velocity.X = -playerSpeed
|
|
||||||
}
|
|
||||||
if ev.Right {
|
|
||||||
velocity.X = playerSpeed
|
|
||||||
}
|
|
||||||
if ev.Up && (s.Player.Grounded() || s.playerJumpCounter >= 0 || s.antigravity) {
|
|
||||||
velocity.Y = -playerSpeed
|
|
||||||
|
|
||||||
if s.Player.Grounded() {
|
|
||||||
s.playerJumpCounter = 12
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ev.Down && s.antigravity {
|
|
||||||
velocity.Y = playerSpeed
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.Player.Grounded() {
|
if ev.Left {
|
||||||
s.playerJumpCounter--
|
velocity.X = -playerSpeed
|
||||||
|
} else if ev.Right {
|
||||||
|
velocity.X = playerSpeed
|
||||||
|
}
|
||||||
|
if ev.Up {
|
||||||
|
velocity.Y = -playerSpeed
|
||||||
|
} else if ev.Down {
|
||||||
|
velocity.Y = playerSpeed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Moving left or right.
|
||||||
|
if ev.Left {
|
||||||
|
direction = -1
|
||||||
|
} else if ev.Right {
|
||||||
|
direction = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up button to signal they want to jump.
|
||||||
|
if ev.Up && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
|
||||||
|
jumping = true
|
||||||
|
|
||||||
|
if s.Player.Grounded() {
|
||||||
|
// Allow them to sustain the jump this many ticks.
|
||||||
|
s.playerJumpCounter = 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moving left or right? Interpolate their velocity by acceleration.
|
||||||
|
if direction != 0 {
|
||||||
|
// TODO: fast turn-around if they change directions so they don't
|
||||||
|
// slip and slide while their velocity updates.
|
||||||
|
velocity.X = physics.Lerp(
|
||||||
|
velocity.X,
|
||||||
|
direction*s.playerPhysics.MaxSpeed.X,
|
||||||
|
s.playerPhysics.Acceleration,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Slow them back to zero using friction.
|
||||||
|
velocity.X = physics.Lerp(
|
||||||
|
velocity.X,
|
||||||
|
0,
|
||||||
|
s.playerPhysics.Friction,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moving upwards (jumping): give them full acceleration upwards.
|
||||||
|
if jumping {
|
||||||
|
velocity.Y = -playerSpeed
|
||||||
|
}
|
||||||
|
|
||||||
|
// While in the air, count down their jump counter; when zero they
|
||||||
|
// cannot jump again until they touch ground.
|
||||||
|
if !s.Player.Grounded() {
|
||||||
|
s.playerJumpCounter--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Player.SetVelocity(velocity)
|
s.Player.SetVelocity(velocity)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
@ -73,6 +74,7 @@ func (vm *VM) RegisterLevelHooks() error {
|
||||||
"Flash": shmem.Flash,
|
"Flash": shmem.Flash,
|
||||||
"RGBA": render.RGBA,
|
"RGBA": render.RGBA,
|
||||||
"Point": render.NewPoint,
|
"Point": render.NewPoint,
|
||||||
|
"Vector": physics.NewVector,
|
||||||
"Self": vm.Self, // i.e., the uix.Actor object
|
"Self": vm.Self, // i.e., the uix.Actor object
|
||||||
"Events": vm.Events,
|
"Events": vm.Events,
|
||||||
"GetTick": func() uint64 {
|
"GetTick": func() uint64 {
|
||||||
|
|
|
@ -6,9 +6,11 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/collision"
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
@ -23,9 +25,10 @@ import (
|
||||||
// as defined in the map: its spawn coordinate and configuration.
|
// as defined in the map: its spawn coordinate and configuration.
|
||||||
// - A uix.Canvas that can present the actor's graphics to the screen.
|
// - A uix.Canvas that can present the actor's graphics to the screen.
|
||||||
type Actor struct {
|
type Actor struct {
|
||||||
doodads.Drawing
|
id string
|
||||||
Actor *level.Actor
|
Drawing *doodads.Drawing
|
||||||
Canvas *Canvas
|
Actor *level.Actor
|
||||||
|
Canvas *Canvas
|
||||||
|
|
||||||
activeLayer int // active drawing frame for display
|
activeLayer int // active drawing frame for display
|
||||||
flagDestroy bool // flag the actor for destruction
|
flagDestroy bool // flag the actor for destruction
|
||||||
|
@ -38,6 +41,11 @@ type Actor struct {
|
||||||
inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item.
|
inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item.
|
||||||
data map[string]string // arbitrary key/value store. DEPRECATED ??
|
data map[string]string // arbitrary key/value store. DEPRECATED ??
|
||||||
|
|
||||||
|
// Movement data.
|
||||||
|
position render.Point
|
||||||
|
velocity physics.Vector
|
||||||
|
grounded bool
|
||||||
|
|
||||||
// Animation variables.
|
// Animation variables.
|
||||||
animations map[string]*Animation
|
animations map[string]*Animation
|
||||||
activeAnimation *Animation
|
activeAnimation *Animation
|
||||||
|
@ -81,6 +89,17 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
||||||
return actor
|
return actor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ID returns the actor's ID. This is the underlying doodle.Drawing.ID().
|
||||||
|
func (a *Actor) ID() string {
|
||||||
|
return a.Drawing.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doodad offers access to the underlying Doodad object.
|
||||||
|
// Shortcut to the `.Drawing.Doodad` property path.
|
||||||
|
func (a *Actor) Doodad() *doodads.Doodad {
|
||||||
|
return a.Drawing.Doodad
|
||||||
|
}
|
||||||
|
|
||||||
// SetGravity configures whether the actor is affected by gravity.
|
// SetGravity configures whether the actor is affected by gravity.
|
||||||
func (a *Actor) SetGravity(v bool) {
|
func (a *Actor) SetGravity(v bool) {
|
||||||
a.hasGravity = v
|
a.hasGravity = v
|
||||||
|
@ -98,6 +117,46 @@ func (a *Actor) IsMobile() bool {
|
||||||
return a.isMobile
|
return a.isMobile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the actor, from the underlying doodads.Drawing.
|
||||||
|
func (a *Actor) Size() render.Rect {
|
||||||
|
return a.Drawing.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Velocity returns the actor's current velocity vector.
|
||||||
|
func (a *Actor) Velocity() physics.Vector {
|
||||||
|
return a.velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVelocity updates the actor's velocity vector.
|
||||||
|
func (a *Actor) SetVelocity(v physics.Vector) {
|
||||||
|
a.velocity = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position returns the actor's position.
|
||||||
|
func (a *Actor) Position() render.Point {
|
||||||
|
return a.position
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveTo sets the actor's position.
|
||||||
|
func (a *Actor) MoveTo(p render.Point) {
|
||||||
|
a.position = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveBy adjusts the actor's position.
|
||||||
|
func (a *Actor) MoveBy(p render.Point) {
|
||||||
|
a.position.Add(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grounded returns if the actor is touching a floor.
|
||||||
|
func (a *Actor) Grounded() bool {
|
||||||
|
return a.grounded
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGrounded sets the actor's grounded value.
|
||||||
|
func (a *Actor) SetGrounded(v bool) {
|
||||||
|
a.grounded = v
|
||||||
|
}
|
||||||
|
|
||||||
// SetNoclip sets the noclip setting for an actor. If true, the actor can
|
// SetNoclip sets the noclip setting for an actor. If true, the actor can
|
||||||
// clip through level geometry.
|
// clip through level geometry.
|
||||||
func (a *Actor) SetNoclip(v bool) {
|
func (a *Actor) SetNoclip(v bool) {
|
||||||
|
@ -187,7 +246,7 @@ func (a *Actor) Inventory() map[string]int {
|
||||||
|
|
||||||
// GetBoundingRect gets the bounding box of the actor's doodad.
|
// GetBoundingRect gets the bounding box of the actor's doodad.
|
||||||
func (a *Actor) GetBoundingRect() render.Rect {
|
func (a *Actor) GetBoundingRect() render.Rect {
|
||||||
return doodads.GetBoundingRect(a)
|
return collision.GetBoundingRect(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHitbox sets the actor's elected hitbox.
|
// SetHitbox sets the actor's elected hitbox.
|
||||||
|
@ -233,26 +292,26 @@ func (a *Actor) GetData(key string) string {
|
||||||
|
|
||||||
// LayerCount returns the number of layers in this actor's drawing.
|
// LayerCount returns the number of layers in this actor's drawing.
|
||||||
func (a *Actor) LayerCount() int {
|
func (a *Actor) LayerCount() int {
|
||||||
return len(a.Doodad.Layers)
|
return len(a.Doodad().Layers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowLayer sets the actor's ActiveLayer to the index given.
|
// ShowLayer sets the actor's ActiveLayer to the index given.
|
||||||
func (a *Actor) ShowLayer(index int) error {
|
func (a *Actor) ShowLayer(index int) error {
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return errors.New("layer index must be 0 or greater")
|
return errors.New("layer index must be 0 or greater")
|
||||||
} else if index > len(a.Doodad.Layers) {
|
} else if index > len(a.Doodad().Layers) {
|
||||||
return fmt.Errorf("layer %d out of range for doodad's layers", index)
|
return fmt.Errorf("layer %d out of range for doodad's layers", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.activeLayer = index
|
a.activeLayer = index
|
||||||
a.Canvas.Load(a.Doodad.Palette, a.Doodad.Layers[index].Chunker)
|
a.Canvas.Load(a.Doodad().Palette, a.Doodad().Layers[index].Chunker)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowLayerNamed sets the actor's ActiveLayer to the one named.
|
// ShowLayerNamed sets the actor's ActiveLayer to the one named.
|
||||||
func (a *Actor) ShowLayerNamed(name string) error {
|
func (a *Actor) ShowLayerNamed(name string) error {
|
||||||
// Find the layer.
|
// Find the layer.
|
||||||
for i, layer := range a.Doodad.Layers {
|
for i, layer := range a.Doodad().Layers {
|
||||||
if layer.Name == name {
|
if layer.Name == name {
|
||||||
return a.ShowLayer(i)
|
return a.ShowLayer(i)
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (a *Actor) AddAnimation(name string, interval int64, layers []interface{})
|
||||||
switch v := name.(type) {
|
switch v := name.(type) {
|
||||||
case string:
|
case string:
|
||||||
var found bool
|
var found bool
|
||||||
for i, layer := range a.Doodad.Layers {
|
for i, layer := range a.Doodad().Layers {
|
||||||
if layer.Name == v {
|
if layer.Name == v {
|
||||||
indexes = append(indexes, i)
|
indexes = append(indexes, i)
|
||||||
found = true
|
found = true
|
||||||
|
@ -80,7 +80,7 @@ func (a *Actor) AddAnimation(name string, interval int64, layers []interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := int(v)
|
iv := int(v)
|
||||||
if iv < len(a.Doodad.Layers) {
|
if iv < len(a.Doodad().Layers) {
|
||||||
indexes = append(indexes, iv)
|
indexes = append(indexes, iv)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("layer numbered '%d' is out of bounds", iv)
|
return fmt.Errorf("layer numbered '%d' is out of bounds", iv)
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/collision"
|
"git.kirsle.net/apps/doodle/pkg/collision"
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/apps/doodle/pkg/scripting"
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
@ -63,23 +63,36 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
|
|
||||||
// Get the actor's velocity to see if it's moving this tick.
|
// Get the actor's velocity to see if it's moving this tick.
|
||||||
v := a.Velocity()
|
v := a.Velocity()
|
||||||
if a.hasGravity && v.Y >= 0 {
|
|
||||||
v.Y += balance.Gravity
|
// Apply gravity to the actor's velocity.
|
||||||
|
if a.hasGravity && !a.Grounded() { //v.Y >= 0 {
|
||||||
|
if !a.Grounded() {
|
||||||
|
v.Y = physics.Lerp(
|
||||||
|
v.Y, // current speed
|
||||||
|
balance.Gravity, // target max gravity falling downwards
|
||||||
|
balance.PlayerAcceleration,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
v.Y = 0
|
||||||
|
}
|
||||||
|
a.SetVelocity(v)
|
||||||
|
// v.Y += balance.Gravity
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not moving, grab the bounding box right now.
|
// If not moving, grab the bounding box right now.
|
||||||
if v == render.Origin {
|
if v.IsZero() {
|
||||||
boxes[i] = doodads.GetBoundingRect(a)
|
boxes[i] = collision.GetBoundingRect(a)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a delta point from their current location to where they
|
// Create a delta point from their current location to where they
|
||||||
// want to move to this tick.
|
// want to move to this tick.
|
||||||
delta := a.Position()
|
delta := physics.VectorFromPoint(a.Position())
|
||||||
delta.Add(v)
|
delta.Add(v)
|
||||||
|
|
||||||
// Check collision with level geometry.
|
// Check collision with level geometry.
|
||||||
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
chkPoint := delta.ToPoint()
|
||||||
|
info, ok := collision.CollidesWithGrid(a, w.chunks, chkPoint)
|
||||||
if ok {
|
if ok {
|
||||||
// Collision happened with world.
|
// Collision happened with world.
|
||||||
if w.OnLevelCollision != nil {
|
if w.OnLevelCollision != nil {
|
||||||
|
@ -89,17 +102,17 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
|
|
||||||
// Move us back where the collision check put us
|
// Move us back where the collision check put us
|
||||||
if !a.noclip {
|
if !a.noclip {
|
||||||
delta = info.MoveTo
|
delta = physics.VectorFromPoint(info.MoveTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the actor's World Position to the new location.
|
// Move the actor's World Position to the new location.
|
||||||
a.MoveTo(delta)
|
a.MoveTo(delta.ToPoint())
|
||||||
|
|
||||||
// Keep the actor from leaving the world borders of bounded maps.
|
// Keep the actor from leaving the world borders of bounded maps.
|
||||||
w.loopContainActorsInsideLevel(a)
|
w.loopContainActorsInsideLevel(a)
|
||||||
|
|
||||||
// Store this actor's bounding box after they've moved.
|
// Store this actor's bounding box after they've moved.
|
||||||
boxes[i] = doodads.GetBoundingRect(a)
|
boxes[i] = collision.GetBoundingRect(a)
|
||||||
}(i, a)
|
}(i, a)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
@ -115,10 +128,12 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
|
|
||||||
collidingActors[a.ID()] = b.ID()
|
collidingActors[a.ID()] = b.ID()
|
||||||
|
|
||||||
|
// log.Error("between boxes: %+v <%s> <%s>", tuple, a.ID(), b.ID())
|
||||||
|
|
||||||
// Call the OnCollide handler for A informing them of B's intersection.
|
// Call the OnCollide handler for A informing them of B's intersection.
|
||||||
if w.scripting != nil {
|
if w.scripting != nil {
|
||||||
var (
|
var (
|
||||||
rect = doodads.GetBoundingRect(b)
|
rect = collision.GetBoundingRect(b)
|
||||||
lastGoodBox = render.Rect{
|
lastGoodBox = render.Rect{
|
||||||
X: originalPositions[b.ID()].X,
|
X: originalPositions[b.ID()].X,
|
||||||
Y: originalPositions[b.ID()].Y,
|
Y: originalPositions[b.ID()].Y,
|
||||||
|
@ -190,7 +205,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
// Did A protest?
|
// Did A protest?
|
||||||
if err == scripting.ErrReturnFalse {
|
if err == scripting.ErrReturnFalse {
|
||||||
// Are they on top?
|
// Are they on top?
|
||||||
aHitbox := doodads.GetBoundingRectHitbox(a, a.Hitbox())
|
aHitbox := collision.GetBoundingRectHitbox(a.Drawing, a.Hitbox())
|
||||||
if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 {
|
if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 {
|
||||||
onTop = true
|
onTop = true
|
||||||
onTopY = test.Y
|
onTopY = test.Y
|
||||||
|
@ -241,7 +256,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
log.Error(
|
log.Error(
|
||||||
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
||||||
"but I didn't store %s original position earlier??",
|
"but I didn't store %s original position earlier??",
|
||||||
a.Doodad.Title, b.Doodad.Title, b.Doodad.Title,
|
a.Doodad().Title, b.Doodad().Title, b.Doodad().Title,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (w *Canvas) InstallScripts() error {
|
||||||
vm.Self = actor
|
vm.Self = actor
|
||||||
vm.Set("Self", vm.Self)
|
vm.Set("Self", vm.Self)
|
||||||
|
|
||||||
if _, err := vm.Run(actor.Doodad.Script); err != nil {
|
if _, err := vm.Run(actor.Doodad().Script); err != nil {
|
||||||
log.Error("Run script for actor %s failed: %s", actor.ID(), err)
|
log.Error("Run script for actor %s failed: %s", actor.ID(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ func (w *Canvas) LinkAdd(a *Actor) error {
|
||||||
// First click, hold onto this actor.
|
// First click, hold onto this actor.
|
||||||
w.linkFirst = a
|
w.linkFirst = a
|
||||||
shmem.Flash("Doodad '%s' selected, click the next Doodad to link it to",
|
shmem.Flash("Doodad '%s' selected, click the next Doodad to link it to",
|
||||||
a.Doodad.Title,
|
a.Doodad().Title,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Second click, call the OnLinkActors handler with the two actors.
|
// Second click, call the OnLinkActors handler with the two actors.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user