Initial commit

This commit is contained in:
Layla 2022-06-05 12:54:30 -04:00
commit cb1bdec005
21 changed files with 702 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2022 Manley
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.

9
ReadMe.md Normal file
View File

@ -0,0 +1,9 @@
# Golang Game Framework
Framework in Golang to simplify game development with SDL in Go.
Currently, only basic rendering has been implemented.
For an example on how to use it, see `game/game.go` and make your own changes!
I have a strong desire to make this into a more flexible and feature complete system, but I cannot give any guarantees. Hopefully at minimum, this project can be seen as a reference for others!

14
core/rect2d.go Normal file
View File

@ -0,0 +1,14 @@
package core
import "fmt"
type Rect2D struct {
X float64
Y float64
W float64
H float64
}
func (rect Rect2D) String() string {
return fmt.Sprintf("X: %v, Y: %v, W: %v, H: %v", rect.X, rect.Y, rect.W, rect.H)
}

19
core/vector2.go Normal file
View File

@ -0,0 +1,19 @@
package core
import "fmt"
type Vector2 struct {
X float64
Y float64
}
func (p Vector2) String() string {
return fmt.Sprintf("X: %v, Y: %v", p.X, p.Y)
}
func (vector Vector2) Add(other Vector2) Vector2 {
return Vector2{
X: vector.X + other.X,
Y: vector.Y + other.Y,
}
}

36
game/game.go Normal file
View File

@ -0,0 +1,36 @@
package game
import (
"log"
"github.com/manleydev/golang-game-framework/node"
"github.com/manleydev/golang-game-framework/rendering"
"github.com/manleydev/golang-game-framework/sdl"
)
func Run(root *node.Node, renderer rendering.Renderer2D) {
texture := sdl.NewSdlTexture2D(renderer, "gopher.bmp")
g := node.NewSprite2D(texture)
defer root.AddChild(g)
g.OnReady(func() error {
log.Println("Hello world!")
return nil
})
g.OnUpdate(func() error {
g.Rotation += 0.01
return nil
})
g.Name = "Gopher"
camera := node.NewCamera2D(&renderer, rendering.NewStandardCamera())
defer root.AddChild(camera)
camera.OnReady(func() error {
return camera.Enable()
})
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/manleydev/golang-game-framework
go 1.16
require github.com/veandco/go-sdl2 v0.4.21 // indirect

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/veandco/go-sdl2 v0.4.21 h1:85AtFYv3vSPZvyOOxkJ03sHoJe8ESjPSZLr0KueQtVY=
github.com/veandco/go-sdl2 v0.4.21/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=

BIN
gopher.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

42
main.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"log"
"github.com/manleydev/golang-game-framework/game"
"github.com/manleydev/golang-game-framework/node"
"github.com/manleydev/golang-game-framework/rendering"
"github.com/manleydev/golang-game-framework/sdl"
)
func main() {
var renderer rendering.Renderer2D = sdl.NewSdlRenderer2D(sdl.SdlRenderer2DInput{
WindowTitle: "Sample Game",
WindowWidth: 1280,
WindowHeight: 720,
})
defer renderer.Destroy()
root := node.NewNode()
root.Name = "Root"
game.Run(&root, renderer)
running := true
for running {
if err := root.Update(); err != nil {
log.Fatal("Update: ", err)
}
if err := renderer.Update(); err != nil {
log.Fatal("Renderer Update: ", err)
}
if err := root.Draw(renderer); err != nil {
log.Fatal("Draw: ", err)
}
if err := renderer.Draw(); err != nil {
log.Fatal("Renderer Draw: ", err)
}
}
}

48
node/camera2d.go Normal file
View File

