[JAVA] Similar non-physics used to express baseball on Minecraft and implementation in Spigot (Pitching / Battering)

Other articles

Bound Catching / Tips

pitching

Although it is a feature that is not so common in other ball games, it is necessary to be able to significantly influence the speed and rotation (change) of the ball at the time of pitching. I don't know if this format is correct, but in Snowball Game, to achieve this, "depending on the name given to the snowball that becomes the ball." A changing ball is thrown. " There is an event called ProjectileLaunchEvent to detect that the ball has been thrown. Use. When this event is called, do ```event.getEntity (). GetShooter ()` `` to check the contents of the throwing player's main hand and set the ball speed to correspond to its name You can change it. (If you want to pitch from off-hand, you can check off-hand if the main hand is not the ball but the off-hand is the ball.)

For the changing sphere, use ``` BukkitRunnable.runTaskTimer (delay, period)``. Normally you would run it with a delay of 0 and a period of 1. You can write to change the velocity of each tick ball by receiving the entity of the thrown ball and the direction and amount of change. However, there are some things to be careful of when determining the direction of change of the ball. The vertical change can be expressed by the fluctuation in the Y direction, but the horizontal change depends on the direction in which the throwing player is facing. From the mound's point of view, the home base may be in the X direction or the Z direction, or even a diagonal stadium, so players can use the changing stadium comfortably in such various variations of the stadium. I have to do it. Therefore, use ``Location.getDirection ()` `` for the direction of horizontal change. All you have to do is set the player's current line of sight as forward.

getHorizontalMove


//move represents the amount of change in the shoot direction per tick when the sign is positive, and the amount of change in the slider direction when the sign is negative.
static Vector getHorizontalMove(Player player, double move, boolean fromMainHand){
    //When throwing to the left, reverse the direction of lateral change
    int modifierWithHand = 1;
    if(player.getMainHand() == MainHand.LEFT && fromMainHand || player.getMainHand() == MainHand.RIGHT && !fromMainHand){
        modifierWithHand = -1;
    }
    Location loc = player.getLocation();
    //Yaw for the direction toward the main hand side+90 ° orientation
    loc.setYaw(loc.getYaw() + 90 * modifierWithHand);
    return loc.getDirection().normalize().multiply(move);
}

By increasing the Yaw of the player's Location by +90 degrees and acquiring the direction in this way, the player's "right" direction can be obtained. After that, you can change the sign of 90 depending on the type of changing ball and the pitching arm. By the way, Snowball Game allows you to set "what kind of name will change what kind of change" in the config file.

problem

There is almost no problem with this implementation regarding pitching, but unlike other baseball games, Minecraft can throw changing balls in any number of directions, so if it is left as it is, it may cause strange behavior. For example, when a four seam (assumed to be set as a ball that changes upward) is thrown directly above, the expected behavior is backward when viewed from the pitcher because the four seam is originally a backspin ball. Although it changes backward, in this implementation, the change in the vertical direction is uniformly defined as the fluctuation related to the Y coordinate, so the behavior is such that it keeps rising straight up and does not fall easily. ..

If this is the only thing, it will not have much effect in the actual game, and it will not be a problem if you dismiss it with "because it is a game" and "because it is a plug-in at most", but the implementation of this changing ball is for hitting the ball. There is a problem when trying to apply. ** The catcher fly doesn't come back. ** ** The detailed method will be described later, but as long as we advocate a baseball plug-in, we naturally want to give the hit ball a change in trajectory such as "elongation" or "slice" depending on the quality of the hit. To achieve this, we want to determine the direction of change at the moment of hitting and use the same Bukkit Runnable used to implement the changing sphere. Then, the fly that should have backspin will simply change to the top of each tick. This means that a catcher fly that should have an extremely strong backspin will simply be a fly that "doesn't fall easily". **This should not be. The catcher fly needs to face the back net side and catch it. ** ** It's too lonely to be unable to express that peculiar difficulty.

solution

A simple knowledge of fluid mechanics was helpful as a solution to this problem. It seems that lift is generated in a cylinder / sphere that moves while rotating in a fluid in a direction proportional to the outer product of the rotation vector (vector expression of rotation) of the motion vector. This is called the Magnus effect. I feel like I often heard it during the heyday of Kyuji Fujikawa. I'm a person who hasn't even taken high school physics, so it's still hard to say that it's a vector cross product. It seems that a ball that rotates clockwise turns toward the middle finger. If you try, it will surely change backward when the backspin ball is moving upward, and it will change forward when it goes up and down. In addition, the amount of change depends on the amount of rotation and the ball speed at that moment. Specifically, assuming that the diameter of the ball is about 73 mm and the atmospheric condition is the standard state, `` `1/8 * Math.PI * Math.PI * 1.205 * Math.pow (73/1000, 3) * velocity.length () * It seems to be about rps``` (reference). (rps is the number of revolutions per second. Note that you often see RPM in TrackMan.) In other words

