Doodads: Warp Doors, Bird, Larger State Blocks
* The blue and orange ON/OFF state blocks have all been increased in size to better match the player character (42x42 up from 33x33) * Added a new mob: the Red Bird. It flies back and forth while maintaining its altitude, similar to the Red Azulian. Planned AI behavior is to divebomb the player when it gets close. Dive sprites are included but not yet hooked up in JavaScript. * Warp Doors! (WIP). They have a golden "W" on them and come in three varieties: Brown, Blue and Orange. The blue and orange ones are sensitive to the State Block and will become dotted outlines when inactive (and can not be entered in this state). The door opens for the player character, makes him disappear, then closes again. The plan is it will then warp you to the location of a linked Warp Door elsewhere on the level, but for now it will just make the player re-appear after completing the Close Door animation.
54
dev-assets/doodads/bird/bird.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Red bird mob.
|
||||||
|
function main() {
|
||||||
|
var speed = 4;
|
||||||
|
var Vx = Vy = 0;
|
||||||
|
var altitude = Self.Position().Y; // original height in the level
|
||||||
|
|
||||||
|
console.log("Bird altitude is %d", altitude);
|
||||||
|
|
||||||
|
var direction = "left";
|
||||||
|
var states = {
|
||||||
|
flying: 0,
|
||||||
|
diving: 1,
|
||||||
|
};
|
||||||
|
var state = states.flying;
|
||||||
|
|
||||||
|
Self.SetMobile(true);
|
||||||
|
Self.SetGravity(false);
|
||||||
|
Self.SetHitbox(0, 10, 46, 32);
|
||||||
|
Self.AddAnimation("fly-left", 100, ["left-1", "left-2"]);
|
||||||
|
Self.AddAnimation("fly-right", 100, ["right-1", "right-2"]);
|
||||||
|
|
||||||
|
Events.OnCollide(function(e) {
|
||||||
|
if (e.Actor.IsMobile() && e.InHitbox) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sample our X position every few frames and detect if we've hit a solid wall.
|
||||||
|
var sampleTick = 0;
|
||||||
|
var sampleRate = 2;
|
||||||
|
var lastSampledX = 0;
|
||||||
|
var lastSampledY = 0;
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
if (sampleTick % sampleRate === 0) {
|
||||||
|
var curX = Self.Position().X;
|
||||||
|
var delta = Math.abs(curX - lastSampledX);
|
||||||
|
if (delta < 5) {
|
||||||
|
direction = direction === "right" ? "left" : "right";
|
||||||
|
}
|
||||||
|
lastSampledX = curX;
|
||||||
|
}
|
||||||
|
sampleTick++;
|
||||||
|
|
||||||
|
// TODO: Vector() requires floats, pain in the butt for JS,
|
||||||
|
// the JS API should be friendlier and custom...
|
||||||
|
var Vx = parseFloat(speed * (direction === "left" ? -1 : 1));
|
||||||
|
Self.SetVelocity(Vector(Vx, 0.0));
|
||||||
|
|
||||||
|
if (!Self.IsAnimating()) {
|
||||||
|
Self.PlayAnimation("fly-"+direction, null);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
BIN
dev-assets/doodads/bird/dive-left.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
dev-assets/doodads/bird/dive-right.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
dev-assets/doodads/bird/left-1.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
dev-assets/doodads/bird/left-2.png
Normal file
After Width: | Height: | Size: 1022 B |
BIN
dev-assets/doodads/bird/right-1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
dev-assets/doodads/bird/right-2.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
|
@ -98,6 +98,17 @@ azulians() {
|
||||||
cd ..
|
cd ..
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mobs() {
|
||||||
|
cd bird/
|
||||||
|
|
||||||
|
doodad convert -t "Bird (red)" left-1.png left-2.png right-1.png right-2.png \
|
||||||
|
dive-left.png dive-right.png bird-red.doodad
|
||||||
|
doodad install-script bird.js bird-red.doodad
|
||||||
|
|
||||||
|
cp *.doodad ../../../assets/doodads/
|
||||||
|
cd ..
|
||||||
|
}
|
||||||
|
|
||||||
objects() {
|
objects() {
|
||||||
cd objects/
|
cd objects/
|
||||||
|
|
||||||
|
@ -136,14 +147,38 @@ onoff() {
|
||||||
cd ..
|
cd ..
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warpdoor() {
|
||||||
|
cd warp-door/
|
||||||
|
|
||||||
|
doodad convert -t "Warp Door" door-1.png door-2.png door-3.png door-4.png warp-door.doodad
|
||||||
|
doodad edit-doodad -q --tag color=none warp-door.doodad
|
||||||
|
doodad install-script warp-door.js warp-door.doodad
|
||||||
|
|
||||||
|
doodad convert -t "Warp Door (Blue)" blue-1.png blue-2.png blue-3.png blue-4.png blue-off.png \
|
||||||
|
warp-door-blue.doodad
|
||||||
|
doodad edit-doodad -q --tag color=blue warp-door-blue.doodad
|
||||||
|
doodad install-script warp-door.js warp-door-blue.doodad
|
||||||
|
|
||||||
|
doodad convert -t "Warp Door (Orange)" orange-off.png orange-1.png orange-2.png orange-3.png orange-4.png \
|
||||||
|
warp-door-orange.doodad
|
||||||
|
doodad edit-doodad -q --tag color=orange warp-door-orange.doodad
|
||||||
|
doodad install-script warp-door.js warp-door-orange.doodad
|
||||||
|
|
||||||
|
cp *.doodad ../../../assets/doodads/
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
}
|
||||||
|
|
||||||
boy
|
boy
|
||||||
buttons
|
buttons
|
||||||
switches
|
switches
|
||||||
doors
|
doors
|
||||||
trapdoors
|
trapdoors
|
||||||
azulians
|
azulians
|
||||||
|
mobs
|
||||||
objects
|
objects
|
||||||
onoff
|
onoff
|
||||||
|
warpdoor
|
||||||
doodad edit-doodad -quiet -lock -author "Noah" ../../assets/doodads/*.doodad
|
doodad edit-doodad -quiet -lock -author "Noah" ../../assets/doodads/*.doodad
|
||||||
doodad edit-doodad -hide ../../assets/doodads/azu-blu.doodad
|
doodad edit-doodad -hide ../../assets/doodads/azu-blu.doodad
|
||||||
doodad edit-doodad -hide ../../assets/doodads/boy.doodad
|
doodad edit-doodad -hide ../../assets/doodads/boy.doodad
|
||||||
|
|
Before Width: | Height: | Size: 741 B After Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 668 B |
Before Width: | Height: | Size: 683 B After Width: | Height: | Size: 713 B |
Before Width: | Height: | Size: 751 B After Width: | Height: | Size: 805 B |
Before Width: | Height: | Size: 650 B After Width: | Height: | Size: 669 B |
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 717 B |
|
@ -1,6 +1,6 @@
|
||||||
// Blue State Block
|
// Blue State Block
|
||||||
function main() {
|
function main() {
|
||||||
Self.SetHitbox(0, 0, 33, 33);
|
Self.SetHitbox(0, 0, 42, 42);
|
||||||
|
|
||||||
// Blue block is ON by default.
|
// Blue block is ON by default.
|
||||||
var state = true;
|
var state = true;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Orange State Block
|
// Orange State Block
|
||||||
function main() {
|
function main() {
|
||||||
Self.SetHitbox(0, 0, 33, 33);
|
Self.SetHitbox(0, 0, 42, 42);
|
||||||
|
|
||||||
// Orange block is OFF by default.
|
// Orange block is OFF by default.
|
||||||
var state = false;
|
var state = false;
|
||||||
|
|
|
@ -5,7 +5,7 @@ var state = false;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s ID '%s' initialized!", Self.Title, Self.ID());
|
console.log("%s ID '%s' initialized!", Self.Title, Self.ID());
|
||||||
Self.SetHitbox(0, 0, 33, 33);
|
Self.SetHitbox(0, 0, 42, 42);
|
||||||
|
|
||||||
// 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
|
||||||
// being touched again.
|
// being touched again.
|
||||||
|
|
BIN
dev-assets/doodads/warp-door/blue-1.png
Normal file
After Width: | Height: | Size: 850 B |
BIN
dev-assets/doodads/warp-door/blue-2.png
Normal file
After Width: | Height: | Size: 976 B |
BIN
dev-assets/doodads/warp-door/blue-3.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
dev-assets/doodads/warp-door/blue-4.png
Normal file
After Width: | Height: | Size: 807 B |
BIN
dev-assets/doodads/warp-door/blue-off.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
dev-assets/doodads/warp-door/door-1.png
Normal file
After Width: | Height: | Size: 891 B |
BIN
dev-assets/doodads/warp-door/door-2.png
Normal file
After Width: | Height: | Size: 1003 B |
BIN
dev-assets/doodads/warp-door/door-3.png
Normal file
After Width: | Height: | Size: 957 B |
BIN
dev-assets/doodads/warp-door/door-4.png
Normal file
After Width: | Height: | Size: 830 B |
BIN
dev-assets/doodads/warp-door/orange-1.png
Normal file
After Width: | Height: | Size: 832 B |
BIN
dev-assets/doodads/warp-door/orange-2.png
Normal file
After Width: | Height: | Size: 967 B |
BIN
dev-assets/doodads/warp-door/orange-3.png
Normal file
After Width: | Height: | Size: 922 B |
BIN
dev-assets/doodads/warp-door/orange-4.png
Normal file
After Width: | Height: | Size: 803 B |
BIN
dev-assets/doodads/warp-door/orange-off.png
Normal file
After Width: | Height: | Size: 687 B |
81
dev-assets/doodads/warp-door/warp-door.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// Warp Doors
|
||||||
|
function main() {
|
||||||
|
console.log("Warp Door %s Initialized", Self.Title);
|
||||||
|
|
||||||
|
Self.SetHitbox(0, 0, 34, 76);
|
||||||
|
|
||||||
|
// Are we a blue or orange door? Regular warp door will be 'none'
|
||||||
|
var color = Self.GetTag("color");
|
||||||
|
var isStateDoor = color === 'blue' || color === 'orange';
|
||||||
|
var state = color === 'blue'; // Blue door is ON by default.
|
||||||
|
|
||||||
|
var animating = false;
|
||||||
|
var collide = false;
|
||||||
|
|
||||||
|
// Declare animations and sprite names.
|
||||||
|
var animSpeed = 100;
|
||||||
|
var spriteDefault, spriteDisabled; // the latter for state doors.
|
||||||
|
if (color === 'blue') {
|
||||||
|
Self.AddAnimation("open", animSpeed, ["blue-2", "blue-3", "blue-4"]);
|
||||||
|
Self.AddAnimation("close", animSpeed, ["blue-4", "blue-3", "blue-2", "blue-1"]);
|
||||||
|
spriteDefault = "blue-1";
|
||||||
|
spriteDisabled = "blue-off";
|
||||||
|
} else if (color === 'orange') {
|
||||||
|
Self.AddAnimation("open", animSpeed, ["orange-2", "orange-3", "orange-4"]);
|
||||||
|
Self.AddAnimation("close", animSpeed, ["orange-4", "orange-3", "orange-2", "orange-1"]);
|
||||||
|
spriteDefault = "orange-1";
|
||||||
|
spriteDisabled = "orange-off";
|
||||||
|
} else {
|
||||||
|
Self.AddAnimation("open", animSpeed, ["door-2", "door-3", "door-4"]);
|
||||||
|
Self.AddAnimation("close", animSpeed, ["door-4", "door-3", "door-2", "door-1"]);
|
||||||
|
spriteDefault = "door-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Warp %s: default=%s disabled=%+v color=%s isState=%+v state=%+v", Self.Title, spriteDefault, spriteDisabled, color, isStateDoor, state);
|
||||||
|
|
||||||
|
// Subscribe to the global state-change if we are a state door.
|
||||||
|
if (isStateDoor) {
|
||||||
|
Message.Subscribe("broadcast:state-change", function(newState) {
|
||||||
|
console.log("Warp %s: received state to %+v", Self.Title, newState);
|
||||||
|
state = color === 'blue' ? !newState : newState;
|
||||||
|
|
||||||
|
// Activate or deactivate the door.
|
||||||
|
Self.ShowLayerNamed(state ? spriteDefault : spriteDisabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: respond to a "Use" button instead of a Collide to open the door.
|
||||||
|
Events.OnCollide(function(e) {
|
||||||
|
if (!e.Settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animating || collide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only players can use doors for now.
|
||||||
|
if (e.Actor.IsPlayer() && e.InHitbox) {
|
||||||
|
if (isStateDoor && !state) {
|
||||||
|
// The state door is inactive (dotted outline).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the open and close animation.
|
||||||
|
animating = true;
|
||||||
|
collide = true;
|
||||||
|
Self.PlayAnimation("open", function() {
|
||||||
|
e.Actor.Hide()
|
||||||
|
Self.PlayAnimation("close", function() {
|
||||||
|
Self.ShowLayerNamed(isStateDoor && !state ? spriteDisabled : spriteDefault);
|
||||||
|
e.Actor.Show()
|
||||||
|
animating = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.OnLeave(function(e) {
|
||||||
|
collide = false;
|
||||||
|
});
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ type Actor struct {
|
||||||
hasGravity bool
|
hasGravity bool
|
||||||
isMobile bool // Mobile character, such as the player or an enemy
|
isMobile bool // Mobile character, such as the player or an enemy
|
||||||
noclip bool // Disable collision detection
|
noclip bool // Disable collision detection
|
||||||
|
hidden bool // invisible, via Hide() and Show()
|
||||||
hitbox render.Rect
|
hitbox render.Rect
|
||||||
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 ??
|
||||||
|
@ -117,6 +118,12 @@ func (a *Actor) IsMobile() bool {
|
||||||
return a.isMobile
|
return a.isMobile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPlayer returns whether the actor is the player character.
|
||||||
|
// It's true when the Actor ID is "PLAYER"
|
||||||
|
func (a *Actor) IsPlayer() bool {
|
||||||
|
return a.Canvas.Name == "PLAYER"
|
||||||
|
}
|
||||||
|
|
||||||
// Size returns the size of the actor, from the underlying doodads.Drawing.
|
// Size returns the size of the actor, from the underlying doodads.Drawing.
|
||||||
func (a *Actor) Size() render.Rect {
|
func (a *Actor) Size() render.Rect {
|
||||||
return a.Drawing.Size()
|
return a.Drawing.Size()
|
||||||
|
@ -157,6 +164,16 @@ func (a *Actor) SetGrounded(v bool) {
|
||||||
a.grounded = v
|
a.grounded = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide makes the actor invisible.
|
||||||
|
func (a *Actor) Hide() {
|
||||||
|
a.hidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a hidden actor.
|
||||||
|
func (a *Actor) Show() {
|
||||||
|
a.hidden = false
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -113,6 +113,12 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
||||||
log.Error("Canvas.drawActors: null actor at index %d (of %d actors)", i, len(w.actors))
|
log.Error("Canvas.drawActors: null actor at index %d (of %d actors)", i, len(w.actors))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip hidden actors.
|
||||||
|
if a.hidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
can = a.Canvas // Canvas widget that draws the actor
|
can = a.Canvas // Canvas widget that draws the actor
|
||||||
actorPoint = a.Position()
|
actorPoint = a.Position()
|
||||||
|
|