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}
|
||||
ShellForegroundColor = render.White
|
||||
ShellPadding int32 = 8
|
||||
ShellFontSize = 14
|
||||
ShellFontSize = 16
|
||||
ShellCursorBlinkRate uint64 = 20
|
||||
ShellHistoryLineCount = 8
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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.
|
||||
func (d *Doodle) PlayLevel(filename string) error {
|
||||
log.Info("Loading level from file: %s", filename)
|
||||
scene := &PlayScene{}
|
||||
err := scene.LoadLevel(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
scene := &PlayScene{
|
||||
Filename: filename,
|
||||
}
|
||||
d.Goto(scene)
|
||||
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 (
|
||||
"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.
|
||||
// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)
|
||||
func Line(x1, y1, x2, y2 int32) chan types.Point {
|
||||
generator := make(chan types.Point)
|
||||
func Line(x1, y1, x2, y2 int32) chan render.Point {
|
||||
generator := make(chan render.Point)
|
||||
|
||||
go func() {
|
||||
var (
|
||||
|
@ -28,7 +28,7 @@ func Line(x1, y1, x2, y2 int32) chan types.Point {
|
|||
x := float64(x1)
|
||||
y := float64(y1)
|
||||
for i := 0; i <= int(step); i++ {
|
||||
generator <- types.Point{
|
||||
generator <- render.Point{
|
||||
X: int32(x),
|
||||
Y: int32(y),
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"git.kirsle.net/apps/doodle/draw"
|
||||
"git.kirsle.net/apps/doodle/types"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
)
|
||||
|
||||
func TestLine(t *testing.T) {
|
||||
|
@ -14,7 +14,7 @@ func TestLine(t *testing.T) {
|
|||
X2 int32
|
||||
Y1 int32
|
||||
Y2 int32
|
||||
Expect []types.Point
|
||||
Expect []render.Point
|
||||
}
|
||||
toString := func(t task) string {
|
||||
return fmt.Sprintf("Line<%d,%d -> %d,%d>",
|
||||
|
@ -29,7 +29,7 @@ func TestLine(t *testing.T) {
|
|||
Y1: 0,
|
||||
X2: 0,
|
||||
Y2: 10,
|
||||
Expect: []types.Point{
|
||||
Expect: []render.Point{
|
||||
{X: 0, Y: 0},
|
||||
{X: 0, Y: 1},
|
||||
{X: 0, Y: 2},
|
||||
|
@ -48,7 +48,7 @@ func TestLine(t *testing.T) {
|
|||
Y1: 10,
|
||||
X2: 15,
|
||||
Y2: 15,
|
||||
Expect: []types.Point{
|
||||
Expect: []render.Point{
|
||||
{X: 10, Y: 10},
|
||||
{X: 11, Y: 11},
|
||||
{X: 12, Y: 12},
|
||||
|
|
145
editor_scene.go
145
editor_scene.go
|
@ -16,9 +16,14 @@ import (
|
|||
|
||||
// EditorScene manages the "Edit Level" game mode.
|
||||
type EditorScene struct {
|
||||
// Configuration for the scene initializer.
|
||||
OpenFile bool
|
||||
Filename string
|
||||
Canvas render.Grid
|
||||
|
||||
// History of all the pixels placed by the user.
|
||||
pixelHistory []Pixel
|
||||
canvas Grid
|
||||
pixelHistory []render.Pixel
|
||||
canvas render.Grid
|
||||
filename string // Last saved filename.
|
||||
|
||||
// Canvas size
|
||||
|
@ -33,11 +38,32 @@ func (s *EditorScene) Name() string {
|
|||
|
||||
// Setup the editor scene.
|
||||
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 {
|
||||
s.pixelHistory = []Pixel{}
|
||||
s.pixelHistory = []render.Pixel{}
|
||||
}
|
||||
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.height = d.height
|
||||
|
@ -52,31 +78,45 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
|||
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.
|
||||
d.Engine.Clear(render.White)
|
||||
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if ev.Button1.Now {
|
||||
pixel := Pixel{
|
||||
start: ev.Button1.Pressed(),
|
||||
x: ev.CursorX.Now,
|
||||
y: ev.CursorY.Now,
|
||||
dx: ev.CursorX.Now,
|
||||
dy: ev.CursorY.Now,
|
||||
log.Warn("Button1: %+v", ev.Button1)
|
||||
pixel := render.Pixel{
|
||||
Start: ev.Button1.Pressed(),
|
||||
X: ev.CursorX.Now,
|
||||
Y: ev.CursorY.Now,
|
||||
DX: ev.CursorX.Last,
|
||||
DY: ev.CursorY.Last,
|
||||
}
|
||||
if pixel.Start {
|
||||
log.Warn("START PIXEL %+v", pixel)
|
||||
}
|
||||
|
||||
// Append unique new pixels.
|
||||
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 !pixel.start && len(s.pixelHistory) > 0 {
|
||||
if !pixel.Start && len(s.pixelHistory) > 0 {
|
||||
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)
|
||||
|
||||
// Save in the pixel canvas map.
|
||||
fmt.Printf("%+v", pixel)
|
||||
s.canvas[pixel] = nil
|
||||
}
|
||||
}
|
||||
|
@ -86,24 +126,26 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
|||
|
||||
// Draw the current frame.
|
||||
func (s *EditorScene) Draw(d *Doodle) error {
|
||||
for i, pixel := range s.pixelHistory {
|
||||
if !pixel.start && i > 0 {
|
||||
prev := s.pixelHistory[i-1]
|
||||
if prev.x == pixel.x && prev.y == pixel.y {
|
||||
d.Engine.DrawPoint(
|
||||
render.Black,
|
||||
render.Point{pixel.x, pixel.y},
|
||||
)
|
||||
} else {
|
||||
d.Engine.DrawLine(
|
||||
render.Black,
|
||||
render.Point{pixel.x, pixel.y},
|
||||
render.Point{prev.x, prev.y},
|
||||
)
|
||||
}
|
||||
}
|
||||
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
||||
}
|
||||
// for i, pixel := range s.pixelHistory {
|
||||
// if !pixel.Start && i > 0 {
|
||||
// prev := s.pixelHistory[i-1]
|
||||
// if prev.X == pixel.X && prev.Y == pixel.Y {
|
||||
// d.Engine.DrawPoint(
|
||||
// render.Black,
|
||||
// render.Point{pixel.X, pixel.Y},
|
||||
// )
|
||||
// } else {
|
||||
// d.Engine.DrawLine(
|
||||
// render.Black,
|
||||
// render.Point{pixel.X, pixel.Y},
|
||||
// render.Point{prev.X, prev.Y},
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// d.Engine.DrawPoint(render.Black, render.Point{pixel.X, pixel.Y})
|
||||
// }
|
||||
|
||||
s.canvas.Draw(d.Engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -111,8 +153,8 @@ func (s *EditorScene) Draw(d *Doodle) error {
|
|||
// LoadLevel loads a level from disk.
|
||||
func (s *EditorScene) LoadLevel(filename string) error {
|
||||
s.filename = filename
|
||||
s.pixelHistory = []Pixel{}
|
||||
s.canvas = Grid{}
|
||||
s.pixelHistory = []render.Pixel{}
|
||||
s.canvas = render.Grid{}
|
||||
|
||||
m, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
|
@ -120,12 +162,12 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
|||
}
|
||||
|
||||
for _, point := range m.Pixels {
|
||||
pixel := Pixel{
|
||||
start: true,
|
||||
x: point.X,
|
||||
y: point.Y,
|
||||
dx: point.X,
|
||||
dy: point.Y,
|
||||
pixel := render.Pixel{
|
||||
Start: true,
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
DX: point.X,
|
||||
DY: point.Y,
|
||||
}
|
||||
s.pixelHistory = append(s.pixelHistory, pixel)
|
||||
s.canvas[pixel] = nil
|
||||
|
@ -153,12 +195,20 @@ func (s *EditorScene) SaveLevel(filename string) {
|
|||
}
|
||||
|
||||
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{
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
X: pixel.X,
|
||||
Y: pixel.Y,
|
||||
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.
|
||||
for pixel := range s.canvas {
|
||||
// A line or a dot?
|
||||
if pixel.x == pixel.dx && pixel.y == pixel.dy {
|
||||
screenshot.Set(int(pixel.x), int(pixel.y), image.Black)
|
||||
if pixel.DX == 0 && pixel.DY == 0 {
|
||||
screenshot.Set(int(pixel.X), int(pixel.Y), image.Black)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -223,3 +273,8 @@ func (s *EditorScene) Screenshot() {
|
|||
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.
|
||||
type Level struct {
|
||||
Version int32 `json:"version"` // File format version spec.
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
Password string `json:"passwd"`
|
||||
Locked bool `json:"locked"`
|
||||
Version int32 `json:"version"` // File format version spec.
|
||||
GameVersion string `json:"gameVersion"` // Game version that created the level.
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
Password string `json:"passwd"`
|
||||
Locked bool `json:"locked"`
|
||||
|
||||
// Level size.
|
||||
Width int32 `json:"w"`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/doodads"
|
||||
"git.kirsle.net/apps/doodle/events"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
|
@ -8,17 +9,19 @@ import (
|
|||
|
||||
// PlayScene manages the "Edit Level" game mode.
|
||||
type PlayScene struct {
|
||||
canvas Grid
|
||||
// Configuration attributes.
|
||||
Filename string
|
||||
Canvas render.Grid
|
||||
|
||||
// Private variables.
|
||||
canvas render.Grid
|
||||
|
||||
// Canvas size
|
||||
width int32
|
||||
height int32
|
||||
|
||||
// Player position and velocity.
|
||||
x int32
|
||||
y int32
|
||||
vx int32
|
||||
vy int32
|
||||
// Player character
|
||||
player doodads.Doodad
|
||||
}
|
||||
|
||||
// Name of the scene.
|
||||
|
@ -28,19 +31,43 @@ func (s *PlayScene) Name() string {
|
|||
|
||||
// Setup the play scene.
|
||||
func (s *PlayScene) Setup(d *Doodle) error {
|
||||
s.x = 10
|
||||
s.y = 10
|
||||
// Given a filename or map data to play?
|
||||
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 {
|
||||
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.height = d.height
|
||||
|
||||
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop the editor scene.
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
@ -50,35 +77,53 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
// Clear the canvas and fill it with white.
|
||||
d.Engine.Clear(render.White)
|
||||
|
||||
for pixel := range s.canvas {
|
||||
d.Engine.DrawPoint(render.Black, render.Point{pixel.x, pixel.y})
|
||||
}
|
||||
s.canvas.Draw(d.Engine)
|
||||
|
||||
// Draw our hero.
|
||||
d.Engine.DrawRect(render.Magenta, render.Rect{s.x, s.y, 16, 16})
|
||||
s.player.Draw(d.Engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
||||
func (s *PlayScene) movePlayer(ev *events.State) {
|
||||
delta := s.player.Position()
|
||||
var playerSpeed int32 = 8
|
||||
var gravity int32 = 2
|
||||
|
||||
if ev.Down.Now {
|
||||
s.y += 4
|
||||
delta.Y += playerSpeed
|
||||
}
|
||||
if ev.Left.Now {
|
||||
s.x -= 4
|
||||
delta.X -= playerSpeed
|
||||
}
|
||||
if ev.Right.Now {
|
||||
s.x += 4
|
||||
delta.X += playerSpeed
|
||||
}
|
||||
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.
|
||||
func (s *PlayScene) LoadLevel(filename string) error {
|
||||
s.canvas = Grid{}
|
||||
s.canvas = render.Grid{}
|
||||
|
||||
m, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
|
@ -86,12 +131,17 @@ func (s *PlayScene) LoadLevel(filename string) error {
|
|||
}
|
||||
|
||||
for _, point := range m.Pixels {
|
||||
pixel := Pixel{
|
||||
x: point.X,
|
||||
y: point.Y,
|
||||
pixel := render.Pixel{
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
}
|
||||
s.canvas[pixel] = 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"git.kirsle.net/apps/doodle/events"
|
||||
)
|
||||
|
@ -103,3 +104,43 @@ var (
|
|||
Magenta = Color{255, 0, 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 {
|
||||
Name() string
|
||||
Setup(*Doodle) error
|
||||
Destroy() error
|
||||
|
||||
// Loop should update the scene's state but not draw anything.
|
||||
Loop(*Doodle, *events.State) error
|
||||
|
@ -19,7 +20,11 @@ type Scene interface {
|
|||
|
||||
// Goto a scene. First it unloads the current scene.
|
||||
func (d *Doodle) Goto(scene Scene) error {
|
||||
// d.scene.Destroy()
|
||||
// Teardown existing scene.
|
||||
if d.scene != nil {
|
||||
d.scene.Destroy()
|
||||
}
|
||||
|
||||
log.Info("Goto Scene")
|
||||
d.scene = scene
|
||||
return d.scene.Setup(d)
|
||||
|
|
6
shell.go
6
shell.go
|
@ -2,6 +2,7 @@ package doodle
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/balance"
|
||||
|
@ -9,6 +10,11 @@ import (
|
|||
"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.
|
||||
type Shell struct {
|
||||
parent *Doodle
|
||||
|
|
Loading…
Reference in New Issue
Block a user