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

Other articles

Bound Pitching / Batting

Catch

There is not much difficulty in implementing the catching part. The basic function for the time being is to pick up the `ProjectileHitEvent``` and move to the judgment when the ball hits the player. In the case of SnowballGame, if you have an item called "grab" off-hand, you can catch the ball unconditionally. At the time of catching, the ball of the entity is removed and the ball is added to the inventory of the catching player. Surprisingly, this alone requires some tips for catching, such as when the ball and the player do not call `ProjectileHitEvent``` when the ground ball is low or the timing is just to bounce to the ground. There is a scene that becomes. (If an outfielder charges with a dash toward a ground ball that crawls on the ground, there is a decent chance of falling behind. Real is real, and for some people it was a specification that evokes trauma.)

Task

It's not as troublesome as calling it a problem, but there are still some unsatisfactory parts in reproducing baseball with this specification. First, there are no catching mistakes (errors) such as hitting a ball. Regarding this, you can make it so that an error occurs with a random probability when the event is called, but then it may feel unreasonable because the player cannot control the error occurrence probability. I want to design it so that the skill affects the quality of play to some extent.

The other is that the difficulty of catching the ball itself is too high. In Minecraft, the player's hit box is basically in a state where the hand is lowered, so with this specification, the concept of "reaching a ball to catch" does not exist, so to speak, for all hits It will be required to "wrap around the front" and catch the ball. Therefore, it is very common for a fast ground ball to pass through the infield, and the types of hit balls that can be caught are very limited until it becomes possible to guess how the hit ball will fly at the moment of hitting with some playing experience. It had been done. In addition, the effect of "not being able to extend the arm" was more pronounced in fly catching in the outfield defense, and there were quite a lot of cases where fly catching failed even when the ball was almost at the drop point.

solution

This is a very simple story, and I made it possible to catch the ball by right-clicking in a certain area near the ball. The detection that a right click has been made is `PlayerInteractEvent``` and ```event.getAction ()` is Action.RIGHT_CLICK_AIR or `RIGHT_CLICK_BLOCK```. You can do this by determining, so make sure that the player has a grab in the off-hand and nothing in the main hand (to avoid malfunction). In addition, by acquiring the distance between the ball and the player within this fixed range, it can be used as an index indicating "bad posture" at the time of catching the ball. That is, the larger this value is, the more likely it is that an "error" will occur. Play skill comes out in the part of trying to catch the ball at the timing when the distance between you and the ball is the closest (or trying to hold the ball at an earlier timing while incorporating the possibility of mistakes to get the batter out) There is room, and the hit judgment is simply expanded a little, so even if the movement is a little slow, the range that can be out has expanded. In SnowballGame, when a right-click is performed, if the ball is in the rectangular parallelepiped of 3 * 4 * 3 from the player's eyes, the catch is judged, and from `Math.Random * 8``` If the distance to the ball is small, the ball is caught successfully, and if it is larger than that, the ball is missed.

tryCatch


//tryCatch(player, player.getEyeLocation(), new Vector(3,4,3), 8)Called in the form of
//The return value is boolean, thinking that there is a usage method that is more convenient if you know the success or failure of catching.
public static boolean tryCatch(Player player, Location from, Vector range, double rate){
		Collection <Entity> nearByEntities = player.getWorld().getNearbyEntities(from, range.getX(), range.getY(), range.getZ());
		Inventory inventory = player.getInventory();
		for(Entity ball : nearByEntities){
			if(ball.getType() == EntityType.SNOWBALL && Util.isBall(ball)){
				if(Math.random() * rate > from.distance(ball.getLocation())){
					ball.remove();
					ItemStack itemBall = Util.getItemBall();
                    //If there is room in the inventory, drop it in the inventory, otherwise drop it as an item
					if(inventory.containsAtLeast(itemBall,1) || inventory.firstEmpty() != -1){
						player.getInventory().addItem(itemBall);
                        return true;
					}else{
						ball.getWorld().dropItem(ball.getLocation(), itemBall);
					}
				}else{
					ball.setGravity(true);
                    //The ball you play adds a random vector while randomly multiplying the velocity
					ball.setVelocity(ball.getVelocity().multiply(Math.random()).add(Vector.getRandom().multiply(0.3)));
				}
			}
		}
		return false;
}

