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
|
||||
rendering and collision detection.
|
||||
* [ ] ???
|
||||
|
||||
# Building
|
||||
|
||||
Fedora dependencies:
|
||||
|
||||
```bash
|
||||
$ sudo dnf install SDL2-devel SDL2_ttf-devel
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"flag"
|
||||
"runtime"
|
||||
|
||||
"github.com/kirsle/doodle"
|
||||
"git.kirsle.net/apps/doodle"
|
||||
)
|
||||
|
||||
// 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}
|
||||
)
|
252
doodle.go
252
doodle.go
|
@ -2,59 +2,81 @@ package doodle
|
|||
|
||||
import (
|
||||
"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/ttf"
|
||||
)
|
||||
|
||||
// Version number.
|
||||
const Version = "0.0.0-alpha"
|
||||
const (
|
||||
// Version number.
|
||||
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.
|
||||
type Doodle struct {
|
||||
Debug bool
|
||||
|
||||
running bool
|
||||
events EventState
|
||||
width int
|
||||
height int
|
||||
startTime time.Time
|
||||
running bool
|
||||
events *events.State
|
||||
width int32
|
||||
height int32
|
||||
|
||||
nextSecond time.Time
|
||||
canvas Grid
|
||||
|
||||
window *sdl.Window
|
||||
surface *sdl.Surface
|
||||
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.
|
||||
func New(debug bool) *Doodle {
|
||||
d := &Doodle{
|
||||
Debug: debug,
|
||||
running: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
Debug: debug,
|
||||
startTime: time.Now(),
|
||||
events: events.New(),
|
||||
running: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
canvas: Grid{},
|
||||
|
||||
nextSecond: time.Now().Add(1 * time.Second),
|
||||
}
|
||||
|
||||
if !debug {
|
||||
log.Config.Level = golog.InfoLevel
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Run initializes SDL and starts the main loop.
|
||||
func (d *Doodle) Run() error {
|
||||
// Initialize SDL.
|
||||
log.Info("Initializing SDL")
|
||||
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
||||
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,
|
||||
|
@ -70,56 +92,33 @@ func (d *Doodle) Run() error {
|
|||
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()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
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)
|
||||
}
|
||||
|
||||
log.Info("Enter Main Loop")
|
||||
for d.running {
|
||||
// Draw a frame and log how long it took.
|
||||
start := time.Now()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// surface, err := window.GetSurface()
|
||||
// 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)
|
||||
log.Warn("Main Loop Exited! Shutting down...")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -128,117 +127,72 @@ type Pixel struct {
|
|||
start bool
|
||||
x 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
|
||||
|
||||
// Loop runs one loop of the game engine.
|
||||
func (d *Doodle) Loop() error {
|
||||
// Poll for events.
|
||||
d.PollEvents()
|
||||
|
||||
d.renderer.Clear()
|
||||
rect := sdl.Rect{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: 800,
|
||||
H: 600,
|
||||
ev, err := d.events.Poll()
|
||||
if err != nil {
|
||||
log.Error("event poll error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the canvas and fill it with white.
|
||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
||||
d.renderer.FillRect(&rect)
|
||||
d.renderer.Clear()
|
||||
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if d.events.LeftClick {
|
||||
fmt.Printf("Pixel at %dx%d\n", d.events.CursorX, d.events.CursorY)
|
||||
if ev.Button1.Now {
|
||||
pixel := Pixel{
|
||||
start: d.events.LeftClick && !d.events.LastLeft,
|
||||
x: d.events.CursorX,
|
||||
y: d.events.CursorY,
|
||||
start: ev.Button1.Now && !ev.Button1.Last,
|
||||
x: ev.CursorX.Now,
|
||||
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)
|
||||
for i, pixel := range pixelHistory {
|
||||
fmt.Printf("Draw: %v\n", pixel)
|
||||
if pixel.start == false && i > 0 {
|
||||
start := pixelHistory[i-1]
|
||||
fmt.Printf("Line from %dx%d -> %dx%d\n", start.x, start.y, pixel.x, pixel.y)
|
||||
d.renderer.DrawLine(
|
||||
int(start.x),
|
||||
int(start.y),
|
||||
int(pixel.x),
|
||||
int(pixel.y),
|
||||
)
|
||||
} else {
|
||||
d.renderer.DrawPoint(
|
||||
int(pixel.x), int(pixel.y),
|
||||
)
|
||||
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.FillRect(&sdl.Rect{pixel.x, pixel.y, 10, 10})
|
||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||
}
|
||||
|
||||
// Draw the FPS.
|
||||
d.DrawDebugOverlay()
|
||||
|
||||
d.renderer.Present()
|
||||
sdl.Delay(1000 / 60)
|
||||
|
||||
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