Skip to content

Commit 7e9ae34

Browse files
committed
Add devtools
This commits adds only basic functionality: * pausing/resuming the game by hitting F12 * screen inspector: showing the cursor coordinates and pixel color
1 parent 9cce257 commit 7e9ae34

File tree

12 files changed

+228
-16
lines changed

12 files changed

+228
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The retro game development engine for Go, inspired by [Pico-8](https://www.lexal
1010

1111
### Is this a new fantasy console?
1212

13-
No, it's not. It's rather a game development library with some additional tools (like a console) which make it simple (and fun!) to write retro games in Go.
13+
No, it's not. It's rather a game development library with dev-tools which make it simple (and fun!) to write retro games in Go.
1414

1515
### What is a retro game?
1616

devtools/control.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package devtools
5+
6+
import (
7+
"github.com/elgopher/pi/devtools/internal/snapshot"
8+
"github.com/elgopher/pi/vm"
9+
)
10+
11+
var (
12+
paused bool
13+
timeWhenPaused float64
14+
)
15+
16+
func pauseGame() {
17+
paused = true
18+
timeWhenPaused = vm.TimeSeconds
19+
snapshot.Take()
20+
}
21+
22+
func resumeGame() {
23+
paused = false
24+
vm.TimeSeconds = timeWhenPaused
25+
}

devtools/devtools.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package devtools
5+
6+
import (
7+
"github.com/elgopher/pi"
8+
"github.com/elgopher/pi/vm"
9+
)
10+
11+
var (
12+
// BgColor is used to draw background behind the text
13+
BgColor byte = 1
14+
// FgColor is used to print text, draw icons etc.
15+
FgColor byte = 7
16+
)
17+
18+
// MustRun runs the game using backend, similarly to pi.MustRun.
19+
//
20+
// Any time you can pause them game by pressing F12. This will
21+
// show screen inspector. F12 again resumes the game.
22+
func MustRun(runBackend func() error) {
23+
update := pi.Update
24+
draw := pi.Draw
25+
26+
pi.Update = func() {
27+
updateDevTools()
28+
29+
if !paused && update != nil {
30+
update()
31+
handleStoppedGame()
32+
}
33+
}
34+
35+
pi.Draw = func() {
36+
if !paused && draw != nil {
37+
draw()
38+
handleStoppedGame()
39+
} else {
40+
drawDevTools()
41+
}
42+
}
43+
44+
pi.MustRun(runBackend)
45+
}
46+
47+
func handleStoppedGame() {
48+
if vm.GameLoopStopped {
49+
pauseGame()
50+
vm.GameLoopStopped = false
51+
}
52+
}

devtools/draw.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package devtools
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/elgopher/pi"
10+
"github.com/elgopher/pi/devtools/internal/icons"
11+
"github.com/elgopher/pi/devtools/internal/snapshot"
12+
"github.com/elgopher/pi/vm"
13+
)
14+
15+
func drawDevTools() {
16+
snapshot.Draw()
17+
drawBar()
18+
drawPointer()
19+
}
20+
21+
func drawBar() {
22+
mouseX, mouseY := pi.MousePos()
23+
barY := vm.ScreenHeight - 7
24+
if mouseY > vm.ScreenHeight/2 {
25+
barY = 0
26+
}
27+
28+
pi.RectFill(0, barY, vm.ScreenWidth, barY+6, BgColor)
29+
30+
mostX := printCoords(mouseX, mouseY, 1, barY+1)
31+
color := pi.Pget(mouseX, mouseY)
32+
printPixelColor(color, mostX+4, barY+1)
33+
}
34+
35+
func printCoords(mouseX int, mouseY int, x, y int) int {
36+
coords := fmt.Sprintf("%d %d", mouseX, mouseY)
37+
return pi.Print(coords, x, y, FgColor)
38+
}
39+
40+
func printPixelColor(color byte, x int, y int) int {
41+
c := fmt.Sprintf("%d", color)
42+
return pi.Print(c, x, y, color)
43+
}
44+
45+
func drawPointer() {
46+
x, y := pi.MousePos()
47+
icons.Draw(icons.Pointer, x, y, FgColor)
48+
}

devtools/internal/icons/icons.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package icons
5+
6+
import (
7+
_ "embed"
8+
"fmt"
9+
10+
"github.com/elgopher/pi"
11+
"github.com/elgopher/pi/font"
12+
)
13+
14+
const (
15+
Pointer = "\u0000"
16+
)
17+
18+
//go:embed icons.png
19+
var iconsPng []byte
20+
21+
var icons = pi.Font{
22+
Width: 4,
23+
WidthSpecial: 8,
24+
Height: 8,
25+
}
26+
27+
func init() {
28+
if err := font.Load(iconsPng, icons.Data[:]); err != nil {
29+
panic(fmt.Sprintf("problem loading devtools icons %s", err))
30+
}
31+
}
32+
33+
func Draw(icon string, x, y int, color byte) {
34+
icons.Print(icon, x, y, color)
35+
}

devtools/internal/icons/icons.png

