Compare commits

...

5 Commits

Author SHA1 Message Date
2413c7d3d4 Added flowers (+ basic simulation).
Added resource loader (load list of textures).
2020-05-09 19:34:43 +02:00
b32017f18a Added clipping to terrain renderer. 2020-05-09 16:48:39 +02:00
0a73529306 Separated the lazy text rendering method. 2020-05-09 16:48:18 +02:00
58aa819a4e Added FPS. 2020-05-09 15:32:43 +02:00
8dd47c5ec2 Added storing of window size. 2020-05-09 14:02:00 +02:00
36 changed files with 632 additions and 209 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,74 @@
tile-dirt: images/tile_dirt.png
tile-grass: images/tile_grass.png
tile-snow: images/tile_snow.png
cactus-small-1: images/cactus_short_NE.png
cactus-small-2: images/cactus_short_NW.png
cactus-small-3: images/cactus_short_SW.png
cactus-small-4: images/cactus_short_SE.png
cactus-tall-1: images/cactus_tall_NE.png
cactus-tall-2: images/cactus_tall_NW.png
cactus-tall-3: images/cactus_tall_SW.png
cactus-tall-4: images/cactus_tall_SE.png
tree-fat-1: images/tree_fat_NE.png
tree-fat-2: images/tree_fat_NW.png
tree-fat-3: images/tree_fat_SW.png
tree-fat-4: images/tree_fat_SE.png
tree-pine-1: images/tree_pineDefaultA_NE.png
tree-pine-2: images/tree_pineDefaultA_NW.png
tree-pine-3: images/tree_pineDefaultA_SW.png
tree-pine-4: images/tree_pineDefaultA_SE.png
grass-small-1: images/grass_NE.png
grass-small-2: images/grass_NW.png
grass-small-3: images/grass_SW.png
grass-small-4: images/grass_SE.png
grass-leafs-1: images/grass_leafs_NE.png
grass-leafs-2: images/grass_leafs_NW.png
grass-leafs-3: images/grass_leafs_SW.png
grass-leafs-4: images/grass_leafs_SE.png
bush-small-1: images/plant_bushSmall_NE.png
bush-small-2: images/plant_bushSmall_NW.png
bush-small-3: images/plant_bushSmall_SW.png
bush-small-4: images/plant_bushSmall_SE.png
bush-large-1: images/plant_bushLarge_NE.png
bush-large-2: images/plant_bushLarge_NW.png
bush-large-3: images/plant_bushLarge_SW.png
bush-large-4: images/plant_bushLarge_SE.png
bush-large-1: images/plant_bushLarge_NE.png
bush-large-2: images/plant_bushLarge_NW.png
bush-large-3: images/plant_bushLarge_SW.png
bush-large-4: images/plant_bushLarge_SE.png
flower-poppy-1: images/flower_yellowC_NE.png
flower-poppy-2: images/flower_yellowC_NW.png
flower-poppy-3: images/flower_yellowC_SW.png
flower-poppy-4: images/flower_yellowC_SE.png
flower-red-c-1: images/flower_redC_NE.png
flower-red-c-2: images/flower_redC_NW.png
flower-red-c-3: images/flower_redC_SW.png
flower-red-c-4: images/flower_redC_SE.png
flower-red-a-1: images/flower_redA_NE.png
flower-red-a-2: images/flower_redA_NW.png
flower-red-a-3: images/flower_redA_SW.png
flower-red-a-4: images/flower_redA_SE.png
flower-purple-a-1: images/flower_purpleA_NE.png
flower-purple-a-2: images/flower_purpleA_NW.png
flower-purple-a-3: images/flower_purpleA_SW.png
flower-purple-a-4: images/flower_purpleA_SE.png
flower-purple-c-1: images/flower_purpleC_NE.png
flower-purple-c-2: images/flower_purpleC_NW.png
flower-purple-c-3: images/flower_purpleC_SW.png
flower-purple-c-4: images/flower_purpleC_SE.png

View File

