加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

3D角色脚本控制与碰撞检测

(2018-01-14 19:19:26)
分类: layabox
转自:https://ldc.layabox.com/doc/?nav=zh-ts-4-1-1

基本需求为:

1、通过摇杆控制器控制角色在场景中来回走动,摇杆控制器松开后,角色停止移动并待机。

2、可通过攻击按钮切换为角色攻击动画,一直按下按钮可不停攻击,点击一次按钮至少播放一次完整的攻击动画,结束后播放之前的动画,攻击按钮优先级大于摇杆,如摇杆还在按下状态,攻击停止后播放移动动画并位移。

3、场景中需要有阻挡,某些地方角色无法行走,当角色行走至阻挡区时停止移动。

4、克隆一个相同的角色,两个角色被同时控制,如其中一个遇到阻挡停止后,另一个角色不会受到影响。

参考效果如下图1:

https://official.layabox.com/laya_data/Chinese/LayaAir_TS/3D/examples/roleScriptControl/img/1.gif(图1)

需用的引擎技术方案分析

1、摇杆:采用2D引擎鼠标监听方式,2D引擎鼠标事件支持多点触摸,适应手机多点的复杂操作。

2、角色控制:LayaAir3D引擎支持组件式开发模型,因此角色控制我们采用脚本组件方式,有效的把控制与显示分开。

3、场景:在文档编写时,3D引擎的高级地形正在完善中,因此场景中的阻挡暂时采用行走区碰撞器与射线检测方式判断。

美术可以在3D场景中制作一个角色可行走区的单独模型,如图2。导出使用时,不进行渲染,但在代码中需为它添加网格碰撞器,由角色前行位置产生一条射线与碰撞器进行碰撞检测,如果无碰撞信息,则角色无法行走,有碰撞信息角色可以行走。当然,也可以反过来,不可行走区域制作一个模型。

当高级地形功能完善后,将出技术文档详细介绍,并推荐开发者们使用高级地形,性能上更加。

https://official.layabox.com/laya_data/Chinese/LayaAir_TS/3D/examples/roleScriptControl/img/2.png(图2)

摇杆控制器与攻击按钮

加载进度页面与“技术文档—3D角色切换与动画”示例中界面与代码基本一致,在此不多做说明。

遥杆控制器与攻击按钮界面通用于2D、3D游戏,开发者们可以参考使用。在LayaAirIDE中创建两个界面,取名为Rocker.ui、Attack.ui,Rocker.ui是由触摸点图片与背景图构成,Attack.ui是由一个攻击按钮构成,它里面还可以加入其它技能按钮进行扩展。界面如下图3、图4.

https://official.layabox.com/laya_data/Chinese/LayaAir_TS/3D/examples/roleScriptControl/img/3.png(图3)

https://official.layabox.com/laya_data/Chinese/LayaAir_TS/3D/examples/roleScriptControl/img/4.png(图4)

