如何使用libgdx編寫一個簡單的游戲(二)— 完善


上一篇介紹游戲雛形的編寫,這一篇將完善部分邏輯並添加更多效果。

例子代碼在https://github.com/htynkn/DartsShaSha,如有需要請自行在tag中下載對應部分。

完善飛鏢邏輯

現在的飛鏢可以旋轉可以飛行了,但是有一個問題卻沒有解決。

首先飛鏢的速度,如果用戶觸摸位置很靠近左側,那么飛鏢的速度就很慢了。

其次,如果用戶觸摸中間位置,默認情況下飛鏢應該是朝那個方向飛行,而不是飛到觸摸位置就消失了。

這里的處理辦法很簡單,就是根據用戶觸摸位置,算出一個X為480的值,這樣飛鏢就可以飛到最右側,同時保持相當的速度。

在createProjectile方法中添加

float r = (target.y - image.getY()) / (target.x - image.getX()); //獲取斜率
float detY = r * 480; //獲取Y的變動量
image.addAction(Actions.moveTo(480 + image.getX(), detY + image.getY(),
				2f)); // 設置飛鏢的移動

這樣基本就解決了問題。

接下來來思考飛鏢的數量和相應位置。

首先飛鏢的速度一定要得到限制,不然滿屏幕飛鏢有什么意思。這里限制飛鏢的數量為5個。

在touchDown的最開始添加

if (projectiles.getChildren().size >= 5) { //限制飛鏢的數量為5個
	return false;
}

 

這樣當屏幕上的飛鏢數量大於等於5時就不會產生新的飛鏢了。

還有就是觸摸的位置,如果觸摸的位置太靠右的話,會出現飛鏢倒飛或者速度過快的問題,所以當觸摸位置太靠近左側的時候就不釋放飛鏢。

在touchDown方法中添加

if (vector3.x < man.getX() + 5) { //如果觸摸太靠近左側就不響應
	return false;
}

 

這里的5是我隨便寫的,僅僅表示個意思。測試一下,覺得5還是太小了,最后我改成10了。

更帶感的對手

說實話,現在的對手一動不動,只會勻速平移。我們先改進它的外貌吧。

我從http://untamed.wild-refuge.net/rmxpresources.php?characters獲取到如下圖片

scythe

打包以后放入assets文件夾中。

因為libgdx只有默認Animation類,但是沒法辦法直接在stage中使用,所以新建一個Scythe類並繼承Actor類。

public Scythe(AtlasRegion atlasRegion) {
	super();
	this.setWidth(titleWidth); //設置高度
	this.setHeight(titleHeight); //設置寬度
	TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); //分割圖塊
	walkFrames = new TextureRegion[4]; //獲取第二行的4幀
	for (int i = 0; i < 4; i++) {
		walkFrames[i] = temp[1][i];
	}
	animation = new Animation(0.1f, walkFrames); //創建動畫,幀間隔0.1
}

因為原圖寬200,高192,一共16張圖,所以每一塊的寬就是50,高48。使用Animation類需要手動提供相關幀,並通過Animation和當前時間獲取的幀。

重寫draw方法如下

@Override
public void draw(SpriteBatch batch, float parentAlpha) {
	stateTime += Gdx.graphics.getDeltaTime(); //獲取總時間
	currentFrame = animation.getKeyFrame(stateTime, true); //獲取當前關鍵幀
	batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
			this.getHeight()); //繪制
}

 

修改TargetGroup中有關region.getRegionHeight()的部分,全部除以4。同時修改Image類Scythe類。

最后完整的Scythe如下

package com.cnblogs.htynkn;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;

public class Scythe extends Actor {
	TextureRegion[] walkFrames; // 保存每一幀
	Animation animation; // 動畫類
	float stateTime; // 總時間
	TextureRegion currentFrame; // 當前幀
	int titleWidth = 50; // 聲明塊寬度
	int titleHeight = 48; // 聲明塊高度

