Cocos creator开发微信小游戏常用


准备工作

  1. 注册小游戏
  2. 生成appid和密钥
  3. 安装Cocos Dashboard
  4. 安装cocos3.8.2
  5. 配置webstorm,看附件章节
  6. 打开”Cocos creator ->偏好设置->程序管理器”,配置好微信开发工具的路径
  7. 打开”项目->项目设置”裁剪好游戏引擎
  8. 项目构建发布

开发

  1. 创建一个2D空项目
  2. 拉入相应的音频、图片、模型

常用功能

拽尾

添加组件->Effects->MontionStreak: 纹理texture使用自己的图片

修改Canvas大小

面板->项目设置->设计宽度720、设计高度1280、取消适配宽高

生命周期

节点生命周期函数:
- onLoad: 当节点首次激活时调用,通常用于初始化节点的状态或获取节点相关的组件。
- start: 在 onLoad 之后调用,用于初始化一些需要在所有组件的 onLoad 方法都执行完毕后再执行的逻辑。
- update: 每帧调用,用于更新节点的逻辑。
- lateUpdate: 在所有 update 函数调用后执行,用于处理一些依赖于其它节点更新后的逻辑。
- onEnable: 当节点被启用时调用,通常在这里注册一些事件监听器。
- onDisable: 当节点被禁用时调用,通常在这里移除一些事件监听器或停止一些动作。
- onDestroy: 当节点被销毁时调用,通常在这里释放资源或清理相关的引用。

组件生命周期函数:
- onLoad: 同节点的 onLoad,在组件被实例化时调用。
- start: 同节点的 start,在组件被实例化后的下一帧调用。
- update: 同节点的 update,每帧调用。
- lateUpdate: 同节点的 lateUpdate,在所有组件的 update 函数调用后执行。
- onEnable: 同节点的 onEnable,在组件被启用时调用。
- onDisable: 同节点的 onDisable,在组件被禁用时调用。
- onDestroy: 同节点的 onDestroy,在组件被销毁时调用。

定义

// 定义节点 [拿不到内部方法]
@property({ type: Node })
public player: Node | null = null;

// 定义节点2 [拿到内部方法]
@property({ type: Node })
public player: Player | null = null;

// 定义预制品列表
@property({ type: [Prefab] })
public blockPrefab: Prefab[] = []; // 方块队列

// 获取真实节点
private playerCom: Component | null = null;



// 复制节点
this.aaa = instantiate(this.blockPrefab)

// 获取节点本身,Player是类名(一开始定义为Node,是无法获取到player本身的方法的,除非定义为Player类型)
this.player.getComponent("Player");

// 获取子节点
this.currentBlock.getChildByName("mid")

// 获取Label节点
let sortNumber = node.getChildByName("labelNumber").getComponent(Label)
// 获取Sprite节点
let sortNumber = node.getChildByName("labelNumber").getComponent(Sprite)

// 添加子节点
this.blockRoot.addChild(this.currentBlock);


// 转坐标
const w_pos = new Vec3(0, 0, 0);
// 本地坐标转世界坐标
let pos = this.mapRoot.getComponent(UITransform).convertToWorldSpaceAR(w_pos); 
// 世界坐标转本地坐标
let pos2 = this.mapRoot.getComponent(UITransform).convertToNodeSpaceAR(w_pos);

// 设置层级
this.nextBlock.setSiblingIndex(-1);

图片处理

// 接口获取到的头像渲染到Sprite上
assetManager.loadRemote(user.avatar, { ext: '.png' }, function (err, texture: ImageAsset) {
    let avatarUrl = node.getChildByName("mask").children[0].getComponent(Sprite)
    avatarUrl.spriteFrame = SpriteFrame.createWithImage(texture)
});

绑定事件

// 绑定事件-TOUCH_START
this.animNode.on(Node.EventType.TOUCH_START, () => {
    this.isPowerMode = true;
    this.speed = this.initSpeed;
    this.xDistance = 0;

    Tween.stopAllByTarget(this.animNode);
    tween(this.animNode)
        .to(0.5, { scale: new Vec3(1, 0.5, 1) })
        .repeat(1)
        .start();
}, this)

缓动系统

