Tons of experimental updates.

This commit is contained in:
2023-10-27 09:59:01 -04:00
parent 8fd27c5df7
commit 1ffc593ebe
11 changed files with 625 additions and 0 deletions

51
character/character.go Normal file
View File

@@ -0,0 +1,51 @@
package character
import (
"image/color"
p "loading/point"
"math"
"math/rand"
"strconv"
)
const (
agentIdMax = 100
offset = 15
xd = offset
yd = offset
)
type Character struct {
Name string
Pose p.Point
Shape []p.Point
Color color.RGBA
Angle float32
DeltaAngle float32
SignFlip float32
Buffer float32
}
func NewCharacter() Character {
return Character{
Name: "Agent-" + strconv.Itoa(rand.Intn(agentIdMax)),
Pose: p.Point{X: 0, Y: 0},
Shape: []p.Point{{X: 0, Y: 0}, {X: xd, Y: 0}, {X: xd, Y: yd}},
Color: color.RGBA{0xFF, 0xFF, 0xFF, 0xFF},
Angle: (rand.Float32() * 2 * math.Pi),
DeltaAngle: 0,
Buffer: offset,
}
}
func (c *Character) SetPose(x float32, y float32) {
c.Pose.X = x
c.Pose.Y = y
}
func (c *Character) LoadShape(vertices []p.Point) {
c.Shape = c.Shape[:0]
c.Shape = append(c.Shape, vertices...)
}

147
drawer.go Normal file
View File

@@ -0,0 +1,147 @@
package main
import (
"fmt"
"image/color"
"loading/character"
"loading/objects"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/hajimehoshi/ebiten/v2/vector"
)
func DrawBobble(b objects.Bobble, image *ebiten.Image) {
if b.Radius < 0 {
return
}
vector.DrawFilledCircle(image, b.Pose.X, b.Pose.Y, b.Radius+10, b.Bordercolor, true)
vector.DrawFilledCircle(image, b.Pose.X, b.Pose.Y, b.Radius, b.Color, true)
}
func DrawCharacter(c character.Character, image *ebiten.Image) {
if len(c.Shape) <= 0 {
return
}
var path vector.Path
path.MoveTo(c.Pose.X, c.Pose.Y)
for _, p := range c.Shape {
//rotation time *dance*
newX := c.Pose.X + float32(math.Cos(float64(c.Angle)))*(p.X) - float32(math.Sin(float64(c.Angle)))*(p.Y)
newY := c.Pose.Y + float32(math.Sin(float64(c.Angle)))*(p.X) + float32(math.Cos(float64(c.Angle)))*(p.Y)
path.LineTo(newX, newY)
}
path.Close()
var vs []ebiten.Vertex
var is []uint16
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = float32(c.Color.R) / 0xFF
vs[i].ColorG = float32(c.Color.B) / 0xFF
vs[i].ColorB = float32(c.Color.G) / 0xFF
vs[i].ColorA = 1
}
op := &ebiten.DrawTrianglesOptions{}
//pose information
//angle := fmt.Sprintf("%.2f", c.Angle*180/math.Pi)
//delta := fmt.Sprintf("%.2f", c.DeltaAngle*180/math.Pi)
//text.Draw(image, angle, mplusTinyFont, int(c.Pose.X+20), int(c.Pose.Y), color.White)
//text.Draw(image, delta, mplusTinyFont, int(c.Pose.X+20), int(c.Pose.Y+15), color.White)
image.DrawTriangles(vs, is, whiteSubImage, op)
}
func (g *Game) DrawLoading(image *ebiten.Image) {
//identifier
text.Draw(image, versionInfo, mplusTinyFont, 20, 60, color.White)
//radial
var xs []int
var ys []int
var tc []uint8
var scales []float32
fade_scale := 0xFF / numTiles
for i := 0; i < numTiles; i++ {
xs = append(xs, int(math.Cos(float64(g.counter)/(math.Pi*3)-float64(i)*math.Pi/3)*40+screenWidth/2-float64(tileWidth)/2))
ys = append(ys, int(math.Sin(float64(g.counter)/(math.Pi*3)-float64(i)*math.Pi/3)*40+screenHeight/2-float64(tileHeight)/2))
tc = append(tc, uint8(0xFF-fade_scale*i))
scales = append(scales, float32(math.Sin(float64(g.counter)/(math.Pi*12))/2+1))
}
for i := numTiles - 1; i >= 0; i-- {
drawTile(image, float32(xs[i]), float32(ys[i]), scales[i], color.RGBA{tc[i], tc[i], tc[i], 0xFF})
}
//Percentage >:]
//percent := (maxPercent - 1/math.Exp(float64(g.counter-1)-10/6)) / maxPercent
offset := float64(4.605)
exppart0 := float64(g.counter-1) / 10000
percent := maxPercent - 1/math.Exp(exppart0-offset)
if percent < 0 {
percent = 0
}
//100-1/e^(x/1000 - 4.6)
text.Draw(image, fmt.Sprintf("%.2f%%", percent), mplusTinyFont, screenWidth/2-70/2, screenHeight-80, color.RGBA{0xC0, 0xC0, 0xC0, 0xC0})
}
func drawTile(screen *ebiten.Image, x float32, y float32, scale float32, c color.RGBA) {
var scaleFactor float32 = 1
if scale > 0 {
scaleFactor = scale
}
drawTileToScale(screen, x, y, scaleFactor, c)
}
func drawTileToScale(screen *ebiten.Image, x float32, y float32, scale float32, c color.RGBA) {
var path vector.Path
//scale delta
xd := tileWidth * scale
yd := tileHeight * scale
//position at object origin shifted slightly for the half width/height adjustment
path.MoveTo(x-xd/2, y-yd/2)
path.LineTo(x+xd/2, y-yd/2)
path.LineTo(x+xd/2, y+yd/2)
path.LineTo(x-xd/2, y+yd/2)
path.LineTo(x-xd/2, y+yd/2)
path.Close()
var vs []ebiten.Vertex
var is []uint16
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = float32(c.R) / 0xFF
vs[i].ColorG = float32(c.B) / 0xFF
vs[i].ColorB = float32(c.G) / 0xFF
vs[i].ColorA = 1
}
op := &ebiten.DrawTrianglesOptions{}
screen.DrawTriangles(vs, is, whiteSubImage, op)
}