	public Scythe(AtlasRegion atlasRegion) {
		super();
		this.setWidth(titleWidth); // 設置高度
		this.setHeight(titleHeight); // 設置寬度
		TextureRegion[][] temp = atlasRegion.split(titleWidth, titleHeight); // 分割圖塊
		walkFrames = new TextureRegion[4]; // 獲取第二行的4幀
		for (int i = 0; i < 4; i++) {
			walkFrames[i] = temp[1][i];
		}
		animation = new Animation(0.1f, walkFrames); // 創建動畫,幀間隔0.1
	}

	@Override
	public void draw(SpriteBatch batch, float parentAlpha) {
		stateTime += Gdx.graphics.getDeltaTime(); // 獲取總時間
		currentFrame = animation.getKeyFrame(stateTime, true); // 獲取當前關鍵幀
		batch.draw(currentFrame, this.getX(), this.getY(), this.getWidth(),
				this.getHeight()); // 繪制
	}
}

 

效果如下:

01

添加血條

我們來試試在給予怪獸血量,即有些怪獸可以承受兩次傷害。這里我們將用到比較基本的東西。

首先是血條的位置,一般來看應該在怪獸正上方,以紅色顯示。

繪制可以用很多種方法,我不怎么習慣mesh那套,所以這里我使用Pixmap類。

先在Sythe類中添加兩個變量

int margin = 2; // 血條和人物之間的間隔
int pixHeight = 5; // 血條高度

然后在繪制方法中添加

Pixmap pixmap = new Pixmap(64, 8, Format.RGBA8888); //生成一張64*8的圖片
pixmap.setColor(Color.BLACK); //設置顏色為黑色
pixmap.drawRectangle(0, 0, titleWidth, pixHeight); //繪制邊框
Texture pixmaptex = new Texture(pixmap); //生成圖片
TextureRegion pix = new TextureRegion(pixmaptex, titleWidth, pixHeight); //切割圖片
batch.draw(pix, this.getX(), this.getY() + this.titleHeight
		+ this.margin); //繪制

這樣我們就有了一個黑色的邊框了

02

然后就是血量的填充了,在添加兩個變量以記錄總血量和當前血量

int maxHp; // 總血量
int currentHp; // 當前血量

繪制血條的代碼添加到繪制完黑框的語句后面

pixmap.setColor(Color.RED); // 設置顏色為紅色
pixmap.fillRectangle(0, 1, titleWidth * currentHp / maxHp,
		pixHeight - 2); // 繪制血條

最后一定要釋放掉pixmap

pixmap.dispose();

這是設置總血量為2,當前血量為1的效果

03

控制轉換

增加了血量以后我們的代碼也需要修改了,在主類DartsShaSha中修改相關的邏輯。

為了方便起見我們將游戲的邏輯賦給控制器。

先新建一個IController

package com.cnblogs.htynkn.controller;

import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Stage;

public abstract class IController extends Group {
	public abstract void update(Stage stage);
}

 

然后新建TargetController類,重寫update方法,在這個方法中我們處理相關邏輯,然后在主類中只需要調用方法就可以。

首先將中已有的代碼拷貝過來,然后在Sythe中添加兩個方法來處理受到傷害和死亡判斷。

public void beAttacked(int damage) {
	if (this.currentHp > damage) { // 如果血量大於傷害就扣除響應數值
		currentHp = currentHp - damage;
	} else if (this.currentHp > 0) { // 如果血量小於傷害但是仍有血量就讓血量歸零
		currentHp = 0;
	}
}

public Boolean isAlive() {
	return this.currentHp > 0;
}

 

然后在TargetController中添加update的相關代碼

 

public void update(Stage stage) {
	Group projectiles = (Group) stage.getRoot().findActor("projectiles"); // 獲取飛鏢所在Group
	Actor[] projectile = projectiles.getChildren().begin();
	Actor[] targets = this.getChildren().begin();
	for (int i = 0; i < projectiles.getChildren().size; i++) {
		Actor actor = projectile[i];
		for (int j = 0; j < this.getChildren().size; j++) {
			Actor target = targets[j];
			if (ProjectileFactory.attackAlive(target, actor)) {
				Scythe scythe = (Scythe) target;
				scythe.beAttacked(1);
				projectiles.removeActor(actor);
				if (!scythe.isAlive()) {
					this.removeActor(target);
				}
				break;
			}
		}
	}
}

 

