├── .github └── FUNDING.yml ├── .gitignore ├── Actor ├── Actor.lua ├── Commands.lua ├── Functions.lua ├── Sprite.lua └── Update.lua ├── Builder ├── Builder.lua ├── Commands.lua ├── Path.lua ├── Setup.lua └── Sprite.lua ├── LICENSE ├── README.md ├── Resources └── Global │ └── Masks │ └── Kaleidoscope │ └── 1.png ├── Scripts ├── Cycle.lua ├── Kaleidoscope │ ├── Pair.lua │ ├── Polygon.lua │ ├── Tile.lua │ └── Triangle.lua ├── Morph │ ├── Bob.lua │ ├── Crop.lua │ ├── Flag.lua │ ├── Pulse.lua │ ├── Spacing.lua │ ├── Split.lua │ ├── Stretch.lua │ └── Wag.lua ├── Particles │ ├── Depth │ │ ├── Actors.lua │ │ ├── Bounce.lua │ │ ├── Cascade.lua │ │ ├── Helix.lua │ │ ├── Spread.lua │ │ └── StaticScatter.lua │ ├── Explosion.lua │ ├── LaneSweep.lua │ └── ScreenBounce.lua └── Tile │ ├── Child.lua │ ├── Quad.lua │ ├── Scroll.lua │ ├── States.lua │ └── Tile.lua └── beat4sprite.lua /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: Engine_Machiner 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Resources/* 2 | !/Resources/Global/ 3 | /Modules/* -------------------------------------------------------------------------------- /Actor/Actor.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Table local resolvePath = tapLua.resolvePath 3 | 4 | local merge = tapLua.Actor.merge 5 | 6 | 7 | local Actor = {} beat4sprite.Actor = Actor 8 | 9 | 10 | local function setMeta( f, tbl ) 11 | 12 | local meta = { __call = f } setmetatable( tbl, meta ) 13 | 14 | end 15 | 16 | 17 | local function lib(keys) 18 | 19 | if not keys then return Actor end 20 | 21 | local function isValid( k, v ) return astro.contains( Actor, k ) end 22 | 23 | return astro.filter( Actor, isValid ) 24 | 25 | end 26 | 27 | local function extend( actor, keys ) 28 | 29 | local lib = lib(keys) astro.merge( actor, lib ) return actor 30 | 31 | end 32 | 33 | Actor.extend = extend 34 | 35 | local function actor( beat4sprite, input ) 36 | 37 | local base = { 38 | 39 | InitCommand=function(self) 40 | 41 | for k,v in pairs( beat4sprite ) do self[k] = v end 42 | 43 | self.extend, self.Commands = nil -- It's not logical. 44 | 45 | end, 46 | 47 | UpdateFunctionCommand=function(self) self:runTimers() end 48 | 49 | } 50 | 51 | local class = input.Class local Actor = tapLua[class] or tapLua.Actor 52 | 53 | input = merge( base, input ) return Actor(input) 54 | 55 | end 56 | 57 | setMeta( actor, Actor ) 58 | 59 | 60 | local Sprite = {} beat4sprite.Sprite = Sprite 61 | 62 | local function sprite( beat4sprite, input ) 63 | 64 | input.Texture = resolvePath( input.Texture ) 65 | 66 | local base = { 67 | 68 | InitCommand=function(self) for k,v in pairs( beat4sprite ) do self[k] = v end end, 69 | 70 | UpdateFunctionCommand=function(self) 71 | 72 | if not self.beat4sprite then return end self:updateRainbow():updateStateDelay() 73 | 74 | end 75 | 76 | } 77 | 78 | input.Class = "Sprite" input = merge( base, input ) return Actor(input) 79 | 80 | end 81 | 82 | setMeta( sprite, Sprite ) 83 | 84 | 85 | local function Quad( input ) input.Class = "Quad" return Actor(input) end 86 | 87 | local function ActorFrame( input ) input.Class = "ActorFrame" return Actor(input) end 88 | 89 | local function ActorProxy( input ) input.Class = "ActorProxy" return Actor(input) end 90 | 91 | --local function ActorFrameTexture( input ) input.Class = "ActorFrameTexture" return Actor(input) end 92 | 93 | local function Text( input ) 94 | 95 | input.Class = "BitmapText" input.Font = resolvePath( input.Font ) return Actor(input) 96 | 97 | end 98 | 99 | 100 | -- Additional actors. 101 | 102 | local function BaseFrame( input ) 103 | 104 | local base = { 105 | 106 | GainFocusCommand=function(self) self:visible(true) end, 107 | LoseFocusCommand=function(self) self:visible(false) end 108 | 109 | } 110 | 111 | input = merge( base, input ) return ActorFrame(input) 112 | 113 | end 114 | 115 | local function Square(path) 116 | 117 | return beat4sprite.Sprite { 118 | 119 | InitCommand=function(self) self:Load( path, "stretch" ):queuecommand("Post") end, 120 | 121 | PostCommand=function(self) local h = self:GetHeight() self:SetWidth(h) end 122 | 123 | } 124 | 125 | end 126 | 127 | local function ScreenQuad(color) 128 | 129 | local Quad = tapLua.ScreenQuad(color) 130 | 131 | Quad.OnCommand = Quad.InitCommand Quad.InitCommand = nil 132 | 133 | return Quad .. { OnCommand=function(self) extend(self) end } 134 | 135 | end 136 | 137 | local function ScreenBlend(blend) 138 | 139 | blend = blend or Blend.Add 140 | 141 | return ScreenQuad() .. { OnCommand=function(self) self:blend(blend) end } 142 | 143 | end 144 | 145 | 146 | astro.merge( beat4sprite, { 147 | 148 | BaseFrame = BaseFrame, ActorFrame = ActorFrame, ActorProxy = ActorProxy, 149 | 150 | Text = Text, Quad = Quad, Square = Square, ScreenQuad = ScreenQuad, 151 | 152 | ScreenBlend = ScreenBlend, extend = extend 153 | 154 | } ) -------------------------------------------------------------------------------- /Actor/Commands.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Cyclic commands because they queue forever on loop. 3 | 4 | local degrees = { 90, 0 } 5 | 6 | 7 | local function SpinX(self) 8 | 9 | local time = self:tweenRate() 10 | 11 | for i,v in ipairs(degrees) do self:linear(time):rotationy(v) end 12 | 13 | self:queuecommand( "Spin" ) 14 | 15 | end 16 | 17 | local function SpinY(self) 18 | 19 | local time = self:tweenRate() 20 | 21 | for i,v in ipairs(degrees) do self:linear(time):rotationx(v) end 22 | 23 | self:queuecommand( "Spin" ) 24 | 25 | end 26 | 27 | local function SpinXY(self) 28 | 29 | local time = self:tweenRate() 30 | 31 | for i,v in ipairs(degrees) do self:linear(time):rotationy(v) end 32 | for i,v in ipairs(degrees) do self:linear(time):rotationx(v) end 33 | 34 | self:queuecommand( "Spin" ) 35 | 36 | end 37 | 38 | 39 | local function Pulse( self, scale ) 40 | 41 | local time = self:tweenRate() local zoom = self:GetZoom() scale = scale or 0 42 | 43 | self:smooth(time):zoom( zoom * scale ):smooth(time):zoom(zoom) 44 | 45 | self:queuecommand("Pulse") 46 | 47 | end 48 | 49 | local function Pulse1(self) Pulse( self, 0 ) end 50 | local function Pulse2(self) Pulse( self, 1.5 ) end 51 | 52 | 53 | local function Alpha(self) 54 | 55 | local time = self:tweenRate() local alpha = self:GetDiffuseAlpha() 56 | 57 | self:linear(time):diffusealpha(0):linear(time):diffusealpha(alpha) 58 | 59 | self:queuecommand("Alpha") 60 | 61 | end 62 | 63 | 64 | beat4sprite.Actor.Commands = { 65 | 66 | SpinX = SpinX, SpinY = SpinY, SpinXY = SpinXY, Pulse1 = Pulse1, Pulse2 = Pulse2, 67 | 68 | Alpha = Alpha 69 | 70 | } -------------------------------------------------------------------------------- /Actor/Functions.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector 3 | 4 | local function isOnGameplay() 5 | 6 | local screen = SCREENMAN:GetTopScreen() 7 | 8 | 9 | local isPlaying = not GAMESTATE:InStepEditor() 10 | 11 | local isEditScreen = screen:GetName() == "ScreenEdit" 12 | 13 | local isValid = isEditScreen and isPlaying 14 | 15 | 16 | local isGameplay = screen:GetScreenType():match("Gameplay") 17 | 18 | return isGameplay or isValid 19 | 20 | end 21 | 22 | local function onGameplay(self) 23 | 24 | if not self:isOnGameplay() then return self end 25 | 26 | self:effectclock('beat'):set_tween_uses_effect_delta(true) 27 | 28 | return self 29 | 30 | end 31 | 32 | 33 | local function configRate() 34 | 35 | local rate = beat4sprite.Config.PreviewRate return isOnGameplay() and 1 or rate 36 | 37 | end 38 | 39 | local function BPM_Rate() 40 | 41 | if not isOnGameplay() then return 1 end 42 | 43 | local bpm = GAMESTATE:GetSongBPS() * 60 bpm = math.floor( bpm * 0.01 ) 44 | 45 | local rate = 1 + bpm return math.ceil( rate * 0.5 ) 46 | 47 | end 48 | 49 | local function rate(self) return self:configRate() * self:BPM_Rate() * 2 end 50 | 51 | local function tweenRate(self) return self.beat4sprite.Rate * self:rate() end 52 | 53 | local function periodRate(self) return self.beat4sprite.Effect.Period * self:rate() end 54 | 55 | local function periodSetup(self) self.Effect.Period = periodRate(self) end 56 | 57 | 58 | local function setupEffect(self) 59 | 60 | local beat4sprite = self.beat4sprite 61 | 62 | local keys = { "Magnitude", "Period", "Offset" } local Effect = {} 63 | 64 | for i,v in ipairs(keys) do Effect[v] = beat4sprite.Effect[v] end 65 | 66 | Effect.Magnitude = Vector( Effect.Magnitude ) -- Do not use the same Vector object. 67 | 68 | self.Effect = Effect return self 69 | 70 | end 71 | 72 | local function setEffect( self, effectKey ) 73 | 74 | local beat4sprite = self.beat4sprite local Colors = beat4sprite.Colors 75 | 76 | 77 | local effect = self[effectKey] effect(self) 78 | 79 | 80 | local Effect = self.Effect 81 | 82 | local magnitude, period, offset = Effect.Magnitude, Effect.Period, Effect.Offset 83 | 84 | self:setEffectMagnitude(magnitude):effectperiod(period) 85 | 86 | offset = offset * period self:effectoffset(offset) 87 | 88 | 89 | if #Colors < 2 then return self end local color1, color2 = Colors[1], Colors[2] 90 | 91 | self:effectcolor1(color1):effectcolor2(color2) 92 | 93 | 94 | return self 95 | 96 | end 97 | 98 | local function setEffectOffset( self, vector ) 99 | 100 | self.Effect.Offset = - vector.x + vector.y return self 101 | 102 | end 103 | 104 | 105 | -- Get the delay time based off a vector. 106 | -- This is used if there's an initial sleep to set a pattern on the planned sequence of tweens. 107 | 108 | local function delayFromVector( self, vector ) return vector.x + vector.y end 109 | 110 | 111 | local function scaleToScreen(self) 112 | 113 | local scale = SCREEN_HEIGHT / self:GetHeight() self:zoom(scale) return self 114 | 115 | end 116 | 117 | local function fitInScreen(self) return scaleToScreen(self):Center() end 118 | 119 | 120 | local function queueCommands(self) 121 | 122 | local commands = self.beat4sprite.Commands self:queueCommands(commands) 123 | 124 | end 125 | 126 | local function init( self, builder ) 127 | 128 | local beat4sprite = builder self.beat4sprite = builder 129 | 130 | 131 | local filter = beat4sprite.Filter local alpha = beat4sprite.Alpha or 1 132 | 133 | self:SetTextureFiltering(filter) self:GetParent():diffusealpha(alpha) 134 | 135 | 136 | self:setupTimers():onGameplay(self) setupEffect(self):periodSetup() queueCommands(self) 137 | 138 | return self 139 | 140 | end 141 | 142 | 143 | local merge = { 144 | 145 | configRate = configRate, BPM_Rate = BPM_Rate, 146 | 147 | init = init, isOnGameplay = isOnGameplay, onGameplay = onGameplay, 148 | 149 | setupEffect = setupEffect, setEffect = setEffect, setEffectOffset = setEffectOffset, 150 | 151 | scaleToScreen = scaleToScreen, fitInScreen = fitInScreen, delayFromVector = delayFromVector, 152 | 153 | rate = rate, statesRate = statesRate, tweenRate = tweenRate, 154 | 155 | periodRate = periodRate, periodSetup = periodSetup 156 | 157 | } 158 | 159 | Astro.Table.merge( beat4sprite.Actor, merge ) -------------------------------------------------------------------------------- /Actor/Sprite.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Table 3 | 4 | 5 | local function hasAnimationType( self, name ) return self.beat4sprite:hasAnimationType(name) end 6 | 7 | 8 | local function pathMatrix(self) 9 | 10 | local path = self:GetTexture():GetPath() if not FILEMAN:DoesFileExist(path) then return end 11 | 12 | return tapLua.spriteMatrix(path) 13 | 14 | end 15 | 16 | -- To get the smallest path matrix number use math.min( Vector:unpack() ). 17 | 18 | 19 | local function defaultStyle(self) 20 | 21 | local states = self.beat4sprite.States local first, last = states.First, states.Last 22 | 23 | if first == last then return { { Frame = first } } end 24 | 25 | 26 | local p = {} local step = last > first and 1 or -1 27 | 28 | for i = first, last, step do p[#p+1] = { Frame = i } end return p 29 | 30 | end 31 | 32 | local Styles = { 33 | 34 | pingPong = function(self) 35 | 36 | local p = defaultStyle(self) local reversed = astro.reversed(p) 37 | 38 | astro.add( p, reversed ) return p 39 | 40 | end 41 | 42 | } 43 | 44 | local function statesProperties(self) 45 | 46 | local states = self.beat4sprite.States local types = states.Types 47 | 48 | for i,v in ipairs(types) do 49 | 50 | local style = Styles[v] if style then return style(self) end 51 | 52 | end 53 | 54 | return defaultStyle(self) 55 | 56 | end 57 | 58 | 59 | local function cycleState( self, state ) return math.ceil(state) % self:GetNumStates() end 60 | 61 | local function statesRate(self) return self.beat4sprite.States.Rate * self.statesDelay * self:rate() * 4 end 62 | 63 | 64 | local function initSprite(self) 65 | 66 | self:set_use_effect_clock_for_texcoords(true) 67 | 68 | local n = self:GetNumStates() if n < 2 then self.statesDelay = 1 return self end 69 | 70 | if self:hasAnimationType("Idle") then self:animate(false) end self.statesDelay = 1 / n 71 | 72 | return self 73 | 74 | end 75 | 76 | local function initParticle( self, builder, i ) 77 | 78 | local zoom = builder:zoom() self:init(builder):initSprite():zoom(zoom) 79 | 80 | local properties = statesProperties(self) self:SetStateProperties(properties) 81 | 82 | if not self:hasAnimationType("Position") then return end 83 | 84 | local s = self:cycleState(i) self:setstate(s) 85 | 86 | end 87 | 88 | 89 | -- Update functions. 90 | 91 | local function updateStateDelay(self) 92 | 93 | local isIdle = self:hasAnimationType("Idle") if isIdle then return self end 94 | 95 | 96 | local n = self:GetNumStates() if n <= 1 then return self end 97 | 98 | 99 | local last = self.lastStatesDelay local current = self:statesRate() 100 | 101 | if last == current then return self end 102 | 103 | 104 | self:SetAllStateDelays(current) self.lastStatesDelay = current 105 | 106 | end 107 | 108 | 109 | local Sprite = beat4sprite.Sprite 110 | 111 | local merge = { 112 | 113 | hasAnimationType = hasAnimationType, initSprite = initSprite, initParticle = initParticle, 114 | 115 | pathMatrix = pathMatrix, updateStateDelay = updateStateDelay, statesProperties = statesProperties, 116 | 117 | setInitialState = setInitialState, statesRate = statesRate, cycleState = cycleState 118 | 119 | } 120 | 121 | astro.merge( Sprite, merge ) -------------------------------------------------------------------------------- /Actor/Update.lua: -------------------------------------------------------------------------------- 1 | 2 | local function rainbowBlink( timer, self ) 3 | 4 | local color = tapLua.Color.random() self:diffuse(color) timer.Time = 0 5 | 6 | end 7 | 8 | local function setupTimers(self) 9 | 10 | local beat4sprite = self.beat4sprite local colors = beat4sprite.Colors 11 | 12 | if colors.Type == "RainbowBlink" then self:timer( 2, rainbowBlink ) end 13 | 14 | return self 15 | 16 | end 17 | 18 | 19 | -- Update functions. 20 | 21 | local function updateRainbow(self) 22 | 23 | local beat4sprite = self.beat4sprite local colors = beat4sprite.Colors 24 | 25 | if colors.Type ~= "Rainbow" then return self end 26 | 27 | 28 | local time = colors.updateTime or 0 local rate = self:GetEffectDelta() / self:periodRate() 29 | 30 | colors.updateTime = time + rate * 0.25 31 | 32 | 33 | local hue = time % 360 local color = HSV( hue, 1, 1 ) 34 | 35 | self:diffuse(color) return self 36 | 37 | end 38 | 39 | 40 | local merge = { setupTimers = setupTimers, updateRainbow = updateRainbow } 41 | 42 | Astro.Table.merge( beat4sprite.Actor, merge ) -------------------------------------------------------------------------------- /Builder/Builder.lua: -------------------------------------------------------------------------------- 1 | 2 | -- It's a builder that returns actors based on different keys and values. 3 | 4 | local Vector = Astro.Vector 5 | 6 | local astro = Astro.Table local deepCopy = astro.Copy.deep 7 | 8 | 9 | local Builder = {} 10 | 11 | local create -- Create function for each builder. 12 | 13 | local function isSingle(tbl) return #tbl == 0 end 14 | 15 | local function wrap(input) return isSingle(input) and { input } or input end 16 | 17 | 18 | -- Array of builders functions. 19 | 20 | local function Load( builders ) 21 | 22 | if not builders.Load then builders = Builder(builders) end 23 | 24 | local wrap = wrap(builders) local t = beat4sprite.ActorFrame {} 25 | 26 | for i,v in ipairs(wrap) do t[#t+1] = v:Load() end 27 | 28 | return t 29 | 30 | end 31 | 32 | local function merge( builders, input ) 33 | 34 | if not input then return builders end local wrap = wrap(input) 35 | 36 | local merged = tapLua.deepMerge( builders, wrap ) 37 | 38 | return Builder(merged) 39 | 40 | end 41 | 42 | 43 | local __index = { Load = Load, merge = merge } 44 | 45 | local function __call( Builder, input ) 46 | 47 | local wrap = wrap(input) astro.Meta.setIndex( wrap, __index ) 48 | 49 | for i,v in ipairs(wrap) do 50 | 51 | v.Scale = v.Scale or input.Scale wrap[i] = create(v) 52 | 53 | end 54 | 55 | return isSingle(input) and wrap[1] or wrap 56 | 57 | end 58 | 59 | 60 | setmetatable( Builder, { __call = __call } ) 61 | 62 | 63 | local function defaults() 64 | 65 | return { 66 | 67 | Scale = SCREEN_HEIGHT / 720, -- The graphics scale, 720p being the default. 68 | 69 | Zoom = 1, Rate = 1, Colors = {}, 70 | 71 | States = { First = 1, Last = 1, Rate = 1, Types = {} }, 72 | 73 | Effect = { Magnitude = Vector(), Offset = 0, Period = 1 }, 74 | 75 | Layers = {}, Script = "Tile/Tile" 76 | 77 | } 78 | 79 | end 80 | 81 | local metaBuilder 82 | 83 | create = function(input) 84 | 85 | local meta = { __index = metaBuilder, input = input } 86 | 87 | 88 | local builder = tapLua.deepMerge( defaults(), input ) 89 | 90 | setmetatable( builder, meta ) 91 | 92 | 93 | builder.Commands = Builder.Commands( input ) 94 | 95 | return builder:setup() 96 | 97 | end 98 | 99 | 100 | -- Set a default background using the back layer using the texture. 101 | 102 | local function morphBackground(self) 103 | 104 | local script = self.Script 105 | 106 | if not script:match("Morph/") or script:match("Morph/Split") then return end 107 | 108 | 109 | local layers = self.Layers if layers.Back then return end 110 | 111 | local Texture = self.Texture layers.Back = Builder.Background(Texture):Load() 112 | 113 | end 114 | 115 | function Builder:Load() 116 | 117 | if not self.Load then return Load(self) end -- If Builder.Load(builders) happens. 118 | 119 | morphBackground(self) 120 | 121 | 122 | local Builder = self local layers = Builder.Layers 123 | 124 | local Script = Builder.Script local main = loadfile(Script)(Builder) 125 | 126 | return beat4sprite.BaseFrame { 127 | 128 | InitCommand=function(self) 129 | 130 | local update = function() self:playcommand("UpdateFunction") end 131 | 132 | self:SetUpdateFunction(update) 133 | 134 | end, 135 | 136 | OnCommand=function(self) self:init(Builder) end, 137 | 138 | layers.Back, main, layers.Front 139 | 140 | } 141 | 142 | end 143 | 144 | 145 | function Builder:merge(input) 146 | 147 | if not input then return self end local copy = deepCopy( self:input() ) 148 | 149 | tapLua.deepMerge( copy, input ) return Builder(copy) 150 | 151 | end 152 | 153 | 154 | function Builder:input() return getmetatable(self).input end 155 | 156 | function Builder:zoom() return self.Zoom * self.Scale end 157 | 158 | function Builder:hasAnimationType(name) 159 | 160 | local types = self.States.Types return astro.contains( types, name ) 161 | 162 | end 163 | 164 | 165 | beat4sprite.Builder = Builder 166 | 167 | 168 | tapLua.FILEMAN.LoadDirectory( beat4sprite.Path .. "Builder/" ) 169 | 170 | metaBuilder = deepCopy(Builder) 171 | 172 | 173 | local function Retro(input) 174 | 175 | local scale = SCREEN_HEIGHT / 240 input.Scale = scale 176 | 177 | input.Filtering = false return Builder(input) 178 | 179 | end 180 | 181 | 182 | -- Builder templates for easy creation. 183 | 184 | local function Background( Texture ) 185 | 186 | return beat4sprite.Builder { Texture = Texture, Mirror = true, ScreenScale = true } 187 | 188 | end 189 | 190 | local function SongBackground( Texture ) 191 | 192 | local Texture = beat4sprite.songBackgroundPath() return background(Texture) 193 | 194 | end 195 | 196 | 197 | local t = { Retro = Retro, Background = Background, SongBackground = SongBackground } 198 | 199 | astro.merge( Builder, t ) -------------------------------------------------------------------------------- /Builder/Commands.lua: -------------------------------------------------------------------------------- 1 | 2 | local Meta = Astro.Table.Meta 3 | 4 | local isString = Astro.Type.isString 5 | 6 | -- It's the commands array containing the commands to be played. 7 | 8 | local function __call( Commands, input ) 9 | 10 | local commands = input.Commands or { "Rotation", "Colors" } 11 | 12 | if isString(commands) then commands = { commands } end 13 | 14 | return Meta.setIndex( commands, Commands ) -- __index == Commands 15 | 16 | end 17 | 18 | local Commands = setmetatable( {}, { __call = __call } ) 19 | 20 | 21 | Commands.find = Astro.Table.find 22 | 23 | function Commands:add(command) 24 | 25 | local value = self:find(command).value if value then return end 26 | 27 | table.insert( self, command ) 28 | 29 | end 30 | 31 | function Commands:sub(command) 32 | 33 | local i = self:find(command).key if not i then return end 34 | 35 | table.remove( self, i ) 36 | 37 | end 38 | 39 | beat4sprite.Builder.Commands = Commands 40 | -------------------------------------------------------------------------------- /Builder/Path.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Type local isTable = astro.isTable 3 | 4 | local isObject = tapLua.Type.isObject local resolvePath = tapLua.resolvePath 5 | 6 | 7 | local Builder = beat4sprite.Builder 8 | 9 | local function path( table, key, directory ) 10 | 11 | local path = table[key] local astro = path:Astro() 12 | 13 | 14 | local isAbsolute = astro:startsWith("[\\/]") 15 | 16 | if isAbsolute then return path end 17 | 18 | 19 | local isRelative = astro:startsWith("%.[\\/]") 20 | 21 | if isRelative then return tapLua.resolvePath(path) end 22 | 23 | 24 | return beat4sprite.Path .. directory .. path 25 | 26 | end 27 | 28 | local function setPath( self, key, folder ) 29 | 30 | folder = folder or key .. "s" folder = folder .. '/' 31 | 32 | 33 | local value = self[key] if not value or isObject(value) then return value end 34 | 35 | if not isTable(value) then self[key] = path( self, key, folder ) end 36 | 37 | 38 | for i,v in ipairs(value) do value[i] = path( value, i, folder ) end 39 | 40 | end 41 | 42 | local function wrapScriptPath(self) 43 | 44 | local script = self.Script local endsWith = script:Astro():endsWith("%.lua") 45 | 46 | if not endsWith then self.Script = script .. ".lua" end 47 | 48 | end 49 | 50 | function Builder:setPaths() 51 | 52 | wrapScriptPath(self) setPath( self, "Texture", "Resources" ) setPath( self, "Script" ) 53 | 54 | return self 55 | 56 | end -------------------------------------------------------------------------------- /Builder/Setup.lua: -------------------------------------------------------------------------------- 1 | 2 | local isColor = tapLua.Color.isColor local Config = beat4sprite.Config 3 | 4 | local astro = Astro.Type local isString = astro.isString 5 | 6 | astro = Astro.Table local Meta = astro.Meta 7 | 8 | local Builder = beat4sprite.Builder 9 | 10 | 11 | function Builder:setCommands(Commands) 12 | 13 | for i,v in ipairs(Commands) do if self[v] then self.Commands:add(v) end end 14 | 15 | return self 16 | 17 | end 18 | 19 | local onColor = { 20 | 21 | function(colors) return isColor(colors) and { colors } end, 22 | 23 | function(colors) 24 | 25 | local isString = isString(colors) if not isString then return end 26 | 27 | if colors:match("Rainbow") then return { Type = colors } end 28 | 29 | return { color(colors) } 30 | 31 | end, 32 | 33 | } 34 | 35 | function Builder:setColors() 36 | 37 | local colors = self.Colors 38 | 39 | for i,v in ipairs(onColor) do 40 | 41 | local new = v(colors) if new then self.Colors = new return self end 42 | 43 | end 44 | 45 | for i,v in ipairs(colors) do colors[i] = isString(v) and color(v) or v end 46 | 47 | return self 48 | 49 | end 50 | 51 | local function depthRate(self) 52 | 53 | local valid = { "Bounce", "Cascade", "Helix", "Spread" } local script = self.Script 54 | 55 | local function isValid( k, v ) return script:match(v) end 56 | 57 | if not astro.contains( valid, isValid ) then return end 58 | 59 | self.Rate = self.Rate * 4 60 | 61 | end 62 | 63 | local function depthColors(self) 64 | 65 | local __index = { Color.White, Color.White } local Colors = self.Colors 66 | 67 | Meta.setIndex( Colors, __index ) 68 | 69 | end 70 | 71 | local function scaleQuantity(self) 72 | 73 | local n = self.Quantity if self.Centered then return n end 74 | 75 | n = n * SCREEN_WIDTH / SCREEN_HEIGHT n = math.ceil(n) self.Quantity = n 76 | 77 | return n 78 | 79 | end 80 | 81 | function Builder:onDepth() 82 | 83 | local FOV = Config.Depth.FOV 84 | 85 | local isDepth = self.Script:match("Depth") if not isDepth then return self end 86 | 87 | self.FOV = self.FOV or FOV self.scaleQuantity = scaleQuantity 88 | 89 | depthColors(self) depthRate(self) return self 90 | 91 | end 92 | 93 | function Builder:setBlend() 94 | 95 | if self.Script:match("Texture") then return self end 96 | 97 | if self.Blend == true then self.Blend = Blend.Modulate end 98 | 99 | return self 100 | 101 | end 102 | 103 | function Builder:setFilter() 104 | 105 | local filter = self.Filter if filter == nil then self.Filter = true end 106 | 107 | end 108 | 109 | 110 | local function wasSetup(self) return getmetatable(self).setup end 111 | 112 | local function finishSetup(self) getmetatable(self).setup = true end 113 | 114 | 115 | function Builder:setup() 116 | 117 | if wasSetup(self) then return self end 118 | 119 | self:setPaths():setSpriteStates() self:onDepth():setColors():setBlend():setFilter() 120 | 121 | finishSetup(self) return self 122 | 123 | end -------------------------------------------------------------------------------- /Builder/Sprite.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Type local isNumber = astro.isNumber local isString = astro.isString 3 | 4 | local Builder = beat4sprite.Builder 5 | 6 | 7 | local function onStatesNumber(self) 8 | 9 | local states = self.States if not isNumber(states) then return end 10 | 11 | local n = states self.States = { First = n, Last = n, Rate = 1, Types = {} } 12 | 13 | end 14 | 15 | local keys = { "First", "Last" } 16 | 17 | local function substract(self) 18 | 19 | local states = self.States 20 | 21 | for i,v in ipairs(keys) do states[v] = states[v] - 1 end 22 | 23 | end 24 | 25 | local function stateTypes(self) 26 | 27 | local states = self.States local types = states.Types 28 | 29 | if not isString(types) then return end states.Types = { types } 30 | 31 | end 32 | 33 | function Builder:setSpriteStates() 34 | 35 | onStatesNumber(self) substract(self) stateTypes(self) 36 | 37 | return self 38 | 39 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/W7W32691S) 2 | 3 | # beat4sprite 4 | beat4sprite is a **library** to create animations in the StepMania engine 5 | inspired by the classic DDR PSX background animations and sprites. 6 | 7 | beat4sprite has been tested through from StepMania 5.0.12 to 5.3 / OutFox. 8 | 9 | [beat4sprite on Youtube](https://youtu.be/NKW4aDbaQvM) 10 | 11 | ## How to install 12 | 13 | 0. Make sure you have [tapLua](https://github.com/EngineMachiner/tapLua). 14 | 15 | ### OutFox 16 | 17 | 1. Copy the beat4sprite folder into the Modules folder of fallback. 18 | 2. Load the tapLua and beat4sprite module once in OutFox through fallback's first screen.
19 | 20 | For example the ScreenInit overlay script should look like this using LoadModule():

21 | 22 | 23 | ### StepMania 24 | 25 | 1. Copy the beat4sprite folder in your "Stepmania/Scripts" folder. 26 | 2. Reload scripts once at first screen if something goes wrong. 27 | 28 | ## How to create 29 | If you want to create your own animations you can check the [Wiki](https://github.com/EngineMachiner/beat4sprite/wiki). 30 | 31 | ## Origin 32 | beat4sprite began as a personal project in 2013.
33 | I used to play with simple BGAnimations or RandomMovies ( Gameplay backgrounds ) on StepMania 3.9, mostly stuff from DDR or ITG. I was interested in the structure of the sprites and how the game read them. 34 | 35 | I got inspired and I wanted to use the retro PSX animations so I tried making videos of them and that didn't blend that well with the song beat. 36 | 37 | Then I considered video encoding, quality and file size not being the best so I moved to scripting and writing code (visually scripting) so I could draw 38 | my own animations. 39 | 40 | Examples made with this library can be found through my repositories.
41 | For more information about the API check the Wiki. 42 | 43 | ## CREDITS 44 | - Project Moondance developers 45 | - RenTheHumanCat 46 | - MadkaT 47 | - leadbman 48 | - Retrozaid 49 | -------------------------------------------------------------------------------- /Resources/Global/Masks/Kaleidoscope/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EngineMachiner/beat4sprite/785e4908ba0d7dcde80cb049199dfe0166e83123/Resources/Global/Masks/Kaleidoscope/1.png -------------------------------------------------------------------------------- /Scripts/Cycle.lua: -------------------------------------------------------------------------------- 1 | 2 | local builder = ... local Actors = builder.Actors local Reversed = builder.Reversed 3 | 4 | local t = beat4sprite.ActorFrame { 5 | 6 | OnCommand=function(self) 7 | 8 | self:init(builder):playcommand("CycleSetup"):queuecommand("FinishCycleSetup") 9 | 10 | end 11 | 12 | } 13 | 14 | 15 | local time = 0 local times = {} 16 | 17 | local function cycleTimeLeft(i) 18 | 19 | local t = 0 local a = i + 1 local b = #times 20 | 21 | for i = a, b do if times[i] then t = t + times[i] end end 22 | 23 | return t 24 | 25 | end 26 | 27 | for i,v in ipairs(Actors) do 28 | 29 | local sleep = {} if not Reversed then i = #Actors - i + 1 end 30 | 31 | t[i] = v .. { 32 | 33 | CycleSetupCommand=function(self) 34 | 35 | local timeLeft = self:GetTweenTimeLeft() times[i] = timeLeft 36 | 37 | sleep[1] = time time = time + timeLeft self:stoptweening() 38 | 39 | end, 40 | 41 | FinishCycleSetupCommand=function(self) 42 | 43 | sleep[2] = cycleTimeLeft(i) self.CycleTimes = sleep self.Index = i 44 | 45 | end 46 | 47 | } 48 | 49 | end 50 | 51 | return t -------------------------------------------------------------------------------- /Scripts/Kaleidoscope/Pair.lua: -------------------------------------------------------------------------------- 1 | 2 | local Actor 3 | 4 | return tapLua.ActorFrame { 5 | 6 | beat4sprite.Load( "Kaleidoscope/Triangle" )(...) .. { 7 | 8 | PostInitCommand=function(self) 9 | 10 | Actor = self self:GetParent():GetParent():queuecommand("SetTarget") 11 | 12 | local size = self:GetSize() self:setPos( size * 0.5 ) 13 | 14 | self:GetParent():setSizeVector(size) 15 | 16 | end 17 | 18 | }, 19 | 20 | Def.ActorProxy { 21 | 22 | SetTargetCommand=function(self) self:SetTarget(Actor):zoomx(-1) end 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /Scripts/Kaleidoscope/Polygon.lua: -------------------------------------------------------------------------------- 1 | 2 | local builder = ... builder.Sides = builder.Sides or 8 local Sides = builder.Sides 3 | 4 | if Sides < 3 then error("A polygon must have more than 2 sides!") end 5 | 6 | 7 | local zoom = SCREEN_WIDTH / SCREEN_HEIGHT 8 | 9 | local t = tapLua.ActorFrame { 10 | 11 | OnCommand=function(self) self:Center():zoom( 0.425 + zoom ) end, 12 | 13 | beat4sprite.Load( "Kaleidoscope/Pair" )( builder ) .. { OnCommand=function(self) Actor = self end } 14 | 15 | } 16 | 17 | 18 | local Angle = 180 / Sides 19 | 20 | for i = 1, Sides do 21 | 22 | t[#t+1] = Def.ActorProxy { 23 | 24 | SetTargetCommand=function(self) self:SetTarget(Actor):rotationz( i * Angle * 2 ) end 25 | 26 | } 27 | 28 | end 29 | 30 | 31 | return t -------------------------------------------------------------------------------- /Scripts/Kaleidoscope/Tile.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector 3 | 4 | 5 | local builder = ... builder.Sides = 6 builder.Scroll = builder.Scroll or Vector( 1, -1 ) 6 | 7 | builder.Zoom = 0.25 8 | 9 | 10 | local Hexagon local offsetX = 16.5 11 | 12 | local function proxy( f ) 13 | 14 | return Def.ActorProxy { OnCommand=function(self) self:SetTarget(Hexagon) f(self) end } 15 | 16 | end 17 | 18 | local t = tapLua.ActorFrame { 19 | 20 | beat4sprite.Load( "Kaleidoscope/Polygon" )( builder ) .. { 21 | 22 | OnCommand=function(self) Hexagon = self self:x(0):CenterY():zoom(1) end 23 | 24 | }, 25 | 26 | Def.ActorFrame { 27 | 28 | OnCommand=function(self) self:x( - offsetX ) end, 29 | 30 | proxy( function(self) self:x( SCREEN_WIDTH - offsetX ) end ), 31 | 32 | proxy( function(self) self:CenterX():y( - SCREEN_CENTER_Y ) end ), 33 | 34 | proxy( function(self) self:CenterX():y( SCREEN_CENTER_Y ) end ) 35 | 36 | } 37 | 38 | } 39 | 40 | 41 | local Texture local Hexagons = t .. { OnCommand=function(self) self:x( - offsetX ) end } 42 | 43 | local Path = beat4sprite.Path .. "Scripts/Tile/Tile.lua" 44 | 45 | return tapLua.ActorFrame { 46 | 47 | tapLua.ActorFrameTexture { 48 | 49 | Hexagons, 50 | 51 | OnCommand=function(self) 52 | 53 | local size = tapLua.screenSize() - Vector( offsetX * 2 ) 54 | 55 | self:setSizeVector(size):EnableAlphaBuffer(true):EnableDepthBuffer(true):Create() 56 | 57 | Texture = self:GetTexture() self:GetParent():queuecommand("LoadHexagon") 58 | 59 | end 60 | 61 | }, 62 | 63 | tapLua.ActorFrame { 64 | 65 | LoadHexagonCommand=function(self) 66 | 67 | beat4sprite.Arguments = beat4sprite.Builder { Texture = Texture, Zoom = 0.375 } 68 | 69 | self:AddChildFromPath(Path) beat4sprite.Arguments = nil self:queuecommand("On") 70 | 71 | end 72 | 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /Scripts/Kaleidoscope/Triangle.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local builder = ... local Texture = builder.Texture 3 | 4 | local Sides = builder.Sides local Scroll = builder.Scroll or Vector(1) 5 | 6 | 7 | local Angle = 180 / Sides 8 | 9 | local Mask = beat4sprite.Path .. "Resources/Global/Masks/Kaleidoscope/" .. "1.png" 10 | 11 | 12 | local Background = beat4sprite.Builder.Background(Texture) Background.Scroll = Scroll 13 | 14 | local zoom = builder.Zoom * 0.75 Background.Rate = 4 / zoom Background.Display = Vector( 1, 1 ) 15 | 16 | 17 | local isOffset = math.random(2) == 2 18 | 19 | Background.Output = { 20 | 21 | LoadSpriteCommand=function(self) 22 | 23 | self:customtexturerect( 0, 0, zoom, zoom ) if not isOffset then return end 24 | 25 | local size = self:GetSize() self:moveTextureBy( - size * 0.25 ) 26 | 27 | end 28 | 29 | } 30 | 31 | 32 | return tapLua.ActorFrame { 33 | 34 | OnCommand=function(self) self:Center() end, 35 | 36 | beat4sprite.Sprite { 37 | 38 | Texture = Mask, 39 | 40 | OnCommand=function(self) 41 | 42 | -- Happy trigonometry =D... 43 | 44 | local Angle = math.rad(Angle) local w = self:GetHeight() * math.tan(Angle) self:SetWidth(w) 45 | 46 | self:MaskSource(true):scaleToScreen() self:zoom( self:GetZoom() * 0.5 )--:SetTextureFiltering(false) 47 | 48 | local size = self:GetZoomedSize() self:GetParent():setSizeVector(size):playcommand("PostInit") 49 | 50 | end 51 | 52 | }, 53 | 54 | tapLua.ActorFrameTexture { 55 | 56 | OnCommand=function(self) 57 | 58 | local size = tapLua.screenSize() self:setSizeVector(size) 59 | 60 | self:EnableAlphaBuffer(true):EnableDepthBuffer(true):Create() 61 | 62 | subTexture = self:GetTexture() 63 | 64 | end, 65 | 66 | Background:Load() 67 | 68 | }, 69 | 70 | beat4sprite.Sprite { 71 | 72 | OnCommand=function(self) 73 | 74 | self:SetTexture(subTexture) self:init(builder):scaleToScreen():invertedMaskDest() 75 | 76 | end 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /Scripts/Morph/Bob.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local defaultLayers = beat4sprite.Config.MorphLayers 3 | 4 | 5 | local builder = ... local Texture = builder.Texture local Effect = builder.Effect 6 | 7 | local Type = builder.Type or 3 local Layers = Effect.Layers or defaultLayers -- Number of layers. 8 | 9 | 10 | local types = { 11 | 12 | { Crop = Vector { y = 1 }, Magnitude = Vector( 1.25 ) }, 13 | 14 | { Crop = Vector(1), Magnitude = Vector { y = 1.25 } }, 15 | 16 | { Crop = Vector( 1, 1 ), Magnitude = Vector( 2, 1 ) } 17 | 18 | } 19 | 20 | local selected = types[Type] local Fade = 0.03 21 | 22 | local crop = beat4sprite.Load("Morph/Crop")("Centered") 23 | 24 | 25 | local t = beat4sprite.ActorFrame {} 26 | 27 | for i = 1, Layers do 28 | 29 | local crop = crop( i, Layers ) 30 | 31 | t[i] = beat4sprite.Sprite { 32 | 33 | Texture = Texture, 34 | 35 | OnCommand=function(self) 36 | 37 | self:init(builder):fitInScreen() 38 | 39 | 40 | local cropVector = selected.Crop 41 | 42 | local fade = cropVector * Fade self:fadeHorizontally( fade.x ):fadeVertically( fade.y ) 43 | local crop = cropVector * crop self:cropHorizontally( crop.x ):cropVertically( crop.y ) 44 | 45 | 46 | local Effect = self.Effect local magnitude = selected.Magnitude * i * 1.333 47 | 48 | Effect.Magnitude = Effect.Magnitude + magnitude / self:aspectRatio() 49 | 50 | Effect.Offset = Effect.Offset - 0.25 self:setEffect("bob") 51 | 52 | end 53 | 54 | } 55 | 56 | end 57 | 58 | return t -------------------------------------------------------------------------------- /Scripts/Morph/Crop.lua: -------------------------------------------------------------------------------- 1 | 2 | local crops = { 3 | 4 | -- The returned value can be set for both horizontally or vertically and it's cropped centered based on the layer. 5 | 6 | Centered = function( i, layers ) 7 | 8 | -- Multiplied by 2 because it's the cropping on both sides. 9 | 10 | local n = layers + 1 n = n * 2 return i / n 11 | 12 | end, 13 | 14 | 15 | -- Returns the values in an array depending on the axis crop value. The cropping looks like slices across. 16 | 17 | Sliced = function( step, fade ) 18 | 19 | return function(value) 20 | 21 | local v = value if v == 0 then return { 0, 0 } end 22 | 23 | return { v - step - fade * 2, 1 - v - 0.016 } 24 | 25 | end 26 | 27 | end 28 | 29 | } 30 | 31 | return crops[...] -------------------------------------------------------------------------------- /Scripts/Morph/Flag.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local defaultLayers = beat4sprite.Config.MorphLayers 3 | 4 | 5 | local builder = ... local Texture = builder.Texture local Effect = builder.Effect 6 | 7 | local Type = builder.Type or 1 local Layers = Effect.Layers or defaultLayers 8 | 9 | local Skip = builder.Skip local Bob = builder.Bob 10 | 11 | 12 | local Fade = 0.03 13 | 14 | local types = { 15 | 16 | { Crop = Vector { y = 1 }, Magnitude = Vector(1) }, 17 | 18 | { Crop = Vector(1), Magnitude = Vector { y = 1 } }, 19 | 20 | } 21 | 22 | local selected = types[Type] local Step = 0.75 / Layers -- Layer slice. 23 | 24 | local crop = beat4sprite.Load("Morph/Crop")("Sliced") crop = crop( Step, Fade ) 25 | 26 | 27 | local function cos(a) a = math.rad(a) return math.cos(a) end 28 | local function sin(a) a = math.rad(a) return math.sin(a) end 29 | 30 | 31 | local t = beat4sprite.ActorFrame {} 32 | 33 | for i = 1, Layers do 34 | 35 | local angle = i * Step * 360 local radius = 30 36 | 37 | t[i] = beat4sprite.Sprite { 38 | 39 | Texture = Texture, 40 | 41 | OnCommand=function(self) 42 | 43 | self:init(builder):fitInScreen() local cropVector = selected.Crop 44 | 45 | local fade = cropVector * Fade self:fadeHorizontally( fade.x ):fadeVertically( fade.y ) 46 | 47 | 48 | local v = cropVector * i / Layers 49 | 50 | local x = crop( v.x ) self:cropleft( x[1] ):cropright( x[2] ) 51 | local y = crop( v.y ) self:croptop( y[1] ):cropbottom( y[2] ) 52 | 53 | 54 | local command = Bob and "Bob" or "Cycle" self:queuecommand(command) 55 | 56 | end, 57 | 58 | BobCommand=function(self) 59 | 60 | local Effect = self.Effect local magnitude = selected.Magnitude * 32 61 | 62 | Effect.Magnitude = Effect.Magnitude + magnitude / self:aspectRatio() 63 | 64 | 65 | local i = i / Layers i = i * 0.75 66 | 67 | Effect.Offset = Effect.Offset + i - 0.25 68 | 69 | 70 | self:setEffect("bob") 71 | 72 | end, 73 | 74 | CycleCommand=function(self) 75 | 76 | local x, y = cos(angle), sin(angle) 77 | 78 | local pos = tapLua.center() + radius * Vector( x, y ) 79 | 80 | 81 | local step = Skip and Step * 32 or Step local time = self:periodRate() * step 82 | 83 | local tween = Skip and self.sleep or self.linear tween( self, time ) self:setPos(pos) 84 | 85 | 86 | angle = angle + step * 360 angle = angle % 360 self:queuecommand("Cycle") 87 | 88 | end 89 | 90 | } 91 | 92 | end 93 | 94 | return t -------------------------------------------------------------------------------- /Scripts/Morph/Pulse.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local defaultLayers = beat4sprite.Config.MorphLayers 3 | 4 | 5 | local builder = ... local Texture = builder.Texture local Effect = builder.Effect 6 | 7 | local Layers = Effect.Layers or defaultLayers local Fade = 0.03 8 | 9 | local crop = beat4sprite.Load("Morph/Crop")("Centered") 10 | 11 | 12 | local t = beat4sprite.ActorFrame {} 13 | 14 | for i = 1, Layers do 15 | 16 | local crop = crop( i, Layers ) 17 | 18 | t[i] = beat4sprite.Sprite { 19 | 20 | Texture = Texture, 21 | 22 | OnCommand=function(self) 23 | 24 | self:init(builder):fitInScreen() 25 | 26 | self:fadeHorizontally(Fade):fadeVertically(Fade) 27 | self:cropHorizontally(crop):cropVertically(crop) 28 | 29 | 30 | local y = i / Layers y = y / self:aspectRatio() 31 | 32 | local magnitude = Vector( 1, 1 ) + Vector { y = y * 0.833 } 33 | 34 | local Effect = self.Effect Effect.Magnitude = Effect.Magnitude + magnitude 35 | 36 | 37 | self:setEffect("pulse") 38 | 39 | end 40 | 41 | } 42 | 43 | end 44 | 45 | return t -------------------------------------------------------------------------------- /Scripts/Morph/Spacing.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local defaultLayers = beat4sprite.Config.MorphLayers 3 | 4 | 5 | local builder = ... local Texture = builder.Texture local Effect = builder.Effect 6 | 7 | local Layers = Effect.Layers or defaultLayers * 0.5 local Displacement = Effect.Displacement or Vector(1.5) 8 | 9 | 10 | local Fade = 0.03 local Step = 0.75 / Layers 11 | 12 | local crop = beat4sprite.Load("Morph/Crop")("Sliced") crop = crop( Step, Fade ) 13 | 14 | 15 | local t = beat4sprite.ActorFrame { OnCommand=function(self) self:Center() end } 16 | 17 | for i = 1, Layers do 18 | 19 | t[i] = beat4sprite.Sprite { 20 | 21 | Texture = Texture, 22 | 23 | OnCommand=function(self) 24 | 25 | self:init(builder):scaleToScreen() self:fadeHorizontally(Fade):fadeVertically(Fade) 26 | 27 | 28 | local i = i / Layers local crop = crop(i) 29 | 30 | self:croptop( crop[1] ):cropbottom( crop[2] ) 31 | 32 | 33 | self:queuecommand("Cycle") 34 | 35 | end, 36 | 37 | CycleCommand=function(self) 38 | 39 | local time = self:periodRate() * 0.5 40 | 41 | local Displacement = i % 2 == 0 and - Displacement or Displacement 42 | 43 | self:linear(time):setPos( Displacement ):smooth(time):setPos( - Displacement ) 44 | 45 | self:queuecommand("Cycle") 46 | 47 | end 48 | 49 | } 50 | 51 | end 52 | 53 | return t -------------------------------------------------------------------------------- /Scripts/Morph/Split.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[ 3 | 4 | Returns a 4 set of sprites with a texture that splits across the screen diagonally. 5 | 6 | Distribution: 7 | 8 | | 1 | 2 | 9 | | 3 | 4 | 10 | 11 | ]] 12 | 13 | local Vector = Astro.Vector local builder = ... 14 | 15 | local Reversed = builder.Reversed 16 | 17 | 18 | local Crop = { 19 | 20 | function(self) self:cropbottom(0.5):cropright(0.5) end, 21 | function(self) self:cropbottom(0.5):cropleft(0.5) end, 22 | 23 | function(self) self:croptop(0.5):cropright(0.5) end, 24 | function(self) self:croptop(0.5):cropleft(0.5) end 25 | 26 | } 27 | 28 | 29 | local t = Def.ActorFrame {} 30 | 31 | local function direction(x) return x * 2 - 3 end 32 | 33 | local function add( row, column ) 34 | 35 | local positions local startPos = tapLua.center() 36 | 37 | local row, column = direction(row), direction(column) 38 | 39 | local function endPos(self) 40 | 41 | local h = self:GetZoomedHeight() * 0.5 42 | 43 | return startPos + Vector( h * column, h * row ) 44 | 45 | end 46 | 47 | 48 | local i = #t + 1 49 | 50 | t[i] = beat4sprite.Sprite { 51 | 52 | Texture = builder.Texture, 53 | 54 | OnCommand=function(self) 55 | 56 | self:init(builder):fitInScreen() local crop = Crop[i] crop(self) 57 | 58 | 59 | local endPos = endPos(self) positions = { startPos, endPos } 60 | 61 | if Reversed then positions = { endPos, startPos } end 62 | 63 | 64 | self:playcommand("Prepare") 65 | 66 | end, 67 | 68 | PrepareCommand=function(self) 69 | 70 | local startPos = positions[1] self:setPos(startPos) 71 | 72 | end, 73 | 74 | MotionCommand=function(self) 75 | 76 | local rate = self:tweenRate() local endPos = positions[2] 77 | 78 | self:linear(rate):setPos(endPos) 79 | 80 | end 81 | 82 | } 83 | 84 | end 85 | 86 | for i = 1, 2 do for j = 1, 2 do add( i, j ) end end 87 | 88 | return t -------------------------------------------------------------------------------- /Scripts/Morph/Stretch.lua: -------------------------------------------------------------------------------- 1 | 2 | -- This effect is an example of clock inaccuracy? 3 | 4 | local Vector = Astro.Vector local planeAxes = Vector.planeAxes 5 | 6 | local builder = ... local Texture = builder.Texture local Type = builder.Type or 2 7 | 8 | local directions = { Vector(1), Vector { y = 1 } } local direction = directions[Type] 9 | 10 | 11 | local subTexture, scroll, pos 12 | 13 | return tapLua.ActorFrame { 14 | 15 | OnCommand=function(self) self:Center() end, 16 | 17 | beat4sprite.ScreenQuad() .. { 18 | 19 | OnCommand=function(self) 20 | 21 | pos = - tapLua.screenSize() for i,v in ipairs(planeAxes) do pos[v] = pos[v] * direction[v] end 22 | 23 | self:init(builder):MaskSource(true):setPos(pos):queuecommand("Cycle") 24 | 25 | end, 26 | 27 | CycleCommand=function(self) 28 | 29 | local t = self:periodRate() * 4 30 | 31 | self:linear(t):xy( 0, 0 ):queuecommand("Prepare") 32 | 33 | self:linear(t):setPos(pos):queuecommand("Prepare"):queuecommand("Cycle") 34 | 35 | end, 36 | 37 | PrepareCommand=function(self) self:GetParent():queuecommand("Reverse") end 38 | 39 | }, 40 | 41 | tapLua.ActorFrameTexture { 42 | 43 | OnCommand=function(self) 44 | 45 | self:setSizeVector( tapLua.screenSize() ) 46 | 47 | self:EnableAlphaBuffer(true):EnableDepthBuffer(true):Create() 48 | 49 | subTexture = self:GetTexture() 50 | 51 | end, 52 | 53 | beat4sprite.Builder.Background(Texture):Load() 54 | 55 | }, 56 | 57 | beat4sprite.Sprite { 58 | 59 | OnCommand=function(self) 60 | 61 | self:SetTexture(subTexture):invertedMaskDest() self:init(builder):initSprite() 62 | 63 | local rectangle = Vector( 1, 1 ) - direction self:customtexturerect( 0, 0, rectangle:unpack() ) 64 | 65 | scroll = direction * 0.25 / self:periodRate() self:texcoordvelocity( scroll:unpack() ) 66 | 67 | end, 68 | 69 | ReverseCommand=function(self) 70 | 71 | scroll = - scroll self:texcoordvelocity( scroll:unpack() ) 72 | 73 | end 74 | 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /Scripts/Morph/Wag.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local defaultLayers = beat4sprite.Config.MorphLayers 3 | 4 | local builder = ... local Texture = builder.Texture local Effect = builder.Effect 5 | 6 | local Layers = Effect.Layers or defaultLayers local Fade = 0.03 7 | 8 | local crop = beat4sprite.Load("Morph/Crop")("Centered") 9 | 10 | 11 | local t = beat4sprite.ActorFrame {} 12 | 13 | for i = 1, Layers do 14 | 15 | local crop = crop( i, Layers ) 16 | 17 | t[i] = beat4sprite.Sprite { 18 | 19 | Texture = Texture, 20 | 21 | OnCommand=function(self) 22 | 23 | self:init(builder):fitInScreen() 24 | 25 | self:fadeHorizontally(Fade):fadeVertically(Fade) 26 | self:cropHorizontally(crop):cropVertically(crop) 27 | 28 | 29 | local Effect = self.Effect local magnitude = Vector { z = i * 0.833 } 30 | 31 | local aspectRatio = self:GetZoomedWidth() / self:GetZoomedHeight() 32 | 33 | Effect.Magnitude = Effect.Magnitude + magnitude / aspectRatio 34 | 35 | Effect.Offset = Effect.Offset - 0.25 self:setEffect("wag") 36 | 37 | end 38 | 39 | } 40 | 41 | end 42 | 43 | return t -------------------------------------------------------------------------------- /Scripts/Particles/Depth/Actors.lua: -------------------------------------------------------------------------------- 1 | 2 | local reverse = Astro.Table.Array.reverse local z = beat4sprite.Config.Depth.Range 3 | 4 | 5 | local builder = ... local FOV = builder.FOV local Texture = builder.Texture 6 | 7 | local Reversed = builder.Reversed local colors = builder.Colors 8 | 9 | local n = builder:scaleQuantity() 10 | 11 | 12 | local min, max = table.unpack(z) local depthLength = max - min 13 | 14 | if Reversed then z = reverse(z) colors = reverse(colors) end 15 | 16 | 17 | local function Frame( table ) 18 | 19 | return beat4sprite.ActorFrame { OnCommand=function(self) self:init(builder) end } .. table 20 | 21 | end 22 | 23 | local MainFrame = Frame { OnCommand=function(self) self:setupDepth(FOV) end } 24 | 25 | 26 | local function alpha(current) 27 | 28 | local length = 100 local max = max - length 29 | 30 | if current < min + length then 31 | 32 | current = current - min return current / length 33 | 34 | elseif current > max then 35 | 36 | current = current - max current = current / length 37 | 38 | return 1 - current 39 | 40 | end 41 | 42 | return 1 43 | 44 | end 45 | 46 | local function drawParticle(self) 47 | 48 | local z = self:GetParent():GetZ() + self:GetZ() 49 | 50 | 51 | local percent = z - min percent = percent / depthLength percent = 1 - percent 52 | 53 | local color = lerp_color( percent, colors[1], colors[2] ) self:diffuse(color) 54 | 55 | 56 | local alpha = alpha(z) alpha = math.max( 0, alpha ) alpha = math.min( 1, alpha ) 57 | 58 | self:diffusealpha(alpha) 59 | 60 | 61 | self:Draw() 62 | 63 | end 64 | 65 | local function children(self) 66 | 67 | local children = self:GetChild("") if #children == 0 then children = { children } end 68 | 69 | return children 70 | 71 | end 72 | 73 | local function draw(self) 74 | 75 | if not self.canDraw then return end 76 | 77 | local children = children(self) for k,v in pairs(children) do drawParticle(v) end 78 | 79 | end 80 | 81 | 82 | local sleep, depth 83 | 84 | local function ActorFrame(i) 85 | 86 | return Frame { 87 | 88 | OnCommand=function(self) self:SetDrawFunction(draw):zbuffer(true):queuecommand("Post") end, 89 | 90 | PostCommand=function(self) 91 | 92 | sleep = self.Sleep or 1 depth = self.Depth or 0 93 | 94 | if Reversed then depth = - depth end 95 | 96 | 97 | local rate = self:tweenRate() * 2 98 | 99 | local i = i - 1 i = i * sleep i = rate * i / n 100 | 101 | self:queuecommand("Prepare"):sleep(i):queuecommand("Motion"):queuecommand("Draw") 102 | 103 | end, 104 | 105 | DrawCommand=function(self) self.canDraw = true end, 106 | 107 | PrepareCommand=function(self) local z = z[2] + depth self:z(z) end, 108 | 109 | MotionCommand=function(self) 110 | 111 | local rate = self:tweenRate() local z = z[1] - depth 112 | 113 | self:linear(rate):z(z) self:queuecommand("Prepare"):queuecommand("Motion") 114 | 115 | end 116 | 117 | } 118 | 119 | end 120 | 121 | local function Particle( i, state ) 122 | 123 | state = state or i 124 | 125 | return beat4sprite.Sprite { 126 | 127 | Texture = Texture, 128 | 129 | OnCommand=function(self) self.Index = i self:initParticle( builder, state ) end 130 | 131 | } .. Sprite 132 | 133 | end 134 | 135 | return MainFrame, ActorFrame, Particle -------------------------------------------------------------------------------- /Scripts/Particles/Depth/Bounce.lua: -------------------------------------------------------------------------------- 1 | 2 | local builder = ... builder.Quantity = builder.Quantity or 9 3 | 4 | 5 | local Path = "Particles/Depth/Actors" 6 | 7 | local MainFrame, ActorFrame, Particle = beat4sprite.Load( Path )( builder ) 8 | 9 | 10 | local n = builder.Quantity 11 | 12 | for i = 1, n do 13 | 14 | local Particle = Particle(i) .. { 15 | 16 | OnCommand=function(self) 17 | 18 | local size = self:GetZoomedSize() 19 | 20 | local Effect = self.Effect Effect.Offset = Effect.Offset + i * 3 / n 21 | 22 | local Magnitude = Effect.Magnitude Magnitude.y = Magnitude.y - size.y * 1.5 23 | 24 | Effect.Period = Effect.Period * 0.5 self:setEffect("bounce") 25 | 26 | end 27 | 28 | } 29 | 30 | 31 | MainFrame[i] = ActorFrame(i) .. { 32 | 33 | Particle, 34 | 35 | PrepareCommand=function(self) 36 | 37 | local x = SCREEN_WIDTH x = math.random( - x, x ) 38 | 39 | local y = SCREEN_HEIGHT * 0.25 self:xy( x, y ) 40 | 41 | end 42 | 43 | } 44 | 45 | end 46 | 47 | 48 | return MainFrame -------------------------------------------------------------------------------- /Scripts/Particles/Depth/Cascade.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector 3 | 4 | local builder = ... builder.Quantity = builder.Quantity or 8 5 | 6 | 7 | local Path = "Particles/Depth/Actors" 8 | 9 | local MainFrame, ActorFrame, _Particle = beat4sprite.Load( Path )( builder ) 10 | 11 | 12 | local n = builder.Quantity local z = -48 13 | 14 | local function Particle( i, n ) 15 | 16 | return _Particle(i) .. { 17 | 18 | OnCommand=function(self) 19 | 20 | local offset = self:GetZoomedSize() + Vector { z = z } local n = n + 1 21 | 22 | local i = i - n * 0.5 local pos = offset * i self:setPos(pos) 23 | 24 | self.Z = pos.z 25 | 26 | end 27 | 28 | } 29 | 30 | end 31 | 32 | 33 | local function pos() 34 | 35 | local x = SCREEN_WIDTH * 2 x = math.random( - x, x ) 36 | local y = SCREEN_HEIGHT * 2 y = math.random( - y, y ) 37 | 38 | return x, y 39 | 40 | end 41 | 42 | for i = 1, n do 43 | 44 | local n = math.random( 2, 5 ) local z = math.abs(z) 45 | 46 | MainFrame[i] = ActorFrame(i) .. { 47 | 48 | OnCommand=function(self) self.Depth = z * n * 0.5 end, 49 | 50 | PrepareCommand=function(self) local x, y = pos() self:xy( x, y ) end 51 | 52 | } 53 | 54 | local t = MainFrame[i] for i = 1, n do t[i] = Particle( i, n ) end 55 | 56 | end 57 | 58 | return MainFrame -------------------------------------------------------------------------------- /Scripts/Particles/Depth/Helix.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector 3 | 4 | 5 | local builder = ... local Sprite = builder.Sprite or {} 6 | 7 | local Wise = builder.CounterWise and -1 or 1 builder.Centered = true 8 | 9 | 10 | local Display = builder.Display or function() return false end 11 | 12 | local States = builder.States.Display or function(i) return i + 1 end 13 | 14 | 15 | local Spin = builder.Spin local Effect = builder.Effect 16 | 17 | local FixedAngle = Effect.FixedAngle local Magnitude = builder.Effect.Magnitude 18 | 19 | if Spin and Magnitude.z == 0 then Magnitude.z = 45 * 0.5 end 20 | 21 | 22 | local Waves = builder.Waves or 1 local Whirl = builder.Whirl 23 | 24 | local Quantity = builder.Quantity or 8 if Whirl then Quantity = 32 end 25 | 26 | builder.Quantity = Quantity 27 | 28 | 29 | local n = builder:scaleQuantity() local angleOffset = builder.Angle or 0 30 | 31 | 32 | local Angle = 360 / n 33 | 34 | local function angle(i) 35 | 36 | local i = i - 1 local angle = i * Angle * Wise angle = angle % 360 37 | 38 | return math.rad(angle) 39 | 40 | end 41 | 42 | 43 | local Path = "Particles/Depth/Actors" 44 | 45 | local MainFrame, _ActorFrame, _Particle = beat4sprite.Load( Path )( builder ) 46 | 47 | local function Frame( table ) 48 | 49 | return beat4sprite.ActorFrame { OnCommand=function(self) self:init(builder) end } .. table 50 | 51 | end 52 | 53 | 54 | local function ActorFrame(i) 55 | 56 | local ActorFrame = _ActorFrame(i) local Condition = not Display(i) 57 | 58 | local function OnCommand(self) if Spin then self:setEffect("spin") end end 59 | 60 | return Frame { Condition = Condition, OnCommand = OnCommand, ActorFrame } 61 | 62 | end 63 | 64 | local function Particle( i, s ) 65 | 66 | return Frame { 67 | 68 | _Particle( i, s ) .. Sprite, 69 | 70 | OnCommand=function(self) 71 | 72 | self:GetChild("").Angle = Angle 73 | 74 | if not Spin or not FixedAngle then return end 75 | 76 | local Effect = self.Effect local Magnitude = Effect.Magnitude 77 | 78 | Magnitude.z = - Magnitude.z self:setEffect("spin") 79 | 80 | end 81 | 82 | } 83 | 84 | end 85 | 86 | 87 | local i, s = 0, 0 n = n * Waves 88 | 89 | for x = 1, n do 90 | 91 | i = i + 1 s = States(s) local angle = angle(i) + angleOffset 92 | 93 | 94 | local ActorFrame = ActorFrame(i) MainFrame[i] = ActorFrame 95 | 96 | ActorFrame[1] = ActorFrame[1] .. { 97 | 98 | Particle( i, s ), 99 | 100 | OnCommand=function(self) 101 | 102 | local radius = SCREEN_CENTER_Y self.Sleep = 0.5 / Waves 103 | 104 | local x = math.cos(angle) local y = math.sin(angle) 105 | 106 | local pos = radius * Vector( x, y ) self:setPos(pos) 107 | 108 | end 109 | 110 | } 111 | 112 | end 113 | 114 | 115 | return MainFrame 116 | -------------------------------------------------------------------------------- /Scripts/Particles/Depth/Spread.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local depth = beat4sprite.Config.Depth.Range 3 | 4 | local depthOffset = tapLua.depthOffset 5 | 6 | 7 | local builder = ... local Texture = builder.Texture local Sprite = builder.Sprite or {} 8 | 9 | local Direction = builder.Direction or Vector("Right") local Colors = builder.Colors 10 | 11 | local FOV = builder.FOV local verticalFOV = tapLua.verticalFOV(FOV) 12 | 13 | 14 | builder.Quantity = builder.Quantity or 12 local n = builder:scaleQuantity() 15 | 16 | builder.Rate = builder.Rate * 1.5 17 | 18 | 19 | local min, max = table.unpack(depth) min = min / 4 max = max / 4 local length = max - min 20 | 21 | local function percent(z) local z = z - min return z / length end 22 | 23 | 24 | local function random(y) return math.random( -y, y ) end 25 | 26 | local angle = Direction.y / Direction.x angle = math.atan(angle) angle = math.deg(angle) 27 | 28 | 29 | local t = beat4sprite.ActorFrame { OnCommand=function(self) self:setupDepth(FOV):rotationz(angle) end } 30 | 31 | for i = 1, n do 32 | 33 | t[i] = beat4sprite.Sprite { 34 | 35 | Texture = Texture, 36 | 37 | OnCommand=function(self) 38 | 39 | self:initParticle( builder, i ) self:diffusealpha(0):rotationz( - angle ) 40 | 41 | 42 | local rate = self:tweenRate() local i = i - 1 i = rate * i / n 43 | 44 | self:queuecommand("Prepare"):sleep(i):diffusealpha(1):queuecommand("Motion") 45 | 46 | end, 47 | 48 | PrepareCommand=function(self) 49 | 50 | local size = self:GetZoomedSize() * 0.5 local w, h = size:unpack() 51 | 52 | 53 | local z = math.random( min, max ) 54 | 55 | local x = - SCREEN_CENTER_X - w x = depthOffset( x, z, FOV ) 56 | 57 | local y = SCREEN_CENTER_X + h y = depthOffset( y, z, verticalFOV ) y = random(y) 58 | 59 | self:xyz( x, y, z ) 60 | 61 | 62 | local percent = 1 - percent(z) 63 | 64 | local color = lerp_color( percent, Colors[1], Colors[2] ) self:diffuse(color) 65 | 66 | 67 | self.NextPos = - x 68 | 69 | end, 70 | 71 | MotionCommand=function(self) 72 | 73 | local variation = math.random( 500, 1000 ) * 0.001 74 | 75 | local rate = self:tweenRate() * variation local x = self.NextPos 76 | 77 | self:linear(rate):x(x) self:queuecommand("Prepare"):queuecommand("Motion") 78 | 79 | end 80 | 81 | } .. Sprite 82 | 83 | end 84 | 85 | return t -------------------------------------------------------------------------------- /Scripts/Particles/Depth/StaticScatter.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local depth = beat4sprite.Config.Depth.Range 3 | 4 | local depthOffset = tapLua.depthOffset 5 | 6 | 7 | local builder = ... local Texture = builder.Texture local Sprite = builder.Sprite or {} 8 | 9 | local Colors = builder.Colors local Rotation = builder.Rotation Rotation = Rotation == nil and true or Rotation 10 | 11 | local FOV = builder.FOV local verticalFOV = tapLua.verticalFOV(FOV) 12 | 13 | 14 | builder.Quantity = builder.Quantity or 12 local n = builder:scaleQuantity() 15 | 16 | 17 | local min, max = table.unpack(depth) min = min / 4 max = max / 4 local length = max - min 18 | 19 | local function percent(z) local z = z - min return z / length end 20 | 21 | local function random(x) return math.random( -x, x ) end 22 | 23 | 24 | local t = beat4sprite.ActorFrame { OnCommand=function(self) self:setupDepth(FOV) end } 25 | 26 | for i = 1, n do 27 | 28 | t[i+1] = beat4sprite.Sprite { 29 | 30 | Texture = Texture, 31 | 32 | OnCommand=function(self) self:initParticle( builder, i ) self:playcommand("Scatter") end, 33 | 34 | ScatterCommand=function(self) 35 | 36 | local size = self:GetZoomedSize() * 0.5 local w, h = size:unpack() 37 | 38 | local rotation = Rotation and math.random( 0, 360 ) or 0 self:rotationz(rotation) 39 | 40 | 41 | local z = math.random( min, max ) 42 | 43 | local x = SCREEN_CENTER_X x = depthOffset( x, z, FOV ) x = random(x) 44 | 45 | local y = SCREEN_CENTER_Y y = depthOffset( y, z, verticalFOV ) y = random(y) 46 | 47 | self:xyz( x, y, z ) 48 | 49 | 50 | local percent = 1 - percent(z) 51 | 52 | local color = lerp_color( percent, Colors[1], Colors[2] ) self:diffuse(color) 53 | 54 | 55 | local rate = self:tweenRate() self:sleep(rate):queuecommand("Scatter") 56 | 57 | end 58 | 59 | } .. Sprite 60 | 61 | end 62 | 63 | return t -------------------------------------------------------------------------------- /Scripts/Particles/Explosion.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Should the matrix be filled according to the screen? 3 | 4 | local Vector = Astro.Vector local maxCoordinate = Vector.maxCoordinate 5 | 6 | local planeAxes = Vector.planeAxes 7 | 8 | 9 | local builder = ... local Texture = builder.Texture 10 | 11 | local Sprite = builder.Sprite or {} 12 | 13 | 14 | local max = tapLua.screenSize() max = maxCoordinate(max).value 15 | 16 | local Scale = SCREEN_HEIGHT / 240 local size = Vector( 64, 60 ) * Scale -- 5x4 matrix in 320x240. 17 | 18 | 19 | local n = 0 20 | 21 | local t = beat4sprite.ActorFrame { OnCommand=function(self) self:Center() end } 22 | 23 | for j = 1, 4 do for i = 1, 5 do 24 | 25 | n = n + 1 local n = n 26 | 27 | t[n] = beat4sprite.Sprite { 28 | 29 | Texture = Texture, 30 | 31 | OnCommand=function(self) 32 | 33 | self.TilePos = Vector( i, j ) 34 | 35 | self:initParticle( builder, n ) local direction = Vector( i - 3, j - 2.5 ) 36 | 37 | local size = self:GetZoomedSize() * 2 local pos = Vector( max, max ) + size 38 | 39 | for i,v in ipairs( planeAxes ) do pos[v] = pos[v] * direction[v] end 40 | 41 | self.Pos = pos self:playcommand("Prepare"):queuecommand("Motion") 42 | 43 | end, 44 | 45 | PrepareCommand=function(self) self:xy( 0, 0 ) end, 46 | 47 | MotionCommand=function(self) 48 | 49 | local rate = self:tweenRate() * 4 local pos = self.Pos 50 | 51 | self:linear(rate):setPos(pos) 52 | 53 | end 54 | 55 | } .. Sprite 56 | 57 | end end 58 | 59 | return t -------------------------------------------------------------------------------- /Scripts/Particles/LaneSweep.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Positioning based on the tapLua algorithm to fit sprites in the screen. 3 | 4 | local Vector = Astro.Vector local maxCoordinate = Vector.maxCoordinate 5 | 6 | local astro = Astro.Layout local quantityIn = astro.quantityIn 7 | 8 | local offset = astro.centerOffset 9 | 10 | 11 | local builder = ... local Texture = builder.Texture 12 | 13 | local Sprite = builder.Sprite or {} 14 | 15 | 16 | local t = beat4sprite.ActorFrame { OnCommand=function(self) self:Center() end } 17 | 18 | 19 | local max = tapLua.screenSize() max = maxCoordinate(max).value 20 | 21 | local Scale = SCREEN_HEIGHT / 240 local size = 60 * Scale 22 | 23 | local n = quantityIn( max, size ) offset = offset(n) 24 | 25 | 26 | local function Particle(t) return beat4sprite.Sprite(t) .. Sprite end 27 | 28 | for i = 1, n do 29 | 30 | local x = max local offset = i - offset 31 | 32 | t[#t+1] = Particle { 33 | 34 | Texture = Texture, 35 | 36 | OnCommand=function(self) 37 | 38 | self:initParticle( builder, i ) local rate = self:tweenRate() * i * 0.5 39 | 40 | x = x + self:GetZoomedHeight() * 2 x = i % 2 == 0 and - x or x 41 | 42 | self:x( size * offset ):playcommand("Prepare"):sleep(rate):queuecommand("Cycle") 43 | 44 | end, 45 | 46 | PrepareCommand=function(self) self:y( x * 0.5 ) end, 47 | 48 | CycleCommand=function(self) 49 | 50 | local rate = self:tweenRate() * 4 self:linear(rate):y( - x * 0.5 ) 51 | 52 | self:queuecommand("Prepare"):queuecommand("Cycle") 53 | 54 | end 55 | 56 | } 57 | 58 | 59 | local x = max 60 | 61 | t[#t+1] = Particle { 62 | 63 | Texture = Texture, 64 | 65 | OnCommand=function(self) 66 | 67 | self:initParticle( builder, i ) local rate = self:tweenRate() * i * 0.5 68 | 69 | x = x + self:GetZoomedWidth() * 2 x = i % 2 == 0 and - x or x 70 | 71 | self:y( size * offset ):playcommand("Prepare"):sleep(rate):queuecommand("Cycle") 72 | 73 | end, 74 | 75 | PrepareCommand=function(self) self:x( x * 0.5 ) end, 76 | 77 | CycleCommand=function(self) 78 | 79 | local rate = self:tweenRate() * 4 self:linear(rate):x( - x * 0.5 ) 80 | 81 | self:queuecommand("Prepare"):queuecommand("Cycle") 82 | 83 | end 84 | 85 | } 86 | 87 | end 88 | 89 | 90 | return t -------------------------------------------------------------------------------- /Scripts/Particles/ScreenBounce.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local planeAxes = Vector.planeAxes 3 | 4 | local builder = ... local Texture = builder.Texture local Skip = builder.Skip 5 | 6 | local Sprite = builder.Sprite or {} 7 | 8 | 9 | local t = beat4sprite.ActorFrame { 10 | 11 | OnCommand=function(self) 12 | 13 | local function onUpdate() self:playcommand("Update") end 14 | 15 | self:SetUpdateFunction(onUpdate) 16 | 17 | end 18 | 19 | } 20 | 21 | 22 | local screenSize = tapLua.screenSize() 23 | 24 | local function direction() 25 | 26 | local direction = screenSize - tapLua.center() return Vector.unit( direction ) 27 | 28 | end 29 | 30 | local skipOffset = { 31 | 32 | x = function(self) self:addx( self.Direction.x * 24 * 2 ) end, 33 | y = function(self) self:addy( self.Direction.y * 24 * 2 ) end 34 | 35 | } 36 | 37 | local function check( self, k ) 38 | 39 | local screenDim = screenSize[k] local pos = self:GetPos() pos = pos[k] 40 | 41 | local size = self:GetZoomedSize() size = size[k] * 0.5 42 | 43 | local isInBounds = pos > size and pos < screenDim - size 44 | 45 | if isInBounds then return end self.Direction[k] = - self.Direction[k] 46 | 47 | if not Skip then return end local skip = skipOffset[k] skip(self) 48 | 49 | end 50 | 51 | local function onBounds(self) 52 | 53 | for i,v in ipairs(planeAxes) do check( self, v ) end 54 | 55 | end 56 | 57 | 58 | local n = 0 59 | 60 | local aspectRatio = SCREEN_WIDTH / SCREEN_HEIGHT local maxSprites = 7 * aspectRatio 61 | 62 | for j = 1, 3 do for i = 1, 4 do 63 | 64 | n = n + 1 local n = n 65 | 66 | t[n] = beat4sprite.Sprite { 67 | 68 | Texture = Texture, Condition = n < maxSprites, 69 | 70 | OnCommand=function(self) 71 | 72 | self:initParticle( builder, n ) 73 | 74 | 75 | local i = i - 2.5 local j = j - 2 76 | 77 | local size = self:GetZoomedSize() size = Vector( size.x * i, size.y * j ) 78 | 79 | local pos = tapLua.center() + size * 0.5 self:setPos(pos) 80 | 81 | 82 | self.Time = 0 self.Direction = direction() 83 | 84 | local n = Skip and n * 0.666 or n - 1 self.Sleep = self:tweenRate() * n * 0.75 85 | 86 | end, 87 | 88 | UpdateCommand=function(self) 89 | 90 | local hasTimeLeft = self:GetTweenTimeLeft() > 0 local direction = self.Direction 91 | 92 | if not direction or hasTimeLeft then return end 93 | 94 | 95 | local d = self:GetEffectDelta() d = math.min( d, 0.1 ) -- Clamping needed in case there's a big frame skip! 96 | 97 | self.Time = self.Time + d if self.Time < self.Sleep then return end 98 | 99 | 100 | local rate = self:tweenRate() local x = Skip and 12 or 1 / rate 101 | 102 | direction = direction * d * x * 500 103 | 104 | 105 | local pos = self:GetPos() + direction self:setPos(pos) onBounds(self) 106 | 107 | if Skip then self:sleep( rate * 0.5 ) end 108 | 109 | end 110 | 111 | } .. Sprite 112 | 113 | end end 114 | 115 | 116 | return t -------------------------------------------------------------------------------- /Scripts/Tile/Child.lua: -------------------------------------------------------------------------------- 1 | 2 | local maxCoordinate = Astro.Vector.maxCoordinate 3 | 4 | 5 | -- Returns the tapLua tile actor. 6 | 7 | local input, AFT = beat4sprite.Arguments 8 | 9 | local function setSize(self) 10 | 11 | local screenSize = tapLua.screenSize() local size = self:GetSize() 12 | 13 | local max = maxCoordinate(size) local k, length = max.key, max.value 14 | 15 | local screenLength = screenSize[k] local isBigger = length > screenLength 16 | 17 | 18 | local zoom = isBigger and screenLength / length or 1 19 | 20 | zoom = zoom * 2 -- Double the resolution to keep it high quality. 21 | 22 | size = self:GetSize() * zoom AFT:setSizeVector(size):zoom(zoom) 23 | 24 | self:basezoom(zoom) 25 | 26 | end 27 | 28 | 29 | -- Add spiral traversing function. 30 | 31 | local spiral = tapLua.Load( "Sprite/Spiral" ) 32 | 33 | local function spiralIndex(self) 34 | 35 | local p = self:GetParent() p.Spiral = p.Spiral or spiral(p) local spiral = p.Spiral 36 | 37 | local i = self.Index return spiral[i] 38 | 39 | end 40 | 41 | local function onChildren(self) self.spiralIndex = spiralIndex end 42 | 43 | 44 | return tapLua.Load( "Sprite/Tile", input ) .. { 45 | 46 | PostInitCommand=function(self) 47 | 48 | self:RunCommandsOnChildren(onChildren) self:queuecommand("SetSize") 49 | 50 | end, 51 | 52 | SetSizeCommand=function(self) 53 | 54 | AFT = self:GetParent() setSize(self) local size = AFT:GetSize() 55 | 56 | self:setPos( size * 0.5 ) if isQuad then return end AFT:GetParent():queuecommand("LoadSprite") 57 | 58 | end 59 | 60 | } -------------------------------------------------------------------------------- /Scripts/Tile/Quad.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Table local Builder = beat4sprite.Builder 3 | 4 | local builder = ... local Quad = builder.Quad local isQuad = Quad == true 5 | 6 | 7 | local function quadActor() 8 | 9 | if not Quad or isQuad then return end 10 | 11 | 12 | local input = builder:input() local copy = astro.Copy.deep( input ) 13 | 14 | copy.States = nil copy.Quad = true 15 | 16 | 17 | input = astro.merge( Quad, copy ) return Builder(input):Load() 18 | 19 | end 20 | 21 | 22 | local path = THEME:GetPathG( '', "_white" ) 23 | 24 | local function onQuad(self) 25 | 26 | if not isQuad then return end local size = self:GetSize() 27 | 28 | self:Load(path):setSizeVector(size) 29 | 30 | end 31 | 32 | 33 | return quadActor, onQuad -------------------------------------------------------------------------------- /Scripts/Tile/Scroll.lua: -------------------------------------------------------------------------------- 1 | 2 | local Vector = Astro.Vector local planeAxes = Vector.planeAxes 3 | 4 | local normSqr = Vector.normSqr local config = beat4sprite.Config 5 | 6 | local builder = ... local Scroll = builder.Scroll 7 | 8 | 9 | local orientation = 1 local skippedTime = 0 10 | 11 | local function scrollSpeed() 12 | 13 | local players = GAMESTATE:GetEnabledPlayers() local playerState = GAMESTATE:GetPlayerState( players[1] ) 14 | 15 | local options = playerState:GetCurrentPlayerOptions() 16 | 17 | return options:ScrollSpeed() * 96 * orientation 18 | 19 | end 20 | 21 | local function scrollVelocity( self, isSkip ) 22 | 23 | local velocity = Vector() 24 | 25 | local direction = Scroll.Direction local size = self:GetZoomedSize() 26 | 27 | for i,v in ipairs(planeAxes) do velocity[v] = direction[v] / size[v] end 28 | 29 | 30 | local scrollSync = config.ScrollSync and self:isOnGameplay() and not isSkip 31 | 32 | if scrollSync then return velocity * scrollSpeed() end 33 | 34 | 35 | velocity = velocity * 2 / self:tweenRate() return velocity * 120 * orientation 36 | 37 | end 38 | 39 | local function invertScroll(self) orientation = - orientation end 40 | 41 | local function reverseTime( self, isSkip ) 42 | 43 | local velocity = scrollVelocity( self, isSkip ) 44 | 45 | for i,v in ipairs(planeAxes) do velocity[v] = math.abs( velocity[v] ) end 46 | 47 | local max = math.max( velocity:unpack() ) return 1 / max 48 | 49 | end 50 | 51 | local function onReverseScroll(self) 52 | 53 | if not Scroll.Reverse then return end invertScroll(self) 54 | 55 | local time = reverseTime(self) self:sleep(time):queuecommand("Scroll") 56 | 57 | end 58 | 59 | local function onReverseSkip( self, time ) 60 | 61 | if not Scroll.Reverse then return end skippedTime = skippedTime + time 62 | 63 | if skippedTime < reverseTime( self, true ) then return end 64 | 65 | invertScroll(self) skippedTime = 0 66 | 67 | end 68 | 69 | local function onScrollSkipping(self) 70 | 71 | if not Scroll.Skip then return end 72 | 73 | 74 | local size = self:GetZoomedSize() 75 | 76 | for i,v in ipairs(planeAxes) do size[v] = math.min( size[v], 80 ) end 77 | 78 | 79 | local offset = Vector() 80 | 81 | local direction = Scroll.Direction * orientation 82 | 83 | for i,v in ipairs(planeAxes) do offset[v] = direction[v] * size[v] end 84 | 85 | self:moveTextureBy( - offset ) 86 | 87 | 88 | local time = self:tweenRate() self:sleep(time) 89 | 90 | onReverseSkip( self, time ) self:queuecommand("Scroll") 91 | 92 | 93 | return true 94 | 95 | end 96 | 97 | return scrollVelocity, onReverseScroll, onScrollSkipping -------------------------------------------------------------------------------- /Scripts/Tile/States.lua: -------------------------------------------------------------------------------- 1 | 2 | local builder = ... local States = builder.States 3 | 4 | local Position = States.Position if Position == true then Position = Vector( 1, 1 ) end 5 | 6 | 7 | local function positionState(self) -- Offset state by position. 8 | 9 | if not Position then return end 10 | 11 | 12 | local i, j = self.TilePos:unpack() i, j = Position.x * i, Position.y * j 13 | 14 | local state = self:cycleState( i + j ) self:setstate(state) 15 | 16 | end 17 | 18 | -- Test scrollStates as priority. 19 | 20 | local function scrollStates(self) 21 | 22 | -- Return the state properties based on a direction, tile position and the sprite matrix. 23 | 24 | local p = {} local scroll = States.Scroll 25 | 26 | local n = self:GetNumStates() local pos = self.TilePos - Vector( 1, 1 ) 27 | 28 | local sheet = self:pathMatrix() or States.SheetMatrix 29 | 30 | 31 | local i = pos.x + sheet.x * pos.y 32 | 33 | for a = 1, sheet.y do 34 | 35 | i = i + scroll.x + sheet.x * scroll.y p[#p+1] = { Frame = i % n } 36 | 37 | end 38 | 39 | return p 40 | 41 | end 42 | 43 | return positionState, scrollStates -------------------------------------------------------------------------------- /Scripts/Tile/Tile.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Type 3 | 4 | local isTable = astro.isTable local isString = astro.isString 5 | 6 | 7 | local astro = Astro.Table 8 | 9 | local Vector = Astro.Vector local isVector = Vector.isVector 10 | 11 | local isZero = Vector.isZero local planeAxes = Vector.planeAxes 12 | 13 | local maxCoordinate = Vector.maxCoordinate 14 | 15 | 16 | local Actor = tapLua.Actor 17 | 18 | 19 | -- It's an advanced tiling script using the tapLua Tile.lua script. 20 | 21 | local builder = ... or beat4sprite.Arguments 22 | 23 | local Commands = { "Mirror", "Quad" } builder:setCommands( Commands ) 24 | 25 | 26 | builder.Scroll = builder.Scroll or {} local Scroll = builder.Scroll 27 | 28 | if isVector(Scroll) then builder.Scroll = { Direction = Scroll } end 29 | 30 | Scroll = builder.Scroll 31 | 32 | 33 | local Direction = Scroll.Direction 34 | 35 | if Direction then Scroll.Direction = Vector.unit( Direction ) end 36 | 37 | 38 | local MatrixOffset = builder.MatrixOffset 39 | 40 | local Texture = builder.Texture local States = builder.States 41 | 42 | 43 | local Display = builder.Display 44 | 45 | if Scroll then Display = Display or Scroll.Direction end 46 | 47 | 48 | local Rotation = builder.Rotation or Vector() 49 | 50 | local Blend = builder.Blend local Colors = builder.Colors 51 | 52 | 53 | local Mirror = builder.Mirror local Zoom = builder:zoom() 54 | 55 | if Mirror == true then Mirror = { x = true, y = true } end 56 | 57 | 58 | local function tileUtil( path, ... ) 59 | 60 | local args = ... or builder 61 | 62 | return beat4sprite.Load( "Tile/" .. path )(args) 63 | 64 | end 65 | 66 | 67 | local quadActor, onQuad = tileUtil("Quad") 68 | 69 | local positionState, scrollStates = tileUtil("States") 70 | 71 | local scrollVelocity, onReverseScroll, onScrollSkipping = tileUtil("Scroll") 72 | 73 | 74 | -- 1. Setup the behaviour for the tiled sprites. 75 | 76 | local function setRandomState(self) 77 | 78 | local states = self.beat4sprite.States 79 | 80 | if not self:hasAnimationType("Random") then return end 81 | 82 | states = self:GetNumStates() local state = math.random(states) - 1 83 | 84 | self:setstate(state) local rate = math.random( 500, 1500 ) * 0.001 85 | 86 | self.statesDelay = self.statesDelay * rate * 2 87 | 88 | end 89 | 90 | local function initStates(self) 91 | 92 | if self:GetNumStates() < 2 then return end setRandomState(self) 93 | 94 | 95 | local properties = self:statesProperties() 96 | 97 | if States.Scroll then properties = scrollStates(self) end 98 | 99 | 100 | self:SetStateProperties(properties) positionState(self) 101 | 102 | end 103 | 104 | Commands = { "Visible", "States", "On" } 105 | 106 | local Sprite = beat4sprite.Sprite { 107 | 108 | InitCommand=function(self) 109 | 110 | self:init(builder):initSprite() initStates(self) self:queueCommands(Commands) 111 | 112 | end, 113 | 114 | VisibleCommand=function(self) 115 | 116 | local pos = self.TilePos local visibility = builder.Visibility 117 | 118 | if not visibility then return end 119 | 120 | 121 | local i, j = pos:unpack() local visible = visibility( i, j ) 122 | 123 | self:visible(visible) 124 | 125 | end, 126 | 127 | StatesCommand=function(self) 128 | 129 | local offset = States.Offset if not offset then return end 130 | 131 | self:setstate(offset) 132 | 133 | end, 134 | 135 | RotationCommand=function(self) 136 | 137 | local rotation = Rotation 138 | 139 | 140 | local a = Vector() local pos = self.TilePos 141 | 142 | for i,v in ipairs(planeAxes) do 143 | 144 | if not Mirror then break end local mirror = Mirror[v] == true 145 | 146 | local value = pos[v] + 1 value = value % 2 147 | 148 | if mirror then a[v] = value * 180 end 149 | 150 | end 151 | 152 | local mirror = Vector( a.y, a.x ) 153 | 154 | 155 | rotation = rotation + mirror self:setRotation(rotation) 156 | 157 | end, 158 | 159 | ColorsCommand=function(self) 160 | 161 | if #Colors ~= 1 then return end local color = Colors[1] 162 | 163 | self:diffuse(color) 164 | 165 | end, 166 | 167 | QuadCommand=function(self) 168 | 169 | if isComposed then return end onQuad(self) 170 | 171 | end 172 | 173 | } 174 | 175 | 176 | -- Merging... 177 | 178 | local Sprite1 = builder.Sprite or {} 179 | 180 | Sprite = Actor.commands( Sprite ) Actor.merge( Sprite, Sprite1 ) 181 | 182 | 183 | local Sprite2 = builder.Composition or {} 184 | 185 | if builder.Composition and isString(Texture) then Texture = { Texture } end 186 | 187 | Sprite2.ComposeCommand = Sprite2.InitCommand Sprite2.InitCommand = nil 188 | 189 | local isComposed = isTable(Texture) 190 | 191 | 192 | local Sprite3 = builder.Output or {} 193 | 194 | 195 | local input = { Texture = Texture, Sprite = Sprite, Zoom = Zoom, MatrixOffset = MatrixOffset } 196 | 197 | 198 | local Renderer = tapLua.Sprite.Renderer local isScreenScale = builder.ScreenScale 199 | 200 | local function setScreenScale() 201 | 202 | local w, h = Renderer:GetSize(true) Renderer:zoom( SCREEN_HEIGHT / h ) 203 | 204 | end 205 | 206 | local function onPreload() 207 | 208 | -- Check if should scale to screen height. 209 | 210 | if isScreenScale then setScreenScale() end 211 | 212 | 213 | -- Check mirror when the texture is oversized. 214 | 215 | if MatrixOffset or not Display then return end local w, h = Renderer:GetZoomedSize(true) 216 | 217 | local displayX = w >= SCREEN_WIDTH and Display.x ~= 0 local displayY = h >= SCREEN_HEIGHT and Display.y ~= 0 218 | 219 | local offset = displayX and Vector(1) or Vector() if displayY then offset.y = 1 end 220 | 221 | input.MatrixOffset = offset 222 | 223 | end 224 | 225 | input.onPreload = onPreload 226 | 227 | 228 | -- 2. Composition of textures. 229 | 230 | local function ActorFrameTexture( input ) 231 | 232 | return tapLua.ActorFrameTexture { 233 | 234 | CreateTextureCommand=function(self) 235 | 236 | self:EnableAlphaBuffer(true):EnableDepthBuffer(true):Create() 237 | 238 | end 239 | 240 | } .. input 241 | 242 | end 243 | 244 | local function queueTile(self) self:GetParent():queuecommand("Tile") end 245 | 246 | local function Composition() 247 | 248 | local Actor = Def.Actor { ComposeCommand = queueTile } 249 | 250 | 251 | -- Returns an ActorFrameTexture that composes static textures together from different files. 252 | 253 | if not isComposed then return Actor end 254 | 255 | 256 | -- Could be added to Astro.Vector along with maxCoordinate. 257 | 258 | local function componentVector( vector, component ) 259 | 260 | for i,v in ipairs(planeAxes) do if v ~= component then vector[v] = nil end end 261 | 262 | return vector 263 | 264 | end 265 | 266 | 267 | local function direction(coordinate) 268 | 269 | local direction = Display[coordinate] if direction ~= 0 then return 1 end 270 | 271 | if not isZero(Display) then return 0 end 272 | 273 | 274 | local screenSize = tapLua.screenSize() local max = maxCoordinate(screenSize) 275 | 276 | if max.key == coordinate then return 1 end 277 | 278 | 279 | return 0 280 | 281 | end 282 | 283 | 284 | local function Sprite(input) return beat4sprite.Sprite(input) .. { ComposeCommand = onQuad } end 285 | 286 | 287 | -- First texture. 288 | 289 | local function firstInit(self) 290 | 291 | self.beat4sprite = builder initStates(self) 292 | 293 | local filter = beat4sprite.Filter or false self:SetTextureFiltering(filter) 294 | 295 | self:onGameplay():initSprite():setupEffect() 296 | 297 | end 298 | 299 | local t = tapLua.ActorFrame { 300 | 301 | Sprite { 302 | 303 | Texture = Texture[1], 304 | 305 | ComposeCommand=function(self) 306 | 307 | local p = self:GetParent() local AFT = p:GetParent() 308 | 309 | local size = self:GetSize() p:setPos( size * 0.5 ) 310 | 311 | AFT:setSizeVector(size) firstInit(self) 312 | 313 | end 314 | 315 | } .. Sprite2 316 | 317 | } 318 | 319 | 320 | -- Add the following textures in the display direction. 321 | 322 | for i,v in ipairs(Texture) do for a, coordinate in ipairs(planeAxes) do 323 | 324 | 325 | if #Texture == 1 then break end 326 | 327 | local direction = direction(coordinate) local condition = direction ~= 0 and i > 1 328 | 329 | 330 | local a = tapLua.ActorFrame {} t[#t+1] = a 331 | 332 | a[#a+1] = Sprite { 333 | 334 | Texture = v, Condition = condition, 335 | 336 | ComposeCommand=function(self) 337 | 338 | local p = self:GetParent() local AFT = p:GetParent():GetParent() 339 | 340 | local size = self:GetSize() size = componentVector( size, coordinate ) 341 | 342 | 343 | local newSize = AFT:GetSize() + size AFT:setSizeVector(newSize) 344 | 345 | 346 | newSize = componentVector( newSize, coordinate ) 347 | 348 | local pos = newSize - size p:setPos(pos) 349 | 350 | end 351 | 352 | } 353 | 354 | 355 | -- Add the textures in the missing spots followed by their sequence. 356 | 357 | condition = condition and not isZero(Display) and coordinate == 'y' 358 | 359 | for j = 1, #Texture - 1 do 360 | 361 | local i = i + j - 1 i = i % #Texture + 1 362 | 363 | a[#a+1] = Sprite { 364 | 365 | Texture = Texture[i], Condition = condition, 366 | 367 | ComposeCommand=function(self) 368 | 369 | local w = self:GetWidth() self:x( w * j ) 370 | 371 | end 372 | 373 | } 374 | 375 | end 376 | 377 | 378 | end end 379 | 380 | 381 | local Composition = t 382 | 383 | return ActorFrameTexture { 384 | 385 | Composition, ComposeCommand=function(self) self:queuecommand("Finish") end, 386 | 387 | FinishCommand=function(self) 388 | 389 | self:playcommand("CreateTexture") input.Texture = self:GetTexture() 390 | 391 | queueTile(self) 392 | 393 | end 394 | 395 | } 396 | 397 | end 398 | 399 | 400 | -- 3. Return the actors involved. 401 | 402 | local Width, AFT local Renderer = tapLua.Sprite.Renderer 403 | 404 | local childPath= beat4sprite.Path .. "Scripts/Tile/Child.lua" 405 | 406 | return beat4sprite.ActorFrame { 407 | 408 | OnCommand=function(self) self:queuecommand("Compose") end, Composition(), 409 | 410 | ActorFrameTexture { 411 | 412 | InitCommand=function(self) AFT = self end, 413 | 414 | TileCommand=function(self) 415 | 416 | beat4sprite.Arguments = input self:AddChildFromPath(childPath) beat4sprite.Arguments = nil 417 | 418 | Width = Renderer:GetZoomedWidth() 419 | 420 | end, 421 | 422 | LoadSpriteCommand=function(self) self:playcommand("CreateTexture") end 423 | 424 | }, 425 | 426 | beat4sprite.Sprite { 427 | 428 | OnCommand=function(self) self:Center():init(builder):initSprite() end, 429 | 430 | LoadSpriteCommand=function(self) 431 | 432 | if Blend then self:blend(Blend) end 433 | 434 | 435 | local texture = AFT:GetTexture() self:SetTexture(texture) 436 | 437 | local zoom = 1 / AFT:GetZoom() self:zoom(zoom):playcommand("Scroll") 438 | 439 | 440 | -- Fit backgrounds easily. Maybe should offset on the y axis too. 441 | 442 | if Width >= SCREEN_WIDTH or not isScreenScale then return end 443 | 444 | local x = Width * 0.5 x = - x / zoom self:addimagecoords( x, 0 ) 445 | 446 | end, 447 | 448 | ScrollCommand=function(self) 449 | 450 | if not Scroll.Direction then return end if onScrollSkipping(self) then return end 451 | 452 | local velocity = scrollVelocity(self) self:scrollTexture(velocity) onReverseScroll(self) 453 | 454 | end 455 | 456 | } .. Sprite3, 457 | 458 | quadActor() 459 | 460 | } -------------------------------------------------------------------------------- /beat4sprite.lua: -------------------------------------------------------------------------------- 1 | 2 | local astro = Astro.Table 3 | 4 | 5 | local animationsDirectory = "/BGAnimations/" local path = "/Appearance/Themes/_fallback/Modules/beat4sprite/" 6 | 7 | if tapLua.isLegacy() then path = "/Modules/beat4sprite/" end 8 | 9 | beat4sprite = { Path = path, Modules = {} } 10 | 11 | 12 | local scale = SCREEN_HEIGHT / 720 13 | 14 | local function z(offset) local depth = 250 local i = 1 + offset return depth * i * scale end 15 | 16 | 17 | -- TODO: Color saturation config is not supported. 18 | 19 | beat4sprite.Config = { 20 | 21 | ScrollSync = false, PreviewRate = 0.5, MorphLayers = 64, 22 | 23 | Depth = { Range = { - z(3), z( - 0.75 ) }, FOV = 160 } 24 | 25 | } 26 | 27 | 28 | local function Load(name) 29 | 30 | return loadfile( path .. "Scripts/" .. name .. ".lua" ) 31 | 32 | end 33 | 34 | local function filePath( name, file ) 35 | 36 | file = file or "default.lua" 37 | 38 | return animationsDirectory .. name .. "/" .. file 39 | 40 | end 41 | 42 | local function randomAnimation() 43 | 44 | local animations = FILEMAN:GetDirListing( animationsDirectory, true ) 45 | 46 | local path = astro.random(animations) path = filePath(path) 47 | 48 | return loadfile(path)() 49 | 50 | end 51 | 52 | local function songBackgroundPath() return GAMESTATE:GetCurrentSong():GetBackgroundPath() end 53 | 54 | 55 | astro.merge( beat4sprite, { 56 | 57 | Load = Load, filePath = filePath, randomAnimation = randomAnimation, 58 | 59 | songBackgroundPath = songBackgroundPath 60 | 61 | } ) 62 | 63 | 64 | local LoadDirectory = tapLua.FILEMAN.LoadDirectory local directories = { "Actor", "Builder", "Modules" } 65 | 66 | for i,v in ipairs(directories) do LoadDirectory( path .. v .. '/' ) end 67 | 68 | 69 | -- Should this be added to Astro? 70 | 71 | local Vector = Astro.Vector local planeAxes = Vector.planeAxes 72 | 73 | Vector.maxCoordinate = function(vector) 74 | 75 | local maxKey, maxValue 76 | 77 | for i,v in ipairs(planeAxes) do 78 | 79 | local value = vector[v] 80 | 81 | if not maxKey or value > maxValue then maxKey = v maxValue = value end 82 | 83 | end 84 | 85 | return astro.pair( maxKey, maxValue ) 86 | 87 | end --------------------------------------------------------------------------------