From e4c6d07a4ab4424de474a270e57d8cdb9eeb26b2 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Sat, 20 Dec 2014 13:39:40 -0800 Subject: [PATCH] Added keyboard shortcuts to move, resize, minor-rotate, and toggle collision for Thangs in the level editor. Fixed some issues with stretchy Thangs and collision shapes not updating. Fixed #1699. Fixed #57. Colored collision overlays according to collision categories. --- app/lib/surface/Lank.coffee | 16 ++-- app/lib/surface/LayerAdapter.coffee | 1 + app/lib/surface/Mark.coffee | 16 +++- .../editor/level/thangs/ThangsTabView.coffee | 82 +++++++++++++++---- server_setup.coffee | 2 +- 5 files changed, 88 insertions(+), 29 deletions(-) diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee index b5d6cd749..35932e272 100644 --- a/app/lib/surface/Lank.coffee +++ b/app/lib/surface/Lank.coffee @@ -287,21 +287,21 @@ module.exports = Lank = class Lank extends CocoClass # Let the pending flags know we're here (but not this call stack, they need to delete themselves, and we may be iterating sprites). _.defer => Backbone.Mediator.publish 'surface:flag-appeared', sprite: @ - updateScale: -> + updateScale: (force) -> return unless @sprite - if @thangType.get('matchWorldDimensions') and @thang - if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight + if @thangType.get('matchWorldDimensions') and @thang and @options.camera + if force or @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight or @thang.rotation isnt @lastThangRotation bounds = @sprite.getBounds() return unless bounds - @sprite.scaleX = @thang.width * Camera.PPM / bounds.width - @sprite.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height - @sprite.regX = bounds.width / 2 - @sprite.regY = bounds.height / 2 + @sprite.scaleX = @thang.width * Camera.PPM / bounds.width * (@options.camera.y2x + (1 - @options.camera.y2x) * Math.abs Math.cos @thang.rotation) + @sprite.scaleY = @thang.height * Camera.PPM / bounds.height * (@options.camera.y2x + (1 - @options.camera.y2x) * Math.abs Math.sin @thang.rotation) + @sprite.regX = bounds.width * 3 / 4 # Why not / 2? I don't know. + @sprite.regY = bounds.height * 3 / 4 # Why not / 2? I don't know. unless @thang.spriteName is 'Beam' @sprite.scaleX *= @thangType.get('scale') ? 1 @sprite.scaleY *= @thangType.get('scale') ? 1 - [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height] + [@lastThangWidth, @lastThangHeight, @lastThangRotation] = [@thang.width, @thang.height, @thang.rotation] return scaleX = scaleY = 1 diff --git a/app/lib/surface/LayerAdapter.coffee b/app/lib/surface/LayerAdapter.coffee index da2d88d3b..4132f9c0f 100644 --- a/app/lib/surface/LayerAdapter.coffee +++ b/app/lib/surface/LayerAdapter.coffee @@ -500,6 +500,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass lank.setSprite(sprite) lank.update(true) @container.addChild(sprite) + lank.updateScale true if lank.thangType.get 'matchWorldDimensions' # Otherwise it's at the wrong scale for some reason. renderGroupingKey: (thangType, grouping, colorConfig) -> key = thangType.get('slug') diff --git a/app/lib/surface/Mark.coffee b/app/lib/surface/Mark.coffee index 39b4b4c64..b45e3d994 100644 --- a/app/lib/surface/Mark.coffee +++ b/app/lib/surface/Mark.coffee @@ -216,11 +216,19 @@ module.exports = class Mark extends CocoClass buildDebug: -> shapeName = if @lank.thang.shape in ['ellipsoid', 'disc'] then 'ellipse' else 'rect' - key = "#{shapeName}-debug" + key = "#{shapeName}-debug-#{@lank.thang.collisionCategory}" DEBUG_SIZE = 10 unless key in @layer.spriteSheet.getAnimations() shape = new createjs.Shape() - shape.graphics.beginFill 'rgba(171,205,239,0.5)' + debugColor = { + none: 'rgba(224,255,239,0.25)' + ground: 'rgba(239,171,205,0.5)' + air: 'rgba(131,205,255,0.5)' + ground_and_air: 'rgba(2391,140,239,0.5)' + obstacles: 'rgba(88,88,88,0.5)' + dead: 'rgba(89,171,100,0.25)' + }[@lank.thang.collisionCategory] or 'rgba(171,205,239,0.5)' + shape.graphics.beginFill debugColor bounds = [-DEBUG_SIZE / 2, -DEBUG_SIZE / 2, DEBUG_SIZE, DEBUG_SIZE] if shapeName is 'ellipse' shape.graphics.drawEllipse bounds... @@ -232,9 +240,11 @@ module.exports = class Mark extends CocoClass @sprite = new createjs.Sprite(@layer.spriteSheet) @sprite.gotoAndStop(key) PX = 3 - [w, h] = [Math.max(PX, @lank.thang.width * Camera.PPM), Math.max(PX, @lank.thang.height * Camera.PPM) * @camera.y2x] # TODO: doesn't work with rotation + w = Math.max(PX, @lank.thang.width * Camera.PPM) * (@camera.y2x + (1 - @camera.y2x) * Math.abs Math.cos @lank.thang.rotation) + h = Math.max(PX, @lank.thang.height * Camera.PPM) * (@camera.y2x + (1 - @camera.y2x) * Math.abs Math.sin @lank.thang.rotation) @sprite.scaleX = w / (@layer.resolutionFactor * DEBUG_SIZE) @sprite.scaleY = h / (@layer.resolutionFactor * DEBUG_SIZE) + @sprite.rotation = -@lank.thang.rotation * 180 / Math.PI buildSprite: -> if _.isString @thangType diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee index dbeaa0873..141f31342 100644 --- a/app/views/editor/level/thangs/ThangsTabView.coffee +++ b/app/views/editor/level/thangs/ThangsTabView.coffee @@ -57,14 +57,23 @@ module.exports = class ThangsTabView extends CocoView shortcuts: 'esc': 'selectAddThang' 'delete, del, backspace': 'deleteSelectedExtantThang' - 'left': -> @moveAddThangSelection -1 - 'right': -> @moveAddThangSelection 1 'ctrl+z, ⌘+z': 'undo' 'ctrl+shift+z, ⌘+shift+z': 'redo' - 'alt+left': -> @rotateSelectedThangBy(Math.PI) - 'alt+right': -> @rotateSelectedThangBy(0) - 'alt+up': -> @rotateSelectedThangBy(-Math.PI/2) - 'alt+down': -> @rotateSelectedThangBy(Math.PI/2) + 'alt+c': 'toggleSelectedThangCollision' + 'left': -> @moveSelectedThangBy -1, 0 + 'right': -> @moveSelectedThangBy 1, 0 + 'up': -> @moveSelectedThangBy 0, 1 + 'down': -> @moveSelectedThangBy 0, -1 + 'alt+left': -> @rotateSelectedThangTo Math.PI unless key.shift + 'alt+right': -> @rotateSelectedThangTo 0 unless key.shift + 'alt+up': -> @rotateSelectedThangTo -Math.PI / 2 + 'alt+down': -> @rotateSelectedThangTo Math.PI / 2 + 'alt+shift+left': -> @rotateSelectedThangBy Math.PI / 16 + 'alt+shift+right': -> @rotateSelectedThangBy -Math.PI / 16 + 'shift+left': -> @resizeSelectedThangBy -1, 0 + 'shift+right': -> @resizeSelectedThangBy 1, 0 + 'shift+up': -> @resizeSelectedThangBy 0, 1 + 'shift+down': -> @resizeSelectedThangBy 0, -1 constructor: (options) -> super options @@ -516,7 +525,7 @@ module.exports = class ThangsTabView extends CocoView prefix += segment if not @thangsTreema.get(prefix) then @thangsTreema.set(prefix, {}) - onThangsChanged: => + onThangsChanged: (skipSerialization) => return if @hush # keep the thangs in the same order as before, roughly @@ -527,6 +536,7 @@ module.exports = class ThangsTabView extends CocoView @level.set 'thangs', thangs return if @editThangView + return if skipSerialization serializedLevel = @level.serialize @supermodel, null, null, true try @world.loadFromLevel serializedLevel, false @@ -634,18 +644,56 @@ module.exports = class ThangsTabView extends CocoView onClickRotationButton: (e) -> $('#contextmenu').hide() rotation = parseFloat($(e.target).closest('button').data('rotation')) - @rotateSelectedThangBy rotation * Math.PI + @rotateSelectedThangTo rotation * Math.PI + + modifySelectedThangComponentConfig: (thang, componentOriginal, modificationFunction) -> + return unless thang + @hush = true + thangData = @getThangByID thang.id + thangData = $.extend true, {}, thangData + unless component = _.find thangData.components, {original: componentOriginal} + component = original: componentOriginal, config: {} + thangData.components.push component + modificationFunction component + @thangsTreema.set @pathForThang(thangData), thangData + @hush = false + @onThangsChanged true + thang.stateChanged = true + lank = @surface.lankBoss.lanks[thang.id] + lank.update true + lank.marks.debug?.destroy() + lank.marks.debug = null + lank.setDebug true + + rotateSelectedThangTo: (radians) -> + @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) => + component.config.rotation = radians + @selectedExtantThang.rotation = component.config.rotation rotateSelectedThangBy: (radians) -> - return unless @selectedExtantThang - @hush = true - thangData = @getThangByID(@selectedExtantThang.id) - thangData = $.extend(true, {}, thangData) - component = _.find thangData.components, {original: LevelComponent.PhysicalID} - component.config.rotation = radians - @thangsTreema.set(@pathForThang(thangData), thangData) - @hush = false - @onThangsChanged() + @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) => + component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI) + @selectedExtantThang.rotation = component.config.rotation + + moveSelectedThangBy: (xDir, yDir) -> + @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) => + component.config.pos.x += 0.5 * xDir + component.config.pos.y += 0.5 * yDir + @selectedExtantThang.pos.x = component.config.pos.x + @selectedExtantThang.pos.y = component.config.pos.y + + resizeSelectedThangBy: (xDir, yDir) -> + @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) => + component.config.width = (component.config.width ? 4) + 0.5 * xDir + component.config.height = (component.config.height ? 4) + 0.5 * yDir + @selectedExtantThang.width = component.config.width + @selectedExtantThang.height = component.config.height + + toggleSelectedThangCollision: -> + @modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.CollidesID, (component) => + component.config ?= {} + component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none' + @selectedExtantThang.collisionCategory = component.config.collisionCategory toggleThangsContainer: (e) -> $('#all-thangs').toggleClass('hide') diff --git a/server_setup.coffee b/server_setup.coffee index ef57d7544..7b99ff29d 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -63,7 +63,7 @@ setupExpressMiddleware = (app) -> else express.logger.format('dev', developmentLogging) app.use(express.logger('dev')) - app.use(express.static(path.join(__dirname, 'public'), maxAge: 30 * 60 * 1000)) + app.use(express.static(path.join(__dirname, 'public'), maxAge: 0)) # CloudFlare overrides maxAge, and we don't want local development caching. app.use(useragent.express()) app.use(express.favicon())