Abstract away all SDL logic into isolated package
This commit is contained in:
parent
cf6d5d999c
commit
30be42c343
|
@ -5,6 +5,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle"
|
"git.kirsle.net/apps/doodle"
|
||||||
|
"git.kirsle.net/apps/doodle/render/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build number is the git commit hash.
|
// Build number is the git commit hash.
|
||||||
|
@ -31,7 +32,14 @@ func main() {
|
||||||
filename = args[0]
|
filename = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
app := doodle.New(debug)
|
// SDL engine.
|
||||||
|
engine := sdl.New(
|
||||||
|
"Doodle v"+doodle.Version,
|
||||||
|
800,
|
||||||
|
600,
|
||||||
|
)
|
||||||
|
|
||||||
|
app := doodle.New(debug, engine)
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
if edit {
|
if edit {
|
||||||
app.EditLevel(filename)
|
app.EditLevel(filename)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import "github.com/veandco/go-sdl2/sdl"
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
// Configuration constants.
|
// Configuration constants.
|
||||||
var (
|
var (
|
||||||
DebugTextPadding int32 = 8
|
DebugTextPadding int32 = 8
|
||||||
DebugTextSize = 24
|
DebugTextSize = 24
|
||||||
DebugTextColor = sdl.Color{255, 153, 255, 255}
|
DebugTextColor = render.SkyBlue
|
||||||
DebugTextOutline = sdl.Color{0, 0, 0, 255}
|
DebugTextStroke = render.Grey
|
||||||
|
DebugTextShadow = render.Black
|
||||||
)
|
)
|
||||||
|
|
81
doodle.go
81
doodle.go
|
@ -7,8 +7,6 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"github.com/kirsle/golog"
|
"github.com/kirsle/golog"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
"github.com/veandco/go-sdl2/ttf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -24,7 +22,8 @@ const (
|
||||||
|
|
||||||
// Doodle is the game object.
|
// Doodle is the game object.
|
||||||
type Doodle struct {
|
type Doodle struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
|
Engine render.Engine
|
||||||
|
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
running bool
|
running bool
|
||||||
|
@ -34,15 +33,13 @@ type Doodle struct {
|
||||||
height int32
|
height int32
|
||||||
|
|
||||||
scene Scene
|
scene Scene
|
||||||
|
|
||||||
window *sdl.Window
|
|
||||||
renderer *sdl.Renderer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes the game object.
|
// New initializes the game object.
|
||||||
func New(debug bool) *Doodle {
|
func New(debug bool, engine render.Engine) *Doodle {
|
||||||
d := &Doodle{
|
d := &Doodle{
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
|
Engine: engine,
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
events: events.New(),
|
events: events.New(),
|
||||||
running: true,
|
running: true,
|
||||||
|
@ -59,44 +56,10 @@ func New(debug bool) *Doodle {
|
||||||
|
|
||||||
// Run initializes SDL and starts the main loop.
|
// Run initializes SDL and starts the main loop.
|
||||||
func (d *Doodle) Run() error {
|
func (d *Doodle) Run() error {
|
||||||
// Initialize SDL.
|
// Set up the render engine.
|
||||||
log.Info("Initializing SDL")
|
if err := d.Engine.Setup(); err != nil {
|
||||||
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sdl.Quit()
|
|
||||||
|
|
||||||
// Initialize SDL_TTF.
|
|
||||||
log.Info("Initializing SDL_TTF")
|
|
||||||
if err := ttf.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our window.
|
|
||||||
log.Info("Creating the Main Window")
|
|
||||||
window, err := sdl.CreateWindow(
|
|
||||||
"Doodle v"+Version,
|
|
||||||
sdl.WINDOWPOS_CENTERED,
|
|
||||||
sdl.WINDOWPOS_CENTERED,
|
|
||||||
d.width,
|
|
||||||
d.height,
|
|
||||||
sdl.WINDOW_SHOWN,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer window.Destroy()
|
|
||||||
d.window = window
|
|
||||||
|
|
||||||
// Blank out the window in white.
|
|
||||||
log.Info("Creating the SDL Renderer")
|
|
||||||
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
d.renderer = renderer
|
|
||||||
render.Renderer = renderer
|
|
||||||
defer renderer.Destroy()
|
|
||||||
|
|
||||||
// Set up the default scene.
|
// Set up the default scene.
|
||||||
if d.scene == nil {
|
if d.scene == nil {
|
||||||
|
@ -105,31 +68,49 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
log.Info("Enter Main Loop")
|
log.Info("Enter Main Loop")
|
||||||
for d.running {
|
for d.running {
|
||||||
|
start := time.Now() // Record how long this frame took.
|
||||||
d.ticks++
|
d.ticks++
|
||||||
|
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
_, err := d.events.Poll(d.ticks)
|
ev, err := d.Engine.Poll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("event poll error: %s", err)
|
log.Error("event poll error: %s", err)
|
||||||
return err
|
d.running = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a frame and log how long it took.
|
// Global event handlers.
|
||||||
start := time.Now()
|
if ev.EscapeKey.Pressed() {
|
||||||
err = d.scene.Loop(d)
|
log.Error("Escape key pressed, shutting down")
|
||||||
|
d.running = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the scene's logic.
|
||||||
|
err = d.scene.Loop(d, ev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Now().Sub(start)
|
// Draw the debug overlay over all scenes.
|
||||||
|
d.DrawDebugOverlay()
|
||||||
|
|
||||||
|
// Render the pixels to the screen.
|
||||||
|
err = d.Engine.Draw()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("draw error: %s", err)
|
||||||
|
d.running = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// Delay to maintain the target frames per second.
|
// Delay to maintain the target frames per second.
|
||||||
|
elapsed := time.Now().Sub(start)
|
||||||
tmp := elapsed / time.Millisecond
|
tmp := elapsed / time.Millisecond
|
||||||
var delay uint32
|
var delay uint32
|
||||||
if TargetFPS-int(tmp) > 0 { // make sure it won't roll under
|
if TargetFPS-int(tmp) > 0 { // make sure it won't roll under
|
||||||
delay = uint32(TargetFPS - int(tmp))
|
delay = uint32(TargetFPS - int(tmp))
|
||||||
}
|
}
|
||||||
sdl.Delay(delay)
|
d.Engine.Delay(delay)
|
||||||
|
|
||||||
// Track how long this frame took to measure FPS over time.
|
// Track how long this frame took to measure FPS over time.
|
||||||
d.TrackFPS(delay)
|
d.TrackFPS(delay)
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/draw"
|
"git.kirsle.net/apps/doodle/draw"
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditorScene manages the "Edit Level" game mode.
|
// EditorScene manages the "Edit Level" game mode.
|
||||||
|
@ -42,9 +44,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop the editor scene.
|
// Loop the editor scene.
|
||||||
func (s *EditorScene) Loop(d *Doodle) error {
|
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
ev := d.events
|
|
||||||
|
|
||||||
// Taking a screenshot?
|
// Taking a screenshot?
|
||||||
if ev.ScreenshotKey.Pressed() {
|
if ev.ScreenshotKey.Pressed() {
|
||||||
log.Info("Taking a screenshot")
|
log.Info("Taking a screenshot")
|
||||||
|
@ -53,8 +53,7 @@ func (s *EditorScene) Loop(d *Doodle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the canvas and fill it with white.
|
// Clear the canvas and fill it with white.
|
||||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
d.Engine.Clear(render.White)
|
||||||
d.renderer.Clear()
|
|
||||||
|
|
||||||
// Clicking? Log all the pixels while doing so.
|
// Clicking? Log all the pixels while doing so.
|
||||||
if ev.Button1.Now {
|
if ev.Button1.Now {
|
||||||
|
@ -82,29 +81,25 @@ func (s *EditorScene) Loop(d *Doodle) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
|
||||||
for i, pixel := range s.pixelHistory {
|
for i, pixel := range s.pixelHistory {
|
||||||
if !pixel.start && i > 0 {
|
if !pixel.start && i > 0 {
|
||||||
prev := s.pixelHistory[i-1]
|
prev := s.pixelHistory[i-1]
|
||||||
if prev.x == pixel.x && prev.y == pixel.y {
|
if prev.x == pixel.x && prev.y == pixel.y {
|
||||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
d.Engine.DrawPoint(
|
||||||
|
render.Black,
|
||||||
|
render.Point{pixel.x, pixel.y},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
d.renderer.DrawLine(
|
d.Engine.DrawLine(
|
||||||
pixel.x,
|
render.Black,
|
||||||
pixel.y,
|
render.Point{pixel.x, pixel.y},
|
||||||
prev.x,
|
render.Point{prev.x, prev.y},
|
||||||
prev.y,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the FPS.
|
|
||||||
d.DrawDebugOverlay()
|
|
||||||
|
|
||||||
d.renderer.Present()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
// Package events manages mouse and keyboard SDL events for Doodle.
|
// Package events manages mouse and keyboard SDL events for Doodle.
|
||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State keeps track of event states.
|
// State keeps track of event states.
|
||||||
type State struct {
|
type State struct {
|
||||||
// Mouse buttons.
|
// Mouse buttons.
|
||||||
|
@ -15,6 +9,7 @@ type State struct {
|
||||||
|
|
||||||
// Screenshot key.
|
// Screenshot key.
|
||||||
ScreenshotKey *BoolTick
|
ScreenshotKey *BoolTick
|
||||||
|
EscapeKey *BoolTick
|
||||||
Up *BoolTick
|
Up *BoolTick
|
||||||
Left *BoolTick
|
Left *BoolTick
|
||||||
Right *BoolTick
|
Right *BoolTick
|
||||||
|
@ -31,6 +26,7 @@ func New() *State {
|
||||||
Button1: &BoolTick{},
|
Button1: &BoolTick{},
|
||||||
Button2: &BoolTick{},
|
Button2: &BoolTick{},
|
||||||
ScreenshotKey: &BoolTick{},
|
ScreenshotKey: &BoolTick{},
|
||||||
|
EscapeKey: &BoolTick{},
|
||||||
Up: &BoolTick{},
|
Up: &BoolTick{},
|
||||||
Left: &BoolTick{},
|
Left: &BoolTick{},
|
||||||
Right: &BoolTick{},
|
Right: &BoolTick{},
|
||||||
|
@ -39,93 +35,3 @@ func New() *State {
|
||||||
CursorY: &Int32Tick{},
|
CursorY: &Int32Tick{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll for events.
|
|
||||||
func (s *State) Poll(ticks uint64) (*State, error) {
|
|
||||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
|
||||||
switch t := event.(type) {
|
|
||||||
case *sdl.QuitEvent:
|
|
||||||
return s, errors.New("quit")
|
|
||||||
case *sdl.MouseMotionEvent:
|
|
||||||
if DebugMouseEvents {
|
|
||||||
log.Debug("[%d ms] tick:%d MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d",
|
|
||||||
t.Timestamp, ticks, t.Type, t.Which, t.X, t.Y, t.XRel, t.YRel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the cursor position.
|
|
||||||
s.CursorX.Push(t.X)
|
|
||||||
s.CursorY.Push(t.Y)
|
|
||||||
s.Button1.Push(t.State == 1)
|
|
||||||
case *sdl.MouseButtonEvent:
|
|
||||||
if DebugClickEvents {
|
|
||||||
log.Debug("[%d ms] tick:%d MouseButton type:%d id:%d x:%d y:%d button:%d state:%d",
|
|
||||||
t.Timestamp, ticks, t.Type, t.Which, t.X, t.Y, t.Button, t.State,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the cursor position.
|
|
||||||
s.CursorX.Push(t.X)
|
|
||||||
s.CursorY.Push(t.Y)
|
|
||||||
|
|
||||||
// Is a mouse button pressed down?
|
|
||||||
if t.Button == 1 {
|
|
||||||
var eventName string
|
|
||||||
if DebugClickEvents {
|
|
||||||
if t.State == 1 && s.Button1.Now == false {
|
|
||||||
eventName = "DOWN"
|
|
||||||
} else if t.State == 0 && s.Button1.Now == true {
|
|
||||||
eventName = "UP"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if eventName != "" {
|
|
||||||
log.Debug("tick:%d Mouse Button1 %s BEFORE: %+v",
|
|
||||||
ticks,
|
|
||||||
eventName,
|
|
||||||
s.Button1,
|
|
||||||
)
|
|
||||||
s.Button1.Push(eventName == "DOWN")
|
|
||||||
log.Debug("tick:%d Mouse Button1 %s AFTER: %+v",
|
|
||||||
ticks,
|
|
||||||
eventName,
|
|
||||||
s.Button1,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return the event immediately.
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// s.Button2.Push(t.Button == 3 && t.State == 1)
|
|
||||||
case *sdl.MouseWheelEvent:
|
|
||||||
if DebugMouseEvents {
|
|
||||||
log.Debug("[%d ms] tick:%d MouseWheel type:%d id:%d x:%d y:%d",
|
|
||||||
t.Timestamp, ticks, t.Type, t.Which, t.X, t.Y,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case *sdl.KeyboardEvent:
|
|
||||||
log.Debug("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
|
||||||
t.Timestamp, ticks, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
|
||||||
)
|
|
||||||
if t.Repeat == 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t.Keysym.Scancode {
|
|
||||||
case sdl.SCANCODE_F12:
|
|
||||||
s.ScreenshotKey.Push(t.State == 1)
|
|
||||||
case sdl.SCANCODE_UP:
|
|
||||||
s.Up.Push(t.State == 1)
|
|
||||||
case sdl.SCANCODE_LEFT:
|
|
||||||
s.Left.Push(t.State == 1)
|
|
||||||
case sdl.SCANCODE_RIGHT:
|
|
||||||
s.Right.Push(t.State == 1)
|
|
||||||
case sdl.SCANCODE_DOWN:
|
|
||||||
s.Down.Push(t.State == 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
32
fps.go
32
fps.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Frames to cache for FPS calculation.
|
// Frames to cache for FPS calculation.
|
||||||
|
@ -26,7 +25,7 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
text := fmt.Sprintf(
|
label := fmt.Sprintf(
|
||||||
"FPS: %d (%dms) (%d,%d) S:%s F12=screenshot",
|
"FPS: %d (%dms) (%d,%d) S:%s F12=screenshot",
|
||||||
fpsCurrent,
|
fpsCurrent,
|
||||||
fpsSkipped,
|
fpsSkipped,
|
||||||
|
@ -34,20 +33,31 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
d.events.CursorY.Now,
|
d.events.CursorY.Now,
|
||||||
d.scene.Name(),
|
d.scene.Name(),
|
||||||
)
|
)
|
||||||
render.StrokedText(render.TextConfig{
|
|
||||||
Text: text,
|
err := d.Engine.DrawText(
|
||||||
Size: DebugTextSize,
|
render.Text{
|
||||||
Color: DebugTextColor,
|
Text: label,
|
||||||
StrokeColor: DebugTextOutline,
|
Size: 24,
|
||||||
X: DebugTextPadding,
|
Color: DebugTextColor,
|
||||||
Y: DebugTextPadding,
|
Stroke: DebugTextStroke,
|
||||||
})
|
Shadow: DebugTextShadow,
|
||||||
|
},
|
||||||
|
render.Rect{
|
||||||
|
X: DebugTextPadding,
|
||||||
|
Y: DebugTextPadding,
|
||||||
|
W: d.width,
|
||||||
|
H: d.height,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("DrawDebugOverlay: text error: %s", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackFPS shows the current FPS once per second.
|
// TrackFPS shows the current FPS once per second.
|
||||||
func (d *Doodle) TrackFPS(skipped uint32) {
|
func (d *Doodle) TrackFPS(skipped uint32) {
|
||||||
fpsFrames++
|
fpsFrames++
|
||||||
fpsCurrentTicks = sdl.GetTicks()
|
fpsCurrentTicks = d.Engine.GetTicks()
|
||||||
|
|
||||||
// Skip the first second.
|
// Skip the first second.
|
||||||
if fpsCurrentTicks < fpsInterval {
|
if fpsCurrentTicks < fpsInterval {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package doodle
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlayScene manages the "Edit Level" game mode.
|
// PlayScene manages the "Edit Level" game mode.
|
||||||
|
@ -40,43 +40,31 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop the editor scene.
|
// Loop the editor scene.
|
||||||
func (s *PlayScene) Loop(d *Doodle) error {
|
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
s.PollEvents(d.events)
|
s.movePlayer(ev)
|
||||||
|
|
||||||
// Apply gravity.
|
// Apply gravity.
|
||||||
|
|
||||||
return s.Draw(d)
|
return s.Draw(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the pixels on this frame.
|
// Draw the pixels on this frame.
|
||||||
func (s *PlayScene) Draw(d *Doodle) error {
|
func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
// Clear the canvas and fill it with white.
|
// Clear the canvas and fill it with white.
|
||||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
d.Engine.Clear(render.White)
|
||||||
d.renderer.Clear()
|
|
||||||
|
|
||||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
|
||||||
for pixel := range s.canvas {
|
for pixel := range s.canvas {
|
||||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw our hero.
|
// Draw our hero.
|
||||||
d.renderer.SetDrawColor(0, 0, 255, 255)
|
log.Info("hero %s %+v", render.Magenta, render.Magenta)
|
||||||
d.renderer.DrawRect(&sdl.Rect{
|
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16})
|
||||||
X: s.x,
|
|
||||||
Y: s.y,
|
|
||||||
W: 16,
|
|
||||||
H: 16,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Draw the FPS.
|
|
||||||
d.DrawDebugOverlay()
|
|
||||||
d.renderer.Present()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PollEvents checks the event state and updates variables.
|
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
||||||
func (s *PlayScene) PollEvents(ev *events.State) {
|
func (s *PlayScene) movePlayer(ev *events.State) {
|
||||||
if ev.Down.Now {
|
if ev.Down.Now {
|
||||||
s.y += 4
|
s.y += 4
|
||||||
}
|
}
|
||||||
|
|
104
render/interface.go
Normal file
104
render/interface.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine is the interface for the rendering engine, keeping SDL-specific stuff
|
||||||
|
// far away from the core of Doodle.
|
||||||
|
type Engine interface {
|
||||||
|
Setup() error
|
||||||
|
|
||||||
|
// Poll for events like keypresses and mouse clicks.
|
||||||
|
Poll() (*events.State, error)
|
||||||
|
GetTicks() uint32
|
||||||
|
|
||||||
|
// Draw presents the current state to the screen.
|
||||||
|
Draw() error
|
||||||
|
|
||||||
|
// Clear the full canvas and set this color.
|
||||||
|
Clear(Color)
|
||||||
|
DrawPoint(Color, Point)
|
||||||
|
DrawLine(Color, Point, Point)
|
||||||
|
DrawRect(Color, Rect)
|
||||||
|
DrawText(Text, Rect) error
|
||||||
|
|
||||||
|
// Delay for a moment using the render engine's delay method,
|
||||||
|
// implemented by sdl.Delay(uint32)
|
||||||
|
Delay(uint32)
|
||||||
|
|
||||||
|
// Tasks that the Setup function should defer until tear-down.
|
||||||
|
Teardown()
|
||||||
|
|
||||||
|
Loop() error // maybe?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color holds an RGBA color value.
|
||||||
|
type Color struct {
|
||||||
|
Red uint8
|
||||||
|
Green uint8
|
||||||
|
Blue uint8
|
||||||
|
Alpha uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Color) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Color<#%02x%02x%02x>",
|
||||||
|
c.Red, c.Green, c.Blue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point holds an X,Y coordinate value.
|
||||||
|
type Point struct {
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Point) String() string {
|
||||||
|
return fmt.Sprintf("Point<%d,%d>", p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rect has a coordinate and a width and height.
|
||||||
|
type Rect struct {
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
W int32
|
||||||
|
H int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rect) String() string {
|
||||||
|
return fmt.Sprintf("Rect<%d,%d,%d,%d>",
|
||||||
|
r.X, r.Y, r.W, r.H,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text holds information for drawing text.
|
||||||
|
type Text struct {
|
||||||
|
Text string
|
||||||
|
Size int
|
||||||
|
Color Color
|
||||||
|
Stroke Color // Stroke color (if not zero)
|
||||||
|
Shadow Color // Drop shadow color (if not zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) String() string {
|
||||||
|
return fmt.Sprintf("Text<%s>", t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common color names.
|
||||||
|
var (
|
||||||
|
Invisible = Color{}
|
||||||
|
White = Color{255, 255, 255, 255}
|
||||||
|
Grey = Color{153, 153, 153, 255}
|
||||||
|
Black = Color{0, 0, 0, 255}
|
||||||
|
SkyBlue = Color{0, 153, 255, 255}
|
||||||
|
Blue = Color{0, 0, 255, 255}
|
||||||
|
Red = Color{255, 0, 0, 255}
|
||||||
|
Green = Color{0, 255, 0, 255}
|
||||||
|
Cyan = Color{0, 255, 255, 255}
|
||||||
|
Yellow = Color{255, 255, 0, 255}
|
||||||
|
Magenta = Color{255, 0, 255, 255}
|
||||||
|
Pink = Color{255, 153, 255, 255}
|
||||||
|
)
|
44
render/sdl/canvas.go
Normal file
44
render/sdl/canvas.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Package sdl provides an SDL2 renderer for Doodle.
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clear the canvas and set this color.
|
||||||
|
func (r *Renderer) Clear(color render.Color) {
|
||||||
|
if color != r.lastColor {
|
||||||
|
r.renderer.SetDrawColor(color.Red, color.Blue, color.Green, color.Alpha)
|
||||||
|
}
|
||||||
|
r.renderer.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawPoint puts a color at a pixel.
|
||||||
|
func (r *Renderer) DrawPoint(color render.Color, point render.Point) {
|
||||||
|
if color != r.lastColor {
|
||||||
|
r.renderer.SetDrawColor(color.Red, color.Blue, color.Green, color.Alpha)
|
||||||
|
}
|
||||||
|
r.renderer.DrawPoint(point.X, point.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawLine draws a line between two points.
|
||||||
|
func (r *Renderer) DrawLine(color render.Color, a, b render.Point) {
|
||||||
|
if color != r.lastColor {
|
||||||
|
r.renderer.SetDrawColor(color.Red, color.Blue, color.Green, color.Alpha)
|
||||||
|
}
|
||||||
|
r.renderer.DrawLine(a.X, a.Y, b.X, b.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawRect draws a rectangle.
|
||||||
|
func (r *Renderer) DrawRect(color render.Color, rect render.Rect) {
|
||||||
|
if color != r.lastColor {
|
||||||
|
r.renderer.SetDrawColor(color.Red, color.Green, color.Blue, color.Alpha)
|
||||||
|
}
|
||||||
|
r.renderer.DrawRect(&sdl.Rect{
|
||||||
|
X: rect.X,
|
||||||
|
Y: rect.Y,
|
||||||
|
W: rect.W,
|
||||||
|
H: rect.H,
|
||||||
|
})
|
||||||
|
}
|
22
render/sdl/fps.go
Normal file
22
render/sdl/fps.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frames to cache for FPS calculation.
|
||||||
|
const (
|
||||||
|
maxSamples = 100
|
||||||
|
TargetFPS = 1000 / 60
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fpsCurrentTicks uint32 // current time we get sdl.GetTicks()
|
||||||
|
fpsLastTime uint32 // last time we printed the fpsCurrentTicks
|
||||||
|
fpsCurrent int
|
||||||
|
fpsFrames int
|
||||||
|
fpsSkipped uint32
|
||||||
|
fpsInterval uint32 = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var pixelHistory []level.Pixel
|
15
render/sdl/log.go
Normal file
15
render/sdl/log.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
// Verbose debug logging.
|
||||||
|
var (
|
||||||
|
DebugMouseEvents = false
|
||||||
|
DebugClickEvents = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("doodle")
|
||||||
|
}
|
203
render/sdl/sdl.go
Normal file
203
render/sdl/sdl.go
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
// Package sdl provides an SDL2 renderer for Doodle.
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Renderer manages the SDL state.
|
||||||
|
type Renderer struct {
|
||||||
|
// Configurable fields.
|
||||||
|
title string
|
||||||
|
width int32
|
||||||
|
height int32
|
||||||
|
startTime time.Time
|
||||||
|
|
||||||
|
// Private fields.
|
||||||
|
events *events.State
|
||||||
|
window *sdl.Window
|
||||||
|
renderer *sdl.Renderer
|
||||||
|
running bool
|
||||||
|
ticks uint64
|
||||||
|
|
||||||
|
// Optimizations to minimize SDL calls.
|
||||||
|
lastColor render.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates the SDL renderer.
|
||||||
|
func New(title string, width, height int32) *Renderer {
|
||||||
|
return &Renderer{
|
||||||
|
events: events.New(),
|
||||||
|
title: title,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teardown tasks when exiting the program.
|
||||||
|
func (r *Renderer) Teardown() {
|
||||||
|
r.renderer.Destroy()
|
||||||
|
r.window.Destroy()
|
||||||
|
sdl.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the renderer.
|
||||||
|
func (r *Renderer) Setup() error {
|
||||||
|
// Initialize SDL.
|
||||||
|
log.Info("Initializing SDL")
|
||||||
|
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize SDL_TTF.
|
||||||
|
log.Info("Initializing SDL_TTF")
|
||||||
|
if err := ttf.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our window.
|
||||||
|
log.Info("Creating the Main Window")
|
||||||
|
window, err := sdl.CreateWindow(
|
||||||
|
r.title,
|
||||||
|
sdl.WINDOWPOS_CENTERED,
|
||||||
|
sdl.WINDOWPOS_CENTERED,
|
||||||
|
r.width,
|
||||||
|
r.height,
|
||||||
|
sdl.WINDOW_SHOWN,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.window = window
|
||||||
|
|
||||||
|
// Blank out the window in white.
|
||||||
|
log.Info("Creating the SDL Renderer")
|
||||||
|
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r.renderer = renderer
|
||||||
|
render.Renderer = renderer
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTicks gets SDL's current tick count.
|
||||||
|
func (r *Renderer) GetTicks() uint32 {
|
||||||
|
return sdl.GetTicks()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for events.
|
||||||
|
func (r *Renderer) Poll() (*events.State, error) {
|
||||||
|
s := r.events
|
||||||
|
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||||
|
switch t := event.(type) {
|
||||||
|
case *sdl.QuitEvent:
|
||||||
|
return s, errors.New("quit")
|
||||||
|
case *sdl.MouseMotionEvent:
|
||||||
|
if DebugMouseEvents {
|
||||||
|
log.Debug("[%d ms] tick:%d MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d",
|
||||||
|
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, t.XRel, t.YRel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the cursor position.
|
||||||
|
s.CursorX.Push(t.X)
|
||||||
|
s.CursorY.Push(t.Y)
|
||||||
|
s.Button1.Push(t.State == 1)
|
||||||
|
case *sdl.MouseButtonEvent:
|
||||||
|
if DebugClickEvents {
|
||||||
|
log.Debug("[%d ms] tick:%d MouseButton type:%d id:%d x:%d y:%d button:%d state:%d",
|
||||||
|
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, t.Button, t.State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the cursor position.
|
||||||
|
s.CursorX.Push(t.X)
|
||||||
|
s.CursorY.Push(t.Y)
|
||||||
|
|
||||||
|
// Is a mouse button pressed down?
|
||||||
|
if t.Button == 1 {
|
||||||
|
var eventName string
|
||||||
|
if DebugClickEvents {
|
||||||
|
if t.State == 1 && s.Button1.Now == false {
|
||||||
|
eventName = "DOWN"
|
||||||
|
} else if t.State == 0 && s.Button1.Now == true {
|
||||||
|
eventName = "UP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventName != "" {
|
||||||
|
log.Debug("tick:%d Mouse Button1 %s BEFORE: %+v",
|
||||||
|
r.ticks,
|
||||||
|
eventName,
|
||||||
|
s.Button1,
|
||||||
|
)
|
||||||
|
s.Button1.Push(eventName == "DOWN")
|
||||||
|
log.Debug("tick:%d Mouse Button1 %s AFTER: %+v",
|
||||||
|
r.ticks,
|
||||||
|
eventName,
|
||||||
|
s.Button1,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return the event immediately.
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// s.Button2.Push(t.Button == 3 && t.State == 1)
|
||||||
|
case *sdl.MouseWheelEvent:
|
||||||
|
if DebugMouseEvents {
|
||||||
|
log.Debug("[%d ms] tick:%d MouseWheel type:%d id:%d x:%d y:%d",
|
||||||
|
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case *sdl.KeyboardEvent:
|
||||||
|
log.Debug("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
||||||
|
t.Timestamp, r.ticks, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
||||||
|
)
|
||||||
|
if t.Repeat == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Keysym.Scancode {
|
||||||
|
case sdl.SCANCODE_ESCAPE:
|
||||||
|
s.EscapeKey.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_F12:
|
||||||
|
s.ScreenshotKey.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_UP:
|
||||||
|
s.Up.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_LEFT:
|
||||||
|
s.Left.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_RIGHT:
|
||||||
|
s.Right.Push(t.State == 1)
|
||||||
|
case sdl.SCANCODE_DOWN:
|
||||||
|
s.Down.Push(t.State == 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a single frame.
|
||||||
|
func (r *Renderer) Draw() error {
|
||||||
|
r.renderer.Present()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay using sdl.Delay
|
||||||
|
func (r *Renderer) Delay(time uint32) {
|
||||||
|
sdl.Delay(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop is the main loop.
|
||||||
|
func (r *Renderer) Loop() error {
|
||||||
|
return nil
|
||||||
|
}
|
81
render/sdl/text.go
Normal file
81
render/sdl/text.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fonts map[int]*ttf.Font = map[int]*ttf.Font{}
|
||||||
|
|
||||||
|
// LoadFont loads and caches the font at a given size.
|
||||||
|
func LoadFont(size int) (*ttf.Font, error) {
|
||||||
|
if font, ok := fonts[size]; ok {
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
font, err := ttf.OpenFont("./fonts/DejaVuSansMono.ttf", size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fonts[size] = font
|
||||||
|
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawText draws text on the canvas.
|
||||||
|
func (r *Renderer) DrawText(text render.Text, rect render.Rect) error {
|
||||||
|
var (
|
||||||
|
font *ttf.Font
|
||||||
|
surface *sdl.Surface
|
||||||
|
tex *sdl.Texture
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if font, err = LoadFont(text.Size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
write := func(dx, dy int32, color sdl.Color) {
|
||||||
|
if surface, err = font.RenderUTF8Blended(text.Text, color); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer surface.Free()
|
||||||
|
|
||||||
|
if tex, err = r.renderer.CreateTextureFromSurface(surface); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tex.Destroy()
|
||||||
|
|
||||||
|
tmp := &sdl.Rect{
|
||||||
|
X: rect.X + dx,
|
||||||
|
Y: rect.Y + dy,
|
||||||
|
W: surface.W,
|
||||||
|
H: surface.H,
|
||||||
|
}
|
||||||
|
r.renderer.Copy(tex, nil, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the text have a stroke around it?
|
||||||
|
if text.Stroke != render.Invisible {
|
||||||
|
color := ColorToSDL(text.Stroke)
|
||||||
|
write(-1, -1, color)
|
||||||
|
write(-1, 0, color)
|
||||||
|
write(-1, 1, color)
|
||||||
|
write(1, -1, color)
|
||||||
|
write(1, 0, color)
|
||||||
|
write(1, 1, color)
|
||||||
|
write(0, -1, color)
|
||||||
|
write(0, 1, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does it have a drop shadow?
|
||||||
|
if text.Shadow != render.Invisible {
|
||||||
|
write(1, 1, ColorToSDL(text.Shadow))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the text itself.
|
||||||
|
write(0, 0, ColorToSDL(text.Color))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
21
render/sdl/utils.go
Normal file
21
render/sdl/utils.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package sdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ColorToSDL converts Doodle's Color type to an sdl.Color.
|
||||||
|
func ColorToSDL(c render.Color) sdl.Color {
|
||||||
|
return sdl.Color{c.Red, c.Green, c.Blue, c.Alpha}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RectToSDL converts Doodle's Rect type to an sdl.Rect.
|
||||||
|
func RectToSDL(r render.Rect) sdl.Rect {
|
||||||
|
return sdl.Rect{
|
||||||
|
X: r.X,
|
||||||
|
Y: r.Y,
|
||||||
|
W: r.W,
|
||||||
|
H: r.H,
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,57 +33,3 @@ type TextConfig struct {
|
||||||
W int32
|
W int32
|
||||||
H int32
|
H int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokedText draws text with a stroke color around it.
|
|
||||||
func StrokedText(t TextConfig) {
|
|
||||||
stroke := func(copy TextConfig, x, y int32) {
|
|
||||||
copy.Color = t.StrokeColor
|
|
||||||
copy.X += x
|
|
||||||
copy.Y += y
|
|
||||||
Text(copy)
|
|
||||||
}
|
|
||||||
|
|
||||||
stroke(t, -1, -1)
|
|
||||||
stroke(t, -1, 0)
|
|
||||||
stroke(t, -1, 1)
|
|
||||||
|
|
||||||
stroke(t, 1, -1)
|
|
||||||
stroke(t, 1, 0)
|
|
||||||
stroke(t, 1, 1)
|
|
||||||
|
|
||||||
stroke(t, 0, -1)
|
|
||||||
stroke(t, 0, 1)
|
|
||||||
Text(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text draws text on the renderer.
|
|
||||||
func Text(t TextConfig) error {
|
|
||||||
var (
|
|
||||||
font *ttf.Font
|
|
||||||
surface *sdl.Surface
|
|
||||||
tex *sdl.Texture
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if font, err = LoadFont(t.Size); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if surface, err = font.RenderUTF8Blended(t.Text, t.Color); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer surface.Free()
|
|
||||||
|
|
||||||
if tex, err = Renderer.CreateTextureFromSurface(surface); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tex.Destroy()
|
|
||||||
|
|
||||||
Renderer.Copy(tex, nil, &sdl.Rect{
|
|
||||||
X: int32(t.X),
|
|
||||||
Y: int32(t.Y),
|
|
||||||
W: int32(surface.W),
|
|
||||||
H: int32(surface.H),
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
4
scene.go
4
scene.go
|
@ -1,12 +1,14 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/events"
|
||||||
|
|
||||||
// Scene is an abstraction for a game mode in Doodle. The app points to one
|
// Scene is an abstraction for a game mode in Doodle. The app points to one
|
||||||
// scene at a time and that scene has control over the main loop, and its own
|
// scene at a time and that scene has control over the main loop, and its own
|
||||||
// state information.
|
// state information.
|
||||||
type Scene interface {
|
type Scene interface {
|
||||||
Name() string
|
Name() string
|
||||||
Setup(*Doodle) error
|
Setup(*Doodle) error
|
||||||
Loop(*Doodle) error
|
Loop(*Doodle, *events.State) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goto a scene. First it unloads the current scene.
|
// Goto a scene. First it unloads the current scene.
|
||||||
|
|
86
scene/editor.go
Normal file
86
scene/editor.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package scene
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/events"
|
||||||
|
|
||||||
|
// Editor is the drawing mode of the game where the user is clicking and
|
||||||
|
// dragging to draw pixels.
|
||||||
|
type Editor struct{}
|
||||||
|
|
||||||
|
func (s *Editor) String() string {
|
||||||
|
return "Editor"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the scene.
|
||||||
|
func (s *Editor) Setup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop the scene.
|
||||||
|
func (s *Editor) Loop(ev *events.State) error {
|
||||||
|
// Taking a screenshot?
|
||||||
|
if ev.ScreenshotKey.Pressed() {
|
||||||
|
log.Info("Taking a screenshot")
|
||||||
|
d.Screenshot()
|
||||||
|
d.SaveLevel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the canvas and fill it with white.
|
||||||
|
d.renderer.SetDrawColor(255, 255, 255, 255)
|
||||||
|
d.renderer.Clear()
|
||||||
|
|
||||||
|
// Clicking? Log all the pixels while doing so.
|
||||||
|
if ev.Button1.Now {
|
||||||
|
pixel := Pixel{
|
||||||
|
start: ev.Button1.Pressed(),
|
||||||
|
x: ev.CursorX.Now,
|
||||||
|
y: ev.CursorY.Now,
|
||||||
|
dx: ev.CursorX.Now,
|
||||||
|
dy: ev.CursorY.Now,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append unique new pixels.
|
||||||
|
if len(pixelHistory) == 0 || pixelHistory[len(pixelHistory)-1] != pixel {
|
||||||
|
// If not a start pixel, make the delta coord the previous one.
|
||||||
|
if !pixel.start && len(pixelHistory) > 0 {
|
||||||
|
prev := pixelHistory[len(pixelHistory)-1]
|
||||||
|
pixel.dx = prev.x
|
||||||
|
pixel.dy = prev.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pixelHistory = append(pixelHistory, pixel)
|
||||||
|
|
||||||
|
// Save in the pixel canvas map.
|
||||||
|
d.canvas[pixel] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.renderer.SetDrawColor(0, 0, 0, 255)
|
||||||
|
for i, pixel := range pixelHistory {
|
||||||
|
if !pixel.start && i > 0 {
|
||||||
|
prev := pixelHistory[i-1]
|
||||||
|
if prev.x == pixel.x && prev.y == pixel.y {
|
||||||
|
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||||
|
} else {
|
||||||
|
d.renderer.DrawLine(
|
||||||
|
pixel.x,
|
||||||
|
pixel.y,
|
||||||
|
prev.x,
|
||||||
|
prev.y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the FPS.
|
||||||
|
d.DrawDebugOverlay()
|
||||||
|
|
||||||
|
d.renderer.Present()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the scene.
|
||||||
|
func (s *Editor) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
9
scene/log.go
Normal file
9
scene/log.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package scene
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("doodle")
|
||||||
|
}
|
67
scene/scene.go
Normal file
67
scene/scene.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package scene
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scene is an interface for the top level of a game mode. The game points to
|
||||||
|
// one Scene at a time, and that Scene has majority control of the main loop,
|
||||||
|
// and maintains its own state local to that scene.
|
||||||
|
type Scene interface {
|
||||||
|
String() string // the scene's name
|
||||||
|
Setup() error
|
||||||
|
Loop() error
|
||||||
|
Destroy() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager is a type that provides context switching features to manage scenes.
|
||||||
|
type Manager struct {
|
||||||
|
events *events.State
|
||||||
|
scene Scene
|
||||||
|
ticks uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates the new manager.
|
||||||
|
func NewManager(events *events.State) Manager {
|
||||||
|
return Manager{
|
||||||
|
events: events,
|
||||||
|
scene: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to a new scene. This tears down the existing scene, sets up the new one,
|
||||||
|
// and switches control to the new scene.
|
||||||
|
func (m *Manager) Go(scene Scene) error {
|
||||||
|
// Already running a scene?
|
||||||
|
if m.scene != nil {
|
||||||
|
if err := m.scene.Destroy(); err != nil {
|
||||||
|
return fmt.Errorf("couldn't destroy scene %s: %s", m.scene, err)
|
||||||
|
}
|
||||||
|
m.scene = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the new scene.
|
||||||
|
m.scene = scene
|
||||||
|
return m.scene.Setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop the scene manager. This is the game's main loop which runs all the tasks
|
||||||
|
// that fall in the realm of the scene manager.
|
||||||
|
func (m *Manager) Loop() error {
|
||||||
|
if m.scene == nil {
|
||||||
|
return errors.New("no scene loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for events.
|
||||||
|
ev, err := m.events.Poll(m.ticks)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("event poll error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = ev
|
||||||
|
|
||||||
|
return m.scene.Loop()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user