查看原文
其他

用 Cocos Creator 制作平台跳跃游戏

kenshin COCOS 2022-06-10

前言


平台跳跃类游戏如《超级马里奥》《Celeste蔚蓝》等,非常考验玩家的操作和判断,有着非常本真的游戏乐趣。这类游戏乍一看,挺容易做的,但是要做好却不太容易。今天,我将使用 Cocos Creator v2.1.2 演示如何灵活快速地使用 Cocos Creator 来制作这类经典的横版平台跳跃类游戏,主要目的是帮助大家熟悉组件的用法,横版游戏实现方法很多,这里不做讨论!

为了方便大家阅读,蓝色文字为参考链接,统一放在文末,敬请留意!


开始游戏制作


游戏效果预览


美术资源来自互联网


游戏场景设计


参考《超级玛丽》的世界观,我们先在[节点管理器]构建我们的世界节点树,我们添加了摄像机,游戏背景层,世界根节点,地图节点,角色节点。



1.添加摄像机(Main Camera)


摄像机作为玩家观察游戏世界的窗口,Cocos Creator 默认会自动为场景分配一个[摄像机],我们无需手动添加。


2.添加世界根节点(World Root)


添加一个空节点,用于放置游戏内的物体节点。


(1)创建脚本 world.ts 并拖入节点属性面板,用于配置游戏世界参数,比如设置重力加速度 G 的值。


(2)创建脚本 lookat.ts 并拖入节点属性面板,根据 Player 节点的位置同步世界视角。



全局配置 world.ts 代码:

