The function (code below) simply moves a character node (contact.nodeA) back after it has penetrated a collision node (contact.nodeB) that it's not allowed to pass through.
In a scenario where a user is trying to move the character node (contact.nodeA) while it's stuck in a corner, etc. the scene/character repeatedly shakes/glitches while its position is repeatedly being moved back away from contact.nodeB at 60hz.
Is it possible with SCNPhysicsContact in SceneKit to prevent contact.nodeA from moving forward into contact.nodeB in the first place? (ie., why does "contact.penetrationDistance" occur at all? Why can't we just be alerted when a physics contact has begun w/o contact.nodeA actually moving forward into contact.nodeB? We are, after all, catching the contact on "didBegin" yet there's already penetration that we have to readjust.)
(When a user is trying to move the character node while it's "stuck" in a corner, or between two rocks, etc.)
Or, is there another solution to prevent the repeated shaking of contact.nodeA as its position is repeatedly being readjusted to compensate for its penetration into contact.nodeB? Transform instead of position change?
I've already tried setting momentum to 0.0 and removing all animations, etc. However, since contact is still occurring with contact.nodeB, contact.nodeA cannot be moved away since its momentum is still being stopped during contact with nodeB.
Any help would be greatly appreciated.
private func characterNode(_ characterNode: SCNNode, hitWall wall: SCNNode, withContact contact: SCNPhysicsContact) {
if characterNode.name != "collider" { return }
if maxPenetrationDistance > contact.penetrationDistance { return }
var characterPosition = float3(characterNode.parent!.position)
var positionOffset = float3(contact.contactNormal) * Float(contact.penetrationDistance)
maxPenetrationDistance = contact.penetrationDistance
characterPosition.y += positionOffset.y
replacementPositions[characterNode.parent!] = SCNVector3(characterPosition)
}
extension GameViewController : SCNPhysicsContactDelegate {
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if gameState != .playing { return }
contact.match(BitmaskWall) {
(matching, other) in
if other.name == "collider" {
self.characterNode(other, hitWall: matching, withContact: contact)
}
}
}
Related
I've been building a little card game with a friend. We used ds_lists for building the hand, deck, play zone and discard, moving the card objects back and forth between them. One part we've struggled with was checking what zone a card is in. For example: You can see both your hand and the discard at all times but only cards in hand should be playable, i.e. move when dragged into play.
Since the objects don't know what list they're in, we've been manually updating a list of booleans inDeck, inHand, inPlay etc. then nesting the tap event logic in an if block.
Create Event:
var inDeck = false;
var inHand = false;
var inPlay = false;
var inDiscard = false
if (ds_list_find_index(Hand, card) >= 0)
{
inHand = true
}
else if (ds_list_find_index(Play, this_card) >= 0)
{
inPlay = true
}
Repeat for each zone
Tap event:
if (inHand)
{
playCard();
}
if (inPlay)
{
useCard();
}
Is this the best we can do or is there a different approach we could/should take?
Last year was my first successful year in teaching my students to create catch and avoid games in Flash with AS3. This year is getting better. Each year I come here for help at least once.
I'm trying to add shooting into the possibilities for their second game project. I can make the shot happen from the ship, gun, whatever, and make it move, and get rid of it when it is off screen, but have not figured out a clean way to have both the shot and the target go away (removeChild and array.splice) upon collision.
The code I have sort of works, but I keep getting the error,
TypeError: Error #1010: A term is undefined and has no properties. at
DropShootV02_fla::MainTimeline/checkShots()
.
Normally I know that this is because of a mismatch between objects and index numbers, but this is related to the call to a second array in removing boxes and bullets.
For simplicity I'll just include the shooting code. Similar organization creates and drops the boxes.
Any help is appreciated. BTW we are not using external script in an AS file.
var shotSpeed = 18;
var shots:Array = new Array();
import flash.events.MouseEvent;
import flash.events.Event;
stage.addEventListener(MouseEvent.CLICK, fireLaser);
function fireLaser(e:MouseEvent):void
{
if (gameOn==true)
{
var shot:Shot=new Shot();
addChild(shot);
shots.push(shot);
shot.gotoAndStop(1);
shot.x = user.x;
shot.y = user.y;
trace(shots.length);
}
}
addEventListener(Event.ENTER_FRAME, moveShot);
function moveShot(e:Event):void
{
for (var i:int=shots.length-1; i>=0; i--)
{
shots[i].y -= shotSpeed;
if (shots[i].y < -25)
{
removeChild(shots[i]);
shots.splice(i,1);
}
}
}
addEventListener(Event.ENTER_FRAME, checkShots);
function checkShots(e:Event):void
{
for (var i:int=shots.length-1; i>=0; i--)
{
for (var k:int=boxes.length-1; k>=0; k--)
{
if (shots[i].hitTestObject(boxes[k]))
{
if (boxes[i].type == "good")
{
score--;
scoreDisplay.text = "Score:" + score;
}
if (boxes[i].type == "bad")
{
score++;
}
removeChild(boxes[k]);
boxes.splice(k,1);
//if I leave this part out I get no errors,
//but but the bullet goes on to strike again
removeChild(shots[i]);
shots.splice(i,1);
}
}
}
}
Thanks kaarto:
I had tried that previously and kept getting the same error. I used that elsewhere in this game code. Turns out I needed to moderate how often the player was shooting. I changed from shooting with the mouse to using space instead and now the problem is gone. Break is definitely a good one here.
Merge move shot and checkShots in one ENTER_FRAME handler.
I have an array of Bullets and Enemies objects. I want the game to check each element in the arrays to see whether there was a collision between the two or not. If there was any collision, then damage that enemy and delete the bullet. Right now every time a bullet hits an enemy it decreases the health of ALL the enemies in the array, thus killing them all at once. How can I make it so that it only decreases the health of the enemy which I'm shooting at?
//This function handles our collision detection
func didBegin(_ contact: SKPhysicsContact) {
//print("COLLISIONS!")
// If contact is with another object
if contact.bodyA.categoryBitMask == pickupCategory && contact.bodyB.categoryBitMask == playerCategory
{
displayText(text: "Picked up Pistol")
player.setHasPistol(gotPistol: true)
pistol.removeFromParent()
}
//Bullet hits an object
if contact.bodyA.categoryBitMask == objectCategory && contact.bodyB.categoryBitMask == bulletCategory
{
bulletCleanup(killNow: true)
//print("BULLET HAS HIT THE PIPE!")
}
//Do collisions between bullets and enemies...
for bullet in bulletList
{
for enemy in enemyList
{
//if the enemy was hit have them take damage.
if contact.bodyA.categoryBitMask == enemyCategory && contact.bodyB.categoryBitMask == bulletCategory
{
//deletes the bullet after hitting an object
bulletCleanup(killNow: true)
//apply damage
enemy.setHitPoints(setPoints: enemy.getHitPoints() - pistol.getDamage())
// print("BULLET HAS HIT THE enemy!!!!")
}
}
}
}
didBegin(contact:) is called for a single collision between one specific object and another specific object. (actually between physics bodies...)
Your problem is occurring because you are iterating over every bullet and every enemy for a single collision and at each iteration you are testing if the collision was between a bullet and an enemy, which it was, so you are applying the damage to every enemy.
All you need to do is to extract the specific enemy from the SKPhysicsContact object and apply damage to that particular enemy.
Also, I suspect you are missing some contacts because for your contact tests, you are not checking if the bodyA and bodyB physics bodies are reversed i.e.
if contact.bodyA.categoryBitMask == pickupCategory && contact.bodyB.categoryBitMask == playerCategory
bodyA might be player and bodyB might be pickup, so you need to test for this also.
Here is an alternative didBegincontact() you might find helpful (It's Swift2, but that shouldn't be a big problem):
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case pickupCategory | playerCategory
displayText(text: "Picked up Pistol")
player.setHasPistol(gotPistol: true)
let pistol = (contact.bodyA.categoryBitMask == pickupCategory) ? contact.bodyA.node! : contact.bodyB.node!
pistol.removeFromParent()
case objectCategory | bulletCategory
bulletCleanup(killNow: true)
//print("BULLET HAS HIT THE PIPE!")
case enemyCategory| bulletCategory
//deletes the bullet after hitting an object
bulletCleanup(killNow: true)
//apply damage
let enemy = (contact.bodyA.categoryBitMask == enemyCategory) ? contact.bodyA.node! : contact.bodyB.node!
enemy.setHitPoints(setPoints: enemy.getHitPoints() - pistol.getDamage())
default :
//Some other contact has occurred
print("Some other contact")
}
}
I'm not sure how your bulletCleanup() function works - you don't appear to be sending it a specific bullet object so I don't know how it knows which bullet to process. The specific bullet involved in the contact can be obtained in didBegincContact: as follows:
let bullet = (contact.bodyA.categoryBitMask == bulletCategory) ? contact.bodyA.node! : contact.bodyB.node!
which is a short form of saying "is nodeA's categoryBitMask a bulletCategory? If it is, then set bullet equal to nodeA, otherwise set bullet to nodeB.
Note that if your categories are more complicated (i.e. objects can belong to multiple categories), this won't work as the simple AND tests (enemyCategory| bulletCategory) won't match.
I'm working with the new face tracking SDK of Kinect (Microsoft Official), and I noticed that there's difference in detection between c++ and c#-wpf example: the first one is way faster in recognition than the second (the one I want to use, actually). In the c++ version the face tracking is almost on the fly, while in the wpf one it starts ONLY when I put my entire body (so the entire skeleton) in the FOV of Kinect.
Did anyone found out why? I noticed that the skeletonframe provided shows the property "Trackingmode = default", even though I set the kinect skeleton stream on seated.
colorImageFrame.CopyPixelDataTo(this.colorImage);
depthImageFrame.CopyPixelDataTo(this.depthImage);
skeletonFrame.CopySkeletonDataTo(this.skeletonData);
// Update the list of trackers and the trackers with the current frame information
foreach (Skeleton skeleton in this.skeletonData)
{
if (skeleton.TrackingState == SkeletonTrackingState.Tracked
|| skeleton.TrackingState == SkeletonTrackingState.PositionOnly)
{
// We want keep a record of any skeleton, tracked or untracked.
if (!this.trackedSkeletons.ContainsKey(skeleton.TrackingId))
{
this.trackedSkeletons.Add(skeleton.TrackingId, new SkeletonFaceTracker());
}
// Give each tracker the upated frame.
SkeletonFaceTracker skeletonFaceTracker;
if (this.trackedSkeletons.TryGetValue(skeleton.TrackingId,
out skeletonFaceTracker))
{
skeletonFaceTracker.OnFrameReady(this.Kinect,
colorImageFormat,
colorImage,
depthImageFormat,
depthImage,
skeleton);
skeletonFaceTracker.LastTrackedFrame = skeletonFrame.FrameNumber;
}
}
}
The code is the one provide my microsoft with the 1.5 SDK.
I had some information in other forums, specifically here (Thanks to this guy (blog)):
MSDN forum link
Basically, in the c++ example all the methods to track the face are used, both color+depth and color+depth+skeleton, while in the c# only the latter is used. So it only starts when you stand up.
I did some tests, but the other method is still not working for me, I did some modification to the code but with no luck. Here is my modification:
internal void OnFrameReady(KinectSensor kinectSensor, ColorImageFormat colorImageFormat, byte[] colorImage, DepthImageFormat depthImageFormat, short[] depthImage)
{
if (this.faceTracker == null)
{
try
{
this.faceTracker = new Microsoft.Kinect.Toolkit.FaceTracking.FaceTracker(kinectSensor);
}
catch (InvalidOperationException)
{
// During some shutdown scenarios the FaceTracker
// is unable to be instantiated. Catch that exception
// and don't track a face.
//Debug.WriteLine("AllFramesReady - creating a new FaceTracker threw an InvalidOperationException");
this.faceTracker = null;
}
}
if (this.faceTracker != null)
{
FaceTrackFrame frame = this.faceTracker.Track(
colorImageFormat,
colorImage,
depthImageFormat,
depthImage,
Microsoft.Kinect.Toolkit.FaceTracking.Rect.Empty);
//new Microsoft.Kinect.Toolkit.FaceTracking.Rect(100,100,500,400));
this.lastFaceTrackSucceeded = frame.TrackSuccessful;
if (this.lastFaceTrackSucceeded)
{
if (faceTriangles == null)
{
// only need to get this once. It doesn't change.
faceTriangles = frame.GetTriangles();
}
this.facePointsProjected = frame.GetProjected3DShape();
this.rotationVector = frame.Rotation;
this.translationVector = frame.Translation;
this.faceRect = frame.FaceRect;
this.facepoints3D = frame.Get3DShape();
}
}
}
frame.TrackSuccessful is always false. Any idea?
I finally figured it out and made a post on MSDN forums regarding what else needs to be done to get this working.
It's here.
Hope that helps!
I'm trying to make the following script only work when a user in a Certain Group can teleport to the specified x,y,z coordinate.
Source: www.heatonresearch.com
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/
vector target=<190, 197, 64>;
vector offset;
default
{
moving_end()
{
offset = (target- llGetPos()) * (ZERO_ROTATION / llGetRot());
llSitTarget(offset, ZERO_ROTATION);
}
state_entry()
{
llSetText("Teleport pad",<0,0,0>,1.0);
offset = (target- llGetPos()) * (ZERO_ROTATION / llGetRot());
llSetSitText("Teleport");
llSitTarget(offset, ZERO_ROTATION);
}
changed(integer change)
{
if (change & CHANGED_LINK)
{
llSleep(0.5);
if (llAvatarOnSitTarget() != NULL_KEY)
{
llUnSit(llAvatarOnSitTarget());
}
}
}
touch_start(integer i)
{
llSay(0, "Please right-click and select Teleport");
}
}
The teleport script uses two global variables. They are.
vector target=<190, 197, 64>;
vector offset; The target is the coordinate that the teleport script should send the user to. The offset is calculated based on the target and the current position of the teleporter. The offset is the distance that must be traveled to reach the target, starting from the teleporter.
Whenever the teleport pad is moved, the offset must be recalculated. The sit target is then updated.
moving_end()
{
offset = (target- llGetPos()) *
(ZERO_ROTATION / llGetRot());
llSitTarget(offset, ZERO_ROTATION);
} Likewise, when the teleport pad is first created, the offset must be recalculated. Additionally, the sit text is specified. Rotation is also taken into account and neutralized.
state_entry()
{
llSetText("Teleport pad",<0,0,0>,1.0);
offset = (target- llGetPos()) *
(ZERO_ROTATION / llGetRot());
llSetSitText("Teleport");
llSitTarget(offset, ZERO_ROTATION);
} When a user sits on the teleport pad, their avatar sits at the target location. The avatar is then stood up.
changed(integer change)
{
if (change & CHANGED_LINK)
{
llSleep(0.5);
if (llAvatarOnSitTarget() != NULL_KEY)
{
llUnSit(llAvatarOnSitTarget());
}
}
}
Ideas?
I haven't worked in SecondLife for a while, but isn't sit target maximized to 10 meters distance, anyway? And can't people just as easily use sit targets to get past walls and into areas that they shouldn't be in? The best way to do this is not to use scripts (because they can always be bypassed, even push scripts for area security and the like), and instead to just use SecondLife's built-in land security for your plot. Just don't allow anybody except your group to access that parcel at all.
If you really want to do it your way, the function you're looking for is llSameGroup. Just make sure to assign the proper group to your object, then llSameGroup(key id) will return whether or not the passed id is in the same group as the object.
Because SecondLife sucks in many ways in terms of object access and catching events, if I remember correctly you're going to have to keep the sit target in the wrong place at first, then only move it to the correct location if the user sitting is in the same group. Otherwise, the best you can do is have the user sit on it, and because the target is moved already, by the time your script kicks them off your teleporter they will already have teleported where you don't want them to go.
A better option might be to make a teleporter that doesn't use sit target, but actually moves itself to wherever the target location is. That way, you can make it so that it simply doesn't move unless someone of the same group is sitting in it. Doing that is very very simple.
vector targetPos = <100,100,100>;
vector originPos;
default
{
state_entry()
{
originPos = llGetPos();
}
changed(integer type)
{
if(type & CHANGED_LINK && llGetAvatarOnSitTarget() != NULL_KEY)
{
llSetTimerEvent(0.1);
llWhisper(0,"Going up!");
}
}
timer()
{
key sitter = llAvatarOnSitTarget();
//If someone is not sitting here, go back home.
if (sitter == NULL_KEY)
{
llSetPos(originPos);
//If we've reached the origin, stop the timer.
if (llGetPos() == originPos)
{
llSetTimerEvent(0.0);
}
}
//If someone is sitting here, go towards the teleport.
else
{
//Before we move, make sure that they're in our group.
if (llSameGroup(sitter))
{
llSetPos(targetPos);
}
else
{
llUnsit(sitter);
llWhisper(0,"Get off me, man!");
}
//If we've reached the target, unsit the sitter.
if (llGetPos() == targetPos)
{
llUnsit(sitter);
llWhisper(0,"We've arrived!");
}
}
}
}
I just wrote that from scratch after having not played SL for more than a couple years, so please let me know everyone if you spot errors. :-)
Yeah, there appears to be an error somewhere. I'm pretty sure all brackets are closed so it's most likely in the body of the script. I'll keep looking, let you know if I spot it.