Эх сурвалжийг харах

[cocos2dx] Added IK example, see #1532. Also added SkeletonAnimation::setPreUpdateWorldTransformsListener() and SkeletonAnimation::setPostUpdateWorldTransformsListener().

badlogic 5 жил өмнө
parent
commit
656b08a32e

+ 1 - 0
CHANGELOG.md

@@ -88,6 +88,7 @@
 * Updated to cocos2d-x 3.17.1
 * Added mix-and-match example to demonstrate the new Skin API.
 * Exmaple project requires Visual Studio 2019 on Windows
+* Added `SkeletonAnimation::setPreUpdateWorldTransformsListener()` and `SkeletonAnimation::setPreUpdateWorldTransformsListener()`. When set, these callbacks will be invokved before and after the skeleton's `updateWorldTransforms()` method is called. See the `IKExample` how it can be used.
 
 ### SFML
 * Added mix-and-match example to demonstrate the new Skin API.

+ 5 - 6
spine-cocos2dx/example/Classes/AppDelegate.cpp

@@ -32,11 +32,8 @@
 #include <vector>
 #include <string>
 
-#include "RaptorExample.h"
-#include "BatchingExample.h"
-#include "CoinExample.h"
-#include "SkeletonRendererSeparatorExample.h"
-#include "MixAndMatchExample.h"
+#include "IKExample.h"
+#include <spine/spine-cocos2dx.h>
 #include <spine/Debug.h>
 #include "AppMacros.h"
 #include <spine/SkeletonTwoColorBatch.h>
@@ -44,6 +41,8 @@
 USING_NS_CC;
 using namespace std;
 