// 一、移动
// mapPos是相对坐标
tween(this.mapRoot)
    .to(0.5, { position: mapPos }, {
        // 完成后做一些事
        onComplete() {
            console.log("onComplete");
        }
    })
    .repeat(1)
    .start();

// 二、then使用
let afterTween = tween(this.node).to(0, { eulerAngles: new Vec3(0, 0, 0) })
const jumpFunc = tween(this.node)
    .to(0.5, { position: targetPos, eulerAngles: new Vec3(0, 0, -360) })
    .then(afterTween)
    .repeat(1)
    .start();

// 三、队列使用sequence
const endFunc = tween(this.node.parent).call(()=>{
    if (isGameOver) {
        this.gameScene.onCheckOutGame();
    } else {
        if (this.direction === 1) {
            this.gameScene.moveMap(180 - wPos.x, -yDistance);
            // console.log("方向右:", wPos, 180 - wPos.x)
        } else {
            this.gameScene.moveMap(580 - wPos.x, -yDistance);
            // console.log("方向左:", wPos, 580 - wPos.x)
        }
    }
})
tween(this.node).sequence(jumpFunc, endFunc).start();

// 四、缩放
tween(this.animNode)
    .to(0.5, { scale: new Vec3(1, 0.5, 1) })
    .repeat(1)
    .start();
Cocos Creator 3.x 中的缓动系统支持设置多种属性,让你可以创建各种动画效果。以下是一些常用的属性设置:

    位置属性:
    position: 节点的位置。
    x, y, z: 节点的 x、y、z 轴位置。
    
    旋转属性:
    rotation: 节点的旋转角度。
    eulerAngles: 节点的欧拉角度。
    
    缩放属性:
    scale: 节点的缩放比例。
    scaleX, scaleY, scaleZ: 节点在 x、y、z 轴的缩放比例。
    
    透明度属性:
    opacity: 节点的透明度。
    
    颜色属性:
    color: 节点的颜色。
    
    尺寸属性:
    width, height: 节点的宽度和高度。
    contentSize: 节点的内容尺寸。
    
    其他属性:
    angle: 2D 精灵节点的旋转角度。
    progress: 进度条节点的进度值。
    target: 自定义属性动画的目标值。
这些属性可以在动画中使用,通过设置它们的起始值和目标值,缓动系统会自动计算中间值,并根据指定的缓动函数生成动画效果。

页面操作

// 显示
this.checkout.active = true

// 重新加载
director.loadScene("game_scene");

向量

// 计算两个三维坐标的距离
const disVec = new Vec3();
Vec3.subtract(disVec, dstPos, midPos);
let minLength = disVec.length();

// 计算两个二维坐标的距离
let pos = pos1.sub(pos2);
let minLength = pos.length();

接入微信小游戏

// 判断是否在微信小游戏
if(cc.sys.platform === cc.sys.WECHAT_GAME) {
    console.log("微信小游戏")
}

https://www.jianshu.com/p/d929ae3da415

https://blog.csdn.net/weixin_42581660/article/details/127627977?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-127627977-blog-134234851.235^v43^pc_blog_bottom_relevance_base7&spm=1001.2101.3001.4242.1&utm_relevant_index=3

https://blog.csdn.net/sotmwhat/article/details/86651265?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-86651265-blog-134234851.235%5Ev43%5Epc_blog_bottom_relevance_base7&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-86651265-blog-134234851.235%5Ev43%5Epc_blog_bottom_relevance_base7&utm_relevant_index=1

场景管理

详见附件(需要预加载场景,然后每个场景的脚本里跳转)
https://zhuanlan.zhihu.com/p/657486940
https://www.douyin.com/video/7339011723426352447

列表/排行版

详见附件
https://blog.csdn.net/weixin_65084919/article/details/127616738

共享数据域

各个场景共享数据,类似vuex
子域: https://developers.weixin.qq.com/minigame/dev/guide/open-ability/opendata/framework.html

toast封装

import { UITransform, find, Node, Layers, Graphics, Color, Label, tween, Vec3 } from 'cc';

export class Toast {

    /**飘字提醒对象池*/
    private static toastPools: Array<Node> = [];