Tips Here are some of the insights I've gained during the development of Snowball Game and related plugins that could help me create other sports (or otherwise) plugins.

Non-random, non-rotating implementation

Knuckleballs in baseball, and non-rotating balls, which are called floaters or non-rotating balls in other sports, are extremely susceptible to the surrounding environment such as the atmosphere, and their strength is that the trajectory becomes irregular. It is a ball type. I don't know how it is treated in other sports, but in baseball, the knuckleball is a "modern magic ball" for which there is still no definitive strategy, and if it is thrown with high accuracy. It is not an exaggeration to say that it is a lump of romance, such as a former fielder becoming a first-class pitcher and being the best pitcher in the world in the heyday if a knuckle of about 130 kg is thrown. Because it is a changing ball full of such dreams, I definitely want to implement it in a baseball plug-in, but unfortunately, the principle introduced in Pitching is for knuckles. The change is undescriptable.

The change in knuckle cannot be explained by the lift due to the Magnus effect used here, and most of the change is caused by "the baseball ball is not a ball". (To tell the truth, irregular changes occur even in a true sphere, but explanations are omitted here.) Therefore, the direction and amount of changes cannot be described by simple formulas. The calculation of the orbit requires a fairly elaborate simulation, It doesn't seem to be something that an amateur can reach.

In other words, it is necessary to give up from the beginning to firmly reproduce the trajectory of a non-rotating ball. On top of that, the issue is how close the trajectory of the image can be to the actual knuckle.

When implementing irregular changes, the easiest thing to come up with is "add a random vector every tick to the ball". Certainly this can literally cause irregular changes, is easy to implement and looks great as a knuckle expression. However, when you actually throw the "knuckleball" that you implemented in that way, you will immediately notice a sense of discomfort. A knuckle ball in reality is a ball that shows "sway" about 1 to 3 times from the pitcher to the catcher's mitt, and it is completely unpredictable which way it will eventually turn. It can be said that a change in trajectory, such as a slow turn toward the batter and a sudden drop, is a necessary requirement for the expression of a knuckleball.

On the other hand, this "random vector" knuckleball does make unpredictable changes, but it causes too many "shakes". Basically, it progresses while changing in various directions, but in the end, it often cancels out the changes in the opposite directions and comes to a position where it is easy to predict. In the worst case, the ball may become a ball that is not much different from a straight that just trembles in the air, and the original difficulty of hitting a knuckle cannot be expressed with this. javaw_20180604_233815F.gif Random is set to 0.02 as a "standard of the magnitude of random change", and the vector obtained by `Vector.getRandom (). Subtract (Vector.getRandom ()). Multiply (2 * random)` is used as a ball. The one added is the gif above. You can certainly see the movement from side to side, but they cancel each other out and the result is the same ball that hardly changes.

In order to express the knuckle-likeness, it must be in a form that changes in one direction to some extent while changing the direction several times. However, the method of setting some change patterns and branching at random ... feels a little redundant.

In SnowballGame, the method of "starting from a random point on the sin curve and advancing by a random size" is repeated every tick for each of x, y, z, and the obtained vector is added to the ball. This was expressed using.

The change in the orbit is simply whether x, y, and z are + ○○ or-△△, respectively. And the content of the change that is being sought now is that "it will be + or-continuously for an irregular period, and conversely-or + will begin to be + at an irregular timing." Then, in order to express this, it is sufficient to have a function that goes back and forth between + and-in a certain range of values with a certain period as the value of the argument increases. If you add a fractional (random) value of that cycle to each tick argument, it should behave as expected. Of all the functions with these properties, the easiest function I know was sin (or cos). It goes back and forth continuously in the range of 1 to -1 at regular intervals. It does not suddenly become a large value or become infinite. (As far as I know) Of course, it is used simply because it is "convenient", and there is no justification for the usage of this sin. (Speaking of which, the seam of the ball may look like a sine curve.) It's a similar non-physical and dirty way that some science people may have urticaria, but this is how it gets. The knuckle that was made has become a decent knuckle. javaw_20180605_000615F.gif The difference is obvious when compared with the "random vector formula" above. This time, I had a hard time throwing it into the strike zone, but the amount of change was such that I could compete with the batter. The code to implement this knuckle is as follows.

Knuckle


