LibGDX_4.8: 动作(Action)

本文链接: http://blog.csdn.net/xietansheng/article/details/50187485

LibGDX 基础教程(总目录)

1. 概述

动作(Action)是附加在演员身上的在指定时间内随着时间推移而被执行的一些任务逻辑。前面章节介绍的演员都是静态不会动的,给演员附加上动作可以让演员自己“动”起来,相当于 Android 中给 View 添加的动画(Animation)。

2. 动作的分类

LibGDX 中所有的动作均继承自 Action 抽象类,所有的动作实现均在 com.badlogic.gdx.scenes.scene2d.actions 包中。常用的动作可分为 特效类动作控制类动作 两种。

(1)特效类动作

特效类动作会改变演员的状态(位置,尺寸,旋转角度,缩放比,透明度等),还可细分为 绝对动作 和 相对动作。

绝对动作(从 当前状态 过渡到 指定状态 的动作,结果位置确定 并与原位置无关):

  • MoveToAction: 将演员从当前位置移动到指定的位置
  • RotateToAction: 将演员从当前角度旋转到指定的角度
  • ScaleToAction: 将演员从当前的缩放值过渡到指定的缩放值
  • SizeToAction: 将演员从当前尺寸(宽高)过渡到指定尺寸(宽高)
  • AlphaAction: 将演员的透明度从当前 alpha 值过渡到指定的 alpha 值

相对动作(在 当前状态 基础上给 某一状态的值 增加一个增量,结果位置相对于原位置):

  • MoveByAction: 演员在当前位置基础上移动指定的距离
  • RotateByAction: 演员在当前角度值的基础上旋转指定的角度
  • ScaleByAction: 演员在当前缩放值的基础上进行再次缩放
  • SizeByAction: 演员在当前尺寸基础上增加指定的尺寸

(2)控制类动作

控制类动作本身一般不会改变演员的状态,只是用来控制动作的执行(对动作的包装,本身也是一个动作),是控制动作的动作。

  • SequenceAction: 顺序动作,包含一个或多个动作,这些动作按顺序依次执行。
  • ParallelAction: 并行动作,包含一个或多个动作,这些动作一起同时执行。
  • RepeatAction: 重复动作,包含一个动作,重复执行这个被包含的动作。
  • DelayAction: 延时动作,一般添加到顺序动作的动作序列中使用。例如按顺序执行完某一动作后,执行一个延时动作(延时指定时间),然后再继续执行下一个动作。
  • AfterAction: 包含一个动作,该动作和其他动作一起添加到演员身上,等到演员身上的其他所有动作都执行完后,执行该 AfterAction 所包含的动作。
  • RunnableAction: 包含一个 Runnable 实例,可以在指定时机执行自己的代码。例如将一个 RunnableAction 添加到顺序动作的末尾,则可在顺序动作执行完成时执行自己的 Runnable 中 run() 方法内的代码。

3. 代码示例

特别注意: 动作需要附加在演员身上执行(本质上其实就是动态改变演员的状态属性值),所以演示动作的使用,首先需要有一个自定义的演员,这里不能直接引用前面章节自定义的演员,因为前面章节的演员绘制时只处理了 位置,尺寸,缩放比 和 旋转角度(分别对应 Move,Size,Scale,Rotate 这四种动作所需要改变的目标状态属性)。这里还有一个 AlphaAction 动作,AlphaAction 所需要改变的目标属性是演员的 alpha 属性,所以要对前面章节自定义的演员进行 增强封装 ,在绘制时 处理 color/alpha 属性 。如下代码所示:

自定义演员(绘制时处理 位置,尺寸,缩放比,旋转角度 和 color/alpha 等属性):

package com.libgdx.test;
	
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;

/**
 * 自定义演员(绘制时处理 位置,尺寸,缩放比,旋转角度 和 color/alpha 等属性)
 */
public class MyActor extends Actor {

    private TextureRegion region;
    
    public MyActor(TextureRegion region) {
        super();
        this.region = region;
        setSize(this.region.getRegionWidth(), this.region.getRegionHeight());
    }

    public TextureRegion getRegion() {
        return region;
    }

    public void setRegion(TextureRegion region) {
        this.region = region;
        setSize(this.region.getRegionWidth(), this.region.getRegionHeight());
    }

    @Override
    public void act(float delta) {
        super.act(delta);
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);
        if (region == null || !isVisible()) {
            return;
        }
        
        /*
         * 先把 batch 原本的 color 保存起来, 因为 batch 是从外部传进来的, 最好不要改变它原本的状态,
         * 但在这里需要重新设置 batch 的 color, 所以先保存起来, 等当前方法执行完时再将 batch 原本的 color 设置回去。
         */
        Color tempBatchColor = batch.getColor();
        
        /*
         * 实际上演员并没有单独的 alpha 属性, alpha 包含在颜色(color)属性中, rgba color 中的 a 表示 alpha;
         * 演员有 alpha 值, 而父节点(舞台/演员组)中也有 alpha 值(parentAlpha)。 由于最终在演员节点中才真正把纹理
         * 绘制在屏幕上, 才是真正绘制的地方, 而父节点一般用于组织演员, 不会直接绘制任何纹理, 透明度 alpha 值只有在绘制
         * 时才能体现出来, 所以父节点无法体现自己的 alpha 值, 因此父节点会将自己的 alpha 值(就是draw方法中的参数 parentAlpha)
         * 传递给它自己的所有子节点,即最终直接绘制纹理的演员, 让演员结合自身的 alpha 值在绘制时综合体现。
         */
        
        // 获取演员的 color 属性
        Color color = getColor();
        
        /*
         * 处理 color/alpha 属性, 即将演员的 rgba color 设置到纹理画布 batch。
         * 其中的 alpha 需要结合演员和父节点的 alpha, 即演员的 alpha 与父节点的 alpha 相乘,
         * 例如父节点的 alpha 为 0.5, 演员的 alpha 为 0.5, 那么最终的显示效果就是 0.5 * 0.5 = 0.25
         */
        batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
        
        // 处理 位置, 缩放和旋转的支点, 尺寸, 缩放比, 旋转角度
        batch.draw(
                region, 
                getX(), getY(), 
                getOriginX(), getOriginY(), 
                getWidth(), getHeight(), 
                getScaleX(), getScaleY(), 
                getRotation()
        );
        
        // 将 batch 原本的 color 设置回去
        batch.setColor(tempBatchColor);
    }
}

游戏主程序的启动入口类(包含所有动作的演示):

package com.libgdx.test;
		
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.actions.AfterAction;
import com.badlogic.gdx.scenes.scene2d.actions.AlphaAction;
import com.badlogic.gdx.scenes.scene2d.actions.DelayAction;
import com.badlogic.gdx.scenes.scene2d.actions.MoveByAction;
import com.badlogic.gdx.scenes.scene2d.actions.MoveToAction;
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction;
import com.badlogic.gdx.scenes.scene2d.actions.RotateByAction;
import com.badlogic.gdx.scenes.scene2d.actions.RotateToAction;
import com.badlogic.gdx.scenes.scene2d.actions.RunnableAction;
import com.badlogic.gdx.scenes.scene2d.actions.ScaleByAction;
import com.badlogic.gdx.scenes.scene2d.actions.ScaleToAction;
import com.badlogic.gdx.scenes.scene2d.actions.SequenceAction;
import com.badlogic.gdx.scenes.scene2d.actions.SizeByAction;
import com.badlogic.gdx.scenes.scene2d.actions.SizeToAction;
import com.badlogic.gdx.utils.viewport.StretchViewport;
	
/**
 * 游戏程序的启动入口类
 */
public class MainGame extends ApplicationAdapter {
	
	private static final String TAG = MainGame.class.getSimpleName();

	// 视口世界的宽高统使用 480 * 800, 并统一使用伸展视口(StretchViewport)
	public static final float WORLD_WIDTH = 480;
	public static final float WORLD_HEIGHT = 800;
	
	private Texture texture;
	
	private Stage stage;
	
	private MyActor actor;
	
	@Override
	public void create() {
		// 设置 log 输出级别
		Gdx.app.setLogLevel(Application.LOG_DEBUG);
		
		// 创建纹理, badlogic.jpg 图片的宽高为 256 * 256
		texture = new Texture(Gdx.files.internal("badlogic.jpg"));
		
		// 使用伸展视口(StretchViewport)创建舞台
		stage = new Stage(new StretchViewport(WORLD_WIDTH, WORLD_HEIGHT));
		
		// 创建演员
		actor = new MyActor(new TextureRegion(texture));
		
		// 添加演员到舞台
		stage.addActor(actor);
		
		
		/*
		 * 动作演示
		 * 
		 * 有些时候动作需要频繁使用, 如果每次要使用动作都重新创建一个对象, 会消耗性能。
		 * LibGDX 中对动作实例使用了对象池进行了缓存, 使用完一个动作对象后不会直接让系统回收对象, 
		 * 而是放到对象池中缓存待下次重复使用。LibGDX 中所有动作实例的获取均通过 Actions 工具类
		 * 中的静态方法获取(Actions 中实现了对象池缓存)。
		 * 
		 * 获取到一个动作对象后附加在演员身上执行(addAction), 动作执行完后会自动从演员身上移除
		 * 并放回对象池中缓存。 当然在动作还没有执行完时也可在中途手动移除(removeAction)。
		 * 
		 * 下面对每一种动作的演示放在单独方法中, 按顺序打开相应测试方法的注释进行测试(打开一个方法的
		 * 注释后必须要注释掉其他测试方法, 否则多个测试同时执行看不出效果)。
		 */
		
		// 1. 移动动作
		testMoveToAction();
		
		// 2. 移动动作(相对)
		// testMoveByAction();
		
		// 3. 旋转动作
		// testRotateToAction();
		
		// 4. 旋转动作(相对)
		// testRotateByAction();
		
		// 5. 缩放动作
		// testScaleToAction();
		
		// 6. 缩放动作(相对)
		// testScaleByAction();
		
		// 7. 尺寸改变动作
		// testSizeToAction();
		
		// 8. 尺寸改变动作(相对)
		// testSizeByAction();
		
		// 9. 透明度动作
		// testAlphaAction();
		
		// 10. 并行动作
		// testParallelAction();
		
		// 11. 顺序动作(包含了延时动作的演示)
		// testSequenceAction();
		
		// 12. 重复动作
		// testRepeatAction();
		
		// 13. Runnable 动作
		// testRunnableAction();
		
		// 14. After 动作
		// testAfterAction();
	}
	
	/**
	 * 1. 移动动作
	 */
	private void testMoveToAction() {
		// 设置演员初始化位置
		actor.setPosition(0, 0);
		
		// 获取一个 MoveTo 动作, 3 秒内移动到 (150, 300) 的位置
		MoveToAction action = Actions.moveTo(150, 300, 3.0F);
		
		// 将动作附加在演员身上, 执行动作
		actor.addAction(action);
		
		/*
		 * 动作执行原理(查看 Actor 和相应 Action 的源码): 
		 * 
		 * 实际上动作添加到演员身上的后, 动作被存放到一个数组中, 然后在更新演员逻辑的 actor.act()方法中遍历存放动作的数组,
		 * 对每一个动作根据时间步 delta 改变演员相应的状态属性值。然后在绘制演员的 actor.draw() 方法中绘制演员时使用新的
		 * 状态属性值绘制, 和上一帧相比, 就显的演员被“动”起来了。
		 */
	}
	
	/**
	 * 2. 移动动作(相对)
	 */
	private void testMoveByAction() {
		// 演员初始化位置设置显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 获取一个 MoveBy 动作
		// 2 秒内, 在演员在原位置基础上, 水平方向移动 100, 竖直方向移动 -200
		MoveByAction action = Actions.moveBy(100, -200, 2.0F);

		// 将动作附近在演员身上, 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 3. 旋转动作
	 */
	private void testRotateToAction() {
		// 设置演员显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 设置演员的初始角度为 -90 度(逆时针为正, 顺时针为负)
		actor.setRotation(-90);
		
		// 获取一个 RotateTo 动作, 2 秒内 从原角度 旋转到 -270 度(最终角度为 -270 度)
		RotateToAction action = Actions.rotateTo(-270, 2.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 4. 旋转动作(相对)
	 */
	private void testRotateByAction() {
		// 设置演员显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 设置演员的初始角度为 -90 度(逆时针为正, 顺时针为负)
		actor.setRotation(-90);
		
		// 获取一个 RotateBy 动作, 2 秒内 从原角度基础上增加45度(最终角度为 -90 + 45 = -45 度)
		RotateByAction action = Actions.rotateBy(45, 2.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 5. 缩放动作
	 */
	private void testScaleToAction() {
		// 设置演员显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 设置演员的水平和竖直方向初始缩放比分别为 0.5 和 2.0
		actor.setScale(0.5F, 2.0F);
		
		// 获取一个 ScaleTo 动作, 2 秒内水平和竖直方向缩放比从 原缩放比 分别缩放到 1.0 和 1.0,
		// 最终水平和竖直方向缩放比分别为 1.0 和 1.0
		ScaleToAction action = Actions.scaleTo(1.0F, 1.0F, 2.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 6. 缩放动作(相对)
	 */
	private void testScaleByAction() {
		// 设置演员显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 设置演员的水平和竖直方向初始缩放比分别为 0.5 和 0.5
		actor.setScale(0.5F, 0.5F);
		
		// 获取一个 ScaleBy 动作, 2 秒内水平和竖直方向缩放比从 原缩放比基础上 分别加上 0.5 和 0.5(正数表示增加, 负数表示减少),
		// ScaleBy 是对原缩放值单纯的数值相加, 最终水平和竖直方向的缩放比均为: 0.5 + 0.5 = 1.0
		ScaleByAction action = Actions.scaleBy(0.5F, 0.5F, 2.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 7. 尺寸改变动作
	 */
	private void testSizeToAction() {
		// 设置演员初始化位置
		actor.setPosition(0, 0);
		
		// 获取一个 SizeTo 动作, 2 秒内从原来尺寸变到宽为 150, 高为 300 (最终宽高为 150 * 300)
		SizeToAction action = Actions.sizeTo(150, 300, 2.0F);
		
		// 执行动作
		actor.addAction(action);
		
		/*
		 * Scale 和 Size 的区别:
		 * 
		 * Size 指的是演员的宽高, Scale 指的是演员相对于 Size 的缩放比。
		 * 如果 Scale 为 0.5, 
		 * 当 Size 变为 256 时, 绘制到屏幕上的大小变为 256 * 0.5 = 128,
		 * 当 Size 变为 512 时, 绘制到屏幕上的大小变为 512 * 0.5 = 256,
		 */
	}

	/**
	 * 8. 尺寸改变动作(相对)
	 */
	private void testSizeByAction() {
		// 设置演员初始化位置
		actor.setPosition(0, 0);
		
		// 演员初始宽高为图片的宽高 256 * 256
		
		// 获取一个 SizeTo 动作, 2 秒内宽高从原来基础上分别增加150, 300 
		// 最终宽度为: 256 + 150 = 406
		// 最终高度为: 256 + 300 = 556
		SizeByAction action = Actions.sizeBy(150, 300, 2.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 9. 透明度动作
	 */
	private void testAlphaAction() {
		// 设置演员初始化位置
		actor.setPosition(0, 0);
		
		// 设置演员初始 alpha 值为 1(完全不透明, 默认就是为 1)
		actor.getColor().a = 1.0F;
		
		// 获取一个 Alpha 动作, 5 秒内 alpha 变为 0(完全透明)
		AlphaAction action = Actions.alpha(0.0F, 5.0F);
		
		// 执行动作
		actor.addAction(action);
	}
	
	/**
	 * 10. 并行动作: 同时 移动, 缩放, 旋转
	 */
	private void testParallelAction() {
		// 设置演员初始化状态
		actor.setPosition(0, 0);
		actor.setScale(0.5F, 0.5F);
		actor.setRotation(0);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 移动动作
		MoveToAction moveTo = Actions.moveTo(150, 500, 3.0F);
		
		// 缩放动作
		ScaleToAction scaleTo = Actions.scaleTo(1.0F, 1.0F, 3.0F);
		
		// 旋转动作
		RotateByAction rotateBy = Actions.rotateBy(360.0F, 3.0F);
		
		// 并行动作, 包含 moveTo, scaleTo, rotateBy
		ParallelAction parallelAction = Actions.parallel(moveTo, scaleTo, rotateBy);
		
		// 执行并行动作
		actor.addAction(parallelAction);
	}
	
	/**
	 * 11. 顺序动作(包含了延时动作的演示): 先延时, 然后移动, 再旋转并缩放
	 */
	private void testSequenceAction() {
		// 设置演员初始化状态
		actor.setPosition(0, 0);
		actor.setScale(1.0F, 1.0F);
		actor.setRotation(0);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 1. 延时动作, 延时 3 秒
		DelayAction delay = Actions.delay(3.0F);
		
		// 2. 移动动作
		MoveToAction moveTo = Actions.moveTo(150, 500, 3.0F);
		
		// 3. 并行动作, 包含 缩放 和 旋转 两个动作
		ParallelAction parallel = Actions.parallel(
				Actions.scaleTo(0.5F, 0.5F, 3.0F), 
				Actions.rotateBy(360.0F, 3.0F)
		);
		
		// 顺序动作, 包含 delay, moveTo, parallel
		SequenceAction sequenceAction = Actions.sequence(delay, moveTo, parallel);
		
		// 执行顺序动作
		actor.addAction(sequenceAction);
	}
	
	/**
	 * 12. 重复动作: 重复 缩小, 放大
	 */
	private void testRepeatAction() {
		// 设置演员显示到舞台中心
		actor.setPosition(
				actor.getStage().getWidth() / 2 - actor.getWidth() / 2, 
				actor.getStage().getHeight() / 2 - actor.getHeight() / 2
		);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 设置演员初始缩放比
		actor.setScale(1.0F, 1.0F);
		
		// 顺序动作: 先缩小到 0.5, 再放大到 1.0
		SequenceAction sequence = Actions.sequence(
				Actions.scaleTo(0.5F, 0.5F, 2.0F), 
				Actions.scaleTo(1.0F, 1.0F, 2.0F)
		);
		
		// 重复动作: 重复执行 sequence
		RepeatAction repeatAction = Actions.forever(sequence);
		
		// 执行重复动作
		actor.addAction(repeatAction);
	}
	
	/**
	 * 13. Runnable 动作: 适当时机执行自己的代码, 与顺序动作一起使用可用于监听某个动作的完成
	 */
	private void testRunnableAction() {
		// 设置演员初始化状态
		actor.setPosition(0, 0);
		
		// 移动动作
		MoveToAction moveTo = Actions.moveTo(150, 300, 3.0F);
		
		// Runnable 动作
		RunnableAction runnable = Actions.run(new Runnable() {
			@Override
			public void run() {
				// 打印一句 log 表示动作已执行
				Gdx.app.log(TAG, "The runnable action has been running.");
			}
		});
		
		// 顺序动作: 在 moveTo 动作执行完后执行 runnable 动作
		SequenceAction sequence = Actions.sequence(moveTo, runnable);
		
		// 执行顺序动作
		actor.addAction(sequence);
	}
	
	/**
	 * 14. After 动作: 可用于监听演员动作的执行完成
	 */
	private void testAfterAction() {
		// 设置演员初始化状态
		actor.setPosition(0, 0);
		actor.setRotation(0);
		
		// 缩放和旋转支点设置到演员中心
		actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
		
		// 移动动作, 移动 3 秒
		MoveToAction moveTo = Actions.moveTo(150, 300, 3.0F);
		
		// 旋转动作, 旋转 2 秒
		RotateByAction rotateBy = Actions.rotateBy(360.0F, 2.0F);
		
		// Runnable 动作
		RunnableAction runnable = Actions.run(new Runnable() {
			@Override
			public void run() {
				// 打印一句 log 表示动作已执行
				Gdx.app.log(TAG, "演员的其他所有动作都已经执行完了");
			}
		});
		
		// After 动作, 包含一个 runnable 动作
		AfterAction afterAction = Actions.after(runnable);
		
		// 同时添加多个动作到演员: 将立即执行 moveTo 和 rotateBy, 都执行完后执行 afterAction
		actor.addAction(moveTo);
		actor.addAction(rotateBy);
		actor.addAction(afterAction);
	}
	
	@Override
	public void render() {
		// 使用淡蓝色清屏
        Gdx.gl.glClearColor(0.75F, 1, 0.98F, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		
		// 更新舞台逻辑
		stage.act();
		// 绘制舞台
		stage.draw();
	}
	
	@Override
	public void dispose() {
		// 释放资源
		if (stage != null) {
			stage.dispose();
		}
		if (texture != null) {
			texture.dispose();
		}
	}

}

动作演示较多,结果也较复杂,这里不再展示运行结果。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢TS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值