簡單封裝一個白鷺H5開發腳手架

前言

       用白鷺開發H5小遊戲有一段時間了,也開發了幾款H5小遊戲,這篇文章主要整理下開發過程的一些經驗以及本身逐漸封裝出來的一個開發腳手架。vue

腳手架源碼地址

github.com/CB-ysx/egre…ios

建立項目

直接使用EgretLauncher建立一個項目 git

image.png
建立後,咱們主要修改的就是src目錄下的文件,先對文件內容進行一些刪除(去掉一些不必,或者說暫時用不到的代碼)

Platform.ts文件

image.png

LoadingUI.ts文件

整個文件刪除掉,等會咱們再建立一個(至關於一個page,加載頁面)github

Main.ts文件

image.png

建立項目目錄

先在項目src目錄下建立一些文件夾,以下:web

┗src
  ┣━base
  ┃  ┗━存放一些基類
  ┣━common
  ┃  ┗━存放一些公共的類
  ┣━component
  ┃  ┗━存放一些組件
  ┣━data
  ┃  ┗━存放數據
  ┣━net
  ┃  ┗━api、網絡請求
  ┣━page
  ┃  ┗━頁面
  ┣━Main.ts----入口文件
  ┗━Platform.ts----一些變量等的聲明
複製代碼

項目大概就按上面的結構劃分,接下來就來寫一些基類、公共類等的實現vue-router

建立視圖的基類BaseView.ts

在base目錄下建立BaseView.ts文件,BaseView類繼承自egret.DisplayObjectContainer,能夠當成是一個view容器,主要是實現一些view(彈窗、頁面等)裏面常常須要用到的方法。typescript

class BaseView extends egret.DisplayObjectContainer {


    /** * 加載框(可用於網絡請求前調用,或一些異步耗時操做前調用) */
    private _loading: egret.DisplayObjectContainer
    private _loadingIcon: egret.Bitmap

    /** * 記錄是否彈出加載框 */
    private isLoading: boolean = false


    /** * 加載動畫 */
    private loadingTween: egret.Tween

    public constructor() {
        super()
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this)
    }
    
    /** * 被添加到舞臺 */
    public onAddToStage(event: egret.Event) {
    }

    /** * 視圖銷燬 */
    public onDestory() {
    }


    /** * 消息提示 */
    protected toast(msg: string, duration = 2000, cb?: Function) {
	// 後面會寫到如何封裝該Toast類
        Toast.instance.show(msg, duration, cb)
    }

    /** * 顯示加載框 */
    protected showLoading() {
        if (this.isLoading) {
            return
        }
        this.isLoading = true
        if (!this._loading) {
            this._loading = new egret.DisplayObjectContainer()
            let bg = new egret.Shape()
            bg.graphics.beginFill(0x000000, 1)
            bg.graphics.drawRect(0, 0, this.width, this.height)
            bg.graphics.endFill()
            bg.alpha = 0.3
            bg.touchEnabled = true
            this._loading.addChild(bg)
        }
        if (!this._loadingIcon) {
            this._loadingIcon = ResUtil.createBitmap('loading_icon') // 這是本身封裝的獲取bitmap的方法,後面會寫到
            this._loadingIcon.width = 50
            this._loadingIcon.height = 50
            this._loadingIcon.anchorOffsetX = this._loadingIcon.width / 2
            this._loadingIcon.anchorOffsetY = this._loadingIcon.height / 2
            this._loadingIcon.x = this.width / 2
            this._loadingIcon.y = this.height / 2
            this._loading.addChild(this._loadingIcon)
        }
        egret.MainContext.instance.stage.addChild(this._loading)
        this.loadingTween = egret.Tween
                                .get(this._loadingIcon, {loop: true})
                                .to({
                                    rotation: 360
                                }, 500, egret.Ease.sineIn)
    }

    /** * 關閉加載框 */
    protected closeLoading() {
        if (!this.isLoading) {
            return
        }
        if (this.loadingTween) {
            this.loadingTween.setPaused(true)
        }
        this.loadingTween = null
        egret.MainContext.instance.stage.removeChild(this._loading)
        this.isLoading = false
    }
}
複製代碼