import org.bukkit.Particle;
import org.bukkit.entity.Projectile;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
//This class uses the same Runnable as the changing sphere during actual implementation.
public class BallMovingTask extends BukkitRunnable {
    private Projectile ball;
    private double random = 0;
    private double x,y,z;

	public BallMovingTask(Projectile ball, double random) {
        this.ball = ball;
        this.random = random;
        if(random != 0){
        	this.x = Math.random() * 2 * Math.PI;
        	this.y = Math.random() * 2 * Math.PI;
        	this.z = Math.random() * 2 * Math.PI;
        }
	}
    @Override
    public void run() {
    	if(ball.isDead()){
    		this.cancel();
    	}
    	Vector velocity = ball.getVelocity();
    	if(random != 0){
            //Approximately 15 from mound to batter-It is considered to be 20 ticks, so every tick 0~0.3 if you proceed 1~Should "shake" 3 times
        	this.x += Math.random() * 0.3;
        	this.y += Math.random() * 0.3;
        	this.z += Math.random() * 0.3;
        	Vector toAdd = new Vector(Math.sin(x), Math.sin(y), Math.sin(z));
        	toAdd.multiply(random);
        	velocity.add(toAdd);
    	}
    	ball.setVelocity(velocity);
    }

}

If you make it possible to set this random amount of change in the config, it will be adjusted so that it is easy to handle for each player.

Unique gauge using Boss Bar

Regarding baseball batting, there were characteristics such as "stopping your foot" and "the accuracy of the angle and direction is not so important", so it was possible to express it by using a bow that originally had a gauge. , When expressing other sports, it is possible that the bow is inconvenient. For example, when expressing soccer, gauge-type strength settings are used in famous games for the shoot part, but expressing this with a bow is not a very good method. There is a problem that the movement speed of the bow drops significantly while pulling, and the speed at which the gauge accumulates is a little too slow for the expression of the shoot. Therefore, in such a case, a gauge-like UI with a higher degree of freedom is required.

Therefore, Bossbar can be used as one effective means. Originally it was used only to display the remaining health of bosses such as ender dragons and withers, but as you can see from the documentation, it doesn't actually require any entity to call the constructor. Rather, in order to reflect the physical strength of the entity, it is necessary to write the increase / decrease process on its own, and conversely, it can be used freely in addition to displaying the physical strength. The value to be displayed can be directly rewritten between 0-1 by using `Bossbar.setProgress (double)`, so if you write Runnable and rewrite each tick value, it will increase or decrease at any speed. You can create a gauge to do. Of course, there are no disturbing side effects like when using a bow.

There is a golf game plug-in called OutOfBounds that runs with Snowball Game as a prerequisite plug-in in my own plug-in, but in this "easy mode" A strength gauge using BossBar was adopted for mounting. A demo movie (gif) is posted on the Spigot page, so please check it if you like.

In OutofBounds

--If you left-click with the item "Handicap" on your head, the Boss Bar that fills up with 20 ticks will be displayed. --If you left-click again in the displayed state, "swing" will be performed with the strength corresponding to the accumulation condition of the gauge at that time.

The specification is that the hit part of the Snowball Game is used as it is to judge whether the swing hits the ball and how it hits.

To express this, the event listener

PlayerInteract


