225 lines
5.4 KiB
Go
225 lines
5.4 KiB
Go
package gameview
|
|
|
|
import (
|
|
"aego/core/mathx"
|
|
"aego/editor/core/command"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/play"
|
|
"aego/editor/core/services"
|
|
"aego/editor/core/shape"
|
|
"aego/editor/core/vectors"
|
|
"aego/editor/core/workspace"
|
|
"aego/gpu"
|
|
"aego/platform/input"
|
|
"aego/render/debug"
|
|
"aego/render/rt"
|
|
"aego/render/view"
|
|
"aego/ui/core"
|
|
"aego/ui/widgets"
|
|
)
|
|
|
|
const CommandOverlays = "view.game.overlays"
|
|
|
|
type Panel struct {
|
|
play *play.Controller
|
|
vp *services.Viewport
|
|
pool *rt.Pool
|
|
target *rt.Target
|
|
hook func()
|
|
rect mathx.Rect
|
|
bar mathx.Rect
|
|
tl *TimelineBar
|
|
scale float32
|
|
hasCamera bool
|
|
pending bool
|
|
}
|
|
|
|
func New(c *play.Controller, vp *services.Viewport) *Panel {
|
|
return &Panel{play: c, vp: vp, hasCamera: true, tl: NewTimelineBar(c)}
|
|
}
|
|
|
|
func Register(cmds *command.Registry, o *context.Overlays) error {
|
|
return cmds.Register(command.Desc{
|
|
ID: CommandOverlays,
|
|
Name: "Game View Overlays",
|
|
Category: "Window",
|
|
Order: 260,
|
|
Checked: func() bool { return o.On(CommandOverlays) },
|
|
Run: func() error {
|
|
o.Toggle(CommandOverlays)
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func (p *Panel) ID() string {
|
|
return workspace.Game
|
|
}
|
|
|
|
func (p *Panel) Title() string {
|
|
return "Game"
|
|
}
|
|
|
|
func (p *Panel) Release() {
|
|
p.play.SetCapture(false, mathx.Vec2{}, mathx.Vec2{})
|
|
}
|
|
|
|
func (p *Panel) Close() {
|
|
if p.pool == nil {
|
|
return
|
|
}
|
|
if p.target != nil {
|
|
p.pool.Release(p.target)
|
|
p.target = nil
|
|
}
|
|
p.pool.Close()
|
|
}
|
|
|
|
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
|
|
ui := f.UI
|
|
th := &ui.Theme
|
|
if p.pool == nil {
|
|
p.hook = p.render
|
|
p.pool = rt.New(p.vp.GPU, gpu.TexSRGBA8, "editor.gameview")
|
|
}
|
|
scale := ui.Scale()
|
|
pw, ph := int(r.W*scale), int(r.H*scale)
|
|
if pw < 8 || ph < 8 {
|
|
return
|
|
}
|
|
target, err := p.pool.Resize(p.target, pw, ph, true)
|
|
if err != nil {
|
|
ui.TextIn(r, err.Error(), th.Error, core.AlignCenter)
|
|
return
|
|
}
|
|
p.target, p.rect, p.scale = target, r, scale
|
|
ui.List().Texture(target.Region(), r, mathx.Color{R: 1, G: 1, B: 1, A: 1})
|
|
p.pending = true
|
|
p.vp.OnRender(p.hook)
|
|
|
|
if p.play.State() == play.Stopped {
|
|
p.bar = mathx.Rect{}
|
|
p.tl.Reset()
|
|
ui.TextIn(r, "Press Play (Ctrl+P)", th.TextDim, core.AlignCenter)
|
|
return
|
|
}
|
|
id := ui.ID("view")
|
|
origin := mathx.Vec2{X: r.X, Y: r.Y}
|
|
p.bar = mathx.Rect{}
|
|
p.tl.Clear()
|
|
if !p.play.Captured() && !p.vp.Bare {
|
|
p.toolbar(ui, r)
|
|
p.tl.Draw(ui, r)
|
|
}
|
|
m := ui.Mouse()
|
|
onBar := p.bar.Contains(m) || p.tl.Holds(m)
|
|
switch {
|
|
case p.play.Captured():
|
|
p.play.SetCapture(true, origin, mathx.Vec2{X: r.W, Y: r.H})
|
|
if ui.MousePressed(input.MouseLeft) && !r.Contains(ui.Mouse()) {
|
|
p.Release()
|
|
}
|
|
case !onBar && ui.Pressed(id, r):
|
|
p.play.SetCapture(true, origin, mathx.Vec2{X: r.W, Y: r.H})
|
|
}
|
|
if p.play.Captured() {
|
|
ui.List().RectOutline(r, th.Accent, 2)
|
|
if hint := p.play.ReleaseHint(); hint != "" {
|
|
hr := mathx.Rect{X: r.X, Y: r.Y + r.H - th.RowHeight, W: r.W, H: th.RowHeight}
|
|
ui.TextIn(hr, hint, th.TextDim, core.AlignCenter)
|
|
}
|
|
}
|
|
if !p.hasCamera {
|
|
ui.TextIn(r, "No active camera in the scene", th.Warn, core.AlignCenter)
|
|
}
|
|
for _, hud := range p.vp.GameHUD {
|
|
hud(ui, r)
|
|
}
|
|
widgets.Tooltip(ui, id, "Click to send input to the game")
|
|
}
|
|
|
|
func (p *Panel) toolbar(ui *core.Context, r mathx.Rect) {
|
|
th := &ui.Theme
|
|
o := p.vp.Overlay
|
|
overlays := "Overlays: off"
|
|
if o.On(CommandOverlays) {
|
|
overlays = "Overlays: on"
|
|
}
|
|
vecs := "Vectors: off"
|
|
if o.On(vectors.CommandToggle) {
|
|
vecs = "Vectors: on"
|
|
}
|
|
w := ui.TextSize(overlays).X + ui.TextSize(vecs).X + 44
|
|
bar := mathx.Rect{X: r.X + 6, Y: r.Y + 6, W: min(w, r.W-12), H: th.BarHeight}
|
|
ui.PushClip(r)
|
|
tb := widgets.BeginToolbar(ui, bar)
|
|
ui.Outline(bar, th.BarBorder)
|
|
if clicked, _ := tb.TextButton(ui.ID("overlays"), overlays, "Draw module overlays and vectors over the game"); clicked {
|
|
o.Toggle(CommandOverlays)
|
|
}
|
|
if clicked, _ := tb.TextButton(ui.ID("vectors"), vecs, "Draw velocity, force, contact and field vectors (V in the Scene View)"); clicked {
|
|
if !o.On(vectors.CommandToggle) {
|
|
o.Set(CommandOverlays, true)
|
|
}
|
|
o.Toggle(vectors.CommandToggle)
|
|
}
|
|
ui.PopClip()
|
|
p.bar = bar
|
|
}
|
|
|
|
func (p *Panel) render() {
|
|
if !p.pending || p.target == nil {
|
|
return
|
|
}
|
|
p.pending = false
|
|
t := p.target
|
|
p.hasCamera = p.play.Render(t)
|
|
if p.play.State() != play.Stopped && p.hasCamera {
|
|
if p.vp.Overlay.On(CommandOverlays) {
|
|
Overlays(p.vp, p.play)
|
|
}
|
|
return
|
|
}
|
|
v := view.View{
|
|
Target: t.Texture,
|
|
TargetW: t.BucketW,
|
|
TargetH: t.BucketH,
|
|
Viewport: mathx.Rect{W: float32(t.W), H: float32(t.H)},
|
|
PixelScale: p.scale,
|
|
Camera: view.Camera2D{Zoom: 1},
|
|
Clear: mathx.Color{R: 0.02, G: 0.02, B: 0.03, A: 1},
|
|
DoClear: true,
|
|
Label: "gameview.clear",
|
|
}
|
|
p.vp.Renderer.View(v).End()
|
|
}
|
|
|
|
func Overlays(vp *services.Viewport, pc *play.Controller) {
|
|
s := pc.Scene()
|
|
if s == nil || vp == nil || vp.Shapes == nil || vp.Renderer == nil {
|
|
return
|
|
}
|
|
col := mathx.Color{R: 0.7, G: 0.7, B: 0.7, A: 0.7}
|
|
if vp.UI != nil {
|
|
col = vp.UI.Theme.TextDim
|
|
col.A = 0.7
|
|
}
|
|
set := shape.Set{Reg: vp.Shapes}
|
|
oc := shape.OverlayCtx{Scene: s, World: pc.World(), Game: true, Color: col}
|
|
for _, v := range pc.Views() {
|
|
if v.Is3D() {
|
|
continue
|
|
}
|
|
v.DoClear = false
|
|
v.Label = "gameview.overlay"
|
|
f := vp.Renderer.View(v)
|
|
fv := f.View()
|
|
oc.Debug = f.Debug()
|
|
oc.Debug.Space(debug.Overlay)
|
|
oc.View = *fv
|
|
oc.Px = fv.ScreenToWorld(mathx.Vec2{X: 1}).Sub(fv.ScreenToWorld(mathx.Vec2{})).Len()
|
|
set.Overlays(&oc)
|
|
f.End()
|
|
}
|
|
}
|