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.
This commit is contained in:
Noah 2020-12-29 20:31:35 -08:00
parent 11afc7b522
commit 2c1185cc9f
34 changed files with 196 additions and 3 deletions

View 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -98,6 +98,17 @@ azulians() {
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() {
cd objects/
@ -136,14 +147,38 @@ onoff() {
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
buttons
switches
doors
trapdoors
azulians
mobs
objects
onoff
warpdoor
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/boy.doodad

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 717 B

View File

@ -1,6 +1,6 @@
// Blue State Block
function main() {
Self.SetHitbox(0, 0, 33, 33);
Self.SetHitbox(0, 0, 42, 42);
// Blue block is ON by default.
var state = true;

View File

@ -1,6 +1,6 @@
// Orange State Block
function main() {
Self.SetHitbox(0, 0, 33, 33);
Self.SetHitbox(0, 0, 42, 42);
// Orange block is OFF by default.
var state = false;

View File

@ -5,7 +5,7 @@ var state = false;
function main() {
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
// being touched again.

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

View 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;
});
}

View File

@ -37,6 +37,7 @@ type Actor struct {
hasGravity bool
isMobile bool // Mobile character, such as the player or an enemy
noclip bool // Disable collision detection
hidden bool // invisible, via Hide() and Show()
hitbox render.Rect
inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item.
data map[string]string // arbitrary key/value store. DEPRECATED ??
@ -117,6 +118,12 @@ func (a *Actor) IsMobile() bool {
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.
func (a *Actor) Size() render.Rect {
return a.Drawing.Size()
@ -157,6 +164,16 @@ func (a *Actor) SetGrounded(v bool) {
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
// clip through level geometry.
func (a *Actor) SetNoclip(v bool) {

View File

@ -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))
continue
}
// Skip hidden actors.
if a.hidden {
continue
}
var (
can = a.Canvas // Canvas widget that draws the actor
actorPoint = a.Position()