BallMovingTask


import org.bukkit.entity.Projectile;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

public class BallMovingTask extends BukkitRunnable {
    private Vector spinVector;
    private Projectile ball;
    private int rps;
	public BallMovingTask(Projectile ball, Vector spinVector, int rps) {
        this.ball = ball;
        this.spinVector = spinVector;
        this.rps = rps;
    }
    @Override
    public void run() {
        //Cancel the task if the ball dies after bouncing or hitting
    	if(ball.isDead()){
    		this.cancel();
    	}
    	Vector velocity = ball.getVelocity();
    	if(spinVector.length() != 0){
	    	Vector actualMove = velocity.getCrossProduct(spinVector);
	    	if(actualMove.length() != 0){
	    		actualMove.normalize().multiply(1/8 * Math.PI * Math.PI * 1.205 * Math.pow(73/1000, 3) * velocity.length() * rps);
	    	}
	    	velocity.add(actualMove);
    	}
    	ball.setVelocity(velocity);
    }

}

You can create a Runnable like this, and when you pitch, you can ask for rps and spinVector and pass it. However, Snowball Game emphasizes the feature that "the amount of change per tick and the direction of change can be determined by the config", and "after finding the direction of change as seen from the pitching player based on the numerical value of the config" The outer product of the thrown vector of the ball is taken and used as a rotation vector. ”“ The value of the amount of change is given as the length of the spinVector, and the actualMove is adjusted to that length (so the above formula is not used) ”. It has become. In any case, this will bring the catcher fly back in place and the sliced fly will be more likely to stall. 2018-06-01_18.02.31.png By using this calculation method, the orbit of the home run is no longer a symmetric parabola.

Blow

I'm not familiar with the actual situation at all, but I think the part that gives the most individuality to the implementation in baseball games is the batting part. If you want to reproduce the hit exactly, you need to define the shape of the bat and then determine the collision with the flying ball, but it is quite difficult to implement it in Minecraft. Then, what kind of elements should the player feel that "the batting can be reproduced"? As a requirement to compose batting in baseball

――If you don't match the timing and course, you may miss the ball or hit the ball weakly. ――The direction and height of the hit ball are various. --You can adjust the strength of the swing

I think there is something like that. To meet these requirements, Snowball Game uses a bow as a bat.

Since the bow originally has a gauge of pull strength, it is very easy to adjust the strength. Spigot also has EntityShootBowEvent that can detect when a player shoots a bow. Also, this event can get the entity of the arrow that was supposed to be fired even if event.setCancelled (true)` ``, and this arrow is regarded as a bat as it is Entity.getNearbyEntities (x) By performing, y, z) to determine whether or not there is a ball entity in the return value, the hit determination between the ball and the bat can be performed. Up to this point, it is possible to judge the strength of the swing and the swing / hit. After that, you only have to clear the problem of what kind of ball the ball hitting the bat will be. For this, we decided to use Location.subtract (Location)` `` to find and use the "vector from the arrow that looks like a bat to the position where the ball is". If the ball is above the position where the bat is swung, it will be a fly, and if the ball is outside, it will be an opposite field. In addition, if the ball is behind the ball after a delay, it will be a foul that flies toward the back net. The behavior is almost like a sense.

The strength of the hit ball should be such that the farther the ball is from the arrow, the weaker it is. There are many ways to adjust it, but in Snowball Game, the vector from bat to ball is normalized and then `Math.pow (1.3, -batToBall.length ())` is applied. I'm taking the method. A number greater than or equal to the -x power becomes 1 when x is 0, and as x increases, it continuously decreases and does not become a negative number, so it is difficult to handle unexpected numbers. .. Based on the above,

getBattedBall


//GetBattedBall from EntityShootBowEvent(event.getEntity(), event.getProjectile)Call in the form of
static Projectile getBattedBall(Projectile arrow, float force){
    //The range where the ball and bat hit is uniform for simplification 1.2
    Collection <Entity> nearByEntities = arrow.getNearbyEntities(1.2,1.2,1.2);
    for(Entity entity : nearByEntities){
        if(Util.isBall(entity)){
	        Vector batToBall = entity.getLocation().subtract(arrow.getLocation()).toVector();
	        //3 here.5 is a value that gives a home run with a good feeling. There is no particular basis for deriving this.
	        double speed = Math.pow(1.3, -batToBall.length()) * force * 3.5;
	        Projectile battedBall = (Projectile)entity.getWorld().spawnEntity(entity.getLocation(), EntityType().Snowball);
	        battedBall.setShooter(arrow.getShooter());
            battedBall.setVelocity(batToBall.normalize().multiply(speed));
		    return battedBall;
		}
	 }
	 return null;
}

