Stabilize frame rate, add debug overlay
This commit is contained in:
parent
a8e82f4dd2
commit
b7751507e4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -0,0 +1 @@
|
||||||
|
fonts/
|
|
@ -20,3 +20,11 @@ As a rough idea of the milestones needed for this game to work:
|
||||||
* [ ] Start implementing a platformer that uses the custom map format for its
|
* [ ] Start implementing a platformer that uses the custom map format for its
|
||||||
rendering and collision detection.
|
rendering and collision detection.
|
||||||
* [ ] ???
|
* [ ] ???
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
Fedora dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo dnf install SDL2-devel SDL2_ttf-devel
|
||||||
|
```
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/kirsle/doodle"
|
"git.kirsle.net/apps/doodle"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build number is the git commit hash.
|
// Build number is the git commit hash.
|
||||||
|
|
11
config.go
Normal file
11
config.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
|
// Configuration constants.
|
||||||
|
var (
|
||||||
|
DebugTextPadding int32 = 8
|
||||||
|
DebugTextSize = 24
|
||||||
|
DebugTextColor = sdl.Color{255, 153, 255, 255}
|
||||||
|
DebugTextOutline = sdl.Color{0, 0, 0, 255}
|
||||||
|
)
|
234
doodle.go
234
doodle.go
|
@ -2,59 +2,81 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/kirsle/golog"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
// Version number.
|
// Version number.
|
||||||
const Version = "0.0.0-alpha"
|
Version = "0.0.0-alpha"
|
||||||
|
|
||||||
|
// TargetFPS is the frame rate to cap the game to.
|
||||||
|
TargetFPS = uint32(1000 / 60) // 60 FPS
|
||||||
|
|
||||||
|
// Millisecond64 is a time.Millisecond casted to float64.
|
||||||
|
Millisecond64 = float64(time.Millisecond)
|
||||||
|
)
|
||||||
|
|
||||||
// Doodle is the game object.
|
// Doodle is the game object.
|
||||||
type Doodle struct {
|
type Doodle struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
|
startTime time.Time
|
||||||
running bool
|
running bool
|
||||||
events EventState
|
events *events.State
|
||||||
width int
|
width int32
|
||||||
height int
|
height int32
|
||||||
|
|
||||||
|
nextSecond time.Time
|
||||||
|
canvas Grid
|
||||||
|
|
||||||
window *sdl.Window
|
window *sdl.Window
|
||||||
surface *sdl.Surface
|
|
||||||
renderer *sdl.Renderer
|
renderer *sdl.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventState keeps track of important events.
|
|
||||||
type EventState struct {
|
|
||||||
CursorX int32
|
|
||||||
CursorY int32
|
|
||||||
LastX int32
|
|
||||||
LastY int32
|
|
||||||
LeftClick bool
|
|
||||||
LastLeft bool
|
|
||||||
RightClick bool
|
|
||||||
LastRight bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New initializes the game object.
|
// New initializes the game object.
|
||||||
func New(debug bool) *Doodle {
|
func New(debug bool) *Doodle {
|
||||||
d := &Doodle{
|
d := &Doodle{
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
|
startTime: time.Now(),
|
||||||
|
events: events.New(),
|
||||||
running: true,
|
running: true,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
canvas: Grid{},
|
||||||
|
|
||||||
|
nextSecond: time.Now().Add(1 * time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !debug {
|
||||||
|
log.Config.Level = golog.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// Initialize SDL.
|
||||||
|
log.Info("Initializing SDL")
|
||||||
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer sdl.Quit()
|
defer sdl.Quit()
|
||||||
|
|
||||||
|
// Initialize SDL_TTF.
|
||||||
|
log.Info("Initializing SDL_TTF")
|
||||||
|
if err := ttf.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Create our window.
|
// Create our window.
|
||||||
|
log.Info("Creating the Main Window")
|
||||||
window, err := sdl.CreateWindow(
|
window, err := sdl.CreateWindow(
|
||||||
"Doodle v"+Version,
|
"Doodle v"+Version,
|
||||||
sdl.WINDOWPOS_CENTERED,
|
sdl.WINDOWPOS_CENTERED,
|
||||||
|
@ -70,56 +92,33 @@ func (d *Doodle) Run() error {
|
||||||
d.window = window
|
d.window = window
|
||||||
|
|
||||||
// Blank out the window in white.
|
// Blank out the window in white.
|
||||||
|
log.Info("Creating the SDL Renderer")
|
||||||
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
|
renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
d.renderer = renderer
|
d.renderer = renderer
|
||||||
|
render.Renderer = renderer
|
||||||
defer renderer.Destroy()
|
defer renderer.Destroy()
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
log.Info("Enter Main Loop")
|
||||||
d.Loop()
|
|
||||||
// renderer.Clear()
|
|
||||||
// rect := sdl.Rect{
|
|
||||||
// X: 0,
|
|
||||||
// Y: 0,
|
|
||||||
// W: 800,
|
|
||||||
// H: 600,
|
|
||||||
// }
|
|
||||||
// renderer.SetDrawColor(0, 0, 0, 255)
|
|
||||||
// renderer.FillRect(&rect)
|
|
||||||
//
|
|
||||||
// renderer.SetDrawColor(0, 255, 0, 255)
|
|
||||||
// renderer.DrawPoint(10*i, 10*i)
|
|
||||||
//
|
|
||||||
// renderer.Present()
|
|
||||||
//
|
|
||||||
// sdl.Delay(250)
|
|
||||||
}
|
|
||||||
|
|
||||||
for d.running {
|
for d.running {
|
||||||
|
// Draw a frame and log how long it took.
|
||||||
|
start := time.Now()
|
||||||
err = d.Loop()
|
err = d.Loop()
|
||||||
|
elapsed := time.Now().Sub(start)
|
||||||
|
|
||||||
|
tmp := elapsed / time.Millisecond
|
||||||
|
delay := TargetFPS - uint32(tmp)
|
||||||
|
sdl.Delay(delay)
|
||||||
|
|
||||||
|
d.TrackFPS(delay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// surface, err := window.GetSurface()
|
log.Warn("Main Loop Exited! Shutting down...")
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// d.surface = surface
|
|
||||||
//
|
|
||||||
// rect := sdl.Rect{
|
|
||||||
// X: 0,
|
|
||||||
// Y: 0,
|
|
||||||
// W: 200,
|
|
||||||
// H: 200,
|
|
||||||
// }
|
|
||||||
// surface.FillRect(&rect, 0xffff0000)
|
|
||||||
// window.UpdateSurface()
|
|
||||||
//
|
|
||||||
// sdl.Delay(2500)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,117 +127,72 @@ type Pixel struct {
|
||||||
start bool
|
start bool
|
||||||
x int32
|
x int32
|
||||||
y int32
|
y int32
|
||||||
|
dx int32
|
||||||
|
dy int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Pixel) String() string {
|
||||||
|
return fmt.Sprintf("(%d,%d) delta (%d,%d)",
|
||||||
|
p.x, p.y,
|
||||||
|
p.dx, p.dy,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid is a 2D grid of pixels in X,Y notation.
|
||||||
|
type Grid map[Pixel]interface{}
|
||||||
|
|
||||||
var pixelHistory []Pixel
|
var pixelHistory []Pixel
|
||||||
|
|
||||||
// Loop runs one loop of the game engine.
|
// Loop runs one loop of the game engine.
|
||||||
func (d *Doodle) Loop() error {
|
func (d *Doodle) Loop() error {
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
d.PollEvents()
|
ev, err := d.events.Poll()
|
||||||
|
if err != nil {
|
||||||
d.renderer.Clear()
|
log.Error("event poll error: %s", err)
|
||||||
rect := sdl.Rect{
|
return err
|
||||||
X: 0,
|
|
||||||
Y: 0,
|
|
||||||
W: 800,
|
|
||||||
H: 600,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the canvas and fill it with white.
|
||||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
d.renderer.SetDrawColor(255, 255, 255, 255)
|
||||||
d.renderer.FillRect(&rect)
|
d.renderer.Clear()
|
||||||
|
|
||||||
// Clicking? Log all the pixels while doing so.
|
// Clicking? Log all the pixels while doing so.
|
||||||
if d.events.LeftClick {
|
if ev.Button1.Now {
|
||||||
fmt.Printf("Pixel at %dx%d\n", d.events.CursorX, d.events.CursorY)
|
|
||||||
pixel := Pixel{
|
pixel := Pixel{
|
||||||
start: d.events.LeftClick && !d.events.LastLeft,
|
start: ev.Button1.Now && !ev.Button1.Last,
|
||||||
x: d.events.CursorX,
|
x: ev.CursorX.Now,
|
||||||
y: d.events.CursorY,
|
y: ev.CursorY.Now,
|
||||||
|
dx: ev.CursorX.Last,
|
||||||
|
dy: ev.CursorY.Last,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(pixelHistory) == 0 || pixelHistory[len(pixelHistory)-1] != pixel {
|
||||||
pixelHistory = append(pixelHistory, pixel)
|
pixelHistory = append(pixelHistory, pixel)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Colorize all those pixels.
|
|
||||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
d.renderer.SetDrawColor(0, 0, 0, 255)
|
||||||
for i, pixel := range pixelHistory {
|
for i, pixel := range pixelHistory {
|
||||||
fmt.Printf("Draw: %v\n", pixel)
|
if !pixel.start && i > 0 {
|
||||||
if pixel.start == false && i > 0 {
|
prev := pixelHistory[i-1]
|
||||||
start := pixelHistory[i-1]
|
if prev.x == pixel.x && prev.y == pixel.y {
|
||||||
fmt.Printf("Line from %dx%d -> %dx%d\n", start.x, start.y, pixel.x, pixel.y)
|
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||||
d.renderer.DrawLine(
|
|
||||||
int(start.x),
|
|
||||||
int(start.y),
|
|
||||||
int(pixel.x),
|
|
||||||
int(pixel.y),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
d.renderer.DrawPoint(
|
d.renderer.DrawLine(
|
||||||
int(pixel.x), int(pixel.y),
|
pixel.x,
|
||||||
|
pixel.y,
|
||||||
|
prev.x,
|
||||||
|
prev.y,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// d.renderer.FillRect(&sdl.Rect{pixel.x, pixel.y, 10, 10})
|
}
|
||||||
|
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw the FPS.
|
||||||
|
d.DrawDebugOverlay()
|
||||||
|
|
||||||
d.renderer.Present()
|
d.renderer.Present()
|
||||||
sdl.Delay(1000 / 60)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PollEvents checks for keyboard/mouse/etc. events.
|
|
||||||
func (d *Doodle) PollEvents() {
|
|
||||||
for {
|
|
||||||
event := sdl.PollEvent()
|
|
||||||
if event == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the event.
|
|
||||||
switch t := event.(type) {
|
|
||||||
case *sdl.QuitEvent:
|
|
||||||
d.running = false
|
|
||||||
case *sdl.MouseMotionEvent:
|
|
||||||
fmt.Printf("[%d ms] MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d\n",
|
|
||||||
t.Timestamp, t.Type, t.Which, t.X, t.Y, t.XRel, t.YRel,
|
|
||||||
)
|
|
||||||
d.events.LastX = d.events.CursorX
|
|
||||||
d.events.LastY = d.events.CursorY
|
|
||||||
d.events.CursorX = t.X
|
|
||||||
d.events.CursorY = t.Y
|
|
||||||
case *sdl.MouseButtonEvent:
|
|
||||||
fmt.Printf("[%d ms] MouseButton type:%d id:%d x:%d y:%d button:%d state:%d\n",
|
|
||||||
t.Timestamp, t.Type, t.Which, t.X, t.Y, t.Button, t.State,
|
|
||||||
)
|
|
||||||
|
|
||||||
d.events.LastX = d.events.CursorX
|
|
||||||
d.events.LastY = d.events.CursorY
|
|
||||||
d.events.CursorX = t.X
|
|
||||||
d.events.CursorY = t.Y
|
|
||||||
|
|
||||||
d.events.LastLeft = d.events.LeftClick
|
|
||||||
d.events.LastRight = d.events.RightClick
|
|
||||||
|
|
||||||
// Clicking?
|
|
||||||
if t.Button == 1 {
|
|
||||||
if t.State == 1 && d.events.LeftClick == false {
|
|
||||||
d.events.LeftClick = true
|
|
||||||
} else if t.State == 0 && d.events.LeftClick == true {
|
|
||||||
d.events.LeftClick = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.events.RightClick = t.Button == 3 && t.State == 1
|
|
||||||
case *sdl.MouseWheelEvent:
|
|
||||||
fmt.Printf("[%d ms] MouseWheel type:%d id:%d x:%d y:%d\n",
|
|
||||||
t.Timestamp, t.Type, t.Which, t.X, t.Y,
|
|
||||||
)
|
|
||||||
case *sdl.KeyDownEvent:
|
|
||||||
fmt.Printf("[%d ms] Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
|
||||||
t.Timestamp, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
|
||||||
)
|
|
||||||
case *sdl.KeyUpEvent:
|
|
||||||
fmt.Printf("[%d ms] Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
|
||||||
t.Timestamp, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
7
events/debug.go
Normal file
7
events/debug.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package events
|
||||||
|
|
||||||
|
// Debug constants, toggle on or off for SUPER VERBOSE debugging.
|
||||||
|
var (
|
||||||
|
DebugMouseEvents = false
|
||||||
|
DebugClickEvents = true
|
||||||
|
)
|
81
events/events.go
Normal file
81
events/events.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// Package events manages mouse and keyboard SDL events for Doodle.
|
||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State keeps track of event states.
|
||||||
|
type State struct {
|
||||||
|
// Mouse buttons.
|
||||||
|
Button1 BoolFrameState
|
||||||
|
Button2 BoolFrameState
|
||||||
|
|
||||||
|
// Cursor positions.
|
||||||
|
CursorX Int32FrameState
|
||||||
|
CursorY Int32FrameState
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new event state manager.
|
||||||
|
func New() *State {
|
||||||
|
return &State{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for events.
|
||||||
|
func (s *State) Poll() (*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] MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d",
|
||||||
|
t.Timestamp, 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] MouseButton type:%d id:%d x:%d y:%d button:%d state:%d",
|
||||||
|
t.Timestamp, 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 {
|
||||||
|
if DebugClickEvents {
|
||||||
|
if t.State == 1 && s.Button1.Now == false {
|
||||||
|
log.Debug("Mouse Button1 DOWN")
|
||||||
|
} else if t.State == 0 && s.Button1.Now == true {
|
||||||
|
log.Debug("Mouse Button1 UP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Button1.Push(t.State == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Button2.Push(t.Button == 3 && t.State == 1)
|
||||||
|
case *sdl.MouseWheelEvent:
|
||||||
|
if DebugMouseEvents {
|
||||||
|
log.Debug("[%d ms] MouseWheel type:%d id:%d x:%d y:%d",
|
||||||
|
t.Timestamp, t.Type, t.Which, t.X, t.Y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case *sdl.KeyboardEvent:
|
||||||
|
log.Debug("[%d ms] Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n",
|
||||||
|
t.Timestamp, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
9
events/log.go
Normal file
9
events/log.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package events
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("doodle")
|
||||||
|
}
|
25
events/types.go
Normal file
25
events/types.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package events
|
||||||
|
|
||||||
|
// BoolFrameState holds boolean state between this frame and the previous.
|
||||||
|
type BoolFrameState struct {
|
||||||
|
Now bool
|
||||||
|
Last bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32FrameState manages int32 state between this frame and the previous.
|
||||||
|
type Int32FrameState struct {
|
||||||
|
Now int32
|
||||||
|
Last int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a bool state, copying the current Now value to Last.
|
||||||
|
func (bs *BoolFrameState) Push(v bool) {
|
||||||
|
bs.Last = bs.Now
|
||||||
|
bs.Now = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push an int32 state, copying the current Now value to Last.
|
||||||
|
func (is *Int32FrameState) Push(v int32) {
|
||||||
|
is.Last = is.Now
|
||||||
|
is.Now = v
|
||||||
|
}
|
70
fps.go
Normal file
70
fps.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frames to cache for FPS calculation.
|
||||||
|
const maxSamples = 100
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// DrawDebugOverlay draws the debug FPS text on the SDL canvas.
|
||||||
|
func (d *Doodle) DrawDebugOverlay() {
|
||||||
|
if !d.Debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf(
|
||||||
|
"FPS: %d (%dms) (X,Y)=(%d,%d) canvas=%d",
|
||||||
|
fpsCurrent,
|
||||||
|
fpsSkipped,
|
||||||
|
d.events.CursorX.Now,
|
||||||
|
d.events.CursorY.Now,
|
||||||
|
len(pixelHistory),
|
||||||
|
)
|
||||||
|
render.StrokedText(render.TextConfig{
|
||||||
|
Text: text,
|
||||||
|
Size: DebugTextSize,
|
||||||
|
Color: DebugTextColor,
|
||||||
|
StrokeColor: DebugTextOutline,
|
||||||
|
X: DebugTextPadding,
|
||||||
|
Y: DebugTextPadding,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackFPS shows the current FPS once per second.
|
||||||
|
func (d *Doodle) TrackFPS(skipped uint32) {
|
||||||
|
fpsFrames++
|
||||||
|
fpsCurrentTicks = sdl.GetTicks()
|
||||||
|
|
||||||
|
// Skip the first second.
|
||||||
|
if fpsCurrentTicks < fpsInterval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fpsLastTime < fpsCurrentTicks-fpsInterval {
|
||||||
|
log.Debug("Uptime: %s FPS: %d deltaTicks: %d skipped: %dms",
|
||||||
|
time.Now().Sub(d.startTime),
|
||||||
|
fpsCurrent,
|
||||||
|
fpsCurrentTicks-fpsLastTime,
|
||||||
|
skipped,
|
||||||
|
)
|
||||||
|
|
||||||
|
fpsLastTime = fpsCurrentTicks
|
||||||
|
fpsCurrent = fpsFrames
|
||||||
|
fpsFrames = 0
|
||||||
|
fpsSkipped = skipped
|
||||||
|
}
|
||||||
|
}
|
14
log.go
Normal file
14
log.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("doodle")
|
||||||
|
log.Configure(&golog.Config{
|
||||||
|
Level: golog.DebugLevel,
|
||||||
|
Theme: golog.DarkTheme,
|
||||||
|
Colors: golog.ExtendedColor,
|
||||||
|
})
|
||||||
|
}
|
14
render/log.go
Normal file
14
render/log.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("doodle")
|
||||||
|
log.Configure(&golog.Config{
|
||||||
|
Level: golog.DebugLevel,
|
||||||
|
Theme: golog.DarkTheme,
|
||||||
|
Colors: golog.ExtendedColor,
|
||||||
|
})
|
||||||
|
}
|
7
render/render.go
Normal file
7
render/render.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// Package render manages the SDL rendering context for Doodle.
|
||||||
|
package render
|
||||||
|
|
||||||
|
import "github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
|
// Renderer is a singleton instance of the SDL renderer.
|
||||||
|
var Renderer *sdl.Renderer
|
89
render/text.go
Normal file
89
render/text.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextConfig are settings for rendered text.
|
||||||
|
type TextConfig struct {
|
||||||
|
Text string
|
||||||
|
Size int
|
||||||
|
Color sdl.Color
|
||||||
|
StrokeColor sdl.Color
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
W 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user