建立頁面基類BaseScene.ts

在base目錄下建立BaseScene.ts文件,BaseScene類繼承自BaseView,遊戲通常以場景區分,好比開始場景、遊戲場景、結束場景等,這裏就理解爲頁面,因此建立一個場景基類,設置一些默認值,好比全屏寬高等,固然,可能部分場景不須要全屏,那能夠繼承BaseScene而後重寫寬高,或者使用BaseView。json

class BaseScene extends BaseView {

    /**
     * 場景名稱
     */
    private _pageName: string = ''

    public constructor(pageName: string = '') {
        super()
        this._pageName = pageName
        // 設置場景寬高爲舞臺的寬高
        this.width = GameUtil.getStageWidth()
        this.height = GameUtil.getStageHeight()
        // 防止頁面點擊穿透
        this.touchEnabled = true
    }

    public get pageName() {
        return this._pageName
    }

    /**
     * 關於場景動畫部分,後面寫路由時會寫到
     */
    /**
     * 進入動畫執行結束
     */
    public afterEnterAnimation() {
        // console.log(this._pageName, 'afterEnterAnimation')
    }

    /**
     * 離開動畫執行結束
     */
    public afterLeaveAnimation() {
        // console.log(this._pageName, 'afterLeaveAnimation')
    }
}
複製代碼

咱們的場景基類就這麼簡單,之後若是還有通用的場景方法,能夠繼續添加到這個類中。 由於繼承自BaseView,因此場景中也能夠直接使用BaseView中封裝的public方法,好比:用 this.toast() 方法來彈出提示。axios

Toast.ts

這是一個提示組件,在component目錄下建立Toast.ts文件,使用單例模式,統一管理提示。api

class ToastMsg { // 消息格式
    public msg: string
    public duration: number = 2000
    public cb: Function

    public constructor(msg, duration, cb) {
        this.msg = msg
        this.duration = duration
        this.cb = cb
    }
}

/** * 這是模仿Android中的Toast * 實現彈出消息提示,並自動關閉 */
class Toast {

    /** * 消息列表 */
    private _msgList: Array<ToastMsg> = new Array<ToastMsg>()

    private _toasting: boolean = false

    private _stage: egret.Stage // 舞臺

    private _toastContainer: egret.DisplayObjectContainer
    private _shapeBg: egret.Bitmap
    
    public static toast: Toast

    public constructor() {
        // toast到舞臺上,保證消息顯示在最上層
        this._stage = egret.MainContext.instance.stage
        this._toastContainer = new egret.DisplayObjectContainer()
    }

    public static get instance() {
        if (!this.toast) {
            this.toast = new Toast()
        }
        return this.toast
    }

    public show(msg: string, duration = 2000, cb?: Function) {
        // 將消息存進列表
        this._msgList.push(new ToastMsg(msg, duration, cb))

        // 若是當前在顯示消息,那麼不掉用顯示方法,裏面輪詢會調用到的
        if (!this._toasting) {
            this._toasting = true
            this._show()
        }
    }

