首页 > 代码库 > 第一个libgdx程序--仿别踩白块

第一个libgdx程序--仿别踩白块

    学libgdx有一段时间了,有一个星期了吧,边做边学,选的是别踩白块。

    libgdx总体思想可以用下面这张图来表示


    

    总体可分为两层吧,入口不算,Screen+Actor. 这个很好理解啊,你去看话剧表演,得有屏幕吧,屏幕上面有英雄,有坏蛋,还有道具吧,
 所以,简单来说暂且就看作两层。


    下面看看具体项目:


     首先跑起来是这样的:
   
                                 
 

     代码是这样的:


     1. 入口类 FirstGame
       
     既然是入口,主要做一些初始化操作

public class FirstGame extends Game
{

	private MenuScreen menuScreen;

	@Override
	public void create()
	{
		Const.WIDTH = Gdx.graphics.getWidth();
		Const.HEIGHT = Gdx.graphics.getHeight();
		menuScreen = new MenuScreen(this);
		setScreen(menuScreen);
	}

	@Override
	public void render()
	{
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		super.render();
	}

}


     2. 游戏选择界面 MenuScreen

     这里真正开始libgdx部分了,主要是图片按钮TextButton控件的使用及两个动画特效的实现。根据界面显示,用了6个主菜单TextButton和3个子菜单TextButton,代码如下:

public class MenuScreen implements Screen
{

	private TextButtonStyle whiteStyle, blackStyle;
	/** 6个主菜单 */
	private TextButton classicBtn, arcadeBtn, zenBtn, speedBtn, relayBtn, moreBtn;
	/** 3个子菜单 */
	private TextButton mode1Btn, mode2Btn, mode3Btn;
	private Texture texture;
	private Stage stage;
	/** 这里用集合来存放所有点击过的菜单的位置,用来得到哪一个需要收缩,哪一个需要弹出 */
	private List<Integer> idList;
	/** 是否播放动画 */
	private boolean isrun;
	private FirstGame game;

	public MenuScreen(FirstGame game)
	{
		this.game = game;
	}

	@Override
	public void render(float delta)
	{
		/** 收缩菜单与弹出菜单动画特效。这里开始逻辑有点乱,耐心看看吧,一时间没想到更好的办法,唉 ==||*/
		if (isrun)
		{
			int len = idList.size();
			if (len > 0)
			{
				if (len > 1)
				{
					if (idList.get(len - 2) % 2 == 1)
					{
						if (inx > -240)
						{
							modeBtnMoveIn();
						}
					} else
					{
						if (inx < 480)
						{
							modeBtnMoveIn();
						}
					}
				}
				
				if (idList.get(len - 1) % 2 == 1)
				{
					if (outx < 0)
					{
						modeBtnMoveOut();
					} else
					{
						isrun = false;
					}
				} else
				{
					if (outx > 240)
					{
						modeBtnMoveOut();
					} else
					{
						isrun = false;
					}
				}
			}
		}
		Gdx.gl.glClearColor(1, 1, 1, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		stage.draw();
	}

	@Override
	public void resize(int width, int height)
	{
	}

	@Override
	public void show()
	{
		stage = new Stage(new StretchViewport(Const.WIDTH, Const.HEIGHT));
		initBtnStyle();
		initBtn();
		Gdx.input.setInputProcessor(stage);
		idList = new ArrayList<Integer>();
	}

	/** 初始化按钮背景颜色 */
	private void initBtnStyle()
	{
		texture = new Texture(Gdx.files.internal("data/whitebg.png"));
		Sprite sprite = new Sprite(texture);
		SpriteDrawable whiteSpriteDrawable = new SpriteDrawable(sprite);

		whiteStyle = new TextButtonStyle();
		whiteStyle.down = whiteSpriteDrawable;
		whiteStyle.up = whiteSpriteDrawable;
		whiteStyle.font = new BitmapFont();
		whiteStyle.fontColor = Color.BLACK;

		texture = new Texture(Gdx.files.internal("data/black.png"));
		sprite = new Sprite(texture);
		SpriteDrawable blackSpriteDrawable = new SpriteDrawable(sprite);

		blackStyle = new TextButtonStyle();
		blackStyle.down = blackSpriteDrawable;
		blackStyle.up = blackSpriteDrawable;
		blackStyle.font = new BitmapFont();
		blackStyle.fontColor = Color.WHITE;
	}

	/** 实例化按钮 */
	private void initBtn()
	{
		classicBtn = new TextButton("Classic", whiteStyle);
		classicBtn.setPosition(0, Const.HEIGHT * 2 / 3);
		classicBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		classicBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("25", "50", "Discontinuous", 1);
				idList.add(1);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		arcadeBtn = new TextButton("Arcade", blackStyle);
		arcadeBtn.setPosition(Const.WIDTH / 2, Const.HEIGHT * 2 / 3);
		arcadeBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		arcadeBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("Normal", "Faster", "Retrograde", 0);
				idList.add(2);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		zenBtn = new TextButton("Zen", blackStyle);
		zenBtn.setPosition(0, Const.HEIGHT / 3);
		zenBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		zenBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("15\"", "30\"", "Discontinuous", 1);
				idList.add(3);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		speedBtn = new TextButton("Speed", whiteStyle);
		speedBtn.setPosition(Const.WIDTH / 2, Const.HEIGHT / 3);
		speedBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		speedBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("Slow", "Normal", "Retrograde", 0);
				idList.add(4);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		relayBtn = new TextButton("Relay", whiteStyle);
		relayBtn.setPosition(0, 0);
		relayBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		relayBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("8\"", "10\"", "12\"", 1);
				idList.add(5);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		moreBtn = new TextButton("More", blackStyle);
		moreBtn.setPosition(Const.WIDTH / 2, 0);
		moreBtn.setSize(Const.WIDTH / 2, Const.HEIGHT / 3);
		moreBtn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				initmodeBtn("1", "2", "3", 0);
				idList.add(6);
				setInitX();
				isrun = true;
				return super.touchDown(event, x, y, pointer, button);
			}
		});

		stage.addActor(classicBtn);
		stage.addActor(arcadeBtn);
		stage.addActor(zenBtn);
		stage.addActor(speedBtn);
		stage.addActor(relayBtn);
		stage.addActor(moreBtn);

		/** 在这里实例化并注册监听事件,不能去到initmodeBtn再做操作 */
		mode1Btn = new TextButton("", whiteStyle);
		mode2Btn = new TextButton("", blackStyle);
		mode3Btn = new TextButton("", whiteStyle);
		mode1Btn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				setScreen(1);
				return super.touchDown(event, x, y, pointer, button);
			}
		});
		mode2Btn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				setScreen(2);
				return super.touchDown(event, x, y, pointer, button);
			}
		});
		mode3Btn.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				setScreen(3);
				return super.touchDown(event, x, y, pointer, button);
			}
		});
	}

	/** 页面跳转 */
	private void setScreen(int mode)
	{
		switch (idList.get(idList.size() - 1))
		{
		case 1:
			switch (mode)
			{
			case 1:
				game.setScreen(new GameScreen(game, Const.GameModeOne.CLASSIC, Const.GameModeTwo.MODE1));
				break;
			case 2:
				game.setScreen(new GameScreen(game, Const.GameModeOne.CLASSIC, Const.GameModeTwo.MODE2));
				break;
			case 3:
				game.setScreen(new GameScreen(game, Const.GameModeOne.CLASSIC, Const.GameModeTwo.MODE3));
				break;
			default:
				break;
			}
			break;
		case 2:
			switch (mode)
			{
			case 1:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ARCADE, Const.GameModeTwo.MODE1));
				break;
			case 2:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ARCADE, Const.GameModeTwo.MODE2));
				break;
			case 3:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ARCADE, Const.GameModeTwo.MODE3));
				break;
			default:
				break;
			}
			break;
		case 3:
			switch (mode)
			{
			case 1:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ZEN, Const.GameModeTwo.MODE1));
				break;
			case 2:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ZEN, Const.GameModeTwo.MODE2));
				break;
			case 3:
				game.setScreen(new GameScreen(game, Const.GameModeOne.ZEN, Const.GameModeTwo.MODE3));
				break;
			default:
				break;
			}
			break;
		case 4:
			switch (mode)
			{
			case 1:
				game.setScreen(new GameScreen(game, Const.GameModeOne.SPEED, Const.GameModeTwo.MODE1));
				break;
			case 2:
				game.setScreen(new GameScreen(game, Const.GameModeOne.SPEED, Const.GameModeTwo.MODE2));
				break;
			case 3:
				game.setScreen(new GameScreen(game, Const.GameModeOne.SPEED, Const.GameModeTwo.MODE3));
				break;
			default:
				break;
			}
			break;
		case 5:
			switch (mode)
			{
			case 1:
				game.setScreen(new GameScreen(game, Const.GameModeOne.RELAY, Const.GameModeTwo.MODE1));
				break;
			case 2:
				game.setScreen(new GameScreen(game, Const.GameModeOne.RELAY, Const.GameModeTwo.MODE2));
				break;
			case 3:
				game.setScreen(new GameScreen(game, Const.GameModeOne.RELAY, Const.GameModeTwo.MODE3));
				break;
			default:
				break;
			}
			break;
		case 6:
			break;
		default:
			break;
		}
	}

	/** 初始化不同游戏模式按钮 */
	private void initmodeBtn(String s1, String s2, String s3, int flag)
	{
		mode1Btn.setText(s1);
		mode2Btn.setText(s2);
		mode3Btn.setText(s3);
		/** 背景色分了两种情况 */
		if (flag == 1)
		{
			mode1Btn.setStyle(whiteStyle);
			mode2Btn.setStyle(blackStyle);
			mode3Btn.setStyle(whiteStyle);
		} else
		{
			mode1Btn.setStyle(blackStyle);
			mode2Btn.setStyle(whiteStyle);
			mode3Btn.setStyle(blackStyle);
		}
		stage.addActor(mode1Btn);
		stage.addActor(mode2Btn);
		stage.addActor(mode3Btn);
	}

	/** 收缩菜单的x坐标需要平移到哪里为止,弹出菜单的x坐标需要平移到哪里为止 */
	private float inx, outx;
	/** 这两个值分别用来计算弹出菜单的高度和y坐标 */
	private float moveh, moveYEnd;

	/** 按钮收缩动画效果 */
	private void modeBtnMoveIn()
	{
		int len = idList.size();
		if (len > 1)
		{
			switch (idList.get(len - 2))
			{
			case 1:
				moveYEnd = 800 * 2 / 3;
				moveh = 800 / 3;
				inx -= 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			case 2:
				moveYEnd = 800 * 2 / 3;
				moveh = 800 / 3;
				inx += 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			case 3:
				moveYEnd = 800 / 3;
				moveh = 800 / 3;
				inx -= 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			case 4:
				moveYEnd = 800 / 3;
				moveh = 800 / 3;
				inx += 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			case 5:
				moveYEnd = 0;
				moveh = 800 / 3;
				inx -= 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			case 6:
				moveYEnd = 0;
				moveh = 800 / 3;
				inx += 20;
				mode1Btn.setPosition(inx, moveYEnd + moveh * 2 / 3);
				mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode2Btn.setPosition(inx, moveYEnd + moveh / 3);
				mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
				mode3Btn.setPosition(inx, moveYEnd);
				mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
				break;
			default:
				break;
			}
		}
	}

	/** 按钮弹出动画效果 */
	private void modeBtnMoveOut()
	{
		switch (idList.get(idList.size() - 1))
		{
		case 1:
			moveYEnd = 800 * 2 / 3;
			moveh = 800 / 3;
			outx += 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		case 2:
			moveYEnd = 800 * 2 / 3;
			moveh = 800 / 3;
			outx -= 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		case 3:
			moveYEnd = 800 / 3;
			moveh = 800 / 3;
			outx += 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		case 4:
			moveYEnd = 800 / 3;
			moveh = 800 / 3;
			outx -= 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		case 5:
			moveYEnd = 0;
			moveh = 800 / 3;
			outx += 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		case 6:
			moveYEnd = 0;
			moveh = 800 / 3;
			outx -= 15;
			mode1Btn.setPosition(outx, moveYEnd + moveh * 2 / 3);
			mode1Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode2Btn.setPosition(outx, moveYEnd + moveh / 3);
			mode2Btn.setSize(Const.WIDTH / 2, moveh / 3);
			mode3Btn.setPosition(outx, moveYEnd);
			mode3Btn.setSize(Const.WIDTH / 2, moveh / 3);
			break;
		}
	}

	/** 获得收缩菜单和弹出菜单平移的目标位置 */
	private void setInitX()
	{
		int len = idList.size();
		if (len > 1)
		{
			if (idList.get(len - 2) % 2 == 1)
			{
				inx = 0;
			} else
			{
				inx = 240;
			}
		}
		if (idList.get(len - 1) % 2 == 1)
		{
			outx = -240;
		} else
		{
			outx = 480;
		}
	}

	@Override
	public void hide()
	{
	}

	@Override
	public void pause()
	{
	}

	@Override
	public void resume()
	{
	}

	@Override
	public void dispose()
	{
	}

}



     如果看上面代码毫无压力的话(是指基本用法方面,本人代码比较乱++|| ),libgdx的基本用法差不多了。

     3. 演员类 BlockActor
     
     参考OOP里一切皆对象的说法。libgdx里所有用户能看到能动的东西基本都能作为演员,类似你去看话剧,舞台上能动的是不是基本都是演员(可能不是很恰当,初学的话暂时先这么理解吧)。演员有自己的属性,事件,动作等,可以在这里定义。