    /**显示飘字提醒*/
    public static toast(str: string) {
        let toastNode = this.toastPools.shift();
        if (!toastNode) {
            //屏幕节点
            const Canvas = find("Canvas");
            const CanvasSize = Canvas.getComponent(UITransform).contentSize;

            //飘字节点
            toastNode = new Node();
            toastNode.layer = Layers.Enum.UI_2D;
            toastNode.parent = Canvas;
            //飘字背景半透明
            const g = toastNode.addComponent(Graphics);
            g.rect(CanvasSize.width / -2, -200 / 2, 720, 60);
            g.fillColor = new Color(0, 0, 0, 150);//填充
            g.stroke();
            g.fill();

            //飘字背文本
            const txtNode = new Node("txt");
            txtNode.layer = Layers.Enum.UI_2D;
            txtNode.parent = toastNode;
            txtNode.addComponent(Label);
            txtNode.setPosition(0, -70);
        }

        toastNode.getChildByName("txt").getComponent(Label).string = str;
        toastNode.setPosition(0, -200, 0);
        toastNode.setScale(1, 1, 1);

        console.log("toastNode", toastNode)

        tween(toastNode)
            .to(0.5, { position: new Vec3(0, 200, 0) })
            .delay(1)
            .to(0.2, { position: new Vec3(0, 300, 0), scale: new Vec3(0, 0, 0) })
            .call(() => {
                this.toastPools.push(toastNode);
            })
            .start()
    }
}

请求封装

import { normalURL } from '../service/serviceHelper'
import { getToken, setToken } from './auth'
import { isWeChatGame } from '../utils/platformUtil'

/**
 * 请求
 * @param uri
 * @param body
 */
const request: any = {}
request.post = (path: string, data: any = {}) => {
    return new Promise((resolve, reject) => {
        if (isWeChatGame) {
            wxPost(path, data, resolve, reject)
        } else {
            jsPost(path, data, resolve, reject)
        }

    })
}
request.get = (path: string, data: any = {}) => {
    return new Promise((resolve, reject) => {
        if (isWeChatGame) {
            wxGet(path, data, resolve, reject)
        } else {
            jsGet(path, data, resolve, reject)
        }
    })
}

/**
 * 微信-post、get请求
 */
const wx: any = window['wx'];
function wxPost (path, data, resolve, reject) {
    wx.request({
        header: getHeader(),
        url: formatPath(path),
        method: 'POST',
        data,
        success (response: any) {
            if (!validateResp(response)) {
                reject(response.data?.msg)
                throw new Error(response.data?.msg)
            }
            resolve(response.data)
        },
        fail (err: any) {
            wx.showToast({
                icon: "none",
                title: `服务响应失败, ${err}`
            })
            reject(err)
        },
        complete () {
        }
    })
}
function wxGet (path, data, resolve, reject) {
    wx.request({
        header: getHeader(),
        url: formatPath(path),
        method: 'GET',
        data,
        success (response: any) {
            if (!validateResp(response)) {
                reject(response.data?.msg)
                throw new Error(response.data?.msg)
            }
            resolve(response.data)
        },
        fail (err: any) {
            wx.showToast({
                icon: "none",
                title: `服务响应失败,${err}`
            })
            reject(err)
        },
        complete () {
        }
    })
}

/**
 * 原生-post、get请求
 */
function jsPost (path, data, resolve, reject) {
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function (event) {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            let resData = JSON.parse(xhr.responseText);
            let DreamKeyToken = xhr.getResponseHeader('DreamKeyToken');
            let response = { data: resData, header: { DreamKeyToken } };
            if (!validateResp(response)) {
                reject(response.data?.msg)
                throw new Error(response.data?.msg)
            }
            resolve(response.data)
        }
    };
    xhr.timeout = 60000;
    xhr.open('POST', path, true);
    xhr.setRequestHeader("Content-type", "application/json;charset=utf-8");
    xhr.send(JSON.stringify(data));
}
function jsGet (path, data, resolve, reject) {
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            let resData = JSON.parse(xhr.responseText);
            let DreamKeyToken = xhr.getResponseHeader('DreamKeyToken');
            let response = { data: resData, header: { DreamKeyToken } };
            if (!validateResp(response)) {
                reject(response.data?.msg)
                throw new Error(response.data?.msg)
            }
            resolve(response.data)
        }
    };
    xhr.timeout = 60000;
    // TODO: path后面添加?aaa=1&bbb=2
    const url = path;
    xhr.open('GET', url, true);
    xhr.send();
}