@ -0,0 +1,48 @@
package node
import (
"errors"
"github.com/manleydev/golang-game-framework/rendering"
)
type Camera2D struct {
Node2D
Zoom float64
camera *rendering.Camera2D
renderer *rendering.Renderer2D
}
func (camera *Camera2D) Update() error {
if err := camera.Node2D.Update(); err != nil {
return err
}
camRef := *(camera.camera)
camRef.SetOffset(camera.GetGlobalPosition())
camRef.SetZoom(camera.Zoom)
return nil
}
func (camera *Camera2D) Enable() error {
if camera.camera == nil {
return errors.New("camera is nil")
}
return (*camera.renderer).SetCamera(camera.camera)
}
func (node *Camera2D) AddChild(child INode) {
node.internalAddChild(node, child)
}
func NewCamera2D(renderer *rendering.Renderer2D, cam *rendering.Camera2D) *Camera2D {
camera := Camera2D{
Node2D: NewNode2D(),
camera: cam,
renderer: renderer,
Zoom: 1.0,
}
return &camera
}

7
node/inode.go Normal file
View File

@ -0,0 +1,7 @@
package node
type INode interface {
GetName() string
Update() error
ready(INode) error
}

120
node/node.go Normal file
View File

@ -0,0 +1,120 @@
package node
import (
"log"
"github.com/manleydev/golang-game-framework/rendering"
)
type Node struct {
Name string
children []INode
parent INode
onReadyMethods []func() error
onUpdateMethods []func() error
onDraw2dMethods []func(rendering.Renderer2D) error
}
func (node *Node) ready(parent INode) error {
node.parent = parent
for _, readyMethod := range node.onReadyMethods {
if err := readyMethod(); err != nil {
return err
}
}
return nil
}
func (node *Node) OnReady(callback func() error) {
if callback == nil {
return
}
node.onReadyMethods = append(node.onReadyMethods, callback)
}
func (node *Node) Update() error {
for _, child := range node.children {
if err := child.Update(); err != nil {
return err
}
}
for _, updateMethod := range node.onUpdateMethods {
if err := updateMethod(); err != nil {
return err
}
}
return nil
}
func (node *Node) OnUpdate(callback func() error) {
if callback == nil {
return
}
node.onUpdateMethods = append(node.onUpdateMethods, callback)
}
func (node *Node) Draw(renderer rendering.Renderer2D) error {
for _, child := range node.children {
drawable, ok := child.(interface {
Draw(rendering.Renderer2D) error
})
if !ok {
continue
}
if err := drawable.Draw(renderer); err != nil {
return err
}
}
for _, drawMethod := range node.onDraw2dMethods {
if err := drawMethod(renderer); err != nil {
return err
}
}
return nil
}
func (node *Node) OnDraw2D(callback func(rendering.Renderer2D) error) {
if callback == nil {
return
}
node.onDraw2dMethods = append(node.onDraw2dMethods, callback)
}
func (node *Node) GetName() string {
return node.Name
}
func (node *Node) GetChild(index int) INode {
if index < len(node.children) {
return nil
}
return node.children[index]
}
func (node *Node) internalAddChild(parent INode, child INode) {
node.children = append(node.children, child)
if err := child.ready(parent); err != nil {
log.Fatalf("Node(%s) AddChild: %v", node.Name, err)
}
}
func (node *Node) AddChild(child INode) {
node.internalAddChild(node, child)
}
func (node *Node) GetParent() INode {
return node.parent
}
func NewNode() Node {
return Node{
children: []INode{},
}
}

38
node/node2d.go Normal file
View File

@ -0,0 +1,38 @@
package node
import (
"github.com/manleydev/golang-game-framework/core"
)
type Node2D struct {
Node
Position core.Vector2
Rotation float64
}
func (node *Node2D) GetPosition() core.Vector2 {
return node.Position
}
func (node *Node2D) GetGlobalPosition() core.Vector2 {
if node.parent == nil {
return node.Position
}
global2d, ok := (node.parent).(interface {
GetGlobalPosition() core.Vector2
})
if ok {
return node.Position.Add(global2d.GetGlobalPosition())
}
return node.Position
}
func (node *Node2D) AddChild(child INode) {
node.internalAddChild(node, child)
}
func NewNode2D() Node2D {
return Node2D{
Node: NewNode(),
}
}

46
node/sprite2d.go Normal file
View File