135 Bytes
Loading
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package snapshot
5+
6+
import (
7+
"github.com/elgopher/pi/vm"
8+
)
9+
10+
var snapshot []byte
11+
12+
func Take() {
13+
if snapshot == nil {
14+
snapshot = make([]byte, len(vm.ScreenData))
15+
}
16+
copy(snapshot, vm.ScreenData)
17+
}
18+
19+
func Draw() {
20+
copy(vm.ScreenData, snapshot)
21+
}

devtools/update.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// (c) 2022 Jacek Olszak
2+
// This code is licensed under MIT license (see LICENSE for details)
3+
4+
package devtools
5+
6+
import "github.com/elgopher/pi/key"
7+
8+
func updateDevTools() {
9+
if key.Btnp(key.F12) {
10+
if !paused {
11+
pauseGame()
12+
} else {
13+
resumeGame()
14+
}
15+
}
16+
}

docs/ROADMAP.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@
3030
* [ ] Menu screen
3131
* [ ] controller mapping editor
3232
* [ ] keyboard mapping editor
33-
* [ ] Development console
34-
* [ ] stopping, resuming the game
33+
* [ ] Development tools
34+
* [x] controlling the game
35+
* [x] pausing, resuming
3536
* [x] add a programmatic way to stop the game
36-
* [ ] resume the game using console command
37-
* [ ] scripting (running π functions)
38-
* [ ] screen inspector
37+
* [ ] taking screenshots
38+
* [x] screen inspector
39+
* [x] presenting pixel coords and color
40+
* [ ] zoom-in, zoom-out
41+
* [ ] drawing on the screen using Pi functions such as Pset, shapes, Spr etc.
42+
* [ ] storing function calls in the clipboard and history file
43+
* [ ] palette inspector
44+
* [ ] display, draw palette
3945
* [ ] sprite-sheet editor
4046
* [ ] map editor
4147
* [ ] sound editor

examples/hello/main.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,35 @@ package main
44
import (
55
"embed"
66

7-
"github.com/elgopher/pi"
8-
"github.com/elgopher/pi/ebitengine"
7+
"github.com/elgopher/pi" // core package used to draw, print and control the input
8+
"github.com/elgopher/pi/devtools" // tools used only during development
9+
"github.com/elgopher/pi/ebitengine" // engine capable of rendering the game on multiple operating systems
910
)
1011

12+
// Tell Go compiler to embed sprite-sheet file inside binary:
13+
//
1114
//go:embed sprite-sheet.png
1215
var resources embed.FS
1316

1417
func main() {
18+
// Tell Pi to use resources embedded inside binary:
1519
pi.Resources = resources
20+
// Pi runs the game in a loop. 30 times per second, Pi
21+
// asks the game to draw a frame. This callback function
22+
// can be set by following code:
1623
pi.Draw = func() {
24+
// Clear entire screen each frame with color 0 (black):
1725
pi.Cls()
1826
// Draw "HELLO WORLD". Each letter is a different sprite.
1927
// Sprite 0 is H, 1 is E, 2 is L etc.
2028
for i := 0; i < 12; i++ {
21-
// calculate the position of letter on screen:
29+
// Calculate the position of letter on screen:
2230
x := 20 + i*8
2331
y := pi.Cos(pi.Time()+float64(i)/64) * 60
24-
// draw sprite:
32+
// Draw sprite:
2533
pi.Spr(i, x, 60+int(y))
2634
}
2735
}
28-
pi.MustRun(ebitengine.Backend)
36+
// Run game with devtools (Hit F12 to show screen inspector)
37+
devtools.MustRun(ebitengine.Backend)
2938
}

pi.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ var (
4949
// ScreenHeight specifies the height of the screen (in pixels).
5050
ScreenHeight = defaultScreenHeight
5151

52-
Resources fs.ReadFileFS // Resources contains files like sprite-sheet.png
52+
Resources fs.ReadFileFS // Resources contains files like sprite-sheet.png, custom-font.png
5353
)
5454

5555
var booted bool
@@ -184,9 +184,9 @@ func MustBoot() {
184184
}
185185
}
186186

187-
// Stop will stop the game loop after Update is finished, but before Draw.
188-
// For now the entire app will be closed, but later it will show a dev console instead,
189-
// where developer will be able to resume the game.
187+
// Stop will stop the game loop after Update or Draw is finished.
188+
// If you are using devtools, the game will be paused. Otherwise, the game
189+
// will be closed.
190190
func Stop() {
191191
vm.GameLoopStopped = true
192192
}

vm/vm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
// Package vm is low-level package for directly accessing virtual machine data,
55
// such as screen pixels, sprite-sheet, fonts or buttons state.
6-
// This data can be manipulated by backend, console, utility functions, or
6+
// This data can be manipulated by backend, devtools, utility functions, or
77
// a game itself. It is very useful for writing custom tools, new backends or
88
// even entire new API to be used by games. Code using vm package directly
99
// could be very fast, because it can use low-level Go functions such as copy.

0 commit comments

Comments
 (0)