This repository is demo how to use ebite framework to write rpg in golang
package main
import (
"fmt"
"image"
"image/color"
"log"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/leetcode-golang-classroom/golang-rpg-tutorial/internal/tilemap"
)
// Sprite - struct for
type Sprite struct {
Img *ebiten.Image
X, Y float64
}
// Enemy - struct will follow
type Enemy struct {
*Sprite
FollowsPlayer bool
}
// Game - struct for the game
type Game struct {
// player structure
player *Player
enemies []*Enemy
potions []*Potion
tilemapJSON *tilemap.TilemapJSON
tilemapImg *ebiten.Image
}
// Potion - struct for potion
type Potion struct {
*Sprite
AmtHeal uint32
}
// Player - struct
type Player struct {
*Sprite
Health uint32
}
func (g *Game) Update() error {
// react to key press
if ebiten.IsKeyPressed(ebiten.KeyRight) {
g.player.X += 2
}
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
g.player.X -= 2
}
if ebiten.IsKeyPressed(ebiten.KeyDown) {
g.player.Y += 2
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
g.player.Y -= 2
}
for _, sprite := range g.enemies {
if !sprite.FollowsPlayer {
continue
}
if sprite.X < g.player.X {
sprite.X += 1
} else if sprite.X > g.player.X {
sprite.X -= 1
}
if sprite.Y < g.player.Y {
sprite.Y += 1
} else if sprite.Y > g.player.Y {
sprite.Y -= 1
}
}
for _, potion := range g.potions {
// fake collision
if g.player.X > potion.X {
g.player.Health += potion.AmtHeal
fmt.Printf("Picked up potion! Health: %d\n", g.player.Health)
}
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{120, 180, 255, 255})
opts := ebiten.DrawImageOptions{}
// loop over the layers
for _, layer := range g.tilemapJSON.Layers {
for index, id := range layer.Data {
x := index % layer.Width
y := index / layer.Width
x *= 16
y *= 16
srcX := (id - 1) % 22
srcY := (id - 1) / 22
srcX *= 16
srcY *= 16
opts.GeoM.Translate(float64(x), float64(y))
screen.DrawImage(
g.tilemapImg.SubImage(
image.Rect(srcX, srcY, srcX+16, srcY+16),
).(*ebiten.Image),
&opts,
)
opts.GeoM.Reset()
}
}
opts.GeoM.Translate(g.player.X, g.player.Y)
// draw our player
screen.DrawImage(
g.player.Img.SubImage(
image.Rect(0, 0, 16, 16),
).(*ebiten.Image),
&opts,
)
opts.GeoM.Reset()
for _, enemy := range g.enemies {
opts.GeoM.Translate(enemy.X, enemy.Y)
screen.DrawImage(
enemy.Img.SubImage(
image.Rect(0, 0, 16, 16),
).(*ebiten.Image),
&opts,
)
opts.GeoM.Reset()
}
opts.GeoM.Reset()
for _, potion := range g.potions {
opts.GeoM.Translate(potion.X, potion.Y)
screen.DrawImage(
potion.Img.SubImage(
image.Rect(0, 0, 16, 16),
).(*ebiten.Image),
&opts,
)
opts.GeoM.Reset()
}
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return ebiten.WindowSize()
}
func main() {
ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("Trace Game")
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
playerImg, _, err := ebitenutil.NewImageFromFile("assets/images/ninja.png")
if err != nil {
log.Fatal(err)
}
skeletonImg, _, err := ebitenutil.NewImageFromFile("assets/images/skeleton.png")
if err != nil {
log.Fatal(err)
}
potionImg, _, err := ebitenutil.NewImageFromFile("assets/images/lifepotion.png")
if err != nil {
log.Fatal(err)
}
tilemapImg, _, err := ebitenutil.NewImageFromFile("assets/images/TilesetFloor.png")
if err != nil {
log.Fatal(err)
}
tilemapJSON, err := tilemap.NewTilemapJSON("assets/maps/spawn.json")
if err != nil {
log.Fatal(err)
}
if err := ebiten.RunGame(&Game{
player: &Player{
&Sprite{
Img: playerImg,
X: 100,
Y: 100,
},
3,
},
enemies: []*Enemy{
{
&Sprite{
Img: skeletonImg,
X: 50,
Y: 100,
},
true,
},
{
&Sprite{
Img: skeletonImg,
X: 150,
Y: 200,
},
false,
},
{
&Sprite{
Img: skeletonImg,
X: 100,
Y: 150,
},
false,
},
},
potions: []*Potion{
{
&Sprite{
Img: potionImg,
X: 300,
Y: 400,
},
1.0,
},
},
tilemapJSON: tilemapJSON,
tilemapImg: tilemapImg,
}); err != nil {
log.Fatal(err)
}
}
sudo snap install tiled
package tilemap
import (
"encoding/json"
"os"
)
type TilemapLayerJSON struct {
Data []int `json:"data"`
Width int `json:"width"`
Height int `json:"height"`
}
type TilemapJSON struct {
Layers []TilemapLayerJSON `json:"layers"`
}
func NewTilemapJSON(filepath string) (*TilemapJSON, error) {
contents, err := os.ReadFile(filepath)
if err != nil {
return nil, err
}
var tilemapJSON TilemapJSON
err = json.Unmarshal(contents, &tilemapJSON)
if err != nil {
return nil, err
}
return &tilemapJSON, nil
}
package camera
import "math"
type Camera struct {
X, Y float64
}
func NewCamera(x, y float64) *Camera {
return &Camera{
X: x,
Y: y,
}
}
func (c *Camera) FollowTarget(targetX, targetY, screenWidth, screenHeight float64) {
c.X = -targetX + screenWidth/2.0
c.Y = -targetY + screenHeight/2.0
}
func (c *Camera) Constrain(tilemapWidthPixels, tilemapHeightPixels, screenWidth, screenHeight float64) {
c.X = math.Min(c.X, 0.0)
c.Y = math.Min(c.Y, 0.0)
c.X = math.Max(c.X, screenWidth-tilemapWidthPixels)
c.Y = math.Max(c.Y, screenHeight-tilemapHeightPixels)
}