64
game.go Normal file
View File

@@ -0,0 +1,64 @@
package main
import (
"image/color"
c "loading/character"
"loading/objects"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/text"
)
// Game implements ebiten.Game interface.
type Game struct {
counter int
player c.Character
enemies []c.Character
keys []ebiten.Key
bobbles []objects.Bobble
}
// Layout takes the outside size (e.g., the window size) and returns the (logical) screen size.
// If you don't have to adjust the screen size with the outside size, just return a fixed size.
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 800, 600
}
// Draw draws the game screen.
// Draw is called every frame (typically 1/60[s] for 60Hz display).
func (g *Game) Draw(screen *ebiten.Image) {
dst := screen
dst.Fill(color.RGBA{0x28, 0x28, 0x28, 0xff})
// render the info text
text.Draw(dst, bsofttext, mplusNormalFont, 20, 40, color.White)
//g.DrawChase(dst)
//g.DrawLoading(dst)
g.DrawBobbles(dst)
}
func (g *Game) DrawBobbles(image *ebiten.Image) {
//identifier
text.Draw(image, bobblesInfo, mplusTinyFont, 20, 60, color.White)
//light up the bobbles
for i := 0; i < len(g.bobbles); i++ {
DrawBobble(g.bobbles[i], image)
}
}
func (g *Game) DrawChase(image *ebiten.Image) {
//identifier
text.Draw(image, sceneInfo, mplusTinyFont, 20, 60, color.White)
//draw the enemies first
for i := 0; i < len(g.enemies); i++ {
DrawCharacter(g.enemies[i], image)
}
//followed by the player character
DrawCharacter(g.player, image)
}

108
loader.go Normal file
View File

