Link Start Flags to Change Characters
New feature: link a Start Flag to another doodad in your level and you will play as that doodad instead of Boy. All Creatures are designed to be playable. Playing as "other" doodads leads to interesting effects, like not being able to activate buttons, switches, or warp doors and not having an inventory to pick up keys. The Anvil is fun: it can destroy other mobile doodads by jumping on them. If the actor does not specify that it has gravity, the gameplay starts in antigravity mode. This will be the vast majority of non-mobile doodads and the Bird. Other changes: * The Blue and Red Azulians now share a doodad script. * The Azulians AI is still to walk back and forth, pickup keys and press buttons. The Blue Azulian walks slower than the red one. * The Blue Azulian is no longer hidden from the doodads list. * Actor UUID values in levels are now V1 UUIDs (time-ordered). This will help to reliably resolve conflicts in draw order of overlapping doodads (newest added to level wins). * Link Tool: clicking on a pair of already-linked doodads will now unlink them, so you don't have to delete one to delete the link. * Actor Tool: deleting an actor immediately calls PruneLinks() to clean up any links that the deleted doodad might have.
This commit is contained in:
parent
d7a96d1770
commit
405aaf509d
|
@ -4,11 +4,13 @@ ALL: build
|
||||||
build:
|
build:
|
||||||
doodad convert -t "Blue Azulian" blu-front.png blu-back.png \
|
doodad convert -t "Blue Azulian" blu-front.png blu-back.png \
|
||||||
blu-wr{1,2,3,4}.png blu-wl{1,2,3,4}.png azu-blu.doodad
|
blu-wr{1,2,3,4}.png blu-wl{1,2,3,4}.png azu-blu.doodad
|
||||||
|
doodad edit-doodad --tag "color=blue" azu-blu.doodad
|
||||||
doodad install-script azulian.js azu-blu.doodad
|
doodad install-script azulian.js azu-blu.doodad
|
||||||
|
|
||||||
doodad convert -t "Red Azulian" red-front.png red-back.png \
|
doodad convert -t "Red Azulian" red-front.png red-back.png \
|
||||||
red-wr{1,2,3,4}.png red-wl{1,2,3,4}.png azu-red.doodad
|
red-wr{1,2,3,4}.png red-wl{1,2,3,4}.png azu-red.doodad
|
||||||
doodad install-script azulian-red.js azu-red.doodad
|
doodad edit-doodad --tag "color=red" azu-red.doodad
|
||||||
|
doodad install-script azulian.js azu-red.doodad
|
||||||
|
|
||||||
# Tag the category for these doodads
|
# Tag the category for these doodads
|
||||||
for i in *.doodad; do\
|
for i in *.doodad; do\
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Azulian (Red)
|
||||||
|
// DEPRECATED: they both share azulian.js now.
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
var playerSpeed = 4;
|
var playerSpeed = 4;
|
||||||
var gravity = 4;
|
var gravity = 4;
|
||||||
|
|
|
@ -1,38 +1,74 @@
|
||||||
function main() {
|
// Azulian (Red and Blue)
|
||||||
var playerSpeed = 12;
|
var playerSpeed = 12,
|
||||||
var gravity = 4;
|
animating = false,
|
||||||
var Vx = Vy = 0;
|
direction = "right";
|
||||||
|
|
||||||
var animating = false;
|
function setupAnimations(color) {
|
||||||
var animStart = animEnd = 0;
|
var left = color === 'blue' ? 'blu-wl' : 'red-wl',
|
||||||
var animFrame = animStart;
|
right = color === 'blue' ? 'blu-wr' : 'red-wr',
|
||||||
|
leftFrames = [left + '1', left + '2', left + '3', left + '4'],
|
||||||
|
rightFrames = [right + '1', right + '2', right + '3', right + '4'];
|
||||||
|
|
||||||
|
Self.AddAnimation("walk-left", 100, leftFrames);
|
||||||
|
Self.AddAnimation("walk-right", 100, rightFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
var color = Self.GetTag("color");
|
||||||
|
playerSpeed = color === 'blue' ? 2 : 4;
|
||||||
|
|
||||||
Self.SetMobile(true);
|
Self.SetMobile(true);
|
||||||
Self.SetGravity(true);
|
Self.SetGravity(true);
|
||||||
Self.SetInventory(true);
|
Self.SetInventory(true);
|
||||||
Self.SetHitbox(7, 4, 17, 28);
|
Self.SetHitbox(0, 0, 24, 32);
|
||||||
Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]);
|
setupAnimations(color);
|
||||||
Self.AddAnimation("walk-right", 100, ["blu-wr1", "blu-wr2", "blu-wr3", "blu-wr4"]);
|
|
||||||
|
|
||||||
|
if (Self.IsPlayer()) {
|
||||||
|
return playerControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A.I. pattern: walks back and forth, turning around
|
||||||
|
// when it meets resistance.
|
||||||
|
|
||||||
|
// Sample our X position every few frames and detect if we've hit a solid wall.
|
||||||
|
var sampleTick = 0;
|
||||||
|
var sampleRate = 5;
|
||||||
|
var lastSampledX = 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++;
|
||||||
|
|
||||||
|
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
|
||||||
|
Self.SetVelocity(Vector(Vx, 0.0));
|
||||||
|
|
||||||
|
if (!Self.IsAnimating()) {
|
||||||
|
Self.PlayAnimation("walk-" + direction, null);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerControls() {
|
||||||
|
// Note: player speed is controlled by the engine.
|
||||||
Events.OnKeypress(function (ev) {
|
Events.OnKeypress(function (ev) {
|
||||||
Vx = 0;
|
|
||||||
Vy = 0;
|
|
||||||
|
|
||||||
if (ev.Right) {
|
if (ev.Right) {
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("walk-right", null);
|
Self.PlayAnimation("walk-right", null);
|
||||||
}
|
}
|
||||||
Vx = playerSpeed;
|
|
||||||
} else if (ev.Left) {
|
} else if (ev.Left) {
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("walk-left", null);
|
Self.PlayAnimation("walk-left", null);
|
||||||
}
|
}
|
||||||
Vx = -playerSpeed;
|
|
||||||
} else {
|
} else {
|
||||||
Self.StopAnimation();
|
Self.StopAnimation();
|
||||||
animating = false;
|
animating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self.SetVelocity(Point(Vx, Vy));
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,5 +91,5 @@ objects
|
||||||
onoff
|
onoff
|
||||||
warpdoor
|
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 ../../assets/doodads/azu-blu.doodad
|
||||||
doodad edit-doodad -hide ../../assets/doodads/boy.doodad
|
doodad edit-doodad -hide ../../assets/doodads/boy.doodad
|
||||||
|
|
|
@ -4,6 +4,7 @@ ALL: build
|
||||||
build:
|
build:
|
||||||
# Start Flag
|
# Start Flag
|
||||||
doodad convert -t "Start Flag" start-flag.png start-flag.doodad
|
doodad convert -t "Start Flag" start-flag.png start-flag.doodad
|
||||||
|
doodad install-script start-flag.js start-flag.doodad
|
||||||
|
|
||||||
# Exit Flag
|
# Exit Flag
|
||||||
doodad convert -t "Exit Flag" exit-flag.png exit-flag.doodad
|
doodad convert -t "Exit Flag" exit-flag.png exit-flag.doodad
|
||||||
|
|
11
dev-assets/doodads/objects/start-flag.js
Normal file
11
dev-assets/doodads/objects/start-flag.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Start Flag.
|
||||||
|
function main() {
|
||||||
|
Self.SetHitbox(22 + 16, 16, 75 - 16, 86);
|
||||||
|
|
||||||
|
// Linking a doodad to the Start Flag sets the
|
||||||
|
// player character. Destroy the original doodads.
|
||||||
|
var links = Self.GetLinks();
|
||||||
|
for (var i = 0; i < links.length; i++) {
|
||||||
|
links[i].Destroy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ var (
|
||||||
|
|
||||||
// Window scrolling behavior in Play Mode.
|
// Window scrolling behavior in Play Mode.
|
||||||
ScrollboxOffset = render.Point{ // from center of screen
|
ScrollboxOffset = render.Point{ // from center of screen
|
||||||
X: 40,
|
X: 60,
|
||||||
Y: 100,
|
Y: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Drawing struct {
|
||||||
// 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.NewUUID()).String()
|
||||||
}
|
}
|
||||||
return &Drawing{
|
return &Drawing{
|
||||||
id: id,
|
id: id,
|
||||||
|
|
|
@ -378,6 +378,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
|
||||||
for _, actor := range actors {
|
for _, actor := range actors {
|
||||||
u.Scene.Level.Actors.Remove(actor)
|
u.Scene.Level.Actors.Remove(actor)
|
||||||
}
|
}
|
||||||
|
u.Scene.Level.PruneLinks()
|
||||||
drawing.InstallActors(u.Scene.Level.Actors)
|
drawing.InstallActors(u.Scene.Level.Actors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,8 +396,15 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
|
||||||
// The actors are a uix.Actor which houses a level.Actor which we
|
// The actors are a uix.Actor which houses a level.Actor which we
|
||||||
// want to update to map each other's IDs.
|
// want to update to map each other's IDs.
|
||||||
idA, idB := a.Actor.ID(), b.Actor.ID()
|
idA, idB := a.Actor.ID(), b.Actor.ID()
|
||||||
a.Actor.AddLink(idB)
|
|
||||||
b.Actor.AddLink(idA)
|
// Are they already linked?
|
||||||
|
if a.Actor.IsLinked(idB) || b.Actor.IsLinked(idA) {
|
||||||
|
a.Actor.Unlink(idB)
|
||||||
|
b.Actor.Unlink(idA)
|
||||||
|
} else {
|
||||||
|
a.Actor.AddLink(idB)
|
||||||
|
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)
|
||||||
|
|
|
@ -19,7 +19,7 @@ func (m ActorMap) Inflate() {
|
||||||
// given a random UUIDv4 ID.
|
// given a random UUIDv4 ID.
|
||||||
func (m ActorMap) Add(a *Actor) {
|
func (m ActorMap) Add(a *Actor) {
|
||||||
if a.id == "" {
|
if a.id == "" {
|
||||||
a.id = uuid.Must(uuid.NewRandom()).String()
|
a.id = uuid.Must(uuid.NewUUID()).String()
|
||||||
}
|
}
|
||||||
m[a.id] = a
|
m[a.id] = a
|
||||||
}
|
}
|
||||||
|
@ -57,3 +57,25 @@ func (a *Actor) AddLink(id string) {
|
||||||
}
|
}
|
||||||
a.Links = append(a.Links, id)
|
a.Links = append(a.Links, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unlink removes the linked actor's ID.
|
||||||
|
func (a *Actor) Unlink(id string) {
|
||||||
|
var newLinks []string
|
||||||
|
for _, exist := range a.Links {
|
||||||
|
if exist == id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newLinks = append(newLinks, exist)
|
||||||
|
}
|
||||||
|
a.Links = newLinks
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLinked checks if the actor is linked to the other actor's ID.
|
||||||
|
func (a *Actor) IsLinked(id string) bool {
|
||||||
|
for _, exist := range a.Links {
|
||||||
|
if exist == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (c *Chunk) generateTexture(mask render.Color) (render.Texturer, error) {
|
||||||
// Generate a unique name for this chunk cache.
|
// Generate a unique name for this chunk cache.
|
||||||
var name string
|
var name string
|
||||||
if c.uuid == uuid.Nil {
|
if c.uuid == uuid.Nil {
|
||||||
c.uuid = uuid.Must(uuid.NewRandom())
|
c.uuid = uuid.Must(uuid.NewUUID())
|
||||||
}
|
}
|
||||||
name = c.uuid.String()
|
name = c.uuid.String()
|
||||||
|
|
||||||
|
|
|
@ -251,40 +251,51 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
||||||
|
|
||||||
// setupPlayer creates and configures the Player Character in the level.
|
// setupPlayer creates and configures the Player Character in the level.
|
||||||
func (s *PlayScene) setupPlayer() {
|
func (s *PlayScene) setupPlayer() {
|
||||||
// Load in the player character.
|
|
||||||
player, err := doodads.LoadFile(balance.PlayerCharacterDoodad)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("PlayScene.Setup: failed to load player doodad: %s", err)
|
|
||||||
player = doodads.NewDummy(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the spawn point of the player. Search the level for the
|
// Find the spawn point of the player. Search the level for the
|
||||||
// "start-flag.doodad"
|
// "start-flag.doodad"
|
||||||
var (
|
var (
|
||||||
|
playerCharacterFilename = balance.PlayerCharacterDoodad
|
||||||
|
|
||||||
spawn render.Point
|
spawn render.Point
|
||||||
|
flag *level.Actor
|
||||||
|
flagSize = render.NewRect(86, 86) // TODO: start-flag.doodad is 86x86 px
|
||||||
flagCount int
|
flagCount int
|
||||||
)
|
)
|
||||||
for actorID, actor := range s.Level.Actors {
|
for actorID, actor := range s.Level.Actors {
|
||||||
if actor.Filename == "start-flag.doodad" {
|
if actor.Filename == "start-flag.doodad" {
|
||||||
if flagCount > 1 {
|
// Support alternative player characters: if the Start Flag is linked
|
||||||
|
// to another actor, that actor becomes the player character.
|
||||||
|
for _, linkID := range actor.Links {
|
||||||
|
if linkedActor, ok := s.Level.Actors[linkID]; ok {
|
||||||
|
playerCharacterFilename = linkedActor.Filename
|
||||||
|
log.Info("Playing as: %s", playerCharacterFilename)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: start-flag.doodad is 86x86 pixels but we can't tell that
|
// TODO: start-flag.doodad is 86x86 pixels but we can't tell that
|
||||||
// from right here.
|
// from right here.
|
||||||
size := render.NewRect(86, 86)
|
|
||||||
log.Info("Found start-flag.doodad at %s (ID %s)", actor.Point, actorID)
|
log.Info("Found start-flag.doodad at %s (ID %s)", actor.Point, actorID)
|
||||||
spawn = render.NewPoint(
|
flag = actor
|
||||||
// X: centered inside the flag.
|
break
|
||||||
actor.Point.X+(size.W/2)-(player.Layers[0].Chunker.Size/2),
|
|
||||||
|
|
||||||
// Y: the bottom of the flag, 4 pixels from the floor.
|
|
||||||
actor.Point.Y+size.H-4-(player.Layers[0].Chunker.Size),
|
|
||||||
)
|
|
||||||
flagCount++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load in the player character.
|
||||||
|
player, err := doodads.LoadFile(playerCharacterFilename)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("PlayScene.Setup: failed to load player doodad: %s", err)
|
||||||
|
player = doodads.NewDummy(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn = render.NewPoint(
|
||||||
|
// X: centered inside the flag.
|
||||||
|
flag.Point.X+(flagSize.W/2)-(player.Layers[0].Chunker.Size/2),
|
||||||
|
|
||||||
|
// Y: the bottom of the flag, 4 pixels from the floor.
|
||||||
|
flag.Point.Y+flagSize.H-4-(player.Layers[0].Chunker.Size),
|
||||||
|
)
|
||||||
|
|
||||||
// Surface warnings around the spawn flag.
|
// Surface warnings around the spawn flag.
|
||||||
if flagCount == 0 {
|
if flagCount == 0 {
|
||||||
s.d.Flash("Warning: this level contained no Start Flag.")
|
s.d.Flash("Warning: this level contained no Start Flag.")
|
||||||
|
|
|
@ -63,7 +63,7 @@ type Actor struct {
|
||||||
// If the id is blank, a new UUIDv4 is generated.
|
// If the id is blank, a new UUIDv4 is generated.
|
||||||
func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor {
|
func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = uuid.Must(uuid.NewRandom()).String()
|
id = uuid.Must(uuid.NewUUID()).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
size := doodad.Layers[0].Chunker.Size
|
size := doodad.Layers[0].Chunker.Size
|
||||||
|
|
Loading…
Reference in New Issue
Block a user