Skip to content
实现购买物品功能

实现购买物品功能

预计阅读时间 20 分钟

本章我们要将玩家的金币与解锁建筑关联起来,实现扣钱才能解锁物品的功能。

制作显示玩家拥有金币数 UI

  • 在 UI 下新建 UI,更名为 MainUI,并进入 MainUI 设计器界面
  • 拖入一个容器控件,放在合适位置,取名为 GoldCanvas
  • 拖入两个个图片控件,一个文本控件到 GoldCanvas 中,调整到合适位置
  • 大一点的图片控件作为背景(131231),小一点的图片控件作为金币图标(162901)
  • 金币数量文本控件对象更名为 goldTxt

img

  • 点击导出所有脚本,之后点击确认导出,导出的 UI 脚本之后会用到

img

  • 可以在工程内容的脚本中看到,多出了一个 ui-generate 文件夹,这里就是导出的脚本所存放的地方。要注意的是这个目录每次重新导出都会被覆盖,所以我们不要将功能逻辑放在该目录下任何脚本中,需要使用请直接继承。

img

模块化脚本

关于模块化可以提前阅读,了解更多用法 :【模块管理和数据中心】——为什么需要模块管理?① 口袋方舟论坛|面向全年龄的UGC互动内容平台与交流社区 (ark.online)

  • 在脚本下新建一个文件夹,命名为 ui,这个文件夹表示存放 UI 相关逻辑脚本的地方。
  • 在 ui 文件夹下新建一个 UI 脚本,命名为 MainUI

img

  • 将 MainUI 中的代码清空,将以下代码全拷贝到 MainUI 中,这里我们继承了 MainUI_Generate 这个类。这个类就是刚刚通过 UI 编辑器一键导出的,导出后类名为 UI名字_generate
TypeScript
import MainUI_Generate from "../ui-generate/MainUI_generate";

// 继承的 MainUI_Generate 就是从 UI 编辑器器中导出来的脚本
export default class MainUI extends MainUI_Generate {

}
import MainUI_Generate from "../ui-generate/MainUI_generate";

// 继承的 MainUI_Generate 就是从 UI 编辑器器中导出来的脚本
export default class MainUI extends MainUI_Generate {

}
  • 在脚本下新建一个 "modules" 的文件夹
    • 在 modules 目录下再新建一个 “ player ” 的文件夹。
    • 在 player 目录下新建 “PlayerModuleC” 、”PlayerModuleS“、”PlayerData“脚本,分别代表:玩家模块客户端脚本、玩家模块服务端脚本、玩家模块数据脚本。

img

  • PlayerData 脚本中定义 PlayerData 类,继承 Subdata,用来存放模块的数据。
    • 定义一个公共变量 gold,并初始化金币为 55 。
    • 定义一个监听金币数量改变的回调 Action 。
    • 定义一个改变金币数量的方法。
TypeScript
export class PlayerData extends Subdata {

    /** 金币数量 */
    public gold: number = 55;

    /** 金币改变的回调,在需要知道金币改变的地方监听即可 */
    public onGoldChange: Action = new Action();

    /**
     * 改变金币的数量
     * @param deltaNum 改变值,为负数就是减
     */
    public changeGold(deltaNum: number) {
        this.gold += deltaNum;
        // 服务端改变金币,将这个操作同步给客户端
        this.syncToClient();
        this.onGoldChange.call(this.gold);
    }
}
export class PlayerData extends Subdata {

    /** 金币数量 */
    public gold: number = 55;

    /** 金币改变的回调,在需要知道金币改变的地方监听即可 */
    public onGoldChange: Action = new Action();

    /**
     * 改变金币的数量
     * @param deltaNum 改变值,为负数就是减
     */
    public changeGold(deltaNum: number) {
        this.gold += deltaNum;
        // 服务端改变金币,将这个操作同步给客户端
        this.syncToClient();
        this.onGoldChange.call(this.gold);
    }
}
  • PlayerModuleC 脚本中定义 PlayerModuleC 类,继承 ModuleC,客户端的操作放在这里完成
    • 在 onStart 时使用 UIManager 显示 MainUI 。
    • onStart 会在这个模块被注册成功后,启动时调用。
TypeScript
import MainUI from "../../ui/MainUI";
import { PlayerData } from "./PlayerData";
import { PlayerModuleS } from "./PlayerModuleS";

export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
    protected override onStart(): void {
        // 显示 MainUI 这里传递的是我们创建出的 UI 脚本的类
        UIService.show(MainUI);
    }
}
import MainUI from "../../ui/MainUI";
import { PlayerData } from "./PlayerData";
import { PlayerModuleS } from "./PlayerModuleS";

export class PlayerModuleC extends ModuleC<PlayerModuleS, PlayerData> {
    protected override onStart(): void {
        // 显示 MainUI 这里传递的是我们创建出的 UI 脚本的类
        UIService.show(MainUI);
    }
}
  • PlayerModuleS 脚本中定义 PlayerModuleS 类,继承 ModuleS,服务端的操作放在这儿完成
TypeScript
import HomeHelper from "../../utils/HomeHelper";
import { PlayerData } from "./PlayerData";
import { PlayerModuleC } from "./PlayerModuleC";

