11import { CELL_SIZE } from "../util/constants.ts"
22import type {
3+ Dir ,
34 IField ,
45 IProp ,
56 LoadOptions ,
@@ -22,11 +23,14 @@ export class Prop implements IProp {
2223 readonly j : number
2324 readonly type : PropType
2425 readonly def : PropDefinition
26+ #motion: Motion | null = null
2527 readonly #actionQueue = new ActionQueue < Prop , PropAction > (
2628 ( field , action ) => {
2729 switch ( action . type ) {
2830 case "break" : {
29- return "next"
31+ this . #motion = new MotionBreak ( this . #image! , action . dir )
32+ this . #motion. cb = action . cb
33+ return "end"
3034 }
3135 case "remove" : {
3236 field . props . remove ( this . i , this . j )
@@ -81,6 +85,16 @@ export class Prop implements IProp {
8185 this . #pushed = pushed
8286 }
8387
88+ /**
89+ * Vanishes the prop, but keeps its space occupied
90+ * Useful for removing the visual representation, but keeping the processing of the action queue
91+ */
92+ vanish ( ) : void {
93+ const canvas = new OffscreenCanvas ( CELL_SIZE , CELL_SIZE )
94+ canvas . getContext ( "2d" )
95+ this . #image = canvas . transferToImageBitmap ( )
96+ }
97+
8498 async loadAssets ( options : LoadOptions ) {
8599 const loadImage = options . loadImage
86100 if ( ! loadImage ) {
@@ -94,6 +108,9 @@ export class Prop implements IProp {
94108 }
95109
96110 image ( ) : ImageBitmap {
111+ if ( this . #motion instanceof MotionBreak ) {
112+ return this . #motion. image ( )
113+ }
97114 return this . #image ?? fallbackImage
98115 }
99116
@@ -118,7 +135,17 @@ export class Prop implements IProp {
118135 }
119136
120137 step ( field : IField ) {
121- this . #actionQueue. process ( this , field )
138+ if ( ! this . #motion) {
139+ this . #actionQueue. process ( this , field )
140+ }
141+
142+ if ( this . #motion) {
143+ this . #motion. step ( )
144+ if ( this . #motion. finished ) {
145+ this . #motion. cb ?.( this . #motion)
146+ this . #motion = null
147+ }
148+ }
122149 }
123150
124151 onPushed ( event : PushedEvent , field : IField ) : void {
@@ -142,8 +169,62 @@ class PushedDelegateBreak implements PushedDelegate {
142169 onPushed ( event : PushedEvent , prop : Prop , field : IField ) : void {
143170 prop . enqueueActions (
144171 { type : "wait" , until : field . time + event . peakAt } ,
172+ {
173+ type : "break" ,
174+ dir : event . dir ,
175+ cb : ( ) => {
176+ prop . vanish ( )
177+ } ,
178+ } ,
145179 { type : "splash" , hue : 0 , sat : 1 , light : 0 , alpha : 0.15 , radius : 2 } ,
146180 { type : "remove" } ,
147181 )
148182 }
149183}
184+
185+ interface Motion {
186+ step ( ) : void
187+ finished : boolean
188+ cb ?: ( motion : Motion ) => void
189+ }
190+
191+ class MotionBreak implements Motion {
192+ #image: ImageBitmap
193+ #dir: Dir
194+ #positions = [ ...Array ( 16 ) . keys ( ) ]
195+ cb ?: ( motion : Motion ) => void
196+ constructor ( image : ImageBitmap , dir : Dir ) {
197+ this . #image = image
198+ this . #dir = dir
199+ }
200+
201+ step ( ) : void {
202+ const y = this . #positions. shift ( ) !
203+ const canvas = new OffscreenCanvas ( CELL_SIZE , CELL_SIZE )
204+ const ctx = canvas . getContext ( "2d" ) !
205+ ctx . drawImage ( this . #image, 0 , 0 )
206+ switch ( this . #dir) {
207+ case "up" :
208+ ctx . clearRect ( 0 , CELL_SIZE - 1 - y , CELL_SIZE , 1 )
209+ break
210+ case "down" :
211+ ctx . clearRect ( 0 , y , CELL_SIZE , 1 )
212+ break
213+ case "left" :
214+ ctx . clearRect ( CELL_SIZE - 1 - y , 0 , 1 , CELL_SIZE )
215+ break
216+ case "right" :
217+ ctx . clearRect ( y , 0 , 1 , CELL_SIZE )
218+ break
219+ }
220+ this . #image = canvas . transferToImageBitmap ( )
221+ }
222+
223+ image ( ) : ImageBitmap {
224+ return this . #image
225+ }
226+
227+ get finished ( ) : boolean {
228+ return this . #positions. length === 0
229+ }
230+ }
0 commit comments