java写Mario线程小游戏

写了一个线程小游戏,放上来供有需要童鞋的参考.

1.界面
界面
2. Mario类的几个方法
Mario切换方向和图片的方法:

public void run() {
        while (true) {
            ThreadSleep.threadSleep(10);
            // 切换Mario走的方向的实际操作,同时要判断是否撞到障碍物
            if (isLeft) {
                currentImg = img_Left;
                hit = isHit("left");
                if (!hit) {
                    if (this.x > 0) {
                        this.x -= speedX;
                    }
                }
            } else if (isRight) {
                currentImg = img_Right;
                hit = isHit("right");
                if (!hit) {
                    if (this.x > 300) {
                        DeX -= speedX; // 背景图动
                    } else {
                        this.x += speedX;
                    }
                }
            }
            if (isUp) {
                hit = isHit("up");
                if (!hit) {
                    if (!isJump) {
                        if (!isGravite) {
                            isJump = true;
                            jump();
                        }
                    }
                }
            }
            if (isStop) {
                currentImg = img_Stop;
            }
            isHit("down");
        }
    }

判断是否死亡:

void isDie() {
        while (true) {
            ThreadSleep.threadSleep(10);
            if (this.y < constNum.frameWidth) {
                this.y += this.speedY;
            } else {
                break;
            }
        }
        ms.stopMusic();
    }

判断是否撞击到障碍物:

public boolean isHit(String direction) {
        Rectangle rectangle = null;
        // 构造一个预判断的Mario长方形
        if ("left".equals(direction)) {
            rectangle = new Rectangle(this.x - this.speedX - DeX, this.y,
                    this.width, this.height);
        } else if ("right".equals(direction)) {
            rectangle = new Rectangle(this.x + this.speedX - DeX, this.y,
                    this.width, this.height);
        } else if ("up".equals(direction)) {
            rectangle = new Rectangle(this.x - DeX, this.y - this.speedY,
                    this.width, this.height);
        } else if ("down".equals(direction)) {
            rectangle = new Rectangle(this.x - DeX, this.y + this.speedY,
                    this.width, this.height);
        }
        // 遍历所有的障碍物
        List<entity> list = EntitySingleton.getInstance().getList();
        for (int i = 0; i < list.size(); i++) {
            entity entitys = list.get(i);
            // 自带的长方形判断是否重叠的方法
            boolean intersects = entitys.intersects(rectangle);
            if (intersects) {
                // 判断碰撞的障碍物是否是金币
                if (entitys instanceof Coin) {
                    music ms = new music("musics//coin.wav", false);
                    list.remove(entitys);
                }
                // 判断撞到的是否是墙壁
                if (entitys instanceof Brick) {
                    isDie();
                }
                return true;
            }
        }
        return false;
    }

引力方法,使Mario不会悬浮在空中不掉下来:

public void isGravite() {
        while (true) {
            ThreadSleep.threadSleep(10);
            // 判断Mario在高空中
            if (constNum.marioY > this.y) {
                if (!isHit("down")) {
                    if (!isJump) {
                        this.y += this.speedY;
                        isGravite = true;
                        continue;
                    }
                }
            }
            isGravite = false;
        }

    }

Mario跳跃的方法:

private void jump() {
        new Thread() {
            public void run() {
                for (int i = 0; i < 130; i++) {
                    hit = isHit("up");
                    if (!hit) {
                        y -= speedY;
                        ThreadSleep.threadSleep(10);
                    } else {
                        break;
                    }
                }
                for (int i = 0; i < 130; i++) {
                    hit = isHit("down");
                    if (!hit) {
                        if (y < constNum.marioY) {
                            y += speedY;
                            ThreadSleep.threadSleep(10);
                        }
                    } else {
                        break;
                    }
                }
                isJump = false;
            };
        }.start();
    }

3.主窗口paint方法的重写:

public void paint(Graphics g) {
        // super.paint(g);
        // 画一张空图,把Mario这些画在空图上,再把空图画在窗口上,这样解决了刷新是一闪一闪的问题
        Image img = createImage(getWidth(), getHeight());
        Graphics gg = img.getGraphics();
        gg.drawImage(backgroundImg, mario.getDeX(), 0, null);// 画背景图
        mario.drawSelf(gg);// 画Mario
        list = EntitySingleton.getInstance().getList();// 遍历障碍物,并画出来
        for (int i = 0; i < list.size(); i++) {
            entity entitys = list.get(i);
            entitys.drawSelf(gg, mario.getDeX());
        }
        if (MyListener.listBullet != null) {// 画子弹
            for (int i = 0; i < MyListener.listBullet.size(); i++) {
                Bullet bullet = MyListener.listBullet.get(i);
                if (bullet.isDie) {
                    MyListener.listBullet.remove(bullet);
                }
                bullet.drawSelf(gg, 0);
            }
        }
        g.drawImage(img, 0, 0, null);// 把创的空图画在窗口上
    }

4.我们最喜欢的源码:

package frame;

import java.awt.LayoutManager;

import javax.swing.JFrame;

public abstract class ParentFrame extends JFrame {
    private static final long serialVersionUID = 1L;

    public ParentFrame(int width, int height) {
        this(null, width, height, null);
    }

    public ParentFrame(String title, int width, int height,
            LayoutManager manager) {
        setTitle(title);
        setSize(width, height);
        setLocationRelativeTo(null);
        // setUndecorated(true);
        // getGraphicsConfiguration().getDevice().setFullScreenWindow(this);//
        // 全屏必须在去修饰之后

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(manager);
        initUI();
        setVisible(true);
        initListener();
    }

    /**
     * 添加组件
     */
    public abstract void initUI();

    /**
     * 添加监听
     */
    public abstract void initListener();

}

package frame;

import java.awt.Graphics;
import java.awt.Image;
import java.util.List;

import Entity.Bullet;
import Entity.Coin;
import Entity.Mario;
import Entity.Pipe;
import Entity.constNum;
import Entity.entity;
import MyListener.MyListener;
import Singleton.EntitySingleton;
import Util.ThreadSleep;

public class MainFrame extends ParentFrame {
    Image backgroundImg;
    Mario mario;
    Pipe pipe;
    Coin coin;
    List<entity> list;

    /**
     * 
     * @param width
     *            窗口的宽度
     * @param height
     *            窗口的高度
     */
    public MainFrame(int width, int height) {
        super(width, height);
        // 开一个线程不断刷新界面
        new Thread() {
            public void run() {
                while (true) {
                    ThreadSleep.threadSleep(100);
                    // 重绘
                    repaint();
                }
            };
        }.start();
        mario.isGravite();
    }

    // 初始化Mario
    public void initUI() {
        backgroundImg = GetImage.getImages("image\\startBack.jpg");
        mario = new Mario();
    }

    // 添加键盘监听
    public void initListener() {
        this.addKeyListener(new MyListener(mario));
    }

    // 重写paint方法
    public void paint(Graphics g) {
        // super.paint(g);
        // 画一张空图,把Mario这些画在空图上,再把空图画在窗口上,这样解决了刷新是一闪一闪的问题
        Image img = createImage(getWidth(), getHeight());
        Graphics gg = img.getGraphics();
        gg.drawImage(backgroundImg, mario.getDeX(), 0, null);// 画背景图
        mario.drawSelf(gg);// 画Mario
        list = EntitySingleton.getInstance().getList();// 遍历障碍物,并画出来
        for (int i = 0; i < list.size(); i++) {
            entity entitys = list.get(i);
            entitys.drawSelf(gg, mario.getDeX());
        }
        if (MyListener.listBullet != null) {// 画子弹
            for (int i = 0; i < MyListener.listBullet.size(); i++) {
                Bullet bullet = MyListener.listBullet.get(i);
                if (bullet.isDie) {
                    MyListener.listBullet.remove(bullet);
                }
                bullet.drawSelf(gg, 0);
            }
        }
        g.drawImage(img, 0, 0, null);// 把创的空图画在窗口上
    }

    public static void main(String[] args) {

        MainFrame frame = new MainFrame(constNum.frameWidth,
                constNum.frameHeight);
    }

}

package Entity;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.List;

import Singleton.EntitySingleton;
import Util.ThreadSleep;
import frame.GetImage;

public class Mario extends Rectangle implements Runnable {
    int speedX = 1;
    int speedY = 1;

    // 标记是否暂停游戏
    public static boolean pause = false;

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getSpeedX() {
        return speedX;
    }

    public void setSpeedX(int speedX) {
        this.speedX = speedX;
    }

    public int getSpeedY() {
        return speedY;
    }

    public void setSpeedY(int speedY) {
        this.speedY = speedY;
    }

    public Image getCurrentImg() {
        return currentImg;
    }

    public void setCurrentImg(Image currentImg) {
        this.currentImg = currentImg;
    }

    // 分别是当前的图片,向左走时的图片,向右走时的图片,不动时的图片
    private Image currentImg;
    private Image img_Left;
    private Image img_Right;
    private Image img_Stop;

    public Mario() {
        super(10, constNum.marioY, 32, 34);
        // Mario一初始化,就播放背景音乐
        ms = new music("musics//backgroundmusic_new.wav", true);
        // 加载图片
        img_Left = GetImage.getImages("image\\mari_left.gif");
        img_Right = GetImage.getImages("image\\mari_right.gif");
        img_Stop = GetImage.getImages("image\\mari1.png");
        currentImg = img_Stop;
        new Thread(this).start();
    }

    /**
     * 把自己画到画布上
     * 
     * @param gg
     *            画笔Graphics
     */
    public void drawSelf(Graphics gg) {
        gg.drawImage(currentImg, this.x, this.y, width, height, null);
    }

    // 记录偏移量
    private int DeX = 0;
    // 标记是否撞到障碍物
    boolean hit;

    public void run() {
        while (true) {
            ThreadSleep.threadSleep(10);
            // 切换Mario走的方向的实际操作,同时要判断是否撞到障碍物
            if (isLeft) {
                currentImg = img_Left;
                hit = isHit("left");
                if (!hit) {
                    if (this.x > 0) {
                        this.x -= speedX;
                    }
                }
            } else if (isRight) {
                currentImg = img_Right;
                hit = isHit("right");
                if (!hit) {
                    if (this.x > 300) {
                        DeX -= speedX; // 背景图动
                    } else {
                        this.x += speedX;
                    }
                }
            }
            if (isUp) {
                hit = isHit("up");
                if (!hit) {
                    if (!isJump) {
                        if (!isGravite) {
                            isJump = true;
                            jump();
                        }
                    }
                }
            }
            if (isStop) {
                currentImg = img_Stop;
            }
            isHit("down");
        }
    }

    /**
     * 判断是否死亡
     */
    void isDie() {
        while (true) {
            ThreadSleep.threadSleep(10);
            if (this.y < constNum.frameWidth) {
                this.y += this.speedY;
            } else {
                break;
            }
        }
        ms.stopMusic();
    }