@ -0,0 +1,46 @@
package node
import (
"github.com/manleydev/golang-game-framework/rendering"
)
type Sprite2D struct {
Node2D
texture rendering.Texture2D
visible bool
}
func (sprite *Sprite2D) Draw(renderer rendering.Renderer2D) error {
if !sprite.visible {
return nil
}
if err := sprite.Node.Draw(renderer); err != nil {
return err
}
return renderer.DrawTexture2D(rendering.DrawTexture2DInput{
Texture: sprite.texture,
Rect: sprite.texture.GetRect(),
Position: sprite.GetGlobalPosition(),
Rotation: sprite.Rotation,
})
}
func (sprite *Sprite2D) SetVisible(v bool) {
sprite.visible = v
}
func (node *Sprite2D) AddChild(child INode) {
node.internalAddChild(node, child)
}
func NewSprite2D(texture rendering.Texture2D) *Sprite2D {
sprite := Sprite2D{
Node2D: NewNode2D(),
visible: true,
}
sprite.texture = texture
return &sprite
}

18
rendering/camera2d.go Normal file
View File

@ -0,0 +1,18 @@
package rendering
import "github.com/manleydev/golang-game-framework/core"
type Camera2D interface {
GetZoom() float64
SetZoom(float64)
GetOffset() core.Vector2
SetOffset(core.Vector2)
GetModifiers() Camera2DModifiers
}
type Camera2DModifiers struct {
Zoom float64
Offset core.Vector2
}

21
rendering/render2d.go Normal file
View File

@ -0,0 +1,21 @@
package rendering
import (
"github.com/manleydev/golang-game-framework/core"
)
type Renderer2D interface {
DrawTexture2D(DrawTexture2DInput) error
Draw() error
Update() error
Destroy()
SetCamera(*Camera2D) error
}
type DrawTexture2DInput struct {
Texture Texture2D
Rect core.Rect2D
Position core.Vector2
Rotation float64
}

View File

@ -0,0 +1,37 @@
package rendering
import "github.com/manleydev/golang-game-framework/core"
type StandardCamera2D struct {
zoom float64
offset core.Vector2
}
func (camera *StandardCamera2D) GetZoom() float64 {
return camera.zoom
}
func (camera *StandardCamera2D) SetZoom(zoom float64) {
camera.zoom = zoom
}
func (camera *StandardCamera2D) GetOffset() core.Vector2 {
return camera.offset
}
func (camera *StandardCamera2D) SetOffset(offset core.Vector2) {
camera.offset = offset
}
func (camera *StandardCamera2D) GetModifiers() Camera2DModifiers {
return Camera2DModifiers{
Zoom: camera.zoom,
Offset: camera.offset,
}
}
func NewStandardCamera() *Camera2D {
var camera Camera2D = &StandardCamera2D{
zoom: 1,
offset: core.Vector2{},
}
return &camera
}

9
rendering/texture2d.go Normal file
View File

@ -0,0 +1,9 @@
package rendering
import "github.com/manleydev/golang-game-framework/core"
type Texture2D interface {
Destroy()
GetCenter() core.Vector2
GetRect() core.Rect2D
}

133
sdl/sdl_render2d.go Normal file
View File

