LibGdx练习-像素鸟(五)

LibGdx练习-像素鸟(五)

游戏界面元素

资源加载完成后我们进入游戏界面。GameScreen继承BaseScreen,拥有一个stage和一个MainGame实例。

先来创建一下我们游戏界面所需要的组成元素。

//    背景
    private ImageActor bgImage;
    //    地板
    private FloorActor floorImage;
    //    点击提示
    private ImageActor tapTipImage;
    //    准备提示
    private ImageActor getReadyActor;
    //    鸟
    private BirdActor birdActor;
    //    分
    private int score;
    //    分数显示
    private Label scoreLable;
    //    水管集合
    private CopyOnWriteArrayList<BarActor> barImageList = new CopyOnWriteArrayList<>();
    //    对象池
    private Pool<BarActor> barImagePool;
    //    初始鸟高度
    private float birdStartPositionY;
    //    下水管最小值
    private float minDownBarTopY;
    //    下水管最大值
    private float maxDownBarTopY;
    //   距离下次生成水管的时间累加器
    private float generateBarTimeCounter;
    //  游戏状态
    private GameState gameState;
    //    暂停按钮
    private Button button;
//    结束界面
    private OverGroup overGroup;

在界面元素中可以看到我们所需要用到的鸟、水管、地板都是我们独立封装过的对象,我们先来看下。

BirdActor

BirdActor描述了鸟的对象,鸟采用了动画的展示形式,那么鸟在飞行和死亡的状态下要有一个神么样的动画展示形式呢?看下源码。

public class BirdActor extends BaseAnimationActor {

    /** 当前游戏状态 */
    private GameState gameState;
    
    /** 小鸟竖直方向上的速度 */
    private float velocityY;
    
    /** 小鸟竖直方向上的重力加速度 */
    private float gravity = Res.Physics.GRAVITY;

    public BirdActor(MainGame mainGame) {
        super(mainGame);

        // 创建小鸟动画
        Animation animation = new Animation(
                0.2F,
                AssetsUtil.atlas.findRegions(Res.Atlas.IMAGE_BIRD_YELLOW_01_TO_03)
        );
        // 动画循环播放
        animation.setPlayMode(Animation.PlayMode.LOOP);
        // 设置小鸟动画
        setAnimation(animation);

        // 初始化为准备状态
        refreshFrameAndRotation(GameState.ready);
    }

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

        // 在 飞翔状态 或 状态水管后掉落到地板前 才应用物理效应
        if (gameState == GameState.fly || gameState == GameState.die) {
	        /*
	         * 应用物理效应(简单模拟物理效果, 帧率较低时物理效果的误差可能较大)
	         * v = v0 + a * t
	         * s = v0 * t + a * t^2
	         */
	        // 递增速度
	        velocityY += gravity * delta;
	        // 递增位移
	        setY(getY() + velocityY * delta);
        }

        // 正在飞翔状态时改变小鸟的角度
        if (gameState == GameState.fly) {
            changeBirdRotation(delta);
        }
    }

    /**
     * 根据游戏状态刷新小鸟状态
     * @param gameState
     */
    public void refreshFrameAndRotation(GameState gameState) {
        if (gameState == null || this.gameState == gameState) {
            return;
        }
        
        this.gameState = gameState;
        
        switch (this.gameState) {
            case ready: {
            	// 准备状态循环播放动画, 帧持续时间为 0.2 秒
                setPlayAnimation(true);
                setRotation(0);
                getAnimation().setFrameDuration(0.2F);
                break;
            }
            case fly: {
            	// 准备状态循环播放动画, 帧持续时间为 0.18 秒
                setPlayAnimation(true);
                getAnimation().setFrameDuration(0.18F);
                break;
            }
            case die: {
                break;
            }
            case gameOver: {
            	// 游戏结束状态停止播放动画, 并固定显示第1帧
                setPlayAnimation(false);
                setFixedShowKeyFrameIndex(1);
                setRotation(-90);
                break;
            }
            case pause:{
                setPlayAnimation(false);
                break;
            }
            case resume:{
                setPlayAnimation(true);
                break;
            }
        }
    }
    
    public float getVelocityY() {
		return velocityY;
	}

	public void setVelocityY(float velocityY) {
		this.velocityY = velocityY;
	}

	/**
     * 根据数值方向速度变化值改变小鸟的旋转角度
     * @param delta
     */
    private void changeBirdRotation(float delta) {

        float rotation = getRotation();

        rotation += (velocityY * delta);

        if (velocityY > 0) {
            // 向上飞时稍微加大角度旋转的速度
            rotation += (velocityY * delta) * 1.5F;
        } else {
            // 向下飞时稍微减小角度旋转的速度
            rotation += (velocityY * delta) * 0.2F;
        }

        // 校准旋转角度: -75 <= rotation <= 45
        if (rotation < -75) {
            rotation = -75;
        } else if (rotation > 45) {
            rotation = 45;
        }

        // 设置小鸟的旋转角度
        setRotation(rotation);
    }

}