@ -47,10 +47,18 @@ func run() error {
Y: sdl.WINDOWPOS_UNDEFINED, Y: sdl.WINDOWPOS_UNDEFINED,
} }
} }
if ctx.Settings.Window.Size == nil {
ctx.Settings.Window.Size = &tins2020.Point{
X: 800,
Y: 600,
}
}
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1") sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
window, err := sdl.CreateWindow("TINS 2020", ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y, window, err := sdl.CreateWindow("TINS 2020",
800, 600, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE) ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y,
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
if err != nil { if err != nil {
return err return err
} }
@ -64,62 +72,31 @@ func run() error {
ctx.Init(renderer) ctx.Init(renderer)
err = ctx.Fonts.Load("default", "fonts/OpenSans-Regular.ttf", 12) err = ctx.Fonts.LoadDesc(
tins2020.FontDescriptor{Name: "debug", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
tins2020.FontDescriptor{Name: "default", Path: "fonts/FiraMono-Regular.ttf", Size: 10},
)
if err != nil { if err != nil {
return err return err
} }
err = ctx.Textures.Load( textureLoader := tins2020.NewResourceLoader()
"tile-dirt", "images/tile_dirt.png", err = textureLoader.LoadFromFile(&ctx.Resources, "textures.txt", func(name, content string) error {
"tile-grass", "images/tile_grass.png", return ctx.Textures.Load(name, content)
"tile-snow", "images/tile_snow.png", })
"cactus-small-1", "images/cactus_short_NE.png",
"cactus-small-2", "images/cactus_short_NW.png",
"cactus-small-3", "images/cactus_short_SW.png",
"cactus-small-4", "images/cactus_short_SE.png",
"cactus-tall-1", "images/cactus_tall_NE.png",
"cactus-tall-2", "images/cactus_tall_NW.png",
"cactus-tall-3", "images/cactus_tall_SW.png",
"cactus-tall-4", "images/cactus_tall_SE.png",
"tree-fat-1", "images/tree_fat_NE.png",
"tree-fat-2", "images/tree_fat_NW.png",
"tree-fat-3", "images/tree_fat_SW.png",
"tree-fat-4", "images/tree_fat_SE.png",
"tree-pine-1", "images/tree_pineDefaultA_NE.png",
"tree-pine-2", "images/tree_pineDefaultA_NW.png",
"tree-pine-3", "images/tree_pineDefaultA_SW.png",
"tree-pine-4", "images/tree_pineDefaultA_SE.png",
"grass-small-1", "images/grass_NE.png",
"grass-small-2", "images/grass_NW.png",
"grass-small-3", "images/grass_SW.png",
"grass-small-4", "images/grass_SE.png",
"grass-leafs-1", "images/grass_leafs_NE.png",
"grass-leafs-2", "images/grass_leafs_NW.png",
"grass-leafs-3", "images/grass_leafs_SW.png",
"grass-leafs-4", "images/grass_leafs_SE.png",
"bush-small-1", "images/plant_bushSmall_NE.png",
"bush-small-2", "images/plant_bushSmall_NW.png",
"bush-small-3", "images/plant_bushSmall_SW.png",
"bush-small-4", "images/plant_bushSmall_SE.png",
"bush-large-1", "images/plant_bushLarge_NE.png",
"bush-large-2", "images/plant_bushLarge_NW.png",
"bush-large-3", "images/plant_bushLarge_SW.png",
"bush-large-4", "images/plant_bushLarge_SE.png",
)
if err != nil { if err != nil {
return err return err
} }
game := tins2020.NewGame() game := tins2020.NewGame()
control := tins2020.NewTerrainRenderer(game.Terrain)
err = control.Init(ctx) app := tins2020.NewContainer()
overlays := tins2020.NewContainer()
overlays.AddChild(&tins2020.FPS{})
content := tins2020.NewContainer()
app.AddChild(content)
app.AddChild(overlays)
content.AddChild(tins2020.NewTerrainRenderer(game.Terrain))
err = app.Init(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -135,6 +112,9 @@ func run() error {
case sdl.WINDOWEVENT_MOVED: case sdl.WINDOWEVENT_MOVED:
x, y := window.GetPosition() x, y := window.GetPosition()
ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y} ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y}
case sdl.WINDOWEVENT_SIZE_CHANGED:
w, h := window.GetSize()
ctx.Settings.Window.Size = &tins2020.Point{X: w, Y: h}
} }
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
switch e.Keysym.Sym { switch e.Keysym.Sym {
@ -142,8 +122,9 @@ func run() error {
ctx.Quit() ctx.Quit()
} }
} }
control.Handle(ctx, event) app.Handle(ctx, event)
} }
game.Update()
if ctx.ShouldQuit { if ctx.ShouldQuit {
break break
@ -151,7 +132,7 @@ func run() error {
renderer.SetDrawColor(0, 0, 0, 255) renderer.SetDrawColor(0, 0, 0, 255)
renderer.Clear() renderer.Clear()
control.Render(ctx) app.Render(ctx)
renderer.Present() renderer.Present()
} }
return nil return nil

39
container.go Normal file
View File

@ -0,0 +1,39 @@
package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type Container struct {
Children []Control
}
func NewContainer() *Container {
return &Container{}
}
func (c *Container) AddChild(child Control) {
c.Children = append(c.Children, child)
}
func (c *Container) Handle(ctx *Context, event sdl.Event) {
for _, child := range c.Children {
child.Handle(ctx, event)
}
}
func (c *Container) Render(ctx *Context) {
for _, child := range c.Children {
child.Render(ctx)
}
}
func (c *Container) Init(ctx *Context) error {
for _, child := range c.Children {
err := child.Init(ctx)
if err != nil {
return err
}
}
return nil
}

18
control.go Normal file
View File

@ -0,0 +1,18 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Control interface {
Init(*Context) error
Handle(*Context, sdl.Event)
Render(*Context)
}
type ControlBase struct {
}
func (b *ControlBase) Handle(*Context, sdl.Event) {}
func (b *ControlBase) Render(*Context) {}
func (b *ControlBase) Init(*Context) error { return nil }

46
flower.go Normal file
View File

@ -0,0 +1,46 @@
package tins2020
type Flower struct {
Traits FlowerTraits
}
type FlowerTraits struct {
Spread float32
Life float32
LifeModifier float32
Resistance FlowerResistance
}
func (t *FlowerTraits) UpdateModifier(temp, humid float32) {
mod := float32(1)
cold := temp * 2
mod *= Min32(1, Sqrt32(t.Resistance.Cold*t.Resistance.Cold+cold*cold))
hot := (1 - temp) * 2
mod *= Min32(1, Sqrt32(t.Resistance.Hot*t.Resistance.Hot+hot*hot))
dry := humid * 2
mod *= Min32(1, Sqrt32(t.Resistance.Dry*t.Resistance.Dry+dry*dry))
wet := (1 - humid) * 2
mod *= Min32(1, Sqrt32(t.Resistance.Wet*t.Resistance.Wet+wet*wet))
t.LifeModifier = mod
}
type FlowerResistance struct {
Cold float32 // 0 will die almost instantly, 1 is completely resistant.
Hot float32
Dry float32
Wet float32
}
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate.
func NewPoppyTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.01,
Life: 0.9991,
Resistance: FlowerResistance{
Cold: 0.3,
Hot: 0.3,
Dry: 0.3,
Wet: 0.3,
},
}
}

