上一课中,我们创建了游戏角色。这节课中,我们将会创建菜单,以便后面使用。
由于只是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.csdn.net/ml3947。另外我的个人博客:http://www.wjfxgame.com.