/**
 * 格式化请求路径
 * @param path
 */
const formatPath = (path: string) => {
    if (path.startsWith('http://') || path.startsWith('https://')) {
        return path
    } else {
        return normalURL + path
    }
}

/**
 * 设置请求头Header
 */
const getHeader = () => {
    const DreamKeyToken = getToken() || ''
    return { DreamKeyToken }
}

/**
 * 验证返回
 * @param resp
 */
const validateResp = (resp: any) => {
    if (!resp || !resp.data) {
        wx.showToast({ title: '请求失败, 返回数据异常', icon: 'none' })
        return false
    }
    if (resp.data.code === 403) {
        // TODO: token过期
        wx.reLaunch({ url: '/packages/home/pages/personal-center?inviteBtnShow=1' })
    } else if (resp.data.code === 401 && resp.data.msg === '[授权凭证]查询错误') {
        wx.navigateTo({ url: '/pages/login/index' })
    } else if (resp.data.code !== 200) {
        // 补丁
        if (resp.data.msg === '用户信息不存在') return true
        wx.showToast({ title: `${resp.data.msg}`, icon: 'none' })
        return false
    } else if (resp.data.code === 200 && resp.header.DreamKeyToken) {
        setToken(resp.header.DreamKeyToken)
    }

    return true
}

export default request;

适配分辨率

项目设置->适配宽高

多平台适配(见附件): https://forum.cocos.org/t/topic/91643

开放数据域

用于生成一个项目外的作用域(可以成为子域,项目本身称为主域),打包的时候 “勾选生成开放数据域” ,就会生成一个openDataContext的文件夹,可以在里面去写排行版;只有在开放数据域内才能访问wx.getFriendCloudStorage() 和 wx.getGroupCloudStorage()两个API

网上使用开放域开发排行版示例(见附件):
https://forum.cocos.org/t/topic/92773

cocos3官网文档(见附件): https://docs.cocos.com/creator/3.0/manual/zh/editor/publish/build-open-data-context.html?h=%E5%BC%80%E6%94%BE%E6%95%B0%E6%8D%AE

// 主域和开放数据域交互
主域 -> 开放数据域: wx.postMessage()
开放数据域: wx.onMessage()

SubContextView 需要在右侧添加组件选择 Miscellaneous->SubContextView

特效-贝塞尔跳跃

import { math, Vec2, Vec3 } from "cc";

export class JumpActionUtil {
    static jumpTo_v3(to: Vec3, height: number): Readonly<Vec3> {
        return { value: to, progress: this._jumpTo_progress(3, height) } as any as Vec3;
    }
    static jumpTo_v2(to: Vec3, height: number): Readonly<Vec2> {
        return { value: to, progress: this._jumpTo_progress(2, height) } as any as Vec2;
    }
    private static _jumpTo_progress(max: number, height: number): (from: number, to: number, cur: number, pcs: number) => number {
        let i = max;
        let heightSqrt = Math.sqrt(height);
        return (from: number, to: number, cur: number, pcs: number) => {

            // 使用序列耦合区分xyz轴: 1: x, 2: y, 3: z
            if (i >= max) i = 1;
            else i++;

            // let rsl = from + (to - from) * pcs; // lerp
            let rsl = math.lerp(from, to, pcs);

            if (i === 2) { // y轴的增量算法
                let du = Math.abs(1 - pcs * 2); // [0,1] > [1,0,1]
                rsl += height - Math.pow(heightSqrt * du, 2);
            }

            return rsl;
        };
    }
}
const jumpFunc = tween(this.node)
    .to(0.5, { worldPosition: JumpActionUtil.jumpTo_v3(wPos, 200) }, { easing: 'sineInOut' })
    .call(() => { console.log("[tween][end]"); })
    .repeat(1)
    .start();

超出隐藏

添加一个Mask组件,设定宽高,type设置为rect/xxx

其它参考链接

  1. 配置webstorm
  2. wx.d.zip文件
  3. 场景管理
  4. 排名列表
  5. 多平台分辨率适配
  6. 开放数据域-coco3说明
  7. 开放数据域开发好友排行版
  8. 开放数据域开发好友排行版2

文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录