probe
This commit is contained in:
95
anim2d.cm
Normal file
95
anim2d.cm
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
var film2d = use('film2d')
|
||||||
|
var graphics = use('graphics')
|
||||||
|
|
||||||
|
var anim_proto = {
|
||||||
|
type: 'sprite',
|
||||||
|
|
||||||
|
play: function(name) {
|
||||||
|
var anim = name ? this._anims[name] : this._anims
|
||||||
|
if (!anim || !anim.frames) return this
|
||||||
|
this._anim = anim
|
||||||
|
this._frame = 0
|
||||||
|
this._elapsed = 0
|
||||||
|
this._playing = true
|
||||||
|
this.image = anim.frames[0].image
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function() {
|
||||||
|
this._playing = false
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
|
resume: function() {
|
||||||
|
this._playing = true
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(dt) {
|
||||||
|
if (!this._playing || !this._anim) return
|
||||||
|
var frames = this._anim.frames
|
||||||
|
this._elapsed += dt
|
||||||
|
while (this._elapsed >= frames[this._frame].time) {
|
||||||
|
this._elapsed -= frames[this._frame].time
|
||||||
|
this._frame++
|
||||||
|
if (this._frame >= length(frames)) {
|
||||||
|
if (this._anim.loop) {
|
||||||
|
this._frame = 0
|
||||||
|
} else {
|
||||||
|
this._frame = length(frames) - 1
|
||||||
|
this._playing = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.image = frames[this._frame].image
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
film2d.unregister(this._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(props) {
|
||||||
|
var img = props.image || props.anim
|
||||||
|
var anims = graphics.texture(img)
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
type: 'sprite',
|
||||||
|
pos: {x: 0, y: 0},
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
anchor_x: 0.5,
|
||||||
|
anchor_y: 0.5,
|
||||||
|
rotation: 0,
|
||||||
|
color: {r: 1, g: 1, b: 1, a: 1},
|
||||||
|
opacity: 1,
|
||||||
|
tint: {r: 1, g: 1, b: 1, a: 1},
|
||||||
|
filter: 'nearest',
|
||||||
|
plane: 'default',
|
||||||
|
layer: 0,
|
||||||
|
groups: [],
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = object(defaults, props)
|
||||||
|
data._anims = anims
|
||||||
|
data._anim = null
|
||||||
|
data._frame = 0
|
||||||
|
data._elapsed = 0
|
||||||
|
data._playing = false
|
||||||
|
data.image = null
|
||||||
|
|
||||||
|
var s = meme(anim_proto, data)
|
||||||
|
|
||||||
|
// Auto-play: if anims is a single animation, start it
|
||||||
|
if (anims && anims.frames) {
|
||||||
|
s._anim = anims
|
||||||
|
s._frame = 0
|
||||||
|
s._playing = true
|
||||||
|
s.image = anims.frames[0].image
|
||||||
|
}
|
||||||
|
|
||||||
|
film2d.register(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
@@ -128,9 +128,8 @@ var basecam = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cam.make = function()
|
cam.make = function(config) {
|
||||||
{
|
return meme(basecam, config || {})
|
||||||
return meme(basecam)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cam
|
return cam
|
||||||
|
|||||||
108
collision2d.cm
Normal file
108
collision2d.cm
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
var collision2d = {}
|
||||||
|
|
||||||
|
collision2d.body = function(config) {
|
||||||
|
var c = config || {}
|
||||||
|
return {
|
||||||
|
type: c.type || 'aabb',
|
||||||
|
width: c.width || 0,
|
||||||
|
height: c.height || 0,
|
||||||
|
radius: c.radius || 0,
|
||||||
|
offset: c.offset || {x: 0, y: 0},
|
||||||
|
layer: c.layer || 0,
|
||||||
|
mask: c.mask || 0xFFFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function body_center(body, pos) {
|
||||||
|
return {
|
||||||
|
x: pos.x + body.offset.x,
|
||||||
|
y: pos.y + body.offset.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_aabb_aabb(a, ap, b, bp) {
|
||||||
|
var ac = body_center(a, ap)
|
||||||
|
var bc = body_center(b, bp)
|
||||||
|
var ahw = a.width * 0.5
|
||||||
|
var ahh = a.height * 0.5
|
||||||
|
var bhw = b.width * 0.5
|
||||||
|
var bhh = b.height * 0.5
|
||||||
|
return abs(ac.x - bc.x) < ahw + bhw && abs(ac.y - bc.y) < ahh + bhh
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_circle_circle(a, ap, b, bp) {
|
||||||
|
var ac = body_center(a, ap)
|
||||||
|
var bc = body_center(b, bp)
|
||||||
|
var dx = ac.x - bc.x
|
||||||
|
var dy = ac.y - bc.y
|
||||||
|
var r = a.radius + b.radius
|
||||||
|
return dx * dx + dy * dy < r * r
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_aabb_circle(aabb, ap, circ, cp) {
|
||||||
|
var ac = body_center(aabb, ap)
|
||||||
|
var cc = body_center(circ, cp)
|
||||||
|
var hw = aabb.width * 0.5
|
||||||
|
var hh = aabb.height * 0.5
|
||||||
|
var cx = max(ac.x - hw, min(cc.x, ac.x + hw))
|
||||||
|
var cy = max(ac.y - hh, min(cc.y, ac.y + hh))
|
||||||
|
var dx = cc.x - cx
|
||||||
|
var dy = cc.y - cy
|
||||||
|
return dx * dx + dy * dy < circ.radius * circ.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
collision2d.test = function(a, a_pos, b, b_pos) {
|
||||||
|
if (a.layer & b.mask == 0 || b.layer & a.mask == 0)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (a.type == 'aabb' && b.type == 'aabb')
|
||||||
|
return test_aabb_aabb(a, a_pos, b, b_pos)
|
||||||
|
if (a.type == 'circle' && b.type == 'circle')
|
||||||
|
return test_circle_circle(a, a_pos, b, b_pos)
|
||||||
|
if (a.type == 'aabb' && b.type == 'circle')
|
||||||
|
return test_aabb_circle(a, a_pos, b, b_pos)
|
||||||
|
if (a.type == 'circle' && b.type == 'aabb')
|
||||||
|
return test_aabb_circle(b, b_pos, a, a_pos)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
collision2d.overlap = function(body, pos, others) {
|
||||||
|
var results = []
|
||||||
|
var i = 0
|
||||||
|
var other = null
|
||||||
|
for (i = 0; i < length(others); i++) {
|
||||||
|
other = others[i]
|
||||||
|
if (collision2d.test(body, pos, other.body, other.pos))
|
||||||
|
push(results, other)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
collision2d.overlap_point = function(point, bodies) {
|
||||||
|
var results = []
|
||||||
|
var i = 0
|
||||||
|
var b = null
|
||||||
|
var c = null
|
||||||
|
var hw = 0
|
||||||
|
var hh = 0
|
||||||
|
var dx = 0
|
||||||
|
var dy = 0
|
||||||
|
for (i = 0; i < length(bodies); i++) {
|
||||||
|
b = bodies[i]
|
||||||
|
c = body_center(b.body, b.pos)
|
||||||
|
if (b.body.type == 'aabb') {
|
||||||
|
hw = b.body.width * 0.5
|
||||||
|
hh = b.body.height * 0.5
|
||||||
|
if (abs(point.x - c.x) < hw && abs(point.y - c.y) < hh)
|
||||||
|
push(results, b)
|
||||||
|
} else if (b.body.type == 'circle') {
|
||||||
|
dx = point.x - c.x
|
||||||
|
dy = point.y - c.y
|
||||||
|
if (dx * dx + dy * dy < b.body.radius * b.body.radius)
|
||||||
|
push(results, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
return collision2d
|
||||||
@@ -410,4 +410,39 @@ function _calc_presentation(src, dst, mode) {
|
|||||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _last_plan = null
|
||||||
|
|
||||||
|
var _orig_compile = compositor.compile
|
||||||
|
compositor.compile = function(config) {
|
||||||
|
_last_plan = _orig_compile(config)
|
||||||
|
return _last_plan
|
||||||
|
}
|
||||||
|
|
||||||
|
compositor.snapshot = function() {
|
||||||
|
if (!_last_plan) return null
|
||||||
|
var planes = []
|
||||||
|
var i = 0
|
||||||
|
var pass = null
|
||||||
|
for (i = 0; i < length(_last_plan.passes); i++) {
|
||||||
|
pass = _last_plan.passes[i]
|
||||||
|
if (pass.type == 'render') {
|
||||||
|
push(planes, {
|
||||||
|
drawable_count: length(pass.drawables),
|
||||||
|
camera: pass.camera ? {
|
||||||
|
pos: pass.camera.pos,
|
||||||
|
width: pass.camera.width,
|
||||||
|
height: pass.camera.height
|
||||||
|
} : null,
|
||||||
|
target_size: pass.target_size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pass_count: length(_last_plan.passes),
|
||||||
|
target_count: length(array(_last_plan.targets)),
|
||||||
|
screen_size: _last_plan.screen_size,
|
||||||
|
planes: planes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return compositor
|
return compositor
|
||||||
103
core.cm
103
core.cm
@@ -9,11 +9,11 @@
|
|||||||
// update: function(dt) { ... },
|
// update: function(dt) { ... },
|
||||||
// render: function() { return graph }
|
// render: function() { return graph }
|
||||||
// })
|
// })
|
||||||
|
//
|
||||||
|
// Headless mode (no window, no input, no render, no audio):
|
||||||
|
// core.start({ headless: true, update: function(dt) { ... } })
|
||||||
|
|
||||||
var video = use('sdl3/video')
|
|
||||||
var events = use('sdl3/input')
|
|
||||||
var time_mod = use('time')
|
var time_mod = use('time')
|
||||||
var debug_imgui = use('debug_imgui')
|
|
||||||
|
|
||||||
var core = {}
|
var core = {}
|
||||||
|
|
||||||
@@ -25,40 +25,56 @@ var _window = null
|
|||||||
var _last_time = 0
|
var _last_time = 0
|
||||||
var _framerate = 60
|
var _framerate = 60
|
||||||
|
|
||||||
var imgui = use('imgui')
|
// Lazy-loaded modules (only in non-headless mode)
|
||||||
|
var video = null
|
||||||
|
var events = null
|
||||||
|
var imgui = null
|
||||||
|
var debug_imgui = null
|
||||||
|
|
||||||
// Start the application
|
// Start the application
|
||||||
core.start = function(config) {
|
core.start = function(config) {
|
||||||
_config = config
|
_config = config
|
||||||
_framerate = config.framerate || 60
|
_framerate = config.framerate || 60
|
||||||
|
_running = true
|
||||||
|
_last_time = time_mod.number()
|
||||||
|
|
||||||
|
if (config.probe) _register_probes()
|
||||||
|
|
||||||
|
if (config.headless) {
|
||||||
|
_headless_loop()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load SDL modules
|
||||||
|
video = use('sdl3/video')
|
||||||
|
events = use('sdl3/input')
|
||||||
|
imgui = use('imgui')
|
||||||
|
debug_imgui = use('debug_imgui')
|
||||||
|
|
||||||
// Initialize SDL GPU backend
|
// Initialize SDL GPU backend
|
||||||
var sdl_gpu = use('sdl_gpu')
|
var sdl_gpu = use('sdl_gpu')
|
||||||
_backend = sdl_gpu
|
_backend = sdl_gpu
|
||||||
|
|
||||||
var init_result = _backend.init({
|
var init_result = _backend.init({
|
||||||
width: config.width || 1280,
|
width: config.width || 1280,
|
||||||
height: config.height || 720,
|
height: config.height || 720,
|
||||||
title: config.title || "Prosperon"
|
title: config.title || "Prosperon"
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!init_result) {
|
if (!init_result) {
|
||||||
log.console("core: Failed to initialize backend")
|
log.console("core: Failed to initialize backend")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
_window = _backend.get_window()
|
_window = _backend.get_window()
|
||||||
|
|
||||||
if ((config.imgui || config.editor) && imgui.init) {
|
if ((config.imgui || config.editor) && imgui.init) {
|
||||||
imgui.init(_window, _backend.get_device())
|
imgui.init(_window, _backend.get_device())
|
||||||
}
|
}
|
||||||
|
|
||||||
_running = true
|
|
||||||
_last_time = time_mod.number()
|
|
||||||
|
|
||||||
// Start main loop
|
// Start main loop
|
||||||
_main_loop()
|
_main_loop()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +121,24 @@ function fps_get_avg() {
|
|||||||
var _current_fps = 0
|
var _current_fps = 0
|
||||||
var _frame_time_ms = 0
|
var _frame_time_ms = 0
|
||||||
|
|
||||||
|
// Headless loop — update only, no window/input/render/audio
|
||||||
|
function _headless_loop() {
|
||||||
|
if (!_running) return
|
||||||
|
|
||||||
|
var now = time_mod.number()
|
||||||
|
var dt = now - _last_time
|
||||||
|
_last_time = now
|
||||||
|
|
||||||
|
if (_config.update) _config.update(dt)
|
||||||
|
|
||||||
|
var frame_time = 1 / _framerate
|
||||||
|
var elapsed = time_mod.number() - now
|
||||||
|
var delay = frame_time - elapsed
|
||||||
|
if (delay < 0) delay = 0
|
||||||
|
|
||||||
|
$delay(_headless_loop, delay)
|
||||||
|
}
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
function _main_loop() {
|
function _main_loop() {
|
||||||
var frame_start = time_mod.number()
|
var frame_start = time_mod.number()
|
||||||
@@ -232,4 +266,45 @@ function _main_loop() {
|
|||||||
$delay(_main_loop, delay)
|
$delay(_main_loop, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _register_probes() {
|
||||||
|
var probe = use('probe')
|
||||||
|
var film2d = use('film2d')
|
||||||
|
var world = use('world')
|
||||||
|
var comp = use('compositor')
|
||||||
|
var input_mod = use('input')
|
||||||
|
var tween_mod = use('tween')
|
||||||
|
var graphics_mod = use('graphics')
|
||||||
|
|
||||||
|
probe.register("drawables", {
|
||||||
|
all: function(args) { return film2d.snapshot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("world", {
|
||||||
|
all: function(args) { return world.snapshot() },
|
||||||
|
count: function(args) { return world.count() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("compositor", {
|
||||||
|
all: function(args) { return comp.snapshot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("input", {
|
||||||
|
all: function(args) { return input_mod.snapshot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("tweens", {
|
||||||
|
all: function(args) { return tween_mod.snapshot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("assets", {
|
||||||
|
all: function(args) { return graphics_mod.snapshot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("core", {
|
||||||
|
fps: function(args) {
|
||||||
|
return {fps: _current_fps, frame_time_ms: _frame_time_ms}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return core
|
return core
|
||||||
|
|||||||
@@ -5,229 +5,91 @@ type: docs
|
|||||||
|
|
||||||
# draw2d
|
# draw2d
|
||||||
|
|
||||||
|
A collection of retained-mode 2D drawing factories. Each factory creates an object that auto-registers with `film2d` and renders via the compositor. Destroy objects when no longer needed.
|
||||||
|
|
||||||
A collection of 2D drawing functions that operate in screen space. Provides primitives
|
```javascript
|
||||||
for lines, rectangles, text, sprite drawing, etc.
|
var draw = use('draw2d')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Factories
|
||||||
|
|
||||||
### point(pos, size, color) <sub>function</sub>
|
### draw.sprite(props)
|
||||||
|
|
||||||
|
Create a sprite from an image. Auto-registers with `film2d`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var s = draw.sprite({
|
||||||
|
image: "hero.png",
|
||||||
|
pos: {x: 100, y: 200},
|
||||||
|
width: 32, height: 32,
|
||||||
|
plane: 'game', layer: 0
|
||||||
|
})
|
||||||
|
s.destroy() // remove from renderer
|
||||||
|
```
|
||||||
|
|
||||||
|
See `sprite.cm` for full property list: `pos`, `image`, `width`, `height`, `anchor_x`, `anchor_y`, `rotation`, `color`, `opacity`, `tint`, `filter`, `plane`, `layer`, `groups`, `visible`, `flip`, `fit`, `uv`.
|
||||||
|
|
||||||
**pos**: A 2D position ([x, y]) where the point should be drawn.
|
### draw.shape.rect(props) / circle(props) / ellipse(props) / pill(props)
|
||||||
|
|
||||||
**size**: The size of the point (not currently affecting rendering).
|
Create SDF shapes. Supports fill, stroke, rounded corners, dashing, feathering, and texture fill.
|
||||||
|
|
||||||
**color**: The color of the point, defaults to Color.blue.
|
```javascript
|
||||||
|
var box = draw.shape.rect({
|
||||||
|
pos: {x: 50, y: 50}, width: 100, height: 60,
|
||||||
|
fill: {r: 1, g: 0, b: 0, a: 1},
|
||||||
|
stroke: {r: 1, g: 1, b: 1, a: 1}, stroke_thickness: 2,
|
||||||
|
radius: 8,
|
||||||
|
plane: 'game'
|
||||||
|
})
|
||||||
|
|
||||||
|
var ball = draw.shape.circle({
|
||||||
|
pos: {x: 200, y: 200}, radius: 16,
|
||||||
|
fill: {r: 0, g: 1, b: 0, a: 1},
|
||||||
|
plane: 'game', groups: ['glow']
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
**Returns**: None
|
Properties: `shape_type`, `pos`, `width`, `height`, `radius`, `corner_style`, `feather`, `stroke_thickness`, `stroke_align`, `dash_len`, `gap_len`, `dash_offset`, `cap`, `join`, `fill`, `stroke`, `blend`, `opacity`, `fill_tex`, `uv`, `plane`, `layer`, `groups`, `visible`.
|
||||||
|
|
||||||
|
### draw.text(props)
|
||||||
|
|
||||||
### line(points, color, thickness, pipeline) <sub>function</sub>
|
Create a text label. Updates live when you change `.text`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var label = draw.text({
|
||||||
|
text: "Score: 0",
|
||||||
|
pos: {x: 10, y: 500},
|
||||||
|
font: "fonts/dos", size: 16,
|
||||||
|
color: {r: 1, g: 1, b: 1, a: 1},
|
||||||
|
plane: 'hud'
|
||||||
|
})
|
||||||
|
label.text = "Score: 42" // updates on next frame
|
||||||
|
```
|
||||||
|
|
||||||
|
### draw.tilemap(props)
|
||||||
|
|
||||||
|
Create a tile-based map. See `tilemap2d.cm`.
|
||||||
|
|
||||||
**points**: An array of 2D positions representing the line vertices.
|
### draw.anim(props)
|
||||||
|
|
||||||
**color**: The color of the line, default Color.white.
|
Create an animated sprite from an aseprite/gif file. Auto-plays if the image has frames.
|
||||||
|
|
||||||
**thickness**: The line thickness, default 1.
|
```javascript
|
||||||
|
var anim = draw.anim({
|
||||||
|
image: "hero.aseprite",
|
||||||
|
pos: {x: 100, y: 100},
|
||||||
|
plane: 'game'
|
||||||
|
})
|
||||||
|
anim.play("walk") // play named animation
|
||||||
|
anim.stop() // pause
|
||||||
|
anim.resume() // resume
|
||||||
|
anim.update(dt) // advance frame (call from update loop)
|
||||||
|
```
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
## Lifecycle
|
||||||
|
|
||||||
|
All draw2d objects register with `film2d` on creation. Call `.destroy()` to unregister and remove from rendering. Set `.visible = false` to hide without destroying.
|
||||||
|
|
||||||
**Returns**: None
|
## Planes and Groups
|
||||||
|
|
||||||
|
|
||||||
### cross(pos, size, color, thickness, pipe) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**pos**: The center of the cross as a 2D position ([x, y]).
|
|
||||||
|
|
||||||
**size**: Half the size of each cross arm.
|
|
||||||
|
|
||||||
**color**: The color of the cross, default Color.red.
|
|
||||||
|
|
||||||
**thickness**: The thickness of each line, default 1.
|
|
||||||
|
|
||||||
**pipe**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### arrow(start, end, color, wingspan, wingangle, pipe) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**start**: The start position of the arrow ([x, y]).
|
|
||||||
|
|
||||||
**end**: The end (tip) position of the arrow ([x, y]).
|
|
||||||
|
|
||||||
**color**: The color, default Color.red.
|
|
||||||
|
|
||||||
**wingspan**: The length of each arrowhead 'wing', default 4.
|
|
||||||
|
|
||||||
**wingangle**: Wing rotation in degrees, default 10.
|
|
||||||
|
|
||||||
**pipe**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### rectangle(rect, color, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**rect**: A rectangle object with {x, y, width, height}.
|
|
||||||
|
|
||||||
**color**: The fill color, default Color.white.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### tile(image, rect, color, tile, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
:raises Error: If no image is provided.
|
|
||||||
|
|
||||||
|
|
||||||
**image**: An image object or string path to a texture.
|
|
||||||
|
|
||||||
**rect**: A rectangle specifying draw location/size ({x, y, width, height}).
|
|
||||||
|
|
||||||
**color**: The color tint, default Color.white.
|
|
||||||
|
|
||||||
**tile**: A tiling definition ({repeat_x, repeat_y}), default tile_def.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### slice9(image, rect, slice, color, info, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
:raises Error: If no image is provided.
|
|
||||||
|
|
||||||
|
|
||||||
**image**: An image object or string path to a texture.
|
|
||||||
|
|
||||||
**rect**: A rectangle specifying draw location/size, default [0, 0].
|
|
||||||
|
|
||||||
**slice**: The pixel inset or spacing for the 9-slice (number or object).
|
|
||||||
|
|
||||||
**color**: The color tint, default Color.white.
|
|
||||||
|
|
||||||
**info**: A slice9 info object controlling tiling of edges/corners.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### image(image, rect, rotation, color, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
:raises Error: If no image is provided.
|
|
||||||
|
|
||||||
|
|
||||||
**image**: An image object or string path to a texture.
|
|
||||||
|
|
||||||
**rect**: A rectangle specifying draw location/size, default [0,0]; width/height default to image size.
|
|
||||||
|
|
||||||
**rotation**: Rotation in degrees (not currently used).
|
|
||||||
|
|
||||||
**color**: The color tint, default none.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: A sprite object that was created for this draw call.
|
|
||||||
|
|
||||||
|
|
||||||
### images(image, rects, config) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
:raises Error: If no image is provided.
|
|
||||||
|
|
||||||
|
|
||||||
**image**: An image object or string path to a texture.
|
|
||||||
|
|
||||||
**rects**: An array of rectangle objects ({x, y, width, height}) to draw.
|
|
||||||
|
|
||||||
**config**: (Unused) Additional config data if needed.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: An array of sprite objects created and queued for rendering.
|
|
||||||
|
|
||||||
|
|
||||||
### sprites(sprites, sort, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**sprites**: An array of sprite objects to draw.
|
|
||||||
|
|
||||||
**sort**: Sorting mode or order, default 0.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### circle(pos, radius, color, inner_radius, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**pos**: Center of the circle ([x, y]).
|
|
||||||
|
|
||||||
**radius**: The circle radius.
|
|
||||||
|
|
||||||
**color**: The fill color of the circle, default none.
|
|
||||||
|
|
||||||
**inner_radius**: (Unused) Possibly ring thickness, default 1.
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
|
||||||
### text(text, rect, font, size, color, wrap, pipeline) <sub>function</sub>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**text**: The string to draw.
|
|
||||||
|
|
||||||
**rect**: A rectangle specifying draw position (and possibly wrapping area).
|
|
||||||
|
|
||||||
**font**: A font object or string path, default sysfont.
|
|
||||||
|
|
||||||
**size**: (Unused) Possibly intended for scaling the font size.
|
|
||||||
|
|
||||||
**color**: The text color, default Color.white.
|
|
||||||
|
|
||||||
**wrap**: Pixel width for text wrapping, default 0 (no wrap).
|
|
||||||
|
|
||||||
**pipeline**: (Optional) A pipeline or rendering state object.
|
|
||||||
|
|
||||||
|
|
||||||
**Returns**: None
|
|
||||||
|
|
||||||
|
Every drawable has a `plane` (string) and optional `groups` (array of strings). The compositor renders drawables per-plane. Groups route drawables through effects (bloom, mask, etc.).
|
||||||
|
|||||||
@@ -105,13 +105,58 @@ Each system module provides behavior that operates on the entity's data. No clas
|
|||||||
|
|
||||||
## Querying Entities
|
## Querying Entities
|
||||||
|
|
||||||
The world module lets you find entities:
|
The world module provides several ways to find and iterate entities:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var world = use('world')
|
var world = use('world')
|
||||||
|
|
||||||
// All entities are tracked internally
|
// Iterate all entities
|
||||||
// Query patterns are still being developed
|
world.each(function(entity) {
|
||||||
|
log.console(entity)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter entities by predicate — returns array
|
||||||
|
var enemies = world.query(function(e) { return e.team == 'enemy' })
|
||||||
|
|
||||||
|
// Find first matching entity
|
||||||
|
var player = world.find(function(e) { return e.is_player })
|
||||||
|
|
||||||
|
// Get entity count
|
||||||
|
var n = world.count()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating Entities
|
||||||
|
|
||||||
|
Call `world.update(dt)` each frame to tick all entities that have an `update(dt)` method:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In your core.start() update callback:
|
||||||
|
core.start({
|
||||||
|
update: function(dt) {
|
||||||
|
world.update(dt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading Levels
|
||||||
|
|
||||||
|
Load a level from a JSON array of entity definitions:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
world.load_level([
|
||||||
|
{"script": "entities/goblin", "pos": {"x": 100, "y": 200}},
|
||||||
|
{"script": "entities/tree", "pos": {"x": 300, "y": 100}}
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
Each entry's `script` field is loaded via `use()` as the prototype. All other fields are applied as overrides.
|
||||||
|
|
||||||
|
## Clearing the World
|
||||||
|
|
||||||
|
Remove and destroy all entities:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
world.clear()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Override Rules
|
## Override Rules
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ Prosperon is not a monolithic engine with a global state object. It is a collect
|
|||||||
| `compositor` | Build render plans from scene configs |
|
| `compositor` | Build render plans from scene configs |
|
||||||
| `input` | Action mapping, device routing |
|
| `input` | Action mapping, device routing |
|
||||||
| `sound` | Audio playback |
|
| `sound` | Audio playback |
|
||||||
| `world` | Entity management |
|
| `world` | Entity management (add, query, update, levels) |
|
||||||
| `camera` | Viewport into the world |
|
| `camera` | Viewport into the world |
|
||||||
|
| `draw2d` | Re-exports sprite, shape, text, tilemap, anim |
|
||||||
| `text2d` | Text rendering |
|
| `text2d` | Text rendering |
|
||||||
| `shape2d` | SDF shapes |
|
| `shape2d` | SDF shapes (rect, circle, ellipse, pill) |
|
||||||
|
| `anim2d` | Sprite animation playback (aseprite/gif) |
|
||||||
|
| `collision2d` | AABB + circle overlap queries |
|
||||||
| `tilemap2d` | Grid-based tile rendering |
|
| `tilemap2d` | Grid-based tile rendering |
|
||||||
| `tween` | Value interpolation |
|
| `tween` | Value interpolation |
|
||||||
| `resources` | Asset path resolution |
|
| `resources` | Asset path resolution |
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ var sprite = use('sprite')
|
|||||||
var tilemap = use('tilemap2d')
|
var tilemap = use('tilemap2d')
|
||||||
var text = use('text2d')
|
var text = use('text2d')
|
||||||
var shape = use('shape2d')
|
var shape = use('shape2d')
|
||||||
|
var anim = use('anim2d')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sprite,
|
sprite,
|
||||||
tilemap,
|
tilemap,
|
||||||
text,
|
text,
|
||||||
shape
|
shape,
|
||||||
}
|
anim
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
return {
|
return {
|
||||||
title:"Bunnymark",
|
title: "Bunnymark",
|
||||||
width:1200,
|
width: 1200,
|
||||||
height:600,
|
height: 600
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,124 @@
|
|||||||
var draw = use('draw2d')
|
var core = use('core')
|
||||||
var render = use('render')
|
var camera = use('camera')
|
||||||
var graphics = use('graphics')
|
var compositor = use('compositor')
|
||||||
var sprite = use('sprite')
|
var input = use('input')
|
||||||
var geom = use('geometry')
|
var sprite_factory = use('sprite')
|
||||||
var config = use('config')
|
var text2d = use('text2d')
|
||||||
var color = use('color')
|
|
||||||
var random = use('random')
|
var random = use('random')
|
||||||
|
|
||||||
var bunnyTex = graphics.texture("bunny")
|
var GW = 1200, GH = 600
|
||||||
|
|
||||||
// We'll store our bunnies in an array of objects: { x, y, vx, vy }
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
|
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
|
|
||||||
|
input.configure({
|
||||||
|
action_map: {
|
||||||
|
spawn: ['mouse_button_left']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bunny tracking
|
||||||
var bunnies = []
|
var bunnies = []
|
||||||
|
var bunny_sprites = []
|
||||||
|
var BUNNY_W = 26, BUNNY_H = 37
|
||||||
|
|
||||||
// Start with some initial bunnies:
|
// HUD
|
||||||
var i = 0;
|
var count_label = text2d({
|
||||||
for (i = 0; i < 100; i++) {
|
text: "Bunnies: 0", pos: {x: 10, y: GH - 25},
|
||||||
push(bunnies, {
|
plane: 'hud', size: 16, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
x: random.random() * config.width,
|
})
|
||||||
y: random.random() * config.height,
|
var fps_label = text2d({
|
||||||
|
text: "FPS: 0", pos: {x: 10, y: GH - 45},
|
||||||
|
plane: 'hud', size: 16, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
var fps_timer = 0
|
||||||
|
var frame_count = 0
|
||||||
|
|
||||||
|
function add_bunny(x, y) {
|
||||||
|
var bunny = {
|
||||||
vx: (random.random() * 300) - 150,
|
vx: (random.random() * 300) - 150,
|
||||||
vy: (random.random() * 300) - 150
|
vy: (random.random() * 300) - 150
|
||||||
|
}
|
||||||
|
var s = sprite_factory({
|
||||||
|
image: "bunny",
|
||||||
|
pos: {x: x, y: y},
|
||||||
|
width: BUNNY_W, height: BUNNY_H,
|
||||||
|
anchor_x: 0.5, anchor_y: 0.5,
|
||||||
|
plane: 'game'
|
||||||
})
|
})
|
||||||
|
push(bunnies, bunny)
|
||||||
|
push(bunny_sprites, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var update = function(dt) {
|
// Initial bunnies
|
||||||
// If left mouse is down, spawn some more bunnies:
|
var i = 0
|
||||||
var mouse = input.mousestate()
|
for (i = 0; i < 100; i++) {
|
||||||
var i = 0;
|
add_bunny(
|
||||||
var b = null;
|
random.random() * GW,
|
||||||
if (mouse.left)
|
random.random() * GH
|
||||||
for (i = 0; i < 50; i++) {
|
)
|
||||||
push(bunnies, {
|
}
|
||||||
x: mouse.x,
|
count_label.text = "Bunnies: " + length(bunnies)
|
||||||
y: mouse.y,
|
|
||||||
vx: (random.random() * 300) - 150,
|
// Mouse position tracking
|
||||||
vy: (random.random() * 300) - 150
|
var mouse_x = GW / 2, mouse_y = GH / 2
|
||||||
})
|
|
||||||
|
var comp_config = {
|
||||||
|
clear: {r: 0.2, g: 0.2, b: 0.3, a: 1},
|
||||||
|
planes: [
|
||||||
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
||||||
|
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
core.start({
|
||||||
|
width: 1200, height: 600, title: "Bunnymark",
|
||||||
|
|
||||||
|
input: function(ev) {
|
||||||
|
if (ev.type == 'mouse_motion') {
|
||||||
|
mouse_x = ev.pos.x
|
||||||
|
mouse_y = ev.pos.y
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function(dt) {
|
||||||
|
// Spawn bunnies while clicking
|
||||||
|
var down = input.player1().down()
|
||||||
|
if (down.spawn) {
|
||||||
|
var si = 0
|
||||||
|
for (si = 0; si < 50; si++) {
|
||||||
|
add_bunny(mouse_x, mouse_y)
|
||||||
|
}
|
||||||
|
count_label.text = "Bunnies: " + length(bunnies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bunny positions and bounce them inside the screen:
|
// Update all bunnies
|
||||||
for (i = 0; i < length(bunnies); i++) {
|
var b = null, s = null
|
||||||
b = bunnies[i]
|
for (i = 0; i < length(bunnies); i++) {
|
||||||
b.x += b.vx * dt
|
b = bunnies[i]
|
||||||
b.y += b.vy * dt
|
s = bunny_sprites[i]
|
||||||
|
|
||||||
// Bounce off left/right edges
|
s.pos.x += b.vx * dt
|
||||||
if (b.x < 0) { b.x = 0; b.vx = -b.vx }
|
s.pos.y += b.vy * dt
|
||||||
else if (b.x > config.width) { b.x = config.width; b.vx = -b.vx }
|
|
||||||
|
|
||||||
// Bounce off bottom/top edges
|
if (s.pos.x < 0) { s.pos.x = 0; b.vx = -b.vx }
|
||||||
if (b.y < 0) { b.y = 0; b.vy = -b.vy }
|
else if (s.pos.x > GW) { s.pos.x = GW; b.vx = -b.vx }
|
||||||
else if (b.y > config.height) { b.y = config.height; b.vy = -b.vy }
|
if (s.pos.y < 0) { s.pos.y = 0; b.vy = -b.vy }
|
||||||
|
else if (s.pos.y > GH) { s.pos.y = GH; b.vy = -b.vy }
|
||||||
|
}
|
||||||
|
|
||||||
|
// FPS counter
|
||||||
|
frame_count++
|
||||||
|
fps_timer += dt
|
||||||
|
if (fps_timer >= 1) {
|
||||||
|
fps_label.text = "FPS: " + frame_count
|
||||||
|
frame_count = 0
|
||||||
|
fps_timer -= 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return compositor.execute(compositor.compile(comp_config))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
var hud = function() {
|
|
||||||
draw.images(bunnyTex, bunnies)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,403 +1,232 @@
|
|||||||
/* main.js – runs the demo with your prototype-based grid */
|
var core = use('core')
|
||||||
|
var camera = use('camera')
|
||||||
|
var compositor = use('compositor')
|
||||||
|
var input = use('input')
|
||||||
|
var shape = use('shape2d')
|
||||||
|
var sprite_factory = use('sprite')
|
||||||
|
var text2d = use('text2d')
|
||||||
|
|
||||||
var json = use('json')
|
var Grid = use('grid')
|
||||||
var draw2d = use('prosperon/draw2d')
|
var MovementSystem = use('movement').MovementSystem
|
||||||
|
var startingPos = use('pieces').startingPosition
|
||||||
|
var rules = use('rules')
|
||||||
|
|
||||||
var blob = use('blob')
|
var S = 60
|
||||||
|
var GW = S * 8, GH = S * 8
|
||||||
|
|
||||||
/*──── import our pieces + systems ───────────────────────────────────*/
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
var Grid = use('grid'); // your new ctor
|
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
var MovementSystem = use('movement').MovementSystem;
|
|
||||||
var startingPos = use('pieces').startingPosition;
|
|
||||||
var rules = use('rules');
|
|
||||||
|
|
||||||
/*──── build board ───────────────────────────────────────────────────*/
|
input.configure({
|
||||||
var grid = Grid(8, 8);
|
action_map: {
|
||||||
grid.width = 8; // (the ctor didn't store them)
|
select: ['mouse_button_left'],
|
||||||
grid.height = 8;
|
cancel: ['escape', 'mouse_button_right']
|
||||||
|
}
|
||||||
var mover = MovementSystem(grid, rules);
|
})
|
||||||
startingPos(grid);
|
|
||||||
|
// Build board
|
||||||
/*──── networking and game state ─────────────────────────────────────*/
|
var grid = Grid(8, 8)
|
||||||
var gameState = 'waiting'; // 'waiting', 'searching', 'server_waiting', 'connected'
|
grid.width = 8
|
||||||
var isServer = false;
|
grid.height = 8
|
||||||
var opponent = null;
|
var mover = MovementSystem(grid, rules)
|
||||||
var myColor = null; // 'white' or 'black'
|
startingPos(grid)
|
||||||
var isMyTurn = false;
|
|
||||||
|
// Board squares (shape2d)
|
||||||
function updateTitle() {
|
var light_color = {r: 0.93, g: 0.93, b: 0.85, a: 1}
|
||||||
var title = "Misty Chess - ";
|
var dark_color = {r: 0.45, g: 0.55, b: 0.35, a: 1}
|
||||||
|
var select_color = {r: 1, g: 0.84, b: 0, a: 1}
|
||||||
if (gameState == 'waiting') {
|
var valid_color = {r: 0.6, g: 0.8, b: 0.4, a: 1}
|
||||||
title += "Press S to start server or J to join";
|
|
||||||
} else if (gameState == 'searching') {
|
var board_shapes = []
|
||||||
title += "Searching for server...";
|
var bx = 0, by = 0
|
||||||
} else if (gameState == 'server_waiting') {
|
for (by = 0; by < 8; by++) {
|
||||||
title += "Waiting for player to join...";
|
var row = []
|
||||||
} else if (gameState == 'connected') {
|
for (bx = 0; bx < 8; bx++) {
|
||||||
if (myColor) {
|
var col = ((bx + by) & 1) ? dark_color : light_color
|
||||||
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
|
push(row, shape.rect({
|
||||||
} else {
|
pos: {x: bx * S + S / 2, y: by * S + S / 2},
|
||||||
title += mover.turn + " turn";
|
width: S, height: S,
|
||||||
}
|
fill: {r: col.r, g: col.g, b: col.b, a: col.a},
|
||||||
}
|
plane: 'game', layer: 0
|
||||||
|
}))
|
||||||
log.console(title)
|
}
|
||||||
}
|
push(board_shapes, row)
|
||||||
|
}
|
||||||
// Initialize title
|
|
||||||
updateTitle();
|
// Piece sprites — one per piece, keyed by piece object
|
||||||
|
var piece_sprites = {}
|
||||||
/*──── mouse → click-to-move ─────────────────────────────────────────*/
|
var piece_id = 0
|
||||||
var selectPos = null;
|
grid.each(function(p) {
|
||||||
var hoverPos = null;
|
piece_id++
|
||||||
var holdingPiece = false;
|
p._id = piece_id
|
||||||
|
piece_sprites[piece_id] = sprite_factory({
|
||||||
var opponentMousePos = null;
|
image: p.sprite,
|
||||||
var opponentHoldingPiece = false;
|
pos: {x: p.coord[0] * S + S / 2, y: p.coord[1] * S + S / 2},
|
||||||
var opponentSelectPos = null;
|
width: S, height: S,
|
||||||
|
anchor_x: 0.5, anchor_y: 0.5,
|
||||||
function handleMouseButtonDown(e) {
|
plane: 'game', layer: 1
|
||||||
if (e.which != 0) return;
|
})
|
||||||
|
})
|
||||||
// Don't allow piece selection unless we have an opponent
|
|
||||||
if (gameState != 'connected' || !opponent) return;
|
// Selection state
|
||||||
|
var selectPos = null
|
||||||
var mx = e.mouse.x;
|
var validMoves = []
|
||||||
var my = e.mouse.y;
|
|
||||||
|
// Mouse position in grid coords
|
||||||
var c = [floor(mx / 60), floor(my / 60)];
|
var hover_gx = -1, hover_gy = -1
|
||||||
if (!grid.inBounds(c)) return;
|
|
||||||
|
// Status text
|
||||||
var cell = grid.at(c);
|
var status_label = text2d({
|
||||||
if (length(cell) && cell[0].colour == mover.turn) {
|
text: "White's turn", pos: {x: 10, y: GH - 20},
|
||||||
selectPos = c;
|
plane: 'hud', size: 14, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
holdingPiece = true;
|
})
|
||||||
// Send pickup notification to opponent
|
|
||||||
if (opponent) {
|
function update_status() {
|
||||||
send(opponent, {
|
status_label.text = mover.turn + "'s turn"
|
||||||
type: 'piece_pickup',
|
}
|
||||||
pos: c
|
|
||||||
});
|
function reset_board_colors() {
|
||||||
}
|
var x = 0, y = 0
|
||||||
} else {
|
for (y = 0; y < 8; y++) {
|
||||||
selectPos = null;
|
for (x = 0; x < 8; x++) {
|
||||||
}
|
var col = ((x + y) & 1) ? dark_color : light_color
|
||||||
}
|
board_shapes[y][x].fill = {r: col.r, g: col.g, b: col.b, a: col.a}
|
||||||
|
}
|
||||||
function handleMouseButtonUp(e) {
|
}
|
||||||
if (e.which != 0 || !holdingPiece || !selectPos) return;
|
}
|
||||||
|
|
||||||
// Don't allow moves unless we have an opponent and it's our turn
|
function highlight_selection() {
|
||||||
if (gameState != 'connected' || !opponent || !isMyTurn) {
|
reset_board_colors()
|
||||||
holdingPiece = false;
|
if (!selectPos) return
|
||||||
return;
|
|
||||||
}
|
// Highlight selected square
|
||||||
|
board_shapes[selectPos[1]][selectPos[0]].fill = {
|
||||||
var mx = e.mouse.x;
|
r: select_color.r, g: select_color.g, b: select_color.b, a: select_color.a
|
||||||
var my = e.mouse.y;
|
}
|
||||||
|
|
||||||
var c = [floor(mx / 60), floor(my / 60)];
|
// Highlight valid moves
|
||||||
if (!grid.inBounds(c)) {
|
var i = 0
|
||||||
holdingPiece = false;
|
for (i = 0; i < length(validMoves); i++) {
|
||||||
return;
|
var m = validMoves[i]
|
||||||
}
|
board_shapes[m[1]][m[0]].fill = {
|
||||||
|
r: valid_color.r, g: valid_color.g, b: valid_color.b, a: valid_color.a
|
||||||
if (mover.tryMove(grid.at(selectPos)[0], c)) {
|
}
|
||||||
log.console("Made move from", selectPos, "to", c);
|
}
|
||||||
// Send move to opponent
|
}
|
||||||
log.console("Sending move to opponent:", opponent);
|
|
||||||
send(opponent, {
|
function compute_valid_moves(from) {
|
||||||
type: 'move',
|
validMoves = []
|
||||||
from: selectPos,
|
var piece = grid.at(from)[0]
|
||||||
to: c
|
if (!piece) return
|
||||||
});
|
|
||||||
isMyTurn = false; // It's now opponent's turn
|
var x = 0, y = 0, to = null, dest = null
|
||||||
log.console("Move sent, now opponent's turn");
|
for (y = 0; y < 8; y++) {
|
||||||
selectPos = null;
|
for (x = 0; x < 8; x++) {
|
||||||
updateTitle();
|
to = [x, y]
|
||||||
}
|
dest = grid.at(to)
|
||||||
|
if (length(dest) && dest[0].colour == piece.colour) continue
|
||||||
holdingPiece = false;
|
if (rules.canMove(piece, from, to, grid))
|
||||||
|
push(validMoves, to)
|
||||||
// Send piece drop notification to opponent
|
}
|
||||||
if (opponent) {
|
}
|
||||||
send(opponent, {
|
}
|
||||||
type: 'piece_drop'
|
|
||||||
});
|
function sync_piece_sprites() {
|
||||||
}
|
grid.each(function(p) {
|
||||||
}
|
var spr = piece_sprites[p._id]
|
||||||
|
if (!spr) return
|
||||||
function handleMouseMotion(e) {
|
if (p.captured) {
|
||||||
var mx = e.pos.x;
|
spr.visible = false
|
||||||
var my = e.pos.y;
|
} else {
|
||||||
|
spr.pos.x = p.coord[0] * S + S / 2
|
||||||
var c = [floor(mx / 60), floor(my / 60)];
|
spr.pos.y = p.coord[1] * S + S / 2
|
||||||
if (!grid.inBounds(c)) {
|
spr.visible = true
|
||||||
hoverPos = null;
|
}
|
||||||
return;
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
hoverPos = c;
|
// Input handler
|
||||||
|
var game_input = {
|
||||||
// Send mouse position to opponent in real-time
|
on_input: function(action, data) {
|
||||||
if (opponent && gameState == 'connected') {
|
if (!data.pressed) return
|
||||||
send(opponent, {
|
|
||||||
type: 'mouse_move',
|
if (action == 'cancel') {
|
||||||
pos: c,
|
selectPos = null
|
||||||
holding: holdingPiece,
|
validMoves = []
|
||||||
selectPos: selectPos
|
highlight_selection()
|
||||||
});
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (action == 'select' && hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
|
||||||
function handleKeyDown(e) {
|
var clicked = [hover_gx, hover_gy]
|
||||||
// S key - start server
|
var cell = grid.at(clicked)
|
||||||
if (e.scancode == 22 && gameState == 'waiting') { // S key
|
|
||||||
startServer();
|
if (selectPos) {
|
||||||
}
|
// Try to move
|
||||||
// J key - join server
|
var is_valid = false
|
||||||
else if (e.scancode == 13 && gameState == 'waiting') { // J key
|
var i = 0
|
||||||
joinServer();
|
for (i = 0; i < length(validMoves); i++) {
|
||||||
}
|
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
|
||||||
}
|
is_valid = true
|
||||||
|
break
|
||||||
/*──── drawing helpers ───────────────────────────────────────────────*/
|
}
|
||||||
/* ── constants ─────────────────────────────────────────────────── */
|
}
|
||||||
var S = 60; // square size in px
|
|
||||||
var light = [0.93,0.93,0.93,1];
|
if (is_valid) {
|
||||||
var dark = [0.25,0.25,0.25,1];
|
var src_piece = grid.at(selectPos)[0]
|
||||||
var allowedColor = [1.0, 0.84, 0.0, 1.0]; // Gold for allowed moves
|
if (src_piece && mover.tryMove(src_piece, clicked)) {
|
||||||
var myMouseColor = [0.0, 1.0, 0.0, 1.0]; // Green for my mouse
|
sync_piece_sprites()
|
||||||
var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
|
update_status()
|
||||||
|
}
|
||||||
/* ── draw one 8×8 chess board ──────────────────────────────────── */
|
selectPos = null
|
||||||
function drawBoard() {
|
validMoves = []
|
||||||
var y = 0;
|
} else if (length(cell) && cell[0].colour == mover.turn) {
|
||||||
var x = 0;
|
// Select different piece
|
||||||
var isMyHover = null;
|
selectPos = clicked
|
||||||
var isOpponentHover = null;
|
compute_valid_moves(selectPos)
|
||||||
var isValidMove = null;
|
} else {
|
||||||
var color = null;
|
selectPos = null
|
||||||
for (y = 0; y < 8; ++y)
|
validMoves = []
|
||||||
for (x = 0; x < 8; ++x) {
|
}
|
||||||
isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
|
} else {
|
||||||
isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
|
// Select piece
|
||||||
isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
|
if (length(cell) && cell[0].colour == mover.turn) {
|
||||||
|
selectPos = clicked
|
||||||
color = ((x+y)&1) ? dark : light;
|
compute_valid_moves(selectPos)
|
||||||
|
}
|
||||||
if (isValidMove) {
|
}
|
||||||
color = allowedColor; // Gold for allowed moves
|
highlight_selection()
|
||||||
} else if (isMyHover && !isOpponentHover) {
|
}
|
||||||
color = myMouseColor; // Green for my mouse
|
}
|
||||||
} else if (isOpponentHover) {
|
}
|
||||||
color = opponentMouseColor; // Red for opponent mouse
|
input.player1().possess(game_input)
|
||||||
}
|
|
||||||
|
var comp_config = {
|
||||||
draw2d.rectangle(
|
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
|
||||||
{ x: x*S, y: y*S, width: S, height: S },
|
planes: [
|
||||||
{ thickness: 0 },
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
||||||
{ color: color }
|
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
|
||||||
);
|
]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
core.start({
|
||||||
function isValidMoveForTurn(from, to) {
|
width: 640, height: 640, title: "Chess",
|
||||||
if (!grid.inBounds(to)) return false;
|
|
||||||
|
input: function(ev) {
|
||||||
var piece = grid.at(from)[0];
|
if (ev.type == 'mouse_motion') {
|
||||||
if (!piece) return false;
|
// Convert pixel coords to grid coords via camera
|
||||||
|
var wp = game_cam.window_to_world(ev.pos.x, ev.pos.y)
|
||||||
// Check if the destination has a piece of the same color
|
if (wp) {
|
||||||
var destCell = grid.at(to);
|
hover_gx = floor(wp.x / S)
|
||||||
if (length(destCell) && destCell[0].colour == piece.colour) {
|
hover_gy = floor(wp.y / S)
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return rules.canMove(piece, from, to, grid);
|
|
||||||
}
|
update: function(dt) {
|
||||||
|
},
|
||||||
/* ── draw every live piece ─────────────────────────────────────── */
|
|
||||||
function drawPieces() {
|
render: function() {
|
||||||
var piece = null;
|
return compositor.execute(compositor.compile(comp_config))
|
||||||
var r = null;
|
|
||||||
var opponentPiece = null;
|
|
||||||
|
|
||||||
grid.each(function (p) {
|
|
||||||
if (p.captured) return;
|
|
||||||
|
|
||||||
// Skip drawing the piece being held (by me or opponent)
|
|
||||||
if (holdingPiece && selectPos &&
|
|
||||||
p.coord[0] == selectPos[0] &&
|
|
||||||
p.coord[1] == selectPos[1]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip drawing the piece being held by opponent
|
|
||||||
if (opponentHoldingPiece && opponentSelectPos &&
|
|
||||||
p.coord[0] == opponentSelectPos[0] &&
|
|
||||||
p.coord[1] == opponentSelectPos[1]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pr = { x: p.coord[0]*S, y: p.coord[1]*S,
|
|
||||||
width:S, height:S };
|
|
||||||
|
|
||||||
draw2d.image(p.sprite, pr);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Draw the held piece at the mouse position if we're holding one
|
|
||||||
if (holdingPiece && selectPos && hoverPos) {
|
|
||||||
piece = grid.at(selectPos)[0];
|
|
||||||
if (piece) {
|
|
||||||
r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
|
|
||||||
width:S, height:S };
|
|
||||||
|
|
||||||
draw2d.image(piece.sprite, r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw opponent's held piece if they're dragging one
|
|
||||||
if (opponentHoldingPiece && opponentSelectPos && opponentMousePos) {
|
|
||||||
opponentPiece = grid.at(opponentSelectPos)[0];
|
|
||||||
if (opponentPiece) {
|
|
||||||
r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S,
|
|
||||||
width:S, height:S };
|
|
||||||
|
|
||||||
// Draw with slight transparency to show it's the opponent's piece
|
|
||||||
draw2d.image(opponentPiece.sprite, r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(dt)
|
|
||||||
{
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw()
|
|
||||||
{
|
|
||||||
draw2d.clear()
|
|
||||||
drawBoard()
|
|
||||||
drawPieces()
|
|
||||||
return draw2d.get_commands()
|
|
||||||
}
|
|
||||||
|
|
||||||
function startServer() {
|
|
||||||
gameState = 'server_waiting';
|
|
||||||
isServer = true;
|
|
||||||
myColor = 'white';
|
|
||||||
isMyTurn = true;
|
|
||||||
updateTitle();
|
|
||||||
|
|
||||||
$portal(e => {
|
|
||||||
log.console("Portal received contact message");
|
|
||||||
// Reply with this actor to establish connection
|
|
||||||
log.console ($self)
|
|
||||||
send(e, $self);
|
|
||||||
log.console("Portal replied with server actor");
|
|
||||||
}, 5678);
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinServer() {
|
|
||||||
gameState = 'searching';
|
|
||||||
updateTitle();
|
|
||||||
|
|
||||||
function contact_fn(actor, reason) {
|
|
||||||
log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
|
|
||||||
if (actor) {
|
|
||||||
opponent = actor;
|
|
||||||
log.console("Connection established with server, sending join request");
|
|
||||||
|
|
||||||
// Send a greet message with our actor object
|
|
||||||
send(opponent, {
|
|
||||||
type: 'greet',
|
|
||||||
client_actor: $self
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.console(`Failed to connect: ${reason}`);
|
|
||||||
gameState = 'waiting';
|
|
||||||
updateTitle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$contact(contact_fn, {
|
|
||||||
address: "192.168.0.149",
|
|
||||||
port: 5678
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$receiver(e => {
|
|
||||||
var fromCell = null;
|
|
||||||
var piece = null;
|
|
||||||
|
|
||||||
if (e.kind == 'update')
|
|
||||||
send(e, update(e.dt))
|
|
||||||
else if (e.kind == 'draw')
|
|
||||||
send(e, draw())
|
|
||||||
else if (e.type == 'game_start' || e.type == 'move' || e.type == 'greet')
|
|
||||||
log.console("Receiver got message:", e.type, e);
|
|
||||||
|
|
||||||
if (e.type == 'greet') {
|
|
||||||
log.console("Server received greet from client");
|
|
||||||
// Store the client's actor object for ongoing communication
|
|
||||||
opponent = e.client_actor;
|
|
||||||
log.console("Stored client actor:", opponent);
|
|
||||||
gameState = 'connected';
|
|
||||||
updateTitle();
|
|
||||||
|
|
||||||
// Send game_start to the client
|
|
||||||
log.console("Sending game_start to client");
|
|
||||||
send(opponent, {
|
|
||||||
type: 'game_start',
|
|
||||||
your_color: 'black'
|
|
||||||
});
|
|
||||||
log.console("game_start message sent to client");
|
|
||||||
}
|
|
||||||
else if (e.type == 'game_start') {
|
|
||||||
log.console("Game starting, I am:", e.your_color);
|
|
||||||
myColor = e.your_color;
|
|
||||||
isMyTurn = (myColor == 'white');
|
|
||||||
gameState = 'connected';
|
|
||||||
updateTitle();
|
|
||||||
} else if (e.type == 'move') {
|
|
||||||
log.console("Received move from opponent:", e.from, "to", e.to);
|
|
||||||
// Apply opponent's move
|
|
||||||
fromCell = grid.at(e.from);
|
|
||||||
if (length(fromCell)) {
|
|
||||||
piece = fromCell[0];
|
|
||||||
if (mover.tryMove(piece, e.to)) {
|
|
||||||
isMyTurn = true; // It's now our turn
|
|
||||||
updateTitle();
|
|
||||||
log.console("Applied opponent move, now my turn");
|
|
||||||
} else {
|
|
||||||
log.console("Failed to apply opponent move");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.console("No piece found at from position");
|
|
||||||
}
|
|
||||||
} else if (e.type == 'mouse_move') {
|
|
||||||
// Update opponent's mouse position
|
|
||||||
opponentMousePos = e.pos;
|
|
||||||
opponentHoldingPiece = e.holding;
|
|
||||||
opponentSelectPos = e.selectPos;
|
|
||||||
} else if (e.type == 'piece_pickup') {
|
|
||||||
// Opponent picked up a piece
|
|
||||||
opponentSelectPos = e.pos;
|
|
||||||
opponentHoldingPiece = true;
|
|
||||||
} else if (e.type == 'piece_drop') {
|
|
||||||
// Opponent dropped their piece
|
|
||||||
opponentHoldingPiece = false;
|
|
||||||
opponentSelectPos = null;
|
|
||||||
} else if (e.type == 'mouse_button_down') {
|
|
||||||
handleMouseButtonDown(e)
|
|
||||||
} else if (e.type == 'mouse_button_up') {
|
|
||||||
handleMouseButtonUp(e)
|
|
||||||
} else if (e.type == 'mouse_motion') {
|
|
||||||
handleMouseMotion(e)
|
|
||||||
} else if (e.type == 'key_down') {
|
|
||||||
handleKeyDown(e)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
// Chess game configuration for Moth framework
|
|
||||||
return {
|
return {
|
||||||
title: "Chess",
|
title: "Chess",
|
||||||
resolution: { width: 480, height: 480 },
|
width: 640,
|
||||||
internal_resolution: { width: 480, height: 480 },
|
height: 640
|
||||||
fps: 60,
|
}
|
||||||
clearColor: [22/255, 120/255, 194/255, 1],
|
|
||||||
mode: 'stretch' // No letterboxing for chess
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
return {
|
return {
|
||||||
title: "Pong",
|
title: "Pong",
|
||||||
width: 858,
|
width: 960,
|
||||||
height: 525
|
height: 540
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,127 @@
|
|||||||
// main.js
|
var core = use('core')
|
||||||
var draw = use('draw2d')
|
var camera = use('camera')
|
||||||
var config = use('config')
|
var compositor = use('compositor')
|
||||||
var color = use('color')
|
var input = use('input')
|
||||||
var random = use('random')
|
var shape = use('shape2d')
|
||||||
|
var text2d = use('text2d')
|
||||||
|
|
||||||
prosperon.camera.transform.pos = [0,0]
|
var GW = 480, GH = 270
|
||||||
|
var paddleW = 8, paddleH = 50, speed = 200
|
||||||
var paddleW = 10, paddleH = 80
|
var ballSize = 8
|
||||||
var p1 = {x: 30, y: config.height*0.5, speed: 300}
|
var bvx = 220, bvy = 150
|
||||||
var p2 = {x: config.width-30, y: config.height*0.5, speed: 300}
|
|
||||||
var ball = {x: 0, y: 0, vx: 220, vy: 150, size: 10}
|
|
||||||
var score1 = 0, score2 = 0
|
var score1 = 0, score2 = 0
|
||||||
|
|
||||||
function resetBall() {
|
// Cameras: game at pixel-art res, HUD at native
|
||||||
ball.x = config.width*0.5
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
ball.y = config.height*0.5
|
var hud_cam = camera.make({width: 960, height: 540, pos: {x: 480, y: 270}})
|
||||||
// give it a random vertical bounce
|
|
||||||
ball.vy = (random.random()<0.5 ? -1:1)*150
|
// Action mapping
|
||||||
// keep horizontal speed to the same magnitude
|
input.configure({
|
||||||
ball.vx = ball.vx>0 ? 220 : -220
|
action_map: {
|
||||||
|
p1_up: ['w'],
|
||||||
|
p1_down: ['s'],
|
||||||
|
p2_up: ['up'],
|
||||||
|
p2_down: ['down']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Retained shapes — game plane
|
||||||
|
var midline = shape.rect({
|
||||||
|
pos: {x: GW / 2, y: GH / 2}, width: 2, height: GH,
|
||||||
|
fill: {r: 0.3, g: 0.3, b: 0.3, a: 1}, plane: 'game'
|
||||||
|
})
|
||||||
|
|
||||||
|
var p1 = shape.rect({
|
||||||
|
pos: {x: 20, y: GH / 2}, width: paddleW, height: paddleH,
|
||||||
|
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game'
|
||||||
|
})
|
||||||
|
|
||||||
|
var p2 = shape.rect({
|
||||||
|
pos: {x: GW - 20, y: GH / 2}, width: paddleW, height: paddleH,
|
||||||
|
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game'
|
||||||
|
})
|
||||||
|
|
||||||
|
var ball = shape.rect({
|
||||||
|
pos: {x: GW / 2, y: GH / 2}, width: ballSize, height: ballSize,
|
||||||
|
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', groups: ['glow']
|
||||||
|
})
|
||||||
|
|
||||||
|
// HUD plane — score text
|
||||||
|
var score_label = text2d({
|
||||||
|
text: "0 0", pos: {x: 420, y: 490},
|
||||||
|
plane: 'hud', size: 32, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
function reset_ball() {
|
||||||
|
ball.pos.x = GW / 2
|
||||||
|
ball.pos.y = GH / 2
|
||||||
|
bvy = (bvy > 0 ? -1 : 1) * 150
|
||||||
|
bvx = bvx > 0 ? -220 : 220
|
||||||
}
|
}
|
||||||
|
|
||||||
resetBall()
|
// Compositor: game plane with bloom on ball, HUD overlay
|
||||||
|
var comp_config = {
|
||||||
var update = function(dt) {
|
clear: {r: 0, g: 0, b: 0, a: 1},
|
||||||
// Move paddles: positive Y is up, so W/↑ means p.y += speed
|
planes: [
|
||||||
if (input.keyboard.down('w')) p1.y += p1.speed*dt
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
||||||
if (input.keyboard.down('s')) p1.y -= p1.speed*dt
|
{name: 'hud', camera: hud_cam, resolution: {width: 960, height: 540}, presentation: 'stretch'}
|
||||||
|
],
|
||||||
// Paddle 2 movement (ArrowUp = up, ArrowDown = down)
|
group_effects: {
|
||||||
if (input.keyboard.down('i')) p2.y += p2.speed*dt
|
glow: {effects: [{type: 'bloom', threshold: 0.3, intensity: 2}]}
|
||||||
if (input.keyboard.down('k')) p2.y -= p2.speed*dt
|
}
|
||||||
|
|
||||||
// Clamp paddles to screen
|
|
||||||
if (p1.y < paddleH*0.5) p1.y = paddleH*0.5
|
|
||||||
if (p1.y > config.height - paddleH*0.5) p1.y = config.height - paddleH*0.5
|
|
||||||
if (p2.y < paddleH*0.5) p2.y = paddleH*0.5
|
|
||||||
if (p2.y > config.height - paddleH*0.5) p2.y = config.height - paddleH*0.5
|
|
||||||
|
|
||||||
// Move ball
|
|
||||||
ball.x += ball.vx*dt
|
|
||||||
ball.y += ball.vy*dt
|
|
||||||
|
|
||||||
// Bounce top/bottom
|
|
||||||
if (ball.y+ball.size*0.5>config.height || ball.y-ball.size*0.5<0) ball.vy = -ball.vy
|
|
||||||
|
|
||||||
// Check paddle collisions
|
|
||||||
// p1 bounding box
|
|
||||||
var left1 = p1.x - paddleW*0.5, right1 = p1.x + paddleW*0.5
|
|
||||||
var top1 = p1.y + paddleH*0.5, bottom1 = p1.y - paddleH*0.5
|
|
||||||
// p2 bounding box
|
|
||||||
var left2 = p2.x - paddleW*0.5, right2 = p2.x + paddleW*0.5
|
|
||||||
var top2 = p2.y + paddleH*0.5, bottom2 = p2.y - paddleH*0.5
|
|
||||||
|
|
||||||
// ball half-edges
|
|
||||||
var l = ball.x - ball.size*0.5, r = ball.x + ball.size*0.5
|
|
||||||
var b = ball.y - ball.size*0.5, t = ball.y + ball.size*0.5
|
|
||||||
|
|
||||||
// Collide with paddle 1?
|
|
||||||
if (r>left1 && l<right1 && t>bottom1 && b<top1)
|
|
||||||
ball.vx = abs(ball.vx)
|
|
||||||
// Collide with paddle 2?
|
|
||||||
if (r>left2 && l<right2 && t>bottom2 && b<top2)
|
|
||||||
ball.vx = -abs(ball.vx)
|
|
||||||
|
|
||||||
// Check left/right out-of-bounds
|
|
||||||
if (r<0) { score2++; resetBall() }
|
|
||||||
if (l>config.width) { score1++; resetBall() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hud = function() {
|
core.start({
|
||||||
// Clear screen black
|
width: 960, height: 540, title: "Pong",
|
||||||
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
|
||||||
|
|
||||||
// Draw paddles
|
update: function(dt) {
|
||||||
draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
|
var down = input.player1().down()
|
||||||
draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
|
|
||||||
|
|
||||||
// Draw ball
|
// Move paddles
|
||||||
draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, color.white)
|
if (down.p1_up) p1.pos.y += speed * dt
|
||||||
|
if (down.p1_down) p1.pos.y -= speed * dt
|
||||||
|
if (down.p2_up) p2.pos.y += speed * dt
|
||||||
|
if (down.p2_down) p2.pos.y -= speed * dt
|
||||||
|
|
||||||
// Simple score display
|
// Clamp paddles
|
||||||
var msg = score1 + " " + score2
|
var hh = paddleH / 2
|
||||||
draw.text(msg, {x:0, y:10, width:config.width, height:40}, null, 0, color.white, 0)
|
if (p1.pos.y < hh) p1.pos.y = hh
|
||||||
}
|
if (p1.pos.y > GH - hh) p1.pos.y = GH - hh
|
||||||
|
if (p2.pos.y < hh) p2.pos.y = hh
|
||||||
|
if (p2.pos.y > GH - hh) p2.pos.y = GH - hh
|
||||||
|
|
||||||
|
// Move ball
|
||||||
|
ball.pos.x += bvx * dt
|
||||||
|
ball.pos.y += bvy * dt
|
||||||
|
|
||||||
|
// Bounce top/bottom
|
||||||
|
var bs = ballSize / 2
|
||||||
|
if (ball.pos.y - bs < 0 || ball.pos.y + bs > GH) bvy = -bvy
|
||||||
|
|
||||||
|
// Paddle collisions
|
||||||
|
var bx = ball.pos.x, by = ball.pos.y
|
||||||
|
var pw = paddleW / 2, ph = paddleH / 2
|
||||||
|
if (bx - bs < p1.pos.x + pw && bx + bs > p1.pos.x - pw &&
|
||||||
|
by + bs > p1.pos.y - ph && by - bs < p1.pos.y + ph)
|
||||||
|
bvx = abs(bvx)
|
||||||
|
if (bx + bs > p2.pos.x - pw && bx - bs < p2.pos.x + pw &&
|
||||||
|
by + bs > p2.pos.y - ph && by - bs < p2.pos.y + ph)
|
||||||
|
bvx = -abs(bvx)
|
||||||
|
|
||||||
|
// Scoring
|
||||||
|
if (bx < 0) {
|
||||||
|
score2++
|
||||||
|
score_label.text = score1 + " " + score2
|
||||||
|
reset_ball()
|
||||||
|
}
|
||||||
|
if (bx > GW) {
|
||||||
|
score1++
|
||||||
|
score_label.text = score1 + " " + score2
|
||||||
|
reset_ball()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return compositor.execute(compositor.compile(comp_config))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,125 +1,192 @@
|
|||||||
// main.js
|
var core = use('core')
|
||||||
var draw = use('draw2d')
|
var camera = use('camera')
|
||||||
var render = use('render')
|
var compositor = use('compositor')
|
||||||
var graphics = use('graphics')
|
|
||||||
var input = use('input')
|
var input = use('input')
|
||||||
var config = use('config')
|
var shape = use('shape2d')
|
||||||
var color = use('color')
|
var text2d = use('text2d')
|
||||||
|
var tw = use('tween')
|
||||||
|
var ease = use('ease')
|
||||||
var random = use('random')
|
var random = use('random')
|
||||||
|
|
||||||
prosperon.camera.transform.pos = [0,0]
|
var GW = 600, GH = 600
|
||||||
|
|
||||||
var cellSize = 20
|
var cellSize = 20
|
||||||
var gridW = floor(config.width / cellSize)
|
var gridW = GW / cellSize
|
||||||
var gridH = floor(config.height / cellSize)
|
var gridH = GH / cellSize
|
||||||
|
|
||||||
var snake = null, direction = null, nextDirection = null, apple = null
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
var moveInterval = 0.1
|
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
var moveTimer = 0
|
|
||||||
var gameState = "playing"
|
|
||||||
|
|
||||||
function resetGame() {
|
// Action mapping with possession
|
||||||
var cx = floor(gridW / 2)
|
input.configure({
|
||||||
var cy = floor(gridH / 2)
|
action_map: {
|
||||||
snake = [
|
up: ['w', 'up'],
|
||||||
{x: cx, y: cy},
|
down: ['s', 'down'],
|
||||||
{x: cx-1, y: cy},
|
left: ['a', 'left'],
|
||||||
{x: cx-2, y: cy}
|
right: ['d', 'right'],
|
||||||
]
|
restart: ['space']
|
||||||
direction = {x:1, y:0}
|
}
|
||||||
nextDirection = {x:1, y:0}
|
})
|
||||||
spawnApple()
|
|
||||||
gameState = "playing"
|
// Game state
|
||||||
moveTimer = 0
|
var snake_shapes = []
|
||||||
|
var snake_pos = []
|
||||||
|
var dir = {x: 1, y: 0}
|
||||||
|
var next_dir = {x: 1, y: 0}
|
||||||
|
var move_timer = 0
|
||||||
|
var move_interval = 0.12
|
||||||
|
var state = 'playing'
|
||||||
|
var gameover_label = null
|
||||||
|
var score = 0
|
||||||
|
var score_label = null
|
||||||
|
|
||||||
|
// Input handler entity — possessed by player1
|
||||||
|
var game_input = {
|
||||||
|
on_input: function(action, data) {
|
||||||
|
if (!data.pressed) return
|
||||||
|
if (state == 'playing') {
|
||||||
|
if (action == 'up' && dir.y != -1) next_dir = {x: 0, y: 1}
|
||||||
|
if (action == 'down' && dir.y != 1) next_dir = {x: 0, y: -1}
|
||||||
|
if (action == 'left' && dir.x != 1) next_dir = {x: -1, y: 0}
|
||||||
|
if (action == 'right' && dir.x != -1) next_dir = {x: 1, y: 0}
|
||||||
|
}
|
||||||
|
if (action == 'restart' && state == 'gameover') reset_game()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.player1().possess(game_input)
|
||||||
|
|
||||||
|
function grid_to_world(gx, gy) {
|
||||||
|
return {x: gx * cellSize + cellSize / 2, y: gy * cellSize + cellSize / 2}
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnApple() {
|
// Apple
|
||||||
apple = {x:floor(random.random()*gridW), y:floor(random.random()*gridH)}
|
var apple_gx = 0, apple_gy = 0
|
||||||
// Re-spawn if apple lands on snake
|
var apple_shape = shape.rect({
|
||||||
var i = 0;
|
pos: {x: 0, y: 0}, width: cellSize - 2, height: cellSize - 2,
|
||||||
for (i=0; i<length(snake); i++)
|
fill: {r: 1, g: 0.2, b: 0.2, a: 1}, plane: 'game'
|
||||||
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function wrap(pos) {
|
function spawn_apple() {
|
||||||
if (pos.x < 0) pos.x = gridW - 1
|
apple_gx = floor(random.random() * gridW)
|
||||||
if (pos.x >= gridW) pos.x = 0
|
apple_gy = floor(random.random() * gridH)
|
||||||
if (pos.y < 0) pos.y = gridH - 1
|
var i = 0
|
||||||
if (pos.y >= gridH) pos.y = 0
|
for (i = 0; i < length(snake_pos); i++) {
|
||||||
}
|
if (snake_pos[i].x == apple_gx && snake_pos[i].y == apple_gy) {
|
||||||
|
spawn_apple()
|
||||||
resetGame()
|
|
||||||
|
|
||||||
var update = function(dt) {
|
|
||||||
if (gameState != "playing") return
|
|
||||||
moveTimer += dt
|
|
||||||
if (moveTimer < moveInterval) return
|
|
||||||
moveTimer -= moveInterval
|
|
||||||
|
|
||||||
// Update direction
|
|
||||||
direction = {x: nextDirection.x, y: nextDirection.y}
|
|
||||||
|
|
||||||
// New head
|
|
||||||
var head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y}
|
|
||||||
wrap(head)
|
|
||||||
|
|
||||||
// Check collision with body
|
|
||||||
var i = 0;
|
|
||||||
for (i=0; i<length(snake); i++) {
|
|
||||||
if (snake[i].x == head.x && snake[i].y == head.y) {
|
|
||||||
gameState = "gameover"
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var wp = grid_to_world(apple_gx, apple_gy)
|
||||||
// Place head
|
apple_shape.pos.x = wp.x
|
||||||
snake.unshift(head)
|
apple_shape.pos.y = wp.y
|
||||||
|
|
||||||
// Eat apple?
|
|
||||||
if (head.x == apple.x && head.y == apple.y) spawnApple()
|
|
||||||
else pop(snake)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hud = function() {
|
function reset_game() {
|
||||||
// Optional clear screen
|
var i = 0
|
||||||
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
for (i = 0; i < length(snake_shapes); i++)
|
||||||
|
snake_shapes[i].destroy()
|
||||||
|
snake_shapes = []
|
||||||
|
snake_pos = []
|
||||||
|
|
||||||
// Draw snake
|
var cx = floor(gridW / 2)
|
||||||
var i = 0;
|
var cy = floor(gridH / 2)
|
||||||
var s = null;
|
var wp = null
|
||||||
var msg = null;
|
for (i = 0; i < 3; i++) {
|
||||||
for (i=0; i<length(snake); i++) {
|
push(snake_pos, {x: cx - i, y: cy})
|
||||||
s = snake[i]
|
wp = grid_to_world(cx - i, cy)
|
||||||
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, color.green)
|
push(snake_shapes, shape.rect({
|
||||||
|
pos: {x: wp.x, y: wp.y}, width: cellSize - 2, height: cellSize - 2,
|
||||||
|
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw apple
|
dir = {x: 1, y: 0}
|
||||||
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
|
next_dir = {x: 1, y: 0}
|
||||||
|
state = 'playing'
|
||||||
if (gameState == "gameover") {
|
move_timer = 0
|
||||||
msg = "GAME OVER! Press SPACE to restart."
|
score = 0
|
||||||
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, null, 0, color.white)
|
if (score_label) score_label.text = "Score: 0"
|
||||||
}
|
if (gameover_label) { gameover_label.destroy(); gameover_label = null }
|
||||||
|
spawn_apple()
|
||||||
}
|
}
|
||||||
|
|
||||||
// No immediate reversal
|
// HUD
|
||||||
// "Up" means y=1, so going physically up on screen
|
score_label = text2d({
|
||||||
var inputs = {
|
text: "Score: 0", pos: {x: 10, y: GH - 30},
|
||||||
up: function() {
|
plane: 'hud', size: 16, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
if (direction.y != -1) nextDirection = {x:0,y:1}
|
})
|
||||||
},
|
|
||||||
down: function() {
|
reset_game()
|
||||||
if (direction.y != 1) nextDirection = {x:0,y:-1}
|
|
||||||
},
|
var comp_config = {
|
||||||
left: function() {
|
clear: {r: 0.08, g: 0.08, b: 0.1, a: 1},
|
||||||
if (direction.x != 1) nextDirection = {x:-1,y:0}
|
planes: [
|
||||||
},
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
||||||
right: function() {
|
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
|
||||||
if (direction.x != -1) nextDirection = {x:1,y:0}
|
]
|
||||||
},
|
|
||||||
space: function() {
|
|
||||||
if (gameState=="gameover") resetGame()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//input.player[0].control()
|
core.start({
|
||||||
|
width: 600, height: 600, title: "Snake",
|
||||||
|
|
||||||
|
update: function(dt) {
|
||||||
|
if (state != 'playing') return
|
||||||
|
move_timer += dt
|
||||||
|
if (move_timer < move_interval) return
|
||||||
|
move_timer -= move_interval
|
||||||
|
|
||||||
|
dir.x = next_dir.x
|
||||||
|
dir.y = next_dir.y
|
||||||
|
|
||||||
|
// New head position
|
||||||
|
var hx = snake_pos[0].x + dir.x
|
||||||
|
var hy = snake_pos[0].y + dir.y
|
||||||
|
|
||||||
|
// Wrap
|
||||||
|
if (hx < 0) hx = gridW - 1
|
||||||
|
if (hx >= gridW) hx = 0
|
||||||
|
if (hy < 0) hy = gridH - 1
|
||||||
|
if (hy >= gridH) hy = 0
|
||||||
|
|
||||||
|
// Self collision
|
||||||
|
var i = 0
|
||||||
|
for (i = 0; i < length(snake_pos); i++) {
|
||||||
|
if (snake_pos[i].x == hx && snake_pos[i].y == hy) {
|
||||||
|
state = 'gameover'
|
||||||
|
gameover_label = text2d({
|
||||||
|
text: "GAME OVER — SPACE to restart",
|
||||||
|
pos: {x: GW / 2 - 170, y: GH / 2},
|
||||||
|
plane: 'hud', size: 20, color: {r: 1, g: 0.3, b: 0.3, a: 1}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add head with tween from old head position
|
||||||
|
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
|
||||||
|
var new_wp = grid_to_world(hx, hy)
|
||||||
|
snake_pos.unshift({x: hx, y: hy})
|
||||||
|
var head = shape.rect({
|
||||||
|
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
|
||||||
|
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
||||||
|
})
|
||||||
|
// Smooth tween from old position to new position
|
||||||
|
tw.tween(head.pos).to({x: new_wp.x, y: new_wp.y}, move_interval).ease(ease.linear)
|
||||||
|
snake_shapes.unshift(head)
|
||||||
|
|
||||||
|
// Eat apple?
|
||||||
|
if (hx == apple_gx && hy == apple_gy) {
|
||||||
|
score++
|
||||||
|
score_label.text = "Score: " + score
|
||||||
|
spawn_apple()
|
||||||
|
} else {
|
||||||
|
// Remove tail
|
||||||
|
var tail = pop(snake_shapes)
|
||||||
|
tail.destroy()
|
||||||
|
pop(snake_pos)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return compositor.execute(compositor.compile(comp_config))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,123 +1,151 @@
|
|||||||
var draw = use('draw2d')
|
var core = use('core')
|
||||||
|
var camera = use('camera')
|
||||||
|
var compositor = use('compositor')
|
||||||
var input = use('input')
|
var input = use('input')
|
||||||
var config = use('config')
|
var shape = use('shape2d')
|
||||||
var color = use('color')
|
var text2d = use('text2d')
|
||||||
var random = use('random')
|
var random = use('random')
|
||||||
|
|
||||||
prosperon.camera.transform.pos = [0,0]
|
|
||||||
|
|
||||||
// Board constants
|
|
||||||
var COLS = 10, ROWS = 20
|
var COLS = 10, ROWS = 20
|
||||||
var TILE = 6 // each cell is 6x6
|
var TILE = 8
|
||||||
|
var GW = COLS * TILE + 80 // extra space for next piece + score
|
||||||
|
var GH = ROWS * TILE
|
||||||
|
|
||||||
// Board storage (2D), each cell is either 0 or a [r,g,b,a] color
|
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
var board = []
|
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
|
||||||
|
|
||||||
// Gravity timing
|
// Action mapping with possession for discrete inputs
|
||||||
var baseGravity = 0.8 // seconds between drops at level 0
|
input.configure({
|
||||||
var gravityTimer = 0
|
action_map: {
|
||||||
|
left: ['a', 'left'],
|
||||||
// Current piece & position
|
right: ['d', 'right'],
|
||||||
var piece = null
|
rotate: ['w', 'up'],
|
||||||
var pieceX = 0
|
soft_drop: ['s', 'down'],
|
||||||
var pieceY = 0
|
hard_drop: ['space']
|
||||||
|
}
|
||||||
// Next piece
|
})
|
||||||
var nextPiece = null
|
|
||||||
|
|
||||||
// Score/lines/level
|
|
||||||
var score = 0
|
|
||||||
var linesCleared = 0
|
|
||||||
var level = 0
|
|
||||||
|
|
||||||
// Rotation lock to prevent spinning with W
|
|
||||||
var rotateHeld = false
|
|
||||||
var gameOver = false
|
|
||||||
|
|
||||||
// Horizontal movement gating
|
|
||||||
var hMoveTimer = 0
|
|
||||||
var hDelay = 0.2 // delay before repeated moves begin
|
|
||||||
var hRepeat = 0.05 // time between repeated moves
|
|
||||||
var prevLeft = false
|
|
||||||
var prevRight = false
|
|
||||||
|
|
||||||
// Tetrimino definitions
|
// Tetrimino definitions
|
||||||
var SHAPES = {
|
var SHAPES = {
|
||||||
I: { color:[0,1,1,1], blocks:[[0,0],[1,0],[2,0],[3,0]] },
|
I: {color: {r: 0, g: 1, b: 1, a: 1}, blocks: [[0, 0], [1, 0], [2, 0], [3, 0]]},
|
||||||
O: { color:[1,1,0,1], blocks:[[0,0],[1,0],[0,1],[1,1]] },
|
O: {color: {r: 1, g: 1, b: 0, a: 1}, blocks: [[0, 0], [1, 0], [0, 1], [1, 1]]},
|
||||||
T: { color:[1,0,1,1], blocks:[[0,0],[1,0],[2,0],[1,1]] },
|
T: {color: {r: 1, g: 0, b: 1, a: 1}, blocks: [[0, 0], [1, 0], [2, 0], [1, 1]]},
|
||||||
S: { color:[0,1,0,1], blocks:[[1,0],[2,0],[0,1],[1,1]] },
|
S: {color: {r: 0, g: 1, b: 0, a: 1}, blocks: [[1, 0], [2, 0], [0, 1], [1, 1]]},
|
||||||
Z: { color:[1,0,0,1], blocks:[[0,0],[1,0],[1,1],[2,1]] },
|
Z: {color: {r: 1, g: 0, b: 0, a: 1}, blocks: [[0, 0], [1, 0], [1, 1], [2, 1]]},
|
||||||
J: { color:[0,0,1,1], blocks:[[0,0],[0,1],[1,1],[2,1]] },
|
J: {color: {r: 0, g: 0, b: 1, a: 1}, blocks: [[0, 0], [0, 1], [1, 1], [2, 1]]},
|
||||||
L: { color:[1,0.5,0,1], blocks:[[2,0],[0,1],[1,1],[2,1]] }
|
L: {color: {r: 1, g: 0.5, b: 0, a: 1}, blocks: [[2, 0], [0, 1], [1, 1], [2, 1]]}
|
||||||
}
|
}
|
||||||
var shapeKeys = array(SHAPES)
|
var shapeKeys = array(SHAPES)
|
||||||
|
|
||||||
// Initialize board with empty (0)
|
// Board: 2D array, null or color object
|
||||||
function initBoard() {
|
var board = []
|
||||||
|
// Board shapes: one shape per cell, toggled visible
|
||||||
|
var board_shapes = []
|
||||||
|
|
||||||
|
function init_board() {
|
||||||
board = []
|
board = []
|
||||||
var r = 0;
|
board_shapes = []
|
||||||
var row = null;
|
var r = 0, c = 0
|
||||||
var c = 0;
|
for (r = 0; r < ROWS; r++) {
|
||||||
for (r=0; r<ROWS; r++) {
|
var row = []
|
||||||
row = []
|
var srow = []
|
||||||
for (c=0; c<COLS; c++) push(row, 0)
|
for (c = 0; c < COLS; c++) {
|
||||||
|
push(row, null)
|
||||||
|
push(srow, shape.rect({
|
||||||
|
pos: {x: c * TILE + TILE / 2, y: r * TILE + TILE / 2},
|
||||||
|
width: TILE - 1, height: TILE - 1,
|
||||||
|
fill: {r: 0.5, g: 0.5, b: 0.5, a: 1},
|
||||||
|
plane: 'game', layer: 0, visible: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
push(board, row)
|
push(board, row)
|
||||||
|
push(board_shapes, srow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initBoard()
|
|
||||||
|
|
||||||
function randomShape() {
|
// Active piece
|
||||||
var key = shapeKeys[floor(random.random()*length(shapeKeys))]
|
var piece = null, pieceX = 0, pieceY = 0
|
||||||
// Make a copy of the shape's blocks
|
var piece_shapes = []
|
||||||
|
var nextPiece = null
|
||||||
|
|
||||||
|
// Score
|
||||||
|
var score = 0, linesCleared = 0, level = 0
|
||||||
|
var gameOver = false
|
||||||
|
|
||||||
|
// Gravity
|
||||||
|
var baseGravity = 0.8
|
||||||
|
var gravityTimer = 0
|
||||||
|
var softDropping = false
|
||||||
|
|
||||||
|
// Horizontal DAS
|
||||||
|
var hMoveTimer = 0
|
||||||
|
var hDelay = 0.18
|
||||||
|
var hRepeat = 0.05
|
||||||
|
var hDir = 0
|
||||||
|
var hHeld = false
|
||||||
|
|
||||||
|
// Rotate lock
|
||||||
|
var rotateHeld = false
|
||||||
|
|
||||||
|
var score_label = text2d({
|
||||||
|
text: "Score: 0", pos: {x: COLS * TILE + 5, y: GH - 15},
|
||||||
|
plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
})
|
||||||
|
var level_label = text2d({
|
||||||
|
text: "Level: 0", pos: {x: COLS * TILE + 5, y: GH - 30},
|
||||||
|
plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
})
|
||||||
|
var next_label = text2d({
|
||||||
|
text: "Next:", pos: {x: COLS * TILE + 5, y: 50},
|
||||||
|
plane: 'hud', size: 8, color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Next piece display shapes
|
||||||
|
var next_shapes = []
|
||||||
|
var ns = 0
|
||||||
|
for (ns = 0; ns < 4; ns++) {
|
||||||
|
push(next_shapes, shape.rect({
|
||||||
|
pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
|
||||||
|
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 2, visible: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function random_shape() {
|
||||||
|
var key = shapeKeys[floor(random.random() * length(shapeKeys))]
|
||||||
return {
|
return {
|
||||||
type: key,
|
type: key,
|
||||||
color: SHAPES[key].color,
|
color: SHAPES[key].color,
|
||||||
blocks: array(SHAPES[key].blocks, b => [b[0], b[1]])
|
blocks: array(SHAPES[key].blocks, function(b) { return [b[0], b[1]] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnPiece() {
|
|
||||||
piece = nextPiece || randomShape()
|
|
||||||
nextPiece = randomShape()
|
|
||||||
pieceX = 3
|
|
||||||
pieceY = 0
|
|
||||||
// Collision on spawn => game over
|
|
||||||
if (collides(pieceX, pieceY, piece.blocks)) gameOver = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function collides(px, py, blocks) {
|
function collides(px, py, blocks) {
|
||||||
var i = 0;
|
var i = 0, x = 0, y = 0
|
||||||
var x = 0;
|
for (i = 0; i < length(blocks); i++) {
|
||||||
var y = 0;
|
|
||||||
for (i=0; i<length(blocks); i++) {
|
|
||||||
x = px + blocks[i][0]
|
x = px + blocks[i][0]
|
||||||
y = py + blocks[i][1]
|
y = py + blocks[i][1]
|
||||||
if (x<0 || x>=COLS || y<0 || y>=ROWS) return true
|
if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return true
|
||||||
if (y>=0 && board[y][x]) return true
|
if (y >= 0 && board[y][x]) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock piece into board
|
function lock_piece() {
|
||||||
function lockPiece() {
|
var i = 0, x = 0, y = 0
|
||||||
var i = 0;
|
for (i = 0; i < length(piece.blocks); i++) {
|
||||||
var x = 0;
|
|
||||||
var y = 0;
|
|
||||||
for (i=0; i<length(piece.blocks); i++) {
|
|
||||||
x = pieceX + piece.blocks[i][0]
|
x = pieceX + piece.blocks[i][0]
|
||||||
y = pieceY + piece.blocks[i][1]
|
y = pieceY + piece.blocks[i][1]
|
||||||
if (y>=0) board[y][x] = piece.color
|
if (y >= 0 && y < ROWS && x >= 0 && x < COLS) {
|
||||||
|
board[y][x] = piece.color
|
||||||
|
board_shapes[y][x].fill = piece.color
|
||||||
|
board_shapes[y][x].visible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate 90° clockwise
|
function rotate_blocks(blocks) {
|
||||||
function rotate(blocks) {
|
var i = 0, x = 0, y = 0
|
||||||
// (x,y) => (y,-x)
|
for (i = 0; i < length(blocks); i++) {
|
||||||
var i = 0;
|
|
||||||
var x = 0;
|
|
||||||
var y = 0;
|
|
||||||
for (i=0; i<length(blocks); i++) {
|
|
||||||
x = blocks[i][0]
|
x = blocks[i][0]
|
||||||
y = blocks[i][1]
|
y = blocks[i][1]
|
||||||
blocks[i][0] = y
|
blocks[i][0] = y
|
||||||
@@ -125,173 +153,190 @@ function rotate(blocks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLines() {
|
function clear_lines() {
|
||||||
var lines = 0
|
var lines = 0
|
||||||
var r = ROWS-1;
|
var r = ROWS - 1, c = 0, full = false, newRow = null
|
||||||
var newRow = null;
|
while (r >= 0) {
|
||||||
var c = 0;
|
full = true
|
||||||
for (r=ROWS-1; r>=0;) {
|
for (c = 0; c < COLS; c++) {
|
||||||
if (every(board[r], cell => cell)) {
|
if (!board[r][c]) { full = false; break }
|
||||||
|
}
|
||||||
|
if (full) {
|
||||||
lines++
|
lines++
|
||||||
// remove row
|
// Shift rows down
|
||||||
board = array(array(board, 0, r), array(board, r+1))
|
var sr = r
|
||||||
// add empty row on top
|
while (sr > 0) {
|
||||||
newRow = []
|
for (c = 0; c < COLS; c++) {
|
||||||
for (c=0; c<COLS; c++) push(newRow, 0)
|
board[sr][c] = board[sr - 1][c]
|
||||||
board.unshift(newRow)
|
if (board[sr][c]) {
|
||||||
|
board_shapes[sr][c].fill = board[sr][c]
|
||||||
|
board_shapes[sr][c].visible = true
|
||||||
|
} else {
|
||||||
|
board_shapes[sr][c].visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sr--
|
||||||
|
}
|
||||||
|
for (c = 0; c < COLS; c++) {
|
||||||
|
board[0][c] = null
|
||||||
|
board_shapes[0][c].visible = false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
r--
|
r--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Score
|
if (lines == 1) score += 100
|
||||||
if (lines==1) score += 100
|
else if (lines == 2) score += 300
|
||||||
else if (lines==2) score += 300
|
else if (lines == 3) score += 500
|
||||||
else if (lines==3) score += 500
|
else if (lines == 4) score += 800
|
||||||
else if (lines==4) score += 800
|
|
||||||
linesCleared += lines
|
linesCleared += lines
|
||||||
level = floor(linesCleared/10)
|
level = floor(linesCleared / 10)
|
||||||
|
score_label.text = "Score: " + score
|
||||||
|
level_label.text = "Level: " + level
|
||||||
}
|
}
|
||||||
|
|
||||||
function placePiece() {
|
function update_piece_shapes() {
|
||||||
lockPiece()
|
var i = 0
|
||||||
clearLines()
|
for (i = 0; i < 4; i++) {
|
||||||
spawnPiece()
|
var bx = pieceX + piece.blocks[i][0]
|
||||||
|
var by = pieceY + piece.blocks[i][1]
|
||||||
|
piece_shapes[i].pos.x = bx * TILE + TILE / 2
|
||||||
|
piece_shapes[i].pos.y = by * TILE + TILE / 2
|
||||||
|
piece_shapes[i].fill = piece.color
|
||||||
|
piece_shapes[i].visible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hard drop
|
function update_next_display() {
|
||||||
function hardDrop() {
|
var i = 0
|
||||||
while(!collides(pieceX, pieceY+1, piece.blocks)) pieceY++
|
for (i = 0; i < 4; i++) {
|
||||||
placePiece()
|
var bx = nextPiece.blocks[i][0]
|
||||||
|
var by = nextPiece.blocks[i][1]
|
||||||
|
next_shapes[i].pos.x = (COLS + 1) * TILE + bx * TILE + TILE / 2
|
||||||
|
next_shapes[i].pos.y = 20 + by * TILE + TILE / 2
|
||||||
|
next_shapes[i].fill = nextPiece.color
|
||||||
|
next_shapes[i].visible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnPiece()
|
function spawn_piece() {
|
||||||
|
piece = nextPiece || random_shape()
|
||||||
var update = function(dt) {
|
nextPiece = random_shape()
|
||||||
if (gameOver) return
|
pieceX = 3
|
||||||
|
pieceY = 0
|
||||||
// ======= Horizontal Movement Gate =======
|
if (collides(pieceX, pieceY, piece.blocks)) {
|
||||||
var leftPressed = input.keyboard.down('a')
|
gameOver = true
|
||||||
var rightPressed = input.keyboard.down('d')
|
var i = 0
|
||||||
var horizontalMove = 0
|
for (i = 0; i < 4; i++) piece_shapes[i].visible = false
|
||||||
|
return
|
||||||
// If user just pressed A, move once & start gating
|
|
||||||
if (leftPressed && !prevLeft) {
|
|
||||||
horizontalMove = -1
|
|
||||||
hMoveTimer = hDelay
|
|
||||||
}
|
|
||||||
// If user is holding A & the timer is up, move again, then reset timer to repeat
|
|
||||||
else if (leftPressed && hMoveTimer <= 0) {
|
|
||||||
horizontalMove = -1
|
|
||||||
hMoveTimer = hRepeat
|
|
||||||
}
|
}
|
||||||
|
update_piece_shapes()
|
||||||
|
update_next_display()
|
||||||
|
}
|
||||||
|
|
||||||
// Same logic for D
|
function place_piece() {
|
||||||
if (rightPressed && !prevRight) {
|
lock_piece()
|
||||||
horizontalMove = 1
|
clear_lines()
|
||||||
hMoveTimer = hDelay
|
spawn_piece()
|
||||||
} else if (rightPressed && hMoveTimer <= 0) {
|
}
|
||||||
horizontalMove = 1
|
|
||||||
hMoveTimer = hRepeat
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move horizontally if it doesn't collide
|
// Create 4 shapes for active piece (layer 1 = on top of board)
|
||||||
if (horizontalMove < 0 && !collides(pieceX-1, pieceY, piece.blocks)) pieceX--
|
var pi = 0
|
||||||
else if (horizontalMove > 0 && !collides(pieceX+1, pieceY, piece.blocks)) pieceX++
|
for (pi = 0; pi < 4; pi++) {
|
||||||
|
push(piece_shapes, shape.rect({
|
||||||
|
pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
|
||||||
|
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 1, visible: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// If neither A nor D is pressed, reset the timer so next press is immediate
|
init_board()
|
||||||
if (!leftPressed && !rightPressed) {
|
spawn_piece()
|
||||||
hMoveTimer = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrement horizontal timer
|
// Compositor
|
||||||
hMoveTimer -= dt
|
var comp_config = {
|
||||||
prevLeft = leftPressed
|
clear: {r: 0, g: 0, b: 0, a: 1},
|
||||||
prevRight = rightPressed
|
planes: [
|
||||||
// ======= End Horizontal Movement Gate =======
|
{name: 'game', camera: game_cam, resolution: {width: GW, height: GH}, presentation: 'letterbox'},
|
||||||
|
{name: 'hud', camera: hud_cam, resolution: {width: GW, height: GH}, presentation: 'stretch'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// Rotate with W (once per press, no spinning)
|
core.start({
|
||||||
var test = null;
|
width: 640, height: 480, title: "Tetris",
|
||||||
if (input.keyboard.down('w')) {
|
|
||||||
if (!rotateHeld) {
|
|
||||||
rotateHeld = true
|
|
||||||
test = array(piece.blocks, b => [b[0], b[1]])
|
|
||||||
rotate(test)
|
|
||||||
if (!collides(pieceX, pieceY, test)) piece.blocks = test
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rotateHeld = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Soft drop if S is held (accelerates gravity)
|
update: function(dt) {
|
||||||
var fallSpeed = input.keyboard.down('s') ? 10 : 1
|
if (gameOver) return
|
||||||
|
|
||||||
// Gravity
|
var down = input.player1().down()
|
||||||
gravityTimer += dt * fallSpeed
|
var test = null
|
||||||
var dropInterval = max(0.1, baseGravity - level*0.05)
|
|
||||||
if (gravityTimer >= dropInterval) {
|
// Horizontal movement with DAS
|
||||||
gravityTimer = 0
|
var wantLeft = down.left
|
||||||
if (!collides(pieceX, pieceY+1, piece.blocks)) {
|
var wantRight = down.right
|
||||||
pieceY++
|
var newDir = 0
|
||||||
|
if (wantLeft && !wantRight) newDir = -1
|
||||||
|
else if (wantRight && !wantLeft) newDir = 1
|
||||||
|
|
||||||
|
if (newDir != 0) {
|
||||||
|
if (newDir != hDir || !hHeld) {
|
||||||
|
// First press
|
||||||
|
if (!collides(pieceX + newDir, pieceY, piece.blocks)) pieceX += newDir
|
||||||
|
hMoveTimer = hDelay
|
||||||
|
hDir = newDir
|
||||||
|
hHeld = true
|
||||||
|
} else {
|
||||||
|
hMoveTimer -= dt
|
||||||
|
if (hMoveTimer <= 0) {
|
||||||
|
if (!collides(pieceX + newDir, pieceY, piece.blocks)) pieceX += newDir
|
||||||
|
hMoveTimer = hRepeat
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
placePiece()
|
hDir = 0
|
||||||
|
hHeld = false
|
||||||
|
hMoveTimer = 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Hard drop if space is held
|
// Rotate (once per press)
|
||||||
if (input.keyboard.down('space')) {
|
if (down.rotate) {
|
||||||
// hardDrop()
|
if (!rotateHeld) {
|
||||||
}
|
rotateHeld = true
|
||||||
}
|
test = array(piece.blocks, function(b) { return [b[0], b[1]] })
|
||||||
|
rotate_blocks(test)
|
||||||
var hud = function() {
|
if (!collides(pieceX, pieceY, test)) piece.blocks = test
|
||||||
// Clear screen
|
}
|
||||||
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
} else {
|
||||||
|
rotateHeld = false
|
||||||
// Draw board
|
|
||||||
var r = 0;
|
|
||||||
var c = 0;
|
|
||||||
var cell = null;
|
|
||||||
var i = 0;
|
|
||||||
var x = 0;
|
|
||||||
var y = 0;
|
|
||||||
var nx = 0;
|
|
||||||
var ny = 0;
|
|
||||||
var dx = 0;
|
|
||||||
var dy = 0;
|
|
||||||
for (r=0; r<ROWS; r++) {
|
|
||||||
for (c=0; c<COLS; c++) {
|
|
||||||
cell = board[r][c]
|
|
||||||
if (!cell) continue
|
|
||||||
draw.rectangle({x:c*TILE, y:(ROWS-1-r)*TILE, width:TILE, height:TILE}, cell)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Draw falling piece
|
// Soft drop
|
||||||
if (!gameOver && piece) {
|
softDropping = down.soft_drop
|
||||||
for (i=0; i<length(piece.blocks); i++) {
|
|
||||||
x = pieceX + piece.blocks[i][0]
|
// Hard drop (once per press)
|
||||||
y = pieceY + piece.blocks[i][1]
|
if (down.hard_drop) {
|
||||||
draw.rectangle({x:x*TILE, y:(ROWS-1-y)*TILE, width:TILE, height:TILE}, piece.color)
|
while (!collides(pieceX, pieceY + 1, piece.blocks)) pieceY++
|
||||||
|
place_piece()
|
||||||
|
update_piece_shapes()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Next piece window
|
// Gravity
|
||||||
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
|
var fallSpeed = softDropping ? 10 : 1
|
||||||
if (nextPiece) {
|
gravityTimer += dt * fallSpeed
|
||||||
for (i=0; i<length(nextPiece.blocks); i++) {
|
var dropInterval = max(0.05, baseGravity - level * 0.05)
|
||||||
nx = nextPiece.blocks[i][0]
|
if (gravityTimer >= dropInterval) {
|
||||||
ny = nextPiece.blocks[i][1]
|
gravityTimer = 0
|
||||||
dx = 12 + nx
|
if (!collides(pieceX, pieceY + 1, piece.blocks)) {
|
||||||
dy = 16 - ny
|
pieceY++
|
||||||
draw.rectangle({x:dx*TILE, y:(ROWS-1-dy)*TILE, width:TILE, height:TILE}, nextPiece.color)
|
} else {
|
||||||
|
place_piece()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Score & Level
|
update_piece_shapes()
|
||||||
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
|
},
|
||||||
draw.text(info, {x:70, y:30, width:90, height:50}, null, 0, color.white)
|
|
||||||
|
|
||||||
if (gameOver) {
|
render: function() {
|
||||||
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
|
return compositor.execute(compositor.compile(comp_config))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|||||||
28
film2d.cm
28
film2d.cm
@@ -523,4 +523,32 @@ function _mat_eq(a, b) {
|
|||||||
return a.blend == b.blend && a.sampler == b.sampler
|
return a.blend == b.blend && a.sampler == b.sampler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
film2d.snapshot = function() {
|
||||||
|
var result = []
|
||||||
|
var ids = array(registry)
|
||||||
|
var i = 0
|
||||||
|
var d = null
|
||||||
|
for (i = 0; i < length(ids); i++) {
|
||||||
|
d = registry[ids[i]]
|
||||||
|
if (!d) continue
|
||||||
|
push(result, {
|
||||||
|
id: ids[i],
|
||||||
|
type: d.type,
|
||||||
|
pos: d.pos ? {x: d.pos.x, y: d.pos.y} : null,
|
||||||
|
width: d.width,
|
||||||
|
height: d.height,
|
||||||
|
plane: d.plane || 'default',
|
||||||
|
layer: d.layer || 0,
|
||||||
|
groups: d.groups || [],
|
||||||
|
visible: d.visible != false,
|
||||||
|
fill: d.fill,
|
||||||
|
color: d.color,
|
||||||
|
opacity: d.opacity,
|
||||||
|
image: d.image ? (is_text(d.image) ? d.image : '(texture)') : null,
|
||||||
|
text: d.text
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
return film2d
|
return film2d
|
||||||
21
graphics.cm
21
graphics.cm
@@ -466,4 +466,25 @@ graphics.queue_sprite_mesh = function(queue) {
|
|||||||
return [mesh.pos, mesh.uv, mesh.color, mesh.indices]
|
return [mesh.pos, mesh.uv, mesh.color, mesh.indices]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graphics.snapshot = function() {
|
||||||
|
var images = []
|
||||||
|
var fonts = []
|
||||||
|
arrfor(array(cache), function(k) {
|
||||||
|
var entry = cache[k]
|
||||||
|
var info = {name: k}
|
||||||
|
if (entry && entry.width) {
|
||||||
|
info.width = entry.width
|
||||||
|
info.height = entry.height
|
||||||
|
} else if (entry && entry.frames) {
|
||||||
|
info.frame_count = length(entry.frames)
|
||||||
|
info.type = 'animation'
|
||||||
|
}
|
||||||
|
push(images, info)
|
||||||
|
})
|
||||||
|
arrfor(array(fontcache), function(k) {
|
||||||
|
push(fonts, k)
|
||||||
|
})
|
||||||
|
return {images: images, fonts: fonts}
|
||||||
|
}
|
||||||
|
|
||||||
return graphics
|
return graphics
|
||||||
|
|||||||
31
input.cm
31
input.cm
@@ -260,16 +260,43 @@ function user(index) {
|
|||||||
return _users[index]
|
return _users[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapshot() {
|
||||||
|
var users = []
|
||||||
|
var i = 0
|
||||||
|
var u = null
|
||||||
|
var target = null
|
||||||
|
for (i = 0; i < length(_users); i++) {
|
||||||
|
u = _users[i]
|
||||||
|
target = u.target()
|
||||||
|
push(users, {
|
||||||
|
index: u.index,
|
||||||
|
device_kind: u.device_kind(),
|
||||||
|
active_device: u.active_device,
|
||||||
|
paired_devices: array(u.paired_devices),
|
||||||
|
down: u.down(),
|
||||||
|
control_stack_depth: length(u.control_stack),
|
||||||
|
target: target ? (target.name || '(entity)') : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
max_users: _config.max_users,
|
||||||
|
pairing: _config.pairing,
|
||||||
|
action_map: _config.action_map,
|
||||||
|
users: users
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
configure: configure,
|
configure: configure,
|
||||||
ingest: ingest,
|
ingest: ingest,
|
||||||
user: user,
|
user: user,
|
||||||
|
snapshot: snapshot,
|
||||||
|
|
||||||
player1() { return _users[0] },
|
player1() { return _users[0] },
|
||||||
player2() { return _users[1] },
|
player2() { return _users[1] },
|
||||||
player3() { return _users[2] },
|
player3() { return _users[2] },
|
||||||
player4() { return _users[3] },
|
player4() { return _users[3] },
|
||||||
|
|
||||||
// Re-export for convenience
|
// Re-export for convenience
|
||||||
devices: devices,
|
devices: devices,
|
||||||
backend: backend
|
backend: backend
|
||||||
|
|||||||
45
snapshot.cm
Normal file
45
snapshot.cm
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
var film2d = use('film2d')
|
||||||
|
var world = use('world')
|
||||||
|
var compositor = use('compositor')
|
||||||
|
var input = use('input')
|
||||||
|
var tween = use('tween')
|
||||||
|
var graphics = use('graphics')
|
||||||
|
|
||||||
|
var snapshot = {}
|
||||||
|
|
||||||
|
snapshot.drawables = function() {
|
||||||
|
return film2d.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.entities = function() {
|
||||||
|
return world.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.compositor = function() {
|
||||||
|
return compositor.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.input = function() {
|
||||||
|
return input.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.tweens = function() {
|
||||||
|
return tween.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.assets = function() {
|
||||||
|
return graphics.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.all = function() {
|
||||||
|
return {
|
||||||
|
drawables: film2d.snapshot(),
|
||||||
|
entities: world.snapshot(),
|
||||||
|
compositor: compositor.snapshot(),
|
||||||
|
input: input.snapshot(),
|
||||||
|
tweens: tween.snapshot(),
|
||||||
|
assets: graphics.snapshot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot
|
||||||
17
tween.cm
17
tween.cm
@@ -254,11 +254,26 @@ $delay(() => {
|
|||||||
}
|
}
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
|
function snapshot() {
|
||||||
|
var result = []
|
||||||
|
arrfor(TweenEngine.tweens, function(tw) {
|
||||||
|
push(result, {
|
||||||
|
startVals: tw.startVals,
|
||||||
|
endVals: tw.endVals,
|
||||||
|
duration: tw.duration,
|
||||||
|
startTime: tw.startTime,
|
||||||
|
easing: tw.easing ? (tw.easing.name || 'unknown') : 'none'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return {active_count: length(TweenEngine.tweens), tweens: result}
|
||||||
|
}
|
||||||
|
|
||||||
var tween_ret = {
|
var tween_ret = {
|
||||||
init,
|
init,
|
||||||
Timeline,
|
Timeline,
|
||||||
TweenEngine,
|
TweenEngine,
|
||||||
tween
|
tween,
|
||||||
|
snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
return tween_ret
|
return tween_ret
|
||||||
|
|||||||
62
world.cm
62
world.cm
@@ -16,4 +16,66 @@ world.destroy_entity = function(entity) {
|
|||||||
delete entities[entity]
|
delete entities[entity]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
world.update = function(dt) {
|
||||||
|
arrfor(array(entities), function(e) {
|
||||||
|
if (is_function(e.update))
|
||||||
|
e.update(dt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
world.each = function(fn) {
|
||||||
|
arrfor(array(entities), fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
world.query = function(filter_fn) {
|
||||||
|
return filter(array(entities), filter_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
world.find = function(predicate) {
|
||||||
|
var keys = array(entities)
|
||||||
|
var i = 0
|
||||||
|
for (i = 0; i < length(keys); i++) {
|
||||||
|
if (predicate(keys[i]))
|
||||||
|
return keys[i]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
world.clear = function() {
|
||||||
|
arrfor(array(entities), function(e) {
|
||||||
|
if (is_function(e.on_destroy))
|
||||||
|
e.on_destroy()
|
||||||
|
})
|
||||||
|
entities = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.load_level = function(json_array) {
|
||||||
|
var i = 0
|
||||||
|
var entry = null
|
||||||
|
var proto = null
|
||||||
|
var entity = null
|
||||||
|
for (i = 0; i < length(json_array); i++) {
|
||||||
|
entry = json_array[i]
|
||||||
|
proto = entry.script ? use(entry.script) : {}
|
||||||
|
entity = world.add_entity(proto, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.count = function() {
|
||||||
|
return length(array(entities))
|
||||||
|
}
|
||||||
|
|
||||||
|
world.snapshot = function() {
|
||||||
|
var result = []
|
||||||
|
arrfor(array(entities), function(e) {
|
||||||
|
var snap = {}
|
||||||
|
arrfor(array(e), function(k) {
|
||||||
|
if (!is_function(e[k]))
|
||||||
|
snap[k] = e[k]
|
||||||
|
})
|
||||||
|
push(result, snap)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
return world
|
return world
|
||||||
|
|||||||
Reference in New Issue
Block a user