上文中,我们已经有了一个漂亮的人物角色,但是对于一个游戏而言,除了可操控的游戏角色,各种弹出框也是必须设计考虑的一部分。
比如你希望播放一个过场动画,弹出一个对话框,打开背包栏目等等。
为了实现以上功能,我们需要另一个相关组件(scene 场景)
本文中为了方便,将着重介绍FXGL引擎提供的"subscene(子场景)"
1.方法源码解析
如果需要在fxgl中创建一个子场景十分容易,只需要创建一个类继承自Subscene类即可。
在subscene中有几个方法和属性十分关键。
- 属性
contentRoot 这是渲染node的根节点,所有在scene中的节点都应该渲染源自于此。我们可以在这个属性上操控当前的渲染组件。
input 这是作用于scene中的输入事件,有些输入会在场景弹出是才奏效,比如聊天中的下一句,比如整理背包等操作。
2.方法
FXGL.getSceneService().popSubScene(); 此方法移除子场景
FXGL.getSceneService().pushSubScene(); 此方法加入子场景,加入时游戏主场景暂停。
2.创建一个可以触发的对话框
首先创建TalkScene类,代码逻辑十分简单,运用的新的方法在上方已经说明,这里就不多说明了。
package com.dam.wonder.ui;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.input.UserAction;
import com.almasb.fxgl.scene.SubScene;
import com.almasb.fxgl.ui.FontType;
import com.dam.wonder.pojo.Talk;
import javafx.geometry.Point2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.*;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* 对话窗口ui
*/
public class TalkScene extends SubScene {
//对话的数组
private final List<Talk> list = new ArrayList<>();
//text的文本
private final Text text;
//矩形
private final Rectangle rectangle;
//存放text和包裹的矩形的容器
private StackPane stackPane;
//左边的立绘
private final ImageView playerDrawAsk;
//右边的立绘
private final ImageView playerDrawAnswer;
/**
* 构造方法
*/
private TalkScene() {
double width = FXGL.getAppWidth();
double height = FXGL.getAppHeight() / 4d;
//初始化ui的框体图片
Image image = new Image("assets/ui/pics/talk_bg.png");
ImageView imageView = new ImageView(image);
//保持长宽比
imageView.setFitWidth(width);
imageView.setFitHeight(height);
imageView.setTranslateX(0);
imageView.setTranslateY(height *3 + 30);
//初始化两立绘的位置
playerDrawAnswer = new ImageView();
playerDrawAsk = new ImageView();
playerDrawAnswer.setPreserveRatio(true);
playerDrawAsk.setPreserveRatio(true);
playerDrawAnswer.setFitWidth(width/3d);
playerDrawAsk.setFitWidth(width/3d);
playerDrawAsk.setTranslateX(20);
playerDrawAnswer.setTranslateX((width*2.6)/4);
playerDrawAsk.setTranslateY(height*2.5 );
playerDrawAnswer.setTranslateY(height*2.5 );
//初始化text文本
text = FXGL.getUIFactoryService().newText("", Color.PINK, FontType.GAME,22);
text.visibleProperty().set(true);
text.setFill(new LinearGradient(0, 0, 1, 2, true, CycleMethod.REPEAT, new Stop(0, Color.AQUA), new Stop(0.5f, Color.RED)));
text.setStrokeWidth(1);
text.setStroke(Color.PINK);
//给立绘增加一个呼吸效果(变大变小)
FXGL.animationBuilder(this)
.repeatInfinitely()
.duration(Duration.seconds(2))
.autoReverse(true)
.scale(playerDrawAnswer)
.from(new Point2D(1, 1))
.to(new Point2D(1.05, 1.05))
.buildAndPlay();
//给立绘增加一个呼吸效果(变大变小)
FXGL.animationBuilder(this)
.repeatInfinitely()
.duration(Duration.seconds(2))
.autoReverse(true)
.scale(playerDrawAsk)
.from(new Point2D(1, 1))
.to(new Point2D(1.05, 1.05))
.buildAndPlay();
text.setTranslateX(20);
text.setTranslateY(height *3 +20);
rectangle = new Rectangle(width,height);
rectangle.setTranslateX(0);
rectangle.setTranslateY(height*3);
//把矩形透明度调低
rectangle.setOpacity(0);
getContentRoot().getChildren().add(imageView);
//添加一个input事件 鼠标左建进一步对话
getInput().addAction(new UserAction("remove") {
@Override
protected void onActionBegin() {
getContentRoot().getChildren().remove(stackPane);
getContentRoot().getChildren().remove(playerDrawAsk);
getContentRoot().getChildren().remove(playerDrawAnswer);
nextTalk();
}
}, MouseButton.PRIMARY);
}
private static TalkScene instance = new TalkScene();
//简单的单例模式 方便调用
public static synchronized TalkScene getInstance() {
if (instance == null) {
instance = new TalkScene();
}
return instance;
}
//展示对话框的方法
public synchronized void show (List<Talk> talkList) {
FXGL.getSceneService().pushSubScene(this);
ArrayList<Talk> arrayList = new ArrayList<>();
arrayList.addAll(talkList);
arrayList.forEach(t -> {
int length = t.getTalk().length();
if (length>20) {
for (int i = 0; i < length/20; i++) {
String substring = t.getTalk().substring(0, Math.min(t.getTalk().length(), 20));
Talk talk = new Talk();
talk.setTalk(substring);
talk.setAsk(t.isAsk());
talk.setShowDrawUrl(t.getShowDrawUrl());
list.add(talk);
if (t.getTalk().length()>20) {
t.setTalk(t.getTalk().substring(20,t.getTalk().length() -20));
}else {
list.add(t);
break;
}
}
}else {
list.add(t);
}
});
nextTalk();
}
/**
* 下一句话
*/
public void nextTalk() {
if (list.size()<1) {
getInput().clearAll();
FXGL.getSceneService().popSubScene();
}else {
Talk s = list.get(0);
list.remove(s);
showTalk(s);
}
}
/**
* 展示文字
*/
private void showTalk(Talk talk) {
text.setText(talk.getTalk());
StackPane stackPane = new StackPane( rectangle, text);
this.stackPane =stackPane;
if (talk.isAsk()) {
playerDrawAsk.setImage(new Image(talk.getShowDrawUrl()));
getContentRoot().getChildren().add(playerDrawAsk);
}else {
playerDrawAnswer.setImage(new Image(talk.getShowDrawUrl()));
getContentRoot().getChildren().add(playerDrawAnswer);
}
getContentRoot().getChildren().add(stackPane);
}
}
实体类 talk类
package com.dam.wonder.pojo;
import lombok.Data;
@Data
public class Talk {
//讨论的文字
private String talk;
//立绘地址
private String showDrawUrl;
//是左边的还是右边的
private boolean isAsk;
}
构建talk的工厂方法,后面会改造成读取配置文件生产对话的方法。
package com.dam.wonder.factory;
import com.dam.wonder.pojo.Talk;
import java.util.ArrayList;
import java.util.List;
/**
* 对话组装工厂
*/
public class TalkFactory {
public static List<Talk> buildTalkList() {
List<Talk> talkList = new ArrayList<>();
Talk talk = new Talk();
talk.setTalk("嘤嘤嘤嘤嘤");
talk.setAsk(true);
talk.setShowDrawUrl("assets/textures/drawing/green/5146.png");
talkList.add(talk);
Talk talk1 = new Talk();
talk1.setTalk("嘤嘤嘤嘤");
talk1.setAsk(false);
talk1.setShowDrawUrl("assets/textures/drawing/wan/t4-1.png");
talkList.add(talk1);
return talkList;
}
}
主app基本没有修改,只是把之前的抽奖的图标改成了触发对话框场景
package com.dam.wonder;
import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.app.scene.Viewport;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.input.UserAction;
import com.dam.wonder.component.MoveComponent;
import com.dam.wonder.constant.EntityType;
import com.dam.wonder.factory.CustomerEntityFactory;
import com.dam.wonder.factory.GameEntityFactory;
import com.dam.wonder.factory.TalkFactory;
import com.dam.wonder.pojo.Talk;
import com.dam.wonder.ui.TalkScene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Rectangle;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public class GameApp extends GameApplication {
/**
* Can be overridden to provide global variables.
*
* @param vars map containing CVars (global variables)
*
*/
@Override
protected void initGameVars(Map<String, Object> vars) {
vars.put("score",0);
vars.put("talk","");
}
/**
* Called every frame _only_ in Play state.
*
* @param tpf time per frame
*/
@Override
protected void onUpdate(double tpf) {
FXGL.inc("score",1);
}
/**
* Initialize input, i.e. bind key presses, bind mouse buttons.
* <pre>
* Example:
*
* Input input = getInput();
* input.addAction(new UserAction("Move Left") {
* protected void onAction() {
* playerControl.moveLeft();
* }
* }, KeyCode.A);
* </pre>
*/
@Override
protected void initInput() {
FXGL.getInput().addAction(new UserAction("up") {
@Override
protected void onAction() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).up();
}
@Override
protected void onActionEnd() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).stopY();
}
}, KeyCode.W);
FXGL.getInput().addAction(new UserAction("down") {
@Override
protected void onAction() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).down();
}
@Override
protected void onActionEnd() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).stopY();
}
}, KeyCode.S);
FXGL.getInput().addAction(new UserAction("left") {
@Override
protected void onAction() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).left();
}
@Override
protected void onActionEnd() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).stopX();
}
}, KeyCode.A);
FXGL.getInput().addAction(new UserAction("right") {
@Override
protected void onAction() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).right();
}
@Override
protected void onActionEnd() {
List<Entity> entitiesByType = FXGL.getGameWorld().getEntitiesByType(EntityType.PLANE);
entitiesByType.get(0).getComponent(MoveComponent.class).stopX();
}
}, KeyCode.D);
FXGL.getInput().addAction(new UserAction("show talk") {
@Override
protected void onActionBegin() {
List<Talk> talkList = TalkFactory.buildTalkList();
TalkScene instance = TalkScene.getInstance();
instance.show(talkList);
}
}, KeyCode.P);
}
/**
* Initialize UI objects.
*/
@Override
protected void initUI() {
Image image = new Image("assets/ui/buttons/ui1.png");
Rectangle rectangle = new Rectangle(50, 50);
rectangle.setFill(new ImagePattern(image));
rectangle.setOnMouseClicked(e -> FXGL.getInput().mockKeyPress(KeyCode.P));
FXGL.addUINode(rectangle,900,20);
}
/**
* Initialize game objects.
*/
@Override
protected void initGame() {
setLevel();
FXGL.getGameWorld().addEntityFactory(new GameEntityFactory());
Entity entity = CustomerEntityFactory.createEntity(EntityType.PLANE);
//绑定视角 固定视角
FXGL.getGameWorld().addEntity(entity);
Viewport viewport = FXGL.getGameScene().getViewport();
viewport.setBounds(-10000,-10000,250 *70,10000);
viewport.bindToEntity(entity, FXGL.getAppWidth() / 2, FXGL.getAppHeight() / 2);
}
@Override
protected void initSettings(GameSettings settings) {
settings.setTitle("demo");
settings.setHeight(720);
settings.setWidth(1080);
settings.setDeveloperMenuEnabled(true);
}
public static void main(String[] args) {
launch(args);
}
private void setLevel() {
//首先移除全部的实体
FXGL.getGameWorld().getEntitiesCopy().forEach(t -> t.removeFromWorld());
//加载地图
FXGL.setLevelFromMap("tmx/new_home.tmx");
}
}
当!成果就是下面这个!点击鼠标左键会依次进行对话,结束后自动弹出对话框!
查看源码欢迎查看源代码仓库。分支:场景