View File

@ -1,18 +1,66 @@
package tins2020 package tins2020
import ( import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf" "github.com/veandco/go-sdl2/ttf"
"opslag.de/schobers/fs/vfs" "opslag.de/schobers/fs/vfs"
) )
type Font struct {
*ttf.Font
}
func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) (*Texture, error) {
surface, err := f.RenderUTF8Solid(text, color)
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := NewTextureFromSurface(renderer, surface)
if err != nil {
return nil, err
}
return texture, nil
}
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
texture, err := f.Render(renderer, text, pos, color)
if err != nil {
return err
}
defer texture.Destroy()
texture.Copy(renderer, texture.RectOffset(pos))
return nil
}
type Fonts struct { type Fonts struct {
dir vfs.CopyDir dir vfs.CopyDir
fonts map[string]*ttf.Font fonts map[string]*Font
} }
func (f *Fonts) Init(dir vfs.CopyDir) { func (f *Fonts) Init(dir vfs.CopyDir) {
f.dir = dir f.dir = dir
f.fonts = map[string]*ttf.Font{} f.fonts = map[string]*Font{}
}
func (f *Fonts) Font(name string) *Font { return f.fonts[name] }
type FontDescriptor struct {
Name string
Path string
Size int
}
func (f *Fonts) LoadDesc(fonts ...FontDescriptor) error {
for _, desc := range fonts {
err := f.Load(desc.Name, desc.Path, desc.Size)
if err != nil {
return fmt.Errorf("error loading '%s'; error: %v", desc.Name, err)
}
}
return nil
} }
func (f *Fonts) Load(name, path string, size int) error { func (f *Fonts) Load(name, path string, size int) error {
@ -27,7 +75,7 @@ func (f *Fonts) Load(name, path string, size int) error {
if font, ok := f.fonts[name]; ok { if font, ok := f.fonts[name]; ok {
font.Close() font.Close()
} }
f.fonts[name] = font f.fonts[name] = &Font{font}
return nil return nil
} }

41
fpsrenderer.go Normal file
View File

@ -0,0 +1,41 @@
package tins2020
import (
"fmt"
"time"
"github.com/veandco/go-sdl2/sdl"
)
type FPS struct {
ControlBase
start time.Time
stamp time.Duration
slot int
ticks []int
total int
}
func (f *FPS) Init(*Context) error {
f.start = time.Now()
f.stamp = 0
f.ticks = make([]int, 51)
return nil
}
func (f *FPS) Render(ctx *Context) {
elapsed := time.Since(f.start)
stamp := elapsed / (20 * time.Millisecond)
for f.stamp < stamp {
f.total += f.ticks[f.slot]
f.slot = (f.slot + 1) % len(f.ticks)
f.total -= f.ticks[f.slot]
f.ticks[f.slot] = 0
f.stamp++
}
f.ticks[f.slot]++
font := ctx.Fonts.Font("debug")
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 5), sdl.Color{R: 255, G: 255, B: 255, A: 255})
}

109
game.go
View File

@ -2,14 +2,15 @@ package tins2020
import ( import (
"math/rand" "math/rand"
"time"
"opslag.de/schobers/geom/noise"
) )
type Game struct { type Game struct {
Money int
Terrain *Map Terrain *Map
Flowers []Flower
Bees []Bee start time.Time
lastUpdate time.Duration
} }
type Map struct { type Map struct {
@ -18,55 +19,21 @@ type Map struct {
Variant NoiseMap Variant NoiseMap
PlaceX NoiseMap // displacement map of props PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap PlaceY NoiseMap
Flowers map[Point]Flower
} }
type NoiseMap interface { func (m *Map) AddFlower(pos Point, traits FlowerTraits) {
Value(x, y int32) float64 m.Flowers[pos] = m.NewFlower(pos, traits)
} }
func NewNoiseMap(seed int64) NoiseMap { func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower {
return &noiseMap{ flower := Flower{
noise: noise.NewPerlin(seed), Traits: traits,
alpha: 2,
beta: 4,
harmonics: 2,
} }
} temp, humid := float32(m.Temp.Value(pos.X, pos.Y)), float32(m.Humid.Value(pos.X, pos.Y))
flower.Traits.UpdateModifier(temp, humid)
func NewRandomNoiseMap(seed int64) NoiseMap { return flower
return &randomNoiseMap{noise.NewPerlin(seed)}
}
type noiseMap struct {
noise *noise.Perlin
alpha, beta float64
harmonics int
}
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m *noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
type randomNoiseMap struct {
*noise.Perlin
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m randomNoiseMap) Value(x, y int32) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
} }
func NewGame() *Game { func NewGame() *Game {
@ -76,18 +43,52 @@ func NewGame() *Game {
Variant: NewRandomNoiseMap(rand.Int63()), Variant: NewRandomNoiseMap(rand.Int63()),
PlaceX: NewRandomNoiseMap(rand.Int63()), PlaceX: NewRandomNoiseMap(rand.Int63()),
PlaceY: NewRandomNoiseMap(rand.Int63()), PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{},
} }
terrain.AddFlower(Pt(0, 0), NewPoppyTraits())
return &Game{ return &Game{
Money: 100,
Terrain: terrain, Terrain: terrain,
Flowers: []Flower{},
Bees: []Bee{}, start: time.Now(),
} }
} }
type Flower struct { func (g *Game) Update() {
Location PointF update := time.Since(g.start)
for g.lastUpdate < update {
g.tick()
g.lastUpdate += time.Millisecond * 10
}
} }
type Bee struct { func (g *Game) tick() {
Location PointF randomNeighbor := func(pos Point) Point {
switch rand.Intn(4) {
case 0:
return Pt(pos.X-1, pos.Y)
case 1:
return Pt(pos.X, pos.Y-1)
case 2:
return Pt(pos.X+1, pos.Y)
case 3:
return Pt(pos.X, pos.Y+1)
}
return pos
}
flowers := map[Point]Flower{}
for pos, flower := range g.Terrain.Flowers {
if rand.Float32() < flower.Traits.Spread {
dst := randomNeighbor(pos)
if _, ok := g.Terrain.Flowers[dst]; !ok {
if _, ok := flowers[dst]; !ok {
flowers[dst] = g.Terrain.NewFlower(dst, flower.Traits)
}
}
}
if rand.Float32() < flower.Traits.Life*flower.Traits.LifeModifier {
flowers[pos] = flower
}
}
g.Terrain.Flowers = flowers
} }

