From 5b139941ee9c66c0dd317ee0175975a90bb3cd2f Mon Sep 17 00:00:00 2001 From: Jake B Date: Sun, 9 Sep 2018 18:48:08 +1000 Subject: [PATCH 01/27] Update gml-go from current in-progress game --- .travis.yml | 4 +- gml/camera.go | 116 +- gml/collision.go | 98 +- gml/collision_test.go | 20 +- gml/draw.go | 5 +- gml/draw_headless.go | 13 +- gml/draw_nonheadless.go | 91 +- gml/font_manager_nonheadless.go | 5 +- gml/geom.go | 9 + gml/init_js_test.go | 18 + gml/init_test.go | 8 + gml/instance.go | 115 +- gml/instance_iterator.go | 43 + gml/instance_test.go | 15 +- gml/internal/file/file_js.go | 4 +- gml/internal/file/user_debug.go | 23 +- gml/internal/file/user_nondebug.go | 10 +- gml/internal/geom/deprecated.go | 37 + gml/internal/geom/rect.go | 75 + gml/internal/geom/size.go | 13 + gml/internal/geom/vec.go | 16 + gml/internal/math/math.go | 50 - gml/internal/object/instance.go | 19 + gml/internal/object/object.go | 17 +- gml/internal/object/object_manager.go | 18 +- gml/internal/reditor/reditor.go | 14 + gml/internal/reditor/reditor_debug.go | 12 + gml/internal/reditor/reditor_nondebug.go | 7 + gml/internal/room/room.go | 13 + gml/internal/room/room.pb.go | 253 +- gml/internal/room/room.proto | 16 +- gml/internal/room/room_config.pb.go | 334 +++ gml/internal/room/room_config.proto | 7 + gml/internal/room/room_layer.go | 5 + gml/internal/room/room_layer_background.pb.go | 419 +++ gml/internal/room/room_layer_background.proto | 11 + gml/internal/room/room_layer_config.pb.go | 452 ++++ gml/internal/room/room_layer_config.proto | 14 + gml/internal/room/room_layer_instance.pb.go | 417 +++ gml/internal/room/room_layer_instance.proto | 13 + gml/internal/room/room_layer_kind.pb.go | 59 + gml/internal/room/room_layer_kind.proto | 9 + gml/internal/room/room_layer_sprite.pb.go | 415 +++ gml/internal/room/room_layer_sprite.proto | 13 + gml/internal/room/room_manager_nonjs.go | 483 +++- gml/internal/room/room_object.pb.go | 34 +- gml/internal/room/room_object.proto | 2 +- gml/internal/room/room_sprite_object.pb.go | 479 ++++ gml/internal/room/room_sprite_object.proto | 11 + gml/internal/space/space.go | 16 + .../space.go => space/space_bucket.go} | 31 +- gml/internal/space/space_object.go | 18 + gml/internal/sprite/sprite.go | 8 +- gml/internal/sprite/sprite_asset.go | 8 +- .../sprite/sprite_frame_nonheadless.go | 6 +- gml/internal/sprite/sprite_manager_debug.go | 4 +- gml/internal/sprite/sprite_state.go | 10 +- gml/internal/timegml/now_js.go | 14 + gml/internal/timegml/now_notjs.go | 11 + gml/internal/user/user.go | 17 + gml/keyboard_vk.go | 1 + gml/keyboard_vk_nonheadless.go | 21 +- gml/main.go | 25 +- gml/main_headless.go | 17 +- gml/main_nonheadless.go | 9 +- gml/math.go | 13 - gml/mouse_buttons_headless.go | 2 +- gml/mouse_buttons_nonheadless.go | 2 +- gml/mouse_headless.go | 12 +- gml/mouse_nonheadless.go | 60 +- gml/{main_test.go => object_test.go} | 10 +- gml/room_editor.go | 21 +- gml/room_editor_debug.go | 2335 +++++++++++++++-- gml/room_instance.go | 78 +- gml/room_instance_layer.go | 23 + gml/room_instance_layer_background.go | 43 + gml/room_instance_layer_instance.go | 21 + gml/room_instance_layer_sprite.go | 36 + gml/state.go | 139 +- 79 files changed, 6416 insertions(+), 929 deletions(-) create mode 100644 gml/geom.go create mode 100644 gml/init_js_test.go create mode 100644 gml/init_test.go create mode 100644 gml/instance_iterator.go create mode 100644 gml/internal/geom/deprecated.go create mode 100644 gml/internal/geom/rect.go create mode 100644 gml/internal/geom/size.go create mode 100644 gml/internal/geom/vec.go delete mode 100644 gml/internal/math/math.go create mode 100644 gml/internal/object/instance.go create mode 100644 gml/internal/reditor/reditor.go create mode 100644 gml/internal/reditor/reditor_debug.go create mode 100644 gml/internal/reditor/reditor_nondebug.go create mode 100644 gml/internal/room/room.go create mode 100644 gml/internal/room/room_config.pb.go create mode 100644 gml/internal/room/room_config.proto create mode 100644 gml/internal/room/room_layer.go create mode 100644 gml/internal/room/room_layer_background.pb.go create mode 100644 gml/internal/room/room_layer_background.proto create mode 100644 gml/internal/room/room_layer_config.pb.go create mode 100644 gml/internal/room/room_layer_config.proto create mode 100644 gml/internal/room/room_layer_instance.pb.go create mode 100644 gml/internal/room/room_layer_instance.proto create mode 100644 gml/internal/room/room_layer_kind.pb.go create mode 100644 gml/internal/room/room_layer_kind.proto create mode 100644 gml/internal/room/room_layer_sprite.pb.go create mode 100644 gml/internal/room/room_layer_sprite.proto create mode 100644 gml/internal/room/room_sprite_object.pb.go create mode 100644 gml/internal/room/room_sprite_object.proto create mode 100644 gml/internal/space/space.go rename gml/internal/{object/space.go => space/space_bucket.go} (82%) create mode 100644 gml/internal/space/space_object.go create mode 100644 gml/internal/timegml/now_js.go create mode 100644 gml/internal/timegml/now_notjs.go create mode 100644 gml/internal/user/user.go delete mode 100644 gml/math.go rename gml/{main_test.go => object_test.go} (82%) create mode 100644 gml/room_instance_layer.go create mode 100644 gml/room_instance_layer_background.go create mode 100644 gml/room_instance_layer_instance.go create mode 100644 gml/room_instance_layer_sprite.go diff --git a/.travis.yml b/.travis.yml index 1025ae5..d019e8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,5 +32,5 @@ script: # # No tests exist yet # - #- go test -v ./... - - go vet -v ./... + - go test -v ./... + - go vet -v -composites=false ./... diff --git a/gml/camera.go b/gml/camera.go index 7775182..4728216 100644 --- a/gml/camera.go +++ b/gml/camera.go @@ -1,44 +1,75 @@ package gml import ( + "math" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" ) var ( - __currentCamera *camera - cameraList [8]camera + gCameraManager *cameraManager = newCameraState() ) +type cameraManager struct { + cameras [8]camera + current *camera +} + type camera struct { enabled bool follow object.ObjectType - Vec - size Vec + geom.Vec + size geom.Vec + scale geom.Vec +} + +func newCameraState() *cameraManager { + manager := new(cameraManager) + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + view.scale.X = 1 + view.scale.Y = 1 + } + return manager +} + +func (view *camera) Size() geom.Vec { + return view.size +} + +func (view *camera) Scale() geom.Vec { + return view.scale } func CameraSetEnabled(index int) { - view := &cameraList[index] + view := &gCameraManager.cameras[index] view.enabled = true } func cameraGetActive() *camera { - return __currentCamera + return gCameraManager.current } func cameraSetActive(index int) { - __currentCamera = &cameraList[index] + gCameraManager.current = &gCameraManager.cameras[index] } func cameraClearActive() { - __currentCamera = nil + gCameraManager.current = nil +} + +func CameraGetViewPos(index int) geom.Vec { + view := &gCameraManager.cameras[index] + return view.Vec } -func CameraSetViewPos(index int, pos Vec) { - view := &cameraList[index] +func CameraSetViewPos(index int, pos geom.Vec) { + view := &gCameraManager.cameras[index] view.Vec = pos if inst := view.follow; inst != nil { - roomInst := RoomGetInstance(inst.BaseObject().RoomInstanceIndex()) + roomInst := RoomGetInstance(object.RoomInstanceIndex(inst.BaseObject())) if roomInst != nil { room := roomInst.room left := float64(room.Left) @@ -60,46 +91,61 @@ func CameraSetViewPos(index int, pos Vec) { if view.Y+view.size.Y > bottom { view.Y = bottom - view.size.Y } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) } } } -func CameraSetViewSize(index int, size Vec) { - view := &cameraList[index] +func CameraSetViewSize(index int, size geom.Vec) { + view := &gCameraManager.cameras[index] view.size = size } func CameraSetViewTarget(index int, inst object.ObjectType) { - view := &cameraList[index] + view := &gCameraManager.cameras[index] view.follow = inst } +func cameraInstanceDestroy(inst object.ObjectType) { + manager := gCameraManager + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + if view.follow == inst { + view.follow = nil + } + } +} + func (view *camera) update() { if view.follow != nil { //cam := cameraGetActive() inst := view.follow.BaseObject() + if inst != nil { + roomInst := RoomGetInstance(object.RoomInstanceIndex(inst)) + if roomInst != nil { + room := roomInst.room + left := float64(room.Left) + right := float64(room.Right) + top := float64(room.Top) + bottom := float64(room.Bottom) - roomInst := RoomGetInstance(inst.RoomInstanceIndex()) - if roomInst != nil { - room := roomInst.room - left := float64(room.Left) - right := float64(room.Right) - top := float64(room.Top) - bottom := float64(room.Bottom) - - view.X = inst.X - (view.size.X / 2) - view.Y = inst.Y - (view.size.Y / 2) - if view.X < left { - view.X = left - } - if view.X+view.size.X > right { - view.X = right - view.size.X - } - if view.Y < top { - view.Y = top - } - if view.Y+view.size.Y > bottom { - view.Y = bottom - view.size.Y + view.X = inst.X - (view.size.X / 2) + view.Y = inst.Y - (view.size.Y / 2) + if view.X < left { + view.X = left + } + if view.X+view.size.X > right { + view.X = right - view.size.X + } + if view.Y < top { + view.Y = top + } + if view.Y+view.size.Y > bottom { + view.Y = bottom - view.size.Y + } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) } } } diff --git a/gml/collision.go b/gml/collision.go index 2da5102..e89d81c 100644 --- a/gml/collision.go +++ b/gml/collision.go @@ -1,5 +1,10 @@ package gml +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + const ( DEBUG_COLLISION = false ) @@ -8,54 +13,71 @@ type collisionObject interface { BaseObject() *Object } -func PlaceFree(instType collisionObject, position Vec) bool { +func PlaceFree(instType collisionObject, position geom.Vec) bool { baseObj := instType.BaseObject() - - var instanceManager *instanceManager - { - inst := baseObj - - if room := RoomGetInstance(inst.RoomInstanceIndex()); room == nil { - instanceManager = gState.globalInstances - } else { - instanceManager = &room.instanceManager - } + room := RoomGetInstance(object.RoomInstanceIndex(baseObj)) + if room == nil { + panic("RoomInstance this object belongs to has been destroyed") } + // Keep pointer to space object to avoid comparing collision + // against self inst := baseObj.Space - r1Left := position.X - r1Right := r1Left + float64(inst.Size.X) - r1Top := position.Y - r1Bottom := r1Top + float64(inst.Size.Y) + + // Create collision rect at position provided in function + r1 := inst.Rect + r1.Vec = position + r1.Size = inst.Size //var debugString string hasCollision := false - for _, bucket := range instanceManager.spaces.Buckets() { - for i := 0; i < bucket.Len(); i++ { - other := bucket.Get(i) - r2Left := other.X - r2Right := r2Left + float64(other.Size.X) - r2Top := other.Y - r2Bottom := r2Top + float64(other.Size.Y) - - // NOTE(Jake): 2018-07-08 - // - // For JavaScript performance, we get a 1.2x speedup if we - // handle as much logic in one if-statement as possible. - // - // For native binaries, it doesn't seem to change performance noticeably - // at all if I add "if inst == other || !instanceManager.spaces.IsUsed(i) { continue; }" - // - // ("gjbt" and Chrome 67 Windows were for benchmarking) - // - if r1Left < r2Right && r1Right > r2Left && - r1Top < r2Bottom && r1Bottom > r2Top && - inst != other && - bucket.IsUsed(i) { - hasCollision = true + for i := 0; i < len(room.instanceLayers); i++ { + spaces := &room.instanceLayers[i].manager.spaces + for _, bucket := range spaces.Buckets() { + for i := 0; i < bucket.Len(); i++ { + other := bucket.Get(i) + // NOTE(Jake): 2018-07-08 + // + // For JavaScript performance, we get a 1.2x speedup if we + // handle as much logic in one if-statement as possible. + // + // For native binaries, it doesn't seem to change performance noticeably + // at all if I add "if inst == other || !instanceManager.spaces.IsUsed(i) { continue; }" + // + // ("gjbt" and Chrome 67 Windows were for benchmarking) + // + // NOTE(Jake): 2018-08-11 + // + // Heavily refactored this since the above benchmark. But who cares really. I'll probably + // need to re-do this collision engine so it supports spatial hashing. + // + if other.Solid() && + r1.CollisionRectangle(other.Rect) && + inst != other && + bucket.IsUsed(i) { + hasCollision = true + } } } } + for i := 0; i < len(room.spriteLayers); i++ { + layer := &room.spriteLayers[i] + if !layer.hasCollision { + continue + } + spaces := layer.spaces + for _, bucket := range spaces.Buckets() { + for i := 0; i < bucket.Len(); i++ { + other := bucket.Get(i) + if r1.CollisionRectangle(other.Rect) && + inst != other && + bucket.IsUsed(i) { + hasCollision = true + } + } + } + } + /*if DEBUG_COLLISION && len(debugString) > 0 { // Get calling function name / line diff --git a/gml/collision_test.go b/gml/collision_test.go index 7a09d0d..fa27faf 100644 --- a/gml/collision_test.go +++ b/gml/collision_test.go @@ -2,6 +2,8 @@ package gml import ( "testing" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) // NOTE(Jake): 2018-07-08 @@ -35,13 +37,13 @@ func BenchmarkPlaceFree250(b *testing.B) { // everything is considered solid. // for i := 0; i < 250; i++ { - roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer) + InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer) } - playerInstance := roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) + playerInstance := InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) b.ResetTimer() for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, V(32, 32)) + PlaceFree(playerInstance, geom.Vec{32, 32}) } } @@ -61,13 +63,13 @@ func BenchmarkPlaceFree500(b *testing.B) { // everything is considered solid. // for i := 0; i < 500; i++ { - roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer) + InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer) } - playerInstance := roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) + playerInstance := InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) b.ResetTimer() for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, V(32, 32)) + PlaceFree(playerInstance, geom.Vec{32, 32}) } } @@ -87,11 +89,11 @@ func BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities(b *testing.B) { // everything is considered solid. // for i := 0; i < 250; i++ { - roomInstance.InstanceCreate(V(float64(i*32), 0), ObjDummyPlayer) + InstanceCreateRoom(geom.Vec{float64(i * 32.0), 0}, roomInstance, ObjDummyPlayer) } movingEntityInstances := make([]*DummyPlayer, 1024) for i := 0; i < len(movingEntityInstances); i++ { - movingEntityInstances[i] = roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) + movingEntityInstances[i] = InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) } b.ResetTimer() @@ -102,7 +104,7 @@ func BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities(b *testing.B) { // Assume each entity would call PlaceFree() at least 10 times each. // for i := 0; i < 10; i++ { - PlaceFree(movingEntityInstance, V(32, 32)) + PlaceFree(movingEntityInstance, geom.Vec{32, 32}) } } } diff --git a/gml/draw.go b/gml/draw.go index 08d68ce..878ac4c 100644 --- a/gml/draw.go +++ b/gml/draw.go @@ -1,9 +1,10 @@ package gml import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -func DrawSelf(state *sprite.SpriteState, position Vec) { - DrawSpriteExt(state.Sprite(), state.ImageIndex(), position, state.ImageScale) +func DrawSelf(state *sprite.SpriteState, position geom.Vec) { + DrawSpriteScaled(state.Sprite(), state.ImageIndex(), position, state.ImageScale) } diff --git a/gml/draw_headless.go b/gml/draw_headless.go index 7171fe4..bc3b533 100644 --- a/gml/draw_headless.go +++ b/gml/draw_headless.go @@ -8,13 +8,22 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) +func DrawSetGUI(guiMode bool) { +} + func DrawSprite(spr *sprite.Sprite, subimage float64, position Vec) { } -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { +func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { +} + +func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec, alpha float64) { +} + +func DrawRectangle(pos Vec, size Vec, col color.Color) { } -func DrawRectangle(pos Vec, size Vec, col color.RGBA) { +func DrawRectangleBorder(position Vec, size Vec, color color.Color, borderSize float64, borderColor color.Color) { } func DrawText(position Vec, message string) { diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index 7a6ebe8..7f663a6 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -4,53 +4,106 @@ package gml import ( "image/color" + "math" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/text" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -func DrawSprite(spr *sprite.Sprite, subimage float64, position Vec) { - screen := gScreen - { - camPos := cameraGetActive().Vec - position.X -= camPos.X - position.Y -= camPos.Y - } +var ( + isDrawGuiMode = false +) - frame := sprite.GetRawFrame(spr, subimage) +func DrawSetGUI(guiMode bool) { + isDrawGuiMode = guiMode +} + +func DrawSprite(spr *sprite.Sprite, subimage float64, position geom.Vec) { + screen := gScreen + position = maybeApplyOffsetByCamera(position) + frame := sprite.GetRawFrame(spr, int(math.Floor(subimage))) op := ebiten.DrawImageOptions{} op.GeoM.Translate(position.X, position.Y) screen.DrawImage(frame, &op) } +func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec) { + DrawSpriteExt(spr, subimage, position, scale, 1.0) +} + // draw_sprite_ext( sprite, subimg, x, y, xscale, yscale, rot, colour, alpha ); -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { +func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec, alpha float64) { screen := gScreen - { - camPos := cameraGetActive().Vec - position.X -= camPos.X - position.Y -= camPos.Y - } + position = maybeApplyOffsetByCamera(position) + // NOTE(Jake): 2018-07-09 + // + // This doesn't work. A cleaner solution might be to + // render everything to a seperate image if possible then + // scale that and render. + // + // Since this is only really needed for the map editor, I dont + // have a problem with it. + // + //scale.X *= view.Scale().X + //scale.Y *= view.Scale().Y - frame := sprite.GetRawFrame(spr, subimage) + frame := sprite.GetRawFrame(spr, int(math.Floor(subimage))) op := ebiten.DrawImageOptions{} - // op.GeoM.Scale(width/float64(ew), height/float64(eh)) op.GeoM.Scale(scale.X, scale.Y) op.GeoM.Translate(position.X, position.Y) + + op.ColorM.Scale(1.0, 1.0, 1.0, alpha) + //op.Colorgeom.RotateHue(float64(360)) + screen.DrawImage(frame, &op) } -func DrawRectangle(pos Vec, size Vec, col color.RGBA) { +func DrawRectangle(position geom.Vec, size geom.Vec, col color.Color) { screen := gScreen - ebitenutil.DrawRect(screen, pos.X, pos.Y, size.X, size.Y, col) + position = maybeApplyOffsetByCamera(position) + + ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, col) } -func DrawText(position Vec, message string) { +func DrawRectangleBorder(position geom.Vec, size geom.Vec, color color.Color, borderSize float64, borderColor color.Color) { + screen := gScreen + position = maybeApplyOffsetByCamera(position) + ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, borderColor) + position.X += borderSize + position.Y += borderSize + size.X -= borderSize * 2 + size.Y -= borderSize * 2 + ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, color) +} + +func DrawText(position geom.Vec, message string) { screen := gScreen if !g_fontManager.hasFontSet() { panic("Must call DrawSetFont() before calling DrawText.") } + position = maybeApplyOffsetByCamera(position) text.Draw(screen, message, g_fontManager.currentFont.font, int(position.X), int(position.Y), color.White) } + +func DrawTextColor(position geom.Vec, message string, col color.Color) { + screen := gScreen + if !g_fontManager.hasFontSet() { + panic("Must call DrawSetFont() before calling DrawText.") + } + position = maybeApplyOffsetByCamera(position) + text.Draw(screen, message, g_fontManager.currentFont.font, int(position.X), int(position.Y), col) +} + +func maybeApplyOffsetByCamera(position geom.Vec) geom.Vec { + if !isDrawGuiMode { + if view := cameraGetActive(); view != nil { + position.X -= view.X + position.Y -= view.Y + } + } + return position +} diff --git a/gml/font_manager_nonheadless.go b/gml/font_manager_nonheadless.go index 3cecfe3..7e06fd6 100644 --- a/gml/font_manager_nonheadless.go +++ b/gml/font_manager_nonheadless.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "github.com/golang/freetype/truetype" - "github.com/hajimehoshi/ebiten/ebitenutil" + "github.com/silbinarywolf/gml-go/gml/internal/file" "golang.org/x/image/font" ) @@ -37,7 +37,7 @@ func LoadFont(name string, settings FontSettings) *Font { } path := AssetsDirectory() + "/fonts/" + name + ".ttf" - fileData, err := ebitenutil.OpenFile(path) + fileData, err := file.OpenFile(path) if err != nil { panic(errors.New("Unable to find font: " + path + ". Error: " + err.Error())) } @@ -46,6 +46,7 @@ func LoadFont(name string, settings FontSettings) *Font { if err != nil { panic(errors.New("Unable to read font file into bytes: " + path)) } + //fmt.Printf("%v\n", b) tt, err := truetype.Parse(b) if err != nil { panic(errors.New("Unable to parse true type font file: " + path + ", err: " + err.Error())) diff --git a/gml/geom.go b/gml/geom.go new file mode 100644 index 0000000..d6135f2 --- /dev/null +++ b/gml/geom.go @@ -0,0 +1,9 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +type Vec = geom.Vec + +type Size = geom.Size diff --git a/gml/init_js_test.go b/gml/init_js_test.go new file mode 100644 index 0000000..8504f17 --- /dev/null +++ b/gml/init_js_test.go @@ -0,0 +1,18 @@ +// +build js + +package gml + +import ( + "fmt" + "testing" + + "github.com/gopherjs/gopherjs/js" +) + +// NOTE(Jake): 2018-09-09 +// Can't find the documentation but this was needed for GopherJS +func TestMain(m *testing.M) { + i := m.Run() + + js.Global.Call("eval", fmt.Sprintf("window.$GopherJSTestResult = %v", i)) +} diff --git a/gml/init_test.go b/gml/init_test.go new file mode 100644 index 0000000..1707165 --- /dev/null +++ b/gml/init_test.go @@ -0,0 +1,8 @@ +package gml + +func init() { + // Setup + ObjectInitTypes([]ObjectType{ + ObjDummyPlayer: new(DummyPlayer), + }) +} diff --git a/gml/instance.go b/gml/instance.go index c26d82a..4e4487e 100644 --- a/gml/instance.go +++ b/gml/instance.go @@ -1,10 +1,21 @@ package gml -import "github.com/silbinarywolf/gml-go/gml/internal/object" +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/space" +) +/*type InstanceIndex struct { + layerIndex int + roomInstanceIndex int + instanceIndex int + obj object.ObjectType +} +*/ type instanceManagerResettableData struct { instances []object.ObjectType - spaces object.SpaceBucketArray + spaces space.SpaceBucketArray } func (manager *instanceManager) reset() { @@ -21,41 +32,86 @@ func newInstanceManager() *instanceManager { return manager } -func (manager *instanceManager) InstanceCreate(position Vec, objectIndex object.ObjectIndex, roomInstanceIndex int) object.ObjectType { - // Create and add to entity list - index := len(manager.instances) +func instanceCreateLayer(position geom.Vec, layer *RoomInstanceLayerInstance, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { + return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) + /*result := InstanceIndex{ + layerIndex: layer.index, + roomInstanceIndex: roomInst.Index(), + instanceIndex: len(layer.manager.instances), + } + result.obj = layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) + return result.obj*/ +} +func InstanceCreateRoom(position geom.Vec, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { + // NOTE(Jake): 2018-07-22 // - var inst object.ObjectType - { - spaceIndex := manager.spaces.GetNew() - space := manager.spaces.Get(spaceIndex) - inst = object.NewRawInstance(objectIndex, index, roomInstanceIndex, space, spaceIndex) - manager.instances = append(manager.instances, inst) - } + // For now instances default to the last instance layer + // + layerIndex := len(roomInst.instanceLayers) - 1 + //fmt.Printf("InstanceCreateRoom: Create on layer %d\n", layerIndex) + layer := &roomInst.instanceLayers[layerIndex] + return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) +} - // Attach +func InstanceExists(inst object.ObjectType) bool { baseObj := inst.BaseObject() + if baseObj == nil { + return false + } + roomInst := RoomGetInstance(object.RoomInstanceIndex(baseObj)) + // todo(Jake): 2018-08-20 + // + // Check to see if current entity is destroyed + // + return roomInst != nil +} + +func (manager *instanceManager) InstanceCreate(position geom.Vec, objectIndex object.ObjectIndex, roomInstanceIndex, layerIndex int) object.ObjectType { + // Create and add to entity list + index := len(manager.instances) + + // Get Pos/Size part of instance (SpaceObject) + spaceIndex := manager.spaces.GetNew() + space := manager.spaces.Get(spaceIndex) + + // Get instance + inst := object.NewRawInstance(objectIndex, index, roomInstanceIndex, layerIndex, space, spaceIndex) + manager.instances = append(manager.instances, inst) // Init and Set position inst.Create() - baseObj.Vec = position + inst.BaseObject().Vec = position return inst } -func (manager *instanceManager) InstanceDestroy(inst object.ObjectType) { - be := inst.BaseObject() +func InstanceDestroy(inst object.ObjectType) { + // Destroy this + inst.Destroy() + cameraInstanceDestroy(inst) - // Free up space slot - be.Space = nil - if be.SpaceIndex() > -1 { - manager.spaces.Remove(be.SpaceIndex()) + baseObj := inst.BaseObject() + + // Get slots + roomInstanceIndex := object.RoomInstanceIndex(baseObj) + layerIndex := object.LayerInstanceIndex(baseObj) + index := object.InstanceIndex(baseObj) + + // Get manager + roomInst := &gState.roomInstances[roomInstanceIndex] + layerInst := &roomInst.instanceLayers[layerIndex] + manager := &layerInst.manager + + // Free up SpaceObject slot + spaceIndex := baseObj.SpaceIndex() + baseObj.Space = nil + if spaceIndex > -1 { + manager.spaces.Remove(spaceIndex) } // Unordered delete - i := be.Index() lastEntry := manager.instances[len(manager.instances)-1] - manager.instances[i] = lastEntry + manager.instances[index] = lastEntry manager.instances = manager.instances[:len(manager.instances)-1] } @@ -79,19 +135,10 @@ func (manager *instanceManager) update(animationUpdate bool) { } func (manager *instanceManager) draw() { - for i := 0; i < len(cameraList); i++ { - cam := &cameraList[i] - if !cam.enabled { + for _, inst := range manager.instances { + if inst == nil { continue } - cam.update() - cameraSetActive(i) - for _, inst := range manager.instances { - if inst == nil { - continue - } - inst.Draw() - } + inst.Draw() } - cameraClearActive() } diff --git a/gml/instance_iterator.go b/gml/instance_iterator.go new file mode 100644 index 0000000..29257d8 --- /dev/null +++ b/gml/instance_iterator.go @@ -0,0 +1,43 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +type instanceIteratorState struct { + roomInstanceIndex int + layerIndex int + instanceIndex int +} + +func InstancesIterator(inst object.ObjectType) instanceIteratorState { + roomInstanceIndex := object.RoomInstanceIndex(inst.BaseObject()) + return instanceIteratorState{ + instanceIndex: -1, + roomInstanceIndex: roomInstanceIndex, + } +} + +func (iterator *instanceIteratorState) Next() bool { + roomInst := &gState.roomInstances[iterator.roomInstanceIndex] + if iterator.layerIndex >= len(roomInst.instanceLayers) { + return false + } + layer := &roomInst.instanceLayers[iterator.layerIndex] + iterator.instanceIndex++ + for { + if iterator.instanceIndex < len(layer.manager.instances) { + return true + } + iterator.instanceIndex = 0 + iterator.layerIndex++ + if iterator.layerIndex < len(roomInst.instanceLayers) { + continue + } + return false + } +} + +func (iterator *instanceIteratorState) Value() object.ObjectType { + return gState.roomInstances[iterator.roomInstanceIndex].instanceLayers[iterator.layerIndex].manager.instances[iterator.instanceIndex] +} diff --git a/gml/instance_test.go b/gml/instance_test.go index d7dcba6..12506c1 100644 --- a/gml/instance_test.go +++ b/gml/instance_test.go @@ -1,12 +1,12 @@ package gml -import ( - "testing" - - "github.com/silbinarywolf/gml-go/gml/internal/object" -) - -func TestInstanceDestroyStability(t *testing.T) { +// NOTE(Jake): 2018-09-09 +// +// This test sucks and is out of date. +// the server still crashes, probably during a for-loop +// of every instance +// +/*func TestInstanceDestroyStability(t *testing.T) { roomInstance := RoomInstanceEmptyCreate() roomInstances := make([]object.ObjectType, 1024) for i := 0; i < len(roomInstances); i++ { @@ -48,3 +48,4 @@ func TestInstanceDestroyStability(t *testing.T) { } } } +*/ diff --git a/gml/internal/file/file_js.go b/gml/internal/file/file_js.go index 4d38b5b..1e501c3 100644 --- a/gml/internal/file/file_js.go +++ b/gml/internal/file/file_js.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/gopherjs/gopherjs/js" + "github.com/gopherjs/gopherwasm/js" "github.com/hajimehoshi/ebiten/ebitenutil" ) @@ -20,7 +20,7 @@ func OpenFile(path string) (readSeekCloser, error) { func calculateProgramDir() string { // Setup program dir - location := js.Global.Get("location") + location := js.Global().Get("location") result := location.Get("href").String() result = filepath.Dir(result) result = strings.TrimPrefix(result, "file:/") diff --git a/gml/internal/file/user_debug.go b/gml/internal/file/user_debug.go index e112f8c..c23b859 100644 --- a/gml/internal/file/user_debug.go +++ b/gml/internal/file/user_debug.go @@ -2,21 +2,19 @@ package file -import ( - "os/user" - "path" - "strings" -) - -var ( +/*var ( debugUsername string = "▲not-set▲" -) +)*/ -func DebugUsernameFileSafe() string { - return debugUsername -} +// NOTE(Jake): 2018-07-15 +// +// Deprecated in favour of XID UUID +// +//func DebugUsernameFileSafe() string { +// return debugUsername +//} -func init() { +/*func init() { // Setup file-safe escaped username user, _ := user.Current() username := user.Username @@ -26,3 +24,4 @@ func init() { username = strings.Replace(username, "_", "-", -1) debugUsername = username } +*/ diff --git a/gml/internal/file/user_nondebug.go b/gml/internal/file/user_nondebug.go index 13dd67b..ed2fdc9 100644 --- a/gml/internal/file/user_nondebug.go +++ b/gml/internal/file/user_nondebug.go @@ -2,6 +2,10 @@ package file -func DebugUsernameFileSafe() string { - return "" -} +// NOTE(Jake): 2018-07-15 +// +// Deprecated in favour of XID UUID +// +//func DebugUsernameFileSafe() string { +// return "" +//} diff --git a/gml/internal/geom/deprecated.go b/gml/internal/geom/deprecated.go new file mode 100644 index 0000000..ab5bea0 --- /dev/null +++ b/gml/internal/geom/deprecated.go @@ -0,0 +1,37 @@ +package geom + +// +// NOTE(Jake): 2018-08-11 +// +// This code is only used by the room_editor_debug.go. +// Need to look at removing it in favour of making rect.go nicer. +// + +type RoomEditorDebugRect struct { + LeftTop Vec + RightBottom Vec +} + +func (rect *RoomEditorDebugRect) Left() float64 { return rect.LeftTop.X } +func (rect *RoomEditorDebugRect) Right() float64 { return rect.RightBottom.X } +func (rect *RoomEditorDebugRect) Top() float64 { return rect.LeftTop.Y } +func (rect *RoomEditorDebugRect) Bottom() float64 { return rect.RightBottom.Y } + +func R(a Vec, b Vec) RoomEditorDebugRect { + rect := RoomEditorDebugRect{} + if a.X < b.X { + rect.LeftTop.X = a.X + rect.RightBottom.X = b.X + } else { + rect.LeftTop.X = b.X + rect.RightBottom.X = a.X + } + if a.Y < b.Y { + rect.LeftTop.Y = a.Y + rect.RightBottom.Y = b.Y + } else { + rect.LeftTop.Y = b.Y + rect.RightBottom.Y = a.Y + } + return rect +} diff --git a/gml/internal/geom/rect.go b/gml/internal/geom/rect.go new file mode 100644 index 0000000..3335d0e --- /dev/null +++ b/gml/internal/geom/rect.go @@ -0,0 +1,75 @@ +package geom + +type Rect struct { + Vec // Position (contains X,Y) + Size Size // Size (X,Y) +} + +func (rect *Rect) Pos() Vec { + return rect.Vec +} + +func (rect *Rect) Left() float64 { return rect.Vec.X } +func (rect *Rect) Right() float64 { return rect.Vec.X + float64(rect.Size.X) } +func (rect *Rect) Top() float64 { return rect.Vec.Y } +func (rect *Rect) Bottom() float64 { return rect.Vec.Y + float64(rect.Size.Y) } + +func (rect *Rect) DistancePoint(point Vec) float64 { + yDist := 0.0 + if point.Y < rect.Top() { + yDist = rect.Top() - point.Y + } else if point.Y > rect.Bottom() { + yDist = point.Y - rect.Bottom() + } + xDist := 0.0 + if point.X < rect.Left() { + xDist = rect.Left() - point.X + } else if point.X > rect.Right() { + xDist = point.X - rect.Right() + } + return Vec{xDist, yDist}.Norm() +} + +// https://stackoverflow.com/questions/4978323/how-to-calculate-distance-between-two-rectangles-context-a-game-in-lua +func (rect *Rect) DistanceRect(otherRect Rect) float64 { + left := otherRect.Right() < rect.Left() + right := rect.Right() < otherRect.Left() + bottom := otherRect.Bottom() < rect.Top() + top := rect.Bottom() < otherRect.Top() + if top && left { + // dist((x1, y1b), (x2b, y2)) + return Vec{rect.Left(), rect.Bottom()}.DistancePoint(Vec{otherRect.Right(), otherRect.Top()}) + } else if left && bottom { + // dist((x1, y1), (x2b, y2b)) + return Vec{rect.Left(), rect.Top()}.DistancePoint(Vec{otherRect.Right(), otherRect.Bottom()}) + } else if bottom && right { + // dist((x1b, y1), (x2, y2b)) + return Vec{rect.Right(), rect.Top()}.DistancePoint(Vec{otherRect.Left(), otherRect.Bottom()}) + } else if right && top { + // dist((x1b, y1b), (x2, y2)) + return Vec{rect.Right(), rect.Bottom()}.DistancePoint(Vec{otherRect.Left(), otherRect.Bottom()}) + } else if left { + // x1 - x2b + return rect.Left() - otherRect.Right() + } else if right { + // x2 - x1b + return otherRect.Left() - rect.Right() + } else if bottom { + // y1 - y2b + return rect.Top() - otherRect.Bottom() + } else if top { + // y2 - y1b + return otherRect.Top() - rect.Bottom() + } + return 0 +} + +func (rect *Rect) CollisionPoint(pos Vec) bool { + return pos.X > rect.Left() && pos.X < rect.Right() && + pos.Y > rect.Top() && pos.Y < rect.Bottom() +} + +func (r1 Rect) CollisionRectangle(r2 Rect) bool { + return r1.Right() > r2.Left() && r1.Bottom() > r2.Top() && + r1.Left() < r2.Right() && r1.Top() < r2.Bottom() +} diff --git a/gml/internal/geom/size.go b/gml/internal/geom/size.go new file mode 100644 index 0000000..592d76c --- /dev/null +++ b/gml/internal/geom/size.go @@ -0,0 +1,13 @@ +package geom + +type Size struct { + // NOTE(Jake): 2018-07-08 + // + // When profiling the collision.go tests, I tried adjusting these types to: + // - uint32, uint16, int16, int, float32 + // + // When benchmarking with "go test --bench=." and "gjbt --bench=.", it seemed + // that int32 gave the best performance + // + X, Y int32 +} diff --git a/gml/internal/geom/vec.go b/gml/internal/geom/vec.go new file mode 100644 index 0000000..fff2ec8 --- /dev/null +++ b/gml/internal/geom/vec.go @@ -0,0 +1,16 @@ +package geom + +import "math" + +type Vec struct { + X, Y float64 +} + +func (v Vec) Sub(ov Vec) Vec { return Vec{v.X - ov.X, v.Y - ov.Y} } + +// Norm returns the vector's norm. +func (v Vec) Norm() float64 { return math.Sqrt(v.Dot(v)) } + +func (v Vec) Dot(ov Vec) float64 { return v.X*ov.X + v.Y*ov.Y } + +func (v Vec) DistancePoint(ov Vec) float64 { return v.Sub(ov).Norm() } diff --git a/gml/internal/math/math.go b/gml/internal/math/math.go deleted file mode 100644 index 4c425ce..0000000 --- a/gml/internal/math/math.go +++ /dev/null @@ -1,50 +0,0 @@ -package math - -type Vec struct { - X, Y float64 -} - -func V(x float64, y float64) Vec { - return Vec{X: x, Y: y} -} - -type Size struct { - // NOTE(Jake): 2018-07-08 - // - // When profiling the collision.go tests, I tried adjusting these types to: - // - uint32, uint16, int16, int, float32 - // - // When benchmarking with "go test --bench=." and "gjbt --bench=.", it seemed - // that int32 gave the best performance - // - X, Y int32 -} - -type Rect struct { - LeftTop Vec - RightBottom Vec -} - -func (rect *Rect) Left() float64 { return rect.LeftTop.X } -func (rect *Rect) Right() float64 { return rect.RightBottom.X } -func (rect *Rect) Top() float64 { return rect.LeftTop.Y } -func (rect *Rect) Bottom() float64 { return rect.RightBottom.Y } - -func R(a Vec, b Vec) Rect { - rect := Rect{} - if a.X < b.X { - rect.LeftTop.X = a.X - rect.RightBottom.X = b.X - } else { - rect.LeftTop.X = b.X - rect.RightBottom.X = a.X - } - if a.Y < b.Y { - rect.LeftTop.Y = a.Y - rect.RightBottom.Y = b.Y - } else { - rect.LeftTop.Y = b.Y - rect.RightBottom.Y = a.Y - } - return rect -} diff --git a/gml/internal/object/instance.go b/gml/internal/object/instance.go new file mode 100644 index 0000000..4eec9d4 --- /dev/null +++ b/gml/internal/object/instance.go @@ -0,0 +1,19 @@ +package object + +type instanceObject struct { + index int // index in the 'entities' array + roomInstanceIndex int // Room Instance Index belongs to + layerInstanceIndex int // Layer belongs to +} + +func InstanceIndex(inst *Object) int { + return inst.index +} + +func RoomInstanceIndex(inst *Object) int { + return inst.roomInstanceIndex +} + +func LayerInstanceIndex(inst *Object) int { + return inst.layerInstanceIndex +} diff --git a/gml/internal/object/object.go b/gml/internal/object/object.go index d5eae1a..c282fa9 100644 --- a/gml/internal/object/object.go +++ b/gml/internal/object/object.go @@ -3,7 +3,7 @@ package object import ( "math" - m "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/space" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) @@ -14,15 +14,15 @@ type ObjectType interface { ObjectIndex() ObjectIndex ObjectName() string Create() + Destroy() Update() Draw() } type Object struct { sprite.SpriteState // Sprite (contains SetSprite) - SpaceObject - index int // index in the 'entities' array - roomInstanceIndex int // index of the room in the 'room' array + space.SpaceObject + instanceObject imageAngleRadians float64 // Image Angle } @@ -32,13 +32,10 @@ func (inst *Object) create() { } func (inst *Object) BaseObject() *Object { return inst } -func (inst *Object) Index() int { return inst.index } -func (inst *Object) RoomInstanceIndex() int { return inst.roomInstanceIndex } -func (inst *Object) Pos() m.Vec { return inst.Vec } func (inst *Object) ImageAngleRadians() float64 { return inst.imageAngleRadians } func (inst *Object) ImageAngle() float64 { return inst.imageAngleRadians * (180 / math.Pi) } -//func (inst *Object) ImageScale() m.Vec { return inst.imageScale } +//func (inst *Object) ImageScale() geom.Vec { return inst.imageScale } func (inst *Object) SetSprite(sprite *sprite.Sprite) { inst.SpriteState.SetSprite(sprite) @@ -60,3 +57,7 @@ func (inst *Object) SetImageAngle(angleInDegrees float64) { func (inst *Object) SetImageAngleRadians(angleInRadians float64) { inst.imageAngleRadians = angleInRadians } + +func (inst *Object) CollisionInstance(otherInst ObjectType) bool { + return inst.Rect.CollisionRectangle(otherInst.BaseObject().Rect) +} diff --git a/gml/internal/object/object_manager.go b/gml/internal/object/object_manager.go index ad7ab4e..ea6e567 100644 --- a/gml/internal/object/object_manager.go +++ b/gml/internal/object/object_manager.go @@ -1,6 +1,11 @@ package object -import "reflect" +import ( + "fmt" + "reflect" + + "github.com/silbinarywolf/gml-go/gml/internal/space" +) var ( gObjectManager *objectManager = newObjectManager() @@ -30,6 +35,11 @@ func InitTypes(objTypes []ObjectType) { } name := objType.ObjectName() objectIndex := objType.ObjectIndex() + otherID, used := manager.nameToID[name] + if used { + otherType := manager.idToEntityData[otherID] + panic(fmt.Sprintf("You cannot have two objects with the same object name.\n- %T::ObjectName() == %s\n- %T::ObjectName() == %s", objType, objType.ObjectName(), otherType, otherType.ObjectName())) + } manager.nameToID[name] = objectIndex } } @@ -45,7 +55,7 @@ func NameToID() map[string]ObjectIndex { return gObjectManager.nameToID } -func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, space *Space, spaceIndex int) ObjectType { +func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, layerIndex int, space *space.Space, spaceIndex int) ObjectType { // Create valToCopy := gObjectManager.idToEntityData[objectIndex] inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) @@ -54,6 +64,7 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, s baseObj := inst.BaseObject() baseObj.index = index baseObj.roomInstanceIndex = roomInstanceIndex + baseObj.layerInstanceIndex = layerIndex // todo(Jake): 2018-07-08 // // Figure out a cleaner way to handle this functionality across @@ -61,8 +72,7 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, s // // Perhaps force objects to have to be created via an instance manager. // - baseObj.Space = space - baseObj.spaceIndex = spaceIndex + baseObj.SpaceObject.Init(space, spaceIndex) baseObj.create() return inst diff --git a/gml/internal/reditor/reditor.go b/gml/internal/reditor/reditor.go new file mode 100644 index 0000000..9a893dc --- /dev/null +++ b/gml/internal/reditor/reditor.go @@ -0,0 +1,14 @@ +package reditor + +type Menu int + +const ( + MenuNone Menu = 0 + iota + MenuEntity + MenuSprite + MenuBackground + MenuNewRoom + MenuLoadRoom + MenuNewLayer + MenuSetOrder +) diff --git a/gml/internal/reditor/reditor_debug.go b/gml/internal/reditor/reditor_debug.go new file mode 100644 index 0000000..f02f477 --- /dev/null +++ b/gml/internal/reditor/reditor_debug.go @@ -0,0 +1,12 @@ +// +build debug + +package reditor + +import ( + "github.com/rs/xid" +) + +func UUID() string { + guid := xid.New() + return guid.String() +} diff --git a/gml/internal/reditor/reditor_nondebug.go b/gml/internal/reditor/reditor_nondebug.go new file mode 100644 index 0000000..0880ac3 --- /dev/null +++ b/gml/internal/reditor/reditor_nondebug.go @@ -0,0 +1,7 @@ +// +build !debug + +package reditor + +func UUID() string { + panic("Not implemented for non-debug builds.") +} diff --git a/gml/internal/room/room.go b/gml/internal/room/room.go new file mode 100644 index 0000000..6366720 --- /dev/null +++ b/gml/internal/room/room.go @@ -0,0 +1,13 @@ +package room + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/file" +) + +func (room *Room) Filepath() string { + return file.AssetsDirectory + "/room/" + room.Config.UUID +} + +//func (room *Room) LayerCount() int { +// return len(room.InstanceLayers) +//} diff --git a/gml/internal/room/room.pb.go b/gml/internal/room/room.pb.go index 9e4f37d..4b5b3de 100644 --- a/gml/internal/room/room.pb.go +++ b/gml/internal/room/room.pb.go @@ -2,15 +2,28 @@ // source: room.proto /* - Package gml is a generated protocol buffer package. + Package room is a generated protocol buffer package. It is generated from these files: room.proto + room_config.proto + room_layer_background.proto + room_layer_config.proto + room_layer_instance.proto + room_layer_kind.proto + room_layer_sprite.proto room_object.proto + room_sprite_object.proto It has these top-level messages: Room + RoomConfig + RoomLayerBackground + RoomLayerConfig + RoomLayerInstance + RoomLayerSprite RoomObject + RoomSpriteObject */ package room @@ -32,15 +45,16 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Room struct { - Filepath string `protobuf:"bytes,1,opt,name=Filepath,proto3" json:"Filepath,omitempty"` - Left int32 `protobuf:"varint,2,opt,name=Left,proto3" json:"Left,omitempty"` - Right int32 `protobuf:"varint,3,opt,name=Right,proto3" json:"Right,omitempty"` - Top int32 `protobuf:"varint,4,opt,name=Top,proto3" json:"Top,omitempty"` - Bottom int32 `protobuf:"varint,5,opt,name=Bottom,proto3" json:"Bottom,omitempty"` - Instances []*RoomObject `protobuf:"bytes,6,rep,name=Instances" json:"Instances,omitempty"` - // Room Editor - UserEntityCount int64 `protobuf:"varint,7,opt,name=UserEntityCount,proto3" json:"UserEntityCount,omitempty"` - DeletedInstances []*RoomObject `protobuf:"bytes,8,rep,name=DeletedInstances" json:"DeletedInstances,omitempty"` + Config *RoomConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Left int32 `protobuf:"varint,2,opt,name=Left,proto3" json:"Left,omitempty"` + Right int32 `protobuf:"varint,3,opt,name=Right,proto3" json:"Right,omitempty"` + Top int32 `protobuf:"varint,4,opt,name=Top,proto3" json:"Top,omitempty"` + Bottom int32 `protobuf:"varint,5,opt,name=Bottom,proto3" json:"Bottom,omitempty"` + // Layers + InstanceLayers []*RoomLayerInstance `protobuf:"bytes,6,rep,name=InstanceLayers" json:"InstanceLayers,omitempty"` + BackgroundLayers []*RoomLayerBackground `protobuf:"bytes,7,rep,name=BackgroundLayers" json:"BackgroundLayers,omitempty"` + SpriteLayers []*RoomLayerSprite `protobuf:"bytes,8,rep,name=SpriteLayers" json:"SpriteLayers,omitempty"` + DeletedLayers []string `protobuf:"bytes,9,rep,name=DeletedLayers" json:"DeletedLayers,omitempty"` } func (m *Room) Reset() { *m = Room{} } @@ -48,11 +62,11 @@ func (m *Room) String() string { return proto.CompactTextString(m) } func (*Room) ProtoMessage() {} func (*Room) Descriptor() ([]byte, []int) { return fileDescriptorRoom, []int{0} } -func (m *Room) GetFilepath() string { +func (m *Room) GetConfig() *RoomConfig { if m != nil { - return m.Filepath + return m.Config } - return "" + return nil } func (m *Room) GetLeft() int32 { @@ -83,29 +97,36 @@ func (m *Room) GetBottom() int32 { return 0 } -func (m *Room) GetInstances() []*RoomObject { +func (m *Room) GetInstanceLayers() []*RoomLayerInstance { if m != nil { - return m.Instances + return m.InstanceLayers } return nil } -func (m *Room) GetUserEntityCount() int64 { +func (m *Room) GetBackgroundLayers() []*RoomLayerBackground { if m != nil { - return m.UserEntityCount + return m.BackgroundLayers } - return 0 + return nil } -func (m *Room) GetDeletedInstances() []*RoomObject { +func (m *Room) GetSpriteLayers() []*RoomLayerSprite { if m != nil { - return m.DeletedInstances + return m.SpriteLayers + } + return nil +} + +func (m *Room) GetDeletedLayers() []string { + if m != nil { + return m.DeletedLayers } return nil } func init() { - proto.RegisterType((*Room)(nil), "gml.Room") + proto.RegisterType((*Room)(nil), "room.Room") } func (m *Room) Marshal() (dAtA []byte, err error) { size := m.Size() @@ -122,11 +143,15 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Filepath) > 0 { + if m.Config != nil { dAtA[i] = 0xa i++ - i = encodeVarintRoom(dAtA, i, uint64(len(m.Filepath))) - i += copy(dAtA[i:], m.Filepath) + i = encodeVarintRoom(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 } if m.Left != 0 { dAtA[i] = 0x10 @@ -148,8 +173,8 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintRoom(dAtA, i, uint64(m.Bottom)) } - if len(m.Instances) > 0 { - for _, msg := range m.Instances { + if len(m.InstanceLayers) > 0 { + for _, msg := range m.InstanceLayers { dAtA[i] = 0x32 i++ i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) @@ -160,13 +185,20 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i += n } } - if m.UserEntityCount != 0 { - dAtA[i] = 0x38 - i++ - i = encodeVarintRoom(dAtA, i, uint64(m.UserEntityCount)) + if len(m.BackgroundLayers) > 0 { + for _, msg := range m.BackgroundLayers { + dAtA[i] = 0x3a + i++ + i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } } - if len(m.DeletedInstances) > 0 { - for _, msg := range m.DeletedInstances { + if len(m.SpriteLayers) > 0 { + for _, msg := range m.SpriteLayers { dAtA[i] = 0x42 i++ i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) @@ -177,6 +209,21 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.DeletedLayers) > 0 { + for _, s := range m.DeletedLayers { + dAtA[i] = 0x4a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } return i, nil } @@ -192,8 +239,8 @@ func encodeVarintRoom(dAtA []byte, offset int, v uint64) int { func (m *Room) Size() (n int) { var l int _ = l - l = len(m.Filepath) - if l > 0 { + if m.Config != nil { + l = m.Config.Size() n += 1 + l + sovRoom(uint64(l)) } if m.Left != 0 { @@ -208,21 +255,30 @@ func (m *Room) Size() (n int) { if m.Bottom != 0 { n += 1 + sovRoom(uint64(m.Bottom)) } - if len(m.Instances) > 0 { - for _, e := range m.Instances { + if len(m.InstanceLayers) > 0 { + for _, e := range m.InstanceLayers { l = e.Size() n += 1 + l + sovRoom(uint64(l)) } } - if m.UserEntityCount != 0 { - n += 1 + sovRoom(uint64(m.UserEntityCount)) + if len(m.BackgroundLayers) > 0 { + for _, e := range m.BackgroundLayers { + l = e.Size() + n += 1 + l + sovRoom(uint64(l)) + } } - if len(m.DeletedInstances) > 0 { - for _, e := range m.DeletedInstances { + if len(m.SpriteLayers) > 0 { + for _, e := range m.SpriteLayers { l = e.Size() n += 1 + l + sovRoom(uint64(l)) } } + if len(m.DeletedLayers) > 0 { + for _, s := range m.DeletedLayers { + l = len(s) + n += 1 + l + sovRoom(uint64(l)) + } + } return n } @@ -270,9 +326,9 @@ func (m *Room) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Filepath", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRoom @@ -282,20 +338,24 @@ func (m *Room) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthRoom } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.Filepath = string(dAtA[iNdEx:postIndex]) + if m.Config == nil { + m.Config = &RoomConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 0 { @@ -375,7 +435,7 @@ func (m *Room) Unmarshal(dAtA []byte) error { } case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Instances", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InstanceLayers", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -399,16 +459,16 @@ func (m *Room) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Instances = append(m.Instances, &RoomObject{}) - if err := m.Instances[len(m.Instances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.InstanceLayers = append(m.InstanceLayers, &RoomLayerInstance{}) + if err := m.InstanceLayers[len(m.InstanceLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UserEntityCount", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BackgroundLayers", wireType) } - m.UserEntityCount = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRoom @@ -418,14 +478,26 @@ func (m *Room) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.UserEntityCount |= (int64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } + if msglen < 0 { + return ErrInvalidLengthRoom + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BackgroundLayers = append(m.BackgroundLayers, &RoomLayerBackground{}) + if err := m.BackgroundLayers[len(m.BackgroundLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeletedInstances", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SpriteLayers", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -449,11 +521,40 @@ func (m *Room) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DeletedInstances = append(m.DeletedInstances, &RoomObject{}) - if err := m.DeletedInstances[len(m.DeletedInstances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.SpriteLayers = append(m.SpriteLayers, &RoomLayerSprite{}) + if err := m.SpriteLayers[len(m.SpriteLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedLayers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoom + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoom + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedLayers = append(m.DeletedLayers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRoom(dAtA[iNdEx:]) @@ -583,21 +684,25 @@ var ( func init() { proto.RegisterFile("room.proto", fileDescriptorRoom) } var fileDescriptorRoom = []byte{ - // 249 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xca, 0xcf, 0xcf, - 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0xcf, 0xcd, 0x91, 0x12, 0x04, 0x09, 0xc4, - 0xe7, 0x27, 0x65, 0xa5, 0x26, 0x97, 0x40, 0xc4, 0x95, 0x7a, 0x98, 0xb8, 0x58, 0x82, 0xf2, 0xf3, - 0x73, 0x85, 0xa4, 0xb8, 0x38, 0xdc, 0x32, 0x73, 0x52, 0x0b, 0x12, 0x4b, 0x32, 0x24, 0x18, 0x15, - 0x18, 0x35, 0x38, 0x83, 0xe0, 0x7c, 0x21, 0x21, 0x2e, 0x16, 0x9f, 0xd4, 0xb4, 0x12, 0x09, 0x26, - 0x05, 0x46, 0x0d, 0xd6, 0x20, 0x30, 0x5b, 0x48, 0x84, 0x8b, 0x35, 0x28, 0x33, 0x3d, 0xa3, 0x44, - 0x82, 0x19, 0x2c, 0x08, 0xe1, 0x08, 0x09, 0x70, 0x31, 0x87, 0xe4, 0x17, 0x48, 0xb0, 0x80, 0xc5, - 0x40, 0x4c, 0x21, 0x31, 0x2e, 0x36, 0xa7, 0xfc, 0x92, 0x92, 0xfc, 0x5c, 0x09, 0x56, 0xb0, 0x20, - 0x94, 0x27, 0xa4, 0xcb, 0xc5, 0xe9, 0x99, 0x57, 0x5c, 0x92, 0x98, 0x97, 0x9c, 0x5a, 0x2c, 0xc1, - 0xa6, 0xc0, 0xac, 0xc1, 0x6d, 0xc4, 0xaf, 0x97, 0x9e, 0x9b, 0xa3, 0x07, 0x72, 0x8d, 0x3f, 0xd8, - 0x89, 0x41, 0x08, 0x15, 0x42, 0x1a, 0x5c, 0xfc, 0xa1, 0xc5, 0xa9, 0x45, 0xae, 0x79, 0x25, 0x99, - 0x25, 0x95, 0xce, 0xf9, 0xa5, 0x79, 0x25, 0x12, 0xec, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0xe8, 0xc2, - 0x42, 0xd6, 0x5c, 0x02, 0x2e, 0xa9, 0x39, 0xa9, 0x25, 0xa9, 0x29, 0x08, 0xf3, 0x39, 0xb0, 0x9b, - 0x8f, 0xa1, 0xd0, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, - 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x1c, 0x4e, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x1e, 0xba, 0x15, 0x9d, 0x4d, 0x01, 0x00, 0x00, + // 305 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0x71, 0x93, 0x06, 0x7a, 0x05, 0x14, 0x2c, 0xa0, 0x6e, 0x91, 0xa2, 0x08, 0x31, 0x64, + 0xea, 0x50, 0x26, 0x26, 0xa4, 0x02, 0x03, 0x52, 0x27, 0xc3, 0x5e, 0xb5, 0xc1, 0x0d, 0x11, 0x4d, + 0x2e, 0x4a, 0xcc, 0xc0, 0x9b, 0xf0, 0x48, 0x8c, 0x3c, 0x02, 0x0a, 0x33, 0xef, 0x80, 0x72, 0x71, + 0x69, 0x1b, 0x96, 0xe8, 0xfc, 0x7f, 0xbf, 0xff, 0xbb, 0x8b, 0x01, 0x72, 0xc4, 0x64, 0x98, 0xe5, + 0xa8, 0x91, 0xdb, 0x55, 0x3d, 0x38, 0xaa, 0xbe, 0xd3, 0x10, 0xd3, 0x45, 0x1c, 0xd5, 0x60, 0xd0, + 0x27, 0x69, 0x39, 0x7b, 0x53, 0xf9, 0x34, 0x4e, 0x0b, 0x3d, 0x4b, 0x43, 0x65, 0xd0, 0xd9, 0x06, + 0x9a, 0xcf, 0xc2, 0x97, 0x28, 0xc7, 0xd7, 0xf4, 0xc9, 0xc0, 0xde, 0x06, 0x2c, 0xb2, 0x3c, 0xd6, + 0xe6, 0xd6, 0xf9, 0x4f, 0x0b, 0x6c, 0x89, 0x98, 0xf0, 0x00, 0x9c, 0x1b, 0xea, 0x24, 0x98, 0xcf, + 0x82, 0xee, 0xc8, 0x1d, 0xd2, 0x3c, 0x15, 0xab, 0x75, 0x69, 0x38, 0xe7, 0x60, 0x4f, 0xd4, 0x42, + 0x8b, 0x96, 0xcf, 0x82, 0xb6, 0xa4, 0x9a, 0x1f, 0x43, 0x5b, 0xc6, 0xd1, 0xb3, 0x16, 0x16, 0x89, + 0xf5, 0x81, 0xbb, 0x60, 0x3d, 0x62, 0x26, 0x6c, 0xd2, 0xaa, 0x92, 0x9f, 0x82, 0x33, 0x46, 0xad, + 0x31, 0x11, 0x6d, 0x12, 0xcd, 0x89, 0x5f, 0xc3, 0xe1, 0xbd, 0x59, 0x67, 0x52, 0x0d, 0x59, 0x08, + 0xc7, 0xb7, 0x82, 0xee, 0xa8, 0xb7, 0x9e, 0x82, 0xf4, 0x95, 0x49, 0x36, 0xec, 0xfc, 0x0e, 0xdc, + 0xf1, 0xdf, 0xd2, 0x26, 0x62, 0x97, 0x22, 0xfa, 0x8d, 0x88, 0xb5, 0x4d, 0xfe, 0xbb, 0xc2, 0xaf, + 0x60, 0xff, 0x81, 0x7e, 0x8f, 0x89, 0xd8, 0xa3, 0x88, 0x93, 0x46, 0x44, 0x6d, 0x91, 0x5b, 0x56, + 0x7e, 0x01, 0x07, 0xb7, 0x6a, 0xa9, 0xb4, 0x5a, 0xb5, 0xef, 0xf8, 0x56, 0xd0, 0x91, 0xdb, 0xe2, + 0xd8, 0xfd, 0x28, 0x3d, 0xf6, 0x59, 0x7a, 0xec, 0xab, 0xf4, 0xd8, 0xfb, 0xb7, 0xb7, 0x33, 0x77, + 0xe8, 0x21, 0x2e, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x15, 0x8d, 0x3d, 0x6a, 0x00, 0x02, 0x00, + 0x00, } diff --git a/gml/internal/room/room.proto b/gml/internal/room/room.proto index ea62ab9..8e5116b 100644 --- a/gml/internal/room/room.proto +++ b/gml/internal/room/room.proto @@ -1,17 +1,21 @@ syntax = "proto3"; package room; -import "room_object.proto"; +import "room_config.proto"; +import "room_layer_instance.proto"; +import "room_layer_background.proto"; +import "room_layer_sprite.proto"; message Room { - string Filepath = 1; + RoomConfig Config = 1; int32 Left = 2; int32 Right = 3; int32 Top = 4; int32 Bottom = 5; - repeated RoomObject Instances = 6; // todo(Jake): 2018-06-10, Rename 'Instances' to 'Objects' - // Room Editor - int64 UserEntityCount = 7; - repeated RoomObject DeletedObjects = 8; + // Layers + repeated RoomLayerInstance InstanceLayers = 6; + repeated RoomLayerBackground BackgroundLayers = 7; + repeated RoomLayerSprite SpriteLayers = 8; + repeated string DeletedLayers = 9; } diff --git a/gml/internal/room/room_config.pb.go b/gml/internal/room/room_config.pb.go new file mode 100644 index 0000000..2c36717 --- /dev/null +++ b/gml/internal/room/room_config.pb.go @@ -0,0 +1,334 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_config.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomConfig struct { + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` +} + +func (m *RoomConfig) Reset() { *m = RoomConfig{} } +func (m *RoomConfig) String() string { return proto.CompactTextString(m) } +func (*RoomConfig) ProtoMessage() {} +func (*RoomConfig) Descriptor() ([]byte, []int) { return fileDescriptorRoomConfig, []int{0} } + +func (m *RoomConfig) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomConfig) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func init() { + proto.RegisterType((*RoomConfig)(nil), "room.RoomConfig") +} +func (m *RoomConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.UUID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomConfig(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if len(m.Name) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomConfig(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + } + return i, nil +} + +func encodeVarintRoomConfig(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomConfig) Size() (n int) { + var l int + _ = l + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomConfig(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovRoomConfig(uint64(l)) + } + return n +} + +func sovRoomConfig(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomConfig(x uint64) (n int) { + return sovRoomConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomConfig + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomConfig(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomConfig = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_config.proto", fileDescriptorRoomConfig) } + +var fileDescriptorRoomConfig = []byte{ + // 110 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0xca, 0xcf, 0xcf, + 0x8d, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, + 0x09, 0x29, 0x99, 0x70, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0x3a, 0x83, 0x65, 0x84, 0x84, 0xb8, 0x58, + 0x42, 0x43, 0x3d, 0x5d, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x90, 0x98, 0x5f, + 0x62, 0x6e, 0xaa, 0x04, 0x13, 0x44, 0x0c, 0xc4, 0x76, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, + 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0x1b, 0x6a, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x72, 0xf8, 0x5c, 0xa2, 0x69, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_config.proto b/gml/internal/room/room_config.proto new file mode 100644 index 0000000..1103c7c --- /dev/null +++ b/gml/internal/room/room_config.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package room; + +message RoomConfig { + string UUID = 1; + string Name = 2; +} diff --git a/gml/internal/room/room_layer.go b/gml/internal/room/room_layer.go new file mode 100644 index 0000000..2319d60 --- /dev/null +++ b/gml/internal/room/room_layer.go @@ -0,0 +1,5 @@ +package room + +type RoomLayer interface { + GetConfig() *RoomLayerConfig +} diff --git a/gml/internal/room/room_layer_background.pb.go b/gml/internal/room/room_layer_background.pb.go new file mode 100644 index 0000000..174e14c --- /dev/null +++ b/gml/internal/room/room_layer_background.pb.go @@ -0,0 +1,419 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_background.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerBackground struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + SpriteName string `protobuf:"bytes,2,opt,name=SpriteName,proto3" json:"SpriteName,omitempty"` + X int32 `protobuf:"varint,3,opt,name=X,proto3" json:"X,omitempty"` + Y int32 `protobuf:"varint,4,opt,name=Y,proto3" json:"Y,omitempty"` +} + +func (m *RoomLayerBackground) Reset() { *m = RoomLayerBackground{} } +func (m *RoomLayerBackground) String() string { return proto.CompactTextString(m) } +func (*RoomLayerBackground) ProtoMessage() {} +func (*RoomLayerBackground) Descriptor() ([]byte, []int) { + return fileDescriptorRoomLayerBackground, []int{0} +} + +func (m *RoomLayerBackground) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerBackground) GetSpriteName() string { + if m != nil { + return m.SpriteName + } + return "" +} + +func (m *RoomLayerBackground) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *RoomLayerBackground) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func init() { + proto.RegisterType((*RoomLayerBackground)(nil), "room.RoomLayerBackground") +} +func (m *RoomLayerBackground) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerBackground) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.SpriteName) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(len(m.SpriteName))) + i += copy(dAtA[i:], m.SpriteName) + } + if m.X != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.X)) + } + if m.Y != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.Y)) + } + return i, nil +} + +func encodeVarintRoomLayerBackground(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerBackground) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerBackground(uint64(l)) + } + l = len(m.SpriteName) + if l > 0 { + n += 1 + l + sovRoomLayerBackground(uint64(l)) + } + if m.X != 0 { + n += 1 + sovRoomLayerBackground(uint64(m.X)) + } + if m.Y != 0 { + n += 1 + sovRoomLayerBackground(uint64(m.Y)) + } + return n +} + +func sovRoomLayerBackground(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerBackground(x uint64) (n int) { + return sovRoomLayerBackground(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerBackground) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerBackground: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerBackground: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerBackground + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpriteName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerBackground + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpriteName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) + } + m.X = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.X |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) + } + m.Y = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Y |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRoomLayerBackground(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerBackground + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerBackground(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerBackground + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerBackground(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerBackground = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerBackground = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_background.proto", fileDescriptorRoomLayerBackground) } + +var fileDescriptorRoomLayerBackground = []byte{ + // 179 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2e, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x4f, 0x4a, 0x4c, 0xce, 0x4e, 0x2f, 0xca, 0x2f, 0xcd, + 0x4b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x4a, 0x89, 0x23, 0x29, 0x49, + 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0x87, 0x48, 0x2b, 0x35, 0x30, 0x72, 0x09, 0x07, 0xe5, 0xe7, 0xe7, + 0xfa, 0x80, 0xa4, 0x9c, 0xe0, 0x9a, 0x85, 0x74, 0xb9, 0xd8, 0x9c, 0xc1, 0xea, 0x24, 0x18, 0x15, + 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf5, 0x40, 0x26, 0xe8, 0xc1, 0x95, 0x42, 0x24, 0x83, 0xa0, 0x8a, + 0x84, 0xe4, 0xb8, 0xb8, 0x82, 0x0b, 0x8a, 0x32, 0x4b, 0x52, 0xfd, 0x12, 0x73, 0x53, 0x25, 0x98, + 0x14, 0x18, 0x35, 0x38, 0x83, 0x90, 0x44, 0x84, 0x78, 0xb8, 0x18, 0x23, 0x24, 0x98, 0x15, 0x18, + 0x35, 0x58, 0x83, 0x18, 0x23, 0x40, 0xbc, 0x48, 0x09, 0x16, 0x08, 0x2f, 0xd2, 0x49, 0xe0, 0xc4, + 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x21, + 0x89, 0x0d, 0xec, 0x36, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x87, 0xbe, 0x87, 0xd9, + 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_background.proto b/gml/internal/room/room_layer_background.proto new file mode 100644 index 0000000..23758f2 --- /dev/null +++ b/gml/internal/room/room_layer_background.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package room; + +import "room_layer_config.proto"; + +message RoomLayerBackground { + RoomLayerConfig Config = 1; + string SpriteName = 2; + int32 X = 3; + int32 Y = 4; +} diff --git a/gml/internal/room/room_layer_config.pb.go b/gml/internal/room/room_layer_config.pb.go new file mode 100644 index 0000000..a39b7f0 --- /dev/null +++ b/gml/internal/room/room_layer_config.pb.go @@ -0,0 +1,452 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_config.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerConfig struct { + Kind RoomLayerKind `protobuf:"varint,1,opt,name=Kind,proto3,enum=room.RoomLayerKind" json:"Kind,omitempty"` + UUID string `protobuf:"bytes,2,opt,name=UUID,proto3" json:"UUID,omitempty"` + Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"` + Order int32 `protobuf:"varint,4,opt,name=Order,proto3" json:"Order,omitempty"` + // RoomLayerSprite Only + HasCollision bool `protobuf:"varint,5,opt,name=HasCollision,proto3" json:"HasCollision,omitempty"` +} + +func (m *RoomLayerConfig) Reset() { *m = RoomLayerConfig{} } +func (m *RoomLayerConfig) String() string { return proto.CompactTextString(m) } +func (*RoomLayerConfig) ProtoMessage() {} +func (*RoomLayerConfig) Descriptor() ([]byte, []int) { return fileDescriptorRoomLayerConfig, []int{0} } + +func (m *RoomLayerConfig) GetKind() RoomLayerKind { + if m != nil { + return m.Kind + } + return RoomLayerKind_None +} + +func (m *RoomLayerConfig) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomLayerConfig) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *RoomLayerConfig) GetOrder() int32 { + if m != nil { + return m.Order + } + return 0 +} + +func (m *RoomLayerConfig) GetHasCollision() bool { + if m != nil { + return m.HasCollision + } + return false +} + +func init() { + proto.RegisterType((*RoomLayerConfig)(nil), "room.RoomLayerConfig") +} +func (m *RoomLayerConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Kind != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(m.Kind)) + } + if len(m.UUID) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if len(m.Name) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + } + if m.Order != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(m.Order)) + } + if m.HasCollision { + dAtA[i] = 0x28 + i++ + if m.HasCollision { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + +func encodeVarintRoomLayerConfig(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerConfig) Size() (n int) { + var l int + _ = l + if m.Kind != 0 { + n += 1 + sovRoomLayerConfig(uint64(m.Kind)) + } + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomLayerConfig(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovRoomLayerConfig(uint64(l)) + } + if m.Order != 0 { + n += 1 + sovRoomLayerConfig(uint64(m.Order)) + } + if m.HasCollision { + n += 2 + } + return n +} + +func sovRoomLayerConfig(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerConfig(x uint64) (n int) { + return sovRoomLayerConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + m.Kind = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Kind |= (RoomLayerKind(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Order", wireType) + } + m.Order = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Order |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HasCollision", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.HasCollision = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipRoomLayerConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerConfig + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerConfig(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerConfig = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_config.proto", fileDescriptorRoomLayerConfig) } + +var fileDescriptorRoomLayerConfig = []byte{ + // 200 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x48, 0x89, 0x22, 0x49, 0x67, 0x67, 0xe6, 0xa5, 0x40, + 0x24, 0x95, 0xe6, 0x30, 0x72, 0xf1, 0x07, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0x24, 0x9c, 0xc1, 0xda, + 0x84, 0xd4, 0xb9, 0x58, 0xbc, 0x33, 0xf3, 0x52, 0x24, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x84, + 0xf5, 0x40, 0x3a, 0xf5, 0xe0, 0x8a, 0x40, 0x52, 0x41, 0x60, 0x05, 0x42, 0x42, 0x5c, 0x2c, 0xa1, + 0xa1, 0x9e, 0x2e, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0x36, 0x48, 0xcc, 0x2f, 0x31, + 0x37, 0x55, 0x82, 0x19, 0x22, 0x06, 0x62, 0x0b, 0x89, 0x70, 0xb1, 0xfa, 0x17, 0xa5, 0xa4, 0x16, + 0x49, 0xb0, 0x28, 0x30, 0x6a, 0xb0, 0x06, 0x41, 0x38, 0x42, 0x4a, 0x5c, 0x3c, 0x1e, 0x89, 0xc5, + 0xce, 0xf9, 0x39, 0x39, 0x99, 0xc5, 0x99, 0xf9, 0x79, 0x12, 0xac, 0x0a, 0x8c, 0x1a, 0x1c, 0x41, + 0x28, 0x62, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, + 0xe3, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x77, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, + 0xe6, 0x27, 0xdb, 0xac, 0xef, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_config.proto b/gml/internal/room/room_layer_config.proto new file mode 100644 index 0000000..c3863b3 --- /dev/null +++ b/gml/internal/room/room_layer_config.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package room; + +import "room_layer_kind.proto"; + +message RoomLayerConfig { + RoomLayerKind Kind = 1; + string UUID = 2; + string Name = 3; + int32 Order = 4; + + // RoomLayerSprite Only + bool HasCollision = 5; +} diff --git a/gml/internal/room/room_layer_instance.pb.go b/gml/internal/room/room_layer_instance.pb.go new file mode 100644 index 0000000..3060043 --- /dev/null +++ b/gml/internal/room/room_layer_instance.pb.go @@ -0,0 +1,417 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_instance.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerInstance struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Instances []*RoomObject `protobuf:"bytes,2,rep,name=Instances" json:"Instances,omitempty"` + // Room Editor only + DeletedInstances []*RoomObject `protobuf:"bytes,3,rep,name=DeletedInstances" json:"DeletedInstances,omitempty"` +} + +func (m *RoomLayerInstance) Reset() { *m = RoomLayerInstance{} } +func (m *RoomLayerInstance) String() string { return proto.CompactTextString(m) } +func (*RoomLayerInstance) ProtoMessage() {} +func (*RoomLayerInstance) Descriptor() ([]byte, []int) { + return fileDescriptorRoomLayerInstance, []int{0} +} + +func (m *RoomLayerInstance) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerInstance) GetInstances() []*RoomObject { + if m != nil { + return m.Instances + } + return nil +} + +func (m *RoomLayerInstance) GetDeletedInstances() []*RoomObject { + if m != nil { + return m.DeletedInstances + } + return nil +} + +func init() { + proto.RegisterType((*RoomLayerInstance)(nil), "room.RoomLayerInstance") +} +func (m *RoomLayerInstance) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerInstance) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.Instances) > 0 { + for _, msg := range m.Instances { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if len(m.DeletedInstances) > 0 { + for _, msg := range m.DeletedInstances { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeVarintRoomLayerInstance(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerInstance) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + if len(m.Instances) > 0 { + for _, e := range m.Instances { + l = e.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + } + if len(m.DeletedInstances) > 0 { + for _, e := range m.DeletedInstances { + l = e.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + } + return n +} + +func sovRoomLayerInstance(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerInstance(x uint64) (n int) { + return sovRoomLayerInstance(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerInstance) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerInstance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerInstance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Instances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Instances = append(m.Instances, &RoomObject{}) + if err := m.Instances[len(m.Instances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedInstances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedInstances = append(m.DeletedInstances, &RoomObject{}) + if err := m.DeletedInstances[len(m.DeletedInstances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomLayerInstance(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerInstance + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerInstance(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerInstance + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerInstance(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerInstance = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerInstance = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_instance.proto", fileDescriptorRoomLayerInstance) } + +var fileDescriptorRoomLayerInstance = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2c, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0xcf, 0xcc, 0x2b, 0x2e, 0x49, 0xcc, 0x4b, 0x4e, 0xd5, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x49, 0x09, 0x82, 0x15, 0xe4, 0x27, 0x65, + 0xa5, 0x26, 0x97, 0x40, 0x24, 0xa4, 0xc4, 0x91, 0xf4, 0x24, 0xe7, 0xe7, 0xa5, 0x65, 0xa6, 0x43, + 0x24, 0x94, 0x36, 0x30, 0x72, 0x09, 0x06, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0xa4, 0x3c, 0xa1, 0xa6, + 0x09, 0xe9, 0x72, 0xb1, 0x39, 0x83, 0x55, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x89, 0xea, + 0x81, 0xf4, 0xeb, 0xc1, 0x15, 0x42, 0x24, 0x83, 0xa0, 0x8a, 0x84, 0xf4, 0xb8, 0x38, 0x61, 0x5a, + 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x04, 0x10, 0x3a, 0xfc, 0xc1, 0x0e, 0x09, 0x42, + 0x28, 0x11, 0xb2, 0xe1, 0x12, 0x70, 0x49, 0xcd, 0x49, 0x2d, 0x49, 0x4d, 0x41, 0x68, 0x63, 0xc6, + 0xa1, 0x0d, 0x43, 0xa5, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, + 0x24, 0xc7, 0x38, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0xd8, 0x2f, 0xc6, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x9a, 0x11, 0x61, 0x2c, 0x1a, 0x01, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_instance.proto b/gml/internal/room/room_layer_instance.proto new file mode 100644 index 0000000..140ac7a --- /dev/null +++ b/gml/internal/room/room_layer_instance.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package room; + +import "room_object.proto"; +import "room_layer_config.proto"; + +message RoomLayerInstance { + RoomLayerConfig Config = 1; + repeated RoomObject Instances = 2; + + // Room Editor only + repeated RoomObject DeletedInstances = 3; +} diff --git a/gml/internal/room/room_layer_kind.pb.go b/gml/internal/room/room_layer_kind.pb.go new file mode 100644 index 0000000..ed348d9 --- /dev/null +++ b/gml/internal/room/room_layer_kind.pb.go @@ -0,0 +1,59 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_kind.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerKind int32 + +const ( + RoomLayerKind_None RoomLayerKind = 0 + RoomLayerKind_Instance RoomLayerKind = 1 + RoomLayerKind_Background RoomLayerKind = 2 + RoomLayerKind_Sprite RoomLayerKind = 3 +) + +var RoomLayerKind_name = map[int32]string{ + 0: "None", + 1: "Instance", + 2: "Background", + 3: "Sprite", +} +var RoomLayerKind_value = map[string]int32{ + "None": 0, + "Instance": 1, + "Background": 2, + "Sprite": 3, +} + +func (x RoomLayerKind) String() string { + return proto.EnumName(RoomLayerKind_name, int32(x)) +} +func (RoomLayerKind) EnumDescriptor() ([]byte, []int) { return fileDescriptorRoomLayerKind, []int{0} } + +func init() { + proto.RegisterEnum("room.RoomLayerKind", RoomLayerKind_name, RoomLayerKind_value) +} + +func init() { proto.RegisterFile("room_layer_kind.proto", fileDescriptorRoomLayerKind) } + +var fileDescriptorRoomLayerKind = []byte{ + // 142 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2d, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0xcf, 0xce, 0xcc, 0x4b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x62, 0x01, 0x09, 0x6b, 0x39, 0x73, 0xf1, 0x06, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0x64, + 0xbd, 0x33, 0xf3, 0x52, 0x84, 0x38, 0xb8, 0x58, 0xfc, 0xf2, 0xf3, 0x52, 0x05, 0x18, 0x84, 0x78, + 0xb8, 0x38, 0x3c, 0xf3, 0x8a, 0x4b, 0x12, 0xf3, 0x92, 0x53, 0x05, 0x18, 0x85, 0xf8, 0xb8, 0xb8, + 0x9c, 0x12, 0x93, 0xb3, 0xd3, 0x8b, 0xf2, 0x4b, 0xf3, 0x52, 0x04, 0x98, 0x84, 0xb8, 0xb8, 0xd8, + 0x82, 0x0b, 0x8a, 0x32, 0x4b, 0x52, 0x05, 0x98, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, + 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0x76, 0x18, + 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x5e, 0xdf, 0x54, 0x6c, 0x7c, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_kind.proto b/gml/internal/room/room_layer_kind.proto new file mode 100644 index 0000000..4238e62 --- /dev/null +++ b/gml/internal/room/room_layer_kind.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package room; + +enum RoomLayerKind { + None = 0; + Instance = 1; + Background = 2; + Sprite = 3; +} diff --git a/gml/internal/room/room_layer_sprite.pb.go b/gml/internal/room/room_layer_sprite.pb.go new file mode 100644 index 0000000..2a0e299 --- /dev/null +++ b/gml/internal/room/room_layer_sprite.pb.go @@ -0,0 +1,415 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_sprite.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerSprite struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Sprites []*RoomSpriteObject `protobuf:"bytes,2,rep,name=Sprites" json:"Sprites,omitempty"` + // Room Editor only + DeletedSprites []*RoomSpriteObject `protobuf:"bytes,3,rep,name=DeletedSprites" json:"DeletedSprites,omitempty"` +} + +func (m *RoomLayerSprite) Reset() { *m = RoomLayerSprite{} } +func (m *RoomLayerSprite) String() string { return proto.CompactTextString(m) } +func (*RoomLayerSprite) ProtoMessage() {} +func (*RoomLayerSprite) Descriptor() ([]byte, []int) { return fileDescriptorRoomLayerSprite, []int{0} } + +func (m *RoomLayerSprite) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerSprite) GetSprites() []*RoomSpriteObject { + if m != nil { + return m.Sprites + } + return nil +} + +func (m *RoomLayerSprite) GetDeletedSprites() []*RoomSpriteObject { + if m != nil { + return m.DeletedSprites + } + return nil +} + +func init() { + proto.RegisterType((*RoomLayerSprite)(nil), "room.RoomLayerSprite") +} +func (m *RoomLayerSprite) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerSprite) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.Sprites) > 0 { + for _, msg := range m.Sprites { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if len(m.DeletedSprites) > 0 { + for _, msg := range m.DeletedSprites { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeVarintRoomLayerSprite(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerSprite) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + if len(m.Sprites) > 0 { + for _, e := range m.Sprites { + l = e.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + } + if len(m.DeletedSprites) > 0 { + for _, e := range m.DeletedSprites { + l = e.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + } + return n +} + +func sovRoomLayerSprite(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerSprite(x uint64) (n int) { + return sovRoomLayerSprite(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerSprite) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerSprite: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerSprite: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sprites", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sprites = append(m.Sprites, &RoomSpriteObject{}) + if err := m.Sprites[len(m.Sprites)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedSprites", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedSprites = append(m.DeletedSprites, &RoomSpriteObject{}) + if err := m.DeletedSprites[len(m.DeletedSprites)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomLayerSprite(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerSprite + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerSprite(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerSprite + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerSprite(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerSprite = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerSprite = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_sprite.proto", fileDescriptorRoomLayerSprite) } + +var fileDescriptorRoomLayerSprite = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x2f, 0x2e, 0x28, 0xca, 0x2c, 0x49, 0xd5, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x48, 0x49, 0x80, 0xa5, 0x21, 0x12, 0xf1, 0xf9, 0x49, + 0x59, 0xa9, 0xc9, 0x25, 0x10, 0x79, 0x29, 0x64, 0x8d, 0xc9, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0x10, + 0x09, 0xa5, 0x4d, 0x8c, 0x5c, 0xfc, 0x41, 0xf9, 0xf9, 0xb9, 0x3e, 0x20, 0xa9, 0x60, 0xb0, 0x4e, + 0x21, 0x5d, 0x2e, 0x36, 0x67, 0xb0, 0x1a, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x51, 0x3d, + 0x90, 0x6e, 0x3d, 0xb8, 0x32, 0x88, 0x64, 0x10, 0x54, 0x91, 0x90, 0x01, 0x17, 0x3b, 0x44, 0x63, + 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x18, 0x42, 0x3d, 0x44, 0xc2, 0x1f, 0xec, 0x94, + 0x20, 0x98, 0x32, 0x21, 0x3b, 0x2e, 0x3e, 0x97, 0xd4, 0x9c, 0xd4, 0x92, 0xd4, 0x14, 0x98, 0x46, + 0x66, 0xbc, 0x1a, 0xd1, 0x54, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, + 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x7d, 0x63, 0x0c, 0x08, 0x00, + 0x00, 0xff, 0xff, 0x6b, 0x42, 0x8b, 0x1a, 0x21, 0x01, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_sprite.proto b/gml/internal/room/room_layer_sprite.proto new file mode 100644 index 0000000..591c6fc --- /dev/null +++ b/gml/internal/room/room_layer_sprite.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package room; + +import "room_sprite_object.proto"; +import "room_layer_config.proto"; + +message RoomLayerSprite { + RoomLayerConfig Config = 1; + repeated RoomSpriteObject Sprites = 2; + + // Room Editor only + repeated RoomSpriteObject DeletedSprites = 3; +} diff --git a/gml/internal/room/room_manager_nonjs.go b/gml/internal/room/room_manager_nonjs.go index 5fe518a..6a52137 100644 --- a/gml/internal/room/room_manager_nonjs.go +++ b/gml/internal/room/room_manager_nonjs.go @@ -8,15 +8,20 @@ package room import ( "bufio" "bytes" + "encoding/json" "io/ioutil" "os" + "path" "path/filepath" + "sort" "strconv" "strings" "time" "github.com/silbinarywolf/gml-go/gml/internal/file" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/space" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) func (room *Room) writeDataFile(roomPath string) error { @@ -31,114 +36,9 @@ func (room *Room) writeDataFile(roomPath string) error { return nil } -func (room *Room) readInstance(instancePath string) { - //println("Loading ", instancePath, "...") - instanceFileData, err := file.OpenFile(instancePath) - if err != nil { - panic("Unable to find map entity file: " + err.Error()) - } - bytesData, err := ioutil.ReadAll(instanceFileData) - instanceFileData.Close() - if err != nil { - panic("Unable to find map entity file: Read all: " + err.Error()) - } - bytesReader := bytes.NewReader(bytesData) - scanner := bufio.NewScanner(bytesReader) - - // Entity name - scanner.Scan() - entityName := strings.TrimSpace(scanner.Text()) - - // X - scanner.Scan() - x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) - x := int32(x64) - if err != nil { - println("Error parsing Y of entity", entityName) - return - } - - // Y - scanner.Scan() - y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) - y := int32(y64) - if err != nil { - println("Error parsing X of entity", entityName) - return - } - if err := scanner.Err(); err != nil { - println("Error parsing entity, error: ", err.Error()) - return - } - objectIndex, ok := object.ObjectGetIndex(entityName) - if !ok { - println("Missing mapping of name \"" + entityName + "\" to entity ID. Is this name defined in your gml.Init()?") - return - } - - // Set room dimensions - { - // NOTE(Jake): 2018-06-02 - // - // Probably a slow hack to get the entity size - // for building map data on-fly, but whatever! - // - inst := object.NewRawInstance(objectIndex, 0, 0, new(object.Space), -1) - baseObj := inst.BaseObject() - inst.Create() - - x := int32(x) - y := int32(y) - width := int32(baseObj.Size.X) - height := int32(baseObj.Size.Y) - - if x < room.Left { - room.Left = x - } - right := x + width - if right > room.Right { - room.Right = right - } - if y < room.Top { - room.Top = y - } - bottom := y + height - if bottom > room.Bottom { - room.Bottom = bottom + height - } - } - - basename := filepath.Base(instancePath) - filename := strings.TrimSuffix(basename, filepath.Ext(basename)) - - // Increase entity counter - { - filenameParts := strings.Split(filename, "_") - if len(filenameParts) == 3 { - id := filenameParts[len(filenameParts)-1] - count, err := strconv.ParseInt(id, 10, 64) - if err == nil { - if count > room.UserEntityCount { - username := filenameParts[len(filenameParts)-2] - if username == file.DebugUsernameFileSafe() { - room.UserEntityCount = count - } - } - } else { - println(filename, ": Skipping, Error parsing the last part (entity ID) after splitting by _") - } - } else { - println(filename, ": Expected to split into 3 parts, not", len(filenameParts)) - } - } - - room.Instances = append(room.Instances, &RoomObject{ - Filename: filename, - ObjectIndex: int32(objectIndex), - X: x, - Y: y, - }) -} +//func loadInstanceLayer(layer *RoomLayerInstance) { +// +//} func LoadRoom(name string) *Room { manager := gRoomManager @@ -148,41 +48,18 @@ func LoadRoom(name string) *Room { return res } - // Load from *.data file if it exists - //if mapDataFile, _ := loadMapFromData(name); mapDataFile != nil { - // manager.assetMap[name] = mapDataFile - // return mapDataFile - //} - - roomPath := file.AssetsDirectory + "/room/" + name + // Load room + start := time.Now() - // Read entities - instancePathList := make([]string, 0, 1000) - err := filepath.Walk(roomPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) - return err - } - if info.IsDir() { - return nil - } - instancePathList = append(instancePathList, path) - return nil - }) + // Load from *.data file if it exists + /*room, err := loadRoomFromDataFile(name) if err != nil { panic(err) - } - - // - room := new(Room) - room.Filepath = roomPath - room.Instances = make([]*RoomObject, 0, len(instancePathList)) - start := time.Now() - for _, instance := range instancePathList { - room.readInstance(instance) - } + }*/ + room := loadRoomFromDirectoryFiles(name) elapsed := time.Since(start) println("Room \"" + name + "\" took " + elapsed.String() + " to load.") + manager.assetMap[name] = room // NOTE(Jake): 2018-05-29 @@ -192,7 +69,335 @@ func LoadRoom(name string) *Room { // // Write out *.data file (for browsers / fast client loading) // - room.DebugWriteDataFile(roomPath) + room.DebugWriteDataFile(room.Filepath()) + + return room +} + +func loadRoomFromDirectoryFiles(name string) *Room { + // + room := new(Room) + room.Config = new(RoomConfig) + // NOTE(Jake): 2018-07-21 + // + // I might want UUID to be an actual UUID in the future + // however can't do this yet as the folder name needs to be the room + // name currently. + // + room.Config.UUID = name + room.Config.Name = name + //room.Instances = make([]*RoomObject, 0, len(instancePathList)) + roomPath := room.Filepath() + { + // Read config + configPath := roomPath + "/config.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + bytes, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + } + if err := json.Unmarshal(bytes, room.Config); err != nil { + panic("Error unmarshalling load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + } + } + { + // Read layer directories + var layerPathList []string + err := filepath.Walk(roomPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + if !info.IsDir() { + // Skip files + return nil + } + if path == roomPath { + // Skip self + return nil + } + layerPathList = append(layerPathList, path) + return nil + }) + if err != nil { + panic(err) + } + // Load each layer config + for _, pathStr := range layerPathList { + configPath := pathStr + "/config.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + bytesData, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + layerConfig := new(RoomLayerConfig) + if err := json.Unmarshal(bytesData, layerConfig); err != nil { + panic("Error unmarshalling load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + //fmt.Printf("%v", layerConfig.String()) + switch layerConfig.Kind { + case RoomLayerKind_Instance: + layer := new(RoomLayerInstance) + layer.Config = layerConfig + layerPath := roomPath + "/" + layer.Config.UUID + + // Read instance files + var pathList []string + err := filepath.Walk(layerPath, func(pathStr string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + // Skip directories and only get *.txt files + if info.IsDir() || + path.Ext(pathStr) != ".txt" { + return nil + } + pathList = append(pathList, pathStr) + return nil + }) + if err != nil { + panic(err) + } + + // Read each individual file + for _, path := range pathList { + //println("Loading ", instancePath, "...") + instanceFileData, err := file.OpenFile(path) + if err != nil { + panic("Unable to find map entity file: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(instanceFileData) + instanceFileData.Close() + if err != nil { + panic("Unable to find map entity file: Read all: " + err.Error()) + } + bytesReader := bytes.NewReader(bytesData) + scanner := bufio.NewScanner(bytesReader) + + // Entity name + scanner.Scan() + entityName := strings.TrimSpace(scanner.Text()) + + // X + scanner.Scan() + x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + x := int32(x64) + if err != nil { + println("Error parsing Y of entity", entityName) + continue + } + + // Y + scanner.Scan() + y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + y := int32(y64) + if err != nil { + println("Error parsing X of entity", entityName) + continue + } + if err := scanner.Err(); err != nil { + println("Error parsing entity, error: ", err.Error()) + continue + } + objectIndex, ok := object.ObjectGetIndex(entityName) + if !ok { + println("Missing mapping of name \"" + entityName + "\" to entity ID. Is this name defined in your gml.Init()?") + continue + } + + // Set room dimensions + { + // NOTE(Jake): 2018-06-02 + // + // Probably a slow hack to get the entity size + // for building map data on-fly, but whatever! + // + inst := object.NewRawInstance(objectIndex, 0, 0, 0, new(space.Space), -1) + baseObj := inst.BaseObject() + inst.Create() + + x := int32(x) + y := int32(y) + width := int32(baseObj.Size.X) + height := int32(baseObj.Size.Y) + + if x < room.Left { + room.Left = x + } + if right := x + width; right > room.Right { + room.Right = right + } + if y < room.Top { + room.Top = y + } + if bottom := y + height; bottom > room.Bottom { + room.Bottom = bottom + height + } + } + + basename := filepath.Base(path) + uuid := strings.TrimSuffix(basename, filepath.Ext(basename)) + + layer.Instances = append(layer.Instances, &RoomObject{ + UUID: uuid, + ObjectIndex: int32(objectIndex), + X: x, + Y: y, + }) + } + + room.InstanceLayers = append(room.InstanceLayers, layer) + case RoomLayerKind_Background: + layer := new(RoomLayerBackground) + layerPath := roomPath + "/" + layerConfig.UUID + + configPath := layerPath + "/background.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + if err := json.Unmarshal(bytesData, layer); err != nil { + panic("Error unmarshalling load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + // NOTE(Jake): 2018-07-29 + // + // "Config" data should ideally not be written into the above struct + // so we're just ensuring here that config.json is the "source of truth" + // for this data. + // + layer.Config = layerConfig + room.BackgroundLayers = append(room.BackgroundLayers, layer) + case RoomLayerKind_Sprite: + layer := new(RoomLayerSprite) + layer.Config = layerConfig + layerPath := roomPath + "/" + layer.Config.UUID + + // Read instance files + var pathList []string + err := filepath.Walk(layerPath, func(pathStr string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + // Skip directories and only get *.txt files + if info.IsDir() || + path.Ext(pathStr) != ".txt" { + return nil + } + pathList = append(pathList, pathStr) + return nil + }) + if err != nil { + panic(err) + } + + // Read each individual file + for _, path := range pathList { + //println("Loading ", instancePath, "...") + instanceFileData, err := file.OpenFile(path) + if err != nil { + panic("Unable to find sprite object file: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(instanceFileData) + instanceFileData.Close() + if err != nil { + panic("Unable to find sprite object file: Read all: " + err.Error()) + } + bytesReader := bytes.NewReader(bytesData) + scanner := bufio.NewScanner(bytesReader) + + // Entity name + scanner.Scan() + spriteName := strings.TrimSpace(scanner.Text()) + + // X + scanner.Scan() + x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + x := int32(x64) + if err != nil { + println("Error parsing Y of sprite object", spriteName) + continue + } + + // Y + scanner.Scan() + y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + y := int32(y64) + if err != nil { + println("Error parsing X of sprite object", spriteName) + continue + } + if err := scanner.Err(); err != nil { + println("Error parsing sprite object, error: ", err.Error()) + continue + } + + // Set room dimensions + { + spr := sprite.LoadSprite(spriteName) + if spr == nil { + println("Error loading sprite sprite \"", spriteName, "\" error: ", err.Error()) + continue + } + x := int32(x) + y := int32(y) + width := int32(spr.Size().X) + height := int32(spr.Size().Y) + + if x < room.Left { + room.Left = x + } + if right := x + width; right > room.Right { + room.Right = right + } + if y < room.Top { + room.Top = y + } + if bottom := y + height; bottom > room.Bottom { + room.Bottom = bottom + height + } + } + + basename := filepath.Base(path) + uuid := strings.TrimSuffix(basename, filepath.Ext(basename)) + layer.Sprites = append(layer.Sprites, &RoomSpriteObject{ + UUID: uuid, + SpriteName: spriteName, + X: x, + Y: y, + }) + } + + room.SpriteLayers = append(room.SpriteLayers, layer) + default: + panic("Unknown or unhandled layer kind: " + layerConfig.Kind.String() + "(" + strconv.Itoa(int(layerConfig.Kind)) + ")") + } + } + } + { + // Sort each layer + sort.Slice(room.InstanceLayers, func(i, j int) bool { + return room.InstanceLayers[i].Config.Order < room.InstanceLayers[j].Config.Order + }) + sort.Slice(room.BackgroundLayers, func(i, j int) bool { + return room.BackgroundLayers[i].Config.Order < room.BackgroundLayers[j].Config.Order + }) + } return room } diff --git a/gml/internal/room/room_object.pb.go b/gml/internal/room/room_object.pb.go index 55bfcfe..6946c04 100644 --- a/gml/internal/room/room_object.pb.go +++ b/gml/internal/room/room_object.pb.go @@ -15,7 +15,7 @@ var _ = fmt.Errorf var _ = math.Inf type RoomObject struct { - Filename string `protobuf:"bytes,1,opt,name=Filename,proto3" json:"Filename,omitempty"` + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` ObjectIndex int32 `protobuf:"varint,2,opt,name=ObjectIndex,proto3" json:"ObjectIndex,omitempty"` X int32 `protobuf:"varint,3,opt,name=X,proto3" json:"X,omitempty"` Y int32 `protobuf:"varint,4,opt,name=Y,proto3" json:"Y,omitempty"` @@ -26,9 +26,9 @@ func (m *RoomObject) String() string { return proto.CompactTextString func (*RoomObject) ProtoMessage() {} func (*RoomObject) Descriptor() ([]byte, []int) { return fileDescriptorRoomObject, []int{0} } -func (m *RoomObject) GetFilename() string { +func (m *RoomObject) GetUUID() string { if m != nil { - return m.Filename + return m.UUID } return "" } @@ -72,11 +72,11 @@ func (m *RoomObject) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Filename) > 0 { + if len(m.UUID) > 0 { dAtA[i] = 0xa i++ - i = encodeVarintRoomObject(dAtA, i, uint64(len(m.Filename))) - i += copy(dAtA[i:], m.Filename) + i = encodeVarintRoomObject(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) } if m.ObjectIndex != 0 { dAtA[i] = 0x10 @@ -108,7 +108,7 @@ func encodeVarintRoomObject(dAtA []byte, offset int, v uint64) int { func (m *RoomObject) Size() (n int) { var l int _ = l - l = len(m.Filename) + l = len(m.UUID) if l > 0 { n += 1 + l + sovRoomObject(uint64(l)) } @@ -168,7 +168,7 @@ func (m *RoomObject) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Filename", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -193,7 +193,7 @@ func (m *RoomObject) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Filename = string(dAtA[iNdEx:postIndex]) + m.UUID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 0 { @@ -381,14 +381,14 @@ var ( func init() { proto.RegisterFile("room_object.proto", fileDescriptorRoomObject) } var fileDescriptorRoomObject = []byte{ - // 141 bytes of a gzipped FileDescriptorProto + // 137 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0xca, 0xcf, 0xcf, 0x8d, 0xcf, 0x4f, 0xca, 0x4a, 0x4d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, - 0x09, 0x29, 0xa5, 0x71, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0xfa, 0x83, 0x65, 0x84, 0xa4, 0xb8, 0x38, - 0xdc, 0x32, 0x73, 0x52, 0xf3, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xe0, - 0x7c, 0x21, 0x05, 0x2e, 0x6e, 0x88, 0x2a, 0xcf, 0xbc, 0x94, 0xd4, 0x0a, 0x09, 0x26, 0x05, 0x46, - 0x0d, 0xd6, 0x20, 0x64, 0x21, 0x21, 0x1e, 0x2e, 0xc6, 0x08, 0x09, 0x66, 0xb0, 0x38, 0x63, 0x04, - 0x88, 0x17, 0x29, 0xc1, 0x02, 0xe1, 0x45, 0x3a, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, - 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x9d, 0x61, 0x0c, - 0x08, 0x00, 0x00, 0xff, 0xff, 0x11, 0x27, 0x75, 0xff, 0x9b, 0x00, 0x00, 0x00, + 0x09, 0x29, 0xc5, 0x71, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0xfa, 0x83, 0x65, 0x84, 0x84, 0xb8, 0x58, + 0x42, 0x43, 0x3d, 0x5d, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x21, 0x05, 0x2e, + 0x6e, 0x88, 0xac, 0x67, 0x5e, 0x4a, 0x6a, 0x85, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0xb2, + 0x90, 0x10, 0x0f, 0x17, 0x63, 0x84, 0x04, 0x33, 0x58, 0x9c, 0x31, 0x02, 0xc4, 0x8b, 0x94, 0x60, + 0x81, 0xf0, 0x22, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0xd6, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, + 0xff, 0x53, 0x4d, 0x2c, 0x53, 0x93, 0x00, 0x00, 0x00, } diff --git a/gml/internal/room/room_object.proto b/gml/internal/room/room_object.proto index 8cf5545..f68b8ef 100644 --- a/gml/internal/room/room_object.proto +++ b/gml/internal/room/room_object.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package room; message RoomObject { - string Filename = 1; + string UUID = 1; int32 ObjectIndex = 2; int32 X = 3; int32 Y = 4; diff --git a/gml/internal/room/room_sprite_object.pb.go b/gml/internal/room/room_sprite_object.pb.go new file mode 100644 index 0000000..8e25e1d --- /dev/null +++ b/gml/internal/room/room_sprite_object.pb.go @@ -0,0 +1,479 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_sprite_object.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomSpriteObject struct { + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` + X int32 `protobuf:"varint,2,opt,name=X,proto3" json:"X,omitempty"` + Y int32 `protobuf:"varint,3,opt,name=Y,proto3" json:"Y,omitempty"` + Width int32 `protobuf:"varint,4,opt,name=Width,proto3" json:"Width,omitempty"` + Height int32 `protobuf:"varint,5,opt,name=Height,proto3" json:"Height,omitempty"` + SpriteName string `protobuf:"bytes,6,opt,name=SpriteName,proto3" json:"SpriteName,omitempty"` +} + +func (m *RoomSpriteObject) Reset() { *m = RoomSpriteObject{} } +func (m *RoomSpriteObject) String() string { return proto.CompactTextString(m) } +func (*RoomSpriteObject) ProtoMessage() {} +func (*RoomSpriteObject) Descriptor() ([]byte, []int) { return fileDescriptorRoomSpriteObject, []int{0} } + +func (m *RoomSpriteObject) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomSpriteObject) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *RoomSpriteObject) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func (m *RoomSpriteObject) GetWidth() int32 { + if m != nil { + return m.Width + } + return 0 +} + +func (m *RoomSpriteObject) GetHeight() int32 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RoomSpriteObject) GetSpriteName() string { + if m != nil { + return m.SpriteName + } + return "" +} + +func init() { + proto.RegisterType((*RoomSpriteObject)(nil), "room.RoomSpriteObject") +} +func (m *RoomSpriteObject) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomSpriteObject) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.UUID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if m.X != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.X)) + } + if m.Y != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Y)) + } + if m.Width != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Width)) + } + if m.Height != 0 { + dAtA[i] = 0x28 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Height)) + } + if len(m.SpriteName) > 0 { + dAtA[i] = 0x32 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(len(m.SpriteName))) + i += copy(dAtA[i:], m.SpriteName) + } + return i, nil +} + +func encodeVarintRoomSpriteObject(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomSpriteObject) Size() (n int) { + var l int + _ = l + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomSpriteObject(uint64(l)) + } + if m.X != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.X)) + } + if m.Y != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Y)) + } + if m.Width != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Width)) + } + if m.Height != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Height)) + } + l = len(m.SpriteName) + if l > 0 { + n += 1 + l + sovRoomSpriteObject(uint64(l)) + } + return n +} + +func sovRoomSpriteObject(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomSpriteObject(x uint64) (n int) { + return sovRoomSpriteObject(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomSpriteObject) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomSpriteObject: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomSpriteObject: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomSpriteObject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) + } + m.X = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.X |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) + } + m.Y = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Y |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Width", wireType) + } + m.Width = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Width |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpriteName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomSpriteObject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpriteName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomSpriteObject(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomSpriteObject + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomSpriteObject(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomSpriteObject + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomSpriteObject(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomSpriteObject = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomSpriteObject = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_sprite_object.proto", fileDescriptorRoomSpriteObject) } + +var fileDescriptorRoomSpriteObject = []byte{ + // 178 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x28, 0xca, 0xcf, 0xcf, + 0x8d, 0x2f, 0x2e, 0x28, 0xca, 0x2c, 0x49, 0x8d, 0xcf, 0x4f, 0xca, 0x4a, 0x4d, 0x2e, 0xd1, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xc9, 0x28, 0x4d, 0x60, 0xe4, 0x12, 0x08, 0xca, 0xcf, + 0xcf, 0x0d, 0x06, 0xab, 0xf0, 0x07, 0x2b, 0x10, 0x12, 0xe2, 0x62, 0x09, 0x0d, 0xf5, 0x74, 0x91, + 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x78, 0xb8, 0x18, 0x23, 0x24, 0x98, 0x14, + 0x18, 0x35, 0x58, 0x83, 0x18, 0x23, 0x40, 0xbc, 0x48, 0x09, 0x66, 0x08, 0x2f, 0x52, 0x48, 0x84, + 0x8b, 0x35, 0x3c, 0x33, 0xa5, 0x24, 0x43, 0x82, 0x05, 0x2c, 0x02, 0xe1, 0x08, 0x89, 0x71, 0xb1, + 0x79, 0xa4, 0x66, 0xa6, 0x67, 0x94, 0x48, 0xb0, 0x82, 0x85, 0xa1, 0x3c, 0x21, 0x39, 0x2e, 0x2e, + 0x88, 0x6d, 0x7e, 0x89, 0xb9, 0xa9, 0x12, 0x6c, 0x60, 0x3b, 0x90, 0x44, 0x9c, 0x04, 0x4e, 0x3c, + 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, + 0xd8, 0xc0, 0x2e, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xca, 0x0e, 0xa9, 0x1b, 0xcd, 0x00, + 0x00, 0x00, +} diff --git a/gml/internal/room/room_sprite_object.proto b/gml/internal/room/room_sprite_object.proto new file mode 100644 index 0000000..af7d4d1 --- /dev/null +++ b/gml/internal/room/room_sprite_object.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package room; + +message RoomSpriteObject { + string UUID = 1; + int32 X = 2; + int32 Y = 3; + int32 Width = 4; + int32 Height = 5; + string SpriteName = 6; +} diff --git a/gml/internal/space/space.go b/gml/internal/space/space.go new file mode 100644 index 0000000..9e80e29 --- /dev/null +++ b/gml/internal/space/space.go @@ -0,0 +1,16 @@ +package space + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +type Space struct { + solid bool + geom.Rect +} + +func (record *Space) SetSolid(isSolid bool) { + record.solid = isSolid +} + +func (record *Space) Solid() bool { return record.solid } diff --git a/gml/internal/object/space.go b/gml/internal/space/space_bucket.go similarity index 82% rename from gml/internal/object/space.go rename to gml/internal/space/space_bucket.go index fbabbed..395204d 100644 --- a/gml/internal/object/space.go +++ b/gml/internal/space/space_bucket.go @@ -1,27 +1,9 @@ -package object - -import ( - m "github.com/silbinarywolf/gml-go/gml/internal/math" -) +package space const ( - spaceBucketSize = 256 + spaceBucketSize = 128 ) -type SpaceObject struct { - *Space - spaceIndex int -} - -func (space *SpaceObject) SpaceIndex() int { - return space.spaceIndex -} - -type Space struct { - m.Vec // Position (contains X,Y) - Size m.Size // Size (X,Y) -} - type SpaceBucketArray struct { length int buckets []*SpaceBucket @@ -61,6 +43,7 @@ func (array *SpaceBucketArray) Get(index int) *Space { } func (array *SpaceBucketArray) Remove(index int) { + //log.Printf("DEBUG: Removing from bucket %d, index %d. (array.length = %d)", index/spaceBucketSize, index%spaceBucketSize, array.length) bucket := array.buckets[index/spaceBucketSize] bucketIndex := index % spaceBucketSize if !bucket.used[bucketIndex] { @@ -83,10 +66,10 @@ func (array *SpaceBucketArray) Buckets() []*SpaceBucket { // In fact, checking "IsUsed" seems to be an almost-free operation when added // to the end of the if-statement checking collisions // -func (array *SpaceBucketArray) IsUsed(index int) bool { - bucket := array.buckets[index/spaceBucketSize] - return bucket.used[index%spaceBucketSize] -} +//func (array *SpaceBucketArray) IsUsed(index int) bool { +// bucket := array.buckets[index/spaceBucketSize] +// return bucket.used[index%spaceBucketSize] +//} func (array *SpaceBucketArray) Len() int { return array.length diff --git a/gml/internal/space/space_object.go b/gml/internal/space/space_object.go new file mode 100644 index 0000000..0ab010d --- /dev/null +++ b/gml/internal/space/space_object.go @@ -0,0 +1,18 @@ +package space + +type SpaceObject struct { + *Space + spaceIndex int +} + +func (record *SpaceObject) Init(space *Space, spaceIndex int) { + if record.Space != nil { + panic("Can only initialize SpaceObject once.") + } + record.Space = space + record.spaceIndex = spaceIndex +} + +func (space *SpaceObject) SpaceIndex() int { + return space.spaceIndex +} diff --git a/gml/internal/sprite/sprite.go b/gml/internal/sprite/sprite.go index fd7b71d..2664519 100644 --- a/gml/internal/sprite/sprite.go +++ b/gml/internal/sprite/sprite.go @@ -1,18 +1,18 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) type Sprite struct { name string frames []SpriteFrame - size math.Size + size geom.Size imageSpeed float64 } func (spr *Sprite) Name() string { return spr.name } -func (spr *Sprite) Size() math.Size { return spr.size } +func (spr *Sprite) Size() geom.Size { return spr.size } /*func (spr *Sprite) GetFrame(index int) *SpriteFrame { return &spr.frames[index] @@ -35,7 +35,7 @@ func newSprite(name string, frames []SpriteFrame, config spriteConfig) *Sprite { height = frameHeight } } - spr.size = math.Size{ + spr.size = geom.Size{ X: int32(width), Y: int32(height), } diff --git a/gml/internal/sprite/sprite_asset.go b/gml/internal/sprite/sprite_asset.go index 9c7c531..bab0b66 100644 --- a/gml/internal/sprite/sprite_asset.go +++ b/gml/internal/sprite/sprite_asset.go @@ -1,17 +1,17 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) type spriteAssetFrame struct { - Size math.Vec + Size geom.Vec Data []byte } type spriteAsset struct { Name string - Size math.Vec + Size geom.Vec ImageSpeed float64 Frames []spriteAssetFrame } @@ -32,7 +32,7 @@ func newSpriteAsset(name string, frames []spriteAssetFrame, config spriteConfig) height = frameHeight } } - spr.Size = math.Vec{ + spr.Size = geom.Vec{ X: width, Y: height, } diff --git a/gml/internal/sprite/sprite_frame_nonheadless.go b/gml/internal/sprite/sprite_frame_nonheadless.go index 7687281..f945891 100644 --- a/gml/internal/sprite/sprite_frame_nonheadless.go +++ b/gml/internal/sprite/sprite_frame_nonheadless.go @@ -5,7 +5,6 @@ package sprite import ( "bytes" "image" - "math" "github.com/hajimehoshi/ebiten" ) @@ -36,7 +35,7 @@ func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { // This is called by draw_nonheadless.go in the parent package // so that it can draw the image. // -func GetRawFrame(spr *Sprite, index float64) *ebiten.Image { +func GetRawFrame(spr *Sprite, index int) *ebiten.Image { // NOTE(Jake): 2018-06-17 // // Golang does not "cast", it uses type conversion, which means @@ -45,6 +44,5 @@ func GetRawFrame(spr *Sprite, index float64) *ebiten.Image { // // https://stackoverflow.com/questions/35115868/how-to-round-to-nearest-int-when-casting-float-to-int-in-go // - i := int(math.Floor(index)) - return spr.frames[i].image + return spr.frames[index].image } diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index b33438d..c0ef272 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -15,7 +15,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/silbinarywolf/gml-go/gml/internal/file" - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) var ( @@ -108,7 +108,7 @@ func debugWriteSprite(name string) { } imageSize := image.Bounds().Size() frame := spriteAssetFrame{ - Size: math.V(float64(imageSize.X), float64(imageSize.Y)), + Size: geom.Vec{float64(imageSize.X), float64(imageSize.Y)}, Data: buf.Bytes(), } frames = append(frames, frame) diff --git a/gml/internal/sprite/sprite_state.go b/gml/internal/sprite/sprite_state.go index bbacc6f..673735a 100644 --- a/gml/internal/sprite/sprite_state.go +++ b/gml/internal/sprite/sprite_state.go @@ -1,12 +1,12 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) type SpriteState struct { sprite *Sprite - ImageScale math.Vec + ImageScale geom.Vec imageIndex float64 } @@ -16,8 +16,10 @@ func (state *SpriteState) ImageNumber() float64 { return float64(len(state.sprit func (state *SpriteState) ImageSpeed() float64 { return state.sprite.imageSpeed } func (state *SpriteState) SetSprite(sprite *Sprite) { - state.sprite = sprite - state.imageIndex = 0 + if state.sprite != sprite { + state.sprite = sprite + state.imageIndex = 0 + } } func (state *SpriteState) SetImageIndex(imageIndex float64) { diff --git a/gml/internal/timegml/now_js.go b/gml/internal/timegml/now_js.go new file mode 100644 index 0000000..0d83d53 --- /dev/null +++ b/gml/internal/timegml/now_js.go @@ -0,0 +1,14 @@ +// +build js + +package timegml + +import ( + "time" + + "github.com/gopherjs/gopherwasm/js" +) + +func Now() int64 { + // time.Now() is not reliable until GopherJS supports performance.now(). + return int64(js.Global().Get("performance").Call("now").Float() * float64(time.Millisecond)) +} diff --git a/gml/internal/timegml/now_notjs.go b/gml/internal/timegml/now_notjs.go new file mode 100644 index 0000000..1af04a4 --- /dev/null +++ b/gml/internal/timegml/now_notjs.go @@ -0,0 +1,11 @@ +// +build !js + +package timegml + +import ( + "time" +) + +func Now() int64 { + return time.Now().UnixNano() +} diff --git a/gml/internal/user/user.go b/gml/internal/user/user.go new file mode 100644 index 0000000..e19d1fc --- /dev/null +++ b/gml/internal/user/user.go @@ -0,0 +1,17 @@ +package user + +import ( + "os" + "runtime" +) + +func HomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} diff --git a/gml/keyboard_vk.go b/gml/keyboard_vk.go index df33902..f3cfc51 100644 --- a/gml/keyboard_vk.go +++ b/gml/keyboard_vk.go @@ -45,6 +45,7 @@ const ( VkNumpad7 VkNumpad8 VkNumpad9 + VkNumpadEnter VkMultiply // multiply key on the numeric keypad VkDivide // divide key on the numeric keypad VkAdd // key on the numeric keypad diff --git a/gml/keyboard_vk_nonheadless.go b/gml/keyboard_vk_nonheadless.go index d2bdb1f..28efc7a 100644 --- a/gml/keyboard_vk_nonheadless.go +++ b/gml/keyboard_vk_nonheadless.go @@ -41,16 +41,17 @@ var keyboardVkToEbiten = []ebiten.Key{ VkF10: ebiten.KeyF10, VkF11: ebiten.KeyF11, VkF12: ebiten.KeyF11, - VkNumpad0: ebiten.Key0, - VkNumpad1: ebiten.Key1, - VkNumpad2: ebiten.Key2, - VkNumpad3: ebiten.Key3, - VkNumpad4: ebiten.Key4, - VkNumpad5: ebiten.Key5, - VkNumpad6: ebiten.Key6, - VkNumpad7: ebiten.Key7, - VkNumpad8: ebiten.Key8, - VkNumpad9: ebiten.Key9, + VkNumpad0: ebiten.KeyKP0, + VkNumpad1: ebiten.KeyKP1, + VkNumpad2: ebiten.KeyKP2, + VkNumpad3: ebiten.KeyKP3, + VkNumpad4: ebiten.KeyKP4, + VkNumpad5: ebiten.KeyKP5, + VkNumpad6: ebiten.KeyKP6, + VkNumpad7: ebiten.KeyKP7, + VkNumpad8: ebiten.KeyKP8, + VkNumpad9: ebiten.KeyKP9, + VkNumpadEnter: ebiten.KeyKPEnter, VkMultiply: 0, // multiply key on the numeric keypad VkDivide: 0, // divide key on the numeric keypad VkAdd: 0, // key on the numeric keypad diff --git a/gml/main.go b/gml/main.go index 166424b..9650d8b 100644 --- a/gml/main.go +++ b/gml/main.go @@ -2,6 +2,7 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" + "github.com/silbinarywolf/gml-go/gml/internal/timegml" ) type mainFunctions struct { @@ -12,18 +13,23 @@ type mainFunctions struct { var gMainFunctions *mainFunctions = new(mainFunctions) var ( - gWidth int - gHeight int + gWindowWidth int + gWindowHeight int + gWindowScale float64 // Window scale + //lastFrameTime int64 ) func update() error { + frameStartTime := timegml.Now() + //frameOffset := timegml.Now() - lastFrameTime sprite.DebugWatch() keyboardUpdate() keyboardStringUpdate() mouseUpdate() if EditorIsActive() { - EditorUpdate() - EditorDraw() + cameraSetActive(0) + editorUpdate() + cameraClearActive() } else { gMainFunctions.update() } @@ -32,15 +38,22 @@ func update() error { gMainFunctions.gameStart() g_game.hasGameRestarted = false } + gState.frameBudgetNanosecondsUsed = timegml.Now() - frameStartTime + //gState.frameBudgetNanosecondsUsed += frameOffset + //lastFrameTime = timegml.Now() return nil } func windowWidth() int { - return gWidth + return gWindowWidth } func windowHeight() int { - return gHeight + return gWindowHeight +} + +func windowScale() float64 { + return gWindowScale } func Update(animationUpdate bool) { diff --git a/gml/main_headless.go b/gml/main_headless.go index 81cb583..f03e600 100644 --- a/gml/main_headless.go +++ b/gml/main_headless.go @@ -8,9 +8,10 @@ func Draw() { // no-op } -func Run(gameStartFunc func(), updateFunc func(), width int, height int, title string) { - gWidth = width - gHeight = height +func Run(gameStartFunc func(), updateFunc func(), width int, height int, scale float64, title string) { + gWindowWidth = width + gWindowHeight = height + gWindowScale = scale gMainFunctions.gameStart = gameStartFunc gMainFunctions.update = updateFunc @@ -22,6 +23,16 @@ func Run(gameStartFunc func(), updateFunc func(), width int, height int, title s select { case <-tick: updateFunc() + // todo(Jake): 2018-07-10 + // + // Should improve this to be more robust! + // - https://trello.com/c/1RUkMGOx/55-improve-clock-for-headless-mode + // + // However, I'm deferring this effort as the way Ebiten works might change + // in the future: + // - https://github.com/hajimehoshi/ebiten/issues/605 + // + // time.Sleep(time.Second / 60) } } } diff --git a/gml/main_nonheadless.go b/gml/main_nonheadless.go index 6c3944a..afdc303 100644 --- a/gml/main_nonheadless.go +++ b/gml/main_nonheadless.go @@ -19,14 +19,15 @@ func Draw() { gState.draw() } -func Run(gameStartFunc func(), updateFunc func(), width int, height int, title string) { - gWidth = width - gHeight = height +func Run(gameStartFunc func(), updateFunc func(), width int, height int, scale float64, title string) { + gWindowWidth = width + gWindowHeight = height + gWindowScale = scale gMainFunctions.gameStart = gameStartFunc gMainFunctions.update = updateFunc gMainFunctions.gameStart() ebiten.SetRunnableInBackground(true) - ebiten.Run(updateEbiten, width, height, 2, title) + ebiten.Run(updateEbiten, width, height, scale, title) } diff --git a/gml/math.go b/gml/math.go deleted file mode 100644 index 88e39d5..0000000 --- a/gml/math.go +++ /dev/null @@ -1,13 +0,0 @@ -package gml - -import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" -) - -type Vec = math.Vec - -type Size = math.Size - -func V(x float64, y float64) math.Vec { - return math.Vec{X: x, Y: y} -} diff --git a/gml/mouse_buttons_headless.go b/gml/mouse_buttons_headless.go index 777a89c..b30a4dc 100644 --- a/gml/mouse_buttons_headless.go +++ b/gml/mouse_buttons_headless.go @@ -6,5 +6,5 @@ const ( MbLeft int = iota + 0 MbRight MbMiddle - mbSize + MbSize ) diff --git a/gml/mouse_buttons_nonheadless.go b/gml/mouse_buttons_nonheadless.go index 36b1b9a..bc967ff 100644 --- a/gml/mouse_buttons_nonheadless.go +++ b/gml/mouse_buttons_nonheadless.go @@ -8,5 +8,5 @@ const ( MbLeft int = iota + int(ebiten.MouseButtonLeft) MbRight = int(ebiten.MouseButtonRight) MbMiddle = int(ebiten.MouseButtonMiddle) - mbSize + MbSize = int(ebiten.MouseButtonMiddle) + 1 ) diff --git a/gml/mouse_headless.go b/gml/mouse_headless.go index 04df0f4..8f3e2a6 100644 --- a/gml/mouse_headless.go +++ b/gml/mouse_headless.go @@ -3,7 +3,7 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) func MouseCheckButton(button int) bool { @@ -15,11 +15,15 @@ func MouseCheckPressed(button int) bool { } func MousePosition() Vec { - return math.V(0, 0) + return geom.Vec{} } -func mousePosition() math.Vec { - return math.V(0, 0) +func mousePosition() geom.Vec { + return geom.Vec{} +} + +func mouseScreenPosition() Vec { + return geom.Vec{} } func mouseUpdate() { diff --git a/gml/mouse_nonheadless.go b/gml/mouse_nonheadless.go index e02a4ca..187d044 100644 --- a/gml/mouse_nonheadless.go +++ b/gml/mouse_nonheadless.go @@ -3,14 +3,14 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" - "github.com/hajimehoshi/ebiten" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) var ( - pressingMouseButtonLastFrame [mbSize]bool - _mousePos math.Vec + mouseButtonPress [MbSize]int // this array is reset every frame + pressingMouseButtonLastFrame [MbSize]bool + _mousePos geom.Vec ) func MouseCheckButton(button int) bool { @@ -18,7 +18,8 @@ func MouseCheckButton(button int) bool { } func MouseCheckPressed(button int) bool { - isHeld := MouseCheckButton(button) + return mouseButtonPress[button] == 1 + /*isHeld := MouseCheckButton(button) if !isHeld { pressingMouseButtonLastFrame[button] = false } @@ -28,30 +29,36 @@ func MouseCheckPressed(button int) bool { if isHeld { pressingMouseButtonLastFrame[button] = true } - return isHeld + return isHeld*/ } -func MousePosition() Vec { - return mousePosition() +func MousePosition() geom.Vec { + return _mousePos } -/*func MouseX() float64 { - x, _ := ebiten.CursorPosition() - return float64(x) +// Get the mouse position relative to the window +func mouseScreenPosition() geom.Vec { + x, y := ebiten.CursorPosition() + return geom.Vec{float64(x), float64(y)} } -func MouseY() float64 { - _, y := ebiten.CursorPosition() - return float64(y) +// +// NOTE(Jake): 2018-07-10 +// +// Ebiten doesn't have mouseWheel() support on a stable version yet and +// it doesn't support browser mouse wheel. +// - https://github.com/hajimehoshi/ebiten/issues/630 +// +// I'll look into this later! +// +/*func mouseWheel() geom.Vec { + xoff, yoff := ebiten.MouseWheel() + return geom.V(xoff, yoff) }*/ -func mousePosition() math.Vec { - return _mousePos -} - func mouseUpdate() { x, y := ebiten.CursorPosition() - newPos := math.V(float64(x), float64(y)) + newPos := geom.Vec{float64(x), float64(y)} // NOTE(Jake): 2018-06-09 // @@ -64,11 +71,20 @@ func mouseUpdate() { // // This is future-me's problem though! // - cam := &cameraList[0] - newPos.X += cam.X - newPos.Y += cam.Y + viewPos := CameraGetViewPos(0) + newPos.X += viewPos.X + newPos.Y += viewPos.Y _mousePos = newPos + + // Add code to check mouse inputs + for btn := MbLeft; btn < MbSize; btn++ { + if MouseCheckButton(btn) { + mouseButtonPress[btn]++ + } else { + mouseButtonPress[btn] = 0 + } + } } //mouse_check_button_pressed diff --git a/gml/main_test.go b/gml/object_test.go similarity index 82% rename from gml/main_test.go rename to gml/object_test.go index 615e4f4..c0fbdc1 100644 --- a/gml/main_test.go +++ b/gml/object_test.go @@ -17,15 +17,11 @@ func (_ *DummyPlayer) ObjectName() string { return "DummyPlayer" } func (inst *DummyPlayer) Create() { inst.Size.X = 32 inst.Size.Y = 32 + inst.SetSolid(true) } func (_ *DummyPlayer) Update() {} -func (_ *DummyPlayer) Draw() {} +func (_ *DummyPlayer) Destroy() {} -func init() { - // Setup - ObjectInitTypes([]ObjectType{ - ObjDummyPlayer: new(DummyPlayer), - }) -} +func (_ *DummyPlayer) Draw() {} diff --git a/gml/room_editor.go b/gml/room_editor.go index 9c4eb6c..c6bc883 100644 --- a/gml/room_editor.go +++ b/gml/room_editor.go @@ -2,16 +2,13 @@ package gml -import ( - m "github.com/silbinarywolf/gml-go/gml/internal/math" - "github.com/silbinarywolf/gml-go/gml/internal/object" -) +import "github.com/silbinarywolf/gml-go/gml/internal/room" func roomEditorUsername() string { return "" } -func EditorInit() { +func EditorInit(exitEditorFunc func(room *room.Room)) { } func EditorIsInitialized() bool { @@ -25,18 +22,8 @@ func EditorIsActive() bool { func EditorSetRoom(room *Room) { } -func EditorAddInstance(pos m.Vec, objectIndex object.ObjectIndex) *RoomObject { - return nil +func editorUpdate() { } -func EditorRemoveInstance(index int) { -} - -func EditorUpdate() { -} - -func EditorDraw() { -} - -func EditorSave() { +func editorDraw() { } diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 23644fa..0fde2cb 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -4,32 +4,103 @@ package gml import ( "bufio" + "encoding/json" + "fmt" "image/color" + "io/ioutil" "math" "os" + "path/filepath" + "sort" "strconv" "strings" + "time" "github.com/silbinarywolf/gml-go/gml/internal/file" - m "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/reditor" + "github.com/silbinarywolf/gml-go/gml/internal/room" + "github.com/silbinarywolf/gml-go/gml/internal/space" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" + "github.com/silbinarywolf/gml-go/gml/internal/user" ) +// +// NOTE(Jake): 2018-07-10 +// +// I'd like to split this out into its own package but before I can +// do that I need to figure out: +// - How I'll access Mouse / Keyboard inputs +// - Split out camera.go into an internal package +// +// + +//type roomInfo struct { +// *room.Room +//} + type roomEditor struct { - initialized bool - editingRoom *Room + initialized bool + editingRoom *room.Room + editingLayer room.RoomLayer + objectIndexToData []object.ObjectType + spriteList []*sprite.Sprite + spriteMap map[string]*sprite.Sprite + + camPos Vec + lastMousePos Vec + lastMouseScreenPos Vec - camPos Vec - lastMousePos Vec + menuOpened reditor.Menu + menuLayerKind room.RoomLayerKind + hasUnsavedChanges bool - isEntityMenuOpen bool entityMenuFiltered []object.ObjectType - objectSelected object.ObjectType + spriteMenuFiltered []*sprite.Sprite + + objectSelected object.ObjectType + spriteSelected *sprite.Sprite + + mouseHold [MbSize]bool + gridEnabled bool + + cameraStateBeforeEnteringEditingMode cameraManager + + // Callbacks + exitEditorFunc func(room *room.Room) + + // + statusText string + statusTimer time.Time + + // Constants + roomDirectory string + + // Backing / temporary pools + tempLayers []room.RoomLayer } +type roomEditorConfig struct { + RoomSelected string `json:"RoomSelected,omitempty"` + LayerSelected string `json:"LayerSelected,omitempty"` + BrushSelected string `json:"BrushSelected,omitempty"` +} + +var ( + gRoomEditor *roomEditor +) + func newRoomEditor() *roomEditor { - // Create stub instances to use for rendering map view + // NOTE(Jake): 2018-07-11 + // + // Create stub instances to use for rendering map view. + // + // This provides us: + // - The entity size (as set in Create()) + // - The default sprite of the object + // idToEntityData := object.IDToEntityData() objectIndexToData := make([]object.ObjectType, len(idToEntityData)) for i, obj := range idToEntityData { @@ -37,29 +108,104 @@ func newRoomEditor() *roomEditor { continue } objectIndex := obj.ObjectIndex() - inst := object.NewRawInstance(objectIndex, i, 0, new(object.Space), -1) + inst := object.NewRawInstance(objectIndex, i, 0, 0, new(space.Space), -1) inst.Create() objectIndexToData[i] = inst } + // NOTE(Jake): 2018-07-25 + // + // Load all sprites + // + var spriteList []*sprite.Sprite + spriteMap := make(map[string]*sprite.Sprite) + spritePath := file.AssetsDirectory + "/sprites" + err := filepath.Walk(spritePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + path + ": " + err.Error()) + return err + } + if !info.IsDir() { + // Skip files + return nil + } + if path == spritePath { + // Skip self + return nil + } + name := filepath.Base(path) + sprite := LoadSprite(name) + spriteList = append(spriteList, sprite) + spriteMap[sprite.Name()] = sprite + return nil + }) + if err != nil { + panic(err) + } + return &roomEditor{ - initialized: true, - //editingRoom: nil, - objectIndexToData: objectIndexToData, + initialized: true, + objectIndexToData: objectIndexToData, + //objectNameToData: objectNameToData, + spriteList: spriteList, + spriteMap: spriteMap, lastMousePos: MousePosition(), entityMenuFiltered: make([]object.ObjectType, 0, len(objectIndexToData)), + spriteMenuFiltered: make([]*sprite.Sprite, 0, len(spriteList)), + roomDirectory: file.AssetsDirectory + "/room/", + tempLayers: make([]room.RoomLayer, 0, 25), + gridEnabled: false, } } -var ( - gRoomEditor *roomEditor -) +func (editor *roomEditor) IsMenuOpen() bool { + return editor.menuOpened != reditor.MenuNone +} + +func (editor *roomEditor) calculateAndSortLayers() { + editor.tempLayers = editor.tempLayers[:0] + editor.layers() +} + +func (editor *roomEditor) MouseClearButton(mb int) { + editor.mouseHold[mb] = false +} + +func (editor *roomEditor) MouseCheckButton(mb int) bool { + return editor.mouseHold[mb] +} -func roomEditorUsername() string { - return file.DebugUsernameFileSafe() +func (editor *roomEditor) layers() []room.RoomLayer { + // NOTE(Jake): 2018-08-05 + // + // At the start of each EditorUpdate() / frame, we reset + // the tempLayers length to 0 so this will only be calculated once + // per frame. + // + if len(editor.tempLayers) > 0 { + return editor.tempLayers + } + editingRoom := editor.editingRoom + + // Put all layer types into one array and sort + layers := editor.tempLayers[:0] + for _, layer := range editingRoom.InstanceLayers { + layers = append(layers, layer) + } + for _, layer := range editingRoom.BackgroundLayers { + layers = append(layers, layer) + } + for _, layer := range editingRoom.SpriteLayers { + layers = append(layers, layer) + } + sort.Slice(layers, func(i, j int) bool { + return layers[i].GetConfig().Order < layers[j].GetConfig().Order + }) + editor.tempLayers = layers + return layers } -func roomEditorEditingRoom() *Room { +func roomEditorEditingRoom() *room.Room { return gRoomEditor.editingRoom } @@ -73,11 +219,16 @@ func snapToGrid(val float64, grid float64) float64 { return base * grid } -func EditorInit() { +func EditorInit(exitEditorFunc func(room *room.Room)) { if gRoomEditor != nil { panic("EditorInit: Room Editor is already initialized.") } gRoomEditor = newRoomEditor() + gRoomEditor.exitEditorFunc = exitEditorFunc + // TODO(Jake): 2018-07-10 + // + // Load editor font (possibly by embedding data into `reditor`?) + // } func EditorIsInitialized() bool { @@ -89,119 +240,253 @@ func EditorIsActive() bool { } func EditorSetRoom(room *Room) { - gRoomEditor.editingRoom = room + roomEditor := gRoomEditor + if roomEditor.editorChangeRoom(room) { + roomEditor.editorConfigLoad() + } +} +func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { + if roomEditor.editingRoom == room { + // If no changes + return false + } + if room == nil { + editingRoom := roomEditor.editingRoom + roomEditor.editingRoom = nil + // Reset camera settings back + *gCameraManager = roomEditor.cameraStateBeforeEnteringEditingMode + // Execute custom user-code logic + if gRoomEditor.exitEditorFunc != nil { + gRoomEditor.exitEditorFunc(editingRoom) + } + return false + } + roomEditor.editingRoom = room + roomEditor.editingLayer = nil + roomEditor.cameraStateBeforeEnteringEditingMode = *gCameraManager + roomEditor.calculateAndSortLayers() + roomEditor.calculateRoomBounds() + + // NOTE(Jake): 2018-07-09 + // + // If you move around as the player a bit then go into the + // editor. Retain the same camera position. // + roomEditor.camPos = CameraGetViewPos(0) CameraSetEnabled(0) - CameraSetViewSize(0, V(float64(windowWidth()), float64(windowHeight()))) + CameraSetViewSize(0, geom.Vec{float64(windowWidth()), float64(windowHeight())}) + CameraSetViewTarget(0, nil) + return true } -func EditorAddInstance(pos Vec, objectIndex object.ObjectIndex) *RoomObject { - room := roomEditorEditingRoom() - if room == nil { - return nil +func editorUpdate() { + roomEditor := gRoomEditor + editingRoom := roomEditor.editingRoom + if editingRoom == nil { + return } - count := room.UserEntityCount - room.UserEntityCount++ + isMenuOpen := roomEditor.IsMenuOpen() + roomEditor.calculateAndSortLayers() // reset layers / recalculate sort order lazily with layers() + canUseBrush := true + grid := geom.Vec{32, 32} - // Get unique username - username := roomEditorUsername() + // Setup mouse left, this is so we can force it to false to disable brush strokes (ie. for UI clicks) + if MouseCheckPressed(MbLeft) { + roomEditor.mouseHold[MbLeft] = true + } + if !MouseCheckButton(MbLeft) { + roomEditor.mouseHold[MbLeft] = false + } + if MouseCheckPressed(MbMiddle) { + roomEditor.mouseHold[MbMiddle] = true + } + if !MouseCheckButton(MbMiddle) { + roomEditor.mouseHold[MbMiddle] = false + } + if MouseCheckPressed(MbRight) { + roomEditor.mouseHold[MbRight] = true + } + if !MouseCheckButton(MbRight) { + roomEditor.mouseHold[MbRight] = false + } - // - //inst := roomEditor.objectIndexToData[objectIndex] - //baseObj := inst.BaseObject() - roomObj := &RoomObject{ - Filename: "entity_" + username + "_" + strconv.FormatInt(count, 10), - ObjectIndex: int32(objectIndex), - X: int32(pos.X), - Y: int32(pos.Y), + // Remove status text if time passed + if roomEditor.statusText != "" && + time.Since(roomEditor.statusTimer).Seconds() > 5 { + roomEditor.statusText = "" } - room.Instances = append(room.Instances, roomObj) - return roomObj -} -func EditorRemoveInstance(index int) { - room := roomEditorEditingRoom() + isHoldingControl := KeyboardCheck(VkControl) + if isHoldingControl { + // Enable / disable grid + if KeyboardCheckPressed(VkG) { + roomEditor.gridEnabled = !roomEditor.gridEnabled + } - // Unordered delete instance - entryBeingDeleted := room.Instances[index] - lastEntry := room.Instances[len(room.Instances)-1] - room.Instances[index] = lastEntry - room.Instances = room.Instances[:len(room.Instances)-1] + switch l := roomEditor.editingLayer.(type) { + case nil: + // no-op + if KeyboardCheckPressed(VkP) { + roomEditor.setStatusText("No layer selected. Cannot perform CTRL+P action.") + } + case *room.RoomLayerInstance: + // Open entity select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuEntity + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } - // Track deleted entities - room.DeletedInstances = append(room.DeletedInstances, entryBeingDeleted) -} + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + case *room.RoomLayerBackground: + // Open background select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuBackground + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } -func EditorUpdate() { - room := roomEditorEditingRoom() - if room == nil { - return - } - roomEditor := gRoomEditor - isMenuOpen := roomEditor.isEntityMenuOpen + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + case *room.RoomLayerSprite: + // Open background select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSprite + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } - // NOTE(Jake): 2018-06-04 - // - // A hack to set the camera context - // - cameraSetActive(0) - defer cameraClearActive() + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + default: + panic(fmt.Sprintf("CTRL+Key: Unimplemented layer type: %T", l)) + } - isHoldingControl := KeyboardCheck(VkControl) - if isHoldingControl { - // Open entity select menu - if KeyboardCheckPressed(VkP) { - roomEditor.isEntityMenuOpen = !roomEditor.isEntityMenuOpen - if roomEditor.isEntityMenuOpen { + // Exit editor + if KeyboardCheckPressed(VkR) { + roomEditor.editorChangeRoom(nil) + return + } + + // Load map + if KeyboardCheckPressed(VkL) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuLoadRoom + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + + // New map + if KeyboardCheckPressed(VkN) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuNewRoom ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone } } // Save if KeyboardCheckPressed(VkS) { - EditorSave() - println("Saved room:", room.Filepath) + editorSave() } } // Close open menus if KeyboardCheckPressed(VkEscape) { - roomEditor.isEntityMenuOpen = false + roomEditor.menuOpened = reditor.MenuNone } // Handle filtering / selection - if roomEditor.isEntityMenuOpen { - typingText := KeyboardString() - roomEditor.entityMenuFiltered = roomEditor.entityMenuFiltered[:0] - for _, obj := range roomEditor.objectIndexToData { - if obj == nil { - continue - } - name := obj.ObjectName() - hasMatch := typingText == "" || - strings.Contains(name, typingText) - if !hasMatch { - continue - } - obj.BaseObject().ImageUpdate() - roomEditor.entityMenuFiltered = append(roomEditor.entityMenuFiltered, obj) - } + switch roomEditor.menuOpened { + case reditor.MenuNewRoom, + reditor.MenuLoadRoom, + reditor.MenuNewLayer, + reditor.MenuSetOrder: + if inputSelectPressed() { + if typingText := KeyboardString(); len(typingText) > 0 { + switch roomEditor.menuOpened { + case reditor.MenuNewRoom: + // Create new room if typed + roomEditor.newRoom(typingText) + case reditor.MenuLoadRoom: + // Load room if typed + roomEditor.loadRoom(typingText) + case reditor.MenuNewLayer: + // Create new room if typed + roomEditor.newLayerAndSelected(editingRoom, typingText, roomEditor.menuLayerKind) + case reditor.MenuSetOrder: + // + layer := roomEditor.editingLayer + if layer == nil { + roomEditor.setStatusText("Cannot open set order menu when not editing any layer.") + break + } + order, err := strconv.Atoi(typingText) + if err != nil { + roomEditor.setStatusText("Cannot set order to invalid value: " + typingText) + break + } + layer.GetConfig().Order = int32(order) + sort.Slice(roomEditor.editingRoom.InstanceLayers, func(i, j int) bool { + return roomEditor.editingRoom.InstanceLayers[i].Config.Order < roomEditor.editingRoom.InstanceLayers[j].Config.Order + }) + sort.Slice(roomEditor.editingRoom.BackgroundLayers, func(i, j int) bool { + return roomEditor.editingRoom.BackgroundLayers[i].Config.Order < roomEditor.editingRoom.BackgroundLayers[j].Config.Order + }) + sort.Slice(roomEditor.editingRoom.SpriteLayers, func(i, j int) bool { + return roomEditor.editingRoom.SpriteLayers[i].Config.Order < roomEditor.editingRoom.SpriteLayers[j].Config.Order + }) + roomEditor.hasUnsavedChanges = true + default: + panic("Unhandled menu type (ie. new room, new layer, set order)") + } - // - if KeyboardCheckPressed(VkEnter) && - len(roomEditor.entityMenuFiltered) > 0 { - selectedObj := roomEditor.entityMenuFiltered[0] - roomEditor.objectSelected = selectedObj - roomEditor.isEntityMenuOpen = false + } + // Close menu + roomEditor.menuOpened = reditor.MenuNone } } if !isMenuOpen && !isHoldingControl { + camPos := &roomEditor.camPos { - // Move camera - camPos := &roomEditor.camPos + // Move camera with WASD var speed float64 = 4 if KeyboardCheck(VkShift) { speed = 8 @@ -219,261 +504,1733 @@ func EditorUpdate() { CameraSetViewPos(0, *camPos) } - lastMousePos := roomEditor.lastMousePos - roomEditor.lastMousePos = MousePosition() + lastMouseScreenPos := roomEditor.lastMouseScreenPos + roomEditor.lastMouseScreenPos = mouseScreenPosition() - grid := V(32, 32) + { + // Move camera with middle mouse + if MouseCheckButton(MbMiddle) { + mouseDistMoved := mouseScreenPosition() + mouseDistMoved.X -= lastMouseScreenPos.X + mouseDistMoved.Y -= lastMouseScreenPos.Y + sensitivity := -1.0 + if mouseDistMoved.X > 0 { + camPos.X += mouseDistMoved.X * sensitivity + } else if mouseDistMoved.X < 0 { + camPos.X += mouseDistMoved.X * sensitivity + } + if mouseDistMoved.Y > 0 { + camPos.Y += mouseDistMoved.Y * sensitivity + } else if mouseDistMoved.Y < 0 { + camPos.Y += mouseDistMoved.Y * sensitivity + } + CameraSetViewPos(0, *camPos) + } + } + } - // Left click - if MouseCheckButton(MbLeft) && - roomEditor.objectSelected != nil { - objectIndexSelected := roomEditor.objectSelected.ObjectIndex() - // NOTE(Jake): 2018-06-10 - // - // We need to handle mouse click between the last mouse position - // and current mouse position so that there are no gaps when you're - // dragging the mouse across long distances. - // - rect := m.R(MousePosition(), lastMousePos) - for x := rect.Left(); x <= rect.Right(); x += grid.X { - for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { - mousePos := V(x, y) - - // Check to make sure we aren't placing over the top - // of an existing entity - hasCollision := false - for _, obj := range room.Instances { + lastMousePos := roomEditor.lastMousePos + roomEditor.lastMousePos = MousePosition() + + // Draw + { + cameraSize := cameraGetActive().Size() + + { + // Fill screen with gray + DrawSetGUI(true) + DrawRectangle(geom.Vec{0, 0}, cameraSize, color.RGBA{153, 153, 153, 255}) + DrawSetGUI(false) + + // Draw black box using room bounds + borderWidth := 2.0 + pos := geom.Vec{float64(editingRoom.Left), float64(editingRoom.Top)} + pos.X -= borderWidth + pos.Y -= borderWidth + size := geom.Vec{float64(editingRoom.Right - editingRoom.Left), float64(editingRoom.Bottom - editingRoom.Top)} + size.X += borderWidth * 2 + size.Y += borderWidth * 2 + DrawRectangleBorder(pos, size, color.Black, 2, color.White) + } + + // Draw layers + { + for _, layer := range roomEditor.layers() { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + // Draw room instances + for _, obj := range layer.Instances { inst := roomEditorObjectIndexToData(obj.ObjectIndex) if inst == nil { continue } - pos := V(float64(obj.X), float64(obj.Y)) - size := inst.BaseObject().Size - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - if mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom { - hasCollision = true + baseObj := inst.BaseObject() + baseObj.X = float64(obj.X) + baseObj.Y = float64(obj.Y) + spr := baseObj.Sprite() + DrawSpriteScaled(spr, 0, baseObj.Pos(), baseObj.ImageScale) + } + case *room.RoomLayerBackground: + if layer.SpriteName == "" { + // If no sprite, don't draw anything + break + } + // Draw bg + sprite, ok := roomEditor.spriteMap[layer.SpriteName] + if !ok { + // If unable to find sprite, don't draw anything + // ie. handle case where background layer is using a deleted sprite + break + } + x := float64(layer.X) + y := float64(layer.Y) + width := float64(sprite.Size().X) + DrawSprite(sprite, 0, geom.Vec{x, y}) + { + // Tile left + x := x + for x > float64(editingRoom.Left) { + x -= width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } + { + // Tile left + x := x + for x < float64(editingRoom.Right) { + x += width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } + case *room.RoomLayerSprite: + // Draw room sprites + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + DrawSprite(sprite, 0, geom.Vec{float64(obj.X), float64(obj.Y)}) + } + default: + panic(fmt.Sprintf("Unhandled type: %T", layer)) + } + } + // Old render code (before layers) + /*instances := editingRoom.Instances + for _, obj := range instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + baseObj := inst.BaseObject() + baseObj.X = float64(obj.X) + baseObj.Y = float64(obj.Y) + inst.Draw() + }*/ + } + + if canUseBrush && + !isMenuOpen { + // Place tile + switch layer := roomEditor.editingLayer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Draw selected tile + if selectedObj := roomEditor.objectSelected; selectedObj != nil { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + + // Draw + drawObject(selectedObj, mousePos) + } + case *room.RoomLayerSprite: + // Draw selected sprite + if selectedBrush := roomEditor.spriteSelected; selectedBrush != nil { + pos := MousePosition() + if isHoldingControl { + maybeNewPos, ok := roomEditor.getSnapPosition(pos, selectedBrush.Size(), layer) + if ok { + pos = maybeNewPos + DrawSpriteExt(selectedBrush, 0, pos, geom.Vec{1, 1}, 0.85) } + } else { + // Grid mode + pos.X = snapToGrid(pos.X, grid.X) + pos.Y = snapToGrid(pos.Y, grid.Y) + DrawSpriteExt(selectedBrush, 0, pos, geom.Vec{1, 1}, 0.85) } + } + case *room.RoomLayerBackground: + // no-op + default: + panic(fmt.Sprintf("Unimplemented layer type: %T", layer)) + } + } + + if !isMenuOpen { + // Draw grid + if !isHoldingControl && + roomEditor.gridEnabled { + DrawSetGUI(true) + cameraPos := CameraGetViewPos(0) + windowWidth := float64(windowWidth()) + windowHeight := float64(windowHeight()) + xOffset := math.Mod(cameraPos.X, grid.X) + yOffset := math.Mod(cameraPos.Y, grid.Y) + for y := 0.0; y < windowHeight+grid.Y; y += grid.Y { + DrawRectangle(geom.Vec{0, y - yOffset}, geom.Vec{windowWidth + grid.X, 1}, color.White) + } + for x := 0.0; x < windowWidth+grid.X; x += grid.X { + DrawRectangle(geom.Vec{x - xOffset, 0}, geom.Vec{1, windowHeight + grid.Y}, color.White) + } + DrawSetGUI(false) + } + } + if roomEditor.menuOpened == reditor.MenuNone { + DrawSetGUI(true) + + // Draw layer widget + { + yStart := 0.0 + var x, y, width, height float64 + x = 0 + y = yStart + width = 320 + height = float64(len(roomEditor.layers())+1)*32.0 + 16 + DrawRectangle(geom.Vec{x, y}, geom.Vec{width, height}, color.RGBA{0, 0, 0, 220}) + y += 24 + DrawText(geom.Vec{x + 16, y}, "Layers:") + y += 16 + for _, layer := range roomEditor.layers() { + config := layer.GetConfig() + layerName := config.Name + layerType := "Unknown" + switch layer.(type) { + case *room.RoomLayerInstance: + layerType = "Instance" + case *room.RoomLayerBackground: + layerType = "Background" + case *room.RoomLayerSprite: + layerType = "Sprite" + default: + layerType = "Unhandled case" + } + layerText := layerName + " - " + layerType + " Layer (Order: " + strconv.Itoa(int(config.Order)) + ")" + if layer == roomEditor.editingLayer { + layerText += " [Selected]" + } + if drawTextButton(geom.Vec{x, y}, layerText) { + if roomEditor.editingLayer == layer { + roomEditor.editingLayer = nil + } else { + roomEditor.editingLayer = layer + } + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + y = yStart + height + switch layer := roomEditor.editingLayer.(type) { + case *room.RoomLayerSprite: + text := "Collision OFF" + if layer.Config.HasCollision { + text = "Collision ON" + } + if drawButton(geom.Vec{x, y}, text) { + layer.Config.HasCollision = !layer.Config.HasCollision + roomEditor.hasUnsavedChanges = true + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + if drawButton(geom.Vec{x, y}, "Add Instance Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Instance + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if drawButton(geom.Vec{x, y}, "Add Background Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Background + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if drawButton(geom.Vec{x, y}, "Add Sprite Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Sprite + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + text := "Grid OFF" + if roomEditor.gridEnabled { + text = "Grid ON" + } + if drawButton(geom.Vec{x, y}, text) { + roomEditor.gridEnabled = !roomEditor.gridEnabled + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if layer := roomEditor.editingLayer; layer != nil { + if drawButton(geom.Vec{x, y}, "Delete Selected Layer") { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.InstanceLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.InstanceLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + case *room.RoomLayerBackground: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.BackgroundLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.BackgroundLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + case *room.RoomLayerSprite: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.SpriteLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.SpriteLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + default: + roomEditor.setStatusText(fmt.Sprintf("Unhandled deletion case for: %T", layer)) + } + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + + } + DrawSetGUI(false) + } + + if canUseBrush && + !isMenuOpen { + // Place tile + switch layer := roomEditor.editingLayer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Left click + if roomEditor.MouseCheckButton(MbLeft) && + roomEditor.objectSelected != nil { + objectIndexSelected := roomEditor.objectSelected.ObjectIndex() + // NOTE(Jake): 2018-06-10 // - if !hasCollision { - // Snap to grid - mousePos.X = snapToGrid(mousePos.X, grid.X) - mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + didCreate := false + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + pos := geom.Vec{float64(obj.X), float64(obj.Y)} + size := inst.BaseObject().Size + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + hasCollision = true + } + } - roomObj := EditorAddInstance(mousePos, objectIndexSelected) - println("Create entity:", roomObj.String()) + // + if !hasCollision { + // Snap to grid + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + + // Add instance + pos := mousePos + //count := editingRoom.UserEntityCount + //editingRoom.UserEntityCount++ + + // + roomObj := &RoomObject{ + UUID: reditor.UUID(), + ObjectIndex: int32(objectIndexSelected), + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Instances = append(layer.Instances, roomObj) + + println("Create entity:", roomObj.String()) + didCreate = true + } + } + } + if didCreate { + roomEditor.hasUnsavedChanges = true + roomEditor.calculateRoomBounds() } } + + // Holding Right click + if roomEditor.MouseCheckButton(MbRight) { + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + previousDeletedCount := len(layer.DeletedInstances) + + // Mark deleted entities + for i, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + pos := geom.Vec{float64(obj.X), float64(obj.Y)} + size := inst.BaseObject().Size + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + // + record := layer.Instances[i] + + // Track deleted entities for when you save + layer.DeletedInstances = append(layer.DeletedInstances, record) + roomEditor.hasUnsavedChanges = true + } + } + + // Handle deletes + if previousDeletedCount != len(layer.DeletedInstances) { + // NOTE(Jake): 2018-07-11 + // + // Iterate over newly deleted entities and delete them from + // the room + // + didDelete := false + for i := previousDeletedCount; i < len(layer.DeletedInstances); i++ { + record := layer.DeletedInstances[i] + for i, obj := range layer.Instances { + if obj == record { + { + // Unordered Remove + layer.Instances[len(layer.Instances)-1], layer.Instances[i] = layer.Instances[i], layer.Instances[len(layer.Instances)-1] + layer.Instances = layer.Instances[:len(layer.Instances)-1] + } + didDelete = true + println("Deleted entity: " + record.UUID) + break + } + } + } + if didDelete { + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + } + } + } + } + case *room.RoomLayerSprite: + // Left click + if roomEditor.MouseCheckButton(MbLeft) && + roomEditor.spriteSelected != nil { + spriteName := roomEditor.spriteSelected.Name() + //spriteWidth := roomEditor.spriteSelected.Size().X + //spriteHeight := roomEditor.spriteSelected.Size().Y + + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + didCreate := false + if isHoldingControl { + selectedBrush := roomEditor.spriteSelected + brushWidth := float64(selectedBrush.Size().X) / 2 + brushHeight := float64(selectedBrush.Size().Y) / 2 + + for x := rect.Left(); x <= rect.Right(); x += brushWidth { + for y := rect.Top(); y <= rect.Bottom(); y += brushHeight { + pos := geom.Vec{x, y} + maybeNewPos, ok := roomEditor.getSnapPosition(pos, selectedBrush.Size(), layer) + if !ok { + continue + } + pos = maybeNewPos + + brushRect := geom.Rect{} + brushRect.Vec = pos + brushRect.Size = roomEditor.spriteSelected.Size() + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + hasCollision = true + } + } + + // + if !hasCollision { + // + roomSpriteObj := &room.RoomSpriteObject{ + UUID: reditor.UUID(), + SpriteName: spriteName, + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Sprites = append(layer.Sprites, roomSpriteObj) + + println("Create sprite:", roomSpriteObj.String()) + didCreate = true + } + } + } + } else { + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + pos := geom.Vec{x, y} + // Grid mode + pos.X = snapToGrid(pos.X, grid.X) + pos.Y = snapToGrid(pos.Y, grid.Y) + + brushRect := geom.Rect{} + brushRect.Vec = pos + brushRect.Size = roomEditor.spriteSelected.Size() + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + hasCollision = true + } + } + + // + if !hasCollision { + // + roomSpriteObj := &room.RoomSpriteObject{ + UUID: reditor.UUID(), + SpriteName: spriteName, + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Sprites = append(layer.Sprites, roomSpriteObj) + + println("Create sprite:", roomSpriteObj.String()) + didCreate = true + } + } + } + } + if didCreate { + roomEditor.hasUnsavedChanges = true + roomEditor.calculateRoomBounds() + } + } + + // Holding Right click + if roomEditor.MouseCheckButton(MbRight) { + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + previousDeletedCount := len(layer.DeletedSprites) + + // Mark deleted + for i, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + width := float64(sprite.Size().X) + height := float64(sprite.Size().Y) + left := float64(obj.X) + right := left + width + top := float64(obj.Y) + bottom := top + height + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + // + record := layer.Sprites[i] + + // Track deleted entities for when you save + layer.DeletedSprites = append(layer.DeletedSprites, record) + roomEditor.hasUnsavedChanges = true + } + } + + // Handle deletes + if previousDeletedCount != len(layer.DeletedSprites) { + // NOTE(Jake): 2018-07-11 + // + // Iterate over newly deleted entities and delete them from + // the room + // + didDelete := false + for i := previousDeletedCount; i < len(layer.DeletedSprites); i++ { + record := layer.DeletedSprites[i] + for i, obj := range layer.Sprites { + if obj == record { + { + // Unordered Remove + layer.Sprites[len(layer.Sprites)-1], layer.Sprites[i] = layer.Sprites[i], layer.Sprites[len(layer.Sprites)-1] + layer.Sprites = layer.Sprites[:len(layer.Sprites)-1] + } + didDelete = true + println("Deleted entity: " + record.UUID) + break + } + } + } + if didDelete { + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + } + } + } + } + case *room.RoomLayerBackground: + if roomEditor.MouseCheckButton(MbLeft) { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + layer.X = int32(mousePos.X) + layer.Y = int32(mousePos.Y) + + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + default: + panic(fmt.Sprintf("Unimplemented layer type: %T", layer)) } } - // Holding Right click - if MouseCheckButton(MbRight) { - // NOTE(Jake): 2018-06-10 - // - // We need to handle mouse click between the last mouse position - // and current mouse position so that there are no gaps when you're - // dragging the mouse across long distances. + // Select menu + if roomEditor.menuOpened != reditor.MenuNone { + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + // - rect := m.R(MousePosition(), lastMousePos) - for x := rect.Left(); x <= rect.Right(); x += grid.X { - for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { - mousePos := V(x, y) + var x float64 = float64(windowWidth()) / 2 + var y float64 = 32 + switch roomEditor.menuOpened { + case reditor.MenuEntity: + typingText := KeyboardString() + roomEditor.entityMenuFiltered = roomEditor.entityMenuFiltered[:0] + for _, obj := range roomEditor.objectIndexToData { + if obj == nil { + continue + } + name := obj.ObjectName() + hasMatch := hasFilterMatch(name, typingText) + if !hasMatch { + continue + } + // NOTE(Jake): 2018-07-11 + // + // Animating in the object list isn't particularly useful. + // + //obj.BaseObject().ImageUpdate() + roomEditor.entityMenuFiltered = append(roomEditor.entityMenuFiltered, obj) + } - for i, obj := range room.Instances { - inst := roomEditorObjectIndexToData(obj.ObjectIndex) - if inst == nil { - continue - } - pos := V(float64(obj.X), float64(obj.Y)) - size := inst.BaseObject().Size - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - if mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom { - EditorRemoveInstance(i) - println("Deleted entity:", obj.Filename) + // + if inputSelectPressed() && + len(roomEditor.entityMenuFiltered) > 0 { + selectedObj := roomEditor.entityMenuFiltered[0] + // Set + roomEditor.objectSelected = selectedObj + roomEditor.editorConfigSave() + + roomEditor.menuOpened = reditor.MenuNone + } + + { + searchText := "Search for object (type + press enter)" + DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) + y += 24 + } + { + typingText := KeyboardString() + DrawText(geom.Vec{x, y}, typingText) + DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") + y += 24 + } + previewSize := geom.Vec{32, 32} + for _, obj := range roomEditor.entityMenuFiltered { + // + baseObj := obj.BaseObject() + pos := baseObj.Vec + //size := baseObj.Size + //oldImageScale := baseObj.ImageScale + + // NOTE(Jake): 2018-07-10 + // + // I've already wasted time thinking about centering this + // and the text above. Lets not look into this until we + // feel the need. + // + // Also look at other similar UI experiences, because + // maybe we wont need to center this to get good UX. + // + pos.X = x - 40 + pos.Y = y - (previewSize.Y / 2) + //baseObj.ImageScale.X = previewSize.X / float64(size.X) + //baseObj.ImageScale.Y = previewSize.Y / float64(size.Y) + //obj.Draw() + drawObjectPreview(obj, pos, previewSize) + name := obj.ObjectName() + DrawText(geom.Vec{x, y}, name) + //baseObj.ImageScale = oldImageScale + y += previewSize.Y + 16 + } + case reditor.MenuSprite, + reditor.MenuBackground: + typingText := KeyboardString() + roomEditor.spriteMenuFiltered = roomEditor.spriteMenuFiltered[:0] + for _, spr := range roomEditor.spriteList { + if spr == nil { + continue + } + hasMatch := hasFilterMatch(spr.Name(), typingText) + if !hasMatch { + continue + } + // NOTE(Jake): 2018-07-11 + // + // Animating in the object list isn't particularly useful. + // + //obj.BaseObject().ImageUpdate() + roomEditor.spriteMenuFiltered = append(roomEditor.spriteMenuFiltered, spr) + } + + // + if inputSelectPressed() && + len(roomEditor.spriteMenuFiltered) > 0 { + selectedSpr := roomEditor.spriteMenuFiltered[0] + switch roomEditor.menuOpened { + case reditor.MenuSprite: + // Set + roomEditor.spriteSelected = selectedSpr + roomEditor.editorConfigSave() + case reditor.MenuBackground: + switch layer := roomEditor.editingLayer.(type) { + case *room.RoomLayerBackground: + if layer.SpriteName != selectedSpr.Name() { + layer.SpriteName = selectedSpr.Name() + roomEditor.hasUnsavedChanges = true + } + default: + panic(fmt.Sprintf("MenuBackground: Unhandled layer type for %T", layer)) } } + roomEditor.menuOpened = reditor.MenuNone + } + + // + { + searchText := "Search for image (type + press enter)" + DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) + y += 24 + } + { + typingText := KeyboardString() + DrawText(geom.Vec{x, y}, typingText) + DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") + y += 24 + } + previewSize := geom.Vec{32, 32} + for _, spr := range roomEditor.spriteMenuFiltered { + var pos geom.Vec + pos.X = x - 40 + pos.Y = y - (previewSize.Y / 2) + calcPreviewSize := previewSize + calcPreviewSize.X /= float64(spr.Size().X) + calcPreviewSize.Y /= float64(spr.Size().Y) + DrawSpriteScaled(spr, 0, pos, calcPreviewSize) + name := spr.Name() + DrawText(geom.Vec{x, y}, name) + y += previewSize.Y + 16 } + case reditor.MenuNewRoom, + reditor.MenuLoadRoom, + reditor.MenuNewLayer, + reditor.MenuSetOrder: + searchText := "" + switch roomEditor.menuOpened { + case reditor.MenuNewRoom: + searchText = "Enter name for new room:" + case reditor.MenuLoadRoom: + searchText = "Enter name to load room:" + case reditor.MenuNewLayer: + searchText = "Enter name for new layer:" + case reditor.MenuSetOrder: + searchText = "Set value for order (ie. -1000, 100, 10000, 5, -3, 0):" + default: + panic("Invalid menu type, No search text defined") + } + + { + DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) + y += 24 + } + { + typingText := KeyboardString() + DrawText(geom.Vec{x, y}, typingText) + DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") + y += 24 + } + } + DrawSetGUI(false) + } + + // Draw status + { + DrawSetGUI(true) + editingString := "" + editingString += "Editing: " + filepath.Base(roomEditor.editingRoom.Filepath()) + { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + editingString += " | " + strconv.FormatFloat(mousePos.X, 'f', -1, 64) + "," + strconv.FormatFloat(mousePos.Y, 'f', -1, 64) + "px" + } + if text := roomEditor.statusText; text != "" { + editingString += " | " + text } + if selectedObj := roomEditor.objectSelected; selectedObj != nil { + editingString += " | Selected: " + selectedObj.ObjectName() + //DrawText(geom.V(0, 16), "Selected: "+selectedObj.ObjectName()) + } + editingString += " | Frame Usage: " + FrameUsage() + //editingString += " | CTRL+P = Open Entity Menu" + //editingString += " | CTRL+N = Create new room" + DrawRectangle(geom.Vec{0, cameraSize.Y - 32}, geom.Vec{StringWidth(editingString) + 16, 32}, color.RGBA{0, 0, 0, 128}) + DrawText(geom.Vec{0, cameraSize.Y - 16}, editingString) + + DrawSetGUI(false) } } } -func EditorDraw() { - room := roomEditorEditingRoom() - if room == nil { +func drawObjectPreview(inst object.ObjectType, pos geom.Vec, fitToSize geom.Vec) { + baseObj := inst.BaseObject() + sprite := baseObj.Sprite() + spriteSize := sprite.Size() + scale := geom.Vec{fitToSize.X / float64(spriteSize.X), fitToSize.Y / float64(spriteSize.Y)} + if scale.X > 1 { + scale.X = 1 + } + if scale.Y > 1 { + scale.Y = 1 + } + DrawSpriteScaled(sprite, 0, pos, scale) +} + +func drawObject(inst object.ObjectType, pos geom.Vec) { + baseObj := inst.BaseObject() + //baseObj.Vec = pos + sprite := baseObj.Sprite() + DrawSprite(sprite, 0, pos) +} + +func drawRoomObject(roomObject *room.RoomObject, pos geom.Vec) { + inst := roomEditorObjectIndexToData(roomObject.ObjectIndex) + if inst == nil { return } - roomEditor := gRoomEditor - isMenuOpen := roomEditor.isEntityMenuOpen + drawObject(inst, pos) +} - // NOTE(Jake): 2018-06-04 - // - // A hack to set the camera context - // - cameraSetActive(0) - defer cameraClearActive() - currentCamera := cameraGetActive() - - instances := room.Instances - for _, obj := range instances { - inst := roomEditorObjectIndexToData(obj.ObjectIndex) - if inst == nil { - continue +func drawTextButton(pos geom.Vec, text string) bool { + // Config + paddingH := 32.0 + size := geom.Vec{StringWidth(text) + paddingH, 24} + + // Handle mouse over + isMouseOver := isMouseScreenOver(pos, size) + + // Draw highlight bg + if isMouseOver { + DrawRectangle(pos, size, color.White) + } + + // Draw Text + pos.X += paddingH * 0.5 + pos.Y += 16 + if isMouseOver { + DrawTextColor(pos, text, color.Black) + } else { + DrawTextColor(pos, text, color.White) + } + return MouseCheckPressed(MbLeft) && isMouseOver +} + +func drawButton(pos geom.Vec, text string) bool { + // Config + paddingH := 32.0 + borderWidth := 2.0 + size := geom.Vec{StringWidth(text) + paddingH, 24} + + // Handle mouse over + isMouseOver := isMouseScreenOver(pos, size) + var innerRectColor color.RGBA + if isMouseOver { + innerRectColor = color.RGBA{180, 180, 180, 255} + } else { + innerRectColor = color.RGBA{255, 255, 255, 255} + } + + // Draw Border (outer rect) + DrawRectangleBorder(pos, size, innerRectColor, borderWidth, color.RGBA{0, 162, 232, 255}) + /* pos.X += borderWidth + pos.Y += borderWidth + size.X -= borderWidth * 2 + size.Y -= borderWidth * 2 + + // Draw Rect (inner rect) + DrawRectangle(pos, size, innerRectColor)*/ + + // Draw Text + pos.X += paddingH * 0.5 + pos.Y += 16 + DrawTextColor(pos, text, color.Black) + return MouseCheckPressed(MbLeft) && isMouseOver +} + +func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, layer *room.RoomLayerSprite) (geom.Vec, bool) { + offsetX := float64(brushSize.X) + offsetY := float64(brushSize.Y) + var horizTarget, vertTarget geom.Rect + var horizSide, vertSide int + //fmt.Printf("Reset\n") + if horizSide == 0 { + targetPos := pos + targetPos.X += offsetX + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the left-side of record + if pos.X <= other.Left() { + if horizSide == 0 || + (other.DistancePoint(pos) < horizTarget.DistancePoint(pos)) { + horizTarget = other + horizSide = 1 + } + } + } + } + } + { + targetPos := pos + targetPos.X -= offsetX + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the right-side of record + if pos.X > other.Right() { + if horizSide == 0 || + (other.DistancePoint(pos) < horizTarget.DistancePoint(pos)) { + horizTarget = other + horizSide = -1 + } + } + } } - baseObj := inst.BaseObject() - baseObj.X = float64(obj.X) - baseObj.Y = float64(obj.Y) - inst.Draw() } - if !isMenuOpen { - grid := V(32, 32) + { + targetPos := pos + targetPos.Y += offsetY + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } - // Draw selected - if selectedObj := roomEditor.objectSelected; selectedObj != nil { - mousePos := MousePosition() - mousePos.X = snapToGrid(mousePos.X, grid.X) - mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() - // Draw - baseObj := selectedObj.BaseObject() - baseObj.Vec = mousePos - selectedObj.Draw() + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the top-side of record + if pos.Y < other.Top() { + if vertSide == 0 || + (other.DistancePoint(pos) < vertTarget.DistancePoint(pos)) { + vertTarget = other + vertSide = 1 + } + } + } } } + { + targetPos := pos + targetPos.Y -= offsetY - // Entity select menu - if roomEditor.isEntityMenuOpen { - { - //screen := gScreen - //screen.Fill(color.NRGBA{0x00, 0x00, 0x00, 128}) - DrawRectangle(m.V(0, 0), m.V(2048, 2048), color.RGBA{0, 0, 0, 190}) + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the bottom-side of record + if pos.Y > other.Bottom() { + if vertSide == 0 || + (other.DistancePoint(pos) < vertTarget.DistancePoint(pos)) { + vertTarget = other + vertSide = -1 + } + } + } } - var x float64 = 128 - var y float64 = 32 - { - searchText := "Search for object (type + press enter)" - DrawText(m.V(128-(StringWidth(searchText)/4), y), searchText) - y += 24 + } + if horizSide != 0 || vertSide != 0 { + //brushRect := geom.Rect{} + //brushRect.Vec = pos + //brushRect.Size = brushSize + + var horizDist, vertDist float64 + horizDist = 9999 + vertDist = 9999 + if horizSide != 0 { + horizDist = horizTarget.DistancePoint(pos) } - { - typingText := KeyboardString() - DrawText(m.V(x, y), typingText) - DrawText(m.V(x+StringWidth(typingText), y), "|") - y += 24 + if vertSide != 0 { + vertDist = vertTarget.DistancePoint(pos) } - previewSize := m.V(32, 32) - for _, obj := range roomEditor.entityMenuFiltered { - // - baseObj := obj.BaseObject() - pos := &baseObj.Vec - size := baseObj.Size - oldImageScale := baseObj.ImageScale - { - pos.X = x - 40 + currentCamera.X - pos.Y = y - (previewSize.Y / 2) + currentCamera.Y - baseObj.ImageScale.X = previewSize.X / float64(size.X) - baseObj.ImageScale.Y = previewSize.Y / float64(size.Y) - obj.Draw() - DrawText(m.V(x, y), obj.ObjectName()) + if horizDist < vertDist { + switch horizSide { + case 0: + // no-op + case -1: + pos.X = horizTarget.Right() + pos.Y = horizTarget.Top() + case 1: + pos.X = horizTarget.Left() - float64(brushSize.X) + pos.Y = horizTarget.Top() + default: + panic(fmt.Sprintf("unknown horiz side: %d", horizSide)) + } + } else { + switch vertSide { + case 0: + // no-op + case -1: + pos.X = vertTarget.Left() + pos.Y = vertTarget.Bottom() + case 1: + pos.X = vertTarget.Left() + pos.Y = vertTarget.Top() - float64(brushSize.Y) + default: + panic(fmt.Sprintf("unknown vert side: %d", vertSide)) } - baseObj.ImageScale = oldImageScale - y += 48 } + return pos, true } + return pos, false } -func EditorSave() { - room := roomEditorEditingRoom() - if room == nil { +func isMouseOver(pos geom.Vec, size geom.Vec) bool { + mousePos := MousePosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} + +func isMouseScreenOver(pos geom.Vec, size geom.Vec) bool { + mousePos := mouseScreenPosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} + +func (roomEditor *roomEditor) calculateRoomBounds() { + // Reset room size + editingRoom := roomEditor.editingRoom + editingRoom.Left = 0 + editingRoom.Right = 0 + editingRoom.Top = 0 + editingRoom.Bottom = 0 + for _, layer := range roomEditor.layers() { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + for _, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + x := int32(obj.X) + y := int32(obj.Y) + size := inst.BaseObject().Size + width := int32(size.X) + height := int32(size.Y) + if x < editingRoom.Left { + editingRoom.Left = x + } + if right := x + width; right > editingRoom.Right { + editingRoom.Right = right + } + if y < editingRoom.Top { + editingRoom.Top = y + } + if bottom := y + height; bottom > editingRoom.Bottom { + editingRoom.Bottom = bottom + height + } + } + case *room.RoomLayerSprite: + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { + continue + } + x := int32(obj.X) + y := int32(obj.Y) + width := int32(sprite.Size().X) + height := int32(sprite.Size().Y) + if x < editingRoom.Left { + editingRoom.Left = x + } + if right := x + width; right > editingRoom.Right { + editingRoom.Right = right + } + if y < editingRoom.Top { + editingRoom.Top = y + } + if bottom := y + height; bottom > editingRoom.Bottom { + editingRoom.Bottom = bottom + height + } + } + } + } +} + +func (roomEditor *roomEditor) newRoom(name string) { + if roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("Cannot create new room with unsaved changes. Restart the game and editor, then press CTRL+N") + return + } + roomFilepath := roomEditor.roomDirectory + reditor.UUID() + if _, err := os.Stat(roomFilepath); !os.IsNotExist(err) { + roomEditor.setStatusText("Cannot create room \"" + filepath.Base(roomFilepath) + "\". That room name is already taken and exists.") return } - roomDirectory := room.Filepath + editingRoom := &room.Room{ + Config: &room.RoomConfig{ + UUID: reditor.UUID(), + Name: name, + }, + } + if roomEditor.editingRoom != nil { + // Copy configs of current rooms layers + for _, layer := range roomEditor.editingRoom.InstanceLayers { + var copyLayer room.RoomLayerInstance + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.InstanceLayers = append(editingRoom.InstanceLayers, ©Layer) + } + for _, layer := range roomEditor.editingRoom.SpriteLayers { + var copyLayer room.RoomLayerSprite + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.SpriteLayers = append(editingRoom.SpriteLayers, ©Layer) + } + for _, layer := range roomEditor.editingRoom.BackgroundLayers { + var copyLayer room.RoomLayerBackground + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.BackgroundLayers = append(editingRoom.BackgroundLayers, ©Layer) + } + } + roomEditor.editorChangeRoom(editingRoom) +} - // Delete objects - deletedInstances := room.DeletedInstances - // NOTE(Jake): 2018-06-10 - // - // Clear out 'DeletedInstances' before we save - // the map file as data. - // - // We don't want this information - // serialized. - // - room.DeletedInstances = nil - for _, obj := range deletedInstances { - fname := obj.Filename - filepath := roomDirectory + "/" + fname + ".txt" - err := os.Remove(filepath) - if err != nil { - println("Error deleting instance:", err.Error()) +func (roomEditor *roomEditor) loadRoom(name string) { + if roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("Cannot load room with unsaved changes. Restart the game and editor.") + return + } + roomFilepath := roomEditor.roomDirectory + name + if _, err := os.Stat(roomFilepath); os.IsNotExist(err) { + roomEditor.setStatusText("Room \"" + name + "\" does not exist.") + return + } + editingRoom := LoadRoom(name) + if editingRoom == nil { + roomEditor.setStatusText("Room \"" + name + "\" could not be loaded.") + return + } + roomEditor.editorChangeRoom(editingRoom) +} + +/*func (roomEditor *roomEditor) onCollisionSprite(layer *room.RoomLayerSprite, x, y float64, width, height float64, callback func(left, top, right, bottom float64)) { + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite, ok := roomEditor.spriteMap[spriteName] + if !ok { continue } + + other := space.Space{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + r2Left := other.X + r2Top := other.Y + r2Right := r2Left + float64(other.Size.X) + r2Bottom := r2Top + float64(other.Size.Y) + + if r1Right > r2Left && r1Bottom > r2Top && + r1Left < r2Right && r1Top < r2Bottom { + callback(r2Left, r2Top, r2Right, r2Bottom) + } + } +}*/ + +func (roomEditor *roomEditor) newLayerAndSelected(editingRoom *room.Room, text string, kind room.RoomLayerKind) { + text = strings.TrimSpace(text) + config := &room.RoomLayerConfig{ + Kind: kind, + UUID: reditor.UUID(), + Name: text, + } + switch kind { + case room.RoomLayerKind_Instance: + layer := &room.RoomLayerInstance{Config: config} + editingRoom.InstanceLayers = append(editingRoom.InstanceLayers, layer) + roomEditor.editingLayer = layer + case room.RoomLayerKind_Background: + layer := &room.RoomLayerBackground{Config: config} + editingRoom.BackgroundLayers = append(editingRoom.BackgroundLayers, layer) + roomEditor.editingLayer = layer + case room.RoomLayerKind_Sprite: + layer := &room.RoomLayerSprite{Config: config} + editingRoom.SpriteLayers = append(editingRoom.SpriteLayers, layer) + roomEditor.editingLayer = layer + default: + panic("Unhandled room layer kind: %s" + kind.String()) + } + roomEditor.editorConfigSave() +} + +func (roomEditor *roomEditor) setStatusText(text string) { + roomEditor.statusText = text + roomEditor.statusTimer = time.Now() + println(text) +} + +func inputSelectPressed() bool { + return KeyboardCheckPressed(VkEnter) || KeyboardCheckPressed(VkNumpadEnter) +} + +func hasFilterMatch(s string, filterBy string) bool { + return filterBy == "" || + strings.Index(s, filterBy) >= 0 +} + +func (roomEditor *roomEditor) editorConfigLoad() { + roomEditor.editingLayer = nil + + // Load room editor config + roomEditorConfigPath := user.HomeDir() + "/.gmlgo" + if _, err := os.Stat(roomEditorConfigPath); os.IsNotExist(err) { + os.Mkdir(roomEditorConfigPath, 0700) } - // Write objects + configPath := roomEditorConfigPath + "/config.json" + fileData, err := file.OpenFile(configPath) + if err == nil { + bytes, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading " + configPath + "\n" + "Error: " + err.Error()) + } + editorConfig := roomEditorConfig{} + if err := json.Unmarshal(bytes, &editorConfig); err != nil { + panic("Error unmarshalling " + configPath + "\n" + "Error: " + err.Error()) + } + // Set layer from config + for _, layer := range roomEditor.layers() { + if layer.GetConfig().UUID == editorConfig.LayerSelected { + roomEditor.editingLayer = layer + break + } + } + // Set brush from config + switch roomEditor.editingLayer.(type) { + case *room.RoomLayerSprite: + obj, ok := roomEditor.spriteMap[editorConfig.BrushSelected] + if ok && + obj.Name() == editorConfig.BrushSelected { + roomEditor.spriteSelected = obj + break + } + case *room.RoomLayerInstance: + for _, obj := range roomEditor.objectIndexToData { + if obj != nil && + obj.ObjectName() == editorConfig.BrushSelected { + roomEditor.objectSelected = obj + break + } + } + } + } else { + println("No editor config exists: " + configPath) + } + +} + +func (roomEditor *roomEditor) editorConfigSave() { + var editorConfig roomEditorConfig + if roomEditor.editingLayer != nil { + editorConfig.LayerSelected = roomEditor.editingLayer.GetConfig().UUID + switch roomEditor.editingLayer.(type) { + case *room.RoomLayerInstance: + if roomEditor.objectSelected != nil { + editorConfig.BrushSelected = roomEditor.objectSelected.ObjectName() + } + case *room.RoomLayerSprite: + if roomEditor.spriteSelected != nil { + editorConfig.BrushSelected = roomEditor.spriteSelected.Name() + } + case *room.RoomLayerBackground: + // no-op + } + } + if roomEditor.editingRoom != nil { + editorConfig.RoomSelected = roomEditor.editingRoom.Config.UUID + } + + roomEditorConfigPath := user.HomeDir() + "/.gmlgo" + if _, err := os.Stat(roomEditorConfigPath); os.IsNotExist(err) { + os.Mkdir(roomEditorConfigPath, 0700) + } + + json, _ := json.MarshalIndent(editorConfig, "", "\t") + configPath := roomEditorConfigPath + "/config.json" + err := ioutil.WriteFile(configPath, json, 0644) + if err != nil { + println("Failed to write room editor config: " + configPath + "\n" + "Error: " + err.Error()) + } +} + +func editorSave() { + roomEditor := gRoomEditor + editingRoom := roomEditor.editingRoom + if editingRoom == nil { + return + } + roomDirectory := editingRoom.Filepath() + if !roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("No changes made to:" + roomDirectory) + return + } + + // Create room directory + if _, err := os.Stat(roomDirectory); os.IsNotExist(err) { + os.Mkdir(roomDirectory, 0700) + } + + // Write config { - for _, obj := range room.Instances { - data := roomEditorObjectIndexToData(obj.ObjectIndex) - fname := obj.Filename - filepath := roomDirectory + "/" + fname + ".txt" + json, _ := json.MarshalIndent(editingRoom.Config, "", "\t") + err := ioutil.WriteFile(roomDirectory+"/config.json", json, 0644) + if err != nil { + panic("Failed to write room config.json file: " + roomDirectory + "\n" + "Error: " + err.Error()) + } + } - file, err := os.Create(filepath) - if err != nil { - println("Error writing file:", err.Error()) + // NOTE(Jake): 2018-07-21 + // + // Not sure how I should handle errors yet... + // will need to see how it plays out in practice. + // + didErrorOccur := false - // todo(jake): 2018-06-10 - // - // Error recovery here? - // + // Handle instance layers + { + for _, layer := range roomEditor.layers() { + config := layer.GetConfig() + layerDirectory := roomDirectory + "/" + config.UUID - continue + // Create layer directory if it doesn't exist. + if _, err := os.Stat(layerDirectory); os.IsNotExist(err) { + os.Mkdir(layerDirectory, 0700) } - name := data.ObjectName() - x := obj.X - y := obj.Y + // Write config + { + json, _ := json.MarshalIndent(config, "", "\t") + err := ioutil.WriteFile(layerDirectory+"/config.json", json, 0644) + if err != nil { + break + } + } - // - w := bufio.NewWriter(file) - w.WriteString(name) - w.WriteByte('\n') - w.WriteString(strconv.Itoa(int(x))) - w.WriteByte('\n') - w.WriteString(strconv.Itoa(int(y))) - w.WriteByte('\n') - // NOTE(Jake): 2018-06-11 - // - // Why are we writing the relative filename [x] times here? - // - // To stop git from confusing deleted entities with renaming/moving - // a file. (ie. you'd delete an entity, create a new entity, but git would handle - // it as a rename/move) - // - // I'm actually not sure if a Git rename/move could lead to merge conflicts... - // but I'm going to err on the side of caution here until I decide to investigate - // this further. - // - w.WriteString(fname) - w.WriteString(fname) - w.WriteString(fname) - w.WriteString(fname) - w.Flush() + switch layer := layer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Deleted instances + if deletedInstances := layer.DeletedInstances; len(deletedInstances) > 0 { + // NOTE(Jake): 2018-06-10 + // + // Clear out 'DeletedInstances' before we save + // the map file as data. + // + // We don't want this information + // serialized. + // + layer.DeletedInstances = nil + for _, obj := range deletedInstances { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + if _, err := os.Stat(filepath); os.IsNotExist(err) { + // Ignore if it does not exist + continue + } + err := os.Remove(filepath) + if err != nil { + println("Error deleting instance:", err.Error()) + didErrorOccur = true + continue + } + } + } + + // Write instances + if instances := layer.Instances; len(instances) > 0 { + for _, obj := range instances { + data := roomEditorObjectIndexToData(obj.ObjectIndex) + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + + file, err := os.Create(filepath) + if err != nil { + println("Error writing file:", err.Error()) + didErrorOccur = true + // todo(jake): 2018-06-10 + // + // Error recovery here? + // + + continue + } + + name := data.ObjectName() + x := obj.X + y := obj.Y + + // + w := bufio.NewWriter(file) + w.WriteString(name) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(x))) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(y))) + w.WriteByte('\n') + // NOTE(Jake): 2018-06-11 + // + // Why are we writing the relative filename [x] times here? + // + // To stop git from confusing deleted entities with renaming/moving + // a file. (ie. you'd delete an entity, create a new entity, but git would handle + // it as a rename/move) + // + // I'm actually not sure if a Git rename/move could lead to merge conflicts... + // but I'm going to err' on the side of caution here until I decide to investigate + // this further. + // + for i := 0; i < 4; i++ { + w.WriteString(uuid) + } + w.Flush() + + file.Close() + } + } + case *room.RoomLayerBackground: + // Write everything + { + // NOTE(Jake): 2018-07-29 + // + // Ideally I'd want to disable the config data being saved into this + // as we already get that information from config.json. + // + json, _ := json.MarshalIndent(layer, "", "\t") + err := ioutil.WriteFile(layerDirectory+"/background.json", json, 0644) + if err != nil { + break + } + } + case *room.RoomLayerSprite: + // Deleted instances + if deletedRecords := layer.DeletedSprites; len(deletedRecords) > 0 { + // NOTE(Jake): 2018-06-10 + // + // Clear out 'DeletedInstances' before we save + // the map file as data. + // + // We don't want this information + // serialized. + // + layer.DeletedSprites = nil + for _, obj := range deletedRecords { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + if _, err := os.Stat(filepath); os.IsNotExist(err) { + // Ignore if it does not exist + continue + } + err := os.Remove(filepath) + if err != nil { + println("Error deleting instance:", err.Error()) + didErrorOccur = true + continue + } + } + } - file.Close() + // Write instances + if records := layer.Sprites; len(records) > 0 { + for _, obj := range records { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + + file, err := os.Create(filepath) + if err != nil { + println("Error writing sprite object file:", err.Error()) + didErrorOccur = true + // todo(jake): 2018-06-10 + // + // Error recovery here? + // + + continue + } + + name := obj.SpriteName + x := obj.X + y := obj.Y + + // + w := bufio.NewWriter(file) + w.WriteString(name) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(x))) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(y))) + w.WriteByte('\n') + // NOTE(Jake): 2018-06-11 + // + // Why are we writing the relative filename [x] times here? + // + // To stop git from confusing deleted entities with renaming/moving + // a file. (ie. you'd delete an entity, create a new entity, but git would handle + // it as a rename/move) + // + // I'm actually not sure if a Git rename/move could lead to merge conflicts... + // but I'm going to err' on the side of caution here until I decide to investigate + // this further. + // + for i := 0; i < 4; i++ { + w.WriteString(uuid) + } + w.Flush() + + file.Close() + } + } + default: + panic(fmt.Sprintf("Unimplemented save logic for layer type: %T", layer)) + } + } + } + + // Handle deletion of layers + if deletedLayers := editingRoom.DeletedLayers; len(deletedLayers) > 0 { + // NOTE(Jake): 2018-08-05 + // + // Clear deleted layers out so they aren't serialized into the binary map + // + editingRoom.DeletedLayers = editingRoom.DeletedLayers[:0] + for _, layerUUID := range deletedLayers { + layerDirectory := roomDirectory + "/" + layerUUID + os.RemoveAll(layerDirectory) } } // Save data copy of file - room.DebugWriteDataFile(room.Filepath) + editingRoom.DebugWriteDataFile(editingRoom.Filepath()) + + // Save room! + baseName := filepath.Base(editingRoom.Filepath()) + if didErrorOccur { + roomEditor.setStatusText(baseName + " saved but with errors... Check console logs.") + } else { + roomEditor.setStatusText(baseName + " saved successfully!") + } + + roomEditor.hasUnsavedChanges = false } diff --git a/gml/room_instance.go b/gml/room_instance.go index 10f3a83..463c1d3 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -2,54 +2,60 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/room" ) +type RoomInstance struct { + used bool + index int + room *Room + + instanceLayers []RoomInstanceLayerInstance + spriteLayers []RoomInstanceLayerSprite + drawLayers []RoomInstanceLayerDraw +} + func RoomInstanceCreate(room *Room) *RoomInstance { roomInst := gState.createNewRoomInstance(room) return roomInst } +func RoomInstanceDestroy(roomInst *RoomInstance) { + gState.deleteRoomInstance(roomInst) +} + func RoomInstanceEmptyCreate() *RoomInstance { roomInst := gState.createNewRoomInstance(nil) return roomInst } -type RoomInstance struct { - used bool - index int - room *Room - instanceManager instanceManager -} - func (roomInst *RoomInstance) Index() int { return roomInst.index } -/*func (roomInst *RoomInstance) CreateSnapshot() []byte { - now := time.Now() - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(roomInst.instanceManager.instances) - if err != nil { - panic(err) - } - result := buf.Bytes() - println("Time to encode:", time.Now().Sub(now).String(), ", Size in bytes:", len(result)) - return result +func (roomInst *RoomInstance) Room() *room.Room { + return roomInst.room } -func (roomInst *RoomInstance) RestoreSnapshot(data []byte) { - now := time.Now() - var buf bytes.Buffer - buf.Write(data) - enc := gob.NewDecoder(&buf) - err := enc.Decode(roomInst.instanceManager.instances) - if err != nil { - panic(err) +type roomInstanceObject interface { + BaseObject() *object.Object +} + +func RoomInstanceInstances(inst roomInstanceObject) []object.ObjectType { + roomInstanceIndex := object.RoomInstanceIndex(inst.BaseObject()) + roomInst := RoomGetInstance(roomInstanceIndex) + if roomInst == nil { + return nil } - println("Time to decode:", time.Now().Sub(now).String()) -}*/ + instanceLayer := &roomInst.instanceLayers[len(roomInst.instanceLayers)-1] + return instanceLayer.manager.instances +} +// NOTE(Jake):2018-08-19 +// +// I might want to make this private so a user +// can only manipulate a room instance via functions +// func RoomGetInstance(roomInstanceIndex int) *RoomInstance { roomInst := &gState.roomInstances[roomInstanceIndex] if roomInst.used { @@ -58,6 +64,12 @@ func RoomGetInstance(roomInstanceIndex int) *RoomInstance { return nil } +// todo(Jake): 2018-07-22 +// Figure out this +/*func (roomInst *RoomInstance) InstanceCreateLayer(position Vec, layer *RoomInstanceLayerInstance, objectIndex object.ObjectIndex) object.ObjectType { + +} + func (roomInst *RoomInstance) InstanceCreate(position Vec, objectIndex object.ObjectIndex) object.ObjectType { return roomInst.instanceManager.InstanceCreate(position, objectIndex, roomInst.Index()) } @@ -65,12 +77,16 @@ func (roomInst *RoomInstance) InstanceCreate(position Vec, objectIndex object.Ob func (roomInst *RoomInstance) InstanceDestroy(inst object.ObjectType) { manager := &roomInst.instanceManager manager.InstanceDestroy(inst) -} +}*/ func (roomInst *RoomInstance) update(animationUpdate bool) { - roomInst.instanceManager.update(animationUpdate) + for _, layer := range roomInst.instanceLayers { + layer.update(animationUpdate) + } } func (roomInst *RoomInstance) draw() { - roomInst.instanceManager.draw() + for _, layer := range roomInst.drawLayers { + layer.draw() + } } diff --git a/gml/room_instance_layer.go b/gml/room_instance_layer.go new file mode 100644 index 0000000..5305995 --- /dev/null +++ b/gml/room_instance_layer.go @@ -0,0 +1,23 @@ +package gml + +//type RoomInstanceLayer interface { +// update(animationUpdate bool) +// draw() +//} + +type RoomInstanceLayerDrawBase struct { + drawOrder int32 +} + +func (layer *RoomInstanceLayerDrawBase) order() int32 { + return layer.drawOrder +} + +type RoomInstanceLayerUpdate interface { + update(animationUpdate bool) +} + +type RoomInstanceLayerDraw interface { + draw() + order() int32 +} diff --git a/gml/room_instance_layer_background.go b/gml/room_instance_layer_background.go new file mode 100644 index 0000000..9b66d4c --- /dev/null +++ b/gml/room_instance_layer_background.go @@ -0,0 +1,43 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type RoomInstanceLayerBackground struct { + RoomInstanceLayerDrawBase + name string + sprite *sprite.Sprite + x, y float64 + roomLeft float64 + roomRight float64 +} + +func (layer *RoomInstanceLayerBackground) order() int32 { + return layer.drawOrder +} + +func (layer *RoomInstanceLayerBackground) draw() { + sprite := layer.sprite + width := float64(sprite.Size().X) + x := layer.x + y := layer.y + DrawSprite(sprite, 0, geom.Vec{x, y}) + { + // Tile left + x := x + for x > float64(layer.roomLeft) { + x -= width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } + { + // Tile left + x := x + for x < float64(layer.roomRight) { + x += width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } +} diff --git a/gml/room_instance_layer_instance.go b/gml/room_instance_layer_instance.go new file mode 100644 index 0000000..08eca6c --- /dev/null +++ b/gml/room_instance_layer_instance.go @@ -0,0 +1,21 @@ +package gml + +type RoomInstanceLayerInstance struct { + RoomInstanceLayerDrawBase + index int + name string + manager instanceManager + //_parent *RoomInstance +} + +//func (layer *RoomInstanceLayerInstance) parent() *RoomInstance { +// return layer._parent +//} + +func (layer *RoomInstanceLayerInstance) update(animationUpdate bool) { + layer.manager.update(animationUpdate) +} + +func (layer *RoomInstanceLayerInstance) draw() { + layer.manager.draw() +} diff --git a/gml/room_instance_layer_sprite.go b/gml/room_instance_layer_sprite.go new file mode 100644 index 0000000..4c3fae6 --- /dev/null +++ b/gml/room_instance_layer_sprite.go @@ -0,0 +1,36 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/space" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type RoomInstanceLayerSpriteObject struct { + geom.Vec + Sprite *sprite.Sprite +} + +type RoomInstanceLayerSprite struct { + RoomInstanceLayerDrawBase + name string + sprites []RoomInstanceLayerSpriteObject + spaces space.SpaceBucketArray + hasCollision bool +} + +func (layer *RoomInstanceLayerSprite) order() int32 { + return layer.drawOrder +} + +func (layer *RoomInstanceLayerSprite) draw() { + //screen := gScreen + for _, record := range layer.sprites { + /*position := maybeApplyOffsetByCamera(record.Vec) + frame := sprite.GetRawFrame(record.Sprite, 0) // int(math.Floor(subimage)) + op := ebiten.DrawImageOptions{} + op.GeoM.Translate(position.X, position.Y) + screen.DrawImage(frame, &op)*/ + DrawSprite(record.Sprite, 0, geom.Vec{record.X, record.Y}) + } +} diff --git a/gml/state.go b/gml/state.go index bd326f2..9ba697e 100644 --- a/gml/state.go +++ b/gml/state.go @@ -1,7 +1,12 @@ package gml import ( + "sort" + "strconv" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) var ( @@ -9,8 +14,11 @@ var ( ) type state struct { - globalInstances *instanceManager - roomInstances []RoomInstance + globalInstances *instanceManager + roomInstances []RoomInstance + gWidth int + gHeight int + frameBudgetNanosecondsUsed int64 } func newState() *state { @@ -20,6 +28,14 @@ func newState() *state { } } +func FrameUsage() string { + frameBudgetUsed := gState.frameBudgetNanosecondsUsed + timeTaken := float64(frameBudgetUsed) / 16000000.0 + //fmt.Printf("Time used: %v / 16000000.0\n", frameBudgetUsed) + text := strconv.FormatFloat(timeTaken*100, 'f', 6, 64) + return text + "%" +} + func (state *state) createNewRoomInstance(room *Room) *RoomInstance { state.roomInstances = append(state.roomInstances, RoomInstance{ used: true, @@ -31,14 +47,102 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { // If non-blank room instance, use room data to create if roomInst.room != nil { - // Instantiate instances for this room - for _, obj := range roomInst.room.Instances { - roomInst.InstanceCreate(V(float64(obj.X), float64(obj.Y)), object.ObjectIndex(obj.ObjectIndex)) + // Instance layers + if len(room.InstanceLayers) > 0 { + roomInst.instanceLayers = make([]RoomInstanceLayerInstance, len(room.InstanceLayers)) + for i := 0; i < len(room.InstanceLayers); i++ { + layerData := room.InstanceLayers[i] + roomInst.instanceLayers[i] = RoomInstanceLayerInstance{ + index: i, + } + layer := &roomInst.instanceLayers[i] + layer.drawOrder = layerData.Config.Order + for _, obj := range layerData.Instances { + instanceCreateLayer(geom.Vec{float64(obj.X), float64(obj.Y)}, layer, roomInst, object.ObjectIndex(obj.ObjectIndex)) + } + roomInst.drawLayers = append(roomInst.drawLayers, layer) + } + } else { + // If no instance layers exist in the room data, create one. + roomInst.instanceLayers = make([]RoomInstanceLayerInstance, 1) + roomInst.instanceLayers[0] = RoomInstanceLayerInstance{ + index: 0, + } + roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.instanceLayers[0]) + } + // Background layers + for i := 0; i < len(room.BackgroundLayers); i++ { + layerData := room.BackgroundLayers[i] + spriteName := layerData.SpriteName + if spriteName == "" { + continue + } + layer := new(RoomInstanceLayerBackground) + layer.x = float64(layerData.X) + layer.y = float64(layerData.Y) + layer.roomLeft = float64(room.Left) + layer.roomRight = float64(room.Right) + layer.sprite = LoadSprite(spriteName) + layer.drawOrder = layerData.Config.Order + roomInst.drawLayers = append(roomInst.drawLayers, layer) } + // Sprite layers + for i := 0; i < len(room.SpriteLayers); i++ { + layerData := room.SpriteLayers[i] + hasCollision := layerData.Config.HasCollision + layer := RoomInstanceLayerSprite{} + layer.hasCollision = hasCollision + layer.sprites = make([]RoomInstanceLayerSpriteObject, 0, len(layerData.Sprites)) + for _, sprObj := range layerData.Sprites { + // Add draw sprite + spr := sprite.LoadSprite(sprObj.SpriteName) + record := RoomInstanceLayerSpriteObject{ + Sprite: spr, + } + record.X = float64(sprObj.X) + record.Y = float64(sprObj.Y) + layer.sprites = append(layer.sprites, record) + if hasCollision { + // Add collision + space := layer.spaces.Get(layer.spaces.GetNew()) + space.X = float64(sprObj.X) + space.Y = float64(sprObj.Y) + } + } + //sort.Slice(layer.sprites, func(i, j int) bool { + // return layer.sprites[i].Sprite.Name() < layer.sprites[j].Sprite.Name() + //}) + layer.drawOrder = layerData.Config.Order + roomInst.spriteLayers = append(roomInst.spriteLayers, layer) + roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.spriteLayers[len(roomInst.spriteLayers)-1]) + } + // Sort draw layers by order + sort.Slice(roomInst.drawLayers, func(i, j int) bool { + return roomInst.drawLayers[i].order() < roomInst.drawLayers[j].order() + }) } return roomInst } +func (state *state) deleteRoomInstance(roomInst *RoomInstance) { + for _, layer := range roomInst.instanceLayers { + // NOTE(Jake): 2018-08-21 + // + // Running Destroy() on each rather than InstanceDestroy() + // for speed purposes + // + for _, inst := range layer.manager.instances { + //InstanceDestroy() + inst.Destroy() + cameraInstanceDestroy(inst) + } + layer.manager.reset() + } + + roomInst.used = false + *roomInst = RoomInstance{} +} + func (state *state) update(animationUpdate bool) { // Simulate global instances state.globalInstances.update(animationUpdate) @@ -54,15 +158,24 @@ func (state *state) update(animationUpdate bool) { } func (state *state) draw() { - // Render global instances - state.globalInstances.draw() - - // Render each instance in each room instance - for i := 1; i < len(state.roomInstances); i++ { - roomInst := &state.roomInstances[i] - if !roomInst.used { + for i := 0; i < len(gCameraManager.cameras); i++ { + view := &gCameraManager.cameras[i] + if !view.enabled { continue } - roomInst.draw() + view.update() + cameraSetActive(i) + // Render global instances + state.globalInstances.draw() + + // Render each instance in each room instance + for i := 1; i < len(state.roomInstances); i++ { + roomInst := &state.roomInstances[i] + if !roomInst.used { + continue + } + roomInst.draw() + } } + cameraClearActive() } From 727c29319bd844907759589cee9fb080791f6c17 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sun, 9 Sep 2018 19:06:43 +1000 Subject: [PATCH 02/27] test: add "go get github.com/golang/protobuf" --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d019e8c..bbc3cbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ addons: install: - go get -t -v github.com/hajimehoshi/ebiten/... + - go get -t -v github.com/golang/protobuf/... before_script: - export DISPLAY=:99.0 From 5c0b6024fd1e5222107eda1d545c5acbb5a3b6aa Mon Sep 17 00:00:00 2001 From: Jake B Date: Wed, 21 Nov 2018 19:47:50 +1100 Subject: [PATCH 03/27] various changes --- gml/animation_editor_debug.go | 444 ++++++++++++++++++ gml/animation_editor_nodebug.go | 7 + gml/camera.go | 149 +----- gml/camera_headless.go | 64 +++ gml/camera_nonheadless.go | 222 +++++++++ gml/collision.go | 30 +- gml/debug.go | 8 +- gml/debug_debug.go | 71 +++ gml/debug_nodebug.go | 14 + gml/draw_debug.go | 70 +++ gml/draw_headless.go | 7 + gml/draw_nonheadless.go | 37 +- gml/game.go | 11 - gml/instance.go | 125 +++-- gml/instance_iterator.go | 43 -- gml/instance_iterator_object.go | 58 +++ gml/instance_iterator_room.go | 9 + gml/internal/geom/size.go | 4 + gml/internal/object/instance.go | 21 +- gml/internal/object/object.go | 10 +- gml/internal/object/object_manager.go | 24 +- gml/internal/room/room_manager_nonjs.go | 26 +- gml/internal/space/space.go | 16 - gml/internal/space/space_bucket.go | 107 ----- gml/internal/space/space_object.go | 18 - gml/internal/sprite/collision_mask.go | 17 + gml/internal/sprite/sprite.go | 50 +- gml/internal/sprite/sprite_asset.go | 17 +- gml/internal/sprite/sprite_config.go | 8 +- gml/internal/sprite/sprite_frame.go | 9 + gml/internal/sprite/sprite_frame_headless.go | 7 +- .../sprite/sprite_frame_nonheadless.go | 7 +- gml/internal/sprite/sprite_manager.go | 41 +- gml/internal/sprite/sprite_manager_debug.go | 45 +- .../sprite/sprite_manager_nondebug.go | 3 + gml/keyboard_vk.go | 10 + gml/keyboard_vk_nonheadless.go | 10 + gml/main.go | 40 +- gml/room_editor.go | 7 - gml/room_editor_debug.go | 324 +++---------- gml/room_instance.go | 21 +- gml/room_instance_iterator.go | 30 ++ gml/room_instance_layer_sprite.go | 14 +- gml/sprite.go | 5 +- gml/sprite_selector_debug.go | 111 +++++ gml/sprite_selector_nodebug.go | 9 + gml/state.go | 58 +-- gml/state_headless.go | 7 + gml/state_nonheadless.go | 42 ++ 49 files changed, 1692 insertions(+), 795 deletions(-) create mode 100644 gml/animation_editor_debug.go create mode 100644 gml/animation_editor_nodebug.go create mode 100644 gml/camera_headless.go create mode 100644 gml/camera_nonheadless.go create mode 100644 gml/debug_nodebug.go create mode 100644 gml/draw_debug.go delete mode 100644 gml/game.go delete mode 100644 gml/instance_iterator.go create mode 100644 gml/instance_iterator_object.go create mode 100644 gml/instance_iterator_room.go delete mode 100644 gml/internal/space/space.go delete mode 100644 gml/internal/space/space_bucket.go delete mode 100644 gml/internal/space/space_object.go create mode 100644 gml/internal/sprite/collision_mask.go create mode 100644 gml/internal/sprite/sprite_frame.go create mode 100644 gml/room_instance_iterator.go create mode 100644 gml/sprite_selector_debug.go create mode 100644 gml/sprite_selector_nodebug.go create mode 100644 gml/state_headless.go create mode 100644 gml/state_nonheadless.go diff --git a/gml/animation_editor_debug.go b/gml/animation_editor_debug.go new file mode 100644 index 0000000..739d528 --- /dev/null +++ b/gml/animation_editor_debug.go @@ -0,0 +1,444 @@ +// +build debug + +package gml + +import ( + "encoding/json" + "image/color" + "io/ioutil" + "math" + "strconv" + + "github.com/silbinarywolf/gml-go/gml/internal/file" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type animMenu int + +const ( + animMenuNone animMenu = 0 + iota + animMenuSprite + animMenuSpriteBboxLeft + animMenuSpriteBboxTop + animMenuSpriteBboxRight + animMenuSpriteBboxBottom +) + +const ( + handleDragNone int = 0 + iota + handleDragLeftTop + handleDragRightTop + handleDragRightBottom + handleDragLeftBottom +) + +var ( + animationEditor *debugAnimationEditor +) + +type debugAnimationEditor struct { + debugSpriteViewer + menuOpened animMenu + handleDragging int + handleDragBeginPos geom.Vec + spriteViewing SpriteState + isInPlayback bool +} + +type animationEditorConfig struct { + SpriteSelected string `json:"SpriteSelected,omitempty"` +} + +func animationEditorLazyLoad() { + if animationEditor != nil { + return + } + animationEditor = new(debugAnimationEditor) + animationEditor.animationConfigLoad() +} + +func (editor *debugAnimationEditor) animationConfigLoad() { + configPath := debugConfigPath("animation_editor") + fileData, err := file.OpenFile(configPath) + if err == nil { + bytes, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading " + configPath + "\n" + "Error: " + err.Error()) + } + editorConfig := animationEditorConfig{} + if err := json.Unmarshal(bytes, &editorConfig); err != nil { + panic("Error unmarshalling " + configPath + "\n" + "Error: " + err.Error()) + } + name := editorConfig.SpriteSelected + editor.spriteViewing = SpriteState{} + // todo(Jake): 2018-10-28 + // Add function to load a sprite if it exists, we don't want to crash + // if we remove a sprite that we previously had loaded. + spr := sprite.LoadSprite(name) + editor.spriteViewing.SetSprite(spr) + } +} + +func (editor *debugAnimationEditor) animationConfigSave() { + editorConfig := animationEditorConfig{} + editorConfig.SpriteSelected = editor.spriteViewing.Sprite().Name() + json, _ := json.MarshalIndent(editorConfig, "", "\t") + configPath := debugConfigPath("animation_editor") + err := ioutil.WriteFile(configPath, json, 0644) + if err != nil { + println("Failed to write animation editor config: " + configPath + "\n" + "Error: " + err.Error()) + } +} + +func (editor *debugAnimationEditor) animationEditorToggleMenu(menu animMenu) { + if editor.menuOpened == menu { + menu = animMenuNone + } + spr := editor.spriteViewing.Sprite() + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + collisionMask := sprite.GetCollisionMask(spr, imageIndex, 0) + value, err := strconv.ParseFloat(KeyboardString(), 64) + if err == nil { + switch editor.menuOpened { + case animMenuSpriteBboxLeft: + diff := value - collisionMask.Rect.X + collisionMask.Rect.X = value + collisionMask.Rect.Size.X -= int32(diff) + case animMenuSpriteBboxTop: + diff := value - collisionMask.Rect.Y + collisionMask.Rect.Y = value + collisionMask.Rect.Size.Y -= int32(diff) + case animMenuSpriteBboxRight: + collisionMask.Rect.Size.X = int32(value - collisionMask.Rect.X) + case animMenuSpriteBboxBottom: + collisionMask.Rect.Size.Y = int32(value - collisionMask.Rect.Y) + } + } + editor.menuOpened = menu +} + +func animationEditorUpdate() { + animationEditorLazyLoad() + editor := animationEditor + DrawSetGUI(true) + + // + { + pos := geom.Vec{16, 16} + DrawTextColor(pos, "Animation Editor", color.White) + pos.Y += 24 + DrawTextColor(pos, "Space = Play/Pause Animation", color.White) + pos.Y += 24 + DrawTextColor(pos, "CTRL + P = Open Sprite List", color.White) + if spr := editor.spriteViewing.Sprite(); spr != nil { + pos.Y += 24 + DrawTextColor(pos, "CTRL + S = Save", color.White) + + if KeyboardCheck(VkControl) && KeyboardCheckPressed(VkS) { + err := sprite.DebugWriteSpriteConfig(spr) + if err != nil { + panic(err) + } + } + } + } + + // Shortcut keys + if KeyboardCheck(VkControl) { + if KeyboardCheckPressed(VkP) { + editor.animationEditorToggleMenu(animMenuSprite) + } + } + if KeyboardCheckPressed(VkSpace) { + editor.isInPlayback = !editor.isInPlayback + } + + // Change frame viewing + if spr := editor.spriteViewing.Sprite(); spr != nil { + imageIndex := math.Floor(editor.spriteViewing.ImageIndex()) + if KeyboardCheckPressed(VkLeft) { + imageIndex -= 1 + if imageIndex < 0 { + imageIndex = editor.spriteViewing.ImageNumber() - 1 + } + editor.spriteViewing.SetImageIndex(imageIndex) + } + if KeyboardCheckPressed(VkRight) { + imageIndex += 1 + if imageIndex > editor.spriteViewing.ImageNumber() { + imageIndex = 0 + } + editor.spriteViewing.SetImageIndex(imageIndex) + } + } + + // + var collisionMask *sprite.CollisionMask + var inheritCollisionMask *sprite.CollisionMask + if spr := editor.spriteViewing.Sprite(); spr != nil { + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + switch collisionMask.Kind { + case sprite.CollisionMaskInherit: + for ; imageIndex > 0; imageIndex-- { + collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + if collisionMask.Kind != sprite.CollisionMaskInherit { + break + } + } + if imageIndex == 0 { + collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + if collisionMask.Kind == sprite.CollisionMaskInherit { + collisionMask = &sprite.CollisionMask{ + Kind: sprite.CollisionMaskManual, + Rect: geom.Rect{ + Size: spr.Size(), + }, + } + } + } + inheritCollisionMask = collisionMask + case sprite.CollisionMaskManual: + // + } + } + + if spr := editor.spriteViewing.Sprite(); spr != nil { + size := spr.Size() + pos := geom.Vec{float64(windowWidth()/2) - (float64(size.X) / 2), float64(windowHeight()/2) - (float64(size.Y) / 2)} + + { + // Draw backdrop + pos := pos + DrawRectangle(pos, size.Vec(), color.RGBA{195, 195, 195, 255}) + } + + // Sprite + if editor.isInPlayback { + editor.spriteViewing.ImageUpdate() + } + DrawSprite(spr, editor.spriteViewing.ImageIndex(), pos) + + if collisionMask != nil { + // Draw collision box + var rect geom.Rect = collisionMask.Rect + rect.X += pos.X + rect.Y += pos.Y + DrawRectangle(rect.Vec, rect.Size.Vec(), color.RGBA{255, 0, 0, 128}) + } + + if collisionMask != nil && + inheritCollisionMask == nil { + // Draw resize handles + offset := pos + + // Get distance mouse moved + var diffX, diffY float64 + { + mousePos := mouseScreenPosition() + handleBeginPos := editor.handleDragBeginPos + diffX = mousePos.X - handleBeginPos.X + diffY = mousePos.Y - handleBeginPos.Y + if KeyboardCheck(VkControl) { + diffX = math.Round(diffX / 4) + diffY = math.Round(diffY / 4) + } + } + + { + // Top-Left + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Left() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Top() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragLeftTop { + collisionMask.Rect.X += diffX + collisionMask.Rect.Size.X -= int32(diffX) + collisionMask.Rect.Y += diffY + collisionMask.Rect.Size.Y -= int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragLeftTop + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Top-Right + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Right() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Top() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragRightTop { + collisionMask.Rect.Size.X += int32(diffX) + collisionMask.Rect.Y += diffY + collisionMask.Rect.Size.Y -= int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragRightTop + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Bottom-Left + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Left() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Bottom() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragLeftBottom { + collisionMask.Rect.X += diffX + collisionMask.Rect.Size.X -= int32(diffX) + //collisionMask.Rect.Y = diffY + collisionMask.Rect.Size.Y += int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragLeftBottom + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Bottom-Right + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Right() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Bottom() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragRightBottom { + collisionMask.Rect.Size.X += int32(diffX) + collisionMask.Rect.Size.Y += int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragRightBottom + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Update State + editor.handleDragBeginPos = mouseScreenPosition() + if !MouseCheckButton(MbLeft) { + editor.handleDragging = handleDragNone + } + } + } + } + + if editor.menuOpened != animMenuNone { + switch editor.menuOpened { + case animMenuSprite: + if selectedSpr, ok := animationEditor.debugSpriteViewer.Update(); ok { + editor.spriteViewing = SpriteState{} + editor.spriteViewing.SetSprite(selectedSpr) + editor.menuOpened = animMenuNone + editor.animationConfigSave() + } + } + } + + if spr := editor.spriteViewing.Sprite(); spr != nil { + basePos := geom.Vec{(float64(windowWidth()) / 2) - 140, float64(windowHeight())} + basePos.Y -= 210 + + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + DrawTextF(basePos, "Frame: %d", imageIndex) + basePos.Y += 24 + if drawButton(basePos, "Kind: Inherit") { + collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask.Kind = sprite.CollisionMaskInherit + } + basePos.Y += 30 + if drawButton(basePos, "Kind: Manual") { + collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + if collisionMask.Kind != sprite.CollisionMaskManual { + collisionMask.Rect = inheritCollisionMask.Rect + collisionMask.Kind = sprite.CollisionMaskManual + } + } + basePos.Y += 40 + + pos := basePos + + // + drawMask := inheritCollisionMask + if drawMask == nil { + drawMask = collisionMask + } + + { + text := strconv.FormatFloat(drawMask.Rect.Left(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk1) { + editor.animationEditorToggleMenu(animMenuSpriteBboxLeft) + if editor.menuOpened == animMenuSpriteBboxLeft { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Left (CTRL + 1)", text, editor.menuOpened == animMenuSpriteBboxLeft) { + editor.animationEditorToggleMenu(animMenuSpriteBboxLeft) + } + } + { + pos.Y += 24 + + text := strconv.FormatFloat(drawMask.Rect.Bottom(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk3) { + editor.animationEditorToggleMenu(animMenuSpriteBboxBottom) + if editor.menuOpened == animMenuSpriteBboxBottom { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Bottom (CTRL + 3)", text, editor.menuOpened == animMenuSpriteBboxBottom) { + editor.animationEditorToggleMenu(animMenuSpriteBboxBottom) + } + } + pos = basePos + pos.X += 160 + { + text := strconv.FormatFloat(drawMask.Rect.Top(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk2) { + editor.animationEditorToggleMenu(animMenuSpriteBboxTop) + if editor.menuOpened == animMenuSpriteBboxTop { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Top (CTRL + 2)", text, editor.menuOpened == animMenuSpriteBboxTop) { + editor.animationEditorToggleMenu(animMenuSpriteBboxTop) + } + } + { + pos.Y += 24 + + text := strconv.FormatFloat(drawMask.Rect.Right(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk4) { + editor.animationEditorToggleMenu(animMenuSpriteBboxRight) + if editor.menuOpened == animMenuSpriteBboxRight { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Right (CTRL + 4)", text, editor.menuOpened == animMenuSpriteBboxRight) { + editor.animationEditorToggleMenu(animMenuSpriteBboxRight) + } + } + } +} diff --git a/gml/animation_editor_nodebug.go b/gml/animation_editor_nodebug.go new file mode 100644 index 0000000..482d4fd --- /dev/null +++ b/gml/animation_editor_nodebug.go @@ -0,0 +1,7 @@ +// +build !debug + +package gml + +func animationEditorUpdate() { + +} diff --git a/gml/camera.go b/gml/camera.go index 4728216..e6ef96b 100644 --- a/gml/camera.go +++ b/gml/camera.go @@ -1,152 +1,7 @@ -package gml - -import ( - "math" +// +build headless - "github.com/silbinarywolf/gml-go/gml/internal/geom" - "github.com/silbinarywolf/gml-go/gml/internal/object" -) +package gml var ( gCameraManager *cameraManager = newCameraState() ) - -type cameraManager struct { - cameras [8]camera - current *camera -} - -type camera struct { - enabled bool - follow object.ObjectType - geom.Vec - size geom.Vec - scale geom.Vec -} - -func newCameraState() *cameraManager { - manager := new(cameraManager) - for i := 0; i < len(manager.cameras); i++ { - view := &manager.cameras[i] - view.scale.X = 1 - view.scale.Y = 1 - } - return manager -} - -func (view *camera) Size() geom.Vec { - return view.size -} - -func (view *camera) Scale() geom.Vec { - return view.scale -} - -func CameraSetEnabled(index int) { - view := &gCameraManager.cameras[index] - view.enabled = true -} - -func cameraGetActive() *camera { - return gCameraManager.current -} - -func cameraSetActive(index int) { - gCameraManager.current = &gCameraManager.cameras[index] -} - -func cameraClearActive() { - gCameraManager.current = nil -} - -func CameraGetViewPos(index int) geom.Vec { - view := &gCameraManager.cameras[index] - return view.Vec -} - -func CameraSetViewPos(index int, pos geom.Vec) { - view := &gCameraManager.cameras[index] - view.Vec = pos - - if inst := view.follow; inst != nil { - roomInst := RoomGetInstance(object.RoomInstanceIndex(inst.BaseObject())) - if roomInst != nil { - room := roomInst.room - left := float64(room.Left) - right := float64(room.Right) - top := float64(room.Top) - bottom := float64(room.Bottom) - - view.X = pos.X - (view.size.X / 2) - view.Y = pos.Y - (view.size.Y / 2) - if view.X < left { - view.X = left - } - if view.X+view.size.X > right { - view.X = right - view.size.X - } - if view.Y < top { - view.Y = top - } - if view.Y+view.size.Y > bottom { - view.Y = bottom - view.size.Y - } - view.X = math.Floor(view.X) - view.Y = math.Floor(view.Y) - } - } -} - -func CameraSetViewSize(index int, size geom.Vec) { - view := &gCameraManager.cameras[index] - view.size = size -} - -func CameraSetViewTarget(index int, inst object.ObjectType) { - view := &gCameraManager.cameras[index] - view.follow = inst -} - -func cameraInstanceDestroy(inst object.ObjectType) { - manager := gCameraManager - for i := 0; i < len(manager.cameras); i++ { - view := &manager.cameras[i] - if view.follow == inst { - view.follow = nil - } - } -} - -func (view *camera) update() { - if view.follow != nil { - //cam := cameraGetActive() - inst := view.follow.BaseObject() - if inst != nil { - roomInst := RoomGetInstance(object.RoomInstanceIndex(inst)) - if roomInst != nil { - room := roomInst.room - left := float64(room.Left) - right := float64(room.Right) - top := float64(room.Top) - bottom := float64(room.Bottom) - - view.X = inst.X - (view.size.X / 2) - view.Y = inst.Y - (view.size.Y / 2) - if view.X < left { - view.X = left - } - if view.X+view.size.X > right { - view.X = right - view.size.X - } - if view.Y < top { - view.Y = top - } - if view.Y+view.size.Y > bottom { - view.Y = bottom - view.size.Y - } - view.X = math.Floor(view.X) - view.Y = math.Floor(view.Y) - } - } - } -} diff --git a/gml/camera_headless.go b/gml/camera_headless.go new file mode 100644 index 0000000..0a5487b --- /dev/null +++ b/gml/camera_headless.go @@ -0,0 +1,64 @@ +// +build headless + +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +type cameraManager struct { +} + +type camera struct { +} + +func newCameraState() *cameraManager { + return nil +} + +func (view *camera) Reset() { +} + +func CameraCreate(index int, windowX, windowY, windowWidth, windowHeight float64) { +} + +func CameraSetSize(index int, windowWidth, windowHeight float64) { +} + +// cameraGetActive gets the current camera we're drawing objects onto +func cameraGetActive() *camera { + return nil +} + +// cameraSetActive gets the current camera we want to draw objects onto +func cameraSetActive(index int) { +} + +func cameraClearActive() { +} + +func CameraGetViewPos(index int) geom.Vec { + return geom.Vec{0, 0} +} + +func CameraSetViewPos(index int, pos geom.Vec) { +} + +func CameraSetViewSize(index int, size geom.Vec) { +} + +func CameraSetViewTarget(index int, inst object.ObjectType) { +} + +func cameraClear(index int) { +} + +func cameraDraw(index int) { +} + +func cameraInstanceDestroy(inst object.ObjectType) { +} + +func (view *camera) update() { +} diff --git a/gml/camera_nonheadless.go b/gml/camera_nonheadless.go new file mode 100644 index 0000000..afeb1e5 --- /dev/null +++ b/gml/camera_nonheadless.go @@ -0,0 +1,222 @@ +// +build !headless + +package gml + +import ( + "math" + "strconv" + + "github.com/hajimehoshi/ebiten" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +var ( + gCameraManager *cameraManager = newCameraState() +) + +type cameraManager struct { + cameras [8]camera + current *camera +} + +type camera struct { + enabled bool + follow object.ObjectType + geom.Vec + windowPos geom.Vec + size geom.Vec + scale geom.Vec + screen *ebiten.Image +} + +func newCameraState() *cameraManager { + manager := new(cameraManager) + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + view.Reset() + } + return manager +} + +func (view *camera) Reset() { + view.size.X = float64(windowWidth()) + view.size.Y = float64(windowHeight()) + view.scale.X = 1 + view.scale.Y = 1 +} + +//func (view *camera) Size() geom.Vec { +// return view.size +//} + +func (view *camera) Scale() geom.Vec { + return view.scale +} + +func CameraCreate(index int, windowX, windowY, windowWidth, windowHeight float64) { + view := &gCameraManager.cameras[index] + if view.enabled { + panic("Camera " + strconv.Itoa(index) + " is already enabled.") + return + } + if windowWidth == 0 || + windowHeight == 0 { + panic("Cannot have camera window width or height of 0") + } + view.windowPos.X = windowX + view.windowPos.Y = windowY + view.size.X = windowWidth + view.size.Y = windowHeight + view.enabled = true +} + +func CameraSetSize(index int, windowWidth, windowHeight float64) { + view := &gCameraManager.cameras[index] + if !view.enabled { + panic("Camera " + strconv.Itoa(index) + " is not enabled.") + } + view.size.X = windowWidth + view.size.Y = windowHeight +} + +// cameraGetActive gets the current camera we're drawing objects onto +func cameraGetActive() *camera { + return gCameraManager.current +} + +// cameraSetActive gets the current camera we want to draw objects onto +func cameraSetActive(index int) { + gCameraManager.current = &gCameraManager.cameras[index] +} + +func cameraClearActive() { + gCameraManager.current = nil +} + +func CameraGetViewPos(index int) geom.Vec { + view := &gCameraManager.cameras[index] + return view.Vec +} + +func CameraSetViewPos(index int, pos geom.Vec) { + view := &gCameraManager.cameras[index] + view.Vec = pos + + if inst := view.follow; inst != nil { + roomInst := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if roomInst != nil { + room := roomInst.room + left := float64(room.Left) + right := float64(room.Right) + top := float64(room.Top) + bottom := float64(room.Bottom) + + view.X = pos.X - (view.size.X / 2) + view.Y = pos.Y - (view.size.Y / 2) + if view.X < left { + view.X = left + } + if view.X+view.size.X > right { + view.X = right - view.size.X + } + if view.Y < top { + view.Y = top + } + if view.Y+view.size.Y > bottom { + view.Y = bottom - view.size.Y + } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) + } + } +} + +func CameraSetViewSize(index int, size geom.Vec) { + view := &gCameraManager.cameras[index] + view.size = size +} + +func CameraSetViewTarget(index int, inst object.ObjectType) { + view := &gCameraManager.cameras[index] + view.follow = inst +} + +func cameraClear(index int) { + view := &gCameraManager.cameras[index] + view.screen.Clear() +} + +func cameraDraw(index int) { + view := &gCameraManager.cameras[index] + op := ebiten.DrawImageOptions{} + op.GeoM.Scale(view.scale.X, view.scale.Y) + op.GeoM.Translate(view.windowPos.X, view.windowPos.Y) + gScreen.DrawImage(view.screen, &op) +} + +func cameraInstanceDestroy(inst object.ObjectType) { + manager := gCameraManager + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + if view.follow == inst { + view.follow = nil + } + } +} + +func (view *camera) update() { + // Update screen render target + { + mustCreateNewRenderTarget := false + if view.screen == nil { + // Create new camera + mustCreateNewRenderTarget = true + } else { + // Resize camera + if int(view.size.X) != view.screen.Bounds().Max.X || + int(view.size.Y) != view.screen.Bounds().Max.Y { + mustCreateNewRenderTarget = true + } + } + if mustCreateNewRenderTarget { + image, err := ebiten.NewImage(int(view.size.X), int(view.size.Y), ebiten.FilterDefault) + if err != nil { + panic(err) + } + view.screen = image + } + } + + // Update player follow + if view.follow != nil { + inst := view.follow.BaseObject() + if inst != nil { + roomInst := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if roomInst != nil { + room := roomInst.room + left := float64(room.Left) + right := float64(room.Right) + top := float64(room.Top) + bottom := float64(room.Bottom) + + view.X = inst.X - (view.size.X / 2) + view.Y = inst.Y - (view.size.Y / 2) + if view.X < left { + view.X = left + } + if view.X+view.size.X > right { + view.X = right - view.size.X + } + if view.Y < top { + view.Y = top + } + if view.Y+view.size.Y > bottom { + view.Y = bottom - view.size.Y + } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) + } + } + } +} diff --git a/gml/collision.go b/gml/collision.go index e89d81c..1114bdd 100644 --- a/gml/collision.go +++ b/gml/collision.go @@ -2,7 +2,6 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/geom" - "github.com/silbinarywolf/gml-go/gml/internal/object" ) const ( @@ -14,16 +13,12 @@ type collisionObject interface { } func PlaceFree(instType collisionObject, position geom.Vec) bool { - baseObj := instType.BaseObject() - room := RoomGetInstance(object.RoomInstanceIndex(baseObj)) + inst := instType.BaseObject() + room := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) if room == nil { panic("RoomInstance this object belongs to has been destroyed") } - // Keep pointer to space object to avoid comparing collision - // against self - inst := baseObj.Space - // Create collision rect at position provided in function r1 := inst.Rect r1.Vec = position @@ -32,7 +27,15 @@ func PlaceFree(instType collisionObject, position geom.Vec) bool { //var debugString string hasCollision := false for i := 0; i < len(room.instanceLayers); i++ { - spaces := &room.instanceLayers[i].manager.spaces + for _, other := range room.instanceLayers[i].manager.instances { + other := other.BaseObject() + if other.Solid() && + r1.CollisionRectangle(other.Rect) && + inst != other { + hasCollision = true + } + } + /*spaces := &room.instanceLayers[i].manager.spaces for _, bucket := range spaces.Buckets() { for i := 0; i < bucket.Len(); i++ { other := bucket.Get(i) @@ -58,14 +61,19 @@ func PlaceFree(instType collisionObject, position geom.Vec) bool { hasCollision = true } } - } + }*/ } for i := 0; i < len(room.spriteLayers); i++ { layer := &room.spriteLayers[i] if !layer.hasCollision { continue } - spaces := layer.spaces + for _, other := range layer.sprites { + if r1.CollisionRectangle(other.Rect()) { + hasCollision = true + } + } + /*spaces := layer.sprites for _, bucket := range spaces.Buckets() { for i := 0; i < bucket.Len(); i++ { other := bucket.Get(i) @@ -75,7 +83,7 @@ func PlaceFree(instType collisionObject, position geom.Vec) bool { hasCollision = true } } - } + }*/ } /*if DEBUG_COLLISION && diff --git a/gml/debug.go b/gml/debug.go index 6905805..14dd06a 100644 --- a/gml/debug.go +++ b/gml/debug.go @@ -1,7 +1,9 @@ -// +build !debug - package gml +type debugMenu int + const ( - debugMode = false + debugMenuNone debugMenu = 0 + iota + debugMenuRoomEditor + debugMenuAnimationEditor ) diff --git a/gml/debug_debug.go b/gml/debug_debug.go index ed2dd3e..9e28ce0 100644 --- a/gml/debug_debug.go +++ b/gml/debug_debug.go @@ -2,6 +2,77 @@ package gml +import ( + "os" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" + "github.com/silbinarywolf/gml-go/gml/internal/user" +) + const ( debugMode = true ) + +var ( + debugMenuID = debugMenuNone +) + +func debugConfigPath(name string) string { + configPath := user.HomeDir() + "/.gmlgo" + if _, err := os.Stat(configPath); os.IsNotExist(err) { + os.Mkdir(configPath, 0700) + } + configPath = configPath + "/" + name + ".json" + return configPath +} + +func debugMenuOpenOrToggleClosed(id debugMenu) { + if debugMenuID != id { + debugMenuID = id + } else { + debugMenuID = debugMenuNone + + // Reset camera + CameraSetViewSize(0, geom.Vec{float64(windowWidth()), float64(windowHeight())}) + CameraSetViewTarget(0, nil) + } +} + +func debugUpdate() { + sprite.DebugWatch() + + if KeyboardCheck(VkControl) { + if KeyboardCheckPressed(VkA) { + debugMenuOpenOrToggleClosed(debugMenuAnimationEditor) + } + } +} + +func debugDrawIsMouseOver(pos geom.Vec, size geom.Vec) bool { + if DrawGetGUI() { + return isMouseScreenOver(pos, size) + } else { + return isMouseOver(pos, size) + } +} + +func isMouseOver(pos geom.Vec, size geom.Vec) bool { + mousePos := MousePosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} + +func isMouseScreenOver(pos geom.Vec, size geom.Vec) bool { + mousePos := mouseScreenPosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} diff --git a/gml/debug_nodebug.go b/gml/debug_nodebug.go new file mode 100644 index 0000000..9a9476b --- /dev/null +++ b/gml/debug_nodebug.go @@ -0,0 +1,14 @@ +// +build !debug + +package gml + +const ( + debugMode = false +) + +const ( + debugMenuID = debugMenuNone +) + +func debugUpdate() { +} diff --git a/gml/draw_debug.go b/gml/draw_debug.go new file mode 100644 index 0000000..85e1178 --- /dev/null +++ b/gml/draw_debug.go @@ -0,0 +1,70 @@ +// +build debug + +package gml + +import ( + "image/color" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +func drawInputText(pos *geom.Vec, label string, text string, isFocused bool) bool { + size := geom.Vec{100, 20} + DrawTextColor(geom.Vec{pos.X, pos.Y}, label, color.White) + pos.Y += 12 + borderCol := color.RGBA{255, 255, 255, 255} + isMouseOver := debugDrawIsMouseOver(*pos, size) + if isMouseOver { + borderCol = color.RGBA{255, 255, 0, 255} + } + if isFocused { + text = KeyboardString() + "|" + borderCol = color.RGBA{255, 0, 0, 255} + } + DrawRectangleBorder(*pos, size, color.Black, 2, borderCol) + DrawTextColor(geom.Vec{pos.X + 8, pos.Y + 16}, text, color.White) + pos.Y += size.Y + if MouseCheckPressed(MbLeft) && isMouseOver { + if !isFocused { + SetKeyboardString(text) + } + return true + } + if isFocused && + (KeyboardCheckPressed(VkEnter) || KeyboardCheckPressed(VkNumpadEnter)) { + return true + } + return false +} + +func drawButton(pos geom.Vec, text string) bool { + // Config + paddingH := 32.0 + borderWidth := 2.0 + size := geom.Vec{StringWidth(text) + paddingH, 24} + + // Handle mouse over + isMouseOver := debugDrawIsMouseOver(pos, size) + var innerRectColor color.RGBA + if isMouseOver { + innerRectColor = color.RGBA{180, 180, 180, 255} + } else { + innerRectColor = color.RGBA{255, 255, 255, 255} + } + + // Draw Border (outer rect) + DrawRectangleBorder(pos, size, innerRectColor, borderWidth, color.RGBA{0, 162, 232, 255}) + /* pos.X += borderWidth + pos.Y += borderWidth + size.X -= borderWidth * 2 + size.Y -= borderWidth * 2 + + // Draw Rect (inner rect) + DrawRectangle(pos, size, innerRectColor)*/ + + // Draw Text + pos.X += paddingH * 0.5 + pos.Y += 16 + DrawTextColor(pos, text, color.Black) + return MouseCheckPressed(MbLeft) && isMouseOver +} diff --git a/gml/draw_headless.go b/gml/draw_headless.go index bc3b533..86b5ca7 100644 --- a/gml/draw_headless.go +++ b/gml/draw_headless.go @@ -8,6 +8,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) +func DrawGetGUI() bool { + return false +} + func DrawSetGUI(guiMode bool) { } @@ -28,3 +32,6 @@ func DrawRectangleBorder(position Vec, size Vec, color color.Color, borderSize f func DrawText(position Vec, message string) { } + +func DrawTextF(position Vec, message string, args ...interface{}) { +} diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index 7f663a6..4480eda 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -3,6 +3,7 @@ package gml import ( + "fmt" "image/color" "math" @@ -18,17 +19,20 @@ var ( isDrawGuiMode = false ) +func DrawGetGUI() bool { + return isDrawGuiMode +} + func DrawSetGUI(guiMode bool) { isDrawGuiMode = guiMode } func DrawSprite(spr *sprite.Sprite, subimage float64, position geom.Vec) { - screen := gScreen position = maybeApplyOffsetByCamera(position) frame := sprite.GetRawFrame(spr, int(math.Floor(subimage))) op := ebiten.DrawImageOptions{} op.GeoM.Translate(position.X, position.Y) - screen.DrawImage(frame, &op) + drawGetTarget().DrawImage(frame, &op) } func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec) { @@ -37,7 +41,6 @@ func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position geom.Vec, s // draw_sprite_ext( sprite, subimg, x, y, xscale, yscale, rot, colour, alpha ); func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec, alpha float64) { - screen := gScreen position = maybeApplyOffsetByCamera(position) // NOTE(Jake): 2018-07-09 // @@ -59,43 +62,51 @@ func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position geom.Vec, scal op.ColorM.Scale(1.0, 1.0, 1.0, alpha) //op.Colorgeom.RotateHue(float64(360)) - screen.DrawImage(frame, &op) + drawGetTarget().DrawImage(frame, &op) } func DrawRectangle(position geom.Vec, size geom.Vec, col color.Color) { - screen := gScreen position = maybeApplyOffsetByCamera(position) - ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, col) + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, col) } func DrawRectangleBorder(position geom.Vec, size geom.Vec, color color.Color, borderSize float64, borderColor color.Color) { - screen := gScreen position = maybeApplyOffsetByCamera(position) - ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, borderColor) + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, borderColor) position.X += borderSize position.Y += borderSize size.X -= borderSize * 2 size.Y -= borderSize * 2 - ebitenutil.DrawRect(screen, position.X, position.Y, size.X, size.Y, color) + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, color) } func DrawText(position geom.Vec, message string) { - screen := gScreen if !g_fontManager.hasFontSet() { panic("Must call DrawSetFont() before calling DrawText.") } position = maybeApplyOffsetByCamera(position) - text.Draw(screen, message, g_fontManager.currentFont.font, int(position.X), int(position.Y), color.White) + text.Draw(drawGetTarget(), message, g_fontManager.currentFont.font, int(position.X), int(position.Y), color.White) } func DrawTextColor(position geom.Vec, message string, col color.Color) { - screen := gScreen if !g_fontManager.hasFontSet() { panic("Must call DrawSetFont() before calling DrawText.") } position = maybeApplyOffsetByCamera(position) - text.Draw(screen, message, g_fontManager.currentFont.font, int(position.X), int(position.Y), col) + text.Draw(drawGetTarget(), message, g_fontManager.currentFont.font, int(position.X), int(position.Y), col) +} + +func DrawTextF(position Vec, message string, args ...interface{}) { + message = fmt.Sprintf(message, args...) + DrawText(position, message) +} + +func drawGetTarget() *ebiten.Image { + if camera := cameraGetActive(); camera != nil { + return camera.screen + } + return gScreen } func maybeApplyOffsetByCamera(position geom.Vec) geom.Vec { diff --git a/gml/game.go b/gml/game.go deleted file mode 100644 index 6859079..0000000 --- a/gml/game.go +++ /dev/null @@ -1,11 +0,0 @@ -package gml - -type gameState struct { - hasGameRestarted bool -} - -var g_game gameState - -func GameRestart() { - g_game.hasGameRestarted = true -} diff --git a/gml/instance.go b/gml/instance.go index 4e4487e..3e28bdc 100644 --- a/gml/instance.go +++ b/gml/instance.go @@ -3,27 +3,10 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" - "github.com/silbinarywolf/gml-go/gml/internal/space" ) -/*type InstanceIndex struct { - layerIndex int - roomInstanceIndex int - instanceIndex int - obj object.ObjectType -} -*/ -type instanceManagerResettableData struct { - instances []object.ObjectType - spaces space.SpaceBucketArray -} - -func (manager *instanceManager) reset() { - manager.instanceManagerResettableData = instanceManagerResettableData{} -} - type instanceManager struct { - instanceManagerResettableData + instances []object.ObjectType } func newInstanceManager() *instanceManager { @@ -32,25 +15,35 @@ func newInstanceManager() *instanceManager { return manager } +func (manager *instanceManager) reset() { + *manager = instanceManager{} +} + func instanceCreateLayer(position geom.Vec, layer *RoomInstanceLayerInstance, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) - /*result := InstanceIndex{ - layerIndex: layer.index, - roomInstanceIndex: roomInst.Index(), - instanceIndex: len(layer.manager.instances), +} + +func InstanceChangeRoom(inst object.ObjectType, roomInstanceIndex int) { + roomInst := &gState.roomInstances[roomInstanceIndex] + if !roomInst.used { + return } - result.obj = layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) - return result.obj*/ + // NOTE(Jake): 2018-07-22 + // For now instances default to the last instance layer + layerIndex := len(roomInst.instanceLayers) - 1 + layer := &roomInst.instanceLayers[layerIndex] + + instanceRemove(inst) + layer.manager.instanceAdd(inst, roomInst.Index(), layer.index) } -func InstanceCreateRoom(position geom.Vec, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { +func InstanceCreateRoom(position geom.Vec, roomInstanceIndex int, objectIndex object.ObjectIndex) object.ObjectType { + roomInst := &gState.roomInstances[roomInstanceIndex] // NOTE(Jake): 2018-07-22 - // // For now instances default to the last instance layer - // layerIndex := len(roomInst.instanceLayers) - 1 - //fmt.Printf("InstanceCreateRoom: Create on layer %d\n", layerIndex) layer := &roomInst.instanceLayers[layerIndex] + //fmt.Printf("InstanceCreateRoom: Create on layer %d\n", layerIndex) return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) } @@ -59,7 +52,7 @@ func InstanceExists(inst object.ObjectType) bool { if baseObj == nil { return false } - roomInst := RoomGetInstance(object.RoomInstanceIndex(baseObj)) + roomInst := roomGetInstance(baseObj.RoomInstanceIndex()) // todo(Jake): 2018-08-20 // // Check to see if current entity is destroyed @@ -67,16 +60,19 @@ func InstanceExists(inst object.ObjectType) bool { return roomInst != nil } +func (manager *instanceManager) instanceAdd(inst object.ObjectType, roomInstanceIndex, layerIndex int) { + // Move entity to new list + index := len(manager.instances) + object.MoveInstance(inst, index, roomInstanceIndex, layerIndex) + manager.instances = append(manager.instances, inst) +} + func (manager *instanceManager) InstanceCreate(position geom.Vec, objectIndex object.ObjectIndex, roomInstanceIndex, layerIndex int) object.ObjectType { // Create and add to entity list index := len(manager.instances) - // Get Pos/Size part of instance (SpaceObject) - spaceIndex := manager.spaces.GetNew() - space := manager.spaces.Get(spaceIndex) - // Get instance - inst := object.NewRawInstance(objectIndex, index, roomInstanceIndex, layerIndex, space, spaceIndex) + inst := object.NewRawInstance(objectIndex, index, roomInstanceIndex, layerIndex) manager.instances = append(manager.instances, inst) // Init and Set position @@ -85,15 +81,11 @@ func (manager *instanceManager) InstanceCreate(position geom.Vec, objectIndex ob return inst } -func InstanceDestroy(inst object.ObjectType) { - // Destroy this - inst.Destroy() - cameraInstanceDestroy(inst) - +func instanceRemove(inst object.ObjectType) { baseObj := inst.BaseObject() // Get slots - roomInstanceIndex := object.RoomInstanceIndex(baseObj) + roomInstanceIndex := baseObj.RoomInstanceIndex() layerIndex := object.LayerInstanceIndex(baseObj) index := object.InstanceIndex(baseObj) @@ -102,31 +94,68 @@ func InstanceDestroy(inst object.ObjectType) { layerInst := &roomInst.instanceLayers[layerIndex] manager := &layerInst.manager - // Free up SpaceObject slot - spaceIndex := baseObj.SpaceIndex() - baseObj.Space = nil - if spaceIndex > -1 { - manager.spaces.Remove(spaceIndex) + if manager.instances[index] != inst { + panic("instanceRemove failed as instance provided has already been removed") } + // Get index + /*index := -1 + for i, otherInst := range manager.instances { + if inst == otherInst { + index = i + } + } + if index == -1 { + panic("instanceRemove failed as instance provided has already been removed") + }*/ // Unordered delete + // NOTE(Jake): 2018-09-15 + // Im aware this sometimes causes the server to crash... + // but I also don't want to fix this yet as I might store each type of an + // entity in its own bucket array soon... + // + // At the very least I should maybe make this a "mark as deleted" + // system where it cleans up the entity list at the end of the frame. + // lastEntry := manager.instances[len(manager.instances)-1] manager.instances[index] = lastEntry + object.SetInstanceIndex(lastEntry.BaseObject(), index) manager.instances = manager.instances[:len(manager.instances)-1] } +func InstanceDestroy(inst object.ObjectType) { + baseObj := inst.BaseObject() + if object.IsDestroyed(baseObj) { + // NOTE(Jake): 2018-10-07 + // Maybe making this just silently returning will be better / less error + // prone? For now lets be strict. + panic("Cannot call InstanceDestroy on an object more than once.") + return + } + + // Run user-destroy code + inst.Destroy() + + // Mark as destroyed + object.MarkAsDestroyed(baseObj) + + // NOTE(Jake): 2018-10-07 + // Remove at the end of the frame (gState.update) + gState.instancesMarkedForDelete = append(gState.instancesMarkedForDelete, inst) +} + func (manager *instanceManager) update(animationUpdate bool) { { instances := manager.instances for _, inst := range instances { + if inst == nil { + continue + } inst.Update() } if animationUpdate { for _, inst := range instances { - if inst == nil { - continue - } baseObj := inst.BaseObject() baseObj.SpriteState.ImageUpdate() } diff --git a/gml/instance_iterator.go b/gml/instance_iterator.go deleted file mode 100644 index 29257d8..0000000 --- a/gml/instance_iterator.go +++ /dev/null @@ -1,43 +0,0 @@ -package gml - -import ( - "github.com/silbinarywolf/gml-go/gml/internal/object" -) - -type instanceIteratorState struct { - roomInstanceIndex int - layerIndex int - instanceIndex int -} - -func InstancesIterator(inst object.ObjectType) instanceIteratorState { - roomInstanceIndex := object.RoomInstanceIndex(inst.BaseObject()) - return instanceIteratorState{ - instanceIndex: -1, - roomInstanceIndex: roomInstanceIndex, - } -} - -func (iterator *instanceIteratorState) Next() bool { - roomInst := &gState.roomInstances[iterator.roomInstanceIndex] - if iterator.layerIndex >= len(roomInst.instanceLayers) { - return false - } - layer := &roomInst.instanceLayers[iterator.layerIndex] - iterator.instanceIndex++ - for { - if iterator.instanceIndex < len(layer.manager.instances) { - return true - } - iterator.instanceIndex = 0 - iterator.layerIndex++ - if iterator.layerIndex < len(roomInst.instanceLayers) { - continue - } - return false - } -} - -func (iterator *instanceIteratorState) Value() object.ObjectType { - return gState.roomInstances[iterator.roomInstanceIndex].instanceLayers[iterator.layerIndex].manager.instances[iterator.instanceIndex] -} diff --git a/gml/instance_iterator_object.go b/gml/instance_iterator_object.go new file mode 100644 index 0000000..4d42b67 --- /dev/null +++ b/gml/instance_iterator_object.go @@ -0,0 +1,58 @@ +package gml + +import ( + "reflect" + + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +type instanceIteratorObjectState struct { + roomInstanceIndex int + layerIndex int + instanceIndex int +} + +// Iterate over instances that belong to the same room as provided argument +func InstancesIteratorObject(inst object.ObjectType) instanceIteratorObjectState { + if inst == nil || + reflect.ValueOf(inst).IsNil() { + return instanceIteratorObjectState{ + roomInstanceIndex: -1, + } + } + baseObj := inst.BaseObject() + roomInstanceIndex := baseObj.RoomInstanceIndex() + return instanceIteratorObjectState{ + instanceIndex: -1, + roomInstanceIndex: roomInstanceIndex, + } +} + +func (iterator *instanceIteratorObjectState) Next() bool { + if iterator.roomInstanceIndex == -1 { + return false + } + roomInst := &gState.roomInstances[iterator.roomInstanceIndex] + if iterator.layerIndex >= len(roomInst.instanceLayers) { + return false + } +loop: + iterator.instanceIndex++ + layer := &roomInst.instanceLayers[iterator.layerIndex] + for iterator.instanceIndex < len(layer.manager.instances) { + if !object.IsDestroyed(layer.manager.instances[iterator.instanceIndex].BaseObject()) { + return true + } + iterator.instanceIndex++ + } + iterator.instanceIndex = 0 + iterator.layerIndex++ + if iterator.layerIndex < len(roomInst.instanceLayers) { + goto loop + } + return false +} + +func (iterator *instanceIteratorObjectState) Value() object.ObjectType { + return gState.roomInstances[iterator.roomInstanceIndex].instanceLayers[iterator.layerIndex].manager.instances[iterator.instanceIndex] +} diff --git a/gml/instance_iterator_room.go b/gml/instance_iterator_room.go new file mode 100644 index 0000000..46b9c9e --- /dev/null +++ b/gml/instance_iterator_room.go @@ -0,0 +1,9 @@ +package gml + +// Iterate over instances in the provided room +func InstancesIteratorRoom(roomInstanceIndex int) instanceIteratorObjectState { + return instanceIteratorObjectState{ + instanceIndex: -1, + roomInstanceIndex: roomInstanceIndex, + } +} diff --git a/gml/internal/geom/size.go b/gml/internal/geom/size.go index 592d76c..e25b95d 100644 --- a/gml/internal/geom/size.go +++ b/gml/internal/geom/size.go @@ -11,3 +11,7 @@ type Size struct { // X, Y int32 } + +func (s *Size) Vec() Vec { + return Vec{float64(s.X), float64(s.Y)} +} diff --git a/gml/internal/object/instance.go b/gml/internal/object/instance.go index 4eec9d4..7a5f11f 100644 --- a/gml/internal/object/instance.go +++ b/gml/internal/object/instance.go @@ -1,17 +1,30 @@ package object type instanceObject struct { + isDestroyed bool index int // index in the 'entities' array roomInstanceIndex int // Room Instance Index belongs to layerInstanceIndex int // Layer belongs to } -func InstanceIndex(inst *Object) int { - return inst.index +func (inst *Object) RoomInstanceIndex() int { + return inst.roomInstanceIndex } -func RoomInstanceIndex(inst *Object) int { - return inst.roomInstanceIndex +func IsDestroyed(inst *Object) bool { + return inst.isDestroyed +} + +func MarkAsDestroyed(inst *Object) { + inst.isDestroyed = true +} + +func SetInstanceIndex(inst *Object, index int) { + inst.index = index +} + +func InstanceIndex(inst *Object) int { + return inst.index } func LayerInstanceIndex(inst *Object) int { diff --git a/gml/internal/object/object.go b/gml/internal/object/object.go index c282fa9..27443d8 100644 --- a/gml/internal/object/object.go +++ b/gml/internal/object/object.go @@ -3,7 +3,7 @@ package object import ( "math" - "github.com/silbinarywolf/gml-go/gml/internal/space" + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) @@ -21,8 +21,9 @@ type ObjectType interface { type Object struct { sprite.SpriteState // Sprite (contains SetSprite) - space.SpaceObject + geom.Rect instanceObject + solid bool imageAngleRadians float64 // Image Angle } @@ -31,6 +32,11 @@ func (inst *Object) create() { inst.ImageScale.Y = 1.0 } +func (inst *Object) SetSolid(isSolid bool) { + inst.solid = isSolid +} + +func (inst *Object) Solid() bool { return inst.solid } func (inst *Object) BaseObject() *Object { return inst } func (inst *Object) ImageAngleRadians() float64 { return inst.imageAngleRadians } func (inst *Object) ImageAngle() float64 { return inst.imageAngleRadians * (180 / math.Pi) } diff --git a/gml/internal/object/object_manager.go b/gml/internal/object/object_manager.go index ea6e567..5b7ccd9 100644 --- a/gml/internal/object/object_manager.go +++ b/gml/internal/object/object_manager.go @@ -3,8 +3,6 @@ package object import ( "fmt" "reflect" - - "github.com/silbinarywolf/gml-go/gml/internal/space" ) var ( @@ -55,8 +53,22 @@ func NameToID() map[string]ObjectIndex { return gObjectManager.nameToID } -func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, layerIndex int, space *space.Space, spaceIndex int) ObjectType { - // Create +func MoveInstance(inst ObjectType, index int, roomInstanceIndex int, layerIndex int) { + // Initialize object + baseObj := inst.BaseObject() + baseObj.index = index + baseObj.roomInstanceIndex = roomInstanceIndex + baseObj.layerInstanceIndex = layerIndex +} + +func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, layerIndex int) ObjectType { + valToCopy := gObjectManager.idToEntityData[objectIndex] + inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) + MoveInstance(inst, index, roomInstanceIndex, layerIndex) + baseObj := inst.BaseObject() + baseObj.create() + return inst + /*// Create valToCopy := gObjectManager.idToEntityData[objectIndex] inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) @@ -73,9 +85,7 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, l // Perhaps force objects to have to be created via an instance manager. // baseObj.SpaceObject.Init(space, spaceIndex) - baseObj.create() - - return inst + baseObj.create()*/ } func ObjectGetIndex(name string) (ObjectIndex, bool) { diff --git a/gml/internal/room/room_manager_nonjs.go b/gml/internal/room/room_manager_nonjs.go index 6a52137..522b298 100644 --- a/gml/internal/room/room_manager_nonjs.go +++ b/gml/internal/room/room_manager_nonjs.go @@ -20,7 +20,6 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" "github.com/silbinarywolf/gml-go/gml/internal/object" - "github.com/silbinarywolf/gml-go/gml/internal/space" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) @@ -75,6 +74,18 @@ func LoadRoom(name string) *Room { } func loadRoomFromDirectoryFiles(name string) *Room { + objectTypeToInitState := make(map[object.ObjectIndex]object.ObjectType) + defer func() { + for _, inst := range objectTypeToInitState { + // NOTE(Jake): 2018-09-15 + // Cleanup entities or else they might stay alive on the server + // ie. networked entities + // I had to fix a bug where 26 enemies were "there" because this + // Destroy() wasn't here. + inst.Destroy() + } + }() + // room := new(Room) room.Config = new(RoomConfig) @@ -223,9 +234,18 @@ func loadRoomFromDirectoryFiles(name string) *Room { // Probably a slow hack to get the entity size // for building map data on-fly, but whatever! // - inst := object.NewRawInstance(objectIndex, 0, 0, 0, new(space.Space), -1) + // NOTE(Jake): 2018-09-15 + // Should definitely look into an entity having default + // hitbox etc as Create() can cause bugs, like multiple + // networked entities because I didn't call gml.Destroy() + // + inst, ok := objectTypeToInitState[objectIndex] + if !ok { + inst = object.NewRawInstance(objectIndex, 0, 0, 0) + inst.Create() + objectTypeToInitState[objectIndex] = inst + } baseObj := inst.BaseObject() - inst.Create() x := int32(x) y := int32(y) diff --git a/gml/internal/space/space.go b/gml/internal/space/space.go deleted file mode 100644 index 9e80e29..0000000 --- a/gml/internal/space/space.go +++ /dev/null @@ -1,16 +0,0 @@ -package space - -import ( - "github.com/silbinarywolf/gml-go/gml/internal/geom" -) - -type Space struct { - solid bool - geom.Rect -} - -func (record *Space) SetSolid(isSolid bool) { - record.solid = isSolid -} - -func (record *Space) Solid() bool { return record.solid } diff --git a/gml/internal/space/space_bucket.go b/gml/internal/space/space_bucket.go deleted file mode 100644 index 395204d..0000000 --- a/gml/internal/space/space_bucket.go +++ /dev/null @@ -1,107 +0,0 @@ -package space - -const ( - spaceBucketSize = 128 -) - -type SpaceBucketArray struct { - length int - buckets []*SpaceBucket -} - -type SpaceBucket struct { - usedCount int - spaces [spaceBucketSize]Space - used [spaceBucketSize]bool -} - -func NewSpaceBucketArray() *SpaceBucketArray { - result := new(SpaceBucketArray) - return result -} - -func (array *SpaceBucketArray) GetNew() int { - for b, bucket := range array.buckets { - index := bucket.getNew() - if index == -1 { - continue - } - array.length++ - return index + (b * spaceBucketSize) - } - // Create new bucket, all other buckets are full! - bucket := new(SpaceBucket) - b := len(array.buckets) - array.buckets = append(array.buckets, bucket) - array.length++ - return bucket.getNew() + (b * spaceBucketSize) -} - -func (array *SpaceBucketArray) Get(index int) *Space { - bucket := array.buckets[index/spaceBucketSize] - return &bucket.spaces[index%spaceBucketSize] -} - -func (array *SpaceBucketArray) Remove(index int) { - //log.Printf("DEBUG: Removing from bucket %d, index %d. (array.length = %d)", index/spaceBucketSize, index%spaceBucketSize, array.length) - bucket := array.buckets[index/spaceBucketSize] - bucketIndex := index % spaceBucketSize - if !bucket.used[bucketIndex] { - panic("Invalid operation. Cannot remove unused Space{} object.") - } - bucket.remove(bucketIndex) - array.length-- -} - -func (array *SpaceBucketArray) Buckets() []*SpaceBucket { - return array.buckets -} - -// NOTE(Jake): 2018-07-08 -// -// Experimented with returning this as a second value in `Get`, however -// the thing that yielded the best performance on both JS and native outputs -// was checking "IsUsed()" seperately. -// -// In fact, checking "IsUsed" seems to be an almost-free operation when added -// to the end of the if-statement checking collisions -// -//func (array *SpaceBucketArray) IsUsed(index int) bool { -// bucket := array.buckets[index/spaceBucketSize] -// return bucket.used[index%spaceBucketSize] -//} - -func (array *SpaceBucketArray) Len() int { - return array.length -} - -func (bucket *SpaceBucket) Get(index int) *Space { - return &bucket.spaces[index] -} - -func (bucket *SpaceBucket) IsUsed(index int) bool { - return bucket.used[index] -} - -func (_ *SpaceBucket) Len() int { - return spaceBucketSize -} - -func (bucket *SpaceBucket) remove(index int) { - bucket.used[index] = false - bucket.usedCount-- -} - -func (bucket *SpaceBucket) getNew() int { - if bucket.usedCount == spaceBucketSize { - return -1 - } - for i, _ := range bucket.used { - if !bucket.used[i] { - bucket.used[i] = true - bucket.usedCount++ - return i - } - } - return -1 -} diff --git a/gml/internal/space/space_object.go b/gml/internal/space/space_object.go deleted file mode 100644 index 0ab010d..0000000 --- a/gml/internal/space/space_object.go +++ /dev/null @@ -1,18 +0,0 @@ -package space - -type SpaceObject struct { - *Space - spaceIndex int -} - -func (record *SpaceObject) Init(space *Space, spaceIndex int) { - if record.Space != nil { - panic("Can only initialize SpaceObject once.") - } - record.Space = space - record.spaceIndex = spaceIndex -} - -func (space *SpaceObject) SpaceIndex() int { - return space.spaceIndex -} diff --git a/gml/internal/sprite/collision_mask.go b/gml/internal/sprite/collision_mask.go new file mode 100644 index 0000000..8fab00e --- /dev/null +++ b/gml/internal/sprite/collision_mask.go @@ -0,0 +1,17 @@ +package sprite + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +const ( + CollisionMaskInherit CollisionMaskKind = 0 + iota + CollisionMaskManual +) + +type CollisionMaskKind int + +type CollisionMask struct { + Kind CollisionMaskKind + Rect geom.Rect +} diff --git a/gml/internal/sprite/sprite.go b/gml/internal/sprite/sprite.go index 2664519..f1a8742 100644 --- a/gml/internal/sprite/sprite.go +++ b/gml/internal/sprite/sprite.go @@ -4,6 +4,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/geom" ) +const ( + maxCollisionMasks = 3 +) + type Sprite struct { name string frames []SpriteFrame @@ -11,8 +15,23 @@ type Sprite struct { imageSpeed float64 } -func (spr *Sprite) Name() string { return spr.name } -func (spr *Sprite) Size() geom.Size { return spr.size } +func (spr *Sprite) Name() string { return spr.name } +func (spr *Sprite) Size() geom.Size { return spr.size } +func (spr *Sprite) ImageSpeed() float64 { return spr.imageSpeed } +func (spr *Sprite) rect() geom.Rect { + return geom.Rect{ + Vec: geom.Vec{}, + Size: spr.Size(), + } +} + +func GetCollisionMask(spr *Sprite, frame int, kind int) *CollisionMask { + // masks := &spr.frames[frame].collisionMasks[kind].masks[kind] + //if len(masks) == 0 { + // panic("Should have at least 1 collision mask defined") + //} + return &spr.frames[frame].collisionMasks[kind] +} /*func (spr *Sprite) GetFrame(index int) *SpriteFrame { return &spr.frames[index] @@ -24,20 +43,23 @@ func newSprite(name string, frames []SpriteFrame, config spriteConfig) *Sprite { spr.frames = frames spr.imageSpeed = config.ImageSpeed - width := 0 - height := 0 - for _, frame := range frames { - frameWidth, frameHeight := frame.Size() - if width < frameWidth { - width = frameWidth + if len(frames) > 0 { + width := 0 + height := 0 + for _, frame := range frames { + frameWidth, frameHeight := frame.Size() + if width < frameWidth { + width = frameWidth + } + if height < frameHeight { + height = frameHeight + } + //frame.collisionMasks = make([]SpriteCollisionMask, maxCollisionMasks) } - if height < frameHeight { - height = frameHeight + spr.size = geom.Size{ + X: int32(width), + Y: int32(height), } } - spr.size = geom.Size{ - X: int32(width), - Y: int32(height), - } return spr } diff --git a/gml/internal/sprite/sprite_asset.go b/gml/internal/sprite/sprite_asset.go index bab0b66..7efbd9f 100644 --- a/gml/internal/sprite/sprite_asset.go +++ b/gml/internal/sprite/sprite_asset.go @@ -5,8 +5,9 @@ import ( ) type spriteAssetFrame struct { - Size geom.Vec - Data []byte + Size geom.Vec + CollisionMasks [maxCollisionMasks]CollisionMask + Data []byte } type spriteAsset struct { @@ -36,5 +37,17 @@ func newSpriteAsset(name string, frames []spriteAssetFrame, config spriteConfig) X: width, Y: height, } + + // Load collision masks + for maskID, mask := range config.CollisionMasks { + for frameIndex, _ := range spr.Frames { + if frameMask, ok := mask[frameIndex]; ok { + mask := &spr.Frames[frameIndex].CollisionMasks[maskID] + *mask = frameMask + // fmt.Printf("%v, mask id: %d, frame id: %d\n", frameMask, maskID, frameIndex) + } + } + } + return spr } diff --git a/gml/internal/sprite/sprite_config.go b/gml/internal/sprite/sprite_config.go index 7fc6cf3..f15f076 100644 --- a/gml/internal/sprite/sprite_config.go +++ b/gml/internal/sprite/sprite_config.go @@ -13,11 +13,13 @@ type spriteConfig struct { // Remember! JSON unmarshal won't work on // unexported fields! // - ImageSpeed float64 `json:"ImageSpeed"` + ImageSpeed float64 `json:"ImageSpeed"` + CollisionMasks map[int]map[int]CollisionMask `json:"CollisionMasks"` } -func loadConfig(path string) spriteConfig { - fileData, err := file.OpenFile(path) +func loadConfig(name string) spriteConfig { + configPath := file.AssetsDirectory + "/sprites/" + name + "/config.json" + fileData, err := file.OpenFile(configPath) if err != nil { return spriteConfig{} } diff --git a/gml/internal/sprite/sprite_frame.go b/gml/internal/sprite/sprite_frame.go new file mode 100644 index 0000000..cb98aae --- /dev/null +++ b/gml/internal/sprite/sprite_frame.go @@ -0,0 +1,9 @@ +package sprite + +type spriteFrameShared struct { + collisionMasks [maxCollisionMasks]CollisionMask +} + +func (spr *spriteFrameShared) init(frameData spriteAssetFrame) { + spr.collisionMasks = frameData.CollisionMasks +} diff --git a/gml/internal/sprite/sprite_frame_headless.go b/gml/internal/sprite/sprite_frame_headless.go index b0602b9..ddbf4c3 100644 --- a/gml/internal/sprite/sprite_frame_headless.go +++ b/gml/internal/sprite/sprite_frame_headless.go @@ -7,16 +7,19 @@ import ( ) type SpriteFrame struct { + spriteFrameShared width, height int } func (frame *SpriteFrame) Size() (width int, height int) { return frame.width, frame.height } func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { - return SpriteFrame{ + r := SpriteFrame{ width: int(frameData.Size.X), height: int(frameData.Size.Y), - }, nil + } + r.init(frameData) + return r, nil } // NOTE(Jake): 2018-06-17 diff --git a/gml/internal/sprite/sprite_frame_nonheadless.go b/gml/internal/sprite/sprite_frame_nonheadless.go index f945891..0b89d0b 100644 --- a/gml/internal/sprite/sprite_frame_nonheadless.go +++ b/gml/internal/sprite/sprite_frame_nonheadless.go @@ -10,6 +10,7 @@ import ( ) type SpriteFrame struct { + spriteFrameShared image *ebiten.Image } @@ -25,9 +26,11 @@ func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { if err != nil { return SpriteFrame{}, err } - return SpriteFrame{ + r := SpriteFrame{ image: sheet, - }, nil + } + r.init(frameData) + return r, nil } // NOTE(Jake): 2018-06-17 diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index 9132a7d..aa9c829 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -13,14 +13,34 @@ var ( g_spriteManager = newSpriteManager() ) -func newSpriteManager() SpriteManager { - manager := SpriteManager{} +type spriteManager struct { + assetMap map[string]*Sprite + assetList []*Sprite +} + +func newSpriteManager() *spriteManager { + manager := &spriteManager{} manager.assetMap = make(map[string]*Sprite) + manager.assetList = make([]*Sprite, 1, 10) return manager } -type SpriteManager struct { - assetMap map[string]*Sprite +func SpriteList() []*Sprite { + return g_spriteManager.assetList[1:] +} + +func LoadSprite(name string) *Sprite { + manager := g_spriteManager + + // Use already loaded asset + if res, ok := manager.assetMap[name]; ok { + return res + } + result := loadSprite(name) + manager.assetMap[name] = result + manager.assetList = append(manager.assetList, result) + + return result } func loadSpriteFromData(name string) *spriteAsset { @@ -66,16 +86,3 @@ func loadSprite(name string) *Sprite { }) return result } - -func LoadSprite(name string) *Sprite { - manager := g_spriteManager - - // Use already loaded asset - if res, ok := manager.assetMap[name]; ok { - return res - } - result := loadSprite(name) - manager.assetMap[name] = result - - return result -} diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index c0ef272..9bacd5d 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -5,6 +5,7 @@ package sprite import ( "bytes" "encoding/gob" + "encoding/json" "errors" "image" "image/png" @@ -69,6 +70,39 @@ FileWatchLoop: } } +func DebugWriteSpriteConfig(spr *Sprite) error { + name := spr.Name() + config := loadConfig(name) + + // Write collision masks + { + collisionMasks := make(map[int]map[int]CollisionMask) + masks := make(map[int]CollisionMask) + for i, _ := range spr.frames { + mask := *GetCollisionMask(spr, i, 0) + if mask.Kind == CollisionMaskInherit { + delete(masks, i) + } else { + masks[i] = mask + } + } + collisionMasks[0] = masks + config.CollisionMasks = collisionMasks + } + + configPath := file.AssetsDirectory + "/sprites/" + name + "/config.json" + + json, err := json.MarshalIndent(config, "", "\t") + if err != nil { + return err + } + err = ioutil.WriteFile(configPath, json, 0644) + if err != nil { + return errors.New("Unable to write sprite config out to file: " + configPath + ", error:" + err.Error()) + } + return nil +} + func debugWriteSprite(name string) { folderPath := file.AssetsDirectory + "/sprites/" + name + "/" @@ -79,6 +113,10 @@ func debugWriteSprite(name string) { watcher.Remove(folderPath) watcher.Add(folderPath) + // Read config information (if it exists) + var config spriteConfig + config = loadConfig(name) + // Load frames // // NOTE(Jake): 2018-03-12 @@ -114,14 +152,11 @@ func debugWriteSprite(name string) { frames = append(frames, frame) } - // Read config information (if it exists) - var config spriteConfig - configPath := folderPath + "config.json" - config = loadConfig(configPath) - // Create sprite asset := newSpriteAsset(name, frames, config) + // + // Write to file { spritePath := file.AssetsDirectory + "/sprites/" + name diff --git a/gml/internal/sprite/sprite_manager_nondebug.go b/gml/internal/sprite/sprite_manager_nondebug.go index 7282b10..a5624cf 100644 --- a/gml/internal/sprite/sprite_manager_nondebug.go +++ b/gml/internal/sprite/sprite_manager_nondebug.go @@ -5,5 +5,8 @@ package sprite func DebugWatch() { } +func DebugWriteSpriteConfig(spr *Sprite) { +} + func debugWriteSprite(name string) { } diff --git a/gml/keyboard_vk.go b/gml/keyboard_vk.go index f3cfc51..89caff4 100644 --- a/gml/keyboard_vk.go +++ b/gml/keyboard_vk.go @@ -35,6 +35,16 @@ const ( VkF10 VkF11 VkF12 + Vk0 + Vk1 + Vk2 + Vk3 + Vk4 + Vk5 + Vk6 + Vk7 + Vk8 + Vk9 VkNumpad0 VkNumpad1 VkNumpad2 diff --git a/gml/keyboard_vk_nonheadless.go b/gml/keyboard_vk_nonheadless.go index 28efc7a..49988fa 100644 --- a/gml/keyboard_vk_nonheadless.go +++ b/gml/keyboard_vk_nonheadless.go @@ -41,6 +41,16 @@ var keyboardVkToEbiten = []ebiten.Key{ VkF10: ebiten.KeyF10, VkF11: ebiten.KeyF11, VkF12: ebiten.KeyF11, + Vk0: ebiten.Key0, + Vk1: ebiten.Key1, + Vk2: ebiten.Key2, + Vk3: ebiten.Key3, + Vk4: ebiten.Key4, + Vk5: ebiten.Key5, + Vk6: ebiten.Key6, + Vk7: ebiten.Key7, + Vk8: ebiten.Key8, + Vk9: ebiten.Key9, VkNumpad0: ebiten.KeyKP0, VkNumpad1: ebiten.KeyKP1, VkNumpad2: ebiten.KeyKP2, diff --git a/gml/main.go b/gml/main.go index 9650d8b..971c0f9 100644 --- a/gml/main.go +++ b/gml/main.go @@ -1,7 +1,6 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/sprite" "github.com/silbinarywolf/gml-go/gml/internal/timegml" ) @@ -16,31 +15,52 @@ var ( gWindowWidth int gWindowHeight int gWindowScale float64 // Window scale - //lastFrameTime int64 ) func update() error { frameStartTime := timegml.Now() - //frameOffset := timegml.Now() - lastFrameTime - sprite.DebugWatch() keyboardUpdate() keyboardStringUpdate() mouseUpdate() - if EditorIsActive() { + + debugUpdate() + + switch debugMenuID { + case debugMenuNone: + gMainFunctions.update() + case debugMenuRoomEditor: cameraSetActive(0) + cameraClear(0) + editorUpdate() + + cameraDraw(0) cameraClearActive() - } else { - gMainFunctions.update() + case debugMenuAnimationEditor: + cameraSetActive(0) + cameraClear(0) + + animationEditorUpdate() + + cameraDraw(0) + cameraClearActive() + default: + panic("Invalid debug mode.") } if g_game.hasGameRestarted { + panic("todo: Fix / test this. I assume its broken") gState.globalInstances.reset() gMainFunctions.gameStart() g_game.hasGameRestarted = false } - gState.frameBudgetNanosecondsUsed = timegml.Now() - frameStartTime - //gState.frameBudgetNanosecondsUsed += frameOffset - //lastFrameTime = timegml.Now() + + // NOTE(Jake): 2018-09-29 + // Ignoring when 0 is reported. This happens on Windows + // and just makes the frame usage timer annoying. + frameBudgetUsed := timegml.Now() - frameStartTime + if frameBudgetUsed > 0 { + gState.frameBudgetNanosecondsUsed = frameBudgetUsed + } return nil } diff --git a/gml/room_editor.go b/gml/room_editor.go index c6bc883..a6b36b4 100644 --- a/gml/room_editor.go +++ b/gml/room_editor.go @@ -15,15 +15,8 @@ func EditorIsInitialized() bool { return false } -func EditorIsActive() bool { - return false -} - func EditorSetRoom(room *Room) { } func editorUpdate() { } - -func editorDraw() { -} diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 0fde2cb..7cb909e 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -21,9 +21,7 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/object" "github.com/silbinarywolf/gml-go/gml/internal/reditor" "github.com/silbinarywolf/gml-go/gml/internal/room" - "github.com/silbinarywolf/gml-go/gml/internal/space" "github.com/silbinarywolf/gml-go/gml/internal/sprite" - "github.com/silbinarywolf/gml-go/gml/internal/user" ) // @@ -41,13 +39,15 @@ import ( //} type roomEditor struct { + spriteViewer debugSpriteViewer + initialized bool editingRoom *room.Room editingLayer room.RoomLayer objectIndexToData []object.ObjectType - spriteList []*sprite.Sprite - spriteMap map[string]*sprite.Sprite + //spriteList []*sprite.Sprite + //spriteMap map[string]*sprite.Sprite camPos Vec lastMousePos Vec @@ -58,7 +58,7 @@ type roomEditor struct { hasUnsavedChanges bool entityMenuFiltered []object.ObjectType - spriteMenuFiltered []*sprite.Sprite + //spriteMenuFiltered []*sprite.Sprite objectSelected object.ObjectType spriteSelected *sprite.Sprite @@ -108,53 +108,23 @@ func newRoomEditor() *roomEditor { continue } objectIndex := obj.ObjectIndex() - inst := object.NewRawInstance(objectIndex, i, 0, 0, new(space.Space), -1) + inst := object.NewRawInstance(objectIndex, i, 0, 0) inst.Create() objectIndexToData[i] = inst } - // NOTE(Jake): 2018-07-25 - // - // Load all sprites - // - var spriteList []*sprite.Sprite - spriteMap := make(map[string]*sprite.Sprite) - spritePath := file.AssetsDirectory + "/sprites" - err := filepath.Walk(spritePath, func(path string, info os.FileInfo, err error) error { - if err != nil { - println("prevent panic by handling failure accessing a path " + path + ": " + err.Error()) - return err - } - if !info.IsDir() { - // Skip files - return nil - } - if path == spritePath { - // Skip self - return nil - } - name := filepath.Base(path) - sprite := LoadSprite(name) - spriteList = append(spriteList, sprite) - spriteMap[sprite.Name()] = sprite - return nil - }) - if err != nil { - panic(err) - } - return &roomEditor{ initialized: true, objectIndexToData: objectIndexToData, //objectNameToData: objectNameToData, - spriteList: spriteList, - spriteMap: spriteMap, + //spriteList: spriteList, + //spriteMap: spriteMap, lastMousePos: MousePosition(), entityMenuFiltered: make([]object.ObjectType, 0, len(objectIndexToData)), - spriteMenuFiltered: make([]*sprite.Sprite, 0, len(spriteList)), - roomDirectory: file.AssetsDirectory + "/room/", - tempLayers: make([]room.RoomLayer, 0, 25), - gridEnabled: false, + //spriteMenuFiltered: make([]*sprite.Sprite, 0, len(spriteList)), + roomDirectory: file.AssetsDirectory + "/room/", + tempLayers: make([]room.RoomLayer, 0, 25), + gridEnabled: false, } } @@ -235,14 +205,11 @@ func EditorIsInitialized() bool { return gRoomEditor != nil } -func EditorIsActive() bool { - return gRoomEditor != nil && roomEditorEditingRoom() != nil -} - func EditorSetRoom(room *Room) { roomEditor := gRoomEditor if roomEditor.editorChangeRoom(room) { roomEditor.editorConfigLoad() + debugMenuOpenOrToggleClosed(debugMenuRoomEditor) } } @@ -256,6 +223,7 @@ func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { roomEditor.editingRoom = nil // Reset camera settings back *gCameraManager = roomEditor.cameraStateBeforeEnteringEditingMode + debugMenuOpenOrToggleClosed(debugMenuNone) // Execute custom user-code logic if gRoomEditor.exitEditorFunc != nil { gRoomEditor.exitEditorFunc(editingRoom) @@ -274,7 +242,6 @@ func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { // editor. Retain the same camera position. // roomEditor.camPos = CameraGetViewPos(0) - CameraSetEnabled(0) CameraSetViewSize(0, geom.Vec{float64(windowWidth()), float64(windowHeight())}) CameraSetViewTarget(0, nil) return true @@ -534,7 +501,7 @@ func editorUpdate() { // Draw { - cameraSize := cameraGetActive().Size() + cameraSize := cameraGetActive().size { // Fill screen with gray @@ -576,12 +543,7 @@ func editorUpdate() { break } // Draw bg - sprite, ok := roomEditor.spriteMap[layer.SpriteName] - if !ok { - // If unable to find sprite, don't draw anything - // ie. handle case where background layer is using a deleted sprite - break - } + sprite := sprite.LoadSprite(layer.SpriteName) x := float64(layer.X) y := float64(layer.Y) width := float64(sprite.Size().X) @@ -606,10 +568,7 @@ func editorUpdate() { // Draw room sprites for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) DrawSprite(sprite, 0, geom.Vec{float64(obj.X), float64(obj.Y)}) } default: @@ -1039,10 +998,7 @@ func editorUpdate() { hasCollision := false for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1087,10 +1043,7 @@ func editorUpdate() { hasCollision := false for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1142,10 +1095,8 @@ func editorUpdate() { // Mark deleted for i, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) + width := float64(sprite.Size().X) height := float64(sprite.Size().Y) left := float64(obj.X) @@ -1212,15 +1163,17 @@ func editorUpdate() { // Select menu if roomEditor.menuOpened != reditor.MenuNone { - DrawSetGUI(true) - // Add black opacity over screen with menu open - DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) - - // - var x float64 = float64(windowWidth()) / 2 - var y float64 = 32 switch roomEditor.menuOpened { case reditor.MenuEntity: + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + // + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } typingText := KeyboardString() roomEditor.entityMenuFiltered = roomEditor.entityMenuFiltered[:0] for _, obj := range roomEditor.objectIndexToData { @@ -1253,14 +1206,14 @@ func editorUpdate() { { searchText := "Search for object (type + press enter)" - DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) - y += 24 + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 } { typingText := KeyboardString() - DrawText(geom.Vec{x, y}, typingText) - DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") - y += 24 + DrawText(ui, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 } previewSize := geom.Vec{32, 32} for _, obj := range roomEditor.entityMenuFiltered { @@ -1279,51 +1232,29 @@ func editorUpdate() { // Also look at other similar UI experiences, because // maybe we wont need to center this to get good UX. // - pos.X = x - 40 - pos.Y = y - (previewSize.Y / 2) + pos.X = ui.X - 40 + pos.Y = ui.Y - (previewSize.Y / 2) //baseObj.ImageScale.X = previewSize.X / float64(size.X) //baseObj.ImageScale.Y = previewSize.Y / float64(size.Y) //obj.Draw() drawObjectPreview(obj, pos, previewSize) name := obj.ObjectName() - DrawText(geom.Vec{x, y}, name) + DrawText(ui, name) //baseObj.ImageScale = oldImageScale - y += previewSize.Y + 16 + ui.Y += previewSize.Y + 16 } case reditor.MenuSprite, reditor.MenuBackground: - typingText := KeyboardString() - roomEditor.spriteMenuFiltered = roomEditor.spriteMenuFiltered[:0] - for _, spr := range roomEditor.spriteList { - if spr == nil { - continue - } - hasMatch := hasFilterMatch(spr.Name(), typingText) - if !hasMatch { - continue - } - // NOTE(Jake): 2018-07-11 - // - // Animating in the object list isn't particularly useful. - // - //obj.BaseObject().ImageUpdate() - roomEditor.spriteMenuFiltered = append(roomEditor.spriteMenuFiltered, spr) - } - - // - if inputSelectPressed() && - len(roomEditor.spriteMenuFiltered) > 0 { - selectedSpr := roomEditor.spriteMenuFiltered[0] + if spriteSelected, ok := roomEditor.spriteViewer.Update(); ok { switch roomEditor.menuOpened { case reditor.MenuSprite: - // Set - roomEditor.spriteSelected = selectedSpr + roomEditor.spriteSelected = spriteSelected roomEditor.editorConfigSave() case reditor.MenuBackground: switch layer := roomEditor.editingLayer.(type) { case *room.RoomLayerBackground: - if layer.SpriteName != selectedSpr.Name() { - layer.SpriteName = selectedSpr.Name() + if layer.SpriteName != spriteSelected.Name() { + layer.SpriteName = spriteSelected.Name() roomEditor.hasUnsavedChanges = true } default: @@ -1332,32 +1263,6 @@ func editorUpdate() { } roomEditor.menuOpened = reditor.MenuNone } - - // - { - searchText := "Search for image (type + press enter)" - DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) - y += 24 - } - { - typingText := KeyboardString() - DrawText(geom.Vec{x, y}, typingText) - DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") - y += 24 - } - previewSize := geom.Vec{32, 32} - for _, spr := range roomEditor.spriteMenuFiltered { - var pos geom.Vec - pos.X = x - 40 - pos.Y = y - (previewSize.Y / 2) - calcPreviewSize := previewSize - calcPreviewSize.X /= float64(spr.Size().X) - calcPreviewSize.Y /= float64(spr.Size().Y) - DrawSpriteScaled(spr, 0, pos, calcPreviewSize) - name := spr.Name() - DrawText(geom.Vec{x, y}, name) - y += previewSize.Y + 16 - } case reditor.MenuNewRoom, reditor.MenuLoadRoom, reditor.MenuNewLayer, @@ -1376,15 +1281,24 @@ func editorUpdate() { panic("Invalid menu type, No search text defined") } + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + // + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } { - DrawText(geom.Vec{x - (StringWidth(searchText) / 4), y}, searchText) - y += 24 + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 } { typingText := KeyboardString() - DrawText(geom.Vec{x, y}, typingText) - DrawText(geom.Vec{x + StringWidth(typingText), y}, "|") - y += 24 + DrawText(ui, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 } } DrawSetGUI(false) @@ -1472,38 +1386,6 @@ func drawTextButton(pos geom.Vec, text string) bool { return MouseCheckPressed(MbLeft) && isMouseOver } -func drawButton(pos geom.Vec, text string) bool { - // Config - paddingH := 32.0 - borderWidth := 2.0 - size := geom.Vec{StringWidth(text) + paddingH, 24} - - // Handle mouse over - isMouseOver := isMouseScreenOver(pos, size) - var innerRectColor color.RGBA - if isMouseOver { - innerRectColor = color.RGBA{180, 180, 180, 255} - } else { - innerRectColor = color.RGBA{255, 255, 255, 255} - } - - // Draw Border (outer rect) - DrawRectangleBorder(pos, size, innerRectColor, borderWidth, color.RGBA{0, 162, 232, 255}) - /* pos.X += borderWidth - pos.Y += borderWidth - size.X -= borderWidth * 2 - size.Y -= borderWidth * 2 - - // Draw Rect (inner rect) - DrawRectangle(pos, size, innerRectColor)*/ - - // Draw Text - pos.X += paddingH * 0.5 - pos.Y += 16 - DrawTextColor(pos, text, color.Black) - return MouseCheckPressed(MbLeft) && isMouseOver -} - func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, layer *room.RoomLayerSprite) (geom.Vec, bool) { offsetX := float64(brushSize.X) offsetY := float64(brushSize.Y) @@ -1520,10 +1402,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1552,10 +1431,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1584,10 +1460,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1616,10 +1489,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1684,26 +1554,6 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, return pos, false } -func isMouseOver(pos geom.Vec, size geom.Vec) bool { - mousePos := MousePosition() - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - return mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom -} - -func isMouseScreenOver(pos geom.Vec, size geom.Vec) bool { - mousePos := mouseScreenPosition() - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - return mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom -} - func (roomEditor *roomEditor) calculateRoomBounds() { // Reset room size editingRoom := roomEditor.editingRoom @@ -1740,10 +1590,8 @@ func (roomEditor *roomEditor) calculateRoomBounds() { case *room.RoomLayerSprite: for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } + sprite := sprite.LoadSprite(spriteName) + x := int32(obj.X) y := int32(obj.Y) width := int32(sprite.Size().X) @@ -1824,31 +1672,6 @@ func (roomEditor *roomEditor) loadRoom(name string) { roomEditor.editorChangeRoom(editingRoom) } -/*func (roomEditor *roomEditor) onCollisionSprite(layer *room.RoomLayerSprite, x, y float64, width, height float64, callback func(left, top, right, bottom float64)) { - for _, obj := range layer.Sprites { - spriteName := obj.SpriteName - sprite, ok := roomEditor.spriteMap[spriteName] - if !ok { - continue - } - - other := space.Space{} - other.X = float64(obj.X) - other.Y = float64(obj.Y) - other.Size = sprite.Size() - - r2Left := other.X - r2Top := other.Y - r2Right := r2Left + float64(other.Size.X) - r2Bottom := r2Top + float64(other.Size.Y) - - if r1Right > r2Left && r1Bottom > r2Top && - r1Left < r2Right && r1Top < r2Bottom { - callback(r2Left, r2Top, r2Right, r2Bottom) - } - } -}*/ - func (roomEditor *roomEditor) newLayerAndSelected(editingRoom *room.Room, text string, kind room.RoomLayerKind) { text = strings.TrimSpace(text) config := &room.RoomLayerConfig{ @@ -1892,14 +1715,7 @@ func hasFilterMatch(s string, filterBy string) bool { func (roomEditor *roomEditor) editorConfigLoad() { roomEditor.editingLayer = nil - - // Load room editor config - roomEditorConfigPath := user.HomeDir() + "/.gmlgo" - if _, err := os.Stat(roomEditorConfigPath); os.IsNotExist(err) { - os.Mkdir(roomEditorConfigPath, 0700) - } - - configPath := roomEditorConfigPath + "/config.json" + configPath := debugConfigPath("room_editor") fileData, err := file.OpenFile(configPath) if err == nil { bytes, err := ioutil.ReadAll(fileData) @@ -1920,9 +1736,8 @@ func (roomEditor *roomEditor) editorConfigLoad() { // Set brush from config switch roomEditor.editingLayer.(type) { case *room.RoomLayerSprite: - obj, ok := roomEditor.spriteMap[editorConfig.BrushSelected] - if ok && - obj.Name() == editorConfig.BrushSelected { + obj := sprite.LoadSprite(editorConfig.BrushSelected) + if obj.Name() == editorConfig.BrushSelected { roomEditor.spriteSelected = obj break } @@ -1962,13 +1777,8 @@ func (roomEditor *roomEditor) editorConfigSave() { editorConfig.RoomSelected = roomEditor.editingRoom.Config.UUID } - roomEditorConfigPath := user.HomeDir() + "/.gmlgo" - if _, err := os.Stat(roomEditorConfigPath); os.IsNotExist(err) { - os.Mkdir(roomEditorConfigPath, 0700) - } - json, _ := json.MarshalIndent(editorConfig, "", "\t") - configPath := roomEditorConfigPath + "/config.json" + configPath := debugConfigPath("room_editor") err := ioutil.WriteFile(configPath, json, 0644) if err != nil { println("Failed to write room editor config: " + configPath + "\n" + "Error: " + err.Error()) diff --git a/gml/room_instance.go b/gml/room_instance.go index 463c1d3..b75592f 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -15,12 +15,21 @@ type RoomInstance struct { drawLayers []RoomInstanceLayerDraw } -func RoomInstanceCreate(room *Room) *RoomInstance { +func RoomInstanceName(roomInstanceIndex int) string { + roomInst := &gState.roomInstances[roomInstanceIndex] + if !roomInst.used { + return "" + } + return roomInst.room.Config.UUID +} + +func RoomInstanceCreate(room *Room) int { roomInst := gState.createNewRoomInstance(room) - return roomInst + return roomInst.index } -func RoomInstanceDestroy(roomInst *RoomInstance) { +func RoomInstanceDestroy(roomInstanceIndex int) { + roomInst := &gState.roomInstances[roomInstanceIndex] gState.deleteRoomInstance(roomInst) } @@ -41,7 +50,7 @@ type roomInstanceObject interface { BaseObject() *object.Object } -func RoomInstanceInstances(inst roomInstanceObject) []object.ObjectType { +/*func RoomInstanceInstances(inst roomInstanceObject) []object.ObjectType { roomInstanceIndex := object.RoomInstanceIndex(inst.BaseObject()) roomInst := RoomGetInstance(roomInstanceIndex) if roomInst == nil { @@ -49,14 +58,14 @@ func RoomInstanceInstances(inst roomInstanceObject) []object.ObjectType { } instanceLayer := &roomInst.instanceLayers[len(roomInst.instanceLayers)-1] return instanceLayer.manager.instances -} +}*/ // NOTE(Jake):2018-08-19 // // I might want to make this private so a user // can only manipulate a room instance via functions // -func RoomGetInstance(roomInstanceIndex int) *RoomInstance { +func roomGetInstance(roomInstanceIndex int) *RoomInstance { roomInst := &gState.roomInstances[roomInstanceIndex] if roomInst.used { return roomInst diff --git a/gml/room_instance_iterator.go b/gml/room_instance_iterator.go new file mode 100644 index 0000000..a3b239e --- /dev/null +++ b/gml/room_instance_iterator.go @@ -0,0 +1,30 @@ +package gml + +type roomInstanceIteratorState struct { + roomInstanceIndex int +} + +func RoomInstanceIterator() roomInstanceIteratorState { + return roomInstanceIteratorState{ + roomInstanceIndex: 0, + } +} + +func (iterator *roomInstanceIteratorState) Next() bool { + for { + roomInst := &gState.roomInstances[iterator.roomInstanceIndex] + if iterator.roomInstanceIndex >= len(gState.roomInstances) { + return false + } + if !roomInst.used { + iterator.roomInstanceIndex++ + continue + } + iterator.roomInstanceIndex++ + return true + } +} + +func (iterator *roomInstanceIteratorState) Value() int { + return iterator.roomInstanceIndex +} diff --git a/gml/room_instance_layer_sprite.go b/gml/room_instance_layer_sprite.go index 4c3fae6..e85fc94 100644 --- a/gml/room_instance_layer_sprite.go +++ b/gml/room_instance_layer_sprite.go @@ -2,7 +2,6 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/geom" - "github.com/silbinarywolf/gml-go/gml/internal/space" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) @@ -11,11 +10,18 @@ type RoomInstanceLayerSpriteObject struct { Sprite *sprite.Sprite } +func (record *RoomInstanceLayerSpriteObject) Rect() geom.Rect { + r := geom.Rect{} + r.Vec = record.Vec + r.Size = record.Sprite.Size() + return r +} + type RoomInstanceLayerSprite struct { RoomInstanceLayerDrawBase - name string - sprites []RoomInstanceLayerSpriteObject - spaces space.SpaceBucketArray + name string + sprites []RoomInstanceLayerSpriteObject + //spaces space.SpaceBucketArray hasCollision bool } diff --git a/gml/sprite.go b/gml/sprite.go index a68523c..da72f72 100644 --- a/gml/sprite.go +++ b/gml/sprite.go @@ -4,10 +4,13 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) +// todo(Jake): 2018-10-27 +// Consider changing this to `type SpriteIndex int` and exposing +// Sprite functions by accessing the assetList. type Sprite = sprite.Sprite type SpriteState = sprite.SpriteState -func LoadSprite(name string) *Sprite { +func SpriteLoad(name string) *Sprite { return sprite.LoadSprite(name) } diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go new file mode 100644 index 0000000..7ac0205 --- /dev/null +++ b/gml/sprite_selector_debug.go @@ -0,0 +1,111 @@ +// +build debug + +package gml + +import ( + "image/color" + "os" + "path/filepath" + + "github.com/silbinarywolf/gml-go/gml/internal/file" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +var ( + debugScratchSpriteList []*sprite.Sprite + debugSpriteViewerLoaded bool +) + +type debugSpriteViewer struct { +} + +func (viewer *debugSpriteViewer) lazyLoad() { + if debugSpriteViewerLoaded { + return + } + debugSpriteViewerLoaded = true + spritePath := file.AssetsDirectory + "/sprites" + err := filepath.Walk(spritePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + path + ": " + err.Error()) + return err + } + if !info.IsDir() { + // Skip files + return nil + } + if path == spritePath { + // Skip self + return nil + } + name := filepath.Base(path) + sprite.LoadSprite(name) + return nil + }) + if err != nil { + panic(err) + } +} + +func (viewer *debugSpriteViewer) Update() (*sprite.Sprite, bool) { + viewer.lazyLoad() + + typingText := KeyboardString() + spriteMenuFiltered := debugScratchSpriteList[:0] + for _, spr := range sprite.SpriteList() { + hasMatch := hasFilterMatch(spr.Name(), typingText) + if !hasMatch { + continue + } + spriteMenuFiltered = append(spriteMenuFiltered, spr) + } + + // Input + selected := KeyboardCheckPressed(VkEnter) || + KeyboardCheckPressed(VkNumpadEnter) + if selected && + len(spriteMenuFiltered) > 0 { + selectedSpr := spriteMenuFiltered[0] + ClearKeyboardString() + return selectedSpr, true + } + + // Draw + { + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } + + { + searchText := "Search for image (type + press enter)" + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 + } + { + typingText := KeyboardString() + DrawText(geom.Vec{ui.X, ui.Y}, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 + } + previewSize := geom.Vec{32, 32} + for _, spr := range spriteMenuFiltered { + var pos geom.Vec + pos.X = ui.X - 40 + pos.Y = ui.Y - (previewSize.Y / 2) + calcPreviewSize := previewSize + calcPreviewSize.X /= float64(spr.Size().X) + calcPreviewSize.Y /= float64(spr.Size().Y) + DrawSpriteScaled(spr, 0, pos, calcPreviewSize) + name := spr.Name() + DrawText(geom.Vec{ui.X, ui.Y}, name) + ui.Y += previewSize.Y + 16 + } + } + return nil, false +} diff --git a/gml/sprite_selector_nodebug.go b/gml/sprite_selector_nodebug.go new file mode 100644 index 0000000..d158428 --- /dev/null +++ b/gml/sprite_selector_nodebug.go @@ -0,0 +1,9 @@ +// +build !debug + +package gml + +type debugSpriteViewer struct { +} + +func (viewer *debugSpriteViewer) Update() { +} diff --git a/gml/state.go b/gml/state.go index 9ba697e..a10bc84 100644 --- a/gml/state.go +++ b/gml/state.go @@ -11,11 +11,22 @@ import ( var ( gState *state = newState() + g_game gameState ) +type gameState struct { + hasGameRestarted bool +} + +func GameRestart() { + g_game.hasGameRestarted = true +} + type state struct { globalInstances *instanceManager roomInstances []RoomInstance + instancesMarkedForDelete []object.ObjectType + isCreatingRoomInstance bool gWidth int gHeight int frameBudgetNanosecondsUsed int64 @@ -33,7 +44,12 @@ func FrameUsage() string { timeTaken := float64(frameBudgetUsed) / 16000000.0 //fmt.Printf("Time used: %v / 16000000.0\n", frameBudgetUsed) text := strconv.FormatFloat(timeTaken*100, 'f', 6, 64) - return text + "%" + return text + "% (" + strconv.Itoa(int(gState.frameBudgetNanosecondsUsed)) + "ns)" +} + +// Check if RoomInstanceEmptyCreate() create is being executed +func IsCreatingRoomInstance() bool { + return gState.isCreatingRoomInstance } func (state *state) createNewRoomInstance(room *Room) *RoomInstance { @@ -41,6 +57,10 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { used: true, room: room, }) + state.isCreatingRoomInstance = true + defer func() { + state.isCreatingRoomInstance = false + }() index := len(state.roomInstances) - 1 roomInst := &state.roomInstances[index] roomInst.index = index @@ -82,7 +102,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { layer.y = float64(layerData.Y) layer.roomLeft = float64(room.Left) layer.roomRight = float64(room.Right) - layer.sprite = LoadSprite(spriteName) + layer.sprite = sprite.LoadSprite(spriteName) layer.drawOrder = layerData.Config.Order roomInst.drawLayers = append(roomInst.drawLayers, layer) } @@ -102,16 +122,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { record.X = float64(sprObj.X) record.Y = float64(sprObj.Y) layer.sprites = append(layer.sprites, record) - if hasCollision { - // Add collision - space := layer.spaces.Get(layer.spaces.GetNew()) - space.X = float64(sprObj.X) - space.Y = float64(sprObj.Y) - } } - //sort.Slice(layer.sprites, func(i, j int) bool { - // return layer.sprites[i].Sprite.Name() < layer.sprites[j].Sprite.Name() - //}) layer.drawOrder = layerData.Config.Order roomInst.spriteLayers = append(roomInst.spriteLayers, layer) roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.spriteLayers[len(roomInst.spriteLayers)-1]) @@ -155,27 +166,10 @@ func (state *state) update(animationUpdate bool) { } roomInst.update(animationUpdate) } -} -func (state *state) draw() { - for i := 0; i < len(gCameraManager.cameras); i++ { - view := &gCameraManager.cameras[i] - if !view.enabled { - continue - } - view.update() - cameraSetActive(i) - // Render global instances - state.globalInstances.draw() - - // Render each instance in each room instance - for i := 1; i < len(state.roomInstances); i++ { - roomInst := &state.roomInstances[i] - if !roomInst.used { - continue - } - roomInst.draw() - } + // Remove deleted entities + for _, inst := range state.instancesMarkedForDelete { + instanceRemove(inst) } - cameraClearActive() + state.instancesMarkedForDelete = state.instancesMarkedForDelete[:0] } diff --git a/gml/state_headless.go b/gml/state_headless.go new file mode 100644 index 0000000..cbdecd3 --- /dev/null +++ b/gml/state_headless.go @@ -0,0 +1,7 @@ +// +build headless + +package gml + +// draw does not need to execute any logic for headless mode +func (state *state) draw() { +} diff --git a/gml/state_nonheadless.go b/gml/state_nonheadless.go new file mode 100644 index 0000000..f3db074 --- /dev/null +++ b/gml/state_nonheadless.go @@ -0,0 +1,42 @@ +// +build !headless + +package gml + +func (state *state) draw() { + for i := 0; i < len(gCameraManager.cameras); i++ { + view := &gCameraManager.cameras[i] + if !view.enabled { + continue + } + view.update() + cameraSetActive(i) + + cameraClear(i) + + // Render global instances + state.globalInstances.draw() + + if view.follow != nil { + // Render instances in same room as instance following + inst := view.follow.BaseObject() + roomInst := roomGetInstance(inst.RoomInstanceIndex()) + if roomInst == nil { + panic("RoomInstance this object belongs to has been destroyed") + } + roomInst.draw() + } else { + // Render each instance in each room instance + for i := 1; i < len(state.roomInstances); i++ { + roomInst := &state.roomInstances[i] + if !roomInst.used { + continue + } + roomInst.draw() + } + } + + // Render camera onto OS-window + cameraDraw(i) + } + cameraClearActive() +} From 0e0ee1b57705fd86b82aac5e4e46dd05199a1e3f Mon Sep 17 00:00:00 2001 From: Jake B Date: Wed, 21 Nov 2018 22:05:19 +1100 Subject: [PATCH 04/27] Expose WindowWidth() / WindowHeight() / WindowScale() functions, Fix default instance layer creation for room Fixes #5 --- gml/internal/sprite/sprite_state.go | 18 ++++++++++++++---- gml/main.go | 15 +++++++++++++++ gml/room_instance.go | 5 +++++ gml/state.go | 19 ++++++++++++------- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/gml/internal/sprite/sprite_state.go b/gml/internal/sprite/sprite_state.go index 673735a..84adc9d 100644 --- a/gml/internal/sprite/sprite_state.go +++ b/gml/internal/sprite/sprite_state.go @@ -10,10 +10,20 @@ type SpriteState struct { imageIndex float64 } -func (state *SpriteState) Sprite() *Sprite { return state.sprite } -func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } -func (state *SpriteState) ImageNumber() float64 { return float64(len(state.sprite.frames)) } -func (state *SpriteState) ImageSpeed() float64 { return state.sprite.imageSpeed } +func (state *SpriteState) Sprite() *Sprite { return state.sprite } +func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } +func (state *SpriteState) ImageSpeed() float64 { + if state.sprite == nil { + return 0 + } + return state.sprite.imageSpeed +} +func (state *SpriteState) ImageNumber() float64 { + if state.sprite == nil { + return 0 + } + return float64(len(state.sprite.frames)) +} func (state *SpriteState) SetSprite(sprite *Sprite) { if state.sprite != sprite { diff --git a/gml/main.go b/gml/main.go index 971c0f9..8fc3bc6 100644 --- a/gml/main.go +++ b/gml/main.go @@ -64,14 +64,29 @@ func update() error { return nil } +func WindowWidth() int { + return gWindowWidth +} + +func WindowHeight() int { + return gWindowHeight +} + +func WindowScale() float64 { + return gWindowScale +} + +// todo: replace windowWidth() with WindowWidth() func windowWidth() int { return gWindowWidth } +// todo: replace windowHeight() with WindowHeight() func windowHeight() int { return gWindowHeight } +// todo: replace windowScale() with WindowScale() func windowScale() float64 { return gWindowScale } diff --git a/gml/room_instance.go b/gml/room_instance.go index b75592f..959039c 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -23,6 +23,11 @@ func RoomInstanceName(roomInstanceIndex int) string { return roomInst.room.Config.UUID } +func RoomInstanceNew() int { + roomInst := gState.createNewRoomInstance(nil) + return roomInst.index +} + func RoomInstanceCreate(room *Room) int { roomInst := gState.createNewRoomInstance(room) return roomInst.index diff --git a/gml/state.go b/gml/state.go index a10bc84..45c4ed1 100644 --- a/gml/state.go +++ b/gml/state.go @@ -65,6 +65,18 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { roomInst := &state.roomInstances[index] roomInst.index = index + if room == nil || + len(room.InstanceLayers) == 0 { + // Create default instance layer if... + // - No instance layers exist in the room data + // - Creating blank room + roomInst.instanceLayers = make([]RoomInstanceLayerInstance, 1) + roomInst.instanceLayers[0] = RoomInstanceLayerInstance{ + index: 0, + } + roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.instanceLayers[0]) + } + // If non-blank room instance, use room data to create if roomInst.room != nil { // Instance layers @@ -82,13 +94,6 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { } roomInst.drawLayers = append(roomInst.drawLayers, layer) } - } else { - // If no instance layers exist in the room data, create one. - roomInst.instanceLayers = make([]RoomInstanceLayerInstance, 1) - roomInst.instanceLayers[0] = RoomInstanceLayerInstance{ - index: 0, - } - roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.instanceLayers[0]) } // Background layers for i := 0; i < len(room.BackgroundLayers); i++ { From 0b24bf7a0fb91782c6ea43718ba55883b5ad89a6 Mon Sep 17 00:00:00 2001 From: Jake B Date: Fri, 23 Nov 2018 21:57:04 +1100 Subject: [PATCH 05/27] example/spaceship: start spaceship example game --- .gitignore | 2 ++ examples/spaceship/.gitignore | 3 ++ examples/spaceship/game/game.go | 36 ++++++++++++++++++++ examples/spaceship/game/obj.go | 11 ++++++ examples/spaceship/game/obj_bullet.go | 33 ++++++++++++++++++ examples/spaceship/game/obj_player.go | 49 +++++++++++++++++++++++++++ examples/spaceship/main.go | 10 ++++++ 7 files changed, 144 insertions(+) create mode 100644 .gitignore create mode 100644 examples/spaceship/.gitignore create mode 100644 examples/spaceship/game/game.go create mode 100644 examples/spaceship/game/obj.go create mode 100644 examples/spaceship/game/obj_bullet.go create mode 100644 examples/spaceship/game/obj_player.go create mode 100644 examples/spaceship/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66e3370 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmd/**/*.exe +/cmd/gmlgo/gmlgo diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore new file mode 100644 index 0000000..fda37cf --- /dev/null +++ b/examples/spaceship/.gitignore @@ -0,0 +1,3 @@ +assets/**/*.data +*.exe +spaceship diff --git a/examples/spaceship/game/game.go b/examples/spaceship/game/game.go new file mode 100644 index 0000000..1ad2226 --- /dev/null +++ b/examples/spaceship/game/game.go @@ -0,0 +1,36 @@ +package game + +//go:generate gmlgo_gen + +import "github.com/silbinarywolf/gml-go/gml" + +const ( + WindowTitle = "Spaceship" + WindowWidth = 640 + WindowHeight = 480 + WindowScale = 1 +) + +var ( + gameWorld GameWorld +) + +type GameWorld struct { + // todo(Jake): 2018-11-28 - #6 + // Change int to gml.RoomIndex + CurrentRoomIndex int +} + +func GameStart() { + // Setup camera + // todo(Jake): 2018-11-28 - #3 + // Change CameraCreate to use geom.Size for w/h + gml.CameraCreate(0, 0, 0, float64(gml.WindowWidth()), float64(gml.WindowHeight())) + gameWorld.CurrentRoomIndex = gml.RoomInstanceNew() + gml.InstanceCreateRoom(gml.Vec{32, 32}, gameWorld.CurrentRoomIndex, ObjPlayer) +} + +func GameUpdate() { + gml.Update(true) + gml.Draw() +} diff --git a/examples/spaceship/game/obj.go b/examples/spaceship/game/obj.go new file mode 100644 index 0000000..f3b9c44 --- /dev/null +++ b/examples/spaceship/game/obj.go @@ -0,0 +1,11 @@ +package game + +import "github.com/silbinarywolf/gml-go/gml" + +func init() { + gml.ObjectInitTypes([]gml.ObjectType{ + // This is used by gml.InstanceCreate to clone new instances by ObjectIndex + ObjPlayer: new(Player), + ObjBullet: new(Bullet), + }) +} diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go new file mode 100644 index 0000000..1decc6c --- /dev/null +++ b/examples/spaceship/game/obj_bullet.go @@ -0,0 +1,33 @@ +package game + +import "github.com/silbinarywolf/gml-go/gml" + +const ObjBullet = 2 + +type Bullet struct { + gml.Object +} + +func (inst *Bullet) ObjectIndex() gml.ObjectIndex { + return ObjBullet +} + +func (inst *Bullet) ObjectName() string { + return "Bullet" +} + +func (inst *Bullet) Create() { + inst.SetSprite(gml.SpriteLoad("spaceship")) +} + +func (inst *Bullet) Destroy() { + +} + +func (inst *Bullet) Update() { + inst.Y -= 8 +} + +func (inst *Bullet) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) +} diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go new file mode 100644 index 0000000..29f4a15 --- /dev/null +++ b/examples/spaceship/game/obj_player.go @@ -0,0 +1,49 @@ +package game + +import "github.com/silbinarywolf/gml-go/gml" + +const ObjPlayer = 1 + +type Player struct { + gml.Object +} + +func (inst *Player) ObjectIndex() gml.ObjectIndex { + return ObjPlayer +} + +// todo(Jake): 2018-11-22 +// Make this auto-generated based on the struct name +func (inst *Player) ObjectName() string { + return "Player" +} + +func (inst *Player) Create() { + inst.SetSprite(gml.SpriteLoad("spaceship")) +} + +func (inst *Player) Destroy() { + +} + +func (inst *Player) Update() { + if gml.KeyboardCheck(gml.VkLeft) { + inst.X -= 8 + } + if gml.KeyboardCheck(gml.VkRight) { + inst.X += 8 + } + if gml.KeyboardCheck(gml.VkUp) { + inst.Y -= 8 + } + if gml.KeyboardCheck(gml.VkDown) { + inst.Y += 8 + } + if gml.KeyboardCheckPressed(gml.VkSpace) { + gml.InstanceCreateRoom(inst.Pos(), gameWorld.CurrentRoomIndex, ObjBullet) + } +} + +func (inst *Player) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) +} diff --git a/examples/spaceship/main.go b/examples/spaceship/main.go new file mode 100644 index 0000000..d575a54 --- /dev/null +++ b/examples/spaceship/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/silbinarywolf/gml-go/examples/spaceship/game" + "github.com/silbinarywolf/gml-go/gml" +) + +func main() { + gml.Run(game.GameStart, game.GameUpdate, game.WindowWidth, game.WindowHeight, game.WindowScale, game.WindowTitle) +} From 7421df787dd2e1303599dbc240cd207c2853a5fa Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 24 Nov 2018 16:17:38 +1100 Subject: [PATCH 06/27] asset: Rename "assets" to "asset" and "sprites" to "sprite" for consistency with Go packages Fixes #4 --- gml/file.go | 6 ++--- gml/font_manager_nonheadless.go | 2 +- gml/internal/file/file.go | 21 ++++++++++------ gml/internal/file/file_js.go | 28 +++++++++------------ gml/internal/file/file_nonjs.go | 7 ++---- gml/internal/file/user_debug.go | 27 -------------------- gml/internal/file/user_nondebug.go | 11 -------- gml/internal/room/room.go | 2 +- gml/internal/room/room_manager.go | 6 ++++- gml/internal/sprite/sprite_config.go | 2 +- gml/internal/sprite/sprite_manager.go | 6 ++++- gml/internal/sprite/sprite_manager_debug.go | 6 ++--- gml/room_editor_debug.go | 2 +- gml/sprite_selector_debug.go | 3 +-- 14 files changed, 49 insertions(+), 80 deletions(-) delete mode 100644 gml/internal/file/user_debug.go delete mode 100644 gml/internal/file/user_nondebug.go diff --git a/gml/file.go b/gml/file.go index 35b9d11..c8ab62b 100644 --- a/gml/file.go +++ b/gml/file.go @@ -7,8 +7,8 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" ) -func AssetsDirectory() string { - return file.AssetsDirectory +func AssetDirectory() string { + return file.AssetDirectory } func ProgramDirectory() string { @@ -16,7 +16,7 @@ func ProgramDirectory() string { } func ReadFileAsString(path string) (string, error) { - path = AssetsDirectory() + "/" + path + path = AssetDirectory() + "/" + path fileData, err := file.OpenFile(path) if err != nil { return "", err diff --git a/gml/font_manager_nonheadless.go b/gml/font_manager_nonheadless.go index 7e06fd6..489b622 100644 --- a/gml/font_manager_nonheadless.go +++ b/gml/font_manager_nonheadless.go @@ -36,7 +36,7 @@ func LoadFont(name string, settings FontSettings) *Font { return result } - path := AssetsDirectory() + "/fonts/" + name + ".ttf" + path := AssetDirectory() + "/fonts/" + name + ".ttf" fileData, err := file.OpenFile(path) if err != nil { panic(errors.New("Unable to find font: " + path + ". Error: " + err.Error())) diff --git a/gml/internal/file/file.go b/gml/internal/file/file.go index 6f98c17..f6fd792 100644 --- a/gml/internal/file/file.go +++ b/gml/internal/file/file.go @@ -6,7 +6,16 @@ import ( ) var ( - AssetsDirectory string = "▲not-set▲" + AssetDirectory string = "▲not-set▲" + + // todo(Jake): 2018-11-24 + // Think of a better name? ProgramPath? + // The name should work as both a full URL (web output) and full directory path. + ProgramDirectory string = computeProgramDirectory() +) + +const ( + assetDirectoryBase = "asset" ) // ReadSeekCloser is io.ReadSeeker and io.Closer. @@ -17,13 +26,11 @@ type readSeekCloser interface { func init() { // NOTE(Jake): 2018-06-03 - // // Allow setting asset dir via environment variable for `go test` support - // - AssetsDirectory = os.Getenv("GML_ASSET_DIR") - if AssetsDirectory != "" { - AssetsDirectory = AssetsDirectory + "/assets" + AssetDirectory = os.Getenv("GML_ASSET_DIR") + if AssetDirectory != "" { + AssetDirectory = AssetDirectory + "/" + assetDirectoryBase } else { - AssetsDirectory = ProgramDirectory + "/assets" + AssetDirectory = ProgramDirectory + "/" + assetDirectoryBase } } diff --git a/gml/internal/file/file_js.go b/gml/internal/file/file_js.go index 1e501c3..4c862a7 100644 --- a/gml/internal/file/file_js.go +++ b/gml/internal/file/file_js.go @@ -10,27 +10,23 @@ import ( "github.com/hajimehoshi/ebiten/ebitenutil" ) -var ( - ProgramDirectory string = calculateProgramDir() -) - func OpenFile(path string) (readSeekCloser, error) { return ebitenutil.OpenFile(path) } -func calculateProgramDir() string { - // Setup program dir +// computeProgramDirectory returns the directory or url that the executable is running from +func computeProgramDirectory() string { location := js.Global().Get("location") - result := location.Get("href").String() - result = filepath.Dir(result) - result = strings.TrimPrefix(result, "file:/") - if strings.HasPrefix(result, "http:/") { - result = strings.TrimPrefix(result, "http:/") - result = "http://" + result + url := location.Get("href").String() + url = filepath.Dir(url) + url = strings.TrimPrefix(url, "file:/") + if strings.HasPrefix(url, "http:/") { + url = strings.TrimPrefix(url, "http:/") + url = "http://" + url } - if strings.HasPrefix(result, "https:/") { - result = strings.TrimPrefix(result, "https:/") - result = "https://" + result + if strings.HasPrefix(url, "https:/") { + url = strings.TrimPrefix(url, "https:/") + url = "https://" + url } - return result + return url } diff --git a/gml/internal/file/file_nonjs.go b/gml/internal/file/file_nonjs.go index 4ef8a93..ab71732 100644 --- a/gml/internal/file/file_nonjs.go +++ b/gml/internal/file/file_nonjs.go @@ -10,15 +10,12 @@ import ( "path/filepath" ) -var ( - ProgramDirectory string = calculateProgramDir() -) - func OpenFile(path string) (readSeekCloser, error) { return os.Open(filepath.FromSlash(path)) } -func calculateProgramDir() string { +// computeProgramDirectory returns the directory or url that the executable is running from +func computeProgramDirectory() string { exePath, err := os.Executable() if err != nil { panic(err) diff --git a/gml/internal/file/user_debug.go b/gml/internal/file/user_debug.go deleted file mode 100644 index c23b859..0000000 --- a/gml/internal/file/user_debug.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build debug - -package file - -/*var ( - debugUsername string = "▲not-set▲" -)*/ - -// NOTE(Jake): 2018-07-15 -// -// Deprecated in favour of XID UUID -// -//func DebugUsernameFileSafe() string { -// return debugUsername -//} - -/*func init() { - // Setup file-safe escaped username - user, _ := user.Current() - username := user.Username - username = path.Clean(username) - username = strings.Replace(username, "/", "-", -1) - username = strings.Replace(username, "\\", "-", -1) - username = strings.Replace(username, "_", "-", -1) - debugUsername = username -} -*/ diff --git a/gml/internal/file/user_nondebug.go b/gml/internal/file/user_nondebug.go deleted file mode 100644 index ed2fdc9..0000000 --- a/gml/internal/file/user_nondebug.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !debug - -package file - -// NOTE(Jake): 2018-07-15 -// -// Deprecated in favour of XID UUID -// -//func DebugUsernameFileSafe() string { -// return "" -//} diff --git a/gml/internal/room/room.go b/gml/internal/room/room.go index 6366720..203deb1 100644 --- a/gml/internal/room/room.go +++ b/gml/internal/room/room.go @@ -5,7 +5,7 @@ import ( ) func (room *Room) Filepath() string { - return file.AssetsDirectory + "/room/" + room.Config.UUID + return file.AssetDirectory + "/" + RoomDirectoryBase + "/" + room.Config.UUID } //func (room *Room) LayerCount() int { diff --git a/gml/internal/room/room_manager.go b/gml/internal/room/room_manager.go index b15ce84..4014970 100644 --- a/gml/internal/room/room_manager.go +++ b/gml/internal/room/room_manager.go @@ -8,6 +8,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" ) +const ( + RoomDirectoryBase = "room" +) + var ( gRoomManager = newRoomManager() ) @@ -25,7 +29,7 @@ func newRoomManager() *RoomManager { func loadRoomFromDataFile(name string) (*Room, error) { start := time.Now() - roomDataPath := file.AssetsDirectory + "/room/" + name + ".data" + roomDataPath := file.AssetDirectory + "/" + RoomDirectoryBase + "/" + name + ".data" dataFile, err := file.OpenFile(roomDataPath) if err != nil { return nil, err diff --git a/gml/internal/sprite/sprite_config.go b/gml/internal/sprite/sprite_config.go index f15f076..31ba161 100644 --- a/gml/internal/sprite/sprite_config.go +++ b/gml/internal/sprite/sprite_config.go @@ -18,7 +18,7 @@ type spriteConfig struct { } func loadConfig(name string) spriteConfig { - configPath := file.AssetsDirectory + "/sprites/" + name + "/config.json" + configPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/config.json" fileData, err := file.OpenFile(configPath) if err != nil { return spriteConfig{} diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index aa9c829..3760366 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -9,6 +9,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" ) +const ( + SpriteDirectoryBase = "sprite" +) + var ( g_spriteManager = newSpriteManager() ) @@ -44,7 +48,7 @@ func LoadSprite(name string) *Sprite { } func loadSpriteFromData(name string) *spriteAsset { - path := file.AssetsDirectory + "/sprites/" + name + ".data" + path := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + ".data" fileData, err := file.OpenFile(path) if err != nil { //panic(errors.New("Unable to find image: " + path)) diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index 9bacd5d..9b8ceb3 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -90,7 +90,7 @@ func DebugWriteSpriteConfig(spr *Sprite) error { config.CollisionMasks = collisionMasks } - configPath := file.AssetsDirectory + "/sprites/" + name + "/config.json" + configPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/config.json" json, err := json.MarshalIndent(config, "", "\t") if err != nil { @@ -104,7 +104,7 @@ func DebugWriteSpriteConfig(spr *Sprite) error { } func debugWriteSprite(name string) { - folderPath := file.AssetsDirectory + "/sprites/" + name + "/" + folderPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/" // NOTE(Jake): 2018-06-18 // @@ -159,7 +159,7 @@ func debugWriteSprite(name string) { // Write to file { - spritePath := file.AssetsDirectory + "/sprites/" + name + spritePath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name var data bytes.Buffer gob.NewEncoder(&data).Encode(asset) err := ioutil.WriteFile(spritePath+".data", data.Bytes(), 0644) diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 7cb909e..4e0b0fa 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -122,7 +122,7 @@ func newRoomEditor() *roomEditor { lastMousePos: MousePosition(), entityMenuFiltered: make([]object.ObjectType, 0, len(objectIndexToData)), //spriteMenuFiltered: make([]*sprite.Sprite, 0, len(spriteList)), - roomDirectory: file.AssetsDirectory + "/room/", + roomDirectory: file.AssetDirectory + "/" + room.RoomDirectoryBase + "/", tempLayers: make([]room.RoomLayer, 0, 25), gridEnabled: false, } diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go index 7ac0205..65ab412 100644 --- a/gml/sprite_selector_debug.go +++ b/gml/sprite_selector_debug.go @@ -25,10 +25,9 @@ func (viewer *debugSpriteViewer) lazyLoad() { return } debugSpriteViewerLoaded = true - spritePath := file.AssetsDirectory + "/sprites" + spritePath := file.AssetDirectory + "/" + sprite.SpriteDirectoryBase err := filepath.Walk(spritePath, func(path string, info os.FileInfo, err error) error { if err != nil { - println("prevent panic by handling failure accessing a path " + path + ": " + err.Error()) return err } if !info.IsDir() { From d5944b4eb636e3bad72700835a2f440f053e59cc Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 24 Nov 2018 17:12:41 +1100 Subject: [PATCH 07/27] example/spaceship: minor structural tweaks --- LICENSE.md | 21 +++++++++++++++++++++ examples/spaceship/.gitignore | 3 ++- examples/spaceship/asset/gmlgo.go | 8 ++++++++ examples/spaceship/game/game.go | 2 -- examples/spaceship/game/gmlgo.go | 26 ++++++++++++++++++++++++++ examples/spaceship/game/obj.go | 11 ----------- examples/spaceship/game/obj_bullet.go | 12 +----------- examples/spaceship/game/obj_player.go | 15 +-------------- gml/object.go | 1 + gml/room_instance.go | 1 + gml/state.go | 2 +- 11 files changed, 62 insertions(+), 40 deletions(-) create mode 100644 LICENSE.md create mode 100644 examples/spaceship/asset/gmlgo.go create mode 100644 examples/spaceship/game/gmlgo.go delete mode 100644 examples/spaceship/game/obj.go diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3f638e6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Jake Bentvelzen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore index fda37cf..2020638 100644 --- a/examples/spaceship/.gitignore +++ b/examples/spaceship/.gitignore @@ -1,3 +1,4 @@ -assets/**/*.data +asset/**/*.data *.exe +**/*_gen.go spaceship diff --git a/examples/spaceship/asset/gmlgo.go b/examples/spaceship/asset/gmlgo.go new file mode 100644 index 0000000..47e2612 --- /dev/null +++ b/examples/spaceship/asset/gmlgo.go @@ -0,0 +1,8 @@ +package asset + +// todo(Jake): 2018-11-24 +// Auto-generate this file + +const ( + SprSpaceship = 1 +) diff --git a/examples/spaceship/game/game.go b/examples/spaceship/game/game.go index 1ad2226..dbdee44 100644 --- a/examples/spaceship/game/game.go +++ b/examples/spaceship/game/game.go @@ -1,7 +1,5 @@ package game -//go:generate gmlgo_gen - import "github.com/silbinarywolf/gml-go/gml" const ( diff --git a/examples/spaceship/game/gmlgo.go b/examples/spaceship/game/gmlgo.go new file mode 100644 index 0000000..7ec7c85 --- /dev/null +++ b/examples/spaceship/game/gmlgo.go @@ -0,0 +1,26 @@ +package game + +import "github.com/silbinarywolf/gml-go/gml" + +// todo(Jake): 2018-11-24 - Github Issue #9 +// Make this file auto-generated + +const ( + _ gml.ObjectIndex = 0 + ObjPlayer = 1 + ObjBullet = 2 +) + +func (inst *Player) ObjectIndex() gml.ObjectIndex { return ObjPlayer } +func (inst *Player) ObjectName() string { return "Player" } + +func (inst *Bullet) ObjectIndex() gml.ObjectIndex { return ObjBullet } +func (inst *Bullet) ObjectName() string { return "Bullet" } + +func init() { + gml.ObjectInitTypes([]gml.ObjectType{ + // This is used by gml.InstanceCreate to clone new instances by ObjectIndex + ObjPlayer: new(Player), + ObjBullet: new(Bullet), + }) +} diff --git a/examples/spaceship/game/obj.go b/examples/spaceship/game/obj.go deleted file mode 100644 index f3b9c44..0000000 --- a/examples/spaceship/game/obj.go +++ /dev/null @@ -1,11 +0,0 @@ -package game - -import "github.com/silbinarywolf/gml-go/gml" - -func init() { - gml.ObjectInitTypes([]gml.ObjectType{ - // This is used by gml.InstanceCreate to clone new instances by ObjectIndex - ObjPlayer: new(Player), - ObjBullet: new(Bullet), - }) -} diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index 1decc6c..9845fb2 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -2,22 +2,12 @@ package game import "github.com/silbinarywolf/gml-go/gml" -const ObjBullet = 2 - type Bullet struct { gml.Object } -func (inst *Bullet) ObjectIndex() gml.ObjectIndex { - return ObjBullet -} - -func (inst *Bullet) ObjectName() string { - return "Bullet" -} - func (inst *Bullet) Create() { - inst.SetSprite(gml.SpriteLoad("spaceship")) + inst.SetSprite(gml.SpriteLoad("Spaceship")) } func (inst *Bullet) Destroy() { diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go index 29f4a15..c177f35 100644 --- a/examples/spaceship/game/obj_player.go +++ b/examples/spaceship/game/obj_player.go @@ -2,28 +2,15 @@ package game import "github.com/silbinarywolf/gml-go/gml" -const ObjPlayer = 1 - type Player struct { gml.Object } -func (inst *Player) ObjectIndex() gml.ObjectIndex { - return ObjPlayer -} - -// todo(Jake): 2018-11-22 -// Make this auto-generated based on the struct name -func (inst *Player) ObjectName() string { - return "Player" -} - func (inst *Player) Create() { - inst.SetSprite(gml.SpriteLoad("spaceship")) + inst.SetSprite(gml.SpriteLoad("Spaceship")) } func (inst *Player) Destroy() { - } func (inst *Player) Update() { diff --git a/gml/object.go b/gml/object.go index bd6a3e7..36e3eb4 100644 --- a/gml/object.go +++ b/gml/object.go @@ -15,6 +15,7 @@ func ObjectGetIndex(name string) (object.ObjectIndex, bool) { return res, ok } +// ObjectInitTypes is required to be called so the engine can create game objects func ObjectInitTypes(objTypes []object.ObjectType) { object.InitTypes(objTypes) } diff --git a/gml/room_instance.go b/gml/room_instance.go index 959039c..79a12e1 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -38,6 +38,7 @@ func RoomInstanceDestroy(roomInstanceIndex int) { gState.deleteRoomInstance(roomInst) } +// todo(Jake): 2018-11-24: Github Issue #12 func RoomInstanceEmptyCreate() *RoomInstance { roomInst := gState.createNewRoomInstance(nil) return roomInst diff --git a/gml/state.go b/gml/state.go index 45c4ed1..afcf93b 100644 --- a/gml/state.go +++ b/gml/state.go @@ -47,7 +47,7 @@ func FrameUsage() string { return text + "% (" + strconv.Itoa(int(gState.frameBudgetNanosecondsUsed)) + "ns)" } -// Check if RoomInstanceEmptyCreate() create is being executed +// Check if createNewRoomInstance() create is being executed func IsCreatingRoomInstance() bool { return gState.isCreatingRoomInstance } From 0df19e2e2e9a5870556b95e89cdbb3f3c161b3bc Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 24 Nov 2018 17:19:29 +1100 Subject: [PATCH 08/27] gmlgo: add WIP generator based on stringer Updates #9 --- cmd/gmlgo/gmlgo.go | 656 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 656 insertions(+) create mode 100644 cmd/gmlgo/gmlgo.go diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go new file mode 100644 index 0000000..0fe8776 --- /dev/null +++ b/cmd/gmlgo/gmlgo.go @@ -0,0 +1,656 @@ +package main // import "github.com/silbinarywolf/gml-go/cmd/gmlgo" + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/build" + "go/constant" + "go/format" + "go/importer" + "go/parser" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strings" +) + +var ( + output = flag.String("output", "", "output file name; default srcdir/_string.go") + trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") + linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") + buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") +) + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of gmlgo:\n") + fmt.Fprintf(os.Stderr, "\tgmlgo [flags] [directory]\n") + fmt.Fprintf(os.Stderr, "\tgmlgo [flags] files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "For more information, see:\n") + fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/gmlgo\n") + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() +} + +func main() { + log.SetFlags(0) + log.SetPrefix("gmlgo: ") + flag.Usage = Usage + flag.Parse() + var tags []string + if len(*buildTags) > 0 { + tags = strings.Split(*buildTags, ",") + } + + // We accept either one directory or a list of files. Which do we have? + args := flag.Args() + if len(args) == 0 { + // Default: process whole package in current directory. + args = []string{"."} + } + + // Parse the package once. + var dir string + g := Generator{ + trimPrefix: *trimprefix, + lineComment: *linecomment, + } + if len(args) == 1 && isDirectory(args[0]) { + dir = args[0] + g.parsePackageDir(args[0], tags) + } else { + if len(tags) != 0 { + log.Fatal("-tags option applies only to directories, not when files are specified") + } + dir = filepath.Dir(args[0]) + g.parsePackageFiles(args) + } + + // Print the header and package clause. + g.Printf("// Code generated by \"gmlgo %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) + g.Printf("\n") + g.Printf("package %s", g.pkg.name) + g.Printf("\n") + g.Printf("import \"strconv\"\n") // Used by all methods. + + // Run generate + g.generate() + + // Format the output. + src := g.format() + + // Write to file. + outputName := *output + if outputName == "" { + baseName := fmt.Sprintf("gmlgo_gen.go") + outputName = filepath.Join(dir, strings.ToLower(baseName)) + } + err := ioutil.WriteFile(outputName, src, 0644) + if err != nil { + log.Fatalf("writing output: %s", err) + } +} + +// isDirectory reports whether the named file is a directory. +func isDirectory(name string) bool { + info, err := os.Stat(name) + if err != nil { + log.Fatal(err) + } + return info.IsDir() +} + +// Generator holds the state of the analysis. Primarily used to buffer +// the output for format.Source. +type Generator struct { + buf bytes.Buffer // Accumulated output. + pkg *Package // Package we are scanning. + + trimPrefix string + lineComment bool +} + +func (g *Generator) Printf(format string, args ...interface{}) { + fmt.Fprintf(&g.buf, format, args...) +} + +// File holds a single parsed file and associated data. +type File struct { + pkg *Package // Package to which this file belongs. + file *ast.File // Parsed AST. + // These fields are reset for each type being generated. + values []Value // Accumulator for constant values of that type. + + trimPrefix string + lineComment bool +} + +type Package struct { + dir string + name string + defs map[*ast.Ident]types.Object + files []*File + typesPkg *types.Package +} + +func buildContext(tags []string) *build.Context { + ctx := build.Default + ctx.BuildTags = tags + return &ctx +} + +// parsePackageDir parses the package residing in the directory. +func (g *Generator) parsePackageDir(directory string, tags []string) { + pkg, err := buildContext(tags).ImportDir(directory, 0) + if err != nil { + log.Fatalf("cannot process directory %s: %s", directory, err) + } + var names []string + names = append(names, pkg.GoFiles...) + //names = append(names, pkg.CgoFiles...) + // TODO: Need to think about constants in test files. Maybe write type_string_test.go + // in a separate pass? For later. + // names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. + //names = append(names, pkg.SFiles...) + names = prefixDirectory(directory, names) + g.parsePackage(directory, names, nil) +} + +// parsePackageFiles parses the package occupying the named files. +func (g *Generator) parsePackageFiles(names []string) { + g.parsePackage(".", names, nil) +} + +// prefixDirectory places the directory name on the beginning of each name in the list. +func prefixDirectory(directory string, names []string) []string { + if directory == "." { + return names + } + ret := make([]string, len(names)) + for i, name := range names { + ret[i] = filepath.Join(directory, name) + } + return ret +} + +// parsePackage analyzes the single package constructed from the named files. +// If text is non-nil, it is a string to be used instead of the content of the file, +// to be used for testing. parsePackage exits if there is an error. +func (g *Generator) parsePackage(directory string, names []string, text interface{}) { + var files []*File + var astFiles []*ast.File + g.pkg = new(Package) + fs := token.NewFileSet() + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + continue + } + parsedFile, err := parser.ParseFile(fs, name, text, parser.ParseComments) + if err != nil { + log.Fatalf("parsing package: %s: %s", name, err) + } + astFiles = append(astFiles, parsedFile) + files = append(files, &File{ + file: parsedFile, + pkg: g.pkg, + trimPrefix: g.trimPrefix, + lineComment: g.lineComment, + }) + } + if len(astFiles) == 0 { + log.Fatalf("%s: no buildable Go files", directory) + } + g.pkg.name = astFiles[0].Name.Name + g.pkg.files = files + g.pkg.dir = directory + g.pkg.typeCheck(fs, astFiles) +} + +// check type-checks the package so we can evaluate contants whose values we are printing. +func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) { + pkg.defs = make(map[*ast.Ident]types.Object) + config := types.Config{ + IgnoreFuncBodies: true, // We only need to evaluate constants. + Importer: importer.Default(), // func defaultImporter() types.Importer + FakeImportC: true, + } + info := &types.Info{ + Defs: pkg.defs, + } + typesPkg, err := config.Check(pkg.dir, fs, astFiles, info) + if err != nil { + log.Fatalf("checking package: %s", err) + } + pkg.typesPkg = typesPkg +} + +// generate produces the String method for the named type. +func (g *Generator) generate() { + values := make([]Value, 0, 100) + for _, file := range g.pkg.files { + // Set the state for this run of the walker. + file.values = nil + if file.file != nil { + fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) + ast.Inspect(file.file, func(n ast.Node) bool { + var s string + switch n := n.(type) { + case *ast.BasicLit: + s = n.Value + case *ast.Ident: + s = n.Name + case *ast.StructType: + //if n.Fields.NumFields() == 0 { + // break + //} + fmt.Printf("STRUCT\n") + for _, field := range n.Fields.List { + fmt.Printf("%v\n", field.Names) + } + } + _ = s + //fmt.Printf("generate: %T\n", n) + return true + }) + //ast.Inspect(file.file, file.genDecl) + //fmt.Printf("generate: %v\n\n", file) + values = append(values, file.values...) + } + } + + if len(values) == 0 { + return + //log.Fatalf("no values defined for type %s", typeName) + } + runs := splitIntoRuns(values) + // The decision of which pattern to use depends on the number of + // runs in the numbers. If there's only one, it's easy. For more than + // one, there's a tradeoff between complexity and size of the data + // and code vs. the simplicity of a map. A map takes more space, + // but so does the code. The decision here (crossover at 10) is + // arbitrary, but considers that for large numbers of runs the cost + // of the linear scan in the switch might become important, and + // rather than use yet another algorithm such as binary search, + // we punt and use a map. In any case, the likelihood of a map + // being necessary for any realistic example other than bitmasks + // is very low. And bitmasks probably deserve their own analysis, + // to be done some other day. + g.buildOneRun(runs) + /*switch { + case len(runs) == 1: + g.buildOneRun(runs, typeName) + case len(runs) <= 10: + g.buildMultipleRuns(runs, typeName) + default: + g.buildMap(runs, typeName) + }*/ +} + +// splitIntoRuns breaks the values into runs of contiguous sequences. +// For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}. +// The input slice is known to be non-empty. +func splitIntoRuns(values []Value) [][]Value { + // We use stable sort so the lexically first name is chosen for equal elements. + sort.Stable(byValue(values)) + // Remove duplicates. Stable sort has put the one we want to print first, + // so use that one. The String method won't care about which named constant + // was the argument, so the first name for the given value is the only one to keep. + // We need to do this because identical values would cause the switch or map + // to fail to compile. + j := 1 + for i := 1; i < len(values); i++ { + if values[i].value != values[i-1].value { + values[j] = values[i] + j++ + } + } + values = values[:j] + runs := make([][]Value, 0, 10) + for len(values) > 0 { + // One contiguous sequence per outer loop. + i := 1 + for i < len(values) && values[i].value == values[i-1].value+1 { + i++ + } + runs = append(runs, values[:i]) + values = values[i:] + } + return runs +} + +// format returns the gofmt-ed contents of the Generator's buffer. +func (g *Generator) format() []byte { + src, err := format.Source(g.buf.Bytes()) + if err != nil { + // Should never happen, but can arise when developing this code. + // The user can compile the output to see the error. + log.Printf("warning: internal error: invalid Go generated: %s", err) + log.Printf("warning: compile the package to analyze the error") + return g.buf.Bytes() + } + return src +} + +// Value represents a declared constant. +type Value struct { + name string // The name of the constant. + // The value is stored as a bit pattern alone. The boolean tells us + // whether to interpret it as an int64 or a uint64; the only place + // this matters is when sorting. + // Much of the time the str field is all we need; it is printed + // by Value.String. + value uint64 // Will be converted to int64 when needed. + signed bool // Whether the constant is a signed type. + str string // The string representation given by the "go/constant" package. +} + +func (v *Value) String() string { + return v.str +} + +// byValue lets us sort the constants into increasing order. +// We take care in the Less method to sort in signed or unsigned order, +// as appropriate. +type byValue []Value + +func (b byValue) Len() int { return len(b) } +func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byValue) Less(i, j int) bool { + if b[i].signed { + return int64(b[i].value) < int64(b[j].value) + } + return b[i].value < b[j].value +} + +// genDecl processes one declaration clause. +func (f *File) genDecl(node ast.Node) bool { + decl, ok := node.(*ast.GenDecl) + if !ok || decl.Tok != token.CONST { + // We only care about const declarations. + return true + } + // The name of the type of the constants we are declaring. + // Can change if this is a multi-element declaration. + typ := "" + // Loop over the elements of the declaration. Each element is a ValueSpec: + // a list of names possibly followed by a type, possibly followed by values. + // If the type and value are both missing, we carry down the type (and value, + // but the "go/types" package takes care of that). + for _, spec := range decl.Specs { + vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. + if vspec.Type == nil && len(vspec.Values) > 0 { + // "X = 1". With no type but a value. If the constant is untyped, + // skip this vspec and reset the remembered type. + typ = "" + + { + // NOTE(Jake): 2018-11-23 + // Continue here + bl, ok := vspec.Values[0].(*ast.BasicLit) + if !ok { + continue + } + fmt.Printf("%s\n", decl.Tok.String()) + fmt.Printf("%s\n", vspec.Names[0]) + panic(bl.Value) // Returns 1 + } + + // If this is a simple type conversion, remember the type. + // We don't mind if this is actually a call; a qualified call won't + // be matched (that will be SelectorExpr, not Ident), and only unusual + // situations will result in a function call that appears to be + // a type conversion. + ce, ok := vspec.Values[0].(*ast.CallExpr) + if !ok { + continue + } + id, ok := ce.Fun.(*ast.Ident) + if !ok { + continue + } + typ = id.Name + } + if vspec.Type != nil { + // "X T". We have a type. Remember it. + ident, ok := vspec.Type.(*ast.Ident) + if !ok { + continue + } + typ = ident.Name + } + //if typ != f.typeName { + // This is not the type we're looking for. + //continue + //} + // We now have a list of names (from one line of source code) all being + // declared with the desired type. + // Grab their names and actual values and store them in f.values. + for _, name := range vspec.Names { + if name.Name == "_" { + continue + } + // This dance lets the type checker find the values for us. It's a + // bit tricky: look up the object declared by the name, find its + // types.Const, and extract its value. + obj, ok := f.pkg.defs[name] + if !ok { + log.Fatalf("no value for constant %s", name) + } + info := obj.Type().Underlying().(*types.Basic).Info() + if info&types.IsInteger == 0 { + log.Fatalf("can't handle non-integer constant type %s", typ) + } + value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. + if value.Kind() != constant.Int { + log.Fatalf("can't happen: constant is not an integer %s", name) + } + i64, isInt := constant.Int64Val(value) + u64, isUint := constant.Uint64Val(value) + if !isInt && !isUint { + log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) + } + if !isInt { + u64 = uint64(i64) + } + v := Value{ + name: name.Name, + value: u64, + signed: info&types.IsUnsigned == 0, + str: value.String(), + } + if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { + v.name = strings.TrimSpace(c.Text()) + } + v.name = strings.TrimPrefix(v.name, f.trimPrefix) + f.values = append(f.values, v) + } + } + return false +} + +// Helpers + +// usize returns the number of bits of the smallest unsigned integer +// type that will hold n. Used to create the smallest possible slice of +// integers to use as indexes into the concatenated strings. +func usize(n int) int { + switch { + case n < 1<<8: + return 8 + case n < 1<<16: + return 16 + default: + // 2^32 is enough constants for anyone. + return 32 + } +} + +// declareIndexAndNameVars declares the index slices and concatenated names +// strings representing the runs of values. +func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) { + var indexes, names []string + for i, run := range runs { + index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i)) + if len(run) != 1 { + indexes = append(indexes, index) + } + names = append(names, name) + } + g.Printf("const (\n") + for _, name := range names { + g.Printf("\t%s\n", name) + } + g.Printf(")\n\n") + + if len(indexes) > 0 { + g.Printf("var (") + for _, index := range indexes { + g.Printf("\t%s\n", index) + } + g.Printf(")\n\n") + } +} + +// declareIndexAndNameVar is the single-run version of declareIndexAndNameVars +func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) { + index, name := g.createIndexAndNameDecl(run, typeName, "") + g.Printf("const %s\n", name) + g.Printf("var %s\n", index) +} + +// createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var". +func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) { + b := new(bytes.Buffer) + indexes := make([]int, len(run)) + for i := range run { + b.WriteString(run[i].name) + indexes[i] = b.Len() + } + nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String()) + nameLen := b.Len() + b.Reset() + fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen)) + for i, v := range indexes { + if i > 0 { + fmt.Fprintf(b, ", ") + } + fmt.Fprintf(b, "%d", v) + } + fmt.Fprintf(b, "}") + return b.String(), nameConst +} + +// declareNameVars declares the concatenated names string representing all the values in the runs. +func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) { + g.Printf("const _%s_name%s = \"", typeName, suffix) + for _, run := range runs { + for i := range run { + g.Printf("%s", run[i].name) + } + } + g.Printf("\"\n") +} + +// buildOneRun generates the variables and String method for a single run of contiguous values. +func (g *Generator) buildOneRun(runs [][]Value) { + values := runs[0] + g.Printf("\n") + typeName := "typeNameHere_ToBeReplaced" + g.declareIndexAndNameVar(values, typeName) + // The generated code is simple enough to write as a Printf format. + lessThanZero := "" + if values[0].signed { + lessThanZero = "i < 0 || " + } + if values[0].value == 0 { // Signed or unsigned, 0 is still 0. + g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero) + } else { + g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero) + } +} + +// Arguments to format are: +// [1]: type name +// [2]: size of index element (8 for uint8 etc.) +// [3]: less than zero check (for signed types) +const stringOneRun = `func (i %[1]s) String() string { + if %[3]si >= %[1]s(len(_%[1]s_index)-1) { + return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]] +} +` + +// Arguments to format are: +// [1]: type name +// [2]: lowest defined value for type, as a string +// [3]: size of index element (8 for uint8 etc.) +// [4]: less than zero check (for signed types) +/* + */ +const stringOneRunWithOffset = `func (i %[1]s) String() string { + i -= %[2]s + if %[4]si >= %[1]s(len(_%[1]s_index)-1) { + return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")" + } + return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]] +} +` + +// buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. +// For this pattern, a single Printf format won't do. +/*func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) { + g.Printf("\n") + g.declareIndexAndNameVars(runs, typeName) + g.Printf("func (i %s) String() string {\n", typeName) + g.Printf("\tswitch {\n") + for i, values := range runs { + if len(values) == 1 { + g.Printf("\tcase i == %s:\n", &values[0]) + g.Printf("\t\treturn _%s_name_%d\n", typeName, i) + continue + } + g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1]) + if values[0].value != 0 { + g.Printf("\t\ti -= %s\n", &values[0]) + } + g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n", + typeName, i, typeName, i, typeName, i) + } + g.Printf("\tdefault:\n") + g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName) + g.Printf("\t}\n") + g.Printf("}\n") +}*/ + +// buildMap handles the case where the space is so sparse a map is a reasonable fallback. +// It's a rare situation but has simple code. +/*func (g *Generator) buildMap(runs [][]Value, typeName string) { + g.Printf("\n") + g.declareNameVars(runs, typeName, "") + g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName) + n := 0 + for _, values := range runs { + for _, value := range values { + g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name)) + n += len(value.name) + } + } + g.Printf("}\n\n") + g.Printf(stringMap, typeName) +}*/ + +// Argument to format is the type name. +const stringMap = `func (i %[1]s) String() string { + if str, ok := _%[1]s_map[i]; ok { + return str + } + return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" +} +` From e7ed58f0dfdcaa0ffb070e0e71443227a078b717 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 24 Nov 2018 18:31:14 +1100 Subject: [PATCH 09/27] sprite: Start work on changing sprite functions to return SpriteIndex instead of *Sprite Updates #2 --- examples/spaceship/asset/gmlgo.go | 13 ++++++- examples/spaceship/game/obj_bullet.go | 7 ++-- examples/spaceship/game/obj_player.go | 7 ++-- gml/animation_editor_debug.go | 2 +- gml/internal/room/room_manager_nonjs.go | 2 +- gml/internal/sprite/sprite.go | 3 ++ gml/internal/sprite/sprite_manager.go | 40 ++++++++++++++------- gml/internal/sprite/sprite_manager_debug.go | 7 ++-- gml/room_editor_debug.go | 22 ++++++------ gml/sprite.go | 14 +++++--- gml/sprite_selector_debug.go | 2 +- gml/state.go | 4 +-- 12 files changed, 83 insertions(+), 40 deletions(-) diff --git a/examples/spaceship/asset/gmlgo.go b/examples/spaceship/asset/gmlgo.go index 47e2612..353a676 100644 --- a/examples/spaceship/asset/gmlgo.go +++ b/examples/spaceship/asset/gmlgo.go @@ -1,8 +1,19 @@ package asset +import "github.com/silbinarywolf/gml-go/gml" + // todo(Jake): 2018-11-24 // Auto-generate this file const ( - SprSpaceship = 1 + _ gml.SpriteIndex = 0 + SprSpaceship = 1 ) + +func init() { + gml.SpriteInitializeIndexToName([]string{ + SprSpaceship: "Spaceship", + }, map[string]gml.SpriteIndex{ + "Spaceship": SprSpaceship, + }) +} diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index 9845fb2..409ad51 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -1,13 +1,16 @@ package game -import "github.com/silbinarywolf/gml-go/gml" +import ( + "github.com/silbinarywolf/gml-go/examples/spaceship/asset" + "github.com/silbinarywolf/gml-go/gml" +) type Bullet struct { gml.Object } func (inst *Bullet) Create() { - inst.SetSprite(gml.SpriteLoad("Spaceship")) + inst.SetSprite(gml.SpriteLoad(asset.SprSpaceship)) } func (inst *Bullet) Destroy() { diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go index c177f35..aade426 100644 --- a/examples/spaceship/game/obj_player.go +++ b/examples/spaceship/game/obj_player.go @@ -1,13 +1,16 @@ package game -import "github.com/silbinarywolf/gml-go/gml" +import ( + "github.com/silbinarywolf/gml-go/examples/spaceship/asset" + "github.com/silbinarywolf/gml-go/gml" +) type Player struct { gml.Object } func (inst *Player) Create() { - inst.SetSprite(gml.SpriteLoad("Spaceship")) + inst.SetSprite(gml.SpriteLoad(asset.SprSpaceship)) } func (inst *Player) Destroy() { diff --git a/gml/animation_editor_debug.go b/gml/animation_editor_debug.go index 739d528..d6b2122 100644 --- a/gml/animation_editor_debug.go +++ b/gml/animation_editor_debug.go @@ -75,7 +75,7 @@ func (editor *debugAnimationEditor) animationConfigLoad() { // todo(Jake): 2018-10-28 // Add function to load a sprite if it exists, we don't want to crash // if we remove a sprite that we previously had loaded. - spr := sprite.LoadSprite(name) + spr := sprite.SpriteLoadByName(name) editor.spriteViewing.SetSprite(spr) } } diff --git a/gml/internal/room/room_manager_nonjs.go b/gml/internal/room/room_manager_nonjs.go index 522b298..9298b98 100644 --- a/gml/internal/room/room_manager_nonjs.go +++ b/gml/internal/room/room_manager_nonjs.go @@ -369,7 +369,7 @@ func loadRoomFromDirectoryFiles(name string) *Room { // Set room dimensions { - spr := sprite.LoadSprite(spriteName) + spr := sprite.SpriteLoadByName(spriteName) if spr == nil { println("Error loading sprite sprite \"", spriteName, "\" error: ", err.Error()) continue diff --git a/gml/internal/sprite/sprite.go b/gml/internal/sprite/sprite.go index f1a8742..59cba9b 100644 --- a/gml/internal/sprite/sprite.go +++ b/gml/internal/sprite/sprite.go @@ -15,9 +15,12 @@ type Sprite struct { imageSpeed float64 } +type SpriteIndex int32 + func (spr *Sprite) Name() string { return spr.name } func (spr *Sprite) Size() geom.Size { return spr.size } func (spr *Sprite) ImageSpeed() float64 { return spr.imageSpeed } +func (spr *Sprite) isUsed() bool { return len(spr.frames) > 0 } func (spr *Sprite) rect() geom.Rect { return geom.Rect{ Vec: geom.Vec{}, diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index 3760366..b2c0bdb 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -18,32 +18,48 @@ var ( ) type spriteManager struct { - assetMap map[string]*Sprite - assetList []*Sprite + assetList []Sprite + spriteNameToIndex map[string]SpriteIndex + spriteIndexToName []string } func newSpriteManager() *spriteManager { manager := &spriteManager{} - manager.assetMap = make(map[string]*Sprite) - manager.assetList = make([]*Sprite, 1, 10) return manager } +func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]SpriteIndex) { + g_spriteManager.spriteIndexToName = indexToName + g_spriteManager.spriteNameToIndex = nameToIndex + g_spriteManager.assetList = make([]Sprite, len(g_spriteManager.spriteIndexToName)) +} + +// todo(Jake): 2018-24-11 - Github #14 +// Remove SpriteList() as it's brittle and only used by sprite_selector.go func SpriteList() []*Sprite { - return g_spriteManager.assetList[1:] + panic("Broke sprite_selector(), need to fix") + return nil + //return g_spriteManager.assetList[1:] } -func LoadSprite(name string) *Sprite { +func SpriteLoadByName(name string) *Sprite { + index := g_spriteManager.spriteNameToIndex[name] + return SpriteLoad(index) +} + +func SpriteLoad(index SpriteIndex) *Sprite { manager := g_spriteManager - // Use already loaded asset - if res, ok := manager.assetMap[name]; ok { - return res + sprite := &manager.assetList[index] + // todo(Jake): have a "isUsed" var or function instead of checking + // for frames + if sprite.isUsed() { + return sprite } + name := g_spriteManager.spriteIndexToName[index] + // todo(Jake): change loadSprite() to return Sprite, not *Sprite result := loadSprite(name) - manager.assetMap[name] = result - manager.assetList = append(manager.assetList, result) - + *sprite = *result return result } diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index 9b8ceb3..2c3644d 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -32,6 +32,10 @@ func init() { //watcher.Close() } +func debugSpriteByName() { + +} + func DebugWatch() { // Get list of sprites updated this frame var watcherSpritesToUpdate []string @@ -60,9 +64,8 @@ FileWatchLoop: } // If those sprites are loaded, reload them - manager := g_spriteManager for _, spriteName := range watcherSpritesToUpdate { - spr := manager.assetMap[spriteName] + spr := SpriteLoadByName(spriteName) if spr != nil { newSprData := loadSprite(spriteName) *spr = *newSprData diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 4e0b0fa..1a7abdb 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -543,7 +543,7 @@ func editorUpdate() { break } // Draw bg - sprite := sprite.LoadSprite(layer.SpriteName) + sprite := sprite.SpriteLoadByName(layer.SpriteName) x := float64(layer.X) y := float64(layer.Y) width := float64(sprite.Size().X) @@ -568,7 +568,7 @@ func editorUpdate() { // Draw room sprites for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) DrawSprite(sprite, 0, geom.Vec{float64(obj.X), float64(obj.Y)}) } default: @@ -998,7 +998,7 @@ func editorUpdate() { hasCollision := false for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1043,7 +1043,7 @@ func editorUpdate() { hasCollision := false for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1095,7 +1095,7 @@ func editorUpdate() { // Mark deleted for i, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) width := float64(sprite.Size().X) height := float64(sprite.Size().Y) @@ -1402,7 +1402,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1431,7 +1431,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1460,7 +1460,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1489,7 +1489,7 @@ func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) other := geom.Rect{} other.X = float64(obj.X) @@ -1590,7 +1590,7 @@ func (roomEditor *roomEditor) calculateRoomBounds() { case *room.RoomLayerSprite: for _, obj := range layer.Sprites { spriteName := obj.SpriteName - sprite := sprite.LoadSprite(spriteName) + sprite := sprite.SpriteLoadByName(spriteName) x := int32(obj.X) y := int32(obj.Y) @@ -1736,7 +1736,7 @@ func (roomEditor *roomEditor) editorConfigLoad() { // Set brush from config switch roomEditor.editingLayer.(type) { case *room.RoomLayerSprite: - obj := sprite.LoadSprite(editorConfig.BrushSelected) + obj := sprite.SpriteLoadByName(editorConfig.BrushSelected) if obj.Name() == editorConfig.BrushSelected { roomEditor.spriteSelected = obj break diff --git a/gml/sprite.go b/gml/sprite.go index da72f72..a75b34c 100644 --- a/gml/sprite.go +++ b/gml/sprite.go @@ -4,13 +4,17 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -// todo(Jake): 2018-10-27 -// Consider changing this to `type SpriteIndex int` and exposing -// Sprite functions by accessing the assetList. +// todo(Jake): 2018-11-24 - Github Issue #2 +// Remove Sprite in favour of exposing "SpriteIndex" type Sprite = sprite.Sprite +type SpriteIndex = sprite.SpriteIndex type SpriteState = sprite.SpriteState -func SpriteLoad(name string) *Sprite { - return sprite.LoadSprite(name) +func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]SpriteIndex) { + sprite.SpriteInitializeIndexToName(indexToName, nameToIndex) +} + +func SpriteLoad(index SpriteIndex) *Sprite { + return sprite.SpriteLoad(index) } diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go index 65ab412..d79e4be 100644 --- a/gml/sprite_selector_debug.go +++ b/gml/sprite_selector_debug.go @@ -39,7 +39,7 @@ func (viewer *debugSpriteViewer) lazyLoad() { return nil } name := filepath.Base(path) - sprite.LoadSprite(name) + sprite.SpriteLoadByName(name) return nil }) if err != nil { diff --git a/gml/state.go b/gml/state.go index afcf93b..027db6f 100644 --- a/gml/state.go +++ b/gml/state.go @@ -107,7 +107,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { layer.y = float64(layerData.Y) layer.roomLeft = float64(room.Left) layer.roomRight = float64(room.Right) - layer.sprite = sprite.LoadSprite(spriteName) + layer.sprite = sprite.SpriteLoadByName(spriteName) layer.drawOrder = layerData.Config.Order roomInst.drawLayers = append(roomInst.drawLayers, layer) } @@ -120,7 +120,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { layer.sprites = make([]RoomInstanceLayerSpriteObject, 0, len(layerData.Sprites)) for _, sprObj := range layerData.Sprites { // Add draw sprite - spr := sprite.LoadSprite(sprObj.SpriteName) + spr := sprite.SpriteLoadByName(sprObj.SpriteName) record := RoomInstanceLayerSpriteObject{ Sprite: spr, } From 8605c7c99681e3de9548649c83b43aae0deeb9f2 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 24 Nov 2018 21:51:42 +1100 Subject: [PATCH 10/27] sprite: change *Sprite to SpriteIndex (int) Fixes #2 --- examples/spaceship/game/obj_player.go | 2 +- gml/animation_editor_debug.go | 35 ++++++++-------- gml/draw.go | 2 +- gml/draw_headless.go | 6 +-- gml/draw_nonheadless.go | 16 +++---- gml/internal/object/object.go | 9 ++-- gml/internal/room/room_manager_nonjs.go | 9 ++-- gml/internal/sprite/sprite.go | 34 +++++++++------ .../sprite/sprite_frame_nonheadless.go | 4 +- gml/internal/sprite/sprite_manager.go | 42 ++++++++++++------- gml/internal/sprite/sprite_manager_debug.go | 16 ++++--- gml/internal/sprite/sprite_state.go | 38 +++++++++++------ gml/room_editor_debug.go | 16 ++++--- gml/room_instance_layer_background.go | 2 +- gml/room_instance_layer_sprite.go | 6 +-- gml/sprite.go | 8 ++-- gml/sprite_selector_debug.go | 12 +++--- gml/state.go | 2 +- 18 files changed, 146 insertions(+), 113 deletions(-) diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go index aade426..dfba572 100644 --- a/examples/spaceship/game/obj_player.go +++ b/examples/spaceship/game/obj_player.go @@ -10,7 +10,7 @@ type Player struct { } func (inst *Player) Create() { - inst.SetSprite(gml.SpriteLoad(asset.SprSpaceship)) + inst.SetSprite(asset.SprSpaceship) } func (inst *Player) Destroy() { diff --git a/gml/animation_editor_debug.go b/gml/animation_editor_debug.go index d6b2122..c246518 100644 --- a/gml/animation_editor_debug.go +++ b/gml/animation_editor_debug.go @@ -82,7 +82,7 @@ func (editor *debugAnimationEditor) animationConfigLoad() { func (editor *debugAnimationEditor) animationConfigSave() { editorConfig := animationEditorConfig{} - editorConfig.SpriteSelected = editor.spriteViewing.Sprite().Name() + editorConfig.SpriteSelected = editor.spriteViewing.SpriteIndex().Name() json, _ := json.MarshalIndent(editorConfig, "", "\t") configPath := debugConfigPath("animation_editor") err := ioutil.WriteFile(configPath, json, 0644) @@ -95,9 +95,9 @@ func (editor *debugAnimationEditor) animationEditorToggleMenu(menu animMenu) { if editor.menuOpened == menu { menu = animMenuNone } - spr := editor.spriteViewing.Sprite() + spriteIndex := editor.spriteViewing.SpriteIndex() imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) - collisionMask := sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask := sprite.GetCollisionMask(spriteIndex, imageIndex, 0) value, err := strconv.ParseFloat(KeyboardString(), 64) if err == nil { switch editor.menuOpened { @@ -131,12 +131,13 @@ func animationEditorUpdate() { DrawTextColor(pos, "Space = Play/Pause Animation", color.White) pos.Y += 24 DrawTextColor(pos, "CTRL + P = Open Sprite List", color.White) - if spr := editor.spriteViewing.Sprite(); spr != nil { + + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex != sprite.SprUndefined { pos.Y += 24 DrawTextColor(pos, "CTRL + S = Save", color.White) if KeyboardCheck(VkControl) && KeyboardCheckPressed(VkS) { - err := sprite.DebugWriteSpriteConfig(spr) + err := sprite.DebugWriteSpriteConfig(spriteIndex) if err != nil { panic(err) } @@ -155,7 +156,7 @@ func animationEditorUpdate() { } // Change frame viewing - if spr := editor.spriteViewing.Sprite(); spr != nil { + if spr := editor.spriteViewing.SpriteIndex(); spr.IsValid() { imageIndex := math.Floor(editor.spriteViewing.ImageIndex()) if KeyboardCheckPressed(VkLeft) { imageIndex -= 1 @@ -176,24 +177,24 @@ func animationEditorUpdate() { // var collisionMask *sprite.CollisionMask var inheritCollisionMask *sprite.CollisionMask - if spr := editor.spriteViewing.Sprite(); spr != nil { + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) - collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) switch collisionMask.Kind { case sprite.CollisionMaskInherit: for ; imageIndex > 0; imageIndex-- { - collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) if collisionMask.Kind != sprite.CollisionMaskInherit { break } } if imageIndex == 0 { - collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) if collisionMask.Kind == sprite.CollisionMaskInherit { collisionMask = &sprite.CollisionMask{ Kind: sprite.CollisionMaskManual, Rect: geom.Rect{ - Size: spr.Size(), + Size: spriteIndex.Size(), }, } } @@ -204,8 +205,8 @@ func animationEditorUpdate() { } } - if spr := editor.spriteViewing.Sprite(); spr != nil { - size := spr.Size() + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { + size := spriteIndex.Size() pos := geom.Vec{float64(windowWidth()/2) - (float64(size.X) / 2), float64(windowHeight()/2) - (float64(size.Y) / 2)} { @@ -218,7 +219,7 @@ func animationEditorUpdate() { if editor.isInPlayback { editor.spriteViewing.ImageUpdate() } - DrawSprite(spr, editor.spriteViewing.ImageIndex(), pos) + DrawSprite(spriteIndex, editor.spriteViewing.ImageIndex(), pos) if collisionMask != nil { // Draw collision box @@ -357,7 +358,7 @@ func animationEditorUpdate() { } } - if spr := editor.spriteViewing.Sprite(); spr != nil { + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { basePos := geom.Vec{(float64(windowWidth()) / 2) - 140, float64(windowHeight())} basePos.Y -= 210 @@ -365,12 +366,12 @@ func animationEditorUpdate() { DrawTextF(basePos, "Frame: %d", imageIndex) basePos.Y += 24 if drawButton(basePos, "Kind: Inherit") { - collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) collisionMask.Kind = sprite.CollisionMaskInherit } basePos.Y += 30 if drawButton(basePos, "Kind: Manual") { - collisionMask = sprite.GetCollisionMask(spr, imageIndex, 0) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) if collisionMask.Kind != sprite.CollisionMaskManual { collisionMask.Rect = inheritCollisionMask.Rect collisionMask.Kind = sprite.CollisionMaskManual diff --git a/gml/draw.go b/gml/draw.go index 878ac4c..9622d10 100644 --- a/gml/draw.go +++ b/gml/draw.go @@ -6,5 +6,5 @@ import ( ) func DrawSelf(state *sprite.SpriteState, position geom.Vec) { - DrawSpriteScaled(state.Sprite(), state.ImageIndex(), position, state.ImageScale) + DrawSpriteScaled(state.SpriteIndex(), state.ImageIndex(), position, state.ImageScale) } diff --git a/gml/draw_headless.go b/gml/draw_headless.go index 86b5ca7..cfca667 100644 --- a/gml/draw_headless.go +++ b/gml/draw_headless.go @@ -15,13 +15,13 @@ func DrawGetGUI() bool { func DrawSetGUI(guiMode bool) { } -func DrawSprite(spr *sprite.Sprite, subimage float64, position Vec) { +func DrawSprite(spr sprite.SpriteIndex, subimage float64, position Vec) { } -func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { +func DrawSpriteScaled(spr sprite.SpriteIndex, subimage float64, position Vec, scale Vec) { } -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec, alpha float64) { +func DrawSpriteExt(spr sprite.SpriteIndex, subimage float64, position Vec, scale Vec, alpha float64) { } func DrawRectangle(pos Vec, size Vec, col color.Color) { diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index 4480eda..c547f5c 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -27,20 +27,16 @@ func DrawSetGUI(guiMode bool) { isDrawGuiMode = guiMode } -func DrawSprite(spr *sprite.Sprite, subimage float64, position geom.Vec) { - position = maybeApplyOffsetByCamera(position) - frame := sprite.GetRawFrame(spr, int(math.Floor(subimage))) - op := ebiten.DrawImageOptions{} - op.GeoM.Translate(position.X, position.Y) - drawGetTarget().DrawImage(frame, &op) +func DrawSprite(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec) { + DrawSpriteExt(spriteIndex, subimage, position, geom.Vec{1, 1}, 1.0) } -func DrawSpriteScaled(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec) { - DrawSpriteExt(spr, subimage, position, scale, 1.0) +func DrawSpriteScaled(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec, scale geom.Vec) { + DrawSpriteExt(spriteIndex, subimage, position, scale, 1.0) } // draw_sprite_ext( sprite, subimg, x, y, xscale, yscale, rot, colour, alpha ); -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position geom.Vec, scale geom.Vec, alpha float64) { +func DrawSpriteExt(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec, scale geom.Vec, alpha float64) { position = maybeApplyOffsetByCamera(position) // NOTE(Jake): 2018-07-09 // @@ -54,7 +50,7 @@ func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position geom.Vec, scal //scale.X *= view.Scale().X //scale.Y *= view.Scale().Y - frame := sprite.GetRawFrame(spr, int(math.Floor(subimage))) + frame := sprite.GetRawFrame(spriteIndex, int(math.Floor(subimage))) op := ebiten.DrawImageOptions{} op.GeoM.Scale(scale.X, scale.Y) op.GeoM.Translate(position.X, position.Y) diff --git a/gml/internal/object/object.go b/gml/internal/object/object.go index 27443d8..59f1804 100644 --- a/gml/internal/object/object.go +++ b/gml/internal/object/object.go @@ -43,16 +43,17 @@ func (inst *Object) ImageAngle() float64 { return inst.imageAngleRadians //func (inst *Object) ImageScale() geom.Vec { return inst.imageScale } -func (inst *Object) SetSprite(sprite *sprite.Sprite) { - inst.SpriteState.SetSprite(sprite) +func (inst *Object) SetSprite(spriteIndex sprite.SpriteIndex) { + inst.SpriteState.SetSprite(spriteIndex) // Infer width and height if they aren't manually set // (This might be a bad idea, too magic! But feels like Game Maker, so...) + size := spriteIndex.Size() if inst.Size.X == 0 { - inst.Size.X = sprite.Size().X + inst.Size.X = size.X } if inst.Size.Y == 0 { - inst.Size.Y = sprite.Size().Y + inst.Size.Y = size.Y } } diff --git a/gml/internal/room/room_manager_nonjs.go b/gml/internal/room/room_manager_nonjs.go index 9298b98..ec35bc5 100644 --- a/gml/internal/room/room_manager_nonjs.go +++ b/gml/internal/room/room_manager_nonjs.go @@ -369,15 +369,16 @@ func loadRoomFromDirectoryFiles(name string) *Room { // Set room dimensions { - spr := sprite.SpriteLoadByName(spriteName) - if spr == nil { + spriteIndex := sprite.SpriteLoadByName(spriteName) + if spriteIndex == sprite.SprUndefined { println("Error loading sprite sprite \"", spriteName, "\" error: ", err.Error()) continue } x := int32(x) y := int32(y) - width := int32(spr.Size().X) - height := int32(spr.Size().Y) + size := spriteIndex.Size() + width := int32(size.X) + height := int32(size.Y) if x < room.Left { room.Left = x diff --git a/gml/internal/sprite/sprite.go b/gml/internal/sprite/sprite.go index 59cba9b..a26ec43 100644 --- a/gml/internal/sprite/sprite.go +++ b/gml/internal/sprite/sprite.go @@ -8,6 +8,8 @@ const ( maxCollisionMasks = 3 ) +const SprUndefined SpriteIndex = 0 + type Sprite struct { name string frames []SpriteFrame @@ -15,25 +17,31 @@ type Sprite struct { imageSpeed float64 } -type SpriteIndex int32 - -func (spr *Sprite) Name() string { return spr.name } -func (spr *Sprite) Size() geom.Size { return spr.size } -func (spr *Sprite) ImageSpeed() float64 { return spr.imageSpeed } -func (spr *Sprite) isUsed() bool { return len(spr.frames) > 0 } +func (spr *Sprite) Name() string { return spr.name } +func (spr *Sprite) isLoaded() bool { return len(spr.frames) > 0 } func (spr *Sprite) rect() geom.Rect { return geom.Rect{ Vec: geom.Vec{}, - Size: spr.Size(), + Size: spr.size, } } -func GetCollisionMask(spr *Sprite, frame int, kind int) *CollisionMask { - // masks := &spr.frames[frame].collisionMasks[kind].masks[kind] - //if len(masks) == 0 { - // panic("Should have at least 1 collision mask defined") - //} - return &spr.frames[frame].collisionMasks[kind] +type SpriteIndex int32 + +func (spriteIndex SpriteIndex) Name() string { return g_spriteManager.assetList[spriteIndex].name } +func (spriteIndex SpriteIndex) Size() geom.Size { return g_spriteManager.assetList[spriteIndex].size } +func (spriteIndex SpriteIndex) ImageSpeed() float64 { + return g_spriteManager.assetList[spriteIndex].imageSpeed +} +func (spriteIndex SpriteIndex) IsValid() bool { + return spriteIndex > 0 +} +func (spriteIndex SpriteIndex) IsLoaded() bool { + return len(g_spriteManager.assetList[spriteIndex].frames) > 0 +} + +func Frames(spriteIndex SpriteIndex) []SpriteFrame { + return g_spriteManager.assetList[spriteIndex].frames } /*func (spr *Sprite) GetFrame(index int) *SpriteFrame { diff --git a/gml/internal/sprite/sprite_frame_nonheadless.go b/gml/internal/sprite/sprite_frame_nonheadless.go index 0b89d0b..d76da85 100644 --- a/gml/internal/sprite/sprite_frame_nonheadless.go +++ b/gml/internal/sprite/sprite_frame_nonheadless.go @@ -38,7 +38,7 @@ func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { // This is called by draw_nonheadless.go in the parent package // so that it can draw the image. // -func GetRawFrame(spr *Sprite, index int) *ebiten.Image { +func GetRawFrame(spriteIndex SpriteIndex, index int) *ebiten.Image { // NOTE(Jake): 2018-06-17 // // Golang does not "cast", it uses type conversion, which means @@ -47,5 +47,5 @@ func GetRawFrame(spr *Sprite, index int) *ebiten.Image { // // https://stackoverflow.com/questions/35115868/how-to-round-to-nearest-int-when-casting-float-to-int-in-go // - return spr.frames[index].image + return Frames(spriteIndex)[index].image } diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index b2c0bdb..93fc717 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -34,33 +34,45 @@ func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]Sp g_spriteManager.assetList = make([]Sprite, len(g_spriteManager.spriteIndexToName)) } -// todo(Jake): 2018-24-11 - Github #14 -// Remove SpriteList() as it's brittle and only used by sprite_selector.go -func SpriteList() []*Sprite { - panic("Broke sprite_selector(), need to fix") +func SpriteNames() []string { + return g_spriteManager.spriteIndexToName +} + +func sprite(index SpriteIndex) *Sprite { + sprite := &g_spriteManager.assetList[index] + if sprite.isLoaded() { + return sprite + } return nil - //return g_spriteManager.assetList[1:] } -func SpriteLoadByName(name string) *Sprite { - index := g_spriteManager.spriteNameToIndex[name] - return SpriteLoad(index) +func SpriteLoadByName(name string) SpriteIndex { + index, ok := g_spriteManager.spriteNameToIndex[name] + if !ok { + return SprUndefined + } + return index } -func SpriteLoad(index SpriteIndex) *Sprite { +/*func SpriteSize(index SpriteIndex) geom.Size { manager := g_spriteManager + sprite := &manager.assetList[index] + if !sprite.isUsed() { + panic("sprite: Invalid sprite.") + } + return sprite.Size() +}*/ +func SpriteLoad(index SpriteIndex) { + manager := g_spriteManager sprite := &manager.assetList[index] - // todo(Jake): have a "isUsed" var or function instead of checking - // for frames - if sprite.isUsed() { - return sprite + if sprite.isLoaded() { + return } - name := g_spriteManager.spriteIndexToName[index] + name := manager.spriteIndexToName[index] // todo(Jake): change loadSprite() to return Sprite, not *Sprite result := loadSprite(name) *sprite = *result - return result } func loadSpriteFromData(name string) *spriteAsset { diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index 2c3644d..13a3c66 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -65,15 +65,19 @@ FileWatchLoop: // If those sprites are loaded, reload them for _, spriteName := range watcherSpritesToUpdate { - spr := SpriteLoadByName(spriteName) - if spr != nil { - newSprData := loadSprite(spriteName) - *spr = *newSprData + spriteIndex := SpriteLoadByName(spriteName) + if spriteIndex == SprUndefined { + continue } + spr := sprite(spriteIndex) + newSprData := loadSprite(spriteName) + *spr = *newSprData } } -func DebugWriteSpriteConfig(spr *Sprite) error { +// DebugWriteSpriteConfig is called by the animation editor +func DebugWriteSpriteConfig(spriteIndex SpriteIndex) error { + spr := sprite(spriteIndex) name := spr.Name() config := loadConfig(name) @@ -82,7 +86,7 @@ func DebugWriteSpriteConfig(spr *Sprite) error { collisionMasks := make(map[int]map[int]CollisionMask) masks := make(map[int]CollisionMask) for i, _ := range spr.frames { - mask := *GetCollisionMask(spr, i, 0) + mask := *GetCollisionMask(spriteIndex, i, 0) if mask.Kind == CollisionMaskInherit { delete(masks, i) } else { diff --git a/gml/internal/sprite/sprite_state.go b/gml/internal/sprite/sprite_state.go index 84adc9d..eff96e7 100644 --- a/gml/internal/sprite/sprite_state.go +++ b/gml/internal/sprite/sprite_state.go @@ -5,29 +5,43 @@ import ( ) type SpriteState struct { - sprite *Sprite - ImageScale geom.Vec - imageIndex float64 + spriteIndex SpriteIndex + ImageScale geom.Vec + imageIndex float64 } -func (state *SpriteState) Sprite() *Sprite { return state.sprite } -func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } +func GetCollisionMask(spriteIndex SpriteIndex, imageIndex int, kind int) *CollisionMask { + spr := sprite(spriteIndex) + if spr == nil { + return nil + } + return &spr.frames[imageIndex].collisionMasks[kind] +} + +func (state *SpriteState) SpriteIndex() SpriteIndex { return state.spriteIndex } +func (state *SpriteState) sprite() SpriteIndex { return state.spriteIndex } +func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } func (state *SpriteState) ImageSpeed() float64 { - if state.sprite == nil { + if state.spriteIndex == SprUndefined { return 0 } - return state.sprite.imageSpeed + spr := sprite(state.spriteIndex) + return spr.imageSpeed } func (state *SpriteState) ImageNumber() float64 { - if state.sprite == nil { + if state.spriteIndex == SprUndefined { return 0 } - return float64(len(state.sprite.frames)) + spr := sprite(state.spriteIndex) + return float64(len(spr.frames)) } -func (state *SpriteState) SetSprite(sprite *Sprite) { - if state.sprite != sprite { - state.sprite = sprite +func (state *SpriteState) SetSprite(spriteIndex SpriteIndex) { + if state.spriteIndex != spriteIndex { + if !spriteIndex.IsLoaded() { + SpriteLoad(spriteIndex) + } + state.spriteIndex = spriteIndex state.imageIndex = 0 } } diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 1a7abdb..38e268c 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -61,7 +61,7 @@ type roomEditor struct { //spriteMenuFiltered []*sprite.Sprite objectSelected object.ObjectType - spriteSelected *sprite.Sprite + spriteSelected SpriteIndex mouseHold [MbSize]bool gridEnabled bool @@ -534,8 +534,7 @@ func editorUpdate() { baseObj := inst.BaseObject() baseObj.X = float64(obj.X) baseObj.Y = float64(obj.Y) - spr := baseObj.Sprite() - DrawSpriteScaled(spr, 0, baseObj.Pos(), baseObj.ImageScale) + DrawSpriteScaled(baseObj.SpriteIndex(), 0, baseObj.Pos(), baseObj.ImageScale) } case *room.RoomLayerBackground: if layer.SpriteName == "" { @@ -607,7 +606,7 @@ func editorUpdate() { } case *room.RoomLayerSprite: // Draw selected sprite - if selectedBrush := roomEditor.spriteSelected; selectedBrush != nil { + if selectedBrush := roomEditor.spriteSelected; selectedBrush.IsValid() { pos := MousePosition() if isHoldingControl { maybeNewPos, ok := roomEditor.getSnapPosition(pos, selectedBrush.Size(), layer) @@ -962,7 +961,7 @@ func editorUpdate() { case *room.RoomLayerSprite: // Left click if roomEditor.MouseCheckButton(MbLeft) && - roomEditor.spriteSelected != nil { + roomEditor.spriteSelected.IsValid() { spriteName := roomEditor.spriteSelected.Name() //spriteWidth := roomEditor.spriteSelected.Size().X //spriteHeight := roomEditor.spriteSelected.Size().Y @@ -1335,7 +1334,7 @@ func editorUpdate() { func drawObjectPreview(inst object.ObjectType, pos geom.Vec, fitToSize geom.Vec) { baseObj := inst.BaseObject() - sprite := baseObj.Sprite() + sprite := baseObj.SpriteIndex() spriteSize := sprite.Size() scale := geom.Vec{fitToSize.X / float64(spriteSize.X), fitToSize.Y / float64(spriteSize.Y)} if scale.X > 1 { @@ -1350,8 +1349,7 @@ func drawObjectPreview(inst object.ObjectType, pos geom.Vec, fitToSize geom.Vec) func drawObject(inst object.ObjectType, pos geom.Vec) { baseObj := inst.BaseObject() //baseObj.Vec = pos - sprite := baseObj.Sprite() - DrawSprite(sprite, 0, pos) + DrawSprite(baseObj.SpriteIndex(), 0, pos) } func drawRoomObject(roomObject *room.RoomObject, pos geom.Vec) { @@ -1766,7 +1764,7 @@ func (roomEditor *roomEditor) editorConfigSave() { editorConfig.BrushSelected = roomEditor.objectSelected.ObjectName() } case *room.RoomLayerSprite: - if roomEditor.spriteSelected != nil { + if roomEditor.spriteSelected.IsValid() { editorConfig.BrushSelected = roomEditor.spriteSelected.Name() } case *room.RoomLayerBackground: diff --git a/gml/room_instance_layer_background.go b/gml/room_instance_layer_background.go index 9b66d4c..e782800 100644 --- a/gml/room_instance_layer_background.go +++ b/gml/room_instance_layer_background.go @@ -8,7 +8,7 @@ import ( type RoomInstanceLayerBackground struct { RoomInstanceLayerDrawBase name string - sprite *sprite.Sprite + sprite sprite.SpriteIndex x, y float64 roomLeft float64 roomRight float64 diff --git a/gml/room_instance_layer_sprite.go b/gml/room_instance_layer_sprite.go index e85fc94..60ee811 100644 --- a/gml/room_instance_layer_sprite.go +++ b/gml/room_instance_layer_sprite.go @@ -7,13 +7,13 @@ import ( type RoomInstanceLayerSpriteObject struct { geom.Vec - Sprite *sprite.Sprite + sprite sprite.SpriteIndex } func (record *RoomInstanceLayerSpriteObject) Rect() geom.Rect { r := geom.Rect{} r.Vec = record.Vec - r.Size = record.Sprite.Size() + r.Size = record.sprite.Size() return r } @@ -37,6 +37,6 @@ func (layer *RoomInstanceLayerSprite) draw() { op := ebiten.DrawImageOptions{} op.GeoM.Translate(position.X, position.Y) screen.DrawImage(frame, &op)*/ - DrawSprite(record.Sprite, 0, geom.Vec{record.X, record.Y}) + DrawSprite(record.sprite, 0, geom.Vec{record.X, record.Y}) } } diff --git a/gml/sprite.go b/gml/sprite.go index a75b34c..4fb43c7 100644 --- a/gml/sprite.go +++ b/gml/sprite.go @@ -4,9 +4,6 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -// todo(Jake): 2018-11-24 - Github Issue #2 -// Remove Sprite in favour of exposing "SpriteIndex" -type Sprite = sprite.Sprite type SpriteIndex = sprite.SpriteIndex type SpriteState = sprite.SpriteState @@ -15,6 +12,7 @@ func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]Sp sprite.SpriteInitializeIndexToName(indexToName, nameToIndex) } -func SpriteLoad(index SpriteIndex) *Sprite { - return sprite.SpriteLoad(index) +func SpriteLoad(index SpriteIndex) SpriteIndex { + sprite.SpriteLoad(index) + return index } diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go index d79e4be..cbee7ec 100644 --- a/gml/sprite_selector_debug.go +++ b/gml/sprite_selector_debug.go @@ -13,7 +13,7 @@ import ( ) var ( - debugScratchSpriteList []*sprite.Sprite + debugScratchSpriteList []sprite.SpriteIndex debugSpriteViewerLoaded bool ) @@ -47,17 +47,17 @@ func (viewer *debugSpriteViewer) lazyLoad() { } } -func (viewer *debugSpriteViewer) Update() (*sprite.Sprite, bool) { +func (viewer *debugSpriteViewer) Update() (sprite.SpriteIndex, bool) { viewer.lazyLoad() typingText := KeyboardString() spriteMenuFiltered := debugScratchSpriteList[:0] - for _, spr := range sprite.SpriteList() { - hasMatch := hasFilterMatch(spr.Name(), typingText) + for spriteIndex, name := range sprite.SpriteNames() { + hasMatch := hasFilterMatch(name, typingText) if !hasMatch { continue } - spriteMenuFiltered = append(spriteMenuFiltered, spr) + spriteMenuFiltered = append(spriteMenuFiltered, sprite.SpriteIndex(spriteIndex)) } // Input @@ -106,5 +106,5 @@ func (viewer *debugSpriteViewer) Update() (*sprite.Sprite, bool) { ui.Y += previewSize.Y + 16 } } - return nil, false + return sprite.SprUndefined, false } diff --git a/gml/state.go b/gml/state.go index 027db6f..a222658 100644 --- a/gml/state.go +++ b/gml/state.go @@ -122,7 +122,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { // Add draw sprite spr := sprite.SpriteLoadByName(sprObj.SpriteName) record := RoomInstanceLayerSpriteObject{ - Sprite: spr, + sprite: spr, } record.X = float64(sprObj.X) record.Y = float64(sprObj.Y) From bc1dfc1a39d59236f5192685ad19fef5b662f881 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 26 Nov 2018 21:31:53 +1100 Subject: [PATCH 11/27] gmlgo: do more work on code generator, able to determine structs that embed gml.Object Updates #9 --- cmd/gmlgo/gmlgo.go | 183 +++++++++++++-------------------------------- 1 file changed, 52 insertions(+), 131 deletions(-) diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index 0fe8776..15d573b 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -6,7 +6,6 @@ import ( "fmt" "go/ast" "go/build" - "go/constant" "go/format" "go/importer" "go/parser" @@ -230,45 +229,73 @@ func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) { pkg.typesPkg = typesPkg } +type Struct struct { + Name string +} + // generate produces the String method for the named type. func (g *Generator) generate() { - values := make([]Value, 0, 100) + var structsUsingGMLObject []Struct for _, file := range g.pkg.files { - // Set the state for this run of the walker. - file.values = nil + gmlPackageName := "" if file.file != nil { fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) ast.Inspect(file.file, func(n ast.Node) bool { - var s string switch n := n.(type) { - case *ast.BasicLit: - s = n.Value - case *ast.Ident: - s = n.Name - case *ast.StructType: - //if n.Fields.NumFields() == 0 { - // break - //} - fmt.Printf("STRUCT\n") - for _, field := range n.Fields.List { - fmt.Printf("%v\n", field.Names) + // import "github.com/silbinarywolf/gml-go/gml" + case *ast.ImportSpec: + if n.Path.Value == "\"github.com/silbinarywolf/gml-go/gml\"" { + gmlPackageName = "gml" + if n.Name != nil { + // import gml "github.com/silbinarywolf/gml-go/gml" + gmlPackageName = n.Name.Name + } } + return false + // type XXXX struct + case *ast.TypeSpec: + structName := n.Name.Name + switch n := n.Type.(type) { + // type XXXX struct + case *ast.StructType: + if n.Incomplete || + gmlPackageName == "" { + return false + } + for _, field := range n.Fields.List { + switch fieldType := field.Type.(type) { + case *ast.SelectorExpr: + switch kind := fieldType.X.(type) { + case *ast.Ident: + // Find embedded "gml.Object" + if kind.Name == gmlPackageName && + fieldType.Sel.Name == "Object" { + structsUsingGMLObject = append(structsUsingGMLObject, Struct{ + Name: structName, + }) + } + } + } + } + return false + } + return false } - _ = s - //fmt.Printf("generate: %T\n", n) return true }) - //ast.Inspect(file.file, file.genDecl) - //fmt.Printf("generate: %v\n\n", file) - values = append(values, file.values...) } } - if len(values) == 0 { + if len(structsUsingGMLObject) == 0 { return - //log.Fatalf("no values defined for type %s", typeName) } - runs := splitIntoRuns(values) + + for _, record := range structsUsingGMLObject { + fmt.Printf("- %s\n", record.Name) + } + panic("End") + + //runs := splitIntoRuns(values) // The decision of which pattern to use depends on the number of // runs in the numbers. If there's only one, it's easy. For more than // one, there's a tradeoff between complexity and size of the data @@ -281,7 +308,7 @@ func (g *Generator) generate() { // being necessary for any realistic example other than bitmasks // is very low. And bitmasks probably deserve their own analysis, // to be done some other day. - g.buildOneRun(runs) + //g.buildOneRun(runs) /*switch { case len(runs) == 1: g.buildOneRun(runs, typeName) @@ -368,112 +395,6 @@ func (b byValue) Less(i, j int) bool { return b[i].value < b[j].value } -// genDecl processes one declaration clause. -func (f *File) genDecl(node ast.Node) bool { - decl, ok := node.(*ast.GenDecl) - if !ok || decl.Tok != token.CONST { - // We only care about const declarations. - return true - } - // The name of the type of the constants we are declaring. - // Can change if this is a multi-element declaration. - typ := "" - // Loop over the elements of the declaration. Each element is a ValueSpec: - // a list of names possibly followed by a type, possibly followed by values. - // If the type and value are both missing, we carry down the type (and value, - // but the "go/types" package takes care of that). - for _, spec := range decl.Specs { - vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. - if vspec.Type == nil && len(vspec.Values) > 0 { - // "X = 1". With no type but a value. If the constant is untyped, - // skip this vspec and reset the remembered type. - typ = "" - - { - // NOTE(Jake): 2018-11-23 - // Continue here - bl, ok := vspec.Values[0].(*ast.BasicLit) - if !ok { - continue - } - fmt.Printf("%s\n", decl.Tok.String()) - fmt.Printf("%s\n", vspec.Names[0]) - panic(bl.Value) // Returns 1 - } - - // If this is a simple type conversion, remember the type. - // We don't mind if this is actually a call; a qualified call won't - // be matched (that will be SelectorExpr, not Ident), and only unusual - // situations will result in a function call that appears to be - // a type conversion. - ce, ok := vspec.Values[0].(*ast.CallExpr) - if !ok { - continue - } - id, ok := ce.Fun.(*ast.Ident) - if !ok { - continue - } - typ = id.Name - } - if vspec.Type != nil { - // "X T". We have a type. Remember it. - ident, ok := vspec.Type.(*ast.Ident) - if !ok { - continue - } - typ = ident.Name - } - //if typ != f.typeName { - // This is not the type we're looking for. - //continue - //} - // We now have a list of names (from one line of source code) all being - // declared with the desired type. - // Grab their names and actual values and store them in f.values. - for _, name := range vspec.Names { - if name.Name == "_" { - continue - } - // This dance lets the type checker find the values for us. It's a - // bit tricky: look up the object declared by the name, find its - // types.Const, and extract its value. - obj, ok := f.pkg.defs[name] - if !ok { - log.Fatalf("no value for constant %s", name) - } - info := obj.Type().Underlying().(*types.Basic).Info() - if info&types.IsInteger == 0 { - log.Fatalf("can't handle non-integer constant type %s", typ) - } - value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. - if value.Kind() != constant.Int { - log.Fatalf("can't happen: constant is not an integer %s", name) - } - i64, isInt := constant.Int64Val(value) - u64, isUint := constant.Uint64Val(value) - if !isInt && !isUint { - log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) - } - if !isInt { - u64 = uint64(i64) - } - v := Value{ - name: name.Name, - value: u64, - signed: info&types.IsUnsigned == 0, - str: value.String(), - } - if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { - v.name = strings.TrimSpace(c.Text()) - } - v.name = strings.TrimPrefix(v.name, f.trimPrefix) - f.values = append(f.values, v) - } - } - return false -} - // Helpers // usize returns the number of bits of the smallest unsigned integer From af927090b8d0e759a42854d5ef8ef0a52ded4fc0 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 26 Nov 2018 21:33:57 +1100 Subject: [PATCH 12/27] example/spaceship: add bullet sprite and small ttf font --- examples/spaceship/asset/font/tiny.ttf | Bin 0 -> 6656 bytes examples/spaceship/asset/gmlgo.go | 3 +++ examples/spaceship/asset/sprite/Bullet/0.png | Bin 0 -> 223 bytes examples/spaceship/game/game.go | 9 +++++++-- examples/spaceship/game/obj_bullet.go | 2 +- gml/font_manager_nonheadless.go | 6 +++++- 6 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 examples/spaceship/asset/font/tiny.ttf create mode 100644 examples/spaceship/asset/sprite/Bullet/0.png diff --git a/examples/spaceship/asset/font/tiny.ttf b/examples/spaceship/asset/font/tiny.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a4474118affb3f7f297723c18f5031ca07956d04 GIT binary patch literal 6656 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 literal 0 HcmV?d00001 diff --git a/examples/spaceship/asset/gmlgo.go b/examples/spaceship/asset/gmlgo.go index 353a676..85e9571 100644 --- a/examples/spaceship/asset/gmlgo.go +++ b/examples/spaceship/asset/gmlgo.go @@ -8,12 +8,15 @@ import "github.com/silbinarywolf/gml-go/gml" const ( _ gml.SpriteIndex = 0 SprSpaceship = 1 + SprBullet = 2 ) func init() { gml.SpriteInitializeIndexToName([]string{ SprSpaceship: "Spaceship", + SprBullet: "Bullet", }, map[string]gml.SpriteIndex{ "Spaceship": SprSpaceship, + "Bullet": SprBullet, }) } diff --git a/examples/spaceship/asset/sprite/Bullet/0.png b/examples/spaceship/asset/sprite/Bullet/0.png new file mode 100644 index 0000000000000000000000000000000000000000..43066efcc00d2fca9259e6260a1057ddba707e41 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}c0*}aI z1_r*vAk26?e?qM&&cqjLgrMUq8LvX z$B+p3x91GG4j2e9JN{QYf9A62B^GgRGYPZauFP*;mpwjd_oyMk=y%%kyYdh1FtN9n rX)IjHY_$M^(%8?(OZbAJtt7v^$0h5gK<|w}dl)=j{an^LB{Ts5TUbJ= literal 0 HcmV?d00001 diff --git a/examples/spaceship/game/game.go b/examples/spaceship/game/game.go index dbdee44..8da4c50 100644 --- a/examples/spaceship/game/game.go +++ b/examples/spaceship/game/game.go @@ -14,14 +14,19 @@ var ( ) type GameWorld struct { - // todo(Jake): 2018-11-28 - #6 + // todo(Jake): 2018-11-24 - #6 // Change int to gml.RoomIndex CurrentRoomIndex int } func GameStart() { + // todo(Jake): 2018-11-24 - #15 + // - Simplify this so that you can just pass "asset.FntTiny"? + // - Change LoadFont to return FontIndex + gml.DrawSetFont(gml.LoadFont("tiny", gml.FontSettings{})) + // Setup camera - // todo(Jake): 2018-11-28 - #3 + // todo(Jake): 2018-11-24 - #3 // Change CameraCreate to use geom.Size for w/h gml.CameraCreate(0, 0, 0, float64(gml.WindowWidth()), float64(gml.WindowHeight())) gameWorld.CurrentRoomIndex = gml.RoomInstanceNew() diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index 409ad51..3083388 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -10,7 +10,7 @@ type Bullet struct { } func (inst *Bullet) Create() { - inst.SetSprite(gml.SpriteLoad(asset.SprSpaceship)) + inst.SetSprite(gml.SpriteLoad(asset.SprBullet)) } func (inst *Bullet) Destroy() { diff --git a/gml/font_manager_nonheadless.go b/gml/font_manager_nonheadless.go index 489b622..28575a6 100644 --- a/gml/font_manager_nonheadless.go +++ b/gml/font_manager_nonheadless.go @@ -13,6 +13,10 @@ import ( var g_fontManager = newFontManager() +const ( + fontDirectoryBase = "font" +) + type FontManager struct { currentFont *Font assetMap map[string]*Font @@ -36,7 +40,7 @@ func LoadFont(name string, settings FontSettings) *Font { return result } - path := AssetDirectory() + "/fonts/" + name + ".ttf" + path := AssetDirectory() + "/" + fontDirectoryBase + "/" + name + ".ttf" fileData, err := file.OpenFile(path) if err != nil { panic(errors.New("Unable to find font: " + path + ". Error: " + err.Error())) From c3842aa1450d0e1d57f0734dd994debfcbded422 Mon Sep 17 00:00:00 2001 From: Jake B Date: Tue, 27 Nov 2018 21:52:04 +1100 Subject: [PATCH 13/27] gmlgo: first working pass of the gmlgo code generator Updates #9 --- cmd/gmlgo/gmlgo.go | 388 ++++++------------------------ examples/spaceship/asset/gmlgo.go | 5 +- examples/spaceship/game/gmlgo.go | 23 +- 3 files changed, 88 insertions(+), 328 deletions(-) diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index 15d573b..d73c5af 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -16,25 +16,27 @@ import ( "os" "path/filepath" "sort" + "strconv" "strings" ) +const ( + version = "0.1.0" +) + var ( - output = flag.String("output", "", "output file name; default srcdir/_string.go") - trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") - linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") - buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") +//buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") ) // Usage is a replacement usage function for the flags package. func Usage() { fmt.Fprintf(os.Stderr, "Usage of gmlgo:\n") - fmt.Fprintf(os.Stderr, "\tgmlgo [flags] [directory]\n") - fmt.Fprintf(os.Stderr, "\tgmlgo [flags] files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "\tgmlgo [directory]\n") + fmt.Fprintf(os.Stderr, "\tgmlgo files... # Must be a single package\n") fmt.Fprintf(os.Stderr, "For more information, see:\n") - fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/gmlgo\n") - fmt.Fprintf(os.Stderr, "Flags:\n") - flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\thttp://godoc.org/github.com/silbinarywolf/gml-go/cmd/gmlgo\n") + //fmt.Fprintf(os.Stderr, "Flags:\n") + //flag.PrintDefaults() } func main() { @@ -43,9 +45,10 @@ func main() { flag.Usage = Usage flag.Parse() var tags []string + /*var tags []string if len(*buildTags) > 0 { tags = strings.Split(*buildTags, ",") - } + }*/ // We accept either one directory or a list of files. Which do we have? args := flag.Args() @@ -56,10 +59,7 @@ func main() { // Parse the package once. var dir string - g := Generator{ - trimPrefix: *trimprefix, - lineComment: *linecomment, - } + g := Generator{} if len(args) == 1 && isDirectory(args[0]) { dir = args[0] g.parsePackageDir(args[0], tags) @@ -71,12 +71,19 @@ func main() { g.parsePackageFiles(args) } - // Print the header and package clause. - g.Printf("// Code generated by \"gmlgo %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) - g.Printf("\n") - g.Printf("package %s", g.pkg.name) - g.Printf("\n") - g.Printf("import \"strconv\"\n") // Used by all methods. + // get filename + baseName := fmt.Sprintf("gmlgo.go") + outputName := filepath.Join(dir, strings.ToLower(baseName)) + + // check existing file + input, err := ioutil.ReadFile(outputName) + if err != nil { + log.Fatalf("reading file: %s", err) + } + if !strings.Contains(string(input), "// Code generated by \"gmlgo") { + log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) + return + } // Run generate g.generate() @@ -84,16 +91,18 @@ func main() { // Format the output. src := g.format() - // Write to file. - outputName := *output - if outputName == "" { - baseName := fmt.Sprintf("gmlgo_gen.go") - outputName = filepath.Join(dir, strings.ToLower(baseName)) + // Check if any changes + if bytes.Equal(input, src) { + log.Printf("no changes to %s\n", outputName) + return } - err := ioutil.WriteFile(outputName, src, 0644) + + // Write to file. + err = ioutil.WriteFile(outputName, src, 0644) if err != nil { - log.Fatalf("writing output: %s", err) + log.Fatalf("error writing output: %s", err) } + log.Printf("updated %s\n", outputName) } // isDirectory reports whether the named file is a directory. @@ -110,9 +119,6 @@ func isDirectory(name string) bool { type Generator struct { buf bytes.Buffer // Accumulated output. pkg *Package // Package we are scanning. - - trimPrefix string - lineComment bool } func (g *Generator) Printf(format string, args ...interface{}) { @@ -123,11 +129,6 @@ func (g *Generator) Printf(format string, args ...interface{}) { type File struct { pkg *Package // Package to which this file belongs. file *ast.File // Parsed AST. - // These fields are reset for each type being generated. - values []Value // Accumulator for constant values of that type. - - trimPrefix string - lineComment bool } type Package struct { @@ -152,11 +153,6 @@ func (g *Generator) parsePackageDir(directory string, tags []string) { } var names []string names = append(names, pkg.GoFiles...) - //names = append(names, pkg.CgoFiles...) - // TODO: Need to think about constants in test files. Maybe write type_string_test.go - // in a separate pass? For later. - // names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. - //names = append(names, pkg.SFiles...) names = prefixDirectory(directory, names) g.parsePackage(directory, names, nil) } @@ -196,10 +192,8 @@ func (g *Generator) parsePackage(directory string, names []string, text interfac } astFiles = append(astFiles, parsedFile) files = append(files, &File{ - file: parsedFile, - pkg: g.pkg, - trimPrefix: g.trimPrefix, - lineComment: g.lineComment, + file: parsedFile, + pkg: g.pkg, }) } if len(astFiles) == 0 { @@ -233,13 +227,13 @@ type Struct struct { Name string } -// generate produces the String method for the named type. +// generate produces the code for object indexes func (g *Generator) generate() { var structsUsingGMLObject []Struct for _, file := range g.pkg.files { gmlPackageName := "" if file.file != nil { - fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) + //fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) ast.Inspect(file.file, func(n ast.Node) bool { switch n := n.(type) { // import "github.com/silbinarywolf/gml-go/gml" @@ -290,65 +284,45 @@ func (g *Generator) generate() { return } - for _, record := range structsUsingGMLObject { - fmt.Printf("- %s\n", record.Name) + // Sort alphabetically + sort.Slice(structsUsingGMLObject[:], func(i, j int) bool { + return structsUsingGMLObject[i].Name < structsUsingGMLObject[j].Name + }) + + // Print the header and package clause. + g.Printf("// Code generated by \"gmlgo %s\";%s; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "), version) + g.Printf("\n") + g.Printf(`package ` + g.pkg.name + ` + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +const ( +`) + for i, record := range structsUsingGMLObject { + g.Printf(" Obj" + record.Name + " gml.ObjectIndex = " + strconv.Itoa(i+1) + "\n") } - panic("End") - - //runs := splitIntoRuns(values) - // The decision of which pattern to use depends on the number of - // runs in the numbers. If there's only one, it's easy. For more than - // one, there's a tradeoff between complexity and size of the data - // and code vs. the simplicity of a map. A map takes more space, - // but so does the code. The decision here (crossover at 10) is - // arbitrary, but considers that for large numbers of runs the cost - // of the linear scan in the switch might become important, and - // rather than use yet another algorithm such as binary search, - // we punt and use a map. In any case, the likelihood of a map - // being necessary for any realistic example other than bitmasks - // is very low. And bitmasks probably deserve their own analysis, - // to be done some other day. - //g.buildOneRun(runs) - /*switch { - case len(runs) == 1: - g.buildOneRun(runs, typeName) - case len(runs) <= 10: - g.buildMultipleRuns(runs, typeName) - default: - g.buildMap(runs, typeName) - }*/ -} + g.Printf(`) -// splitIntoRuns breaks the values into runs of contiguous sequences. -// For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}. -// The input slice is known to be non-empty. -func splitIntoRuns(values []Value) [][]Value { - // We use stable sort so the lexically first name is chosen for equal elements. - sort.Stable(byValue(values)) - // Remove duplicates. Stable sort has put the one we want to print first, - // so use that one. The String method won't care about which named constant - // was the argument, so the first name for the given value is the only one to keep. - // We need to do this because identical values would cause the switch or map - // to fail to compile. - j := 1 - for i := 1; i < len(values); i++ { - if values[i].value != values[i-1].value { - values[j] = values[i] - j++ - } +`) + + for _, record := range structsUsingGMLObject { + g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") + g.Printf("func (inst *" + record.Name + ") ObjectName() string { return \"" + record.Name + "\" }\n") + g.Printf("\n") } - values = values[:j] - runs := make([][]Value, 0, 10) - for len(values) > 0 { - // One contiguous sequence per outer loop. - i := 1 - for i < len(values) && values[i].value == values[i-1].value+1 { - i++ - } - runs = append(runs, values[:i]) - values = values[i:] + g.Printf(` + +func init() { + gml.ObjectInitTypes([]gml.ObjectType{ +`) + for _, record := range structsUsingGMLObject { + g.Printf(" Obj" + record.Name + ": new(" + record.Name + "),\n") } - return runs + g.Printf(` }) +} +`) } // format returns the gofmt-ed contents of the Generator's buffer. @@ -363,215 +337,3 @@ func (g *Generator) format() []byte { } return src } - -// Value represents a declared constant. -type Value struct { - name string // The name of the constant. - // The value is stored as a bit pattern alone. The boolean tells us - // whether to interpret it as an int64 or a uint64; the only place - // this matters is when sorting. - // Much of the time the str field is all we need; it is printed - // by Value.String. - value uint64 // Will be converted to int64 when needed. - signed bool // Whether the constant is a signed type. - str string // The string representation given by the "go/constant" package. -} - -func (v *Value) String() string { - return v.str -} - -// byValue lets us sort the constants into increasing order. -// We take care in the Less method to sort in signed or unsigned order, -// as appropriate. -type byValue []Value - -func (b byValue) Len() int { return len(b) } -func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b byValue) Less(i, j int) bool { - if b[i].signed { - return int64(b[i].value) < int64(b[j].value) - } - return b[i].value < b[j].value -} - -// Helpers - -// usize returns the number of bits of the smallest unsigned integer -// type that will hold n. Used to create the smallest possible slice of -// integers to use as indexes into the concatenated strings. -func usize(n int) int { - switch { - case n < 1<<8: - return 8 - case n < 1<<16: - return 16 - default: - // 2^32 is enough constants for anyone. - return 32 - } -} - -// declareIndexAndNameVars declares the index slices and concatenated names -// strings representing the runs of values. -func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) { - var indexes, names []string - for i, run := range runs { - index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i)) - if len(run) != 1 { - indexes = append(indexes, index) - } - names = append(names, name) - } - g.Printf("const (\n") - for _, name := range names { - g.Printf("\t%s\n", name) - } - g.Printf(")\n\n") - - if len(indexes) > 0 { - g.Printf("var (") - for _, index := range indexes { - g.Printf("\t%s\n", index) - } - g.Printf(")\n\n") - } -} - -// declareIndexAndNameVar is the single-run version of declareIndexAndNameVars -func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) { - index, name := g.createIndexAndNameDecl(run, typeName, "") - g.Printf("const %s\n", name) - g.Printf("var %s\n", index) -} - -// createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var". -func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) { - b := new(bytes.Buffer) - indexes := make([]int, len(run)) - for i := range run { - b.WriteString(run[i].name) - indexes[i] = b.Len() - } - nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String()) - nameLen := b.Len() - b.Reset() - fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen)) - for i, v := range indexes { - if i > 0 { - fmt.Fprintf(b, ", ") - } - fmt.Fprintf(b, "%d", v) - } - fmt.Fprintf(b, "}") - return b.String(), nameConst -} - -// declareNameVars declares the concatenated names string representing all the values in the runs. -func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) { - g.Printf("const _%s_name%s = \"", typeName, suffix) - for _, run := range runs { - for i := range run { - g.Printf("%s", run[i].name) - } - } - g.Printf("\"\n") -} - -// buildOneRun generates the variables and String method for a single run of contiguous values. -func (g *Generator) buildOneRun(runs [][]Value) { - values := runs[0] - g.Printf("\n") - typeName := "typeNameHere_ToBeReplaced" - g.declareIndexAndNameVar(values, typeName) - // The generated code is simple enough to write as a Printf format. - lessThanZero := "" - if values[0].signed { - lessThanZero = "i < 0 || " - } - if values[0].value == 0 { // Signed or unsigned, 0 is still 0. - g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero) - } else { - g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero) - } -} - -// Arguments to format are: -// [1]: type name -// [2]: size of index element (8 for uint8 etc.) -// [3]: less than zero check (for signed types) -const stringOneRun = `func (i %[1]s) String() string { - if %[3]si >= %[1]s(len(_%[1]s_index)-1) { - return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]] -} -` - -// Arguments to format are: -// [1]: type name -// [2]: lowest defined value for type, as a string -// [3]: size of index element (8 for uint8 etc.) -// [4]: less than zero check (for signed types) -/* - */ -const stringOneRunWithOffset = `func (i %[1]s) String() string { - i -= %[2]s - if %[4]si >= %[1]s(len(_%[1]s_index)-1) { - return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")" - } - return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]] -} -` - -// buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. -// For this pattern, a single Printf format won't do. -/*func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) { - g.Printf("\n") - g.declareIndexAndNameVars(runs, typeName) - g.Printf("func (i %s) String() string {\n", typeName) - g.Printf("\tswitch {\n") - for i, values := range runs { - if len(values) == 1 { - g.Printf("\tcase i == %s:\n", &values[0]) - g.Printf("\t\treturn _%s_name_%d\n", typeName, i) - continue - } - g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1]) - if values[0].value != 0 { - g.Printf("\t\ti -= %s\n", &values[0]) - } - g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n", - typeName, i, typeName, i, typeName, i) - } - g.Printf("\tdefault:\n") - g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName) - g.Printf("\t}\n") - g.Printf("}\n") -}*/ - -// buildMap handles the case where the space is so sparse a map is a reasonable fallback. -// It's a rare situation but has simple code. -/*func (g *Generator) buildMap(runs [][]Value, typeName string) { - g.Printf("\n") - g.declareNameVars(runs, typeName, "") - g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName) - n := 0 - for _, values := range runs { - for _, value := range values { - g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name)) - n += len(value.name) - } - } - g.Printf("}\n\n") - g.Printf(stringMap, typeName) -}*/ - -// Argument to format is the type name. -const stringMap = `func (i %[1]s) String() string { - if str, ok := _%[1]s_map[i]; ok { - return str - } - return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" -} -` diff --git a/examples/spaceship/asset/gmlgo.go b/examples/spaceship/asset/gmlgo.go index 85e9571..59ce7bf 100644 --- a/examples/spaceship/asset/gmlgo.go +++ b/examples/spaceship/asset/gmlgo.go @@ -6,9 +6,8 @@ import "github.com/silbinarywolf/gml-go/gml" // Auto-generate this file const ( - _ gml.SpriteIndex = 0 - SprSpaceship = 1 - SprBullet = 2 + SprSpaceship gml.SpriteIndex = 1 + SprBullet gml.SpriteIndex = 2 ) func init() { diff --git a/examples/spaceship/game/gmlgo.go b/examples/spaceship/game/gmlgo.go index 7ec7c85..aaca719 100644 --- a/examples/spaceship/game/gmlgo.go +++ b/examples/spaceship/game/gmlgo.go @@ -1,26 +1,25 @@ -package game +// Code generated ;0.1.0; DO NOT EDIT. -import "github.com/silbinarywolf/gml-go/gml" +package game -// todo(Jake): 2018-11-24 - Github Issue #9 -// Make this file auto-generated +import ( + "github.com/silbinarywolf/gml-go/gml" +) const ( - _ gml.ObjectIndex = 0 - ObjPlayer = 1 - ObjBullet = 2 + ObjBullet gml.ObjectIndex = 1 + ObjPlayer gml.ObjectIndex = 2 ) -func (inst *Player) ObjectIndex() gml.ObjectIndex { return ObjPlayer } -func (inst *Player) ObjectName() string { return "Player" } - func (inst *Bullet) ObjectIndex() gml.ObjectIndex { return ObjBullet } func (inst *Bullet) ObjectName() string { return "Bullet" } +func (inst *Player) ObjectIndex() gml.ObjectIndex { return ObjPlayer } +func (inst *Player) ObjectName() string { return "Player" } + func init() { gml.ObjectInitTypes([]gml.ObjectType{ - // This is used by gml.InstanceCreate to clone new instances by ObjectIndex - ObjPlayer: new(Player), ObjBullet: new(Bullet), + ObjPlayer: new(Player), }) } From 6f289f87aacf4833de3cb8985757e3a1f31e184a Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 1 Dec 2018 17:13:26 +1100 Subject: [PATCH 14/27] gmlgo: get code generator working for sprite assets Updates #9 --- .travis.yml | 2 + cmd/gmlgo/gmlgo.go | 289 +++++++++++++++++--------- examples/spaceship/.gitignore | 7 +- examples/spaceship/asset/gmlgo.go | 21 -- examples/spaceship/game/gmlgo.go | 25 --- examples/spaceship/game/obj_bullet.go | 3 +- examples/spaceship/game/obj_player.go | 3 +- 7 files changed, 206 insertions(+), 144 deletions(-) delete mode 100644 examples/spaceship/asset/gmlgo.go delete mode 100644 examples/spaceship/game/gmlgo.go diff --git a/.travis.yml b/.travis.yml index bbc3cbb..39c73ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,8 @@ before_script: script: - go build -v ./... + - go build -tags debug -v ./... + - go build -tags headless -v ./... # NOTE(Jake): 2018-06-20 # # No tests exist yet diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index d73c5af..b1eb215 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -21,7 +21,9 @@ import ( ) const ( - version = "0.1.0" + defaultImportName = "gml" + importString = "\"github.com/silbinarywolf/gml-go/gml\"" + version = "0.1.0" ) var ( @@ -44,53 +46,50 @@ func main() { log.SetPrefix("gmlgo: ") flag.Usage = Usage flag.Parse() - var tags []string - /*var tags []string - if len(*buildTags) > 0 { - tags = strings.Split(*buildTags, ",") - }*/ - - // We accept either one directory or a list of files. Which do we have? - args := flag.Args() - if len(args) == 0 { - // Default: process whole package in current directory. - args = []string{"."} - } - // Parse the package once. - var dir string - g := Generator{} - if len(args) == 1 && isDirectory(args[0]) { - dir = args[0] - g.parsePackageDir(args[0], tags) - } else { - if len(tags) != 0 { - log.Fatal("-tags option applies only to directories, not when files are specified") - } - dir = filepath.Dir(args[0]) - g.parsePackageFiles(args) - } + dir := "game" // get filename - baseName := fmt.Sprintf("gmlgo.go") + baseName := fmt.Sprintf("gmlgo_gen.go") outputName := filepath.Join(dir, strings.ToLower(baseName)) // check existing file - input, err := ioutil.ReadFile(outputName) - if err != nil { - log.Fatalf("reading file: %s", err) - } - if !strings.Contains(string(input), "// Code generated by \"gmlgo") { - log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) - return + var input []byte + if _, err := os.Stat(outputName); !os.IsNotExist(err) { + input, err = ioutil.ReadFile(outputName) + if err != nil { + log.Fatalf("reading file: %s", err) + } + if len(input) == 0 { + log.Printf("cannot generate %s as it's empty. rename or delete your %s file.", outputName, outputName) + return + } + if !strings.Contains(string(input), "// Code generated by \"gmlgo") { + log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) + return + } } // Run generate + g := Generator{} + g.parsePackageDir(dir, []string{}) g.generate() + // If no generated output, don't write anything + if g.buf.Len() == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } + // Format the output. src := g.format() + // If no generated output, don't write anything + if len(src) == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } + // Check if any changes if bytes.Equal(input, src) { log.Printf("no changes to %s\n", outputName) @@ -98,7 +97,7 @@ func main() { } // Write to file. - err = ioutil.WriteFile(outputName, src, 0644) + err := ioutil.WriteFile(outputName, src, 0644) if err != nil { log.Fatalf("error writing output: %s", err) } @@ -149,7 +148,7 @@ func buildContext(tags []string) *build.Context { func (g *Generator) parsePackageDir(directory string, tags []string) { pkg, err := buildContext(tags).ImportDir(directory, 0) if err != nil { - log.Fatalf("cannot process directory %s: %s", directory, err) + log.Fatalf("parsePackageDir: cannot parse %s: %s", directory, err) } var names []string names = append(names, pkg.GoFiles...) @@ -157,11 +156,6 @@ func (g *Generator) parsePackageDir(directory string, tags []string) { g.parsePackage(directory, names, nil) } -// parsePackageFiles parses the package occupying the named files. -func (g *Generator) parsePackageFiles(names []string) { - g.parsePackage(".", names, nil) -} - // prefixDirectory places the directory name on the beginning of each name in the list. func prefixDirectory(directory string, names []string) []string { if directory == "." { @@ -209,9 +203,10 @@ func (g *Generator) parsePackage(directory string, names []string, text interfac func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) { pkg.defs = make(map[*ast.Ident]types.Object) config := types.Config{ - IgnoreFuncBodies: true, // We only need to evaluate constants. - Importer: importer.Default(), // func defaultImporter() types.Importer - FakeImportC: true, + IgnoreFuncBodies: true, // We only need to evaluate constants. + Importer: importer.Default(), // func defaultImporter() types.Importer + FakeImportC: true, + DisableUnusedImportCheck: true, } info := &types.Info{ Defs: pkg.defs, @@ -227,57 +222,63 @@ type Struct struct { Name string } +type AssetKind struct { + Name string + Assets []string +} + // generate produces the code for object indexes func (g *Generator) generate() { var structsUsingGMLObject []Struct for _, file := range g.pkg.files { gmlPackageName := "" - if file.file != nil { - //fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) - ast.Inspect(file.file, func(n ast.Node) bool { - switch n := n.(type) { - // import "github.com/silbinarywolf/gml-go/gml" - case *ast.ImportSpec: - if n.Path.Value == "\"github.com/silbinarywolf/gml-go/gml\"" { - gmlPackageName = "gml" - if n.Name != nil { - // import gml "github.com/silbinarywolf/gml-go/gml" - gmlPackageName = n.Name.Name - } + if file.file == nil { + continue + } + //fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) + ast.Inspect(file.file, func(n ast.Node) bool { + switch n := n.(type) { + // import "github.com/silbinarywolf/gml-go/gml" + case *ast.ImportSpec: + if n.Path.Value == importString { + gmlPackageName = defaultImportName + if n.Name != nil { + // import gml "github.com/silbinarywolf/gml-go/gml" + gmlPackageName = n.Name.Name } - return false + } + return false + // type XXXX struct + case *ast.TypeSpec: + structName := n.Name.Name + switch n := n.Type.(type) { // type XXXX struct - case *ast.TypeSpec: - structName := n.Name.Name - switch n := n.Type.(type) { - // type XXXX struct - case *ast.StructType: - if n.Incomplete || - gmlPackageName == "" { - return false - } - for _, field := range n.Fields.List { - switch fieldType := field.Type.(type) { - case *ast.SelectorExpr: - switch kind := fieldType.X.(type) { - case *ast.Ident: - // Find embedded "gml.Object" - if kind.Name == gmlPackageName && - fieldType.Sel.Name == "Object" { - structsUsingGMLObject = append(structsUsingGMLObject, Struct{ - Name: structName, - }) - } + case *ast.StructType: + if n.Incomplete || + gmlPackageName == "" { + return false + } + for _, field := range n.Fields.List { + switch fieldType := field.Type.(type) { + case *ast.SelectorExpr: + switch kind := fieldType.X.(type) { + case *ast.Ident: + // Find embedded "gml.Object" + if kind.Name == gmlPackageName && + fieldType.Sel.Name == "Object" { + structsUsingGMLObject = append(structsUsingGMLObject, Struct{ + Name: structName, + }) } } } - return false } return false } - return true - }) - } + return false + } + return true + }) } if len(structsUsingGMLObject) == 0 { @@ -290,10 +291,12 @@ func (g *Generator) generate() { }) // Print the header and package clause. - g.Printf("// Code generated by \"gmlgo %s\";%s; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "), version) + g.Printf("// Code generated by \"gmlgo %s\";%s\n", strings.Join(os.Args[1:], " "), version) + g.Printf("// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY.\n") g.Printf("\n") g.Printf(`package ` + g.pkg.name + ` - +`) + g.Printf(` import ( "github.com/silbinarywolf/gml-go/gml" ) @@ -307,22 +310,122 @@ const ( `) - for _, record := range structsUsingGMLObject { - g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") - g.Printf("func (inst *" + record.Name + ") ObjectName() string { return \"" + record.Name + "\" }\n") - g.Printf("\n") + { + // Read asset names + files, err := ioutil.ReadDir("asset") + if err != nil { + log.Fatal(err) + } + var assetKinds []AssetKind + for _, f := range files { + switch name := f.Name(); name { + case "font": + case "sprite": + files, err := ioutil.ReadDir("asset/" + name) + if err != nil { + log.Fatal(err) + } + var assetNames []string + for _, f := range files { + if f.IsDir() { + //assetName := f.Name() + assetNames = append(assetNames, f.Name()) + //g.Printf(" Spr%s gml.SpriteIndex = %d\n", assetName, assetCount) + //assetCount++ + } + } + if len(assetNames) > 0 { + assetKinds = append(assetKinds, AssetKind{ + Name: name, + Assets: assetNames, + }) + } + default: + if !f.IsDir() { + // Ignore files + continue + } + log.Fatal(fmt.Errorf("Unexpected asset kind directory: %s", name)) + } + } + // Generate asset indexes + for _, assetKind := range assetKinds { + if len(assetKind.Assets) == 0 { + continue + } + var prefix, gotype string + switch assetKind.Name { + case "font": + prefix = "Fnt" + gotype = "todo" // todo: Implement gml.FontIndex + case "sprite": + prefix = "Spr" + gotype = "gml.SpriteIndex" + default: + panic("Unimplemented asset kind: " + assetKind.Name) + } + + { + g.Printf("const (\n") + for i, assetName := range assetKind.Assets { + // ie. SprPlayer gml.SpriteIndex = 1 + g.Printf(" %s%s %s = %d\n", prefix, assetName, gotype, i+1) + } + g.Printf("\n)\n\n") + } + { + g.Printf("var _gen_%s_index_to_name = []string{\n", prefix) + for _, assetName := range assetKind.Assets { + // ie. SprPlayer: "Player" + g.Printf(" %s%s: \"%s\",\n", prefix, assetName, assetName) + } + g.Printf("\n}\n\n") + } + { + g.Printf("var _gen_%s_name_to_index = map[string]gml.SpriteIndex{\n", prefix) + for _, assetName := range assetKind.Assets { + // ie. "Player": SprPlayer + g.Printf(" \"%s\": %s%s,\n", assetName, prefix, assetName) + } + g.Printf("\n}\n") + } + switch assetKind.Name { + case "font": + prefix = "Fnt" + gotype = "todo" // todo: Implement gml.FontIndex + case "sprite": + g.Printf(` +func init() { + gml.SpriteInitializeIndexToName(_gen_Spr_index_to_name, _gen_Spr_name_to_index) +} + +`) + default: + panic("Unimplemented asset kind: " + assetKind.Name) + } + } } - g.Printf(` + + { + // Write Object types + for _, record := range structsUsingGMLObject { + g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") + g.Printf("func (inst *" + record.Name + ") ObjectName() string { return \"" + record.Name + "\" }\n") + g.Printf("\n") + } + g.Printf("\n") + g.Printf(` func init() { gml.ObjectInitTypes([]gml.ObjectType{ `) - for _, record := range structsUsingGMLObject { - g.Printf(" Obj" + record.Name + ": new(" + record.Name + "),\n") - } - g.Printf(` }) + for _, record := range structsUsingGMLObject { + g.Printf(" Obj" + record.Name + ": new(" + record.Name + "),\n") + } + g.Printf(` }) } `) + } } // format returns the gofmt-ed contents of the Generator's buffer. diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore index 2020638..2b99e8d 100644 --- a/examples/spaceship/.gitignore +++ b/examples/spaceship/.gitignore @@ -1,4 +1,9 @@ +# Ignore compiled asset data asset/**/*.data +# Ignore code generated files +gmlgo_gen.go +**/gmlgo_gen.go +# Ignore binary for Windows *.exe -**/*_gen.go +# Ignore binary for Linux / Mac spaceship diff --git a/examples/spaceship/asset/gmlgo.go b/examples/spaceship/asset/gmlgo.go deleted file mode 100644 index 59ce7bf..0000000 --- a/examples/spaceship/asset/gmlgo.go +++ /dev/null @@ -1,21 +0,0 @@ -package asset - -import "github.com/silbinarywolf/gml-go/gml" - -// todo(Jake): 2018-11-24 -// Auto-generate this file - -const ( - SprSpaceship gml.SpriteIndex = 1 - SprBullet gml.SpriteIndex = 2 -) - -func init() { - gml.SpriteInitializeIndexToName([]string{ - SprSpaceship: "Spaceship", - SprBullet: "Bullet", - }, map[string]gml.SpriteIndex{ - "Spaceship": SprSpaceship, - "Bullet": SprBullet, - }) -} diff --git a/examples/spaceship/game/gmlgo.go b/examples/spaceship/game/gmlgo.go deleted file mode 100644 index aaca719..0000000 --- a/examples/spaceship/game/gmlgo.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated ;0.1.0; DO NOT EDIT. - -package game - -import ( - "github.com/silbinarywolf/gml-go/gml" -) - -const ( - ObjBullet gml.ObjectIndex = 1 - ObjPlayer gml.ObjectIndex = 2 -) - -func (inst *Bullet) ObjectIndex() gml.ObjectIndex { return ObjBullet } -func (inst *Bullet) ObjectName() string { return "Bullet" } - -func (inst *Player) ObjectIndex() gml.ObjectIndex { return ObjPlayer } -func (inst *Player) ObjectName() string { return "Player" } - -func init() { - gml.ObjectInitTypes([]gml.ObjectType{ - ObjBullet: new(Bullet), - ObjPlayer: new(Player), - }) -} diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index 3083388..ca921c1 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -1,7 +1,6 @@ package game import ( - "github.com/silbinarywolf/gml-go/examples/spaceship/asset" "github.com/silbinarywolf/gml-go/gml" ) @@ -10,7 +9,7 @@ type Bullet struct { } func (inst *Bullet) Create() { - inst.SetSprite(gml.SpriteLoad(asset.SprBullet)) + inst.SetSprite(gml.SpriteLoad(SprBullet)) } func (inst *Bullet) Destroy() { diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go index dfba572..cad6200 100644 --- a/examples/spaceship/game/obj_player.go +++ b/examples/spaceship/game/obj_player.go @@ -1,7 +1,6 @@ package game import ( - "github.com/silbinarywolf/gml-go/examples/spaceship/asset" "github.com/silbinarywolf/gml-go/gml" ) @@ -10,7 +9,7 @@ type Player struct { } func (inst *Player) Create() { - inst.SetSprite(asset.SprSpaceship) + inst.SetSprite(SprSpaceship) } func (inst *Player) Destroy() { From a6c9893647adb2b02981ace08f75f055a87722a0 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 1 Dec 2018 22:24:38 +1100 Subject: [PATCH 15/27] collision: add CollisionRectList to check collisions against other instances and change objectIndex to be stored on the base object struct --- cmd/gmlgo/gmlgo.go | 2 +- examples/spaceship/game/obj_bullet.go | 12 ++++++++- examples/spaceship/game/obj_enemy_ship.go | 25 ++++++++++++++++++ gml/collision.go | 32 +++++++++++++++++++++++ gml/instance.go | 5 ++++ gml/internal/object/object.go | 2 ++ gml/internal/object/object_manager.go | 1 + 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 examples/spaceship/game/obj_enemy_ship.go diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index b1eb215..5679ece 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -409,7 +409,7 @@ func init() { { // Write Object types for _, record := range structsUsingGMLObject { - g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") + //g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") g.Printf("func (inst *" + record.Name + ") ObjectName() string { return \"" + record.Name + "\" }\n") g.Printf("\n") } diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index ca921c1..7f7e191 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -9,7 +9,7 @@ type Bullet struct { } func (inst *Bullet) Create() { - inst.SetSprite(gml.SpriteLoad(SprBullet)) + inst.SetSprite(SprBullet) } func (inst *Bullet) Destroy() { @@ -18,6 +18,16 @@ func (inst *Bullet) Destroy() { func (inst *Bullet) Update() { inst.Y -= 8 + + for _, other := range gml.CollisionRectList(inst, inst.Pos()) { + other, ok := other.(*Player) + if !ok { + continue + } + inst.X += 8 + other.X += 1 + //gml.InstanceDestroy(other) + } } func (inst *Bullet) Draw() { diff --git a/examples/spaceship/game/obj_enemy_ship.go b/examples/spaceship/game/obj_enemy_ship.go new file mode 100644 index 0000000..589b86f --- /dev/null +++ b/examples/spaceship/game/obj_enemy_ship.go @@ -0,0 +1,25 @@ +package game + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +type EnemyShip struct { + gml.Object +} + +func (inst *EnemyShip) Create() { + inst.SetSprite(SprSpaceship) +} + +func (inst *EnemyShip) Destroy() { + +} + +func (inst *EnemyShip) Update() { + inst.Y -= 8 +} + +func (inst *EnemyShip) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) +} diff --git a/gml/collision.go b/gml/collision.go index 1114bdd..a91fe71 100644 --- a/gml/collision.go +++ b/gml/collision.go @@ -2,6 +2,7 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" ) const ( @@ -12,6 +13,37 @@ type collisionObject interface { BaseObject() *Object } +func CollisionRectList(instType collisionObject, position geom.Vec) []object.ObjectType { + inst := instType.BaseObject() + room := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if room == nil { + panic("RoomInstance this object belongs to has been destroyed") + } + + // Create collision rect at position provided in function + r1 := inst.Rect + r1.Vec = position + r1.Size = inst.Size + + // todo(Jake): 2018-12-01 - #18 + // Consider pooling reusable object.ObjectType slices to + // improve performance. + var list []object.ObjectType + for i := 0; i < len(room.instanceLayers); i++ { + for _, otherT := range room.instanceLayers[i].manager.instances { + other := otherT.BaseObject() + if r1.CollisionRectangle(other.Rect) && + inst != other { + list = append(list, otherT) + } + } + } + if len(list) == 0 { + return nil + } + return list +} + func PlaceFree(instType collisionObject, position geom.Vec) bool { inst := instType.BaseObject() room := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) diff --git a/gml/instance.go b/gml/instance.go index 3e28bdc..50284d0 100644 --- a/gml/instance.go +++ b/gml/instance.go @@ -23,6 +23,11 @@ func instanceCreateLayer(position geom.Vec, layer *RoomInstanceLayerInstance, ro return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) } +func InstanceGet(index object.ObjectIndex) object.ObjectType { + panic("todo: Implement InstanceGet()") + return nil +} + func InstanceChangeRoom(inst object.ObjectType, roomInstanceIndex int) { roomInst := &gState.roomInstances[roomInstanceIndex] if !roomInst.used { diff --git a/gml/internal/object/object.go b/gml/internal/object/object.go index 59f1804..1e2a9a0 100644 --- a/gml/internal/object/object.go +++ b/gml/internal/object/object.go @@ -23,6 +23,7 @@ type Object struct { sprite.SpriteState // Sprite (contains SetSprite) geom.Rect instanceObject + objectIndex ObjectIndex solid bool imageAngleRadians float64 // Image Angle } @@ -38,6 +39,7 @@ func (inst *Object) SetSolid(isSolid bool) { func (inst *Object) Solid() bool { return inst.solid } func (inst *Object) BaseObject() *Object { return inst } +func (inst *Object) ObjectIndex() ObjectIndex { return inst.objectIndex } func (inst *Object) ImageAngleRadians() float64 { return inst.imageAngleRadians } func (inst *Object) ImageAngle() float64 { return inst.imageAngleRadians * (180 / math.Pi) } diff --git a/gml/internal/object/object_manager.go b/gml/internal/object/object_manager.go index 5b7ccd9..7a4d0e0 100644 --- a/gml/internal/object/object_manager.go +++ b/gml/internal/object/object_manager.go @@ -66,6 +66,7 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, l inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) MoveInstance(inst, index, roomInstanceIndex, layerIndex) baseObj := inst.BaseObject() + baseObj.objectIndex = objectIndex baseObj.create() return inst /*// Create From 9f3aac4b18356eaa774603e42730c8b1e24ae928 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sat, 1 Dec 2018 23:09:57 +1100 Subject: [PATCH 16/27] room: Change room instance layer structs to be unexported and add some documentation to a few functions --- gml/animation_editor_debug.go | 2 +- gml/draw_nonheadless.go | 2 ++ gml/instance.go | 2 +- gml/internal/sprite/sprite_manager.go | 1 + gml/room.go | 4 +-- gml/room_editor_debug.go | 4 +-- gml/room_instance.go | 50 +++++++-------------------- gml/room_instance_layer.go | 8 ++--- gml/room_instance_layer_background.go | 8 ++--- gml/room_instance_layer_instance.go | 8 ++--- gml/room_instance_layer_sprite.go | 14 ++++---- gml/sprite_selector_debug.go | 2 +- gml/sprite_selector_nodebug.go | 2 +- gml/state.go | 21 ++++++----- 14 files changed, 55 insertions(+), 73 deletions(-) diff --git a/gml/animation_editor_debug.go b/gml/animation_editor_debug.go index c246518..5dfa5cf 100644 --- a/gml/animation_editor_debug.go +++ b/gml/animation_editor_debug.go @@ -349,7 +349,7 @@ func animationEditorUpdate() { if editor.menuOpened != animMenuNone { switch editor.menuOpened { case animMenuSprite: - if selectedSpr, ok := animationEditor.debugSpriteViewer.Update(); ok { + if selectedSpr, ok := animationEditor.debugSpriteViewer.update(); ok { editor.spriteViewing = SpriteState{} editor.spriteViewing.SetSprite(selectedSpr) editor.menuOpened = animMenuNone diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index c547f5c..f8b81b8 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -19,10 +19,12 @@ var ( isDrawGuiMode = false ) +// DrawGetGUI returns whether Draw functions will draw relative to the screen or not func DrawGetGUI() bool { return isDrawGuiMode } +// DrawSetGUI allows you to set whether you want to draw relative to the screen (true) or to the world (false) func DrawSetGUI(guiMode bool) { isDrawGuiMode = guiMode } diff --git a/gml/instance.go b/gml/instance.go index 50284d0..ad8b20f 100644 --- a/gml/instance.go +++ b/gml/instance.go @@ -19,7 +19,7 @@ func (manager *instanceManager) reset() { *manager = instanceManager{} } -func instanceCreateLayer(position geom.Vec, layer *RoomInstanceLayerInstance, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { +func instanceCreateLayer(position geom.Vec, layer *roomInstanceLayerInstance, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) } diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index 93fc717..ecd9231 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -28,6 +28,7 @@ func newSpriteManager() *spriteManager { return manager } +// SpriteInitializeIndexToName is used by code generated by gmlgo so you can query a sprite by index or name func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]SpriteIndex) { g_spriteManager.spriteIndexToName = indexToName g_spriteManager.spriteNameToIndex = nameToIndex diff --git a/gml/room.go b/gml/room.go index b94ef7f..023a274 100644 --- a/gml/room.go +++ b/gml/room.go @@ -4,10 +4,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/room" ) +// todo(Jake): 2018-12-01 +// Remove this, change LoadRoom functions to return gml.RoomIndex type Room = room.Room -type RoomObject = room.RoomObject - func LoadRoom(name string) *Room { return room.LoadRoom(name) } diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 38e268c..f04b94c 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -871,7 +871,7 @@ func editorUpdate() { //editingRoom.UserEntityCount++ // - roomObj := &RoomObject{ + roomObj := &room.RoomObject{ UUID: reditor.UUID(), ObjectIndex: int32(objectIndexSelected), X: int32(pos.X), @@ -1244,7 +1244,7 @@ func editorUpdate() { } case reditor.MenuSprite, reditor.MenuBackground: - if spriteSelected, ok := roomEditor.spriteViewer.Update(); ok { + if spriteSelected, ok := roomEditor.spriteViewer.update(); ok { switch roomEditor.menuOpened { case reditor.MenuSprite: roomEditor.spriteSelected = spriteSelected diff --git a/gml/room_instance.go b/gml/room_instance.go index 79a12e1..f21ff30 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -2,7 +2,6 @@ package gml import ( "github.com/silbinarywolf/gml-go/gml/internal/object" - "github.com/silbinarywolf/gml-go/gml/internal/room" ) type RoomInstance struct { @@ -10,48 +9,45 @@ type RoomInstance struct { index int room *Room - instanceLayers []RoomInstanceLayerInstance - spriteLayers []RoomInstanceLayerSprite - drawLayers []RoomInstanceLayerDraw + instanceLayers []roomInstanceLayerInstance + spriteLayers []roomInstanceLayerSprite + drawLayers []roomInstanceLayerDraw } -func RoomInstanceName(roomInstanceIndex int) string { +// todo(Jake): 2018-12-01: Remove this if it feels unnecessary or goes unused +// RoomInstanceName get the name of the room used by the room instance +/*func RoomInstanceName(roomInstanceIndex int) string { roomInst := &gState.roomInstances[roomInstanceIndex] if !roomInst.used { return "" } return roomInst.room.Config.UUID -} +}*/ +// RoomInstanceNew create a new empty room instance programmatically func RoomInstanceNew() int { roomInst := gState.createNewRoomInstance(nil) return roomInst.index } -func RoomInstanceCreate(room *Room) int { +// RoomInstanceCreate will create a new instance of the room given +// todo(Jake): 2018-12-01: #6: Change *Room to be gml.RoomIndex +func todo__roomInstanceCreate(room *Room) int { roomInst := gState.createNewRoomInstance(room) return roomInst.index } +// RoomInstanceDestroy destroys a room instance func RoomInstanceDestroy(roomInstanceIndex int) { roomInst := &gState.roomInstances[roomInstanceIndex] gState.deleteRoomInstance(roomInst) } -// todo(Jake): 2018-11-24: Github Issue #12 -func RoomInstanceEmptyCreate() *RoomInstance { - roomInst := gState.createNewRoomInstance(nil) - return roomInst -} - +// todo(Jake): 2018-12-01: Github #19: Remove this func (roomInst *RoomInstance) Index() int { return roomInst.index } -func (roomInst *RoomInstance) Room() *room.Room { - return roomInst.room -} - type roomInstanceObject interface { BaseObject() *object.Object } @@ -66,11 +62,6 @@ type roomInstanceObject interface { return instanceLayer.manager.instances }*/ -// NOTE(Jake):2018-08-19 -// -// I might want to make this private so a user -// can only manipulate a room instance via functions -// func roomGetInstance(roomInstanceIndex int) *RoomInstance { roomInst := &gState.roomInstances[roomInstanceIndex] if roomInst.used { @@ -79,21 +70,6 @@ func roomGetInstance(roomInstanceIndex int) *RoomInstance { return nil } -// todo(Jake): 2018-07-22 -// Figure out this -/*func (roomInst *RoomInstance) InstanceCreateLayer(position Vec, layer *RoomInstanceLayerInstance, objectIndex object.ObjectIndex) object.ObjectType { - -} - -func (roomInst *RoomInstance) InstanceCreate(position Vec, objectIndex object.ObjectIndex) object.ObjectType { - return roomInst.instanceManager.InstanceCreate(position, objectIndex, roomInst.Index()) -} - -func (roomInst *RoomInstance) InstanceDestroy(inst object.ObjectType) { - manager := &roomInst.instanceManager - manager.InstanceDestroy(inst) -}*/ - func (roomInst *RoomInstance) update(animationUpdate bool) { for _, layer := range roomInst.instanceLayers { layer.update(animationUpdate) diff --git a/gml/room_instance_layer.go b/gml/room_instance_layer.go index 5305995..e397a84 100644 --- a/gml/room_instance_layer.go +++ b/gml/room_instance_layer.go @@ -5,19 +5,19 @@ package gml // draw() //} -type RoomInstanceLayerDrawBase struct { +type roomInstanceLayerDrawBase struct { drawOrder int32 } -func (layer *RoomInstanceLayerDrawBase) order() int32 { +func (layer *roomInstanceLayerDrawBase) order() int32 { return layer.drawOrder } -type RoomInstanceLayerUpdate interface { +type roomInstanceLayerUpdate interface { update(animationUpdate bool) } -type RoomInstanceLayerDraw interface { +type roomInstanceLayerDraw interface { draw() order() int32 } diff --git a/gml/room_instance_layer_background.go b/gml/room_instance_layer_background.go index e782800..d71acc5 100644 --- a/gml/room_instance_layer_background.go +++ b/gml/room_instance_layer_background.go @@ -5,8 +5,8 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -type RoomInstanceLayerBackground struct { - RoomInstanceLayerDrawBase +type roomInstanceLayerBackground struct { + roomInstanceLayerDrawBase name string sprite sprite.SpriteIndex x, y float64 @@ -14,11 +14,11 @@ type RoomInstanceLayerBackground struct { roomRight float64 } -func (layer *RoomInstanceLayerBackground) order() int32 { +func (layer *roomInstanceLayerBackground) order() int32 { return layer.drawOrder } -func (layer *RoomInstanceLayerBackground) draw() { +func (layer *roomInstanceLayerBackground) draw() { sprite := layer.sprite width := float64(sprite.Size().X) x := layer.x diff --git a/gml/room_instance_layer_instance.go b/gml/room_instance_layer_instance.go index 08eca6c..551831e 100644 --- a/gml/room_instance_layer_instance.go +++ b/gml/room_instance_layer_instance.go @@ -1,7 +1,7 @@ package gml -type RoomInstanceLayerInstance struct { - RoomInstanceLayerDrawBase +type roomInstanceLayerInstance struct { + roomInstanceLayerDrawBase index int name string manager instanceManager @@ -12,10 +12,10 @@ type RoomInstanceLayerInstance struct { // return layer._parent //} -func (layer *RoomInstanceLayerInstance) update(animationUpdate bool) { +func (layer *roomInstanceLayerInstance) update(animationUpdate bool) { layer.manager.update(animationUpdate) } -func (layer *RoomInstanceLayerInstance) draw() { +func (layer *roomInstanceLayerInstance) draw() { layer.manager.draw() } diff --git a/gml/room_instance_layer_sprite.go b/gml/room_instance_layer_sprite.go index 60ee811..1cf5334 100644 --- a/gml/room_instance_layer_sprite.go +++ b/gml/room_instance_layer_sprite.go @@ -5,31 +5,31 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -type RoomInstanceLayerSpriteObject struct { +type roomInstanceLayerSpriteObject struct { geom.Vec sprite sprite.SpriteIndex } -func (record *RoomInstanceLayerSpriteObject) Rect() geom.Rect { +func (record *roomInstanceLayerSpriteObject) Rect() geom.Rect { r := geom.Rect{} r.Vec = record.Vec r.Size = record.sprite.Size() return r } -type RoomInstanceLayerSprite struct { - RoomInstanceLayerDrawBase +type roomInstanceLayerSprite struct { + roomInstanceLayerDrawBase name string - sprites []RoomInstanceLayerSpriteObject + sprites []roomInstanceLayerSpriteObject //spaces space.SpaceBucketArray hasCollision bool } -func (layer *RoomInstanceLayerSprite) order() int32 { +func (layer *roomInstanceLayerSprite) order() int32 { return layer.drawOrder } -func (layer *RoomInstanceLayerSprite) draw() { +func (layer *roomInstanceLayerSprite) draw() { //screen := gScreen for _, record := range layer.sprites { /*position := maybeApplyOffsetByCamera(record.Vec) diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go index cbee7ec..ff00c4f 100644 --- a/gml/sprite_selector_debug.go +++ b/gml/sprite_selector_debug.go @@ -47,7 +47,7 @@ func (viewer *debugSpriteViewer) lazyLoad() { } } -func (viewer *debugSpriteViewer) Update() (sprite.SpriteIndex, bool) { +func (viewer *debugSpriteViewer) update() (sprite.SpriteIndex, bool) { viewer.lazyLoad() typingText := KeyboardString() diff --git a/gml/sprite_selector_nodebug.go b/gml/sprite_selector_nodebug.go index d158428..489d220 100644 --- a/gml/sprite_selector_nodebug.go +++ b/gml/sprite_selector_nodebug.go @@ -5,5 +5,5 @@ package gml type debugSpriteViewer struct { } -func (viewer *debugSpriteViewer) Update() { +func (viewer *debugSpriteViewer) update() { } diff --git a/gml/state.go b/gml/state.go index a222658..fadc7ae 100644 --- a/gml/state.go +++ b/gml/state.go @@ -39,6 +39,8 @@ func newState() *state { } } +// FrameUsage returns a string like "1% (55ns)" to tell you how much +// of your frame budget has been utilized. (Assumes 60FPS) func FrameUsage() string { frameBudgetUsed := gState.frameBudgetNanosecondsUsed timeTaken := float64(frameBudgetUsed) / 16000000.0 @@ -47,7 +49,8 @@ func FrameUsage() string { return text + "% (" + strconv.Itoa(int(gState.frameBudgetNanosecondsUsed)) + "ns)" } -// Check if createNewRoomInstance() create is being executed +// IsCreatingRoomInstance returns whether this instance was created by a room or not, rather +// than programmatically. func IsCreatingRoomInstance() bool { return gState.isCreatingRoomInstance } @@ -70,8 +73,8 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { // Create default instance layer if... // - No instance layers exist in the room data // - Creating blank room - roomInst.instanceLayers = make([]RoomInstanceLayerInstance, 1) - roomInst.instanceLayers[0] = RoomInstanceLayerInstance{ + roomInst.instanceLayers = make([]roomInstanceLayerInstance, 1) + roomInst.instanceLayers[0] = roomInstanceLayerInstance{ index: 0, } roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.instanceLayers[0]) @@ -81,10 +84,10 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { if roomInst.room != nil { // Instance layers if len(room.InstanceLayers) > 0 { - roomInst.instanceLayers = make([]RoomInstanceLayerInstance, len(room.InstanceLayers)) + roomInst.instanceLayers = make([]roomInstanceLayerInstance, len(room.InstanceLayers)) for i := 0; i < len(room.InstanceLayers); i++ { layerData := room.InstanceLayers[i] - roomInst.instanceLayers[i] = RoomInstanceLayerInstance{ + roomInst.instanceLayers[i] = roomInstanceLayerInstance{ index: i, } layer := &roomInst.instanceLayers[i] @@ -102,7 +105,7 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { if spriteName == "" { continue } - layer := new(RoomInstanceLayerBackground) + layer := new(roomInstanceLayerBackground) layer.x = float64(layerData.X) layer.y = float64(layerData.Y) layer.roomLeft = float64(room.Left) @@ -115,13 +118,13 @@ func (state *state) createNewRoomInstance(room *Room) *RoomInstance { for i := 0; i < len(room.SpriteLayers); i++ { layerData := room.SpriteLayers[i] hasCollision := layerData.Config.HasCollision - layer := RoomInstanceLayerSprite{} + layer := roomInstanceLayerSprite{} layer.hasCollision = hasCollision - layer.sprites = make([]RoomInstanceLayerSpriteObject, 0, len(layerData.Sprites)) + layer.sprites = make([]roomInstanceLayerSpriteObject, 0, len(layerData.Sprites)) for _, sprObj := range layerData.Sprites { // Add draw sprite spr := sprite.SpriteLoadByName(sprObj.SpriteName) - record := RoomInstanceLayerSpriteObject{ + record := roomInstanceLayerSpriteObject{ sprite: spr, } record.X = float64(sprObj.X) From fcb52b30214fbee99b102bc09f307c6a9feca26d Mon Sep 17 00:00:00 2001 From: Jake B Date: Sun, 2 Dec 2018 00:55:13 +1100 Subject: [PATCH 17/27] room: remove Editor* functions, change opening room editor to be CTRL+R only if you're in debug mode and fix crash bugs with room editor --- cmd/gmlgo/gmlgo.go | 27 +++++++-- gml/debug_debug.go | 3 + gml/file.go | 9 +-- gml/internal/object/object_manager.go | 15 ++++- gml/main.go | 1 + gml/object.go | 4 +- gml/room_editor.go | 9 ++- gml/room_editor_debug.go | 84 ++++++++++++++------------- 8 files changed, 91 insertions(+), 61 deletions(-) diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index 5679ece..ccef45e 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -406,6 +406,26 @@ func init() { } } + { + // Write object index list + g.Printf("var _gen_Obj_index_list = []gml.ObjectIndex{\n") + for _, record := range structsUsingGMLObject { + // ie. ObjPlayer, + g.Printf(" Obj%s,\n", record.Name) + } + g.Printf("\n}\n\n") + } + + { + // Write object index to data list + g.Printf("var _gen_Obj_index_to_data = []gml.ObjectType{\n") + for _, record := range structsUsingGMLObject { + // ie. ObjPlayer: new(Player), + g.Printf(" Obj%s: new(%s),\n", record.Name, record.Name) + } + g.Printf("\n}\n\n") + } + { // Write Object types for _, record := range structsUsingGMLObject { @@ -417,12 +437,7 @@ func init() { g.Printf(` func init() { - gml.ObjectInitTypes([]gml.ObjectType{ -`) - for _, record := range structsUsingGMLObject { - g.Printf(" Obj" + record.Name + ": new(" + record.Name + "),\n") - } - g.Printf(` }) + gml.ObjectInitTypes(_gen_Obj_index_to_data, _gen_Obj_index_list) } `) } diff --git a/gml/debug_debug.go b/gml/debug_debug.go index 9e28ce0..e968a74 100644 --- a/gml/debug_debug.go +++ b/gml/debug_debug.go @@ -46,6 +46,9 @@ func debugUpdate() { if KeyboardCheckPressed(VkA) { debugMenuOpenOrToggleClosed(debugMenuAnimationEditor) } + if KeyboardCheckPressed(VkR) { + debugMenuOpenOrToggleClosed(debugMenuRoomEditor) + } } } diff --git a/gml/file.go b/gml/file.go index c8ab62b..1050c76 100644 --- a/gml/file.go +++ b/gml/file.go @@ -1,9 +1,6 @@ package gml import ( - "io/ioutil" - "strings" - "github.com/silbinarywolf/gml-go/gml/internal/file" ) @@ -15,7 +12,10 @@ func ProgramDirectory() string { return file.ProgramDirectory } -func ReadFileAsString(path string) (string, error) { +// todo(Jake): 2018-12-02: #21 +// Deprecated. Only used in private project, can be removed after we support "data" binary files +// FileStringReadAll will read a file from the "asset" directory, used to be ReadFileAsString +/*func FileStringReadAll(path string) (string, error) { path = AssetDirectory() + "/" + path fileData, err := file.OpenFile(path) if err != nil { @@ -28,3 +28,4 @@ func ReadFileAsString(path string) (string, error) { } return strings.TrimSpace(string(bytesData)), nil } +*/ diff --git a/gml/internal/object/object_manager.go b/gml/internal/object/object_manager.go index 7a4d0e0..f396a9a 100644 --- a/gml/internal/object/object_manager.go +++ b/gml/internal/object/object_manager.go @@ -10,8 +10,9 @@ var ( ) type objectManager struct { - idToEntityData []ObjectType - nameToID map[string]ObjectIndex + idToEntityData []ObjectType + objectIndexList []ObjectIndex + nameToID map[string]ObjectIndex } func newObjectManager() *objectManager { @@ -21,11 +22,12 @@ func newObjectManager() *objectManager { } } -func InitTypes(objTypes []ObjectType) { +func InitTypes(objTypes []ObjectType, objectIndexList []ObjectIndex) { manager := gObjectManager if manager.idToEntityData != nil { panic("Cannot call init type function more than once.") } + manager.objectIndexList = objectIndexList manager.idToEntityData = objTypes for _, objType := range objTypes { if objType == nil { @@ -42,6 +44,10 @@ func InitTypes(objTypes []ObjectType) { } } +func ObjectIndexList() []ObjectIndex { + return gObjectManager.objectIndexList +} + func IDToEntityData() []ObjectType { return gObjectManager.idToEntityData } @@ -63,6 +69,9 @@ func MoveInstance(inst ObjectType, index int, roomInstanceIndex int, layerIndex func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, layerIndex int) ObjectType { valToCopy := gObjectManager.idToEntityData[objectIndex] + if valToCopy == nil { + panic("Invalid objectIndex given") + } inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) MoveInstance(inst, index, roomInstanceIndex, layerIndex) baseObj := inst.BaseObject() diff --git a/gml/main.go b/gml/main.go index 8fc3bc6..3cb5b56 100644 --- a/gml/main.go +++ b/gml/main.go @@ -32,6 +32,7 @@ func update() error { cameraSetActive(0) cameraClear(0) + editorLazyInit() editorUpdate() cameraDraw(0) diff --git a/gml/object.go b/gml/object.go index 36e3eb4..7480f27 100644 --- a/gml/object.go +++ b/gml/object.go @@ -16,6 +16,6 @@ func ObjectGetIndex(name string) (object.ObjectIndex, bool) { } // ObjectInitTypes is required to be called so the engine can create game objects -func ObjectInitTypes(objTypes []object.ObjectType) { - object.InitTypes(objTypes) +func ObjectInitTypes(objectTypeToData []object.ObjectType, objectIndexList []object.ObjectIndex) { + object.InitTypes(objectTypeToData, objectIndexList) } diff --git a/gml/room_editor.go b/gml/room_editor.go index a6b36b4..22ed786 100644 --- a/gml/room_editor.go +++ b/gml/room_editor.go @@ -2,20 +2,19 @@ package gml -import "github.com/silbinarywolf/gml-go/gml/internal/room" - func roomEditorUsername() string { return "" } -func EditorInit(exitEditorFunc func(room *room.Room)) { -} - +/* func EditorIsInitialized() bool { return false } func EditorSetRoom(room *Room) { +}*/ + +func editorLazyInit() { } func editorUpdate() { diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index f04b94c..217c480 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -69,7 +69,7 @@ type roomEditor struct { cameraStateBeforeEnteringEditingMode cameraManager // Callbacks - exitEditorFunc func(room *room.Room) + //exitEditorFunc func(room *room.Room) // statusText string @@ -101,13 +101,9 @@ func newRoomEditor() *roomEditor { // - The entity size (as set in Create()) // - The default sprite of the object // - idToEntityData := object.IDToEntityData() - objectIndexToData := make([]object.ObjectType, len(idToEntityData)) - for i, obj := range idToEntityData { - if obj == nil { - continue - } - objectIndex := obj.ObjectIndex() + objectIndexList := object.ObjectIndexList() + objectIndexToData := make([]object.ObjectType, len(objectIndexList)) + for i, objectIndex := range objectIndexList { inst := object.NewRawInstance(objectIndex, i, 0, 0) inst.Create() objectIndexToData[i] = inst @@ -133,6 +129,9 @@ func (editor *roomEditor) IsMenuOpen() bool { } func (editor *roomEditor) calculateAndSortLayers() { + if editor.editingRoom == nil { + return + } editor.tempLayers = editor.tempLayers[:0] editor.layers() } @@ -189,19 +188,17 @@ func snapToGrid(val float64, grid float64) float64 { return base * grid } -func EditorInit(exitEditorFunc func(room *room.Room)) { - if gRoomEditor != nil { - panic("EditorInit: Room Editor is already initialized.") +func editorLazyInit() { + if gRoomEditor == nil { + gRoomEditor = newRoomEditor() } - gRoomEditor = newRoomEditor() - gRoomEditor.exitEditorFunc = exitEditorFunc // TODO(Jake): 2018-07-10 // // Load editor font (possibly by embedding data into `reditor`?) // } -func EditorIsInitialized() bool { +/*func EditorIsInitialized() bool { return gRoomEditor != nil } @@ -212,22 +209,21 @@ func EditorSetRoom(room *Room) { debugMenuOpenOrToggleClosed(debugMenuRoomEditor) } } - +*/ func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { if roomEditor.editingRoom == room { // If no changes return false } if room == nil { - editingRoom := roomEditor.editingRoom roomEditor.editingRoom = nil // Reset camera settings back *gCameraManager = roomEditor.cameraStateBeforeEnteringEditingMode debugMenuOpenOrToggleClosed(debugMenuNone) // Execute custom user-code logic - if gRoomEditor.exitEditorFunc != nil { - gRoomEditor.exitEditorFunc(editingRoom) - } + //if gRoomEditor.exitEditorFunc != nil { + // gRoomEditor.exitEditorFunc(editingRoom) + //} return false } roomEditor.editingRoom = room @@ -249,12 +245,8 @@ func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { func editorUpdate() { roomEditor := gRoomEditor - editingRoom := roomEditor.editingRoom - if editingRoom == nil { - return - } - isMenuOpen := roomEditor.IsMenuOpen() roomEditor.calculateAndSortLayers() // reset layers / recalculate sort order lazily with layers() + isMenuOpen := roomEditor.IsMenuOpen() canUseBrush := true grid := geom.Vec{32, 32} @@ -415,7 +407,7 @@ func editorUpdate() { roomEditor.loadRoom(typingText) case reditor.MenuNewLayer: // Create new room if typed - roomEditor.newLayerAndSelected(editingRoom, typingText, roomEditor.menuLayerKind) + roomEditor.newLayerAndSelected(roomEditor.editingRoom, typingText, roomEditor.menuLayerKind) case reditor.MenuSetOrder: // layer := roomEditor.editingLayer @@ -449,6 +441,11 @@ func editorUpdate() { } } + /*editingRoom := roomEditor.editingRoom + if editingRoom == nil { + return + }*/ + if !isMenuOpen && !isHoldingControl { camPos := &roomEditor.camPos @@ -510,18 +507,20 @@ func editorUpdate() { DrawSetGUI(false) // Draw black box using room bounds - borderWidth := 2.0 - pos := geom.Vec{float64(editingRoom.Left), float64(editingRoom.Top)} - pos.X -= borderWidth - pos.Y -= borderWidth - size := geom.Vec{float64(editingRoom.Right - editingRoom.Left), float64(editingRoom.Bottom - editingRoom.Top)} - size.X += borderWidth * 2 - size.Y += borderWidth * 2 - DrawRectangleBorder(pos, size, color.Black, 2, color.White) + if editingRoom := roomEditor.editingRoom; editingRoom != nil { + borderWidth := 2.0 + pos := geom.Vec{float64(editingRoom.Left), float64(editingRoom.Top)} + pos.X -= borderWidth + pos.Y -= borderWidth + size := geom.Vec{float64(editingRoom.Right - editingRoom.Left), float64(editingRoom.Bottom - editingRoom.Top)} + size.X += borderWidth * 2 + size.Y += borderWidth * 2 + DrawRectangleBorder(pos, size, color.Black, 2, color.White) + } } // Draw layers - { + if editingRoom := roomEditor.editingRoom; editingRoom != nil { for _, layer := range roomEditor.layers() { switch layer := layer.(type) { case *room.RoomLayerInstance: @@ -649,10 +648,9 @@ func editorUpdate() { } if roomEditor.menuOpened == reditor.MenuNone { - DrawSetGUI(true) - // Draw layer widget - { + if roomEditor.editingRoom != nil { + DrawSetGUI(true) yStart := 0.0 var x, y, width, height float64 x = 0 @@ -811,9 +809,8 @@ func editorUpdate() { } y += 32 } - + DrawSetGUI(false) } - DrawSetGUI(false) } if canUseBrush && @@ -1307,12 +1304,17 @@ func editorUpdate() { { DrawSetGUI(true) editingString := "" - editingString += "Editing: " + filepath.Base(roomEditor.editingRoom.Filepath()) + if editingRoom := roomEditor.editingRoom; editingRoom != nil { + editingString += "Editing: " + filepath.Base(roomEditor.editingRoom.Filepath()) + } { mousePos := MousePosition() mousePos.X = snapToGrid(mousePos.X, grid.X) mousePos.Y = snapToGrid(mousePos.Y, grid.Y) - editingString += " | " + strconv.FormatFloat(mousePos.X, 'f', -1, 64) + "," + strconv.FormatFloat(mousePos.Y, 'f', -1, 64) + "px" + if editingString != "" { + editingString += " | " + } + editingString += strconv.FormatFloat(mousePos.X, 'f', -1, 64) + "," + strconv.FormatFloat(mousePos.Y, 'f', -1, 64) + "px" } if text := roomEditor.statusText; text != "" { editingString += " | " + text From eae05d702d133824156e4733e40f99d01c7547b0 Mon Sep 17 00:00:00 2001 From: Jake B Date: Sun, 2 Dec 2018 21:59:12 +1100 Subject: [PATCH 18/27] example/spaceship: add alarm system, use it to spawn enemy spaceships, add bullets destroying enemy ships, fix bug with collision checking not respecting destroyed objects, add player scoring system and replace font with easier to read font. --- README.md | 3 ++- examples/spaceship/.gitignore | 1 + .../asset/font/Alte Haas Grotesk licence.rtf | 16 ++++++++++++ .../asset/font/AlteHaasGroteskRegular.ttf | Bin 0 -> 143896 bytes examples/spaceship/asset/font/tiny.ttf | Bin 6656 -> 0 bytes examples/spaceship/game/game.go | 9 ++++--- examples/spaceship/game/obj_bullet.go | 12 ++++++--- examples/spaceship/game/obj_enemy_ship.go | 3 ++- examples/spaceship/game/obj_player.go | 14 ++++++++++- gml/alarm.go | 23 ++++++++++++++++++ gml/collision.go | 3 ++- gml/font_manager.go | 2 ++ gml/font_manager_nonheadless.go | 2 ++ 13 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf create mode 100644 examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf delete mode 100644 examples/spaceship/asset/font/tiny.ttf create mode 100644 gml/alarm.go diff --git a/README.md b/README.md index 9c757ae..e91f870 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,5 @@ This project is mostly for fun and I have no intentions to get anything done unl ## Credits -* [Hajime Hoshi](https://github.com/hajimehoshi/ebiten) for his fantastically simple 2D game library, [https://github.com/hajimehoshi/ebiten](Ebiten). +* [Hajime Hoshi](https://github.com/hajimehoshi/ebiten) for their fantastically simple 2D game library, [https://github.com/hajimehoshi/ebiten](Ebiten). +* [Yann Le Coroller ](www.yannlecoroller.com) for their free to use Helvetica style font. \ No newline at end of file diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore index 2b99e8d..17b99c6 100644 --- a/examples/spaceship/.gitignore +++ b/examples/spaceship/.gitignore @@ -1,5 +1,6 @@ # Ignore compiled asset data asset/**/*.data +asset/font/*.rtf# # Ignore code generated files gmlgo_gen.go **/gmlgo_gen.go diff --git a/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf b/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf new file mode 100644 index 0000000..2b3359b --- /dev/null +++ b/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf @@ -0,0 +1,16 @@ +{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf270 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural + +\f0\fs24 \cf0 Alte Haas Grotesk is a typeface that look like an helvetica printed in an old Muller-Brockmann Book.\ +\ +These fonts are freeware and can be distributed as long as they are \ +together with this text file. \ +\ +I would appreciate very much to see what you have done with it anyway.\ +\ +yann le coroller \ +www.yannlecoroller.com\ +yann@lecoroller.com} \ No newline at end of file diff --git a/examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf b/examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..766c4d58146e2d7c5b0bd57b7c75f3997c0cf67b GIT binary patch literal 143896 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 diff --git a/examples/spaceship/game/game.go b/examples/spaceship/game/game.go index 8da4c50..7e1ba35 100644 --- a/examples/spaceship/game/game.go +++ b/examples/spaceship/game/game.go @@ -21,16 +21,19 @@ type GameWorld struct { func GameStart() { // todo(Jake): 2018-11-24 - #15 - // - Simplify this so that you can just pass "asset.FntTiny"? + // - Simplify this so that you can just pass "asset.AlteHaasGroteskRegular"? // - Change LoadFont to return FontIndex - gml.DrawSetFont(gml.LoadFont("tiny", gml.FontSettings{})) + gml.DrawSetFont(gml.LoadFont("AlteHaasGroteskRegular", gml.FontSettings{ + Size: 16, // 12pt == 16px + DPI: 96, + })) // Setup camera // todo(Jake): 2018-11-24 - #3 // Change CameraCreate to use geom.Size for w/h gml.CameraCreate(0, 0, 0, float64(gml.WindowWidth()), float64(gml.WindowHeight())) gameWorld.CurrentRoomIndex = gml.RoomInstanceNew() - gml.InstanceCreateRoom(gml.Vec{32, 32}, gameWorld.CurrentRoomIndex, ObjPlayer) + gml.InstanceCreateRoom(gml.Vec{float64(gml.WindowWidth()) / 2, float64(gml.WindowHeight()) / 2}, gameWorld.CurrentRoomIndex, ObjPlayer) } func GameUpdate() { diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go index 7f7e191..84348a5 100644 --- a/examples/spaceship/game/obj_bullet.go +++ b/examples/spaceship/game/obj_bullet.go @@ -6,6 +6,10 @@ import ( type Bullet struct { gml.Object + // todo(Jake): 2018-12-02 - #24 + // Swap this to gml.InstanceIndex when ready + // Maybe also add "vet" functionality - #25 + Owner gml.ObjectType } func (inst *Bullet) Create() { @@ -20,13 +24,13 @@ func (inst *Bullet) Update() { inst.Y -= 8 for _, other := range gml.CollisionRectList(inst, inst.Pos()) { - other, ok := other.(*Player) + other, ok := other.(*EnemyShip) if !ok { continue } - inst.X += 8 - other.X += 1 - //gml.InstanceDestroy(other) + owner := inst.Owner.(*Player) + owner.Score += 1 + gml.InstanceDestroy(other) } } diff --git a/examples/spaceship/game/obj_enemy_ship.go b/examples/spaceship/game/obj_enemy_ship.go index 589b86f..db39b2a 100644 --- a/examples/spaceship/game/obj_enemy_ship.go +++ b/examples/spaceship/game/obj_enemy_ship.go @@ -10,6 +10,7 @@ type EnemyShip struct { func (inst *EnemyShip) Create() { inst.SetSprite(SprSpaceship) + inst.ImageScale.Y = -1 } func (inst *EnemyShip) Destroy() { @@ -17,7 +18,7 @@ func (inst *EnemyShip) Destroy() { } func (inst *EnemyShip) Update() { - inst.Y -= 8 + inst.Y += 8 } func (inst *EnemyShip) Draw() { diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go index cad6200..2acfa13 100644 --- a/examples/spaceship/game/obj_player.go +++ b/examples/spaceship/game/obj_player.go @@ -1,11 +1,15 @@ package game import ( + "math/rand" + "github.com/silbinarywolf/gml-go/gml" ) type Player struct { gml.Object + enemyCreateAlarm gml.Alarm + Score int } func (inst *Player) Create() { @@ -16,6 +20,11 @@ func (inst *Player) Destroy() { } func (inst *Player) Update() { + if inst.enemyCreateAlarm.Update(60) { + // Spawn enemies at the top of the frame, every 60 frames + gml.InstanceCreateRoom(gml.Vec{float64(rand.Intn(gml.WindowWidth())), 0}, gameWorld.CurrentRoomIndex, ObjEnemyShip) + } + if gml.KeyboardCheck(gml.VkLeft) { inst.X -= 8 } @@ -29,10 +38,13 @@ func (inst *Player) Update() { inst.Y += 8 } if gml.KeyboardCheckPressed(gml.VkSpace) { - gml.InstanceCreateRoom(inst.Pos(), gameWorld.CurrentRoomIndex, ObjBullet) + bullet := gml.InstanceCreateRoom(inst.Pos(), gameWorld.CurrentRoomIndex, ObjBullet).(*Bullet) + bullet.Owner = inst } } func (inst *Player) Draw() { gml.DrawSelf(&inst.SpriteState, inst.Pos()) + + gml.DrawTextF(gml.Vec{0, 32}, "Score: %d", inst.Score) } diff --git a/gml/alarm.go b/gml/alarm.go new file mode 100644 index 0000000..4d2ba28 --- /dev/null +++ b/gml/alarm.go @@ -0,0 +1,23 @@ +package gml + +type Alarm struct { + timeSet int + timeLeft int +} + +// todo(Jake): 2018-12-02: #23 +// I'd like to test this alarm system against Game Maker and +// see if I can make it feel the same. +// (ie. you give the same values as Game Maker, you can get the same results) +func (alarm *Alarm) Update(frames int) bool { + if alarm.timeSet == 0 { + alarm.timeSet = frames + alarm.timeLeft = frames + } + alarm.timeLeft -= 1 + if alarm.timeLeft <= 0 { + alarm.timeSet = 0 + return true + } + return false +} diff --git a/gml/collision.go b/gml/collision.go index a91fe71..0df3189 100644 --- a/gml/collision.go +++ b/gml/collision.go @@ -32,7 +32,8 @@ func CollisionRectList(instType collisionObject, position geom.Vec) []object.Obj for i := 0; i < len(room.instanceLayers); i++ { for _, otherT := range room.instanceLayers[i].manager.instances { other := otherT.BaseObject() - if r1.CollisionRectangle(other.Rect) && + if !object.IsDestroyed(other) && + r1.CollisionRectangle(other.Rect) && inst != other { list = append(list, otherT) } diff --git a/gml/font_manager.go b/gml/font_manager.go index dcb91b0..e72f3bb 100644 --- a/gml/font_manager.go +++ b/gml/font_manager.go @@ -1,5 +1,7 @@ package gml +// todo(Jake): 2018-12-02 - #27 +// Consider simplifying this or removing it type FontSettings struct { DPI float64 Size float64 diff --git a/gml/font_manager_nonheadless.go b/gml/font_manager_nonheadless.go index 28575a6..f687d03 100644 --- a/gml/font_manager_nonheadless.go +++ b/gml/font_manager_nonheadless.go @@ -17,6 +17,8 @@ const ( fontDirectoryBase = "font" ) +// todo(Jake): 2018-12-02 - #26 +// Stop exposing FontManager struct type FontManager struct { currentFont *Font assetMap map[string]*Font From 8bee4a556f6e93ee62a95b060c80c468a43b7d71 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 3 Dec 2018 19:20:59 +1100 Subject: [PATCH 19/27] gmlgo: allow gmlgo to take in a parameter with the directory for travis ci and disable "go test" as there are no working tests currently Updates #30 --- .travis.yml | 11 +++-- cmd/gmlgo/gmlgo.go | 113 +++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39c73ba..284bd98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,12 +28,15 @@ before_script: - sleep 3 script: - - go build -v ./... - - go build -tags debug -v ./... - - go build -tags headless -v ./... + - go install -tags debug -v ./gml/... + - go install -tags headless -v ./gml/... + - go install -v ./gml + - go install -v ./cmd/gmlgo + - gmlgo ./examples/spaceship + - go build -v ./examples/... # NOTE(Jake): 2018-06-20 # # No tests exist yet # - - go test -v ./... + - #go test -v ./... - go vet -v -composites=false ./... diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index ccef45e..3468f4d 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -1,3 +1,5 @@ +// I test this by running the following from the root dir: +// - go install -v ./cmd/gmlgo && gmlgo ./examples/spaceship package main // import "github.com/silbinarywolf/gml-go/cmd/gmlgo" import ( @@ -47,61 +49,73 @@ func main() { flag.Usage = Usage flag.Parse() - dir := "game" - - // get filename - baseName := fmt.Sprintf("gmlgo_gen.go") - outputName := filepath.Join(dir, strings.ToLower(baseName)) + // We accept either one directory or a list of files. Which do we have? + args := flag.Args() + if len(args) == 0 { + // Default: process whole package in current directory. + args = []string{"."} + } - // check existing file - var input []byte - if _, err := os.Stat(outputName); !os.IsNotExist(err) { - input, err = ioutil.ReadFile(outputName) - if err != nil { - log.Fatalf("reading file: %s", err) - } - if len(input) == 0 { - log.Printf("cannot generate %s as it's empty. rename or delete your %s file.", outputName, outputName) - return - } - if !strings.Contains(string(input), "// Code generated by \"gmlgo") { - log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) - return + for _, dir := range args { + // todo(Jake): 2018-12-03 - #33 + // Replace "game" with scanning each sub-package, throw an error if multiple packages + // have multiple objects. Constraint for now will be all object types need to be in the same package + gameDir := filepath.Join(dir, "game") + + // get filename + baseName := fmt.Sprintf("gmlgo_gen.go") + outputName := filepath.Join(gameDir, strings.ToLower(baseName)) + + // check existing file + var input []byte + if _, err := os.Stat(outputName); !os.IsNotExist(err) { + input, err = ioutil.ReadFile(outputName) + if err != nil { + log.Fatalf("reading file: %s", err) + } + if len(input) == 0 { + log.Printf("cannot generate %s as it's empty. rename or delete your %s file.", outputName, outputName) + return + } + if !strings.Contains(string(input), "// Code generated by \"gmlgo") { + log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) + return + } } - } - // Run generate - g := Generator{} - g.parsePackageDir(dir, []string{}) - g.generate() + // Run generate + g := Generator{} + g.parsePackageDir(gameDir, []string{}) + g.generate(dir) - // If no generated output, don't write anything - if g.buf.Len() == 0 { - log.Printf("no gml.Object structs found, no output for %s\n", outputName) - return - } + // If no generated output, don't write anything + if g.buf.Len() == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } - // Format the output. - src := g.format() + // Format the output. + src := g.format() - // If no generated output, don't write anything - if len(src) == 0 { - log.Printf("no gml.Object structs found, no output for %s\n", outputName) - return - } + // If no generated output, don't write anything + if len(src) == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } - // Check if any changes - if bytes.Equal(input, src) { - log.Printf("no changes to %s\n", outputName) - return - } + // Check if any changes + if bytes.Equal(input, src) { + log.Printf("no changes to %s\n", outputName) + return + } - // Write to file. - err := ioutil.WriteFile(outputName, src, 0644) - if err != nil { - log.Fatalf("error writing output: %s", err) + // Write to file. + err := ioutil.WriteFile(outputName, src, 0644) + if err != nil { + log.Fatalf("error writing output: %s", err) + } + log.Printf("updated %s\n", outputName) } - log.Printf("updated %s\n", outputName) } // isDirectory reports whether the named file is a directory. @@ -228,7 +242,7 @@ type AssetKind struct { } // generate produces the code for object indexes -func (g *Generator) generate() { +func (g *Generator) generate(dir string) { var structsUsingGMLObject []Struct for _, file := range g.pkg.files { gmlPackageName := "" @@ -312,7 +326,8 @@ const ( { // Read asset names - files, err := ioutil.ReadDir("asset") + assetDir := filepath.Join(dir, "asset") + files, err := ioutil.ReadDir(assetDir) if err != nil { log.Fatal(err) } @@ -321,7 +336,7 @@ const ( switch name := f.Name(); name { case "font": case "sprite": - files, err := ioutil.ReadDir("asset/" + name) + files, err := ioutil.ReadDir(filepath.Join(assetDir, name)) if err != nil { log.Fatal(err) } From 06e4b755f4c3780fcfb2cdce9360f9d19fe19ec7 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 3 Dec 2018 19:34:47 +1100 Subject: [PATCH 20/27] gmlgo: remove old broken benchmarking tests, update minimum Go version to 1.11 Updates #30 --- .travis.yml | 4 +- README.md | 2 +- cmd/gmlgo/gmlgo.go | 3 +- gml/collision_test.go | 111 ---------------------- gml/{init_js_test.go => gopherjs_test.go} | 0 gml/init_test.go | 8 -- 6 files changed, 5 insertions(+), 123 deletions(-) delete mode 100644 gml/collision_test.go rename gml/{init_js_test.go => gopherjs_test.go} (100%) delete mode 100644 gml/init_test.go diff --git a/.travis.yml b/.travis.yml index 284bd98..17b5ed8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - "1.10" + - "1.11" addons: apt: @@ -38,5 +38,5 @@ script: # # No tests exist yet # - - #go test -v ./... + - go test -v ./... - go vet -v -composites=false ./... diff --git a/README.md b/README.md index e91f870..119eed5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ go get github.com/silbinarywolf/gml-go ## Requirements -* Golang 1.10+ +* Golang 1.11+ ## Documentation diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index 3468f4d..6003a55 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -305,7 +305,8 @@ func (g *Generator) generate(dir string) { }) // Print the header and package clause. - g.Printf("// Code generated by \"gmlgo %s\";%s\n", strings.Join(os.Args[1:], " "), version) + g.Printf("// Code generated by \"gmlgo\"\n") + g.Printf("// %s.\n", version) g.Printf("// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY.\n") g.Printf("\n") g.Printf(`package ` + g.pkg.name + ` diff --git a/gml/collision_test.go b/gml/collision_test.go deleted file mode 100644 index fa27faf..0000000 --- a/gml/collision_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package gml - -import ( - "testing" - - "github.com/silbinarywolf/gml-go/gml/internal/geom" -) - -// NOTE(Jake): 2018-07-08 -// -// Native: ("go test --bench=.") -// ------- -// BenchmarkPlaceFree250-4 3000000 424 ns/op -// BenchmarkPlaceFree500-4 2000000 877 ns/op -// BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities-4 100 23510338 ns/op -// -// JS: ("GOOS=linux gjbt --bench=."") -// --- -// BenchmarkPlaceFree250 500000 2326 ns/op -// BenchmarkPlaceFree500 300000 3910 ns/op -// BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities 10 103200000 ns/op -// - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 250 "wall" solid entities -// - 1 player entity -// - Player entity calling "PlaceFree" -// -func BenchmarkPlaceFree250(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 250; i++ { - InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer) - } - playerInstance := InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, geom.Vec{32, 32}) - } -} - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 500 "wall" solid entities -// - 1 player entity -// - Player entity calling "PlaceFree" -// -func BenchmarkPlaceFree500(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 500; i++ { - InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer) - } - playerInstance := InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, geom.Vec{32, 32}) - } -} - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 250 "wall" solid entities -// - 1024 moving/non-trivial entities -// - All 1024 moving entities calling "PlaceFree" 10 times. -// -func BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 250; i++ { - InstanceCreateRoom(geom.Vec{float64(i * 32.0), 0}, roomInstance, ObjDummyPlayer) - } - movingEntityInstances := make([]*DummyPlayer, 1024) - for i := 0; i < len(movingEntityInstances); i++ { - movingEntityInstances[i] = InstanceCreateRoom(geom.Vec{0, 0}, roomInstance, ObjDummyPlayer).(*DummyPlayer) - } - - b.ResetTimer() - for n := 0; n < b.N; n++ { - for _, movingEntityInstance := range movingEntityInstances { - // NOTE(Jake): 2018-07-07 - // - // Assume each entity would call PlaceFree() at least 10 times each. - // - for i := 0; i < 10; i++ { - PlaceFree(movingEntityInstance, geom.Vec{32, 32}) - } - } - } -} diff --git a/gml/init_js_test.go b/gml/gopherjs_test.go similarity index 100% rename from gml/init_js_test.go rename to gml/gopherjs_test.go diff --git a/gml/init_test.go b/gml/init_test.go deleted file mode 100644 index 1707165..0000000 --- a/gml/init_test.go +++ /dev/null @@ -1,8 +0,0 @@ -package gml - -func init() { - // Setup - ObjectInitTypes([]ObjectType{ - ObjDummyPlayer: new(DummyPlayer), - }) -} From aa87e0ed3f11ed17eb41c6c556539e5c4ec4e563 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 3 Dec 2018 20:39:16 +1100 Subject: [PATCH 21/27] gmlgo: add basic test for generating object meta data Updates #30 --- cmd/gmlgo/gmlgo.go | 33 ++++++++++++------ cmd/gmlgo/golden_test.go | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 cmd/gmlgo/golden_test.go diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go index 6003a55..855c76a 100644 --- a/cmd/gmlgo/gmlgo.go +++ b/cmd/gmlgo/gmlgo.go @@ -86,7 +86,8 @@ func main() { // Run generate g := Generator{} g.parsePackageDir(gameDir, []string{}) - g.generate(dir) + g.generate() + g.generateAssets(dir) // If no generated output, don't write anything if g.buf.Len() == 0 { @@ -242,7 +243,7 @@ type AssetKind struct { } // generate produces the code for object indexes -func (g *Generator) generate(dir string) { +func (g *Generator) generate() { var structsUsingGMLObject []Struct for _, file := range g.pkg.files { gmlPackageName := "" @@ -306,7 +307,7 @@ func (g *Generator) generate(dir string) { // Print the header and package clause. g.Printf("// Code generated by \"gmlgo\"\n") - g.Printf("// %s.\n", version) + g.Printf("// %s\n", version) g.Printf("// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY.\n") g.Printf("\n") g.Printf(`package ` + g.pkg.name + ` @@ -315,16 +316,12 @@ func (g *Generator) generate(dir string) { import ( "github.com/silbinarywolf/gml-go/gml" ) - -const ( -`) - for i, record := range structsUsingGMLObject { - g.Printf(" Obj" + record.Name + " gml.ObjectIndex = " + strconv.Itoa(i+1) + "\n") - } - g.Printf(`) - `) + g.generateObjectIndexes(structsUsingGMLObject) + g.generateObjectMetaAndMethods(structsUsingGMLObject) +} +func (g *Generator) generateAssets(dir string) { { // Read asset names assetDir := filepath.Join(dir, "asset") @@ -421,7 +418,21 @@ func init() { } } } +} + +func (g *Generator) generateObjectIndexes(structsUsingGMLObject []Struct) { + g.Printf(` +const ( +`) + for i, record := range structsUsingGMLObject { + g.Printf(" Obj" + record.Name + " gml.ObjectIndex = " + strconv.Itoa(i+1) + "\n") + } + g.Printf(`) + +`) +} +func (g *Generator) generateObjectMetaAndMethods(structsUsingGMLObject []Struct) { { // Write object index list g.Printf("var _gen_Obj_index_list = []gml.ObjectIndex{\n") diff --git a/cmd/gmlgo/golden_test.go b/cmd/gmlgo/golden_test.go new file mode 100644 index 0000000..850d610 --- /dev/null +++ b/cmd/gmlgo/golden_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "testing" +) + +// Golden represents a test case. +type Golden struct { + name string + input string // input; the package clause is provided when running the test. + output string // exected output. +} + +var golden = []Golden{ + {"simple", simple_in, simple_out}, +} + +// Each example starts with "type XXX [u]int", with a single space separating them. + +// Simple test: enumeration of type int starting at 0. +const simple_in = ` +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +type GameObjectA struct { + gml.Object +} +` + +const simple_out = `// Code generated by "gmlgo" +// 0.1.0 +// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY. + +package test + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +const ( + ObjGameObjectA gml.ObjectIndex = 1 +) + +var _gen_Obj_index_list = []gml.ObjectIndex{ + ObjGameObjectA, +} + +var _gen_Obj_index_to_data = []gml.ObjectType{ + ObjGameObjectA: new(GameObjectA), +} + +func (inst *GameObjectA) ObjectName() string { return "GameObjectA" } + +func init() { + gml.ObjectInitTypes(_gen_Obj_index_to_data, _gen_Obj_index_list) +} +` + +func TestGolden(t *testing.T) { + for _, test := range golden { + g := Generator{} + input := "package test\n" + test.input + file := test.name + ".go" + g.parsePackage(".", []string{file}, input) + g.generate() + got := string(g.format()) + if got != test.output { + t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output) + } + } +} From 31b3d33d79b81ae8d4ae09242c0e71d8a2a81868 Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 3 Dec 2018 21:42:52 +1100 Subject: [PATCH 22/27] gmlgo: remove "go vet" and focus tests on "cmd" folder only as no tests exist in "gml" yet Updates #30 --- .travis.yml | 6 ++---- gml/draw_nonheadless.go | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17b5ed8..1118c1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,7 @@ script: - go install -v ./cmd/gmlgo - gmlgo ./examples/spaceship - go build -v ./examples/... + - go test -v ./cmd/... # NOTE(Jake): 2018-06-20 - # # No tests exist yet - # - - go test -v ./... - - go vet -v -composites=false ./... + #- go test -v ./gml/... diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index f8b81b8..619aa3f 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -95,9 +95,8 @@ func DrawTextColor(position geom.Vec, message string, col color.Color) { text.Draw(drawGetTarget(), message, g_fontManager.currentFont.font, int(position.X), int(position.Y), col) } -func DrawTextF(position Vec, message string, args ...interface{}) { - message = fmt.Sprintf(message, args...) - DrawText(position, message) +func DrawTextF(position Vec, format string, args ...interface{}) { + DrawText(position, fmt.Sprintf(format, args...)) } func drawGetTarget() *ebiten.Image { From c80788fc64a6e86f151b4df961b4a89eaba82a5c Mon Sep 17 00:00:00 2001 From: Jake B Date: Mon, 3 Dec 2018 21:57:32 +1100 Subject: [PATCH 23/27] gmlgo: add missing dependencies to travis ci and maybe fix go install gml command Updates #30 --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1118c1c..f836d9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,14 @@ addons: - libxxf86vm-dev install: + # Underlying framework used - go get -t -v github.com/hajimehoshi/ebiten/... + # Used by some structures - go get -t -v github.com/golang/protobuf/... + # Debug Mode: Live file reloading (watch files for changes) + - go get -t -v github.com/fsnotify/fsnotify/... + # Debug Mode: Used for generating UUID's for room objects, etc. + - go get -t -v github.com/rs/xid/... before_script: - export DISPLAY=:99.0 @@ -30,7 +36,7 @@ before_script: script: - go install -tags debug -v ./gml/... - go install -tags headless -v ./gml/... - - go install -v ./gml + - go install -v ./gml/... - go install -v ./cmd/gmlgo - gmlgo ./examples/spaceship - go build -v ./examples/... From 0035523dfcd88385633fe8aa24943dbaa475887e Mon Sep 17 00:00:00 2001 From: Jake B Date: Tue, 4 Dec 2018 20:16:23 +1100 Subject: [PATCH 24/27] gmlgo: add testing debug and headless builds of game examples --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f836d9d..27e01c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,8 @@ script: - go install -v ./gml/... - go install -v ./cmd/gmlgo - gmlgo ./examples/spaceship + - go build -tags debug -v ./examples/... + - go build -tags headless -v ./examples/... - go build -v ./examples/... - go test -v ./cmd/... # NOTE(Jake): 2018-06-20 From e4e6eff9e34ac2c9fa6664282eb4065f5aca37fb Mon Sep 17 00:00:00 2001 From: Jake B Date: Tue, 4 Dec 2018 20:35:19 +1100 Subject: [PATCH 25/27] gmlgo: fix .gitignore and commit missing asset --- examples/spaceship/.gitignore | 2 +- examples/spaceship/asset/sprite/Spaceship/0.png | Bin 0 -> 808 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 examples/spaceship/asset/sprite/Spaceship/0.png diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore index 17b99c6..05516f4 100644 --- a/examples/spaceship/.gitignore +++ b/examples/spaceship/.gitignore @@ -7,4 +7,4 @@ gmlgo_gen.go # Ignore binary for Windows *.exe # Ignore binary for Linux / Mac -spaceship +./spaceship diff --git a/examples/spaceship/asset/sprite/Spaceship/0.png b/examples/spaceship/asset/sprite/Spaceship/0.png new file mode 100644 index 0000000000000000000000000000000000000000..58cc8f62509f27a0df911eb9167e250891b5234c GIT binary patch literal 808 zcmV+@1K0eCP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0=-E@K~#8N?VUkZ z!!Qg5Q?9_4CHFrOOSYT=5Dx=w(~>y0tS`^J$74kyOhZ#mJrCgAaY&E5&~I5AnOwwdp7=} zgRB*z?yc!%sZ?c9mJn0Riu?k2jp@s}H3B2_6%`4_pb+V0Sy36PGNvdigD4}ltSm(p*jYlPm6fe3 zBc-e!qU`bPaYw=!6e5)@d*){xe}y!%ilnTfj1;m)?-DQMWzA4jK~~m?{bavuWW|!Q zW~%8gaY9+U5VcM?S%|t#C|QWQPv`?iRO|_5Pb_c3$fBzM87|8tA!-{#Wfe(?+6FtTk`a4r zm@G!$eke%%v>$R2#a= z%3hQ=955Q;F$%lMqN?^Zmt~R=)rY2^0FI<(d3=3YdVQ!mJ4*;;HKyl5qnRv5V1Fp9 z1HI~MB8#egI4lcM>-4CLF(||#SyXkO)^$;q5Y@7%${)%SqDmH3jbXPeMD3&HA%ig} z#4cG>HI~h?5LHjBQOFn+Vv{VYiUDN_@vkhZiUVZ{aaR^qwWGy}kg|k8mLo^&x+#kh z*oPy+C`$-r39%}R5f~H765>i0RmFv}ggBE$Rk5M00o1>k*{mF< Date: Tue, 4 Dec 2018 20:41:40 +1100 Subject: [PATCH 26/27] ci: add cache --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 27e01c6..3cb56b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,12 @@ language: go go: - "1.11" +# https://restic.net/blog/2018-09-02/travis-build-cache +cache: + directories: + - $HOME/.cache/go-build + - $HOME/gopath/pkg/mod + addons: apt: sources: From 98076026cf024f4e3eff9b0d9031c4a981eae718 Mon Sep 17 00:00:00 2001 From: Jake B Date: Tue, 4 Dec 2018 21:20:14 +1100 Subject: [PATCH 27/27] doc: update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 119eed5..a3ae91f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Game Maker Language Go [![Build Status](https://travis-ci.org/silbinarywolf/gml-go.svg?branch=master)](https://travis-ci.org/silbinarywolf/gml-go) +[![Documentation](https://godoc.org/github.com/silbinarywolf/gml-go?status.svg)](https://github.com/silbinarywolf/gml-go) +[![Report Card](https://goreportcard.com/badge/github.com/silbinarywolf/gml-go)](https://godoc.org/github.com/silbinarywolf/gml-go) -**NOTE: This is currently a hobby project and not meant for any use other than my own. If you are interested in this or use this, please let me know so I can improve documentation, tagging, etc** +**NOTE: This project is currently undergoing a large refactoring effort to help ease workflow and serialization. I'm also aiming to improve the documentation, add examples and improve test coverage. This is still just a hobby project for now!** This is a library / framework that aims to create workflow like Game Maker, but utilizing the Go programming language. @@ -18,7 +20,7 @@ go get github.com/silbinarywolf/gml-go ## Documentation -* TODO when the library has progressed +* TODO when this library has been refactored * [License](LICENSE.md) # Rough Roadmap @@ -33,9 +35,8 @@ This project is mostly for fun and I have no intentions to get anything done unl - This should cover installing "gofmt" / "goimports" - Transitioning from GML to Golang, major / minor differences * Add build tools to help with: + - [x] Auto generating entity IDs and the like. - Packing assets into texture atlases - - Converting Tiled maps into an internal engine format - - (maybe) Auto generating entity IDs and the like. ## Credits