const {ccclass, property} = cc._decorator;
@ccclassexport default class CWorld extends cc.Component {
@property() WorldFallG: number = 0;
@property() WorldWalkA: number = 0;
static G: number = 0; static WalkA: number = 0; // LIFE-CYCLE CALLBACKS:
onLoad () { CWorld.G = this.WorldFallG; CWorld.WalkA = this.WorldWalkA; }
start () { // enable Collision System cc.director.getCollisionManager().enabled = true; cc.director.getCollisionManager().enabledDebugDraw = true; cc.director.getCollisionManager().enabledDrawBoundingBox = true; }
// update (dt) {}}

世界视角控制 lookat.ts 代码:

const { ccclass, property } = cc._decorator;
@ccclassexport default class NewClass extends cc.Component {
@property(cc.Node) target: cc.Node = null;
@property(cc.Node) map: cc.Node = null;
boundingBox: cc.Rect = null; screenMiddle: cc.Vec2 = null;
minX: number = 0; maxX: number = 0; minY: number = 0; maxY: number = 0;
isRun: boolean = true;
// LIFE-CYCLE CALLBACKS:
onLoad() { this.boundingBox = new cc.Rect(0, 0, this.map.width, this.map.height); let winsize = cc.winSize; this.screenMiddle = new cc.Vec2(winsize.width / 2, winsize.height / 2); this.minX = -(this.boundingBox.xMax - winsize.width); this.maxX = this.boundingBox.xMin; this.minY = -(this.boundingBox.yMax - winsize.height); this.maxY = this.boundingBox.yMin; }
update() { if (!this.isRun) return; let pos = this.node.convertToWorldSpaceAR(cc.Vec2.ZERO); let targertPos = this.target.convertToWorldSpaceAR(cc.Vec2.ZERO); let dis = pos.sub(targertPos); let dest = this.screenMiddle.add(dis); dest.x = cc.misc.clampf(dest.x, this.minX, this.maxX); dest.y = this.minY; this.node.position = this.node.parent.convertToNodeSpaceAR(dest); }}


3.添加角色(Player)


我们控制的游戏主角节点,作为游戏世界视角的焦点。


4.添加地图(Tiled Map)


[Tiled](支持 TiledMap v1.0) 制作好的地图资源 level01,拖入到世界节点下面,自动会生成地图节点,这时候可以查看展开的 TiledMap 地图层级。



根据 TiledMap 设计的物体类型,需要对物体进行实例化,我们创建 waorldmap.ts 脚本来完成这个工作,下图是我们已配置的地图层级和物体的 Prefab 资源。

 


地图对象的实例化,分为几步:


  • 实例化类型对应的 Prefab 资源

  • 设置碰撞组

  • 设置物体大小

  • 添加碰撞组件

  • 设置物体的类型标签


在 waorldmap.ts 中,水对象的实例化过程如下:

// get waters layer and traverse all water objects.var waters = this.worldMap.getObjectGroup(this.waterLayerName);for (var i = 1; i < 8; i++) { var waterName = 'water' + i; var waterBlock = waters.getObject(waterName); var waterNode = cc.instantiate(this.ColliderPreName);
// set group name for Collider System. waterNode.group = 'water'; // set size waterNode.width = waterBlock.width; waterNode.height = waterBlock.height; waterNode.x = waterBlock.x; waterNode.y = waterBlock.y - waterBlock.height; // add collider component. waterNode.addComponent(cc.BoxCollider); waterNode.getComponent(cc.BoxCollider).size = cc.size(waterNode.width, waterNode.height); waterNode.getComponent(cc.BoxCollider).offset = new cc.Vec2(waterNode.width / 2, -waterNode.height / 2);
// set tag for check when collision. waterNode.getComponent(cc.BoxCollider).tag = 6; this.node.addChild(waterNode);}


5.添加碰撞规则


世界物体包含了角色,地面,方块,金币,甲壳虫,水,蘑菇,创建碰撞组和碰撞组来约束物体彼此之间碰撞规则。



6.游戏物体设计


游戏物体会根据本身的特性去进行分类做成预制体,预制体根据物体特性,添加下面内容:


  • 碰撞特性

  • 动作表现

  • 音效表现

  • 行为控制脚本


7.物体 Prefab 制作


例如下面是甲壳虫的资源目录,包含了甲壳虫动画文件 beetle_anim,预制体资源  beetle_node,皮肤文件 beetle_skin,行为控制脚本 beetle_script。



给甲壳虫预制体添加动作组件



给甲壳虫预制体添加碰撞组件



给甲壳虫预制体添加脚本组件,设置了移动速度,缩放系数,音效等属性



下面是制作甲壳虫脚本,用于碰撞检测和行为控制:

const { ccclass, property } = cc._decorator;
@ccclassexport default class enemy extends cc.Component { @property() speed: cc.Vec2 = new cc.Vec2(0, 0);
@property scaleX: number = 1;
@property canMove: boolean = true;
@property({type: cc.AudioClip}) dieAudio: cc.AudioClip = null;
anim: cc.Animation = null;
// LIFE-CYCLE CALLBACKS:
onLoad() { this.node.scaleX = 1; this.anim = this.getComponent(cc.Animation); }
start() {
}
// onCollisionEnter overrated onCollisionEnter(other, self) { if (other.tag == 5) { this.turn(); this.speed.x = -this.speed.x; }
var otherAabb = other.world.aabb; var otherPreAabb = other.world.preAabb.clone();
var selfAabb = self.world.aabb; var selfPreAabb = self.world.preAabb.clone(); selfPreAabb.y = selfAabb.y; otherPreAabb.y = otherAabb.y;
if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) { if (selfPreAabb.yMax < otherPreAabb.yMax && other.node.group == 'player') { this.todie(); } } }
todie() { cc.audioEngine.play(this.dieAudio, false, 1); this.anim.play('beetled'); this.canMove = false; this.node.height = this.node.height * 0.3; this.node.runAction(cc.fadeOut(.5)); this.scheduleOnce(function () { this.node.removeFromParent(); }, 0.5); }
update(dt) { if (this.canMove) { this.node.x -= this.speed.x * dt; } }
turn() { this.node.scaleX = -this.node.scaleX; }}


8.角色逻辑设计


作为游戏的核心,角色的行为设计是比较复杂,主要分为控制事件和碰撞事件两部分。


