Jos*_*ner 5 actionscript-3 game-physics
我正在尝试为游戏创建一系列移动对象(AS3).到目前为止,我可以将对象拖到另一个对象后面,但我所能做的就是根据距离使链接更接近另一个链接.它不现实地工作,它仍然只遵循一个方向.如果我试图将链条向相反的方向拉,它就不起作用了.
我需要一个公式来拉动任何链接后面的链条.
我还需要一个公式,使链接在静止时落在链的其余部分之下.我现在设置它的方式,链接试图下降,但它们只是直接下降而不是被拉到链的其余部分之下.
有一些链和字符串教程,但它们似乎都没有引入重力或双向牵引.
Bli*_*n67 12
最好的选择是verlet链模拟.它结合了重力,约束,并且可以在任何点施加力.
在verlet集成(将数学分为步骤)中,您只存储顶点的当前位置和前一帧中的位置.与存储当前位置和当前速度和方向的欧几里德积分不同,微粒积分将当前速度和方向存储为位置之间的差异.这非常适合您所处的模拟类型,其中复杂的交互不必担心动量,旋转和许多其他问题.你所做的只是记住最后一个位置并设置新的,其他一切都自动发生.
我将使用javascript表示法,因为我从未使用过actionscript.
sim以点(顶点)开始.顶点是没有大小的自由点.它每帧都有重力,它受到地面等环境对象的约束.
存储顶点所需的信息
vertex = {
x : 100, // current position
y : 100,
lx : 100, // last position This point is not moving hence the last pos
ly : 100, // is the same as the current
}
Run Code Online (Sandbox Code Playgroud)
对于动画的每个帧,您可以将各种力应用于顶点
移动点函数和一些常量
GRAV = 0.9; // force of gravity
GROUND = 400; // Y position of the ground. Y is down the screen
GROUND_BOUNCE = 0.5; // Amount of bounce from the ground
DRAG = 0.9; // Amount of friction or drag to apply per frame
dx = (vertex.x-vertex.lx) * DRAG; // get the speed and direction as a vector
dy = (vertex.y-vertex.ly) * DRAG; // including drag
vertex.lx = vertex.x; // set the last position to the current
vertex.ly = vertex.y;
vertex.x += dx; // add the current movement
vertex.y += dy;
vertex.y += GRAV; // add the gravity
Run Code Online (Sandbox Code Playgroud)
每个框架还需要应用约束.它们几乎可以是任何东西.
现在,这只是基础.如果位置大于地线,则将点移离地面.该点也可以是固定的(不需要施加上述移动阻力和重力)或固定到移动物体,如鼠标.
因为最后一个位置(vertex.lx,vertex.ly)有助于定义当前的运动.当我们改变方向(击中地面)时,我们必须改变最后位置以正确描述新方向,这将最后位置置于地下.
约束点函数
if(vertex.y > GROUND){
// we need the current speed
dx = (vertex.x-vertex.lx) * DRAG;
dy = (vertex.y-vertex.ly) * DRAG;
speed = sqrt(dx*dx+dy*dy);
vertex.y = GROUND; // put the current y on the ground
vertex.ly = GROUND + dy * GROUND_BOUNCE; // set the last point into the
// ground and reduce the distance
// to account for bounce
vertex.lx += (dy / speed) * vx; // depending on the angle of contact
// reduce the change in x and set
// the new last x position
}
Run Code Online (Sandbox Code Playgroud)
这是重力,空气摩擦和约束的基本数学,如果我们创建一个点并应用数学它将落到地面,反弹几次然后停下来.
因为我们需要很多顶点,所以你需要创建一个它们的数组来调用这个答案points.
现在是时候连接这些点并将一堆自由浮动顶点转换成各种各样的模拟结构.
对于此答案,该行表示链中的链接.
上图是为了帮助可视化概念.A和B是两个顶点,它们旁边的红点是顶点的最后位置.红色箭头显示线条约束将移动顶点的近似方向.线的长度是固定的,下面的算法试图找到使所有线尽可能接近该长度的最佳解决方案.
line = {
pointIndex1 : 0, // array index of connected point
pointIndex2 : 1, // array index of second connected point
length : 100, // the length of the line.
// in the demo below I also include a image index and
// a draw function
}
Run Code Online (Sandbox Code Playgroud)
一条线连接两个顶点.顶点可以有许多连接到它的线.
在我们应用移动,拖动,重力和任何其他约束之后的每个帧我们应用线约束.从上图中可以看出,两个顶点的最后位置位于距离线太远的位置,无法连接线.为了固定线,我们将两个顶点移动到线的中心点(红色箭头).如果两个点对于线长度太近,我们会将这些点移开.
以下是用于执行此操作的数学运算.
约束线函数
p1 = points[line.pointIndex1]; // get first point
p2 = points[line.pointIndex2]; // get second point
// get the distance between the points
dx = p2.x - p1.x;
dy = p2.y - p1.y;
distance = sqrt(dx * dx + dy * dy);
// get the fractional distance the points need to move toward or away from center of
// line to make line length correct
fraction = ((line.length - distance) / distance) / 2; // divide by 2 as each point moves half the distance to
// correct the line length
dx *= fraction; // convert that fraction to actual amount of movement needed
dy *= fraction;
p1.x -=dx; // move first point to the position to correct the line length
p1.y -=dy;
p2.x +=dx; // move the second point in the opposite direction to do the same.
p2.y +=dy;
Run Code Online (Sandbox Code Playgroud)
这种约束非常简单,因为我们使用的是verlet集成,所以我们不必担心点或线的速度和方向.最重要的是,我们不必处理任何轮换,因为这也需要处理.
At this point we have done all the math needed for a single line, we can add more lines, connecting the end point of the first line to the start of the second line and so on, however long we need the chain. Once we have all the points connected we apply the standard constraints to all the points and then apply the line constraints one by one.
This is where we run into a small problem. When we move the points to correct the length of the first line we move the start point of the next line, then when we move the points for the next line we move the endpoint of the first line breaking its length constraint. When we have gone through all the lines the only line that will have the correct length will be the last one. All the other lines will be pulled slightly toward the last line.
We could leave it as is and that would give the chain a somewhat elastic feel (undesirable in this case but for whips and ropes it works great), we could do a full inverse kinematics end point solution for the line segments (way to hard) or we can cheat. If you apply the line constraint again you move all the points a little more towards the correct solution. We do this again and again, correcting a little for the error introduced in the every previous pass.
This iterative process will move towards a solution but it will never be a perfect, yet we can quickly get to a situation where the error is visually unnoticeable. For convenience I like to call the number of iteration the stiffness of the sim. A value of 1 means the lines are elastic, a value of 10 means there is almost no noticeable stretching of the lines.
Note that the longer the chain the more noticeable the stretching becomes and thus the more iterations need to be done to get the required stiffness.
Note this method of finding a solution has a flaw. There are many cases where there is more than one solution to the arrangement of points and lines. If there is a lot of movement in the system it may start oscillating between the two (or more) possible solutions. If this happens you should limit the amount of movement a user can add to the system.
Putting that all together we need to run the sim once every frame. We have an array of points, and an array of lines connecting the points. We move all the points, then we apply the line constraints. Then render the result ready to do all again next frame.
STIFFNESS = 10; // how much the lines resist stretching
points.forEach(point => { // for each point in the sim
move(point); // add gravity drag
constrainGround(point); // stop it from going through the ground line
})
for(i = 0; i < STIFFNESS; i+= 1){ // number of times to apply line constraint
lines.forEach(line => { // for each line in the sim
constrainLine(line);
})
}
drawPoints(); // draw all the points
drawLines(); // draw all the lines.
Run Code Online (Sandbox Code Playgroud)
The above method provides a great line simulation, It also does rigid bodies, rag dolls, bridges, boxes, cows, goats, cats, and dogs. By fixing points you can do a variety of hanging ropes and chains, create pulleys, tank tread. Great for simulating 2D cars and bikes. But remember they are all visually acceptable but are not at all a real physics simulation.
You want a chain. To make a chain we need to give the lines some width. So each vertex needs a radius, and the ground constraint needs to take that into account. We also want the chain to not fall in on itself so we need a new constraint that prevents balls (AKA vertices) from overlapping each other. This will add quite an extra load to the sim as each ball needs to be tested against each other ball, and as we adjust the position to stop overlap we add error to the line lengths so we need to do each constraint in turn many times to get a good solution per frame.
And the final part is the details of the graphics, Each line needs to have a reference to a image that is a visual representation of the chain.
I will leave all that up to you to work out how best to do in actionscript.
The following demo shows the result of all the above. It may not be what you want, and there are other ways to solve the problem. This method has several problems
该功能你会感兴趣的是constrainPoint,constrainLine,movePoint,和doSim(就在后位if(points.length > 0){的runSim)所有剩下的只是支持和样板.
最好被视为整页(我使图像有点太大oops ... :(
要查看链单击并按住鼠标右键以添加第一个块,然后添加链接到链.我没有限制链条的长度.单击并按住左按钮以抓取并拖动链的任何部分并阻止.
var points = [];
var lines = [];
var pointsStart;
var fric = 0.999; // drag or air friction
var surF = 0.999; // ground and box friction
var grav = 0.9; // gravity
var ballRad = 10; // chain radius set as ball radius
var stiffness = 12; // number of itterations for line constraint
const fontSize = 33;
var chainImages = [new Image(),new Image(),new Image()];
chainImages[0].src = "https://i.stack.imgur.com/m0xqQ.png";
chainImages[1].src = "https://i.stack.imgur.com/fv77t.png";
chainImages[2].src = "https://i.stack.imgur.com/tVSqL.png";
// add a point
function addPoint(x,y,vx,vy,rad = 10,fixed = false){
points.push({
x:x,
y:y,
ox:x-vx,
oy:y-vy,
fixed : fixed,
radius : rad,
})
return points[points.length-1];
}
// add a constrained line
function addLine(p1,p2,image){
lines.push({
p1,p2,image,
len : Math.hypot(p1.x - p2.x,p1.y-p2.y),
draw(){
if(this.image !== undefined){
var img = chainImages[this.image];
var xdx = this.p2.x - this.p1.x;
var xdy = this.p2.y - this.p1.y;
var len = Math.hypot(xdx,xdy);
xdx /= len;
xdy /= len;
if(this.image === 2){ // oops block drawn in wrong direction. Fix just rotate here
// also did not like the placement of
// the block so this line's image
// is centered on the lines endpoint
ctx.setTransform(xdx,xdy,-xdy,xdx,this.p2.x, this.p2.y);
ctx.rotate(-Math.PI /2);
}else{
ctx.setTransform(xdx,xdy,-xdy,xdx,(this.p1.x + this.p2.x)/2,(this.p1.y + this.p2.y)/2);
}
ctx.drawImage(img,-img.width /2,- img.height / 2);
}
}
})
return lines[lines.length-1];
}
// Constrain a point to the edge of the canvas
function constrainPoint(p){
if(p.fixed){
return;
}
var vx = (p.x - p.ox) * fric;
var vy = (p.y - p.oy) * fric;
var len = Math.hypot(vx,vy);
var r = p.radius;
if(p.y <= r){
p.y = r;
p.oy = r + vy * surF;
}
if(p.y >= h - r){
var c = vy / len
p.y = h - r
p.oy = h - r + vy * surF;
p.ox += c * vx;
}
if(p.x < r){
p.x = r;
p.ox = r + vx * surF;
}
if(p.x > w - r){
p.x = w - r;
p.ox = w - r + vx * surF;
}
}
// move a point
function movePoint(p){
if(p.fixed){
return;
}
var vx = (p.x - p.ox) * fric;
var vy = (p.y - p.oy) * fric;
p.ox = p.x;
p.oy = p.y;
p.x += vx;
p.y += vy;
p.y += grav;
}
// move a line's end points constrain the points to the lines length
function constrainLine(l){
var dx = l.p2.x - l.p1.x;
var dy = l.p2.y - l.p1.y;
var ll = Math.hypot(dx,dy);
var fr = ((l.len - ll) / ll) / 2;
dx *= fr;
dy *= fr;
if(l.p2.fixed){
if(!l.p1.fixed){
l.p1.x -=dx * 2;
l.p1.y -=dy * 2;
}
}else if(l.p1.fixed){
if(!l.p2.fixed){
l.p2.x +=dx * 2;
l.p2.y +=dy * 2;
}
}else{
l.p1.x -=dx;
l.p1.y -=dy;
l.p2.x +=dx;
l.p2.y +=dy;
}
}
// locate the poitn closest to x,y (used for editing)
function closestPoint(x,y){
var min = 40;
var index = -2;
for(var i = 0; i < points.length; i ++){
var p = points[i];
var dist = Math.hypot(p.x-x,p.y-y);
p.mouseDist = dist;
if(dist < min){
min = dist;
index = i;
}
}
return index;
}
function constrainPoints(){
for(var i = 0; i < points.length; i ++){
constrainPoint(points[i]);
}
}
function movePoints(){
for(var i = 0; i < points.length; i ++){
movePoint(points[i]);
}
}
function constrainLines(){
for(var i = 0; i < lines.length; i ++){
constrainLine(lines[i]);
}
}
function drawLines(){
// draw back images first
for(var i = 0; i < lines.length; i ++){
if(lines[i].image !== 1){
lines[i].draw();
}
}
for(var i = 0; i < lines.length; i ++){
if(lines[i].image === 1){
lines[i].draw();
}
}
}
// Adds the block at end of chain
function createBlock(x,y){
var i = chainImages[2];
var w = i.width;
var h = i.height;
var p1 = addPoint(x,y+16,0,0,8);
var p2 = addPoint(x-w/2,y+27,0,0,1);
var p3 = addPoint(x+w/2,y+27,0,0,1);
var p4 = addPoint(x+w/2,y+h,0,0,1);
var p5 = addPoint(x-w/2,y+h,0,0,1);
var p6 = addPoint(x,y+h/2,0,0,1);
addLine(p1,p2);
addLine(p1,p3);
addLine(p1,p4);
addLine(p1,p5);
addLine(p1,p6,2);
addLine(p2,p3);
addLine(p2,p4);
addLine(p2,p5);
addLine(p2,p6);
addLine(p3,p4);
addLine(p3,p5);
addLine(p3,p6);
addLine(p4,p5);
addLine(p4,p6);
addLine(p5,p6);
var p7 = addPoint(x,y + 16-(chainImages[0].width-ballRad * 2),0,0,ballRad);
addLine(p1,p7,1);
}
var lastChainLink = 0;
function addChainLink(){
var lp = points[points.length-1];
addPoint(lp.x,lp.y-(chainImages[0].width-ballRad*2),0,0,ballRad);
addLine(points[points.length-2],points[points.length-1],lastChainLink % 2);
lastChainLink += 1;
}
function loading(){
ctx.setTransform(1,0,0,1,0,0)
ctx.clearRect(0,0,w,h);
ctx.fillStyle = "black";
ctx.fillText("Loading media pleaase wait!!",w/2,30);
if(chainImages.every(image=>image.complete)){
doSim = runSim;
}
}
var onResize = function(){ // called from boilerplate
blockAttached = false;
lines.length = 0; // remove all lines and points.
points.length = 0;
lastChainLink = 0; // controls which chain image to use next
holdingCount = 0;
holding = -1;
mouse.buttonRaw = 0;
}
var blockAttached = false;
var linkAddSpeed = 20;
var linkAddCount = 0;
var holding = -1; // the index of the link the mouse has grabbed
var holdingCount = 0;
function runSim(){
ctx.setTransform(1,0,0,1,0,0)
ctx.clearRect(0,0,w,h);
ctx.fillStyle = "black";
if(points.length < 12){
ctx.fillText("Right mouse button click hold to add chain.",w/2,30);
}
if(holdingCount < 180){
if(mouse.buttonRaw & 1 && holding === -2){
ctx.fillText("Nothing to grab here.",w/2,66);
}else{
ctx.fillText("Left mouse button to grab and move chain.",w/2,66);
}
}
if(mouse.buttonRaw & 4){
if(linkAddCount > 0){ // delay adding links
linkAddCount-=1;
}else{
if(!blockAttached ){
createBlock(mouse.x,mouse.y)
blockAttached = true;
}else{
addChainLink(mouse.x,mouse.y);
}
linkAddCount = linkAddSpeed;
}
}
if(points.length > 0){
if(mouse.buttonRaw & 1){
if(holding < 0){
holding = closestPoint(mouse.x,mouse.y);
}
}else{
holding = -1;
}
movePoints();
constrainPoints();
// attach the last link to the mouse
if(holding > -1){
var mousehold = points[holding];
mousehold.ox = mousehold.x = mouse.x;
mousehold.oy = mousehold.y = mouse.y;
holdingCount += 1; // used to hide help;
}
for(var i = 0; i < stiffness; i++){
constrainLines();
if(holding > -1){
mousehold.ox = mousehold.x = mouse.x;
mousehold.oy = mousehold.y = mouse.y;
}
}
drawLines();
}else{
holding = -1;
}
}
var doSim = loading;
/*********************************************************************************************/
/* Boilerplate not part of answer from here down */
/*********************************************************************************************/
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
function start(x,y,col,w){ctx.lineWidth = w;ctx.strokeStyle = col;ctx.beginPath();ctx.moveTo(x,y)}
function line(x,y){ctx.lineTo(x,y)}
function end(){ctx.stroke()}
function drawLine(l) {ctx.lineWidth = 1;ctx.strokeStyle = "Black";ctx.beginPath();ctx.moveTo(l.p1.x,l.p1.y);ctx.lineTo(l.p2.x,l.p2.y); ctx.stroke();}
function drawPoint(p,col = "black", size = 3){ctx.fillStyle = col;ctx.beginPath();ctx.arc(p.x,p.y,size,0,Math.PI * 2);ctx.fill();}
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c, cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
ctx.font = fontSize + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
e.preventDefault();
}
m.start = function (element) {
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
return mouse;
})();
function update(timer) { // Main update loop
doSim(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1068 次 |
| 最近记录: |