@ -0,0 +1,133 @@
package sdl
import (
"errors"
"log"
"github.com/manleydev/golang-game-framework/core"
"github.com/manleydev/golang-game-framework/rendering"
"github.com/veandco/go-sdl2/sdl"
)
type SdlRenderer2D struct {
window *sdl.Window
renderer *sdl.Renderer
currentCamera *rendering.Camera2D
}
type SdlRenderer2DInput struct {
WindowTitle string
WindowWidth int32
WindowHeight int32
Fullscreen bool
}
func NewSdlRenderer2D(input SdlRenderer2DInput) *SdlRenderer2D {
w := &SdlRenderer2D{}
var err error
w.window, err = sdl.CreateWindow(input.WindowTitle,
sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
input.WindowWidth, input.WindowHeight,
sdl.WINDOW_VULKAN)
if err != nil {
log.Print("Failed to create SDL window: ", err)
return nil
}
if input.Fullscreen {
if err := w.window.SetFullscreen(sdl.WINDOW_FULLSCREEN_DESKTOP); err != nil {
log.Print("Failed to set fullscreen: ", err)
return nil
}
}
w.window.SetResizable(true)
w.renderer, err = sdl.CreateRenderer(w.window, -1, sdl.RENDERER_ACCELERATED)
if err != nil {
log.Print("Failed to create SDL renderer: ", err)
return nil
}
return w
}
func (w SdlRenderer2D) Destroy() {
if err := w.window.Destroy(); err != nil {
log.Print("Failed to destroy SDL window: ", err)
}
if err := w.renderer.Destroy(); err != nil {
log.Print("Failed to destroy SDL renderer: ", err)
}
}
func (w *SdlRenderer2D) SetCamera(camera *rendering.Camera2D) error {
w.currentCamera = camera
return nil
}
func (w SdlRenderer2D) Update() error {
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch event.(type) {
case *sdl.QuitEvent:
return errors.New("Quit system not yet implemented!")
}
}
return nil
}
func (w SdlRenderer2D) DrawTexture2D(input rendering.DrawTexture2DInput) error {
tex, ok := input.Texture.(*SdlTexture2D)
if !ok {
return errors.New("Texture is not an SdlTexture2D!")
}
center := tex.GetCenter()
modifiers := rendering.Camera2DModifiers{
Zoom: 1.0,
Offset: core.Vector2{X: 0, Y: 0},
}
if w.currentCamera != nil {
modifiers = (*w.currentCamera).GetModifiers()
}
if err := w.renderer.CopyEx(tex.texture,
&sdl.Rect{
X: int32(input.Rect.X),
Y: int32(input.Rect.Y),
W: int32(input.Rect.W),
H: int32(input.Rect.H),
},
&sdl.Rect{
X: int32((input.Position.X - modifiers.Offset.X) * modifiers.Zoom),
Y: int32((input.Position.Y - modifiers.Offset.Y) * modifiers.Zoom),
W: int32(input.Rect.W * modifiers.Zoom),
H: int32(input.Rect.H * modifiers.Zoom),
}, input.Rotation,
&sdl.Point{
X: int32(center.X * modifiers.Zoom),
Y: int32(center.Y * modifiers.Zoom),
},
sdl.FLIP_NONE); err != nil {
return err
}
return nil
}
func (w SdlRenderer2D) Draw() error {
w.renderer.Present()
if err := w.renderer.SetDrawColor(0, 0, 0, 255); err != nil {
return err
}
if err := w.renderer.Clear(); err != nil {
return err
}
return nil
}

70
sdl/sdl_texture2d.go Normal file
View File

@ -0,0 +1,70 @@
package sdl
import (
"log"
"github.com/manleydev/golang-game-framework/core"
"github.com/manleydev/golang-game-framework/rendering"
"github.com/veandco/go-sdl2/sdl"
)
type SdlTexture2D struct {
texture *sdl.Texture
}
func NewSdlTexture2D(renderer rendering.Renderer2D, bmpPath string) *SdlTexture2D {
sdlRenderer, ok := renderer.(*SdlRenderer2D)
if !ok {
log.Print("Renderer is not an SDL renderer")
return nil
}
tex := SdlTexture2D{}
surface, err := sdl.LoadBMP(bmpPath)
if err != nil {
log.Print("Failed to load bmp: ", err)
return nil
}
defer surface.Free()
tex.texture, err = sdlRenderer.renderer.CreateTextureFromSurface(surface)
if err != nil {
log.Print("Failed to create SDL texture: ", err)
return nil
}
return &tex
}
func (tex SdlTexture2D) GetCenter() core.Vector2 {
_, _, width, height, err := tex.texture.Query()
if err != nil {
log.Print("Could not get texture center: ", err)
return core.Vector2{}
}
return core.Vector2{
X: float64(width / 2),
Y: float64(height / 2),
}
}
func (tex SdlTexture2D) GetRect() core.Rect2D {
_, _, width, height, err := tex.texture.Query()
if err != nil {
log.Print("Could not get texture data: ", err)
return core.Rect2D{}
}
return core.Rect2D{
X: 0,
Y: 0,
W: float64(width),
H: float64(height),
}
}
func (tex SdlTexture2D) Destroy() {
if err := tex.texture.Destroy(); err != nil {
log.Print("Failed to destroy SDL texture: ", err)
}
}