实现邮箱产出金币功能
预计阅读时间 15 分钟
本章我们要创建一个邮箱模型,并实现在玩家每次碰到它的时候获得一定的金币逻辑。
前面我们讲了购买建筑的功能,现在玩家还没有能赚取金币得途径,本章节我们要制作一个能持续产出金币的邮箱,它的产出速率固定,产出基数随解锁的建筑增多而增多。
创建邮箱模型
- 找一个合适的模型(52241)或者自己拼一个,拖入场中合适位置,并调整为适当大小
- 在 ui 下的 prefab 目录中新建"GoldInfo"ui,并进入 UI 设计器界面。
- 拖入两个文本控件,并分别命名为”goldNumTxt“,”addGoldPerSecTxt“。
- 将 goldNumTxt 文本控件对象中的文本更改为”0“,并设置字体颜色为金黄色,将文本大小改为 300 * 100。
- 将 addGoldPerSecTxt 文本控件对象中的文本更改为”1/秒“,并设置字体颜色为绿色,将文本大小改为 300 * 100。
- 将 Root 的大小改为 300 * 200,保存。
- 给邮箱创建世界 UI,并绑定 GoldInfo,调整大小和位置
- 右键信箱,创建一个触发器游戏功能对象
- 在 prefab 脚本目录下新建脚本“MailBox”,并拖入到信箱下
实现“MailBox”脚本客户端逻辑
- PlayerModuleC 中增加改变金币的方法
TypeScript
/**
* 客户端改变金币
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
/**
* 客户端改变金币
* @param deltaNum 要改变的数量
*/
public async changeGold(deltaNum: number): Promise<boolean> {
return this.server.net_changeGold(deltaNum);
}
- PlayerModuleS 中增加增加金币的方法,这个方法以
net_
开头,在 module 中可以支持 RPC 调用。
TypeScript
/**
* 服务端改变金币
* @param deltaNum 要改变的数量
*/
public net_changeGold(deltaNum: number):boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
/**
* 服务端改变金币
* @param deltaNum 要改变的数量
*/
public net_changeGold(deltaNum: number):boolean {
return this.changeGold(this.currentPlayerId, deltaNum);
}
- 在客户端拿到世界 UI 的 targetUI 对象
- 根据 targetUI 拿到两个文本控件对象
- 定时器根据基数每秒钟增加金币
- 已积累的金币数量与增加基数回显
- 客户端拿到触发器对象
- 绑定玩家进入事件
- 玩家进入时,将已积累的金币全部取出
- 监听金币基数改变的事件
TypeScript
import { PlayerModuleC } from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币数量 */
private _goldNum: number = 0;
/** 金币数量增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件对象 */
private _goldNumTxt: TextBlock;
/** 增加金币数量文本控件对象 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
// 如果是在服务端,直接退出
if (SystemUtil.isServer()) return;
// 异步初始化
this.init();
}
public async init() {
// 等待这个模型在客户端加载好
await this.gameObject.asyncReady();
// 拿到世界UI
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁时,清除时器
if (this.inter) {
clearInterval(this.inter);
this.inter = null;
}
}
}
import { PlayerModuleC } from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币数量 */
private _goldNum: number = 0;
/** 金币数量增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件对象 */
private _goldNumTxt: TextBlock;
/** 增加金币数量文本控件对象 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
// 如果是在服务端,直接退出
if (SystemUtil.isServer()) return;
// 异步初始化
this.init();
}
public async init() {
// 等待这个模型在客户端加载好
await this.gameObject.asyncReady();
// 拿到世界UI
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁时,清除时器
if (this.inter) {
clearInterval(this.inter);
this.inter = null;
}
}
}
- 最后在 BuildInfo 中增加收益的参数,以及玩家解锁建筑时,为该玩家发送金币增长基数增长的事件
TypeScript
@Property({ group: "基本信息", displayName: "每秒带来的收益" })
public profit: number = 1;
/**
* 初始化解锁建筑按钮
*/
protected async initUnlockBtn() {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.guid);
// 设置按钮的父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.relativeLocation = this.unlockBtnLoc;
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.showBuild();
// 金币增长基数
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
} else {
console.error("钱不够!");
}
}
}
// 拿到解锁按钮预制体下面的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
// 绑定触发器的进入事件
trigger.onEnter.add(this._unlockBuildFun);
}
}
@Property({ group: "基本信息", displayName: "每秒带来的收益" })
public profit: number = 1;
/**
* 初始化解锁建筑按钮
*/
protected async initUnlockBtn() {
// 注意这儿spawn的 GameObjectID 是解锁按钮预制体的id,第二个参数指资源类型,这儿因为是预制体的资源所以传递GameObjPoolSourceType.Prefab
this._unlockBtn = await GameObjPool.asyncSpawn("D442F26A43DED08F57F592B57CC2B56E", GameObjPoolSourceType.Prefab);
// 初始化所有玩家的世界UI
this.initWorldUIAllPlayer(this._unlockBtn.guid);
// 设置按钮的父节点为当前对象
this._unlockBtn.parent = this.gameObject;
// 设置按钮的相对位置
this._unlockBtn.relativeLocation = this.unlockBtnLoc;
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.showBuild();
// 金币增长基数
Event.dispatchToClient(other.player, "GoldGrowthRate", this.profit);
} else {
console.error("钱不够!");
}
}
}
// 拿到解锁按钮预制体下面的触发器
const trigger = this._unlockBtn.getChildByName("触发器") as Trigger;
// 绑定触发器的进入事件
trigger.onEnter.add(this._unlockBuildFun);
}
}
- 运行游戏,查看效果
完整代码
当前 MailBox
脚本完整代码(点击展开)
typescript
import PlayerModuleC from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币 */
private _goldNum: number = 0;
/** 金币增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件 */
private _goldNumTxt: TextBlock;
/** 金币增加基数文本控件 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
protected onStart(): void {
// 如果是服务端直接退出
if (SystemUtil.isServer()) return;
this.init();
}
public async init() {
// 等待邮箱模型加载好
await this.gameObject.asyncReady();
// 拿到世界UI逻辑对象
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本控件内容
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 创建定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
// 获取触发器 添加进入事件 进入之后获取金币
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币增加基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁计时器
if(this.inter){
clearInterval(this.inter);
this.inter = null;
}
}
}
import PlayerModuleC from "../modules/player/PlayerModuleC";
@Component
export default class MailBox extends Script {
/** 当前金币 */
private _goldNum: number = 0;
/** 金币增加基数 */
private _alterNum: number = 1;
/** 金币数量文本控件 */
private _goldNumTxt: TextBlock;
/** 金币增加基数文本控件 */
private _addGoldTxt: TextBlock;
/** 定时器对象 */
private inter: any;
protected onStart(): void {
// 如果是服务端直接退出
if (SystemUtil.isServer()) return;
this.init();
}
public async init() {
// 等待邮箱模型加载好
await this.gameObject.asyncReady();
// 拿到世界UI逻辑对象
const worldUI = this.gameObject.getChildByName("世界UI") as UIWidget;
// 拿到targetUI
const targetUI = worldUI.getTargetUIWidget();
// 拿到文本控件
this._goldNumTxt = targetUI.findChildByPath("RootCanvas/goldNumTxt") as TextBlock;
this._addGoldTxt = targetUI.findChildByPath("RootCanvas/addGoldPerSecTxt") as TextBlock;
// 初始化文本控件内容
this._goldNumTxt.text = this._goldNum.toString();
this._addGoldTxt.text = this._alterNum + "/秒";
// 创建定时器
this.inter = setInterval(() => {
this._goldNum += this._alterNum;
this._goldNumTxt.text = this._goldNum.toString();
}, 1000);
// 获取触发器 添加进入事件 进入之后获取金币
const trigger = this.gameObject.getChildByName("触发器") as Trigger;
trigger.onEnter.add(() => {
ModuleService.getModule(PlayerModuleC).changeGold(this._goldNum);
this._goldNum = 0;
this._goldNumTxt.text = this._goldNum.toString();
});
// 监听金币增加基数改变的事件
Event.addServerListener("GoldGrowthRate", (deltaNum: number) => {
this._alterNum += deltaNum;
this._addGoldTxt.text = this._alterNum + "/秒";
})
}
protected onDestroy(): void {
// 销毁计时器
if(this.inter){
clearInterval(this.inter);
this.inter = null;
}
}
}