3D游戏中NPC的跳跃技能实现、原理解析、视频演示及代码演示 - LuoYing RPG
2017-04-21 00:00
721 查看
LuoYing RPG的技能系统已经开发了很长一段时间,期间对于技能系统的重构也进行了很多次,其中对于走路、跑步、空闲、攻击、射击、魔法等技能都已经有了一个实现,但由于在“落樱之剑”中没有没有对“跳跃”技能的要求,所以“跳跃”技能的实现一直放在优先级非常靠后的情况,最近几天想想觉得如果不实现确实有一些遗憾,所以这几天把跳跃技能实现了,同时也已经集成到了编辑器中,后面技能系统应该会放下一段时间。
先来看一下跳跃技能在落樱RPG中的的实现原理,这个跳跃技能依赖物理系统,整个技能过程由三个阶段组成:
起跳阶段,这个阶段会执行起跳动画,即角色起跳时的预备动作或跃向空中时的动作。
滞空阶段:这个阶段会执行角色在空中坠落时的动画,一般这个阶段的时间是不确定的,动画循环模式一般是单向(loop)或是周期(cycle)循环。
落地阶段:这个阶段会执行角色落地时的缓冲动作,然后技能结束。
那么要在游戏中让NPC实现跳跃功能,要怎么做呢?
一般来说,首先需要用3D软件制作一个角色模型,并实现模型跳跃时的三个阶段的动画,如上所示(当然,如果不制作模型或者动画也是可以的,可以随便找一个模型代替,只不过最终实现起来不是太好看而已,没有模型动画的话,你只能看到一个僵硬的物理跳跃现象)
然后在游戏程序中是这样实现的, 把整个技能过程分成几个阶段:
第一阶段就是播放起跳动画了,在起跳动画结束或者快结束的时候给角色一个起跳的物理作用力。
由于这个起跳的物理作用力,角色会飞向作用力的方向(一般为空中),同时角色结束第一阶段的起跳动画,转而执行“滞空动画”,即在空中的坠落时的摆动动画,然而这个时间是不确定的,因为角色跳多高、从多高落下是不确定的,所以这个过程的动画可以一直循环,并持续判断角色是否接触到了地面(这个判断可以使用射线检测)。
当角色接触到地面时接着结束第二阶段的动画,转而播放“落地动画",落地动画播放结束后就结束整个技能过程。
简单的来说,整个跳跃过程的实现就是这样的,这里用”简单的来说“是因为实际上整个技能系统中的各种技能是可能互相影响的,比如角色在跳跃到空中的时候可能会被攻击、被其它技能打断等,但这里不想把问题搞得太复杂。
先来看一下这个跳跃技能在LuoYing RPG中的xml配置:
在LuoYing RPG中所有的游戏元素几乎都是可配置的, 比如这个跳跃技能,三阶段的跳跃动画、时间限制、跳跃力、物理作用力时间点、技能优先级等都是可以配置的。
3ff0
XML配置起来虽然比较自由,但稍微有一些麻烦,也不够可视化,那么来看一下在编辑器中的样子,后面再看代码是怎么实现的:
跳跃技能的代码实现原理(如果要查看完整的代码,请参考我的开源项目:LuoYing RPG)
先来看一下跳跃技能在落樱RPG中的的实现原理,这个跳跃技能依赖物理系统,整个技能过程由三个阶段组成:
起跳阶段,这个阶段会执行起跳动画,即角色起跳时的预备动作或跃向空中时的动作。
滞空阶段:这个阶段会执行角色在空中坠落时的动画,一般这个阶段的时间是不确定的,动画循环模式一般是单向(loop)或是周期(cycle)循环。
落地阶段:这个阶段会执行角色落地时的缓冲动作,然后技能结束。
那么要在游戏中让NPC实现跳跃功能,要怎么做呢?
一般来说,首先需要用3D软件制作一个角色模型,并实现模型跳跃时的三个阶段的动画,如上所示(当然,如果不制作模型或者动画也是可以的,可以随便找一个模型代替,只不过最终实现起来不是太好看而已,没有模型动画的话,你只能看到一个僵硬的物理跳跃现象)
然后在游戏程序中是这样实现的, 把整个技能过程分成几个阶段:
第一阶段就是播放起跳动画了,在起跳动画结束或者快结束的时候给角色一个起跳的物理作用力。
由于这个起跳的物理作用力,角色会飞向作用力的方向(一般为空中),同时角色结束第一阶段的起跳动画,转而执行“滞空动画”,即在空中的坠落时的摆动动画,然而这个时间是不确定的,因为角色跳多高、从多高落下是不确定的,所以这个过程的动画可以一直循环,并持续判断角色是否接触到了地面(这个判断可以使用射线检测)。
当角色接触到地面时接着结束第二阶段的动画,转而播放“落地动画",落地动画播放结束后就结束整个技能过程。
简单的来说,整个跳跃过程的实现就是这样的,这里用”简单的来说“是因为实际上整个技能系统中的各种技能是可能互相影响的,比如角色在跳跃到空中的时候可能会被攻击、被其它技能打断等,但这里不想把问题搞得太复杂。
先来看一下这个跳跃技能在LuoYing RPG中的xml配置:
<skillBase id="skillJumpTagBase" types="jump" prior="30" overlapTypes="skin,walk,run" interruptTypes="idle,wait" /> <skillJump id="skillJumpBase" extends="skillJumpTagBase" useTime="1" /> <skillJump id="skillSinbadJumpTest" extends="skillJumpBase" animStart="JumpStart" animInAir="JumpLoop" animEnd="JumpEnd" jumpForce="0,400,0" useTimeInStart="0.5" useTimeInAir="2" useTimeInEnd="0.5" forceApplyTime="0.3" />
在LuoYing RPG中所有的游戏元素几乎都是可配置的, 比如这个跳跃技能,三阶段的跳跃动画、时间限制、跳跃力、物理作用力时间点、技能优先级等都是可以配置的。
3ff0
XML配置起来虽然比较自由,但稍微有一些麻烦,也不够可视化,那么来看一下在编辑器中的样子,后面再看代码是怎么实现的:
跳跃技能的代码实现原理(如果要查看完整的代码,请参考我的开源项目:LuoYing RPG)
package name.huliqing.luoying.object.skill; import com.jme3.animation.LoopMode; import com.jme3.bullet.control.BetterCharacterControl; import com.jme3.math.Vector3f; import com.jme3.util.TempVars; import java.util.logging.Level; import java.util.logging.Logger; import name.huliqing.luoying.data.SkillData; import name.huliqing.luoying.message.StateCode; import name.huliqing.luoying.object.attribute.NumberAttribute; import name.huliqing.luoying.object.module.ChannelModule; /** * 跳跃技能 * @author huliqing */ public class JumpSkill extends AbstractSkill { private static final Logger LOG = Logger.getLogger(JumpSkill.class.getName()); private ChannelModule channelModule; // 角色的起跳、滞空、落地动画 private String animStart; private String animInAir; private String animEnd; // 滞空动画的循环模式 private LoopMode animInAirLoop = LoopMode.Cycle; // 角色的起跳动作、空中动作、落地动作的时间, private float useTimeInStart = 0.5f; private float useTimeInAir = 1f; private float useTimeInEnd = 0.3f; // 跳跃作用力的开始时间 private float forceApplyTime = 0.3f; // 跳跃的作用力这个力量强度和角色的质量大小有关系,当角色的质量越大,就需要更强的跳跃力 private Vector3f jumpForce = new Vector3f(0, 100, 0); // 如果角色是在向前走动的时候进行跳跃,那么跳跃的时候会有一个向前的冲力,这个参数用于控制这个冲力的大小, // 例如,如果不想让角色跳得太远,则要减少这个值,否则增加这个值。 private float walkForceIntensity = 0.3f; // 跳跃的强度(绑定实体属性) private String bindJumpIntensityAttribute; // 一个用于超时结束当前动作的限制,避免当角色卡在空中时无法退出当前技能的BUG private float timeout = 15; // ---- 不需要开放的参数 // 标记相应的各动作的动画是否已经执行过 private boolean animStartPlayed; private boolean animInAirPlayed; private boolean animEndPlayed; private boolean forceApplied; // 计算技能在空中的停留时间 private float timeUsedInAir; // 计算技能在落下地面后的落地动画时间 private float timeUsedInEnd; // ---- inner private NumberAttribute jumpIntensityAttribute; // 角色控制器 private BetterCharacterControl bcc; private Vector3f lastWalkDirection = new Vector3f(); private float lastPhysicsDamping; @Override public void setData(SkillData data) { super.setData(data); animStart = data.getAsString("animStart"); animInAir = data.getAsString("animInAir"); animEnd = data.getAsString("animEnd"); animInAirLoop = getLoopMode(data.getAsString("animInAirLoop")); useTimeInStart = data.getAsFloat("useTimeInStart", useTimeInStart); useTimeInAir = data.getAsFloat("useTimeInAir", useTimeInAir); useTimeInEnd = data.getAsFloat("useTimeInEnd", useTimeInEnd); forceApplyTime = data.getAsFloat("forceApplyTime", forceApplyTime); jumpForce = data.getAsVector3f("jumpForce", jumpForce); walkForceIntensity = data.getAsFloat("walkForceIntensity", walkForceIntensity); bindJumpIntensityAttribute = data.getAsString("bindJumpIntensityAttribute"); timeout = data.getAsFloat("timeout", timeout); animStartPlayed = data.getAsBoolean("animStartPlayed", animStartPlayed); animInAirPlayed = data.getAsBoolean("animInAirPlayed", animInAirPlayed); animEndPlayed = data.getAsBoolean("animEndPlayed", animEndPlayed); forceApplied = data.getAsBoolean("forceApplied", forceApplied); timeUsedInAir = data.getAsFloat("timeUsedInAir", timeUsedInAir); timeUsedInEnd = data.getAsFloat("timeUsedInEnd", timeUsedInEnd); // 内部参数 lastWalkDirection = data.getAsVector3f("_lastWalkDirection", lastWalkDirection); lastPhysicsDamping = data.getAsFloat("_lastPhysicsDamping", lastPhysicsDamping); } @Override public void updateDatas() { super.updateDatas(); data.setAttribute("animStartPlayed", animStartPlayed); data.setAttribute("animInAirPlayed", animInAirPlayed); data.setAttribute("animEndPlayed", animEndPlayed); data.setAttribute("forceApplied", forceApplied); data.setAttribute("timeUsedInAir", timeUsedInAir); data.setAttribute("timeUsedInEnd", timeUsedInEnd); // 内部参数 data.setAttribute("_lastWalkDirection", lastWalkDirection); data.setAttribute("_lastPhysicsDamping", lastPhysicsDamping); // 不会改变的数据不需要更新回去 } @Override public void initialize() { super.initialize(); channelModule = actor.getModule(ChannelModule.class); jumpIntensityAttribute = actor.getAttribute(bindJumpIntensityAttribute, NumberAttribute.class); // JumpStart动画 if (!animStartPlayed) { animStartPlayed = true; if (animStart != null) { channelModule.playAnim(animStart, null, LoopMode.DontLoop, useTimeInStart , 0); } } bcc = actor.getSpatial().getControl(BetterCharacterControl.class); if (bcc != null) { // 获取物理控制器,并记住跳跃之前角色的移动方向,跳跃之前必须先将角色的移动清0。 // 因为这个WalkDirection是持续的作用力过程,会造成跳跃空中时,如果遇到障碍物有可能会导致角色紧贴着物体不会落下 // 或者遇到障碍物后这个力仍然一直推着角色向前滑动的bug. lastWalkDirection.set(bcc.getWalkDirection()); bcc.setWalkDirection(new Vector3f()); // 记住这个阻尼值,当角色在跳跃时这个值必须清0,否则会导致很难跳起来。在角色跳到空中后,这个设置可以还原。 lastPhysicsDamping = bcc.getPhysicsDamping(); } else { LOG.log(Level.WARNING, "Jump failure! BetterCharacterControl not found from Entity, entityId={0}, uniqueId={1}" , new Object[] {actor.getData().getId(), actor.getData().getUniqueId()}); } } @Override public void cleanup() { animStartPlayed = false; animInAirPlayed = false; animEndPlayed = false; forceApplied = false; timeUsedInAir = 0; timeUsedInEnd = 0; if (bcc != null) { // 不应该恢复动量,这不是这个技能应该做的,有可能造成BUG,由walkSkill去处理就行(如果WalkSkill同时在执行)。 // 那么当JumpSkill执行完之后,WalkSkill应该自己去恢复这个移动量. // bcc.setWalkDirection(lastWalkDirection) // 这个应该恢复,因为技能有可能在中途被打断,所在必须确保技能结束的时候恢复这个参数 bcc.setPhysicsDamping(lastPhysicsDamping); } super.cleanup(); } @Override public int checkState() { bcc = actor.getSpatial().getControl(BetterCharacterControl.class); if (bcc == null || !bcc.isOnGround()) { return StateCode.SKILL_USE_FAILURE; } return super.checkState(); } @Override protected void doSkillUpdate(float tpf) { if (bcc == null) { return; } // 跳跃的作用力是由jumpDir所设置方向上的力加上角色移动方向上的力合成的。 if (!forceApplied && time >= forceApplyTime) { forceApplied = true; TempVars tv = TempVars.get(); Vector3f finalJumpForce = tv.vect1.set(jumpForce); actor.getSpatial().getWorldRotation().mult(finalJumpForce, finalJumpForce); Vector3f walkDirectionForce = tv.vect2.set(lastWalkDirection).multLocal(jumpForce.length()) .multLocal(walkForceIntensity); // walkForceIntensity调整向前的冲力 if (jumpIntensityAttribute != null) { finalJumpForce.multLocal(jumpIntensityAttribute.floatValue()); walkDirectionForce.multLocal(jumpIntensityAttribute.floatValue()); } finalJumpForce.addLocal(walkDirectionForce); bcc.setPhysicsDamping(0); bcc.setJumpForce(finalJumpForce); bcc.jump(); tv.release(); } // 执行空中落下时的动画 if (!animInAirPlayed && time >= useTimeInStart) { animInAirPlayed = true; if (animInAir != null) { channelModule.playAnim(animInAir, null, animInAirLoop, useTimeInAir, 0); } } // 稍微延迟一下后再判断是否isOnGround(),因为bcc.jump()后角色不会立即离开地面. if (!animEndPlayed && timeUsedInAir > 0.05f) { if (bcc.isOnGround() || time >= timeout) { animEndPlayed = true; if (animEnd != null) { channelModule.playAnim(animEnd, null, LoopMode.DontLoop, useTimeInEnd, 0); } // 当角色跳跃到空中之后重新设置回阻尼。 bcc.setPhysicsDamping(lastPhysicsDamping); // LOG.log(Level.INFO, "animEndPlayed, bcc.isOnGround={0}", new Object[]{bcc.isOnGround()}); } } if (animInAirPlayed) { timeUsedInAir += tpf; } if (animEndPlayed) { timeUsedInEnd += tpf; if (timeUsedInEnd >= useTimeInEnd) { bcc.setWalkDirection(lastWalkDirection); // 还原walkDirection } } } @Override public boolean isEnd() { // remove20170418,因为跳跃技能有一个滞空的情况,这个情况会导致技能在空中的时间是不确定的。 // 因而技能的整个执行时间也是不确定的。 // super.isEnd(); if (bcc == null) return true; if (time > timeout) return true; // 当最后一阶段(落地动作)时间执行完毕时,视为跳跃技能技能结束 if (timeUsedInEnd >= useTimeInEnd) { return true; } return false; } @Override public void restoreAnimation() { // 只恢复空中动画 if (animInAirPlayed && !animEndPlayed) { if (animInAir != null) { channelModule.restoreAnimation(animInAir, null, animInAirLoop, useTimeInAir, 0); } } } // 获取loopMode设置,如果找不到匹配,则默认使用loop模式 private LoopMode getLoopMode(String name) { for (LoopMode lm : LoopMode.values()) { if (lm.name().equals(name)) { return lm; } } return LoopMode.Loop; } }
相关文章推荐
- Java中2.5D游戏的设计与实现(3)—八方走法实现原理及相关代码
- js解析xml字符串和xml文档实现原理及代码(针对ie与火狐)
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- Java2.5D游戏的设计与实现(3)—八方走法实现原理及相关代码 推荐
- 3D游戏常用技巧Normal Mapping (法线贴图)原理解析——基础篇
- JS 实现2+2=5的代码 实现原理解析
- 浅析SkipList跳跃表原理及代码实现
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- jquery绑定原理 简单解析与实现代码分享
- OpenGL实现3D模型自由旋转——之代码解析
- 找工作知识储备(3)---从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用
- 浅析SkipList跳跃表原理及代码实现
- 广度优先搜索算法的典型应用——消灭小星星游戏的核心代码实现与解析
- 快速傅里叶变换(FFT)的原理、实现及代码解析(附C#源码)
- js解析xml字符串和xml文档实现原理及代码(针对ie与火狐)
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用 .
- 从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用 .
- 找工作知识储备(3)---从头说12种排序算法:原理、图解、动画视频演示、代码以及笔试面试题目中的应用