    private _show() {
	// 列表長度大於0,說明隊列中還有消息,繼續彈出
        if(this._msgList.length > 0) {
	    // 獲取第一條消息
            let msg = this._msgList.shift()

            this._toastContainer.alpha = 0

            // 建立文本
            let textField: egret.TextField = new egret.TextField()
            textField.text = msg.msg
            textField.textAlign = 'center'
            textField.textColor = 0xffffff
            textField.size = 24

            // 文本與黑色背景邊緣的寬度
            let space = 40

            // 若是文字寬度超過屏幕寬度的0.8倍,就設置一下(至關於設置外容器的最大寬度,暫時只想到這個辦法)
            if (textField.textWidth >= GameUtil.getStageWidth() * 0.8) {
                textField.width = GameUtil.getStageWidth() * 0.8 - space
            }
            // 設置裝文本的容器的寬高和錨點,文本的錨點
            this._toastContainer.width = textField.width + space
            this._toastContainer.height = textField.height + space

            this._toastContainer.anchorOffsetX = this._toastContainer.width / 2
            this._toastContainer.anchorOffsetY = this._toastContainer.height

            textField.anchorOffsetX = textField.width / 2
            textField.anchorOffsetY = textField.height / 2

            textField.x = this._toastContainer.width / 2
            textField.y = this._toastContainer.height / 2

            // 容器的位置,在底部橫向居中
            this._toastContainer.x = GameUtil.getStageWidth() / 2
            this._toastContainer.y = GameUtil.getStageHeight() * 0.8

            let shapeBg = new egret.Shape()
            shapeBg.graphics.beginFill(0x000000, 1)
            shapeBg.graphics.drawRoundRect(0, 0, this._toastContainer.width, this._toastContainer.height, 20, 20)
            shapeBg.graphics.endFill()

            this._toastContainer.addChild(shapeBg)
            this._toastContainer.addChild(textField)

            this._stage.addChild(this._toastContainer)
            
            // 彈出和消失動畫
            egret.Tween
                .get(this._toastContainer)
                .to({
                    alpha: 1
                }, 200, egret.Ease.sineIn)
                .wait(msg.duration) // 等待xxx毫秒後再消失
                .to({
                    alpha: 0
                }, 200, egret.Ease.sineIn)
                .wait(100)
                .call(()=> {
                    this._toastContainer.removeChild(shapeBg)
                    this._toastContainer.removeChild(textField)
                    this._stage.removeChild(this._toastContainer)
                    msg.cb && msg.cb()
                    this._show() // 繼續顯示下一條消息
                }, this)
        } else {
            this._toasting = false
        }
    }
}
複製代碼

GameUtil.ts

接下來建立一個工具類,方便用於獲取舞臺寬高等,在common中建立GameUtil.ts

/** * 工具類 */
/** * 工具類 */
class GameUtil {

    public static isIos: boolean = /iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase())

    public static getTopStage(): egret.Stage {
        return egret.MainContext.instance.stage
    }
    /** * 獲取舞臺高度 */
    public static getStageHeight(): number {
        return egret.MainContext.instance.stage.stageHeight
    }

    /* * 獲取舞臺寬度 */
    public static getStageWidth(): number {
        return egret.MainContext.instance.stage.stageWidth
    }

    // 是容器可點擊
    public static tap(bitmap: egret.DisplayObject, callback, thisObject) {
        bitmap.touchEnabled = true
        bitmap.addEventListener(egret.TouchEvent.TOUCH_TAP, callback, thisObject)
    }

    // 建立圓角矩形
    public static drawRoundRect(shape: egret.Shape, color: number, width: number, height: number, round: number, rArr: Array<number>) {
        shape.graphics.clear()
        shape.graphics.beginFill(color, 1)
        shape.graphics.drawRoundRect(0, 0, width, height, round, round)
        shape.graphics.endFill()
        let roundArr: Array<number> = [0, 1, 2, 3].filter(item=> rArr.indexOf(item) === -1)
        let rectData: Array<Array<number>> = [
            [0, 0],
            [1, 0],
            [0, 1],
            [1, 1]
        ]
        for (let i = 0;i < roundArr.length;++i) {
            let x = (width - round) * rectData[roundArr[i]][0]
            let y = (height - round) * rectData[roundArr[i]][1]
            shape.graphics.beginFill(color, 1)
            shape.graphics.drawRect(x, y, round, round)
            shape.graphics.endFill()
        }
    }
}
複製代碼

ResUtil.ts

資源管理,用於獲取圖片等,在common中建立ResUtil.ts

class ResUtil {
    private bitmapList: {[index: string]: egret.Texture} = {}
    private movieClipList: {[index: string]: egret.MovieClip} = {}

    private static resUtil: ResUtil

    public static get instance() {
        if (!this.resUtil) {
            this.resUtil = new ResUtil()
        }
        return this.resUtil
    }

    /** * 加載網絡圖片 * item: { * url: 'xxxx' // 圖片地址 * xxxx // 本身附帶其餘參數 * } */
    public static loadImageByUrl(item, callback) {
        try {
            RES.getResByUrl(item.url, (event)=> {
                callback && callback({
                    status: 1,
                    event: event,
                    item: item
                })
            }, this, RES.ResourceItem.TYPE_IMAGE)
        } catch (e) {
            console.error(e)
            callback && callback({
                status: 0
            })
        }
    }

