• Unity
  • Spineboy Object Oriented Sample and adding Prefab Bullet

  • Modificato
Related Discussions
...

Hello, I am a beginner to Unity and Spine and have been learning using Spineboy. I am using the Objected Oriented Example where Spineboy can move, jump, aim and shoot. After studying this code, I have decided to add a shoot controller script which simply generates bullet prefab when the shoot event is detected. This shoot controller uses the transform position of a "FirePoint" object which is using the bonefollower script to update the position transform based on the "gun-tip" bone.

Everything works well except that when I begin shooting or the first shot is always originating in the wrong location. Meaning the first shot originates from the location of the gun tip when it is in the idle animation location. If I continue to hold the mouse down, subsequent shots(starting from the second) are coming from the correct gun-tip point.

It seems that my shoot prefab starts before the gun-tip is in the right location from the aim and shoot animations. I am not sure how to resolve this? Please help as I a have been stuck on this for days.

Thank you,

I'm afraid we can't help you without seeing any of your code.

In general it should help if you enter Play mode in Unity with hitting Pause first, then you can go forward frame-by-frame using the "Next Frame" button and check where the Transform of your BoneFollower is located in each frame in the Scene view. If it is located where it should be, then your problem lies with your bullet spawning logic code. In general we can only recommend to get familiar with Unity before adding one more layer of complexity by using Spine.

Hello, Thank you for the reply. My apologies for not being specific.

I am adding a bullet prefab and shoot function to the Spineboy Object Oriented Example. I have added a ShootBullet() function to the PlayShoot() which is called when the ShootEvent occurs. The firingPoint position is an empty object which is using the bonefollower script to track "gun-tip".

To debug, I have put Debug.Log(firingPoint.position) as the last line in the ShootBullet() function.
With the mouse location held in place on screen and upon holding down the mouse button, the firingPoint position reads:

First Bullet: (-3.2, -1.8, 0.0)
Second Bullet and beyond: (-3.3, -1.0, 0.0)

The first Bullet firePoint seems to be location of the "gun-tip" when in the idle pose. The Second bullet and beyond fire point is correct.

I am sharing the relevant portions of the code below which is a truncated version to save you some eye strain. (my changes/additions are in bold)

using UnityEngine;
using System.Collections;
using Spine.Unity;

namespace Spine.Unity.Examples {
public class SpineboyBeginnerView : MonoBehaviour {

	#region Inspector
	[Header("Components")]
	public SpineboyBeginnerModel model;
	public SkeletonAnimation skeletonAnimation;
	public float speed;
	public float movement;
	private Rigidbody2D rbody;

	public AnimationReferenceAsset run, idle, aim, shoot, jump;
	public EventDataReferenceAsset footstepEvent;

	[Header("Audio")]
	public float footstepPitchOffset = 0.2f;
	public float gunsoundPitchOffset = 0.13f;
	public AudioSource footstepSource, gunSource, jumpSource;

	[Header("Effects")]
	public ParticleSystem gunParticles;
	#endregion

	SpineBeginnerBodyState previousViewState;

	public Transform firingPoint;
	public GameObject bulletPrefab;
	public Vector2 aimm;
	public float bulletspeed = 15f;

	void Start () {
		if (skeletonAnimation == null) return;
		model.ShootEvent += PlayShoot;
		model.StartAimEvent += StartPlayingAim;
		model.StopAimEvent += StopPlayingAim;
		skeletonAnimation.AnimationState.Event += HandleEvent;
		rbody = GetComponent<Rigidbody2D>();
	}

	void HandleEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
		if (e.Data == footstepEvent.EventData)
			PlayFootstepSound();
	}
	

	#region Transient Actions
	public void PlayShoot () {
		// Play the shoot animation on track 1.
		var shootTrack = skeletonAnimation.AnimationState.SetAnimation(1, shoot, false);
		shootTrack.AttachmentThreshold = 1f;
		shootTrack.MixDuration = 0f;
		var empty1 = skeletonAnimation.state.AddEmptyAnimation(1, 0.5f, 0.1f);
		empty1.AttachmentThreshold = 1f;

		// Play the aim animation on track 2 to aim at the mouse target.
		var aimTrack = skeletonAnimation.AnimationState.SetAnimation(2, aim, false);
		aimTrack.AttachmentThreshold = 1f;
		aimTrack.MixDuration = 0f;
		var empty2 = skeletonAnimation.state.AddEmptyAnimation(2, 0.5f, 0.1f);
		empty2.AttachmentThreshold = 1f;

		gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
		gunSource.Play();
		//gunParticles.randomSeed = (uint)Random.Range(0, 100);
		gunParticles.Play();
		[b]ShootBullet();[/b]
	}

