• Runtimes
  • Unity Joystick and Spineboy

  • Изменено
Related Discussions
...

Hello
i use for my private example for mobile your spineboy. With joystick the boy can run and jump. i shoot, the boons and the bullet follow the touch.
After using the joystick the shooting is only horizontal , not in the touch direction. BeginnerInput for touching and the BeginnerView for shooting.

public class SpineboyBeginnerInput

public void OnValidate()
      {            
if (skeletonAnimation == null) skeletonAnimation = GetComponent<SkeletonAnimation>(); if (model == null) model = GetComponent<SpineboyBeginnerModel>(); } #endregion private void Start() { rb = GetComponent<Rigidbody2D>(); //myScreenPos = Camera.main.WorldToScreenPoint(this.transform.position); } public void FixedUpdate() { rb.MovePosition(rb.position + move * speed * Time.fixedDeltaTime); } public void Update() { //Debug.Log("Target_update"); if (model == null) return; float currentHorizontal = Input.GetAxisRaw(horizontalAxis); currentHorizontal = joystick.Horizontal; model.TryMove(currentHorizontal); //if (Input.GetButton(attackButton)) if (Input.touchCount > 0) { //Debug.Log("Input: Touch - " + Input.touchCount); touch = Input.GetTouch(0); } else { return; } if (Input.touchCount < 0 || touch.phase != TouchPhase.Began) { return; } if (IsPointerOverGameObject()) { //Debug.Log("Input: Touch Over UI " + Input.touchCount); } else { model.TryShoot(); } if (Input.GetButtonDown(jumpButton)) model.TryJump(); } public bool IsPointerOverGameObject() { if (EventSystem.current.IsPointerOverGameObject()) { return true; } // Check touches for (int i = 0; i < Input.touchCount; i++) { var touch = Input.GetTouch(i); if (touch.phase == TouchPhase.Began) { if (EventSystem.current.IsPointerOverGameObject(touch.fingerId)) { return true; } } } return false; } } }

a part from public class SpineboyBeginnerView

public void PlayShoot()
         {
         // Play the shoot animation on track 1.
         var shootTrack = skeletonAnimation.AnimationState.SetAnimation(1, shoot, false);
            Debug.Log("PlayShoot: Shooting");
            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();
        
     }

Most likely you didn't set the aim-IK-target bone location to your touch location. At least I see no such code while quickly going over your posting. Please have a look at the code in 4 - Object Oriented Sample that is used to position the aim target at the mouse click position.

You could also make debugging on the target (mobile) device easier by adding a visual clue (spawning a cube, etc) at the last touch position, if you think the wrong position might be used.

Hallo Harald,
in der ersten Phase möchte ich kein "bullet" hinzufügen. Habe zwar den Spawnpoint angelegt aber nicht aktiviert. Habe mir die Lösung von @vhristov angesehen. skeletonAnimation.Update(0) löst das Problem bei mir leider noch nicht. Mit BonFollower muß ich da eigentlich noch nicht arbeiten, da ich kein bullet habe.
Der Spineboy schießt Feuer auch ohne Spawnelement und schießt auch zuerst in die Touchrichtung. Allerdings nach dem Joystick-Move nicht mehr, nur noch horizontal.
Ich verstehe auch Deine prinzipiellen Hinweise auf die Doku "Life cicle", aber daß muß man erst einmal verdauen und die Stellschrauben erkennen.
Muß ich nach jedem Joystickmove die Tracks initialisieren bzw. updaten ? Welche Parameter soll ich mir im Debug ansehen bzw. prüfen ob die Tracks noch nicht leer sind ?

Grüße aus Mecklenburg Vorpommern

superkerni написал

Habe mir die Lösung von @vhristov angesehen. skeletonAnimation.Update(0) löst das Problem bei mir leider noch nicht.

Welche Lösung von @vhristov? Mir fehlt jetzt leider der Kontext.

superkerni написал

Der Spineboy schießt Feuer auch ohne Spawnelement und schießt auch zuerst in die Touchrichtung. Allerdings nach dem Joystick-Move nicht mehr, nur noch horizontal.

Ich sehe leider im Code oben nicht, wie das aim-target auf eine entsprechende touch-position gesetzt wird.

Zum Debuggen würde ich empfehlen, einen BoneFollower auf den aim-ik-target bone zu setzen und einen 3D-Cube auf das selbige GameObject als child anzuhängen. Dann sieht man, ob das aim target an der richtigen Stelle ist.

Muß ich nach jedem Joystickmove die Tracks initialisieren bzw. updaten ?

Ich weiß jetzt nicht was Deine Methode Joystickmove macht, aber generell müssen dafür keine Tracks re-initialisiert werden oder händisch ein update aufgerufen werden. Es sollte sich alles mit den normalen SetAnimation bzw. AddAnimation Aufrufen lösen lassen.

Vielen Dank

Harald написал

Welche Lösung von @vhristov? Mir fehlt jetzt leider der Kontext.

Spineboy Object Oriented Sample and adding Prefab Bullet

Ich sehe leider im Code oben nicht, wie das aim-target auf eine entsprechende touch-position gesetzt wird.

I

Die TouchPosition ist dieselbe wie die mousePosition habe ich ausführlich getestet.

public class SpineboyTargetController : MonoBehaviour
   {
      public SkeletonAnimation skeletonAnimation;
      [SpineBone(dataField: "skeletonAnimation")]
      public string boneName;
      public new Camera camera;
      public Joystick joystick;
      Bone bone;

  void OnValidate()
  {
     if (skeletonAnimation == null) skeletonAnimation = GetComponent<SkeletonAnimation>();
  }
  void Start()
  {
     bone = skeletonAnimation.Skeleton.FindBone(boneName);
  }
  void Update()
  {
     var mousePosition = Input.mousePosition;
     var worldMousePosition = camera.ScreenToWorldPoint(mousePosition);
     var skeletonSpacePoint = skeletonAnimation.transform.InverseTransformPoint(worldMousePosition);
     skeletonSpacePoint.x *= skeletonAnimation.Skeleton.ScaleX;
     skeletonSpacePoint.y *= skeletonAnimation.Skeleton.ScaleY;
     bone.SetLocalPosition(skeletonSpacePoint);
  }
   }

Im Update wird die Position gesetzt. Die Joystick-methode regelt einfach nur den "move" an sich. Die Bewegungen (bones) des Spineboys realisieren die Animationen.

Wenn die aim IK target Position stimmt, dann wird vermutlich etwas an der Logik nicht stimmen, welche die jeweiligen Animationen startet. Hier können wir leider nur den Hinweis geben, entweder mit Debug.Log print statements printf-debugging zu betreiben (debug output wann immer eine Animation gesetzt wird), oder entsprechend breakpoints zu setzen (was u.U. aber weniger gut funktionieren wird, wenn User-Input involviert ist).

19 дней спустя
  • Изменено

Hallo Harald, Du hast hoffentlich Weihnachten gut überstanden.

Zum Thema. Ich habe einige Analysen durchgeführt, aber man endet immer bei der folgenden Befehlsfolge, die nur vor dem ersten move seine Dienste tut.

var mousePosition = Input.mousePosition;
         var worldMousePosition = camera.ScreenToWorldPoint(mousePosition);
         var skeletonSpacePoint = skeletonAnimation.transform.InverseTransformPoint(worldMousePosition);
         skeletonSpacePoint.x *= skeletonAnimation.Skeleton.ScaleX;
         skeletonSpacePoint.y *= skeletonAnimation.Skeleton.ScaleY;
         bone.SetLocalPosition(skeletonSpacePoint);

