Added new LevelDialogueView. Messed around with dimming a bit more.

This commit is contained in:
Nick Winter 2014-11-08 11:35:25 -08:00
parent 4cb641689c
commit 1b94868197
15 changed files with 225 additions and 17 deletions

View file

@ -638,8 +638,9 @@ module.exports = Lank = class Lank extends CocoClass
onDialogue: (e) -> onDialogue: (e) ->
return unless @thang?.id is e.spriteID return unless @thang?.id is e.spriteID
label = @addLabel 'dialogue', Label.STYLE_DIALOGUE unless @thang?.id is 'Hero Placeholder' # Don't show these for heroes, because they aren't actually first-person, just LevelDialogueView narration
label.setText e.blurb or '...' label = @addLabel 'dialogue', Label.STYLE_DIALOGUE
label.setText e.blurb or '...'
sound = e.sound ? AudioPlayer.soundForDialogue e.message, @thangType.get 'soundTriggers' sound = e.sound ? AudioPlayer.soundForDialogue e.message, @thangType.get 'soundTriggers'
@dialogueSoundInstance?.stop() @dialogueSoundInstance?.stop()
if @dialogueSoundInstance = @playSound sound, false if @dialogueSoundInstance = @playSound sound, false

View file

@ -235,7 +235,6 @@
tome_available_spells: "Available Spells" tome_available_spells: "Available Spells"
tome_your_skills: "Your Skills" tome_your_skills: "Your Skills"
tome_current_method: "Current Method" tome_current_method: "Current Method"
hud_continue: "Continue (shift+space)"
hud_continue_short: "Continue" hud_continue_short: "Continue"
code_saved: "Code Saved" code_saved: "Code Saved"
skip_tutorial: "Skip (esc)" skip_tutorial: "Skip (esc)"

View file

@ -50,6 +50,8 @@ $level-resize-transition-time: 0.5s
#stop-real-time-playback-button #stop-real-time-playback-button
display: block display: block
z-index: 20 z-index: 20
#level-dialogue-view
display: none
.level-content .level-content
margin: 0px auto margin: 0px auto

View file

@ -8,9 +8,11 @@
overflow: visible overflow: visible
&.controls-disabled &.controls-disabled
@include opacity(0.5)
pointer-events: none pointer-events: none
.wood-background, .hinge, .avatar-wrapper-container, .center
@include filter(brightness(50%))
.wood-background .wood-background
position: absolute position: absolute
left: 0 left: 0
@ -19,7 +21,7 @@
background-size: auto 100% background-size: auto 100%
width: 100% width: 100%
height: 100px height: 100px
z-index: 2 z-index: 4
.hinge .hinge
position: absolute position: absolute
@ -28,7 +30,7 @@
width: 27px width: 27px
height: 44px height: 44px
background-size: contain background-size: contain
z-index: 2 z-index: 4
pointer-events: none pointer-events: none
.hinge-0 .hinge-0
@ -50,7 +52,7 @@
left: 18% left: 18%
left: -webkit-calc(50% - (560px - 100px) / 2 - 10px) left: -webkit-calc(50% - (560px - 100px) / 2 - 10px)
left: calc(50% - (560px - 100px) / 2 - 10px) left: calc(50% - (560px - 100px) / 2 - 10px)
z-index: 3 z-index: 5
.thang-canvas-wrapper .thang-canvas-wrapper
width: 80px width: 80px
@ -99,7 +101,7 @@
font-family: Open Sans Condensed font-family: Open Sans Condensed
font-weight: bold font-weight: bold
font-size: 16px font-size: 16px
z-index: 2 z-index: 4
@include transition(0.5s ease) @include transition(0.5s ease)
&:hover &:hover

View file

