首页 > 解决方案 > 如何仅在加速时限制玩家的速度?

问题描述

我正在开发一个使用 Unity 内置物理的 2.5D 太空射击游戏。一切都发生在 2D 空间中,但所有模型都是 3D 的。

玩家(太空船)可以使用控制器轴旋转,并且可以在按住按钮时加速(例如 xbox 控制器的 A 按钮)。

玩家可以移动的速度(maxEngineSpeed)是有限制的,我在 FixedUpdate 中将 RigidBody 的速度限制为如下:

if (rb.velocity.magnitude > maxEngineSpeed)
{
    rb.velocity = Vector2.ClampMagnitude(rb.velocity, maxEngineSpeed);
}

现在的问题是,这可以防止速度达到高于 maxEngineSpeed 的值。

我想要一种仅在玩家加速时限制速度的行为。如果玩家以某种方式从盘绕或子弹击中获得更多速度,则不应限制速度。我们可以认为它就像宇宙飞船的引擎中没有足够的动力来更快地行驶。这就像线性阻力,但仅在加速时(不加速时,船根本不减速)。我有道具可以让玩家获得更高的最大速度,所以这很重要。

这将如何实施?我试图仅在玩家加速时限制速度,但随后它会立即将其钳制到指定值并且看起来不自然。加速时会慢慢减小幅度的协同程序会起作用吗?但是,它是否必须考虑玩家的方向和当前速度?

编辑:澄清:在实践中我想问一个刚体“如果我在你的移动速度超过 maxEngineSpeed 时向你施加这个力,它会增加你的速度吗?如果会,不要施加力,如果它会降低你的速度,然后应用它”。

编辑:为了更清楚起见,将 maxSpeed 变量名称更改为 maxEngineSpeed。

标签: unity3dgame-physics

解决方案


下拉并拖动

有很多方法可以实现你想要的。下面我通过一个工作演示展示了两种可能的方法,让您对性能和不同之处有所了解。还在底部链接到另一个演示。

拉下

您可以通过定义最大超速和超速阻力系数来降低速度

下拉法

定义设置

 float pullDown = 0.1f;  // dimensionless > 0, < 1 
 float maxOverSpeed = 5.0f;
 float maxSpeed = 4.0f
 float acceleration = 0.1f;

每帧

 if (accelerate && speed < maxSpeed) { speed += acceleration }

 // clamp max over speed
 speed = speed > maxOverSpeed ? maxOverSpeed : speed;

 float speedAdjust = speed - maxSpeed;

 // pull speed down if needed
 speed -= speedAdjust > 0.0f ? speedAdjust * pullDown : 0.0f;

 // set the velocity magnitude to the new  speed

我个人不喜欢这种方法,因为它是一个滑行模型,船可以加速并保持它,没有减速,但它确实可以更好地控制速度。

我首选的方法是使用简单的阻力系数。轻微修改以在超速时添加额外的平局

然而,这使得很难知道将给予一些加速度的最大速度是多少。有一个公式可以给你一个阻力系数来匹配加速度的最大速度,或者加速度来匹配阻力系数的最大速度,但是我不记得了,因为我发现它已经有好几年了我需要使用它。

我对它进行调整并定义一个近似值,对其进行测试并进行改进,直到我感觉正确为止。实际上,如果问玩家的最大速度是多少?我所知道的不是太快也不是太慢。:P

拖动方法

定义

  float acceleration = 0.1f;
  float drag = 1.0f - 0.021f;
  float overSpeedDrag = 1.0f - 0.026f;
  float maxSpeed = 4;

每帧

  // apply drag depending on speed
  speed *= speed > maxSpeed ? overSpeedDrag : drag;
  if (accelerate) { speed += acceleration }
            
  // set the velocity magnitude to the new current speed

 

例子

这些方法作为代码对实际结果没有太大的感觉,因此下面的代码片段实现了这两种方法,因此您可以看到并感受它们是如何工作的。