    /** * 建立圖片 * @param name * @param suffix */
    public static createBitmap(name: string, suffix: string = 'png'): egret.Bitmap {
        const key = `${name}_${suffix}`
        let result = new egret.Bitmap()
        if (!this.instance.bitmapList[key]) {
            this.instance.bitmapList[key] = RES.getRes(key)
        }
        result.texture = this.instance.bitmapList[key]
        return result
    }

    /** * 建立動圖 * @param name * @param suffix */
    public static createMovieClip(name: string): egret.MovieClip {
        let result = new egret.MovieClip()
        if (!this.instance.movieClipList[name]) {
            const data_stay: any = RES.getRes(name + '_json')
            const texture_stay: egret.Texture = RES.getRes(name + '_png')
            const mcFactory_stay: egret.MovieClipDataFactory = new egret.MovieClipDataFactory(data_stay, texture_stay)
            this.instance.movieClipList[name] = new egret.MovieClip(mcFactory_stay.generateMovieClipData(name))
        }
        result.movieClipData = this.instance.movieClipList[name].movieClipData
        return result
    }

    /** * 獲取動圖的最大寬高 * @param name * @param suffix */
    public static getMoviceClipWH(name: string): {w: number, h: number} {
        let mw = 0
        let mh = 0
        const movie = ResUtil.createMovieClip(name)
        const tData = movie.movieClipData.textureData
        for (let key in tData) {
            mw = Math.max(tData[key].w, mw)
            mh = Math.max(tData[key].h, mh)
        }
        return {
            w: mw,
            h: mh
        }
    }
}
複製代碼

路由

有了以上一些基類,咱們還須要作一個頁面路由跳轉控制,這裏模仿vue-router的使用方法,本身實現一個簡單的Router,直接在src目錄下建立Router.ts文件。

class Router {

	private static router: Router

    	private _stage: egret.DisplayObjectContainer // 場景容器

	/** * 當前路由信息 */
	private _route: {
		name: string,
		meta: any,
		params: any,
		scene: BaseScene
	} = {name: '', meta: null, scene: null, params: null}

	private _params: {
		meta: any,
		params: any,
	} = {meta: null, params: null}

	/** * 路由映射表 */
	private _routeMap: SelfMap<{
		className: any,
		name: string,
		meta: any,
		params?: any
	}> = new SelfMap<{
		className: any,
		name: string,
		meta: any,
		params?: any
	}>()

	/** * 頁面離開動畫 */
	private _leaveAnimation: SelfAnimation

	/** * 頁面進入動畫 */
	private _enterAnimation: SelfAnimation

	/** * */
	private _beforeEach: Function = (to, from, next)=> {next()}

	private constructor() {
	}

    	public static get instance() {
        	if (!this.router) {
            		this.router = new Router()
        	}
        	return this.router
    	}

	public static get params() {
		return this.instance._params.params
	}

	public static get meta() {
		return this.instance._params.meta
	}

	/** * 初始化,設置放置頁面的容器 */
	public static init(main: egret.DisplayObjectContainer) {
        	if (this.instance._stage) {
            		console.log('已經建立過場景容器')
            		return
        	}
        	if (!main) {
            		console.log('主場景不能爲空')
            		return
        	}
		// 建立一個新容器放在main上,專門用來存放頁面,這樣能保證toast一直在全部頁面上
		this.instance._stage = new egret.DisplayObjectContainer()
        	main.addChild(this.instance._stage)
		return this
	}

	/** * 註冊頁面 */
	public static register(key: string, className: any, meta: any = null) {
		this.instance._routeMap.put(key, {className: className, name: key, meta: meta})
		return this
	}

	/** * 跳轉路由以前作的事 */
	public static beforeEach(beforeEach: Function) {
		this.instance._beforeEach = beforeEach
		return this
	}

	/** * 設置頁面離開動畫 */
	public static setLeaveAnimation(animation?: SelfAnimation) {
		this.instance._leaveAnimation = animation
		return this
	}