@ -0,0 +1,91 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#level-dialogue-view
+keyframes(speakingPulse)
from
@include box-shadow(0px 0px 8px #333)
color: white
50%
@include box-shadow(0px 0px 35px skyblue)
color: skyblue
to
@include box-shadow(0px 0px 8px #333)
color: white
width: 417px
height: 296px
background: transparent url(/images/level/code_palette_wood_background.png)
background-size: 100% auto
position: absolute
bottom: -296px + 40px
//left: -webkit-calc(27.5% - 417px / 2)
left: -webkit-calc(55% - 417px)
// Bounce in
@include transition(1s cubic-bezier(.17,.89,.42,1.36))
z-index: 2
&.active
display: block
bottom: -20px
&.speaking
.dialogue-area
.bubble
@include animation(speakingPulse 1.5s infinite)
.dialogue-area
position: relative
height: 100%
width: 100%
z-index: 1
.bubble
position: relative
margin: 20px
padding: 10px 10px 30px 10px
color: white
font-weight: bold
background: rgb(45, 35, 234)
border: black solid 1px
border-radius: 10px
font-size: 18px
line-height: 20px
strong
color: #09B057
.hud-hint
font-weight: normal
color: #ddd
font-size: 14px
line-height: 16px
vertical-align: middle
.enter
position: absolute
right: 10px
bottom: 10px
div.dot
background: #337
width: 8px
height: 8px
position: absolute
right: 8px
top: 9px
border-radius: 5px
button, .alert
padding: 2px 5px
.enter button.with-dot
padding-right: 20px
h3
margin: 0
font-size: 16px
line-height: 16px
color: #338
button
margin-left: 10px

View file

@ -12,7 +12,11 @@
background-size: 100% 100% background-size: 100% 100%
// Counteract 50px height of absolutely positioned control bar, but overlap by 10px of jagged transparent top. // Counteract 50px height of absolutely positioned control bar, but overlap by 10px of jagged transparent top.
margin-top: 50px - 10px margin-top: 50px - 10px
z-index: 2 z-index: 3
&.controls-disabled
pointer-events: none
@include filter(brightness(50%))
button button
font-size: 26px font-size: 26px

View file

@ -43,7 +43,7 @@
&.read-only &.read-only
background: linear-gradient(to bottom, rgba(0,0,0,0.25) 0%,rgba(0,0,0,0.25) 100%), url(/images/level/code_editor_top_bar_wood_background.png) background: linear-gradient(to bottom, rgba(0,0,0,0.25) 0%,rgba(0,0,0,0.25) 100%), url(/images/level/code_editor_top_bar_wood_background.png)
background-size: 100% 100% background-size: 100% 100%
> * > *:not(.spell-tool-buttons)
@include opacity(0.5) @include opacity(0.5)
.thang-avatar-view .thang-avatar-view

View file

@ -30,6 +30,8 @@
#thang-hud #thang-hud
#level-dialogue-view
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
.footer .footer

View file

@ -0,0 +1,2 @@
.dialogue-area
p.bubble.dialogue-bubble

View file

@ -495,9 +495,9 @@ requiredGearByLevel =
'descending-further': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} 'descending-further': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'the-second-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} 'the-second-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'dread-door': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'} 'dread-door': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
'known-enemy': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'} 'known-enemy': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', torso: 'leather-tunic'}
'master-of-names': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'master-of-names': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} 'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
'tactical-strike': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} 'tactical-strike': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}

View file

@ -9,7 +9,7 @@ module.exports = class DialogueAnimator
@childrenToAdd = _.map(d[0].childNodes, (e) -> return e) @childrenToAdd = _.map(d[0].childNodes, (e) -> return e)
@t0 = new Date() @t0 = new Date()
@charsAdded = 0 @charsAdded = 0
@charsPerSecond = 50 @charsPerSecond = 25
tick: -> tick: ->
if not @charsToAdd and not @childAnimator if not @charsToAdd and not @childAnimator

View file

@ -0,0 +1,104 @@
CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/level-dialogue-view'
DialogueAnimator = require './DialogueAnimator'
module.exports = class LevelDialogueView extends CocoView
id: 'level-dialogue-view'
template: template
subscriptions:
'sprite:speech-updated': 'onSpriteDialogue'
'level:sprite-clear-dialogue': 'onSpriteClearDialogue'
'level:shift-space-pressed': 'onShiftSpacePressed'
'level:escape-pressed': 'onEscapePressed'
'sprite:dialogue-sound-completed': 'onDialogueSoundCompleted'
events:
'click': 'onClick'
onClick: (e) ->
Backbone.Mediator.publish 'tome:focus-editor', {}
onFrameChanged: (e) ->
@timeProgress = e.progress
@update()
onSpriteDialogue: (e) ->
return unless e.message
@$el.addClass 'active speaking'
@setMessage e.message, e.mood, e.responses
window.tracker?.trackEvent 'Heard Sprite', {message: e.message, label: e.message}, ['Google Analytics']
onDialogueSoundCompleted: ->
@$el.removeClass 'speaking'
onSpriteClearDialogue: ->
@$el.removeClass 'active speaking'
setMessage: (message, mood, responses) ->
message = marked message
# Fix old HTML icons like <i class='icon-play'></i> in the Markdown
message = message.replace /&lt;i class=&#39;(.+?)&#39;&gt;&lt;\/i&gt;/, "<i class='$1'></i>"
clearInterval(@messageInterval) if @messageInterval
@bubble = $('.dialogue-bubble', @$el)
@bubble.removeClass(@lastMood) if @lastMood
@lastMood = mood
@bubble.text('')
group = $('<div class="enter secret"></div>')
@bubble.append(group)
if responses
@lastResponses = responses
for response in responses
button = $('<button class="btn btn-small banner"></button>').text(response.text)
button.addClass response.buttonClass if response.buttonClass
group.append(button)
response.button = $('button:last', group)
else
s = $.i18n.t('play_level.hud_continue_short', defaultValue: 'Continue')
sk = $.i18n.t('play_level.skip_tutorial', defaultValue: 'skip: esc')
if not @escapePressed
group.append('<span class="hud-hint">' + sk + '</span>')
group.append($('<button class="btn btn-small banner with-dot">' + s + ' <div class="dot"></div></button>'))
@lastResponses = null
@animator = new DialogueAnimator(message, @bubble)
@messageInterval = setInterval(@addMoreMessage, 1000 / 30) # 30 FPS
addMoreMessage: =>
if @animator.done()
clearInterval(@messageInterval)
@messageInterval = null
$('.enter', @bubble).removeClass('secret').css('opacity', 0.0).delay(500).animate({opacity: 1.0}, 500, @animateEnterButton)
if @lastResponses
buttons = $('.enter button')
for response, i in @lastResponses
channel = response.channel.replace 'level-set-playing', 'level:set-playing' # Easier than migrating all those victory buttons.
f = (r) => => setTimeout((-> Backbone.Mediator.publish(channel, r.event or {})), 10)
$(buttons[i]).click(f(response))
else
$('.enter', @bubble).click(-> Backbone.Mediator.publish('script:end-current-script', {}))
return
@animator.tick()
onShiftSpacePressed: (e) ->
@shiftSpacePressed = (@shiftSpacePressed || 0) + 1
# We don't need to handle script:end-current-script--that's done--but if we do have
# custom buttons, then we need to trigger the one that should fire (the last one).
# If we decide that always having the last one fire is bad, we should make it smarter.
return unless @lastResponses?.length
r = @lastResponses[@lastResponses.length - 1]
channel = r.channel.replace 'level-set-playing', 'level:set-playing'
_.delay (-> Backbone.Mediator.publish(channel, r.event or {})), 10
onEscapePressed: (e) ->
@escapePressed = true
animateEnterButton: =>
return unless @bubble
button = $('.enter', @bubble)
dot = $('.dot', button)
dot.animate({opacity: 0.2}, 300).animate({opacity: 1.9}, 600, @animateEnterButton)
destroy: ->
clearInterval(@messageInterval) if @messageInterval
super()