public class BlockActor extends Actor
{
	private GameScreen screen;
	public ImageButton mImageButton;
	private TextureRegionDrawable startRegionDrawable, blackRegionDrawable, whiteRegionDrawable, greyRegionDrawable, redRegionDrawable;
	private TextureRegion startRegion, blackRegion, whiteRegion, greyRegion, redRegion;
	private Texture startTexture, blackTexture, whiteTexture, greyTexture, redTexture;

	public BlockActor(GameScreen screen, BlockColor blockColor, float x, float y, int nameIndex)
	{
		this.screen = screen;
		startTexture = new Texture(Gdx.files.internal("data/start.png"));
		startRegion = new TextureRegion(startTexture);
		startRegionDrawable = new TextureRegionDrawable(startRegion);
		blackTexture = new Texture(Gdx.files.internal("data/black.png"));
		whiteTexture = new Texture(Gdx.files.internal("data/white.png"));
		greyTexture = new Texture(Gdx.files.internal("data/grey.png"));
		redTexture = new Texture(Gdx.files.internal("data/red.png"));
		whiteRegion = new TextureRegion(whiteTexture, 120, 200);
		whiteRegionDrawable = new TextureRegionDrawable(whiteRegion);
		redRegion = new TextureRegion(redTexture, 120, 200);
		redRegionDrawable = new TextureRegionDrawable(redRegion);
		blackRegion = new TextureRegion(blackTexture, 120, 200);
		blackRegionDrawable = new TextureRegionDrawable(blackRegion);
		greyRegion = new TextureRegion(greyTexture, 120, 200);
		greyRegionDrawable = new TextureRegionDrawable(greyRegion);

		if (nameIndex == 1 && blockColor == Const.BlockColor.BLACKBLOCK)
		{
			mImageButton = new ImageButton(startRegionDrawable, greyRegionDrawable);
			mImageButton.setName(nameIndex + "");
		} else if (blockColor == Const.BlockColor.BLACKBLOCK)
		{
			mImageButton = new ImageButton(blackRegionDrawable, greyRegionDrawable);
			mImageButton.setName(nameIndex + "");
		} else
		{
			mImageButton = new ImageButton(whiteRegionDrawable, redRegionDrawable);
			mImageButton.setName("-1");
		}
		mImageButton.setSize(120, 200);
		mImageButton.setPosition(x, y);
		mImageButton.addListener(new InputListener()
		{
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
			{
				return true;
			}

			@Override
			public void touchUp(InputEvent event, float x, float y, int pointer, int button)
			{
				int position = Integer.parseInt(mImageButton.getName() + "");
				if (position == 1)
				{
					BlockActor.this.screen.prerowpostion = position;
					BlockActor.this.screen.isStart = true;
					BlockActor.this.screen.blockState = Const.BlockState.MOVEING;
				} else
				{
					if (BlockActor.this.screen.gameModeOne == Const.GameModeOne.CLASSIC && position == Const.BLOCKCOUNT)
					{
						BlockActor.this.screen.isSuc = true;
						BlockActor.this.screen.blockState = Const.BlockState.IDLE;
						BlockActor.this.screen.game.setScreen(new DeadScreen(BlockActor.this.screen));
					} else
					{
						if (position == BlockActor.this.screen.prerowpostion + 1)
						{
							BlockActor.this.screen.prerowpostion = position;
							BlockActor.this.screen.blockState = Const.BlockState.MOVEING;
						} else
						{
							BlockActor.this.screen.blockState = Const.BlockState.IDLE;
							BlockActor.this.screen.game.setScreen(new DeadScreen(BlockActor.this.screen));
						}
					}
				}
				super.touchUp(event, x, y, pointer, button);
			}
		});
	}

	@Override
	public void draw(Batch batch, float parentAlpha)
	{
		super.draw(batch, parentAlpha);
	}

}




    我的博客其他文章列表
   
 http://my.oschina.net/helu