	/** * 設置頁面進入動畫 */
	public static setEnterAnimation(animation?: SelfAnimation) {
		this.instance._enterAnimation = animation
		return this
	}

	/** * 退出頁面,有動畫就執行動畫 */
	private leavePage(page: BaseScene, callback: Function, leaveAnimation: SelfAnimation) {
		if (!page) {
			callback && callback()
			return
		}
		let animation = leaveAnimation || this._leaveAnimation

		page.onDestory()
		if (animation) {
			animation.execute(page, ()=> {
				callback && callback()
			})
		} else {
			callback && callback()
		}
	}

	/** * 加載頁面,有動畫就執行動畫 */
	private enterPage(page: BaseScene, callback: Function, enterAnimation: SelfAnimation) {
		let animation = enterAnimation || this._enterAnimation
		if (animation) {
			animation.beforeAnim(page)
		}
		this._stage.addChild(page)
		if (animation) {
			animation.execute(page, ()=> {
				callback && callback()
			})
		} else {
			callback && callback()
		}
	}

	private _currentLeavePage: BaseScene = null

	public static to(config: {
		name: string,
		params?: any,
		leaveAnimation?: SelfAnimation,
		enterAnimation?: SelfAnimation
	}) {
		let page = this.instance._routeMap.get(config.name)
		if (page == undefined) {
			console.error(`scene ${config.name} is not register`)
			return
		}
		let currentRoute = this.instance._route
		this.instance._beforeEach(page, currentRoute, (to: string = config.name)=> {
			if (to != config.name) {
				config.name = to
				page = this.instance._routeMap.get(config.name)
				if (page == undefined) {
					console.error(`scene ${config.name} is not register`)
					return
				}
			}
			let currentLeavePage = this.instance._currentLeavePage
			// 若是當前離開的頁面跟正要離開的頁面同樣,不執行
			if (currentLeavePage && currentRoute.scene && currentLeavePage.pageName === currentRoute.scene.pageName) {
				return
			}
			// 若是要進入的頁面跟當前頁面同樣,不執行
			if (config.name === currentRoute.name) {
				return
			}
			this.instance._currentLeavePage = currentRoute.scene
			currentLeavePage = currentRoute.scene
			this.instance.leavePage(currentRoute.scene, ()=> {
				currentLeavePage && currentLeavePage.afterLeaveAnimation()
				let newPage = new page.className(config.name)
				this.instance._params.meta = page.meta
				this.instance._params.params = config.params
				this.instance.enterPage(newPage, ()=> {
					currentLeavePage && this.instance._stage.removeChild(currentLeavePage)
					currentLeavePage = this.instance._currentLeavePage = null
					currentRoute.name = config.name
					currentRoute.meta = page.meta
					currentRoute.scene = newPage
					currentRoute.params = config.params
					newPage.afterEnterAnimation()
				}, config.enterAnimation)
			}, config.leaveAnimation)
		})
	}
}
複製代碼

其中用到了SelfMap和SelfAnimation都是本身實現的簡單的工具類,這裏就不介紹,能夠在源碼中查看。

使用方法

在入口文件Main.ts的runGame方法中註冊路由

...
private async runGame() {
        Router.init(this)
            .setLeaveAnimation(new SelfAnimation().add({alpha: 0}, 300))// 設置頁面離開動畫
            .setEnterAnimation(new Animation().init({alpha: 0}).add({alpha: 1}, 300)) // 設置頁面進入動畫
            .register('loading', LoadingScene) // 註冊加載頁
            .register('game', GameScene) // 註冊遊戲場景路由
            .to({ // 跳轉到加載頁頁面
                name: 'loading'
            })
        ...
}
...

// 在其餘地方跳轉頁面能夠直接使用
// Router.to({name: xxxx})
複製代碼

加載頁

主要用於顯示加載資源進度以及作一些初始化操做

class LoadingScene extends BaseScene {

    private progressBar: egret.Shape
    private progressText: egret.TextField
    private barWidth: number = 0
    private barHeight: number = 0
    private startTime: number = 0

    private preload: number = 2 // preload資源組的數量
    private main: number = 100 - this.preload

