Basic Collision Detection, Toggle Between Play/Edit
Known bugs: * The Pixel format in the Grid has DX and DY attributes and it wreaks havoc on collision detection in Play Mode when you come straight from the editor. Reloading the map from disk to play is OK cuz it lacks these attrs.
This commit is contained in:
parent
e13dd62309
commit
e141203c4b
|
@ -8,10 +8,10 @@ var (
|
||||||
ShellBackgroundColor = render.Color{0, 10, 20, 128}
|
ShellBackgroundColor = render.Color{0, 10, 20, 128}
|
||||||
ShellForegroundColor = render.White
|
ShellForegroundColor = render.White
|
||||||
ShellPadding int32 = 8
|
ShellPadding int32 = 8
|
||||||
ShellFontSize = 14
|
ShellFontSize = 16
|
||||||
ShellCursorBlinkRate uint64 = 20
|
ShellCursorBlinkRate uint64 = 20
|
||||||
ShellHistoryLineCount = 8
|
ShellHistoryLineCount = 8
|
||||||
|
|
||||||
// Ticks that a flashed message persists for.
|
// Ticks that a flashed message persists for.
|
||||||
FlashTTL uint64 = 200
|
FlashTTL uint64 = 400
|
||||||
)
|
)
|
||||||
|
|
113
doodads/doodads.go
Normal file
113
doodads/doodads.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package doodads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Doodad is a reusable drawing component used in Doodle. Doodads are buttons,
|
||||||
|
// doors, switches, the player characters themselves, anything that isn't a part
|
||||||
|
// of the level geometry.
|
||||||
|
type Doodad interface {
|
||||||
|
ID() string
|
||||||
|
|
||||||
|
// Position and velocity, not saved to disk.
|
||||||
|
Position() render.Point
|
||||||
|
Velocity() render.Point
|
||||||
|
Size() render.Rect
|
||||||
|
|
||||||
|
// Movement commands.
|
||||||
|
MoveBy(render.Point) // Add {X,Y} to current Position.
|
||||||
|
MoveTo(render.Point) // Set current Position to {X,Y}.
|
||||||
|
|
||||||
|
// Implement the Draw function.
|
||||||
|
Draw(render.Engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collide describes how a collision occurred.
|
||||||
|
type Collide struct {
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
W int32
|
||||||
|
H int32
|
||||||
|
Top bool
|
||||||
|
Left bool
|
||||||
|
Right bool
|
||||||
|
Bottom bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollidesWithGrid checks if a Doodad collides with level geometry.
|
||||||
|
func CollidesWithGrid(d Doodad, grid *render.Grid) (Collide, bool) {
|
||||||
|
var (
|
||||||
|
P = d.Position()
|
||||||
|
S = d.Size()
|
||||||
|
topLeft = P
|
||||||
|
topRight = render.Point{
|
||||||
|
X: P.X + S.W,
|
||||||
|
Y: P.Y,
|
||||||
|
}
|
||||||
|
bottomLeft = render.Point{
|
||||||
|
X: P.X,
|
||||||
|
Y: P.Y + S.H,
|
||||||
|
}
|
||||||
|
bottomRight = render.Point{
|
||||||
|
X: bottomLeft.X + S.W,
|
||||||
|
Y: P.Y + S.H,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bottom edge.
|
||||||
|
for point := range render.IterLine2(bottomLeft, bottomRight) {
|
||||||
|
if grid.Exists(render.Pixel{
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}) {
|
||||||
|
return Collide{
|
||||||
|
Bottom: true,
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top edge.
|
||||||
|
for point := range render.IterLine2(topLeft, topRight) {
|
||||||
|
if grid.Exists(render.Pixel{
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}) {
|
||||||
|
return Collide{
|
||||||
|
Top: true,
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for point := range render.IterLine2(topLeft, bottomLeft) {
|
||||||
|
if grid.Exists(render.Pixel{
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}) {
|
||||||
|
return Collide{
|
||||||
|
Left: true,
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for point := range render.IterLine2(topRight, bottomRight) {
|
||||||
|
if grid.Exists(render.Pixel{
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}) {
|
||||||
|
return Collide{
|
||||||
|
Right: true,
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collide{}, false
|
||||||
|
}
|
70
doodads/player.go
Normal file
70
doodads/player.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package doodads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PlayerID is the Doodad ID for the player character.
|
||||||
|
const PlayerID = "PLAYER"
|
||||||
|
|
||||||
|
// Player is a special doodad for the player character.
|
||||||
|
type Player struct {
|
||||||
|
point render.Point
|
||||||
|
velocity render.Point
|
||||||
|
size render.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlayer creates the special Player Character doodad.
|
||||||
|
func NewPlayer() *Player {
|
||||||
|
return &Player{
|
||||||
|
point: render.Point{
|
||||||
|
X: 100,
|
||||||
|
Y: 100,
|
||||||
|
},
|
||||||
|
size: render.Rect{
|
||||||
|
W: 16,
|
||||||
|
H: 16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID of the Player singleton.
|
||||||
|
func (p *Player) ID() string {
|
||||||
|
return PlayerID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position of the player.
|
||||||
|
func (p *Player) Position() render.Point {
|
||||||
|
return p.point
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveBy a relative delta position.
|
||||||
|
func (p *Player) MoveBy(by render.Point) {
|
||||||
|
p.point.X += by.X
|
||||||
|
p.point.Y += by.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveTo an absolute position.
|
||||||
|
func (p *Player) MoveTo(to render.Point) {
|
||||||
|
p.point = to
|
||||||
|
}
|
||||||
|
|
||||||
|
// Velocity returns the player's current velocity.
|
||||||
|
func (p *Player) Velocity() render.Point {
|
||||||
|
return p.velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the player's size.
|
||||||
|
func (p *Player) Size() render.Rect {
|
||||||
|
return p.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the player sprite.
|
||||||
|
func (p *Player) Draw(e render.Engine) {
|
||||||
|
e.DrawRect(render.Magenta, render.Rect{
|
||||||
|
X: p.point.X,
|
||||||
|
Y: p.point.Y,
|
||||||
|
W: p.size.W,
|
||||||
|
H: p.size.H,
|
||||||
|
})
|
||||||
|
}
|
26
doodle.go
26
doodle.go
|
@ -1,7 +1,6 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
@ -167,30 +166,9 @@ func (d *Doodle) EditLevel(filename string) error {
|
||||||
// PlayLevel loads a map from JSON into the PlayScene.
|
// PlayLevel loads a map from JSON into the PlayScene.
|
||||||
func (d *Doodle) PlayLevel(filename string) error {
|
func (d *Doodle) PlayLevel(filename string) error {
|
||||||
log.Info("Loading level from file: %s", filename)
|
log.Info("Loading level from file: %s", filename)
|
||||||
scene := &PlayScene{}
|
scene := &PlayScene{
|
||||||
err := scene.LoadLevel(filename)
|
Filename: filename,
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
d.Goto(scene)
|
d.Goto(scene)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pixel TODO: not a global
|
|
||||||
type Pixel struct {
|
|
||||||
start bool
|
|
||||||
x int32
|
|
||||||
y int32
|
|
||||||
dx int32
|
|
||||||
dy int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Pixel) String() string {
|
|
||||||
return fmt.Sprintf("(%d,%d) delta (%d,%d)",
|
|
||||||
p.x, p.y,
|
|
||||||
p.dx, p.dy,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid is a 2D grid of pixels in X,Y notation.
|
|
||||||
type Grid map[Pixel]interface{}
|
|
||||||
|
|
|
@ -3,13 +3,13 @@ package draw
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/types"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Line is a generator that returns the X,Y coordinates to draw a line.
|
// Line is a generator that returns the X,Y coordinates to draw a line.
|
||||||
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
|
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
|
||||||
func Line(x1, y1, x2, y2 int32) chan types.Point {
|
func Line(x1, y1, x2, y2 int32) chan render.Point {
|
||||||
generator := make(chan types.Point)
|
generator := make(chan render.Point)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var (
|
var (
|
||||||
|
@ -28,7 +28,7 @@ func Line(x1, y1, x2, y2 int32) chan types.Point {
|
||||||
x := float64(x1)
|
x := float64(x1)
|
||||||
y := float64(y1)
|
y := float64(y1)
|
||||||
for i := 0; i <= int(step); i++ {
|
for i := 0; i <= int(step); i++ {
|
||||||
generator <- types.Point{
|
generator <- render.Point{
|
||||||
X: int32(x),
|
X: int32(x),
|
||||||
Y: int32(y),
|
Y: int32(y),
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/draw"
|
"git.kirsle.net/apps/doodle/draw"
|
||||||
"git.kirsle.net/apps/doodle/types"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLine(t *testing.T) {
|
func TestLine(t *testing.T) {
|
||||||
|
@ -14,7 +14,7 @@ func TestLine(t *testing.T) {
|
||||||
X2 int32
|
X2 int32
|
||||||
Y1 int32
|
Y1 int32
|
||||||
Y2 int32
|
Y2 int32
|
||||||
Expect []types.Point
|
Expect []render.Point
|
||||||
}
|
}
|
||||||
toString := func(t task) string {
|
toString := func(t task) string {
|
||||||
return fmt.Sprintf("Line<%d,%d -> %d,%d>",
|
return fmt.Sprintf("Line<%d,%d -> %d,%d>",
|
||||||
|
@ -29,7 +29,7 @@ func TestLine(t *testing.T) {
|
||||||
Y1: 0,
|
Y1: 0,
|
||||||
X2: 0,
|
X2: 0,
|
||||||
Y2: 10,
|
Y2: 10,
|
||||||
Expect: []types.Point{
|
Expect: []render.Point{
|
||||||
{X: 0, Y: 0},
|
{X: 0, Y: 0},
|
||||||
{X: 0, Y: 1},
|
{X: 0, Y: 1},
|
||||||
{X: 0, Y: 2},
|
{X: 0, Y: 2},
|
||||||
|
@ -48,7 +48,7 @@ func TestLine(t *testing.T) {
|
||||||
Y1: 10,
|
Y1: 10,
|
||||||
X2: 15,
|
X2: 15,
|
||||||
Y2: 15,
|
Y2: 15,
|
||||||
Expect: []types.Point{
|
Expect: []render.Point{
|
||||||
{X: 10, Y: 10},
|
{X: 10, Y: 10},
|
||||||
{X: 11, Y: 11},
|
{X: 11, Y: 11},
|
||||||
{X: 12, Y: 12},
|
{X: 12, Y: 12},
|
||||||
|
|
145
editor_scene.go
145
editor_scene.go
|
@ -16,9 +16,14 @@ import (
|
||||||
|
|
||||||
// EditorScene manages the "Edit Level" game mode.
|
// EditorScene manages the "Edit Level" game mode.
|
||||||
type EditorScene struct {
|
type EditorScene struct {
|
||||||
|
// Configuration for the scene initializer.
|
||||||
|
OpenFile bool
|
||||||
|
Filename string
|
||||||
|
Canvas render.Grid
|
||||||
|
|
||||||
// History of all the pixels placed by the user.
|
// History of all the pixels placed by the user.
|
||||||
pixelHistory []Pixel
|
pixelHistory []render.Pixel
|
||||||
canvas Grid
|
canvas render.Grid
|
||||||
filename string // Last saved filename.
|
filename string // Last saved filename.
|
||||||
|
|
||||||
// Canvas size
|
// Canvas size
|
||||||
|
@ -33,11 +38,32 @@ func (s *EditorScene) Name() string {
|
||||||
|
|
||||||
// Setup the editor scene.
|
// Setup the editor scene.
|
||||||
func (s *EditorScene) Setup(d *Doodle) error {
|
func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
|
// Were we given configuration data?
|
||||||
|
if s.Filename != "" {
|
||||||
|
log.Debug("EditorScene: Set filename to %s", s.Filename)
|
||||||
|
s.filename = s.Filename
|
||||||
|
s.Filename = ""
|
||||||
|
if s.OpenFile {
|
||||||
|
log.Debug("EditorScene: Loading map from filename at %s", s.filename)
|
||||||
|
if err := s.LoadLevel(s.filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Canvas != nil {
|
||||||
|
log.Debug("EditorScene: Received Canvas from caller")
|
||||||
|
s.canvas = s.Canvas
|
||||||
|
s.Canvas = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Flash("Editor Mode. Press 'P' to play this map.")
|
||||||
|
|
||||||
if s.pixelHistory == nil {
|
if s.pixelHistory == nil {
|
||||||
s.pixelHistory = []Pixel{}
|
s.pixelHistory = []render.Pixel{}
|
||||||
}
|
}
|
||||||
if s.canvas == nil {
|
if s.canvas == nil {
|
||||||
s.canvas = Grid{}
|
log.Debug("EditorScene: Setting default canvas to an empty grid")
|
||||||
|
s.canvas = render.Grid{}
|
||||||
}
|
}
|
||||||
s.width = d.width // TODO: canvas width = copy the window size
|
s.width = d.width // TODO: canvas width = copy the window size
|
||||||
s.height = d.height
|
s.height = d.height
|
||||||
|
@ -52,31 +78,45 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
s.Screenshot()
|
s.Screenshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Switching to Play Mode?
|
||||||
|
if ev.KeyName.Read() == "p" {
|
||||||
|
log.Info("Play Mode, Go!")
|
||||||
|
d.Goto(&PlayScene{
|
||||||
|
Canvas: s.canvas,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the canvas and fill it with white.
|
// Clear the canvas and fill it with white.
|
||||||
d.Engine.Clear(render.White)
|
d.Engine.Clear(render.White)
|
||||||
|
|
||||||
// Clicking? Log all the pixels while doing so.
|
// Clicking? Log all the pixels while doing so.
|
||||||
if ev.Button1.Now {
|
if ev.Button1.Now {
|
||||||
pixel := Pixel{
|
log.Warn("Button1: %+v", ev.Button1)
|
||||||
start: ev.Button1.Pressed(),
|
pixel := render.Pixel{
|
||||||
x: ev.CursorX.Now,
|
Start: ev.Button1.Pressed(),
|
||||||
y: ev.CursorY.Now,
|
X: ev.CursorX.Now,
|
||||||
dx: ev.CursorX.Now,
|
Y: ev.CursorY.Now,
|
||||||
dy: ev.CursorY.Now,
|
DX: ev.CursorX.Last,
|
||||||
|
DY: ev.CursorY.Last,
|
||||||
|
}
|
||||||
|
if pixel.Start {
|
||||||
|
log.Warn("START PIXEL %+v", pixel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append unique new pixels.
|
// Append unique new pixels.
|
||||||
if len(s.pixelHistory) == 0 || s.pixelHistory[len(s.pixelHistory)-1] != pixel {
|
if len(s.pixelHistory) == 0 || s.pixelHistory[len(s.pixelHistory)-1] != pixel {
|
||||||
// If not a start pixel, make the delta coord the previous one.
|
// If not a start pixel, make the delta coord the previous one.
|
||||||
if !pixel.start && len(s.pixelHistory) > 0 {
|
if !pixel.Start && len(s.pixelHistory) > 0 {
|
||||||
prev := s.pixelHistory[len(s.pixelHistory)-1]
|
prev := s.pixelHistory[len(s.pixelHistory)-1]
|
||||||
pixel.dx = prev.x
|
pixel.DY = prev.Y
|
||||||
pixel.dy = prev.y
|
pixel.DX = prev.X
|
||||||
}
|
}
|
||||||
|
|
||||||
s.pixelHistory = append(s.pixelHistory, pixel)
|
s.pixelHistory = append(s.pixelHistory, pixel)
|
||||||
|
|
||||||
// Save in the pixel canvas map.
|
// Save in the pixel canvas map.
|
||||||
|
fmt.Printf("%+v", pixel)
|
||||||
s.canvas[pixel] = nil
|
s.canvas[pixel] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,24 +126,26 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
|
|
||||||
// Draw the current frame.
|
// Draw the current frame.
|
||||||
func (s *EditorScene) Draw(d *Doodle) error {
|
func (s *EditorScene) Draw(d *Doodle) error {
|
||||||
for i, pixel := range s.pixelHistory {
|
// for i, pixel := range s.pixelHistory {
|
||||||
if !pixel.start && i > 0 {
|
// if !pixel.Start && i > 0 {
|
||||||
prev := s.pixelHistory[i-1]
|
// prev := s.pixelHistory[i-1]
|
||||||
if prev.x == pixel.x && prev.y == pixel.y {
|
// if prev.X == pixel.X && prev.Y == pixel.Y {
|
||||||
d.Engine.DrawPoint(
|
// d.Engine.DrawPoint(
|
||||||
render.Black,
|
// render.Black,
|
||||||
render.Point{pixel.x, pixel.y},
|
// render.Point{pixel.X, pixel.Y},
|
||||||
)
|
// )
|
||||||
} else {
|
// } else {
|
||||||
d.Engine.DrawLine(
|
// d.Engine.DrawLine(
|
||||||
render.Black,
|
// render.Black,
|
||||||
render.Point{pixel.x, pixel.y},
|
// render.Point{pixel.X, pixel.Y},
|
||||||
render.Point{prev.x, prev.y},
|
// render.Point{prev.X, prev.Y},
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
// d.Engine.DrawPoint(render.Black, render.Point{pixel.X, pixel.Y})
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
s.canvas.Draw(d.Engine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -111,8 +153,8 @@ func (s *EditorScene) Draw(d *Doodle) error {
|
||||||
// LoadLevel loads a level from disk.
|
// LoadLevel loads a level from disk.
|
||||||
func (s *EditorScene) LoadLevel(filename string) error {
|
func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
s.filename = filename
|
s.filename = filename
|
||||||
s.pixelHistory = []Pixel{}
|
s.pixelHistory = []render.Pixel{}
|
||||||
s.canvas = Grid{}
|
s.canvas = render.Grid{}
|
||||||
|
|
||||||
m, err := level.LoadJSON(filename)
|
m, err := level.LoadJSON(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -120,12 +162,12 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, point := range m.Pixels {
|
for _, point := range m.Pixels {
|
||||||
pixel := Pixel{
|
pixel := render.Pixel{
|
||||||
start: true,
|
Start: true,
|
||||||
x: point.X,
|
X: point.X,
|
||||||
y: point.Y,
|
Y: point.Y,
|
||||||
dx: point.X,
|
DX: point.X,
|
||||||
dy: point.Y,
|
DY: point.Y,
|
||||||
}
|
}
|
||||||
s.pixelHistory = append(s.pixelHistory, pixel)
|
s.pixelHistory = append(s.pixelHistory, pixel)
|
||||||
s.canvas[pixel] = nil
|
s.canvas[pixel] = nil
|
||||||
|
@ -153,12 +195,20 @@ func (s *EditorScene) SaveLevel(filename string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for pixel := range s.canvas {
|
for pixel := range s.canvas {
|
||||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
if pixel.DX == 0 && pixel.DY == 0 {
|
||||||
m.Pixels = append(m.Pixels, level.Pixel{
|
m.Pixels = append(m.Pixels, level.Pixel{
|
||||||
X: point.X,
|
X: pixel.X,
|
||||||
Y: point.Y,
|
Y: pixel.Y,
|
||||||
Palette: 0,
|
Palette: 0,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
for point := range render.IterLine(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
|
||||||
|
m.Pixels = append(m.Pixels, level.Pixel{
|
||||||
|
X: point.X,
|
||||||
|
Y: point.Y,
|
||||||
|
Palette: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,10 +239,10 @@ func (s *EditorScene) Screenshot() {
|
||||||
// Fill in the dots we drew.
|
// Fill in the dots we drew.
|
||||||
for pixel := range s.canvas {
|
for pixel := range s.canvas {
|
||||||
// A line or a dot?
|
// A line or a dot?
|
||||||
if pixel.x == pixel.dx && pixel.y == pixel.dy {
|
if pixel.DX == 0 && pixel.DY == 0 {
|
||||||
screenshot.Set(int(pixel.x), int(pixel.y), image.Black)
|
screenshot.Set(int(pixel.X), int(pixel.Y), image.Black)
|
||||||
} else {
|
} else {
|
||||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
for point := range draw.Line(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
|
||||||
screenshot.Set(int(point.X), int(point.Y), image.Black)
|
screenshot.Set(int(point.X), int(point.Y), image.Black)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,3 +273,8 @@ func (s *EditorScene) Screenshot() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy the scene.
|
||||||
|
func (s *EditorScene) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ import (
|
||||||
|
|
||||||
// Level is the container format for Doodle map drawings.
|
// Level is the container format for Doodle map drawings.
|
||||||
type Level struct {
|
type Level struct {
|
||||||
Version int32 `json:"version"` // File format version spec.
|
Version int32 `json:"version"` // File format version spec.
|
||||||
Title string `json:"title"`
|
GameVersion string `json:"gameVersion"` // Game version that created the level.
|
||||||
Author string `json:"author"`
|
Title string `json:"title"`
|
||||||
Password string `json:"passwd"`
|
Author string `json:"author"`
|
||||||
Locked bool `json:"locked"`
|
Password string `json:"passwd"`
|
||||||
|
Locked bool `json:"locked"`
|
||||||
|
|
||||||
// Level size.
|
// Level size.
|
||||||
Width int32 `json:"w"`
|
Width int32 `json:"w"`
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
@ -8,17 +9,19 @@ import (
|
||||||
|
|
||||||
// PlayScene manages the "Edit Level" game mode.
|
// PlayScene manages the "Edit Level" game mode.
|
||||||
type PlayScene struct {
|
type PlayScene struct {
|
||||||
canvas Grid
|
// Configuration attributes.
|
||||||
|
Filename string
|
||||||
|
Canvas render.Grid
|
||||||
|
|
||||||
|
// Private variables.
|
||||||
|
canvas render.Grid
|
||||||
|
|
||||||
// Canvas size
|
// Canvas size
|
||||||
width int32
|
width int32
|
||||||
height int32
|
height int32
|
||||||
|
|
||||||
// Player position and velocity.
|
// Player character
|
||||||
x int32
|
player doodads.Doodad
|
||||||
y int32
|
|
||||||
vx int32
|
|
||||||
vy int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the scene.
|
// Name of the scene.
|
||||||
|
@ -28,19 +31,43 @@ func (s *PlayScene) Name() string {
|
||||||
|
|
||||||
// Setup the play scene.
|
// Setup the play scene.
|
||||||
func (s *PlayScene) Setup(d *Doodle) error {
|
func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
s.x = 10
|
// Given a filename or map data to play?
|
||||||
s.y = 10
|
if s.Canvas != nil {
|
||||||
|
log.Debug("PlayScene.Setup: loading map from given canvas")
|
||||||
|
s.canvas = s.Canvas
|
||||||
|
|
||||||
|
} else if s.Filename != "" {
|
||||||
|
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
|
||||||
|
s.LoadLevel(s.Filename)
|
||||||
|
s.Filename = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
s.player = doodads.NewPlayer()
|
||||||
|
|
||||||
if s.canvas == nil {
|
if s.canvas == nil {
|
||||||
s.canvas = Grid{}
|
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
|
||||||
|
s.canvas = render.Grid{}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.width = d.width // TODO: canvas width = copy the window size
|
s.width = d.width // TODO: canvas width = copy the window size
|
||||||
s.height = d.height
|
s.height = d.height
|
||||||
|
|
||||||
|
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop the editor scene.
|
// Loop the editor scene.
|
||||||
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
|
// Switching to Edit Mode?
|
||||||
|
if ev.KeyName.Read() == "e" {
|
||||||
|
log.Info("Edit Mode, Go!")
|
||||||
|
d.Goto(&EditorScene{
|
||||||
|
Canvas: s.canvas,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
s.movePlayer(ev)
|
s.movePlayer(ev)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,35 +77,53 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
// Clear the canvas and fill it with white.
|
// Clear the canvas and fill it with white.
|
||||||
d.Engine.Clear(render.White)
|
d.Engine.Clear(render.White)
|
||||||
|
|
||||||
for pixel := range s.canvas {
|
s.canvas.Draw(d.Engine)
|
||||||
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw our hero.
|
// Draw our hero.
|
||||||
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16})
|
s.player.Draw(d.Engine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
||||||
func (s *PlayScene) movePlayer(ev *events.State) {
|
func (s *PlayScene) movePlayer(ev *events.State) {
|
||||||
|
delta := s.player.Position()
|
||||||
|
var playerSpeed int32 = 8
|
||||||
|
var gravity int32 = 2
|
||||||
|
|
||||||
if ev.Down.Now {
|
if ev.Down.Now {
|
||||||
s.y += 4
|
delta.Y += playerSpeed
|
||||||
}
|
}
|
||||||
if ev.Left.Now {
|
if ev.Left.Now {
|
||||||
s.x -= 4
|
delta.X -= playerSpeed
|
||||||
}
|
}
|
||||||
if ev.Right.Now {
|
if ev.Right.Now {
|
||||||
s.x += 4
|
delta.X += playerSpeed
|
||||||
}
|
}
|
||||||
if ev.Up.Now {
|
if ev.Up.Now {
|
||||||
s.y -= 4
|
delta.Y -= playerSpeed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply gravity.
|
||||||
|
delta.Y += gravity
|
||||||
|
|
||||||
|
// Draw a ray and check for collision.
|
||||||
|
var lastOk = s.player.Position()
|
||||||
|
for point := range render.IterLine2(s.player.Position(), delta) {
|
||||||
|
s.player.MoveTo(point)
|
||||||
|
if _, ok := doodads.CollidesWithGrid(s.player, &s.canvas); ok {
|
||||||
|
s.player.MoveTo(lastOk)
|
||||||
|
} else {
|
||||||
|
lastOk = s.player.Position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.player.MoveTo(lastOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadLevel loads a level from disk.
|
// LoadLevel loads a level from disk.
|
||||||
func (s *PlayScene) LoadLevel(filename string) error {
|
func (s *PlayScene) LoadLevel(filename string) error {
|
||||||
s.canvas = Grid{}
|
s.canvas = render.Grid{}
|
||||||
|
|
||||||
m, err := level.LoadJSON(filename)
|
m, err := level.LoadJSON(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -86,12 +131,17 @@ func (s *PlayScene) LoadLevel(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, point := range m.Pixels {
|
for _, point := range m.Pixels {
|
||||||
pixel := Pixel{
|
pixel := render.Pixel{
|
||||||
x: point.X,
|
X: point.X,
|
||||||
y: point.Y,
|
Y: point.Y,
|
||||||
}
|
}
|
||||||
s.canvas[pixel] = nil
|
s.canvas[pixel] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy the scene.
|
||||||
|
func (s *PlayScene) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
49
render/grid.go
Normal file
49
render/grid.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pixel TODO: not a global
|
||||||
|
// TODO get rid of this ugly thing.
|
||||||
|
type Pixel struct {
|
||||||
|
Start bool
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
DX int32
|
||||||
|
DY int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pixel) String() string {
|
||||||
|
return fmt.Sprintf("(%d,%d) delta (%d,%d)",
|
||||||
|
p.X, p.Y,
|
||||||
|
p.DX, p.DY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid is a 2D grid of pixels in X,Y notation.
|
||||||
|
type Grid map[Pixel]interface{}
|
||||||
|
|
||||||
|
// Exists returns true if the point exists on the grid.
|
||||||
|
func (g *Grid) Exists(p Pixel) bool {
|
||||||
|
if _, ok := (*g)[p]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the grid efficiently.
|
||||||
|
func (g *Grid) Draw(e Engine) {
|
||||||
|
for pixel := range *g {
|
||||||
|
if pixel.DX == 0 && pixel.DY == 0 {
|
||||||
|
e.DrawPoint(Black, Point{
|
||||||
|
X: pixel.X,
|
||||||
|
Y: pixel.Y,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for point := range IterLine(pixel.X, pixel.Y, pixel.DX, pixel.DY) {
|
||||||
|
e.DrawPoint(Black, point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
)
|
)
|
||||||
|
@ -103,3 +104,43 @@ var (
|
||||||
Magenta = Color{255, 0, 255, 255}
|
Magenta = Color{255, 0, 255, 255}
|
||||||
Pink = Color{255, 153, 255, 255}
|
Pink = Color{255, 153, 255, 255}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IterLine is a generator that returns the X,Y coordinates to draw a line.
|
||||||
|
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
|
||||||
|
func IterLine(x1, y1, x2, y2 int32) chan Point {
|
||||||
|
generator := make(chan Point)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var (
|
||||||
|
dx = float64(x2 - x1)
|
||||||
|
dy = float64(y2 - y1)
|
||||||
|
)
|
||||||
|
var step float64
|
||||||
|
if math.Abs(dx) >= math.Abs(dy) {
|
||||||
|
step = math.Abs(dx)
|
||||||
|
} else {
|
||||||
|
step = math.Abs(dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
dx = dx / step
|
||||||
|
dy = dy / step
|
||||||
|
x := float64(x1)
|
||||||
|
y := float64(y1)
|
||||||
|
for i := 0; i <= int(step); i++ {
|
||||||
|
generator <- Point{
|
||||||
|
X: int32(x),
|
||||||
|
Y: int32(y),
|
||||||
|
}
|
||||||
|
x += dx
|
||||||
|
y += dy
|
||||||
|
}
|
||||||
|
|
||||||
|
close(generator)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterLine2(p1 Point, p2 Point) chan Point {
|
||||||
|
return IterLine(p1.X, p1.Y, p2.X, p2.Y)
|
||||||
|
}
|
||||||
|
|
7
scene.go
7
scene.go
|
@ -8,6 +8,7 @@ import "git.kirsle.net/apps/doodle/events"
|
||||||
type Scene interface {
|
type Scene interface {
|
||||||
Name() string
|
Name() string
|
||||||
Setup(*Doodle) error
|
Setup(*Doodle) error
|
||||||
|
Destroy() error
|
||||||
|
|
||||||
// Loop should update the scene's state but not draw anything.
|
// Loop should update the scene's state but not draw anything.
|
||||||
Loop(*Doodle, *events.State) error
|
Loop(*Doodle, *events.State) error
|
||||||
|
@ -19,7 +20,11 @@ type Scene interface {
|
||||||
|
|
||||||
// Goto a scene. First it unloads the current scene.
|
// Goto a scene. First it unloads the current scene.
|
||||||
func (d *Doodle) Goto(scene Scene) error {
|
func (d *Doodle) Goto(scene Scene) error {
|
||||||
// d.scene.Destroy()
|
// Teardown existing scene.
|
||||||
|
if d.scene != nil {
|
||||||
|
d.scene.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("Goto Scene")
|
log.Info("Goto Scene")
|
||||||
d.scene = scene
|
d.scene = scene
|
||||||
return d.scene.Setup(d)
|
return d.scene.Setup(d)
|
||||||
|
|
6
shell.go
6
shell.go
|
@ -2,6 +2,7 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
@ -9,6 +10,11 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flash a message to the user.
|
||||||
|
func (d *Doodle) Flash(template string, v ...interface{}) {
|
||||||
|
d.shell.Write(fmt.Sprintf(template, v...))
|
||||||
|
}
|
||||||
|
|
||||||
// Shell implements the developer console in-game.
|
// Shell implements the developer console in-game.
|
||||||
type Shell struct {
|
type Shell struct {
|
||||||
parent *Doodle
|
parent *Doodle
|
||||||
|
|
Loading…
Reference in New Issue
Block a user