    /**
     * 判断是否撞击到障碍物,并且要进行预判断,不然很有可能会卡在障碍物里面出不来
     * 
     * @param direction
     *            预判断需要的Mario的下一步的方向
     * @return
     */
    public boolean isHit(String direction) {
        Rectangle rectangle = null;
        // 构造一个预判断的Mario长方形
        if ("left".equals(direction)) {
            rectangle = new Rectangle(this.x - this.speedX - DeX, this.y,
                    this.width, this.height);
        } else if ("right".equals(direction)) {
            rectangle = new Rectangle(this.x + this.speedX - DeX, this.y,
                    this.width, this.height);
        } else if ("up".equals(direction)) {
            rectangle = new Rectangle(this.x - DeX, this.y - this.speedY,
                    this.width, this.height);
        } else if ("down".equals(direction)) {
            rectangle = new Rectangle(this.x - DeX, this.y + this.speedY,
                    this.width, this.height);
        }
        // 遍历所有的障碍物
        List<entity> list = EntitySingleton.getInstance().getList();
        for (int i = 0; i < list.size(); i++) {
            entity entitys = list.get(i);
            // 自带的长方形判断是否重叠的方法
            boolean intersects = entitys.intersects(rectangle);
            if (intersects) {
                // 判断碰撞的障碍物是否是金币
                if (entitys instanceof Coin) {
                    music ms = new music("musics//coin.wav", false);
                    list.remove(entitys);
                }
                // 判断撞到的是否是墙壁
                if (entitys instanceof Brick) {
                    isDie();
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Mario正在跳跃
     */
    private void jump() {
        new Thread() {
            public void run() {
                for (int i = 0; i < 130; i++) {
                    hit = isHit("up");
                    if (!hit) {
                        y -= speedY;
                        ThreadSleep.threadSleep(10);
                    } else {
                        break;
                    }
                }
                for (int i = 0; i < 130; i++) {
                    hit = isHit("down");
                    if (!hit) {
                        if (y < constNum.marioY) {
                            y += speedY;
                            ThreadSleep.threadSleep(10);
                        }
                    } else {
                        break;
                    }
                }
                isJump = false;
            };
        }.start();
    }

    private boolean isGravite = false;

    /**
     * 地面对Mario的引力 当Mario跳上去跳到障碍物上,之后不再掉下来的时候,要把Mario吸下来
     */
    public void isGravite() {
        while (true) {
            ThreadSleep.threadSleep(10);
            // 判断Mario在高空中
            if (constNum.marioY > this.y) {
                if (!isHit("down")) {
                    if (!isJump) {
                        this.y += this.speedY;
                        isGravite = true;
                        continue;
                    }
                }
            }
            isGravite = false;
        }

    }

    private boolean isLeft = false;
    private boolean isRight = false;
    private boolean isUp = false;
    private boolean isDown = false;

    private boolean isJump = false;
    private boolean isStop = true;
    public music ms;

    public void setLeft(boolean isLeft) {
        this.isLeft = isLeft;
    }

    public void setRight(boolean isRight) {
        this.isRight = isRight;
    }

    public void setUp(boolean isUp) {
        this.isUp = isUp;
    }

    public void setDown(boolean isDown) {
        this.isDown = isDown;
    }

    public void setStop(boolean isStop) {
        this.isStop = isStop;
    }

    public void setDeX(int DeX) {
        this.DeX = DeX;
    }

    public int getDeX() {
        return DeX;
    }

    public boolean getUp() {
        return isUp;
    }

    public boolean getLeft() {
        return isLeft;
    }

    public boolean getRight() {
        return isRight;
    }
}

package MyListener;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import Entity.Bullet;
import Entity.Mario;
import Util.ThreadSleep;

public class MyListener implements KeyListener {
    Mario mario;
    public static ArrayList<Bullet> listBullet = new ArrayList<Bullet>();

    public MyListener(Mario mario) {
        this.mario = mario;
    }

    public void keyTyped(KeyEvent e) {
    }

    /**
     * 鼠标按下时,改变标记Mario走的方向的标记变量
     */
    public void keyPressed(KeyEvent e) {
        // 87 w
        // 83 s
        // 65 a
        // 68 d
        // 71 g
        if (e.getKeyCode() == 87) {
            mario.setUp(true);
            mario.setStop(false);
        } else if (e.getKeyCode() == 83) {
            mario.setDown(true);
            mario.setStop(false);
        } else if (e.getKeyCode() == 65) {
            mario.setLeft(true);
            mario.setStop(false);
        } else if (e.getKeyCode() == 68) {
            mario.setRight(true);
            mario.setStop(false);
        } else if (e.getKeyCode() == 32) {// 游戏暂停
            mario.pause = !mario.pause;
            System.out.println(mario.pause);
            if (mario.pause) {
                mario.ms.stopMusic();
            } else {
                mario.ms.startMusic();
            }
            new Thread() {
                public void run() {
                    ThreadSleep.threadSleepOther(500);
                };
            }.start();

        } else if (e.getKeyCode() == 71) {// 发射子弹
            Bullet bullet = new Bullet(mario.x, mario.y, mario.getLeft());
            listBullet.add(bullet);
        }
    }

    /**
     * 鼠标松开时,改变标记Mario走的方向的标记变量
     */
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == 87) {
            mario.setUp(false);
            mario.setStop(true);
        } else if (e.getKeyCode() == 83) {
            mario.setDown(true);
            mario.setStop(false);
        } else if (e.getKeyCode() == 65) {
            mario.setLeft(false);
            mario.setStop(true);
        } else if (e.getKeyCode() == 68) {
            mario.setRight(false);
            mario.setStop(true);
        }
    }

}

package Singleton;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import Entity.Brick;
import Entity.Coin;
import Entity.Pipe;
import Entity.entity;

public class EntitySingleton {
    /**
     * 单例模式,不能在外面初始化
     */
    private EntitySingleton() {

    }

    static EntitySingleton instance = new EntitySingleton();

    public static EntitySingleton getInstance() {
        return instance;
    }

    List<entity> list = null;

    public List<entity> getList() {

        if (list == null) {
            list = new ArrayList<entity>();
            readMap();
        }
        return list;
    }

    List<String> listLine = new ArrayList<String>();

    /**
     * 读地图
     */
    public void readMap() {
        BufferedReader br = null;
        String lines = null;
        try {
            FileReader reader = new FileReader("marioMap.txt");
            br = new BufferedReader(reader);
            while ((lines = br.readLine()) != null) {
                listLine.add(lines);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    br = null;
                }
            }
        }

        int row = 0;
        int col = 0;
        if (listLine != null) {
            row = listLine.size();
            col = listLine.get(0).length();
        }

        for (int i = 0; i < row; i++) {
            String line = listLine.get(i);
            for (int j = 0; j < col; j++) {
                char eg = line.charAt(j);
                int x = j * 30;
                int y = i * 30;
                if ('1' == eg) { // 1代表墙,2代表金币,3代表水管,5代表墙
                    ;
                } else if ('2' == eg) {
                    Coin coin = new Coin(x, y);
                    list.add(coin);
                } else if ('3' == eg) {
                    Pipe pipe = new Pipe(x, y);
                    list.add(pipe);
                } else if ('5' == eg) {
                    Brick brick = new Brick(x, y);
                    list.add(brick);
                }
            }
        }
    }

}

package Util;

import Entity.Mario;

public class ThreadSleep {
    /**
     * 休眠 控制游戏暂停的
     * 
     * @param millis
     */
    public static void threadSleepOther(long millis) {
        while (Mario.pause) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // try {
        // Thread.sleep(millis);
        // } catch (InterruptedException e) {
        // e.printStackTrace();
        // }
    }

