【ZZ】使用Displacement Map Filter创建静态的扭曲效果

 

Create a Static Distortion Effect Using the Displacement Map Filter

Create a Static Distortion Effect Using the Displacement Map Filter


I’m a freelance designer specializing in motion graphics and interactive design. When I’m not working on client projects I make games over at The Pencil Farm .

Final Result Preview

Let's take a look at the final result we will be working towards:

Step 1: About Displacement Mapping

A displacement map works by using the color values from one image to alter the position of pixels in another image. This effect is often used to make a flat graphic 'wrap' around a dimensional image. We're going to use it here to distort a button so it looks like it's receiving static interference.

displacement example

You can read more about displacement mapping here .

Step 2: Set up Your Flash File

Create a new Flash file (ActionScript 3).Your movie settings will vary depending on your game. For this demo I'm setting up my movie as 500x300, black background, and 30 fps.

Flash file setup

Step 3: Create a Simple Button

Create a new Button symbol on the stage (Insert > New Symbol). Design the 4 states for your button. The exact design should be something that matches your game. Something glowy and semi-transparent works well with this effect. I used a font called Genetrix Square for mine, but you should use something that matches the look of your game.

Give your button an instance name of 'button1'.

button properties

Step 4: Test

If you save and test your movie (Control > Test Movie) now you should see your button on the stage responding to the mouse with the rollover states you designed. Like this:

Step 5: Create the JitteryButton Class

We need to add custom functionality to our button. We'll accomplish this by creating a new custom class and putting our simple button inside it.

