I am trying to make a basketball shoot game. My issue is with my projected trajectory line. It seems to be pretty accurate when the initial velocity isn't above ~140. I have tried adjusting my formula a ton, I mostly just followed this tutorial: https://www.iforce2d.net/b2dtut/projected-trajectory
Here is the code; I am not sure if something is wrong with my formula or there is something I am missing in regards to Box2D within Flutter Flame (what I'm using to develop this)
import 'dart:async';
import 'package:bball_blast/Background.dart';
import 'package:bball_blast/entities/Hoop.dart';
import 'package:bball_blast/entities/Wall.dart';
import 'package:bball_blast/entities/ball.dart';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:bball_blast/config.dart';
class BBallBlast extends Forge2DGame with PanDetector, HasGameRef<BBallBlast>{
BBallBlast(): super(
gravity: Vector2(0,gravity),
camera: CameraComponent.withFixedResolution(width: gameWidth, height: gameHeight),
);
//vars we need to be visible thoughout entire file
late Ball ball;
double linearImpulseStrengthMult = 12.5;
late Vector2 impulse;
//positional vars
double startPosX = 00;
double startPosY = 00;
late Vector2 startPos;
//Vars for determining how ball should be thrown
late Offset startOfDrag;
late Offset currentDragPos;
late Offset dragBehindBall = Offset.zero;
bool isDragging = false;
bool isShot = false;
@override
FutureOr<void> onLoad() async {
//set startPos of ball
startPos = Vector2(startPosX, startPosY);
//load images into cache
await images.loadAllImages();
//make ballSprite and ball
Sprite ballImg = await loadSprite('ball.png');
ball = Ball(this, Vector2(startPosX, startPosY), ballImg);
//add leftWall and rightWall, and ceiling
Wall wallLeft = Wall(Vector2(camera.visibleWorldRect.topLeft.dx-1, camera.visibleWorldRect.topLeft.dy), 1.0, gameHeight);
Wall wallRight = Wall(Vector2(camera.visibleWorldRect.topRight.dx+1, camera.visibleWorldRect.topRight.dy), 1.0, gameHeight);
Wall ceiling = Wall(Vector2(camera.visibleWorldRect.topLeft.dx-1, camera.visibleWorldRect.topRight.dy-1), gameWidth, 1.0);
//create hoopimg, hoop, and add it
Sprite hoopImg = await loadSprite('hoop.png');
Hoop hoop = Hoop(this, true, hoopImg);
//add components to game
world.addAll([Background(), ball, wallLeft, wallRight, ceiling, hoop]);
debugMode=true;
super.onLoad();
}
//-----------------------INPUT HANDLING (DRAGS)-----------------------
@override
void onPanStart(DragStartInfo info) {
//when user drags screen, store whre it happened and let program know dragging=true
startOfDrag = Offset(info.eventPosition.global.x, info.eventPosition.global.y);
isDragging = true;
isShot = false;
}
@override
void onPanUpdate(DragUpdateInfo info) {
//we get the dragPos, then we get the distance of the drag relative to starting point
//then apply it to our "dragBehindBall" which is given to trajectory drawing
currentDragPos = Offset(info.eventPosition.global.x, info.eventPosition.global.y);
double relX = startOfDrag.dx - currentDragPos.dx;
double relY = startOfDrag.dy - currentDragPos.dy;
dragBehindBall = Offset((relX), (relY));
}
@override
void onPanEnd(DragEndInfo info) {
//make ball move when thrown
ball.body.setType(BodyType.dynamic);
impulse = Vector2(dragBehindBall.dx, dragBehindBall.dy) * linearImpulseStrengthMult;
ball.body.applyLinearImpulse(impulse);
print("SHOT STRENGTH: ${Vector2(dragBehindBall.dx, dragBehindBall.dy) * linearImpulseStrengthMult}");
print("VEL: ${ball.body.linearVelocity}");
print("BALL MASS: ${ball.body.mass}");
//reset necessary vars
isDragging=false;
isShot = true;
dragBehindBall = Offset(startPosX, startPosY);
}
int count = 1;
@override
void update(double dt) {
super.update(dt);
//print("TIMESTEP: $count VEL: ${ball.body.linearVelocity} \n POS: ${ball.getSuperPosition()}");
}
}
import 'dart:async';
import 'dart:ui';
import 'package:bball_blast/BBallBlast.dart';
import 'package:bball_blast/config.dart';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
class Ball extends BodyComponent {
@override
final BBallBlast game;
@override
final Vector2 position;
//points to draw for trajectory
late List<Vector2> points;
//TODO: CHANGE WHEN YOU CHANGE BODY
late double velocityRatio = 1/28.274333882308138;
int steps = 180;
Ball(this.game, this.position, Sprite sprite) : super (
renderBody: false,
//start body as static then set as dynamic when it is shot
bodyDef: BodyDef()
..position = position
..type = BodyType.static
..linearDamping = 0,
fixtureDefs: [
FixtureDef(CircleShape()..radius = 6/2)
..restitution = 0.3
..density = 1
//..friction = 0.5
],
//add our sprite
children: [
SpriteComponent(
sprite: sprite,
size: Vector2 (7, 7),
anchor: Anchor.center,
)
]
);
@override
Future<void> onLoad() async {
super.onLoad();
}
@override
void render(Canvas canvas) {
super.render(canvas);
//draw projected trajectory with info given by drags
if (game.isDragging) {
//we multiply the input by that number as it's the ratio that converts pixel to velocity
Vector2 initialVelocity = Vector2(game.dragBehindBall.dx, game.dragBehindBall.dy) * game.linearImpulseStrengthMult * velocityRatio;
//initialVelocity = _checkVelMax(initialVelocity);
//print(initialVelocity);
//get points to draw projected trajectory
points = trajectoryPoints(initialVelocity, Vector2(game.startPosX, game.startPosY), steps, (1/60)); //60 fps so our dt is 1/60
Paint paint = Paint()
..color = const Color.fromRGBO(244, 67, 54, 1)
..strokeWidth = 0.5
..style = PaintingStyle.stroke;
for (int i = 0; i < points.length - 1; i++) {
canvas.drawLine(
Offset(points[i].x, points[i].y),
Offset(points[i + 1].x, points[i + 1].y),
paint,
);
}
}
}
List<Vector2> trajectoryPoints(Vector2 initialVelocity, Vector2 startPos, int steps, double timeStep) {
List<Vector2> points = [];
for(int i=0; i<steps+1; i++) {
//get the timestep for a certain time in place
double t = i * timeStep;
//multiply velocity by timestep to get appropriate velocity and grav at ceratain time
Vector2 stepVel = initialVelocity * t;
Vector2 stepGrav = Vector2(0.0, 0.5* gravity *(t*t));
//calculate displacement based on stepVel and stepGrav in relation to starting position
double xDisplacement = startPos.x + stepVel.x;
double yDisplacement = startPos.y + stepVel.y + stepGrav.y;
//form our vectors
Vector2 pos = Vector2(xDisplacement, yDisplacement);
points.add(pos);
}
return points;
}
Vector2 getSuperPosition(){
return super.position;
}
}
Thank you for any help someone may provide.
You are hitting the Box2D max velocity, that is why Forge2DGame
is zoomed to 10.0 by default, but that zoom level disappears when you use CameraComponent.withFixedResolution
.