代码位于顶部(在 JavaScript 中)PULL_DOWNDRAG在函数中标记了两种不同的方法update() {

  • 船速以每秒像素 (Pps) 为单位
  • 两艘船具有相同的加速度常数,但是 B 船(阻力法)不以恒定速率加速。
  • A 船会滑行,B 船总是会停下来。
  • 单击凹凸以踢船的速度。

const accelFunction = {
    get vel() { return new Vec2(0, 0) },
    speed: 0,
    acceleration: 0.1,
    maxSpeed: 4,
    
    // drag constants
    drag: 1 - 0.0241,
    overSpeedDrag: 1 - 0.0291,

    // pulldown constants;
    overSpeed: 5,
    pullDown: 0.1,

    update() {
        if (this.method === DRAG) { // Drag method
            this.speed *= this.speed > this.maxSpeed ? this.overSpeedDrag: this.drag;
            if (this.accelerate) { this.speed += this.acceleration }


        } else {  // Pull down method
            if (this.accelerate && this.speed < this.maxSpeed) { this.speed += this.acceleration }
            this.speed = this.speed > this.maxOverSpeed ? this.maxOverSpeed : this.speed;
            var speedAdjust = this.speed - this.maxSpeed;
            this.speed -= speedAdjust > 0 ? speedAdjust * this.pullDown : 0;
        }

        // move ship
        this.vel.length = this.speed;
        this.pos.add(this.vel);
    },
}







/* rest of code unrelated to anwser */




















requestAnimationFrame(start);
const ctx = canvas.getContext("2d");
const PULL_DOWN = 0;
const DRAG = 1;
var shipA, shipB;
var bgPos;
function Ship(method, control, controlBump) {  // creates a Player ship

    control.addEventListener("mousedown",() => API.go());
    control.addEventListener("mouseup",() => API.coast());
    control.addEventListener("mouseout",() => API.coast());
    controlBump.addEventListener("click",() => API.bump());
    const API = {    
        ...accelFunction,
        pos: new Vec2(100, 50 + method * 50),
        method, // 0 drag method, 1 pulldown
        draw() {
            ctx.setTransform(1,0,0,1,this.pos.x - bgPos.x, this.pos.y)
            ctx.strokeStyle = "#FFF";
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.lineTo(20, 0);
            ctx.lineTo(-20, -20);
            ctx.lineTo(-20,  20);
            ctx.closePath();
            ctx.stroke();
            ctx.fillText(this.method ? "B" : "A", -11, 3);     
            ctx.fillText((this.speed * 60 | 0) + "Pps", 80, 3);
            if (this.accelerate) {
                ctx.strokeStyle = "#FF0";
                ctx.beginPath();
                ctx.lineTo(-20, -10);
                ctx.lineTo(-30 - Math.rand(0,10), 0);
                ctx.lineTo(-20,  10);
                ctx.stroke();
            }
                
        
        },
        focus: false,
        reset() {
            this.focus = false;
            this.vel.zero();
            this.pos.init(100, 50 + this.method * 50);
            this.speed = 0;
            this.accelerate = false;
        },
        go() {
            this.accelerate = true;
            this.focus  = true;
            if (this.method === 1) { shipA.reset() }
            else { shipB.reset() }
        },
        coast() {
            this.accelerate = false;
        },    
        bump() {
            this.speed += 1;
        },

    };
    return API;
}
function start() {
   init();
   requestAnimationFrame(mainLoop);
}

function mainLoop() {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,500,170);
 
    shipA.update();
    shipB.update();
    
    bgPos.x = shipA.focus ? shipA.pos.x - 50 :  shipB.pos.x - 50 ;
    drawBG(bgPos);
    
    shipA.draw();
    shipB.draw();
    requestAnimationFrame(mainLoop);

}

function drawBG(bgPos) {
    ctx.fillStyle = "#FFF";
    ctx.beginPath();
    const bgX = -bgPos.x + 100000;
    for (const p of background) {
        x = (p.x + bgX) % 504 - 2;
        ctx.rect(x, p.y, 2, 2);
    } 
    ctx.fill();
}


const BG_COUNT = 200;
const background = [];
function init() {
    ctx.font = "16px arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    bgPos = new Vec2();
    shipA = Ship(PULL_DOWN, goA, bumpA);
    shipB = Ship(DRAG, goB, bumpB);
    var i = BG_COUNT;
    while (i--) {
        background.push(new Vec2(Math.rand(0, 10000), Math.rand(-1, 170)));    
    }
}






/* Math LIB Vec2 and math extensions */
Math.rand = (m, M) => Math.random() * (M - m) + m;
function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) { this.x = x; this.y = y }
Vec2.prototype = {
    init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, 
    zero() { this.x = this.y = 0; return this },
    add(v, res = this) { res.x = this.x + v.x; res.y = this.y + v.y; return res },
    scale(val, res = this) { res.x = this.x * val; res.y = this.y * val; return res },
    get length() { return this.lengthSqr ** 0.5 },
    set length(l) { 
        const len = this.lengthSqr;
        len > 0 ? this.scale(l / len ** 0.5) : (this.x = l);
    },
    get lengthSqr() { return this.x * this.x + this.y * this.y },
};
canvas {background: #347;}
div {
   position: absolute;
   top: 150px;
   left: 20px;
}
span { color: white; font-family: arial }
<canvas id="canvas" width="500" height="170"></canvas>
<div>
<button id="goA">Go A</button>
<button id="bumpA">Bump A</button>
<button id="goB">Go B</button>
<button id="bumpB">Bump B</button>
<span> Methods: A = Pull down B = Drag </span>
</div>

没有限制

这些方法有很多变体,并且有很多关于 SO 的示例(我在该主题中写了很多答案。例如,请参阅演示片段(答案底部),例如拖动方法修改)。

您使用哪种方法在很大程度上取决于您希望交互感觉如何,没有正确或错误的方法,因为游戏物理与真实物理有很大不同。


推荐阅读