It will be something like that. After that, you can add seasonings such as "The faster the pitching speed, the easier it is to fly" and "The range that hits the bat makes Y feel smaller".

problem

The first problem with this method may be immediately apparent to those who have read the above pitches carefully. In other words, it is difficult to set the rotation of the hit ball. It may be possible to simulate the state of the ball at the time of collision from the curvature of the bat and the elasticity of the ball, but I do not want to do such a difficult calculation. Since there is a need for an element that can be used to define the rotation of the hit ball, which is the "positional relationship between the bat and the ball," I would like to use this. Then, I would like to take the direction of the hit ball and the outer product of the vector from the bat to the ball in the same way as explained at the end of the pitching, but this is also not possible. This is because the two vectors have the same orientation but differ only in length. The outer product of parallel vectors is always a zero vector. (Same as the theory that a gyroball becomes a vertical slider)

Another problem is that I made a video before, so please take a look. In short, it is not possible to produce a ball that has both a high hitting speed and a high hitting angle. If you hit the ball higher than the position of the arrow, the fly will be higher, but the hitting speed will decrease as the distance increases, and if you hit the arrow closer to the ball, the hitting speed will increase, but the ball will hit at a lower angle. Become. This is the same for ground balls, and it is difficult to create ground balls that pierce the ground at a deep angle and jump high. In other words, the hit ball has become less diverse.

So how do we solve these problems?

Swing trajectory implementation

In SnowballGame, the solution was to define a "swing" vector and add it to the hit vector when hitting. In this way, the vector of the motion of the hit ball points in a different direction from the vector of the ball from the bat, and the outer product of the two vectors can be taken as the rotation vector of the hit ball. In addition, the variety of hit balls can be secured by manipulating the swing trajectory. It would be even better if each batter could have their own personality, such as upper swing and down swing.

The question then is how to define that "swing trajectory". I think that the most similar point in this plug-in is here, but my experience and technical books I read in the past 83% 90% E3% 83% 83% E3% 83% 86% E3% 82% A3% E3% 83% B3% E3% 82% B0% E3% 81% AE% E6% AD% A3% E4% BD% From 93-% E6% 89% 8B% E5% A1% 9A-% E4% B8% 80% E5% BF% 97 / dp / 4583045654), it is said that the swing in baseball consists of the following three exercises. I thought.

--Rotating motion parallel to the ground due to hip rotation --Up and down movement made with shoulder blades --Exercise to extend the arm diagonally backward

This classification is not always correct, but Snowball Game implements swing trajectories based on it.