    public async onAddToStage() {
        this.drawBg();
        this.initProgress();

        try {
            this.startTime = new Date().getTime()
            await RES.loadConfig(`resource/default.res.json?v=${Math.random()}`, "resource/")
            await RES.loadGroup("preload", 0, {
                onProgress: (current: number, total: number)=> {
                    let progress = current / total * this.preload
                    let width = this.barWidth * progress / 100
                    this.drawProgress(width, Math.floor(progress))
                }
            })
            // 由於logo是圖片,須要等資源加載回來才能夠繪製
            this.drawLogo();
            this.load()
        } catch(e) {
            console.error(e)
        }
    }

    private async load() {
        await RES.loadGroup("main", 0, {
            onProgress: (current: number, total: number)=> {
                let progress = current / total * this.main + this.preload
                let width = this.barWidth * progress / 100
                this.drawProgress(width, Math.floor(progress))
            }
        })
    }

    private initProgress() {
        this.barWidth = this.width * 0.6
        this.barHeight = 20
        this.progressBar = new egret.Shape()
        this.progressBar.width = this.barWidth
        this.progressBar.height = this.barHeight
        this.progressBar.x = (this.width - this.barWidth) / 2
        this.progressBar.y = (this.height - this.barHeight) / 2
        this.addChild(this.progressBar)

        this.progressText = new egret.TextField()
        this.progressText.textColor = 0x005660
        this.progressText.size = 28
        this.progressText.strokeColor = 0xFFFFFF
        this.progressText.stroke = 3
        this.progressText.width = this.width
        this.progressText.textAlign = 'center'
        this.progressText.text = '0%'
        this.progressText.y = this.progressBar.y - this.progressText.height - 20
        this.addChild(this.progressText)
    }

    private drawProgress(width, progress) {
        this.progressBar.graphics.clear()
        this.progressBar.graphics.beginFill(0xFFFFFF, 1)
        this.progressBar.graphics.drawRoundRect(0, 0, width, this.barHeight, this.barHeight, this.barHeight)
        this.progressBar.graphics.endFill()
        this.progressText.text = `${progress}%`
        if (progress == 100) {
            let diff = new Date().getTime() - this.startTime
            diff = diff < 1000 ? (1000 - diff) : 300
            egret.setTimeout(()=> {
                // 加載完成跳轉到遊戲頁面
                Router.to({name: 'game'})
            }, this, diff)
        }
    }

    private drawBg() {
        let bg: egret.Shape = new egret.Shape();
        bg.graphics.beginFill(0x56A1D2);
        bg.graphics.drawRect(0, 0, this.width, this.height);
        bg.graphics.endFill();
        this.addChild(bg);
    }

    private drawLogo() {
        let logo: egret.Bitmap = ResUtil.createBitmap('egret_icon');
        logo.x = (this.width - logo.width) / 2
        this.addChild(logo);
    }
}
複製代碼

遊戲頁面

class GameScene extends BaseScene {

    public async onAddToStage() {
        let bg: egret.Bitmap = ResUtil.createBitmap('bg', 'jpg');
        bg.width = this.width;
        bg.height = this.height;
        this.addChild(bg);

        let btn: egret.Shape = new egret.Shape();
        GameUtil.drawRoundRect(btn, 0xFFFFFF, 300, 100, 10, [0, 1, 2, 3]);
        btn.x = (this.width - btn.width) / 2;
        btn.y = (this.height - btn.height) / 2;
        this.addChild(btn);

        let btnText: egret.TextField = new egret.TextField();
        btnText.text = '點擊';
        btnText.textColor = 0x000000;
        btnText.x = (this.width - btnText.width) / 2;
        btnText.y = (this.height - btnText.height) / 2;
        this.addChild(btnText);

        GameUtil.tap(btn, ()=> {
            this.toast('點擊按鈕');
        }, this)
    }
}
複製代碼

封裝網絡請求工具

這裏使用的是egret.HttpRequest來封裝的,固然也能夠本身引入axios等框架。

Http.ts

在net目錄下建立Http.ts,對egret.HttpRequest封裝,支持promise

class Http {

    private baseUrl: string = ''

    public static http: Http