IDE发布导出资源后,在项目ui文件夹中会产生对应的类,我们建立View文件夹并创建RockerView、AttackView类继承它,在里面编写摇杆控制、攻击逻辑代码,示例如下:


  1. class RockerView extends ui.RockerUI{
  2. private touchRect:Laya.Sprite;
  3. private originPiont:Laya.Point;
  4. private deltaX:number;
  5. private deltaY:number;
  6. private curTouchId:number=0;
  7. private isDown:Boolean=false;
  8. public angle:number=-1;
  9. public radians:number=-1;
  10. public isleftControl:Boolean=true;
  11. constructor(touchSp:Laya.Sprite) {
  12. super();
  13. this.touchRect = touchSp;
  14. //鼠标按下事件监听
  15. this.touchRect.on(Laya.Event.MOUSE_DOWN,this,this.onMouseDown);
  16. //鼠标抬起事件监听
  17. this.touchRect.on(Laya.Event.MOUSE_UP,this,this.onMouseUp);
  18. //鼠标是否移除屏幕事件监听
  19. // this.touchRect.on(Laya.Event.MOUSE_OUT,this,this.onBlur);
  20. //控制器中心点位置初始化
  21. this.originPiont = new Laya.Point(this.width/2,this.height/2);
  22. //默认为控制器不显示
  23. this.visible = false;
  24. }
  25. private onMouseDown(e:Laya.Event):void{
  26. //左右手遥控
  27. if(this.isleftControl){
  28. //如果按下时是右边屏幕位置或已经按下鼠标,则返回
  29. if(e.stageX > Laya.stage.width/2 || this.isDown)return;
  30. }
  31. else{
  32. //如果按下时是左边屏幕位置或已经按下鼠标,则返回
  33. if(e.stageX <</span> Laya.stage.width/2 || this.isDown)return;
  34. }
  35. //记录当前按下id
  36. this.curTouchId = e.touchId;
  37. //已按下
  38. this.isDown = true;
  39. //更新摇杆到屏幕按下位置
  40. this.pos(Laya.stage.mouseX - (this.width / 2),Laya.stage.mouseY - (this.height / 2));
  41. //初始化摇杆控制点位置
  42. this.knob.pos(this.width / 2, this.height / 2);
  43. //按下后显示摇杆
  44. this.visible = true;
  45. //摇杆移动控制事件监听
  46. this.touchRect.on(Laya.Event.MOUSE_MOVE,this,this.onMove);
  47. }
  48. private onMouseUp(e:Laya.Event):void{
  49. //如果不是上次的点击id,返回(避免多点抬起,以第一次按下id为准)
  50. if(e.touchId != this.curTouchId)return;
  51. this.isDown = false;
  52. this.visible = false;
  53. //移除摇杆移动事件监听
  54. this.touchRect.off(Laya.Event.MOUSE_MOVE,this,this.onMove);
  55. //修改摇杆角度与弧度为-1(代表无角度)
  56. this.radians = this.angle = -1;
  57. }
  58. private onMove(e:Laya.Event):void{
  59. //如果不是上次的点击id,返回(避免多点抬起,以第一次按下id为准)
  60. if(e.touchId != this.curTouchId)return;
  61. //将移动时的鼠标屏幕坐标转化为摇杆局部坐标
  62. var locationPos:Laya.Point = this.globalToLocal(new Laya.Point(Laya.stage.mouseX,Laya.stage.mouseY),false);
  63. //更新摇杆控制点位置
  64. this.knob.pos(locationPos.x,locationPos.y);
  65. //更新控制点与摇杆中心点位置距离
  66. this.deltaX = locationPos.x - this.originPiont.x;
  67. this.deltaY = locationPos.y - this.originPiont.y;
  68. //计算控制点在摇杆中的角度
  69. var dx:number = this.deltaX * this.deltaX;
  70. var dy:number = this.deltaY * this.deltaY;
  71. this.angle = Math.atan2(this.deltaX,this.deltaY) * 180 / Math.PI;
  72. if(this.angle <</span> 0) this.angle += 360;
  73. //对角度取整
  74. this.angle = Math.round(this.angle);
  75. //计算控制点在摇杆中的弧度
  76. this.radians = Math.PI / 180 * this.angle;
  77. //强制控制点与中心距离不超过80像素
  78. if(dx+dy >= 80*80){
  79. //控制点在半径为80像素的位置(根据弧度变化)
  80. var x:number = Math.floor(Math.sin(this.radians) * 80 +this.originPiont.x);
  81. var y:number = Math.floor(Math.cos(this.radians) * 80 + this.originPiont.y);
  82. this.knob.pos(x,y);
  83. }
  84. else{
  85. //不超过80像素取原坐标
  86. this.knob.pos(locationPos.x,locationPos.y);
  87. }
  88. }
  89. }

  1. class attackView extends ui.attackUI {
  2. public isAttack:Boolean = false;
  3. private touchId:number;
  4. constructor() {
  5. super();
  6. //按钮按下与抬起事件监听
  7. this.btn_attack.on(Laya.Event.MOUSE_DOWN,this,this.onAttack);
  8. this.stage.on(Laya.Event.MOUSE_UP,this,this.onUp);
  9. }
  10. private onUp(e:Laya.Event):void{
  11. //如果抬起时的ID与按下时的相同 则为不攻击
  12. if(e.touchId == this.touchId) this.isAttack = false;
  13. }
  14. private onAttack(e:Laya.Event):void{
  15. //获取按下时的id
  16. this.touchId = e.touchId;
  17. //获取事件传参值
  18. this.isAttack = true;
  19. }
  20. }