    /**
     * 其他地方用来休眠的
     * 
     * @param millis
     */
    public static void threadSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package frame;

import java.awt.Image;

import javax.swing.ImageIcon;

public class GetImage {
    // 通过路径得到图片
    public static Image getImages(String path) {
        // 做缓存
        return new ImageIcon(path).getImage();
    }

}

package Entity;

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

public class music implements Runnable {
    private String filename;
    SourceDataLine auline = null;

    public music(String wavfile, boolean isLoop) {
        filename = wavfile;
        this.isLoop = isLoop;
        // 开启自己
        new Thread(this).start();

    }

    boolean isLoop = true;

    public void run() {

        do {
            File soundFile = new File(filename);
            AudioInputStream audioInputStream = null;
            try {
                audioInputStream = AudioSystem.getAudioInputStream(soundFile);
            } catch (Exception e1) {
                e1.printStackTrace();
                return;
            }
            AudioFormat format = audioInputStream.getFormat();
            // FloatControl
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

            try {
                auline = (SourceDataLine) AudioSystem.getLine(info);
                auline.open(format); // 打开
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            auline.start(); // 开始播放
            int nBytesRead = 0;
            byte[] abData = new byte[512];
            try {
                while (nBytesRead != -1) {
                    nBytesRead = audioInputStream
                            .read(abData, 0, abData.length);
                    if (nBytesRead >= 0)
                        auline.write(abData, 0, nBytesRead);
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                auline.drain();
                auline.close();
            }

        } while (isLoop);

    }

    public void stopMusic() {
        isLoop = false;
        if (auline != null) {
            auline.stop();
        }
    }

    public void startMusic() {
        if (auline != null)
            auline.start();
    }
}

package Entity;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;

import javax.swing.ImageIcon;

public class entity extends Rectangle {
    // 继承了长方形,主要用来判断俩个长方形是否有重叠部分
    // 长方形的只要特征点是:一定有它的x坐标,y坐标,width宽度,height高度
    Image img;

    public entity(int x, int y, int width, int height, String path) {
        super(x, y, width, height);// 初始化自己
        this.img = new ImageIcon(path).getImage();
    }

    /**
     * 用来把自己放到画布上
     * 
     * @param gg
     *            画笔Graphics
     * @param deX
     *            相对窗口的偏移量
     */
    public void drawSelf(Graphics gg, int deX) {
        gg.drawImage(img, this.x + deX, y, width, height, null);
    }
}

package Entity;

import java.awt.Graphics;

public class Pipe extends entity {// 水管

    public Pipe(int x, int y) {
        super(x, y, 60, 120, "image\\pipe.png");
    }

    public void drawself(Graphics gg, int deX) {
        drawSelf(gg, deX);
    }
}

package Entity;

import java.awt.Graphics;

public class Coin extends entity {

    public Coin(int x, int y) {
        super(x, y, 15, 25, "image//coin_brick_null.png");
    }

    public void drawself(Graphics gg, int deX) {
        drawSelf(gg, deX);
    }
}

package Entity;

import java.awt.Graphics;

import Util.ThreadSleep;

public class Bullet extends entity implements Runnable {
    int speedX = 2;
    public boolean isDie = false;

    public Bullet(int x, int y, boolean isLeft) {
        super(x, y, 10, 10, "image//brick.png");
        if (!isLeft) {
            speedX = -2;
        } else {
            speedX = 2;
        }
        new Thread(this) {
        }.start();
    }

    public void drawself(Graphics gg, int deX) {
        drawSelf(gg, deX);
    }

    public void run() {
        while (!isDie) {
            ThreadSleep.threadSleep(10);
            x += speedX;
        }
    }

    public void isDie() {// 当子弹飞出视野,要即使清除掉它,减少内存的浪费
        if (this.x < 0 || this.x > constNum.frameWidth) {
            isDie = true;
        }
    }
}

package Entity;

import java.awt.Graphics;

public class Brick extends entity {

    public Brick(int x, int y) {
        super(x, y, 30, 30, "image//brick.png");
    }

    public void drawself(Graphics gg, int deX) {
        drawSelf(gg, deX);
    }
}

package Entity;

public class Blank { // 各种障碍物

}

package Entity;

public class constNum {
    // 存放常量,很大的方便之处
    // eg:这样方便对窗体大小调整后,不用再修改判断是否离开了视野
    public static final int marioY = 358;
    public static final int frameWidth = 550;
    public static final int frameHeight = 450;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值