【全新玩法3D肉鸽】基础教程

概述

在肉鸽游戏火热的时候,一直在想如何给用户更多的操作,让玩家通过自己的操作策略体验更多的趣味性。

最开始想到的就是让用户能够主动释放技能,但是主动释放技能似乎对于目前市面上那些需要高频触发技能的肉鸽玩法并不合适,而且仅仅支持主动释放技能,操作策略上还是有些欠缺。

经过深思熟虑后,我们决定采用3D形式,并结合屏幕点击、双击、滑动以及长按等多种交互方式来打造一款以普通攻击为核心机制的游戏。同时,在游戏中加入可由玩家主动控制释放的技能元素。这样的设计不仅能够提升操作过程中的趣味性,还能从视觉上给玩家带来更加沉浸式的体验。
由此,做了一款这样的游戏。
宠物2 00_00_00-00_00_30 翻滚加技能 00_00_00-00_00_30 攻击 00_00_00-00_00_30 技能2 00_00_00-00_00_30 长按 00_00_00-00_00_30 抓取 00_00_00-00_00_30

资源已经上架商城
https://store.cocos.com/app/detail/7163

想体验的可以扫描此二维码
gh_6120d207ca63_258

本次将教大家如何通过操作手势实现人物控制,以及如何实现人物连击

单击移动

  1. 搭建场景,在场景中加入地面以及玩家。
  2. 在地面和玩家节点上分别加入PlaneCollider和CapsuleCollider
  3. 接下来我们需要做的就是点击屏幕时,判断屏幕点对应的地面坐标点在人物的哪个方向即可,由于人物在y轴上没有移动,所以只要计算当前点击点xz坐标和人物当前xz坐标后就可以了。
//首先监听点击事件
input.on(Input.EventType.TOUCH_END, this.TouchEnd, this);
//屏幕点击抬起时通过raycast计算出射线与地面的交点,即屏幕点对应的地面点
    private GetTouchPoint3d(TouchPoint: Vec2): Vec3 | null {
        this.MainCamera.screenPointToRay(TouchPoint.x, TouchPoint.y, this.Ray);
        if (PhysicsSystem.instance.raycast(this.Ray)) {
            let RayCastResults = PhysicsSystem.instance.raycastResults;
            for (let i = 0; i < RayCastResults.length; i++) {
                let res = RayCastResults[i];
                if (res.collider.node.name == 'TouchCollider') {
                    return res.hitPoint.clone();
                }
            }
        }
        return null;
    }
计算出地面点后通过勾股定理计算出人物在xz方向各自的速度,以及人物的朝向即可
    private GetMoveDir(TouchPoint3d: Vec3, StartPoint: Vec3 = null): number[] {
        if (!StartPoint) {
            StartPoint = this.node.getPosition();
        }
        let deltax = TouchPoint3d.x - StartPoint.x;
        let deltaz = TouchPoint3d.z - StartPoint.z;
        let dis = Math.sqrt(deltax * deltax + deltaz * deltaz);
        let Speedx = deltax / dis * this.Speed;
        let Speedz = deltaz / dis * this.Speed;
        let Angle = Math.atan2(Speedx, Speedz) * 180 / Math.PI;
        return [Speedx, Speedz, Angle];
    }

计算出结果后,根据xz的速度更新人物位置

this.node.setRotationFromEuler(new Vec3(0, this.MoveDir[2], 0));
this.UpdatePos(this.node.position.x + this.MoveDir[0] * ratio, this.node.position.y, this.node.position.z + this.MoveDir[1] * ratio);
//这里我设置了一个ratio系数,因为我希望每次点击是由快到慢的移动一段距离,而不是一直朝某个方向移动

双击翻滚

有了单击移动后,双击实现原理就比较简单了,只需要在第一次点击时记录下点击的时间,每次点击的时候计算两次时间的间隔,如果小于一定值,就判定为双击,否则还是单击

//初始化变量
private TouchEndTime: number = 0;
//触发单击事件时,计算时间差
let CurTime = new Date().getTime();
 if (CurTime - this.TouchEndTime < 200) {
//这里需要将时间重置,避免下次单击时判断还是双击
   this.TouchEndTime = 0;
//双击,翻滚,改变人物的状态
   this.ChangePlayerState(PLAYERSTATE.JUMP);
 } else {
// 单击,移动,记录单击的时间
this.TouchEndTime = CurTime;
this.ChangePlayerState(PLAYERSTATE.RUN);
}

滑动

滑动判断,并不能直接使用TOUCH_MOVE事件进行处理,需要设定一个阈值,当滑动达到一定距离才会触发,避免误触发

//开启滑动事件监听
input.on(Input.EventType.TOUCH_START, this.TouchStart, this);
input.on(Input.EventType.TOUCH_MOVE, this.TouchMove, this);
//触发TOUCH_START时,记录下当前点击点
this.TouchScreenPoint = event.touch.getLocation();

计算两个点的距离时,由于每隔屏幕分辨率不同,所以是要将屏幕坐标转换到世界坐标

//触发TOUCH_MOVE时,计算当前move的点与TOUCH_START点的距离,超过一点距离就触发滑动
//屏幕坐标转世界坐标
let curpos = new Vec3(event.touch.getLocation().x, event.touch.getLocation().y, 0);
this.UiCamera.screenToWorld(curpos, curpos);
let touchpos = new Vec3(this.TouchScreenPoint.x, this.TouchScreenPoint.y, 0);
this.UiCamera.screenToWorld(touchpos, touchpos);