    public static get instance() {
        if (!this.http) {
            this.http = new Http()
        }
        return this.http
    }

    private constructor() {
        
    }

    private request({method = egret.HttpMethod.GET, url, params = {}, headers = {}}): Promise<any> {
        if (!(/http(|s):\/\//.test(url))) {
            url = this.baseUrl + url
        }
        let _params: any = ''
        let _headers = {}
        _headers['Content-Type'] = 'application/x-www-form-urlencoded'
        // 若是有傳入,則覆蓋掉
        for (let key in headers) {
            _headers[key] = headers[key]
        }
        if (_headers['Content-Type'] === 'application/json') {
            _params = JSON.stringify(params)
            _params = _params.replace(/\+/g, "%2B").replace(/\&/g, "%26")
            // console.log(_params)
        } else {
            for (let key in params) {
                _params += `${key}=${('' + params[key]).replace(/\&/g, "%26")}&`
            }
            _params = _params.replace(/\+/g, "%2B")
            // console.log(_params)
            if (_params.length > 0) {
                _params = _params.substring(0, _params.length - 1)
            }
            if (method === egret.HttpMethod.GET) {
                url += `?${_params}`
            }
        }

        return new Promise((resolve, reject)=> {
            let request = new egret.HttpRequest()
            request.responseType = egret.HttpResponseType.TEXT
            request.open(url, method)
            for (let key in _headers) {
                request.setRequestHeader(key, _headers[key])
            }
            if (method === egret.HttpMethod.GET) {
                request.send()
            } else {
                request.send(_params)
            }
            request.addEventListener(egret.Event.COMPLETE, (event)=> {
                dealResult(event)
            }, this)
            request.addEventListener(egret.IOErrorEvent.IO_ERROR, (event)=> {
                dealResult(event, false)
            }, this)
            function dealResult(event, success = true) {
                let response
                try {
                    response = JSON.parse(request.response)
                } catch(e) {
                    response = request.response
                }
                if (success) {
                    resolve(response)
                } else {
                    reject(response)
                }
            }
        })
    }

    public setBaseUrl(baseUrl: string) {
        this.baseUrl = baseUrl
    }

    public get(url: string, params = {}, headers = {}): Promise<any> {
        return this.request({
            url: url,
            params: params,
            headers: headers
        })
    }

    public post(url: string, params = {}, headers = {}): Promise<any> {
        return this.request({
            method: egret.HttpMethod.POST,
            url: url,
            params: params,
            headers: headers
        })
    }
}
複製代碼

Api.ts

一樣在net目錄下建立Api.ts文件,用於管理本身的api接口

class Api {

    private static api: Api

    private constructor() {
        let baseUrl: string = 'http://xxx.com'
        if (DEBUG) {
            baseUrl = 'http://localhost:3000'
        }
        Http.instance.setBaseUrl(baseUrl)
    }

    public static get instance() {
        if (!this.api) {
            this.api = new Api()
        }
        return this.api
    }

    /** * 統一處理結果 */
    private predeal(http: Promise<any>): Promise<any> {
        return http.then(response=> {
                    if (response.code == '0') {
                        // 這個結構根據本身的數據肯定
                        return Promise.resolve(response.payload.data || response.payload || response)
                    } else {
                        return Promise.reject(response)
                    }
                })
                .catch(response=> {
                    return Promise.reject(response.msg || '請求出錯')
                })
    }

    /** * get */
    private get(url: string, params = {}, headers = {}): Promise<any> {
        return this.predeal(Http.instance.get(url, params, headers))
    }

    /** * post */
    private post(url: string, params = {}, headers = {}): Promise<any> {
        return this.predeal(Http.instance.post(url, params, headers))
    }

    /** * get例子 */
    public getInfo(): Promise<any> {
        return this.get('/info')
    }

