Fill ground layer with dirt and trees

This commit is contained in:
Trevor Slocum 2022-01-10 13:15:03 -08:00
parent 35a2293967
commit a994349b78
9 changed files with 230 additions and 76 deletions

View File

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.5" tiledversion="1.7.2" orientation="isometric" renderorder="right-down" width="3" height="3" tilewidth="128" tileheight="64" infinite="0" nextlayerid="4" nextobjectid="1">
<map version="1.5" tiledversion="1.7.2" orientation="isometric" renderorder="right-down" width="2" height="2" tilewidth="128" tileheight="64" infinite="0" nextlayerid="4" nextobjectid="1">
<tileset firstgid="1" source="../image/tileset/MRMO_BRIK.tsx"/>
<layer id="1" name="1" width="3" height="3">
<layer id="1" name="1" width="2" height="2">
<data encoding="csv">
161,161,161,
161,161,161,
161,529,161
161,161,
161,527
</data>
</layer>
<layer id="2" name="2" width="3" height="3" offsetx="0" offsety="-80">
<layer id="2" name="2" width="2" height="2" offsetx="0" offsety="-80">
<data encoding="csv">
375,375,375,
372,372,372,
375,375,375
183,183,
188,188
</data>
</layer>
</map>

View File

@ -80,6 +80,45 @@ func (g *game) Update() error {
if world.World.ResetGame {
world.Reset()
err := world.LoadTileset()
if err != nil {
return err
}
// Fill below ground layer.
dirtTile := uint32(9*32 + (0))
grassTile := uint32(11*32 + (0))
treeTileA := uint32(5*32 + (25))
treeTileB := uint32(5*32 + (27))
var img uint32
for x := range world.World.Level.Tiles[0] {
for y := range world.World.Level.Tiles[0][x] {
img = dirtTile
if rand.Intn(128) == 0 {
img = grassTile
world.World.Level.Tiles[0][x][y].Sprite = world.World.TileImages[img+world.World.TileImagesFirstGID]
for offsetX := -2 - rand.Intn(7); offsetX < 2+rand.Intn(7); offsetX++ {
for offsetY := -2 - rand.Intn(7); offsetY < 2+rand.Intn(7); offsetY++ {
if x+offsetX >= 0 && y+offsetY >= 0 && x+offsetX < 256 && y+offsetY < 256 {
world.World.Level.Tiles[0][x+offsetX][y+offsetY].Sprite = world.World.TileImages[img+world.World.TileImagesFirstGID]
if rand.Intn(2) == 0 {
if rand.Intn(3) == 0 {
world.World.Level.Tiles[1][x+offsetX][y+offsetY].EnvironmentSprite = world.World.TileImages[treeTileA+world.World.TileImagesFirstGID]
} else {
world.World.Level.Tiles[1][x+offsetX][y+offsetY].EnvironmentSprite = world.World.TileImages[treeTileB+world.World.TileImagesFirstGID]
}
}
}
}
}
} else {
if world.World.Level.Tiles[0][x][y].Sprite != nil {
continue
}
world.World.Level.Tiles[0][x][y].Sprite = world.World.TileImages[img+world.World.TileImagesFirstGID]
}
}
}
world.BuildStructure(world.StructureHouse1, false, 0, 0)
@ -124,7 +163,7 @@ func (g *game) Update() error {
// renderSprite renders a sprite on the screen.
func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, geoScale float64, colorScale float64, alpha float64, hFlip bool, vFlip bool, sprite *ebiten.Image, target *ebiten.Image) int {
if alpha < .01 || colorScale < .01 {
if alpha < .01 {
return 0
}
@ -135,21 +174,20 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
// Skip drawing tiles that are out of the screen.
drawX, drawY := world.IsoToScreen(xi, yi)
if drawX+padding < 0 || drawY+padding < 0 || drawX > float64(world.World.ScreenW) || drawY > float64(world.World.ScreenH) {
//log.Println("SKIP", drawX, drawY, world.World.ScreenW, world.World.ScreenH)
if drawX+padding < 0 || drawY+padding < 0 || drawX-padding > float64(world.World.ScreenW) || drawY-padding > float64(world.World.ScreenH) {
return 0
}
g.op.GeoM.Reset()
/*if hFlip {
s.op.GeoM.Scale(-1, 1)
s.op.GeoM.Translate(TileWidth, 0)
if hFlip {
g.op.GeoM.Scale(-1, 1)
g.op.GeoM.Translate(world.TileSize, 0)
}
if vFlip {
s.op.GeoM.Scale(1, -1)
s.op.GeoM.Translate(0, TileWidth)
}*/
g.op.GeoM.Scale(1, -1)
g.op.GeoM.Translate(0, world.TileSize)
}
// Move to current isometric position.
g.op.GeoM.Translate(xi, yi+offsety)
@ -197,18 +235,21 @@ func (g *game) Draw(screen *ebiten.Image) {
continue
}
var sprite *ebiten.Image
alpha := 1.0
colorScale := 1.0
if tile.HoverSprite != nil {
sprite = tile.HoverSprite
alpha = 0.8
colorScale = 0.6
if !world.World.HoverValid {
colorScale = 0.1
}
} else if tile.Sprite != nil {
sprite = tile.Sprite
} else if tile.EnvironmentSprite != nil {
sprite = tile.EnvironmentSprite
} else {
continue
}
g.renderSprite(float64(x), float64(y), 0, float64(i*-80), 0, 1, colorScale, alpha, false, false, sprite, screen)
g.renderSprite(float64(x), float64(y), 0, float64(i*-80), 0, 1, colorScale, 1, false, false, sprite, screen)
}
}
}

4
go.mod
View File

@ -6,6 +6,7 @@ require (
code.rocketnine.space/tslocum/gohan v0.0.0-20220106015515-0231e09ad78e
github.com/hajimehoshi/ebiten/v2 v2.2.3
github.com/lafriks/go-tiled v0.6.0
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
)
require (
@ -15,8 +16,7 @@ require (
github.com/jfreymuth/oggvorbis v1.0.3 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
golang.org/x/exp v0.0.0-20220104160115-025e73f80486 // indirect
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
golang.org/x/mobile v0.0.0-20220104184238-4a8be17bd2e3 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe // indirect
)

4
go.sum
View File

@ -72,8 +72,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe h1:W8vbETX/n8S6EmY0Pu4Ix7VvpsJUESTwl0oCK8MJOgk=
golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

31
goreleaser.yml Normal file
View File

@ -0,0 +1,31 @@
project_name: citylimits
builds:
-
id: citylimits
# ldflags:
# - -s -w -X code.rocketnine.space/tslocum/citylimits/main.Version={{.Version}}
goos:
- js
- linux
- windows
goarch:
- amd64
- wasm
archives:
-
id: citylimits
builds:
- citylimits
replacements:
386: i386
format_overrides:
- goos: js
format: zip
- goos: windows
format: zip
files:
- ./*.md
- LICENSE
checksum:
name_template: 'checksums.txt'

View File

@ -21,12 +21,17 @@ type playerMoveSystem struct {
rewindTicks int
nextRewindTick int
scrollDragX, scrollDragY int
scrollCamStartX, scrollCamStartY float64
}
func NewPlayerMoveSystem(player gohan.Entity, m *MovementSystem) *playerMoveSystem {
return &playerMoveSystem{
player: player,
movement: m,
player: player,
movement: m,
scrollDragX: -1,
scrollDragY: -1,
}
}
@ -98,6 +103,13 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
}
}
world.World.CamScaleTarget += scrollY * (world.World.CamScaleTarget / 7)
const minZoom = .15
const maxZoom = 2
if world.World.CamScaleTarget < minZoom {
world.World.CamScaleTarget = minZoom
} else if world.World.CamScaleTarget > maxZoom {
world.World.CamScaleTarget = maxZoom
}
// Smooth zoom transition.
div := 10.0
@ -131,6 +143,7 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
}
}
const scrollEdgeSize = 5
x, y := ebiten.CursorPosition()
if !world.World.GotCursorPosition {
if x != 0 || y != 0 {
@ -139,15 +152,32 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
return nil
}
}
if x == 0 {
world.World.CamX -= camSpeed
} else if x == world.World.ScreenW-1 {
world.World.CamX += camSpeed
}
if y == 0 {
world.World.CamY -= camSpeed
} else if y == world.World.ScreenH-1 {
world.World.CamY += camSpeed
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonMiddle) {
if s.scrollDragX == -1 && s.scrollDragY == -1 {
ebiten.SetCursorMode(ebiten.CursorModeCaptured)
s.scrollDragX, s.scrollDragY = x, y
s.scrollCamStartX, s.scrollCamStartY = world.World.CamX, world.World.CamY
} else {
dx, dy := float64(x-s.scrollDragX)/world.World.CamScale, float64(y-s.scrollDragY)/world.World.CamScale
world.World.CamX, world.World.CamY = s.scrollCamStartX-dx, s.scrollCamStartY-dy
}
} else {
if s.scrollDragX != -1 && s.scrollDragY != -1 {
s.scrollDragX, s.scrollDragY = -1, -1
ebiten.SetCursorMode(ebiten.CursorModeVisible)
} else if x >= 0 && y >= 0 && x < world.World.ScreenW && y < world.World.ScreenH {
// Pan via screen edge.
if x <= scrollEdgeSize {
world.World.CamX -= camSpeed
} else if x >= world.World.ScreenW-scrollEdgeSize-1 {
world.World.CamX += camSpeed
}
if y <= scrollEdgeSize {
world.World.CamY -= camSpeed
} else if y >= world.World.ScreenH-scrollEdgeSize-1 {
world.World.CamY += camSpeed
}
}
}
if world.World.HoverStructure != 0 {

View File

@ -83,21 +83,20 @@ func (s *RenderSystem) renderSprite(x float64, y float64, offsetx float64, offse
// Skip drawing tiles that are out of the screen.
drawX, drawY := world.IsoToScreen(xi, yi)
if drawX+padding < 0 || drawY+padding < 0 || drawX > float64(world.World.ScreenW) || drawY > float64(world.World.ScreenH) {
//log.Println("SKIP", drawX, drawY, world.World.ScreenW, world.World.ScreenH)
if drawX+padding < 0 || drawY+padding < 0 || drawX-padding > float64(world.World.ScreenW) || drawY-padding > float64(world.World.ScreenH) {
return 0
}
s.op.GeoM.Reset()
/*if hFlip {
if hFlip {
s.op.GeoM.Scale(-1, 1)
s.op.GeoM.Translate(TileWidth, 0)
}
if vFlip {
s.op.GeoM.Scale(1, -1)
s.op.GeoM.Translate(0, TileWidth)
}*/
}
// Move to current isometric position.
s.op.GeoM.Translate(xi, yi)

View File

@ -5,8 +5,9 @@ import (
)
type Tile struct {
Sprite *ebiten.Image
HoverSprite *ebiten.Image
Sprite *ebiten.Image
EnvironmentSprite *ebiten.Image
HoverSprite *ebiten.Image
}
type GameLevel struct {
@ -19,6 +20,10 @@ func NewLevel(size int) *GameLevel {
l := &GameLevel{
size: size,
}
const startingLayers = 2
for i := 0; i < startingLayers; i++ {
l.AddLayer()
}
return l
}
@ -26,12 +31,9 @@ func (l *GameLevel) AddLayer() {
tileMap := make([][]*Tile, l.size)
for x := 0; x < l.size; x++ {
tileMap[x] = make([]*Tile, l.size)
// TODO
/*for y := range tileMap[x] {
tileMap[x][y] = &Tile{
Sprite: asset.ImgWhiteSquare,
}
}*/
for y := 0; y < l.size; y++ {
tileMap[x][y] = &Tile{}
}
}
l.Tiles = append(l.Tiles, tileMap)
}

View File

@ -23,9 +23,11 @@ const (
StructurePoliceStation
)
const startingZoom = 0.5
var World = &GameWorld{
CamScale: 1,
CamScaleTarget: 1,
CamScale: startingZoom,
CamScaleTarget: startingZoom,
CamMoving: true,
PlayerWidth: 8,
PlayerHeight: 32,
@ -69,6 +71,7 @@ type GameWorld struct {
HoverStructure int
HoverX, HoverY int
HoverLastX, HoverLastY int
HoverValid bool
Map *tiled.Map
ObjectGroups []*tiled.ObjectGroup
@ -83,7 +86,8 @@ type GameWorld struct {
BrokenPieceA, BrokenPieceB gohan.Entity
TileImages map[uint32]*ebiten.Image
TileImages map[uint32]*ebiten.Image
TileImagesFirstGID uint32
ResetGame bool
@ -116,9 +120,7 @@ func Reset() {
World.MessageVisible = false
}
func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Structure, error) {
World.Level.ClearHoverSprites()
func LoadMap(structureType int) (*tiled.Map, error) {
loader := tiled.Loader{
FileSystem: asset.FS,
}
@ -139,28 +141,35 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
log.Fatalf("error parsing world: %+v", err)
}
if placeX-m.Width < 0 || placeY-m.Height < 0 || placeX > 256 || placeY > 256 {
return nil, errors.New("invalid location: building does not fit")
return m, err
}
func LoadTileset() error {
m, err := LoadMap(StructureHouse1)
if err != nil {
return err
}
// Load tileset.
tileset := m.Tilesets[0]
if len(World.tilesets) == 0 {
imgPath := filepath.Join("./image/tileset/", tileset.Image.Source)
f, err := asset.FS.Open(filepath.FromSlash(imgPath))
if err != nil {
panic(err)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
panic(err)
}
World.tilesets = append(World.tilesets, ebiten.NewImageFromImage(img))
if len(World.tilesets) != 0 {
return nil // Already loaded.
}
tileset := m.Tilesets[0]
imgPath := filepath.Join("./image/tileset/", tileset.Image.Source)
f, err := asset.FS.Open(filepath.FromSlash(imgPath))
if err != nil {
panic(err)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
panic(err)
}
World.tilesets = append(World.tilesets, ebiten.NewImageFromImage(img))
// Load tiles.
for i := uint32(0); i < uint32(tileset.TileCount); i++ {
@ -168,6 +177,22 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
World.TileImages[i+tileset.FirstGID] = World.tilesets[0].SubImage(rect).(*ebiten.Image)
}
World.TileImagesFirstGID = tileset.FirstGID
return nil
}
func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Structure, error) {
World.Level.ClearHoverSprites()
m, err := LoadMap(structureType)
if err != nil {
return nil, err
}
if placeX-m.Width < 0 || placeY-m.Height < 0 || placeX > 256 || placeY > 256 {
return nil, errors.New("invalid location: building does not fit")
}
createTileEntity := func(t *tiled.LayerTile, x float64, y float64) gohan.Entity {
mapTile := ECS.NewEntity()
ECS.AddComponent(mapTile, &component.PositionComponent{
@ -195,6 +220,32 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
// TODO Add entity
if hover {
World.HoverValid = true
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
tx, ty := (x+placeX)-m.Width, (y+placeY)-m.Height
if World.Level.Tiles[1][tx][ty].Sprite != nil {
World.HoverValid = false
break // TODO
}
}
}
}
for y := 0; y < m.Height; y++ {
for x := 0; x < m.Width; x++ {
tx, ty := (x+placeX)-m.Width, (y+placeY)-m.Height
if hover {
World.Level.Tiles[0][tx][ty].HoverSprite = World.TileImages[World.TileImagesFirstGID]
} else {
World.Level.Tiles[0][tx][ty].Sprite = World.TileImages[World.TileImagesFirstGID]
World.Level.Tiles[0][tx][ty].EnvironmentSprite = nil
World.Level.Tiles[1][tx][ty].EnvironmentSprite = nil
}
}
}
var t *tiled.LayerTile
for i, layer := range m.Layers {
for y := 0; y < m.Height; y++ {
@ -209,18 +260,17 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
continue
}
for i > len(World.Level.Tiles)-1 {
layerNum := i + 1
for layerNum > len(World.Level.Tiles)-1 {
World.Level.AddLayer()
}
tx, ty := (x+placeX)-m.Width, (y+placeY)-m.Height
if World.Level.Tiles[i][tx][ty] == nil {
World.Level.Tiles[i][tx][ty] = &Tile{}
}
if hover {
World.Level.Tiles[i][tx][ty].HoverSprite = World.TileImages[t.Tileset.FirstGID+t.ID]
World.Level.Tiles[layerNum][tx][ty].HoverSprite = World.TileImages[t.Tileset.FirstGID+t.ID]
} else {
World.Level.Tiles[i][tx][ty].Sprite = World.TileImages[t.Tileset.FirstGID+t.ID]
World.Level.Tiles[layerNum][tx][ty].Sprite = World.TileImages[t.Tileset.FirstGID+t.ID]
}
// TODO handle flipping
@ -371,6 +421,9 @@ func IsoToScreen(x, y float64) (float64, float64) {
}
func ScreenToIso(x, y int) (float64, float64) {
// Offset to first above ground layer.
y += int(float64(32) * World.CamScale)
cx, cy := float64(World.ScreenW/2), float64(World.ScreenH/2)
return ((float64(x) - cx) / World.CamScale) + World.CamX, ((float64(y) - cy) / World.CamScale) + World.CamY
}