37
math.go Normal file
View File

@ -0,0 +1,37 @@
package tins2020
import "math"
func Abs32(x float32) float32 {
if x < 0 {
return -x
}
return x
}
func AbsSub32(a, b float32) float32 {
if a > b {
return a - b
}
return b - a
}
func Ceil32(x float32) float32 { return float32(math.Ceil(float64(x))) }
func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) }
func Max32(a, b float32) float32 {
if a > b {
return a
}
return b
}
func Min32(a, b float32) float32 {
if a < b {
return a
}
return b
}
func Sqrt32(x float32) float32 { return float32(math.Sqrt(float64(x))) }

52
noisemap.go Normal file
View File

@ -0,0 +1,52 @@
package tins2020
import "opslag.de/schobers/geom/noise"
type NoiseMap interface {
Value(x, y int32) float64
}
func NewNoiseMap(seed int64) NoiseMap {
return &noiseMap{
noise: noise.NewPerlin(seed),
alpha: 2,
beta: 4,
harmonics: 2,
}
}
func NewRandomNoiseMap(seed int64) NoiseMap {
return &randomNoiseMap{noise.NewPerlin(seed)}
}
type noiseMap struct {
noise *noise.Perlin
alpha, beta float64
harmonics int
}
func clipNormalized(x float64) float64 {
if x < 0 {
return 0
}
if x > 1 {
return 1
}
return x
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m *noiseMap) Value(x, y int32) float64 {
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
return clipNormalized(value)
}
type randomNoiseMap struct {
*noise.Perlin
}
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
func (m randomNoiseMap) Value(x, y int32) float64 {
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
return clipNormalized(value)
}

90
projection.go Normal file
View File