示例主类

示例主类中基本没有控制方面的逻辑,同样是创建场景、摄像机、角色。示例中将不使用灯光,用光照贴图即可,建议开发者们如果场景中没有动态光,可不添加灯光,性能上会高很多,角色阴影可以使用透明贴图模型片。

场景上需从场景模型中获取行走区域模型moveArea,可以设置它为不渲染,代码为moveArea.meshRender.enable=false,并给它加上网格碰撞器MeshCollider,网格碰撞器检测较为精确,与模型本身一致,镂空的区域将不会被检测到。当然,性能上开销将较大。

摇杆、攻击按钮及摄像机移动量设置为静态,以方便角色控制脚本使用和控制。

主类全部代码如下:


  1. class Example_roleControl {
  2. private scene:Laya.Scene;
  3. private role:Laya.Sprite3D;
  4. public camera:Laya.Camera;
  5. public static rocker:RockerView;
  6. public static attack:attackView;
  7. public static cameraTranslate:Laya.Vector3;
  8. constructor() {
  9. //初始化引擎
  10. Laya3D.init(1334,750,true);
  11. //画布垂直居中对齐
  12. Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;
  13. //画布水平居中对齐
  14. Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
  15. //等比缩放
  16. Laya.stage.scaleMode = Laya.Stage.SCALE_FIXED_AUTO;
  17. //自动横屏,游戏的水平方向始终与浏览器屏幕较短边保持垂直
  18. Laya.stage.screenMode = Laya.Stage.SCREEN_HORIZONTAL;
  19. Example_roleControl.cameraTranslate = new Laya.Vector3;
  20. //预加载2D资源
  21. Laya.loader.load("res/atlas/myAssets.atlas",Laya.Handler.create(this,this.on2DComplete));
  22. }
  23. private on2DComplete():void{
  24. //实例化摇杆控制器
  25. Example_roleControl.rocker = new RockerView(Laya.stage);
  26. Laya.stage.addChild(Example_roleControl.rocker);
  27. //实例化攻击按钮
  28. Example_roleControl.attack = new attackView();
  29. Laya.stage.addChild(Example_roleControl.attack);
  30. //实例化加载进度页面
  31. var progress:ProgressView = new ProgressView();
  32. Laya.stage.addChild(progress);
  33. //加载3D资源
  34. Laya.loader.create(["LayaScene_scene03/scene03.ls","LayaScene_girl/girl.lh"],Laya.Handler.create(this,this.on3DComplete));
  35. }
  36. private on3DComplete():void{
  37. //创建3D场景
  38. this.createScene();
  39. //创建3D摄像机
  40. this.createCamera();
  41. //创建3D角色
  42. this.createRole();
  43. //游戏帧循环
  44. Laya.timer.frameLoop(1,this,this.onFrameLoop);
  45. }
  46. private createScene():void{
  47. //实例化场景
  48. this.scene = Laya.loader.getRes("LayaScene_scene03/scene03.ls");
  49. Laya.stage.addChild(this.scene);
  50. //将场景层级调至最低(UI界面后面)
  51. Laya.stage.setChildIndex(this.scene,0);
  52. //获取场景模型中的角色移动碰撞区模型
  53. var moveArea:Laya.MeshSprite3D = this.scene.getChildAt(0).getChildByName("MoveArea") as Laya.MeshSprite3D;
  54. //设置为不渲染
  55. moveArea.meshRender.enable = false;
  56. //加载网格碰撞器组件
  57. moveArea.addComponent(Laya.MeshCollider);
  58. }
  59. private createCamera():void{
  60. //实例化摄像机
  61. this.camera = new Laya.Camera();
  62. //移动摄像机位置
  63. this.camera.transform.translate(new Laya.Vector3(0,4.5,12));
  64. //设置摄像机视野范围(角度)
  65. this.camera.fieldOfView = 25;
  66. this.scene.addChild(this.camera);
  67. }
  68. private createRole():void{
  69. //实例化角色
  70. this.role = Laya.loader.getRes("LayaScene_girl/girl.lh");
  71. this.scene.addChild(this.role);
  72. //角色位置
  73. this.role.transform.position = new Laya.Vector3(0,0,1);
  74. //加入角色控制器脚本
  75. this.role.addComponent(RoleControlScript);
  76. //摄像机对准角色模型附近位置
  77. this.camera.transform.lookAt(new Laya.Vector3(0,0.5,1),new Laya.Vector3(0,0,0));
  78. //克隆一个角色(克隆包括子对象、组件、脚本等。tips:用此方法克隆Sprite3D继承类会有问题)
  79. var role1:Laya.Sprite3D = Laya.Sprite3D.instantiate(this.role);
  80. this.scene.addChild(role1);
  81. //角色位置
  82. this.role.transform.position = new Laya.Vector3(-1,0,1);
  83. }
  84. private onFrameLoop():void{
  85. //摄像机位置改变(数据为角色控制脚本修改)
  86. this.camera.transform.translate(Example_roleControl.cameraTranslate,false);
  87. }
  88. }
  89. new Example_roleControl;

