FXGL JAVA游戏引擎 教程 05.场景 (萌妹在线哭泣)

本文详细介绍了如何在FXGL游戏引擎中创建和管理子场景,特别是用于显示对话框的`TalkScene`类。通过继承`SubScene`并利用其属性和方法,实现了对话框的显示、输入事件处理以及动画效果。此外,还展示了如何通过`TalkFactory`创建对话实例,并在主应用中响应按键触发对话展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上文中,我们已经有了一个漂亮的人物角色,但是对于一个游戏而言,除了可操控的游戏角色,各种弹出框也是必须设计考虑的一部分。
比如你希望播放一个过场动画,弹出一个对话框,打开背包栏目等等。
为了实现以上功能,我们需要另一个相关组件(scene 场景)
本文中为了方便,将着重介绍FXGL引擎提供的"subscene(子场景)"

1.方法源码解析

如果需要在fxgl中创建一个子场景十分容易,只需要创建一个类继承自Subscene类即可。
在subscene中有几个方法和属性十分关键。

  1. 属性
    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");
    }
}

当!成果就是下面这个!点击鼠标左键会依次进行对话,结束后自动弹出对话框!
在这里插入图片描述
在这里插入图片描述
查看源码欢迎查看源代码仓库。分支:场景

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值