//超过一定距离则触发滑动
let deltax = curpos.x - touchpos.x;
let deltay = curpos.y - touchpos.y;
if (deltax * deltax + deltay * deltay < 15625) {
    return;
}
//滑动
this.MoveDir = this.GetMoveDir(this.GetTouchPoint3d(event.touch.getLocation()), this.TouchStartPoint);
this.node.setRotationFromEuler(new Vec3(0, this.MoveDir[2], 0));
this.ChangePlayerState(PLAYERSTATE.GRAB);

长按

长按逻辑是在update中实现的,触发TOUCH_START时,记录点击的开始时间,在update中计算当前时间和点击开始时间的时间差,大于一定值就触发长按事件。

//TOUCH_START事件记录当前时间
this.TouchStartTime = new Date().getTime();
//update中计算时间差
 if (this.TouchStartTime && new Date().getTime() - this.TouchStartTime > 300) {
            // 长按
         this.ChangePlayerState(PLAYERSTATE.XULI);    
}

连击

实现连击,其实就是控制人物状态

//设定一个枚举,记录主角的状态
export enum PLAYERSTATE {
    RUN,
    ATK1,
    ATK2,
    ATK3,
    ATK4,
    ATK5,
}

做一个面片,给人物加一个MeshCollider进行敌人检测,当敌人进入此区域时,就改变人物状态
碰撞检测.PNG

//开启敌人碰撞监听,监听到敌人进入此区域时,将敌人节点加入列表
this.node.getChildByName('EnemyDetect').getComponent(MeshCollider).on('onTriggerEnter', this.EnemyEnter, this);

EnemyEnter(event: ITriggerEvent) {
        this.AroundEnemys.add(event.otherCollider.node);
    }

在update中判断,AroundEnemys列表是否有敌人,若有敌人,则根据当前状态切换至下一个状态。

//这里需要设定一个时间,由于状态在update中切换的,所以当用户点击没有敌人的方向时,需要一定时间后才能判断是否再进入攻击状态,否则可能导致始终处于攻击状态
if (this.ActionTime > 0.1 && this.AroundEnemys.size > 0) {
    switch (this.CurState) {
            case PLAYERSTATE.RUN:
            case PLAYERSTATE.ATK5:
                this.ChangePlayerState(PLAYERSTATE.ATK1);
                break;
            case PLAYERSTATE.ATK1:
                this.ChangePlayerState(PLAYERSTATE.ATK2);
                break;
            case PLAYERSTATE.ATK2:
                this.ChangePlayerState(PLAYERSTATE.ATK3);
                break;
            case PLAYERSTATE.ATK3:
                this.ChangePlayerState(PLAYERSTATE.ATK4);
                break;
            case PLAYERSTATE.ATK4:
                this.ChangePlayerState(PLAYERSTATE.ATK5);
                break;
        }

}
3赞

大佬,项目上架Store,记得在商品介绍中提供软著、设计文档、美术工程截图等创作证明材料,并打上水印。

在此感谢分享,感谢对Cocos、Cocos Store、开发者社区的支持 :muscle:

牛逼大佬啊,请问你的模型是自己做的还是买的啊?我也想做3d游戏,但是模型不好弄。

牛皮plus

不错,体验了一下蛮不错的,加载很快秒进好评,楼主碰撞用的物理引擎吗还是自己计算的

收到收到。

网上买一些然后自己去调整

因为目前测试下来官方提供的物理引擎足够用,所有没有花时间再去研究

牛逼,刚卡死,杀了重进,就看到修复的公告

那是以前的修复公告 :joy:

引导会卡死我也遇到了

不过 setRotationFromEuler 和 new Vec3() 尽量少用
this.node.setRotationFromEuler(new Vec3(0, this.MoveDir[2], 0));
euler to quat 的计算比较麻烦

    public static fromEuler<Out extends IQuatLike> (out: Out, x: number, y: number, z: number): Out {
        x *= halfToRad;
        y *= halfToRad;
        z *= halfToRad;

        const sx = Math.sin(x);
        const cx = Math.cos(x);
        const sy = Math.sin(y);
        const cy = Math.cos(y);
        const sz = Math.sin(z);
        const cz = Math.cos(z);

        out.x = sx * cy * cz + cx * sy * sz;
        out.y = cx * sy * cz + sx * cy * sz;
        out.z = cx * cy * sz - sx * sy * cz;
        out.w = cx * cy * cz - sx * sy * sz;

        return out;
    }

可以写个EulerY to quat 转换 quat 的 x z 就不用算了,少了4次三角函数计算

const halfToRad = 0.5 * 3.1415926 / 180.0;
const temp_Q_0 = new Quat();
export function quatFromAngleY<Out extends IQuatLike>(out: Out, y: number) {
    y *= halfToRad;
    out.x = out.z = 0;
    out.y = Math.sin(y);
    out.w = Math.cos(y);
    return out;
}

quatFromAngleY(temp_Q_0, config.eulerY);
this.node.setRotation(temp_Q_0);
2赞

感谢大佬指点

以上,大家有什么问题,也可以沟通交流

玩的有点晕

是不是觉得操作上不如传统的摇杆舒服

感觉应该竖屏,竖屏配单手

这个主意很不错,但是要实际看下效果,如果地图是上下走动的效果会好一些,左右距离太小,不好看到敌人

switch少个break :joy:导致释放技能时同时释放了另外一个技能,另外一个技能资源没加载,导致卡死了,已经修复

不错很流畅,就是人物有点飘