@ -0,0 +1,90 @@
package tins2020
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
type projection struct {
center PointF
zoom float32
zoomInv float32
windowRect sdl.Rect
tileScreenDelta PointF
tileScreenDeltaInv PointF
tileScreenOffset Point
tileScreenSize Point
tileFitScreenSize Point
windowCenter Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
}
func (p *projection) mapToScreen(x, y int32) Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) Point {
translated := PtF(x-p.center.X, y-p.center.Y)
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
}
func (p *projection) screenToMap(x, y int32) PointF {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToMapRel(x, y int32) PointF {
normX := p.zoom * float32(x)
normY := p.zoom * float32(y)
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToTileFitRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileFitScreenSize.X, Y: pos.Y - p.tileFitScreenSize.Y, W: 2 * p.tileFitScreenSize.X, H: 2 * p.tileFitScreenSize.Y}
}
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
}
func (p *projection) update(renderer *sdl.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
p.tileFitScreenSize = Pt(int32(p.zoomInv*65), int32(p.zoomInv*32))
windowW, windowH, err := renderer.GetOutputSize()
if err != nil {
log.Fatal(err)
}
p.windowCenter = Pt(windowW/2, windowH/2)
p.windowRect = sdl.Rect{X: 0, Y: 0, W: windowW - 0, H: windowH - 0}
}
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
topLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y)
topRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y)
bottomLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y+p.windowRect.H)
bottomRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y+p.windowRect.H)
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
pos := p.mapToScreen(x, y)
rectFit := p.screenToTileFitRect(pos)
if rectFit.X+rectFit.W < p.windowRect.X || rectFit.Y+rectFit.H < p.windowRect.Y {
continue
}
if rectFit.X > p.windowRect.X+p.windowRect.W || rectFit.Y > p.windowRect.Y+p.windowRect.H {
break
}
action(x, y, pos)
}
}
}

View File

@ -1,9 +0,0 @@
package tins2020
import "github.com/veandco/go-sdl2/sdl"
type Control interface {
Init(*Context) error
Handle(*Context, sdl.Event)
Render(*Context)
}

51
resourceloader.go Normal file
View File

@ -0,0 +1,51 @@
package tins2020
import (
"bufio"
"fmt"
"strings"
)
type ResourceLoader struct {
Resources map[string]string
}
func NewResourceLoader() *ResourceLoader {
return &ResourceLoader{}
}
func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
f, err := res.Fs().Open(name)
if err != nil {
return err
}
defer f.Close()
l.Resources = map[string]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
definition := scanner.Text()
sep := strings.Index(definition, ":")
if sep == -1 {
continue
}
name := strings.TrimSpace(definition[:sep])
content := strings.TrimSpace(definition[sep+1:])
l.Resources[name] = content
}
return nil
}
func (l *ResourceLoader) LoadFromFile(res *Resources, name string, action func(string, string) error) error {
err := l.parseResourcesFile(res, name)
if err != nil {
return err
}
for name, content := range l.Resources {
err = action(name, content)
if err != nil {
return fmt.Errorf("cannot load resource '%s'; error: %v", name, err)
}
}
return nil
}

View File

