Skip to content

Instantly share code, notes, and snippets.

@samwightt
Last active February 16, 2022 07:20
Show Gist options
  • Save samwightt/1dfc7c85464fd93c634fa0e48482679c to your computer and use it in GitHub Desktop.
Save samwightt/1dfc7c85464fd93c634fa0e48482679c to your computer and use it in GitHub Desktop.
Ebiten example with perlin noise generation
module samw.dev/ebiten
go 1.15
require (
github.com/aquilax/go-perlin v1.1.0
github.com/hajimehoshi/ebiten/v2 v2.2.4
)
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s=
github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
github.com/hajimehoshi/ebiten v1.12.12 h1:JvmF1bXRa+t+/CcLWxrJCRsdjs2GyBYBSiFAfIqDFlI=
github.com/hajimehoshi/ebiten/v2 v2.2.4 h1:/+qrmbv+W6scgVWwQJ7IyiI2z4y8QM2n0JDHStNC+Ns=
github.com/hajimehoshi/ebiten/v2 v2.2.4/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0=
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ=
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5 h1:peBP2oZO/xVnGMaWMCyFEI0WENsGj71wx5K12mRELHQ=
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
// This generates Perlin noise with ebiten and lets you move around using the arrow keys.
// To try out yourself: Install Go (latest version). Create a directory and copy / paste the files in there.
// Run `go get` to install all the packages. Then run `go run .` to run the game.
package main
// Importing some packages we need to run the game. Main packages are a color library, perlin noise stuff, and
// some utilities embitten provides.
import (
"image/color"
"log"
"github.com/aquilax/go-perlin"
ebiten "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
// We'll create a grid of tiles and fill them up with Perlin noise. The screen size will be square to make things simpler.
const (
// Size of the screen.
SCREEN_SIZE = 500
// Size of the tiles (in pixels)
TILE_SIZE = 25
// Number of tiles in the grid
NUM_TILES = SCREEN_SIZE / TILE_SIZE
)
/// Struct (basically an object or class) to hold the current position.
/// `justChanged` is a boolean that is set to true when the position changes,
/// and cleared using the `ClearChanged` method.
type Position struct {
x int
y int
justChanged bool
}
// Quick explanation of go syntax.
//
// Functions start with `func`, followed by a name and their arguments.
// The function body is wrapped in curly braces, like in C or Java.
//
// Example: `func HelloWorld() {
// Code here
// }`
//
// Go is a strongly typed language (like Java or C), so you have to write types for arguments
// and return types. Generally, types go *after* the variable name, like in Java or C.
// Types for arguments go in the parentheses, return types appear after the parentheses.
//
// Example:
//
// `func HelloWorld(name string) string {
// return "Hello, " + name
// }`
//
// Functions are called like in C or Java: `HelloWorld("John")`. Variables are created using `:=`
// and assigned with `=`. Use `:=` when in doubt, the go compiler will scream at you if you're creating
// a variable that already exists.
//
// Go allows you to create *structs*, which are basically classes in Java. Structs are defined using a type
// definition like this:
//
// type Position struct {
// x int
// y int
// }
//
// Types for each of the fields appear after the name of the field. Structs are created like this:
//
// Position { x: 0, y: 0 }
//
// If a field is omitted in a struct, it will be set to the zero value for that type. For example, the zero
// value for `int` is 0, for boolean is `false`, etc. So the above code can be simplified to this:
//
// Position{}
//
// To put methods on structs, you write a `func` with the struct in paranthesis before the function name.
// We could implement a `Move` method on the `Position` struct like this:
//
// func (p *Position) Move(x int, y int) {
// p.x += x
// p.y += y
// }
//
// The parenthesis are like a second set of arguments, but you can only have one of them. They will let you call
// the method on the struct like this:
//
// pos := Position{}
// pos.Move(1, 2)
//
// Arguments in Go are copied before they're passed in. These are *deep copies* by default. This might take a while on larger structs.
// To get around this, we can use *pointers*, just like in C. So above, we're using a pointer to the struct, instead of a copy. If we removed the
// asterisk (`*`) from the type, we'd get a copy instead. If you want to change fields on a struct, use a pointer, not a copy.
//
// Congrats, you now know Go! It's not too hard.
/// Clears the `justChanged` flag.
func (x *Position) ClearChanged() {
x.justChanged = false
}
/// Increments the position by `x` and `y`. Also sets the `justChanged` value to `true`.
func (a *Position) Increment(x int, y int) {
a.x += x
a.y += y
a.justChanged = true
}
/// The current state of the game. We have a list of tiles and a position.
type Game struct{
tiles [][]float64
currentPos Position
}
/// Ebiten requires that we create a few methods on our `Game` struct.
/// It has an interface (also called `Game`) with some functions. This is one of them.
///
/// This function gets called on every 'tick' (default: 60 times per second). We use it
/// to update our game state.
func (g *Game) Update() error {
// Check to see if the user has pressed the arrow keys.
g.HandleKeyPress()
// If the position has changed, we need to regenerate the tiles.
if g.currentPos.justChanged {
// Create a new perlin noise generator. Idk what these do, I fiddled until they worked.
noise := perlin.NewPerlin(2, 2, 3, 100)
// This is a for loop, `range` is a utility for looping over arrays. `i` here is an int,
// this is equivalent to i := 0; i < len(g.tiles); i++.
for i := range g.tiles {
for j := range g.tiles[i] {
// Current position is the current x, y in the tile plus our position offset.
x := i + g.currentPos.x
y := j + g.currentPos.y
// Perlin noise generator will not work unless you divide the position by 10 for some reason.
g.tiles[i][j] = noise.Noise2D(float64(x) / 10., float64(y) / 10.)
}
}
// We've updated the position, clear the `justChanged` flag.
g.currentPos.ClearChanged()
}
return nil
}
/// Checks to see if any of the arrow keys are pressed currently. If they are, moves the position as necessary.
func (g *Game) HandleKeyPress() {
if ebiten.IsKeyPressed(ebiten.KeyUp) {
g.currentPos.Increment(0, -1)
} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
g.currentPos.Increment(0, 1)
} else if ebiten.IsKeyPressed(ebiten.KeyLeft) {
g.currentPos.Increment(-1, 0)
} else if ebiten.IsKeyPressed(ebiten.KeyRight) {
g.currentPos.Increment(1, 0)
}
}
/// This is another one of the required functions for ebiten. `Draw` is called on every frame,
/// after `Update`. It's where we draw things on the screen.
func (g *Game) Draw(screen *ebiten.Image) {
// Range returns two values. The first is the index, the second is the value.
// This is equivalent to for i := 0; i < len(g.tiles); i++ { tileRow := g.tiles[i] }
for i, tileRow := range g.tiles {
for j, tile := range tileRow {
// Create a color. RGB values are white, the last value (the alpha) is based on the perlin noise value.
color := color.RGBA{0xff, 0xff, 0xff, uint8((tile + 1.) * 100.)}
// Actually draw the rectangle. The `screen` is the rectangle we're drawing inside, the third + fourth arguments
// are the x, y position of the rectangle, and the fifth + sixth arguments are the width and height of the rectangle.
ebitenutil.DrawRect(screen, float64(i * TILE_SIZE), float64(j * TILE_SIZE), TILE_SIZE, TILE_SIZE, color)
}
}
}
/// Last function required by embitten. This just returns the screen height and width.
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return SCREEN_SIZE, SCREEN_SIZE
}
/// Function that creates a new position.
func NewPosition() Position {
return Position {
justChanged: true,
}
}
/// Function that creates a new `Game` struct.
func NewGame() Game {
// Create an empty 2D array of tiles. `make` is like C's `malloc`, except Go is garbage collected.
// Once we stop using this value, it'll collect it for us automatically.
tiles := make([][]float64, NUM_TILES)
for i := range tiles {
tiles[i] = make([]float64, NUM_TILES)
}
return Game {
tiles: tiles,
currentPos: NewPosition(),
}
}
/// `main` is called when we start the program, it's like C's `main`.
func main() {
// Set the window size and title.
ebiten.SetWindowSize(SCREEN_SIZE, SCREEN_SIZE)
ebiten.SetWindowTitle("Hello, World!")
// Create a new instance of our game struct.
game := NewGame()
// Pass the struct to ebiten to run. ebiten will take care of calling our functions as needed.
if err := ebiten.RunGame(&game); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment