Cocos Creator引擎开发:VR角色技能与战斗系统_(5).VR战斗系统基础架构

VR战斗系统基础架构

在虚拟现实游戏中,战斗系统是核心部分之一,它直接影响玩家的游戏体验和沉浸感。本节将详细介绍如何在Cocos Creator引擎中构建一个基础的VR战斗系统,包括角色控制、技能系统、战斗逻辑和碰撞检测等关键组件。我们将通过具体的代码示例来展示如何实现这些功能,确保您能够理解和应用这些技术。

角色控制

在VR战斗系统中,角色控制是玩家与游戏世界交互的基础。我们将介绍如何实现基本的角色移动、旋转和交互控制。

角色移动

角色移动可以分为两类:瞬移(Teleport)和连续移动(Continuous Movement)。瞬移适用于较大的地图,而连续移动适用于需要精确控制的场景。

瞬移

瞬移通常通过玩家的控制器(如手柄)来实现。玩家可以通过控制器选择目标位置,然后瞬间移动到该位置。以下是一个实现瞬移的基本代码示例:


// Teleport.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 瞬移的目标位置

        targetPosition: cc.Vec3,

        // 瞬移的距离限制

        maxTeleportDistance: 5,

        // 瞬移的特效节点

        teleportEffect: cc.Node

    },



    // 更新瞬移的目标位置

    updateTargetPosition: function (position) {

        this.targetPosition = position;

    },



    // 执行瞬移

    teleport: function () {

        // 获取当前角色位置

        const currentPosition = this.node.position;

        // 计算目标位置与当前位置的距离

        const distance = cc.Vec3.distance(currentPosition, this.targetPosition);



        if (distance <= this.maxTeleportDistance) {

            // 移动角色到目标位置

            this.node.setPosition(this.targetPosition);

            // 播放瞬移特效

            this.teleportEffect.active = true;

            this.teleportEffect.opacity = 255;



            // 动画结束后隐藏特效

            this.teleportEffect.runAction(cc.fadeOut(1.0));

        } else {

            console.log("目标位置超出瞬移范围");

        }

    }

});

连续移动

连续移动通过控制器的摇杆或按钮来实现。玩家可以通过控制器输入方向和速度,实现角色的连续移动。以下是一个实现连续移动的基本代码示例:


// ContinuousMovement.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 移动速度

        moveSpeed: 5,

        // 旋转速度

        rotateSpeed: 100

    },



    // 更新角色位置和旋转

    update: function (dt) {

        // 获取控制器输入

        const inputX = cc.input.getAxis("Horizontal");

        const inputY = cc.input.getAxis("Vertical");



        // 计算移动方向

        const moveDirection = new cc.Vec3(inputX, 0, inputY);

        moveDirection.normalize();



        // 移动角色

        this.node.position = this.node.position.add(moveDirection.mulScalar(this.moveSpeed * dt));



        // 计算旋转角度

        const rotationAngle = inputX * this.rotateSpeed * dt;



        // 旋转角色

        this.node.eulerAngles = this.node.eulerAngles.add(new cc.Vec3(0, rotationAngle, 0));

    }

});

角色旋转

角色旋转可以通过控制器输入或头部追踪来实现。以下是一个通过控制器输入实现角色旋转的代码示例:


// RotateByController.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 旋转速度

        rotateSpeed: 100

    },



    // 更新角色旋转

    update: function (dt) {

        // 获取控制器输入

        const inputX = cc.input.getAxis("Horizontal");



        // 计算旋转角度

        const rotationAngle = inputX * this.rotateSpeed * dt;



        // 旋转角色

        this.node.eulerAngles = this.node.eulerAngles.add(new cc.Vec3(0, rotationAngle, 0));

    }

});

交互控制

交互控制允许玩家与游戏世界中的物体进行互动,例如拾取物品、打开门等。以下是一个实现交互控制的代码示例:


// Interact.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 交互距离

        interactDistance: 2,

        // 交互提示UI

        interactUI: cc.Node

    },



    // 更新交互提示

    update: function (dt) {

        // 获取玩家前方的目标物体

        const target = this.getTarget();



        if (target) {

            // 显示交互提示

            this.interactUI.active = true;

            // 检测交互按钮是否被按下

            if (cc.input.isButtonPressed("Interact")) {

                // 执行交互逻辑

                this.interactWith(target);

            }

        } else {

            // 隐藏交互提示

            this.interactUI.active = false;

        }

    },



    // 获取玩家前方的目标物体

    getTarget: function () {

        const ray = this.node.getComponent(cc.Camera).screenPointToRay(new cc.Vec2(0.5, 0.5));

        const hitResult = cc.physics.raycastClosest(ray.origin, ray.origin.add(ray.direction.mulScalar(this.interactDistance)));



        if (hitResult && hitResult.collider) {

            return hitResult.collider.node;

        }



        return null;

    },



    // 与目标物体交互

    interactWith: function (target) {

        if (target.getComponent("Interactable")) {

            target.getComponent("Interactable").interact();

        }

    }

});

技能系统

技能系统是VR战斗系统中不可或缺的一部分。我们将介绍如何实现基本的技能系统,包括技能的定义、冷却时间和释放逻辑。

技能定义

技能可以通过定义一个技能类来实现,每个技能类包含技能的属性和方法。以下是一个简单的技能类示例:


// Skill.js

cc.Class({

    name: 'Skill',

    properties: {

        // 技能名称

        name: "",

        // 技能冷却时间(秒)

        cooldown: 0,

        // 技能最后一次释放时间

        lastUsedTime: 0

    },



    // 检查技能是否可以使用

    canUse: function (currentTime) {

        return currentTime - this.lastUsedTime >= this.cooldown;

    },



    // 使用技能

    use: function (currentTime) {

        if (this.canUse(currentTime)) {

            this.lastUsedTime = currentTime;

            return true;

        }

        return false;

    }

});

技能管理

技能管理器负责管理和调用角色的技能。以下是一个技能管理器的示例代码:


// SkillManager.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 技能列表

        skills: {

            default: [],

            type: Skill

        }

    },



    // 更新技能状态

    update: function (dt) {

        const currentTime = cc.director.getTime();



        // 检查每个技能是否可以使用

        for (let i = 0; i < this.skills.length; i++) {

            if (this.skills[i].canUse(currentTime)) {

                // 检测技能按钮是否被按下

                if (cc.input.isButtonPressed(`Skill${i + 1}`)) {

                    this.skills[i].use(currentTime);

                    // 执行技能效果

                    this.executeSkillEffect(this.skills[i]);

                }

            }

        }

    },



    // 执行技能效果

    executeSkillEffect: function (skill) {

        switch (skill.name) {

            case "Fireball":

                this.createFireball();

                break;

            case "Heal":

                this.healPlayer();

                break;

            // 其他技能

            default:

                console.log("未知技能");

        }

    },



    // 创建火球

    createFireball: function () {

        const fireballPrefab = cc.find("Prefabs/Fireball").getComponent(cc.Prefab);

        const fireball = cc.instantiate(fireballPrefab);

        fireball.setPosition(this.node.position);

        this.node.parent.addChild(fireball);

    },



    // 治疗玩家

    healPlayer: function () {

        const player = cc.find("Player");

        const playerHealth = player.getComponent("Health");

        playerHealth.heal(20);

    }

});

技能释放逻辑

技能释放逻辑包括技能的效果、范围和目标选择等。以下是一个火球技能的实现示例:


// Fireball.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 火球速度

        speed: 10,

        // 火球范围

        range: 5,

        // 火球伤害

        damage: 20

    },



    onLoad: function () {

        // 初始化火球方向

        this.direction = this.node.getComponent(cc.Camera).forward;

        this.direction.y = 0;

        this.direction.normalize();

    },



    update: function (dt) {

        // 移动火球

        this.node.position = this.node.position.add(this.direction.mulScalar(this.speed * dt));



        // 检测火球是否超出范围

        if (cc.Vec3.distance(this.node.position, this.node.parent.position) > this.range) {

            this.node.destroy();

        }

    },



    onCollisionEnter: function (other, self) {

        // 检测碰撞物体是否为敌人

        if (other.tag === "Enemy") {

            const enemyHealth = other.node.getComponent("Health");

            enemyHealth.takeDamage(this.damage);

            this.node.destroy();

        }

    }

});

战斗逻辑

战斗逻辑包括攻击、防御、伤害计算和生命值管理等。我们将通过具体的代码示例来展示如何实现这些功能。

攻击

攻击可以分为近战攻击和远程攻击。以下是一个近战攻击的实现示例:


// MeleeAttack.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 攻击范围

        attackRange: 1,

        // 攻击伤害

        attackDamage: 10,

        // 攻击动画

        attackAnimation: cc.Animation

    },



    // 执行近战攻击

    attack: function () {

        this.attackAnimation.play("Attack");



        // 检测攻击范围内的敌人

        const enemies = cc.physics.queryColliders(this.node, this.attackRange, "Enemy");



        for (let i = 0; i < enemies.length; i++) {

            const enemyHealth = enemies[i].node.getComponent("Health");

            enemyHealth.takeDamage(this.attackDamage);

        }

    }

});

远程攻击

远程攻击通常涉及发射弹道或投掷物体。以下是一个远程攻击的实现示例:


// RangedAttack.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 攻击范围

        attackRange: 5,

        // 攻击伤害

        attackDamage: 20,

        // 弹道预制体

        projectilePrefab: cc.Prefab

    },



    // 执行远程攻击

    attack: function () {

        const projectile = cc.instantiate(this.projectilePrefab);

        projectile.setPosition(this.node.position);

        this.node.parent.addChild(projectile);



        // 设置弹道方向

        const direction = this.node.getComponent(cc.Camera).forward;

        direction.y = 0;

        direction.normalize();



        projectile.getComponent("Projectile").setDirection(direction);

        projectile.getComponent("Projectile").setDamage(this.attackDamage);

    }

});

伤害计算

伤害计算是战斗系统的核心部分之一,它决定了攻击对目标的影响。以下是一个简单的伤害计算示例:


// Health.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 最大生命值

        maxHealth: 100,

        // 当前生命值

        currentHealth: 100,

        // 受到伤害时的动画

        damageAnimation: cc.Animation

    },



    // 受到伤害

    takeDamage: function (damage) {

        this.currentHealth -= damage;

        this.damageAnimation.play("TakeDamage");



        if (this.currentHealth <= 0) {

            this.die();

        }

    },



    // 治疗

    heal: function (amount) {

        this.currentHealth += amount;

        if (this.currentHealth > this.maxHealth) {

            this.currentHealth = this.maxHealth;

        }

    },



    // 死亡

    die: function () {

        this.node.destroy();

    }

});

生命值管理

生命值管理负责处理角色的生命值和状态。以下是一个生命值管理的示例代码:


// HealthBar.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 生命值UI

        healthUI: cc.Node,

        // 生命值UI的最大长度

        maxHealthUIWidth: 100

    },



    // 更新生命值UI

    updateHealthUI: function (currentHealth, maxHealth) {

        const healthRatio = currentHealth / maxHealth;

        this.healthUI.width = this.maxHealthUIWidth * healthRatio;

    }

});

碰撞检测

碰撞检测是战斗系统中用于检测攻击是否命中目标的关键技术。Cocos Creator提供了内置的物理引擎,可以方便地实现碰撞检测。

碰撞检测器

碰撞检测器负责检测物体之间的碰撞。以下是一个实现碰撞检测的示例代码:


// Collider.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 碰撞类型(例如:敌人、墙壁等)

        tag: ""

    },



    onCollisionEnter: function (other, self) {

        console.log(`碰撞检测: ${self.node.name} 碰撞到 ${other.node.name}`);

        if (other.tag === "Enemy") {

            const enemyHealth = other.node.getComponent("Health");

            enemyHealth.takeDamage(10);

        }

    }

});

物理配置

物理配置包括物理材质、碰撞层和碰撞矩阵等。以下是一个配置物理属性的示例代码:


// PhysicsConfig.js

cc.Class({

    extends: cc.Component,



    onLoad: function () {

        // 配置物理材质

        const physicsMaterial = new cc.PhysicsMaterial(0.5, 0.5, 0.5);

        this.node.addComponent(cc.PhysicsBoxCollider).material = physicsMaterial;



        // 配置碰撞层

        this.node.addComponent(cc.PhysicsBoxCollider).group = "Player";

        this.node.addComponent(cc.PhysicsBoxCollider).mask = "Enemy|Wall";



        // 配置碰撞矩阵

        cc.game.addPersistRootNode(this.node);

        const physicsManager = cc.director.getPhysicsManager();

        physicsManager.enabled = true;

        physicsManager.debugDrawFlags = cc.PhysicsManager.DrawBits.e_aabbBit |

                                       cc.PhysicsManager.DrawBits.e_pairBit |

                                       cc.PhysicsManager.DrawBits.e_centerOfMassBit |

                                       cc.PhysicsManager.DrawBits.e_jointBit |

                                       cc.PhysicsManager.DrawBits.e_shapeBit;

    }

});

战斗UI

战斗UI是玩家获取战斗信息的重要途径,包括生命值、技能冷却时间等。我们将介绍如何实现一个基本的战斗UI。

生命值UI

生命值UI显示角色的当前生命值。以下是一个实现生命值UI的示例代码:


// HealthUI.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 生命值UI的条

        healthBar: cc.Sprite,

        // 生命值UI的最大宽度

        maxHealthBarWidth: 100,

        // 角色的健康组件

        playerHealth: {

            default: null,

            type: Health

        }

    },



    // 更新生命值UI

    update: function (dt) {

        const healthRatio = this.playerHealth.currentHealth / this.playerHealth.maxHealth;

        this.healthBar.node.width = this.maxHealthBarWidth * healthRatio;

    }

});

技能冷却UI

技能冷却UI显示技能的冷却时间。以下是一个实现技能冷却UI的示例代码:


// SkillCooldownUI.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 技能UI的条

        skillBar: cc.Sprite,

        // 技能UI的最大宽度

        maxSkillBarWidth: 100,

        // 技能管理器

        skillManager: {

            default: null,

            type: SkillManager

        }

    },



    // 更新技能冷却UI

    update: function (dt) {

        const currentTime = cc.director.getTime();

        for (let i = 0; i < this.skillManager.skills.length; i++) {

            const skill = this.skillManager.skills[i];

            const cooldownBar = this.skillBar.node.children[i].getComponent(cc.Sprite);



            if (skill.canUse(currentTime)) {

                cooldownBar.node.width = this.maxSkillBarWidth;

            } else {

                const elapsed = currentTime - skill.lastUsedTime;

                const remaining = skill.cooldown - elapsed;

                const cooldownRatio = remaining / skill.cooldown;

                cooldownBar.node.width = this.maxSkillBarWidth * cooldownRatio;

            }

        }

    }

});

战斗音效

战斗音效可以增强战斗的沉浸感。我们将介绍如何在Cocos Creator中添加战斗音效。

音效播放

音效可以通过Cocos Creator的音频组件来播放。以下是一个播放音效的示例代码:


// AudioController.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 攻击音效

        attackSound: cc.AudioClip,

        // 受伤音效

        damageSound: cc.AudioClip,

        // 死亡音效

        dieSound: cc.AudioClip

    },



    // 播放攻击音效

    playAttackSound: function () {

        cc.audioEngine.playEffect(this.attackSound, false);

    },



    // 播放受伤音效

    playDamageSound: function () {

        cc.audioEngine.playEffect(this.damageSound, false);

    },



    // 播放死亡音效

    playDieSound: function () {

        cc.audioEngine.playEffect(this.dieSound, false);

    }

});

音效管理

音效管理器负责管理和播放游戏中的音效。以下是一个音效管理器的示例代码:


// AudioManager.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 音效控制器

        audioController: {

            default: null,

            type: AudioController

        }

    },



    // 播放攻击音效

    playAttackSound: function () {

        this.audioController.playAttackSound();

    },



    // 播放受伤音效

    playDamageSound: function () {

        this.audioController.playDamageSound();

    },



    // 播放死亡音效

    playDieSound: function () {

        this.audioController.playDieSound();

    }

});

战斗动画

战斗动画是提升游戏体验的重要元素,它可以增强战斗的真实感和视觉效果。以下是如何在Cocos Creator中实现战斗动画的基本步骤。

动画定义

首先,需要在Cocos Creator的动画编辑器中定义角色的动画,例如攻击动画、受伤动画和死亡动画。这些动画可以通过导入3D模型或手动创建关键帧来实现。

动画播放

在代码中,可以根据战斗逻辑播放相应的动画。以下是一个示例代码:


// AnimationController.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 动画组件

        animation: {

            default: null,

            type: cc.Animation

        }

    },



    // 播放攻击动画

    playAttackAnimation: function () {

        this.animation.play("Attack");

    },



    // 播放受伤动画

    playDamageAnimation: function () {

        this.animation.play("TakeDamage");

    },



    // 播放死亡动画

    playDieAnimation: function () {

        this.animation.play("Die");

    }

});

动画与战斗逻辑的结合

将动画与战斗逻辑结合,可以确保在执行攻击、受伤或死亡等动作时,播放相应的动画。以下是一个近战攻击与动画结合的示例代码:


// MeleeAttackWithAnimation.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 攻击范围

        attackRange: 1,

        // 攻击伤害

        attackDamage: 10,

        // 攻击动画

        attackAnimation: cc.Animation,

        // 动画控制器

        animationController: {

            default: null,

            type: AnimationController

        }

    },



    // 执行近战攻击

    attack: function () {

        this.animationController.playAttackAnimation();



        // 检测攻击范围内的敌人

        const enemies = cc.physics.queryColliders(this.node, this.attackRange, "Enemy");



        for (let i = 0; i < enemies.length; i++) {

            const enemyHealth = enemies[i].node.getComponent("Health");

            enemyHealth.takeDamage(this.attackDamage);

            enemyHealth.node.getComponent(AnimationController).playDamageAnimation();

        }

    }

});

战斗特效

战斗特效可以进一步提升战斗的真实感和视觉效果。Cocos Creator支持多种特效,包括粒子效果、光效和动画效果。以下是如何实现战斗特效的基本步骤。

特效定义

首先,需要在Cocos Creator的资源管理器中创建或导入特效资源,例如粒子系统、光晕效果等。

特效播放

在代码中,可以根据战斗逻辑播放相应的特效。以下是一个示例代码:


// EffectController.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 火球特效预制体

        fireballEffect: cc.Prefab

    },



    // 播放火球特效

    playFireballEffect: function (position) {

        const effectNode = cc.instantiate(this.fireballEffect);

        effectNode.setPosition(position);

        this.node.parent.addChild(effectNode);

    }

});

特效与战斗逻辑的结合

将特效与战斗逻辑结合,可以确保在执行技能时播放相应的特效。以下是一个远程攻击与特效结合的示例代码:


// RangedAttackWithEffect.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 攻击范围

        attackRange: 5,

        // 攻击伤害

        attackDamage: 20,

        // 弹道预制体

        projectilePrefab: cc.Prefab,

        // 特效控制器

        effectController: {

            default: null,

            type: EffectController

        }

    },



    // 执行远程攻击

    attack: function () {

        const projectile = cc.instantiate(this.projectilePrefab);

        projectile.setPosition(this.node.position);

        this.node.parent.addChild(projectile);



        // 设置弹道方向

        const direction = this.node.getComponent(cc.Camera).forward;

        direction.y = 0;

        direction.normalize();



        projectile.getComponent("Projectile").setDirection(direction);

        projectile.getComponent("Projectile").setDamage(this.attackDamage);



        // 播放火球特效

        this.effectController.playFireballEffect(this.node.position);

    }

});

战斗AI

战斗AI是增强游戏挑战性和趣味性的重要部分。我们将介绍如何在Cocos Creator中实现一个基础的战斗AI。

敌人AI

敌人AI负责控制敌人的行为,例如巡逻、追击玩家和攻击玩家等。以下是一个简单的敌人AI实现示例:


// EnemyAI.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 敌人的移动速度

        moveSpeed: 2,

        // 敌人的攻击范围

        attackRange: 1,

        // 敌人的攻击伤害

        attackDamage: 10,

        // 玩家节点

        player: {

            default: null,

            type: cc.Node

        },

        // 敌人的健康组件

        enemyHealth: {

            default: null,

            type: Health

        }

    },



    onLoad: function () {

        this.player = cc.find("Player");

        this.enemyHealth = this.node.getComponent("Health");

    },



    update: function (dt) {

        // 检测玩家是否在攻击范围内

        if (cc.Vec3.distance(this.node.position, this.player.position) <= this.attackRange) {

            this.attackPlayer();

        } else {

            this.moveTowardsPlayer(dt);

        }

    },



    // 移动朝向玩家

    moveTowardsPlayer: function (dt) {

        const direction = this.player.position.sub(this.node.position);

        direction.normalize();



        this.node.position = this.node.position.add(direction.mulScalar(this.moveSpeed * dt));

    },



    // 攻击玩家

    attackPlayer: function () {

        const playerHealth = this.player.getComponent("Health");

        playerHealth.takeDamage(this.attackDamage);



        // 播放攻击音效

        this.node.getComponent(AudioController).playAttackSound();

    }

});

AI状态管理

AI状态管理器负责管理敌人的状态,例如巡逻、追击和攻击等。以下是一个简单的AI状态管理器示例:


// AIStateManager.js

cc.Class({

    extends: cc.Component,



    properties: {

        // 敌人AI组件

        enemyAI: {

            default: null,

            type: EnemyAI

        }

    },



    onLoad: function () {

        this.enemyAI = this.node.getComponent("EnemyAI");

        this.currentAIState = "Patrol";

    },



    update: function (dt) {

        switch (this.currentAIState) {

            case "Patrol":

                this.patrol(dt);

                break;

            case "Chase":

                this.chasePlayer(dt);

                break;

            case "Attack":

                this.attackPlayer();

                break;

            default:

                console.log("未知AI状态");

        }

    },



    // 巡逻

    patrol: function (dt) {

        // 实现巡逻逻辑

        // 例如在指定路径上移动

    },



    // 追击玩家

    chasePlayer: function (dt) {

        this.enemyAI.moveTowardsPlayer(dt);

    },



    // 攻击玩家

    attackPlayer: function () {

        this.enemyAI.attackPlayer();

    },



    // 切换AI状态

    switchAIState: function (state) {

        this.currentAIState = state;

    }

});

总结

通过以上内容,我们详细介绍了如何在Cocos Creator引擎中构建一个基础的VR战斗系统。这个系统包括角色控制、技能系统、战斗逻辑、碰撞检测、战斗UI、战斗音效和战斗特效等多个关键组件。每个组件都通过具体的代码示例来展示如何实现其功能,确保您能够理解和应用这些技术。

在实际开发中,您可能需要根据游戏的具体需求对这些组件进行进一步的扩展和优化。例如,可以添加更复杂的角色行为、更多的技能种类、更精细的战斗逻辑和更丰富的特效表现。希望本节内容能为您的VR战斗系统开发提供有价值的参考和帮助。
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值