(1)控制事件处理

onLoad() { cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);}
onDestroy() { cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);}
onKeyDown(event) { switch (event.keyCode) { case cc.macro.KEY.a: case cc.macro.KEY.left: this.playerLeft(); break; case cc.macro.KEY.d: case cc.macro.KEY.right: this.playerRight(); break; case cc.macro.KEY.w: case cc.macro.KEY.up: this.playerUp(); break; case cc.macro.KEY.down: case cc.macro.KEY.s: this.playerDown(); break; }}
onKeyUp(event) { switch (event.keyCode) {
case cc.macro.KEY.a: case cc.macro.KEY.left: case cc.macro.KEY.d: case cc.macro.KEY.right: this.noLRControlPlayer(); break; case cc.macro.KEY.up: case cc.macro.KEY.w: this.noUpControlPlayer(); break; case cc.macro.KEY.s: case cc.macro.KEY.down: this.noDownControlPlayer(); break; }}


(2)碰撞事件处理


物体对象在实例化时候分配了物体类型标签,下面代码根据标签来指派不同的碰撞逻辑。

onCollisionEnter(other, self) { if (this.touchingNumber == 0) { if (this.buttonIsPressed) this.player_walk(); else this.player_idle(); } switch (other.tag) { case 1://coin.tag = 1 this.collisionCoinEnter(other, self); break; case 2://bonusblock6.tag = 2 case 3://breakableWall = 3 case 7: //bonusblock6withMushroom.tag = 7 this.collisionBonusWallEnter(other, self); break; case 4://enemy.tag = 4 this.collisionEnemyEnter(other, self); break; case 5://platform.tag = 5 this.collisionPlatformEnter(other, self); break; case 6://water.tag = 6 this.collisionWaterEnter(other, self); break; case 8://mushroom.tag = 8 this.collisionMushroomEnter(other, self); break; } }

角色与地面的碰撞处理:

collisionPlatformEnter(other, self) { this.touchingNumber++; this.jumpCount = 0; var otherAabb = other.world.aabb; var otherPreAabb = other.world.preAabb.clone(); var selfAabb = self.world.aabb; var selfPreAabb = self.world.preAabb.clone(); selfPreAabb.x = selfAabb.x; otherPreAabb.x = otherAabb.x;
if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) {
if (this._speed.x < 0 && (selfPreAabb.xMax > otherPreAabb.xMax)) { this.node.x += Math.floor(Math.abs(otherAabb.xMax - selfAabb.xMin)); this.collisionX = -1; } else if (this._speed.x > 0 && (selfPreAabb.xMin < otherPreAabb.xMin)) { this.node.x -= Math.floor(Math.abs(otherAabb.xMin - selfAabb.xMax)); this.collisionX = 1; } else if (this._speed.x == 0 && (selfPreAabb.xMax == otherPreAabb.xMin)) { this.isFallDown = true; }
this._speed.x = 0; other.touchingX = true; return; } selfPreAabb.y = selfAabb.y; otherPreAabb.y = otherAabb.y;
if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) { if (this._speed.y < 0 && (selfPreAabb.yMax > otherPreAabb.yMax)) { this.node.y = otherPreAabb.yMax - this.node.parent.y; this.isJumping = false; this.collisionY = -1; } else if (this._speed.y > 0 && (selfPreAabb.yMin < otherPreAabb.yMin)) { cc.audioEngine.play(this.hit_block_Audio, false, 1); this.node.y = otherPreAabb.yMin - selfPreAabb.height - this.node.parent.y; this.collisionY = 1; }
this._speed.y = 0; other.touchingY = true; } this.isWallCollisionCount++;}

角色与敌人的碰撞:

collisionEnemyEnter(other, self) { // 1st step // get pre aabb, go back before collision var otherAabb = other.world.aabb; var otherPreAabb = other.world.preAabb.clone();
var selfAabb = self.world.aabb; var selfPreAabb = self.world.preAabb.clone();
// 2nd step // forward x-axis, check whether collision on x-axis selfPreAabb.x = selfAabb.x; otherPreAabb.x = otherAabb.x; if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) { if (this._life == 2) { cc.audioEngine.play(this.player_decrease_Audio, false, 1); var actionBy = cc.scaleBy(1, 3 / 5); this.node.runAction(actionBy); this._life--; } else if (this._life == 1) { this.anim.play("player_die"); this.rabbitDieJump(); this.OverNodeLoad(); return; }
if (this._speed.x < 0 && (selfPreAabb.xMax > otherPreAabb.xMax)) { this.node.x += Math.floor(Math.abs(otherAabb.xMax - selfAabb.xMin)); this.collisionX = -1; } else if (this._speed.x > 0 && (selfPreAabb.xMin < otherPreAabb.xMin)) { this.node.x -= Math.floor(Math.abs(otherAabb.xMin - selfAabb.xMax)); this.collisionX = 1; }
this._speed.x = 0; other.touchingX = true; return; }
// 3rd step // forward y-axis, check whether collision on y-axis selfPreAabb.y = selfAabb.y; otherPreAabb.y = otherAabb.y;
if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) { if (this._speed.y < 0 && (selfPreAabb.yMax > otherPreAabb.yMax)) { this.rabbitJump(); return; } if (this._speed.y > 0 && (selfPreAabb.yMax < otherPreAabb.yMax)) { if (this._life == 2) { var actionBy = cc.scaleBy(1, 3 / 5); this.node.runAction(actionBy); this._life--; } else if (this._life == 1) { this.anim.play("player_die"); this.rabbitDieJump(); this.OverNodeLoad(); return; } } this._speed.y = 0; other.touchingY = true; } this.isWallCollisionCount++;}


注意


本教程旨在讲解如何使用 Cocos Creator 编辑器来设计一款横版平台跳跃闯关类游戏,运用 Creator 的组件化思想,减少代码的使用,提供开发效率。所涉及资源来自网络,请勿用于商业目的。


源码下载:

https://github.com/xianyinchen/creator_teach


在学习和使用 Cocos Creator 过程中有什么问题和建议,欢迎移步至 Cocos 中文社区进行交流!


参考链接

复制链接至浏览器中即可打开


Cocos 中文社区:

https://forum.cocos.com/t/creator/47112


Cocos Creator 下载:

https://www.cocos.com/creator


TileMap

https://docs.cocos.com/creator/manual/en/asset-workflow/tiledmap.html


Prefab

https://docs.cocos.com/creator/manual/en/asset-workflow/prefab.html


Keyboard Event

https://docs.cocos.com/creator/manual/en/scripting/player-controls.html#keyboard-events


Atlas

https://docs.cocos.com/creator/manual/en/asset-workflow/atlas.html


节点管理

https://docs.cocos.com/creator/manual/en/content-workflow/node-tree.html


摄像机

https://docs.cocos.com/creator/manual/en/render/camera.html


Tiled

https://www.mapeditor.org/



如果您在使用 Cocos Creator 的过程中,获得了独到的开发心得、见解或是方法,并且乐于分享出来,帮助更多开发者解决技术问题,加速游戏开发效率,期待您与我们联系!




创意小游戏橙皮书发布

Cocos技术派|3D小游戏《快上车》技术分享

用 Cocos Creator 快速制作打地鼠游戏

1周开发,7留35%,小游戏《成语消消对战》团队专访

Cocos Service 全面解析

我的小游戏开发之路|腾讯TGideas周桂华

创意小游戏《荒野日记》Cocos专访:游戏如何讲故事?

5G 云游戏亮相 Chinajoy,大作一键秒玩

Cocos Creator 接入微信小游戏引擎插件指引

Cocos海外开发者专访:遗憾的是没早点开始做游戏

Cocos Creator 重力球游戏制作教程


我就知道你“在看”

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存