@@ -0,0 +1,108 @@
package main
import (
"fmt"
"image/color"
c "loading/character"
"loading/objects"
"log"
"math/rand"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"github.com/hajimehoshi/ebiten/v2/text"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)
func init() {
//tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
tt, err := opentype.Parse(fonts.PressStart2P_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 24,
DPI: dpi,
Hinting: font.HintingVertical,
})
if err != nil {
log.Fatal(err)
}
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 48,
DPI: dpi,
Hinting: font.HintingFull, // Use quantization to save glyph cache images.
})
if err != nil {
log.Fatal(err)
}
mplusTinyFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 10,
DPI: dpi,
Hinting: font.HintingVertical,
})
if err != nil {
log.Fatal(err)
}
// Adjust the line height.
mplusBigFont = text.FaceWithLineHeight(mplusBigFont, 54)
}
func loading() {
game := &Game{}
whiteImage.Fill(color.White)
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle(bsofttext)
fmt.Println(bsofttext)
//loadCharacters(game)
loadBobbles(game)
// Call ebiten.RunGame to start your game loop.
if err := ebiten.RunGame(game); err != nil {
log.Fatal(err)
}
}
func loadBobbles(game *Game) {
// generate bobbles
gridWidth := bubbleGridSpacing * bubbleGridCols
gridHeight := bubbleGridSpacing * bubbleGridRows
gridStartX := float32((screenWidth - gridWidth - bubbleGridSpacing) / 2)
gridStartY := float32((screenHeight - gridHeight - bubbleGridSpacing) / 2)
for i := 0; i < totalBobbles; i++ {
game.bobbles = append(game.bobbles, objects.NewBobble())
ix := float32(i%bubbleGridRows*bubbleGridSpacing + i%bubbleGridRows*bubbleGridSpacing)
iy := float32(i/bubbleGridCols*bubbleGridSpacing + i/bubbleGridCols*bubbleGridSpacing)
game.bobbles[i].Color = color.RGBA{0xFF, 0x00, 0x00, 0xFF}
game.bobbles[i].Bordercolor = color.RGBA{0x80, 0x00, 0x00, 0xFF}
game.bobbles[i].SetPose(gridStartX+ix, gridStartY+iy)
}
}
func loadCharacters(game *Game) {
// generate player character
game.player = c.NewCharacter()
game.player.SetPose(float32(rand.Intn(screenWidth)), float32(rand.Intn(screenHeight)))
// generate enemies
for i := 0; i < enemyCount; i++ {
//create new enemy, slip 'em in
game.enemies = append(game.enemies, c.NewCharacter())
//set random position based on screen dimensions
game.enemies[i].SetPose(float32(rand.Intn(screenWidth)), float32(rand.Intn(screenHeight)))
//distinguish them from the player character
game.enemies[i].Color = color.RGBA{0xFF, 0x00, 0x00, 0xFF}
}
}

23
loading.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import "loading/scenes"
type Loading struct {
//Manager scenes.SceneManager
}
func init() {
}
func (l *Loading) Update() error {
return nil
}
func (l *Loading) Draw() {
}
func (l *Loading) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 800, 600
}

33
main.go
View File

@@ -1,5 +1,38 @@
package main
import (
"image"
"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
)
const (
screenWidth = 800
screenHeight = 600
bsofttext = `bsoft games`
versionInfo = `loading screen MKI`
sceneInfo = `scene a`
bobblesInfo = `bobbles`
enemyCount = 12
totalBobbles = 9
bubbleGridRows = 3
bubbleGridCols = 3
bubbleGridSpacing = 40
)
var (
mplusNormalFont font.Face
mplusBigFont font.Face
mplusTinyFont font.Face
tileWidth float32 = 20
tileHeight float32 = 20
numTiles int = 5
maxPercent float64 = 100
whiteImage = ebiten.NewImage(3, 3)
whiteSubImage = whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func main() {
loading()
}

40
objects/bobble.go Normal file
View File

@@ -0,0 +1,40 @@
package objects
import (
"image/color"
p "loading/point"
"math/rand"
"strconv"
)
const (
defaultRadius = 20
)
type Bobble struct {
Radius float32
Pose p.Point
Id string
Color color.RGBA
Bordercolor color.RGBA
}
func NewBobble() Bobble {
//default bobble values, with white center and black border
return Bobble{
Radius: defaultRadius,
Pose: p.Point{X: 0, Y: 0},
Color: color.RGBA{0xFF, 0xFF, 0xFF, 0xFF},
Bordercolor: color.RGBA{0x00, 0x00, 0x00, 0xFF},
Id: "ID-" + strconv.Itoa(rand.Intn(100)),
}
}
func GetDefaultRadius() float32 {
return float32(defaultRadius)
}
func (b *Bobble) SetPose(x float32, y float32) {
b.Pose.X = x
b.Pose.Y = y
}

23
scenes/menu.go Normal file
View File

@@ -0,0 +1,23 @@
package scenes
import "github.com/hajimehoshi/ebiten/v2"
type Menu struct {
}
func CreateNewMenu() Menu {
return Menu{}
}
func (m *Menu) Draw(screen *ebiten.Image) {
}
func (m *Menu) Update() error {
return nil
}
func (m *Menu) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 800, 600
}

