├── .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 | [](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
--------------------------------------------------------------------------------