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 .. 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

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 // 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;

View File

@ -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;

View File

@ -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.

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 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) {

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)) 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()