Xamarin SpriteKit running action with key - ios

I am using SpriteKit with Xamarin. I'm currently making a game similar to flappy bird, except the pipes can also 'chomp' down and kill you like that. So at any given time my pipe sprites can be running two actions, one for movement along the X axis (when the player is moving) and one for movement along the Y axis (closing the pipes). When the player stops moving along the X axis I want to stop the running action of the pipes that moves them horizontally, but still keep the chomping action going. I'm having trouble setting up a key for my action though. Currently, this is what I have.
public override void TouchesEnded (NSSet touches, UIEvent evt)
{
base.TouchesEnded (touches, evt);
//TODO: Optimize by removing action by key instead of all of them. Figure out how to do this and impliment.
//This needs to be done before refining chomp as it will flow into the chomp logic
//by not stopping a chomp mid-way because the player lifted their finger.
poletop.RemoveAllActions();
polebottom.RemoveAllActions();
pole2top.RemoveAllActions ();
pole2bottom.RemoveAllActions ();
background.RemoveAllActions ();
//restarting the chomping action if it was stopped before.
chomped_return (false);
chomped_return (true);
}
So I'm basically stopping all actions, then restarting only the chomp if it was running (gets restarted in chomped_return).
This isn't very efficient and also causes some lag in-game as it stops and starts.
Code for starting the action along the X axis (player movement). This is the SKAction that I want to have the key so I can stop it and it alone. resetpoles is the completion function.
SKAction movebottompole;
SKAction movetoppole;
movebottompole = SKAction.MoveToX (edgebottom.X, flTime);
movetoppole = SKAction.MoveToX (edgetop.X, flTime);
polebottom.RunAction (movebottompole, resetpoles);
poletop.RunAction(movetoppole, resetpoles);
Chomping down on the player is an instant teleport of the pipes, but here's the code that runs to start the action of the pipes returning to their original position along the Y axis. This is setting up the action that I don't want to stop until it has completed.
public void chomped_return(bool blFirstPole)
{
//define our two actions
SKAction topreturn;
SKAction botreturn;
//define our floats for the time calculation
float flTime = 0;
float flMoveSpeed = 750;
float flDistance = 0.0f;
if (blFirstPole == true)
{
flDistance = (float)polebottom.Position.Y;
}
else if (blFirstPole == false)
{
flDistance = (float)pole2bottom.Position.Y;
}
//calculate time based on distance and vector units/second desired.
flTime = flDistance / flMoveSpeed;
//setup our moveto actions and use the time calculated above. Start the action.
topreturn = SKAction.MoveToY (750.0f, flTime);
botreturn = SKAction.MoveToY (0.0f, flTime);
if (blFirstPole == true)
{
poletop.RunAction (topreturn);
polebottom.RunAction (botreturn);
}
else if (blFirstPole == false)
{
pole2top.RunAction (topreturn);
pole2bottom.RunAction (botreturn);
}
return;
}
The variable blFirstPole is used to determine whether or not we are running the action on the first set of poles or second set (as there can be two on screen at once)
Would really appreciate any assistance, if you need any more information please do let me know!
Thanks

You can use RemoveActionForKey to remove an action with an certain key.
https://developer.xamarin.com/api/member/MonoTouch.SpriteKit.SKNode.RemoveActionForKey/p/System.String/
polebottom.RunAction(movebottompole, "movebottompole");
// remove it
polebottom.RemoveActionForKey("movebottompole");
But now you don't have your completion handler anymore. You can solve it by combining these two actions into one Sequence.
var poleactionWithCompletion = SKAction.Sequence(movebottompole, restpoles);
polebottom.RunAction(poleactionWithCompletion, "movebottompole");
If you need this more often, you can implement an extension method like:
public static class SKNodeExtension
{
public static void RunAction(this SKNode node, SKAction action, SKAction completion, string key)
{
var sequence = SKAction.Sequence(action, completion);
node.RunAction(sequence, key);
}
}
and then run your action like:
polebottom.RunAction(movebottompole, restpoles, "movebottompole");

Related

