Overhaul hollywoo & hollywoo-flixel. Close #178

This commit is contained in:
2023-03-02 07:37:55 -07:00
parent 2913c2b897
commit 994ffaf9cd
14 changed files with 334 additions and 302 deletions

View File

@@ -5,6 +5,7 @@ import kiss.List;
import kiss.FuzzyMap;
import flixel.FlxSprite;
import hollywoo_flixel.FlxMovie;
import hollywoo.Director;
typedef AnimationArgs = {
frames:Array<Int>,

View File

@@ -1,11 +1,15 @@
(prop :FlxStageFacing defaultFacing)
(loadFrom "hollywoo-flixel" "src/hollywoo_flixel/Aliases.kiss")
(prop :StageFacing defaultFacing)
(prop :FuzzyMap<String> animationNames (new FuzzyMap<String>))
(defNew [&prop :String assetPath &opt frameWidth frameHeight :FlxStageFacing defaultFacing :Map<String,AnimationArgs> animations]
(prop :Float z 0)
(defNew [&prop :String assetPath &opt frameWidth frameHeight :StageFacing defaultFacing :Map<String,AnimationArgs> animations]
(super)
(set this.defaultFacing (or defaultFacing Right))
(set this.defaultFacing (or defaultFacing FacingRight))
(if (and frameWidth frameHeight)
(loadGraphic assetPath true frameWidth frameHeight)

View File

@@ -0,0 +1,6 @@
(defAlias &ident Left "Left")
(defAlias &ident LeftBehind "Left2")
(defAlias &ident Right "Right")
(defAlias &ident RightBehind "Right2")
(defAlias &ident FacingLeft (TowardsPosition "OffScreenLeft"))
(defAlias &ident FacingRight (TowardsPosition "OffScreenRight"))

View File

@@ -7,6 +7,7 @@ import flixel.FlxSprite;
import flixel.input.actions.FlxAction;
import flixel.input.actions.FlxActionManager;
import flixel.input.mouse.FlxMouseButton;
import flixel.group.FlxGroup;
import flixel.tweens.FlxTween;
import hollywoo.Movie;
import hollywoo.Scene;
@@ -18,6 +19,7 @@ import flixel.system.FlxSound;
import flixel.util.FlxTimer;
import haxe.Constraints;
import kiss_flixel.SpriteTools;
import haxe.ds.Option;
@:build(kiss.Kiss.build())
class FlxDirector implements Director<String, FlxStagePosition, FlxStageFacing, FlxScreenPosition, ActorFlxSprite, FlxSound, String, FlxSprite, FlxSound> {}
class FlxDirector implements Director<FlxSprite, FlxScreenPosition, ActorFlxSprite, FlxSound, String, FlxSprite, FlxSound> {}

View File

@@ -1,6 +1,10 @@
(prop :FlxActionDigital continueAction)
(prop actionManager (new FlxActionManager))
(prop &mut :Movie<String,FlxStagePosition,FlxStageFacing,FlxScreenPosition,ActorFlxSprite,FlxSound,String,FlxSprite,FlxSound> movie)
(prop &mut :Movie<FlxSprite,FlxScreenPosition,ActorFlxSprite,FlxSound,String,FlxSprite,FlxSound> movie)
(loadFrom "hollywoo-flixel" "src/hollywoo_flixel/Aliases.kiss")
(prop :Array<FlxTypedGroup<FlxSprite>> spriteLayers [])
(var LAYER_MAX 5)
(defNew []
(set continueAction (new FlxActionDigital "Continue" onContinue))
@@ -9,111 +13,62 @@
(continueAction.addMouse LEFT JUST_PRESSED)
(actionManager.addAction continueAction)
(FlxG.inputs.add actionManager)
(set actionManager.resetOnStateSwitch NONE))
(set actionManager.resetOnStateSwitch NONE)
(prop &mut :SceneFlxState currentState)
(method :Void showScene [:Scene<String,FlxStagePosition,FlxStageFacing,FlxScreenPosition,ActorFlxSprite,FlxSprite> scene :Appearance appearance :Continuation cc]
// Close the last scene state
(when currentState
(currentState.close))
// TODO on the first appearance, give a super (for some scenes but probably not others... hm....)
(set currentState (cast scene SceneFlxState))
(set currentState.parent FlxG.state)
(FlxG.state.openSubState currentState)
// Re-show characters in case their actor sprites were moved in different scenes:
(doFor =>name character currentState.characters
(showCharacter character ReAppearance ->:Void {}))
// TODO characters will be in front of every prop layer -- characters need their own group layer
(doFor i (range LAYER_MAX)
(let [g (new FlxTypedGroup<FlxSprite>)]
(spriteLayers.push g)
(FlxG.state.add g))))
(method :Void showSet [:FlxSprite setSprite :SceneTime time :ScenePerspective perspective :Appearance appearance :Continuation cc]
(setSprite.setGraphicSize FlxG.width)
(when (> setSprite.height FlxG.height)
(setSprite.setGraphicSize 0 FlxG.height))
(setSprite.updateHitbox)
(setSprite.screenCenter)
(FlxG.state.add setSprite)
(cc))
(method :Void cleanup []
(FlxG.state.openSubState null))
(method :Void hideSet [:FlxSprite set :Continuation cc]
(FlxG.state.remove set)
(cc))
(var STAGE_LEFT_X 150)
(var STAGE_RIGHT_X (- 1280 150))
(var STAGE_BEHIND_DY -250)
(var ACTOR_Y 500)
(method :Void cleanup [] 0)
(var STAGE_LEFT_X 150.0)
(var STAGE_RIGHT_X (- FlxG.width STAGE_LEFT_X))
(var STAGE_BEHIND_DY 250.0)
(prop :Option<AutoZConfig> autoZConfig (Some (object zPerLayer STAGE_BEHIND_DY frontLayer 0)))
(var ACTOR_Y 500.0)
(var ACTOR_WIDTH 300)
(method :Void showCharacter [:Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> character :Appearance appearance :Continuation cc]
(method :Void showCharacter [:Character<ActorFlxSprite> character :Appearance appearance :Continuation cc]
// TODO on the first appearance, show name and description (maybe? also probably not for all?)
// TODO also allow for manually defined flipped frames so text on clothing doesn't mirror
(set character.actor.flipX ?!(= character.stageFacing character.actor.defaultFacing))
// All actors same width, display centered on x
(character.actor.setGraphicSize ACTOR_WIDTH)
(character.actor.updateHitbox)
(ifLet [(FullControl layer rpos) character.stagePosition]
{
(assert (<= 0 layer SceneFlxState.LAYER_MAX) "Layer $layer is out of range 0-$SceneFlxState.LAYER_MAX")
(SpriteTools.scaleStampOn character.actor (canvas) rpos)
(let [[x y] (SpriteTools.positionOn character.actor (canvas) rpos)]
(set character.actor.x x)
(set character.actor.y y)
(if (= layer SceneFlxState.LAYER_MAX)
// In front of everything:
(currentState.add character.actor)
(.add (nth currentState.spriteLayers layer) character.actor)))
}
{
(set character.actor.x
(- (case character.stagePosition
(Left
(set currentState.actorOnLeft character.actor)
(set currentState.characterOnLeft character)
STAGE_LEFT_X)
(LeftBehind
STAGE_LEFT_X)
(Right
(set currentState.actorOnRight character.actor)
(set currentState.characterOnRight character)
STAGE_RIGHT_X)
(RightBehind
STAGE_RIGHT_X)
(otherwise (throw "unsupported stage position")))
(/ character.actor.width 2)))
(set character.actor.y ACTOR_Y)
(let [bottom (+ character.actor.y character.actor.height)]
(when (> bottom 720)
(-= character.actor.y (- bottom 720))))
(let [&mut reAddFront false]
(case character.stagePosition
((or LeftBehind RightBehind)
(set reAddFront true)
(+= character.actor.y STAGE_BEHIND_DY))
(otherwise))
(when reAddFront
(when currentState.actorOnLeft (currentState.remove currentState.actorOnLeft))
(when currentState.actorOnRight (currentState.remove currentState.actorOnRight)))
(currentState.add character.actor)
(when reAddFront
(when currentState.actorOnLeft (currentState.add currentState.actorOnLeft))
(when currentState.actorOnRight (currentState.add currentState.actorOnRight))))
})
(set character.actor.x
(- character.stagePosition.x
(/ character.actor.width 2)))
(set character.actor.y character.stagePosition.y)
// Bump sprites up from the bottom if they're too tall
(let [bottom (+ character.actor.y character.actor.height)]
(when (> bottom FlxG.height)
(-= character.actor.y (- bottom FlxG.height))))
// Display with y adjusted by z:
(-= character.actor.y character.stagePosition.z)
(FlxG.state.add character.actor)
(cc))
(method :Void hideCharacter [:Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> character :Continuation cc]
(when (= currentState.actorOnLeft character.actor)
(set currentState.actorOnLeft null)
(set currentState.characterOnLeft null))
(when (= currentState.actorOnRight character.actor)
(set currentState.actorOnRight null)
(set currentState.characterOnRight null))
(currentState.remove character.actor)
(method :Void hideCharacter [:Character<ActorFlxSprite> character :Continuation cc]
(FlxG.state.remove character.actor)
(cc))
(method :Void moveCharacter [:Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> character toPos toFacing :Continuation cc]
// Directors don't have to change the character, but FlxDirector does because that state determines
// where actors are drawn in showCharacter:
(set character.stagePosition toPos)
(set character.stageFacing toFacing)
(hideCharacter character
->:Void (showCharacter character ReAppearance cc)))
(method :Void swapCharacters [:Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> a :Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> b :Continuation cc]
(let [noCC ->:Void {}
tempStagePos a.stagePosition
tempStageFacing a.stageFacing]
(moveCharacter a b.stagePosition b.stageFacing noCC)
(moveCharacter b tempStagePos tempStageFacing cc)))
(prop &mut :Null<Continuation> nextCC)
(method onContinue [:FlxActionDigital continueAction]
(whenLet [cc nextCC]
@@ -134,23 +89,24 @@
(prop &mut :FlxSprite titleCard null)
(method :Void showTitleCard [:Array<String> text :Continuation cc]
(set titleCard (new FlxSprite))
(titleCard.makeGraphic 1280 720 FlxColor.BLACK true)
(titleCard.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
(SpriteTools.writeOnSprite (text.shift) TITLE_SIZE titleCard (object x (Percent 0.5) y (Pixels TITLE_Y)))
(localVar &mut subtitleY (+ TITLE_Y TITLE_SIZE TITLE_MARGIN))
(doFor subtitle text
(SpriteTools.writeOnSprite subtitle SUBTITLES_SIZE titleCard (object x (Percent 0.5) y (Pixels subtitleY)))
(+= subtitleY SUBTITLES_SIZE SUBTITLES_MARGIN))
(currentState.add titleCard)
(FlxG.state.add titleCard)
// Allow skipping
(startWaitForInput cc))
(method :Void hideTitleCard []
(currentState.remove titleCard))
(FlxG.state.remove titleCard))
(var DIALOG_X 300)
(var DIALOG_WIDTH (- 1280 ACTOR_WIDTH ACTOR_WIDTH))
(var DIALOG_Y 500)
(var DIALOG_HEIGHT (- 720 DIALOG_Y))
// Make these aliases so they get FlxG.width's current value when queried (not 0 at start time)
(defAlias &ident DIALOG_WIDTH (- FlxG.width ACTOR_WIDTH ACTOR_WIDTH))
(defAlias &ident DIALOG_HEIGHT (- FlxG.height DIALOG_Y))
// TODO these could be customizable to the Actor, wrylies, etc.
(var DIALOG_BOX_COLOR FlxColor.BLACK)
(var DIALOG_COLOR FlxColor.WHITE)
@@ -160,29 +116,17 @@
(var &mut :FlxText dialogText)
(var &mut :FlxText speakerNameText)
(method showDialog [:String speakerName :SpeechType<FlxStagePosition,FlxStageFacing,ActorFlxSprite> type :String wryly :String text :Continuation cc]
(method showDialog [:String speakerName :SpeechType<ActorFlxSprite> type :String wryly :String text :Continuation cc]
// TODO handle text messages, wrylies, off-screen, from-phone, etc. via (case type)
// TODO attribute on-screen dialogue to the character's stageposition
// When an actor is associated with the line, check for an animation matching the wryly
// TODO allow sounds for wrylies, like the dispatch click
(localVar &mut nameOnRight false)
(case type
((OnScreen character)
(case character.stagePosition
((or Right RightBehind) (set nameOnRight true))
(otherwise))
// If the character is behind another character, swap them so the speaker is front
(case character.stagePosition
(LeftBehind
(if currentState.characterOnLeft
(swapCharacters character currentState.characterOnLeft ->:Void {}))
(moveCharacter character Left character.stageFacing ->:Void {}))
(RightBehind
(if currentState.characterOnRight
(swapCharacters character currentState.characterOnRight ->:Void {}))
(moveCharacter character Right character.stageFacing ->:Void {}))
(otherwise))
(when (> character.stagePosition.x (/ FlxG.width 2))
(set nameOnRight true))
// When an actor is associated with the line, check for an animation matching the wryly
(let [actor (the ActorFlxSprite character.actor)]
(if wryly
(actor.playAnimation wryly)
@@ -198,23 +142,23 @@
(set dialogBox (new FlxSprite DIALOG_X DIALOG_Y))
(dialogBox.makeGraphic DIALOG_WIDTH DIALOG_HEIGHT DIALOG_BOX_COLOR))
(currentState.add dialogBox)
(FlxG.state.add dialogBox)
(dialogBox.revive)
// show the dialog
(unless dialogText
(set dialogText (new FlxText DIALOG_X DIALOG_Y DIALOG_WIDTH "" DIALOG_SIZE)))
(currentState.add dialogText)
(FlxG.state.add dialogText)
(set dialogText.text text)
// TODO actually page through the dialog instead of sizing it down?
// ^ though that doesn't work automatically with VO unless individual word times are kept in the json
// (which would be really clunky)
(set dialogText.size DIALOG_SIZE)
(while (< 720 (+ dialogText.y dialogText.height))
(while (< FlxG.height (+ dialogText.y dialogText.height))
(-= dialogText.size 6))
// show the speaker name
(unless speakerNameText
(set speakerNameText (new FlxText DIALOG_X DIALOG_Y 0 "" DIALOG_SIZE)))
(currentState.add speakerNameText)
(FlxG.state.add speakerNameText)
(if speakerName
{
(set speakerNameText.text "${speakerName}:")
@@ -277,12 +221,12 @@
(var PROP_MAX_WIDTH 500)
(method :Void quickShowPropOnScreen [:FlxSprite prop :FlxScreenPosition position :Continuation cc]
(let [left (/ 1280 6)
right (- 1280 left)
upper (/ 720 6)
lower (- 720 upper)
centerX (/ 1280 2)
centerY (/ 720 2)
(let [left (/ FlxG.width 6)
right (- FlxG.width left)
upper (/ FlxG.height 6)
lower (- FlxG.height upper)
centerX (/ FlxG.width 2)
centerY (/ FlxG.height 2)
[x y]
(case position
(Center [centerX centerY])
@@ -317,7 +261,7 @@
(-= prop.y (- propBottom FlxG.height))))
(otherwise)))
(currentState.add prop)))
(FlxG.state.add prop)))
(cc))
(prop &mut :FlxSprite _canvas null)
@@ -328,15 +272,15 @@
_canvas)
(method :Void smartShowPropOnScreen [:FlxSprite prop :Int layer :RelativePosition rpos :Continuation cc]
(assert (<= 0 layer SceneFlxState.LAYER_MAX) "Layer $layer is out of range 0-$SceneFlxState.LAYER_MAX")
(assert (<= 0 layer LAYER_MAX) "Layer $layer is out of range 0-$LAYER_MAX")
(SpriteTools.scaleStampOn prop (canvas) rpos)
(let [[x y] (SpriteTools.positionOn prop (canvas) rpos)]
(set prop.x x)
(set prop.y y)
(if (= layer SceneFlxState.LAYER_MAX)
(if (= layer LAYER_MAX)
// In front of everything:
(currentState.add prop)
(.add (nth currentState.spriteLayers layer) prop)))
(FlxG.state.add prop)
(.add (nth spriteLayers layer) prop)))
(cc))
(method :Void showPropOnScreen [:FlxSprite prop :FlxScreenPosition position :Continuation cc]
@@ -346,8 +290,8 @@
(quickShowPropOnScreen prop position cc)))
(method :Void hideProp [:FlxSprite prop cc]
(currentState.remove prop)
(doFor layer currentState.spriteLayers
(FlxG.state.remove prop)
(doFor layer spriteLayers
(layer.remove prop))
(cc))
@@ -360,19 +304,19 @@
(prop &mut :FlxSprite blackBG null)
(method :Void showBlackScreen []
(set blackBG (new FlxSprite))
(blackBG.makeGraphic 1280 720 FlxColor.BLACK true)
(currentState.add blackBG))
(blackBG.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
(FlxG.state.add blackBG))
(method :Void hideBlackScreen []
(currentState.remove blackBG))
(FlxG.state.remove blackBG))
// TODO maybe credits need their own substate so an after-credits scene could be done.
// currently the bg will cover whatever the final scene was.
(method :Void rollCredits [:Array<CreditsLine> credits cc]
(localVar bg (new FlxSprite))
(bg.makeGraphic 1280 720 FlxColor.BLACK true)
(currentState.add bg)
(localVar &mut textY 720)
(bg.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
(FlxG.state.add bg)
(localVar &mut textY FlxG.height)
(var oneColSize 64)
(var twoColSize 48)
(var threeColSize 32)
@@ -392,9 +336,9 @@
((TwoColumn col1 col2)
(let [t1 (_ctext col1 twoColSize)
t2 (_ctext col2 twoColSize)]
(set t1.x (- (* 1280 0.3) t1.width (/ twoColumnGap 2)))
(set t1.x (- (* FlxG.width 0.3) t1.width (/ twoColumnGap 2)))
(set t1.y textY)
(set t2.x (+ (* 1280 0.3) (/ twoColumnGap 2)))
(set t2.x (+ (* FlxG.width 0.3) (/ twoColumnGap 2)))
(set t2.y textY))
(+= textY twoColSize creditMargin))
// Left-justified, centered, right-justified three column lines
@@ -406,11 +350,11 @@
(set t1.y textY)
(t2.screenCenter)
(set t2.y textY)
(set t3.x (- 1280 creditMargin t3.width))
(set t3.x (- FlxG.width creditMargin t3.width))
(set t3.y textY))
(+= textY threeColSize creditMargin))
(otherwise)))
(doFor text creditsText
(currentState.add text)
(FlxG.state.add text)
(FlxTween.linearMotion text text.x text.y text.x (- text.y textY) 200 false (object onComplete ->:Void _ (cc)))))

View File

@@ -3,12 +3,13 @@ package hollywoo_flixel;
import flixel.FlxState;
import flixel.FlxSprite;
import flixel.system.FlxSound;
import hollywoo.Director;
import hollywoo.Movie;
import hollywoo_flixel.ActorFlxSprite;
import hollywoo_flixel.SceneFlxState;
import kiss_flixel.SpriteTools;
import openfl.Assets;
/*
enum FlxStagePosition {
Left;
Right;
@@ -19,11 +20,7 @@ enum FlxStagePosition {
// AND don't move the object automatically for any reason
FullControl(layer:Int, pos:RelativePosition);
}
enum FlxStageFacing {
Left;
Right;
}
*/
enum FlxScreenPosition {
// Shortcuts
@@ -42,7 +39,7 @@ enum FlxScreenPosition {
/**
* Model/controller of a Hollywoo-Flixel film, and main execution script
*/
class FlxMovie extends Movie<String, FlxStagePosition, FlxStageFacing, FlxScreenPosition, ActorFlxSprite, FlxSound, String, FlxSprite, FlxSound> {
class FlxMovie extends Movie<FlxSprite, FlxScreenPosition, ActorFlxSprite, FlxSound, String, FlxSprite, FlxSound> {
// Think of HollywooFlixelDSL.kiss as the corresponding Kiss file for this class!
public function new(director:FlxDirector, ?voiceLinesAssetPath:String) {
@@ -50,6 +47,28 @@ class FlxMovie extends Movie<String, FlxStagePosition, FlxStageFacing, FlxScreen
if (voiceLinesAssetPath != null) {
voiceLinesJson = Assets.getText(voiceLinesAssetPath);
}
super(director, voiceLinesJson);
stagePositions["Left"] = {
x: FlxDirector.STAGE_LEFT_X,
y: FlxDirector.ACTOR_Y,
z: 0.0
};
stagePositions["Right"] = {
x: FlxDirector.STAGE_RIGHT_X,
y: FlxDirector.ACTOR_Y,
z: 0.0
};
stagePositions["Left2"] = {
x: FlxDirector.STAGE_LEFT_X,
y: FlxDirector.ACTOR_Y,
z: FlxDirector.STAGE_BEHIND_DY
};
stagePositions["Right2"] = {
x: FlxDirector.STAGE_RIGHT_X,
y: FlxDirector.ACTOR_Y,
z: FlxDirector.STAGE_BEHIND_DY
};
}
}

View File

@@ -2,16 +2,12 @@
(defMacroVar subclass true)
(loadFrom "hollywoo" "src/hollywoo/Movie.kiss")
(loadFrom "hollywoo-flixel" "src/hollywoo_flixel/Aliases.kiss")
(method newFlxScene [name set time perspective]
(method newFlxSet [name assetPath]
(let [setSprite (new FlxSprite 0 0)]
(setSprite.loadGraphic (dictGet sets set) false 0 0 true) // Load uniquely so we can draw on sets for specific scenes
(newScene name (cast (new SceneFlxState setSprite time perspective)))))
// Destroy substates when the movie end is reached:
(cleanup
(doFor =>name scene scenes
(.destroy (cast scene SceneFlxState))))
(setSprite.loadGraphic assetPath false 0 0 true) // Load uniquely so we can draw on sets for specific scenes
(newSet name setSprite)))
(method newFlxSound [name path &opt :Float volume]
(set volume (or volume 1))
@@ -56,41 +52,11 @@
(localVar buttonsPerColumn 25)
(doFor [num label] (enumerate (sort (collect (runners.keys))))
(let [runner (dictGet runners label)
b (new flixel.ui.FlxButton 0 buttonY label ->{(.remove (cast (_currentScene) SceneFlxState) buttons)(runner)})]
b (new flixel.ui.FlxButton 0 buttonY label ->{(FlxG.state.remove buttons)(runner)})]
(let [column (Std.int (/ num buttonsPerColumn))]
(set b.x (* b.width column)))
(buttons.add b))
(+= buttonY 20)
(when (= (- buttonsPerColumn 1) (% num buttonsPerColumn))
(set buttonY 0)))
(.add (cast (_currentScene) SceneFlxState) buttons)))
(defMacro withProp [propKey name &body body]
`(let [,name (dictGet props ,propKey)]
,@body
(cc)))
// like withProp, but you promise to call CC yourself in the body:
(defMacro withPropCC [propKey name &body body]
`(let [,name (dictGet props ,propKey)]
,@body))
(defMacro withActor [actorKey name &body body]
`(let [,name (dictGet actors ,actorKey)]
,@body
(cc)))
// like withActor, but you promise to call CC yourself in the body:
(defMacro withActorCC [actorKey name &body body]
`(let [,name (dictGet actors ,actorKey)]
,@body))
(defMacro withSet [sceneKey name &body body]
`(let [,name .setSprite (cast (dictGet scenes ,sceneKey) SceneFlxState)]
,@body
(cc)))
// like withSet, but you promise to call CC yourself in the body:
(defMacro withSetCC [sceneKey name &body body]
`(let [,name .setSprite (cast (dictGet scenes ,sceneKey) SceneFlxState)]
,@body))
(FlxG.state.add buttons)))

View File

@@ -1,15 +0,0 @@
package hollywoo_flixel;
import kiss.Prelude;
import kiss.List;
import kiss.FuzzyMap;
import flixel.FlxSubState;
import flixel.FlxState;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.FlxG;
import hollywoo.Scene;
import hollywoo_flixel.FlxMovie;
@:build(kiss.Kiss.build())
class SceneFlxState extends FlxSubState {}

View File

@@ -1,36 +0,0 @@
// Track which actors are in FRONT of the stage so actors can appear behind them
(prop &mut :ActorFlxSprite actorOnLeft null)
(prop &mut :Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> characterOnLeft null)
(prop &mut :ActorFlxSprite actorOnRight null)
(prop &mut :Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> characterOnRight null)
// Track props in an arbitrary number of layers
(prop :Array<FlxTypedGroup<FlxSprite>> spriteLayers [])
(var LAYER_MAX 5)
(defNew [&prop :FlxSprite setSprite &prop :SceneTime time &prop :ScenePerspective perspective]
[
&mut :FlxState parent null
:FuzzyMap<Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite>> characters (new FuzzyMap<Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite>>)
:Map<FlxSprite,FlxScreenPosition> propsOnScreen (new Map)
]
(super)
(add setSprite)
// TODO characters will be in front of every prop layer -- characters need their own group layer
(doFor i (range LAYER_MAX)
(let [g (new FlxTypedGroup<FlxSprite>)]
(spriteLayers.push g)
(add g))))
(method &override :Void create []
(super.create)
(setSprite.setGraphicSize FlxG.width)
(when (> setSprite.height FlxG.height)
(setSprite.setGraphicSize 0 FlxG.height))
(setSprite.updateHitbox)
(setSprite.screenCenter))
(method &override :Void update [:Float elapsed]
(when parent
(parent.update elapsed))
(super.update elapsed))

View File

@@ -2,6 +2,7 @@ package hollywoo;
import hollywoo.Scene;
import hollywoo.Movie;
import haxe.ds.Option;
enum Appearance {
FirstAppearance;
@@ -10,13 +11,31 @@ enum Appearance {
typedef Continuation = Void -> Void;
interface Director<Set, StagePosition, StageFacing, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack> {
var movie(default, default):Movie<Set, StagePosition, StageFacing, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack>;
function showScene(scene:Scene<Set, StagePosition, StageFacing, ScreenPosition, Actor, Prop>, appearance:Appearance, cc:Continuation):Void;
function showCharacter(character:Character<StagePosition, StageFacing, Actor>, appearance:Appearance, cc:Continuation):Void;
function hideCharacter(character:Character<StagePosition, StageFacing, Actor>, cc:Continuation):Void;
function moveCharacter(character:Character<StagePosition, StageFacing, Actor>, toPos:StagePosition, toFacing:StageFacing, cc:Continuation):Void;
function swapCharacters(a:Character<StagePosition, StageFacing, Actor>, b:Character<StagePosition, StageFacing, Actor>, cc:Continuation):Void;
typedef StagePosition = {
x:Float,
y:Float,
z:Float,
};
enum StageFacing {
TowardsCharacter(name:String);
AwayFromCharacter(name:String);
TowardsPosition(name:String);
AwayFromPosition(name:String);
}
typedef AutoZConfig = {
zPerLayer:Float,
frontLayer:Int
};
interface Director<Set:Cloneable<Set>, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack> {
var movie(default, default):Movie<Set, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack>;
var autoZConfig(default,null):Option<AutoZConfig>;
function showSet(set:Set, time:SceneTime, perspective:ScenePerspective, appearance:Appearance, cc:Continuation):Void;
function hideSet(set:Set, cc:Continuation):Void;
function showCharacter(character:Character<Actor>, appearance:Appearance, cc:Continuation):Void;
function hideCharacter(character:Character<Actor>, cc:Continuation):Void;
function playSound(sound:Sound, volumeMod:Float, waitForEnd:Bool, cc:Continuation):Void;
function stopSound(sound:Sound):Void;
function playSong(song:Song, volumeMod:Float, loop:Bool, waitForEnd:Bool, cc:Continuation):Void;
@@ -25,7 +44,7 @@ interface Director<Set, StagePosition, StageFacing, ScreenPosition, Actor, Sound
function stopVoiceTrack(track:VoiceTrack):Void;
function startWaitForInput(cc:Continuation):Void;
function stopWaitForInput():Void;
function showDialog(speakerName:String, type:SpeechType<StagePosition, StageFacing, Actor>, wryly:String, dialog:String, cc:Continuation):Void;
function showDialog(speakerName:String, type:SpeechType<Actor>, wryly:String, dialog:String, cc:Continuation):Void;
function hideDialog():Void;
function showTitleCard(text:Array<String>, cc:Continuation):Void;
function hideTitleCard():Void;

View File

@@ -1,3 +1,34 @@
(defMacro withProp [propKey name &body body]
`(let [,name (dictGet props ,propKey)]
,@body
(cc)))
// like withProp, but you promise to call CC yourself in the body:
(defMacro withPropCC [propKey name &body body]
`(let [,name (dictGet props ,propKey)]
,@body))
(defMacro withActor [actorKey name &body body]
`(let [,name (dictGet actors ,actorKey)]
,@body
(cc)))
// like withActor, but you promise to call CC yourself in the body:
(defMacro withActorCC [actorKey name &body body]
`(let [,name (dictGet actors ,actorKey)]
,@body))
// Do something with the given scene's instance of its set
(defMacro withSceneSet [sceneKey name &body body]
`(let [,name .set (dictGet scenes ,sceneKey)]
,@body
(cc)))
// like withSceneSet, but you promise to call CC yourself in the body:
(defMacro withSceneSetCC [sceneKey name &body body]
`(let [,name .set (dictGet scenes ,sceneKey)]
,@body))
// When this file is loaded, all expressions in (preload <...>) will be collected. When (end) is called, they will
// be injected into a method called (doPreload).
// This allows assets to be declared in Hollywoo files where they first appear, but still loaded before execution starts.

View File

@@ -13,6 +13,10 @@ import uuid.Uuid;
using kiss.FuzzyMapTools;
typedef Cloneable<T> = {
function clone():T;
}
enum DelayHandling {
Auto;
AutoWithSkip;
@@ -37,8 +41,4 @@ enum CreditsLine {
* Model/controller of a Hollywoo film, and main execution script
*/
@:build(kiss.Kiss.build())
class Movie<Set, StagePosition, StageFacing, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack> extends AsyncEmbeddedScript {
// TODO for some reason this wasn't working when declared in Movie.kiss:
// Mutable representation of frames in time:
var scenes:FuzzyMap<Scene<Set, StagePosition, StageFacing, ScreenPosition, Actor, Prop>> = new FuzzyMap<Scene<Set, StagePosition, StageFacing, ScreenPosition, Actor, Prop>>();
}
class Movie<Set:Cloneable<Set>, ScreenPosition, Actor, Sound, Song, Prop, VoiceTrack> extends AsyncEmbeddedScript {}

View File

@@ -1,3 +1,6 @@
(defMacro makeCC [&body b]
`->:Void {,@b})
// This file is designed to be loaded again by subclasses, with macroVar "subclass" set
(#unless subclass
@@ -14,9 +17,8 @@
(prop &mut :DelayHandling delayHandling AutoWithSkip)
// TODO for some reason this won't work when declared in Kiss syntax:
// Mutable representation of frames in time:
// var scenes:Map<String, Scene<Set, StagePosition, StageFacing, ScreenPosition, Actor>> = [];
(prop :FuzzyMap<Scene<Set,ScreenPosition,Actor,Prop>> scenes (new FuzzyMap<Scene<Set,ScreenPosition,Actor,Prop>>))
(prop :FuzzyMap<Bool> shownScenes (new FuzzyMap<Bool>))
(prop :FuzzyMap<Bool> shownCharacters (new FuzzyMap<Bool>))
@@ -40,7 +42,8 @@
(method :Void showDialog [actorName dialogType wryly text cc]
(when intercutMap
(whenLet [sceneForActor (try (dictGet intercutMap actorName) (catch [e] null))]
(setScene sceneForActor ->{})))
(unless (= sceneForActor sceneKey)
(setScene sceneForActor ->{}))))
(let [cc ->:Void {(director.hideDialog) (cc)}
&mut skipCC cc]
@@ -83,11 +86,66 @@
[])]
(dictSet voiceLines "$actorName $key" (objectWith [start line.start end line.end] trackKey alts))))))
(method _ccForEach <>[T] [:Iterable<T> collection :(T,Continuation)->Void do_ :Continuation finalCC]
(let [:Iterator<T> iter (collection.iterator)]
(withFunctions
[
(:Void doNext []
(if (iter.hasNext)
(do_ (iter.next) doNext)
(finalCC)))
]
(doNext))))
(method _hideCurrentScene [:Continuation cc]
(if sceneKey
// hide current scene background
(let [currentScene (dictGet scenes sceneKey)]
(director.hideSet currentScene.set
(makeCC
// hide current scene characters
(_ccForEach
currentScene.characters
->[:Character<Actor> c :Continuation cc]
(director.hideCharacter c cc)
(makeCC
// hide current scene props, etc.
(_ccForEach
currentScene.propsOnScreen
->[:ScreenProp<ScreenPosition,Prop> p :Continuation cc]
(director.hideProp p.prop cc)
cc))))))
(cc)))
(method _showScene [:Scene<Set,ScreenPosition,Actor,Prop> scene :Appearance appearance :Continuation cc]
// Show current scene background
(director.showSet scene.set scene.time scene.perspective appearance
(makeCC
// Show current scene characters
(_ccForEach
(object iterator ->(scene.characters.keys))
->[:String key :Continuation cc]
(director.showCharacter (dictGet scene.characters key) (appearanceFlag shownCharacters key) cc)
(makeCC
// hide current scene props, etc.
(_ccForEach
scene.propsOnScreen
->[:ScreenProp<ScreenPosition,Prop> p :Continuation cc]
(director.showPropOnScreen p.prop p.screenPosition cc)
cc))))))
(defNew
[
// "View" in the Model-View-Controller architecture:
&prop :Director<Set,StagePosition,StageFacing,ScreenPosition,Actor,Sound,Song,Prop,VoiceTrack> director
&prop :Director<Set,ScreenPosition,Actor,Sound,Song,Prop,VoiceTrack> director
&opt :String voiceLinesJson
&opt :String positionsJson
]
[
:Map<String,StagePosition> stagePositions
(if positionsJson
(haxe.Json.parse (sys.io.File.getContent positionsJson))
(new Map))
]
(set director.movie this)
@@ -99,7 +157,7 @@
// Some real magic happens here. This macro defines a method, AND a reader macro
// for calling it with cc passed automatically if cc is an argument.
// GOTCHA: DO NOT use (method) directly in this file!!
(defMacro hollywooMethod [nameAndType canSkip argList &builder b &body body]
(let [args (expList argList)
numArgs args.length
@@ -162,25 +220,24 @@
(dictSet scenes name (objectWith
[
set
(dictGet sets setKey)
(.clone (dictGet sets setKey))
characters
(new FuzzyMap<Character<StagePosition,StageFacing,Actor>>)
(new FuzzyMap<Character<Actor>>)
propsOnScreen
(new FuzzyMap<Prop>)
(new FuzzyMap<ScreenProp<ScreenPosition,Prop>>)
]
time
perspective)))
(hollywooMethod newScene true [name :Scene<Set,StagePosition,StageFacing,ScreenPosition,Actor,Prop> scene]
(assert isLoading)
(dictSet scenes name scene))
(hollywooMethod setScene false [name :Continuation cc]
(set sceneKey name)
(director.showScene
(dictGet scenes name)
(appearanceFlag shownScenes name)
cc))
(_hideCurrentScene
(makeCC
(set sceneKey name)
(_showScene
(dictGet scenes name)
(appearanceFlag shownScenes name)
cc))))
(hollywooMethod newSound true [name :Sound s]
(assert isLoading)
@@ -221,38 +278,62 @@
(assert isLoading)
(dictSet actors name actor))
(hollywooMethod addCharacter false [actorName :StagePosition position :StageFacing facing :Continuation cc]
(let [character (object stagePosition position stageFacing facing actor (dictGet actors actorName))]
(dictSet .characters (_currentScene) actorName character)
(director.showCharacter
character
(appearanceFlag shownCharacters actorName)
cc)))
(hollywooMethod autoZProcess false [:StagePosition position :Continuation cc]
// handle auto z recursively
(ifLet [(Some (objectWith zPerLayer frontLayer)) director.autoZConfig]
{
(doFor =>name otherCharacter .characters (_currentScene)
(when (and (= position.x otherCharacter.stagePosition.x) (= position.y otherCharacter.stagePosition.y) (= position.z otherCharacter.stagePosition.z))
(moveCharacter name (object x position.x y position.y z (+ otherCharacter.stagePosition.z zPerLayer)) otherCharacter.stageFacing cc)
(return)))
(cc)
}
(cc)))
(hollywooMethod addCharacter false [actorName :Dynamic position :StageFacing facing :Continuation cc]
(let [position (typeCase [position]
([:String pKey] (dictGet stagePositions pKey))
(otherwise position))
character (object stagePosition position stageFacing facing actor (dictGet actors actorName))]
(autoZProcess position
(makeCC
(dictSet .characters (_currentScene) actorName character)
(director.showCharacter
character
(appearanceFlag shownCharacters actorName)
cc)))))
(hollywooMethod removeCharacter false [actorName :Continuation cc]
(let [c (dictGet .characters (_currentScene) actorName)]
(.remove .characters (_currentScene) actorName)
(director.hideCharacter c cc)))
(hollywooMethod moveCharacter false [actorName :StagePosition newPosition :StageFacing newFacing :Continuation cc]
(let [c (dictGet .characters (_currentScene) actorName)]
(director.moveCharacter c newPosition newFacing ->:Void {
(set c.stagePosition newPosition)
(set c.stageFacing newFacing)
(cc)
})))
// INSTANTLY move a character:
(hollywooMethod moveCharacter false [actorName :Dynamic newPosition :StageFacing newFacing :Continuation cc]
(let [newPosition (typeCase [newPosition]
([:String pKey] (dictGet stagePositions pKey))
(otherwise newPosition))]
(removeCharacter actorName
(makeCC
(addCharacter actorName newPosition newFacing cc)))))
// INSTANTLY swap characters
(hollywooMethod swapCharacters false [actorNameA actorNameB :Continuation cc]
// remove both, then re-add both, so they don't trigger
// cascading auto z adjustments on top of each other:
(let [a (dictGet .characters (_currentScene) actorNameA)
b (dictGet .characters (_currentScene) actorNameB)]
(director.swapCharacters a b ->:Void
(let [tempStagePos a.stagePosition
tempStageFacing a.stageFacing]
(set a.stagePosition b.stagePosition)
(set a.stageFacing b.stageFacing)
(set b.stagePosition tempStagePos)
(set b.stageFacing tempStageFacing)
(cc)))))
asp a.stagePosition
asf a.stageFacing
b (dictGet .characters (_currentScene) actorNameB)
bsp b.stagePosition
bsf b.stageFacing]
(removeCharacter actorNameA
(makeCC
(removeCharacter actorNameB
(makeCC
(addCharacter actorNameA bsp bsf
(makeCC
(addCharacter actorNameB asp asf cc)))))))))
// TODO moveCharacter remove them, add them to another scene
// TODO moveCharacterAndFollow remove them, add them to another scene, set that the scene
@@ -262,10 +343,13 @@
(dictSet props name prop))
(hollywooMethod addPropToScreen false [name :ScreenPosition position :Continuation cc]
(dictSet .propsOnScreen (_currentScene) name (dictGet props name))
(director.showPropOnScreen (dictGet props name) position cc))
(let [prop (dictGet props name)]
(dictSet .propsOnScreen (_currentScene) name (object screenPosition position prop prop))
(director.showPropOnScreen prop position cc)))
(hollywooMethod removeProp false [name :Continuation cc]
(.remove .propsOnScreen (_currentScene) name)
// TODO (propsOnStage.remove name)
(director.hideProp (dictGet props name) cc))
// Dialogue:

View File

@@ -1,6 +1,8 @@
package hollywoo;
import kiss.FuzzyMap;
import hollywoo.Director;
import hollywoo.Movie;
enum SceneTime {
Morning;
@@ -15,26 +17,31 @@ enum ScenePerspective {
Mixed;
}
typedef Character<StagePosition, StageFacing, Actor> = {
typedef Character<Actor> = {
stagePosition:StagePosition,
stageFacing:StageFacing,
actor:Actor
};
enum SpeechType<StagePosition, StageFacing, Actor> {
enum SpeechType<Actor> {
Super;
OffScreen(actor:Actor);
VoiceOver(actor:Actor);
TextMessage(actor:Actor);
FromPhone(actor:Actor);
OnScreen(character:Character<StagePosition, StageFacing, Actor>);
OnScreen(character:Character<Actor>);
Custom(type:String, actor:Actor, args:Dynamic);
}
typedef Scene<Set, StagePosition, StageFacing, ScreenPosition, Actor, Prop> = {
typedef ScreenProp<ScreenPosition,Prop> = {
screenPosition:ScreenPosition,
prop:Prop
};
typedef Scene<Set:Cloneable<Set>, ScreenPosition, Actor, Prop> = {
set:Set,
characters:FuzzyMap<Character<StagePosition, StageFacing, Actor>>,
propsOnScreen:FuzzyMap<Prop>,
characters:FuzzyMap<Character<Actor>>,
propsOnScreen:FuzzyMap<ScreenProp<ScreenPosition,Prop>>,
// TODO props on stage
time:SceneTime,
perspective:ScenePerspective