前言
鄙人凭借过人的手速在毕设选题大战中抢到了一个坦克大战的毕设项目,那么本文就来讲解如何初步实现这个案例。那么在俯视坦克-摄像机跟随玩家移动文章中,我提到了一个俯视的坦克大战的小功能-摄像机跟随玩家的实现方式。那么本文就接着上文进行深入,以及修改上文中一部分有误(不合适的观点/实现方式)。
1:上文完善
在上文中为了限制主角移动到场景外部,而给他写了一个area()的方法,在现在看来这种方法是非常错误的实现方式。在本文中我们要在Tiled软件中新建多个不同的图层(按项目需求)(在本实例中我新建了bg_wall“最外层的图层”,wall“地图中的树”,backGround“地图图层”),在这里我将地图中的树拿出来分一个组的主要原因就是可能会添加玩家子弹打掉树木的功能(不需要可只设两个图层)。
将tiledMap组件挂载在场景中,然后在脚本中拿到tiledMap中的不同图层,然后给它加上刚体组件。
start() {
this.addCollider('wall','wall')
this.addCollider('bg_wall','bg_wall')
},
/**
*
* @param {*} oldObj tiledMap中的对象层名
* @param {*} newObj cocos引擎中的分组名
*/
addCollider(oldObj,newObj){
let tiledSize = this.tiledMap.getTileSize();
let layer = this.tiledMap.getLayer(oldObj);
let layerSize = layer.getLayerSize();
for (let i = 0; i < layerSize.width; i++) {
for (let j = 0; j < layerSize.height; j++) {
let tiled = layer.getTiledTileAt(i, j, true);
if (tiled.gid != 0) {
tiled.node.group = newObj;
let body = tiled.node.addComponent(cc.RigidBody);
body.type = cc.RigidBodyType.Static;
let collider = tiled.node.addComponent(cc.PhysicsBoxCollider);
collider.offset = cc.v2(tiledSize.width / 2, tiledSize.height / 2);
collider.size = tiledSize;
collider.apply();
}
}
}
},
然后在场景中,可以看见角色不能超出屏幕,也不像以前那样会在屏幕中产生抖动的效果
2:引擎内分组编辑,以及碰撞分组
这里分玩家,玩家子弹,敌人,敌人子弹,场景中的碰撞物体(树,阻挡物等),还有一个金币,技能碰撞体。
3:敌人生成,
在本案例中,我是在玩家角色为中心的固定区域来生成敌人。以及在tiledMap中随机生成敌人,并通过脚本给两者都加上刚体组件。
start() {
for (let i = 0; i < 5; i++) {
// this.mathPosition();
let a = this.mathPosition();
// cc.log(a);
this.createEnemy(a[0], a[1]);
}
for (let m = 0; m < 5; m++) {
let n = this.mathPos()
this.createEnemy(n[0], n[1]);
}
},
localConvertWorldPointAR(node) {
if (node) {
return node.convertToWorldSpaceAR(cc.v2(0, 0));
}
return null;
},
worldConvertLocalPoint(node, worldPoint) {
if (node) {
return node.convertToNodeSpaceAR(worldPoint);
}
return null;
},
mathPosition() {//玩家附近生成敌人
let playerPos = this.localConvertWorldPointAR(this.player);
let pos = this.worldConvertLocalPoint(this.tiledMap.node, playerPos);
let winWidth = cc.winSize.width;
let winHeight = cc.winSize.height;
let x = pos.x - winWidth / 2 - 1000 + Math.random() * (winWidth / 2 + 1000) * 2;
let y = pos.y - winHeight / 2 - 800 + Math.random() * (winHeight / 2 + 800) * 2;
let arr = [x, y];
return arr;
},
mathPos() {//全地图生成敌人
let x = Math.random() * this.tiledMap.node.width;
let y = Math.random() * this.tiledMap.node.height;
let arr = [x, y]
return arr;
},
createEnemy(x, y) {
let enemy = cc.instantiate(this.enemyPrefab);
let anim = enemy.getComponent(cc.Animation);
enemy.setPosition(x, y)
anim.play();
this.enemyNode.addChild(enemy);
let enemyScripts = enemy.getComponent('enemy')
/**脚本动态添加刚体属性,注意这里的位置**/
let body = enemy.addComponent(cc.RigidBody);
body.enabledContactListener = true;
body.bullet = true;
body.gravityScale = 0;
body.fixedRotation = true;
body.type = cc.RigidBodyType.Dynamic;
let collider = enemy.addComponent(cc.PhysicsBoxCollider);
collider.apply();
enemyScripts.gameInit();
},
3:玩家子弹射击(敌人的子弹射击也能使用这个方法,但要注意要设置不同的状态,否则敌人就会一直处于射击状态)(变量fireTime(开火时间间隔),可以稍微设置长一点,当获取到技能的时候将时间暂时缩短)
touchStart() {//fireBtn按下
this.gameState = 1;
this.shootFire.active = true;
},
touchEnd() {//fireBtn抬起
this.gameState = 0;
this.shootFire.active = false;
},
update(dt) {
if (this.gameState == 1) {//判断是fire按钮按下
if (this.curretTime > this.fireTime) {
let bullet = cc.instantiate(this.bullet);
//坐标系变化(子弹发射)
let shootPos = this.localConvertWorldPointAR(this.shootFire);
let playerNode = this.localConvertWorldPointAR(this.player);
let bulletMove = cc.v2((shootPos.x - playerNode.x) * bulletSpeed, (shootPos.y - playerNode.y) * bulletSpeed);
bullet.getComponent(cc.RigidBody).linearVelocity = bulletMove;
bullet.parent = this.shootNode;
this.curretTime = 0;
}
this.curretTime++;
}
},
在敌人射击的时候可以用这个定时器以及一个三元运算符来更新this.gameState变量的值(三元运算符的使用方法在定时器中已简单注释)
bulletShoot() {
this.schedule(function () {
Math.random() > 0.5 ? this.gameState = 1 : this.gameState = 0;
//表达式1 ? 方法1:方法2//当表达式1成立则执行方法1,表达式1不成立则执行方法2
}, 1);
},
4:子弹碰撞(在本案例中,有多个碰撞,分别为玩家子弹碰到地图中wall层->玩家子弹销毁,玩家子弹碰到敌人->玩家子弹销毁,敌机生命-1,玩家子弹与敌人子弹碰撞->两碰撞体均销毁,敌人子弹碰到地图wall层->敌人子弹销毁,敌人子弹碰到玩家->敌人子弹销毁,玩家生命-1)。这里我主要展示玩家子弹的碰撞脚本。
onBeginContact: function (contact, selfCollider, otherCollider) {
let anim = selfCollider.node.getComponent(cc.Animation);
if (otherCollider.node.group == 'wall'||otherCollider.node.group == 'bg_wall') {//子弹和墙壁碰撞
// cc.log('子弹销毁')
this.bulletDestroy(anim, selfCollider);
} else if (otherCollider.node.group == "enemy") {//敌机碰撞
let hp = otherCollider.node.getComponent('enemy').hp;
hp--;
if (hp == 0) {//敌机生命归零
cc.audioEngine.play(this.destroyAudio, false, 1);
// let enemyAnim = otherCollider.node.getComponent(cc.Animation);
//新生成一个enemy在合适的位置
let gameScripts = cc.find("Canvas/BG").getComponent('game');
let a = gameScripts.mathPosition();
gameScripts.createEnemy(a[0], a[1]);//挂载的game脚本生成一个新敌人
gameScripts.createGold(otherCollider.node.x, otherCollider.node.y);//金币生成
let m = Math.random();//调整生成几率
if (m < 0.2) {
gameScripts.createSkill(otherCollider.node.x, otherCollider.node.y, 1);//生成技能物品(子弹加速)
} else if (m > 0.9) {
gameScripts.createSkill(otherCollider.node.x, otherCollider.node.y, 2);//生成技能物品(恢复生命)
}
otherCollider.node.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 0);
// this.bulletDestroy(enemyAnim, otherCollider);//敌机销毁
otherCollider.node.destroy();//直接销毁敌机
levelEnemyCount++;
}
otherCollider.node.getComponent('enemy').hp = hp;//重新赋值给enemy
this.bulletDestroy(anim, selfCollider);
// cc.log(hp);
this.level();
}
},
/**
* 在enemyBullet脚本中也有同样方法,也可以调用该脚本方法
* @param {*} anim 动画组件
* @param {*} Collider 碰撞体
*/
bulletDestroy(anim, Collider) {//子弹销毁方法
anim.play();
anim.on('finished', function () {
Collider.node.destroy();
}, this);
},
5:当玩家进入地图中固定炮塔的射击范围时,地图中固定炮塔的炮弹指向玩家发射(按一定时间间隔)
效果展示
const fireTime = 60;
const bulletSpeed = 2;
cc.Class({
extends: cc.Component,
properties: {
},
// LIFE-CYCLE CALLBACKS:
onLoad() {
//拿到节点
this.firePos = this.node.getChildByName('firePos');
this.gameScript = cc.find("Canvas/BG").getComponent('game');//拿到BG节点上面挂载的game脚本
this.currentTime = 0;
},
start() {
this.player = cc.find('Canvas/BG/tank')
this.tiledMap = cc.find('Canvas/BG/TiledMap1')
this.bulletNode = cc.find('Canvas/BG/TiledMap1/gameItem/bulletNode');
//固定的炮台
this.pos = this.gameScript.worldConvertLocalPoint(this.tiledMap, this.gameScript.localConvertWorldPointAR(this.firePos))
// cc.log(this.pos);
},
update(dt) {
//玩家控制的坦克
let playerPos = this.gameScript.worldConvertLocalPoint(this.tiledMap, this.gameScript.localConvertWorldPointAR(this.player));
//判断在发射区内
if (Math.abs(playerPos.x - this.pos.x) < 400 && Math.abs(playerPos.y - this.pos.y) < 400) {
var r = Math.atan2(playerPos.y - this.pos.y, playerPos.x - this.pos.x);
var degree = r * 180 / Math.PI;
degree = 360 - degree;
degree = degree - 90;
this.firePos.angle = -degree;
if (this.currentTime > fireTime) {//开火
let missilePrefab = this.gameScript.missile;
let missile = cc.instantiate(missilePrefab);
this.firePos.addChild(missile);
missile.parent = this.bulletNode
missile.getComponent(cc.RigidBody).linearVelocity = cc.v2((playerPos.x - this.pos.x)*bulletSpeed, (playerPos.y - this.pos.y)*bulletSpeed);
this.currentTime = 0
}
this.currentTime++;
}
},
});
总结:
那么以上就是这个案例的主要实现