Create a new ActionScript file named 'JitteryButton.as'. Save this file in the same directory as your main Flash file. Add this code to create the wrapper for our button:

  1. package  {  
  2.   
  3.     import  flash.display.Sprite;  
  4.     import  flash.display.SimpleButton;  
  5.   
  6.     public   class  JitteryButton  extends  Sprite {  
  7.   
  8.         private   var  myButton:SimpleButton;  // holds the reference to our simple button   
  9.   
  10.         // CLASS CONSTRUCTOR   
  11.         public   function  JitteryButton(button:SimpleButton) {  
  12.             myButton = button; // the button on the stage gets passed in    
  13.         }  
  14.   
  15. }  
package {

	import flash.display.Sprite;
	import flash.display.SimpleButton;

	public class JitteryButton extends Sprite {

		private var myButton:SimpleButton; // holds the reference to our simple button

		// CLASS CONSTRUCTOR
		public function JitteryButton(button:SimpleButton) {
			myButton = button; // the button on the stage gets passed in 
		}

}

All this code does so far is accept the simple button and store a reference to it. We'll add more functionality later.

Step 6: Create the Game Class

Create a new ActionScript file named 'Game.as'. This will be the document class for our movie. Save it in the same directory as the main Flash file.This code will add our custom button wrapper around the button on the stage:

  1. package  {  
  2.     import  flash.display.MovieClip;  
  3.   
  4.     public   class  Game  extends  MovieClip {  
  5.   
  6.         private   var  startButton:JitteryButton;  
  7.   
  8.         // CLASS CONSTRUCTOR   
  9.         public   function  Game() {  
  10.             // create the jittery button from the simple button on the stage   
  11.             startButton = new  JitteryButton(button1);  
  12.   
  13.             // add the new button to the stage   
  14.             addChild(startButton);  
  15.         }  
  16.   
  17.     }  
  18.   
  19. }  
package {
	import flash.display.MovieClip;

	public class Game extends MovieClip {

		private var startButton:JitteryButton;

		// CLASS CONSTRUCTOR
		public function Game() {
			// create the jittery button from the simple button on the stage
			startButton = new JitteryButton(button1);

			// add the new button to the stage
			addChild(startButton);
		}

	}

}

This code creates a new instance of our custom JitteryButton class and passes it the button on the stage ('button1').

Of course your document class will end up looking much different since it will have the code for your game in it. Here we're just concerned with the code for our button.

Back inside your Flash file set the document class to 'Game'. Remember, you don't include the file extension here.

set the document class

Step 7: Test Again

If you save and test your movie again at this point you should see exactly the same thing as when we tested in Step 4. The only difference is that now our code is set up to be able to add our custom behavior.

Step 8: Create the Displacement Map Image

We'll now create the image of the static pattern that we'll use to distort our button graphic.

Open Photoshop and create a new image filled with neutral grey (#808080). The image should be slightly wider than your button and about 3 or 4 times as high. My button is 277x56, and my image is 310x220.

We're starting with a neutral grey because that won't effect our image.

create a new image

Step 9: Add Noise

We'll now add a little bit of noise to our image. This won't be very noticeable in our static effect, but it gives it a bit of extra shimmer. You can skip this step if you like.

Duplicate the background layer and name the new layer 'Noise'. You should now have 2 layers filled with neutral grey. Select the new Noise layer and choose Filter > Noise > Add Noise. Set Amount to 120% and Distribution to Uniform. Check Monochromatic.

Hit OK.

Set the 'Noise' layer to 10% opacity.

add noise to the Noise layer

Step 10: Add Lines

Create a new layer called 'Lines'. Now use a 1px pencil brush to add some horizontal black and grey lines to the image.

Remember how these lines will effect our image: anything darker than neutral will shift our image in one direction and anything lighter will shift it in the other.

draw horizontal lines

Step 11: Save the Displacement Map Image

Choose File > Save for Web & Devices and save your image as an 8 color gif named 'staticMap.gif'.

save for web

Step 12:

Back in Flash, import the 'staticMap.gif' to your library (File > Import > Import to Library...). Open the linkage properties, check Export for ActionScript , and set the Class name to 'StaticMap'.

import the image

We can now reference this image in our code by using the StaticMap class name.

Step 13: Create the Displacement Map Filter

Add this function to your JitteryButton class:

  1. // create the displacement map filter   
  2. private   function  createDMFilter():DisplacementMapFilter {  
  3.   
  4.     var  mapBitmap:BitmapData =  new  StaticMap(0,0);  // use the bitmap data from our StaticMap image   
  5.     var  mapPoint:Point       =  new  Point(0, 0);   // position of the StaticMap image in relation to our button   
  6.     var  channels:uint        = BitmapDataChannel.RED;  // which color to use for displacement   
  7.     var  componentX:uint      = channels;  
  8.     var  componentY:uint      = channels;  
  9.     var  scaleX:Number        = 5;  // the amount of horizontal shift   
  10.     var  scaleY:Number        = 1;  // the amount of vertical shift   
  11.     var  mode:String          = DisplacementMapFilterMode.COLOR;  
  12.     var  color:uint           = 0;  
  13.     var  alpha:Number         = 0;  
  14.   
  15.     return   new  DisplacementMapFilter(  
  16.                     mapBitmap,  
  17.                     mapPoint,  
  18.                     componentX,  
  19.                     componentY,  
  20.                     scaleX,  
  21.                     scaleY,  
  22.                     mode,  
  23.                     color,  
  24.                     alpha   );  
  25.   
  26. }     
// create the displacement map filter
private function createDMFilter():DisplacementMapFilter {

	var mapBitmap:BitmapData = new StaticMap(0,0); // use the bitmap data from our StaticMap image
	var mapPoint:Point       = new Point(0, 0);  // position of the StaticMap image in relation to our button
	var channels:uint        = BitmapDataChannel.RED; // which color to use for displacement
	var componentX:uint      = channels;
	var componentY:uint      = channels;
	var scaleX:Number        = 5; // the amount of horizontal shift
	var scaleY:Number        = 1; // the amount of vertical shift
	var mode:String          = DisplacementMapFilterMode.COLOR;
	var color:uint           = 0;
	var alpha:Number         = 0;

	return new DisplacementMapFilter(
					mapBitmap,
					mapPoint,
					componentX,
					componentY,
					scaleX,
					scaleY,
					mode,
					color,
					alpha	);

}	

This function simply creates the Displacement Map Filter using the BitmapData from our StaticMap image. This doesn't need to be in it's own function, I'm just doing it for clarity.

In order for it to work we'll need to import these classes at the top of our JitteryButton class:

  1. import  flash.display.BitmapData;  
  2. import  flash.display.BitmapDataChannel;  
  3. import  flash.filters.DisplacementMapFilter;  
  4. import  flash.filters.DisplacementMapFilterMode;  
  5. import  flash.geom.Point;  
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.filters.DisplacementMapFilter;
import flash.filters.DisplacementMapFilterMode;
import flash.geom.Point;

(You can learn more about the DisplacementMapFilter class in the AS3 documentation )

Step 14: Apply the Filter

We'll now create a variable to hold the filter. We apply it to the button by setting the button's 'filters' property to an array that contains our filter.

Here's the JitteryButton class so far (lines 18 and 25 are new):

  1. package  {  
  2.   
  3.     import  flash.display.Sprite;  
  4.     import  flash.display.SimpleButton;  
  5.     import  flash.display.BitmapData;  
  6.     import  flash.display.BitmapDataChannel;  
  7.     import  flash.filters.DisplacementMapFilter;  
  8.     import  flash.filters.DisplacementMapFilterMode;  
  9.     import  flash.geom.Point;  
  10.   
  11.     import  caurina.transitions.Tweener;  
  12.   
  13.     public   class  JitteryButton  extends  Sprite{  
  14.   
  15.         private   var  myButton:SimpleButton;  
  16.   
  17.         //create a variable to hold the displacement map filter   
  18.         private   var  dmFilter:DisplacementMapFilter = createDMFilter();  
  19.   
  20.         // CLASS CONSTRUCTOR   
  21.         public   function  JitteryButton(button:SimpleButton) {  
  22.             myButton = button;  
  23.   
  24.             // apply the filter to the button   
  25.             myButton.filters = new  Array(dmFilter);  
  26.         }  
  27.   
  28.   
  29.         // create the displacement map filter   
  30.         private   function  createDMFilter():DisplacementMapFilter {  
  31.   
  32.             var  mapBitmap:BitmapData =  new  StaticMap(0,0);  // use the bitmap data from our StaticMap image   
  33.             var  mapPoint:Point       =  new  Point(0, 0);   // this is the position of the StaticMap image in relation to our button   
  34.             var  channels:uint        = BitmapDataChannel.RED;  // which color to use for displacement   
  35.             var  componentX:uint      = channels;  
  36.             var  componentY:uint      = channels;  
  37.             var  scaleX:Number        = 5;  // the amount of horizontal shift   
  38.             var  scaleY:Number        = 1;  // the amount of vertical shift   
  39.             var  mode:String          = DisplacementMapFilterMode.COLOR;  
  40.             var  color:uint           = 0;  
  41.             var  alpha:Number         = 0;  
  42.   
  43.             return   new  DisplacementMapFilter(  
  44.                             mapBitmap,  
  45.                             mapPoint,  
  46.                             componentX,  
  47.                             componentY,  
  48.                             scaleX,  
  49.                             scaleY,  
  50.                             mode,  
  51.                             color,  
  52.                             alpha   );  
  53.   
  54.         }  
  55.   
  56.     }  
  57. }  
package {

	import flash.display.Sprite;
	import flash.display.SimpleButton;
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.filters.DisplacementMapFilter;
	import flash.filters.DisplacementMapFilterMode;
	import flash.geom.Point;

	import caurina.transitions.Tweener;

	public class JitteryButton extends Sprite{

		private var myButton:SimpleButton;

		//create a variable to hold the displacement map filter
		private var dmFilter:DisplacementMapFilter = createDMFilter();

		// CLASS CONSTRUCTOR
		public function JitteryButton(button:SimpleButton) {
			myButton = button;

			// apply the filter to the button
			myButton.filters = new Array(dmFilter);
		}


		// create the displacement map filter
		private function createDMFilter():DisplacementMapFilter {

			var mapBitmap:BitmapData = new StaticMap(0,0); // use the bitmap data from our StaticMap image
			var mapPoint:Point       = new Point(0, 0);  // this is the position of the StaticMap image in relation to our button
			var channels:uint        = BitmapDataChannel.RED; // which color to use for displacement
			var componentX:uint      = channels;
			var componentY:uint      = channels;
			var scaleX:Number        = 5; // the amount of horizontal shift
			var scaleY:Number        = 1; // the amount of vertical shift
			var mode:String          = DisplacementMapFilterMode.COLOR;
			var color:uint           = 0;
			var alpha:Number         = 0;

			return new DisplacementMapFilter(
							mapBitmap,
							mapPoint,
							componentX,
							componentY,
							scaleX,
							scaleY,
							mode,
							color,
							alpha	);

		}

	}
}

Step 15: Test Again

If we save and test the file now we can see the displacement map filter being applied to our button:

You can see how the horizontal lines we drew in the StaticMap are shifting the pixels in our button left and right. The amount of the shift is dependent on the darkness of the lines in the image and the scaleX setting in our Displacement Map Filter.

Unfortunately, the static isn't animating so it looks pretty lame. Let's fix that now...

Step 16: Add the randRange Function

This is a simple function that returns a random integer within a specified range:

  1. // returns a random number between the specified range (inclusive)   
  2. private   function  randRange(min: int , max: int ): int  {  
  3.     var  randomNum: int  = Math.floor(Math.random() * (max - min + 1)) + min;  
  4.     return  randomNum;  
  5. }  
	// returns a random number between the specified range (inclusive)
	private function randRange(min:int, max:int):int {
	    var randomNum:int = Math.floor(Math.random() * (max - min + 1)) + min;
	    return randomNum;
	}

I find it makes it a little easier to generate random values. We'll be randomizing a few different values for our static effect so it will come in handy.

Add it to your JitteryButton class.

Step 17: Animate the Displacement Map Filter

There are a couple of ways we can animate the static effect. The first will be to alter the amount of horizontal shift applied to our button. This is done through the scaleX property of the DisplacementMapFilter.

We can also animate the position of the StaticMap image in relation to our button. This will ensure that the same areas of the button aren't always getting shifted.

To animate the effect we'll add a function called 'displayStatic' that gets called every frame to update these two properties of the filter. Add this function to your JitteryButton class:

  1. // called on ENTER_FRAME   
  2. private   function  displayStatic(e:Event): void  {  
  3.     dmFilter.scaleX = randRange(fuzzMin, fuzzMax);  
  4.     dmFilter.mapPoint = new  Point(0, randRange(0, -160));  
  5.     myButton.filters = new  Array(dmFilter);  
  6. }  
// called on ENTER_FRAME
private function displayStatic(e:Event):void {
	dmFilter.scaleX = randRange(fuzzMin, fuzzMax);
	dmFilter.mapPoint = new Point(0, randRange(0, -160));
	myButton.filters = new Array(dmFilter);
}

The first line of this function randomizes the amount of horizontal shifting to a value between the variables fuzzMin and fuzzMax. Add these two variables to your JitteryButton class:

  1. private   var  fuzzMin: int  = 0;  
  2. private   var  fuzzMax: int  = 2;  
	private var fuzzMin:int = 0;
	private var fuzzMax:int = 2;

The second line of the displayStatic function randomizes the Y position of the StaticMap in relation to our button. We already told the filter to use our StaticMap image so we just need to update the position.

The third line just re-applies the filter to our button.

The last thing we need to do to get this animating is to add the ENTER_FRAME event listener. Add this line to the JitteryButton constructor function:

  1. // start displaying the static effect   
  2. addEventListener(Event.ENTER_FRAME, displayStatic);  
// start displaying the static effect
addEventListener(Event.ENTER_FRAME, displayStatic);

And don't forget to import the Event class at the top of the JitteryButton file:

  1. import  flash.events.Event;  
import flash.events.Event;

Step 18: Test Again

If you save and test the movie now you'll see the effect is making our button shimmer and jump:

That's pretty cool, but we want the effect to react to the mouse as well. Onward...

Step 19: Adjust the Intensity of the Effect

We'll now add two functions to adjust the intensity of the jitter effect. We'll call the effect we currently have Low intensity so we'll add a setting for Medium and High intensity. Add these functions to your JitteryButton class:

  1. // increase the intensity of the static to MEDIUM   
  2. private   function  setStaticMedium(e:MouseEvent =  null ): void  {  
  3.     fuzzMin = 2;  
  4.     fuzzMax = 6;  
  5.     staticLength = randRange(8, 12);  
  6. }  
  7.   
  8. // increase the intensity of the static to HIGH   
  9. private   function  setStaticHigh(e:MouseEvent =  null ): void  {    
  10.     fuzzMin = 12;  
  11.     fuzzMax = 25;  
  12.     staticLength = 12;  
  13. }  
// increase the intensity of the static to MEDIUM
private function setStaticMedium(e:MouseEvent = null):void {
	fuzzMin = 2;
	fuzzMax = 6;
	staticLength = randRange(8, 12);
}

// increase the intensity of the static to HIGH
private function setStaticHigh(e:MouseEvent = null):void {	
	fuzzMin = 12;
	fuzzMax = 25;
	staticLength = 12;
}

You can see that we're adjusting the intensity by setting the values of the fuzzMin and fuzzMax variables. This way our displayStatic function will use these new values when it sets the horizontal shift of the filter.

We also added a variable called staticLength. We'll use this to set how long the more intense effect should last (the number of frames) before returning to low intensity. Add this variable to your JitteryButton class and modify your displayStatic function like so:

  1. private   var  staticLength: int ;  
  2.   
  3. // called on ENTER_FRAME   
  4. private   function  displayStatic(e:Event): void  {  
  5.     dmFilter.scaleX = randRange(fuzzMin, fuzzMax);  
  6.     dmFilter.mapPoint = new  Point(0, randRange(0, -160));  
  7.     myButton.filters = new  Array(dmFilter);  
  8.   
  9.     staticLength--;  
  10.       
  11.     if (staticLength <= 0){  
  12.         fuzzMin = 0;  
  13.         fuzzMax = 2;  
  14.     }  
  15. }  
private var staticLength:int;

// called on ENTER_FRAME
private function displayStatic(e:Event):void {
	dmFilter.scaleX = randRange(fuzzMin, fuzzMax);
	dmFilter.mapPoint = new Point(0, randRange(0, -160));
	myButton.filters = new Array(dmFilter);

	staticLength--;
	
	if(staticLength <= 0){
		fuzzMin = 0;
		fuzzMax = 2;
	}
}

This new code decrements the staticLength variable and resets fuzzMin and fuzzMax to the low intensity values once the value of staticLength reaches zero.

Step 20: Set up the Button Rollover Handlers

To make our button react to the mouse we need to add two mouse event listeners and an event handler function for each.

Add the mouse listeners in the constructor function of your JitteryButton class:

  1. // add the rollover event listeners to the button   
  2. myButton.addEventListener(MouseEvent.ROLL_OVER, onButtonRollOver);  
  3. myButton.addEventListener(MouseEvent.ROLL_OUT, onButtonRollOut);  
// add the rollover event listeners to the button
myButton.addEventListener(MouseEvent.ROLL_OVER, onButtonRollOver);
myButton.addEventListener(MouseEvent.ROLL_OUT, onButtonRollOut);

Now create the two event handlers that are referenced in those two new lines. These also go in the JitteryButton class:

  1. // called on button ROLL_OVER   
  2. private   function  onButtonRollOver(e:MouseEvent): void  {  
  3.     setStaticHigh();  
  4. }  
  5.   
  6. // called on button ROLL_OUT   
  7. private   function  onButtonRollOut(e:MouseEvent): void  {  
  8.     setStaticMedium();  
  9. }  
// called on button ROLL_OVER
private function onButtonRollOver(e:MouseEvent):void {
	setStaticHigh();
}

// called on button ROLL_OUT
private function onButtonRollOut(e:MouseEvent):void {
	setStaticMedium();
}

To make this all work we'll have to import the MouseEvent class at the top of our JitteryButton file:

  1. import  flash.events.MouseEvent;  
import flash.events.MouseEvent;

Now when our button detects a ROLL_OVER event it will call the event handler which in turn calls our setStaticHigh function. This function increases the values of fuzzMin and fuzzMax (used for setting the horizontal shift) for the duration specified by the staticLength variable.

Step 21: Add the Scale Effect

We could stop here. Our effect is animating nicely and reacts to the mouse rollovers. I still feel like something is missing here though. Let's add a little scaling effect.

You'll need to download the Tweener Library for this step if you don't already have it. Place the 'caurina' folder in your project directory and import the Tweener classes at the top of your JitteryButton file:

  1. import  caurina.transitions.Tweener;  
import caurina.transitions.Tweener;

Tweener allows us to add some nice scaling effects with only a couple lines of code. We can add one line to each of our rollover event handlers:

  1. // called on button ROLL_OVER   
  2. private   function  onButtonRollOver(e:MouseEvent): void  {  
  3.     Tweener.addTween(myButton, {scaleX: 1.1, time: .5, transition: "easeOutElastic" });  
  4.     setStaticHigh();  
  5. }  
  6.   
  7. // called on button ROLL_OOUT   
  8. private   function  onButtonRollOut(e:MouseEvent): void  {  
  9.     Tweener.addTween(myButton, {scaleX: 1, time: .5, transition: "easeOutElastic" });  
  10.     setStaticMedium();  
  11. }  
// called on button ROLL_OVER
private function onButtonRollOver(e:MouseEvent):void {
	Tweener.addTween(myButton, {scaleX: 1.1, time: .5, transition: "easeOutElastic"});
	setStaticHigh();
}

// called on button ROLL_OOUT
private function onButtonRollOut(e:MouseEvent):void {
	Tweener.addTween(myButton, {scaleX: 1, time: .5, transition: "easeOutElastic"});
	setStaticMedium();
}

Here we're adding an animation to the rollover handler that scales the button's scaleX property to 110% over .5 seconds. We're using an elastic transition type to give it that bouncy feel. In the rollout handler we're doing the same thing in reverse, scaling it back to 100%.

You can learn more about how to use Tweener in the Tweener documentation .

Step 22: Add Sound

The last thing we need to do make this effect complete is to add some sound. I made my sound effect in Garage Band. You can make your own or try to find one online.

Once you have one you like, import it into your library and set the linkage to export as 'StaticSound'.

To add it to our JitteryButton we first need to import the Sound class:

  1. import  flash.media.Sound;  
import flash.media.Sound;

Then we'll initialize it (add this line just before the constructor function):

  1. private   var  staticSound:Sound =  new  StaticSound();  
private var staticSound:Sound = new StaticSound();

Inside the rollover handler we'll tell the sound to play:

  1. // called on button ROLL_OVER   
  2. private   function  onButtonRollOver(e:MouseEvent): void  {  
  3.     Tweener.addTween(myButton, {scaleX: 1.1, time: .5, transition: "easeOutElastic" });  
  4.     setStaticHigh();  
  5.     staticSound.play();  
  6. }  
// called on button ROLL_OVER
private function onButtonRollOver(e:MouseEvent):void {
	Tweener.addTween(myButton, {scaleX: 1.1, time: .5, transition: "easeOutElastic"});
	setStaticHigh();
	staticSound.play();
}

Now we're good to go. Test your movie and everything should be working. If your button or sound isn't working right check the source files to see my completed JitteryButton class.

Step 23: Add More Buttons

The cool thing about building this effect as a separate class that wraps our button is that we can easily reuse it on other buttons.

If you want to add more buttons to your game menu just create a new button and add it to the stage. Give it the instance name 'button2'. Then inside your document class (the 'Game.as' file) create a new JitteryButton and pass it the new button. Here's how that might look:

  1. package  {  
  2.     import  flash.display.MovieClip;  
  3.   
  4.     public   class  Game  extends  MovieClip {  
  5.   
  6.         private   var  startButton:JitteryButton;  
  7.         private   var  menuButton:JitteryButton;  
  8.   
  9.         // CLASS CONSTRUCTOR   
  10.         public   function  Game() {  
  11.             // create the jittery buttons from the simple buttons on the stage   
  12.             startButton = new  JitteryButton(button1);  
  13.             addChild(startButton);  
  14.   
  15.             // adding a new button is easy!   
  16.             menuButton = new  JitteryButton(button2);  
  17.             addChild(menuButton);  
  18.         }  
  19.   
  20.     }  
  21.   
  22. }  
package {
	import flash.display.MovieClip;

	public class Game extends MovieClip {

		private var startButton:JitteryButton;
		private var menuButton:JitteryButton;

		// CLASS CONSTRUCTOR
		public function Game() {
			// create the jittery buttons from the simple buttons on the stage
			startButton = new JitteryButton(button1);
			addChild(startButton);

			// adding a new button is easy!
			menuButton = new JitteryButton(button2);
			addChild(menuButton);
		}

	}

}

Conclusion

You will almost certainly need to change this code a bit to get it to fit into the structure of your game. Hopefully this tutorial will give you a good starting point though.

If you want to change the look of this effect you can try using different types of images for your StaticMap graphic, and adjusting the values for fuzzMin and fuzzMax.

This is my first tutorial so let me know if there's anything I can do better next time. Thanks for reading!


Related Posts

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值