角色控制脚本组件

组件功能比较强大,而脚本继承于组件。其中重要属性与方法请查阅“技术文档—LayaAir3D之脚本组件”

脚本组件式开发模式为我们提供了另一套思维方式,与继承不同的是它更灵活多变,随时可以添加移除、组合组件,以达到我们所需效果,并且还能达到控制与显示可彻底分离。开发者们可以多多尝试此种方法。

在本例中角色控制我们使用了脚本组件式的方法,在脚本中,我们主要执行以下功能。

1、获取脚本所属角色动画组件,以供控制动画用,在覆写组件_start()方法中获取动画组件。

2、角色动画,行走、待机、攻击方法分离,角色动画完成事件监听。

3、角色碰撞检测,在脚本更新方法_update()中,角色位置射线与行走区碰撞检测,判断角色是否被阻挡。

4、角色更新,在脚本更新方法_update()中,获取摇杆角度、攻击按钮并根据其属性控制角色动画切换。

5、摄像机跟随角色移动的位移量进行同步位移更新。

脚本全部代码如下:


  1. class RoleControlScript extends Laya.Script{
  2. public roleModel:Laya.Sprite3D;
  3. public roleAni:Laya.Animator;
  4. public currentAction:string = "stand";
  5. public aniComplete:Boolean = true;
  6. public speed:number = 0.04;
  7. public camera:Laya.Camera;
  8. private rocker:RockerView;
  9. private attack:attackView;
  10. private lastAngle:number = 0;
  11. private ray:Laya.Ray;
  12. private outHitInfo:Laya.RaycastHit;
  13. constructor() {
  14. super();
  15. this.ray = new Laya.Ray(new Laya.Vector3(0,0,0),new Laya.Vector3(0,-2,0));
  16. this.outHitInfo = new Laya.RaycastHit();
  17. }
  18. public _load(owner:Laya.Sprite3D):void{
  19. //获取控制器UI
  20. this.rocker = Example_roleControl.rocker;
  21. this.attack = Example_roleControl.attack;
  22. }
  23. public _start(state:Laya.RenderState):void{
  24. //获取被绑定脚本的模型,需等待角色实例化完成
  25. //获取有动画组件的内层模型(.lh资源导出时会在角色外包裹一层sprite3D)
  26. this.roleModel = this.owner.getChildByName("girl1") as Laya.Sprite3D;
  27. //模型缩放
  28. this.roleModel.transform.localScale = new Laya.Vector3(0.8,0.8,0.8);
  29. //获取角色动画组件
  30. this.roleAni = this.roleModel.getComponentByType(Laya.Animator) as Laya.Animator;
  31. //动画完成事件监听
  32. this.roleAni.on(Laya.Event.COMPLETE,this,this.onComplete);
  33. }
  34. public _update(state:Laya.RenderState):void{
  35. //如果是攻击状态播放击球动画(优先播放击球动画)
  36. if(this.attack.isAttack){
  37. if(this.currentAction != "play"){
  38. this.play();
  39. //摄像机移动向量
  40. Example_roleControl.cameraTranslate = new Laya.Vector3(0,0,0);
  41. }
  42. }
  43. //上次击球动画如果未结束,不执行以下代码
  44. if(!this.aniComplete)return;
  45. //如果摇杆有方向角度
  46. if(this.rocker.angle != -1){
  47. //摇杆控制角色旋转方向(笨帧摇杆的角度-上一帧的角度=本帧应当旋转的角度)
  48. this.roleModel.transform.rotate(new Laya.Vector3(0,this.rocker.angle - this.lastAngle,0),false,false);
  49. //通过弧度和速度计算角色在x,z轴上移动的量
  50. var speedX:number = Math.sin(this.rocker.radians) * this.speed;
  51. var speedZ:number = Math.cos(this.rocker.radians) * this.speed;
  52. //记录角色本帧的角度
  53. this.lastAngle = this.rocker.angle;
  54. //行走区域碰撞检测,如未与行走区域模型碰撞,则不移动
  55. //射线原点
  56. var rayOrigin:Laya.Vector3 = new Laya.Vector3(0,0,0);
  57. //根据角色位置计算射线原点
  58. Laya.Vector3.add(this.owner.transform.position,new Laya.Vector3(speedX,2,speedZ),rayOrigin);
  59. //射线原点位置更新
  60. this.ray.origin = rayOrigin;
  61. //物理射线与碰撞器相交检测
  62. Laya.Physics.rayCast(this.ray,this.outHitInfo,5);
  63. //如果未有碰撞则返回
  64. if(this.outHitInfo.distance <</span> 0)speedX = speedZ = 0;
  65. //更新角色位置
  66. this.owner.transform.translate(new Laya.Vector3(speedX,0,speedZ),false);
  67. //播放行走动画
  68. if(this.currentAction != "go")this.go();
  69. }
  70. else{
  71. //如果摇杆未有角度则播放待机动画
  72. if(this.currentAction != "stand")this.stand();
  73. }
  74. //摄像机移动向量
  75. //注:因为克隆需求,所以提供移动向量给主类,由主类控制摄像机更新。
  76. //如果只有单一主角,可以直接在脚本中控制摄像机移动。
  77. Example_roleControl.cameraTranslate = new Laya.Vector3(speedX,0,speedZ);
  78. }
  79. private onComplete():void{
  80. //角色动画完成
  81. this.aniComplete = true;
  82. //如果结束的动画剪辑名为play,则播放站立待机动画
  83. if(this.roleAni.currentPlayClip.name == "play") this.stand();
  84. }
  85. public go():void{
  86. this.roleAni.play("go",1.4);
  87. this.currentAction = "go";
  88. }
  89. public stand():void{
  90. this.roleAni.play("stand");
  91. this.currentAction = "stand";
  92. }
  93. public play():void{
  94. this.roleAni.play("play");
  95. this.currentAction = "play";
  96. this.aniComplete = false;
  97. }
  98. }

如果角色有行走go、站立stand、攻击play动画,当脚本加入到此对象上后,就可以像主角一样被控制了。这就是脚本的灵活之处。

编译运行代码,可得出图1演示效果。

0

阅读 收藏 喜欢 打印举报/Report
前一篇:laya.net.Loader
后一篇:List 组件参考
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有