CocosCreator旧活新整-合成大粽子

举报
芝麻粒儿 发表于 2022/06/26 15:07:20 2022/06/26
【摘要】 👉关于作者众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)欢迎关注公众号【空名先生】获取更多资源和交流! 👉前提【为了便于快速的理解,小空特意能用中文的用了中...

👉关于作者

众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!

专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)

欢迎关注公众号【空名先生】获取更多资源和交流!

👉前提

【为了便于快速的理解,小空特意能用中文的用了中文,方便广大群众】

【友情提示:还是尽量使用英文哦】

合成大粽子.gif

👉实践过程

😜启动页面

首先我们创建启动页面scene_start,然后我们添加背景精灵(为单色-只修改其中颜色属性即可)以及两个按钮(开始游戏和退出游戏)

将两个按钮设置为NormalSprite、PressedSprite等不同的按钮状态,接着给按钮子节点的Label文字设置成对应的汉字(字体样式设置为白色加粗及倾斜)

image.png

接着拖动到对应的位置上:

image.png

再来接着增加一个分数显示框,这个分数变化肯定是需要我们代码来控制显示的,所以先创建个MaxScore脚本挂载在上面,后续我们再说脚本。

image.png

😜可爱的动画

上面基本的首页就好啦,然后我们增加点生动的动画吧,让整个界面看起来更加有优势。

  • 1.创建一个空节点挂载Animation组件,在assets文件夹中创建Anim文件
  • 2.将Anim文件指向Animation的Clips
  • 3.然后编辑Anim文件

image.png

直接在【背景动画】节点下增加多个Sprite组件,然后给他们设置不同的尺寸大小以及指定不同的图片素材。

image.png

然后进入动画编辑器页面,为了简单我们就直接给最外层的节点增加动画,我们为他增加个缩放动画(1->0.8->1),缩放尺寸从1到0.8再到1,动画整体持续时间为1s(秒),PingPong形式的动画(乒乓形式来回循环)。

image.png

好啦,首页就基本上有了形状啦。

😜核心脚本

然后我们创建两个脚本(ScriptStart和ScriptMaxScore)

image.png

ScriptStart脚本包含两个方法,分别是加载下一个场景以及结束游戏,上面的两个按钮调用,然后在onLoad方法中读取分数信息,如果有则读取对应的如果没有则设置为0(防止ScriptMaxScore脚本读取值为空)

protected onLoad() {
    console.log("ScriptStart的onLoad");
    //数据加密的考虑只能是用服务器最好
    if (localStorage.getItem(ScriptStatic.MaxScore) == null) {
        console.log("没有最高分")
        //为什么没有的时候要在这设置呢
        localStorage.setItem(ScriptStatic.MaxScore, "0");
    } else {
        console.log("有最高分", localStorage.getItem(ScriptStatic.MaxScore))
    }
}
 
//开始游戏-加载下一个场景
GameStart() {
    director.loadScene("scene_game");
}

//结束游戏
GameExit() {
    game.end;
}

image.png

附注:脚本的生命周期和Unity相似,都是走完所有脚本的onLoad再往下走所有脚本的onEnable接着是所有脚本的start,以此类推。他们相同的生命周期函数执行顺序又受层级管理器中的顺序影响。

image.png

分数我们使用艺术字显示,设置艺术字有两种方式:

一种是直接生成对应的位图字体文件

一种是精灵为一个预制体代码创建多个给每个设置对应的图片即可

我们使用的是第一种方法。

创建文本位图字体(fnt和png两个文件)-不知道怎么创建的可以私信我。

然后将给字体赋值给Label组件的Font属性,记得修改CacheMode,对了顺便看看Layer是否正确,小空在这被坑了一次(一直不显示但是感觉哪都没错,原来是层级低被背景遮盖住了。)

image.png

image.png

接着我们给分数节点弄上脚本ScriptMaxScore

start() {
    console.log("ScriptMaxScore的start");
    //读取保存的分数数据,这个一是在Start的onLoad中设置 一是在游戏结束的时候设置
    let score = localStorage.getItem(ScriptStatic.MaxScore)
    //设置分数艺术字
    let LabelScore = this.node.getComponent(Label);
    if (LabelScore != null) {
        LabelScore.string = score + "".toString();
    } else {
        console.log("获取文本节点为空")
    }
}