How do I only respond to touches within a UI Image?

I have been wracking my brain and using every Google search phrase I can think of and have yet to find a solution.
I have a Unity app with a 3D scene and UI elements that float over it. There is one UI element that is an image of a protractor. That image needs to be drug around the scene, rotated, and scaled. All of that works, the only catch is that is doesn't matter if the user touches the protractor or somewhere else, the protractor always reacts.
I started by looking for something along the lines of Swift's someCGRect.contains(someCGPoint) so that I could ignore anything that isn't in the bounds of the protractor. Image doesn't seem to have such a property so I did lots of other searching.
I finally found this video; https://www.youtube.com/watch?v=sXc8baUK3iY that has basically what I'm looking for… Except is doesn't work.
The video uses a collider and rigid body and then in code checks to see if the collider overlaps the touch point. Looks like exactly what I need. Unfortunately, no touches ever overlap with the collider no matter where they are. After some Debug.Log I found that the extents of the collider are reported as (0, 0, 0). This is clearly why none of the touches overlap with it, but I can't figure out how to make the extents be anything other than 0.
Here is the info from the colliders and rigid body attached to the image:
Box Collider 2D:
Used by Composite: true
Auto Tiling: false
Offset: (0,0)
Size: (1,1)
Rigidbody 2D:
Body Type: Kinematic
Material: None (Physics Material 2D)
Simulated: true
Use Full Kinematic Contact: false
Collision Detection: Discrete
Sleeping Mode: Start Awake
Interpolate: None
Constraints: None
Composite Collider 2D:
Material: None (Physics Material 2D)
is Trigger: false
Used By Effector: false
Offset: (0,0)
Geometry Type: Polygons
Generation Type: Synchronous
Vertex Distance: 0.0005
There is a button that turns the protractor on and off by use of the following code:
public void toggle() {
this.enabled = !this.enabled;
this.gameObject.SetActive(this.enabled);
}
The protractor starts life visible but Start() calls toggle() straight away so the user sees it as starting out off.
This is the code that performs the test to see if the touches should be responded to:
void checkTouches() {
if (Input.touchCount <= 0) {
return;
}
bool oneTouchIn = false;
Collider2D collider = GetComponent<Collider2D>();
Debug.Log("🔵 The bounds of the collider are: " + collider.bounds);
// The above always logs the extents as (0,0,0).
foreach (Touch touch in Input.touches) {
Vector2 touchPos = Camera.main.ScreenToWorldPoint(touch.position);
if(collider.OverlapPoint(touchPos)) {
// Since the extents are all 0 we never find any overlaps
oneTouchIn = true;
break;
}
}
if (!oneTouchIn) {
return; // Always ends up here
}
// We must have at least one touch that is in our bounds.
// Do stuff with the touch(es) here…
}
I've been doing iOS development with Objective-C since the SDK was released and with Swift since it come out but I'm very new to Unity. I'm sure the issue is me missing something silly, but I can't find it.
Does anyone know what I'm missing to make the current version work or an alternate way of only responding to touches that are in bounds?
Image doesn't seem to have such a property
No the Image componemt itself doesn't have that ...
But the rect property of the RectTransform component each UI GameObject has.
It is called Contains. So you could do e.g.
RectTransform imgRectTransform = imageObject.GetComponent<RectTransform>();
Vector2 localTouchPosition = imgRectTransform.InverseTransformPoint(Touch.position);
if (imgRectTransform.rect.Contains(localToichPosition)) { ... }
Alternatively you could use the IPointerEnterHandler and IPointerExitHandler Interfaces in a component on the target Image like e.g.
public class DragableHandler : MonkBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public bool IsHover {get; private set; }
//Detect if the Cursor starts to pass over the GameObject
public void OnPointerEnter(PointerEventData pointerEventData)
{
//Output to console the GameObject's name and the following message
Debug.Log("Cursor Entering " + name + " GameObject");
IsHover = true;
}
//Detect when Cursor leaves the GameObject
public void OnPointerExit(PointerEventData pointerEventData)
{
//Output the following message with the GameObject's name
Debug.Log("Cursor Exiting " + name + " GameObject");
IsHover = false;
}
}
and than in your script check it using
if(imageObject.GetComponent<DragableHandler>().IsHover) { ... }
just also make sure that we the EventSystem you also add Touch Input Module and check the flag Force Module Active.

