+446
Diff
round #0
README.md
README.md
This is a binary file and will not be displayed.
+33
coordinates/main.go
+33
coordinates/main.go
···
2
2
3
3
import (
4
4
"github.com/firefly-zero/firefly-go/firefly"
5
+
"github.com/orsinium-labs/tinymath"
5
6
)
6
7
7
8
var (
···
39
40
firefly.ClearScreen(firefly.ColorNone)
40
41
}
41
42
43
+
// draw 10 points in a circular way
44
+
ps := CirclePointsInt(10, 5)
45
+
for _, p := range ps {
46
+
firefly.DrawPoint(translatePoint(p), firefly.ColorRed)
47
+
}
48
+
42
49
// points in 4 quadrants
43
50
firefly.DrawPoint(translatePoint(firefly.Point{X: 10, Y: 10}), firefly.ColorCyan)
44
51
firefly.DrawPoint(translatePoint(firefly.Point{X: 10, Y: -10}), firefly.ColorGreen)
···
51
58
y := -p.Y + SCREENHEIGHT/2
52
59
// firefly.LogDebug(strings.Join([]string{"x: ", strconv.Itoa(x), ", y: ", strconv.Itoa(y)}, " "))
53
60
return firefly.Point{X: x, Y: y}
61
+
}
62
+
63
+
func CirclePointsInt(radius float32, n int) []firefly.Point {
64
+
points := make([]firefly.Point, 0, n)
65
+
seen := make(map[firefly.Point]struct{}, n)
66
+
67
+
// Wir samplen mit höherer Auflösung und sammeln eindeutige gerundete Punkte,
68
+
// bis wir n Stück haben.
69
+
// Oversample-Faktor dynamisch hochdrehen, falls nötig.
70
+
for oversample := n * 2; len(points) < n; oversample *= 2 {
71
+
for i := 0; i < oversample && len(points) < n; i++ {
72
+
angle := 2 * tinymath.Pi * float32(i) / float32(oversample)
73
+
74
+
x := int(tinymath.Round(radius * tinymath.Cos(angle)))
75
+
y := int(tinymath.Round(radius * tinymath.Sin(angle)))
76
+
77
+
p := firefly.Point{X: x, Y: y}
78
+
if _, ok := seen[p]; ok {
79
+
continue
80
+
}
81
+
seen[p] = struct{}{}
82
+
points = append(points, p)
83
+
}
84
+
}
85
+
86
+
return points
54
87
}
55
88
56
89
func drawCoordinateSystem() {
+11
tmp/go.mod
+11
tmp/go.mod
···
1
+
module github.com/voigt/firefly-zero-playground/tmp
2
+
3
+
go 1.25.0
4
+
5
+
require github.com/gen2brain/raylib-go/raylib v0.55.1
6
+
7
+
require (
8
+
github.com/ebitengine/purego v0.7.1 // indirect
9
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
10
+
golang.org/x/sys v0.20.0 // indirect
11
+
)
+8
tmp/go.sum
+8
tmp/go.sum
···
1
+
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
2
+
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
3
+
github.com/gen2brain/raylib-go/raylib v0.55.1 h1:1rdc10WvvYjtj7qijHnV9T38/WuvlT6IIL+PaZ6cNA8=
4
+
github.com/gen2brain/raylib-go/raylib v0.55.1/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
5
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
6
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
7
+
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
8
+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+196
tmp/main.go
+196
tmp/main.go
···
1
+
package main
2
+
3
+
import (
4
+
rl "github.com/gen2brain/raylib-go/raylib"
5
+
)
6
+
7
+
type Player struct {
8
+
Pos rl.Vector2
9
+
Vel rl.Vector2
10
+
Size rl.Vector2
11
+
OnGround bool
12
+
13
+
// Timers (seconds)
14
+
CoyoteTimer float32
15
+
JumpBuffer float32
16
+
Jumping bool // true after jump start until we land
17
+
}
18
+
19
+
func clamp(v, min, max float32) float32 {
20
+
if v < min {
21
+
return min
22
+
}
23
+
if v > max {
24
+
return max
25
+
}
26
+
return v
27
+
}
28
+
29
+
func approach(current, target, delta float32) float32 {
30
+
if current < target {
31
+
current += delta
32
+
if current > target {
33
+
return target
34
+
}
35
+
return current
36
+
}
37
+
if current > target {
38
+
current -= delta
39
+
if current < target {
40
+
return target
41
+
}
42
+
return current
43
+
}
44
+
return current
45
+
}
46
+
47
+
func main() {
48
+
const screenW, screenH int32 = 960, 540
49
+
rl.InitWindow(screenW, screenH, "raylib-go: platformer feel (accel/decel + buffer/coyote + variable jump + fast-fall)")
50
+
defer rl.CloseWindow()
51
+
rl.SetTargetFPS(60)
52
+
53
+
groundY := float32(screenH - 80)
54
+
groundThickness := float32(80)
55
+
56
+
p := Player{
57
+
Pos: rl.NewVector2(120, groundY-60),
58
+
Vel: rl.NewVector2(0, 0),
59
+
Size: rl.NewVector2(50, 60),
60
+
}
61
+
62
+
// ---------- Tuning ----------
63
+
maxRunSpeed := float32(380)
64
+
65
+
// Separate accel/decel
66
+
groundAccel := float32(2600)
67
+
groundDecel := float32(3200)
68
+
airAccel := float32(1400)
69
+
airDecel := float32(300)
70
+
71
+
// Jump
72
+
jumpSpeed := float32(640)
73
+
74
+
// Variable jump height: cut velocity when releasing jump while going up
75
+
jumpCutMultiplier := float32(0.35)
76
+
77
+
// Coyote time + jump buffer (seconds)
78
+
coyoteTime := float32(0.10)
79
+
jumpBufferTime := float32(0.12)
80
+
81
+
// --- Fast-fall tuning ---
82
+
// Base gravity is used when rising (Vel.Y < 0) and jump is held.
83
+
// When falling (Vel.Y > 0) we apply higher gravity (fallMultiplier).
84
+
// When jump is released early during rise, we apply higher gravity too (lowJumpMultiplier).
85
+
gravity := float32(1200) // "floatier" base on the way up
86
+
fallMultiplier := float32(2.2) // faster fall
87
+
lowJumpMultiplier := float32(2.6) // stronger pull-down if jump released early
88
+
maxFallSpeed := float32(2200) // higher terminal velocity for snappy fall
89
+
90
+
for !rl.WindowShouldClose() {
91
+
dt := rl.GetFrameTime()
92
+
93
+
// ---- Input ----
94
+
var dir float32 = 0
95
+
if rl.IsKeyDown(rl.KeyA) || rl.IsKeyDown(rl.KeyLeft) {
96
+
dir -= 1
97
+
}
98
+
if rl.IsKeyDown(rl.KeyD) || rl.IsKeyDown(rl.KeyRight) {
99
+
dir += 1
100
+
}
101
+
102
+
jumpPressed := rl.IsKeyPressed(rl.KeySpace)
103
+
jumpHeld := rl.IsKeyDown(rl.KeySpace)
104
+
jumpReleased := rl.IsKeyReleased(rl.KeySpace)
105
+
106
+
// ---- Timers ----
107
+
if p.OnGround {
108
+
p.CoyoteTimer = coyoteTime
109
+
} else {
110
+
p.CoyoteTimer = clamp(p.CoyoteTimer-dt, 0, 999)
111
+
}
112
+
113
+
if jumpPressed {
114
+
p.JumpBuffer = jumpBufferTime
115
+
} else {
116
+
p.JumpBuffer = clamp(p.JumpBuffer-dt, 0, 999)
117
+
}
118
+
119
+
// ---- Horizontal movement (accel/decel) ----
120
+
if p.OnGround {
121
+
if dir != 0 {
122
+
target := dir * maxRunSpeed
123
+
p.Vel.X = approach(p.Vel.X, target, groundAccel*dt)
124
+
} else {
125
+
p.Vel.X = approach(p.Vel.X, 0, groundDecel*dt)
126
+
}
127
+
} else {
128
+
if dir != 0 {
129
+
target := dir * maxRunSpeed
130
+
p.Vel.X = approach(p.Vel.X, target, airAccel*dt)
131
+
} else {
132
+
p.Vel.X = approach(p.Vel.X, 0, airDecel*dt)
133
+
}
134
+
}
135
+
p.Vel.X = clamp(p.Vel.X, -maxRunSpeed, maxRunSpeed)
136
+
137
+
// ---- Jump using buffer + coyote ----
138
+
if p.JumpBuffer > 0 && p.CoyoteTimer > 0 {
139
+
p.Vel.Y = -jumpSpeed
140
+
p.OnGround = false
141
+
p.CoyoteTimer = 0
142
+
p.JumpBuffer = 0
143
+
p.Jumping = true
144
+
}
145
+
146
+
// ---- Variable jump height (jump cut) ----
147
+
// Optional: keep it, feels good in addition to lowJumpMultiplier gravity.
148
+
if jumpReleased && p.Jumping && p.Vel.Y < 0 {
149
+
p.Vel.Y *= jumpCutMultiplier
150
+
}
151
+
152
+
// ---- Fast-fall gravity ----
153
+
// If falling: stronger gravity.
154
+
// If rising but jump not held: stronger gravity (low jump).
155
+
g := gravity
156
+
if p.Vel.Y > 0 {
157
+
g *= fallMultiplier
158
+
} else if p.Vel.Y < 0 && !jumpHeld {
159
+
g *= lowJumpMultiplier
160
+
}
161
+
p.Vel.Y += g * dt
162
+
p.Vel.Y = clamp(p.Vel.Y, -100000, maxFallSpeed)
163
+
164
+
// ---- Integrate ----
165
+
p.Pos.X += p.Vel.X * dt
166
+
p.Pos.Y += p.Vel.Y * dt
167
+
168
+
// ---- Ground collision ----
169
+
playerBottom := p.Pos.Y + p.Size.Y
170
+
if playerBottom >= groundY {
171
+
p.Pos.Y = groundY - p.Size.Y
172
+
p.Vel.Y = 0
173
+
p.OnGround = true
174
+
p.Jumping = false
175
+
} else {
176
+
p.OnGround = false
177
+
}
178
+
179
+
// ---- Screen bounds (X) ----
180
+
p.Pos.X = clamp(p.Pos.X, 0, float32(screenW)-p.Size.X)
181
+
if p.Pos.X == 0 || p.Pos.X == float32(screenW)-p.Size.X {
182
+
p.Vel.X = 0
183
+
}
184
+
185
+
// ---- Render ----
186
+
rl.BeginDrawing()
187
+
rl.ClearBackground(rl.RayWhite)
188
+
189
+
rl.DrawRectangle(0, int32(groundY), screenW, int32(groundThickness), rl.LightGray)
190
+
rl.DrawRectangleV(p.Pos, p.Size, rl.DarkBlue)
191
+
192
+
rl.DrawText("Move: A/D or Left/Right Jump: Space (buffer+coyote+variable+fast-fall)", 20, 20, 20, rl.Black)
193
+
194
+
rl.EndDrawing()
195
+
}
196
+
}
+47
touchpad/.zed/tasks.json
+47
touchpad/.zed/tasks.json
···
1
+
[
2
+
{
3
+
"label": "Build & Run Firefly Emulator",
4
+
"command": "firefly_cli build && firefly-emulator --id voigt.go-gamepad",
5
+
//"args": [],
6
+
// Env overrides for the command, will be appended to the terminal's environment from the settings.
7
+
// "env": { "foo": "bar" },
8
+
// Current working directory to spawn the command into, defaults to current project root.
9
+
//"cwd": "/path/to/working/directory",
10
+
// Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`.
11
+
"use_new_terminal": false,
12
+
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
13
+
// "allow_concurrent_runs": false,
14
+
// What to do with the terminal pane and tab, after the command was started:
15
+
// * `always` — always show the task's pane, and focus the corresponding tab in it (default)
16
+
// * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it
17
+
// * `never` — do not alter focus, but still add/reuse the task's tab in its pane
18
+
"reveal": "always",
19
+
// What to do with the terminal pane and tab, after the command has finished:
20
+
// * `never` — Do nothing when the command finishes (default)
21
+
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
22
+
// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`
23
+
"hide": "never",
24
+
// Which shell to use when running a task inside the terminal.
25
+
// May take 3 values:
26
+
// 1. (default) Use the system's default terminal configuration in /etc/passwd
27
+
// "shell": "system"
28
+
// 2. A program:
29
+
// "shell": {
30
+
// "program": "sh"
31
+
// }
32
+
// 3. A program with arguments:
33
+
// "shell": {
34
+
// "with_arguments": {
35
+
// "program": "/bin/bash",
36
+
// "args": ["--login"]
37
+
// }
38
+
// }
39
+
"shell": "system",
40
+
// Whether to show the task line in the output of the spawned task, defaults to `true`.
41
+
"show_summary": true,
42
+
// Whether to show the command line in the output of the spawned task, defaults to `true`.
43
+
"show_command": true,
44
+
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
45
+
// "tags": []
46
+
},
47
+
]
+4
touchpad/firefly.toml
+4
touchpad/firefly.toml
+132
touchpad/gamepad.go
+132
touchpad/gamepad.go
···
1
+
package main
2
+
3
+
import ff "github.com/firefly-zero/firefly-go/firefly"
4
+
5
+
var (
6
+
point *ff.Point
7
+
center ff.Point
8
+
9
+
buttonN bool
10
+
buttonE bool
11
+
buttonS bool
12
+
buttonW bool
13
+
)
14
+
15
+
const (
16
+
radius = 26
17
+
third = ff.Width / 3
18
+
19
+
touchYcenter = third
20
+
touchXcenter = ff.Height/2 - radius/2
21
+
buttonYcenter = third * 2
22
+
buttonXcenter = ff.Height/2 - radius/2
23
+
)
24
+
25
+
func init() {
26
+
ff.Render = render
27
+
ff.Update = update
28
+
}
29
+
30
+
func update() {
31
+
// Buttons
32
+
buttons := ff.ReadButtons(ff.Combined)
33
+
34
+
if buttons.N {
35
+
buttonN = true
36
+
} else {
37
+
buttonN = false
38
+
}
39
+
if buttons.E {
40
+
buttonE = true
41
+
} else {
42
+
buttonE = false
43
+
}
44
+
if buttons.S {
45
+
buttonS = true
46
+
} else {
47
+
buttonS = false
48
+
}
49
+
if buttons.W {
50
+
buttonW = true
51
+
} else {
52
+
buttonW = false
53
+
}
54
+
55
+
// Touchpad
56
+
center = ff.Point{
57
+
X: touchXcenter,
58
+
Y: touchYcenter,
59
+
// X: ff.Width/2 - 45,
60
+
// Y: ff.Height / 2,
61
+
}
62
+
63
+
input, pressed := ff.ReadPad(ff.Combined)
64
+
if pressed {
65
+
point = &ff.Point{
66
+
X: center.X + input.X/20 - radius,
67
+
Y: center.Y - input.Y/20 - radius,
68
+
}
69
+
} else {
70
+
point = nil
71
+
}
72
+
}
73
+
74
+
func render() {
75
+
ff.ClearScreen(ff.ColorWhite)
76
+
77
+
// ff.DrawCircle(ff.Point{X: buttonYcenter - radius, Y: buttonXcenter - radius}, radius*3, ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1})
78
+
// ff.DrawCircle(ff.Point{X: touchYcenter - radius*2, Y: touchXcenter - radius}, radius*3, ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1})
79
+
ff.DrawLine(ff.Point{buttonYcenter, 0}, ff.Point{buttonYcenter, ff.Height}, ff.LineStyle{ff.ColorGray, 1})
80
+
ff.DrawLine(ff.Point{touchYcenter, 0}, ff.Point{touchYcenter, ff.Height}, ff.LineStyle{ff.ColorGray, 1})
81
+
ff.DrawLine(ff.Point{0, ff.Height / 2}, ff.Point{ff.Width, ff.Height / 2}, ff.LineStyle{ff.ColorGray, 1})
82
+
83
+
// Buttons
84
+
// B
85
+
styleE := ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
86
+
if buttonE {
87
+
styleE = ff.Style{FillColor: ff.ColorRed, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
88
+
}
89
+
ff.DrawCircle(ff.Point{X: buttonYcenter + radius, Y: buttonXcenter}, radius, styleE)
90
+
91
+
// A
92
+
styleS := ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
93
+
if buttonS {
94
+
styleS = ff.Style{FillColor: ff.ColorGreen, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
95
+
}
96
+
ff.DrawCircle(ff.Point{X: buttonYcenter, Y: buttonXcenter + radius}, radius, styleS)
97
+
98
+
// Y
99
+
styleN := ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
100
+
if buttonN {
101
+
styleN = ff.Style{FillColor: ff.ColorYellow, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
102
+
}
103
+
ff.DrawCircle(ff.Point{X: buttonYcenter, Y: buttonXcenter - radius}, radius, styleN)
104
+
105
+
// X
106
+
styleW := ff.Style{FillColor: ff.ColorWhite, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
107
+
if buttonW {
108
+
styleW = ff.Style{FillColor: ff.ColorBlue, StrokeColor: ff.ColorDarkBlue, StrokeWidth: 1}
109
+
}
110
+
ff.DrawCircle(ff.Point{X: buttonYcenter - radius, Y: buttonXcenter}, radius, styleW)
111
+
112
+
// Touchpad
113
+
style := ff.Style{
114
+
FillColor: ff.ColorWhite,
115
+
StrokeColor: ff.ColorDarkBlue,
116
+
StrokeWidth: 1,
117
+
}
118
+
119
+
ff.DrawCircle(ff.Point{
120
+
X: touchYcenter - radius*2,
121
+
Y: touchXcenter - radius,
122
+
}, radius*3, style)
123
+
124
+
style = ff.Style{
125
+
FillColor: ff.ColorBlue,
126
+
StrokeColor: ff.ColorDarkBlue,
127
+
StrokeWidth: 1,
128
+
}
129
+
if point != nil {
130
+
ff.DrawCircle(*point, radius, style)
131
+
}
132
+
}
+7
touchpad/go.mod
+7
touchpad/go.mod
+8
touchpad/go.sum
+8
touchpad/go.sum
···
1
+
github.com/firefly-zero/firefly-go v0.8.1 h1:evb25qj7ELM0wy20IIHtW8+4MI1i+wqVOiCsYdTuh3M=
2
+
github.com/firefly-zero/firefly-go v0.8.1/go.mod h1:+X/XGyPdES51OESkV8NSf1mszEBZionoROM7x2pBofw=
3
+
github.com/firefly-zero/firefly-go v0.9.5 h1:BzBr4t76bDVVFwNaWJixs1XasqLNBIdg0k9JXblBfU4=
4
+
github.com/firefly-zero/firefly-go v0.9.5/go.mod h1:+X/XGyPdES51OESkV8NSf1mszEBZionoROM7x2pBofw=
5
+
github.com/orsinium-labs/tinymath v1.0.0 h1:Uzp3GmjWIBxMObx4MQi9ACDu4Q8WKjSRakB1OMo9Bu0=
6
+
github.com/orsinium-labs/tinymath v1.0.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
7
+
github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTeV1vS5UA=
8
+
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
History
2 rounds
0 comments
expand 0 comments
closed without merging