View file

@ -1,7 +1,6 @@
CocoView = require 'views/kinds/CocoView' CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/hud' template = require 'templates/play/level/hud'
prop_template = require 'templates/play/level/hud_prop' prop_template = require 'templates/play/level/hud_prop'
DialogueAnimator = require './DialogueAnimator'
module.exports = class LevelHUDView extends CocoView module.exports = class LevelHUDView extends CocoView
id: 'thang-hud' id: 'thang-hud'
@ -180,6 +179,4 @@ module.exports = class LevelHUDView extends CocoView
destroy: -> destroy: ->
@stage?.stopTalking() @stage?.stopTalking()
clearInterval(@messageInterval) if @messageInterval
clearTimeout @hintNextSelectionTimeout if @hintNextSelectionTimeout
super() super()

View file

@ -141,6 +141,7 @@ module.exports = class LevelPlaybackView extends CocoView
console.warn('error disabling scrubber', error) console.warn('error disabling scrubber', error)
@timePopup?.disable() @timePopup?.disable()
$('#volume-button', @$el).removeClass('disabled') $('#volume-button', @$el).removeClass('disabled')
@$el.addClass 'controls-disabled'
onEnableControls: (e) -> onEnableControls: (e) ->
return if @realTime return if @realTime
@ -152,6 +153,7 @@ module.exports = class LevelPlaybackView extends CocoView
catch error catch error
console.warn('error enabling scrubber', error) console.warn('error enabling scrubber', error)
@timePopup?.enable() @timePopup?.enable()
@$el.removeClass 'controls-disabled'
onSetPlaying: (e) -> onSetPlaying: (e) ->
@playing = (e ? {}).playing ? true @playing = (e ? {}).playing ? true

View file

@ -27,6 +27,7 @@ ProblemAlertView = require './tome/ProblemAlertView'
TomeView = require './tome/TomeView' TomeView = require './tome/TomeView'
ChatView = require './LevelChatView' ChatView = require './LevelChatView'
HUDView = require './LevelHUDView' HUDView = require './LevelHUDView'
LevelDialogueView = require './LevelDialogueView'
ControlBarView = require './ControlBarView' ControlBarView = require './ControlBarView'
LevelPlaybackView = require './LevelPlaybackView' LevelPlaybackView = require './LevelPlaybackView'
GoalsView = require './LevelGoalsView' GoalsView = require './LevelGoalsView'
@ -246,6 +247,7 @@ module.exports = class PlayLevelView extends RootView
@insertSubView new LevelFlagsView world: @world if (@levelID in ['sky-span', 'coinucopia']) or @level.get('type', true) in ['hero-ladder', 'hero-coop'] # TODO: figure out when flags are available @insertSubView new LevelFlagsView world: @world if (@levelID in ['sky-span', 'coinucopia']) or @level.get('type', true) in ['hero-ladder', 'hero-coop'] # TODO: figure out when flags are available
@insertSubView new GoldView {} @insertSubView new GoldView {}
@insertSubView new HUDView {level: @level} @insertSubView new HUDView {level: @level}
@insertSubView new LevelDialogueView {level: @level}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session @insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
if @level.get('type') in ['ladder', 'hero-ladder'] if @level.get('type') in ['ladder', 'hero-ladder']
@insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level @insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level