+using namespace spine;
+
 DebugExtension debugExtension(SpineExtension::getInstance());
 
 AppDelegate::AppDelegate () {
@@ -112,7 +111,7 @@ bool AppDelegate::applicationDidFinishLaunching () {
 	
 	// create a scene. it's an autorelease object
 	//auto scene = RaptorExample::scene();
-	auto scene = BatchingExample::scene();
+	auto scene = IKExample::scene();
 
 	// run
 	director->runWithScene(scene);

+ 2 - 2
spine-cocos2dx/example/Classes/BatchingExample.cpp

@@ -28,7 +28,7 @@
  *****************************************************************************/
 
 #include "BatchingExample.h"
-#include "SpineboyExample.h"
+#include "IKExample.h"
 
 USING_NS_CC;
 using namespace spine;
@@ -96,7 +96,7 @@ bool BatchingExample::init () {
 
 	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
 	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
-		Director::getInstance()->replaceScene(SpineboyExample::scene());
+		Director::getInstance()->replaceScene(IKExample::scene());
 		return true;
 	};
 	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

+ 116 - 0
spine-cocos2dx/example/Classes/IKExample.cpp

@@ -0,0 +1,116 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include "IKExample.h"
+#include "SpineboyExample.h"
+
+USING_NS_CC;
+using namespace spine;
+
+Scene* IKExample::scene () {
+    Scene *scene = Scene::create();
+    scene->addChild(IKExample::create());
+    return scene;
+}
+
+bool IKExample::init () {
+    if (!LayerColor::initWithColor(Color4B(128, 128, 128, 255))) return false;
+	
+	// Load the Spineboy skeleton and create a SkeletonAnimation node from it
+	// centered on the screen.
+    skeletonNode = SkeletonAnimation::createWithJsonFile("spineboy-pro.json", "spineboy.atlas", 0.6f);
+    skeletonNode->setPosition(Vec2(_contentSize.width / 2, 20));
+    addChild(skeletonNode);
+    
+	// Queue the "walk" animation on the first track.
+	skeletonNode->setAnimation(0, "walk", true);
+	
+	// Queue the "aim" animation on a higher track.
+	// It consists of a single frame that positions
+	// the back arm and gun such that they point at
+	// the "crosshair" bone. By setting this
+	// animation on a higher track, it overrides
+	// any changes to the back arm and gun made
+	// by the walk animation, allowing us to
+	// mix the two. The mouse position following
+	// is performed in the lambda below.
+	skeletonNode->setAnimation(1, "aim", true);
+
+	// Next we setup a listener that receives and stores
+	// the current mouse location. The location is converted
+	// to the skeleton's coordinate system.
+	EventListenerMouse* mouseListener = EventListenerMouse::create();
+	mouseListener->onMouseMove = [this] (cocos2d::Event* event) -> void {
+		// convert the mosue location to the skeleton's coordinate space
+		// and store it.
+		EventMouse* mouseEvent = dynamic_cast<EventMouse*>(event);
+		position = skeletonNode->convertToNodeSpace(mouseEvent->getLocationInView());
+	};
+	_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);
+	
+	// Position the "crosshair" bone at the mouse
+	// location.
+	//
+	// When setting the crosshair bone position
+	// to the mouse position, we need to translate
+	// from "skeleton space" to "local bone space".
+	// Note that the local bone space is calculated
+	// using the bone's parent worldToLocal() function!
+	//
+	// After updating the bone position based on the
+	// converted mouse location, we call updateWorldTransforms()
+	// again so the change of the IK target position is
+	// applied to the rest of the skeleton.
+	skeletonNode->setPostUpdateWorldTransformsListener([this] (SkeletonAnimation* node) -> void {
+		Bone* crosshair = node->findBone("crosshair");
+		float localX = 0, localY = 0;
+		crosshair->getParent()->worldToLocal(position.x, position.y, localX, localY);
+		crosshair->setX(localX);
+		crosshair->setY(localY);
+		crosshair->setAppliedValid(false);
+		
+		node->getSkeleton()->updateWorldTransform();
+	});
+	
+	EventListenerTouchOneByOne* listener = EventListenerTouchOneByOne::create();
+	listener->onTouchBegan = [this] (Touch* touch, cocos2d::Event* event) -> bool {
+        Director::getInstance()->replaceScene(SpineboyExample::scene());
+        return true;
+    };
+	
+    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
+	
+    scheduleUpdate();
+
+    return true;
+}
+
+void IKExample::update (float deltaTime) {
+    
+}

+ 29 - 0
spine-cocos2dx/example/Classes/IKExample.h

@@ -0,0 +1,29 @@
+//
+//  IKExample.hpp
+//  spine-cocos2d-x
+//
+//  Created by Mario Zechner on 28.10.19.
+//
+
+#ifndef _IKEXAMPLE_H_
+#define _IKEXAMPLE_H_
+
+#include "cocos2d.h"
+#include <spine/spine-cocos2dx.h>
+
+class IKExample : public cocos2d::LayerColor {
+public:
+    static cocos2d::Scene* scene ();
+
+    CREATE_FUNC (IKExample);
+
+    virtual bool init ();
+
+    virtual void update (float deltaTime);
+
+private:
+    spine::SkeletonAnimation* skeletonNode;
+	cocos2d::Vec2 position;
+};
+
+#endif // _IKEXAMPLE_H_

+ 8 - 0
spine-cocos2dx/example/proj.ios_mac/spine-cocos2d-x.xcodeproj/project.pbxproj

@@ -205,6 +205,8 @@
 		76AAA4471D1811B000C54FCB /* SpineboyExample.h in Sources */ = {isa = PBXBuildFile; fileRef = 76AAA3BF1D180F7C00C54FCB /* SpineboyExample.h */; };
 		76AAA4571D18132D00C54FCB /* common in Resources */ = {isa = PBXBuildFile; fileRef = 76AAA4521D18132D00C54FCB /* common */; };
 		76AAA4581D18132D00C54FCB /* common in Resources */ = {isa = PBXBuildFile; fileRef = 76AAA4521D18132D00C54FCB /* common */; };
+		76C893B0236715B8009D8DC8 /* IKExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76C893AE236715B8009D8DC8 /* IKExample.cpp */; };
+		76C893B1236715B8009D8DC8 /* IKExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76C893AE236715B8009D8DC8 /* IKExample.cpp */; };
 		76D1BFE02029E35200A0272D /* SkeletonRendererSeparatorExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76D1BFDF2029E35200A0272D /* SkeletonRendererSeparatorExample.cpp */; };
 		76D1BFE12029E37700A0272D /* SkeletonRendererSeparatorExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76D1BFDF2029E35200A0272D /* SkeletonRendererSeparatorExample.cpp */; };
 		76D520E61EB362DD00572471 /* CoinExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 76D520E41EB362DD00572471 /* CoinExample.cpp */; };
@@ -392,6 +394,8 @@
 		76AAA40A1D18106000C54FCB /* spine-cocos2dx.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "spine-cocos2dx.cpp"; path = "../../src/spine/spine-cocos2dx.cpp"; sourceTree = "<group>"; };
 		76AAA40B1D18106000C54FCB /* spine-cocos2dx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "spine-cocos2dx.h"; path = "../../src/spine/spine-cocos2dx.h"; sourceTree = "<group>"; };
 		76AAA4521D18132D00C54FCB /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; sourceTree = "<group>"; };
+		76C893AE236715B8009D8DC8 /* IKExample.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IKExample.cpp; sourceTree = "<group>"; };
+		76C893AF236715B8009D8DC8 /* IKExample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IKExample.h; sourceTree = "<group>"; };
 		76D1BFDE2029E35100A0272D /* SkeletonRendererSeparatorExample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SkeletonRendererSeparatorExample.h; sourceTree = "<group>"; };
 		76D1BFDF2029E35200A0272D /* SkeletonRendererSeparatorExample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SkeletonRendererSeparatorExample.cpp; sourceTree = "<group>"; };
 		76D520E41EB362DD00572471 /* CoinExample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CoinExample.cpp; sourceTree = "<group>"; };
@@ -562,6 +566,8 @@
 				76AAA3BB1D180F7C00C54FCB /* RaptorExample.h */,
 				76AAA3BE1D180F7C00C54FCB /* SpineboyExample.cpp */,
 				76AAA3BF1D180F7C00C54FCB /* SpineboyExample.h */,