9
scenes/scene.go Normal file
View File

@@ -0,0 +1,9 @@
package scenes
import "github.com/hajimehoshi/ebiten/v2"
type Scene interface {
Update() error
Draw(screen *ebiten.Image)
Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int)
}

1
scenes/splash.go Normal file
View File

@@ -0,0 +1 @@
package scenes

126
updater.go Normal file
View File

@@ -0,0 +1,126 @@
package main
import (
"loading/objects"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
// Update proceeds the game state.
// Update is called every tick (1/60 [s] by default).
func (g *Game) Update() error {
// Write your game's logical update.
g.counter++
g.UpdatePlayer()
//g.UpdateEnemies()
g.UpdateBobbles()
return nil
}
func (g *Game) UpdateBobbles() {
for i := 0; i < len(g.bobbles); i++ {
bobby := &g.bobbles[i]
bobby.Radius = objects.GetDefaultRadius() + float32(10*math.Sin(float64(g.counter)/(math.Pi*4)))
}
}
func (g *Game) UpdatePlayer() {
//update player angle based on mouse position
mx, my := ebiten.CursorPosition()
playerAngle := float32(math.Atan2(float64(float32(my)-g.player.Pose.Y), float64(float32(mx)-g.player.Pose.X)))
g.player.Angle = playerAngle
//update position based on wasd input
g.keys = inpututil.AppendPressedKeys(g.keys[:0])
step := float32(10)
for _, k := range g.keys {
switch k {
case ebiten.KeyW:
g.player.Pose.Y = g.player.Pose.Y - step
case ebiten.KeyA:
g.player.Pose.X = g.player.Pose.X - step
case ebiten.KeyS:
g.player.Pose.Y = g.player.Pose.Y + step
case ebiten.KeyD:
g.player.Pose.X = g.player.Pose.X + step
default:
}
}
}
func (g *Game) UpdateEnemies() {
/*
we're going to rotate the enemies so they slowly face the player, to do this we'll find the angle delta
between the player and the enemy via atan2, then add approximately one quarter of this value to the
current enemy angle
*/
/*
for _, e := range g.enemies {
//diff := float32(math.Atan2(float64(g.player.Pose.Y-e.Pose.Y), float64(g.player.Pose.X-e.Pose.X)))
e.Angle = e.Angle + math.Pi/4
}
*/
for i := 0; i < len(g.enemies); i++ {
enemy := &g.enemies[i]
//find distancees between enemy and player
dx := float64(g.player.Pose.X - enemy.Pose.X)
dy := float64(g.player.Pose.Y - enemy.Pose.Y)
//current sign
currentSign := enemy.DeltaAngle / float32(math.Abs(float64(enemy.DeltaAngle)))
targetAngle := float32(math.Atan2(dy, dx)) + math.Pi*2
//_, absTargetAngle := math.Modf(float64(targetAngle))
phi := float32(math.Mod(float64(targetAngle-enemy.Angle), float64(2*math.Pi)))
if phi > math.Pi {
enemy.DeltaAngle = math.Pi - phi
} else {
enemy.DeltaAngle = phi
}
//enemy.DeltaAngle = float32(math.Min(2*math.Pi-math.Abs(float64(enemy.Angle-targetAngle)), math.Abs(float64(enemy.Angle-targetAngle))))
enemy.Angle = enemy.Angle + enemy.DeltaAngle/16
enemy.SignFlip = currentSign
for j := 0; j < len(g.enemies); j++ {
if j == i {
continue
}
xdist := (g.enemies[j].Pose.X - enemy.Pose.X)
ydist := (g.enemies[j].Pose.Y - enemy.Pose.Y)
//quick boundary check before we get to the algebra
if xdist > enemy.Buffer || ydist > enemy.Buffer {
continue
}
dist := float32(math.Sqrt(float64(xdist*xdist + ydist*ydist)))
if dist < enemy.Buffer {
enemy.Pose.X = enemy.Pose.X + dist*float32(math.Cos(float64(targetAngle)))
enemy.Pose.Y = enemy.Pose.Y + dist*float32(math.Sin(float64(targetAngle)))
}
}
enemy.Pose.X = enemy.Pose.X + float32(dx)/360
enemy.Pose.Y = enemy.Pose.Y + float32(dy)/360
//enemy.Angle = enemy.Angle + enemy.DeltaAngle/(math.Pi/4)
}
}