😜游戏主界面

经过前面两节的学习,到此开始页面我们就搞定了,接着就是进入开始游戏内容啦。

我们先来分析下具体玩法:

  • 1.页面右上角可以提前预览下一个小球内容样式
  • 2.在当前小球下落未完成之前不得再次释放新的小球,防止过快
  • 3.左侧右侧底侧都不允许超界,有格挡物(碰撞肩擦)
  • 4.小球具有自由落体属性以及适当的回弹效果
  • 5.在小球合并过程中播放适当的音频以及合并效果
  • 6.当超过上侧边界的时候要有输的判定和分数的显示
  • 7.可以重新开始游戏和返回首页

所以我们先给其增加背景,左墙,右墙,以及底部地面,位置刚刚好包裹住背面。

为了里面的物体掉落后不出界,需要给墙体增加碰撞器(BoxCollider2D)以及刚体组件(RigidBody2D)

image.png

😜实现返回和重玩

在进行场景变换前,他俩都需要判断下当前所得分是不是大于历史最高分,大于的话则将当前的分保存替换,否则的话就可以不用管。然后利用【director.loadScene】加载场景即可。

对了,别忘记给场景中的按钮指定点击事件。

//重玩游戏
GameRePlay() {
    //也要判断看是否保存数据
    let MaxScore = localStorage.getItem(ScriptStatic.MaxScore);
    if (MaxScore != null) {
        //当前分数和保存的分数做比较如果大 则保存 如果不大不保存
        //parseInt  第二个参数代表的是进制
        if (parseInt(MaxScore.toString(), 10) < ScriptStatic.CurrentScore) {
            localStorage.setItem(ScriptStatic.MaxScore, ScriptStatic.CurrentScore + "");
        }
    }
    //上面先将分数保存
    ScriptStatic.CurrentScore=0;
    director.loadScene("scene_game");
}


//返回上一场景
ClickBack() {
    let MaxScore = localStorage.getItem(ScriptStatic.MaxScore);
    if (MaxScore != null) {
        //当前分数和保存的分数做比较如果大 则保存 如果不大不保存
        //parseInt  第二个参数代表的是进制
        if (parseInt(MaxScore.toString(), 10) < ScriptStatic.CurrentScore) {
            localStorage.setItem(ScriptStatic.MaxScore, ScriptStatic.CurrentScore + "");
        }
    }
    director.loadScene("scene_start");
}

image.png

😜小球预制体

要知道每次都要创建一个小球,且右上角有预览,所以我们需要一个预制体,后续通过实例化预制体来实现,并且该预制体初始化的时候能够自动随机不同的小球。

右键创建一个2D的Sprite,添加【CirleCollider2D】和【RigidBody2D】组件,刚体组件默认即可,碰撞组件需要修改下摩擦系数【Friction】和弹性系数【Restitution】,一个【0.2】一个【0.3】吧。【Radius】半径需要注意哦,不同的瓜半径不同,再加上碰撞的时候回来贴合的很合适,这个半径就需要和图片很重合最好,不能大也不能小。

对了我们顺便增加个弹性系数吧,这样有回弹效果就更Q啦。

然后增加个脚本,我们想想改脚本应该有啥功能

  • 1.自动变化不同的球体
  • 2.更改当前球体的Collider半径,这样能实现良好的碰撞检测
  • 3.西瓜的碰撞事件以及碰撞后合并
  • 4.死亡线的检测判定
//获取到精灵
let thisSprite = this.node.getComponent(Sprite);
if (thisSprite != null) {
    thisSprite.spriteFrame = this.spriteFrames[index];
} else {
    console.log("changeBall的thisSprite为空")
}
//动态改变创建的预制体的半径
let PrefabCollider = this.node.getComponent(CircleCollider2D);
if (PrefabCollider != null) {
    if (index == 0) {
        PrefabCollider.radius = (54 / 2 -1);
    }
             //……省略部分逻辑
    ScriptStatic.CurrentBallRadius=PrefabCollider.radius;
} else {
    console.log("changeBall的PrefabCollider为空")
}

碰撞的检测

我们使用的是2D的且直接针对Collider注册的监听

TempCollider.on(Contact2DType.BEGIN_CONTACT, this.MethodColliderEnter, this);