Leider finde ich keine Anweisung, wie ich den Urzustand der Animation nach dem Move wieder herstellen kann. Das Bone-System wird nicht mehr mit der Gesamtrotation angesprochen. Gibt es Beispiele zur SkeletonRootMotion", wäre das eventuell der bessere Weg ?
Oder liegt das auch nur am "life cicle" ?

Danke Wolfgang

Harald is noch bis zum 10.1 im wohlverdienten Relaxmodus. Ich hab mir euren Verlauf hier angeschaut und versucht zu verstehen, wo ded Fehler liegen könnte. Leider fällt mir dazu auch nichts ein. Die Update() Funktion ist jedenfalls korrekt, so Input.mousePosition korrekt ist.

Zum Zurücksetzen reicht es eigentlich, den aktuellen AnimationState zu updaten und aufs Skeleton anzuwenden. Beides sollte eigentlich in jedem frame von SkeletonAnimation ausgeführt werden. Ausser deine Tracks sind auf additive animation blending gestellt, was ich aber hier nicht sehe.

Danke, werde noch etwas basteln ....
Input.mousepostion ist identisch mit dem Touch.
Wenn ich model.trymove () lahm lege funktioniert es, also hat die Animation konkret damit zu tun ??

Hm, die tryMove() Methode setzt nur den state soweit ich sehe:
https://github.com/EsotericSoftware/spine-runtimes/blob/4.0/spine-unity/Assets/Spine%20Examples/Scripts/Getting%20Started%20Scripts/SpineboyBeginnerModel.cs#L84

Irgend ein Code muss den state auswerten und entsprechend Animationen setzen, die dann die manuelle gesetzte Position des Knochens, der das IK Target ist, irgendwo überschreibt. Wo kann ich aber leider auf Basis des Codes den ich sehen kann nicht sagen.

Hier das Script mit State-Auswertung in "PlayerAction" (unter Verwendung SpineBeginnerView), mein Hauptscript des Players (Leben-Counter und Collisions habe ich hier weggelassen), SpineBeginnerInput und SpineBeginnerModel liegen auch auf dem Player.

