昨天一时心血来朝就跟室友玩起了FIFA,这一玩不要紧,结果直接把这周的稿子给忘写了。(事后喊一句:"真香!")
在玩的时候,我们就球员带球跑动动作的实现展开了一系列讨论(有人要问了,你咋这么闲打个破游戏还要想这些乱七八糟的?emmmm,可能是因为O(∩_∩)O有病吧):
- 多个球员竞争同一个球时,如何判定球员的发力对球有效?
- 是球员竞争到球的归属权之后支配了球的运动,还是球的运动轨迹单独计算?
就着以上的的问题,我们先有了第一套设计:
- 球员本身具有爆发力(加速度),体能(运动时间衰减),与地面的动摩擦系数(控制速度衰减)这些属性。
- 当多个球员竞争同一个球,我们根据模型触碰球的先后决定球的归属权。
- 当球员竞争到球之后,球本身不具备独立的运动轨迹,而是根据球员的运动轨迹再计算足球的运动轨迹渲染。
class FootBall{
constructor(){
this.onwer = null
}
/**
* 竞争,设置足球归属球员
* @param {球员对象}} player
*/
setOwner(player){
if(!this.onwer){
this.onwer = player
}
}
/**
* @param {运动类型,带球,射门} type
* @param {速度} speed
*/
done(type,speed,player){
if(this.onwer === player ){
//更改运动状态
}
}
}
const footBall = new FootBall();
class Player{
/**
* 球员属性初始化
* @param {速度}} pace
* @param {体能} phy
*/
constructor(pace,phy){
this.pace = pace
this.phy = phy
//球员行为树加载
this.doneTree = {
"转身":(player)=>{
footBall.done("转身",null,player)
},
"射门":(player)=>{
footBall.done("射门","3m/s",player)
},
}
}
/**
* 操作触发事件,转身,射门...
* @param {*} type
*/
done(type){
if(this.doneTree[type]){
this.doneTree[type](this)
}else{
console.log("夭寿了,有人写bug了!")
}
}
}
/**
* 游戏引擎实时渲染启动
*/
app.run()
但是仔细想了想,这套方案有着巨大的缺陷:
- 实际运动物体的单一。这种模式下,我们只能对于球员的运动轨迹设计算法优化。就算是最完美的情况下也只能做到球员跑动的逼真,而无法优化球的运动轨迹。
- 逻辑的高度耦合,球员这个角色做了太多的事情。
需要控制自己的跑动,还要计算足球的运动轨迹。这还是只有一个球员和一个足球的情况。当球场上有多个球员时,这个有着足球归属权的球员需要对每个竞争足球的球员的每一个竞争动作做出判断。当满足条件是则让出归属权。
于是我们又思考了第二套实现:
- 假设球员与足球之间的运动相互独立。
- 所有球员对于球的发力都生效。
- 球员自身对于足球的归属具有一种主观认知。当球员行动时如果认为自身已经竞争到足球,则会触发肢体事件给与球一个运动干涉(在某个方向上给与一个速度)
在这一套实现里,我们将球的运动从球员的运动计算中抽离出来,在更大程度上模拟了真实环境。而对于球员和足球的这两个角色的实现上来看,对于每个角色只需要做好自己的事就可以了。球员只需要关心运动的方向,以及什么情况下会触发肢体动作。而对于足球的实现来说,只需要关心受力之后方向的计算就可以了。刚好符合面向对象的”单一职责原则“。
class FootBall{
constructor(){
}
/**
* 计算新增某个方向的一个速度后,新的速率矢量,由物理引擎调度,每次在设置方向上添加固定的加速度
* @param {方向} direction
* @param {加速度} speed
*/
done(direction,speed){
//如碰撞时间为1s,则这段时间在30读方向获得了3m/s的速度,假设原速度为120度方向的4m/s的话。根据勾股定理则最终获得一个90度方向的一个5m/s的速度
//更改运动状态
}
}
const footBall = new FootBall();
class Player{
/**
* 球员属性初始化
* @param {速度}} pace
* @param {体能} phy
*/
constructor(pace,phy){
this.pace = pace
this.phy = phy
this.hasFootBall = null
//球员行为树加载
this.doneTree = {
"停球":()=>{
//给与足球一个足够大向下的力,使球与地面摩擦停止
},
"转身":()=>{
//给与足球一个方向与现有方向相反的碰撞
},
"射门":()=>{
//给足球一个方向为**度的碰撞
},
}
}
/**
* 操作触发事件,转身,射门...
* @param {*} type
*/
done(type){
if(this.hasFootBall){
this.doneTree[type]()
}
}
getOnwer(distance,other){
//根据条件球员判断自身是否已竞争到足球
this.hasFootBall = distance && other
}
}
/**
* 游戏物理引擎初始化启动
*/
app.run()
好了,又到了全篇的总结(O(∩_∩)O,又被我水了一期)。 emmmm,讲道理说到这我还是没想好这个文章的标题叫什么好。更多的是我想尝试通过讲解一些有趣的事情,来表达一种解决问题的思路吧。大家有疑问的,想喷我的,还有想一起讨论的,请务必在下方留言,蟹蟹。
本期的内容就讲到这里了,我是 IHAP亚楠小萌新,更多精彩内容,尽在 ihap 技术黑洞。