export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {

    /**
     * 改变金币
     * @param pid 要改变金币数量的玩家id
     * @param deltaNum 改变的数量
     */
    public changeGold(pid: number, deltaNum: number): boolean {
        // 获取玩家pid的数据
        const data = this.getPlayerData(pid);
        // 要改变的值是负数,且钱不够
        if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
            return false;
        }
        data.changeGold(deltaNum);
        return true;
    }
}
import HomeHelper from "../../utils/HomeHelper";
import { PlayerData } from "./PlayerData";
import { PlayerModuleC } from "./PlayerModuleC";

export class PlayerModuleS extends ModuleS<PlayerModuleC, PlayerData> {

    /**
     * 改变金币
     * @param pid 要改变金币数量的玩家id
     * @param deltaNum 改变的数量
     */
    public changeGold(pid: number, deltaNum: number): boolean {
        // 获取玩家pid的数据
        const data = this.getPlayerData(pid);
        // 要改变的值是负数,且钱不够
        if (deltaNum < 0 && data.gold < Math.abs(deltaNum)) {
            return false;
        }
        data.changeGold(deltaNum);
        return true;
    }
}
  • 在脚本下新建一个GameStart的脚本(游戏所有逻辑的入口)
    • 定义一个“registerModule”方法,并在 onStart 方法中调用,在其中使用 ModuleManager 将 PlayerModule 注册
TypeScript
import { PlayerData } from "./modules/player/PlayerData";
import { PlayerModuleC } from "./modules/player/PlayerModuleC";
import { PlayerModuleS } from "./modules/player/PlayerModuleS";

@Component
export default class GameStart extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        this.registerModule();
    }

    /** 注册模块 */
    protected registerModule() {
        ModuleService.registerModule(PlayerModuleS, PlayerModuleC, PlayerData);
    }
}
import { PlayerData } from "./modules/player/PlayerData";
import { PlayerModuleC } from "./modules/player/PlayerModuleC";
import { PlayerModuleS } from "./modules/player/PlayerModuleS";

@Component
export default class GameStart extends Script {

    /** 当脚本被实例后,会在第一帧更新前调用此函数 */
    protected onStart(): void {
        this.registerModule();
    }

    /** 注册模块 */
    protected registerModule() {
        ModuleService.registerModule(PlayerModuleS, PlayerModuleC, PlayerData);
    }
}
  • 将 GameStart 脚本拖入到对象列表

img

  • 更新 MainUI 的脚本
TypeScript
import { PlayerData } from "../modules/player/PlayerData";
import MainUI_Generate from "../ui-generate/MainUI_generate";

export default class MainUI extends MainUI_Generate {

    protected onAwake(): void {
        // 初始化金币数量
        this.goldTxt.text = DataCenterC.getData(PlayerData).gold + "";
        // 监听金币数量改变,及时更新金币数量显示
        DataCenterC.getData(PlayerData).onGoldChange.add((goldNum: number) => {
            this.goldTxt.text = goldNum + "";
        });
    }
}
import { PlayerData } from "../modules/player/PlayerData";
import MainUI_Generate from "../ui-generate/MainUI_generate";

export default class MainUI extends MainUI_Generate {

    protected onAwake(): void {
        // 初始化金币数量
        this.goldTxt.text = DataCenterC.getData(PlayerData).gold + "";
        // 监听金币数量改变,及时更新金币数量显示
        DataCenterC.getData(PlayerData).onGoldChange.add((goldNum: number) => {
            this.goldTxt.text = goldNum + "";
        });
    }
}
  • 最后更新 BuildInfo 脚本中的 _unlockBuildFun 方法,需要在金币足够且扣钱成功的前提下才能解锁显示建筑

TIP

但别忘了在文件开头导入 PlayerModuleS 的依赖

typescript
import { PlayerModuleS } from "../modules/player/PlayerModuleS";
import { PlayerModuleS } from "../modules/player/PlayerModuleS";
TypeScript
this._unlockBuildFun = (other: GameObject) => {
    // 判断进入的对象是一个Character实例才创建
    if (other instanceof Character) {
        // 钱购吗
        const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);

        // 扣钱成功才显示
        if (isGoldEnough) {
            // 用完了就先取消绑定
            trigger.onEnter.remove(this._unlockBuildFun);
            // 对象池回收解锁按钮
            GameObjPool.despawn(this._unlockBtn);
            // 显示这个模型
            // this.gameObject.setVisibility(PropertyStatus.On);
            this.showBuild();
        } else {
            console.error("钱不够!");
        }
    }
}
this._unlockBuildFun = (other: GameObject) => {
    // 判断进入的对象是一个Character实例才创建
    if (other instanceof Character) {
        // 钱购吗
        const isGoldEnough = ModuleService.getModule(PlayerModuleS).changeGold(other.player.playerId, -this.unlockPrice);

        // 扣钱成功才显示
        if (isGoldEnough) {
            // 用完了就先取消绑定
            trigger.onEnter.remove(this._unlockBuildFun);
            // 对象池回收解锁按钮
            GameObjPool.despawn(this._unlockBtn);
            // 显示这个模型
            // this.gameObject.setVisibility(PropertyStatus.On);
            this.showBuild();
        } else {
            console.error("钱不够!");
        }
    }
}
  • 运行游戏查看效果