然后在主類中修改相關的實例化代碼,在render中調用update方法。

targetController.update(this.stage); //調用update方法,處理怪獸的邏輯

 

效果如下:

04 

飛鏢的殺傷力和手勢識別

上面的代碼中每一個飛鏢的殺傷力是1,每一個怪獸的血量是2。

現在我們來修改一下飛鏢的控制,讓飛鏢也采用控制器來處理。

在這里設定為觸摸一下屏幕是發射一般的飛鏢,殺傷力為1。而長按以后的殺傷力是2。

libgdx中提供了一個手勢識別的接口,實現它就可以了。這里我選擇繼承GestureAdapter。

public class DartsListener extends GestureAdapter

同時修改飛鏢的控制為控制器模式,新建控制器DartsController。

代碼如下:

package com.cnblogs.htynkn.controller;

import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.cnblogs.htynkn.elements.Dart;

public class DartsController extends IController {

	AtlasRegion region;

	@Override
	public void update(Stage stage) {
		// 如果飛鏢已經飛到則刪除
		Actor[] projectile = this.getChildren().begin();
		for (int j = 0; j < this.getChildren().size; j++) {
			Actor actor = projectile[j];
			if (!this.checkAlive(actor)) {
				this.removeActor(actor);
			}
		}
	}

	public DartsController(AtlasRegion region) {
		this.region = region;
	}

	public void AddDarts(Dart dart) {
		if (this.getChildren().size >= 5) { //如果飛鏢數量大於等於5個就結束
			return;
		}
		float r = (dart.getTarget().y - dart.getY())
				/ (dart.getTarget().x - dart.getX()); // 獲取斜率
		float detY = r * 480; // 獲取Y的變動量
		dart.addAction(Actions.moveTo(480 + dart.getX(), detY + dart.getY(), 2f)); // 設置飛鏢的移動
		this.addActor(dart);
	}

	public Boolean attackAlive(Actor target, Actor projectile) {
		Rectangle rectangle = new Rectangle(target.getX(), target.getY(),
				target.getWidth(), target.getHeight()); // 創建一個矩形
		return rectangle.contains(
				projectile.getX() + projectile.getWidth() / 2,
				projectile.getY() + projectile.getHeight() / 2); // 判斷是否在矩陣中,即是否擊中
	}

	public Boolean checkAlive(Actor projectile) {
		if (projectile.getActions().size == 1) {
			return false;
		}
		return true;
	}

	public Dart createDart() {
		return new Dart(region);
	}
}

其中Dart類代碼如下:

package com.cnblogs.htynkn.elements;

import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Image;

public class Dart extends Image {

	Vector2 target;

	public Dart(AtlasRegion region) {
		super(region);
		this.setOrigin(getWidth() / 2, getHeight() / 2);
		this.addAction(Actions.repeat(50, Actions.rotateBy(360, 0.5f)));
	}

	public void setTarget(Vector2 target) {
		this.target = target;
	}

	public void setTarget(float x, float y) {
		this.target = new Vector2(x, y);
	}

	public Vector2 getTarget() {
		return target;
	}
}

 

因為我們的輸入接受另外有類DartsListener來處理,所以修改主類的繼承如下:

public class DartsShaSha implements ApplicationListener

 

在multiplexer中添加我們新的手勢識別器

GestureDetector gestureDetector = new GestureDetector(
		new DartsListener(this.stage));
multiplexer.addProcessor(gestureDetector); // 添加手勢識別

目前DartsListener中代碼如下

 

package com.cnblogs.htynkn.listener;

import com.badlogic.gdx.input.GestureDetector.GestureAdapter;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.cnblogs.htynkn.controller.DartsController;
import com.cnblogs.htynkn.elements.Dart;

public class DartsListener extends GestureAdapter {

	Stage stage;

	public DartsListener(Stage stage) {
		this.stage = stage;
	}