我们采用switch语句来进行鸟状态的判断,不同的状态会有不同的动画展现形式。

再者,我们需要对于鸟的飞行轨迹进行判定,虽然相对模型世界来讲鸟一直在向前飞,但是对我们开过过程来说其实不然,相对于屏幕来讲,鸟的横坐标并没有移动过,只是纵坐标的变换。

我们对于actor的判断不应该以逻辑为基准,而是应该以父容器为基准。

我们固定鸟的横坐标,并给与鸟一个始终向下的固定的偏移速度,以此达到我们的预期效果。

除此,为了动画更加流畅真实我们还需要对于鸟的角度变换进行描述,根据不同的速度我们继续小鸟不同的角度变换。

注意,一定要给鸟设定最大最小角度,不然鸟转一圈可就尴尬咯。

BarActor

BarActor用以描述水管actor。

public class BarActor extends BaseImageActor implements Poolable {
	
	/** 水平移动速度, px/s */
    private float moveVelocity;
	
    /** 是否是上方水管 */
	private boolean isUpBar;
	
	/** 是否已被小鸟通过 */
	private boolean isPassByBird;
	
	/** 水管是否在移动 */
	private boolean isMove;
	
	public BarActor() {
		super(null);
	}
	
	public BarActor(MainGame mainGame) {
		super(mainGame);
	}
	
	@Override
	public void act(float delta) {
		super.act(delta);
		// 如果水管正在移动状态, 递增水管的水平位移(水平移动)
		if (isMove) {
			setX(getX() + moveVelocity * delta);
		}
	}

	public float getMoveVelocity() {
		return moveVelocity;
	}

	public void setMoveVelocity(float moveVelocity) {
		this.moveVelocity = moveVelocity;
	}

	public boolean isUpBar() {
		return isUpBar;
	}

	public void setUpBar(boolean isUpBar) {
        this.isUpBar = isUpBar;
        if (this.isUpBar) {
            setRegion(AssetsUtil.atlas.findRegion(Res.Atlas.IMAGE_BAR_UP));
        } else {
            setRegion(AssetsUtil.atlas.findRegion(Res.Atlas.IMAGE_BAR_DOWN));
        }
	}

	public boolean isPassByBird() {
		return isPassByBird;
	}

	public void setPassByBird(boolean isPassByBird) {
		this.isPassByBird = isPassByBird;
	}

	public boolean isMove() {
		return isMove;
	}

	public void setMove(boolean isMove) {
		this.isMove = isMove;
	}

	@Override
	public void reset() {
		setMove(false);
		setPassByBird(false);
	}

}

BarActor在移动上很好理解,水平左移即可。

它的核心在于上下水管的判断,空位补充以及碰撞检测。这些东西将影响我们游戏失败或者加分。

FloorActor

再来看FloorActor。

public class FloorActor extends BaseImageActor {

    /** 水平移动速度, px/s */
    private float moveVelocity;

    /** 地板纹理区域 */
    private TextureRegion region;

    /** 水平偏移量 */
    private float offerX;

    /** 地板是否在移动 */
    private boolean isMove;

    public FloorActor(MainGame mainGame) {
        super(mainGame);
        region = AssetsUtil.atlas.findRegion(Res.Atlas.IMAGE_GAME_FLOOR);
        setBounds(0, 0, region.getRegionWidth(), region.getRegionHeight());
    }

    public float getMoveVelocity() {
        return moveVelocity;
    }

    public void setMoveVelocity(float moveVelocity) {
        this.moveVelocity = moveVelocity;
    }

    public boolean isMove() {
        return isMove;
    }

    public void setMove(boolean isMove) {
        this.isMove = isMove;
    }

    @Override
    public void act(float delta) {
         super.act(delta);
        if (isMove) {
            offerX += (delta * moveVelocity);
            offerX %= getWidth();
            if (offerX > 0) {
                offerX -= getWidth();
            }
        }
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        // 绘制两次以达到视觉上的循环移动效果
        batch.draw(region, getX() + offerX, getY(),Res.FIX_WORLD_WIDTH*2,getHeight());
        if (Math.abs(offerX) > 0.001F) {
            batch.draw(region, getX() + (getWidth() + offerX), getY(),Res.FIX_WORLD_WIDTH*2,getHeight());
        }
    }
}

地板的绘制相对来说是比较复杂的,向左根据时间戳移动没什么说的,但是跟水管不同的是水管出现的是完整的,而地板游戏不结束就要一直出现。

当然,我们可以找一个非常长的图片作为地板,但是显然这种方式很蠢笨而且不能一劳永逸。

我们通过地板偏移量和屏幕宽度取余的方式让地板反复横跳,同时重写draw方法进行两次绘制,这样地板就用之不竭啦。

当然,我们的地板还是需要比屏幕宽度大的哦,不然初始就铺不满就谈不到后续绘制了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值