2018-07-26 02:38:54 +00:00
|
|
|
package doodle
|
|
|
|
|
|
|
|
import (
|
2020-01-02 01:50:15 +00:00
|
|
|
"fmt"
|
2022-01-03 06:36:32 +00:00
|
|
|
"math/rand"
|
2020-01-02 01:50:15 +00:00
|
|
|
|
2022-09-24 22:17:25 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
|
2024-04-19 03:23:07 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
|
2022-09-24 22:17:25 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
|
2024-04-19 05:12:56 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
|
Update savegame format, Allow out-of-bounds camera
Updates the savegame.json file format:
* Levels now have a UUID value assigned at first save.
* The savegame.json will now track level completion/score based on UUID,
making it robust to filename changes in either levels or levelpacks.
* The savegame file is auto-migrated on startup - for any levels not
found or have no UUID, no change is made, it's backwards compatible.
* Level Properties window adds an "Advanced" tab to show/re-roll UUID.
New JavaScript API for doodad scripts:
* `Actors.CameraFollowPlayer()` tells the camera to return focus to the
player character. Useful for "cutscene" doodads that freeze the player,
call `Self.CameraFollowMe()` and do a thing before unfreezing and sending the
camera back to the player. (Or it will follow them at their next directional
input control).
* `Self.MoveBy(Point(x, y int))` to move the current actor a bit.
New option for the `doodad` command-line tool:
* `doodad resave <.level or .doodad>` will load and re-save a drawing, to
migrate it to the newest file format versions.
Small tweaks:
* On bounded levels, allow the camera to still follow the player if the player
finds themselves WELL far out of bounds (40 pixels margin). So on bounded
levels you can create "interior rooms" out-of-bounds to Warp Door into.
* New wallpaper: "Atmosphere" has a black starscape pattern that fades into a
solid blue atmosphere.
* Camera strictly follows the player the first 20 ticks, not 60 of level start
* If player is frozen, directional inputs do not take the camera focus back.
2023-03-08 05:55:10 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/savegame"
|
2022-09-24 22:17:25 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/uix"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/updater"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
|
2019-12-28 03:16:34 +00:00
|
|
|
"git.kirsle.net/go/render"
|
|
|
|
"git.kirsle.net/go/render/event"
|
|
|
|
"git.kirsle.net/go/ui"
|
2021-09-11 23:52:22 +00:00
|
|
|
"git.kirsle.net/go/ui/style"
|
2018-07-26 02:38:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// MainScene implements the main menu of Doodle.
|
|
|
|
type MainScene struct {
|
2022-01-03 06:36:32 +00:00
|
|
|
Supervisor *ui.Supervisor
|
|
|
|
LevelFilename string // custom level filename to load in background
|
2019-06-25 21:57:11 +00:00
|
|
|
|
|
|
|
// Background wallpaper canvas.
|
2019-06-28 05:54:46 +00:00
|
|
|
scripting *scripting.Supervisor
|
|
|
|
canvas *uix.Canvas
|
2020-01-02 01:50:15 +00:00
|
|
|
|
|
|
|
// UI components.
|
2023-04-09 04:26:08 +00:00
|
|
|
labelTitle *ui.Label
|
|
|
|
labelSubtitle *ui.Label
|
|
|
|
labelVersion *ui.Label
|
|
|
|
labelHint *ui.Label
|
|
|
|
frame *ui.Frame // Main button frame
|
|
|
|
winRegister *ui.Window
|
|
|
|
winSettings *ui.Window
|
|
|
|
winLevelPacks *ui.Window
|
|
|
|
winPlayLevel *ui.Window
|
|
|
|
winOpenDrawing *ui.Window
|
2020-01-02 01:50:15 +00:00
|
|
|
|
|
|
|
// Update check variables.
|
|
|
|
updateButton *ui.Button
|
|
|
|
updateInfo updater.VersionInfo
|
2021-06-09 04:12:30 +00:00
|
|
|
|
|
|
|
// Lazy scroll variables. See LoopLazyScroll().
|
2022-01-17 02:33:27 +00:00
|
|
|
PauseLazyScroll bool // exported for dev console
|
2021-06-09 04:12:30 +00:00
|
|
|
lazyScrollBounce bool
|
|
|
|
lazyScrollTrajectory render.Point
|
|
|
|
lazyScrollLastValue render.Point
|
2021-12-24 05:11:45 +00:00
|
|
|
|
|
|
|
// Landscape mode: if the screen isn't tall enough to see the main
|
|
|
|
// menu we redo the layout to be landscape friendly. NOTE: this only
|
|
|
|
// happens one time, and does not re-adapt when the window is made
|
|
|
|
// tall enough again.
|
|
|
|
landscapeMode bool
|
2022-04-17 00:50:40 +00:00
|
|
|
|
|
|
|
// Debug F3 overlay vars
|
|
|
|
debLoadingViewport *string
|
2018-07-26 02:38:54 +00:00
|
|
|
}
|
|
|
|
|
2022-03-27 21:23:25 +00:00
|
|
|
/*
|
|
|
|
MakePhotogenic tweaks some variables to make a screenshotable title screen.
|
|
|
|
|
|
|
|
This function is designed to be called from the developer shell:
|
|
|
|
|
|
|
|
$ d.Scene.MakePhotogenic(true)
|
|
|
|
|
|
|
|
It automates the pausing of lazy scroll and hiding of UI elements except
|
|
|
|
for just the title and version number.
|
|
|
|
*/
|
|
|
|
func (s *MainScene) MakePhotogenic(v bool) {
|
|
|
|
if v {
|
|
|
|
s.PauseLazyScroll = true
|
|
|
|
s.ButtonFrame().Hide()
|
|
|
|
s.LabelHint().Hide()
|
|
|
|
} else {
|
|
|
|
s.PauseLazyScroll = false
|
|
|
|
s.ButtonFrame().Show()
|
|
|
|
s.LabelHint().Show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-26 02:38:54 +00:00
|
|
|
// Name of the scene.
|
|
|
|
func (s *MainScene) Name() string {
|
|
|
|
return "Main"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup the scene.
|
|
|
|
func (s *MainScene) Setup(d *Doodle) error {
|
2022-04-17 00:50:40 +00:00
|
|
|
s.debLoadingViewport = new(string)
|
|
|
|
customDebugLabels = []debugLabel{
|
|
|
|
{"Chunks:", s.debLoadingViewport},
|
|
|
|
}
|
|
|
|
|
2018-07-26 03:25:02 +00:00
|
|
|
s.Supervisor = ui.NewSupervisor()
|
|
|
|
|
2019-06-28 05:54:46 +00:00
|
|
|
if err := s.SetupDemoLevel(d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-06-25 21:57:11 +00:00
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Main title label
|
|
|
|
s.labelTitle = ui.NewLabel(ui.Label{
|
|
|
|
Text: branding.AppName,
|
2020-04-22 06:50:45 +00:00
|
|
|
Font: balance.TitleScreenFont,
|
2020-01-02 01:50:15 +00:00
|
|
|
})
|
|
|
|
s.labelTitle.Compute(d.Engine)
|
|
|
|
|
2021-07-12 04:54:28 +00:00
|
|
|
// Subtitle/byline.
|
|
|
|
s.labelSubtitle = ui.NewLabel(ui.Label{
|
|
|
|
Text: branding.Byline,
|
|
|
|
Font: balance.TitleScreenSubtitleFont,
|
|
|
|
})
|
|
|
|
s.labelSubtitle.Compute(d.Engine)
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Version label.
|
|
|
|
ver := ui.NewLabel(ui.Label{
|
2024-04-19 03:23:07 +00:00
|
|
|
Text: builds.Version,
|
2021-07-12 04:54:28 +00:00
|
|
|
Font: balance.TitleScreenVersionFont,
|
2020-01-02 01:50:15 +00:00
|
|
|
})
|
|
|
|
ver.Compute(d.Engine)
|
|
|
|
s.labelVersion = ver
|
|
|
|
|
2021-04-01 02:16:33 +00:00
|
|
|
// Arrow Keys hint label (scroll the demo level).
|
|
|
|
s.labelHint = ui.NewLabel(ui.Label{
|
|
|
|
Text: "Hint: press the Arrow keys",
|
|
|
|
Font: render.Text{
|
|
|
|
Size: 16,
|
|
|
|
Color: render.Grey,
|
|
|
|
Shadow: render.Purple,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
s.labelHint.Compute(d.Engine)
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// "Update Available" button.
|
|
|
|
s.updateButton = ui.NewButton("Update Button", ui.NewLabel(ui.Label{
|
|
|
|
Text: "An update is available!",
|
|
|
|
Font: render.Text{
|
2022-03-06 06:44:54 +00:00
|
|
|
FontFilename: balance.SansBoldFont,
|
2020-01-02 01:50:15 +00:00
|
|
|
Size: 16,
|
|
|
|
Color: render.Blue,
|
|
|
|
Padding: 4,
|
|
|
|
},
|
|
|
|
}))
|
2020-04-07 06:21:17 +00:00
|
|
|
s.updateButton.Handle(ui.Click, func(ed ui.EventData) error {
|
2020-01-02 01:50:15 +00:00
|
|
|
native.OpenURL(s.updateInfo.DownloadURL)
|
2020-04-07 06:21:17 +00:00
|
|
|
return nil
|
2020-01-02 01:50:15 +00:00
|
|
|
})
|
|
|
|
s.updateButton.Compute(d.Engine)
|
|
|
|
s.updateButton.Hide()
|
|
|
|
s.Supervisor.Add(s.updateButton)
|
|
|
|
|
2019-06-25 21:57:11 +00:00
|
|
|
// Main UI button frame.
|
2018-08-02 01:52:52 +00:00
|
|
|
frame := ui.NewFrame("frame")
|
2018-08-01 00:18:13 +00:00
|
|
|
s.frame = frame
|
|
|
|
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
var buttons = []struct {
|
2021-09-11 23:52:22 +00:00
|
|
|
Name string
|
2021-12-31 00:31:45 +00:00
|
|
|
If func() bool
|
2021-09-11 23:52:22 +00:00
|
|
|
Func func()
|
|
|
|
Style *style.Button
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
}{
|
2021-12-24 05:11:45 +00:00
|
|
|
{
|
|
|
|
Name: "Story Mode",
|
|
|
|
Func: func() {
|
|
|
|
if s.winLevelPacks == nil {
|
|
|
|
s.winLevelPacks = windows.NewLevelPackWindow(windows.LevelPack{
|
|
|
|
Supervisor: s.Supervisor,
|
|
|
|
Engine: d.Engine,
|
2021-12-27 04:48:29 +00:00
|
|
|
|
2023-02-19 01:37:54 +00:00
|
|
|
OnPlayLevel: func(lp *levelpack.LevelPack, which levelpack.Level) {
|
2021-12-27 04:48:29 +00:00
|
|
|
if err := d.PlayFromLevelpack(lp, which); err != nil {
|
|
|
|
shmem.FlashError(err.Error())
|
|
|
|
}
|
|
|
|
},
|
2021-12-31 00:31:45 +00:00
|
|
|
OnCloseWindow: func() {
|
2022-01-09 02:27:37 +00:00
|
|
|
s.winLevelPacks.Destroy()
|
|
|
|
s.winLevelPacks = nil
|
2021-12-31 00:31:45 +00:00
|
|
|
},
|
2021-12-24 05:11:45 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
s.winLevelPacks.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (s.winLevelPacks.Size().W / 2),
|
|
|
|
Y: (d.height / 2) - (s.winLevelPacks.Size().H / 2),
|
|
|
|
})
|
|
|
|
s.winLevelPacks.Show()
|
|
|
|
},
|
2021-12-27 04:48:29 +00:00
|
|
|
Style: &balance.ButtonBabyBlue,
|
2021-12-24 05:11:45 +00:00
|
|
|
},
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
{
|
2023-04-09 04:26:08 +00:00
|
|
|
Name: "Play a Level",
|
|
|
|
Func: func() {
|
|
|
|
s.showOpenDrawing(d, true)
|
|
|
|
},
|
2021-09-11 23:52:22 +00:00
|
|
|
Style: &balance.ButtonBabyBlue,
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
},
|
|
|
|
{
|
2021-12-31 00:31:45 +00:00
|
|
|
Name: "New Drawing",
|
2021-09-11 23:52:22 +00:00
|
|
|
Func: d.GotoNewMenu,
|
|
|
|
Style: &balance.ButtonPink,
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
},
|
|
|
|
{
|
2023-04-09 04:26:08 +00:00
|
|
|
Name: "Edit Drawing",
|
|
|
|
Func: func() {
|
|
|
|
s.showOpenDrawing(d, false)
|
|
|
|
},
|
2021-12-31 00:31:45 +00:00
|
|
|
Style: &balance.ButtonPink,
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
},
|
2021-06-20 05:14:41 +00:00
|
|
|
{
|
|
|
|
Name: "Settings",
|
|
|
|
Func: func() {
|
|
|
|
if s.winSettings == nil {
|
|
|
|
s.winSettings = d.MakeSettingsWindow(s.Supervisor)
|
|
|
|
}
|
|
|
|
s.winSettings.Show()
|
|
|
|
},
|
|
|
|
},
|
2021-12-31 00:31:45 +00:00
|
|
|
{
|
|
|
|
Name: "Register",
|
|
|
|
If: func() bool {
|
2024-04-19 05:12:56 +00:00
|
|
|
return balance.DPP && !dpp.Driver.IsRegistered()
|
2021-12-31 00:31:45 +00:00
|
|
|
},
|
|
|
|
Func: func() {
|
|
|
|
if s.winRegister == nil {
|
|
|
|
cfg := windows.License{
|
|
|
|
Supervisor: s.Supervisor,
|
|
|
|
Engine: d.Engine,
|
|
|
|
OnCancel: func() {
|
|
|
|
s.winRegister.Hide()
|
|
|
|
},
|
|
|
|
}
|
|
|
|
cfg.OnLicensed = func() {
|
|
|
|
// License status has changed, reload the window!
|
|
|
|
if s.winRegister != nil {
|
|
|
|
s.winRegister.Hide()
|
|
|
|
}
|
|
|
|
s.winRegister = windows.MakeLicenseWindow(d.width, d.height, cfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg.OnLicensed()
|
|
|
|
}
|
|
|
|
s.winRegister.Show()
|
|
|
|
},
|
|
|
|
Style: &balance.ButtonPrimary,
|
|
|
|
},
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
}
|
|
|
|
for _, button := range buttons {
|
2021-12-31 01:57:13 +00:00
|
|
|
if check := button.If; check != nil && !check() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
button := button
|
|
|
|
btn := ui.NewButton(button.Name, ui.NewLabel(ui.Label{
|
|
|
|
Text: button.Name,
|
|
|
|
Font: balance.StatusFont,
|
|
|
|
}))
|
2020-04-07 06:21:17 +00:00
|
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
button.Func()
|
2020-04-07 06:21:17 +00:00
|
|
|
return nil
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
})
|
2021-09-11 23:52:22 +00:00
|
|
|
if button.Style != nil {
|
|
|
|
btn.SetStyle(button.Style)
|
|
|
|
}
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
s.Supervisor.Add(btn)
|
|
|
|
frame.Pack(btn, ui.Pack{
|
2019-12-29 05:48:49 +00:00
|
|
|
Side: ui.N,
|
2019-12-31 02:13:28 +00:00
|
|
|
PadY: 8,
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
// Fill: true,
|
|
|
|
FillX: true,
|
|
|
|
})
|
|
|
|
}
|
2018-07-26 03:25:02 +00:00
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Check for update in the background.
|
|
|
|
go s.checkUpdate()
|
|
|
|
|
Update savegame format, Allow out-of-bounds camera
Updates the savegame.json file format:
* Levels now have a UUID value assigned at first save.
* The savegame.json will now track level completion/score based on UUID,
making it robust to filename changes in either levels or levelpacks.
* The savegame file is auto-migrated on startup - for any levels not
found or have no UUID, no change is made, it's backwards compatible.
* Level Properties window adds an "Advanced" tab to show/re-roll UUID.
New JavaScript API for doodad scripts:
* `Actors.CameraFollowPlayer()` tells the camera to return focus to the
player character. Useful for "cutscene" doodads that freeze the player,
call `Self.CameraFollowMe()` and do a thing before unfreezing and sending the
camera back to the player. (Or it will follow them at their next directional
input control).
* `Self.MoveBy(Point(x, y int))` to move the current actor a bit.
New option for the `doodad` command-line tool:
* `doodad resave <.level or .doodad>` will load and re-save a drawing, to
migrate it to the newest file format versions.
Small tweaks:
* On bounded levels, allow the camera to still follow the player if the player
finds themselves WELL far out of bounds (40 pixels margin). So on bounded
levels you can create "interior rooms" out-of-bounds to Warp Door into.
* New wallpaper: "Atmosphere" has a black starscape pattern that fades into a
solid blue atmosphere.
* Camera strictly follows the player the first 20 ticks, not 60 of level start
* If player is frozen, directional inputs do not take the camera focus back.
2023-03-08 05:55:10 +00:00
|
|
|
// Migrate the savefile format to UUIDs.
|
|
|
|
go func() {
|
|
|
|
if err := savegame.Migrate(); err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-10-10 03:45:38 +00:00
|
|
|
// Eager load the level in background, no time for load screen.
|
|
|
|
go func() {
|
|
|
|
if err := s.setupAsync(d); err != nil {
|
|
|
|
log.Error("MainScene.setupAsync: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-12-24 05:11:45 +00:00
|
|
|
// Trigger our "Window Resized" function so we can check if the
|
|
|
|
// layout needs to be switched to landscape mode for mobile.
|
|
|
|
s.Resized(d.width, d.height)
|
|
|
|
|
2021-10-10 03:45:38 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-09 04:26:08 +00:00
|
|
|
// common function to show the "Open Drawing" window for the Play Level/Edit Drawing buttons.
|
|
|
|
func (s *MainScene) showOpenDrawing(d *Doodle, forPlay bool) {
|
|
|
|
// Find or create the relevant window.
|
|
|
|
var window *ui.Window
|
|
|
|
if forPlay {
|
|
|
|
window = s.winPlayLevel
|
|
|
|
if window == nil {
|
|
|
|
window = windows.NewOpenDrawingWindow(windows.OpenDrawing{
|
|
|
|
Supervisor: s.Supervisor,
|
|
|
|
Engine: shmem.CurrentRenderEngine,
|
|
|
|
LevelsOnly: true,
|
|
|
|
OnOpenDrawing: func(filename string) {
|
|
|
|
d.PlayLevel(filename)
|
|
|
|
},
|
|
|
|
OnCloseWindow: func() {
|
|
|
|
s.winPlayLevel.Destroy()
|
|
|
|
s.winPlayLevel = nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
s.winPlayLevel = window
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
window = s.winOpenDrawing
|
|
|
|
if window == nil {
|
|
|
|
window = windows.NewOpenDrawingWindow(windows.OpenDrawing{
|
|
|
|
Supervisor: s.Supervisor,
|
|
|
|
Engine: shmem.CurrentRenderEngine,
|
|
|
|
OnOpenDrawing: func(filename string) {
|
|
|
|
d.EditFile(filename)
|
|
|
|
},
|
|
|
|
OnCloseWindow: func() {
|
|
|
|
s.winOpenDrawing.Destroy()
|
|
|
|
s.winOpenDrawing = nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
s.winOpenDrawing = window
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (window.Size().W / 2),
|
|
|
|
Y: (d.height / 2) - (window.Size().H / 2),
|
|
|
|
})
|
|
|
|
window.Show()
|
|
|
|
}
|
|
|
|
|
2021-10-10 03:45:38 +00:00
|
|
|
// setupAsync runs background tasks from setup, e.g. eager load
|
|
|
|
// chunks of the level for cache.
|
|
|
|
func (s *MainScene) setupAsync(d *Doodle) error {
|
|
|
|
loadscreen.PreloadAllChunkBitmaps(s.canvas.Chunker())
|
2018-07-26 02:38:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// checkUpdate checks for a version update and shows the button.
|
|
|
|
func (s *MainScene) checkUpdate() {
|
2021-04-01 02:16:33 +00:00
|
|
|
if shmem.OfflineMode {
|
|
|
|
log.Info("OfflineMode: skip updates check")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
info, err := updater.Check()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-11 19:10:59 +00:00
|
|
|
if info.IsNewerVersionThan(branding.Version) {
|
2020-01-02 01:50:15 +00:00
|
|
|
s.updateInfo = info
|
|
|
|
s.updateButton.Show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-28 05:54:46 +00:00
|
|
|
// SetupDemoLevel configures the wallpaper behind the New screen,
|
|
|
|
// which demos a title screen demo level.
|
|
|
|
func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
|
|
|
// Set up the background wallpaper canvas.
|
|
|
|
s.canvas = uix.NewCanvas(100, false)
|
|
|
|
s.canvas.Scrollable = true
|
|
|
|
s.canvas.Resize(render.Rect{
|
2019-12-28 03:16:34 +00:00
|
|
|
W: d.width,
|
|
|
|
H: d.height,
|
2019-06-28 05:54:46 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
s.scripting = scripting.NewSupervisor()
|
|
|
|
s.canvas.SetScriptSupervisor(s.scripting)
|
|
|
|
|
2022-01-03 06:36:32 +00:00
|
|
|
// Title screen level to load. Pick a random level.
|
2022-02-21 01:46:16 +00:00
|
|
|
var (
|
|
|
|
levelName = balance.DemoLevelName[0]
|
|
|
|
fromLevelPack = true
|
|
|
|
lvl *level.Level
|
|
|
|
)
|
2022-01-03 06:36:32 +00:00
|
|
|
if s.LevelFilename != "" {
|
2022-02-21 01:46:16 +00:00
|
|
|
// User provided a custom level name, nix the demo levelpack.
|
2022-01-03 06:36:32 +00:00
|
|
|
levelName = s.LevelFilename
|
2022-02-21 01:46:16 +00:00
|
|
|
fromLevelPack = false
|
2022-01-03 06:36:32 +00:00
|
|
|
} else if len(balance.DemoLevelName) > 1 {
|
|
|
|
randIndex := rand.Intn(len(balance.DemoLevelName))
|
|
|
|
levelName = balance.DemoLevelName[randIndex]
|
|
|
|
}
|
|
|
|
|
2022-02-21 01:46:16 +00:00
|
|
|
// Get the level from the DemoLevelPack?
|
|
|
|
if fromLevelPack {
|
|
|
|
log.Debug("Initializing titlescreen from DemoLevelPack: %s", balance.DemoLevelPack)
|
|
|
|
lp, err := levelpack.LoadFile(balance.DemoLevelPack)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Error loading DemoLevelPack(%s): %s", balance.DemoLevelPack, err)
|
|
|
|
} else {
|
|
|
|
log.Debug("Loading selected level from pack: %s", levelName)
|
2023-02-19 01:37:54 +00:00
|
|
|
levelbin, err := lp.GetFile("levels/" + levelName)
|
2022-02-21 01:46:16 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Error getting level from DemoLevelpack(%s#%s): %s",
|
|
|
|
balance.DemoLevelPack,
|
|
|
|
levelName,
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
log.Debug("Parsing loaded level data (%d bytes)", len(levelbin))
|
|
|
|
lvl, err = level.FromJSON(levelName, levelbin)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("DemoLevelPack FromJSON(%s): %s", levelName, err)
|
|
|
|
lvl = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// May be a user-provided level.
|
|
|
|
if lvl == nil {
|
|
|
|
if trylvl, err := level.LoadFile(levelName); err == nil {
|
|
|
|
lvl = trylvl
|
|
|
|
} else {
|
|
|
|
log.Error("Error loading demo level %s: %s", balance.DemoLevelName, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If still no level, initialize a basic notebook background.
|
|
|
|
if lvl != nil {
|
2021-07-20 00:14:00 +00:00
|
|
|
s.canvas.LoadLevel(lvl)
|
2019-07-07 03:31:50 +00:00
|
|
|
s.canvas.InstallActors(lvl.Actors)
|
|
|
|
|
|
|
|
// Load all actor scripts.
|
|
|
|
if err := s.scripting.InstallScripts(lvl); err != nil {
|
|
|
|
log.Error("Error with title screen level scripts: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run all actors scripts main function to start them off.
|
|
|
|
if err := s.canvas.InstallScripts(); err != nil {
|
|
|
|
log.Error("Error running actor main() functions: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
2022-01-03 06:36:32 +00:00
|
|
|
// Create a basic notebook level.
|
|
|
|
s.canvas.LoadLevel(&level.Level{
|
|
|
|
Chunker: level.NewChunker(100),
|
|
|
|
Palette: level.NewPalette(),
|
|
|
|
PageType: level.Bounded,
|
|
|
|
MaxWidth: 42,
|
|
|
|
MaxHeight: 42,
|
|
|
|
Wallpaper: "notebook.png",
|
|
|
|
})
|
2019-06-28 05:54:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-26 02:38:54 +00:00
|
|
|
// Loop the editor scene.
|
2019-12-22 22:11:01 +00:00
|
|
|
func (s *MainScene) Loop(d *Doodle, ev *event.State) error {
|
2018-07-26 03:25:02 +00:00
|
|
|
s.Supervisor.Loop(ev)
|
2019-06-25 21:57:11 +00:00
|
|
|
|
2022-04-17 00:50:40 +00:00
|
|
|
inside, outside := s.canvas.LoadUnloadMetrics()
|
|
|
|
*s.debLoadingViewport = fmt.Sprintf("%d in %d out", inside, outside)
|
|
|
|
|
2019-06-28 05:54:46 +00:00
|
|
|
if err := s.scripting.Loop(); err != nil {
|
|
|
|
log.Error("MainScene.Loop: scripting.Loop: %s", err)
|
|
|
|
}
|
|
|
|
|
2021-06-09 04:12:30 +00:00
|
|
|
// Lazily scroll the canvas around, slowly.
|
|
|
|
s.LoopLazyScroll()
|
|
|
|
|
2019-06-28 05:54:46 +00:00
|
|
|
s.canvas.Loop(ev)
|
|
|
|
|
2019-12-22 22:11:01 +00:00
|
|
|
if ev.WindowResized {
|
2022-03-06 20:07:59 +00:00
|
|
|
s.Resized(d.width, d.height)
|
2019-06-25 21:57:11 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 02:38:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-24 05:11:45 +00:00
|
|
|
// Resized the app window.
|
|
|
|
func (s *MainScene) Resized(width, height int) {
|
|
|
|
log.Info("Resized to %dx%d", width, height)
|
|
|
|
|
|
|
|
// If the height is not tall enough for the menu, switch to the horizontal layout.
|
2023-12-09 22:59:31 +00:00
|
|
|
isLandscape := balance.IsShortWide(width, height)
|
|
|
|
if isLandscape != s.landscapeMode {
|
|
|
|
log.Info("Toggled LandscapeMode to: %+v", isLandscape)
|
2021-12-24 05:11:45 +00:00
|
|
|
}
|
2023-12-09 22:59:31 +00:00
|
|
|
s.landscapeMode = isLandscape
|
2021-12-24 05:11:45 +00:00
|
|
|
|
|
|
|
s.canvas.Resize(render.Rect{
|
|
|
|
W: width,
|
|
|
|
H: height,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-17 02:33:27 +00:00
|
|
|
// ButtonFrame returns the main button frame.
|
|
|
|
func (s *MainScene) ButtonFrame() *ui.Frame {
|
|
|
|
return s.frame
|
|
|
|
}
|
|
|
|
|
|
|
|
// LabelVersion returns the version widget.
|
|
|
|
func (s *MainScene) LabelVersion() *ui.Label {
|
|
|
|
return s.labelVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
// LabelHint returns the hint widget.
|
|
|
|
func (s *MainScene) LabelHint() *ui.Label {
|
|
|
|
return s.labelHint
|
|
|
|
}
|
|
|
|
|
2021-12-24 05:11:45 +00:00
|
|
|
// Move things into position for the main menu. This function arranges
|
|
|
|
// the Title, Subtitle, Buttons, etc. into screen relative positions every
|
|
|
|
// tick. This function sets their 'default' values, but if the window is
|
|
|
|
// not tall enough and needs the landscape orientation, positionMenuLandscape()
|
|
|
|
// will override these defaults.
|
|
|
|
func (s *MainScene) positionMenuPortrait(d *Doodle) {
|
|
|
|
// App title label.
|
|
|
|
s.labelTitle.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (s.labelTitle.Size().W / 2),
|
|
|
|
Y: 120,
|
|
|
|
})
|
|
|
|
|
|
|
|
// App subtitle label (byline).
|
|
|
|
s.labelSubtitle.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (s.labelSubtitle.Size().W / 2),
|
|
|
|
Y: s.labelTitle.Point().Y + s.labelTitle.Size().H + 8,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Version label
|
|
|
|
s.labelVersion.MoveTo(render.Point{
|
|
|
|
X: (d.width) - (s.labelVersion.Size().W) - 20,
|
|
|
|
Y: 20,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Hint label.
|
|
|
|
s.labelHint.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (s.labelHint.Size().W / 2),
|
|
|
|
Y: d.height - s.labelHint.Size().H - 32,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Update button.
|
|
|
|
s.updateButton.MoveTo(render.Point{
|
|
|
|
X: 24,
|
|
|
|
Y: d.height - s.updateButton.Size().H - 24,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Button frame.
|
|
|
|
s.frame.MoveTo(render.Point{
|
|
|
|
X: (d.width / 2) - (s.frame.Size().W / 2),
|
|
|
|
Y: 260,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MainScene) positionMenuLandscape(d *Doodle) {
|
|
|
|
s.positionMenuPortrait(d)
|
|
|
|
|
|
|
|
var (
|
|
|
|
col1 = render.Rect{
|
|
|
|
X: 0,
|
|
|
|
Y: 0,
|
|
|
|
W: d.width / 2,
|
|
|
|
H: d.height,
|
|
|
|
}
|
|
|
|
col2 = render.Rect{
|
|
|
|
X: d.width,
|
|
|
|
Y: 0,
|
|
|
|
W: d.width - col1.W,
|
|
|
|
H: d.height,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Title and subtitle move to the left.
|
|
|
|
s.labelTitle.MoveTo(render.Point{
|
|
|
|
X: (col1.W / 2) - (s.labelTitle.Size().W / 2),
|
|
|
|
Y: s.labelTitle.Point().Y,
|
|
|
|
})
|
|
|
|
s.labelSubtitle.MoveTo(render.Point{
|
|
|
|
X: (col1.W / 2) - (s.labelSubtitle.Size().W / 2),
|
|
|
|
Y: s.labelTitle.Point().Y + s.labelTitle.Size().H + 8,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Button frame to the right.
|
|
|
|
s.frame.MoveTo(render.Point{
|
|
|
|
X: (col2.X+col2.W)/2 - (s.frame.Size().W / 2),
|
|
|
|
Y: (d.height / 2) - (s.frame.Size().H / 2),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-06-09 04:12:30 +00:00
|
|
|
// LoopLazyScroll gently scrolls the title screen demo level, called each Loop.
|
|
|
|
func (s *MainScene) LoopLazyScroll() {
|
2022-01-17 02:33:27 +00:00
|
|
|
if s.PauseLazyScroll {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-09 04:12:30 +00:00
|
|
|
// The v1 basic sauce algorithm:
|
|
|
|
// 1. We scroll diagonally downwards and rightwards.
|
|
|
|
// 2. When we scroll downwards far enough, we change direction.
|
|
|
|
// Make a zigzag pattern.
|
|
|
|
// 3. When we reach the right bound of the level
|
|
|
|
// OR some max number of px into an unbounded level:
|
|
|
|
// enter a simple ball bouncing mode like a screensaver.
|
|
|
|
var (
|
|
|
|
zigzagMaxHeight = 512
|
|
|
|
maxScrollX = zigzagMaxHeight * 2
|
|
|
|
lastScrollValue = s.lazyScrollLastValue
|
|
|
|
currentScroll = s.canvas.Scroll
|
|
|
|
)
|
|
|
|
|
|
|
|
// So we have two states:
|
|
|
|
// - Zigzag state (default)
|
|
|
|
// - Bounce state (when we hit a wall)
|
|
|
|
if !s.lazyScrollBounce {
|
|
|
|
// Zigzag state.
|
|
|
|
s.lazyScrollTrajectory = render.Point{
|
|
|
|
X: -1, // down and right
|
|
|
|
Y: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
// When we've gone far enough X, it's also far enough Y.
|
|
|
|
if currentScroll.X < -zigzagMaxHeight {
|
|
|
|
s.lazyScrollTrajectory.Y = 1 // go back up
|
|
|
|
}
|
|
|
|
|
|
|
|
// Have we gotten stuck in a corner? (ending the zigzag phase, for bounded levels)
|
|
|
|
if currentScroll.X < 0 && (currentScroll == lastScrollValue) || currentScroll.X < -maxScrollX {
|
|
|
|
log.Debug("LoopLazyScroll: Ending zigzag phase, enter bounce phase")
|
|
|
|
s.lazyScrollBounce = true
|
|
|
|
s.lazyScrollTrajectory = render.Point{
|
|
|
|
X: -1,
|
|
|
|
Y: -1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-05-04 04:15:39 +00:00
|
|
|
var (
|
|
|
|
// Bounded and bordered levels will naturally hit
|
|
|
|
// an edge and stop scrolling
|
|
|
|
bounceY = currentScroll.Y == lastScrollValue.Y
|
|
|
|
bounceX = currentScroll.X == lastScrollValue.X
|
|
|
|
worldsize = s.canvas.Chunker().WorldSize()
|
|
|
|
viewport = s.canvas.Viewport()
|
|
|
|
)
|
|
|
|
|
|
|
|
// In case of unbounded levels, set limits ourself.
|
|
|
|
if !bounceX {
|
|
|
|
if viewport.X < worldsize.X || viewport.X > worldsize.W {
|
|
|
|
bounceX = true
|
|
|
|
|
|
|
|
// Set the trajectory the right direction immediately.
|
|
|
|
if viewport.X < worldsize.X {
|
|
|
|
s.lazyScrollTrajectory.X = 1
|
|
|
|
} else {
|
|
|
|
s.lazyScrollTrajectory.X = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !bounceY {
|
|
|
|
if viewport.Y < worldsize.Y || viewport.Y > worldsize.H {
|
|
|
|
bounceY = true
|
|
|
|
|
|
|
|
// Set the trajectory the right direction immediately.
|
|
|
|
if viewport.Y < worldsize.Y {
|
|
|
|
s.lazyScrollTrajectory.Y = 1
|
|
|
|
} else {
|
|
|
|
s.lazyScrollTrajectory.Y = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-09 04:12:30 +00:00
|
|
|
// Lazy bounce algorithm.
|
2022-05-04 04:15:39 +00:00
|
|
|
if bounceY {
|
2021-06-09 04:12:30 +00:00
|
|
|
log.Debug("LoopLazyScroll: Hit a floor/ceiling")
|
|
|
|
s.lazyScrollTrajectory.Y = -s.lazyScrollTrajectory.Y
|
|
|
|
}
|
2022-05-04 04:15:39 +00:00
|
|
|
if bounceX {
|
2021-06-09 04:12:30 +00:00
|
|
|
log.Debug("LoopLazyScroll: Hit the side of the map!")
|
|
|
|
s.lazyScrollTrajectory.X = -s.lazyScrollTrajectory.X
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the scroll.
|
|
|
|
s.lazyScrollLastValue = currentScroll
|
|
|
|
s.canvas.ScrollBy(s.lazyScrollTrajectory)
|
|
|
|
}
|
|
|
|
|
2018-07-26 02:38:54 +00:00
|
|
|
// Draw the pixels on this frame.
|
|
|
|
func (s *MainScene) Draw(d *Doodle) error {
|
|
|
|
// Clear the canvas and fill it with white.
|
|
|
|
d.Engine.Clear(render.White)
|
|
|
|
|
2019-06-25 21:57:11 +00:00
|
|
|
s.canvas.Present(d.Engine, render.Origin)
|
|
|
|
|
2019-06-28 05:54:46 +00:00
|
|
|
// Draw a sheen over the level for clarity.
|
2021-06-20 05:14:41 +00:00
|
|
|
d.Engine.DrawBox(render.RGBA(255, 255, 254, 96), render.Rect{
|
2019-06-28 05:54:46 +00:00
|
|
|
X: 0,
|
|
|
|
Y: 0,
|
2019-12-28 03:16:34 +00:00
|
|
|
W: d.width,
|
|
|
|
H: d.height,
|
2019-06-28 05:54:46 +00:00
|
|
|
})
|
|
|
|
|
2021-06-20 05:14:41 +00:00
|
|
|
// Draw out bounding boxes.
|
|
|
|
if DebugCollision {
|
|
|
|
for _, actor := range s.canvas.Actors() {
|
|
|
|
d.DrawCollisionBox(s.canvas, actor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-24 05:11:45 +00:00
|
|
|
// Arrange the main widgets by Portrait or Landscape mode.
|
|
|
|
if s.landscapeMode {
|
|
|
|
s.positionMenuLandscape(d)
|
|
|
|
} else {
|
|
|
|
s.positionMenuPortrait(d)
|
|
|
|
}
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// App title label.
|
|
|
|
s.labelTitle.Present(d.Engine, s.labelTitle.Point())
|
|
|
|
|
2021-07-12 04:54:28 +00:00
|
|
|
// App subtitle label (byline).
|
|
|
|
s.labelSubtitle.Present(d.Engine, s.labelSubtitle.Point())
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Version label
|
|
|
|
s.labelVersion.Present(d.Engine, s.labelVersion.Point())
|
|
|
|
|
2021-04-01 02:16:33 +00:00
|
|
|
// Hint label.
|
|
|
|
s.labelHint.Present(d.Engine, s.labelHint.Point())
|
|
|
|
|
2020-01-02 01:50:15 +00:00
|
|
|
// Update button.
|
|
|
|
s.updateButton.Present(d.Engine, s.updateButton.Point())
|
2018-07-26 02:38:54 +00:00
|
|
|
|
2018-08-01 00:18:13 +00:00
|
|
|
s.frame.Compute(d.Engine)
|
2018-08-05 19:54:57 +00:00
|
|
|
s.frame.Present(d.Engine, s.frame.Point())
|
2018-07-26 02:38:54 +00:00
|
|
|
|
2021-06-17 04:55:45 +00:00
|
|
|
// Present supervised windows.
|
|
|
|
s.Supervisor.Present(d.Engine)
|
|
|
|
|
2018-07-26 02:38:54 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy the scene.
|
|
|
|
func (s *MainScene) Destroy() error {
|
2022-04-09 21:41:24 +00:00
|
|
|
log.Debug("MainScene.Destroy(): clean up the demo level canvas")
|
|
|
|
s.canvas.Destroy()
|
2018-07-26 02:38:54 +00:00
|
|
|
return nil
|
|
|
|
}
|