上一课中,我们创建了游戏角色。这节课中,我们将会创建菜单,以便后面使用。
由于只是Demo,我创建的是最简单的形式,如下图所示:
基于游戏开发中的UI控件通常需要有事件(比如图中的移动,攻击,待机,是有事件处理的),我们应该首先创建自己的文字控件。
文字控件代码如下:
import com.sun.javafx.tk.FontMetrics;
import com.sun.javafx.tk.Toolkit;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
/**
* 文字物件
* @author Wing Mei
*/
@SuppressWarnings("restriction")
public class TextObject extends BaseObject {
private String text;
private Font font = Font.getDefault();
private double fontSize = Font.getDefault().getSize();
private Paint color = Color.BLACK;
public TextObject() {
}
public TextObject(String text){
this.text = text;
}
@Override
public void draw(GraphicsContext gContext) {
gContext.save();
gContext.setFont(font);
gContext.setFill(color);
if (text != null) {
gContext.fillText(text, getX(), getY());
}
gContext.restore();
}
@Override
public void update() {
}
@Override
public boolean isCollisionWith(double x,double y){
if(x > getX() && y > getY() - getHeight() && x < getX() + getWidth() && y < getY() - getHeight() + getHeight()){
return true;
}
return false;
}
@Override
public double getWidth(){
FontMetrics fm = Toolkit.getToolkit().getFontLoader().getFontMetrics(font);
return fm.computeStringWidth(text);
}
@Override
public double getHeight(){
FontMetrics fm = Toolkit.getToolkit().getFontLoader().getFontMetrics(font);
return fm.getLineHeight();
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public Paint getColor() {
return color;
}
public void setColor(Paint color) {
this.color = color;
}
public double getFontSize() {
return fontSize;
}
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
this.font = new Font(font.getFamily(), fontSize);
}
}
由于JavaFX的文字坐标貌似是从左下角开始的,所以碰撞的方法进行了处理。这里,我们使用了FontMetrics来获取文字的宽度和高度(感觉高度不是很准确的样子,先凑合着使用)。
有了文字控件,属性框和操作菜单就要简单很多。其实就是一个背景+N个文字控件而已。
下面是属性框的代码:
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
public class PropertyMenu extends BaseObject {
private TextObject[] textObjects;
private Paint color = Color.BLACK;
private int spaceLine = 5;
public PropertyMenu(int width, int height) {
setWidth(width);
setHeight(height);
textObjects = new TextObject[7];
for (int i = 0; i < textObjects.length; i++) {
textObjects[i] = new TextObject();
textObjects[i].setColor(Color.WHITE);
}
}
/**
* 初始化载入某个角色的属性
* @param player 角色
*/
public void initPlayer(BasePlayer player) {
setProperty(textObjects[0], "姓名", player.getName());
setProperty(textObjects[1], "等级", String.valueOf(player.getLv()));
setProperty(textObjects[2], "攻击", String.valueOf(player.getAttack()));
setProperty(textObjects[3], "防御", String.valueOf(player.getDefense()));
setProperty(textObjects[4], "移动力", String.valueOf(player.getMove()));
setProperty(textObjects[5], "HP", String.valueOf(player.getHp()) + "/" + player.getHpMax());
setProperty(textObjects[6], "EXP", String.valueOf(player.getExp()));
}
private void setProperty(TextObject textObject, String propertyName, String value) {
textObject.setText(propertyName + ":" + value);
textObject.setFontSize(16);
}
@Override
public void draw(GraphicsContext gContext) {
gContext.save();
gContext.setStroke(color);
gContext.setGlobalAlpha(0.8f);
gContext.fillRect(x, y, width, height);
if (textObjects != null) {
for (int i = 0; i < textObjects.length; i++) {
textObjects[i].setX((getWidth() - textObjects[i].getWidth()) / 2 + getX());
textObjects[i].setY(getY() + spaceLine * (i + 1) + textObjects[i].getHeight() * (i + 1));
textObjects[i].draw(gContext);
}
}
gContext.restore();
}
@Override
public void update() {
}
}
属性框的代码很简单,就是绘制一个背景+N个TextObject。通过传入BasePlayer来设置每个TextObject的值。由于属性框无需事件,我们在这里也不做事件处理。
另外,本Demo纯属学习使用,并未做深层次的类结构划分(在我另外的游戏开发库中有做),所以文字控件的绘制需要坐标相对于当前菜单的坐标,需要进行一定的处理。
接下来是操作菜单的代码:
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
public class ActionMenu extends BaseObject {
private TextObject[] textObjects;
private Paint color = Color.BLACK;
private int spaceLine = 5;
private OnMenuItemClickListener onMenuItemClickListener;
public ActionMenu(String[] strs, int width, int height) {
setWidth(width);
setHeight(height);
textObjects = new TextObject[strs.length];
for (int i = 0; i < textObjects.length; i++) {
textObjects[i] = new TextObject();
textObjects[i].setText(strs[i]);
textObjects[i].setColor(Color.WHITE);
textObjects[i].setFontSize(16);
}
}
/**
* 鼠标事件,会执行回调
* @param e 鼠标事件
*/
public void onMousePressed(MouseEvent e) {
if (onMenuItemClickListener != null)
for (int i = 0; i < textObjects.length; i++) {
if (textObjects[i].isCollisionWith(e.getX(), e.getY())) {
onMenuItemClickListener.onMenuItemClick(i);
}
}
}
@Override
public void draw(GraphicsContext gContext) {
gContext.save();
gContext.setGlobalAlpha(0.8f);
gContext.setStroke(color);
gContext.fillRect(x, y, width, height);
for (int i = 0; i < textObjects.length; i++) {
textObjects[i].setX((getWidth() - textObjects[i].getWidth()) / 2 + getX());
textObjects[i].setY(getY() + spaceLine * (i + 1) + textObjects[i].getHeight() * (i + 1));
textObjects[i].draw(gContext);
}
gContext.restore();
}
@Override
public void update() {
}
public TextObject[] getTextObjects() {
return textObjects;
}
public void setTextObjects(TextObject[] textObjects) {
this.textObjects = textObjects;
}
public OnMenuItemClickListener getOnMenuItemClickListener() {
return onMenuItemClickListener;
}
public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
this.onMenuItemClickListener = onMenuItemClickListener;
}
public interface OnMenuItemClickListener {
public void onMenuItemClick(int index);
}
}
在操作菜单中,我们新增加了一个OnMenuItemListener,来用于执行鼠标点击后的回调。这也是Java事件机制中很常用的做法。另外,定义了一个onMousePressed方法,注意,由于是自定义的类,这里的onMousePressed事件是不会执行的。我们需要在Canvas的鼠标事件中,调用该方法来模拟执行鼠标操作事件。主要是鼠标点击,通过遍历TextObject列表来判断点击的是哪个TextObject,然后通过OnMenuItemListener来执行回调。方便我们在其他地方做事件处理。
这样一来,我们两个菜单就都创建好了。接下来,加进我们的Canvas中看看效果吧。
在MainCanvas中加入定义:
// 操作菜单
private ActionMenu actionMenu;
// 属性菜单
private PropertyMenu propertyMenu;
然后初始化:
// 初始化操作菜单
actionMenu = new ActionMenu(new String[] { "移动", "攻击", "待机" }, 50, 100);
actionMenu.setLocation(100, 50);
actionMenu.setOnMenuItemClickListener(index ->{
System.out.println("你点击的是:" + index);
});
// 属性菜单
propertyMenu = new PropertyMenu(100, 200);
propertyMenu.initPlayer(players.get(0));
// 鼠标事件
setOnMousePressed(e ->{
actionMenu.onMousePressed(e);
});
在这里的两个事件都用的是Lambada表达式,不熟悉JDK8的可以自行修改。另外,PropertyMenu由于要载入Player的属性,请在Player都初始化完成后,再添加该代码。
接下来,我们需要将两个菜单绘制出来,在draw方法中添加如下代码:
actionMenu.draw(gContext);
propertyMenu.draw(gContext);
然后我们可以运行看看效果了:
尝试一下事件把,点击移动,攻击,待机等等。
事件执行良好吧?
那么这一节课到此结束了。
下一节课,我们将会用到定时器的内容,将会创建一个定时器供后面使用。
本文章为个人原创,版权所有,转载请注明出处:http://blog.youkuaiyun.com/ml3947。另外我的个人博客:http://www.wjfxgame.com.