Overhaul hollywoo & hollywoo-flixel. Close #178

This commit is contained in:
2023-03-02 07:37:55 -07:00
parent 28892ab188
commit 6efd3b1973
5 changed files with 202 additions and 61 deletions

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