【MethodColliderEnter】方法是我们的逻辑处理,里面包含三个参数【selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null】,官方文档有介绍,在这小空就不自嗨啦。逻辑详情请参考源码,注释很详细。

注意哦:

要想碰撞事件生效,必须在物体的刚体组件中打开监听【EnabledContactListener】, 离谱。

在碰撞事件中进行销毁或者更新某个组件使用【setTimeout】来实现下一帧修改,因为当前帧修改会报异常的。

碰撞检测里面的逻辑函数我们用到了标签【TAG】,并且利用【TAG】做了某些操作的话,所以就要特别注意了,在场景中有些节点的【TAG】默认就是0,很容易造成误操作对象。所以我们修改场景中【背景_左墙】和【背景_右墙】以及【背景_地面】的碰撞组件【TAG】为100。

接着我们的主脚本就用到他了。

😜游戏主脚本

该脚本挂载在了背景黄图上,在开始和结束我们需要注册和解除注册点击事件。点击事件里面逻辑:

  • 1.初始化预制体
  • 2.设置位置
  • 3.随机生成球体
  • 4.右上角预览生成

具体详情可看源码中的【onClickStart】方法

注意:

UI上的脚本要想响应点击事件,必须挂载的物体上有【UITransform】,且在Canvas下

😜更新分数

【分数】节点上挂载【ScriptScore】脚本,脚本内直接开一个每秒更新一次的定时,取之前保存的分数变量即可。

但这并不满足我们,我们的显示一定要优雅,没错,优雅。

image.png

所以我们就需要用到艺术数字,这个开头我们的启动页面最大分数是先沟通的技术。

找到我们的【number.fnt】文件赋值给【Label】的【Font】,然后在代码中增加

this.schedule(()=> {
    //设置分数艺术字
    let LabelScore = this.node.getComponent(Label);
    if (LabelScore != null) {
        LabelScore.string = ScriptStatic.CurrentScore + "".toString();
    } else {
        console.log("获取文本节点为空")
    }
}, 1);

😜死亡判定

当触碰到顶部线条的时候触发。所以UI要有碰撞检测,并且需要根据事件回调进行响应处理。

我们添加【RigidBody2D】和【BoxCollider2D】,注意:要想系统能调用回调,需要激活【EnabledContactListener】,之后在脚本的【onLoad】中注册事件。代码如下:

protected onLoad() {
    let TempCollider = this.node.getComponent(BoxCollider2D);
    if (TempCollider != null) {
        console.log("死亡线注册了碰撞事件")
        TempCollider.on(Contact2DType.BEGIN_CONTACT, this.MethodColliderEnterDeath, this);
    } else {
        console.log("死亡线Collider为空")
    }
}

start() {
    // [3]
}

/**
 * 注意了  要想以上来不响应需要RigiBody2D的 Type 为静态类型 不能是默认的
 * @param selfCollider
 * @param otherCollider
 * @param contact
 * @constructor
 * @private
 */
private MethodColliderEnterDeath(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
    console.log("执行了死亡线的碰撞")
    //判断是否为最大值保存分数
    let MaxScore = localStorage.getItem(ScriptStatic.MaxScore);
    if (MaxScore != null) {
        //利用Number() 将字符串转为 number 可以对比大小
        if (Number(MaxScore) < ScriptStatic.CurrentScore) {
            localStorage.setItem(ScriptStatic.MaxScore, ScriptStatic.CurrentScore + "");
        }
    }
    //展示死亡界面
    if (this.NodeDeath != null) {
        this.NodeDeath.setPosition(0, 0, 0);
    } else {
        console.log("死亡界面为空")
    }
    //禁止背景再有点击响应
    if (this.NodeBg != null) {
        let TempScript = this.NodeBg.getComponent(ScriptGame);
        if (TempScript != null) {
            console.log("取消了点击事件");
            // this.NodeBg.off(Node.EventType.TOUCH_START, TempScript.onClickStart, this);
            TempScript.IsDeath=false;
        } else {
            console.log("获取的脚本为空")
        }
    } else {
        console.log("死亡界面为空")
    }
}

至此,我们的整个开发流程结束。

📢作者:小空和小芝中的小空

📢转载说明-务必注明来源:芝麻粒儿 的个人主页 - 专栏 - 掘金 (juejin.cn)

📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。