    /** * post例子 */
    public postInfo(params): Promise<any> {
        return this.post('/info', params)
    }
}
複製代碼

音頻管理

如今H5基本都離不開bgm等音樂的播放,所以,腳手架也封裝了一個音頻管理類,方便使用,這個寫得比較粗糙,還須要優化。具體代碼在項目中可查看。

localstorage存儲

封裝了CacheStorge類,用於管理localstorage存儲數據,可直接保存獲取對象等數據,詳見common/CacheStorge.ts文件

微信jssdk相關

引入jssdk

引入外部js庫,須要特殊處理,這裏處理過,能夠參照項目,在添加lib目錄裏的wxjssdk,而且在egretProperties.json的modules裏面配置路徑

{
  "engineVersion": "5.2.17",
  "compilerVersion": "5.2.17",
  "template": {},
  "target": {
    "current": "web"
  },
  "modules": [
    {
      "name": "egret"
    },
    {
      "name": "game"
    },
    {
      "name": "tween"
    },
    {
      "name": "assetsmanager"
    },
    {
      "name": "promise"
    },
		{
			"name": "wxjssdk",
			"path": "./libs/wxjssdk"
		}
  ]
}
複製代碼

封裝本身的wxsdk

封裝了獲取簽名信息,已經分享接口,先調用getWxSignPackage獲取簽名配置,該方法裏的數據處理須要根據本身的接口數據作處理。

class WxSdk {

    private signPackage: BodyConfig = new BodyConfig()

    public static wxsdk: WxSdk

    public static get instance() {
        if (!this.wxsdk) {
            this.wxsdk = new WxSdk()
        }
        return this.wxsdk
    }

    private constructor() {
        
    }

    /** * 配置微信簽名 */
    public configWx(): Promise<any> {
        // 這裏有個坑。用//api.24haowan.com時,nonceStr是大寫。用平臺時是:noncestr。切換時記得切換
        if (!this.signPackage.appId || !this.signPackage.timestamp ||
            !this.signPackage.nonceStr || !this.signPackage.signature) {
            return Promise.reject('微信簽名參數錯誤')
        }
        this.signPackage.debug = false
        this.signPackage.jsApiList = [
                // 全部要調用的 API 都要加到這個列表中
                'onMenuShareTimeline', 'onMenuShareAppMessage', 'hideMenuItems',
                // 錄音相關
                'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'onVoicePlayEnd', 'pauseVoice', 'stopVoice', 'uploadVoice', 'downloadVoice',
            ]
        /* 微信接口 */
        wx.config(this.signPackage)
        wx.error(err=> {
            console.error('error: configWx of wxsdk.ts', err)
        })
        return Promise.resolve('微信簽名配置完成')
    }

    /** * 獲取微信簽名信息 */
    public getWxSignPackage(): Promise<any> {
        let params = {
            url: encodeURIComponent(window.location.href.split('#')[0])
        }
        // 獲取微信簽名
        return Http.instance.get('xxxxx', params)
                .then(response=> {
                    let data = response.payload
                    if (!data.appId) {
                        return Promise.reject(response.msg || response)
                    }
                    this.signPackage.appId = data.appId
                    this.signPackage.timestamp = data.timestamp
                    this.signPackage.nonceStr = data.nonceStr
                    this.signPackage.signature = data.signature
                    return Promise.resolve(this.configWx())
                })
                .catch(error=> {
                    console.error('error: getWxSignPackage of wxsdk.js', JSON.stringify(error))
                    return Promise.reject(error)
                })
    }

    public share(shareInfo: ShareBody) {
        if (!shareInfo.title) {
            shareInfo.title = '分享標題'
        }
        if (!shareInfo.link) {
            shareInfo.link = window.location.href.split('#')[0]
        }
        if (!shareInfo.desc) {
            shareInfo.desc = '分享描述'
        }
        wx.ready(()=> {
            console.log('wx ready', shareInfo)
            wx.onMenuShareAppMessage(shareInfo)
            wx.onMenuShareTimeline(shareInfo)
            wx.onMenuShareQQ(shareInfo)
            wx.onMenuShareWeibo(shareInfo)
        })
    }
}
複製代碼

結束

至此,一個簡單的框架就基本完成了,可能還不太完善,但已經能夠知足一些簡單的需求了,本人已經使用這個結構開發了三四個遊戲並都順利上線。

最後再附上github地址: github.com/CB-ysx/egre…

原文連接:codebear.cn/article?id=…

相關文章
相關標籤/搜索