@ -31,4 +31,5 @@ func (s *Settings) Store() error {
type WindowSettings struct { type WindowSettings struct {
Location *Point Location *Point
Size *Point
} }

View File

@ -2,11 +2,9 @@ package tins2020
import ( import (
"fmt" "fmt"
"log"
"opslag.de/schobers/geom"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
) )
type terrainRenderer struct { type terrainRenderer struct {
@ -23,59 +21,6 @@ type interaction struct {
mouseDrag *Point mouseDrag *Point
} }
type projection struct {
center PointF
zoom float32
zoomInv float32
tileScreenDelta PointF
tileScreenDeltaInv PointF
tileScreenOffset Point
tileScreenSize Point
windowCenter Point
}
func newProjection() projection {
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
}
func (p *projection) update(renderer *sdl.Renderer) {
p.zoomInv = 1 / p.zoom
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
windowW, windowH, err := renderer.GetOutputSize()
if err != nil {
log.Fatal(err)
}
p.windowCenter = Pt(windowW/2, windowH/2)
}
func (p *projection) mapToScreen(x, y int32) Point {
return p.mapToScreenF(float32(x), float32(y))
}
func (p *projection) mapToScreenF(x, y float32) Point {
translated := PtF(x-p.center.X, y-p.center.Y)
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
}
func (p *projection) screenToMapRel(x, y int32) PointF {
normX := p.zoom * float32(x)
normY := p.zoom * float32(y)
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
}
func (p *projection) screenToMap(x, y int32) PointF {
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
return p.center.Add(pos)
}
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
}
func NewTerrainRenderer(terrain *Map) Control { func NewTerrainRenderer(terrain *Map) Control {
return &terrainRenderer{terrain: terrain, project: newProjection()} return &terrainRenderer{terrain: terrain, project: newProjection()}
} }
@ -92,25 +37,22 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { if r.interact.mouseLeftDown && r.interact.mouseDrag == nil {
r.interact.mouseDrag = &Point{e.X, e.Y} r.interact.mouseDrag = &Point{e.X, e.Y}
fmt.Println("Drag start", r.interact.mouseDrag)
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { } else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil {
fmt.Println("Drag end", r.interact.mouseDrag)
r.interact.mouseDrag = nil r.interact.mouseDrag = nil
} }
} }
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
r.hover = r.project.screenToMap(e.X, e.Y) r.hover = r.project.screenToMap(e.X, e.Y)
if r.interact.mouseDrag != nil { if r.interact.mouseDrag != nil {
fmt.Println("Dragging...", r.project.center)
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y)) r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y))
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
r.interact.mouseDrag = &Point{e.X, e.Y} r.interact.mouseDrag = &Point{e.X, e.Y}
} }
case *sdl.MouseWheelEvent: case *sdl.MouseWheelEvent:
if e.Y > 0 { if e.Y > 0 && r.project.zoom > .5 {
r.project.zoom *= .5 r.project.zoom *= .5
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
} else if e.Y < 0 { } else if e.Y < 0 && r.project.zoom < 4 {
r.project.zoom *= 2 r.project.zoom *= 2
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
} }
@ -185,41 +127,48 @@ func (r *terrainRenderer) Render(ctx *Context) {
return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier) return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier)
} }
toItemTexture := func(x, y int32) *Texture {
variant := r.terrain.Variant.Value(x, y)
_, ok := r.terrain.Flowers[Pt(x, y)]
if ok {
return variantToTexture("flower-poppy-%d", variant)
}
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
return toPropTexture(temp, humid, variant)
}
// horizontal: [191, 321) = 130 // horizontal: [191, 321) = 130
// vertical: [267,332) = 65 // vertical: [267,332) = 65
hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y))) hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y)))
for y := int32(-100); y < 100; y++ { r.project.visibleTiles(func(x, y int32, pos Point) {
for x := int32(-100); x < 100; x++ { if x == hover.X && y == hover.Y {
if x == hover.X && y == hover.Y { return
continue
}
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
text := toTileTexture(temp, humid)
pos := r.project.mapToScreen(x, y)
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
} }
} temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
text := toTileTexture(temp, humid)
rect := r.project.screenToTileRect(pos)
text.Copy(ctx.Renderer, rect)
})
for y := int32(-100); y < 100; y++ { r.project.visibleTiles(func(x, y int32, pos Point) {
for x := int32(-100); x < 100; x++ { text := toItemTexture(x, y)
variant := r.terrain.Variant.Value(x, y) if text == nil {
if variant < -1 || variant > 1 { return
continue
}
temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y)
text := toPropTexture(temp, humid, variant)
if text == nil {
continue
}
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
pos := r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4))
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
} }
}
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
pos = r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4))
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
})
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255})
// for y := int32(-40); y < 40; y++ {
// for x := int32(-40); x < 40; x++ {
// pos := r.project.mapToScreen(x, y)
// ctx.Fonts.Font("debug").RenderCopy(ctx.Renderer, fmt.Sprintf("(%d, %d)", x, y), pos, sdl.Color{R: 255, A: 255})
// }
// }
} }

View File

@ -24,6 +24,10 @@ func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Textu
func (t *Texture) Rect() *sdl.Rect { return t.rect } func (t *Texture) Rect() *sdl.Rect { return t.rect }
func (t *Texture) RectOffset(offset Point) *sdl.Rect {
return &sdl.Rect{X: offset.X, Y: offset.Y, W: t.rect.W, H: t.rect.H}
}
func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) { func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) {
renderer.Copy(t.texture, t.rect, target) renderer.Copy(t.texture, t.rect, target)
} }