void Start()
         {
            bulletPrefab = GetComponent<Rigidbody2D>();
            if (skeletonAnimation == null) return;
            model.ShootEvent += PlayShoot;
            model.StartAimEvent += StartPlayingAim;
            model.StopAimEvent += StopPlayingAim;
            skeletonAnimation.AnimationState.Event += HandleEvent;
            isJumping = false;
            inputESC = false;
            bone = skeletonAnimation.Skeleton.FindBone(boneName);
         }
         void HandleEvent(Spine.TrackEntry trackEntry, Spine.Event e)
         {
            if (e.Data == footstepEvent.EventData)
               PlayFootstepSound();
         }
   public void FixedUpdate()
   {
      rb.MovePosition(rb.position + move * speed * Time.fixedDeltaTime);
   }
   public void Update()
   {
         if (skeletonAnimation == null) return;
         if (model == null) return;

     move.x = joystick.Horizontal;
     move.y = joystick.Vertical * jumpspeed;

     if (Input.GetKeyDown(KeyCode.Escape))
        {
           #if UNITY_EDITOR
              UnityEditor.EditorApplication.isPlaying = false;
           #else
              Application.Quit();
           #endif
        }
        
         if ((skeletonAnimation.skeleton.ScaleX < 0) != model.facingLeft)
        {   // Detect changes in model.facingLeft
           Turn(model.facingLeft);
        }
  var currentModelState = model.state;
  if (previousViewState != currentModelState)
        {
           PlayNewStableAnimation();
        }
     previousViewState = currentModelState;
     isJumping = false;
     
  } // Update Ende

  
  void PlayNewStableAnimation()
  {
        var newModelState = model.state;
        //Animation nextAnimation;

     // Add conditionals to not interrupt transient animations.
        if (previousViewState == SpineBeginnerBodyState.Jumping && newModelState != SpineBeginnerBodyState.Jumping)
        {
           PlayFootstepSound();
        }
        if (newModelState == SpineBeginnerBodyState.Jumping)
        {
           jumpSource.Play();
               skeletonAnimation.AnimationState.SetAnimation(0, jump, true);
        }
        else
        {
           if (newModelState == SpineBeginnerBodyState.Running)
           {
              //nextAnimation = run;
              skeletonAnimation.AnimationState.SetAnimation(0, run, true);
           }
           else
           {
              //nextAnimation = idle;
              skeletonAnimation.AnimationState.SetAnimation(0, idle, true);
           }
        }
  }
  void PlayFootstepSound()
  {
        footstepSource.Play();
        footstepSource.pitch = GetRandomPitch(footstepPitchOffset);
  }

     [ContextMenu("Check Tracks")]
  void CheckTracks()
     {
        var state = skeletonAnimation.AnimationState;
        //Debug.Log(state.GetCurrent(0));
        //Debug.Log(state.GetCurrent(1));
  }

     #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;

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

        boneFollower = GetComponent<BoneFollower>();

     // 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();
        
        // Attack();
  }
  
  public class QuitOnClick : MonoBehaviour
  {      public void Quit()
     {
        //Spiel beenden
        //Debug.Log("Spiel beendet");

     #if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
     #else
           Application.Quit();
     #endif

     }
  }
  public void StartPlayingAim()
     {
        // Play the aim animation on track 2 to aim at the mouse target.
        var aimTrack = skeletonAnimation.AnimationState.SetAnimation(2, aim, true);
        aimTrack.AttachmentThreshold = 1f;
        aimTrack.MixDuration = 0f;
  }
  public void StopPlayingAim()
     {
        var empty2 = skeletonAnimation.state.AddEmptyAnimation(2, 0.5f, 0.1f);
        empty2.AttachmentThreshold = 1f;
  }
  public void Turn(bool facingLeft)
     {
        skeletonAnimation.Skeleton.ScaleX = facingLeft ? -1f : 1f;
        // Maybe play a transient turning animation too, then call ChangeStableAnimation.
  }
     #endregion
     #region Utility
  public float GetRandomPitch(float maxPitchOffset)
     {
        return 1f + Random.Range(-maxPitchOffset, maxPitchOffset);
     }
     #endregion
  }

"SpineboyBeginerInput"

public void OnValidate()
      {            
if (skeletonAnimation == null) skeletonAnimation = GetComponent<SkeletonAnimation>(); if (model == null) model = GetComponent<SpineboyBeginnerModel>(); } #endregion private void Start() { rb = GetComponent<Rigidbody2D>(); bone = skeletonAnimation.Skeleton.FindBone(boneName); } public void FixedUpdate() { //rb.MovePosition(rb.position + move * speed * Time.fixedDeltaTime); } public void Update() { if (model == null) return; currentHorizontal = joystick.Horizontal; model.TryMove(currentHorizontal); mousePosition = Input.mousePosition; //Touch touch = Input.GetTouch(0); if (Input.touchCount > 0) { //Debug.Log("Input: Touch - " + touch.position); touch = Input.GetTouch(0); dz++; PlayerPrefs.SetInt("Touches", dz); } else { return; } if (Input.touchCount < 0 || touch.phase != TouchPhase.Began) { return; } if (IsPointerOverGameObject()) { // keine shooting } else { mousePosition = Input.mousePosition; worldMousePosition = camera.ScreenToWorldPoint(mousePosition); var skeletonSpacePoint = skeletonAnimation.transform.InverseTransformPoint(worldMousePosition); skeletonSpacePoint.x *= skeletonAnimation.Skeleton.ScaleX; skeletonSpacePoint.y *= skeletonAnimation.Skeleton.ScaleY; bone.SetLocalPosition(skeletonSpacePoint); model.TryShoot(); } } // regelt den Touch über dem Joystick public bool IsPointerOverGameObject() { if (EventSystem.current.IsPointerOverGameObject()) { return true; } // Check touches for (int i = 0; i < Input.touchCount; i++) { var touch = Input.GetTouch(i); if (touch.phase == TouchPhase.Began) { if (EventSystem.current.IsPointerOverGameObject(touch.fingerId)) { return true; } } } return false; } }