Set A bool to true for two seconds then change back to false

As the question states, in spritekit I am trying to find a way to change my value of a bool from false, too true for x amount of seconds then change it back to false.
the logic is i have this var, isStunned, when my player comes in contact with x sprite i call it and my joystick is disabled since the player is "stunned"
if (controlButtonDown) { // If i pressed joystick
if (!isStunned) { // IF i am not stunned
//move player
When my player touches the glue, i set isStunned to yes and the joystick is disabled, however I am wondering of a way to only disable my joystick or not move my sprite for x amount of seconds. I know of SKAction and the wait sequence, but those are actions and I dont think they will apply here. Any help is appreciated.
Thank you
Since you ask specifically with a spritekit tag.
Don't use NSTimer or actions to work with time in a game. Instead use the update function. If you're running at 60fps that means the update function will be called 120 times for two seconds. Using the frames instead of seconds to keep your game updated will avoid the gameplay being changed by lag. Imagine for instance that the players game lags a little, in other words runs slower. 2 seconds is still 2 seconds regardless of game lag, so now he is affected less by the glue than a person who has no lag. So...
First off you should have a variable keeping track of in game time: int _time;
In every update cycle you add one to time: _time++;
You should have a variable keeping track of when the user touched the glue: int _glueTouchedAtTime;
When the user clicks glue you set: _glueTouchedAtTime = time;
You should have a constant defining how long the glue is active: #define GLUE_TIME 120;
When you initiate the game set _glueTouchedAtTime to: _glueTouchedAtTime = -GLUE_TIME; To prevent the glue from being on when (_time == 0)
Testing if the user has touched glue now works like this:
if(_glueTouchedAtTime+GLUE_TIME>time) {
// Glue is active
} else {
// Glue is not active
}
Different glues for different sprites
To have different glues for different sprites I would suggest first doing a general game object (GameObject) as a subclass of either SKNode or SKSpriteNode depending on your needs. Then create a subclass of GameObject called GameObjectGluable. This should have a property called: #property int gluedAtTime;
You glue the glueable game object by: aGameObjectGluable.gluedAtTime = time;
Now you can test:
if(aGameObjectGluable.gluedAtTime +GLUE_TIME>time) {
// Game Object is glued
} else {
// Game object is not glued
}
Set your value-of-interest to true and then fire a NSTimer after two seconds to set it back to false.
For an explanation of how you might use NSTimer, see this SO thread: How do I use NSTimer?
You can use SKAction also. The runBlock method let's you execute blocks of code as actions.
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *reenableJoystick = [SKAction runBlock:^{
joystick.userInteractionEnabled = TRUE;
}];
SKAction *sequence = [SKAction sequence:#[wait, reenableJoystick]];
[self runAction:sequence];

How do I calculate the speed of which a user drags an image?

I have an image that the user can drag to the right and it will spring back when the user releases it. I want to execute some code when a user drags it quickly and releases it. Now I have a very awkward requirement that the user can drag the image, then keep it still for any length of time (for example 5 seconds), then drag it quickly and release it. As long as the image is moving above a certain speed when it is released, it will execute the code. If it falls below the minimum speed, it executes some different code. So that means I can't calculate the length of time between the beginning of the gesture and the end and execute the code depending on the length of time. What can I do? I guess I somehow need to know the speed at which the image is moving in it's last 500 milliseconds before the gesture ends. However I've hit a brick wall figuring out how to do that. Any help would be greatly appreciated.
Can you please include an explanation and possible example code with your answer as that would be a great help.
If you get the start X,Y coordinates of when the image is dragged, and the X,Y coordinates for when the mouse is released, you can use pythagoras' theorm to calculate the distance between the two points: http://en.wikipedia.org/wiki/Pythagorean_theorem
Also, if you start a timer when the mouse is moved (and mouse button is down), and stop it in the mouseup event, you can calculate the speed using the time and distance (speed = distance / time)
edit following comments:
point delayedMousePos;
point previousMousePos;
bool secondDrag = false;
bool isStopped = false;
var timeFirstStopped;
var positionCount = 0;
array previousMousePositions[3];
// timer which monitors mouse position (set to an interval of say, 10ms)
function timerMonitorMousePos_Elapsed() {
point currentMousePos = getMousePos();
if (isStopped == false) {
if (positionCount >= 2) {
array_shift(previousMousePositions); // remove the first element of the array and move everything down to reindex numerical array to start counting from zero
positionCount = 2; // keep positionCount within array bounds
}
previousMousePositions[positionCount] = currentMousePos; // add the new position to the end of the 'stack'
positionCount++;
}
if (currentMousePos == previousMousePos) { // start check for stationary
isStopped = true;
if (timeFirstStopped == null) {
timeFirstStopped = NOW();
} else {
if (NOW() - timeFirstStopped >= 500) { // we have been stopped for at least 500ms (assumes time is counted in milliseconds)
secondDrag = true;
// previousMousePositions[0] = the mouse position 30ms before the mouse stopped
}
}
} else {
isStopped = false;
timeFirstStopped = null;
}
previousMousePos = currentMousePos;
}
I wouldn't use a timer. I would just save the starting date/time along with x,y position when the dragging starts.
When the dragging has ended, save the ending date/time and position. From those information, I can calculate the distance in pixel and duration in milliseconds.
After searching some more on the internet, I finally answered my own question.
I worked out what I needed to do:
My UIPanGestureRecognizer:
- (IBAction)handlePan3:(UIPanGestureRecognizer *)recognizer3
Get the velocity of the users finger moving across the screen:
CGPoint vel = [recognizer velocityInView:self.myView];
Then:
if (vel.x > /*value*/) {
// Your code
}
I was about to give up, but no! I got there in the end. Thanks for everyones help. I've upvoted one or two answers because they were helpful. bobnoble actually gave the suggestion to use velocityInView and I found this other stack overflow question which gave me the info I needed: iOS - Making sense of velocityInView on UIPanGesture

how to stop a rotating movieclip at different angles with other movieclips following, actionscript 3

I have a manometer, this needs to spin from a minimum value to a maximum. right now I have the manometer as a picture and the arrow as a movieclip. I've got it spinning at the right speed, but don't know how to stop it at the lowest/highest pressure.
It's suppose to work like this:
I have two movieclip/buttons; one for simulating more pressure, and one for less.
when the user presses the "more pressure" movieclip/button the pressure begins to rise and the arrow inside the manometer begin to turn.
At the same time as the pressure rises, another movieclip ("stamp") will push uppwards.
then another movieclip/button, "less pressure" simulates pressure drop; when pressed, the same point as the arrow stopped at when pressure rised, will start sinking towards minimum, and the stamp will go down again.
so, when the user presses "more pressure" pressure rises towards maximum, and as soon as the user stop pressing the button, the animation stops (both the stamp and the arrow). And if the user presses "lower pressure", the arrow starts sinking from where it stopped.
heres my code so far: pil1 = manometerarrow, the stamp = stamp, and "less pressure"/"more pressure" = Lpress / mpress
addEventListener (Event.ENTER_FRAME, rotate);
function rotate(event:Event):void
{
pil1.rotation = pil1.rotation+1;
}
ymutlu is on the right track. The mouse down event will only execute once when the mouse is depressed. To make the object rotate continuously while the mouse is depressed you need to increment or decrement the rotation of the object on each frame. I think the following should do the trick:
import flash.events.MouseEvent;
import flash.events.Event;
var rotate = 0;
Hpress.addEventListener(MouseEvent.MOUSE_DOWN, Hpressed);
Hpress.addEventListener(MouseEvent.MOUSE_UP, removeEnterFrame);
Lpress.addEventListener(MouseEvent.MOUSE_DOWN, Lpressed);
Lpress.addEventListener(MouseEvent.MOUSE_UP, removeEnterFrame);
function Hpressed(e:MouseEvent):void
{
rotate = 1;
addEnterFrame();
}
function Lpressed(e:MouseEvent):void
{
rotate = -1;
addEnterFrame();
}
function addEnterFrame():void
{
this.addEventListener(Event.ENTER_FRAME, update);
}
function removeEnterFrame(e:MouseEvent):void
{
this.removeEventListener(Event.ENTER_FRAME, update);
}
function update(e:Event):void
{
pil1.rotation += rotate;
}
hold to varaible that states if max button down or min button down, and check it in enterframe loop. Edited answer on your comment, hope you can sort it out.
addEventListener (Event.ENTER_FRAME, rotate);
function rotate(event:Event):void
{
if(isMaxDown) // true when max button down
pil1.rotation = Math.min(presMax,pil1.rotation+1); // presMax is max value that pressure can go
if(isMinDown) // true when min button down
pil1.rotation = Math.max(presMin,pil1.rotation-1);// presMin is min value that pressure can go
}
// isMaxDown and isMinDown are global values.
Hpress.addEventListener(MouseEvent.MOUSE_DOWN, Hpressed);
Lpress.addEventListener(MouseEvent.MOUSE_DOWN, Lpressed);
Hpress.addEventListener(MouseEvent.MOUSE_UP, H_up);
Lpress.addEventListener(MouseEvent.MOUSE_UP, L_up);
function H_up(e:MouseEvent):void {
isMaxDown=false;
}
function L_up(e:MouseEvent):void {
isMinDown=false;
}
function Hpressed(e:MouseEvent):void {
isMaxDown=true;
}
function Lpressed(e:MouseEvent):void {
isMinDown=true;
}
This code would help you but prob this is not a path to fallow to do somthing like that.

Actionscript 3, rotate object on drag

I have an object I need to rotate by clicking and dragging. Following some AS2 code I got the object to rotate a bit every time the mouse is clicked, but can't get it to work with drag.
needle.addEventListener(MouseEvent.MOUSE_DOWN, fl_ClickToDrag_2);
stage.addEventListener(MouseEvent.MOUSE_UP, fl_ReleaseToDrop_2);
function fl_ClickToDrag_2(event:MouseEvent):void
{
var angle = Math.atan2(mouseY-needle.y,mouseX-needle.x);
// apply rotation to handle by converting angle into degrees
needle.rotation = angle*180/Math.PI;
// rotate the grip opposite the handle so it won't rotate along with it
//this.grip._rotation = -this._rotation;
}
function fl_ReleaseToDrop_2(event:MouseEvent):void
{
needle.stopDrag();
}
Well the problem I see is that the MOUSE_DOWN event only fires once per click, so you only run the code in the handler once.
There could be a better way than this but this is how I'd consider doing it:
EDITED FOR DETAIL:
public class Test extends MovieClip {
private var n:Needle;
public function Test() {
// constructor code
n = new Needle();
stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownF,false,0,true);
stage.addEventListener(MouseEvent.MOUSE_UP,mouseUpF,false,0,true);
n.x = stage.stageWidth/2; //center needle on stage
n.y = stage.stageHeight/2;
addChild(n); //add needle to stage
}
public function mouseDownF(e:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,rotate,false,0,true);
}
public function rotate(e:MouseEvent):void {
var angle:Number = Math.atan2(mouseY - n.y,mouseX - n.x); //get angle in radians (pythagoras)
angle = angle * 180/Math.PI -90; //convert to degrees , the 90 is to have it point to the mouse
n.rotation = angle; //rotate
}
public function mouseUpF(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,rotate);
}
}
So when the user clicks down (mouseDown) it activates an event listener that fires the rotate handler every time the mouse moves. When the user lets go of the click the event listener is destroyed. The false,0,true); when adding the event listener is to make it a weakly referenced listener so that it gets collected by the garbage collector and doesn't just sit in memory taking up space forever.

Resources