	[b]private void ShootBullet()
	{
		GameObject bullet = Instantiate(bulletPrefab, firingPoint.position, Quaternion.Euler(new Vector3(0f, 0f, 0f)));

		var mousePosition = Input.mousePosition;
		var worldMousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
		aimm = new Vector2(worldMousePosition.x - firingPoint.position.x, worldMousePosition.y - firingPoint.position.y);
		aimm.Normalize();
		bullet.GetComponent<Rigidbody2D>().velocity = aimm * bulletspeed;
		Debug.Log(firingPoint.position);
	}[/b]  

I have attached an image that will help to clarify.

From the posted code I believe the issue you are facing is because you have not given time for the animation to be applied.
I believe you might need to do something like this:

public void PlayShoot () {
    // Play the shoot animation on track 1.
    var shootTrack = skeletonAnimation.AnimationState.SetAnimation(1, shoot, false);
    shootTrack.AttachmentThreshold = 1f;
    shootTrack.MixDuration = 0f;
    var empty1 = skeletonAnimation.state.AddEmptyAnimation(1, 0.5f, 0.1f);
    empty1.AttachmentThreshold = 1f;

// Play the aim animation on track 2 to aim at the mouse target.
var aimTrack = skeletonAnimation.AnimationState.SetAnimation(2, aim, false);
aimTrack.AttachmentThreshold = 1f;
aimTrack.MixDuration = 0f;
var empty2 = skeletonAnimation.state.AddEmptyAnimation(2, 0.5f, 0.1f);
empty2.AttachmentThreshold = 1f;

// Update skeleton to apply animations
skeletonAnimation.Update(0);

gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
gunSource.Play();
//gunParticles.randomSeed = (uint)Random.Range(0, 100);
gunParticles.Play();
ShootBullet();
}

Note the: skeletonAnimation.Update(0);
I am still not sure that this will be enough, as the BoneFollower would still get positioned correctly on the next frame.
I am not sure how your code can be modified to take account of all the Updates() that needs to be called for this to work properly.

Isn't the animation quite jerky with all the MixDuration set to 0?

What I did in my case (which seems to be pretty similar) was:

  • Don't touch the mix duration - this lets the spine to smooth the transition from "idle" to "shooting"
  • In spine the IK for the target is animated from 0 to 100 to follow the bone
  • Use an animation event ("shoot"?) from the spine to notify the code when the animation is "ready" to shoot the bullet

I also manually calculate the bullet spawn position, but I believe you might be fine with the bone follower in most cases.

This way I have the control "when" the bullet shall leave the barrel in spine, allowing me to sync the animation with the bullet (for example: gun recoil, fire exiting from the muzzle, etc) and I know with certainty that the gun is pointing in the right direction. In my case the "aim" and "shoot" animations are one single animation (which does not match with the spine boy if I remember correctly), but I believe you can account for this even with separate animations.

Thanks very much @vhristov for answering! Yes, here the problem is most likely that the animation has not been applied yet. So skeletonAnimation.Update(0); does just that. You will then also need to update the BoneFollower, which is done via calling
boneFollower.LateUpdate(). You will then need to add a public BoneFollower boneFollower; member variable or query it once via GetComponent<BoneFollower >() if it's at the same GameObject.

Please note that you can add triple-backticks before and after code sections to format it properly:

``` 

or surround it with

[code]code goes here

[/code]

@vhristov, @[cancellato]

Thank you so much! That did the trick. I really appreciate the patience and the detailed response as I am rather new at this.
I did have to add both skeletonAnimation.Update(0); and boneFollower.LateUpdate(); for it to work correctly.

Take care and thanks again!

Glad to hear it's working as desired now.

Please also be sure to give the following spine-unity documentation sections a read, as it might help you understanding the update order in the long run:
spine-unity Runtime Documentation: Life cycle
spine-unity Runtime Documentation: SkeletonAnimation Component

Also a very important reference, the Spine API Reference pages, e.g. on SetAnimation and AddAnimation.