Da spielt sich ganz schön was ab 🙂 Ohne das ausführen zu können tu ich mir schwer das auf Sicht zu debuggen. Ich habe aber eine Vermutung.

Du hast StartPlayingAim() und StopPlayingAim(). Die manipulieren Track 2 des AnimationState und sind dafür verantwortlich, dass die Gun dem Cursor folgt (oder eben nicht mehr). In StartPlayingAim() setzt du die aim Animation auf looping, d.h. Spineboy aimed bis die Animation vom Track gelöscht wird. Soweit so gut.

Aber in PlayShoot() manipulierst du Track 2 ebenfalls mit:

var aimTrack = skeletonAnimation.AnimationState.SetAnimation(2, aim, false);

Hier wird die Animation nur einmal abgespielt ohne looping. Ist die Animation beendet, zielt Spineboy nicht mehr.

Ich vermute PlayShoot() und StartPlayingAim() kommen sich in die Quere. PlayShoot() "gewinnt" dabei irgendwann, und Spineboy zielt daher nicht mehr auf den Cursor. Warum genau, kann ich wie gesagt in Ermangelung von Debug Möglichkeiten leider nicht sagen.

7 дней спустя

Danke,
ich werde mal die Stati debuggen ... so habe in alle Richtungen getestet, sogar kurzfristig Joystick auf Tastatur umgestellt. Der Shoot findet nach wie vor nur beim ersten Shooting ,inkl. der Armanimationen, das Cursor-Ziel. Nach einer Bewegung ist die Zielerfassung weg.
Intderessant war. Es erscheint beim Shooting ein kleines rotes Fadenkreuz. Beim Start ist es dort wo die Mouse ist, aber nach einem Move ist es immer kurz vor der gun-Mündung (auf dem Bildschirm ca. 3cm) und bleibt dort.

Welche Anweisung stellen den Basiszustand für die Animationen und Bones her ?

Habt Ihr ein Beispiel, wo das Zielschießen mit Spine-Character immer funktioniert ?

Guten Rutsch Wolfgang


Hat keiner eine Idee ?

Soeey, ich muss deine Antwoet übersehen haben! SetToSetupPose() stellt den Basiszustand her. Die kannst du im Event SkeletonAnimation.BeforeApply aneenden. Aber die sollte absolut nicht notwendig sein.

ich kann leider ohne ein Demoprojekt nicht sagen, was da schief läuft. Über die Examples im spine-runtimes repository hinaus haben wir nix. Du hast dein Projekt ja darauf aufgesetzt. Drum wäre es einfacher für uns, wenn wir deine Modifikationen im Ganzen, lokal sehen koennen.

Habe jetzt beim BoneFollower (auf bulletSpawn) als Bone "gun-tip" ausgewählt. Die Bullets starten jetzt richtig von der Pistole und geht in Touchrichtung, aber der Spineboy reagiert nicht !

Problem mit der Animation für die shoot direction gelöst: Vor den Animationen die Tracks bereinigt !! 😃
skeletonAnimation.AnimationState.ClearTracks();

public void PlayShoot()
         {
            skeletonAnimation.AnimationState.ClearTracks();   

     // Play the shoot animation on track 1.
        var shootTrack = skeletonAnimation.AnimationState.SetAnimation(1, shoot, false);
        //Debug.Log("PlayShoot: Shooting");
        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();
           
        Attack();
     }

Ohhhhh. 🙂

Spät aber doch nun auch von mir ein gutes neues Jahr! 🙂 Freut mich sehr, dass Du das Problem beheben konntest, Wolfgang!

Danke Harald und hübsch gesund bleiben 🙂