├── .gitignore ├── 2d3d ├── 2d3d.gif ├── assets │ └── images │ │ └── triforce.png ├── conf.toml ├── main.go └── render │ └── render.go ├── LICENSE ├── README.md ├── asteroids ├── assets │ └── audio │ │ ├── lazer-old1.wav │ │ └── lazer.wav ├── conf.toml ├── game │ ├── asteroid.go │ ├── bullet.go │ ├── explosion.go │ ├── game.go │ ├── math.go │ ├── phys │ │ ├── body.go │ │ ├── cell.go │ │ ├── matrix.go │ │ └── world.go │ ├── player.go │ └── sprite.go └── main.go ├── audio_player ├── assets │ ├── audio │ │ ├── test.flac │ │ ├── test.mp3 │ │ ├── test.ogg │ │ └── test.wav │ └── images │ │ ├── back.png │ │ ├── forward.png │ │ ├── next.png │ │ ├── pause.png │ │ ├── play.png │ │ ├── rewind.png │ │ └── stop.png ├── conf.toml ├── main.go └── ui │ ├── assets.go │ ├── button.go │ └── element.go ├── gocraftmini ├── conf.toml ├── game │ ├── camera.go │ ├── cell.go │ ├── generator.go │ ├── math.go │ ├── sky.go │ ├── voxel.go │ └── world.go └── main.go ├── physics ├── conf.toml └── main.go ├── platformer ├── conf.toml ├── game │ ├── block.go │ ├── debris.go │ ├── entity.go │ ├── explosion.go │ ├── game_object.go │ ├── grenade.go │ ├── guardian.go │ ├── map.go │ ├── math.go │ ├── player.go │ ├── puff.go │ └── util.go └── main.go ├── pong ├── assets │ ├── audio │ │ └── blip.wav │ └── images │ │ └── icon.png ├── conf.toml └── main.go ├── racer ├── assets │ └── images │ │ ├── .DS_Store │ │ ├── background.png │ │ └── sprites.png ├── conf.toml ├── game │ ├── car.go │ ├── colors.go │ ├── game.go │ ├── player.go │ ├── rand.go │ ├── segment.go │ ├── sprite.go │ ├── spritesheet.go │ └── util.go └── main.go ├── screenshots.png ├── star_field └── main.go └── test-all ├── assets ├── audio │ └── bomb.wav ├── fonts │ ├── arial.ttf │ ├── arialbd.ttf │ ├── arialbi.ttf │ ├── ariali.ttf │ ├── image_font.png │ └── mango_smoothie.otf ├── images │ ├── palm_tree.png │ └── particle.png ├── shaders │ └── blackandwhite.glsl └── text │ └── lorem.txt ├── conf.toml └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | -------------------------------------------------------------------------------- /2d3d/2d3d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/2d3d/2d3d.gif -------------------------------------------------------------------------------- /2d3d/assets/images/triforce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/2d3d/assets/images/triforce.png -------------------------------------------------------------------------------- /2d3d/conf.toml: -------------------------------------------------------------------------------- 1 | title = "2d 3d experiment" # The window title (string) 2 | width = 300 # The window width (number) 3 | height = 300 # The window height (number) 4 | borderless = false # Remove all border visuals from the window (boolean) 5 | vsync = true # Enable vertical sync (boolean) 6 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 7 | highdpi = true # Enable high-dpi mode for the window on a Retina display (boolean) 8 | srgb = true # Enable sRGB gamma correction when drawing to the screen (boolean) 9 | centered = true # Center the window in the display 10 | resizable = true 11 | -------------------------------------------------------------------------------- /2d3d/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tanema/amore" 5 | "github.com/tanema/amore/gfx" 6 | 7 | "github.com/tanema/amore-examples/2d3d/render" 8 | ) 9 | 10 | var ( 11 | triforce *render.Fake3D 12 | rotation float32 13 | ) 14 | 15 | const rotationSpeed float32 = 2 16 | 17 | func main() { 18 | triforce, _ = render.New("images/triforce.png", 16, 16) 19 | amore.Start(update, draw) 20 | } 21 | 22 | func update(dt float32) { 23 | rotation += rotationSpeed * dt 24 | } 25 | 26 | func draw() { 27 | gfx.Scale(10) 28 | triforce.Draw(30, 30, rotation) 29 | } 30 | -------------------------------------------------------------------------------- /2d3d/render/render.go: -------------------------------------------------------------------------------- 1 | package render 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | const increment float32 = 1 8 | 9 | type Fake3D struct { 10 | img *gfx.Image 11 | quads []*gfx.Quad 12 | ox, oy float32 13 | } 14 | 15 | func New(filepath string, frameWidth, frameHeight int32) (*Fake3D, error) { 16 | img, err := gfx.NewImage(filepath) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | imageWidth, imageHeight := img.GetWidth(), img.GetHeight() 22 | framesWide, framesHeight := imageWidth/frameWidth, imageHeight/frameHeight 23 | quads := make([]*gfx.Quad, 0, framesWide*framesHeight) 24 | 25 | var x, y int32 26 | for y = 0; y < framesHeight; y++ { 27 | for x = 0; x < framesWide; x++ { 28 | newQuad := gfx.NewQuad(x*frameWidth, y*frameHeight, frameWidth, frameHeight, imageWidth, imageHeight) 29 | quads = append(quads, newQuad) 30 | } 31 | } 32 | 33 | return &Fake3D{ 34 | img: img, 35 | quads: quads, 36 | ox: float32(frameWidth) / 2, 37 | oy: float32(frameHeight) / 2, 38 | }, nil 39 | } 40 | 41 | func (f3d *Fake3D) Draw(x, y, angle float32) { 42 | for _, quad := range f3d.quads { 43 | gfx.Drawq(f3d.img, quad, x, y, angle, 1, 1, f3d.ox, f3d.oy) 44 | y -= increment 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tim Anema 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # These no longer work and this repo is no longer maintained 2 | 3 | # amore-examples 4 | 5 | Examples meant to demonstrate the [Amore Game Framework](https://github.com/tanema/amore) 6 | 7 | ![screenshot of some of the examples](screenshots.png) 8 | 9 | ### pong 10 | 11 | Pong clone, you can try it out by runing `go run main.go` in the directory. 12 | Use the up and down arrows to play. Enter to restart. 13 | 14 | ### asteroids 15 | 16 | Asteroids clone, you can try it out by runing `go run main.go` in the directory . 17 | Operate with the arrow keys and space to fire. Destroy the asteroids. Asteroids 18 | also has a pretty good demonstration of the kind of physics you can implement 19 | yourself. 20 | 21 | ### physics 22 | 23 | Physics demonstrates the usage of [github.com/neguse/go-box2d-lite](https://github.com/neguse/go-box2d-lite) 24 | in amore, for more in depth physics. Normally for most games you dont need this 25 | level of physics but it's nice to know that it's there. 26 | 27 | ### racer 28 | 29 | A pseudo 3d car racer implemented from the [codeincomplete.com](http://codeincomplete.com/posts/2012/6/22/javascript_racer/) 30 | tutorial. Assets are also from that tutorial. 31 | 32 | ### platformer 33 | 34 | This is a re-implementation/port of [bump.lua](https://github.com/kikito/bump.lua) example program, 35 | it shows a good example of a platformer with destructable terrain. 36 | 37 | ### test-all 38 | 39 | Is used for testing every element of the framework. You can try it out by 40 | runing `go run main.go` in the directory. It wont look like much but it's 41 | mostly all there. 42 | -------------------------------------------------------------------------------- /asteroids/assets/audio/lazer-old1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/asteroids/assets/audio/lazer-old1.wav -------------------------------------------------------------------------------- /asteroids/assets/audio/lazer.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/asteroids/assets/audio/lazer.wav -------------------------------------------------------------------------------- /asteroids/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Amore : Asteroids" # The window title (string) 2 | width = 800 # The window width (number) 3 | height = 600 # The window height (number) 4 | vsync = true # Enable vertical sync (boolean) 5 | centered = true # Center the window in the display 6 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 7 | -------------------------------------------------------------------------------- /asteroids/game/asteroid.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | const ( 4 | asteroidSpeed = 100 5 | asteroidSpin = 2 6 | ) 7 | 8 | var asteroidPoints = []float32{ 9 | -10, 0, 10 | -5, 7, 11 | -3, 4, 12 | 1, 10, 13 | 5, 4, 14 | 10, 0, 15 | 5, -6, 16 | 2, -10, 17 | -4, -10, 18 | -4, -5, 19 | -10, 0, 20 | } 21 | 22 | type Asteroid struct { 23 | *Sprite 24 | parent bool 25 | } 26 | 27 | func newAsteroid() *Asteroid { 28 | new_asteroid := &Asteroid{ 29 | parent: true, 30 | } 31 | 32 | new_asteroid.Sprite = NewSprite(new_asteroid, "asteroid", 33 | randMax(screenWidth), 34 | randMax(screenHeight), 35 | randRange(3, 8), 36 | asteroidPoints, true) 37 | new_asteroid.vx = randLimits(asteroidSpeed) 38 | new_asteroid.vy = randLimits(asteroidSpeed) 39 | new_asteroid.vrot = randLimits(asteroidSpin) 40 | 41 | return new_asteroid 42 | } 43 | 44 | func (asteroid *Asteroid) Update(dt float32) { 45 | if collisions := asteroid.UpdateMovement(dt); len(collisions) > 0 { 46 | for _, c := range collisions { 47 | if collisions[0].Name == "ship" { 48 | c.Collidable.Destroy(false) 49 | } else if collisions[0].Name == "bullet" { 50 | score++ 51 | asteroid.Destroy(false) 52 | c.Collidable.Destroy(false) 53 | } 54 | } 55 | } 56 | } 57 | 58 | func (asteroid *Asteroid) Destroy(force bool) { 59 | removeObject(asteroid) 60 | asteroid.Sprite.Destroy() 61 | if !force { 62 | if asteroid.parent { 63 | for i := 0; i < 3; i++ { 64 | a := &Asteroid{parent: false} 65 | a.Sprite = NewSprite(a, "asteroid", 66 | asteroid.x, 67 | asteroid.y, 68 | randRange(1, 3), 69 | asteroidPoints, true) 70 | a.vx = randLimits(asteroidSpeed) 71 | a.vy = randLimits(asteroidSpeed) 72 | a.vrot = randLimits(asteroidSpin) 73 | addObject(a) 74 | } 75 | } else { 76 | newExplosion(asteroid.Sprite) 77 | } 78 | bomb.Play() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /asteroids/game/bullet.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | const ( 4 | bulletSpeed = 500 5 | ) 6 | 7 | type Bullet struct { 8 | *Sprite 9 | } 10 | 11 | func newBullet(x, y, rot float32) *Bullet { 12 | vectorx := sin(rot) 13 | vectory := -cos(rot) 14 | 15 | bullet := &Bullet{} 16 | bullet.Sprite = NewSprite(bullet, "bullet", x+(vectorx*10), y+(vectory*10), 1, 17 | []float32{ 18 | -1, 0, 19 | 1, 0, 20 | }, false) 21 | bullet.rot = rot 22 | bullet.vx = (bulletSpeed * vectorx) 23 | bullet.vy = (bulletSpeed * vectory) 24 | 25 | return bullet 26 | } 27 | 28 | func (bullet *Bullet) Update(dt float32) { 29 | if collisions := bullet.UpdateMovement(dt); len(collisions) > 0 { 30 | bullet.Destroy(false) 31 | for _, c := range collisions { 32 | if c.Name == "asteroid" { 33 | score++ 34 | c.Collidable.Destroy(false) 35 | } 36 | } 37 | } 38 | 39 | if bullet.x > screenWidth || bullet.x < 0 || bullet.y > screenHeight || bullet.y < 0 { 40 | bullet.Destroy(false) 41 | } 42 | } 43 | 44 | func (bullet *Bullet) Destroy(force bool) { 45 | removeObject(bullet) 46 | bullet.Sprite.Destroy() 47 | } 48 | -------------------------------------------------------------------------------- /asteroids/game/explosion.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | const ( 4 | explosionSpeed = 100 5 | explosionTime = 3 6 | ) 7 | 8 | type Explosion struct { 9 | sprites []*Sprite 10 | life float32 11 | } 12 | 13 | func newExplosion(sprite *Sprite) { 14 | points := sprite.GetPoints() 15 | explosion := &Explosion{ 16 | sprites: []*Sprite{}, 17 | } 18 | 19 | for i := 0; i < len(points)-2; i += 2 { 20 | explosion.addSegment(points[i], points[i+1], points[i+2], points[i+3]) 21 | } 22 | 23 | addObject(explosion) 24 | } 25 | 26 | func (explosion *Explosion) addSegment(x0, y0, x1, y1 float32) { 27 | sprite := NewSprite(nil, "explosion", 0, 0, 1, []float32{x0, y0, x1, y1}, false) 28 | rot := atan2(x1-x0, y1-y0) 29 | vectorx := sin(rot) 30 | vectory := -cos(rot) 31 | sprite.vx = explosionSpeed * vectorx 32 | sprite.vy = explosionSpeed * vectory 33 | explosion.sprites = append(explosion.sprites, sprite) 34 | } 35 | 36 | func (explosion *Explosion) Update(dt float32) { 37 | for _, sprite := range explosion.sprites { 38 | sprite.UpdateMovement(dt) 39 | } 40 | explosion.life += dt 41 | if explosion.life >= explosionTime { 42 | explosion.Destroy(false) 43 | } 44 | } 45 | 46 | func (explosion *Explosion) Draw() { 47 | for _, sprite := range explosion.sprites { 48 | sprite.Draw() 49 | } 50 | } 51 | 52 | func (explosion *Explosion) Destroy(force bool) { 53 | removeObject(explosion) 54 | for _, sprite := range explosion.sprites { 55 | sprite.Destroy() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /asteroids/game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tanema/amore-examples/asteroids/game/phys" 7 | 8 | "github.com/tanema/amore/audio" 9 | "github.com/tanema/amore/gfx" 10 | "github.com/tanema/amore/keyboard" 11 | ) 12 | 13 | var debug bool 14 | 15 | type GameObject interface { 16 | Update(dt float32) 17 | Draw() 18 | Destroy(force bool) 19 | } 20 | 21 | const ( 22 | cellSize float32 = 60 23 | ) 24 | 25 | var ( 26 | score = 0 27 | world *phys.World 28 | objects []GameObject 29 | bomb, _ = audio.NewSource("../test-all/assets/audio/bomb.wav", true) 30 | lazer, _ = audio.NewSource("audio/lazer.wav", true) 31 | player *Player 32 | gameOver = false 33 | screenWidth float32 34 | screenHeight float32 35 | ) 36 | 37 | func New() { 38 | keyboard.OnKeyUp = keyup 39 | screenWidth = gfx.GetWidth() 40 | screenHeight = gfx.GetHeight() 41 | reset() 42 | } 43 | 44 | func reset() { 45 | gameOver = false 46 | score = 0 47 | world = phys.NewWorld(screenWidth, screenHeight, cellSize) 48 | player = newPlayer() 49 | objects = []GameObject{ 50 | player, 51 | newAsteroid(), 52 | newAsteroid(), 53 | newAsteroid(), 54 | newAsteroid(), 55 | newAsteroid(), 56 | newAsteroid(), 57 | newAsteroid(), 58 | } 59 | } 60 | 61 | func keyup(key keyboard.Key) { 62 | if key == keyboard.KeyTab { 63 | debug = !debug 64 | } 65 | if key == keyboard.KeyReturn { 66 | reset() 67 | } 68 | } 69 | 70 | func Update(dt float32) { 71 | for _, object := range objects { 72 | object.Update(dt) 73 | } 74 | gameOver = gameOver || len(objects) == 1 75 | } 76 | 77 | func addObject(object GameObject) { 78 | objects = append(objects, object) 79 | } 80 | 81 | func removeObject(object GameObject) { 82 | for i, other := range objects { 83 | if object == other { 84 | objects = append(objects[:i], objects[i+1:]...) 85 | return 86 | } 87 | } 88 | } 89 | 90 | func Draw() { 91 | if debug { 92 | world.DrawGrid() 93 | gfx.Print(fmt.Sprintf("objects: %v", len(objects)), 0, 15) 94 | } 95 | 96 | for _, object := range objects { 97 | object.Draw() 98 | } 99 | gfx.Print(fmt.Sprintf("Score: %v", score), screenWidth-100, 0) 100 | if gameOver { 101 | if len(objects) == 1 && objects[0] == player { 102 | gfx.Print("Game Over. You Won.", screenWidth/2-175, screenHeight/2, 0, 2, 3) 103 | } else { 104 | gfx.Print("Game Over. You Lost.", screenWidth/2-175, screenHeight/2, 0, 2, 2) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /asteroids/game/math.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func init() { 10 | rand.Seed(time.Now().UTC().UnixNano()) 11 | } 12 | 13 | func floor(x float32) float32 { 14 | return float32(math.Floor(float64(x))) 15 | } 16 | 17 | func clamp(x, minX, maxX float32) float32 { 18 | if x < minX { 19 | return minX 20 | } else if x > maxX { 21 | return maxX 22 | } 23 | return x 24 | } 25 | 26 | func randMax(max float32) float32 { 27 | return randRange(0, max) 28 | } 29 | 30 | func randRange(min, max float32) float32 { 31 | return (rand.Float32() * (max - min)) + min 32 | } 33 | 34 | func randLimits(limit float32) float32 { 35 | return randRange(-limit, limit) 36 | } 37 | 38 | func abs(x float32) float32 { 39 | return float32(math.Abs(float64(x))) 40 | } 41 | 42 | func min(x, y float32) float32 { 43 | return float32(math.Min(float64(x), float64(y))) 44 | } 45 | 46 | func max(x, y float32) float32 { 47 | return float32(math.Max(float64(x), float64(y))) 48 | } 49 | 50 | func sin(x float32) float32 { 51 | return float32(math.Sin(float64(x))) 52 | } 53 | 54 | func cos(x float32) float32 { 55 | return float32(math.Cos(float64(x))) 56 | } 57 | 58 | func atan2(x, y float32) float32 { 59 | return float32(math.Atan2(float64(x), float64(y))) 60 | } 61 | 62 | func sqrt(x float32) float32 { 63 | return float32(math.Sqrt(float64(x))) 64 | } 65 | -------------------------------------------------------------------------------- /asteroids/game/phys/body.go: -------------------------------------------------------------------------------- 1 | package phys 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | type Body struct { 8 | world *World 9 | Name string 10 | inital []float32 11 | points []float32 12 | cells []*Cell 13 | Collidable Collidable 14 | } 15 | 16 | type Collidable interface { 17 | Destroy(force bool) 18 | } 19 | 20 | func newBody(world *World, collidable Collidable, name string, points []float32) *Body { 21 | return &Body{ 22 | Name: name, 23 | world: world, 24 | Collidable: collidable, 25 | inital: points, 26 | points: make([]float32, len(points)), 27 | } 28 | } 29 | 30 | func (body *Body) Move(x, y, rot, scale float32) []*Body { 31 | if body.Collidable != nil { 32 | for _, cell := range body.cells { 33 | cell.leave(body) 34 | } 35 | } 36 | 37 | mat := &Matrix{} 38 | mat.translate(x, y, rot, scale) 39 | for i := 0; i < len(body.inital); i += 2 { 40 | body.points[i], body.points[i+1] = mat.multiply(body.inital[i], body.inital[i+1]) 41 | } 42 | 43 | if body.Collidable != nil { 44 | body.cells = []*Cell{} 45 | others := []*Body{} 46 | others_map := map[*Body]bool{} 47 | for i := 0; i < len(body.points); i += 2 { 48 | cell := body.world.CellAt(body.points[i], body.points[i+1]) 49 | 50 | for _, other := range cell.bodies { 51 | if other != body && other.pointInside(body.points[i], body.points[i+1]) { 52 | if _, ok := others_map[other]; !ok { 53 | others = append(others, other) 54 | } 55 | others_map[other] = true 56 | } 57 | } 58 | 59 | body.cells = append(body.cells, cell) 60 | cell.enter(body) 61 | } 62 | 63 | return others 64 | } 65 | 66 | return []*Body{} 67 | } 68 | 69 | func (body *Body) pointInside(x, y float32) bool { 70 | j := 2 71 | oddNodes := false 72 | for i := 0; i < len(body.points); i += 2 { 73 | y0 := body.points[i+1] 74 | y1 := body.points[j+1] 75 | if (y0 < y && y1 >= y) || (y1 < y && y0 >= y) { 76 | if body.points[i]+(y-y0)/(y1-y0)*(body.points[j]-body.points[i]) < x { 77 | oddNodes = !oddNodes 78 | } 79 | } 80 | j += 2 81 | if j == len(body.points) { 82 | j = 0 83 | } 84 | } 85 | return oddNodes 86 | } 87 | 88 | func (body *Body) GetPoints() []float32 { 89 | return body.points 90 | } 91 | 92 | func (body *Body) Remove() { 93 | for _, cell := range body.cells { 94 | cell.leave(body) 95 | } 96 | } 97 | 98 | func (body *Body) Draw() { 99 | gfx.SetColor(255, 0, 0, 100) 100 | for _, cell := range body.cells { 101 | gfx.Rect(gfx.LINE, cell.x, cell.y, cell.width, cell.height) 102 | } 103 | gfx.SetColor(255, 255, 255, 255) 104 | } 105 | -------------------------------------------------------------------------------- /asteroids/game/phys/cell.go: -------------------------------------------------------------------------------- 1 | package phys 2 | 3 | type Cell struct { 4 | x, y, width, height float32 5 | bodies []*Body 6 | } 7 | 8 | func (cell *Cell) enter(body *Body) { 9 | cell.leave(body) // make sure this body is uniqe 10 | cell.bodies = append(cell.bodies, body) 11 | } 12 | 13 | func (cell *Cell) leave(body *Body) { 14 | for i, b := range cell.bodies { 15 | if body == b { 16 | cell.bodies = append(cell.bodies[:i], cell.bodies[i+1:]...) 17 | return 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /asteroids/game/phys/matrix.go: -------------------------------------------------------------------------------- 1 | package phys 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | type Matrix [6]float32 8 | 9 | // rot in radians 10 | func (mat *Matrix) translate(x, y, rot, scale float32) { 11 | sin := float32(math.Sin(float64(rot))) * scale 12 | cos := float32(math.Cos(float64(rot))) * scale 13 | mat[0], mat[1], mat[2], mat[3], mat[4], mat[5] = cos, -sin, x, sin, cos, y 14 | } 15 | 16 | func (mat *Matrix) multiply(x, y float32) (float32, float32) { 17 | return (mat[0] * x) + (mat[1] * y) + mat[2], (mat[3] * x) + (mat[4] * y) + mat[5] 18 | } 19 | -------------------------------------------------------------------------------- /asteroids/game/phys/world.go: -------------------------------------------------------------------------------- 1 | package phys 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/tanema/amore/gfx" 8 | ) 9 | 10 | type World struct { 11 | width float32 12 | height float32 13 | cell_width float32 14 | cell_height float32 15 | numWidth float32 16 | numHeight float32 17 | grid [][]*Cell 18 | } 19 | 20 | func NewWorld(width, height, cell_size float32) *World { 21 | cell_width := nextEqualyDividable(width, cell_size) 22 | cell_height := nextEqualyDividable(height, cell_size) 23 | numWidth := width / cell_width 24 | numHeight := height / cell_height 25 | 26 | //initialize grid 27 | grid := make([][]*Cell, int(numWidth)) 28 | for i := 0; i < int(numWidth); i++ { 29 | grid[i] = make([]*Cell, int(numHeight)) 30 | for j := 0; j < int(numHeight); j++ { 31 | grid[i][j] = &Cell{ 32 | x: float32(i) * cell_width, 33 | y: float32(j) * cell_height, 34 | width: cell_width, 35 | height: cell_height, 36 | } 37 | } 38 | } 39 | 40 | return &World{ 41 | width: width, 42 | height: height, 43 | cell_width: cell_width, 44 | cell_height: cell_height, 45 | numWidth: numWidth, 46 | numHeight: numHeight, 47 | grid: grid, 48 | } 49 | } 50 | 51 | func nextEqualyDividable(unit, size float32) float32 { 52 | for ; math.Mod(float64(unit), float64(size)) != 0; size++ { 53 | } 54 | return size 55 | } 56 | 57 | func (world *World) AddBody(collidable Collidable, name string, x, y, scale float32, points []float32) *Body { 58 | new_body := newBody(world, collidable, name, points) 59 | new_body.Move(x, y, 0, scale) 60 | return new_body 61 | } 62 | 63 | func (world *World) CellAt(x, y float32) *Cell { 64 | x = float32(math.Floor(float64(x / world.cell_width))) 65 | y = float32(math.Floor(float64(y / world.cell_height))) 66 | if x >= world.numWidth { 67 | x -= world.numWidth 68 | } 69 | if x < 0 { 70 | x = world.numWidth - 1 71 | } 72 | if y >= world.numHeight { 73 | y = 0 74 | } 75 | if y < 0 { 76 | y = world.numHeight - 1 77 | } 78 | return world.grid[int(x)][int(y)] 79 | } 80 | 81 | func (world *World) DrawGrid() { 82 | // This really sucks and isn't the best but it is for debugging so it shouldnt 83 | // be used all the time 84 | count := map[*Body]bool{} 85 | for _, row := range world.grid { 86 | for _, cell := range row { 87 | for _, body := range cell.bodies { 88 | count[body] = true 89 | } 90 | } 91 | } 92 | 93 | gfx.Print(fmt.Sprintf("physical objects: %v", len(count)), 0, 30) 94 | gfx.SetColor(100, 100, 100, 100) 95 | for i := world.cell_width; i < world.width; i += world.cell_width { 96 | gfx.Line(i, 0, i, world.height) 97 | } 98 | for i := world.cell_height; i < world.height; i += world.cell_height { 99 | gfx.Line(0, i, world.width, i) 100 | } 101 | gfx.SetColor(255, 255, 255, 255) 102 | } 103 | -------------------------------------------------------------------------------- /asteroids/game/player.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | "github.com/tanema/amore/keyboard" 6 | ) 7 | 8 | const ( 9 | playerAcc = 200 10 | playerMaxSpeed = 400 11 | playerRotationSpeed = 6 12 | playerFireRate = 0.40 13 | playerJetSize = 25 14 | playerJetWidth = 0.15 15 | ) 16 | 17 | type Player struct { 18 | *Sprite 19 | lastFire float32 20 | isAccelerating bool 21 | } 22 | 23 | func newPlayer() *Player { 24 | new_player := &Player{} 25 | new_player.Sprite = NewSprite(new_player, "ship", screenWidth/2, screenHeight/2, 1, 26 | []float32{ 27 | -5, 4, 28 | 0, -12, 29 | 5, 4, 30 | -5, 4, 31 | }, true) 32 | return new_player 33 | } 34 | 35 | func (player *Player) Update(dt float32) { 36 | player.isAccelerating = false 37 | 38 | if keyboard.IsDown(keyboard.KeyLeft) { 39 | player.vrot = -playerRotationSpeed 40 | } else if keyboard.IsDown(keyboard.KeyRight) { 41 | player.vrot = playerRotationSpeed 42 | } else { 43 | player.vrot = 0 44 | } 45 | 46 | if keyboard.IsDown(keyboard.KeyUp) { 47 | player.isAccelerating = true 48 | player.ay = -(playerAcc * cos(player.rot)) 49 | player.ax = playerAcc * sin(player.rot) 50 | } else { 51 | player.ax = 0 52 | player.ay = 0 53 | } 54 | 55 | player.lastFire += dt 56 | if keyboard.IsDown(keyboard.KeySpace) && player.lastFire > playerFireRate { 57 | addObject(newBullet(player.x, player.y, player.rot)) 58 | lazer.Play() 59 | player.lastFire = 0 60 | } 61 | 62 | if collisions := player.UpdateMovement(dt); len(collisions) > 0 { 63 | for _, c := range collisions { 64 | if c.Name == "asteroid" { 65 | player.Destroy(false) 66 | } 67 | } 68 | } 69 | 70 | // limit the ship's speed 71 | if sqrt(player.vx*player.vx+player.vy*player.vy) > playerMaxSpeed { 72 | player.vx *= 0.95 73 | player.vy *= 0.95 74 | } 75 | } 76 | 77 | func (player *Player) Draw() { 78 | player.Sprite.Draw() 79 | 80 | if player.isAccelerating { 81 | points := player.Sprite.body.GetPoints() 82 | gfx.PolyLine([]float32{ 83 | points[0] + ((points[4] - points[0]) * playerJetWidth), 84 | points[1] + ((points[5] - points[1]) * playerJetWidth), 85 | 86 | points[2] + (-sin(player.rot) * playerJetSize), 87 | points[3] + (cos(player.rot) * playerJetSize), 88 | 89 | points[4] + ((points[0] - points[4]) * playerJetWidth), 90 | points[5] + ((points[1] - points[5]) * playerJetWidth), 91 | }) 92 | } 93 | } 94 | 95 | func (player *Player) Destroy(force bool) { 96 | removeObject(player) 97 | player.Sprite.Destroy() 98 | if !force { 99 | bomb.Play() 100 | gameOver = true 101 | newExplosion(player.Sprite) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /asteroids/game/sprite.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | 6 | "github.com/tanema/amore-examples/asteroids/game/phys" 7 | ) 8 | 9 | type Sprite struct { 10 | name string 11 | body *phys.Body 12 | scale float32 13 | x, y, rot float32 14 | vx, vy, vrot float32 15 | ax, ay float32 16 | wraps bool 17 | } 18 | 19 | func NewSprite(collidable phys.Collidable, name string, x, y, scale float32, points []float32, wraps bool) *Sprite { 20 | new_sprite := &Sprite{ 21 | name: name, 22 | body: world.AddBody(collidable, name, x, y, scale, points), 23 | x: x, 24 | y: y, 25 | scale: scale, 26 | wraps: wraps, 27 | } 28 | return new_sprite 29 | } 30 | 31 | func (sprite *Sprite) UpdateMovement(delta float32) []*phys.Body { 32 | sprite.vx += sprite.ax * delta 33 | sprite.vy += sprite.ay * delta 34 | dx, dy, dr := sprite.vx*delta, sprite.vy*delta, sprite.vrot*delta 35 | sprite.x, sprite.y, sprite.rot = sprite.x+dx, sprite.y+dy, sprite.rot+dr 36 | collisions := sprite.body.Move(sprite.x, sprite.y, sprite.rot, sprite.scale) 37 | if sprite.wraps { 38 | if sprite.x > screenWidth { 39 | sprite.x = 0 40 | } else if sprite.x < 0 { 41 | sprite.x = screenWidth 42 | } 43 | if sprite.y > screenHeight { 44 | sprite.y = 0 45 | } else if sprite.y < 0 { 46 | sprite.y = screenHeight 47 | } 48 | } 49 | return collisions 50 | } 51 | 52 | func (sprite *Sprite) Draw() { 53 | if debug { 54 | sprite.body.Draw() 55 | } 56 | gfx.PolyLine(sprite.body.GetPoints()) 57 | } 58 | 59 | func (sprite *Sprite) GetPoints() []float32 { 60 | return sprite.body.GetPoints() 61 | } 62 | 63 | func (sprite *Sprite) Destroy() { 64 | sprite.body.Remove() 65 | } 66 | -------------------------------------------------------------------------------- /asteroids/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tanema/amore-examples/asteroids/game" 7 | 8 | "github.com/tanema/amore" 9 | "github.com/tanema/amore/gfx" 10 | "github.com/tanema/amore/keyboard" 11 | "github.com/tanema/amore/timer" 12 | ) 13 | 14 | func main() { 15 | amore.OnLoad = load 16 | amore.Start(update, draw) 17 | } 18 | 19 | func load() { 20 | game.New() 21 | } 22 | 23 | func update(deltaTime float32) { 24 | if keyboard.IsDown(keyboard.KeyEscape) { 25 | amore.Quit() 26 | } 27 | game.Update(deltaTime) 28 | } 29 | 30 | func draw() { 31 | game.Draw() 32 | gfx.Print(fmt.Sprintf("fps: %v", timer.GetFPS())) 33 | } 34 | -------------------------------------------------------------------------------- /audio_player/assets/audio/test.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/audio/test.flac -------------------------------------------------------------------------------- /audio_player/assets/audio/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/audio/test.mp3 -------------------------------------------------------------------------------- /audio_player/assets/audio/test.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/audio/test.ogg -------------------------------------------------------------------------------- /audio_player/assets/audio/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/audio/test.wav -------------------------------------------------------------------------------- /audio_player/assets/images/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/back.png -------------------------------------------------------------------------------- /audio_player/assets/images/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/forward.png -------------------------------------------------------------------------------- /audio_player/assets/images/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/next.png -------------------------------------------------------------------------------- /audio_player/assets/images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/pause.png -------------------------------------------------------------------------------- /audio_player/assets/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/play.png -------------------------------------------------------------------------------- /audio_player/assets/images/rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/rewind.png -------------------------------------------------------------------------------- /audio_player/assets/images/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/audio_player/assets/images/stop.png -------------------------------------------------------------------------------- /audio_player/conf.toml: -------------------------------------------------------------------------------- 1 | title = "amore player" # The window title (string) 2 | width = 384 # The window width (number) 3 | height = 300 # The window height (number) 4 | resizable = false # Let the window be user-resizable (boolean) 5 | vsync = true # Enable vertical sync (boolean) 6 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 7 | srgb = true # Enable sRGB gamma correction when drawing to the screen (boolean) 8 | centered = true # Center the window in the display 9 | -------------------------------------------------------------------------------- /audio_player/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/tanema/amore" 8 | "github.com/tanema/amore/audio" 9 | "github.com/tanema/amore/gfx" 10 | 11 | "github.com/tanema/amore-examples/audio_player/ui" 12 | ) 13 | 14 | var ( 15 | elements = []ui.Element{} 16 | playing = false 17 | currentTrack = 0 18 | trackNames = []string{"audio/test.wav", "audio/test.mp3", "audio/test.ogg", "audio/test.flac"} 19 | tracks = []*audio.Source{} 20 | ) 21 | 22 | const buttonSize float32 = 64 23 | 24 | func main() { 25 | elements = append(elements, 26 | ui.NewButton(0*buttonSize, 0, buttonSize, buttonSize, ui.BackImg, ui.Clear, func(button *ui.Button) { 27 | tracks[currentTrack].Stop() 28 | currentTrack = (currentTrack - 1 + len(tracks)) % len(tracks) 29 | tracks[currentTrack].Play() 30 | }), 31 | ui.NewButton(1*buttonSize, 0, buttonSize, buttonSize, ui.RewindImg, ui.Clear, func(button *ui.Button) { 32 | tracks[currentTrack].Seek(tracks[currentTrack].Tell() - (5 * time.Second)) 33 | }), 34 | ui.NewButton(2*buttonSize, 0, buttonSize, buttonSize, ui.PlayImg, ui.Clear, func(button *ui.Button) { 35 | if tracks[currentTrack].IsPlaying() { 36 | tracks[currentTrack].Pause() 37 | } else { 38 | tracks[currentTrack].Play() 39 | } 40 | 41 | if tracks[currentTrack].IsPlaying() { 42 | button.SetImage(ui.PauseImg) 43 | } else { 44 | button.SetImage(ui.PlayImg) 45 | } 46 | }), 47 | ui.NewButton(3*buttonSize, 0, buttonSize, buttonSize, ui.StopImg, ui.Clear, func(button *ui.Button) { 48 | tracks[currentTrack].Stop() 49 | }), 50 | ui.NewButton(4*buttonSize, 0, buttonSize, buttonSize, ui.ForwardImg, ui.Clear, func(button *ui.Button) { 51 | tracks[currentTrack].Seek(tracks[currentTrack].Tell() + (5 * time.Second)) 52 | }), 53 | ui.NewButton(5*buttonSize, 0, buttonSize, buttonSize, ui.NextImg, ui.Clear, func(button *ui.Button) { 54 | tracks[currentTrack].Stop() 55 | currentTrack = (currentTrack + 1) % len(tracks) 56 | tracks[currentTrack].Play() 57 | }), 58 | ) 59 | amore.OnLoad = load 60 | amore.Start(update, draw) 61 | } 62 | 63 | func load() { 64 | for _, name := range trackNames { 65 | source, err := audio.NewSource(name, false) 66 | if err != nil { 67 | panic(err) 68 | } 69 | tracks = append(tracks, source) 70 | } 71 | } 72 | 73 | func update(deltaTime float32) { 74 | for _, element := range elements { 75 | element.Update(deltaTime) 76 | } 77 | } 78 | 79 | func draw() { 80 | gfx.SetBackgroundColor(255, 255, 255, 255) 81 | for _, element := range elements { 82 | element.Draw() 83 | } 84 | gfx.SetColorC(ui.Black) 85 | gfx.Print(fmt.Sprintf("%v", tracks[currentTrack].GetDuration()), 0, 64) 86 | gfx.Print(fmt.Sprintf("%v", tracks[currentTrack].Tell()), 0, 128) 87 | gfx.Print(trackNames[currentTrack], 200, 128) 88 | } 89 | -------------------------------------------------------------------------------- /audio_player/ui/assets.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | var ( 8 | PlayImg, _ = gfx.NewImage("images/play.png") 9 | PauseImg, _ = gfx.NewImage("images/pause.png") 10 | StopImg, _ = gfx.NewImage("images/stop.png") 11 | ForwardImg, _ = gfx.NewImage("images/forward.png") 12 | RewindImg, _ = gfx.NewImage("images/rewind.png") 13 | NextImg, _ = gfx.NewImage("images/next.png") 14 | BackImg, _ = gfx.NewImage("images/back.png") 15 | 16 | Black = gfx.NewColor(0, 0, 0, 255) 17 | Clear = gfx.NewColor(0, 0, 0, 0) 18 | ) 19 | -------------------------------------------------------------------------------- /audio_player/ui/button.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | "github.com/tanema/amore/mouse" 6 | ) 7 | 8 | // Button is a clickable rectangle 9 | type Button struct { 10 | x, y, w, h float32 11 | image *gfx.Image 12 | imageW, imageH float32 13 | color *gfx.Color 14 | mouseDown bool 15 | clickDown bool 16 | onClick func(*Button) 17 | } 18 | 19 | // NewButton builds and returns a new button 20 | func NewButton(x, y, w, h float32, image *gfx.Image, color *gfx.Color, onClick func(*Button)) *Button { 21 | button := &Button{ 22 | x: x, y: y, w: w, h: h, 23 | color: color, 24 | onClick: onClick, 25 | } 26 | button.SetImage(image) 27 | return button 28 | } 29 | 30 | // SetImage sets the image for the button 31 | func (button *Button) SetImage(image *gfx.Image) { 32 | button.image = image 33 | button.imageW = button.w / float32(image.GetWidth()) 34 | button.imageH = button.h / float32(image.GetHeight()) 35 | } 36 | 37 | // Update checks for clicks 38 | func (button *Button) Update(dt float32) { 39 | x, y := mouse.GetPosition() 40 | isDown := mouse.IsDown(mouse.LeftButton) 41 | if isDown && !button.mouseDown && button.containsPoint(x, y) { 42 | button.clickDown = true 43 | } else if !isDown { 44 | if button.mouseDown && button.clickDown && button.containsPoint(x, y) { 45 | button.onClick(button) 46 | } 47 | button.clickDown = false 48 | } 49 | button.mouseDown = isDown 50 | } 51 | 52 | // Draw draws the button 53 | func (button *Button) Draw() { 54 | gfx.SetColorC(button.color) 55 | gfx.Rect(gfx.LINE, button.x, button.y, button.w, button.h) 56 | gfx.SetColor(255, 255, 255, 255) 57 | gfx.Draw(button.image, button.x, button.y, 0, button.imageW, button.imageH) 58 | } 59 | 60 | func (button *Button) containsPoint(px, py float32) bool { 61 | return button.x < px && 62 | button.x+button.w > px && 63 | button.y < py && 64 | button.y+button.h > py 65 | } 66 | -------------------------------------------------------------------------------- /audio_player/ui/element.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | // Element is the general interface for all elements to be used 4 | type Element interface { 5 | Update(float32) 6 | Draw() 7 | } 8 | -------------------------------------------------------------------------------- /gocraftmini/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Go Craft Mini" # The window title (string) 2 | width = 800 # The window width (number) 3 | height = 800 # The window height (number) 4 | borderless = false # Remove all border visuals from the window (boolean) 5 | vsync = true # Enable vertical sync (boolean) 6 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 7 | highdpi = true # Enable high-dpi mode for the window on a Retina display (boolean) 8 | srgb = true # Enable sRGB gamma correction when drawing to the screen (boolean) 9 | centered = true # Center the window in the display 10 | resizable = true 11 | -------------------------------------------------------------------------------- /gocraftmini/game/camera.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/tanema/amore/gfx" 4 | 5 | // Camera describes the views 6 | type Camera struct { 7 | x, y int 8 | size float32 9 | half float32 10 | visible int 11 | } 12 | 13 | func newCamera(visible int) *Camera { 14 | return &Camera{visible: visible} 15 | } 16 | 17 | func (camera *Camera) update() { 18 | camera.size = gfx.GetWidth() 19 | camera.half = camera.size / 2 20 | } 21 | 22 | func (camera *Camera) getCellSize() float32 { 23 | return camera.half / (2*float32(camera.visible) + 1) 24 | } 25 | 26 | func (camera *Camera) lookAt(x, y float32) { 27 | camera.x, camera.y = int(x), int(y) 28 | } 29 | 30 | func (camera *Camera) forVisible(world *World, fn func(cell *Cell, x, y, dx, dy float32)) { 31 | for distX := -camera.visible; distX <= camera.visible; distX++ { 32 | for distY := -camera.visible; distY <= camera.visible; distY++ { 33 | x, y := camera.x+distX, camera.y+distY 34 | cell := world.getCell(x, y) 35 | if cell != nil { 36 | fn(cell, float32(x), float32(y), float32(distX), float32(distY)) 37 | } 38 | } 39 | } 40 | } 41 | 42 | func (camera *Camera) worldToScreen(x, y, z float32, relative bool) (float32, float32) { 43 | if relative { 44 | x, y = x-float32(camera.x), y-float32(camera.y) 45 | } 46 | cellSize := camera.getCellSize() 47 | return camera.half + (x-y)*cellSize, camera.half*3/2 + (x+y-(max(z, 0)*2))*cellSize/2 48 | } 49 | -------------------------------------------------------------------------------- /gocraftmini/game/cell.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | // Cell describes one position on the grid 4 | type Cell struct { 5 | biom *Voxel 6 | dirt *Voxel 7 | } 8 | 9 | func newCell(x, y int) *Cell { 10 | return &Cell{ 11 | biom: newVoxel(float32(x), float32(y), -9, 1, 0, 0, 0, 0, true), 12 | dirt: newVoxel(float32(x), float32(y), 0, 1, 0, 0.083, worldSaturation, 0.2, true), 13 | } 14 | } 15 | 16 | func (cell *Cell) getZ() float32 { 17 | return cell.biom.z 18 | } 19 | 20 | func (cell *Cell) setZ(newZ float32) { 21 | cell.biom.z, cell.dirt.height = newZ, newZ 22 | var h, s, l float32 23 | if cell.biom.z < 0 { // water, display depth of water, deeper is darker 24 | h, s, l = (180-cell.biom.z*20/3)/360, 0.99, 0.5 25 | } else if cell.biom.z == 0 { // sand 26 | h, s, l = 0.16, worldSaturation, 0.6 27 | } else if cell.biom.z > 15 { // snow 28 | h, s, l = 0, 0, 0.99 29 | } else { // grass 30 | h, s, l = 0.33, worldSaturation, 0.3 31 | } 32 | cell.biom.h, cell.biom.s, cell.biom.l = h, s, l 33 | } 34 | 35 | func (cell *Cell) update(world *World, x, y, distX, distY float32) { 36 | cell.dirt.update(world, distX, distY) 37 | cell.biom.update(world, distX, distY) 38 | cell.biom.height = 0 39 | if cell.biom.z < 0 { // biom is water 40 | // waves in the water 41 | cell.biom.height = (1.4 + sin(world.timeOfDay*25+cell.biom.y)) / 6 // waves 42 | // reflection off of the water 43 | sunx := float32(world.camera.x) + world.sky.sun.x 44 | suny := float32(world.camera.y) + world.sky.sun.y 45 | p := (sunx - suny - x + y) 46 | cell.biom.shine += 25 * exp(-p*p) 47 | } 48 | } 49 | 50 | func (cell *Cell) draw(camera *Camera, x, y float32) { 51 | if cell.dirt.height > 0 { 52 | cell.dirt.draw(camera, x, y) 53 | } 54 | cell.biom.draw(camera, x, y) 55 | } 56 | -------------------------------------------------------------------------------- /gocraftmini/game/generator.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | func generateTerrain(size, iterations int, smooth bool) [][]*Cell { 4 | terrain := make([][]*Cell, size) 5 | for x := 0; x < size; x++ { 6 | terrain[x] = make([]*Cell, size) 7 | for y := 0; y < size; y++ { 8 | terrain[x][y] = newCell(x, y) 9 | } 10 | } 11 | 12 | for ; iterations >= 0; iterations-- { 13 | px, py, r := randRange(0, size), randRange(0, size), randRange(10, 40) 14 | for x := -r; x <= r; x++ { 15 | for y := -r; y <= r; y++ { 16 | // Increase altitude of cell cell with "bell" function factor 17 | cell := terrain[(px+x+size)%size][(py-y+size)%size] 18 | cell.setZ(cell.getZ() + 5*exp(-(float32(x)*float32(x)+float32(y)*float32(y))/(float32(r)*2))) 19 | } 20 | } 21 | } 22 | 23 | if !smooth { 24 | for x := 0; x < size; x++ { 25 | for y := 0; y < size; y++ { 26 | terrain[x][y].setZ(floor(terrain[x][y].getZ())) 27 | } 28 | } 29 | } 30 | 31 | return terrain 32 | } 33 | -------------------------------------------------------------------------------- /gocraftmini/game/math.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func max(x, y float32) float32 { 10 | return float32(math.Max(float64(x), float64(y))) 11 | } 12 | 13 | func pow(x, y float32) float32 { 14 | return float32(math.Pow(float64(x), float64(y))) 15 | } 16 | 17 | func abs(x float32) float32 { 18 | return float32(math.Abs(float64(x))) 19 | } 20 | 21 | func exp(x float32) float32 { 22 | return float32(math.Exp(float64(x))) 23 | } 24 | 25 | func sin(x float32) float32 { 26 | return float32(math.Sin(float64(x))) 27 | } 28 | 29 | func cos(x float32) float32 { 30 | return float32(math.Cos(float64(x))) 31 | } 32 | 33 | func floor(x float32) float32 { 34 | return float32(math.Floor(float64(x))) 35 | } 36 | 37 | func randRange(min, max int) int { 38 | rand.Seed(time.Now().UTC().UnixNano()) 39 | return rand.Intn(max-min+1) + min 40 | } 41 | -------------------------------------------------------------------------------- /gocraftmini/game/sky.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/tanema/amore/gfx" 4 | 5 | // Sky is the sky! 6 | type Sky struct { 7 | skybox *Voxel 8 | sun *Voxel 9 | } 10 | 11 | func newSky() *Sky { 12 | return &Sky{ 13 | skybox: newVoxel(0, 0, 0, 0, 0, 0.55, worldSaturation, 0.5, false), 14 | sun: newVoxel(0, 0, 0, sunSize, sunSize, 0.16, 0, 1, false), 15 | } 16 | } 17 | 18 | func (sky *Sky) update(world *World) { 19 | sky.skybox.width, sky.skybox.height = gfx.GetWidth()/2, gfx.GetHeight()/2 20 | sky.skybox.update(world, float32(world.size), float32(world.size)) 21 | if world.sin > 0 { // if daytime, sun is larger than moon 22 | sky.sun.width, sky.sun.height = sunSize, sunSize 23 | sky.sun.y = -1 * float32(world.camera.visible) * cos(world.timeOfDay) 24 | sky.sun.z = abs(world.sin * float32(world.camera.visible) * 3.2) 25 | } else { 26 | sky.sun.width, sky.sun.height = moonSize, moonSize 27 | sky.sun.y = -1 * -float32(world.camera.visible) * cos(world.timeOfDay) 28 | sky.sun.z = abs(world.sin * -float32(world.camera.visible) * 3.2) 29 | } 30 | } 31 | 32 | func (sky *Sky) draw(world *World) { 33 | sky.skybox.draw(world.camera, sky.skybox.x, sky.skybox.y) 34 | sky.sun.draw(world.camera, sky.sun.x, sky.sun.y) 35 | } 36 | -------------------------------------------------------------------------------- /gocraftmini/game/voxel.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | // Voxel is the main drawn box 8 | type Voxel struct { 9 | x, y, z float32 10 | width, height float32 11 | h, s, l float32 12 | shine float32 13 | relative bool 14 | } 15 | 16 | func newVoxel(x, y, z, width, height, h, s, l float32, relative bool) *Voxel { 17 | return &Voxel{ 18 | x: x, y: y, z: z, 19 | width: width, height: height, 20 | h: h, s: s, l: l, 21 | relative: relative, 22 | shine: 1, 23 | } 24 | } 25 | 26 | func (voxel *Voxel) update(world *World, x, y float32) { 27 | // starting luminance based on distance from player center 28 | voxel.shine = baseShine + exp(-(x*x+y*y)/playerShineRange*2) 29 | if world.sin > 0 { // if, daytime add sunlight 30 | voxel.shine += world.sin * (2 - world.sin) * (1 - voxel.shine) 31 | } 32 | } 33 | 34 | func (voxel *Voxel) draw(camera *Camera, px, py float32) { 35 | cellSize := float32(1) 36 | if voxel.relative { 37 | cellSize = camera.getCellSize() 38 | } 39 | 40 | gfx.SetColorC(gfx.NewHSLColor(voxel.h, voxel.s, pow(voxel.l, 1/voxel.shine), 1)) 41 | x, y := camera.worldToScreen(px, py, voxel.z, voxel.relative) 42 | width, height := voxel.width*cellSize, voxel.height*cellSize 43 | coords := []float32{ 44 | x + width, y, x, y + width/2, 45 | x - width, y, 46 | x - width, y - height, 47 | x, y - height - width/2, 48 | x + width, y - height, 49 | } 50 | gfx.Polygon(gfx.FILL, coords) 51 | } 52 | -------------------------------------------------------------------------------- /gocraftmini/game/world.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore" 5 | "github.com/tanema/amore/keyboard" 6 | ) 7 | 8 | // World encapsulates the whole environment 9 | type World struct { 10 | size int 11 | terrain [][]*Cell 12 | camera *Camera 13 | timeOfDay float32 14 | sin float32 15 | sky *Sky 16 | player *Voxel 17 | } 18 | 19 | const ( 20 | worldSaturation float32 = 0.99 21 | baseShine float32 = 0.4 22 | playerShineRange float32 = 25 23 | sunSize float32 = 60 24 | moonSize float32 = 30 25 | ) 26 | 27 | // NewWorld generates a new world to render 28 | func NewWorld(worldSize, visible, iterations int, smooth bool) *World { 29 | return &World{ 30 | size: worldSize, 31 | terrain: generateTerrain(worldSize, iterations, smooth), 32 | camera: newCamera(visible), 33 | sky: newSky(), 34 | player: newVoxel(0, 0, 0, 1, 1, 0, worldSaturation, 0.5, true), 35 | } 36 | } 37 | 38 | func (world *World) getCell(x, y int) *Cell { 39 | // if x >= len(world.terrain) || x < 0 || y >= len(world.terrain[x]) || y < 0 { 40 | // return nil 41 | // } 42 | i := (x + world.size) % world.size 43 | j := (y + world.size) % world.size 44 | return world.terrain[i][j] 45 | } 46 | 47 | // Update updates a step in the world 48 | func (world *World) Update(dt float32) { 49 | world.camera.update() 50 | world.updateInput() 51 | world.timeOfDay += dt / 10 52 | world.sin = sin(world.timeOfDay) 53 | world.sky.update(world) 54 | world.camera.forVisible(world, func(cell *Cell, x, y, distX, distY float32) { 55 | cell.update(world, x, y, distX, distY) 56 | }) 57 | } 58 | 59 | // Draw draws one frame 60 | func (world *World) Draw() { 61 | world.sky.draw(world) 62 | world.camera.forVisible(world, func(cell *Cell, x, y, distX, distY float32) { 63 | cell.draw(world.camera, x, y) 64 | if x == world.player.x && y == world.player.y { 65 | world.player.draw(world.camera, x, y) 66 | } 67 | }) 68 | } 69 | 70 | func (world *World) updateInput() { 71 | if keyboard.IsDown(keyboard.KeyEscape) { 72 | amore.Quit() 73 | } 74 | 75 | if keyboard.IsDown(keyboard.KeyLeft) { 76 | world.player.x-- 77 | } else if keyboard.IsDown(keyboard.KeyRight) { 78 | world.player.x++ 79 | } 80 | if keyboard.IsDown(keyboard.KeyUp) { 81 | world.player.y-- 82 | } else if keyboard.IsDown(keyboard.KeyDown) { 83 | world.player.y++ 84 | } 85 | 86 | world.player.x = float32((int(world.player.x) + world.size) % world.size) 87 | world.player.y = float32((int(world.player.y) + world.size) % world.size) 88 | world.camera.lookAt(world.player.x, world.player.y) 89 | cell := world.getCell(int(world.player.x), int(world.player.y)) 90 | 91 | if keyboard.IsDown(keyboard.KeySpace) { 92 | cell.setZ(cell.getZ() + 1) 93 | } else if keyboard.IsDown(keyboard.KeyC) { 94 | cell.setZ(cell.getZ() - 1) 95 | } 96 | 97 | world.player.z = cell.getZ() 98 | 99 | if keyboard.IsDown(keyboard.KeyV) { 100 | world.camera.visible++ 101 | } else if keyboard.IsDown(keyboard.KeyB) { 102 | world.camera.visible-- 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /gocraftmini/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tanema/amore" 5 | 6 | "github.com/tanema/gocraftmini/game" 7 | ) 8 | 9 | func main() { 10 | world := game.NewWorld(149, 20, 300, false) 11 | amore.Start(world.Update, world.Draw) 12 | } 13 | -------------------------------------------------------------------------------- /physics/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Amore with Box2D" # The window title (string) 2 | width = 800 # The window width (number) 3 | height = 600 # The window height (number) 4 | vsync = true # Enable vertical sync (boolean) 5 | centered = true # Center the window in the display 6 | -------------------------------------------------------------------------------- /physics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | 8 | b2d "github.com/neguse/go-box2d-lite/box2dlite" 9 | "github.com/tanema/amore" 10 | "github.com/tanema/amore/gfx" 11 | "github.com/tanema/amore/keyboard" 12 | "github.com/tanema/amore/timer" 13 | ) 14 | 15 | const timeStep = 1.0 / 60 16 | 17 | var ( 18 | gravity = b2d.Vec2{0.0, -10.0} 19 | iterations = 10 20 | world = b2d.NewWorld(gravity, iterations) 21 | title = "" 22 | ) 23 | 24 | func main() { 25 | Demo1() 26 | amore.Start(update, draw) 27 | } 28 | 29 | func update(deltaTime float32) { 30 | if keyboard.IsDown(keyboard.KeyEscape) { 31 | amore.Quit() 32 | } 33 | if keyboard.IsDown(keyboard.Key1) { 34 | Demo1() 35 | } else if keyboard.IsDown(keyboard.Key2) { 36 | Demo2() 37 | } else if keyboard.IsDown(keyboard.Key3) { 38 | Demo3() 39 | } else if keyboard.IsDown(keyboard.Key4) { 40 | Demo4() 41 | } else if keyboard.IsDown(keyboard.Key5) { 42 | Demo5() 43 | } else if keyboard.IsDown(keyboard.Key6) { 44 | Demo6() 45 | } else if keyboard.IsDown(keyboard.Key7) { 46 | Demo7() 47 | } else if keyboard.IsDown(keyboard.Key8) { 48 | Demo8() 49 | } else if keyboard.IsDown(keyboard.Key9) { 50 | Demo9() 51 | } 52 | world.Step(float64(deltaTime)) 53 | } 54 | 55 | func renderBody(b *b2d.Body) { 56 | R := b2d.Mat22ByAngle(b.Rotation) 57 | x := b.Position 58 | h := b2d.MulSV(0.5, b.Width) 59 | 60 | o := b2d.Vec2{400, 400} 61 | S := b2d.Mat22{b2d.Vec2{20.0, 0.0}, b2d.Vec2{0.0, -20.0}} 62 | 63 | v1 := o.Add(S.MulV(x.Add(R.MulV(b2d.Vec2{-h.X, -h.Y})))) 64 | v2 := o.Add(S.MulV(x.Add(R.MulV(b2d.Vec2{h.X, -h.Y})))) 65 | v3 := o.Add(S.MulV(x.Add(R.MulV(b2d.Vec2{h.X, h.Y})))) 66 | v4 := o.Add(S.MulV(x.Add(R.MulV(b2d.Vec2{-h.X, h.Y})))) 67 | 68 | gfx.Line( 69 | float32(v1.X), float32(v1.Y), 70 | float32(v2.X), float32(v2.Y), 71 | float32(v3.X), float32(v3.Y), 72 | float32(v4.X), float32(v4.Y), 73 | float32(v1.X), float32(v1.Y)) 74 | } 75 | 76 | func renderJoint(j *b2d.Joint) { 77 | b1 := j.Body1 78 | b2 := j.Body2 79 | 80 | R1 := b2d.Mat22ByAngle(b1.Rotation) 81 | R2 := b2d.Mat22ByAngle(b2.Rotation) 82 | 83 | x1 := b1.Position 84 | p1 := x1.Add(R1.MulV(j.LocalAnchor1)) 85 | 86 | x2 := b2.Position 87 | p2 := x2.Add(R2.MulV(j.LocalAnchor2)) 88 | 89 | o := b2d.Vec2{400, 400} 90 | S := b2d.Mat22{b2d.Vec2{20.0, 0.0}, b2d.Vec2{0.0, -20.0}} 91 | 92 | x1 = o.Add(S.MulV(x1)) 93 | p1 = o.Add(S.MulV(p1)) 94 | x2 = o.Add(S.MulV(x2)) 95 | p2 = o.Add(S.MulV(p2)) 96 | 97 | gfx.Line(float32(x1.X), float32(x1.Y), float32(p1.X), float32(p1.Y)) 98 | gfx.Line(float32(x2.X), float32(x2.Y), float32(p2.X), float32(p2.Y)) 99 | } 100 | 101 | func draw() { 102 | gfx.SetLineWidth(2) 103 | for _, b := range world.Bodies { 104 | renderBody(b) 105 | } 106 | for _, j := range world.Joints { 107 | renderJoint(j) 108 | } 109 | gfx.Print(fmt.Sprintf("fps: %v", timer.GetFPS())) 110 | gfx.Print("Press numbers 1 -> 9 to see different demos", 0, 15) 111 | gfx.Print(title, 100, 100) 112 | } 113 | 114 | // Single box 115 | func Demo1() { 116 | title = "Single Box" 117 | world.Clear() 118 | 119 | var b1, b2 b2d.Body 120 | 121 | b1.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 122 | b1.Position = b2d.Vec2{0.0, -0.5 * b1.Width.Y} 123 | world.AddBody(&b1) 124 | 125 | b2.Set(&b2d.Vec2{1.0, 1.0}, 200.0) 126 | b2.Position = b2d.Vec2{0.0, 4.0} 127 | world.AddBody(&b2) 128 | } 129 | 130 | // A simple pendulum 131 | func Demo2() { 132 | title = "Single Pendulum" 133 | world.Clear() 134 | 135 | var b2, b1 b2d.Body 136 | var j b2d.Joint 137 | 138 | b1.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 139 | b1.Friction = 0.2 140 | b1.Position = b2d.Vec2{0.0, -0.5 * b1.Width.Y} 141 | b1.Rotation = 0.0 142 | world.AddBody(&b1) 143 | 144 | b2.Set(&b2d.Vec2{1.0, 1.0}, 100.0) 145 | b2.Friction = 0.2 146 | b2.Position = b2d.Vec2{9.0, 11.0} 147 | b2.Rotation = 0.0 148 | world.AddBody(&b2) 149 | 150 | j.Set(&b1, &b2, &b2d.Vec2{0.0, 11.0}) 151 | world.AddJoint(&j) 152 | } 153 | 154 | // Varying friction coefficients 155 | func Demo3() { 156 | title = "Varying friction coefficients" 157 | world.Clear() 158 | 159 | { 160 | var b b2d.Body 161 | b.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 162 | b.Position = b2d.Vec2{0.0, -0.5 * b.Width.Y} 163 | world.AddBody(&b) 164 | } 165 | 166 | { 167 | var b b2d.Body 168 | b.Set(&b2d.Vec2{13.0, 0.25}, math.MaxFloat64) 169 | b.Position = b2d.Vec2{-2.0, 11.0} 170 | b.Rotation = -0.25 171 | world.AddBody(&b) 172 | } 173 | 174 | { 175 | var b b2d.Body 176 | b.Set(&b2d.Vec2{0.25, 1.0}, math.MaxFloat64) 177 | b.Position = b2d.Vec2{5.25, 9.5} 178 | world.AddBody(&b) 179 | } 180 | 181 | { 182 | var b b2d.Body 183 | b.Set(&b2d.Vec2{13.0, 0.25}, math.MaxFloat64) 184 | b.Position = b2d.Vec2{2.0, 7.0} 185 | b.Rotation = 0.25 186 | world.AddBody(&b) 187 | } 188 | 189 | { 190 | var b b2d.Body 191 | b.Set(&b2d.Vec2{0.25, 1.0}, math.MaxFloat64) 192 | b.Position = b2d.Vec2{-5.25, 5.5} 193 | world.AddBody(&b) 194 | } 195 | 196 | frictions := []float64{0.75, 0.5, 0.35, 0.1, 0.0} 197 | for i := 0; i < 5; i++ { 198 | var b b2d.Body 199 | b.Set(&b2d.Vec2{0.5, 0.5}, 25.0) 200 | b.Friction = frictions[i] 201 | b.Position = b2d.Vec2{-7.5 + 2.0*float64(i), 14.0} 202 | world.AddBody(&b) 203 | } 204 | 205 | } 206 | 207 | // A vertical stack 208 | func Demo4() { 209 | title = "A vertical stack" 210 | world.Clear() 211 | 212 | { 213 | var b b2d.Body 214 | b.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 215 | b.Friction = 0.2 216 | b.Position = b2d.Vec2{0.0, -0.5 * b.Width.Y} 217 | b.Rotation = 0.0 218 | world.AddBody(&b) 219 | } 220 | 221 | for i := 0; i < 10; i++ { 222 | var b b2d.Body 223 | b.Set(&b2d.Vec2{1.0, 1.0}, 1.0) 224 | b.Friction = 0.2 225 | x := rand.Float64()*0.2 - 0.1 226 | b.Position = b2d.Vec2{x, 0.51 + 1.05*float64(i)} 227 | world.AddBody(&b) 228 | } 229 | 230 | } 231 | 232 | // A pyramid 233 | func Demo5() { 234 | title = "A pyramid" 235 | world.Clear() 236 | 237 | { 238 | var b b2d.Body 239 | b.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 240 | b.Friction = 0.2 241 | b.Position = b2d.Vec2{0.0, -0.5 * b.Width.Y} 242 | b.Rotation = 0.0 243 | world.AddBody(&b) 244 | } 245 | 246 | x := b2d.Vec2{-6.0, 0.75} 247 | 248 | for i := 0; i < 12; i++ { 249 | y := x 250 | for j := i; j < 12; j++ { 251 | var b b2d.Body 252 | b.Set(&b2d.Vec2{1.0, 1.0}, 10.0) 253 | b.Friction = 0.2 254 | b.Position = y 255 | world.AddBody(&b) 256 | 257 | y = y.Add(b2d.Vec2{1.125, 0.0}) 258 | } 259 | 260 | x = x.Add(b2d.Vec2{0.5625, 2.0}) 261 | } 262 | } 263 | 264 | // A teeter 265 | func Demo6() { 266 | title = "A teeter" 267 | world.Clear() 268 | 269 | var b1, b2, b3, b4, b5 b2d.Body 270 | b1.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 271 | b1.Position = b2d.Vec2{0.0, -0.5 * b1.Width.Y} 272 | world.AddBody(&b1) 273 | 274 | b2.Set(&b2d.Vec2{12.0, 0.25}, 100) 275 | b2.Position = b2d.Vec2{0.0, 1.0} 276 | world.AddBody(&b2) 277 | 278 | b3.Set(&b2d.Vec2{0.5, 0.5}, 25.0) 279 | b3.Position = b2d.Vec2{-5.0, 2.0} 280 | world.AddBody(&b3) 281 | 282 | b4.Set(&b2d.Vec2{0.5, 0.5}, 25.0) 283 | b4.Position = b2d.Vec2{-5.5, 2.0} 284 | world.AddBody(&b4) 285 | 286 | b5.Set(&b2d.Vec2{1.0, 1.0}, 100) 287 | b5.Position = b2d.Vec2{5.5, 15.0} 288 | world.AddBody(&b5) 289 | 290 | { 291 | var j b2d.Joint 292 | j.Set(&b1, &b2, &b2d.Vec2{0.0, 1.0}) 293 | world.AddJoint(&j) 294 | } 295 | 296 | } 297 | 298 | // A suspension bridge 299 | func Demo7() { 300 | title = "A suspension bridge" 301 | world.Clear() 302 | 303 | var ba []*b2d.Body 304 | 305 | { 306 | var b b2d.Body 307 | b.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 308 | b.Friction = 0.2 309 | b.Position = b2d.Vec2{0.0, -0.5 * b.Width.Y} 310 | b.Rotation = 0.0 311 | world.AddBody(&b) 312 | ba = append(ba, &b) 313 | } 314 | 315 | const numPlunks = 15 316 | const mass = 50.0 317 | 318 | for i := 0; i < numPlunks; i++ { 319 | var b b2d.Body 320 | b.Set(&b2d.Vec2{1.0, 0.25}, mass) 321 | b.Friction = 0.2 322 | b.Position = b2d.Vec2{-8.5 + 1.25*float64(i), 5.0} 323 | world.AddBody(&b) 324 | ba = append(ba, &b) 325 | } 326 | 327 | // Tuning 328 | const frequencyHz = 2.0 329 | const dampingRatio = 0.7 330 | 331 | // frequency in radians 332 | const omega = 2.0 * math.Pi * frequencyHz 333 | 334 | // damping coefficient 335 | const d = 2.0 * mass * dampingRatio * omega 336 | 337 | // spring stifness 338 | const k = mass * omega * omega 339 | 340 | // magic formulas 341 | const softness = 1.0 / (d + timeStep*k) 342 | const biasFactor = timeStep * k / (d + timeStep*k) 343 | 344 | for i := 0; i <= numPlunks; i++ { 345 | var j b2d.Joint 346 | j.Set(ba[i], ba[(i+1)%(numPlunks+1)], &b2d.Vec2{-9.125 + 1.25*float64(i), 5.0}) 347 | j.Softness = softness 348 | j.BiasFactor = biasFactor 349 | world.AddJoint(&j) 350 | } 351 | 352 | } 353 | 354 | // Dominos 355 | func Demo8() { 356 | title = "Dominos" 357 | world.Clear() 358 | 359 | var b1 b2d.Body 360 | b1.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 361 | b1.Position = b2d.Vec2{0.0, -0.5 * b1.Width.Y} 362 | world.AddBody(&b1) 363 | 364 | { 365 | var b b2d.Body 366 | b.Set(&b2d.Vec2{12.0, 0.5}, math.MaxFloat64) 367 | b.Position = b2d.Vec2{-1.5, 10.0} 368 | world.AddBody(&b) 369 | } 370 | 371 | for i := 0; i < 10; i++ { 372 | var b b2d.Body 373 | b.Set(&b2d.Vec2{0.2, 2.0}, 10.0) 374 | b.Position = b2d.Vec2{-6.0 + 1.0*float64(i), 11.25} 375 | b.Friction = 0.1 376 | world.AddBody(&b) 377 | } 378 | 379 | { 380 | var b b2d.Body 381 | b.Set(&b2d.Vec2{14.0, 0.5}, math.MaxFloat64) 382 | b.Position = b2d.Vec2{1.0, 6.0} 383 | b.Rotation = 0.3 384 | world.AddBody(&b) 385 | } 386 | 387 | var b2 b2d.Body 388 | b2.Set(&b2d.Vec2{0.5, 3.0}, math.MaxFloat64) 389 | b2.Position = b2d.Vec2{-7.0, 4.0} 390 | world.AddBody(&b2) 391 | 392 | var b3 b2d.Body 393 | b3.Set(&b2d.Vec2{12.0, 0.25}, 20.0) 394 | b3.Position = b2d.Vec2{-0.9, 1.0} 395 | world.AddBody(&b3) 396 | 397 | { 398 | var j b2d.Joint 399 | j.Set(&b1, &b3, &b2d.Vec2{-2.0, 1.0}) 400 | world.AddJoint(&j) 401 | } 402 | 403 | var b4 b2d.Body 404 | b4.Set(&b2d.Vec2{0.5, 0.5}, 10.0) 405 | b4.Position = b2d.Vec2{-10.0, 15.0} 406 | world.AddBody(&b4) 407 | 408 | { 409 | var j b2d.Joint 410 | j.Set(&b2, &b4, &b2d.Vec2{-7.0, 15.0}) 411 | world.AddJoint(&j) 412 | } 413 | 414 | var b5 b2d.Body 415 | b5.Set(&b2d.Vec2{2.0, 2.0}, 20.0) 416 | b5.Position = b2d.Vec2{6.0, 2.5} 417 | b5.Friction = 0.1 418 | world.AddBody(&b5) 419 | 420 | { 421 | var j b2d.Joint 422 | j.Set(&b1, &b5, &b2d.Vec2{6.0, 2.6}) 423 | world.AddJoint(&j) 424 | } 425 | 426 | var b6 b2d.Body 427 | b6.Set(&b2d.Vec2{2.0, 0.2}, 10.0) 428 | b6.Position = b2d.Vec2{6.0, 3.6} 429 | world.AddBody(&b6) 430 | 431 | { 432 | var j b2d.Joint 433 | j.Set(&b5, &b6, &b2d.Vec2{7.0, 3.5}) 434 | world.AddJoint(&j) 435 | } 436 | 437 | } 438 | 439 | // A multi-pendulum 440 | func Demo9() { 441 | title = "A multi-pendulum" 442 | world.Clear() 443 | 444 | var b1 *b2d.Body 445 | 446 | { 447 | var b b2d.Body 448 | b.Set(&b2d.Vec2{100.0, 20.0}, math.MaxFloat64) 449 | b.Position = b2d.Vec2{0.0, -0.5 * b.Width.Y} 450 | world.AddBody(&b) 451 | b1 = &b 452 | } 453 | 454 | const mass = 10.0 455 | 456 | // Tuning 457 | const frequencyHz = 4.0 458 | const dampingRatio = 0.7 459 | 460 | // frequency in radians 461 | const omega = 2.0 * math.Pi * frequencyHz 462 | 463 | // damping coefficient 464 | const d = 2.0 * mass * dampingRatio * omega 465 | 466 | // spring stiffness 467 | const k = mass * omega * omega 468 | 469 | // magic formulas 470 | const softness = 1.0 / (d + timeStep*k) 471 | const biasFactor = timeStep * k / (d + timeStep*k) 472 | 473 | const y = 12.0 474 | 475 | for i := 0; i < 15; i++ { 476 | x := b2d.Vec2{0.5 + float64(i), y} 477 | 478 | var b b2d.Body 479 | b.Set(&b2d.Vec2{0.75, 0.25}, mass) 480 | b.Friction = 0.2 481 | b.Position = x 482 | b.Rotation = 0.0 483 | world.AddBody(&b) 484 | 485 | var j b2d.Joint 486 | j.Set(b1, &b, &b2d.Vec2{float64(i), y}) 487 | j.Softness = softness 488 | j.BiasFactor = biasFactor 489 | world.AddJoint(&j) 490 | 491 | b1 = &b 492 | } 493 | 494 | } 495 | -------------------------------------------------------------------------------- /platformer/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Amore Platformer" # The window title (string) 2 | width = 800 # The window width (number) 3 | height = 600 # The window height (number) 4 | vsync = true # Enable vertical sync (boolean) 5 | centered = true # Center the window in the display 6 | resizable = true 7 | -------------------------------------------------------------------------------- /platformer/game/block.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | type Block struct { 4 | *Entity 5 | indestructible bool 6 | } 7 | 8 | func newBlock(gameMap *Map, l, t, w, h float32, indestructible bool) *Block { 9 | block := &Block{indestructible: indestructible} 10 | block.Entity = newEntity(gameMap, block, "block", l, t, w, h) 11 | block.body.SetStatic(true) 12 | return block 13 | } 14 | 15 | func (block *Block) getColor() (r, g, b float32) { 16 | if block.indestructible { 17 | return 150, 150, 220 18 | } 19 | return 220, 150, 150 20 | } 21 | 22 | func (block *Block) update(dt float32) { 23 | } 24 | 25 | func (block *Block) draw(debug bool) { 26 | r, g, b := block.getColor() 27 | l, t, w, h := block.Extents() 28 | drawFilledRectangle(l, t, w, h, r, g, b) 29 | } 30 | 31 | func (block *Block) damage(intensity float32) { 32 | if !block.indestructible { 33 | block.Entity.damage(intensity) 34 | area := block.w * block.h 35 | debrisNumber := floor(max(30, area/100)) 36 | 37 | for i := float32(1); i <= debrisNumber; i++ { 38 | newDebris(block.gameMap, 39 | randRange(block.l, block.l+block.w), 40 | randRange(block.t, block.t+block.h), 41 | 220, 150, 150, 42 | ) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /platformer/game/debris.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | type Debris struct { 4 | *Entity 5 | r, g, b float32 6 | lifeTime float32 7 | lived float32 8 | } 9 | 10 | func newDebris(gameMap *Map, x, y, r, g, b float32) *Debris { 11 | debris := &Debris{ 12 | r: r, 13 | g: g, 14 | b: b, 15 | lifeTime: 1 + 3*randMax(1), 16 | } 17 | debris.Entity = newEntity(gameMap, 18 | debris, "debris", 19 | x, y, 20 | randRange(5, 10), 21 | randRange(5, 10), 22 | ) 23 | debris.vx = randRange(-50, 50) 24 | debris.vy = randRange(-50, 50) 25 | debris.body.SetResponses(map[string]string{ 26 | "guardian": "bounce", 27 | "block": "bounce", 28 | }) 29 | return debris 30 | } 31 | 32 | func (debris *Debris) moveColliding(dt float32) { 33 | future_l := debris.l + debris.vx*dt 34 | future_t := debris.t + debris.vy*dt 35 | next_l, next_t, cols := debris.Entity.body.Move(future_l, future_t) 36 | for _, col := range cols { 37 | debris.changeVelocityByCollisionNormal(col.Normal.X, col.Normal.Y, 0.1) 38 | } 39 | debris.l, debris.t = next_l, next_t 40 | } 41 | 42 | func (debris *Debris) update(dt float32) { 43 | debris.lived = debris.lived + dt 44 | 45 | if debris.lived >= debris.lifeTime { 46 | debris.destroy() 47 | } else { 48 | debris.changeVelocityByGravity(dt) 49 | debris.moveColliding(dt) 50 | } 51 | } 52 | 53 | func (debris *Debris) draw(debug bool) { 54 | l, t, w, h := debris.Extents() 55 | drawFilledRectangle(l, t, w, h, debris.r, debris.g, debris.b) 56 | } 57 | -------------------------------------------------------------------------------- /platformer/game/entity.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/timer" 5 | 6 | "github.com/tanema/ump" 7 | ) 8 | 9 | const gravityAccel float32 = 500 // pixels per second^2 10 | 11 | type Entity struct { 12 | body_tag string 13 | l, t, w, h float32 14 | vx, vy float32 15 | gameMap *Map 16 | body *ump.Body 17 | created_at float32 18 | } 19 | 20 | func newEntity(gameMap *Map, object gameObject, tag string, l, t, w, h float32) *Entity { 21 | entity := &Entity{ 22 | body_tag: tag, 23 | gameMap: gameMap, 24 | l: l, t: t, w: w, h: h, 25 | created_at: timer.GetTime(), 26 | } 27 | entity.body = gameMap.world.Add(tag, l, t, w, h) 28 | gameMap.objects[entity.body.ID] = object 29 | return entity 30 | } 31 | 32 | func (entity *Entity) updateOrder() int { 33 | return 10000 34 | } 35 | 36 | func (entity *Entity) changeVelocityByGravity(dt float32) { 37 | entity.vy += gravityAccel * dt 38 | } 39 | 40 | func (entity *Entity) changeVelocityByCollisionNormal(nx, ny, bounciness float32) { 41 | if (nx < 0 && entity.vx > 0) || (nx > 0 && entity.vx < 0) { 42 | entity.vx = -entity.vx * bounciness 43 | } 44 | 45 | if (ny < 0 && entity.vy > 0) || (ny > 0 && entity.vy < 0) { 46 | entity.vy = -entity.vy * bounciness 47 | } 48 | } 49 | 50 | func (entity *Entity) GetCenter() (x, y float32) { 51 | return entity.l + (entity.w / 2), entity.t + (entity.h / 2) 52 | } 53 | 54 | func (entity *Entity) Extents() (l, t, w, h float32) { 55 | return entity.l, entity.t, entity.w, entity.h 56 | } 57 | 58 | func (entity *Entity) destroy() { 59 | entity.body.Remove() 60 | delete(entity.gameMap.objects, entity.body.ID) 61 | } 62 | 63 | func (entity *Entity) tag() string { 64 | return entity.body_tag 65 | } 66 | 67 | func (entity *Entity) push(strength float32) { 68 | cx, cy := entity.l+entity.w/2, entity.t+entity.h/2 69 | ox, oy := entity.GetCenter() 70 | dx, dy := ox-cx, oy-cy 71 | dx, dy = clamp(dx, -strength, strength), clamp(dy, -strength, strength) 72 | 73 | if dx > 0 { 74 | dx = strength - dx 75 | } else if dx < 0 { 76 | dx = dx - strength 77 | } 78 | 79 | if dy > 0 { 80 | dy = strength - dy 81 | } else if dy < 0 { 82 | dy = dy - strength 83 | } 84 | 85 | entity.vx += dx 86 | entity.vy += dy 87 | } 88 | 89 | func (entity *Entity) damage(intensity float32) { 90 | entity.destroy() 91 | } 92 | -------------------------------------------------------------------------------- /platformer/game/explosion.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | var ( 4 | explosionWidth float32 = 150 5 | explosionHeight float32 = explosionWidth 6 | explosionMaxPushSpeed float32 = 300 7 | ) 8 | 9 | func newExplosion(grenade *Grenade) { 10 | x, y := grenade.GetCenter() 11 | l, t, w, h := x-explosionWidth/2, y-explosionHeight/2, explosionWidth, explosionHeight 12 | gameMap := grenade.parent.gameMap 13 | world := gameMap.world 14 | gameMap.camera.Shake(6) 15 | 16 | for _, item := range world.QueryRect(l, t, w, h) { 17 | object := gameMap.Get(item) 18 | tag := object.tag() 19 | if tag == "player" || tag == "guardian" || tag == "block" { 20 | object.damage(0.7) 21 | } 22 | } 23 | 24 | radius := float32(50) 25 | for _, item := range world.QueryRect(l-radius, t-radius, w+radius+radius, h+radius+radius) { 26 | object := gameMap.Get(item) 27 | tag := object.tag() 28 | if tag == "player" || tag == "grenade" || tag == "debris" || tag == "puff" { 29 | object.push(300) 30 | } 31 | } 32 | 33 | for i := float32(0); i < randRange(15, 30); i++ { 34 | newPuff( 35 | grenade.parent.gameMap, 36 | randRange(l, l+w), randRange(t, t+h), 37 | 0, -10, 2, 10, 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /platformer/game/game_object.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/tanema/ump" 7 | ) 8 | 9 | type ( 10 | gameObject interface { 11 | update(dt float32) 12 | tag() string 13 | destroy() 14 | push(strength float32) 15 | damage(intensity float32) 16 | draw(bool) 17 | updateOrder() int 18 | } 19 | By func(b1, b2 *ump.Body) bool 20 | bodySorter struct { 21 | bodies []*ump.Body 22 | by func(b1, b2 *ump.Body) bool 23 | } 24 | ) 25 | 26 | func (by By) Sort(bodies []*ump.Body) { 27 | ps := &bodySorter{ 28 | bodies: bodies, 29 | by: by, 30 | } 31 | sort.Sort(ps) 32 | } 33 | 34 | func (s *bodySorter) Len() int { 35 | return len(s.bodies) 36 | } 37 | 38 | func (s *bodySorter) Swap(i, j int) { 39 | s.bodies[i], s.bodies[j] = s.bodies[j], s.bodies[i] 40 | } 41 | 42 | func (s *bodySorter) Less(i, j int) bool { 43 | return s.by(s.bodies[i], s.bodies[j]) 44 | } 45 | -------------------------------------------------------------------------------- /platformer/game/grenade.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | type Grenade struct { 8 | *Entity 9 | parent *Guardian 10 | lived float32 11 | ignoresParent bool 12 | } 13 | 14 | const ( 15 | grenadeLifeTime = float32(4) 16 | grenadeBounciness = float32(0.4) 17 | ) 18 | 19 | func newGrenade(guardian *Guardian, x, y, vx, vy float32) *Grenade { 20 | grenade := &Grenade{ 21 | parent: guardian, 22 | ignoresParent: true, 23 | } 24 | grenade.Entity = newEntity(guardian.gameMap, grenade, "grenade", x, y, 11, 11) 25 | grenade.vx, grenade.vy = vx, vy 26 | grenade.body.SetResponses(map[string]string{ 27 | "guardian": "cross", //This gets toggled once the grenade is outside the guardian 28 | "player": "bounce", 29 | "block": "bounce", 30 | }) 31 | return grenade 32 | } 33 | 34 | func (grenade *Grenade) moveColliding(dt float32) { 35 | future_l := grenade.l + grenade.vx*dt 36 | future_t := grenade.t + grenade.vy*dt 37 | next_l, next_t, cols := grenade.body.Move(future_l, future_t) 38 | 39 | for _, col := range cols { 40 | if col.Body.Tag() == "player" { 41 | grenade.destroy() 42 | return 43 | } 44 | grenade.changeVelocityByCollisionNormal(col.Normal.X, col.Normal.Y, grenadeBounciness) 45 | } 46 | grenade.l, grenade.t = next_l, next_t 47 | } 48 | 49 | func (grenade *Grenade) detectExitedParent() { 50 | if grenade.ignoresParent { 51 | x1, y1, w1, h1 := grenade.Extents() 52 | x2, y2, w2, h2 := grenade.parent.Extents() 53 | grenade.ignoresParent = x1 < x2+w2 && x2 < x1+w1 && y1 < y2+h2 && y2 < y1+h1 54 | if !grenade.ignoresParent { 55 | grenade.body.SetResponse("guardian", "bounce") 56 | } 57 | } 58 | } 59 | 60 | func (grenade *Grenade) updateOrder() int { 61 | return 2 62 | } 63 | 64 | func (grenade *Grenade) update(dt float32) { 65 | grenade.lived += dt 66 | if grenade.lived >= grenadeLifeTime { 67 | grenade.destroy() 68 | } else { 69 | grenade.changeVelocityByGravity(dt) 70 | grenade.moveColliding(dt) 71 | grenade.detectExitedParent() 72 | } 73 | } 74 | 75 | func (grenade *Grenade) draw(debug bool) { 76 | r, g, b := float32(255), float32(0), float32(0) 77 | gfx.SetColor(r, g, b, 255) 78 | 79 | cx, cy := grenade.GetCenter() 80 | gfx.Circle(gfx.LINE, cx, cy, 8) 81 | 82 | percent := grenade.lived / grenadeLifeTime 83 | g = floor(255 * percent) 84 | b = g 85 | 86 | gfx.SetColor(r, g, b, 255) 87 | gfx.Circle(gfx.FILL, cx, cy, 8) 88 | 89 | if debug { 90 | gfx.SetColor(255, 255, 255, 200) 91 | l, t, w, h := grenade.Extents() 92 | gfx.Rect(gfx.LINE, l, t, w, h) 93 | } 94 | } 95 | 96 | func (grenade *Grenade) destroy() { 97 | grenade.Entity.destroy() 98 | newExplosion(grenade) 99 | } 100 | -------------------------------------------------------------------------------- /platformer/game/guardian.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | type Guardian struct { 8 | *Entity 9 | gameMap *Map 10 | activeRadius float32 11 | fireCoolDown float32 // how much time the Guardian takes to "regenerate a grenade" 12 | aimDuration float32 // time it takes to "aim" 13 | targetCoolDown float32 // minimum time between "target acquired" chirps 14 | fireTimer float32 15 | aimTimer float32 16 | timeSinceLastTargetAquired float32 17 | isNearTarget bool 18 | isLoading bool 19 | laserX, laserY float32 20 | } 21 | 22 | func newGuardian(gameMap *Map, l, t float32) *Guardian { 23 | guardian := &Guardian{ 24 | gameMap: gameMap, 25 | activeRadius: 500, 26 | fireCoolDown: 0.75, 27 | aimDuration: 1.25, 28 | targetCoolDown: 2, 29 | timeSinceLastTargetAquired: 2, 30 | } 31 | guardian.Entity = newEntity(gameMap, guardian, "guardian", l, t, 42, 110) 32 | 33 | l, t, w, h := guardian.Extents() 34 | others := gameMap.world.QueryRect(l, t, w, h, "block") 35 | for _, other := range others { 36 | if other.ID != guardian.body.ID { 37 | other.Remove() 38 | } 39 | } 40 | 41 | return guardian 42 | } 43 | 44 | func (guardian *Guardian) update(dt float32) { 45 | guardian.isNearTarget = false 46 | guardian.isLoading = false 47 | guardian.laserX, guardian.laserY = 0, 0 48 | guardian.timeSinceLastTargetAquired += dt 49 | 50 | if guardian.fireTimer < guardian.fireCoolDown { 51 | guardian.fireTimer = guardian.fireTimer + dt 52 | guardian.isLoading = true 53 | } else { 54 | cx, cy := guardian.GetCenter() 55 | tx, ty := guardian.gameMap.Player.GetCenter() 56 | dx, dy := cx-tx, cy-ty 57 | distance2 := dx*dx + dy*dy 58 | 59 | if distance2 <= guardian.activeRadius*guardian.activeRadius { 60 | guardian.isNearTarget = true 61 | bodies := guardian.gameMap.world.QuerySegment(cx, cy, tx, ty, "player", "block", "guardian") 62 | // ignore itemsInfo[0] because thats always guardian 63 | if len(bodies) > 1 { 64 | body := bodies[1] 65 | if body.ID == guardian.gameMap.Player.body.ID { 66 | guardian.laserX, guardian.laserY = guardian.gameMap.Player.GetCenter() 67 | if guardian.aimTimer == 0 && guardian.timeSinceLastTargetAquired >= guardian.targetCoolDown { 68 | guardian.timeSinceLastTargetAquired = 0 69 | } 70 | guardian.aimTimer = guardian.aimTimer + dt 71 | if guardian.aimTimer >= guardian.aimDuration { 72 | guardian.fire() 73 | } 74 | } else { 75 | guardian.aimTimer = 0 76 | } 77 | } 78 | } else { 79 | guardian.aimTimer = 0 80 | } 81 | } 82 | } 83 | 84 | func (guardian *Guardian) updateOrder() int { 85 | return 3 86 | } 87 | 88 | func (guardian *Guardian) draw(debug bool) { 89 | drawFilledRectangle(guardian.l, guardian.t, guardian.w, guardian.h, 255, 0, 255) 90 | 91 | cx, cy := guardian.GetCenter() 92 | gfx.SetColor(255, 0, 0, 255) 93 | radius := float32(8) 94 | if guardian.isLoading { 95 | percent := guardian.fireTimer / guardian.fireCoolDown 96 | alpha := floor(255 * percent) 97 | radius = radius * percent 98 | 99 | gfx.SetColor(0, 100, 200, alpha) 100 | gfx.Circle(gfx.FILL, cx, cy, radius) 101 | gfx.SetColor(0, 100, 200, 255) 102 | gfx.Circle(gfx.LINE, cx, cy, radius) 103 | } else { 104 | if guardian.aimTimer > 0 { 105 | gfx.SetColor(255, 0, 0, 255) 106 | } else { 107 | gfx.SetColor(0, 100, 200, 255) 108 | } 109 | gfx.Circle(gfx.LINE, cx, cy, radius) 110 | gfx.Circle(gfx.FILL, cx, cy, radius) 111 | 112 | if debug { 113 | gfx.SetColor(255, 255, 255, 100) 114 | gfx.Circle(gfx.LINE, cx, cy, guardian.activeRadius) 115 | } 116 | 117 | if guardian.isNearTarget { 118 | tx, ty := guardian.gameMap.Player.GetCenter() 119 | 120 | if debug { 121 | gfx.SetColor(255, 255, 255, 100) 122 | gfx.Line(cx, cy, tx, ty) 123 | } 124 | 125 | if guardian.aimTimer > 0 { 126 | gfx.SetColor(255, 100, 100, 200) 127 | } else { 128 | gfx.SetColor(0, 100, 200, 100) 129 | } 130 | if guardian.laserX != 0 && guardian.laserY != 0 { 131 | gfx.SetLineWidth(2) 132 | gfx.Line(cx, cy, guardian.laserX, guardian.laserY) 133 | gfx.SetLineWidth(1) 134 | } 135 | } 136 | } 137 | } 138 | 139 | func (guardian *Guardian) fire() { 140 | cx, cy := guardian.GetCenter() 141 | tx, ty := guardian.gameMap.Player.GetCenter() 142 | vx, vy := (tx-cx)*3, (ty-cy)*3 143 | newGrenade(guardian, cx, cy, vx, vy) 144 | guardian.fireTimer = 0 145 | guardian.aimTimer = 0 146 | } 147 | 148 | func (guardian *Guardian) damage(intensity float32) { 149 | guardian.destroy() 150 | } 151 | 152 | func (guardian *Guardian) destroy() { 153 | guardian.body.Remove() 154 | for i := 1; i <= 45; i++ { 155 | newDebris(guardian.gameMap, 156 | randRange(guardian.l, guardian.l+guardian.w), 157 | randRange(guardian.t, guardian.t+guardian.h), 158 | 255, 0, 255, 159 | ) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /platformer/game/map.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/lense" 5 | "github.com/tanema/ump" 6 | ) 7 | 8 | type Map struct { 9 | width float32 10 | height float32 11 | updateRadius float32 12 | Player *Player 13 | objects map[uint32]gameObject 14 | debug bool 15 | camera *lense.Camera 16 | world *ump.World 17 | } 18 | 19 | func NewMap(width, height float32, camera *lense.Camera) *Map { 20 | gameMap := &Map{ 21 | width: width, 22 | height: height, 23 | updateRadius: 100, 24 | camera: camera, 25 | } 26 | gameMap.Reset() 27 | return gameMap 28 | } 29 | 30 | func (m *Map) Reset() { 31 | m.objects = map[uint32]gameObject{} 32 | m.world = ump.NewWorld(64) 33 | m.Player = newPlayer(m, 60, 60) 34 | 35 | // walls & ceiling 36 | newBlock(m, 0, 0, m.width, 32, true) 37 | newBlock(m, 0, 32, 32, m.height-64, true) 38 | newBlock(m, m.width-32, 32, 32, m.height-64, true) 39 | 40 | // tiled floor 41 | tilesOnFloor := float32(40) 42 | for i := float32(0); i < tilesOnFloor; i++ { 43 | newBlock(m, i*m.width/tilesOnFloor, m.height-32, m.width/tilesOnFloor, 32, true) 44 | } 45 | 46 | // groups of blocks 47 | for i := 1; i < 60; i++ { 48 | w := randRange(100, 400) 49 | h := randRange(100, 400) 50 | area := w * h 51 | l := randRange(100, m.width-w-200) 52 | t := randRange(100, m.height-h-100) 53 | indestructible := randRange(0, 1) < 0.75 54 | 55 | for j := 1; j < int(floor(area/7000)); j++ { 56 | newBlock(m, 57 | randRange(l, l+w), 58 | randRange(t, t+h), 59 | randRange(32, 100), 60 | randRange(32, 100), 61 | indestructible) 62 | } 63 | } 64 | 65 | for i := 0; i < 10; i++ { 66 | newGuardian(m, 67 | randRange(100, m.width-200), 68 | randRange(100, m.height-150)) 69 | } 70 | } 71 | 72 | func (m *Map) ToggleDebug() { 73 | m.debug = !m.debug 74 | } 75 | 76 | func (m *Map) Update(dt, l, t, w, h float32) { 77 | if m.Player.isDead { 78 | m.Player.deadCounter = m.Player.deadCounter + dt 79 | if m.Player.deadCounter >= deadDuration { 80 | m.Reset() 81 | } 82 | } 83 | 84 | l, t, w, h = l-m.updateRadius, t-m.updateRadius, w+m.updateRadius*2, h+m.updateRadius*2 85 | visibleObject := m.world.QueryRect(l, t, w, h) 86 | 87 | By(func(a1, a2 *ump.Body) bool { 88 | a, aok := m.objects[a1.ID] 89 | b, bok := m.objects[a2.ID] 90 | if !aok || !bok { 91 | return true 92 | } 93 | return a.updateOrder() < b.updateOrder() 94 | }).Sort(visibleObject) 95 | 96 | for _, item := range visibleObject { 97 | object, ok := m.objects[item.ID] 98 | if ok { 99 | object.update(dt) 100 | } 101 | } 102 | } 103 | 104 | func (m *Map) Draw(l, t, w, h float32) { 105 | for _, item := range m.world.QueryRect(l, t, w, h) { 106 | object, ok := m.objects[item.ID] 107 | if ok { 108 | object.draw(m.debug) 109 | } 110 | } 111 | } 112 | 113 | func (m *Map) Get(item *ump.Body) gameObject { 114 | return m.objects[item.ID] 115 | } 116 | -------------------------------------------------------------------------------- /platformer/game/math.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func init() { 10 | rand.Seed(time.Now().UTC().UnixNano()) 11 | } 12 | 13 | func floor(x float32) float32 { 14 | return float32(math.Floor(float64(x))) 15 | } 16 | 17 | func clamp(x, minX, maxX float32) float32 { 18 | if x < minX { 19 | return minX 20 | } else if x > maxX { 21 | return maxX 22 | } 23 | return x 24 | } 25 | 26 | func randMax(max float32) float32 { 27 | return randRange(0, max) 28 | } 29 | 30 | func randRange(min, max float32) float32 { 31 | return (rand.Float32() * (max - min)) + min 32 | } 33 | 34 | func randLimits(limit float32) float32 { 35 | return randRange(-limit, limit) 36 | } 37 | 38 | func abs(x float32) float32 { 39 | return float32(math.Abs(float64(x))) 40 | } 41 | 42 | func min(x, y float32) float32 { 43 | return float32(math.Min(float64(x), float64(y))) 44 | } 45 | 46 | func max(x, y float32) float32 { 47 | return float32(math.Max(float64(x), float64(y))) 48 | } 49 | 50 | func sin(x float32) float32 { 51 | return float32(math.Sin(float64(x))) 52 | } 53 | 54 | func cos(x float32) float32 { 55 | return float32(math.Cos(float64(x))) 56 | } 57 | -------------------------------------------------------------------------------- /platformer/game/player.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/keyboard" 5 | ) 6 | 7 | type Player struct { 8 | *Entity 9 | health float32 10 | deadCounter float32 11 | isJumpingOrFlying bool 12 | isDead bool 13 | onGround bool 14 | achievedFullHealth bool 15 | } 16 | 17 | const ( 18 | deadDuration float32 = 3 // seconds until res-pawn 19 | runAccel float32 = 500 // the player acceleration while going left/right 20 | brakeAccel float32 = 2000 21 | jumpVelocity float32 = 400 // the initial upwards velocity when jumping 22 | beltWidth float32 = 2 23 | beltHeight float32 = 8 24 | ) 25 | 26 | func newPlayer(gameMap *Map, l, t float32) *Player { 27 | player := &Player{ 28 | health: 1, 29 | } 30 | player.Entity = newEntity(gameMap, player, "player", l, t, 32, 64) 31 | player.body.SetResponses(map[string]string{ 32 | "guardian": "slide", 33 | "block": "slide", 34 | }) 35 | return player 36 | } 37 | 38 | func (player *Player) changeVelocityByKeys(dt float32) { 39 | player.isJumpingOrFlying = false 40 | 41 | if player.isDead { 42 | return 43 | } 44 | 45 | if keyboard.IsDown(keyboard.KeyLeft) { 46 | if player.vx > 0 { 47 | player.vx -= dt * brakeAccel 48 | } else { 49 | player.vx -= dt * runAccel 50 | } 51 | } else if keyboard.IsDown(keyboard.KeyRight) { 52 | if player.vx < 0 { 53 | player.vx += dt * brakeAccel 54 | } else { 55 | player.vx += dt * runAccel 56 | } 57 | } else { 58 | brake := dt * -brakeAccel 59 | if player.vx < 0 { 60 | brake = dt * brakeAccel 61 | } 62 | if abs(brake) > abs(player.vx) { 63 | player.vx = 0 64 | } else { 65 | player.vx += brake 66 | } 67 | } 68 | 69 | if keyboard.IsDown(keyboard.KeyUp) && (player.canFly() || player.onGround) { // jump/fly 70 | player.vy = -jumpVelocity 71 | player.isJumpingOrFlying = true 72 | } 73 | } 74 | 75 | func (player *Player) moveColliding(dt float32) { 76 | player.onGround = false 77 | l, t, cols := player.Entity.body.Move(player.l+player.vx*dt, player.t+player.vy*dt) 78 | for _, col := range cols { 79 | if col.Body.Tag() != "puff" { 80 | player.changeVelocityByCollisionNormal(col.Normal.X, col.Normal.Y, 0) 81 | player.onGround = col.Normal.Y == -1 82 | } 83 | } 84 | player.l, player.t = l, t 85 | } 86 | 87 | func (player *Player) updateHealth(dt float32) { 88 | player.achievedFullHealth = false 89 | if player.health < 1 { 90 | player.health = min(1, player.health+dt/6) 91 | player.achievedFullHealth = player.health == 1 92 | } 93 | } 94 | 95 | func (player *Player) playEffects() { 96 | if player.isJumpingOrFlying { 97 | if !player.onGround { 98 | l, t, w, h := player.Extents() 99 | newPuff(player.gameMap, l, t+h/2, 20*(1-randMax(1)), 50, 2, 3) 100 | newPuff(player.gameMap, l+w, t+h/2, 20*(1-randMax(1)), 50, 2, 3) 101 | } 102 | } 103 | } 104 | 105 | func (player *Player) updateOrder() int { 106 | return 1 107 | } 108 | 109 | func (player *Player) update(dt float32) { 110 | player.updateHealth(dt) 111 | player.changeVelocityByKeys(dt) 112 | player.changeVelocityByGravity(dt) 113 | player.playEffects() 114 | player.moveColliding(dt) 115 | } 116 | 117 | func (player *Player) getColor() (r, g, b float32) { 118 | g = floor(255 * player.health) 119 | return 255 - g, g, 0 120 | } 121 | 122 | func (player *Player) canFly() bool { 123 | return player.health == 1 124 | } 125 | 126 | func (player *Player) draw(debug bool) { 127 | r, g, b := player.getColor() 128 | l, t, w, h := player.Extents() 129 | drawFilledRectangle(l, t, w, h, r, g, b) 130 | 131 | if player.canFly() { 132 | drawFilledRectangle(l-beltWidth, t+h/2, w+2*beltWidth, beltHeight, 255, 255, 255) 133 | } 134 | 135 | if debug && player.onGround { 136 | drawFilledRectangle(l, t+h-4, w, 4, 255, 255, 255) 137 | } 138 | } 139 | 140 | func (player *Player) damage(intensity float32) { 141 | if player.isDead { 142 | return 143 | } 144 | 145 | if player.health == 1 { 146 | for i := 1; i <= 3; i++ { 147 | newDebris(player.gameMap, 148 | randRange(player.l, player.l+player.w), 149 | player.t+player.h/2, 150 | 255, 0, 0, 151 | ) 152 | } 153 | } 154 | 155 | player.health = player.health - intensity 156 | if player.health <= 0 { 157 | player.destroy() 158 | player.isDead = true 159 | } 160 | } 161 | 162 | func (player *Player) destroy() { 163 | player.body.Remove() 164 | for i := 1; i <= 20; i++ { 165 | newDebris(player.gameMap, 166 | randRange(player.l, player.l+player.w), 167 | randRange(player.t, player.t+player.h), 168 | 255, 0, 0) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /platformer/game/puff.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | type Puff struct { 4 | *Entity 5 | lived float32 6 | lifeTime float32 7 | } 8 | 9 | func newPuff(gameMap *Map, x, y, vx, vy, minSize, maxSize float32) *Puff { 10 | puff := &Puff{} 11 | puff.Entity = newEntity(gameMap, 12 | puff, "puff", 13 | x, y, 14 | randRange(minSize, maxSize), 15 | randRange(minSize, maxSize), 16 | ) 17 | puff.lifeTime = 0.1 + randMax(1) 18 | puff.vx, puff.vy = vx, vy 19 | return puff 20 | } 21 | 22 | func (puff *Puff) expand(dt float32) { 23 | cx, cy := puff.GetCenter() 24 | percent := puff.lived / puff.lifeTime 25 | if percent < 0.2 { 26 | puff.w = puff.w + (200+percent)*dt 27 | puff.h = puff.h + (200+percent)*dt 28 | } else { 29 | puff.w = puff.w + (20+percent)*dt 30 | } 31 | puff.l = cx - puff.w/2 32 | puff.t = cy - puff.h/2 33 | } 34 | 35 | func (puff *Puff) update(dt float32) { 36 | puff.lived = puff.lived + dt 37 | 38 | if puff.lived >= puff.lifeTime { 39 | puff.destroy() 40 | } else { 41 | puff.expand(dt) 42 | next_l, next_t := puff.l+puff.vx*dt, puff.t+puff.vy*dt 43 | puff.body.Update(next_l, next_t) 44 | puff.l, puff.t = next_l, next_t 45 | } 46 | } 47 | 48 | func (puff *Puff) draw(debug bool) { 49 | percent := min(1, (puff.lived/puff.lifeTime)*1.8) 50 | r, g, b := 255-floor(155*percent), 255-floor(155*percent), float32(100) 51 | l, t, w, h := puff.Extents() 52 | drawFilledRectangle(l, t, w, h, r, g, b) 53 | } 54 | -------------------------------------------------------------------------------- /platformer/game/util.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/tanema/amore/gfx" 4 | 5 | func drawFilledRectangle(l, t, w, h, r, g, b float32) { 6 | gfx.SetColor(r, g, b, 100) 7 | gfx.Rect(gfx.FILL, l, t, w, h) 8 | gfx.SetColor(r, g, b, 255) 9 | gfx.Rect(gfx.LINE, l, t, w, h) 10 | } 11 | -------------------------------------------------------------------------------- /platformer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/tanema/amore" 8 | "github.com/tanema/amore/gfx" 9 | "github.com/tanema/amore/keyboard" 10 | "github.com/tanema/amore/timer" 11 | "github.com/tanema/lense" 12 | 13 | "github.com/tanema/amore-examples/platformer/game" 14 | ) 15 | 16 | var ( 17 | width float32 = 4000 18 | height float32 = 2000 19 | camera *lense.Camera 20 | gameMap *game.Map 21 | ) 22 | 23 | func main() { 24 | amore.OnLoad = onLoad 25 | amore.Start(update, draw) 26 | } 27 | 28 | func onLoad() { 29 | keyboard.OnKeyUp = keypress 30 | camera = lense.New() 31 | gameMap = game.NewMap(width, height, camera) 32 | } 33 | 34 | func update(dt float32) { 35 | l, t, w, h := camera.GetVisible() 36 | gameMap.Update(dt, l, t, w, h) 37 | camera.LookAt(gameMap.Player.GetCenter()) 38 | camera.Update(dt) 39 | } 40 | 41 | func draw() { 42 | camera.Draw(gameMap.Draw) 43 | gfx.SetColor(255, 255, 255, 255) 44 | w, h := gfx.GetWidth(), gfx.GetHeight() 45 | stats := runtime.MemStats{} 46 | runtime.ReadMemStats(&stats) 47 | gfx.Printf(fmt.Sprintf("fps: %v, mem: %vKB", timer.GetFPS(), stats.HeapAlloc/1000000), 200, gfx.AlignRight, w-200, h-40) 48 | } 49 | 50 | func keypress(key keyboard.Key) { 51 | switch key { 52 | case keyboard.KeyEscape: 53 | amore.Quit() 54 | case keyboard.KeyTab: 55 | gameMap.ToggleDebug() 56 | case keyboard.KeyReturn: 57 | gameMap.Reset() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pong/assets/audio/blip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/pong/assets/audio/blip.wav -------------------------------------------------------------------------------- /pong/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/pong/assets/images/icon.png -------------------------------------------------------------------------------- /pong/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Amore : Pong" # The window title (string) 2 | icon = "images/icon.png" # Filepath to an image to use as the window's icon (string) 3 | width = 800 # The window width (number) 4 | height = 600 # The window height (number) 5 | vsync = true # Enable vertical sync (boolean) 6 | centered = true # Center the window in the display 7 | -------------------------------------------------------------------------------- /pong/main.go: -------------------------------------------------------------------------------- 1 | // Simple pong clone without more complex paddle physics. You can tune the ball 2 | // speed and paddle speed for better playability 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/tanema/amore" 9 | "github.com/tanema/amore/audio" 10 | "github.com/tanema/amore/gfx" 11 | "github.com/tanema/amore/keyboard" 12 | "github.com/tanema/amore/window" 13 | ) 14 | 15 | const ( 16 | paddleWidth = 10 17 | paddleHeigth = 100 18 | paddleSpeed = 200 19 | ballSpeed = 200 20 | ) 21 | 22 | var ( 23 | gameOver = false 24 | enemyPosition float32 = 0 25 | screenWidth float32 26 | screenHeight float32 27 | 28 | player = &Paddle{ 29 | x: 800 - paddleWidth, 30 | y: 300 - (paddleHeigth / 2), 31 | width: paddleWidth, 32 | height: paddleHeigth, 33 | speed: paddleSpeed, 34 | color: gfx.NewColor(0, 255, 0, 255), 35 | } 36 | 37 | enemy = &Paddle{ 38 | y: 300 - (paddleHeigth / 2), 39 | width: paddleWidth, 40 | height: paddleHeigth, 41 | speed: paddleSpeed, 42 | color: gfx.NewColor(0, 0, 255, 255), 43 | } 44 | 45 | ball = &Ball{ 46 | x: 400, 47 | y: 300, 48 | vx: 1.5, 49 | vy: 1.5, 50 | radius: 10, 51 | speed: ballSpeed, 52 | color: gfx.NewColor(255, 0, 0, 255), 53 | } 54 | 55 | scoreLabel *gfx.Text 56 | blip, _ = audio.NewSource("audio/blip.wav", true) 57 | bomb, _ = audio.NewSource("../test-all/assets/audio/bomb.wav", true) 58 | ) 59 | 60 | func main() { 61 | window.SetMouseVisible(false) 62 | amore.OnLoad = load 63 | amore.Start(update, draw) 64 | } 65 | 66 | func load() { 67 | screenWidth = gfx.GetWidth() 68 | screenHeight = gfx.GetHeight() 69 | scoreLabel, _ = gfx.NewText( 70 | gfx.GetFont(), 71 | fmt.Sprintf("%v : %v", enemy.score, player.score), 72 | ) 73 | } 74 | 75 | func update(dt float32) { 76 | if keyboard.IsDown(keyboard.KeyEscape) { 77 | amore.Quit() 78 | } 79 | if keyboard.IsDown(keyboard.KeyReturn) { 80 | reset() 81 | } 82 | 83 | if player.score >= 10 || enemy.score >= 10 { 84 | gameOver = true 85 | } 86 | 87 | if gameOver { 88 | return 89 | } 90 | 91 | ball.Update(dt) 92 | 93 | // enemy movements 94 | enemyY := enemy.y + (enemy.height / 3) 95 | if ball.y < enemyY { 96 | enemy.y -= enemy.speed * dt 97 | } else if ball.y > enemyY { 98 | enemy.y += enemy.speed * dt 99 | } 100 | 101 | //if keyboard.IsDown(keyboard.KeyW) { 102 | //enemy.y -= enemy.speed * dt 103 | //} else if keyboard.IsDown(keyboard.KeyS) { 104 | //enemy.y += enemy.speed * dt 105 | //} 106 | 107 | // player movements 108 | if keyboard.IsDown(keyboard.KeyUp) { 109 | player.y -= player.speed * dt 110 | } else if keyboard.IsDown(keyboard.KeyDown) { 111 | player.y += player.speed * dt 112 | } 113 | 114 | scoreLabel.Set(fmt.Sprintf("%v : %v", enemy.score, player.score)) 115 | } 116 | 117 | func reset() { 118 | ball.Reset() 119 | 120 | player.y = 300 - (paddleHeigth / 2) 121 | player.score = 0 122 | 123 | enemy.y = 300 - (paddleHeigth / 2) 124 | enemy.score = 0 125 | 126 | gameOver = false 127 | } 128 | 129 | func draw() { 130 | halfWidth := scoreLabel.GetWidth() / 2 131 | gfx.SetColor(255, 255, 255, 255) 132 | scoreLabel.Draw((screenWidth/2)-halfWidth, 0) 133 | gfx.SetLineWidth(3) 134 | gfx.Line(screenWidth/2+1, 25, screenWidth/2+1, screenHeight) 135 | 136 | ball.Draw() 137 | enemy.Draw() 138 | player.Draw() 139 | 140 | if gameOver { 141 | gameOverString := "Game Over" 142 | wonlost := "You Won" 143 | if enemy.score > player.score { 144 | wonlost = "You Lost" 145 | } 146 | pressEnter := "Press Enter To Restart" 147 | enterWidth := gfx.GetFont().GetWidth(pressEnter) + 20 148 | 149 | gfx.SetColor(0, 0, 0, 255) 150 | leftAlign := gfx.GetWidth()/2 - (enterWidth / 2) 151 | 152 | gfx.Rect(gfx.FILL, leftAlign, 200, enterWidth, 75) 153 | if enemy.score > player.score { 154 | gfx.SetColorC(enemy.color) 155 | } else { 156 | gfx.SetColorC(player.color) 157 | } 158 | gfx.Rect(gfx.LINE, leftAlign, 200, enterWidth, 75) 159 | 160 | gfx.Print(gameOverString, leftAlign+10, 215) 161 | gfx.Print(wonlost, leftAlign+10, 230) 162 | gfx.Print(pressEnter, leftAlign+10, 245) 163 | } 164 | } 165 | 166 | type Ball struct { 167 | x float32 168 | y float32 169 | vx float32 170 | vy float32 171 | radius float32 172 | speed float32 173 | color *gfx.Color 174 | } 175 | 176 | func (ball *Ball) Reset() { 177 | ball.x = 400 178 | ball.y = 300 179 | } 180 | 181 | func (ball *Ball) Update(dt float32) { 182 | ball.x += ball.speed * ball.vx * dt 183 | ball.y += ball.speed * ball.vy * dt 184 | 185 | // enemy's wall 186 | if ball.x <= ball.radius { 187 | // if the ball is past the wall/paddle lets put it before the wall so 188 | // that we wont have multiple collisions 189 | if ball.x < ball.radius { 190 | ball.x = ball.radius 191 | } 192 | 193 | if ball.y >= enemy.y && ball.y <= enemy.y+enemy.height { 194 | blip.Play() 195 | ball.vx = ball.vx * -1 196 | } else { 197 | ball.Reset() 198 | bomb.Play() 199 | player.score += 1 200 | } 201 | } 202 | 203 | if ball.x >= screenWidth-ball.radius { //players wall 204 | // if the ball is past the wall/paddle lets put it before the wall so 205 | // that we wont have multiple collisions 206 | if ball.x > screenWidth-ball.radius { 207 | ball.x = screenWidth - ball.radius 208 | } 209 | 210 | if ball.y >= player.y && ball.y <= player.y+player.height { 211 | blip.Play() 212 | ball.vx = ball.vx * -1 213 | } else { 214 | ball.Reset() 215 | bomb.Play() 216 | enemy.score += 1 217 | } 218 | } 219 | 220 | if ball.y <= ball.radius || ball.y >= screenHeight-ball.radius { 221 | // if the ball is past the wall lets put it before the wall so 222 | // that we wont have multiple collisions 223 | if ball.y < ball.radius { 224 | ball.y = ball.radius 225 | } 226 | if ball.y > screenHeight-ball.radius { 227 | ball.y = screenHeight - ball.radius 228 | } 229 | 230 | blip.Play() 231 | ball.vy = ball.vy * -1 232 | } 233 | } 234 | 235 | func (ball *Ball) Draw() { 236 | gfx.SetColorC(ball.color) 237 | gfx.Circle(gfx.FILL, ball.x, ball.y, ball.radius) 238 | } 239 | 240 | type Paddle struct { 241 | score int 242 | x float32 243 | y float32 244 | vy float32 245 | width float32 246 | height float32 247 | speed float32 248 | color *gfx.Color 249 | } 250 | 251 | func (paddle *Paddle) Draw() { 252 | gfx.SetColorC(paddle.color) 253 | gfx.Rect(gfx.FILL, paddle.x, paddle.y, paddle.width, paddle.height) 254 | } 255 | -------------------------------------------------------------------------------- /racer/assets/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/racer/assets/images/.DS_Store -------------------------------------------------------------------------------- /racer/assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/racer/assets/images/background.png -------------------------------------------------------------------------------- /racer/assets/images/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/racer/assets/images/sprites.png -------------------------------------------------------------------------------- /racer/conf.toml: -------------------------------------------------------------------------------- 1 | title = "Amore Racer" # The window title (string) 2 | width = 800 # The window width (number) 3 | height = 600 # The window height (number) 4 | vsync = true # Enable vertical sync (boolean) 5 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 6 | centered = true # Center the window in the display 7 | -------------------------------------------------------------------------------- /racer/game/car.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | type Car struct { 8 | *Sprite 9 | percent float32 10 | point Point 11 | speed float32 12 | segment *Segment 13 | } 14 | 15 | func newCar(segment *Segment, source *gfx.Quad, z, offset, speed float32) *Car { 16 | new_car := &Car{ 17 | point: Point{ 18 | z: z, 19 | w: source.GetWidth() * sprite_scale, 20 | }, 21 | Sprite: newSprite(source, offset), 22 | speed: speed, 23 | segment: segment, 24 | } 25 | return new_car 26 | } 27 | 28 | func (car *Car) update(dt float32, player *Player) { 29 | car.steer(player) 30 | car.point.z = increase(car.point.z, dt*car.speed, trackLength) 31 | car.percent = percentRemaining(car.point.z, segmentLength) // useful for interpolation during rendering phase 32 | newSegment := findSegment(car.point.z) 33 | if car.segment != newSegment { 34 | for i, c := range car.segment.cars { 35 | if car == c { 36 | car.segment.cars = append(car.segment.cars[:i], car.segment.cars[i+1:]...) 37 | break 38 | } 39 | } 40 | newSegment.cars = append(newSegment.cars, car) 41 | car.segment = newSegment 42 | } 43 | } 44 | 45 | func (car *Car) steer(player *Player) { 46 | var dir float32 47 | lookahead := 20 48 | 49 | // optimization, dont bother steering around other cars when 'out of sight' of the player 50 | if float32(car.segment.index-player.segment.index) > drawDistance { 51 | return 52 | } 53 | 54 | for i := 1; i < lookahead; i++ { 55 | segment := segments[(car.segment.index+i)%len(segments)] 56 | 57 | if (segment == player.segment) && (car.speed > player.speed) && (overlap(player.point.x, player.point.w, car.offset, car.point.w, 1.2)) { 58 | if player.point.x > 0.5 { 59 | dir = -1 60 | } else if (player.point.x < -0.5) || car.offset > player.point.x { 61 | dir = 1 62 | } else { 63 | dir = -1 64 | } 65 | car.offset += dir * 1 / float32(i) * (car.speed - player.speed) / maxSpeed // the closer the cars (smaller i) and the greated the speed ratio, the larger the offset 66 | return 67 | } 68 | 69 | for j := 0; j < len(segment.cars); j++ { 70 | otherCar := segment.cars[j] 71 | if (car.speed > otherCar.speed) && overlap(car.offset, car.point.w, otherCar.offset, otherCar.point.w, 1.2) { 72 | if otherCar.offset > 0.5 { 73 | dir = -1 74 | } else if (otherCar.offset < -0.5) || (car.offset > otherCar.offset) { 75 | dir = 1 76 | } else { 77 | dir = -1 78 | } 79 | car.offset += dir * 1 / float32(i) * (car.speed - otherCar.speed) / maxSpeed 80 | return 81 | } 82 | } 83 | } 84 | 85 | // if no cars ahead, but I have somehow ended up off road, then steer back on 86 | if car.offset < -0.9 { 87 | car.offset += 0.1 88 | } else if car.offset > 0.9 { 89 | car.offset += -0.1 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /racer/game/colors.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | var colors = map[string]*gfx.Color{ 8 | "sky": gfx.NewColor(114, 215, 238, 255), 9 | "tree": gfx.NewColor(0, 81, 8, 255), 10 | "fog": gfx.NewColor(0, 81, 8, 255), 11 | "road": gfx.NewColor(107, 107, 107, 255), 12 | "grass": gfx.NewColor(16, 170, 16, 255), 13 | "rumble": gfx.NewColor(85, 85, 85, 255), 14 | "lane": gfx.NewColor(204, 204, 204, 255), 15 | "start": gfx.NewColor(255, 255, 255, 255), 16 | "finish": gfx.NewColor(0, 0, 0, 255), 17 | } 18 | -------------------------------------------------------------------------------- /racer/game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | 7 | "github.com/tanema/amore/gfx" 8 | ) 9 | 10 | const ( 11 | shortLength = 0 12 | mediumLength = 50 13 | longLength = 100 14 | lowHill = 20 15 | mediumHill = 40 16 | highHill = 60 17 | easyCurve float32 = 2 18 | mediumCurve float32 = 4 19 | hardCurve float32 = 6 20 | ) 21 | 22 | type ( 23 | Point struct { 24 | x, y, z, w, scale float32 25 | } 26 | PointGroup struct { 27 | world Point 28 | camera Point 29 | screen Point 30 | } 31 | ) 32 | 33 | var ( 34 | width float32 // logical canvas width 35 | height float32 // logical canvas height 36 | player *Player 37 | centrifugal float32 = 0.3 // centrifugal force multiplier when going around curves 38 | skySpeed float32 = 0.001 // background sky layer scroll speed when going around curve (or up hill) 39 | hillSpeed float32 = 0.002 // background hill layer scroll speed when going around curve (or up hill) 40 | treeSpeed float32 = 0.003 // background tree layer scroll speed when going around curve (or up hill) 41 | skyOffset float32 = 0 // current sky scroll offset 42 | hillOffset float32 = 0 // current hill scroll offset 43 | treeOffset float32 = 0 // current tree scroll offset 44 | segments []*Segment // array of road segments 45 | cars []*Car // array of cars on the road 46 | resolution float32 = height / 480 // scaling factor to provide resolution independence (computed) 47 | roadWidth float32 = 2000 // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth 48 | segmentLength float32 = 200 // length of a single segment 49 | rumbleLength int = 3 // number of segments per red/white rumble strip 50 | trackLength float32 // z length of entire track (computed) 51 | lanes float32 = 3 // number of lanes 52 | fieldOfView float32 = 100 // angle (degrees) for field of view 53 | cameraHeight float32 = 1000 // z height of camera 54 | cameraDepth float32 = float32(1 / math.Tan(float64(fieldOfView/2)*math.Pi/180)) // z distance camera is from screen (computed) 55 | drawDistance float32 = 300 // number of segments to draw 56 | fogDensity float32 = 8 // exponential fog density 57 | maxSpeed = segmentLength / (1.0 / 60.0) // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier) 58 | accel = maxSpeed / 5 // acceleration rate - tuned until it 'felt' right 59 | breaking = -maxSpeed // deceleration rate when braking 60 | decel = -maxSpeed / 5 // 'natural' deceleration rate when neither accelerating, nor braking 61 | offRoadDecel = -maxSpeed / 2 // off road deceleration is somewhere in between 62 | offRoadLimit = maxSpeed / 4 // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road) 63 | totalCars = 200 // total number of cars on the road 64 | ) 65 | 66 | func New() { 67 | width = gfx.GetWidth() 68 | height = gfx.GetHeight() 69 | initSpriteSheets() 70 | resetRoad() 71 | player = newPlayer(cameraHeight * cameraDepth) 72 | } 73 | 74 | func Update(dt float32) { 75 | startPosition := player.position 76 | player.update(dt) 77 | for _, car := range cars { 78 | car.update(dt, player) 79 | } 80 | skyOffset = increase(skyOffset, skySpeed*player.segment.curve*(player.position-startPosition)/segmentLength, 1) 81 | hillOffset = increase(hillOffset, hillSpeed*player.segment.curve*(player.position-startPosition)/segmentLength, 1) 82 | treeOffset = increase(treeOffset, treeSpeed*player.segment.curve*(player.position-startPosition)/segmentLength, 1) 83 | } 84 | 85 | func Draw() { 86 | baseSegment := findSegment(player.position) 87 | basePercent := percentRemaining(player.position, segmentLength) 88 | player.segment = findSegment(player.position + player.point.z) 89 | playerPercent := percentRemaining(player.position+player.point.z, segmentLength) 90 | playerY := interpolate(player.segment.p1.world.y, player.segment.p2.world.y, playerPercent) 91 | 92 | // render curves from from to back so that we can just render from back to from and 93 | // not calculate clipping 94 | var x float32 = 0 95 | var dx = -(baseSegment.curve * basePercent) 96 | curves := [][]float32{} 97 | for n := 0; n < int(drawDistance); n++ { 98 | segment := segments[(baseSegment.index+n)%len(segments)] 99 | curves = append(curves, []float32{(player.point.x * roadWidth) - x, (player.point.x * roadWidth) - x - dx}) 100 | x = x + dx 101 | dx = dx + segment.curve 102 | } 103 | 104 | gfx.SetBackgroundColorC(colors["fog"]) 105 | gfx.SetColor(255, 255, 255, 255) 106 | drawBackground(width, height, backgrounds["sky"], skyOffset, resolution*skySpeed*playerY) 107 | drawBackground(width, height, backgrounds["hills"], hillOffset, resolution*hillSpeed*playerY) 108 | drawBackground(width, height, backgrounds["trees"], treeOffset, resolution*treeSpeed*playerY) 109 | 110 | for n := int(drawDistance - 1); n > 0; n-- { 111 | segment := segments[(baseSegment.index+n)%len(segments)] 112 | segment.looped = segment.index < baseSegment.index 113 | 114 | loop := trackLength 115 | if !segment.looped { 116 | loop = 0 117 | } 118 | 119 | project(&segment.p1, curves[n][0], playerY+cameraHeight, player.position-loop, cameraDepth, width, height, roadWidth) 120 | project(&segment.p2, curves[n][1], playerY+cameraHeight, player.position-loop, cameraDepth, width, height, roadWidth) 121 | 122 | if segment.p1.camera.z > cameraDepth { // clip by (already rendered) hill 123 | segment.draw(cameraDepth, width, lanes) 124 | 125 | for i := 0; i < len(segment.cars); i++ { 126 | car := segment.cars[i] 127 | spriteScale := interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent) 128 | spriteX := interpolate(segment.p1.screen.x, segment.p2.screen.x, car.percent) + (spriteScale * car.offset * roadWidth * width / 2) 129 | spriteY := interpolate(segment.p1.screen.y, segment.p2.screen.y, car.percent) 130 | car.draw(width, height, roadWidth, spriteScale, spriteX, spriteY, -0.5, -1) 131 | } 132 | 133 | for i := 0; i < len(segment.sprites); i++ { 134 | sprite := segment.sprites[i] 135 | spriteScale := segment.p1.screen.scale 136 | spriteX := segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth * width / 2) 137 | spriteY := segment.p1.screen.y 138 | ov := float32(0) 139 | if sprite.offset < 0 { 140 | ov = -1 141 | } 142 | sprite.draw(width, height, roadWidth, spriteScale, spriteX, spriteY, ov, -1) 143 | } 144 | 145 | drawFog(0, segment.p1.screen.y, width, segment.p2.screen.y-segment.p1.screen.y, exponentialFog(float32(n)/drawDistance, fogDensity)) 146 | } 147 | 148 | } 149 | 150 | player.draw() 151 | } 152 | 153 | func findSegment(z float32) *Segment { 154 | return segments[int(math.Floor(float64(z/segmentLength)))%len(segments)] 155 | } 156 | 157 | func lastY() float32 { 158 | if len(segments) == 0 { 159 | return 0 160 | } 161 | return segments[len(segments)-1].p2.world.y 162 | } 163 | 164 | func addSegment(curve, y float32) { 165 | var n = len(segments) 166 | color := "odd" 167 | if int(math.Floor(float64(float32(n/rumbleLength))))%2 == 0 { 168 | color = "even" 169 | } 170 | segments = append(segments, newSegment(n, lastY(), y, curve, color)) 171 | } 172 | 173 | func addSprite(n int, sprite *gfx.Quad, offset float32) { 174 | segments[n].sprites = append(segments[n].sprites, newSprite(sprite, offset)) 175 | } 176 | 177 | func addRoad(enter, hold, leave int, curve, y float32) { 178 | startY := lastY() 179 | endY := startY + (y * segmentLength) 180 | total := enter + hold + leave 181 | for n := 0; n < enter; n++ { 182 | addSegment(easeIn(0, curve, float32(n)/float32(enter)), easeInOut(startY, endY, float32(n)/float32(total))) 183 | } 184 | for n := 0; n < hold; n++ { 185 | addSegment(curve, easeInOut(startY, endY, float32(enter+n)/float32(total))) 186 | } 187 | for n := 0; n < leave; n++ { 188 | addSegment(easeInOut(curve, 0, float32(n)/float32(leave)), easeInOut(startY, endY, float32(enter+hold+n)/float32(total))) 189 | } 190 | } 191 | 192 | func addStraight(num int) { addRoad(num, num, num, 0, 0) } 193 | func addHill(num int, height float32) { addRoad(num, num, num, 0, height) } 194 | func addCurve(num int, curve, height float32) { addRoad(num, num, num, curve, height) } 195 | 196 | func addLowRollingHills(num int, height float32) { 197 | addRoad(num, num, num, 0, height/2) 198 | addRoad(num, num, num, 0, -height) 199 | addRoad(num, num, num, easyCurve, height) 200 | addRoad(num, num, num, 0, 0) 201 | addRoad(num, num, num, -easyCurve, height/2) 202 | addRoad(num, num, num, 0, 0) 203 | } 204 | 205 | func addSCurves() { 206 | addRoad(mediumLength, mediumLength, mediumLength, -easyCurve, 0) 207 | addRoad(mediumLength, mediumLength, mediumLength, mediumCurve, mediumHill) 208 | addRoad(mediumLength, mediumLength, mediumLength, easyCurve, -lowHill) 209 | addRoad(mediumLength, mediumLength, mediumLength, -easyCurve, mediumHill) 210 | addRoad(mediumLength, mediumLength, mediumLength, -mediumCurve, -mediumHill) 211 | } 212 | 213 | func addBumps() { 214 | addRoad(10, 10, 10, 0, 5) 215 | addRoad(10, 10, 10, 0, -2) 216 | addRoad(10, 10, 10, 0, -5) 217 | addRoad(10, 10, 10, 0, 8) 218 | addRoad(10, 10, 10, 0, 5) 219 | addRoad(10, 10, 10, 0, -7) 220 | addRoad(10, 10, 10, 0, 5) 221 | addRoad(10, 10, 10, 0, -2) 222 | } 223 | 224 | func addDownhillToEnd(num int) { 225 | addRoad(num, num, num, -easyCurve, -lastY()/segmentLength) 226 | } 227 | 228 | func resetRoad() { 229 | segments = []*Segment{} 230 | 231 | addStraight(shortLength) 232 | addLowRollingHills(shortLength, lowHill) 233 | addSCurves() 234 | addCurve(mediumLength, mediumCurve, lowHill) 235 | addBumps() 236 | addLowRollingHills(shortLength, lowHill) 237 | addCurve(longLength*2, mediumCurve, mediumHill) 238 | addStraight(mediumLength) 239 | addHill(mediumLength, highHill) 240 | addSCurves() 241 | addCurve(longLength, -mediumCurve, 0) 242 | addHill(longLength, highHill) 243 | addCurve(longLength, mediumCurve, -lowHill) 244 | addBumps() 245 | addHill(longLength, -mediumHill) 246 | addStraight(mediumLength) 247 | addSCurves() 248 | addDownhillToEnd(200) 249 | 250 | resetSprites() 251 | resetCars() 252 | 253 | segments[10].color = "start" 254 | segments[11].color = "start" 255 | for n := 0; n < rumbleLength; n++ { 256 | segments[len(segments)-1-n].color = "finish" 257 | } 258 | 259 | trackLength = float32(len(segments) * int(segmentLength)) 260 | } 261 | 262 | func resetSprites() { 263 | addSprite(20, spriteSheet["billboard07"], -1) 264 | addSprite(40, spriteSheet["billboard06"], -1) 265 | addSprite(60, spriteSheet["billboard08"], -1) 266 | addSprite(80, spriteSheet["billboard09"], -1) 267 | addSprite(100, spriteSheet["billboard01"], -1) 268 | addSprite(120, spriteSheet["billboard02"], -1) 269 | addSprite(140, spriteSheet["billboard03"], -1) 270 | addSprite(160, spriteSheet["billboard04"], -1) 271 | addSprite(180, spriteSheet["billboard05"], -1) 272 | addSprite(240, spriteSheet["billboard07"], -1.2) 273 | addSprite(240, spriteSheet["billboard06"], 1.2) 274 | addSprite(len(segments)-25, spriteSheet["billboard07"], -1.2) 275 | addSprite(len(segments)-25, spriteSheet["billboard06"], 1.2) 276 | 277 | for n := 10; n < 200; n += 4 + int(math.Floor(float64(n)/100)) { 278 | addSprite(n, spriteSheet["palm_tree"], randRange(0.5, 1)) 279 | addSprite(n, spriteSheet["palm_tree"], randRange(1, 3)) 280 | } 281 | 282 | for n := 250; n < 1000; n += 5 { 283 | addSprite(n, spriteSheet["column"], 1.1) 284 | addSprite(n+rand.Intn(5), spriteSheet["tree1"], -1-randMax(2)) 285 | addSprite(n+rand.Intn(5), spriteSheet["tree2"], -1-randMax(2)) 286 | } 287 | 288 | for n := 200; n < len(segments); n += 3 { 289 | choice := rand.Intn(len(plants) - 1) 290 | side := -1 291 | if rand.Intn(1) == 0 { 292 | side = 1 293 | } 294 | addSprite(n, plants[choice], float32(side)*randRange(2, 7)) 295 | } 296 | 297 | for n := 1000; n < (len(segments) - 50); n += 100 { 298 | side := float32(-1) 299 | if rand.Intn(1) == 0 { 300 | side = 1 301 | } 302 | choice := rand.Intn(len(billboards) - 1) 303 | addSprite(n+rand.Intn(50), billboards[choice], side) 304 | for i := 0; i < 20; i++ { 305 | side = -1 306 | if rand.Intn(1) == 0 { 307 | side = 1 308 | } 309 | choice = rand.Intn(len(plants) - 1) 310 | addSprite(n+rand.Intn(50), plants[choice], side*(1.5+rand.Float32())) 311 | } 312 | } 313 | } 314 | 315 | func resetCars() { 316 | cars = []*Car{} 317 | for n := 0; n < totalCars; n++ { 318 | offset := rand.Float32() 319 | if rand.Intn(1) == 0 { 320 | offset *= -0.8 321 | } else { 322 | offset *= 0.8 323 | } 324 | 325 | z := float32(math.Floor(rand.Float64()*float64(len(segments)))) * segmentLength 326 | choice := rand.Intn(len(carQuads) - 1) 327 | sprite := carQuads[choice] 328 | speed := maxSpeed/4 + rand.Float32()*maxSpeed/2 329 | if sprite == spriteSheet["semi"] { 330 | speed = maxSpeed/4 + rand.Float32()*maxSpeed/4 331 | } 332 | 333 | segment := findSegment(z) 334 | car := newCar(segment, sprite, z, offset, speed) 335 | segment.cars = append(segment.cars, car) 336 | cars = append(cars, car) 337 | } 338 | } 339 | 340 | func project(p *PointGroup, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth float32) { 341 | p.camera.x = p.world.x - cameraX 342 | p.camera.y = p.world.y - cameraY 343 | p.camera.z = p.world.z - cameraZ 344 | p.screen.scale = cameraDepth / p.camera.z 345 | p.screen.x = round((width / 2) + (p.screen.scale * p.camera.x * width / 2)) 346 | p.screen.y = round((height / 2) - (p.screen.scale * p.camera.y * height / 2)) 347 | p.screen.w = round((p.screen.scale * roadWidth * width / 2)) 348 | } 349 | 350 | func drawBackground(width, height float32, layer *gfx.Quad, rotation, offset float32) { 351 | layerx, layery, layerw, layerh := layer.GetViewport() 352 | imagew := layerw / 2 353 | imageh := layerh 354 | 355 | sourcex := layerx + int32(math.Floor(float64(float32(layerw)*rotation))) 356 | sourcey := layery 357 | sourcew := int32(math.Min(float64(imagew), float64(layerx+layerw-sourcex))) 358 | sourceh := imageh 359 | 360 | desty := offset 361 | destw := float32(math.Floor(float64(width * float32(sourcew/imagew)))) 362 | desth := height 363 | 364 | sourceq := gfx.NewQuad(sourcex, sourcey, sourcew, sourceh, background.GetWidth(), background.GetHeight()) 365 | gfx.Drawq(background, sourceq, 0, desty, 0, float32(layerw)/destw, float32(layerh)/desth) 366 | if sourcew < imagew { 367 | sourceq = gfx.NewQuad(layerx, sourcey, imagew, sourceh, background.GetWidth(), background.GetHeight()) 368 | gfx.Drawq(background, sourceq, destw-1, desty, 0, float32(layerw)/(width-destw), float32(layerh)/desth) 369 | } 370 | } 371 | 372 | func drawFog(x, y, width, height, fog float32) { 373 | if fog < 1 { 374 | gfx.SetColor(0, 81, 8, (1-fog)*255) 375 | gfx.Rect(gfx.FILL, x, y, width, height) 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /racer/game/player.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/tanema/amore/keyboard" 7 | ) 8 | 9 | type Player struct { 10 | *Car 11 | position float32 12 | } 13 | 14 | func newPlayer(z float32) *Player { 15 | return &Player{ 16 | Car: newCar(segments[0], spriteSheet["player_straight"], z, 0, 0), 17 | } 18 | } 19 | 20 | func (player *Player) update(dt float32) { 21 | player.segment = findSegment(player.position + player.point.z) 22 | speedPercent := player.speed / maxSpeed 23 | dx := dt * 2 * speedPercent // at top speed, should be able to cross from left to right (-1 to 1) in 1 second 24 | 25 | player.position = increase(player.position, dt*player.speed, trackLength) 26 | 27 | updown := player.segment.p2.world.y - player.segment.p1.world.y 28 | if keyboard.IsDown(keyboard.KeyLeft) { 29 | player.point.x -= dx 30 | if updown > 0 { 31 | player.source = spriteSheet["player_uphill_left"] 32 | } else { 33 | player.source = spriteSheet["player_left"] 34 | } 35 | } else if keyboard.IsDown(keyboard.KeyRight) { 36 | player.point.x += dx 37 | if updown > 0 { 38 | player.source = spriteSheet["player_uphill_right"] 39 | } else { 40 | player.source = spriteSheet["player_right"] 41 | } 42 | } else { 43 | if updown > 0 { 44 | player.source = spriteSheet["player_uphill_straight"] 45 | } else { 46 | player.source = spriteSheet["player_straight"] 47 | } 48 | } 49 | 50 | player.point.x -= (dx * speedPercent * player.segment.curve * centrifugal) 51 | 52 | if keyboard.IsDown(keyboard.KeyUp) { 53 | player.speed = accelerate(player.speed, accel, dt) 54 | } else if keyboard.IsDown(keyboard.KeyDown) { 55 | player.speed = accelerate(player.speed, breaking, dt) 56 | } else { 57 | player.speed = accelerate(player.speed, decel, dt) 58 | } 59 | 60 | if (player.point.x < -1) || (player.point.x > 1) { 61 | if player.speed > offRoadLimit { 62 | player.speed = accelerate(player.speed, offRoadDecel, dt) 63 | } 64 | 65 | for n := 0; n < len(player.segment.sprites); n++ { 66 | sprite := player.segment.sprites[n] 67 | spriteW := sprite.source.GetWidth() * sprite_scale 68 | ov := float32(-1) 69 | if sprite.offset > 0 { 70 | ov = 1 71 | } 72 | if overlap(player.point.x, player.point.w, sprite.offset+spriteW/2*ov, spriteW, 1) { 73 | player.speed = maxSpeed / 5 74 | player.position = increase(player.segment.p1.world.z, -player.point.z, trackLength) // stop in front of sprite (at front of segment) 75 | break 76 | } 77 | } 78 | } 79 | 80 | for n := 0; n < len(player.segment.cars); n++ { 81 | car := player.segment.cars[n] 82 | if player.speed > car.speed { 83 | if overlap(player.point.x, player.point.w, car.offset, car.point.w, 0.8) { 84 | player.speed = car.speed * (car.speed / player.speed) 85 | player.position = increase(car.point.z, -player.point.z, trackLength) 86 | break 87 | } 88 | } 89 | } 90 | 91 | player.point.x = clamp(player.point.x, -3, 3) // dont ever let it go too far out of bounds 92 | player.speed = clamp(player.speed, 0, maxSpeed) // or exceed maxSpeed 93 | } 94 | 95 | func (player *Player) draw() { 96 | playerPercent := percentRemaining(player.position+player.point.z, segmentLength) 97 | destY := (height / 2) - (cameraDepth / player.point.z * interpolate(player.segment.p1.camera.y, player.segment.p2.camera.y, playerPercent) * height / 2) 98 | side := float32(-1) 99 | if rand.Intn(1) == 0 { 100 | side = 1 101 | } 102 | bounce := (1.5 * rand.Float32() * player.speed / maxSpeed * resolution) * side 103 | player.Sprite.draw(width, height, roadWidth, cameraDepth/player.point.z, width/2, destY+bounce, -0.5, -1) 104 | } 105 | -------------------------------------------------------------------------------- /racer/game/rand.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func randMax(max float32) float32 { 8 | return randRange(0, max) 9 | } 10 | 11 | func randLimits(limit float32) float32 { 12 | return randRange(-limit, limit) 13 | } 14 | 15 | func randRange(min, max float32) float32 { 16 | return (rand.Float32() * (max - min)) + min 17 | } 18 | -------------------------------------------------------------------------------- /racer/game/segment.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/tanema/amore/gfx" 7 | ) 8 | 9 | type Segment struct { 10 | index int 11 | p1 PointGroup 12 | p2 PointGroup 13 | curve float32 14 | color string 15 | sprites []*Sprite 16 | cars []*Car 17 | looped bool 18 | } 19 | 20 | func newSegment(n int, lastY, newY, curve float32, color string) *Segment { 21 | return &Segment{ 22 | index: n, 23 | p1: PointGroup{ 24 | world: Point{ 25 | y: lastY, 26 | z: float32(n) * segmentLength, 27 | }, 28 | camera: Point{}, 29 | screen: Point{}, 30 | }, 31 | p2: PointGroup{ 32 | world: Point{ 33 | y: newY, 34 | z: float32(n+1) * segmentLength, 35 | }, 36 | camera: Point{}, 37 | screen: Point{}, 38 | }, 39 | curve: curve, 40 | sprites: []*Sprite{}, 41 | cars: []*Car{}, 42 | color: color, 43 | } 44 | } 45 | 46 | func (segment *Segment) draw(cameraDepth, width, lanes float32) { 47 | if segment.p1.camera.z <= cameraDepth { // clip by (already rendered) hill 48 | return 49 | } 50 | 51 | r1 := segment.rumbleWidth(segment.p1.screen.w, lanes) 52 | r2 := segment.rumbleWidth(segment.p2.screen.w, lanes) 53 | l1 := segment.laneMarkerWidth(segment.p1.screen.w, lanes) 54 | l2 := segment.laneMarkerWidth(segment.p2.screen.w, lanes) 55 | 56 | grasscolor := colors["grass"] 57 | if segment.color == "odd" { 58 | grasscolor = grasscolor.Darken(15) 59 | } 60 | gfx.SetColorC(grasscolor) 61 | gfx.Rect(gfx.FILL, 0, segment.p2.screen.y, width, segment.p1.screen.y-segment.p2.screen.y) 62 | 63 | rumblecolor := colors["rumble"] 64 | if segment.color == "start" || segment.color == "finish" { 65 | rumblecolor = colors[segment.color] 66 | } else if segment.color == "odd" { 67 | rumblecolor = rumblecolor.Darken(15) 68 | } 69 | gfx.SetColorC(rumblecolor) 70 | gfx.Polygon(gfx.FILL, []float32{segment.p1.screen.x - segment.p1.screen.w - r1, segment.p1.screen.y, segment.p1.screen.x - segment.p1.screen.w, segment.p1.screen.y, segment.p2.screen.x - segment.p2.screen.w, segment.p2.screen.y, segment.p2.screen.x - segment.p2.screen.w - r2, segment.p2.screen.y}) 71 | gfx.Polygon(gfx.FILL, []float32{segment.p1.screen.x + segment.p1.screen.w + r1, segment.p1.screen.y, segment.p1.screen.x + segment.p1.screen.w, segment.p1.screen.y, segment.p2.screen.x + segment.p2.screen.w, segment.p2.screen.y, segment.p2.screen.x + segment.p2.screen.w + r2, segment.p2.screen.y}) 72 | 73 | roadcolor := colors["road"] 74 | if segment.color == "start" || segment.color == "finish" { 75 | roadcolor = colors[segment.color] 76 | } else if segment.color == "odd" { 77 | roadcolor = roadcolor.Darken(15) 78 | } 79 | gfx.SetColorC(roadcolor) 80 | gfx.Polygon(gfx.FILL, []float32{segment.p1.screen.x - segment.p1.screen.w, segment.p1.screen.y, segment.p1.screen.x + segment.p1.screen.w, segment.p1.screen.y, segment.p2.screen.x + segment.p2.screen.w, segment.p2.screen.y, segment.p2.screen.x - segment.p2.screen.w, segment.p2.screen.y}) 81 | 82 | if segment.color == "even" { 83 | lanew1 := segment.p1.screen.w * 2 / lanes 84 | lanew2 := segment.p2.screen.w * 2 / lanes 85 | lanex1 := segment.p1.screen.x - segment.p1.screen.w + lanew1 86 | lanex2 := segment.p2.screen.x - segment.p2.screen.w + lanew2 87 | gfx.SetColorC(colors["lane"]) 88 | for lane := 1; lane < int(lanes); lane++ { 89 | gfx.Polygon(gfx.FILL, []float32{lanex1 - l1/2, segment.p1.screen.y, lanex1 + l1/2, segment.p1.screen.y, lanex2 + l2/2, segment.p2.screen.y, lanex2 - l2/2, segment.p2.screen.y}) 90 | lanex1 += lanew1 91 | lanex2 += lanew2 92 | } 93 | } 94 | } 95 | 96 | func (segment *Segment) rumbleWidth(projectedRoadWidth, lanes float32) float32 { 97 | return projectedRoadWidth / float32(math.Max(6, 2*float64(lanes))) 98 | } 99 | 100 | func (segment *Segment) laneMarkerWidth(projectedRoadWidth, lanes float32) float32 { 101 | return projectedRoadWidth / float32(math.Max(32, 8*float64(lanes))) 102 | } 103 | -------------------------------------------------------------------------------- /racer/game/sprite.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | type Sprite struct { 8 | source *gfx.Quad 9 | offset float32 10 | } 11 | 12 | func newSprite(source *gfx.Quad, offset float32) *Sprite { 13 | return &Sprite{ 14 | source: source, 15 | offset: offset, 16 | } 17 | } 18 | 19 | func (sprite *Sprite) draw(width, height, roadWidth, scale, destX, destY, offsetX, offsetY float32) { 20 | w, h := float32(sprite.source.GetWidth()), float32(sprite.source.GetHeight()) 21 | destW := (w * scale * width / 2) * (sprite_scale * roadWidth) 22 | destH := (h * scale * width / 2) * (sprite_scale * roadWidth) 23 | gfx.SetColor(255, 255, 255, 255) 24 | gfx.Drawq(sprites, sprite.source, destX+(destW*offsetX), destY+(destH*offsetY), 0, destW/w, destH/h) 25 | } 26 | -------------------------------------------------------------------------------- /racer/game/spritesheet.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/tanema/amore/gfx" 5 | ) 6 | 7 | var ( 8 | background *gfx.Image // our background image (loaded below) 9 | sprites *gfx.Image // our spritesheet (loaded below) 10 | backgrounds = map[string]*gfx.Quad{} 11 | spriteSheet = map[string]*gfx.Quad{} 12 | billboards = []*gfx.Quad{} 13 | plants = []*gfx.Quad{} 14 | carQuads = []*gfx.Quad{} 15 | sprite_scale float32 16 | ) 17 | 18 | func initSpriteSheets() { 19 | background, _ = gfx.NewImage("images/background.png") 20 | sprites, _ = gfx.NewImage("images/sprites.png") 21 | 22 | backgrounds["hills"] = gfx.NewQuad(5, 5, 1280, 480, background.Width, background.Height) 23 | backgrounds["sky"] = gfx.NewQuad(5, 495, 1280, 480, background.Width, background.Height) 24 | backgrounds["trees"] = gfx.NewQuad(5, 985, 1280, 480, background.Width, background.Height) 25 | 26 | spriteSheet["palm_tree"] = gfx.NewQuad(5, 5, 215, 540, sprites.Width, sprites.Height) 27 | spriteSheet["billboard08"] = gfx.NewQuad(230, 5, 385, 265, sprites.Width, sprites.Height) 28 | spriteSheet["tree1"] = gfx.NewQuad(625, 5, 360, 360, sprites.Width, sprites.Height) 29 | spriteSheet["dead_tree1"] = gfx.NewQuad(5, 555, 135, 332, sprites.Width, sprites.Height) 30 | spriteSheet["billboard09"] = gfx.NewQuad(150, 555, 328, 282, sprites.Width, sprites.Height) 31 | spriteSheet["boulder3"] = gfx.NewQuad(230, 280, 320, 220, sprites.Width, sprites.Height) 32 | spriteSheet["column"] = gfx.NewQuad(995, 5, 200, 315, sprites.Width, sprites.Height) 33 | spriteSheet["billboard01"] = gfx.NewQuad(625, 375, 300, 170, sprites.Width, sprites.Height) 34 | spriteSheet["billboard06"] = gfx.NewQuad(488, 555, 298, 190, sprites.Width, sprites.Height) 35 | spriteSheet["billboard05"] = gfx.NewQuad(5, 897, 298, 190, sprites.Width, sprites.Height) 36 | spriteSheet["billboard07"] = gfx.NewQuad(313, 897, 298, 190, sprites.Width, sprites.Height) 37 | spriteSheet["boulder2"] = gfx.NewQuad(621, 897, 298, 140, sprites.Width, sprites.Height) 38 | spriteSheet["tree2"] = gfx.NewQuad(1205, 5, 282, 295, sprites.Width, sprites.Height) 39 | spriteSheet["billboard04"] = gfx.NewQuad(1205, 310, 268, 170, sprites.Width, sprites.Height) 40 | spriteSheet["dead_tree2"] = gfx.NewQuad(1205, 490, 150, 260, sprites.Width, sprites.Height) 41 | spriteSheet["boulder1"] = gfx.NewQuad(1205, 760, 168, 248, sprites.Width, sprites.Height) 42 | spriteSheet["bush1"] = gfx.NewQuad(5, 1097, 240, 155, sprites.Width, sprites.Height) 43 | spriteSheet["cactus"] = gfx.NewQuad(929, 897, 235, 118, sprites.Width, sprites.Height) 44 | spriteSheet["bush2"] = gfx.NewQuad(255, 1097, 232, 152, sprites.Width, sprites.Height) 45 | spriteSheet["billboard03"] = gfx.NewQuad(5, 1262, 230, 220, sprites.Width, sprites.Height) 46 | spriteSheet["billboard02"] = gfx.NewQuad(245, 1262, 215, 220, sprites.Width, sprites.Height) 47 | spriteSheet["stump"] = gfx.NewQuad(995, 330, 195, 140, sprites.Width, sprites.Height) 48 | spriteSheet["semi"] = gfx.NewQuad(1365, 490, 122, 144, sprites.Width, sprites.Height) 49 | spriteSheet["truck"] = gfx.NewQuad(1365, 644, 100, 78, sprites.Width, sprites.Height) 50 | spriteSheet["car03"] = gfx.NewQuad(1383, 760, 88, 55, sprites.Width, sprites.Height) 51 | spriteSheet["car02"] = gfx.NewQuad(1383, 825, 80, 59, sprites.Width, sprites.Height) 52 | spriteSheet["car04"] = gfx.NewQuad(1383, 894, 80, 57, sprites.Width, sprites.Height) 53 | spriteSheet["car01"] = gfx.NewQuad(1205, 1018, 80, 56, sprites.Width, sprites.Height) 54 | spriteSheet["player_uphill_left"] = gfx.NewQuad(1383, 961, 80, 45, sprites.Width, sprites.Height) 55 | spriteSheet["player_uphill_straight"] = gfx.NewQuad(1295, 1018, 80, 45, sprites.Width, sprites.Height) 56 | spriteSheet["player_uphill_right"] = gfx.NewQuad(1385, 1018, 80, 45, sprites.Width, sprites.Height) 57 | spriteSheet["player_left"] = gfx.NewQuad(995, 480, 80, 41, sprites.Width, sprites.Height) 58 | spriteSheet["player_straight"] = gfx.NewQuad(1085, 480, 80, 41, sprites.Width, sprites.Height) 59 | spriteSheet["player_right"] = gfx.NewQuad(995, 531, 80, 41, sprites.Width, sprites.Height) 60 | 61 | sprite_scale = 0.3 * (1 / spriteSheet["player_straight"].GetWidth()) // the reference sprite width should be 1/3rd the (half-)roadWidth 62 | 63 | billboards = []*gfx.Quad{spriteSheet["billboard01"], spriteSheet["billboard02"], spriteSheet["billboard03"], spriteSheet["billboard04"], spriteSheet["billboard05"], spriteSheet["billboard06"], spriteSheet["billboard07"], spriteSheet["billboard08"], spriteSheet["billboard09"]} 64 | plants = []*gfx.Quad{spriteSheet["tree1"], spriteSheet["tree2"], spriteSheet["dead_tree1"], spriteSheet["dead_tree2"], spriteSheet["palm_tree"], spriteSheet["bush1"], spriteSheet["bush2"], spriteSheet["cactus"], spriteSheet["stump"], spriteSheet["boulder1"], spriteSheet["boulder2"], spriteSheet["boulder3"]} 65 | carQuads = []*gfx.Quad{spriteSheet["car01"], spriteSheet["car02"], spriteSheet["car03"], spriteSheet["car04"], spriteSheet["semi"], spriteSheet["truck"]} 66 | } 67 | -------------------------------------------------------------------------------- /racer/game/util.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | func accelerate(v, accel, dt float32) float32 { 8 | val := v + (accel * dt) 9 | return val 10 | } 11 | 12 | func easeIn(a, b, percent float32) float32 { 13 | return a + (b-a)*float32(math.Pow(float64(percent), 2)) 14 | } 15 | 16 | func easeOut(a, b, percent float32) float32 { 17 | return a + (b-a)*(1-float32(math.Pow(1-float64(percent), 2))) 18 | } 19 | 20 | func easeInOut(a, b, percent float32) float32 { 21 | return a + (b-a)*((-float32(math.Cos(float64(percent)*math.Pi))/2)+0.5) 22 | } 23 | 24 | func interpolate(a, b, percent float32) float32 { 25 | return a + (b-a)*percent 26 | } 27 | 28 | func clamp(value, min, max float32) float32 { 29 | return float32(math.Max(float64(min), math.Min(float64(value), float64(max)))) 30 | } 31 | 32 | func exponentialFog(distance, density float32) float32 { 33 | return 1 / float32(math.Pow(math.E, float64(distance*distance*density))) 34 | } 35 | 36 | func percentRemaining(n, total float32) float32 { 37 | return float32(math.Mod(float64(n), float64(total))) / total 38 | } 39 | 40 | func overlap(x1, w1, x2, w2, percent float32) bool { 41 | var half = percent / 2 42 | var min1 = x1 - (w1 * half) 43 | var max1 = x1 + (w1 * half) 44 | var min2 = x2 - (w2 * half) 45 | var max2 = x2 + (w2 * half) 46 | return !((max1 < min2) || (min1 > max2)) 47 | } 48 | 49 | func increase(start, increment, max float32) float32 { // with looping 50 | var result = start + increment 51 | if math.IsInf(float64(increment), 1) { 52 | panic("ahhh") 53 | } 54 | for result >= max { 55 | result -= max 56 | } 57 | for result < 0 { 58 | result += max 59 | } 60 | return result 61 | } 62 | 63 | func round(val float32) float32 { 64 | if val < 0 { 65 | return float32(int(val - 0.5)) 66 | } 67 | return float32(int(val + 0.5)) 68 | } 69 | -------------------------------------------------------------------------------- /racer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tanema/amore" 5 | "github.com/tanema/amore/keyboard" 6 | 7 | "github.com/tanema/amore-examples/racer/game" 8 | ) 9 | 10 | func main() { 11 | amore.OnLoad = game.New 12 | amore.Start(update, draw) 13 | } 14 | 15 | func update(deltaTime float32) { 16 | if keyboard.IsDown(keyboard.KeyEscape) { 17 | amore.Quit() 18 | } 19 | game.Update(deltaTime) 20 | } 21 | 22 | func draw() { 23 | game.Draw() 24 | } 25 | -------------------------------------------------------------------------------- /screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/screenshots.png -------------------------------------------------------------------------------- /star_field/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/tanema/amore" 8 | "github.com/tanema/amore/gfx" 9 | ) 10 | 11 | const ( 12 | starSpawnDepth float32 = 20 13 | starSpeed float32 = 0.05 14 | screenWidth float32 = 800 // Default screen size 15 | screenHeight float32 = 600 16 | ) 17 | 18 | type star struct { 19 | x float32 20 | y float32 21 | z float32 22 | px float32 23 | py float32 24 | cx float32 25 | cy float32 26 | } 27 | 28 | func randMax(max float32) float32 { 29 | rand.Seed(time.Now().UTC().UnixNano()) 30 | return (rand.Float32() * (max + 1)) 31 | } 32 | 33 | func (s *star) reset() { 34 | s.x = (randMax(screenWidth) - (screenWidth * 0.5)) * starSpawnDepth 35 | s.y = (randMax(screenWidth) - (screenHeight * 0.5)) * starSpawnDepth 36 | s.z = starSpawnDepth 37 | s.px = 0 38 | s.py = 0 39 | } 40 | 41 | func (s *star) update(dt float32) { 42 | s.px = s.x / s.z 43 | s.py = s.y / s.z 44 | s.z -= starSpeed 45 | s.cx = screenWidth * 0.5 46 | s.cy = screenHeight * 0.5 47 | if s.z < 0 || s.px > screenWidth || s.py > screenHeight { 48 | s.reset() 49 | } 50 | } 51 | 52 | func (s *star) draw() { 53 | if s.px == 0 { 54 | return 55 | } 56 | x := s.x / s.z 57 | y := s.y / s.z 58 | gfx.SetLineWidth((1.0/s.z + 1) * 2) 59 | gfx.PolyLine([]float32{x + s.cx, y + s.cy, s.px + s.cx, s.py + s.cy}) 60 | } 61 | 62 | func main() { 63 | units := int(300) 64 | stars := make([]*star, units) 65 | for i := 0; i < units; i++ { 66 | stars[i] = &star{} 67 | stars[i].reset() 68 | } 69 | 70 | amore.Start(func(dt float32) { 71 | for _, s := range stars { 72 | s.update(dt) 73 | } 74 | }, func() { 75 | gfx.SetColor(255, 255, 255, 255) 76 | for _, s := range stars { 77 | s.draw() 78 | } 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /test-all/assets/audio/bomb.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/audio/bomb.wav -------------------------------------------------------------------------------- /test-all/assets/fonts/arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/arial.ttf -------------------------------------------------------------------------------- /test-all/assets/fonts/arialbd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/arialbd.ttf -------------------------------------------------------------------------------- /test-all/assets/fonts/arialbi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/arialbi.ttf -------------------------------------------------------------------------------- /test-all/assets/fonts/ariali.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/ariali.ttf -------------------------------------------------------------------------------- /test-all/assets/fonts/image_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/image_font.png -------------------------------------------------------------------------------- /test-all/assets/fonts/mango_smoothie.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/fonts/mango_smoothie.otf -------------------------------------------------------------------------------- /test-all/assets/images/palm_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/images/palm_tree.png -------------------------------------------------------------------------------- /test-all/assets/images/particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanema/amore-examples/bdb03ccef93e0aad35dbdcd045f438c9ea072767/test-all/assets/images/particle.png -------------------------------------------------------------------------------- /test-all/assets/shaders/blackandwhite.glsl: -------------------------------------------------------------------------------- 1 | vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixel_coords) { 2 | vec4 pixel = Texel(texture, texcoord );//This is the current pixel color 3 | number average = (pixel.r+pixel.b+pixel.g)/3.0; 4 | pixel.r = average; 5 | pixel.g = average; 6 | pixel.b = average; 7 | return pixel; 8 | } 9 | -------------------------------------------------------------------------------- /test-all/assets/text/lorem.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec justo lorem. Nullam lorem lacus, ultrices lobortis porttitor vitae, mattis at sem. Vestibulum mi felis, sollicitudin a risus a, luctus pulvinar nisi. 2 | -------------------------------------------------------------------------------- /test-all/conf.toml: -------------------------------------------------------------------------------- 1 | identity = "" # The name of the save directory (string) 2 | title = "Amore Example" # The window title (string) 3 | icon = "" # Filepath to an image to use as the window's icon (string) 4 | width = 1280 # The window width (number) 5 | height = 720 # The window height (number) 6 | borderless = false # Remove all border visuals from the window (boolean) 7 | resizable = true # Let the window be user-resizable (boolean) 8 | minwidth = 1 # Minimum window width if the window is resizable (number) 9 | minheight = 1 # Minimum window height if the window is resizable (number) 10 | fullscreen = false # Enable fullscreen (boolean) 11 | fullscreentype = "normal" # Standard fullscreen or desktop fullscreen mode (string) 12 | vsync = true # Enable vertical sync (boolean) 13 | msaa = 16 # The number of samples to use with multi-sampled antialiasing (number) 14 | display = 1 # Index of the monitor to show the window in (number) 15 | highdpi = false # Enable high-dpi mode for the window on a Retina display (boolean) 16 | srgb = false # Enable sRGB gamma correction when drawing to the screen (boolean) 17 | centered = true # Center the window in the display 18 | x = -1 # The x-coordinate of the window's position in the specified display (number) 19 | y = -1 # The y-coordinate of the window's position in the specified display (number) 20 | -------------------------------------------------------------------------------- /test-all/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/tanema/amore" 8 | "github.com/tanema/amore/audio" 9 | "github.com/tanema/amore/file" 10 | "github.com/tanema/amore/gfx" 11 | _ "github.com/tanema/amore/joystick" 12 | "github.com/tanema/amore/keyboard" 13 | "github.com/tanema/amore/mouse" 14 | "github.com/tanema/amore/timer" 15 | _ "github.com/tanema/amore/touch" 16 | "github.com/tanema/amore/window" 17 | ) 18 | 19 | var ( 20 | tree *gfx.Image 21 | ttf *gfx.Font 22 | image_font *gfx.Font 23 | mx, my float32 24 | shader *gfx.Shader 25 | bomb *audio.Source 26 | use_shader = false 27 | canvas *gfx.Canvas 28 | quad *gfx.Quad 29 | psystem *gfx.ParticleSystem 30 | triangle_mesh *gfx.Mesh 31 | batch *gfx.SpriteBatch 32 | text *gfx.Text 33 | amore_text *gfx.Text 34 | ) 35 | 36 | func main() { 37 | window.SetMouseVisible(false) 38 | keyboard.OnKeyUp = keyUp 39 | 40 | canvas = gfx.NewCanvas(800, 600) 41 | tree, _ = gfx.NewImage("images/palm_tree.png") 42 | quad = gfx.NewQuad(0, 0, 200, 200, tree.GetWidth(), tree.GetHeight()) 43 | ttf, _ = gfx.NewTTFFont("fonts/arialbd.ttf", 20) 44 | image_font, _ = gfx.NewImageFont("fonts/image_font.png", " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456790.,!?-+/():;%&`'*#=[]\"") 45 | image_font.SetFallbacks(ttf) 46 | shader = gfx.NewShader("shaders/blackandwhite.glsl") 47 | var er error 48 | bomb, er = audio.NewSource("audio/bomb.wav", true) 49 | if er != nil { 50 | panic(er) 51 | } 52 | bomb.SetLooping(true) 53 | text, _ = gfx.NewColorTextExt(ttf, 54 | []string{file.ReadString("text/lorem.txt"), file.ReadString("text/lorem.txt")}, 55 | []*gfx.Color{gfx.NewColor(255, 255, 255, 255), gfx.NewColor(255, 0, 255, 255)}, 56 | 600, gfx.AlignLeft) 57 | amore_text, _ = gfx.NewColorText(ttf, []string{"a", "m", "o", "r", "e"}, 58 | []*gfx.Color{ 59 | gfx.NewColor(0, 255, 0, 255), 60 | gfx.NewColor(255, 0, 255, 255), 61 | gfx.NewColor(255, 255, 0, 255), 62 | gfx.NewColor(0, 0, 255, 255), 63 | gfx.NewColor(255, 255, 255, 255), 64 | }) 65 | 66 | particle, _ := gfx.NewImage("images/particle.png") 67 | psystem = gfx.NewParticleSystem(particle, 10) 68 | psystem.SetParticleLifetime(2, 5) // Particles live at least 2s and at most 5s. 69 | psystem.SetEmissionRate(5) 70 | psystem.SetSizeVariation(1) 71 | psystem.SetLinearAcceleration(-20, -20, 20, 20) // Random movement in all directions. 72 | psystem.SetSpeed(3, 5) 73 | psystem.SetSpin(0.1, 0.5) 74 | psystem.SetSpinVariation(1) 75 | 76 | triangle_mesh, _ = gfx.NewMesh([]float32{ 77 | 25, 0, 78 | 0, 0, 79 | 255, 0, 0, 255, 80 | 81 | 50, 50, 82 | 1, 0, 83 | 255, 0, 0, 255, 84 | 85 | 0, 50, 86 | 1, 1, 87 | 255, 0, 0, 255, 88 | 89 | 0, 0, 90 | 0, 1, 91 | 255, 0, 0, 255, 92 | }, 4) 93 | triangle_mesh.SetTexture(tree) 94 | 95 | q := gfx.NewQuad(50, 50, 50, 50, tree.GetWidth(), tree.GetHeight()) 96 | q2 := gfx.NewQuad(100, 50, 50, 50, tree.GetWidth(), tree.GetHeight()) 97 | batch = gfx.NewSpriteBatch(tree, 4) 98 | batch.Addq(q, 0, 0) 99 | batch.Addq(q2, 50, 0) 100 | batch.Addq(q, 50, 50) 101 | batch.Addq(q2, 0, 50) 102 | batch.Addq(q, 100, 50) 103 | 104 | amore.Start(update, draw) 105 | } 106 | 107 | func keyUp(key keyboard.Key) { 108 | switch key { 109 | case keyboard.KeyEscape: 110 | amore.Quit() 111 | case keyboard.Key1: 112 | use_shader = !use_shader 113 | case keyboard.Key2: 114 | if bomb.IsPlaying() { 115 | bomb.Stop() 116 | } else { 117 | bomb.Play() 118 | } 119 | case keyboard.Key3: 120 | window.ShowMessageBox("title", "message", []string{"okay", "cancel"}, window.MessageBoxInfo, true) 121 | case keyboard.Key4: 122 | triangle_mesh.SetVertexMap([]uint32{0, 1, 2}) 123 | case keyboard.Key5: 124 | triangle_mesh.ClearVertexMap() 125 | case keyboard.Key6: 126 | batch.SetDrawRange(2, 4) 127 | case keyboard.Key7: 128 | batch.ClearDrawRange() 129 | } 130 | } 131 | 132 | func update(deltaTime float32) { 133 | mx, my = mouse.GetPosition() 134 | mx, my = window.PixelToWindowCoords(mx, my) 135 | psystem.Update(deltaTime) 136 | } 137 | 138 | func draw() { 139 | if use_shader { 140 | gfx.SetShader(shader) 141 | } else { 142 | gfx.SetShader(nil) 143 | } 144 | 145 | gfx.SetColor(255, 255, 255, 255) 146 | gfx.SetPointSize(5) 147 | gfx.Points(200, 20) 148 | gfx.Circle(gfx.FILL, 230, 20, 20) 149 | 150 | gfx.SetLineWidth(1) 151 | //text 152 | gfx.Draw(text, 0, 300) 153 | gfx.Rect(gfx.LINE, 0, 300, text.GetWidth(), text.GetHeight()) 154 | 155 | gfx.SetLineWidth(10) 156 | 157 | gfx.SetColor(255, 255, 255, 255) 158 | gfx.Draw(batch, 50, 150) 159 | 160 | //stencil 161 | gfx.Stencil(func() { gfx.Rect(gfx.FILL, 426, 240, 426, 240) }) 162 | gfx.SetStencilTest(gfx.CompareEqual, 0) 163 | gfx.SetColor(239, 96, 17, 255) 164 | gfx.Rect("fill", 400, 200, 826, 440) 165 | gfx.ClearStencilTest() 166 | 167 | // rectangle 168 | gfx.SetCanvas(canvas) 169 | gfx.SetColor(0, 170, 0, 155) 170 | gfx.Rect(gfx.FILL, 20.0, 20.0, 200.0, 200.0) 171 | gfx.Rect(gfx.LINE, 250.0, 20.0, 200.0, 200.0) 172 | gfx.SetCanvas() 173 | gfx.SetColor(255, 255, 255, 255) 174 | gfx.Draw(canvas, 300, 100) 175 | 176 | // circle 177 | gfx.SetColor(170, 0, 0, 255) 178 | gfx.Circle(gfx.FILL, 100.0, 500.0, 50.0) 179 | gfx.Arc(gfx.FILL, 200.0, 500.0, 50.0, 0, math.Pi) 180 | gfx.Ellipse(gfx.FILL, 300.0, 500.0, 50.0, 20.0) 181 | gfx.Circle(gfx.LINE, 100.0, 600.0, 50.0) 182 | gfx.Arc(gfx.LINE, 200.0, 550.0, 50.0, 0, math.Pi) 183 | gfx.Ellipse(gfx.LINE, 300.0, 550.0, 50.0, 20.0) 184 | 185 | // line 186 | gfx.SetColor(0, 0, 170, 255) 187 | gfx.Line( 188 | 800.0, 100.0, 850.0, 100.0, 189 | 900.0, 20.0, 950.0, 100.0, 190 | 1030.0, 100.0, 950.0, 180.0, 191 | ) 192 | 193 | // image 194 | gfx.SetColor(255, 255, 255, 255) 195 | //x, y, rotate radians, scale x, y, offset x, y, shear x, y 196 | gfx.Draw(tree, 500, 50, -0.4, 0.5, 0.8, -100, -200, -0.2, 0.4) 197 | gfx.Drawq(tree, quad, 1000, 500) 198 | 199 | // image font 200 | gfx.SetFont(image_font) 201 | gfx.Print("test one two", 0, 0) 202 | // ttf font 203 | gfx.SetFont(ttf) 204 | gfx.Print("test one two", 200, 100, math.Pi/2, 2, 2) 205 | 206 | //FPS 207 | gfx.SetColor(0, 170, 170, 255) 208 | gfx.Print(fmt.Sprintf("fps: %v", timer.GetFPS()), 1200, 0) 209 | 210 | gfx.Draw(psystem, 200, 200) 211 | 212 | gfx.SetColor(255, 255, 255, 255) 213 | gfx.Draw(triangle_mesh, 50, 50) 214 | 215 | //mouse position 216 | gfx.Circle(gfx.FILL, mx, my, 20.0) 217 | 218 | gfx.Draw(amore_text, 500, 400, 0, 3, 3) 219 | } 220 | --------------------------------------------------------------------------------