+				76C893AE236715B8009D8DC8 /* IKExample.cpp */,
+				76C893AF236715B8009D8DC8 /* IKExample.h */,
 			);
 			name = Classes;
 			path = ../Classes;
@@ -887,6 +893,7 @@
 				763104DD20BC1B5E00927A1E /* Triangulator.cpp in Sources */,
 				763104F320BC1B5E00927A1E /* Animation.cpp in Sources */,
 				76798D1D22A95AB400F77964 /* ConstraintData.cpp in Sources */,
+				76C893B0236715B8009D8DC8 /* IKExample.cpp in Sources */,
 				76AAA40E1D18106000C54FCB /* SkeletonAnimation.cpp in Sources */,
 				76AAA4111D18106000C54FCB /* spine-cocos2dx.cpp in Sources */,
 				763104FC20BC1B5E00927A1E /* PathConstraintData.cpp in Sources */,
@@ -953,6 +960,7 @@
 				763105B420BC1B9700927A1E /* IkConstraintData.cpp in Sources */,
 				763105B520BC1B9700927A1E /* IkConstraintTimeline.cpp in Sources */,
 				763105B620BC1B9700927A1E /* Json.cpp in Sources */,
+				76C893B1236715B8009D8DC8 /* IKExample.cpp in Sources */,
 				763105B720BC1B9700927A1E /* LinkedMesh.cpp in Sources */,
 				763105B820BC1B9700927A1E /* MathUtil.cpp in Sources */,
 				763105B920BC1B9700927A1E /* MeshAttachment.cpp in Sources */,

+ 10 - 0
spine-cocos2dx/src/spine/SkeletonAnimation.cpp

@@ -135,9 +135,11 @@ void SkeletonAnimation::update (float deltaTime) {
 	super::update(deltaTime);
 
 	deltaTime *= _timeScale;
+	if (_preUpdateListener) _preUpdateListener(this);
 	_state->update(deltaTime);
 	_state->apply(*_skeleton);
 	_skeleton->updateWorldTransform();
+	if (_postUpdateListener) _postUpdateListener(this);
 }
 
 void SkeletonAnimation::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t transformFlags) {
@@ -282,6 +284,14 @@ void SkeletonAnimation::setEventListener (const EventListener& listener) {
 	_eventListener = listener;
 }
 
+void SkeletonAnimation::setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
+	_preUpdateListener = listener;
+}
+
+void SkeletonAnimation::setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener &listener) {
+	_postUpdateListener = listener;
+}
+
 void SkeletonAnimation::setTrackStartListener (TrackEntry* entry, const StartListener& listener) {
 	getListeners(entry)->startListener = listener;
 }

+ 7 - 0
spine-cocos2dx/src/spine/SkeletonAnimation.h

@@ -36,12 +36,15 @@
 
 namespace spine {
 
+class SkeletonAnimation;
+
 typedef std::function<void(TrackEntry* entry)> StartListener;
 typedef std::function<void(TrackEntry* entry)> InterruptListener;
 typedef std::function<void(TrackEntry* entry)> EndListener;
 typedef std::function<void(TrackEntry* entry)> DisposeListener;
 typedef std::function<void(TrackEntry* entry)> CompleteListener;
 typedef std::function<void(TrackEntry* entry, Event* event)> EventListener;
+typedef std::function<void(SkeletonAnimation* node)> UpdateWorldTransformsListener;
 
 /** Draws an animated skeleton, providing an AnimationState for applying one or more animations and queuing animations to be
   * played later. */
@@ -87,6 +90,8 @@ public:
 	void setDisposeListener (const DisposeListener& listener);
 	void setCompleteListener (const CompleteListener& listener);
 	void setEventListener (const EventListener& listener);
+	void setPreUpdateWorldTransformsListener(const UpdateWorldTransformsListener& listener);
+	void setPostUpdateWorldTransformsListener(const UpdateWorldTransformsListener& listener);
 
 	void setTrackStartListener (TrackEntry* entry, const StartListener& listener);
 	void setTrackInterruptListener (TrackEntry* entry, const InterruptListener& listener);
@@ -119,6 +124,8 @@ protected:
 	DisposeListener _disposeListener;
 	CompleteListener _completeListener;
 	EventListener _eventListener;
+	UpdateWorldTransformsListener _preUpdateListener;
+	UpdateWorldTransformsListener _postUpdateListener;
 
 private:
 	typedef SkeletonRenderer super;

+ 0 - 4
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/IKTest.java

@@ -61,10 +61,6 @@ public class IKTest extends ApplicationAdapter {
 		// mix the two. The mouse position following
 		// is performed in the render() method below.
 		state.setAnimation(1, "aim", true);
-		
-		// apply the state once, so we have world
-		// bone positions that we can use.
-		skeleton.setToSetupPose();
 	}
 
 	public void render () {