if(e.getAction() == Action.LEFT_CLICK_AIR || e.getAction() == Action.LEFT_CLICK_BLOCK){
	e.setCancelled(true);
	if(player.hasMetadata("taskID") && player.hasMetadata("bar")){
		int isRight = 1;
		if(player.getMainHand() == MainHand.LEFT){
			isRight = -1;
		}
		Location loc = player.getLocation();
		loc.setYaw(loc.getYaw() - 90 * isRight);
		//To the left from the line of sight for simplification(Right-handed)Assuming a swing at 90 degrees
		Vector swing = loc.getDirection().setY(0).normalize();
		BossBar powerBar = (BossBar)player.getMetadata("bar").get(0).value();
		//On the extension of the line of sight 1.Assuming that the club head passes at the position of 8m
		Location start = player.getEyeLocation().add(player.getEyeLocation().getDirection().normalize().multiply(1.8));
		//Snowball Game hit judgment function
		// tryHit(Batter,Bat position,Collision detection xyz,How the bow is pulled,Bat momentum,Coefficient of restitution of the ball)
		SnowballGameAPI.tryHit(player, start, new Vector(1.2,1.2,1.2), (float)powerBar.getProgress(), 1.3, swing, 1);
			Bukkit.getScheduler().cancelTask(player.getMetadata("taskID").get(0).asInt());
			powerBar.removeAll();
			powerBar.setProgress(0.001);
			player.removeMetadata("taskID", plugin);
			return;
		}
		BossBar powerBar = null;
		//It seemed undesirable to create a BossBar every time you swing, so each player's first swing
		//The one created only once is given as metadata and reused.
		if(player.hasMetadata("bar")){
			powerBar = (BossBar)player.getMetadata("bar").get(0).value();
			powerBar.setColor(BarColor.GREEN);
		}else{
			powerBar = Bukkit.getServer().createBossBar("Power", BarColor.GREEN, BarStyle.SOLID);
		}
		powerBar.setProgress(0);
		powerBar.addPlayer(player);
		BukkitTask task = new PowerBarTask(powerBar, player).runTaskTimer(plugin, 0, 1);
		player.setMetadata("taskID", new FixedMetadataValue(plugin, task.getTaskId()));
		player.setMetadata("bar", new FixedMetadataValue(plugin, powerBar));
}

The corresponding Runnable is

PowerBarTask


import org.bukkit.boss.BarColor;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;

public class PowerBarTask extends BukkitRunnable {
	BossBar bar;
	Player player;
	public PowerBarTask(BossBar bar, Player player){
		this.bar = bar;
		this.player = player;
	}
	@Override
	public void run() {
		if(bar.getProgress() == 1){
			bar.setColor(BarColor.GREEN);
			bar.removeAll();
			this.cancel();
			player.removeMetadata("taskID", Swing.getPlugin(Swing.class));
        //We are quickly making adjustments for the play feeling described later.
		}else if(bar.getProgress() > 0.999){
			bar.setProgress(1);
		}else if(bar.getProgress() > 0.95){
			bar.setProgress(0.9999);
			bar.setColor(BarColor.RED);
		}else{
			bar.setProgress(bar.getProgress() + 0.05);
		}
	}

}

It has become. Please note that the task ID must be retained by metadata or other methods because it is necessary to cancel the task from outside the task, and when increasing progress in Runnable, if it is increased as it is, calculation error will occur. Since it does not become 1, it may be necessary to make a slightly quirky judgment. Also, if you hide the Boss bar immediately when the progress should be 1, it will be difficult to "stop the gauge aiming for a full tank", and from the player's point of view, the gauge will disappear before it is full. It is easy to get a feeling of being sick. It will be easier to play if you give it a margin of one to several ticks.

The original gauge using this Boss Bar is not limited to the category of sports reproduction such as soccer, but it is thought that it can be applied in various ways to plug-ins of other genres such as "reservoir attack". Why don't you try it if you like?

In addition to this, memorandum such as passage judgment using Vector.isInAABB (without Projectile HitEvent) and (about) drop point prediction used to create the "HR confirmation effect" plug-in There are some tricks that may be written in, but since there are few methods of application other than baseball, I will end here.

in conclusion

Perhaps it's just my lack of awareness, but when I took a quick look before making this Snowball Game, I was surprised that the number of sports mods and plugins was extremely small. (As for mods, I was able to see one set of several sports, but there was almost no plug-in.) I've seen projects where stadiums and stadiums were built when building modern cities, and projects that reproduce real-life stadiums in Minecraft, but the part of actually using the architecture has been so far. It seems that he didn't make much progress. (Originally, Snowball Game also released what it made for its own resident server where dozens of stadiums are built in the world.)

When it comes to recreating sports within Minecraft, most sports are more fun to do with multiple people than with one person, so it's a big deal to be offered in the form of plugins rather than mods. I personally think it makes sense. I would be honored if you could make use of the knowledge I wrote here when other developers who want to create sports plug-ins appear in the future.

Recommended Posts

Similar non-physics used to express baseball on Minecraft and implementation in Spigot (catch / Tips)
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 (catch / Tips)
Similar non-physics used to express baseball on Minecraft and implementation in Spigot (Pitching / Battering)
Deep Learning from scratch-Chapter 4 tips on deep learning theory and implementation learned in Python