Of these three, the second one is the most difficult. This movement is the part that is the subject of words that describe the swing, such as "upper level down," and the swing width and fluctuation of the vertical movement are also different for each batter. In other words, there is no trajectory that says "this is the correct answer." However, there is an orbit that can be said to be "ideal" if it pushes through forcible reasoning. It is [Brachistochrone](https://ja.wikipedia.org/wiki/%E6%9C%80%E9%80%9F%E9%99%8D%E4%B8%8B%E6%9B%B2 % E7% B7% 9A). In short, this seems to be an orbit that "reaches the fastest when only gravity acts and moves from a certain point on the plane to a certain point". In other words, if you make the assumption that "the time required for the swing should be as short as possible", it can be said that this is the ideal swing trajectory. Actually, the movement of swinging the bat is not "the movement that only gravity acts", so it is still suspicious, but at least it will not be a problem if it is used as a guide for using it in the game. (The other day, I heard that a special feature "Shohei Ohtani's swing trajectory is a cycloid" was set up in a certain program. This brachistochrone curve seems to be a cycloid, so I thought that it was realistic to some extent. It seems to be okay.)

The brachistochrone curve on the two-dimensional plane seems to be `` `x = a (θ-sinθ), y = a (1-cosθ) ```, so this formula of x is used as the coordinates of X and Z. , You can apply the formula of y to the coordinates of Y. The problem is the direction in which this curve is extended, but this is considered in combination with the third movement above, and it is extended diagonally backward (in the case of a right-angled batter, the direction is rotated 90 degrees to the right from the line of sight). And. So

getBatPosition


//The degree of lateral rotation to be implemented in the future is defined as roll.
//rollDirection is 1 for right-handed hitters,If you are a left-handed batter-It becomes 1.
public static Location getBatPosition(Location eye, double roll , int rollDirection){
	Location eyeLoc = eye.clone();
	eyeLoc.setYaw(eyeLoc.getYaw() - (float)(90 * rollDirection));
	Vector push = eyeLoc.getDirection().setY(0).normalize();
	double theta = Math.abs(roll * 2);
	double x = push.normalize().getX() * (theta - Math.sin(theta));
	double y = -(1 - Math.cos(theta));
	double z = push.normalize().getZ() * (theta - Math.sin(theta));
    return eye.clone().add(x,y,z);
}

Can be written. Using this, give a value from 0 to π to roll, and execute the loop of spawning particles at the obtained position 20 times, it will look like this. 2018-06-01_17.01.18.png

At this point, the second motion, horizontal rotation, should be implemented. To tell the truth, until I started writing here, I used Coordinate Transformation to implement this rotation. While organizing what I was writing and doing, I realized that ** "I just need to change the Yaw" **. When I noticed it was natural, but why didn't I understand it until now ...

getBatPosition


public static Location getBatPosition(Location eye, double roll , int rollDirection){
	Location eyeLoc = eye.clone();
    //↓ I just had to change here ...
	eyeLoc.setYaw(eyeLoc.getYaw() - (float)(90 * rollDirection) - Math.toDegrees(roll));
	Vector push = eyeLoc.getDirection().setY(0).normalize();
	double theta = Math.abs(roll * 2);
	double x = push.normalize().getX() * (theta - Math.sin(theta));
	double y = -(1 - Math.cos(theta));
	double z = push.normalize().getZ() * (theta - Math.sin(theta));
    return eye.clone().add(x,y,z);
}

So, this is what I tried to put out particles in the same way as before. 2018-06-01_18.12.51.png Since the swing starts from the direction shifted 90 degrees to the right, the direction in which the line of sight is facing, that is, the direction that becomes the meet point, is where the roll becomes π / 2. Since theta is twice the absolute value of roll, Y takes the lowest value at that time.

Looking at these particles, it may seem that the swinging position is too far from the body, but this swing trajectory is not used for collision detection, so there is no problem. Only the relationship between the position of the arrow and the ball is used for the hit determination, and this swing trajectory is used only for calculating the force applied from the bat to the ball when the bat hits the ball.

That part is

//Meet the ball at a line of sight and 0 from there.01 It is assumed that the ball and bat were in contact with each other up to the position rotated by radians (this number is unfounded).
Vector batMove = getBatPosition(player.getEyeLocation(), (Math.PI / 2 + 0.01) * rolld, rolld,).subtract(getBatPosition(player.getEyeLocation(), Math.PI / 2 * rolld, rolld,));

It would be nice to feel like.

By-product

By expressing the swing trajectory in this way, the problem of "how to define the upper swing and downswing" can be solved simply. In the above `getBatPosition ()`, there are variables that represent two angles, roll and theta. roll is the horizontal rotation from the swing start position, and theta is the degree of rotation of the circle used to draw the brachistochrone curve. As explained earlier, if theta is equal to twice the roll, the point at which the ball is met is the lowest point on the brachistochrone. Before that point, the orbit is from top to bottom, and if it is behind that point, the orbit is from bottom to top. In other words, I think we can define this as a "level swing".

This means that if there is a point of impact of the ball before this point, it can be expressed as a downswing, and if it is later, it can be expressed as an upper swing. In other words, it can be said that if some value is added to theta, the added value will be the upper swing, and if it is reduced, the reduced amount will be the downswing. That means


public static Location getBatPosition(Location eye, double roll , int rollDirection, double upper){
    Location eyeLoc = eye.clone();
    eyeLoc.setYaw(eyeLoc.getYaw() - (float)(90 * rollDirection - Math.toDegrees(roll)));
    Vector push = eyeLoc.getDirection().setY(0).normalize();
    //If it exceeds 1, the cycloid will go around.
    if(Math.abs(upper) > 1){
        upper = 1 * Math.signum(upper);
    }
    //I want to set upper to a value that can be set in the config, so I set it to a magnification of π for easy understanding.
    double theta = Math.abs(roll * 2) + Math.PI * upper;
    double x = push.normalize().getX() * (theta - Math.sin(theta));
    double y = -(1 - Math.cos(theta));
    double z = push.normalize().getZ() * (theta - Math.sin(theta));
    return eye.clone().add(x,y,z);
}

If you write it like this, the larger the upper value, the easier it is to hit the fly, and the smaller it (the sign is negative), the easier it is to hit the ground ball. By doing this, the player will be able to set in the config "what name of bat should be used for what kind of swing" in the same mechanism as a changing ball. It can be said that the customizability has been improved.

In addition, the force from the bat to the ball calculated under this condition is in the direction of "pulling" for the batter. Therefore, when this is added to the ball, the hit ball in the "pulling" direction is most likely to have a long flight distance. It can be said that a more realistic blow has become possible.

Continued

In the next article, I will write about the catching part and some tips that can be used to implement other ball games.

Recommended Posts

Similar non-physics used to express baseball on Minecraft and implementation in Spigot (Pitching / Battering)
Similar non-physics used to express baseball on Minecraft and implementation in Spigot (bound)
Similar non-physics used to express baseball on Minecraft and implementation in Spigot (catch / Tips)