	@Override
	public boolean touchDown(float x, float y, int pointer, int button) {
		DartsController dartsController = (DartsController) stage.getRoot()
				.findActor("dartsController");
		if (dartsController.getChildren().size >= 5) { // 限制飛鏢的數量為5個
			return false;
		}
		Vector3 vector3 = new Vector3(x, y, 0);
		stage.getCamera().unproject(vector3); // 坐標轉化
		Actor man = stage.getRoot().findActor("player");
		if (vector3.x < man.getX() + 10) { // 如果觸摸太靠近左側就不響應
			return false;
		}
		Dart dart = dartsController.createDart();
		dart.setX(man.getX() + man.getWidth() / 2);
		dart.setY(man.getY() + man.getHeight() / 2);
		dart.setTarget(vector3.x, vector3.y);
		dartsController.AddDarts(dart);
		return true;
	}

	@Override
	public boolean longPress(float x, float y) {
		return true;
	}
}

可能還有其他細節的修改,詳細的請參考代碼。

目前我們只能算是重構了一下,游戲效果並沒有改變。現在來設置飛鏢的殺傷力和長按的處理。

在Dart中添加屬性

int power;

在實例化中添加

power = 1; //默認殺傷力為1

將TargetController中的

scythe.beAttacked(1);

 

修改為

scythe.beAttacked(dart.getPower());

 

而中的longPress方法基本和touchDown相同,只是增加了

dart.setPower(2); //設置殺傷力為2
dart.setColor(Color.RED); //設置成紅色

來思考一下處理流程,用戶觸摸屏幕,首先會觸發tap事件,然后是touchDown,最后才是longPress。

也就是目前我們的游戲長按一下會發出一個普通的飛鏢,然后才是我們的紅色飛鏢。

要處理這個問題,我們添加一個DartsDetector類,繼承GestureDetector類。

因為事件的觸發順序是tap->touchdown->longpress->touchup。

所以我們的事件邏輯全部轉移到touchup中,如果是longpress事件就發出紅色飛鏢,如果是touchdown就發出普通飛鏢。

由於我們的DartsListener已經不處理任何邏輯了,所以刪除其中所有代碼。

GestureDetector中的代碼如下

package com.cnblogs.htynkn.listener;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.cnblogs.htynkn.controller.DartsController;
import com.cnblogs.htynkn.elements.Dart;

public class DartsDetector extends GestureDetector {

	Stage stage;

	public DartsDetector(Stage stage, GestureListener listener) {
		super(listener);
		this.stage = stage;
	}
	@Override
	public boolean touchUp(float x, float y, int pointer, int button) {
		DartsController dartsController = (DartsController) stage.getRoot()
				.findActor("dartsController");
		if (dartsController.getChildren().size >= 5) { // 限制飛鏢的數量為5個
			return false;
		}
		Vector3 vector3 = new Vector3(x, y, 0);
		stage.getCamera().unproject(vector3); // 坐標轉化
		Actor man = stage.getRoot().findActor("player");
		if (vector3.x < man.getX() + 10) { // 如果觸摸太靠近左側就不響應
			return super.touchUp(x, y, pointer, button);
		}
		Dart dart = dartsController.createDart();
		dart.setX(man.getX() + man.getWidth() / 2);
		dart.setY(man.getY() + man.getHeight() / 2);
		dart.setTarget(vector3.x, vector3.y);
		if (this.isLongPressed()) { //如果是長按就變成紅色飛鏢
			dart.setPower(2); // 設置殺傷力為2
			dart.setColor(Color.RED); // 設置成紅色
		}
		dartsController.AddDarts(dart);
		return super.touchUp(x, y, pointer, button);
	}
}

 

效果如下:

05

寫在最后

這一篇修改了很多細節,可能部分很小但卻關鍵的修改沒有在文中標明。比如為Actor設置名稱以便通過findActor方法獲取。

如果直接復制有問題,可以從git庫獲取https://github.com/htynkn/DartsShaSha,對應的tag為page2。

apk地址是http://pan.baidu.com/share/link?shareid=331455&uk=4127624209

下一篇將會添加一些聲音效果和資源加載,然后會添加一個統計功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM