Skip to content

Instantly share code, notes, and snippets.

@RikusLategan
Created July 18, 2025 16:07
Show Gist options
  • Select an option

  • Save RikusLategan/eb1f26a4fd1add43ce11e34cce1a70a2 to your computer and use it in GitHub Desktop.

Select an option

Save RikusLategan/eb1f26a4fd1add43ce11e34cce1a70a2 to your computer and use it in GitHub Desktop.
This is the same demo using two different 2d go ported physic engines ( #Chipmunk and #Box2d )
package main
import (
"fmt"
"image/color"
"log"
"math/rand"
"github.com/hajimehoshi/ebiten/v2"
ebitvector "github.com/hajimehoshi/ebiten/v2/vector"
"github.com/ByteArena/box2d"
)
// entity represents a ball in the Box2D world.
type entity struct {
body *box2d.B2Body
radius float64
colour color.RGBA
}
// Game holds our entities and the Box2D world.
type Game struct {
entities []entity
world *box2d.B2World
dt float64
}
// init sets up Box2D, creates dynamic balls, and static walls.
func (g *Game) init() {
g.dt = 1.0 / 60.0
// Create Box2D world with gravity pointing down
gravity := box2d.MakeB2Vec2(0, 1000)
g.world = box2d.MakeB2World(gravity)
// Create 14 balls at random positions/velocities
g.entities = make([]entity, 14)
for i := range g.entities {
x := float64(rand.Intn(640))
y := float64(rand.Intn(480))
radius := 5.0
// Define dynamic body
bd := box2d.NewB2BodyDef()
bd.Type = box2d.B2BodyType.B2DynamicBody
bd.Position = box2d.MakeB2Vec2(x, y)
body := g.world.CreateBody(bd)
// Attach a circle fixture
circle := box2d.NewB2CircleShape()
circle.M_radius = radius
fd := box2d.NewB2FixtureDef()
fd.Shape = circle
fd.Density = 1.0
fd.Friction = 0.7
fd.Restitution = 0.9
body.CreateFixtureFromDef(fd)
// Give it an initial random velocity
vx := -50 + rand.Float64()*100
vy := -50 + rand.Float64()*100
body.SetLinearVelocity(box2d.MakeB2Vec2(vx, vy))
g.entities[i] = entity{
body: body,
radius: radius,
colour: color.RGBA{
uint8(rand.Intn(256)),
uint8(rand.Intn(256)),
uint8(rand.Intn(256)),
0xff,
},
}
}
// Helper to create a static edge (wall)
makeWall := func(x1, y1, x2, y2 float64) {
bd := box2d.NewB2BodyDef()
bd.Type = box2d.B2BodyType.B2StaticBody
body := g.world.CreateBody(bd)
edge := box2d.NewB2EdgeShape()
edge.Set(box2d.MakeB2Vec2(x1, y1), box2d.MakeB2Vec2(x2, y2))
fd := box2d.NewB2FixtureDef()
fd.Shape = edge
fd.Friction = 0.7
fd.Restitution = 1.0
body.CreateFixtureFromDef(fd)
}
screenW, screenH := 640.0, 480.0
wallThickness := 10.0
// Left
makeWall(0, 0, 0, screenH)
// Right
makeWall(screenW, 0, screenW, screenH)
// Top
makeWall(0, 0, screenW, 0)
// Bottom
makeWall(0, screenH, screenW, screenH)
}
// Update steps the Box2D world.
func (g *Game) Update() error {
velIter, posIter := 8, 3
g.world.Step(g.dt, velIter, posIter)
return nil
}
// Draw renders each ball as a filled circle.
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{0x10, 0, 0, 0xff})
for _, e := range g.entities {
pos := e.body.GetPosition()
ebitvector.DrawFilledCircle(
screen,
float32(pos.X),
float32(pos.Y),
float32(e.radius),
e.colour,
true,
)
}
}
// Layout defines the Ebiten window size.
func (g *Game) Layout(w, h int) (int, int) {
return 640, 480
}
func main() {
ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("Gogolf (Box2D)")
game := &Game{}
game.init()
if err := ebiten.RunGame(game); err != nil {
log.Fatal(err)
}
fmt.Println(game.Layout(0, 0))
}
package main
import (
"fmt"
"image/color"
"log"
"math/rand"
"github.com/hajimehoshi/ebiten/v2"
ebitvector "github.com/hajimehoshi/ebiten/v2/vector"
cp "github.com/jakecoffman/cp"
)
// entity represents a ball or other object in the physics simulation.
// Each entity has a physics body, a shape for collision, and a color for rendering.
type entity struct {
body *cp.Body // The physics body (position, velocity, etc.)
shape *cp.Shape // The collision shape (circle, polygon, etc.)
colour color.RGBA // The color used when drawing the entity
}
// Game holds all the state for our game, including entities, physics space, and the mountain platform.
type Game struct {
entities []entity // All dynamic entities (balls) in the game
space *cp.Space // The Chipmunk2D physics simulation space
dt float64 // The physics time step (seconds per frame)
}
// init sets up the physics world, creates balls, the mountain, and the walls.
func (game *Game) init() {
// Set the physics simulation time step to 1/60th of a second.
game.dt = 1.0 / 60.0
// Create a new Chipmunk2D physics space and set gravity (downwards).
game.space = cp.NewSpace()
game.space.SetGravity(cp.Vector{X: 0, Y: 1000})
// --- Create balls ---
// We'll create 14 balls with random positions, velocities, and colors.
game.entities = make([]entity, 14)
for i := 0; i < len(game.entities); i++ {
x := float64(rand.Intn(640)) // Random X position within screen width
y := float64(rand.Intn(480)) // Random Y position within screen height
radius := 5.0 // Ball radius
mass := 1.0 // Ball mass
// Calculate moment of inertia for a circle
moment := cp.MomentForCircle(mass, 0, radius, cp.Vector{})
// Create the physics body and set its position and velocity
body := cp.NewBody(mass, moment)
body.SetPosition(cp.Vector{X: x, Y: y})
body.SetVelocity(-50+rand.Float64()*100, -50+rand.Float64()*100)
// Create the collision shape (circle) and set physics properties
shape := cp.NewCircle(body, radius, cp.Vector{})
shape.SetElasticity(0.9) // Perfectly bouncy
shape.SetFriction(0.7) // Some friction
// Add the body and shape to the physics space
game.space.AddBody(body)
game.space.AddShape(shape)
// Assign a random color to each ball
game.entities[i] = entity{
body: body,
shape: shape,
colour: color.RGBA{uint8(rand.Intn(256)), uint8(rand.Intn(256)), uint8(rand.Intn(256)), 0xff},
}
}
// ...existing code...
// --- Create walls ---
wallThickness := 10.0
screenW, screenH := 640.0, 480.0
// Left wall
leftBody := cp.NewStaticBody()
leftShape := cp.NewSegment(leftBody, cp.Vector{X: 0, Y: 0}, cp.Vector{X: 0, Y: screenH}, wallThickness)
leftShape.SetElasticity(1.0)
leftShape.SetFriction(0.7)
game.space.AddShape(leftShape)
// Rig
rightBody := cp.NewStaticBody()
rightShape := cp.NewSegment(rightBody, cp.Vector{X: screenW, Y: 0}, cp.Vector{X: screenW, Y: screenH}, wallThickness)
rightShape.SetElasticity(1.0)
rightShape.SetFriction(0.7)
game.space.AddShape(rightShape)
// T
topBody := cp.NewStaticBody()
topShape := cp.NewSegment(topBody, cp.Vector{X: 0, Y: 0}, cp.Vector{X: screenW, Y: 0}, wallThickness)
topShape.SetElasticity(1.0)
topShape.SetFriction(0.7)
game.space.AddShape(topShape)
ebitvector.DrawFilledPath()
// Bott
bottomBody := cp.NewStaticBody()
bottomShape := cp.NewSegment(bottomBody, cp.Vector{X: 0, Y: screenH}, cp.Vector{X: screenW, Y: screenH}, wallThickness)
bottomShape.SetElasticity(1.0)
bottomShape.SetFriction(0.7)
game.space.AddShape(bottomShape)
}
// Update advances the physics simulation by one time step.
// All collisions and physics are handled automatically by Chipmunk2D.
func (game *Game) Update() error {
game.space.Step(game.dt)
return nil
}
// Draw renders the game scene using Ebiten.
// Balls are drawn as filled circles, and the mountain is drawn as a polygon.
func (game *Game) Draw(screen *ebiten.Image) {
// Fill the background with a dark color
screen.Fill(color.RGBA{0x10, 0, 0, 0xff})
// Draw each ball
for _, entity := range game.entities {
pos := entity.body.Position()
ebitvector.DrawFilledCircle(screen, float32(pos.X), float32(pos.Y), 5, entity.colour, true)
}
}
// Layout specifies the window size for Ebiten.
func (game *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 640, 480
}
// main is the entry point of the program.
// It sets up the window, initializes the game, and starts the game loop.
func main() {
ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("Gogolf")
game := &Game{}
game.init()
if err := ebiten.RunGame(game); err != nil {
log.Fatal(err)
}
fmt.Println(game.Layout(0, 0))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment